diff --git a/Documentation/ideas.md b/Documentation/ideas.md index a4e8d932..d4f70e32 100644 --- a/Documentation/ideas.md +++ b/Documentation/ideas.md @@ -7,7 +7,6 @@ - Create app to edit WiFi settings (e.g. "forget" and "auto-connect" option) - Show a warning screen if firmware encryption or secure boot are off when saving WiFi credentials. - Show a warning screen when a user plugs in the SD card on a device that only supports mounting at boot. -- Try out Waveshare S3 120MHz mode for PSRAM (see "enabling 120M PSRAM is necessary" in [docs](https://www.waveshare.com/wiki/ESP32-S3-Touch-LCD-4.3#Other_Notes)) - T-Deck has random sdcard SPI crashes due to sharing bus with screen SPI: make it use the LVGL lock for sdcard operations? - Check service/app id on registration to see if it is a duplicate id - Fix screenshot app on ESP32: it currently blocks when allocating memory @@ -18,7 +17,8 @@ - Explore LVGL9's ILI93414 driver for 2.4" Yellow Board - Bug: in LVGL9 with M5Core2, crash when bottom item is clicked without scrolling first - Replace M5Unified and M5GFX with custom drivers (so we can fix the Core2 SD card mounting bug, and so we regain some firmware space) -- Commit fix to esp_lvgl_port to have esp_lvgl_port_disp.c user driver_data instead of user_data +- Commit fix to esp_lvgl_port to have `esp_lvgl_port_disp.c` user driver_data instead of user_data +- Wifi bug: when pressing disconnect while between `WIFI_EVENT_STA_START` and `IP_EVENT_STA_GOT_IP`, then auto-connect becomes activate again. # Core Ideas - Support for displays with different DPI. Consider the layer-based system like on Android. @@ -28,6 +28,7 @@ - Wi-Fi using dispatcher to dispatch its main functionality to the dedicated Wi-Fi CPU core (to avoid main loop hack) # App Ideas +- System logger - Add FreeRTOS task manager functionality to System Info app - BlueTooth keyboard app - Chip 8 emulator diff --git a/Documentation/pics/screenshot-WifiManage.png b/Documentation/pics/screenshot-WifiManage.png index a127f35e..fce2a452 100644 Binary files a/Documentation/pics/screenshot-WifiManage.png and b/Documentation/pics/screenshot-WifiManage.png differ diff --git a/Tactility/Source/app/files/Files.cpp b/Tactility/Source/app/files/Files.cpp index e7599361..37933b60 100644 --- a/Tactility/Source/app/files/Files.cpp +++ b/Tactility/Source/app/files/Files.cpp @@ -187,7 +187,7 @@ static void on_show(App& app, lv_obj_t* parent) { lv_obj_t* toolbar = lvgl::toolbar_create(parent, "Files"); lvgl::toolbar_set_nav_action(toolbar, LV_SYMBOL_CLOSE, &on_exit_app_pressed, nullptr); - lvgl::toolbar_add_action(toolbar, LV_SYMBOL_UP, "Navigate up", &on_navigate_up_pressed, data); + lvgl::toolbar_add_action(toolbar, LV_SYMBOL_UP, &on_navigate_up_pressed, data); data->list = lv_list_create(parent); lv_obj_set_width(data->list, LV_PCT(100)); diff --git a/Tactility/Source/app/wificonnect/WifiConnectBindings.h b/Tactility/Source/app/wificonnect/Bindings.h similarity index 71% rename from Tactility/Source/app/wificonnect/WifiConnectBindings.h rename to Tactility/Source/app/wificonnect/Bindings.h index 3025d573..6353761f 100644 --- a/Tactility/Source/app/wificonnect/WifiConnectBindings.h +++ b/Tactility/Source/app/wificonnect/Bindings.h @@ -7,8 +7,8 @@ namespace tt::app::wificonnect { typedef void (*OnConnectSsid)(const service::wifi::settings::WifiApSettings* settings, bool store, void* context); typedef struct { - OnConnectSsid on_connect_ssid; - void* on_connect_ssid_context; -} WifiConnectBindings; + OnConnectSsid onConnectSsid; + void* onConnectSsidContext; +} Bindings; } // namespace diff --git a/Tactility/Source/app/wificonnect/WifiConnectBundle.h b/Tactility/Source/app/wificonnect/Parameters.h similarity index 100% rename from Tactility/Source/app/wificonnect/WifiConnectBundle.h rename to Tactility/Source/app/wificonnect/Parameters.h diff --git a/Tactility/Source/app/wificonnect/State.cpp b/Tactility/Source/app/wificonnect/State.cpp new file mode 100644 index 00000000..1c0cd077 --- /dev/null +++ b/Tactility/Source/app/wificonnect/State.cpp @@ -0,0 +1,48 @@ +#include "State.h" +#include "Check.h" +#include + +namespace tt::app::wificonnect { + +void State::setConnectionError(bool error) { + tt_check(lock.acquire(TtWaitForever) == TtStatusOk); + connectionError = error; + tt_check(lock.release() == TtStatusOk); +} + +bool State::hasConnectionError() const { + tt_check(lock.acquire(TtWaitForever) == TtStatusOk); + auto result = connectionError; + tt_check(lock.release() == TtStatusOk); + return result; +} + +void State::setApSettings(const service::wifi::settings::WifiApSettings* newSettings) { + tt_check(lock.acquire(TtWaitForever) == TtStatusOk); + memcpy(&this->apSettings, newSettings, sizeof(service::wifi::settings::WifiApSettings)); + tt_check(lock.release() == TtStatusOk); +} + +const service::wifi::settings::WifiApSettings& State::lockApSettings() { + tt_check(lock.acquire(TtWaitForever) == TtStatusOk); + return apSettings; +} + +void State::unlockApSettings() { + tt_check(lock.release() == TtStatusOk); +} + +void State::setConnecting(bool isConnecting) { + tt_check(lock.acquire(TtWaitForever) == TtStatusOk); + connecting = isConnecting; + tt_check(lock.release() == TtStatusOk); +} + +bool State::isConnecting() const { + tt_check(lock.acquire(TtWaitForever) == TtStatusOk); + auto result = connecting; + tt_check(lock.release() == TtStatusOk); + return result; +} + +} // namespace diff --git a/Tactility/Source/app/wificonnect/State.h b/Tactility/Source/app/wificonnect/State.h new file mode 100644 index 00000000..3c3d558a --- /dev/null +++ b/Tactility/Source/app/wificonnect/State.h @@ -0,0 +1,32 @@ +#pragma once + +#include "Mutex.h" +#include "service/wifi/Wifi.h" +#include "service/wifi/WifiSettings.h" + +namespace tt::app::wificonnect { + +class State { + Mutex lock; + service::wifi::settings::WifiApSettings apSettings = { + .ssid = { 0 }, + .password = { 0 }, + .auto_connect = false + }; + bool connectionError = false; + bool connecting = false; +public: + + void setConnectionError(bool error); + bool hasConnectionError() const; + + const service::wifi::settings::WifiApSettings& lockApSettings(); + void unlockApSettings(); + + void setApSettings(const service::wifi::settings::WifiApSettings* newSettings); + + void setConnecting(bool isConnecting); + bool isConnecting() const; +}; + +} // namespace diff --git a/Tactility/Source/app/wificonnect/View.cpp b/Tactility/Source/app/wificonnect/View.cpp new file mode 100644 index 00000000..dc0f7df8 --- /dev/null +++ b/Tactility/Source/app/wificonnect/View.cpp @@ -0,0 +1,214 @@ +#include "View.h" +#include "State.h" +#include "Parameters.h" +#include "WifiConnect.h" + +#include "Log.h" +#include "lvgl.h" +#include "service/gui/Gui.h" +#include "service/wifi/WifiSettings.h" +#include "lvgl/Style.h" +#include "lvgl/Toolbar.h" + +namespace tt::app::wificonnect { + +#define TAG "wifi_connect" + +void View::resetErrors() { + lv_obj_add_flag(password_error, LV_OBJ_FLAG_HIDDEN); + lv_obj_add_flag(ssid_error, LV_OBJ_FLAG_HIDDEN); + lv_obj_add_flag(connection_error, LV_OBJ_FLAG_HIDDEN); +} + +static void onConnect(lv_event_t* event) { + auto* wifi = (WifiConnect*)lv_event_get_user_data(event); + auto& view = wifi->getView(); + + wifi->getState().setConnectionError(false); + view.resetErrors(); + + const char* ssid = lv_textarea_get_text(view.ssid_textarea); + size_t ssid_len = strlen(ssid); + if (ssid_len > TT_WIFI_SSID_LIMIT) { + TT_LOG_E(TAG, "SSID too long"); + lv_label_set_text(view.ssid_error, "SSID too long"); + lv_obj_remove_flag(view.ssid_error, LV_OBJ_FLAG_HIDDEN); + return; + } + + const char* password = lv_textarea_get_text(view.password_textarea); + size_t password_len = strlen(password); + if (password_len > TT_WIFI_CREDENTIALS_PASSWORD_LIMIT) { + TT_LOG_E(TAG, "Password too long"); + lv_label_set_text(view.password_error, "Password too long"); + lv_obj_remove_flag(view.password_error, LV_OBJ_FLAG_HIDDEN); + return; + } + + bool store = lv_obj_get_state(view.remember_switch) & LV_STATE_CHECKED; + + view.setLoading(true); + + service::wifi::settings::WifiApSettings settings; + strcpy((char*)settings.password, password); + strcpy((char*)settings.ssid, ssid); + settings.auto_connect = TT_WIFI_AUTO_CONNECT; // No UI yet, so use global setting:w + + auto* bindings = &wifi->getBindings(); + bindings->onConnectSsid( + &settings, + store, + bindings->onConnectSsidContext + ); +} + +void View::setLoading(bool loading) { + if (loading) { + lv_obj_add_flag(connect_button, LV_OBJ_FLAG_HIDDEN); + lv_obj_remove_flag(connecting_spinner, LV_OBJ_FLAG_HIDDEN); + lv_obj_add_state(password_textarea, LV_STATE_DISABLED); + lv_obj_add_state(ssid_textarea, LV_STATE_DISABLED); + lv_obj_add_state(remember_switch, LV_STATE_DISABLED); + } else { + lv_obj_remove_flag(connect_button, LV_OBJ_FLAG_HIDDEN); + lv_obj_add_flag(connecting_spinner, LV_OBJ_FLAG_HIDDEN); + lv_obj_remove_state(password_textarea, LV_STATE_DISABLED); + lv_obj_remove_state(ssid_textarea, LV_STATE_DISABLED); + lv_obj_remove_state(remember_switch, LV_STATE_DISABLED); + } +} + +void View::createBottomButtons(WifiConnect* wifi, lv_obj_t* parent) { + lv_obj_t* button_container = lv_obj_create(parent); + lv_obj_set_width(button_container, LV_PCT(100)); + lv_obj_set_height(button_container, LV_SIZE_CONTENT); + lvgl::obj_set_style_no_padding(button_container); + lv_obj_set_style_border_width(button_container, 0, 0); + + remember_switch = lv_switch_create(button_container); + lv_obj_add_state(remember_switch, LV_STATE_CHECKED); + lv_obj_align(remember_switch, LV_ALIGN_LEFT_MID, 0, 0); + + lv_obj_t* remember_label = lv_label_create(button_container); + lv_label_set_text(remember_label, "Remember"); + lv_obj_align(remember_label, LV_ALIGN_CENTER, 0, 0); + lv_obj_align_to(remember_label, remember_switch, LV_ALIGN_OUT_RIGHT_MID, 4, 0); + + connecting_spinner = lv_spinner_create(button_container); + lv_obj_set_size(connecting_spinner, 32, 32); + lv_obj_align(connecting_spinner, LV_ALIGN_RIGHT_MID, 0, 0); + lv_obj_add_flag(connecting_spinner, LV_OBJ_FLAG_HIDDEN); + + connect_button = lv_btn_create(button_container); + lv_obj_t* connect_label = lv_label_create(connect_button); + lv_label_set_text(connect_label, "Connect"); + lv_obj_align(connect_button, LV_ALIGN_RIGHT_MID, 0, 0); + lv_obj_add_event_cb(connect_button, &onConnect, LV_EVENT_CLICKED, wifi); +} + +// TODO: Standardize dialogs +void View::init(App& app, WifiConnect* wifiConnect, lv_obj_t* parent) { + + lv_obj_set_flex_flow(parent, LV_FLEX_FLOW_COLUMN); + lvgl::toolbar_create(parent, app); + + 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); + + // SSID + + lv_obj_t* ssid_wrapper = lv_obj_create(wrapper); + lv_obj_set_width(ssid_wrapper, LV_PCT(100)); + lv_obj_set_height(ssid_wrapper, LV_SIZE_CONTENT); + lvgl::obj_set_style_no_padding(ssid_wrapper); + lv_obj_set_style_border_width(ssid_wrapper, 0, 0); + + lv_obj_t* ssid_label_wrapper = lv_obj_create(ssid_wrapper); + lv_obj_set_width(ssid_label_wrapper, LV_PCT(50)); + lv_obj_set_height(ssid_label_wrapper, LV_SIZE_CONTENT); + lv_obj_align(ssid_label_wrapper, LV_ALIGN_LEFT_MID, 0, 0); + lv_obj_set_style_border_width(ssid_label_wrapper, 0, 0); + lv_obj_set_style_pad_left(ssid_label_wrapper, 0, 0); + lv_obj_set_style_pad_right(ssid_label_wrapper, 0, 0); + + lv_obj_t* ssid_label = lv_label_create(ssid_label_wrapper); + lv_label_set_text(ssid_label, "Network:"); + + ssid_textarea = lv_textarea_create(ssid_wrapper); + lv_textarea_set_one_line(ssid_textarea, true); + lv_obj_align(ssid_textarea, LV_ALIGN_RIGHT_MID, 0, 0); + lv_obj_set_width(ssid_textarea, LV_PCT(50)); + + ssid_error = lv_label_create(wrapper); + lv_obj_set_style_text_color(ssid_error, lv_color_make(255, 50, 50), 0); + lv_obj_add_flag(ssid_error, LV_OBJ_FLAG_HIDDEN); + + // Password + + lv_obj_t* password_wrapper = lv_obj_create(wrapper); + lv_obj_set_width(password_wrapper, LV_PCT(100)); + lv_obj_set_height(password_wrapper, LV_SIZE_CONTENT); + lvgl::obj_set_style_no_padding(password_wrapper); + lv_obj_set_style_border_width(password_wrapper, 0, 0); + + lv_obj_t* password_label_wrapper = lv_obj_create(password_wrapper); + lv_obj_set_width(password_label_wrapper, LV_PCT(50)); + lv_obj_set_height(password_label_wrapper, LV_SIZE_CONTENT); + lv_obj_align_to(password_label_wrapper, password_wrapper, LV_ALIGN_LEFT_MID, 0, 0); + lv_obj_set_style_border_width(password_label_wrapper, 0, 0); + lv_obj_set_style_pad_left(password_label_wrapper, 0, 0); + lv_obj_set_style_pad_right(password_label_wrapper, 0, 0); + + lv_obj_t* password_label = lv_label_create(password_label_wrapper); + lv_label_set_text(password_label, "Password:"); + + password_textarea = lv_textarea_create(password_wrapper); + lv_textarea_set_one_line(password_textarea, true); + lv_textarea_set_password_mode(password_textarea, true); + lv_obj_align(password_textarea, LV_ALIGN_RIGHT_MID, 0, 0); + lv_obj_set_width(password_textarea, LV_PCT(50)); + + password_error = lv_label_create(wrapper); + lv_obj_set_style_text_color(password_error, lv_color_make(255, 50, 50), 0); + lv_obj_add_flag(password_error, LV_OBJ_FLAG_HIDDEN); + + // Connection error + connection_error = lv_label_create(wrapper); + lv_obj_set_style_text_color(connection_error, lv_color_make(255, 50, 50), 0); + lv_obj_add_flag(connection_error, LV_OBJ_FLAG_HIDDEN); + + // Bottom buttons + createBottomButtons(wifiConnect, wrapper); + + // Keyboard bindings + service::gui::keyboardAddTextArea(ssid_textarea); + service::gui::keyboardAddTextArea(password_textarea); + + // Init from app parameters + const Bundle& bundle = app.getParameters(); + std::string ssid; + if (bundle.optString(WIFI_CONNECT_PARAM_SSID, ssid)) { + lv_textarea_set_text(ssid_textarea, ssid.c_str()); + } + + std::string password; + if (bundle.optString(WIFI_CONNECT_PARAM_PASSWORD, password)) { + lv_textarea_set_text(password_textarea, password.c_str()); + } +} + +void View::update( + TT_UNUSED Bindings* bindings, + State* state +) { + if (state->hasConnectionError()) { + setLoading(false); + resetErrors(); + lv_label_set_text(connection_error, "Connection failed"); + lv_obj_remove_flag(connection_error, LV_OBJ_FLAG_HIDDEN); + } +} + +} // namespace diff --git a/Tactility/Source/app/wificonnect/View.h b/Tactility/Source/app/wificonnect/View.h new file mode 100644 index 00000000..7d3761f2 --- /dev/null +++ b/Tactility/Source/app/wificonnect/View.h @@ -0,0 +1,37 @@ +#pragma once + +#include "Bindings.h" +#include "State.h" + +#include "app/App.h" +#include "lvgl.h" + +namespace tt::app::wificonnect { + +class WifiConnect; + +class View { + +public: + + lv_obj_t* ssid_textarea = nullptr; + lv_obj_t* ssid_error = nullptr; + lv_obj_t* password_textarea = nullptr; + lv_obj_t* password_error = nullptr; + lv_obj_t* connect_button = nullptr; + lv_obj_t* cancel_button = nullptr; + lv_obj_t* remember_switch = nullptr; + lv_obj_t* connecting_spinner = nullptr; + lv_obj_t* connection_error = nullptr; + lv_group_t* group = nullptr; + + void init(App& app, WifiConnect* wifiConnect, lv_obj_t* parent); + void update(Bindings* bindings, State* state); + + void createBottomButtons(WifiConnect* wifi, lv_obj_t* parent); + void setLoading(bool loading); + void resetErrors(); +}; + + +} // namespace diff --git a/Tactility/Source/app/wificonnect/WifiConnect.cpp b/Tactility/Source/app/wificonnect/WifiConnect.cpp index dcef4e4a..c6cd4880 100644 --- a/Tactility/Source/app/wificonnect/WifiConnect.cpp +++ b/Tactility/Source/app/wificonnect/WifiConnect.cpp @@ -2,7 +2,6 @@ #include "app/App.h" #include "TactilityCore.h" -#include "WifiConnectStateUpdating.h" #include "service/loader/Loader.h" #include "service/wifi/Wifi.h" #include "lvgl/LvglSync.h" @@ -11,124 +10,106 @@ namespace tt::app::wificonnect { #define TAG "wifi_connect" -// Forward declarations -static void event_callback(const void* message, void* context); - -static void on_connect(const service::wifi::settings::WifiApSettings* ap_settings, bool remember, TT_UNUSED void* parameter) { - auto* wifi = static_cast(parameter); - state_set_ap_settings(wifi, ap_settings); - state_set_connecting(wifi, true); - service::wifi::connect(ap_settings, remember); -} - -static WifiConnect* wifi_connect_alloc() { - auto* wifi = static_cast(malloc(sizeof(WifiConnect))); - - PubSub* wifi_pubsub = service::wifi::getPubsub(); - wifi->wifi_subscription = tt_pubsub_subscribe(wifi_pubsub, &event_callback, wifi); - wifi->mutex = tt_mutex_alloc(MutexTypeNormal); - wifi->state = (WifiConnectState) { - .settings = { - .ssid = { 0 }, - .password = { 0 }, - .auto_connect = false, - }, - .connection_error = false, - .is_connecting = false - }; - wifi->bindings = (WifiConnectBindings) { - .on_connect_ssid = &on_connect, - .on_connect_ssid_context = wifi, - }; - wifi->view_enabled = false; - - return wifi; -} - -static void wifi_connect_free(WifiConnect* wifi) { - PubSub* wifi_pubsub = service::wifi::getPubsub(); - tt_pubsub_unsubscribe(wifi_pubsub, wifi->wifi_subscription); - tt_mutex_free(wifi->mutex); - - free(wifi); -} - -void lock(WifiConnect* wifi) { - tt_assert(wifi); - tt_assert(wifi->mutex); - tt_mutex_acquire(wifi->mutex, TtWaitForever); -} - -void unlock(WifiConnect* wifi) { - tt_assert(wifi); - tt_assert(wifi->mutex); - tt_mutex_release(wifi->mutex); -} - -void request_view_update(WifiConnect* wifi) { - lock(wifi); - if (wifi->view_enabled) { - if (lvgl::lock(1000)) { - view_update(&wifi->view, &wifi->bindings, &wifi->state); - lvgl::unlock(); - } else { - TT_LOG_E(TAG, "Failed to lock lvgl"); - } - } - unlock(wifi); -} - -static void event_callback(const void* message, void* context) { +static void eventCallback(const void* message, void* context) { auto* event = static_cast(message); auto* wifi = static_cast(context); + State& state = wifi->getState(); switch (event->type) { case service::wifi::WifiEventTypeConnectionFailed: - if (wifi->state.is_connecting) { - state_set_connecting(wifi, false); - state_set_radio_error(wifi, true); - request_view_update(wifi); + if (state.isConnecting()) { + state.setConnecting(false); + state.setConnectionError(true); + wifi->requestViewUpdate(); } break; case service::wifi::WifiEventTypeConnectionSuccess: - if (wifi->state.is_connecting) { - state_set_connecting(wifi, false); + if (wifi->getState().isConnecting()) { + state.setConnecting(false); service::loader::stopApp(); } break; default: break; } - request_view_update(wifi); + wifi->requestViewUpdate(); } -static void app_show(App& app, lv_obj_t* parent) { - auto* wifi = static_cast(app.getData()); - - lock(wifi); - wifi->view_enabled = true; - view_create(app, wifi, parent); - view_update(&wifi->view, &wifi->bindings, &wifi->state); - unlock(wifi); +static void onConnect(const service::wifi::settings::WifiApSettings* ap_settings, bool remember, TT_UNUSED void* parameter) { + auto* wifi = static_cast(parameter); + wifi->getState().setApSettings(ap_settings); + wifi->getState().setConnecting(true); + service::wifi::connect(ap_settings, remember); } -static void app_hide(App& app) { - auto* wifi = static_cast(app.getData()); +WifiConnect::WifiConnect() { + PubSub* wifi_pubsub = service::wifi::getPubsub(); + wifiSubscription = tt_pubsub_subscribe(wifi_pubsub, &eventCallback, this); + bindings = (Bindings) { + .onConnectSsid = onConnect, + .onConnectSsidContext = this, + }; +} + +WifiConnect::~WifiConnect() { + PubSub* pubsub = service::wifi::getPubsub(); + tt_pubsub_unsubscribe(pubsub, wifiSubscription); +} + +void WifiConnect::lock() { + tt_check(mutex.acquire(TtWaitForever) == TtStatusOk); +} + +void WifiConnect::unlock() { + tt_check(mutex.release() == TtStatusOk); +} + +void WifiConnect::requestViewUpdate() { + lock(); + if (view_enabled) { + if (lvgl::lock(1000)) { + view.update(&bindings, &state); + lvgl::unlock(); + } else { + TT_LOG_E(TAG, "Failed to lock lvgl"); + } + } + unlock(); +} + +void WifiConnect::onShow(App& app, lv_obj_t* parent) { + lock(); + view_enabled = true; + view.init(app, this, parent); + view.update(&bindings, &state); + unlock(); +} + +void WifiConnect::onHide(App& app) { // No need to lock view, as this is called from within Gui's LVGL context - view_destroy(&wifi->view); - lock(wifi); - wifi->view_enabled = false; - unlock(wifi); + lock(); + view_enabled = false; + unlock(); } -static void app_start(App& app) { - auto* wifi_connect = wifi_connect_alloc(); +static void onShow(App& app, lv_obj_t* parent) { + auto* wifi = static_cast(app.getData()); + wifi->onShow(app, parent); +} + +static void onHide(App& app) { + auto* wifi = static_cast(app.getData()); + wifi->onHide(app); +} + +static void onStart(App& app) { + auto* wifi_connect = new WifiConnect(); app.setData(wifi_connect); } -static void app_stop(App& app) { - auto* wifi = static_cast(app.getData()); - tt_assert(wifi != nullptr); - wifi_connect_free(wifi); +static void onStop(App& app) { + auto* wifi_connect = static_cast(app.getData()); + tt_assert(wifi_connect != nullptr); + delete wifi_connect; } extern const Manifest manifest = { @@ -136,10 +117,10 @@ extern const Manifest manifest = { .name = "Wi-Fi Connect", .icon = LV_SYMBOL_WIFI, .type = TypeSettings, - .onStart = &app_start, - .onStop = &app_stop, - .onShow = &app_show, - .onHide = &app_hide + .onStart = &onStart, + .onStop = &onStop, + .onShow = &onShow, + .onHide = &onHide }; } // namespace diff --git a/Tactility/Source/app/wificonnect/WifiConnect.h b/Tactility/Source/app/wificonnect/WifiConnect.h index 5d0b5443..2f3a13d0 100644 --- a/Tactility/Source/app/wificonnect/WifiConnect.h +++ b/Tactility/Source/app/wificonnect/WifiConnect.h @@ -1,21 +1,43 @@ #pragma once +#include "Bindings.h" +#include "State.h" +#include "View.h" + #include "Mutex.h" -#include "WifiConnectBindings.h" -#include "WifiConnectState.h" -#include "WifiConnectView.h" #include "service/wifi/Wifi.h" namespace tt::app::wificonnect { -typedef struct { - PubSubSubscription* wifi_subscription; - Mutex* mutex; - WifiConnectState state; - WifiConnectView view; - bool view_enabled; - WifiConnectBindings bindings; -} WifiConnect; +class WifiConnect { + PubSubSubscription* wifiSubscription; + Mutex mutex; + State state; + View view; + bool view_enabled = false; + Bindings bindings = { + .onConnectSsid = nullptr, + .onConnectSsidContext = nullptr + }; + +public: + + WifiConnect(); + ~WifiConnect(); + + void lock(); + void unlock(); + + void onShow(App& app, lv_obj_t* parent); + void onHide(App& app); + + State& getState() { return state; } + Bindings& getBindings() { return bindings; } + View& getView() { return view; } + + + void requestViewUpdate(); +}; void lock(WifiConnect* wifi); diff --git a/Tactility/Source/app/wificonnect/WifiConnectState.h b/Tactility/Source/app/wificonnect/WifiConnectState.h deleted file mode 100644 index fe6297cf..00000000 --- a/Tactility/Source/app/wificonnect/WifiConnectState.h +++ /dev/null @@ -1,17 +0,0 @@ -#pragma once - -#include "service/wifi/Wifi.h" -#include "service/wifi/WifiSettings.h" - -namespace tt::app::wificonnect { - -/** - * View's state - */ -typedef struct { - service::wifi::settings::WifiApSettings settings; - bool connection_error; - bool is_connecting; -} WifiConnectState; - -} // namespace diff --git a/Tactility/Source/app/wificonnect/WifiConnectStateUpdating.cpp b/Tactility/Source/app/wificonnect/WifiConnectStateUpdating.cpp deleted file mode 100644 index 10c09873..00000000 --- a/Tactility/Source/app/wificonnect/WifiConnectStateUpdating.cpp +++ /dev/null @@ -1,23 +0,0 @@ -#include "WifiConnectStateUpdating.h" - -namespace tt::app::wificonnect { - -void state_set_radio_error(WifiConnect* wifi, bool error) { - lock(wifi); - wifi->state.connection_error = error; - unlock(wifi); -} - -void state_set_ap_settings(WifiConnect* wifi, const service::wifi::settings::WifiApSettings* settings) { - lock(wifi); - memcpy(&(wifi->state.settings), settings, sizeof(service::wifi::settings::WifiApSettings)); - unlock(wifi); -} - -void state_set_connecting(WifiConnect* wifi, bool is_connecting) { - lock(wifi); - wifi->state.is_connecting = is_connecting; - unlock(wifi); -} - -} // namespace diff --git a/Tactility/Source/app/wificonnect/WifiConnectStateUpdating.h b/Tactility/Source/app/wificonnect/WifiConnectStateUpdating.h deleted file mode 100644 index ee934318..00000000 --- a/Tactility/Source/app/wificonnect/WifiConnectStateUpdating.h +++ /dev/null @@ -1,11 +0,0 @@ -#pragma once - -#include "WifiConnect.h" - -namespace tt::app::wificonnect { - -void state_set_radio_error(WifiConnect* wifi, bool error); -void state_set_ap_settings(WifiConnect* wifi, const service::wifi::settings::WifiApSettings* settings); -void state_set_connecting(WifiConnect* wifi, bool is_connecting); - -} // namespace diff --git a/Tactility/Source/app/wificonnect/WifiConnectView.cpp b/Tactility/Source/app/wificonnect/WifiConnectView.cpp deleted file mode 100644 index 77577a61..00000000 --- a/Tactility/Source/app/wificonnect/WifiConnectView.cpp +++ /dev/null @@ -1,227 +0,0 @@ -#include "WifiConnectView.h" - -#include "Log.h" -#include "WifiConnect.h" -#include "WifiConnectBundle.h" -#include "WifiConnectState.h" -#include "WifiConnectStateUpdating.h" -#include "lvgl.h" -#include "service/gui/Gui.h" -#include "service/wifi/WifiSettings.h" -#include "lvgl/Style.h" -#include "lvgl/Toolbar.h" - -namespace tt::app::wificonnect { - -#define TAG "wifi_connect" - -static void view_set_loading(WifiConnectView* view, bool loading); - -static void reset_errors(WifiConnectView* view) { - lv_obj_add_flag(view->password_error, LV_OBJ_FLAG_HIDDEN); - lv_obj_add_flag(view->ssid_error, LV_OBJ_FLAG_HIDDEN); - lv_obj_add_flag(view->connection_error, LV_OBJ_FLAG_HIDDEN); -} - -static void on_connect(lv_event_t* event) { - WifiConnect* wifi = (WifiConnect*)lv_event_get_user_data(event); - WifiConnectView* view = &wifi->view; - - state_set_radio_error(wifi, false); - reset_errors(view); - - const char* ssid = lv_textarea_get_text(view->ssid_textarea); - size_t ssid_len = strlen(ssid); - if (ssid_len > TT_WIFI_SSID_LIMIT) { - TT_LOG_E(TAG, "SSID too long"); - lv_label_set_text(view->ssid_error, "SSID too long"); - lv_obj_remove_flag(view->ssid_error, LV_OBJ_FLAG_HIDDEN); - return; - } - - const char* password = lv_textarea_get_text(view->password_textarea); - size_t password_len = strlen(password); - if (password_len > TT_WIFI_CREDENTIALS_PASSWORD_LIMIT) { - TT_LOG_E(TAG, "Password too long"); - lv_label_set_text(view->password_error, "Password too long"); - lv_obj_remove_flag(view->password_error, LV_OBJ_FLAG_HIDDEN); - return; - } - - bool store = lv_obj_get_state(view->remember_switch) & LV_STATE_CHECKED; - - view_set_loading(view, true); - - service::wifi::settings::WifiApSettings settings; - strcpy((char*)settings.password, password); - strcpy((char*)settings.ssid, ssid); - settings.auto_connect = TT_WIFI_AUTO_CONNECT; // No UI yet, so use global setting:w - - WifiConnectBindings* bindings = &wifi->bindings; - bindings->on_connect_ssid( - &settings, - store, - bindings->on_connect_ssid_context - ); -} - -static void view_set_loading(WifiConnectView* view, bool loading) { - if (loading) { - lv_obj_add_flag(view->connect_button, LV_OBJ_FLAG_HIDDEN); - lv_obj_remove_flag(view->connecting_spinner, LV_OBJ_FLAG_HIDDEN); - lv_obj_add_state(view->password_textarea, LV_STATE_DISABLED); - lv_obj_add_state(view->ssid_textarea, LV_STATE_DISABLED); - lv_obj_add_state(view->remember_switch, LV_STATE_DISABLED); - } else { - lv_obj_remove_flag(view->connect_button, LV_OBJ_FLAG_HIDDEN); - lv_obj_add_flag(view->connecting_spinner, LV_OBJ_FLAG_HIDDEN); - lv_obj_remove_state(view->password_textarea, LV_STATE_DISABLED); - lv_obj_remove_state(view->ssid_textarea, LV_STATE_DISABLED); - lv_obj_remove_state(view->remember_switch, LV_STATE_DISABLED); - } - -} - -void view_create_bottom_buttons(WifiConnect* wifi, lv_obj_t* parent) { - WifiConnectView* view = &wifi->view; - - lv_obj_t* button_container = lv_obj_create(parent); - lv_obj_set_width(button_container, LV_PCT(100)); - lv_obj_set_height(button_container, LV_SIZE_CONTENT); - lvgl::obj_set_style_no_padding(button_container); - lv_obj_set_style_border_width(button_container, 0, 0); - - view->remember_switch = lv_switch_create(button_container); - lv_obj_add_state(view->remember_switch, LV_STATE_CHECKED); - lv_obj_align(view->remember_switch, LV_ALIGN_LEFT_MID, 0, 0); - - lv_obj_t* remember_label = lv_label_create(button_container); - lv_label_set_text(remember_label, "Remember"); - lv_obj_align(remember_label, LV_ALIGN_CENTER, 0, 0); - lv_obj_align_to(remember_label, view->remember_switch, LV_ALIGN_OUT_RIGHT_MID, 4, 0); - - view->connecting_spinner = lv_spinner_create(button_container); - lv_obj_set_size(view->connecting_spinner, 32, 32); - lv_obj_align(view->connecting_spinner, LV_ALIGN_RIGHT_MID, 0, 0); - lv_obj_add_flag(view->connecting_spinner, LV_OBJ_FLAG_HIDDEN); - - view->connect_button = lv_btn_create(button_container); - lv_obj_t* connect_label = lv_label_create(view->connect_button); - lv_label_set_text(connect_label, "Connect"); - lv_obj_align(view->connect_button, LV_ALIGN_RIGHT_MID, 0, 0); - lv_obj_add_event_cb(view->connect_button, &on_connect, LV_EVENT_CLICKED, wifi); -} - -// TODO: Standardize dialogs -void view_create(const App& app, void* wifi, lv_obj_t* parent) { - WifiConnect* wifi_connect = (WifiConnect*)wifi; - WifiConnectView* view = &wifi_connect->view; - - lv_obj_set_flex_flow(parent, LV_FLEX_FLOW_COLUMN); - lvgl::toolbar_create(parent, app); - - 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); - - // SSID - - lv_obj_t* ssid_wrapper = lv_obj_create(wrapper); - lv_obj_set_width(ssid_wrapper, LV_PCT(100)); - lv_obj_set_height(ssid_wrapper, LV_SIZE_CONTENT); - lvgl::obj_set_style_no_padding(ssid_wrapper); - lv_obj_set_style_border_width(ssid_wrapper, 0, 0); - - lv_obj_t* ssid_label_wrapper = lv_obj_create(ssid_wrapper); - lv_obj_set_width(ssid_label_wrapper, LV_PCT(50)); - lv_obj_set_height(ssid_label_wrapper, LV_SIZE_CONTENT); - lv_obj_align(ssid_label_wrapper, LV_ALIGN_LEFT_MID, 0, 0); - lv_obj_set_style_border_width(ssid_label_wrapper, 0, 0); - lv_obj_set_style_pad_left(ssid_label_wrapper, 0, 0); - lv_obj_set_style_pad_right(ssid_label_wrapper, 0, 0); - - lv_obj_t* ssid_label = lv_label_create(ssid_label_wrapper); - lv_label_set_text(ssid_label, "Network:"); - - view->ssid_textarea = lv_textarea_create(ssid_wrapper); - lv_textarea_set_one_line(view->ssid_textarea, true); - lv_obj_align(view->ssid_textarea, LV_ALIGN_RIGHT_MID, 0, 0); - lv_obj_set_width(view->ssid_textarea, LV_PCT(50)); - - view->ssid_error = lv_label_create(wrapper); - lv_obj_set_style_text_color(view->ssid_error, lv_color_make(255, 50, 50), 0); - lv_obj_add_flag(view->ssid_error, LV_OBJ_FLAG_HIDDEN); - - // Password - - lv_obj_t* password_wrapper = lv_obj_create(wrapper); - lv_obj_set_width(password_wrapper, LV_PCT(100)); - lv_obj_set_height(password_wrapper, LV_SIZE_CONTENT); - lvgl::obj_set_style_no_padding(password_wrapper); - lv_obj_set_style_border_width(password_wrapper, 0, 0); - - lv_obj_t* password_label_wrapper = lv_obj_create(password_wrapper); - lv_obj_set_width(password_label_wrapper, LV_PCT(50)); - lv_obj_set_height(password_label_wrapper, LV_SIZE_CONTENT); - lv_obj_align_to(password_label_wrapper, password_wrapper, LV_ALIGN_LEFT_MID, 0, 0); - lv_obj_set_style_border_width(password_label_wrapper, 0, 0); - lv_obj_set_style_pad_left(password_label_wrapper, 0, 0); - lv_obj_set_style_pad_right(password_label_wrapper, 0, 0); - - lv_obj_t* password_label = lv_label_create(password_label_wrapper); - lv_label_set_text(password_label, "Password:"); - - view->password_textarea = lv_textarea_create(password_wrapper); - lv_textarea_set_one_line(view->password_textarea, true); - lv_textarea_set_password_mode(view->password_textarea, true); - lv_obj_align(view->password_textarea, LV_ALIGN_RIGHT_MID, 0, 0); - lv_obj_set_width(view->password_textarea, LV_PCT(50)); - - view->password_error = lv_label_create(wrapper); - lv_obj_set_style_text_color(view->password_error, lv_color_make(255, 50, 50), 0); - lv_obj_add_flag(view->password_error, LV_OBJ_FLAG_HIDDEN); - - // Connection error - view->connection_error = lv_label_create(wrapper); - lv_obj_set_style_text_color(view->connection_error, lv_color_make(255, 50, 50), 0); - lv_obj_add_flag(view->connection_error, LV_OBJ_FLAG_HIDDEN); - - // Bottom buttons - view_create_bottom_buttons(wifi_connect, wrapper); - - // Keyboard bindings - service::gui::keyboardAddTextArea(view->ssid_textarea); - service::gui::keyboardAddTextArea(view->password_textarea); - - // Init from app parameters - const Bundle& bundle = app.getParameters(); - std::string ssid; - if (bundle.optString(WIFI_CONNECT_PARAM_SSID, ssid)) { - lv_textarea_set_text(view->ssid_textarea, ssid.c_str()); - } - - std::string password; - if (bundle.optString(WIFI_CONNECT_PARAM_PASSWORD, password)) { - lv_textarea_set_text(view->password_textarea, password.c_str()); - } -} - -void view_destroy(TT_UNUSED WifiConnectView* view) { - // NO-OP -} - -void view_update( - WifiConnectView* view, - TT_UNUSED WifiConnectBindings* bindings, - WifiConnectState* state -) { - if (state->connection_error) { - view_set_loading(view, false); - reset_errors(view); - lv_label_set_text(view->connection_error, "Connection failed"); - lv_obj_remove_flag(view->connection_error, LV_OBJ_FLAG_HIDDEN); - } -} - -} // namespace diff --git a/Tactility/Source/app/wificonnect/WifiConnectView.h b/Tactility/Source/app/wificonnect/WifiConnectView.h deleted file mode 100644 index c2b6952a..00000000 --- a/Tactility/Source/app/wificonnect/WifiConnectView.h +++ /dev/null @@ -1,27 +0,0 @@ -#pragma once - -#include "app/App.h" -#include "WifiConnectBindings.h" -#include "WifiConnectState.h" -#include "lvgl.h" - -namespace tt::app::wificonnect { - -typedef struct { - lv_obj_t* ssid_textarea; - lv_obj_t* ssid_error; - lv_obj_t* password_textarea; - lv_obj_t* password_error; - lv_obj_t* connect_button; - lv_obj_t* cancel_button; - lv_obj_t* remember_switch; - lv_obj_t* connecting_spinner; - lv_obj_t* connection_error; - lv_group_t* group; -} WifiConnectView; - -void view_create(const App& app, void* wifi, lv_obj_t* parent); -void view_update(WifiConnectView* view, WifiConnectBindings* bindings, WifiConnectState* state); -void view_destroy(WifiConnectView* view); - -} // namespace diff --git a/Tactility/Source/app/wifimanage/State.cpp b/Tactility/Source/app/wifimanage/State.cpp index 63b44f27..eb17f52a 100644 --- a/Tactility/Source/app/wifimanage/State.cpp +++ b/Tactility/Source/app/wifimanage/State.cpp @@ -15,6 +15,20 @@ void State::setRadioState(service::wifi::WifiRadioState state) { tt_check(mutex.release() == TtStatusOk); } +service::wifi::WifiRadioState State::getRadioState() const { + tt_check(mutex.acquire(TtWaitForever) == TtStatusOk); + auto result = radioState; + tt_check(mutex.release() == TtStatusOk); + return result; +} + +bool State::isScanning() const { + tt_check(mutex.acquire(TtWaitForever) == TtStatusOk); + bool result = scanning; + tt_check(mutex.release() == TtStatusOk); + return result; +} + const std::vector& State::lockApRecords() const { tt_check(mutex.acquire(TtWaitForever) == TtStatusOk); return apRecords; @@ -30,4 +44,17 @@ void State::updateApRecords() { tt_check(mutex.release() == TtStatusOk); } +void State::setConnectSsid(std::string ssid) { + tt_check(mutex.acquire(TtWaitForever) == TtStatusOk); + connectSsid = ssid; + tt_check(mutex.release() == TtStatusOk); +} + +std::string State::getConnectSsid() const { + tt_check(mutex.acquire(TtWaitForever) == TtStatusOk); + auto result = connectSsid; + tt_check(mutex.release() == TtStatusOk); + return result; +} + } // namespace diff --git a/Tactility/Source/app/wifimanage/State.h b/Tactility/Source/app/wifimanage/State.h index 5f709100..74fbe7eb 100644 --- a/Tactility/Source/app/wifimanage/State.h +++ b/Tactility/Source/app/wifimanage/State.h @@ -10,7 +10,7 @@ namespace tt::app::wifimanage { */ class State { - Mutex mutex; + Mutex mutex = Mutex(MutexTypeRecursive); bool scanning; service::wifi::WifiRadioState radioState; std::vector apRecords; @@ -20,18 +20,18 @@ public: State() {} void setScanning(bool isScanning); - bool isScanning() const { return scanning; } + bool isScanning() const; void setRadioState(service::wifi::WifiRadioState state); - service::wifi::WifiRadioState getRadioState() const { return radioState; } + service::wifi::WifiRadioState getRadioState() const; void updateApRecords(); const std::vector& lockApRecords() const; void unlockApRecords() const; - void setConnectSsid(std::string ssid) { connectSsid = ssid; } - std::string getConnectSsid() const { return connectSsid; } + void setConnectSsid(std::string ssid); + std::string getConnectSsid() const; }; } // namespace diff --git a/Tactility/Source/app/wifimanage/View.cpp b/Tactility/Source/app/wifimanage/View.cpp index 69954c0d..92852510 100644 --- a/Tactility/Source/app/wifimanage/View.cpp +++ b/Tactility/Source/app/wifimanage/View.cpp @@ -25,6 +25,15 @@ static void on_enable_switch_changed(lv_event_t* event) { } } +static void on_enable_on_boot_switch_changed(lv_event_t* event) { + lv_event_code_t code = lv_event_get_code(event); + auto* enable_switch = static_cast(lv_event_get_target(event)); + if (code == LV_EVENT_VALUE_CHANGED) { + bool is_on = lv_obj_has_state(enable_switch, LV_STATE_CHECKED); + service::wifi::settings::setEnableOnBoot(is_on); + } +} + static void on_disconnect_pressed(lv_event_t* event) { auto* bindings = static_cast(lv_event_get_user_data(event)); bindings->onDisconnect(); @@ -120,6 +129,15 @@ void View::updateWifiToggle(State* state) { } } +void View::updateEnableOnBootToggle() { + lv_obj_clear_state(enable_on_boot_switch, LV_STATE_ANY); + if (service::wifi::settings::shouldEnableOnBoot()) { + lv_obj_add_state(enable_on_boot_switch, LV_STATE_CHECKED); + } else { + lv_obj_remove_state(enable_on_boot_switch, LV_STATE_CHECKED); + } +} + void View::updateConnectedAp(State* state, TT_UNUSED Bindings* bindings) { switch (state->getRadioState()) { case service::wifi::WIFI_RADIO_CONNECTION_PENDING: @@ -141,7 +159,10 @@ void View::init(const App& app, Bindings* bindings, lv_obj_t* parent) { root = parent; lv_obj_set_flex_flow(parent, LV_FLEX_FLOW_COLUMN); - lvgl::toolbar_create(parent, app); + lv_obj_t* toolbar = lvgl::toolbar_create(parent, app); + + enable_switch = lvgl::toolbar_add_switch_action(toolbar); + lv_obj_add_event_cb(enable_switch, on_enable_switch_changed, LV_EVENT_ALL, bindings); lv_obj_t* wrapper = lv_obj_create(parent); lv_obj_set_width(wrapper, LV_PCT(100)); @@ -156,12 +177,12 @@ void View::init(const App& app, Bindings* bindings, lv_obj_t* parent) { lvgl::obj_set_style_bg_invisible(switch_container); lv_obj_t* enable_label = lv_label_create(switch_container); - lv_label_set_text(enable_label, "Wi-Fi"); + lv_label_set_text(enable_label, "Enable on boot"); lv_obj_set_align(enable_label, LV_ALIGN_LEFT_MID); - enable_switch = lv_switch_create(switch_container); - lv_obj_add_event_cb(enable_switch, on_enable_switch_changed, LV_EVENT_ALL, bindings); - lv_obj_set_align(enable_switch, LV_ALIGN_RIGHT_MID); + enable_on_boot_switch = lv_switch_create(switch_container); + lv_obj_add_event_cb(enable_on_boot_switch, on_enable_on_boot_switch_changed, LV_EVENT_ALL, bindings); + lv_obj_set_align(enable_on_boot_switch, LV_ALIGN_RIGHT_MID); connected_ap_container = lv_obj_create(wrapper); lv_obj_set_size(connected_ap_container, LV_PCT(100), LV_SIZE_CONTENT); @@ -207,6 +228,7 @@ void View::init(const App& app, Bindings* bindings, lv_obj_t* parent) { void View::update(Bindings* bindings, State* state) { updateWifiToggle(state); + updateEnableOnBootToggle(); updateScanning(state); updateNetworkList(state, bindings); updateConnectedAp(state, bindings); diff --git a/Tactility/Source/app/wifimanage/View.h b/Tactility/Source/app/wifimanage/View.h index eab212d3..65376c78 100644 --- a/Tactility/Source/app/wifimanage/View.h +++ b/Tactility/Source/app/wifimanage/View.h @@ -11,6 +11,7 @@ class View { private: lv_obj_t* root = nullptr; lv_obj_t* enable_switch = nullptr; + lv_obj_t* enable_on_boot_switch = nullptr; lv_obj_t* scanning_spinner = nullptr; lv_obj_t* networks_label = nullptr; lv_obj_t* networks_list = nullptr; @@ -25,6 +26,7 @@ private: void updateConnectedAp(State* state, TT_UNUSED Bindings* bindings); void updateWifiToggle(State* state); + void updateEnableOnBootToggle(); void updateScanning(State* state); void updateNetworkList(State* state, Bindings* bindings); void createNetworkButton(Bindings* bindings, const service::wifi::WifiApRecord& record); diff --git a/Tactility/Source/app/wifimanage/WifiManage.cpp b/Tactility/Source/app/wifimanage/WifiManage.cpp index a393ec74..64b18369 100644 --- a/Tactility/Source/app/wifimanage/WifiManage.cpp +++ b/Tactility/Source/app/wifimanage/WifiManage.cpp @@ -1,7 +1,7 @@ #include "WifiManage.h" #include "app/App.h" -#include "app/wificonnect/WifiConnectBundle.h" +#include "app/wificonnect/Parameters.h" #include "TactilityCore.h" #include "service/loader/Loader.h" #include "service/wifi/WifiSettings.h" diff --git a/Tactility/Source/lvgl/Toolbar.cpp b/Tactility/Source/lvgl/Toolbar.cpp index 3d599b64..b6bd2e50 100644 --- a/Tactility/Source/lvgl/Toolbar.cpp +++ b/Tactility/Source/lvgl/Toolbar.cpp @@ -103,7 +103,7 @@ void toolbar_set_nav_action(lv_obj_t* obj, const char* icon, lv_event_cb_t callb lv_image_set_src(toolbar->close_button_image, icon); // e.g. LV_SYMBOL_CLOSE } -uint8_t toolbar_add_action(lv_obj_t* obj, const char* icon, const char* text, lv_event_cb_t callback, void* user_data) { +uint8_t toolbar_add_action(lv_obj_t* obj, const char* icon, lv_event_cb_t callback, void* user_data) { auto* toolbar = (Toolbar*)obj; uint8_t id = toolbar->action_count; tt_check(toolbar->action_count < TOOLBAR_ACTION_LIMIT, "max actions reached"); @@ -120,4 +120,11 @@ uint8_t toolbar_add_action(lv_obj_t* obj, const char* icon, const char* text, lv return id; } +lv_obj_t* toolbar_add_switch_action(lv_obj_t* obj) { + auto* toolbar = (Toolbar*)obj; + lv_obj_t* widget = lv_switch_create(toolbar->action_container); + lv_obj_set_pos(widget, 0, 4); // Because aligning doesn't work + return widget; +} + } // namespace diff --git a/Tactility/Source/lvgl/Toolbar.h b/Tactility/Source/lvgl/Toolbar.h index d3396ab5..17bad633 100644 --- a/Tactility/Source/lvgl/Toolbar.h +++ b/Tactility/Source/lvgl/Toolbar.h @@ -22,6 +22,6 @@ lv_obj_t* toolbar_create(lv_obj_t* parent, const std::string& title); lv_obj_t* toolbar_create(lv_obj_t* parent, const app::App& app); void toolbar_set_title(lv_obj_t* obj, const std::string& title); void toolbar_set_nav_action(lv_obj_t* obj, const char* icon, lv_event_cb_t callback, void* user_data); -uint8_t toolbar_add_action(lv_obj_t* obj, const char* icon, const char* text, lv_event_cb_t callback, void* user_data); - +uint8_t toolbar_add_action(lv_obj_t* obj, const char* icon, lv_event_cb_t callback, void* user_data); +lv_obj_t* toolbar_add_switch_action(lv_obj_t* obj); } // namespace diff --git a/TactilityHeadless/Source/service/wifi/WifiEsp.cpp b/TactilityHeadless/Source/service/wifi/WifiEsp.cpp index 54f0b95b..e6f438af 100644 --- a/TactilityHeadless/Source/service/wifi/WifiEsp.cpp +++ b/TactilityHeadless/Source/service/wifi/WifiEsp.cpp @@ -137,10 +137,11 @@ bool isScanning() { void connect(const settings::WifiApSettings* ap, bool remember) { TT_LOG_I(TAG, "connect(%s, %d)", ap->ssid, remember); tt_assert(wifi_singleton); + // Manual connect (e.g. via app) should stop auto-connecting until the connection is established + wifi_singleton->pause_auto_connect = true; lock(wifi_singleton); memcpy(&wifi_singleton->connection_target, ap, sizeof(settings::WifiApSettings)); wifi_singleton->connection_target_remember = remember; - wifi_singleton->pause_auto_connect = false; WifiMessage message = {.type = WifiMessageTypeConnect}; wifi_singleton->queue.put(&message, 100 / portTICK_PERIOD_MS); unlock(wifi_singleton); @@ -155,6 +156,7 @@ void disconnect() { .password = { 0 }, .auto_connect = false }; + // Manual disconnect (e.g. via app) should stop auto-connecting until a new connection is established wifi_singleton->pause_auto_connect = true; WifiMessage message = {.type = WifiMessageTypeDisconnect}; wifi_singleton->queue.put(&message, 100 / portTICK_PERIOD_MS); @@ -274,13 +276,20 @@ static void publish_event_simple(Wifi* wifi, WifiEventType type) { } static bool copy_scan_list(Wifi* wifi) { - if ((wifi->radio_state == WIFI_RADIO_ON || wifi->radio_state == WIFI_RADIO_CONNECTION_ACTIVE) && wifi->scan_active) { - // Create scan list if it does not exist - scan_list_alloc_safely(wifi); - wifi->scan_list_count = 0; - uint16_t record_count = wifi->scan_list_limit; + bool can_fetch_results = (wifi->radio_state == WIFI_RADIO_ON || wifi->radio_state == WIFI_RADIO_CONNECTION_ACTIVE) && + wifi->scan_active; - ESP_ERROR_CHECK(esp_wifi_scan_get_ap_records(&record_count, wifi->scan_list)); + if (!can_fetch_results) { + TT_LOG_I(TAG, "Skip scan result fetching"); + return false; + } + + // Create scan list if it does not exist + scan_list_alloc_safely(wifi); + wifi->scan_list_count = 0; + uint16_t record_count = wifi->scan_list_limit; + esp_err_t scan_result = esp_wifi_scan_get_ap_records(&record_count, wifi->scan_list); + if (scan_result == ESP_OK) { uint16_t safe_record_count = TT_MIN(wifi->scan_list_limit, record_count); wifi->scan_list_count = safe_record_count; TT_LOG_I(TAG, "Scanned %u APs. Showing %u:", record_count, safe_record_count); @@ -290,6 +299,7 @@ static bool copy_scan_list(Wifi* wifi) { } return true; } else { + TT_LOG_I(TAG, "Failed to get scanned records: %s", esp_err_to_name(scan_result)); return false; } } @@ -322,16 +332,20 @@ static void event_handler(TT_UNUSED void* arg, esp_event_base_t event_base, int3 esp_wifi_connect(); } } else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED) { - if (wifi_singleton->radio_state != WIFI_RADIO_OFF_PENDING) { + TT_LOG_I(TAG, "event_handler: disconnected"); + if (wifi_singleton->radio_state == WIFI_RADIO_CONNECTION_PENDING) { wifi_singleton->connection_wait_flags.set(WIFI_FAIL_BIT); - TT_LOG_I(TAG, "event_handler: disconnected"); - wifi_singleton->radio_state = WIFI_RADIO_ON; - publish_event_simple(wifi_singleton, WifiEventTypeDisconnected); } + wifi_singleton->radio_state = WIFI_RADIO_ON; + publish_event_simple(wifi_singleton, WifiEventTypeDisconnected); } else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP) { auto* event = static_cast(event_data); TT_LOG_I(TAG, "event_handler: got ip:" IPSTR, IP2STR(&event->ip_info.ip)); - wifi_singleton->connection_wait_flags.set(WIFI_CONNECTED_BIT); + if (wifi_singleton->radio_state == WIFI_RADIO_CONNECTION_PENDING) { + wifi_singleton->connection_wait_flags.set(WIFI_CONNECTED_BIT); + // We resume auto-connecting only when there was an explicit request by the user for the connection + wifi_singleton->pause_auto_connect = false; // Resume auto-connection + } } else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_SCAN_DONE) { auto* event = static_cast(event_data); TT_LOG_I(TAG, "event_handler: wifi scanning done (scan id %u)", event->scan_id); @@ -594,7 +608,7 @@ static void connect_internal(Wifi* wifi) { esp_err_t set_config_result = esp_wifi_set_config(WIFI_IF_STA, &wifi_config); if (set_config_result != ESP_OK) { wifi->radio_state = WIFI_RADIO_ON; - TT_LOG_E(TAG, "failed to set wifi config (%s)", esp_err_to_name(set_config_result)); + TT_LOG_E(TAG, "Failed to set wifi config (%s)", esp_err_to_name(set_config_result)); publish_event_simple(wifi, WifiEventTypeConnectionFailed); return; } @@ -602,7 +616,7 @@ static void connect_internal(Wifi* wifi) { esp_err_t wifi_start_result = esp_wifi_start(); if (wifi_start_result != ESP_OK) { wifi->radio_state = WIFI_RADIO_ON; - TT_LOG_E(TAG, "failed to start wifi to begin connecting (%s)", esp_err_to_name(wifi_start_result)); + TT_LOG_E(TAG, "Failed to start wifi to begin connecting (%s)", esp_err_to_name(wifi_start_result)); publish_event_simple(wifi, WifiEventTypeConnectionFailed); return; } @@ -680,8 +694,11 @@ static void disconnect_internal_but_keep_active(Wifi* wifi) { } static bool shouldScanForAutoConnect(Wifi* wifi) { - bool is_radio_in_scannable_state = wifi->radio_state == WIFI_RADIO_ON && !wifi->scan_active; - if (!wifi->pause_auto_connect && is_radio_in_scannable_state) { + bool is_radio_in_scannable_state = wifi->radio_state == WIFI_RADIO_ON && + !wifi->scan_active && + !wifi->pause_auto_connect; + + if (is_radio_in_scannable_state) { TickType_t current_time = tt::get_ticks(); bool scan_time_has_looped = (current_time < wifi->last_scan_time); bool no_recent_scan = (current_time - wifi->last_scan_time) > (AUTO_SCAN_INTERVAL / portTICK_PERIOD_MS); @@ -698,7 +715,8 @@ _Noreturn int32_t wifi_main(TT_UNUSED void* parameter) { Wifi* wifi = wifi_singleton; MessageQueue& queue = wifi->queue; - if (TT_WIFI_AUTO_ENABLE) { + if (settings::shouldEnableOnBoot()) { + TT_LOG_I(TAG, "Auto-enabling due to setting"); enable(wifi); scan_internal(wifi); } diff --git a/TactilityHeadless/Source/service/wifi/WifiGlobals.h b/TactilityHeadless/Source/service/wifi/WifiGlobals.h index 64c98fcd..3b604ce3 100644 --- a/TactilityHeadless/Source/service/wifi/WifiGlobals.h +++ b/TactilityHeadless/Source/service/wifi/WifiGlobals.h @@ -1,7 +1,6 @@ #pragma once #define TT_WIFI_AUTO_CONNECT true // Default setting for new Wi-Fi entries -#define TT_WIFI_AUTO_ENABLE false #define TT_WIFI_SCAN_RECORD_LIMIT 16 // default, can be overridden diff --git a/TactilityHeadless/Source/service/wifi/WifiSettings.cpp b/TactilityHeadless/Source/service/wifi/WifiSettings.cpp new file mode 100644 index 00000000..48c0b35c --- /dev/null +++ b/TactilityHeadless/Source/service/wifi/WifiSettings.cpp @@ -0,0 +1,18 @@ +#include "Preferences.h" + +#define WIFI_PREFERENCES_NAMESPACE "wifi" +#define WIFI_PREFERENCES_KEY_ENABLE_ON_BOOT "enable_on_boot" + +namespace tt::service::wifi::settings { + +void setEnableOnBoot(bool enable) { + Preferences(WIFI_PREFERENCES_NAMESPACE).putBool(WIFI_PREFERENCES_KEY_ENABLE_ON_BOOT, enable); +} + +bool shouldEnableOnBoot() { + bool enable = false; + Preferences(WIFI_PREFERENCES_NAMESPACE).optBool(WIFI_PREFERENCES_KEY_ENABLE_ON_BOOT, enable); + return enable; +} + +} // namespace diff --git a/TactilityHeadless/Source/service/wifi/WifiSettings.h b/TactilityHeadless/Source/service/wifi/WifiSettings.h index b9a5b211..0b615007 100644 --- a/TactilityHeadless/Source/service/wifi/WifiSettings.h +++ b/TactilityHeadless/Source/service/wifi/WifiSettings.h @@ -24,4 +24,8 @@ bool save(const WifiApSettings* settings); bool remove(const char* ssid); +void setEnableOnBoot(bool enable); + +bool shouldEnableOnBoot(); + } // namespace