App improvements (#105)

This commit is contained in:
Ken Van Hoeylandt 2024-12-04 23:06:03 +01:00 committed by GitHub
parent fb45790927
commit e86a11b7e2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
29 changed files with 358 additions and 107 deletions

View File

@ -46,7 +46,7 @@ extern void vAssertCalled(unsigned long line, const char* const file);
/* Run time and task stats gathering related definitions. */ /* Run time and task stats gathering related definitions. */
#define configGENERATE_RUN_TIME_STATS 0 #define configGENERATE_RUN_TIME_STATS 0
#define configUSE_TRACE_FACILITY 0 #define configUSE_TRACE_FACILITY 1
#define configUSE_STATS_FORMATTING_FUNCTIONS 0 #define configUSE_STATS_FORMATTING_FUNCTIONS 0
/* Co-routine related definitions. */ /* Co-routine related definitions. */

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.9 KiB

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.1 KiB

After

Width:  |  Height:  |  Size: 4.9 KiB

View File

@ -49,6 +49,7 @@ namespace app {
namespace selectiondialog { extern const Manifest manifest; } namespace selectiondialog { extern const Manifest manifest; }
namespace systeminfo { extern const Manifest manifest; } namespace systeminfo { extern const Manifest manifest; }
namespace textviewer { extern const Manifest manifest; } namespace textviewer { extern const Manifest manifest; }
namespace wifiapsettings { extern const Manifest manifest; }
namespace wificonnect { extern const Manifest manifest; } namespace wificonnect { extern const Manifest manifest; }
namespace wifimanage { extern const Manifest manifest; } namespace wifimanage { extern const Manifest manifest; }
} }
@ -70,6 +71,7 @@ static const std::vector<const app::Manifest*> system_apps = {
&app::selectiondialog::manifest, &app::selectiondialog::manifest,
&app::systeminfo::manifest, &app::systeminfo::manifest,
&app::textviewer::manifest, &app::textviewer::manifest,
&app::wifiapsettings::manifest,
&app::wificonnect::manifest, &app::wificonnect::manifest,
&app::wifimanage::manifest, &app::wifimanage::manifest,
#ifndef ESP_PLATFORM #ifndef ESP_PLATFORM

View File

@ -20,7 +20,7 @@ namespace tt::app::boot {
static int32_t threadCallback(void* context); static int32_t threadCallback(void* context);
struct Data { struct Data {
Data() : thread("", 4096, threadCallback, this) {} Data() : thread("boot", 4096, threadCallback, this) {}
Thread thread; Thread thread;
}; };

View File

@ -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) { 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 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; bool right_is_dir = (*right)->d_type == TT_DT_DIR || (*right)->d_type == TT_DT_CHR;
if (left_is_dir == right_is_dir) { if (left_is_dir == right_is_dir) {
return strcmp((*left)->d_name, (*right)->d_name); return strcmp((*left)->d_name, (*right)->d_name);
} else { } else {

View File

@ -133,6 +133,7 @@ static void onFilePressed(lv_event_t* event) {
switch (dir_entry->d_type) { switch (dir_entry->d_type) {
case TT_DT_DIR: case TT_DT_DIR:
case TT_DT_CHR:
data_set_entries_for_child_path(files_data, dir_entry->d_name); data_set_entries_for_child_path(files_data, dir_entry->d_name);
updateViews(files_data); updateViews(files_data);
break; break;
@ -155,7 +156,7 @@ static void createFileWidget(Data* files_data, lv_obj_t* parent, struct dirent*
tt_check(parent); tt_check(parent);
auto* list = (lv_obj_t*)parent; auto* list = (lv_obj_t*)parent;
const char* symbol; 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; symbol = LV_SYMBOL_DIRECTORY;
} else if (isSupportedImageFile(dir_entry->d_name)) { } else if (isSupportedImageFile(dir_entry->d_name)) {
symbol = LV_SYMBOL_IMAGE; symbol = LV_SYMBOL_IMAGE;

View File

@ -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_set_align(enable_label, LV_ALIGN_LEFT_MID);
lv_obj_t* enable_switch = lv_switch_create(switch_container); 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); lv_obj_set_align(enable_switch, LV_ALIGN_RIGHT_MID);
data->enable_switch = enable_switch; data->enable_switch = enable_switch;

View File

@ -5,6 +5,8 @@
namespace tt::app::systeminfo { namespace tt::app::systeminfo {
#define TAG "system_info"
static size_t getHeapFree() { static size_t getHeapFree() {
#ifdef ESP_PLATFORM #ifdef ESP_PLATFORM
return heap_caps_get_free_size(MALLOC_CAP_INTERNAL); 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); 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) { static void onShow(App& app, lv_obj_t* parent) {
lv_obj_set_flex_flow(parent, LV_FLEX_FLOW_COLUMN); lv_obj_set_flex_flow(parent, LV_FLEX_FLOW_COLUMN);
lvgl::toolbar_create(parent, app); 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, "Heap", getHeapTotal() - getHeapFree(), getHeapTotal());
addMemoryBar(memory_wrapper, "SPI", getSpiTotal() - getSpiFree(), getSpiTotal()); 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 #ifdef ESP_PLATFORM
// Build info // Build info
lv_obj_t* build_info_label = lv_label_create(wrapper); lv_obj_t* build_info_label = lv_label_create(wrapper);

View File

@ -25,9 +25,10 @@ static void onShow(App& app, lv_obj_t* parent) {
const Bundle& bundle = app.getParameters(); const Bundle& bundle = app.getParameters();
std::string file_argument; std::string file_argument;
if (bundle.optString(TEXT_VIEWER_FILE_ARGUMENT, file_argument)) { if (bundle.optString(TEXT_VIEWER_FILE_ARGUMENT, file_argument)) {
std::string prefixed_path = "A:" + file_argument; TT_LOG_I(TAG, "Opening %s", file_argument.c_str());
TT_LOG_I(TAG, "Opening %s", prefixed_path.c_str()); lvgl::label_set_text_file(label, file_argument.c_str());
lvgl::label_set_text_file(label, prefixed_path.c_str()); } else {
lv_label_set_text_fmt(label, "Failed to load %s", file_argument.c_str());
} }
} }

View File

@ -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_obj_t*>(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

View File

@ -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

View File

@ -5,11 +5,13 @@ namespace tt::app::wifimanage {
typedef void (*OnWifiToggled)(bool enable); typedef void (*OnWifiToggled)(bool enable);
typedef void (*OnConnectSsid)(const char* ssid); typedef void (*OnConnectSsid)(const char* ssid);
typedef void (*OnDisconnect)(); typedef void (*OnDisconnect)();
typedef void (*OnShowApSettings)(const char* ssid);
struct Bindings{ struct Bindings{
OnWifiToggled onWifiToggled; OnWifiToggled onWifiToggled;
OnConnectSsid onConnectSsid; OnConnectSsid onConnectSsid;
OnDisconnect onDisconnect; OnDisconnect onDisconnect;
OnShowApSettings onShowApSettings;
}; };
} // namespace } // namespace

View File

@ -13,7 +13,6 @@
namespace tt::app::wifimanage { namespace tt::app::wifimanage {
#define TAG "wifi_main_view" #define TAG "wifi_main_view"
#define SPINNER_HEIGHT 40
static void on_enable_switch_changed(lv_event_t* event) { static void on_enable_switch_changed(lv_event_t* event) {
lv_event_code_t code = lv_event_get_code(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 // region Secondary updates
static void connect(lv_event_t* event) { 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 // 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 // We get the SSID from the button label because it's safer than alloc'ing
// our own and passing it as the event data // our own and passing it as the event data
const char* ssid = lv_label_get_text(label); const char* ssid = lv_label_get_text(label);
if (ssid != nullptr) {
TT_LOG_I(TAG, "Clicked AP: %s", ssid); TT_LOG_I(TAG, "Clicked AP: %s", ssid);
auto* bindings = static_cast<Bindings*>(lv_event_get_user_data(event)); auto* bindings = (Bindings*)lv_event_get_user_data(event);
bindings->onConnectSsid(ssid); bindings->onConnectSsid(ssid);
}
} }
void View::createNetworkButton(Bindings* bindings, const service::wifi::WifiApRecord& record) { 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); const char* icon = service::statusbar::getWifiStatusIconForRssi(record.rssi, record.auth_mode != WIFI_AUTH_OPEN);
lv_obj_t* ap_button = lv_list_add_btn( lv_obj_t* image = lv_image_create(wrapper);
networks_list, lv_image_set_src(image, icon);
icon, lv_obj_align(image, LV_ALIGN_RIGHT_MID, -50, 0);
record.ssid.c_str() }
);
lv_obj_add_event_cb(ap_button, &connect, LV_EVENT_CLICKED, bindings);
} }
void View::updateNetworkList(State* state, Bindings* bindings) { void View::updateNetworkList(State* state, Bindings* bindings) {
lv_obj_clean(networks_list); lv_obj_clean(networks_list);
switch (state->getRadioState()) { switch (state->getRadioState()) {
case service::wifi::WIFI_RADIO_ON_PENDING: case service::wifi::WIFI_RADIO_ON_PENDING:
case service::wifi::WIFI_RADIO_ON: case service::wifi::WIFI_RADIO_ON:
case service::wifi::WIFI_RADIO_CONNECTION_PENDING: case service::wifi::WIFI_RADIO_CONNECTION_PENDING:
case service::wifi::WIFI_RADIO_CONNECTION_ACTIVE: { 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(); 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<std::string> used_ssids; std::set<std::string> used_ssids;
if (!ap_records.empty()) { if (!ap_records.empty()) {
for (auto& record : ap_records) { for (auto& record : ap_records) {
if (used_ssids.find(record.ssid) == used_ssids.end()) { 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); 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_PENDING:
case service::wifi::WIFI_RADIO_OFF: { case service::wifi::WIFI_RADIO_OFF: {
lv_obj_add_flag(networks_list, LV_OBJ_FLAG_HIDDEN); lv_obj_add_flag(networks_list, LV_OBJ_FLAG_HIDDEN);
lv_obj_add_flag(networks_label, LV_OBJ_FLAG_HIDDEN);
break; 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 // endregion Secondary updates
// region Main // 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) { void View::init(const App& app, Bindings* bindings, lv_obj_t* parent) {
root = parent; root = parent;
// Toolbar
lv_obj_set_flex_flow(parent, LV_FLEX_FLOW_COLUMN); lv_obj_set_flex_flow(parent, LV_FLEX_FLOW_COLUMN);
lv_obj_t* toolbar = lvgl::toolbar_create(parent, app); 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); 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); // Wrappers
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);
// Top row: enable/disable lv_obj_t* secondary_flex = lv_obj_create(parent);
lv_obj_t* switch_container = lv_obj_create(wrapper); lv_obj_set_width(secondary_flex, LV_PCT(100));
lv_obj_set_width(switch_container, LV_PCT(100)); lv_obj_set_flex_grow(secondary_flex, 1);
lv_obj_set_height(switch_container, LV_SIZE_CONTENT); lv_obj_set_flex_flow(secondary_flex, LV_FLEX_FLOW_COLUMN);
lvgl::obj_set_style_no_padding(switch_container); lv_obj_set_style_border_width(secondary_flex, 0, 0);
lvgl::obj_set_style_bg_invisible(switch_container); 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_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); 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_ALL, bindings); lv_obj_add_event_cb(enable_on_boot_switch, on_enable_on_boot_switch_changed, LV_EVENT_VALUE_CHANGED, bindings);
lv_obj_set_align(enable_on_boot_switch, LV_ALIGN_RIGHT_MID); lv_obj_align(enable_on_boot_switch, LV_ALIGN_TOP_RIGHT, 0, 0);
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);
// Networks // 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); networks_list = lv_obj_create(wrapper);
lv_obj_set_flex_flow(networks_list, LV_FLEX_FLOW_COLUMN); lv_obj_set_flex_flow(networks_list, LV_FLEX_FLOW_COLUMN);
lv_obj_set_width(networks_list, LV_PCT(100)); lv_obj_set_width(networks_list, LV_PCT(100));
lv_obj_set_height(networks_list, LV_SIZE_CONTENT); lv_obj_set_height(networks_list, LV_SIZE_CONTENT);
lv_obj_set_style_pad_top(networks_list, 8, 0); lv_obj_set_style_pad_top(networks_list, 0, 0);
lv_obj_set_style_pad_bottom(networks_list, 8, 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) { void View::update(Bindings* bindings, State* state) {
@ -231,7 +268,6 @@ void View::update(Bindings* bindings, State* state) {
updateEnableOnBootToggle(); updateEnableOnBootToggle();
updateScanning(state); updateScanning(state);
updateNetworkList(state, bindings); updateNetworkList(state, bindings);
updateConnectedAp(state, bindings);
} }
} // namespace } // namespace

View File

@ -13,10 +13,7 @@ private:
lv_obj_t* enable_switch = nullptr; lv_obj_t* enable_switch = nullptr;
lv_obj_t* enable_on_boot_switch = nullptr; lv_obj_t* enable_on_boot_switch = nullptr;
lv_obj_t* scanning_spinner = nullptr; lv_obj_t* scanning_spinner = nullptr;
lv_obj_t* networks_label = nullptr;
lv_obj_t* networks_list = nullptr; lv_obj_t* networks_list = nullptr;
lv_obj_t* connected_ap_container = nullptr;
lv_obj_t* connected_ap_label = nullptr;
public: public:
View() {} View() {}
void init(const App& app, Bindings* bindings, lv_obj_t* parent); void init(const App& app, Bindings* bindings, lv_obj_t* parent);
@ -24,12 +21,11 @@ public:
private: private:
void updateConnectedAp(State* state, TT_UNUSED Bindings* bindings);
void updateWifiToggle(State* state); void updateWifiToggle(State* state);
void updateEnableOnBootToggle(); void updateEnableOnBootToggle();
void updateScanning(State* state); void updateScanning(State* state);
void updateNetworkList(State* state, Bindings* bindings); 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);
}; };

View File

@ -1,13 +1,14 @@
#include "WifiManage.h" #include "WifiManage.h"
#include "View.h"
#include "State.h"
#include "app/App.h" #include "app/App.h"
#include "app/wificonnect/Parameters.h" #include "app/wificonnect/Parameters.h"
#include "app/wifiapsettings/WifiApSettings.h"
#include "TactilityCore.h" #include "TactilityCore.h"
#include "service/loader/Loader.h" #include "service/loader/Loader.h"
#include "service/wifi/WifiSettings.h" #include "service/wifi/WifiSettings.h"
#include "lvgl/LvglSync.h" #include "lvgl/LvglSync.h"
#include "View.h"
#include "State.h"
namespace tt::app::wifimanage { 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() { static void onDisconnect() {
service::wifi::disconnect(); service::wifi::disconnect();
} }
@ -37,9 +42,10 @@ static void onWifiToggled(bool enabled) {
WifiManage::WifiManage() { WifiManage::WifiManage() {
bindings = (Bindings) { bindings = (Bindings) {
.onWifiToggled = &onWifiToggled, .onWifiToggled = onWifiToggled,
.onConnectSsid = &onConnect, .onConnectSsid = onConnect,
.onDisconnect = &onDisconnect .onDisconnect = onDisconnect,
.onShowApSettings = onShowApSettings
}; };
} }

View File

@ -6,6 +6,8 @@
#include "lvgl/Spacer.h" #include "lvgl/Spacer.h"
#include "lvgl/Style.h" #include "lvgl/Style.h"
#define SPINNER_HEIGHT TOOLBAR_HEIGHT
namespace tt::lvgl { namespace tt::lvgl {
typedef struct { 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); toolbar->action_container = lv_obj_create(obj);
lv_obj_set_width(toolbar->action_container, LV_SIZE_CONTENT); 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_pad_all(toolbar->action_container, 0, 0);
lv_obj_set_style_border_width(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) { lv_obj_t* toolbar_add_switch_action(lv_obj_t* obj) {
auto* toolbar = (Toolbar*)obj; auto* toolbar = (Toolbar*)obj;
lv_obj_t* widget = lv_switch_create(toolbar->action_container); 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; return widget;
} }

View File

@ -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); 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); 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_switch_action(lv_obj_t* obj);
lv_obj_t* toolbar_add_spinner_action(lv_obj_t* obj);
} // namespace } // namespace

View File

@ -191,6 +191,7 @@ static void on_start(Service& service) {
data->thread->setCallback(service_main, data); data->thread->setCallback(service_main, data);
data->thread->setPriority(Thread::PriorityLow); data->thread->setPriority(Thread::PriorityLow);
data->thread->setStackSize(3000); data->thread->setStackSize(3000);
data->thread->setName("statusbar");
data->thread->start(); data->thread->start();
} }

View File

@ -89,6 +89,11 @@ void scan();
*/ */
bool isScanning(); 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. * @brief Returns the access points from the last scan (if any). It only contains public APs.
*/ */

View File

@ -116,6 +116,25 @@ WifiRadioState getRadioState() {
return state; 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() { void scan() {
TT_LOG_I(TAG, "scan()"); TT_LOG_I(TAG, "scan()");
tt_assert(wifi_singleton); tt_assert(wifi_singleton);
@ -723,7 +742,6 @@ _Noreturn int32_t wifi_main(TT_UNUSED void* parameter) {
WifiMessage message; WifiMessage message;
while (true) { while (true) {
TT_LOG_I(TAG, "Message queue %ld", queue.getCount());
if (queue.get(&message, 10000 / portTICK_PERIOD_MS) == TtStatusOk) { if (queue.get(&message, 10000 / portTICK_PERIOD_MS) == TtStatusOk) {
TT_LOG_I(TAG, "Processing message of type %d", message.type); TT_LOG_I(TAG, "Processing message of type %d", message.type);
switch (message.type) { switch (message.type) {

View File

@ -76,6 +76,10 @@ WifiRadioState getRadioState() {
return wifi->radio_state; return wifi->radio_state;
} }
std::string getConnectionTarget() {
return "Home Wifi";
}
void scan() { void scan() {
tt_assert(wifi); tt_assert(wifi);
wifi->scan_active = false; // TODO: enable and then later disable automatically wifi->scan_active = false; // TODO: enable and then later disable automatically
@ -110,17 +114,17 @@ std::vector<WifiApRecord> getScanResults() {
.auth_mode = WIFI_AUTH_WPA2_PSK .auth_mode = WIFI_AUTH_WPA2_PSK
}); });
records.push_back((WifiApRecord) { records.push_back((WifiApRecord) {
.ssid = "Living Room", .ssid = "No place like 127.0.0.1",
.rssi = -67, .rssi = -67,
.auth_mode = WIFI_AUTH_WPA2_PSK .auth_mode = WIFI_AUTH_WPA2_PSK
}); });
records.push_back((WifiApRecord) { records.push_back((WifiApRecord) {
.ssid = "No place like 127.0.0.1", .ssid = "Pretty fly for a Wi-Fi",
.rssi = -70, .rssi = -70,
.auth_mode = WIFI_AUTH_WPA2_PSK .auth_mode = WIFI_AUTH_WPA2_PSK
}); });
records.push_back((WifiApRecord) { records.push_back((WifiApRecord) {
.ssid = "Public Wi-Fi", .ssid = "An AP with a really, really long name",
.rssi = -80, .rssi = -80,
.auth_mode = WIFI_AUTH_WPA2_PSK .auth_mode = WIFI_AUTH_WPA2_PSK
}); });

View File

@ -10,11 +10,11 @@ namespace tt::service::wifi::settings {
* The SSID and secret are increased by 1 byte to facilitate string null termination. * 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. * 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 ssid[TT_WIFI_SSID_LIMIT + 1];
char password[TT_WIFI_CREDENTIALS_PASSWORD_LIMIT + 1]; char password[TT_WIFI_CREDENTIALS_PASSWORD_LIMIT + 1];
bool auto_connect; bool auto_connect;
} WifiApSettings; };
bool contains(const char* ssid); bool contains(const char* ssid);

View File

@ -14,6 +14,7 @@ CONFIG_FREERTOS_TASK_NOTIFICATION_ARRAY_ENTRIES=2
CONFIG_FREERTOS_SMP=n CONFIG_FREERTOS_SMP=n
CONFIG_FREERTOS_UNICORE=n CONFIG_FREERTOS_UNICORE=n
CONFIG_FREERTOS_TIMER_TASK_STACK_DEPTH=4096 CONFIG_FREERTOS_TIMER_TASK_STACK_DEPTH=4096
CONFIG_FREERTOS_USE_TRACE_FACILITY=y
CONFIG_PARTITION_TABLE_CUSTOM=y CONFIG_PARTITION_TABLE_CUSTOM=y
CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions.csv" CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions.csv"
CONFIG_PARTITION_TABLE_FILENAME="partitions.csv" CONFIG_PARTITION_TABLE_FILENAME="partitions.csv"

View File

@ -14,6 +14,7 @@ CONFIG_FREERTOS_TASK_NOTIFICATION_ARRAY_ENTRIES=2
CONFIG_FREERTOS_SMP=n CONFIG_FREERTOS_SMP=n
CONFIG_FREERTOS_UNICORE=n CONFIG_FREERTOS_UNICORE=n
CONFIG_FREERTOS_TIMER_TASK_STACK_DEPTH=4096 CONFIG_FREERTOS_TIMER_TASK_STACK_DEPTH=4096
CONFIG_FREERTOS_USE_TRACE_FACILITY=y
CONFIG_PARTITION_TABLE_CUSTOM=y CONFIG_PARTITION_TABLE_CUSTOM=y
CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions.csv" CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions.csv"
CONFIG_PARTITION_TABLE_FILENAME="partitions.csv" CONFIG_PARTITION_TABLE_FILENAME="partitions.csv"

View File

@ -14,6 +14,7 @@ CONFIG_FREERTOS_TASK_NOTIFICATION_ARRAY_ENTRIES=2
CONFIG_FREERTOS_SMP=n CONFIG_FREERTOS_SMP=n
CONFIG_FREERTOS_UNICORE=n CONFIG_FREERTOS_UNICORE=n
CONFIG_FREERTOS_TIMER_TASK_STACK_DEPTH=4096 CONFIG_FREERTOS_TIMER_TASK_STACK_DEPTH=4096
CONFIG_FREERTOS_USE_TRACE_FACILITY=y
CONFIG_PARTITION_TABLE_CUSTOM=y CONFIG_PARTITION_TABLE_CUSTOM=y
CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions.csv" CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions.csv"
CONFIG_PARTITION_TABLE_FILENAME="partitions.csv" CONFIG_PARTITION_TABLE_FILENAME="partitions.csv"

View File

@ -14,6 +14,7 @@ CONFIG_FREERTOS_TASK_NOTIFICATION_ARRAY_ENTRIES=2
CONFIG_FREERTOS_SMP=n CONFIG_FREERTOS_SMP=n
CONFIG_FREERTOS_UNICORE=n CONFIG_FREERTOS_UNICORE=n
CONFIG_FREERTOS_TIMER_TASK_STACK_DEPTH=4096 CONFIG_FREERTOS_TIMER_TASK_STACK_DEPTH=4096
CONFIG_FREERTOS_USE_TRACE_FACILITY=y
CONFIG_PARTITION_TABLE_CUSTOM=y CONFIG_PARTITION_TABLE_CUSTOM=y
CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions.csv" CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions.csv"
CONFIG_PARTITION_TABLE_FILENAME="partitions.csv" CONFIG_PARTITION_TABLE_FILENAME="partitions.csv"

View File

@ -13,6 +13,8 @@ CONFIG_FREERTOS_HZ=1000
CONFIG_FREERTOS_TASK_NOTIFICATION_ARRAY_ENTRIES=2 CONFIG_FREERTOS_TASK_NOTIFICATION_ARRAY_ENTRIES=2
CONFIG_FREERTOS_SMP=n CONFIG_FREERTOS_SMP=n
CONFIG_FREERTOS_UNICORE=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=y
CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions.csv" CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions.csv"
CONFIG_PARTITION_TABLE_FILENAME="partitions.csv" CONFIG_PARTITION_TABLE_FILENAME="partitions.csv"