diff --git a/Boards/Simulator/Source/FreeRTOSConfig.h b/Boards/Simulator/Source/FreeRTOSConfig.h index 7d1e36ef..d03bc412 100644 --- a/Boards/Simulator/Source/FreeRTOSConfig.h +++ b/Boards/Simulator/Source/FreeRTOSConfig.h @@ -46,7 +46,7 @@ extern void vAssertCalled(unsigned long line, const char* const file); /* Run time and task stats gathering related definitions. */ #define configGENERATE_RUN_TIME_STATS 0 -#define configUSE_TRACE_FACILITY 0 +#define configUSE_TRACE_FACILITY 1 #define configUSE_STATS_FORMATTING_FUNCTIONS 0 /* Co-routine related definitions. */ diff --git a/Documentation/pics/screenshot-Desktop.png b/Documentation/pics/screenshot-Desktop.png index 2da8a0b9..a1c2fd54 100644 Binary files a/Documentation/pics/screenshot-Desktop.png and b/Documentation/pics/screenshot-Desktop.png differ diff --git a/Documentation/pics/screenshot-Settings.png b/Documentation/pics/screenshot-Settings.png index 855bdbdf..4577b372 100644 Binary files a/Documentation/pics/screenshot-Settings.png and b/Documentation/pics/screenshot-Settings.png differ diff --git a/Documentation/pics/screenshot-WifiManage.png b/Documentation/pics/screenshot-WifiManage.png index fce2a452..e33cf5fb 100644 Binary files a/Documentation/pics/screenshot-WifiManage.png and b/Documentation/pics/screenshot-WifiManage.png differ diff --git a/Tactility/Source/Tactility.cpp b/Tactility/Source/Tactility.cpp index 3ca91f4d..4d2ffc00 100644 --- a/Tactility/Source/Tactility.cpp +++ b/Tactility/Source/Tactility.cpp @@ -49,6 +49,7 @@ namespace app { namespace selectiondialog { extern const Manifest manifest; } namespace systeminfo { extern const Manifest manifest; } namespace textviewer { extern const Manifest manifest; } + namespace wifiapsettings { extern const Manifest manifest; } namespace wificonnect { extern const Manifest manifest; } namespace wifimanage { extern const Manifest manifest; } } @@ -70,6 +71,7 @@ static const std::vector system_apps = { &app::selectiondialog::manifest, &app::systeminfo::manifest, &app::textviewer::manifest, + &app::wifiapsettings::manifest, &app::wificonnect::manifest, &app::wifimanage::manifest, #ifndef ESP_PLATFORM diff --git a/Tactility/Source/app/boot/Boot.cpp b/Tactility/Source/app/boot/Boot.cpp index 126886d2..f666f606 100644 --- a/Tactility/Source/app/boot/Boot.cpp +++ b/Tactility/Source/app/boot/Boot.cpp @@ -20,7 +20,7 @@ namespace tt::app::boot { static int32_t threadCallback(void* context); struct Data { - Data() : thread("", 4096, threadCallback, this) {} + Data() : thread("boot", 4096, threadCallback, this) {} Thread thread; }; diff --git a/Tactility/Source/app/files/FileUtils.cpp b/Tactility/Source/app/files/FileUtils.cpp index 63deb35e..be713438 100644 --- a/Tactility/Source/app/files/FileUtils.cpp +++ b/Tactility/Source/app/files/FileUtils.cpp @@ -14,8 +14,8 @@ int dirent_filter_dot_entries(const struct dirent* entry) { } int dirent_sort_alpha_and_type(const struct dirent** left, const struct dirent** right) { - bool left_is_dir = (*left)->d_type == TT_DT_DIR; - bool right_is_dir = (*right)->d_type == TT_DT_DIR; + bool left_is_dir = (*left)->d_type == TT_DT_DIR || (*left)->d_type == TT_DT_CHR; + bool right_is_dir = (*right)->d_type == TT_DT_DIR || (*right)->d_type == TT_DT_CHR; if (left_is_dir == right_is_dir) { return strcmp((*left)->d_name, (*right)->d_name); } else { diff --git a/Tactility/Source/app/files/Files.cpp b/Tactility/Source/app/files/Files.cpp index c30514bd..b3627cc5 100644 --- a/Tactility/Source/app/files/Files.cpp +++ b/Tactility/Source/app/files/Files.cpp @@ -133,6 +133,7 @@ static void onFilePressed(lv_event_t* event) { switch (dir_entry->d_type) { case TT_DT_DIR: + case TT_DT_CHR: data_set_entries_for_child_path(files_data, dir_entry->d_name); updateViews(files_data); break; @@ -155,7 +156,7 @@ static void createFileWidget(Data* files_data, lv_obj_t* parent, struct dirent* tt_check(parent); auto* list = (lv_obj_t*)parent; const char* symbol; - if (dir_entry->d_type == TT_DT_DIR) { + if (dir_entry->d_type == TT_DT_DIR || dir_entry->d_type == TT_DT_CHR) { symbol = LV_SYMBOL_DIRECTORY; } else if (isSupportedImageFile(dir_entry->d_name)) { symbol = LV_SYMBOL_IMAGE; diff --git a/Tactility/Source/app/power/Power.cpp b/Tactility/Source/app/power/Power.cpp index 55caad9a..9a07d29a 100644 --- a/Tactility/Source/app/power/Power.cpp +++ b/Tactility/Source/app/power/Power.cpp @@ -78,7 +78,7 @@ static void onShow(App& app, lv_obj_t* parent) { lv_obj_set_align(enable_label, LV_ALIGN_LEFT_MID); lv_obj_t* enable_switch = lv_switch_create(switch_container); - lv_obj_add_event_cb(enable_switch, onPowerEnabledChanged, LV_EVENT_ALL, data); + lv_obj_add_event_cb(enable_switch, onPowerEnabledChanged, LV_EVENT_VALUE_CHANGED, data); lv_obj_set_align(enable_switch, LV_ALIGN_RIGHT_MID); data->enable_switch = enable_switch; diff --git a/Tactility/Source/app/systeminfo/SystemInfo.cpp b/Tactility/Source/app/systeminfo/SystemInfo.cpp index 1065ef83..c74725a0 100644 --- a/Tactility/Source/app/systeminfo/SystemInfo.cpp +++ b/Tactility/Source/app/systeminfo/SystemInfo.cpp @@ -5,6 +5,8 @@ namespace tt::app::systeminfo { +#define TAG "system_info" + static size_t getHeapFree() { #ifdef ESP_PLATFORM return heap_caps_get_free_size(MALLOC_CAP_INTERNAL); @@ -65,6 +67,47 @@ static void addMemoryBar(lv_obj_t* parent, const char* label, size_t used, size_ lv_obj_set_style_text_align(bottom_label, LV_TEXT_ALIGN_RIGHT, 0); } +#if configUSE_TRACE_FACILITY + +static const char* getTaskState(const TaskStatus_t& task) { + switch (task.eCurrentState) { + case eRunning: + return "running"; + case eReady: + return "ready"; + case eBlocked: + return "blocked"; + case eSuspended: + return "suspended"; + case eDeleted: + return "deleted"; + case eInvalid: + default: + return "invalid"; + } +} + +static void addRtosTask(lv_obj_t* parent, const TaskStatus_t& task) { + lv_obj_t* label = lv_label_create(parent); + const char* name = (task.pcTaskName == nullptr || task.pcTaskName[0] == 0) ? "(unnamed)" : task.pcTaskName; + lv_label_set_text_fmt(label, "%s (%s)", name, getTaskState(task)); +} + +static void addRtosTasks(lv_obj_t* parent) { + UBaseType_t count = uxTaskGetNumberOfTasks(); + TaskStatus_t* tasks = (TaskStatus_t*)malloc(sizeof(TaskStatus_t) * count); + uint32_t totalRuntime = 0; + UBaseType_t actual = uxTaskGetSystemState(tasks, count, &totalRuntime); + for (int i = 0; i < actual; ++i) { + const TaskStatus_t& task = tasks[i]; + TT_LOG_I(TAG, "Task: %s", task.pcTaskName); + addRtosTask(parent, task); + } + free(tasks); +} + +#endif + static void onShow(App& app, lv_obj_t* parent) { lv_obj_set_flex_flow(parent, LV_FLEX_FLOW_COLUMN); lvgl::toolbar_create(parent, app); @@ -86,6 +129,15 @@ static void onShow(App& app, lv_obj_t* parent) { addMemoryBar(memory_wrapper, "Heap", getHeapTotal() - getHeapFree(), getHeapTotal()); addMemoryBar(memory_wrapper, "SPI", getSpiTotal() - getSpiFree(), getSpiTotal()); +#if configUSE_TRACE_FACILITY + lv_obj_t* tasks_label = lv_label_create(wrapper); + lv_label_set_text(tasks_label, "Tasks"); + lv_obj_t* tasks_wrapper = lv_obj_create(wrapper); + lv_obj_set_flex_flow(tasks_wrapper, LV_FLEX_FLOW_COLUMN); + lv_obj_set_size(tasks_wrapper, LV_PCT(100), LV_SIZE_CONTENT); + addRtosTasks(tasks_wrapper); +#endif + #ifdef ESP_PLATFORM // Build info lv_obj_t* build_info_label = lv_label_create(wrapper); diff --git a/Tactility/Source/app/textviewer/TextViewer.cpp b/Tactility/Source/app/textviewer/TextViewer.cpp index faa8132c..b162c54d 100644 --- a/Tactility/Source/app/textviewer/TextViewer.cpp +++ b/Tactility/Source/app/textviewer/TextViewer.cpp @@ -25,9 +25,10 @@ static void onShow(App& app, lv_obj_t* parent) { const Bundle& bundle = app.getParameters(); std::string file_argument; if (bundle.optString(TEXT_VIEWER_FILE_ARGUMENT, file_argument)) { - std::string prefixed_path = "A:" + file_argument; - TT_LOG_I(TAG, "Opening %s", prefixed_path.c_str()); - lvgl::label_set_text_file(label, prefixed_path.c_str()); + TT_LOG_I(TAG, "Opening %s", file_argument.c_str()); + lvgl::label_set_text_file(label, file_argument.c_str()); + } else { + lv_label_set_text_fmt(label, "Failed to load %s", file_argument.c_str()); } } diff --git a/Tactility/Source/app/wifiapsettings/WifiApSettings.cpp b/Tactility/Source/app/wifiapsettings/WifiApSettings.cpp new file mode 100644 index 00000000..d93d35d5 --- /dev/null +++ b/Tactility/Source/app/wifiapsettings/WifiApSettings.cpp @@ -0,0 +1,97 @@ +#include "WifiApSettings.h" +#include "TactilityCore.h" +#include "app/App.h" +#include "lvgl.h" +#include "lvgl/Style.h" +#include "lvgl/Toolbar.h" +#include "service/loader/Loader.h" +#include "service/wifi/WifiSettings.h" + +namespace tt::app::wifiapsettings { + +#define TAG "wifi_ap_settings" + +extern const Manifest manifest; + +void start(const std::string& ssid) { + Bundle bundle; + bundle.putString("ssid", ssid); + service::loader::startApp(manifest.id, false, bundle); +} + +static void onToggleAutoConnect(lv_event_t* event) { + lv_event_code_t code = lv_event_get_code(event); + const Bundle& parameters = *(const Bundle*)lv_event_get_user_data(event); + if (code == LV_EVENT_VALUE_CHANGED) { + auto* enable_switch = static_cast(lv_event_get_target(event)); + bool is_on = lv_obj_has_state(enable_switch, LV_STATE_CHECKED); + std::string ssid = parameters.getString("ssid"); + + service::wifi::settings::WifiApSettings settings {}; + if (service::wifi::settings::load(ssid.c_str(), &settings)) { + settings.auto_connect = is_on; + if (!service::wifi::settings::save(&settings)) { + TT_LOG_E(TAG, "Failed to save settings"); + } + } else { + TT_LOG_E(TAG, "Failed to load settings"); + } + } +} + +static void onShow(App& app, lv_obj_t* parent) { + const Bundle& bundle = app.getParameters(); + std::string ssid = bundle.getString("ssid"); + + lv_obj_set_flex_flow(parent, LV_FLEX_FLOW_COLUMN); + lvgl::toolbar_create(parent, ssid); + + // 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); + 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_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_add_event_cb(auto_connect_switch, onToggleAutoConnect, LV_EVENT_VALUE_CHANGED, (void*)&bundle); + lv_obj_align(auto_connect_switch, LV_ALIGN_TOP_RIGHT, 0, 0); + + service::wifi::settings::WifiApSettings settings {}; + if (service::wifi::settings::load(ssid.c_str(), &settings)) { + if (settings.auto_connect) { + lv_obj_add_state(auto_connect_switch, LV_STATE_CHECKED); + } else { + lv_obj_remove_state(auto_connect_switch, LV_STATE_CHECKED); + } + + } else { + TT_LOG_E(TAG, "Failed to load settings"); + } +} + +extern const Manifest manifest = { + .id = "WifiApSettings", + .name = "Wi-Fi AP Settings", + .icon = LV_SYMBOL_WIFI, + .type = TypeHidden, + .onShow = onShow +}; + +} // namespace + diff --git a/Tactility/Source/app/wifiapsettings/WifiApSettings.h b/Tactility/Source/app/wifiapsettings/WifiApSettings.h new file mode 100644 index 00000000..da6fed9a --- /dev/null +++ b/Tactility/Source/app/wifiapsettings/WifiApSettings.h @@ -0,0 +1,11 @@ +#pragma once + +#include "Bundle.h" +#include "Mutex.h" +#include "service/wifi/Wifi.h" + +namespace tt::app::wifiapsettings { + +void start(const std::string& ssid); + +} // namespace diff --git a/Tactility/Source/app/wifimanage/Bindings.h b/Tactility/Source/app/wifimanage/Bindings.h index 091de0a2..73b91d79 100644 --- a/Tactility/Source/app/wifimanage/Bindings.h +++ b/Tactility/Source/app/wifimanage/Bindings.h @@ -5,11 +5,13 @@ namespace tt::app::wifimanage { typedef void (*OnWifiToggled)(bool enable); typedef void (*OnConnectSsid)(const char* ssid); typedef void (*OnDisconnect)(); +typedef void (*OnShowApSettings)(const char* ssid); struct Bindings{ OnWifiToggled onWifiToggled; OnConnectSsid onConnectSsid; OnDisconnect onDisconnect; + OnShowApSettings onShowApSettings; }; } // namespace diff --git a/Tactility/Source/app/wifimanage/View.cpp b/Tactility/Source/app/wifimanage/View.cpp index 92852510..99c8dcf8 100644 --- a/Tactility/Source/app/wifimanage/View.cpp +++ b/Tactility/Source/app/wifimanage/View.cpp @@ -13,7 +13,6 @@ namespace tt::app::wifimanage { #define TAG "wifi_main_view" -#define SPINNER_HEIGHT 40 static void on_enable_switch_changed(lv_event_t* event) { lv_event_code_t code = lv_event_get_code(event); @@ -42,41 +41,115 @@ static void on_disconnect_pressed(lv_event_t* event) { // region Secondary updates static void connect(lv_event_t* event) { - lv_obj_t* button = lv_event_get_current_target_obj(event); + lv_obj_t* wrapper = lv_event_get_current_target_obj(event); // Assumes that the second child of the button is a label ... risky - lv_obj_t* label = lv_obj_get_child(button, 1); + lv_obj_t* label = lv_obj_get_child(wrapper, 0); // We get the SSID from the button label because it's safer than alloc'ing // our own and passing it as the event data const char* ssid = lv_label_get_text(label); - TT_LOG_I(TAG, "Clicked AP: %s", ssid); - auto* bindings = static_cast(lv_event_get_user_data(event)); - bindings->onConnectSsid(ssid); + if (ssid != nullptr) { + TT_LOG_I(TAG, "Clicked AP: %s", ssid); + auto* bindings = (Bindings*)lv_event_get_user_data(event); + bindings->onConnectSsid(ssid); + } } -void View::createNetworkButton(Bindings* bindings, const service::wifi::WifiApRecord& record) { - const char* icon = service::statusbar::getWifiStatusIconForRssi(record.rssi, record.auth_mode != WIFI_AUTH_OPEN); - lv_obj_t* ap_button = lv_list_add_btn( - networks_list, - icon, - record.ssid.c_str() - ); - lv_obj_add_event_cb(ap_button, &connect, LV_EVENT_CLICKED, bindings); +static void showDetails(lv_event_t* event) { + lv_obj_t* wrapper = lv_event_get_current_target_obj(event); + // Hack: Get the hidden label with the ssid + lv_obj_t* ssid_label = lv_obj_get_child(wrapper, 1); + const char* ssid = lv_label_get_text(ssid_label); + auto* bindings = (Bindings*)lv_event_get_user_data(event); + bindings->onShowApSettings(ssid); +} + +void View::createSsidListItem(Bindings* bindings, const service::wifi::WifiApRecord& record, bool isConnecting) { + lv_obj_t* wrapper = lv_obj_create(networks_list); + lv_obj_add_event_cb(wrapper, &connect, LV_EVENT_CLICKED, bindings); + lv_obj_set_user_data(wrapper, bindings); + lv_obj_set_size(wrapper, LV_PCT(100), LV_SIZE_CONTENT); + lvgl::obj_set_style_no_padding(wrapper); + lv_obj_set_style_margin_all(wrapper, 0, 0); + lv_obj_set_style_border_width(wrapper, 0, 0); + + lv_obj_t* label = lv_label_create(wrapper); + lv_obj_align(label, LV_ALIGN_LEFT_MID, 0, 0); + lv_label_set_text(label, record.ssid.c_str()); + lv_label_set_long_mode(label, LV_LABEL_LONG_SCROLL_CIRCULAR); + lv_obj_set_width(label, LV_PCT(70)); + + lv_obj_t* info_wrapper = lv_obj_create(wrapper); + lv_obj_set_style_pad_all(info_wrapper, 0, 0); + lv_obj_set_style_margin_all(info_wrapper, 0, 0); + lv_obj_set_size(info_wrapper, 36, 36); + lv_obj_set_style_border_color(info_wrapper, lv_theme_get_color_primary(info_wrapper), 0); + lv_obj_add_event_cb(info_wrapper, &showDetails, LV_EVENT_CLICKED, bindings); + lv_obj_align(info_wrapper, LV_ALIGN_RIGHT_MID, 0, 0); + + lv_obj_t* info_label = lv_label_create(info_wrapper); + lv_label_set_text(info_label, "i"); + // Hack: Create a hidden label to store data and pass it to the callback + lv_obj_t* ssid_label = lv_label_create(info_wrapper); + lv_label_set_text(ssid_label, record.ssid.c_str()); + lv_obj_add_flag(ssid_label, LV_OBJ_FLAG_HIDDEN); + lv_obj_set_style_text_color(info_label, lv_theme_get_color_primary(info_wrapper), 0); + lv_obj_align(info_label, LV_ALIGN_CENTER, 0, 0); + + if (isConnecting) { + lv_obj_t* connecting_spinner = lv_spinner_create(wrapper); + lv_obj_set_size(connecting_spinner, 40, 40); + lv_spinner_set_anim_params(connecting_spinner, 1000, 60); + lv_obj_set_style_pad_all(connecting_spinner, 4, 0); + lv_obj_align_to(connecting_spinner, info_wrapper, LV_ALIGN_OUT_LEFT_MID, -8, 0); + } else { + const char* icon = service::statusbar::getWifiStatusIconForRssi(record.rssi, record.auth_mode != WIFI_AUTH_OPEN); + lv_obj_t* image = lv_image_create(wrapper); + lv_image_set_src(image, icon); + lv_obj_align(image, LV_ALIGN_RIGHT_MID, -50, 0); + } } void View::updateNetworkList(State* state, Bindings* bindings) { lv_obj_clean(networks_list); + switch (state->getRadioState()) { case service::wifi::WIFI_RADIO_ON_PENDING: case service::wifi::WIFI_RADIO_ON: case service::wifi::WIFI_RADIO_CONNECTION_PENDING: case service::wifi::WIFI_RADIO_CONNECTION_ACTIVE: { - lv_obj_clear_flag(networks_label, LV_OBJ_FLAG_HIDDEN); + + std::string connection_target = service::wifi::getConnectionTarget(); auto& ap_records = state->lockApRecords(); + + bool is_connected = !connection_target.empty() && + state->getRadioState() == service::wifi::WIFI_RADIO_CONNECTION_ACTIVE; + bool added_connected = false; + if (is_connected) { + if (!ap_records.empty()) { + for (auto &record : ap_records) { + if (record.ssid == connection_target) { + lv_list_add_text(networks_list, "Connected"); + createSsidListItem(bindings, record, false); + added_connected = true; + break; + } + } + } + } + + lv_list_add_text(networks_list, "Other networks"); std::set used_ssids; if (!ap_records.empty()) { for (auto& record : ap_records) { if (used_ssids.find(record.ssid) == used_ssids.end()) { - createNetworkButton(bindings, record); + bool connection_target_match = (record.ssid == connection_target); + bool is_connecting = connection_target_match + && state->getRadioState() == service::wifi::WIFI_RADIO_CONNECTION_PENDING && + !connection_target.empty(); + bool skip = connection_target_match && added_connected; + if (!skip) { + createSsidListItem(bindings, record, is_connecting); + } used_ssids.insert(record.ssid); } } @@ -94,7 +167,6 @@ void View::updateNetworkList(State* state, Bindings* bindings) { case service::wifi::WIFI_RADIO_OFF_PENDING: case service::wifi::WIFI_RADIO_OFF: { lv_obj_add_flag(networks_list, LV_OBJ_FLAG_HIDDEN); - lv_obj_add_flag(networks_label, LV_OBJ_FLAG_HIDDEN); break; } } @@ -138,19 +210,6 @@ void View::updateEnableOnBootToggle() { } } -void View::updateConnectedAp(State* state, TT_UNUSED Bindings* bindings) { - switch (state->getRadioState()) { - case service::wifi::WIFI_RADIO_CONNECTION_PENDING: - case service::wifi::WIFI_RADIO_CONNECTION_ACTIVE: - lv_obj_clear_flag(connected_ap_container, LV_OBJ_FLAG_HIDDEN); - lv_label_set_text(connected_ap_label, state->getConnectSsid().c_str()); - break; - default: - lv_obj_add_flag(connected_ap_container, LV_OBJ_FLAG_HIDDEN); - break; - } -} - // endregion Secondary updates // region Main @@ -158,72 +217,50 @@ void View::updateConnectedAp(State* state, TT_UNUSED Bindings* bindings) { void View::init(const App& app, Bindings* bindings, lv_obj_t* parent) { root = parent; + // Toolbar lv_obj_set_flex_flow(parent, LV_FLEX_FLOW_COLUMN); lv_obj_t* toolbar = lvgl::toolbar_create(parent, app); + scanning_spinner = lvgl::toolbar_add_spinner_action(toolbar); + 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_add_event_cb(enable_switch, on_enable_switch_changed, LV_EVENT_VALUE_CHANGED, bindings); - 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); + // Wrappers - // Top row: enable/disable - lv_obj_t* switch_container = lv_obj_create(wrapper); - lv_obj_set_width(switch_container, LV_PCT(100)); - lv_obj_set_height(switch_container, LV_SIZE_CONTENT); - lvgl::obj_set_style_no_padding(switch_container); - lvgl::obj_set_style_bg_invisible(switch_container); + 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); - lv_obj_t* enable_label = lv_label_create(switch_container); + // 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); + lvgl::obj_set_style_bg_invisible(wrapper); + lv_obj_set_style_border_width(wrapper, 0, 0); + + // Enable on boot + + lv_obj_t* enable_label = lv_label_create(wrapper); lv_label_set_text(enable_label, "Enable on boot"); - lv_obj_set_align(enable_label, LV_ALIGN_LEFT_MID); + lv_obj_align(enable_label, LV_ALIGN_TOP_LEFT, 0, 6); - 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); - lv_obj_set_style_min_height(connected_ap_container, SPINNER_HEIGHT, 0); - lvgl::obj_set_style_no_padding(connected_ap_container); - lv_obj_set_style_border_width(connected_ap_container, 0, 0); - - connected_ap_label = lv_label_create(connected_ap_container); - lv_obj_align(connected_ap_label, LV_ALIGN_LEFT_MID, 0, 0); - - lv_obj_t* disconnect_button = lv_btn_create(connected_ap_container); - lv_obj_add_event_cb(disconnect_button, &on_disconnect_pressed, LV_EVENT_CLICKED, bindings); - lv_obj_t* disconnect_label = lv_label_create(disconnect_button); - lv_label_set_text(disconnect_label, "Disconnect"); - lv_obj_align(disconnect_button, LV_ALIGN_RIGHT_MID, 0, 0); + enable_on_boot_switch = lv_switch_create(wrapper); + lv_obj_add_event_cb(enable_on_boot_switch, on_enable_on_boot_switch_changed, LV_EVENT_VALUE_CHANGED, bindings); + lv_obj_align(enable_on_boot_switch, LV_ALIGN_TOP_RIGHT, 0, 0); // Networks - lv_obj_t* networks_header = lv_obj_create(wrapper); - lv_obj_set_size(networks_header, LV_PCT(100), LV_SIZE_CONTENT); - lv_obj_set_style_min_height(networks_header, SPINNER_HEIGHT, 0); - lvgl::obj_set_style_no_padding(networks_header); - lv_obj_set_style_border_width(networks_header, 0, 0); - - networks_label = lv_label_create(networks_header); - lv_label_set_text(networks_label, "Networks"); - lv_obj_align(networks_label, LV_ALIGN_LEFT_MID, 0, 0); - - scanning_spinner = lv_spinner_create(networks_header); - lv_spinner_set_anim_params(scanning_spinner, 1000, 60); - lv_obj_set_size(scanning_spinner, SPINNER_HEIGHT, SPINNER_HEIGHT); - lv_obj_set_style_pad_top(scanning_spinner, 4, 0); - lv_obj_set_style_pad_bottom(scanning_spinner, 4, 0); - lv_obj_align_to(scanning_spinner, networks_label, LV_ALIGN_OUT_RIGHT_MID, 8, 0); - networks_list = lv_obj_create(wrapper); lv_obj_set_flex_flow(networks_list, LV_FLEX_FLOW_COLUMN); lv_obj_set_width(networks_list, LV_PCT(100)); lv_obj_set_height(networks_list, LV_SIZE_CONTENT); - lv_obj_set_style_pad_top(networks_list, 8, 0); - lv_obj_set_style_pad_bottom(networks_list, 8, 0); + lv_obj_set_style_pad_top(networks_list, 0, 0); + lv_obj_set_style_pad_bottom(networks_list, 0, 0); + lv_obj_align(networks_list, LV_ALIGN_TOP_LEFT, 0, 44); } void View::update(Bindings* bindings, State* state) { @@ -231,7 +268,6 @@ void View::update(Bindings* bindings, State* state) { updateEnableOnBootToggle(); updateScanning(state); updateNetworkList(state, bindings); - updateConnectedAp(state, bindings); } } // namespace diff --git a/Tactility/Source/app/wifimanage/View.h b/Tactility/Source/app/wifimanage/View.h index 65376c78..33c9aa19 100644 --- a/Tactility/Source/app/wifimanage/View.h +++ b/Tactility/Source/app/wifimanage/View.h @@ -13,10 +13,7 @@ private: 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; - lv_obj_t* connected_ap_container = nullptr; - lv_obj_t* connected_ap_label = nullptr; public: View() {} void init(const App& app, Bindings* bindings, lv_obj_t* parent); @@ -24,12 +21,11 @@ public: 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); + void createSsidListItem(Bindings* bindings, const service::wifi::WifiApRecord& record, bool isConnecting); }; diff --git a/Tactility/Source/app/wifimanage/WifiManage.cpp b/Tactility/Source/app/wifimanage/WifiManage.cpp index 64b18369..65f8d389 100644 --- a/Tactility/Source/app/wifimanage/WifiManage.cpp +++ b/Tactility/Source/app/wifimanage/WifiManage.cpp @@ -1,13 +1,14 @@ #include "WifiManage.h" +#include "View.h" +#include "State.h" #include "app/App.h" #include "app/wificonnect/Parameters.h" +#include "app/wifiapsettings/WifiApSettings.h" #include "TactilityCore.h" #include "service/loader/Loader.h" #include "service/wifi/WifiSettings.h" #include "lvgl/LvglSync.h" -#include "View.h" -#include "State.h" namespace tt::app::wifimanage { @@ -27,6 +28,10 @@ static void onConnect(const char* ssid) { } } +static void onShowApSettings(const char* ssid) { + wifiapsettings::start(ssid); +} + static void onDisconnect() { service::wifi::disconnect(); } @@ -37,9 +42,10 @@ static void onWifiToggled(bool enabled) { WifiManage::WifiManage() { bindings = (Bindings) { - .onWifiToggled = &onWifiToggled, - .onConnectSsid = &onConnect, - .onDisconnect = &onDisconnect + .onWifiToggled = onWifiToggled, + .onConnectSsid = onConnect, + .onDisconnect = onDisconnect, + .onShowApSettings = onShowApSettings }; } diff --git a/Tactility/Source/lvgl/Toolbar.cpp b/Tactility/Source/lvgl/Toolbar.cpp index b6bd2e50..bc7208a1 100644 --- a/Tactility/Source/lvgl/Toolbar.cpp +++ b/Tactility/Source/lvgl/Toolbar.cpp @@ -6,6 +6,8 @@ #include "lvgl/Spacer.h" #include "lvgl/Style.h" +#define SPINNER_HEIGHT TOOLBAR_HEIGHT + namespace tt::lvgl { typedef struct { @@ -80,6 +82,7 @@ lv_obj_t* toolbar_create(lv_obj_t* parent, const std::string& title) { toolbar->action_container = lv_obj_create(obj); lv_obj_set_width(toolbar->action_container, LV_SIZE_CONTENT); + lv_obj_set_flex_flow(toolbar->action_container, LV_FLEX_FLOW_ROW); lv_obj_set_style_pad_all(toolbar->action_container, 0, 0); lv_obj_set_style_border_width(toolbar->action_container, 0, 0); @@ -123,7 +126,16 @@ uint8_t toolbar_add_action(lv_obj_t* obj, const char* icon, lv_event_cb_t callba 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 + lv_obj_set_style_margin_top(widget, 4, 0); // Because aligning doesn't work + return widget; +} + +lv_obj_t* toolbar_add_spinner_action(lv_obj_t* obj) { + auto* toolbar = (Toolbar*)obj; + lv_obj_t* widget = lv_spinner_create(toolbar->action_container); + lv_obj_set_size(widget, SPINNER_HEIGHT, SPINNER_HEIGHT); + lv_spinner_set_anim_params(widget, 1000, 60); + lv_obj_set_style_pad_all(widget, 4, 0); return widget; } diff --git a/Tactility/Source/lvgl/Toolbar.h b/Tactility/Source/lvgl/Toolbar.h index 17bad633..86dcb710 100644 --- a/Tactility/Source/lvgl/Toolbar.h +++ b/Tactility/Source/lvgl/Toolbar.h @@ -24,4 +24,5 @@ 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, lv_event_cb_t callback, void* user_data); lv_obj_t* toolbar_add_switch_action(lv_obj_t* obj); +lv_obj_t* toolbar_add_spinner_action(lv_obj_t* obj); } // namespace diff --git a/Tactility/Source/service/statusbar/Statusbar.cpp b/Tactility/Source/service/statusbar/Statusbar.cpp index a6246819..cff1477e 100644 --- a/Tactility/Source/service/statusbar/Statusbar.cpp +++ b/Tactility/Source/service/statusbar/Statusbar.cpp @@ -191,6 +191,7 @@ static void on_start(Service& service) { data->thread->setCallback(service_main, data); data->thread->setPriority(Thread::PriorityLow); data->thread->setStackSize(3000); + data->thread->setName("statusbar"); data->thread->start(); } diff --git a/TactilityHeadless/Source/service/wifi/Wifi.h b/TactilityHeadless/Source/service/wifi/Wifi.h index 992341d8..5ca51b80 100644 --- a/TactilityHeadless/Source/service/wifi/Wifi.h +++ b/TactilityHeadless/Source/service/wifi/Wifi.h @@ -89,6 +89,11 @@ void scan(); */ bool isScanning(); +/** + * @return true the ssid name or empty string + */ +std::string getConnectionTarget(); + /** * @brief Returns the access points from the last scan (if any). It only contains public APs. */ diff --git a/TactilityHeadless/Source/service/wifi/WifiEsp.cpp b/TactilityHeadless/Source/service/wifi/WifiEsp.cpp index e6f438af..4dc5da23 100644 --- a/TactilityHeadless/Source/service/wifi/WifiEsp.cpp +++ b/TactilityHeadless/Source/service/wifi/WifiEsp.cpp @@ -116,6 +116,25 @@ WifiRadioState getRadioState() { return state; } +std::string getConnectionTarget() { + lock(wifi_singleton); + std::string result; + switch (wifi_singleton->radio_state) { + case WIFI_RADIO_CONNECTION_PENDING: + case WIFI_RADIO_CONNECTION_ACTIVE: + result = wifi_singleton->connection_target.ssid; + break; + case WIFI_RADIO_ON: + case WIFI_RADIO_ON_PENDING: + case WIFI_RADIO_OFF_PENDING: + case WIFI_RADIO_OFF: + result = ""; + break; + } + unlock(wifi_singleton); + return result; +} + void scan() { TT_LOG_I(TAG, "scan()"); tt_assert(wifi_singleton); @@ -723,7 +742,6 @@ _Noreturn int32_t wifi_main(TT_UNUSED void* parameter) { WifiMessage message; while (true) { - TT_LOG_I(TAG, "Message queue %ld", queue.getCount()); if (queue.get(&message, 10000 / portTICK_PERIOD_MS) == TtStatusOk) { TT_LOG_I(TAG, "Processing message of type %d", message.type); switch (message.type) { diff --git a/TactilityHeadless/Source/service/wifi/WifiMock.cpp b/TactilityHeadless/Source/service/wifi/WifiMock.cpp index 9a9394e4..5d6f80a6 100644 --- a/TactilityHeadless/Source/service/wifi/WifiMock.cpp +++ b/TactilityHeadless/Source/service/wifi/WifiMock.cpp @@ -76,6 +76,10 @@ WifiRadioState getRadioState() { return wifi->radio_state; } +std::string getConnectionTarget() { + return "Home Wifi"; +} + void scan() { tt_assert(wifi); wifi->scan_active = false; // TODO: enable and then later disable automatically @@ -110,17 +114,17 @@ std::vector getScanResults() { .auth_mode = WIFI_AUTH_WPA2_PSK }); records.push_back((WifiApRecord) { - .ssid = "Living Room", + .ssid = "No place like 127.0.0.1", .rssi = -67, .auth_mode = WIFI_AUTH_WPA2_PSK }); records.push_back((WifiApRecord) { - .ssid = "No place like 127.0.0.1", + .ssid = "Pretty fly for a Wi-Fi", .rssi = -70, .auth_mode = WIFI_AUTH_WPA2_PSK }); records.push_back((WifiApRecord) { - .ssid = "Public Wi-Fi", + .ssid = "An AP with a really, really long name", .rssi = -80, .auth_mode = WIFI_AUTH_WPA2_PSK }); diff --git a/TactilityHeadless/Source/service/wifi/WifiSettings.h b/TactilityHeadless/Source/service/wifi/WifiSettings.h index 0b615007..285226b6 100644 --- a/TactilityHeadless/Source/service/wifi/WifiSettings.h +++ b/TactilityHeadless/Source/service/wifi/WifiSettings.h @@ -10,11 +10,11 @@ namespace tt::service::wifi::settings { * The SSID and secret are increased by 1 byte to facilitate string null termination. * This makes it easier to use the char array as a string in various places. */ -typedef struct { +struct WifiApSettings { char ssid[TT_WIFI_SSID_LIMIT + 1]; char password[TT_WIFI_CREDENTIALS_PASSWORD_LIMIT + 1]; bool auto_connect; -} WifiApSettings; +}; bool contains(const char* ssid); diff --git a/sdkconfig.board.lilygo_tdeck b/sdkconfig.board.lilygo_tdeck index f8d7a90a..883211b0 100644 --- a/sdkconfig.board.lilygo_tdeck +++ b/sdkconfig.board.lilygo_tdeck @@ -14,6 +14,7 @@ CONFIG_FREERTOS_TASK_NOTIFICATION_ARRAY_ENTRIES=2 CONFIG_FREERTOS_SMP=n CONFIG_FREERTOS_UNICORE=n CONFIG_FREERTOS_TIMER_TASK_STACK_DEPTH=4096 +CONFIG_FREERTOS_USE_TRACE_FACILITY=y CONFIG_PARTITION_TABLE_CUSTOM=y CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions.csv" CONFIG_PARTITION_TABLE_FILENAME="partitions.csv" diff --git a/sdkconfig.board.m5stack_core2 b/sdkconfig.board.m5stack_core2 index c08aed7b..9bf63768 100644 --- a/sdkconfig.board.m5stack_core2 +++ b/sdkconfig.board.m5stack_core2 @@ -14,6 +14,7 @@ CONFIG_FREERTOS_TASK_NOTIFICATION_ARRAY_ENTRIES=2 CONFIG_FREERTOS_SMP=n CONFIG_FREERTOS_UNICORE=n CONFIG_FREERTOS_TIMER_TASK_STACK_DEPTH=4096 +CONFIG_FREERTOS_USE_TRACE_FACILITY=y CONFIG_PARTITION_TABLE_CUSTOM=y CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions.csv" CONFIG_PARTITION_TABLE_FILENAME="partitions.csv" diff --git a/sdkconfig.board.m5stack_cores3 b/sdkconfig.board.m5stack_cores3 index 2164388b..e9e28f25 100644 --- a/sdkconfig.board.m5stack_cores3 +++ b/sdkconfig.board.m5stack_cores3 @@ -14,6 +14,7 @@ CONFIG_FREERTOS_TASK_NOTIFICATION_ARRAY_ENTRIES=2 CONFIG_FREERTOS_SMP=n CONFIG_FREERTOS_UNICORE=n CONFIG_FREERTOS_TIMER_TASK_STACK_DEPTH=4096 +CONFIG_FREERTOS_USE_TRACE_FACILITY=y CONFIG_PARTITION_TABLE_CUSTOM=y CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions.csv" CONFIG_PARTITION_TABLE_FILENAME="partitions.csv" diff --git a/sdkconfig.board.yellow_board b/sdkconfig.board.yellow_board index 8f8148b8..cc610426 100644 --- a/sdkconfig.board.yellow_board +++ b/sdkconfig.board.yellow_board @@ -14,6 +14,7 @@ CONFIG_FREERTOS_TASK_NOTIFICATION_ARRAY_ENTRIES=2 CONFIG_FREERTOS_SMP=n CONFIG_FREERTOS_UNICORE=n CONFIG_FREERTOS_TIMER_TASK_STACK_DEPTH=4096 +CONFIG_FREERTOS_USE_TRACE_FACILITY=y CONFIG_PARTITION_TABLE_CUSTOM=y CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions.csv" CONFIG_PARTITION_TABLE_FILENAME="partitions.csv" diff --git a/sdkconfig.defaults b/sdkconfig.defaults index ce02a0d7..131bb3ad 100644 --- a/sdkconfig.defaults +++ b/sdkconfig.defaults @@ -13,6 +13,8 @@ CONFIG_FREERTOS_HZ=1000 CONFIG_FREERTOS_TASK_NOTIFICATION_ARRAY_ENTRIES=2 CONFIG_FREERTOS_SMP=n CONFIG_FREERTOS_UNICORE=n +CONFIG_FREERTOS_TIMER_TASK_STACK_DEPTH=4096 +CONFIG_FREERTOS_USE_TRACE_FACILITY=y CONFIG_PARTITION_TABLE_CUSTOM=y CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions.csv" CONFIG_PARTITION_TABLE_FILENAME="partitions.csv"