From 103588948f66012cbeadf148a0241d1344a3b69d Mon Sep 17 00:00:00 2001 From: Ken Van Hoeylandt Date: Mon, 9 Dec 2024 23:37:21 +0100 Subject: [PATCH] Implement forgetting WiFi APs (#116) .. and fix the simulator build --- Boards/Simulator/Source/hal/Configuration.cpp | 5 +- Boards/Simulator/Source/hal/Power.cpp | 31 -------- .../Simulator/Source/hal/SimulatorPower.cpp | 44 +++++++++++ Boards/Simulator/Source/hal/SimulatorPower.h | 25 +++++++ Documentation/ideas.md | 3 +- .../app/wifiapsettings/WifiApSettings.cpp | 75 +++++++++++++++---- Tactility/Source/service/loader/Loader.cpp | 59 ++++++++------- .../Source/service/wifi/WifiSettingsMock.cpp | 4 +- 8 files changed, 166 insertions(+), 80 deletions(-) delete mode 100644 Boards/Simulator/Source/hal/Power.cpp create mode 100644 Boards/Simulator/Source/hal/SimulatorPower.cpp create mode 100644 Boards/Simulator/Source/hal/SimulatorPower.h diff --git a/Boards/Simulator/Source/hal/Configuration.cpp b/Boards/Simulator/Source/hal/Configuration.cpp index a0cf648e..cfe48175 100644 --- a/Boards/Simulator/Source/hal/Configuration.cpp +++ b/Boards/Simulator/Source/hal/Configuration.cpp @@ -1,4 +1,5 @@ #include "hal/Configuration.h" +#include "hal/SimulatorPower.h" #include "LvglTask.h" #include "src/lv_init.h" #include "SdlDisplay.h" @@ -6,8 +7,6 @@ #define TAG "hardware" -extern const tt::hal::Power power; - static bool initBoot() { lv_init(); lvgl_task_start(); @@ -30,7 +29,7 @@ extern const tt::hal::Configuration hardware = { .createDisplay = createDisplay, .createKeyboard = createKeyboard, .sdcard = nullptr, - .power = &power, + .power = simulatorPower, .i2c = { tt::hal::i2c::Configuration { .name = "Internal", diff --git a/Boards/Simulator/Source/hal/Power.cpp b/Boards/Simulator/Source/hal/Power.cpp deleted file mode 100644 index ce7012cf..00000000 --- a/Boards/Simulator/Source/hal/Power.cpp +++ /dev/null @@ -1,31 +0,0 @@ -#include "hal/Power.h" - -static bool is_charging_enabled = false; - -static bool isCharging() { - return is_charging_enabled; -} - -static bool isChargingEnabled() { - return is_charging_enabled; -} - -static void setChargingEnabled(bool enabled) { - is_charging_enabled = enabled; -} - -static uint8_t getChargeLevel() { - return 204; -} - -static int32_t getCurrent() { - return is_charging_enabled ? 100 : -50; -} - -extern const tt::hal::Power power = { - .isCharging = isCharging, - .isChargingEnabled = isChargingEnabled, - .setChargingEnabled = setChargingEnabled, - .getChargeLevel = getChargeLevel, - .getCurrent = getCurrent -}; diff --git a/Boards/Simulator/Source/hal/SimulatorPower.cpp b/Boards/Simulator/Source/hal/SimulatorPower.cpp new file mode 100644 index 00000000..c0568457 --- /dev/null +++ b/Boards/Simulator/Source/hal/SimulatorPower.cpp @@ -0,0 +1,44 @@ +#include "SimulatorPower.h" + +#define TAG "simulator_power" + +bool SimulatorPower::supportsMetric(MetricType type) const { + switch (type) { + case IS_CHARGING: + case CURRENT: + case BATTERY_VOLTAGE: + case CHARGE_LEVEL: + return true; + } + + return false; // Safety guard for when new enum values are introduced +} + +bool SimulatorPower::getMetric(Power::MetricType type, Power::MetricData& data) { + switch (type) { + case IS_CHARGING: + data.valueAsBool = true; + return true; + case CURRENT: + data.valueAsInt32 = 42; + return true; + case BATTERY_VOLTAGE: + data.valueAsUint32 = 4032; + return true; + case CHARGE_LEVEL: + data.valueAsUint8 = 80; + return true; + } + + return false; // Safety guard for when new enum values are introduced +} + +static std::shared_ptr power; + +std::shared_ptr simulatorPower() { + if (power == nullptr) { + power = std::make_shared(); + } + return power; +} + diff --git a/Boards/Simulator/Source/hal/SimulatorPower.h b/Boards/Simulator/Source/hal/SimulatorPower.h new file mode 100644 index 00000000..3b4bc735 --- /dev/null +++ b/Boards/Simulator/Source/hal/SimulatorPower.h @@ -0,0 +1,25 @@ +#pragma once + +#include "hal/Power.h" +#include + +using namespace tt::hal; + +class SimulatorPower : public Power { + + bool allowedToCharge = false; + +public: + + SimulatorPower() {} + ~SimulatorPower() {} + + bool supportsMetric(MetricType type) const override; + bool getMetric(Power::MetricType type, Power::MetricData& data) override; + + bool supportsChargeControl() const { return true; } + bool isAllowedToCharge() const { return allowedToCharge; } + void setAllowedToCharge(bool canCharge) { allowedToCharge = canCharge; } +}; + +std::shared_ptr simulatorPower(); diff --git a/Documentation/ideas.md b/Documentation/ideas.md index af563708..8ef50f61 100644 --- a/Documentation/ideas.md +++ b/Documentation/ideas.md @@ -1,10 +1,11 @@ # TODOs +- AppContext's onResult should pass the app id (or launch request id!) that was started, so we can differentiate between multiple types of apps being launched +- Create AlertDialog app like on Android with title, message and optional buttons. Update WifiApSettings with it. - Loader: Use Timer instead of Thread, and move API to `tt::app::` - Gpio: Use Timer instead of Thread - I2cScannerThread: Use Timer instead of Thread - Bug: I2C Scanner is on M5Stack devices is broken - WiFi AP Connect app: add "Forget" option. -- T-Deck Plus: Implement battery status - Make firmwares available via release process - Make firmwares available via web serial website - Bug: When closing a top level app, there's often an error "can't stop root app" diff --git a/Tactility/Source/app/wifiapsettings/WifiApSettings.cpp b/Tactility/Source/app/wifiapsettings/WifiApSettings.cpp index 7e992247..9bf597a5 100644 --- a/Tactility/Source/app/wifiapsettings/WifiApSettings.cpp +++ b/Tactility/Source/app/wifiapsettings/WifiApSettings.cpp @@ -1,6 +1,7 @@ #include "WifiApSettings.h" #include "TactilityCore.h" #include "app/AppContext.h" +#include "app/selectiondialog/SelectionDialog.h" #include "lvgl.h" #include "lvgl/Style.h" #include "lvgl/Toolbar.h" @@ -29,6 +30,14 @@ void start(const std::string& ssid) { service::loader::startApp(manifest.id, false, bundle); } +static void onPressForget(lv_event_t* event) { + std::vector choices = { + "Yes", + "No" + }; + selectiondialog::start("Are you sure?", choices); +} + static void onToggleAutoConnect(lv_event_t* event) { lv_event_code_t code = lv_event_get_code(event); @@ -67,30 +76,35 @@ static void onShow(AppContext& app, lv_obj_t* parent) { // Wrappers - lv_obj_t* secondary_flex = lv_obj_create(parent); - lv_obj_set_width(secondary_flex, LV_PCT(100)); - lv_obj_set_flex_grow(secondary_flex, 1); - lv_obj_set_flex_flow(secondary_flex, LV_FLEX_FLOW_COLUMN); - lv_obj_set_style_border_width(secondary_flex, 0, 0); - lvgl::obj_set_style_no_padding(secondary_flex); - lvgl::obj_set_style_bg_invisible(secondary_flex); - - // align() methods don't work on flex, so we need this extra wrapper - lv_obj_t* wrapper = lv_obj_create(secondary_flex); - lv_obj_set_size(wrapper, LV_PCT(100), LV_SIZE_CONTENT); + lv_obj_t* wrapper = lv_obj_create(parent); + lv_obj_set_width(wrapper, LV_PCT(100)); + lv_obj_set_flex_grow(wrapper, 1); + lv_obj_set_flex_flow(wrapper, LV_FLEX_FLOW_COLUMN); lvgl::obj_set_style_bg_invisible(wrapper); - lv_obj_set_style_border_width(wrapper, 0, 0); // Auto-connect toggle - lv_obj_t* auto_connect_label = lv_label_create(wrapper); + lv_obj_t* auto_connect_wrapper = lv_obj_create(wrapper); + lv_obj_set_size(auto_connect_wrapper, LV_PCT(100), LV_SIZE_CONTENT); + lvgl::obj_set_style_no_padding(auto_connect_wrapper); + lv_obj_set_style_border_width(auto_connect_wrapper, 0, 0); + + lv_obj_t* auto_connect_label = lv_label_create(auto_connect_wrapper); lv_label_set_text(auto_connect_label, "Auto-connect"); lv_obj_align(auto_connect_label, LV_ALIGN_TOP_LEFT, 0, 6); - lv_obj_t* auto_connect_switch = lv_switch_create(wrapper); + lv_obj_t* auto_connect_switch = lv_switch_create(auto_connect_wrapper); lv_obj_add_event_cb(auto_connect_switch, onToggleAutoConnect, LV_EVENT_VALUE_CHANGED, (void*)&paremeters); lv_obj_align(auto_connect_switch, LV_ALIGN_TOP_RIGHT, 0, 0); + lv_obj_t* forget_button = lv_button_create(wrapper); + lv_obj_set_width(forget_button, LV_PCT(100)); + lv_obj_align_to(forget_button, auto_connect_wrapper, LV_ALIGN_OUT_BOTTOM_MID, 0, 10); + lv_obj_add_event_cb(forget_button, onPressForget, LV_EVENT_CLICKED, nullptr); + lv_obj_t* forget_button_label = lv_label_create(forget_button); + lv_obj_align(forget_button_label, LV_ALIGN_CENTER, 0, 0); + lv_label_set_text(forget_button_label, "Forget"); + service::wifi::settings::WifiApSettings settings {}; if (service::wifi::settings::load(ssid.c_str(), &settings)) { if (settings.auto_connect) { @@ -104,12 +118,43 @@ static void onShow(AppContext& app, lv_obj_t* parent) { } } +void onResult(AppContext& app, Result result, const Bundle& bundle) { + auto index = selectiondialog::getResultIndex(bundle); + if (index == 0) {// Yes + auto* app = optWifiApSettingsApp(); + if (app == nullptr) { + return; + } + + auto parameters = app->getParameters(); + tt_check(parameters != nullptr, "Parameters missing"); + + std::string ssid = parameters->getString("ssid"); + if (service::wifi::settings::remove(ssid.c_str())) { + TT_LOG_I(TAG, "Removed SSID"); + + if ( + service::wifi::getRadioState() == service::wifi::WIFI_RADIO_CONNECTION_ACTIVE && + service::wifi::getConnectionTarget() == ssid + ) { + service::wifi::disconnect(); + } + + // Stop self + service::loader::stopApp(); + } else { + TT_LOG_E(TAG, "Failed to remove SSID"); + } + } +} + extern const AppManifest manifest = { .id = "WifiApSettings", .name = "Wi-Fi AP Settings", .icon = LV_SYMBOL_WIFI, .type = TypeHidden, - .onShow = onShow + .onShow = onShow, + .onResult = onResult }; } // namespace diff --git a/Tactility/Source/service/loader/Loader.cpp b/Tactility/Source/service/loader/Loader.cpp index 3a4fbde2..3fa97cd6 100644 --- a/Tactility/Source/service/loader/Loader.cpp +++ b/Tactility/Source/service/loader/Loader.cpp @@ -267,39 +267,15 @@ static void do_stop_app() { TT_LOG_I(TAG, "Free heap: %zu", heap_caps_get_free_size(MALLOC_CAP_INTERNAL)); #endif + app::AppOnResult on_result = nullptr; + app::AppInstance* app_to_resume = nullptr; // If there's a previous app, resume it if (!loader_singleton->app_stack.empty()) { - app::AppInstance* app_to_resume = loader_singleton->app_stack.top(); + app_to_resume = loader_singleton->app_stack.top(); tt_assert(app_to_resume); app_transition_to_state(*app_to_resume, app::StateShowing); - auto on_result = app_to_resume->getManifest().onResult; - if (on_result != nullptr) { - if (result_holder != nullptr) { - auto result_bundle = result_holder->resultData.get(); - if (result_bundle != nullptr) { - on_result( - *app_to_resume, - result_holder->result, - *result_bundle - ); - } else { - const Bundle empty_bundle; - on_result( - *app_to_resume, - result_holder->result, - empty_bundle - ); - } - } else { - const Bundle empty_bundle; - on_result( - *app_to_resume, - app::ResultCancelled, - empty_bundle - ); - } - } + on_result = app_to_resume->getManifest().onResult; } loader_unlock(); @@ -314,6 +290,33 @@ static void do_stop_app() { } }; tt_pubsub_publish(loader_singleton->pubsub_external, &event_external); + + if (on_result != nullptr && app_to_resume != nullptr) { + if (result_holder != nullptr) { + auto result_bundle = result_holder->resultData.get(); + if (result_bundle != nullptr) { + on_result( + *app_to_resume, + result_holder->result, + *result_bundle + ); + } else { + const Bundle empty_bundle; + on_result( + *app_to_resume, + result_holder->result, + empty_bundle + ); + } + } else { + const Bundle empty_bundle; + on_result( + *app_to_resume, + app::ResultCancelled, + empty_bundle + ); + } + } } diff --git a/TactilityHeadless/Source/service/wifi/WifiSettingsMock.cpp b/TactilityHeadless/Source/service/wifi/WifiSettingsMock.cpp index 1325f505..3c3860e2 100644 --- a/TactilityHeadless/Source/service/wifi/WifiSettingsMock.cpp +++ b/TactilityHeadless/Source/service/wifi/WifiSettingsMock.cpp @@ -16,11 +16,11 @@ bool load(const char* ssid, WifiApSettings* settings) { } bool save(const WifiApSettings* settings) { - return false; + return true; } bool remove(const char* ssid) { - return false; + return true; } } // namespace