Tactility modules refactored (#13)

* refactor modules

* moved esp_lvgl_port to libs/

* added missing file

* fix for sim build

* various sim/pc fixes

* lvgl improvements

* added missing cmake files
This commit is contained in:
Ken Van Hoeylandt
2024-01-20 14:10:19 +01:00
committed by GitHub
parent a94baf0d00
commit 6bd65abbb4
1317 changed files with 388085 additions and 626 deletions
-9
View File
@@ -1,9 +0,0 @@
idf_component_register(
REQUIRES spiffs
)
set(ASSETS_SRC_DIR "${CMAKE_CURRENT_SOURCE_DIR}/assets")
spiffs_create_partition_image(assets ${ASSETS_SRC_DIR} FLASH_IN_PROJECT)
set(CONFIG_SRC_DIR "${CMAKE_CURRENT_SOURCE_DIR}/config")
spiffs_create_partition_image(config ${CONFIG_SRC_DIR} FLASH_IN_PROJECT)
Binary file not shown.

Before

Width:  |  Height:  |  Size: 274 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 224 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 312 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 309 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 336 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 307 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 344 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 312 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 353 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 370 B

-26
View File
@@ -1,26 +0,0 @@
idf_component_register(
SRC_DIRS "src"
"src/apps/desktop"
"src/apps/system/system_info"
"src/apps/system/wifi_connect"
"src/apps/system/wifi_manage"
"src/services/loader"
"src/services/gui"
"src/services/gui/widgets"
"src/services/wifi"
"src/ui"
INCLUDE_DIRS "src"
REQUIRES
esp_lcd
esp_lcd_touch
esp_lvgl_port
esp_wifi
driver
fatfs
nvs_flash
spiffs
)
target_link_libraries(${COMPONENT_LIB} ${IDF_TARGET_NAME} tactility-core)
@@ -1,43 +0,0 @@
#include "app_manifest_registry.h"
#include "check.h"
#include "lvgl.h"
#include "services/loader/loader.h"
static void on_app_pressed(lv_event_t* e) {
lv_event_code_t code = lv_event_get_code(e);
if (code == LV_EVENT_CLICKED) {
const AppManifest* manifest = lv_event_get_user_data(e);
loader_start_app(manifest->id, false, NULL);
}
}
static void create_app_widget(const AppManifest* manifest, void* _Nullable parent) {
tt_check(parent);
lv_obj_t* list = (lv_obj_t*)parent;
lv_obj_t* btn = lv_list_add_btn(list, LV_SYMBOL_FILE, manifest->name);
lv_obj_add_event_cb(btn, &on_app_pressed, LV_EVENT_CLICKED, (void*)manifest);
}
static void desktop_show(App app, lv_obj_t* parent) {
UNUSED(app);
lv_obj_t* list = lv_list_create(parent);
lv_obj_set_size(list, LV_PCT(100), LV_PCT(100));
lv_obj_center(list);
lv_list_add_text(list, "System");
tt_app_manifest_registry_for_each_of_type(AppTypeSystem, list, create_app_widget);
lv_list_add_text(list, "User");
tt_app_manifest_registry_for_each_of_type(AppTypeUser, list, create_app_widget);
}
const AppManifest desktop_app = {
.id = "desktop",
.name = "Desktop",
.icon = NULL,
.type = AppTypeDesktop,
.on_start = NULL,
.on_stop = NULL,
.on_show = &desktop_show,
.on_hide = NULL
};
@@ -1,43 +0,0 @@
#include "app_manifest.h"
#include "core_extra_defines.h"
#include "esp_wifi.h"
#include "lvgl.h"
#include "thread.h"
static void app_show(App app, lv_obj_t* parent) {
UNUSED(app);
lv_obj_t* heap_info = lv_label_create(parent);
lv_label_set_recolor(heap_info, true);
lv_obj_set_width(heap_info, 200);
lv_obj_set_style_text_align(heap_info, LV_TEXT_ALIGN_CENTER, 0);
lv_label_set_text_fmt(
heap_info,
"Heap available:\n%d / %d",
heap_caps_get_free_size(MALLOC_CAP_INTERNAL),
heap_caps_get_total_size(MALLOC_CAP_INTERNAL)
);
lv_obj_align(heap_info, LV_ALIGN_CENTER, 0, -20);
lv_obj_t* spi_info = lv_label_create(parent);
lv_label_set_recolor(spi_info, true);
lv_obj_set_width(spi_info, 200);
lv_obj_set_style_text_align(spi_info, LV_TEXT_ALIGN_CENTER, 0);
lv_label_set_text_fmt(
spi_info,
"SPI available\n%d / %d",
heap_caps_get_free_size(MALLOC_CAP_SPIRAM),
heap_caps_get_total_size(MALLOC_CAP_SPIRAM)
);
lv_obj_align(spi_info, LV_ALIGN_CENTER, 0, 20);
}
AppManifest system_info_app = {
.id = "systeminfo",
.name = "System Info",
.icon = NULL,
.type = AppTypeSystem,
.on_start = NULL,
.on_stop = NULL,
.on_show = &app_show
};
@@ -1,124 +0,0 @@
#include "wifi_connect.h"
#include "app.h"
#include "esp_lvgl_port.h"
#include "services/wifi/wifi.h"
#include "tactility_core.h"
#include "wifi_connect_state_updating.h"
#define TAG "wifi_connect"
// Forward declarations
static void wifi_connect_event_callback(const void* message, void* context);
static void on_connect(const char* ssid, const char* password, void* parameter) {
UNUSED(parameter);
wifi_connect(ssid, password);
}
static WifiConnect* wifi_connect_alloc() {
WifiConnect* wifi = malloc(sizeof(WifiConnect));
PubSub* wifi_pubsub = wifi_get_pubsub();
wifi->wifi_subscription = tt_pubsub_subscribe(wifi_pubsub, &wifi_connect_event_callback, wifi);
wifi->mutex = tt_mutex_alloc(MutexTypeNormal);
wifi->state = (WifiConnectState) {
.radio_state = wifi_get_radio_state()
};
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 = wifi_get_pubsub();
tt_pubsub_unsubscribe(wifi_pubsub, wifi->wifi_subscription);
tt_mutex_free(wifi->mutex);
free(wifi);
}
void wifi_connect_lock(WifiConnect* wifi) {
tt_assert(wifi);
tt_assert(wifi->mutex);
tt_mutex_acquire(wifi->mutex, TtWaitForever);
}
void wifi_connect_unlock(WifiConnect* wifi) {
tt_assert(wifi);
tt_assert(wifi->mutex);
tt_mutex_release(wifi->mutex);
}
void wifi_connect_request_view_update(WifiConnect* wifi) {
wifi_connect_lock(wifi);
if (wifi->view_enabled) {
if (lvgl_port_lock(1000)) {
wifi_connect_view_update(&wifi->view, &wifi->bindings, &wifi->state);
lvgl_port_unlock();
} else {
TT_LOG_E(TAG, "failed to lock lvgl");
}
}
wifi_connect_unlock(wifi);
}
static void wifi_connect_event_callback(const void* message, void* context) {
const WifiEvent* event = (const WifiEvent*)message;
WifiConnect* wifi = (WifiConnect*)context;
wifi_connect_state_set_radio_state(wifi, wifi_get_radio_state());
switch (event->type) {
case WifiEventTypeRadioStateOn:
wifi_scan();
break;
default:
break;
}
wifi_connect_request_view_update(wifi);
}
static void app_show(App app, lv_obj_t* parent) {
WifiConnect* wifi = (WifiConnect*)tt_app_get_data(app);
wifi_connect_lock(wifi);
wifi->view_enabled = true;
wifi_connect_view_create(app, wifi, parent);
wifi_connect_view_update(&wifi->view, &wifi->bindings, &wifi->state);
wifi_connect_unlock(wifi);
}
static void app_hide(App app) {
WifiConnect* wifi = (WifiConnect*)tt_app_get_data(app);
// No need to lock view, as this is called from within Gui's LVGL context
wifi_connect_view_destroy(&wifi->view);
wifi_connect_lock(wifi);
wifi->view_enabled = false;
wifi_connect_unlock(wifi);
}
static void app_start(App app) {
WifiConnect* wifi_connect = wifi_connect_alloc(app);
tt_app_set_data(app, wifi_connect);
}
static void app_stop(App app) {
WifiConnect* wifi = tt_app_get_data(app);
tt_assert(wifi != NULL);
wifi_connect_free(wifi);
tt_app_set_data(app, NULL);
}
AppManifest wifi_connect_app = {
.id = "wifi_connect",
.name = "Wi-Fi Connect",
.icon = NULL,
.type = AppTypeSystem,
.on_start = &app_start,
.on_stop = &app_stop,
.on_show = &app_show,
.on_hide = &app_hide
};
@@ -1,30 +0,0 @@
#pragma once
#include "mutex.h"
#include "services/wifi/wifi.h"
#include "wifi_connect_bindings.h"
#include "wifi_connect_state.h"
#include "wifi_connect_view.h"
#ifdef __cplusplus
extern "C" {
#endif
typedef struct {
PubSubSubscription* wifi_subscription;
Mutex* mutex;
WifiConnectState state;
WifiConnectView view;
bool view_enabled;
WifiConnectBindings bindings;
} WifiConnect;
void wifi_connect_lock(WifiConnect* wifi);
void wifi_connect_unlock(WifiConnect* wifi);
void wifi_connect_request_view_update(WifiConnect* wifi);
#ifdef __cplusplus
}
#endif
@@ -1,10 +0,0 @@
#pragma once
#include <stdbool.h>
typedef void (*OnConnectSsid)(const char* ssid, const char password[64], void* context);
typedef struct {
OnConnectSsid on_connect_ssid;
void* on_connect_ssid_context;
} WifiConnectBindings;
@@ -1,12 +0,0 @@
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
#define WIFI_CONNECT_PARAM_SSID "ssid" // String
#define WIFI_CONNECT_PARAM_PASSWORD "password" // String
#ifdef __cplusplus
}
#endif
@@ -1,21 +0,0 @@
#pragma once
#include <stdbool.h>
#include "app.h"
#include "services/wifi/wifi.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* View's state
*/
typedef struct {
WifiRadioState radio_state;
bool connection_error;
} WifiConnectState;
#ifdef __cplusplus
}
#endif
@@ -1,7 +0,0 @@
#include "wifi_connect_state_updating.h"
void wifi_connect_state_set_radio_state(WifiConnect* wifi, WifiRadioState state) {
wifi_connect_lock(wifi);
wifi->state.radio_state = state;
wifi_connect_unlock(wifi);
}
@@ -1,13 +0,0 @@
#pragma once
#include "wifi_connect.h"
#ifdef __cplusplus
extern "C" {
#endif
void wifi_connect_state_set_radio_state(WifiConnect* wifi, WifiRadioState state);
#ifdef __cplusplus
}
#endif
@@ -1,136 +0,0 @@
#include "wifi_connect_view.h"
#include "log.h"
#include "lvgl.h"
#include "services/gui/gui.h"
#include "services/wifi/wifi_credentials.h"
#include "ui/spacer.h"
#include "ui/style.h"
#include "wifi_connect.h"
#include "wifi_connect_bundle.h"
#include "wifi_connect_state.h"
#define TAG "wifi_connect"
static void show_keyboard(lv_event_t* event) {
gui_keyboard_show(event->current_target);
lv_obj_scroll_to_view(event->current_target, LV_ANIM_ON);
}
static void hide_keyboard(lv_event_t* event) {
UNUSED(event);
gui_keyboard_hide();
}
static void on_connect(lv_event_t* event) {
WifiConnect* wifi = (WifiConnect*)event->user_data;
WifiConnectView* view = &wifi->view;
const char* ssid = lv_textarea_get_text(view->ssid_textarea);
const char* password = lv_textarea_get_text(view->password_textarea);
if (strlen(password) > 63) {
// TODO: UI feedback
TT_LOG_E(TAG, "Password too long");
return;
}
char password_buffer[64];
strcpy(password_buffer, password);
WifiConnectBindings* bindings = &wifi->bindings;
bindings->on_connect_ssid(
ssid,
password_buffer,
bindings->on_connect_ssid_context
);
if (lv_obj_get_state(view->remember_switch) == LV_STATE_CHECKED) {
tt_wifi_credentials_set(ssid, password_buffer);
}
}
void wifi_connect_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);
tt_lv_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->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 wifi_connect_view_create(App app, void* wifi, lv_obj_t* parent) {
WifiConnect* wifi_connect = (WifiConnect*)wifi;
WifiConnectView* view = &wifi_connect->view;
// TODO: Standardize this into "window content" function?
// TODO: It can then be dynamically determined based on screen res and size
lv_obj_set_flex_flow(parent, LV_FLEX_FLOW_COLUMN);
lv_obj_set_style_pad_top(parent, 8, 0);
lv_obj_set_style_pad_bottom(parent, 8, 0);
lv_obj_set_style_pad_left(parent, 16, 0);
lv_obj_set_style_pad_right(parent, 16, 0);
view->root = parent;
lv_obj_t* ssid_label = lv_label_create(parent);
lv_label_set_text(ssid_label, "Network:");
view->ssid_textarea = lv_textarea_create(parent);
lv_textarea_set_one_line(view->ssid_textarea, true);
tt_lv_spacer_create(parent, 1, 8);
lv_obj_t* password_label = lv_label_create(parent);
lv_label_set_text(password_label, "Password:");
view->password_textarea = lv_textarea_create(parent);
lv_textarea_set_one_line(view->password_textarea, true);
lv_textarea_set_password_mode(view->password_textarea, true);
tt_lv_spacer_create(parent, 1, 8);
wifi_connect_view_create_bottom_buttons(wifi, parent);
lv_obj_add_event_cb(view->ssid_textarea, show_keyboard, LV_EVENT_FOCUSED, NULL);
lv_obj_add_event_cb(view->ssid_textarea, hide_keyboard, LV_EVENT_DEFOCUSED, NULL);
lv_obj_add_event_cb(view->ssid_textarea, hide_keyboard, LV_EVENT_READY, NULL);
lv_obj_add_event_cb(view->password_textarea, show_keyboard, LV_EVENT_FOCUSED, NULL);
lv_obj_add_event_cb(view->password_textarea, hide_keyboard, LV_EVENT_DEFOCUSED, NULL);
lv_obj_add_event_cb(view->password_textarea, hide_keyboard, LV_EVENT_READY, NULL);
// Init from app parameters
Bundle* _Nullable bundle = tt_app_get_parameters(app);
if (bundle) {
char* ssid;
if (tt_bundle_opt_string(bundle, WIFI_CONNECT_PARAM_SSID, &ssid)) {
lv_textarea_set_text(view->ssid_textarea, ssid);
}
char* password;
if (tt_bundle_opt_string(bundle, WIFI_CONNECT_PARAM_PASSWORD, &password)) {
lv_textarea_set_text(view->password_textarea, password);
}
}
}
void wifi_connect_view_destroy(WifiConnectView* view) {
}
void wifi_connect_view_update(WifiConnectView* view, WifiConnectBindings* bindings, WifiConnectState* state) {
UNUSED(view);
UNUSED(bindings);
UNUSED(state);
}
@@ -1,26 +0,0 @@
#pragma once
#include "lvgl.h"
#include "wifi_connect_state.h"
#include "wifi_connect_bindings.h"
#ifdef __cplusplus
extern "C" {
#endif
typedef struct {
lv_obj_t* root;
lv_obj_t* ssid_textarea;
lv_obj_t* password_textarea;
lv_obj_t* connect_button;
lv_obj_t* cancel_button;
lv_obj_t* remember_switch;
} WifiConnectView;
void wifi_connect_view_create(App app, void* wifi, lv_obj_t* parent);
void wifi_connect_view_update(WifiConnectView* view, WifiConnectBindings* bindings, WifiConnectState* state);
void wifi_connect_view_destroy(WifiConnectView* view);
#ifdef __cplusplus
}
#endif
@@ -1,166 +0,0 @@
#include "wifi_manage.h"
#include "app.h"
#include "apps/system/wifi_connect/wifi_connect_bundle.h"
#include "esp_lvgl_port.h"
#include "services/loader/loader.h"
#include "tactility_core.h"
#include "wifi_manage_state_updating.h"
#include "wifi_manage_view.h"
#include "services/wifi/wifi_credentials.h"
#define TAG "wifi_manage"
// Forward declarations
static void wifi_manage_event_callback(const void* message, void* context);
static void on_connect(const char* ssid) {
char password[TT_WIFI_CREDENTIALS_PASSWORD_LIMIT];
if (tt_wifi_credentials_get(ssid, password)) {
TT_LOG_I(TAG, "Connecting with known credentials");
wifi_connect(ssid, password);
} else {
TT_LOG_I(TAG, "Starting connection dialog");
Bundle bundle = tt_bundle_alloc();
tt_bundle_put_string(bundle, WIFI_CONNECT_PARAM_SSID, ssid);
tt_bundle_put_string(bundle, WIFI_CONNECT_PARAM_PASSWORD, "");
loader_start_app("wifi_connect", false, bundle);
}
}
static void on_disconnect() {
wifi_disconnect();
}
static void on_wifi_toggled(bool enabled) {
wifi_set_enabled(enabled);
}
static WifiManage* wifi_manage_alloc() {
WifiManage* wifi = malloc(sizeof(WifiManage));
PubSub* wifi_pubsub = wifi_get_pubsub();
wifi->wifi_subscription = tt_pubsub_subscribe(wifi_pubsub, &wifi_manage_event_callback, wifi);
wifi->mutex = tt_mutex_alloc(MutexTypeNormal);
wifi->state = (WifiManageState) {
.scanning = wifi_is_scanning(),
.radio_state = wifi_get_radio_state()
};
wifi->view_enabled = false;
wifi->bindings = (WifiManageBindings) {
.on_wifi_toggled = &on_wifi_toggled,
.on_connect_ssid = &on_connect,
.on_disconnect = &on_disconnect
};
return wifi;
}
static void wifi_manage_free(WifiManage* wifi) {
PubSub* wifi_pubsub = wifi_get_pubsub();
tt_pubsub_unsubscribe(wifi_pubsub, wifi->wifi_subscription);
tt_mutex_free(wifi->mutex);
free(wifi);
}
void wifi_manage_lock(WifiManage* wifi) {
tt_assert(wifi);
tt_assert(wifi->mutex);
tt_mutex_acquire(wifi->mutex, TtWaitForever);
}
void wifi_manage_unlock(WifiManage* wifi) {
tt_assert(wifi);
tt_assert(wifi->mutex);
tt_mutex_release(wifi->mutex);
}
void wifi_manage_request_view_update(WifiManage* wifi) {
wifi_manage_lock(wifi);
if (wifi->view_enabled) {
if (lvgl_port_lock(1000)) {
wifi_manage_view_update(&wifi->view, &wifi->bindings, &wifi->state);
lvgl_port_unlock();
} else {
TT_LOG_E(TAG, "failed to lock lvgl");
}
}
wifi_manage_unlock(wifi);
}
static void wifi_manage_event_callback(const void* message, void* context) {
const WifiEvent* event = (const WifiEvent*)message;
WifiManage* wifi = (WifiManage*)context;
wifi_manage_state_set_radio_state(wifi, wifi_get_radio_state());
switch (event->type) {
case WifiEventTypeScanStarted:
wifi_manage_state_set_scanning(wifi, true);
break;
case WifiEventTypeScanFinished:
wifi_manage_state_set_scanning(wifi, false);
wifi_manage_state_update_scanned_records(wifi);
break;
case WifiEventTypeRadioStateOn:
if (!wifi_is_scanning()) {
wifi_scan();
}
break;
default:
break;
}
wifi_manage_request_view_update(wifi);
}
static void app_show(App app, lv_obj_t* parent) {
WifiManage* wifi = (WifiManage*)tt_app_get_data(app);
// State update (it has its own locking)
wifi_manage_state_set_radio_state(wifi, wifi_get_radio_state());
wifi_manage_state_set_scanning(wifi, wifi_is_scanning());
wifi_manage_state_update_scanned_records(wifi);
// View update
wifi_manage_lock(wifi);
wifi->view_enabled = true;
strcpy((char*)wifi->state.connect_ssid, "Connected"); // TODO update with proper SSID
wifi_manage_view_create(&wifi->view, &wifi->bindings, parent);
wifi_manage_view_update(&wifi->view, &wifi->bindings, &wifi->state);
wifi_manage_unlock(wifi);
WifiRadioState radio_state = wifi_get_radio_state();
if (radio_state == WIFI_RADIO_ON && !wifi_is_scanning()) {
wifi_scan();
}
}
static void app_hide(App app) {
WifiManage* wifi = (WifiManage*)tt_app_get_data(app);
wifi_manage_lock(wifi);
wifi->view_enabled = false;
wifi_manage_unlock(wifi);
}
static void app_start(App app) {
WifiManage* wifi = wifi_manage_alloc();
tt_app_set_data(app, wifi);
}
static void app_stop(App app) {
WifiManage* wifi = (WifiManage*)tt_app_get_data(app);
tt_assert(wifi != NULL);
wifi_manage_free(wifi);
tt_app_set_data(app, NULL);
}
AppManifest wifi_manage_app = {
.id = "wifi_manage",
.name = "Wi-Fi",
.icon = NULL,
.type = AppTypeSystem,
.on_start = &app_start,
.on_stop = &app_stop,
.on_show = &app_show,
.on_hide = &app_hide
};
@@ -1,28 +0,0 @@
#pragma once
#include "mutex.h"
#include "services/wifi/wifi.h"
#include "wifi_manage_view.h"
#ifdef __cplusplus
extern "C" {
#endif
typedef struct {
PubSubSubscription* wifi_subscription;
Mutex* mutex;
WifiManageState state;
WifiManageView view;
bool view_enabled;
WifiManageBindings bindings;
} WifiManage;
void wifi_manage_lock(WifiManage* wifi);
void wifi_manage_unlock(WifiManage* wifi);
void wifi_manage_request_view_update(WifiManage* wifi);
#ifdef __cplusplus
}
#endif
@@ -1,13 +0,0 @@
#pragma once
#include <stdbool.h>
typedef void (*OnWifiToggled)(bool enable);
typedef void (*OnConnectSsid)(const char* ssid);
typedef void (*OnDisconnect)();
typedef struct {
OnWifiToggled on_wifi_toggled;
OnConnectSsid on_connect_ssid;
OnDisconnect on_disconnect;
} WifiManageBindings;
@@ -1,25 +0,0 @@
#pragma once
#include <stdbool.h>
#include "services/wifi/wifi.h"
#ifdef __cplusplus
extern "C" {
#endif
#define WIFI_SCAN_AP_RECORD_COUNT 16
/**
* View's state
*/
typedef struct {
bool scanning;
WifiRadioState radio_state;
uint8_t connect_ssid[33];
WifiApRecord ap_records[WIFI_SCAN_AP_RECORD_COUNT];
uint16_t ap_records_count;
} WifiManageState;
#ifdef __cplusplus
}
#endif
@@ -1,23 +0,0 @@
#include "wifi_manage.h"
void wifi_manage_state_set_scanning(WifiManage* wifi, bool is_scanning) {
wifi_manage_lock(wifi);
wifi->state.scanning = is_scanning;
wifi_manage_unlock(wifi);
}
void wifi_manage_state_set_radio_state(WifiManage* wifi, WifiRadioState state) {
wifi_manage_lock(wifi);
wifi->state.radio_state = state;
wifi_manage_unlock(wifi);
}
void wifi_manage_state_update_scanned_records(WifiManage* wifi) {
wifi_manage_lock(wifi);
wifi_get_scan_results(
wifi->state.ap_records,
WIFI_SCAN_AP_RECORD_COUNT,
&wifi->state.ap_records_count
);
wifi_manage_unlock(wifi);
}
@@ -1,15 +0,0 @@
#pragma once
#include "wifi_manage.h"
#ifdef __cplusplus
extern "C" {
#endif
void wifi_manage_state_set_scanning(WifiManage* wifi, bool is_scanning);
void wifi_manage_state_set_radio_state(WifiManage* wifi, WifiRadioState state);
void wifi_manage_state_update_scanned_records(WifiManage* wifi);
#ifdef __cplusplus
}
#endif
@@ -1,222 +0,0 @@
#include "wifi_manage_view.h"
#include "log.h"
#include "services/wifi/wifi.h"
#include "ui/style.h"
#include "wifi_manage_state.h"
#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);
lv_obj_t* enable_switch = lv_event_get_target(event);
if (code == LV_EVENT_VALUE_CHANGED) {
bool is_on = lv_obj_has_state(enable_switch, LV_STATE_CHECKED);
WifiManageBindings* bindings = (WifiManageBindings*)event->user_data;
bindings->on_wifi_toggled(is_on);
}
}
static void on_disconnect_pressed(lv_event_t* event) {
WifiManageBindings* bindings = (WifiManageBindings*)event->user_data;
bindings->on_disconnect();
}
// region Secondary updates
static const char* get_network_icon(int8_t rssi, wifi_auth_mode_t auth_mode) {
if (rssi > -67) {
if (auth_mode == WIFI_AUTH_OPEN)
return "A:/assets/network_wifi.png";
else
return "A:/assets/network_wifi_locked.png";
} else if (rssi > -70) {
if (auth_mode == WIFI_AUTH_OPEN)
return "A:/assets/network_wifi_3_bar.png";
else
return "A:/assets/network_wifi_3_bar_locked.png";
} else if (rssi > -80) {
if (auth_mode == WIFI_AUTH_OPEN)
return "A:/assets/network_wifi_2_bar.png";
else
return "A:/assets/network_wifi_2_bar_locked.png";
} else {
if (auth_mode == WIFI_AUTH_OPEN)
return "A:/assets/network_wifi_1_bar.png";
else
return "A:/assets/network_wifi_1_bar_locked.png";
}
}
static void connect(lv_event_t* event) {
lv_obj_t* button = event->current_target;
// Assumes that the second child of the button is a label ... risky
lv_obj_t* label = lv_obj_get_child(button, 1);
// 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);
WifiManageBindings* bindings = (WifiManageBindings*)event->user_data;
bindings->on_connect_ssid(ssid);
}
static void create_network_button(WifiManageView* view, WifiManageBindings* bindings, WifiApRecord* record) {
const char* ssid = (const char*)record->ssid;
const char* icon = get_network_icon(record->rssi, record->auth_mode);
lv_obj_t* ap_button = lv_list_add_btn(
view->networks_list,
icon,
ssid
);
lv_obj_add_event_cb(ap_button, &connect, LV_EVENT_CLICKED, bindings);
}
static void update_network_list(WifiManageView* view, WifiManageState* state, WifiManageBindings* bindings) {
lv_obj_clean(view->networks_list);
switch (state->radio_state) {
case WIFI_RADIO_ON_PENDING:
case WIFI_RADIO_ON:
case WIFI_RADIO_CONNECTION_PENDING:
case WIFI_RADIO_CONNECTION_ACTIVE: {
lv_obj_clear_flag(view->networks_label, LV_OBJ_FLAG_HIDDEN);
if (state->ap_records_count > 0) {
for (int i = 0; i < state->ap_records_count; ++i) {
create_network_button(view, bindings, &state->ap_records[i]);
}
lv_obj_clear_flag(view->networks_list, LV_OBJ_FLAG_HIDDEN);
} else if (state->scanning) {
lv_obj_add_flag(view->networks_list, LV_OBJ_FLAG_HIDDEN);
} else {
lv_obj_clear_flag(view->networks_list, LV_OBJ_FLAG_HIDDEN);
lv_obj_t* label = lv_label_create(view->networks_list);
lv_label_set_text(label, "No networks found.");
}
break;
}
case WIFI_RADIO_OFF_PENDING:
case WIFI_RADIO_OFF: {
lv_obj_add_flag(view->networks_list, LV_OBJ_FLAG_HIDDEN);
lv_obj_add_flag(view->networks_label, LV_OBJ_FLAG_HIDDEN);
break;
}
}
}
void update_scanning(WifiManageView* view, WifiManageState* state) {
if (state->radio_state == WIFI_RADIO_ON && state->scanning) {
lv_obj_clear_flag(view->scanning_spinner, LV_OBJ_FLAG_HIDDEN);
} else {
lv_obj_add_flag(view->scanning_spinner, LV_OBJ_FLAG_HIDDEN);
}
}
static void update_wifi_toggle(WifiManageView* view, WifiManageState* state) {
lv_obj_clear_state(view->enable_switch, LV_STATE_ANY);
switch (state->radio_state) {
case WIFI_RADIO_ON:
case WIFI_RADIO_CONNECTION_PENDING:
case WIFI_RADIO_CONNECTION_ACTIVE:
lv_obj_add_state(view->enable_switch, LV_STATE_CHECKED);
break;
case WIFI_RADIO_ON_PENDING:
lv_obj_add_state(view->enable_switch, LV_STATE_CHECKED | LV_STATE_DISABLED);
break;
case WIFI_RADIO_OFF:
break;
case WIFI_RADIO_OFF_PENDING:
lv_obj_add_state(view->enable_switch, LV_STATE_DISABLED);
break;
}
}
static void update_connected_ap(WifiManageView* view, WifiManageState* state, WifiManageBindings* bindings) {
switch (state->radio_state) {
case WIFI_RADIO_CONNECTION_PENDING:
case WIFI_RADIO_CONNECTION_ACTIVE:
lv_obj_clear_flag(view->connected_ap_container, LV_OBJ_FLAG_HIDDEN);
lv_label_set_text(view->connected_ap_label, (const char*)state->connect_ssid);
break;
default:
lv_obj_add_flag(view->connected_ap_container, LV_OBJ_FLAG_HIDDEN);
break;
}
}
// endregion Secondary updates
// region Main
void wifi_manage_view_create(WifiManageView* view, WifiManageBindings* bindings, lv_obj_t* parent) {
view->root = parent;
// TODO: Standardize this into "window content" function?
// TODO: It can then be dynamically determined based on screen res and size
lv_obj_set_flex_flow(parent, LV_FLEX_FLOW_COLUMN);
lv_obj_set_style_pad_top(parent, 8, 0);
lv_obj_set_style_pad_bottom(parent, 8, 0);
lv_obj_set_style_pad_left(parent, 16, 0);
lv_obj_set_style_pad_right(parent, 16, 0);
// Top row: enable/disable
lv_obj_t* switch_container = lv_obj_create(parent);
lv_obj_set_width(switch_container, LV_PCT(100));
lv_obj_set_height(switch_container, LV_SIZE_CONTENT);
tt_lv_obj_set_style_no_padding(switch_container);
tt_lv_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_obj_set_align(enable_label, LV_ALIGN_LEFT_MID);
view->enable_switch = lv_switch_create(switch_container);
lv_obj_add_event_cb(view->enable_switch, on_enable_switch_changed, LV_EVENT_ALL, bindings);
lv_obj_set_align(view->enable_switch, LV_ALIGN_RIGHT_MID);
view->connected_ap_container = lv_obj_create(parent);
lv_obj_set_size(view->connected_ap_container, LV_PCT(100), LV_SIZE_CONTENT);
lv_obj_set_style_min_height(view->connected_ap_container, SPINNER_HEIGHT, 0);
tt_lv_obj_set_style_no_padding(view->connected_ap_container);
lv_obj_set_style_border_width(view->connected_ap_container, 0, 0);
view->connected_ap_label = lv_label_create(view->connected_ap_container);
lv_obj_align(view->connected_ap_label, LV_ALIGN_LEFT_MID, 0, 0);
lv_obj_t* disconnect_button = lv_btn_create(view->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
lv_obj_t* networks_header = lv_obj_create(parent);
lv_obj_set_size(networks_header, LV_PCT(100), LV_SIZE_CONTENT);
lv_obj_set_style_min_height(networks_header, SPINNER_HEIGHT, 0);
tt_lv_obj_set_style_no_padding(networks_header);
lv_obj_set_style_border_width(networks_header, 0, 0);
view->networks_label = lv_label_create(networks_header);
lv_label_set_text(view->networks_label, "Networks");
lv_obj_align(view->networks_label, LV_ALIGN_LEFT_MID, 0, 0);
view->scanning_spinner = lv_spinner_create(networks_header, 1000, 60);
lv_obj_set_size(view->scanning_spinner, SPINNER_HEIGHT, SPINNER_HEIGHT);
lv_obj_set_style_pad_top(view->scanning_spinner, 4, 0);
lv_obj_set_style_pad_bottom(view->scanning_spinner, 4, 0);
lv_obj_align_to(view->scanning_spinner, view->networks_label, LV_ALIGN_OUT_RIGHT_MID, 8, 0);
view->networks_list = lv_obj_create(parent);
lv_obj_set_flex_flow(view->networks_list, LV_FLEX_FLOW_COLUMN);
lv_obj_set_width(view->networks_list, LV_PCT(100));
lv_obj_set_height(view->networks_list, LV_SIZE_CONTENT);
lv_obj_set_style_pad_top(view->networks_list, 8, 0);
lv_obj_set_style_pad_bottom(view->networks_list, 8, 0);
}
void wifi_manage_view_update(WifiManageView* view, WifiManageBindings* bindings, WifiManageState* state) {
update_wifi_toggle(view, state);
update_scanning(view, state);
update_network_list(view, state, bindings);
update_connected_ap(view, state, bindings);
}
@@ -1,26 +0,0 @@
#pragma once
#include "lvgl.h"
#include "wifi_manage_bindings.h"
#include "wifi_manage_state.h"
#ifdef __cplusplus
extern "C" {
#endif
typedef struct {
lv_obj_t* root;
lv_obj_t* enable_switch;
lv_obj_t* scanning_spinner;
lv_obj_t* networks_label;
lv_obj_t* networks_list;
lv_obj_t* connected_ap_container;
lv_obj_t* connected_ap_label;
} WifiManageView;
void wifi_manage_view_create(WifiManageView* view, WifiManageBindings* bindings, lv_obj_t* parent);
void wifi_manage_view_update(WifiManageView* view, WifiManageBindings* bindings, WifiManageState* state);
#ifdef __cplusplus
}
#endif
-31
View File
@@ -1,31 +0,0 @@
#include "check.h"
#include "devices_i.h"
#define TAG "hardware"
Hardware tt_hardware_init(const HardwareConfig _Nonnull* config) {
if (config->bootstrap != NULL) {
TT_LOG_I(TAG, "Bootstrapping");
config->bootstrap();
}
tt_check(config->display_driver != NULL, "no display driver configured");
DisplayDriver display_driver = config->display_driver();
TT_LOG_I(TAG, "display with driver %s", display_driver.name);
DisplayDevice* display = tt_display_device_alloc(&display_driver);
TouchDevice* touch = NULL;
if (config->touch_driver != NULL) {
TouchDriver touch_driver = config->touch_driver();
TT_LOG_I(TAG, "touch with driver %s", touch_driver.name);
touch = tt_touch_alloc(&touch_driver);
} else {
TT_LOG_I(TAG, "no touch configured");
touch = NULL;
}
return (Hardware) {
.display = display,
.touch = touch
};
}
-17
View File
@@ -1,17 +0,0 @@
#pragma once
#include "display.h"
#include "touch.h"
#ifdef __cplusplus
extern "C" {
#endif
typedef struct {
DisplayDevice* _Nonnull display;
TouchDevice* _Nullable touch;
} Hardware;
#ifdef __cplusplus
}
#endif
-13
View File
@@ -1,13 +0,0 @@
#pragma once
#include "tactility.h"
#ifdef __cplusplus
extern "C" {
#endif
Hardware tt_hardware_init(const HardwareConfig _Nonnull* config);
#ifdef __cplusplus
}
#endif
-15
View File
@@ -1,15 +0,0 @@
#include "check.h"
#include "display.h"
DisplayDevice _Nonnull* tt_display_device_alloc(DisplayDriver _Nonnull* driver) {
DisplayDevice _Nonnull* display = malloc(sizeof(DisplayDevice));
memset(display, 0, sizeof(DisplayDevice));
tt_check(driver->create_display_device(display), "failed to create display");
tt_check(display->io_handle != NULL);
tt_check(display->display_handle != NULL);
tt_check(display->horizontal_resolution != 0);
tt_check(display->vertical_resolution != 0);
tt_check(display->draw_buffer_height > 0);
tt_check(display->bits_per_pixel > 0);
return display;
}
-38
View File
@@ -1,38 +0,0 @@
#pragma once
#include <esp_lcd_panel_io.h>
#ifdef __cplusplus
extern "C" {
#endif
typedef struct {
esp_lcd_panel_io_handle_t _Nonnull io_handle;
esp_lcd_panel_handle_t _Nonnull display_handle;
uint16_t horizontal_resolution;
uint16_t vertical_resolution;
uint16_t draw_buffer_height;
uint16_t bits_per_pixel;
bool double_buffering;
bool mirror_x;
bool mirror_y;
bool swap_xy;
bool monochrome;
} DisplayDevice;
typedef bool (*CreateDisplay)(DisplayDevice* display);
typedef struct {
char name[32];
CreateDisplay create_display_device;
} DisplayDriver;
/**
* @param[in] driver
* @return allocated display object
*/
DisplayDevice _Nonnull* tt_display_device_alloc(DisplayDriver _Nonnull* driver);
#ifdef __cplusplus
}
#endif
-59
View File
@@ -1,59 +0,0 @@
#include "check.h"
#include "esp_lvgl_port.h"
#include "graphics_i.h"
#include "lvgl.h"
#define TAG "lvgl"
Lvgl tt_graphics_init(Hardware _Nonnull* hardware) {
const lvgl_port_cfg_t lvgl_cfg = {
.task_priority = 4,
.task_stack = 4096,
.task_affinity = -1, // core pinning
.task_max_sleep_ms = 500,
.timer_period_ms = 5
};
tt_check(lvgl_port_init(&lvgl_cfg) == ESP_OK, "lvgl port init failed");
DisplayDevice _Nonnull* display = hardware->display;
// Add display
TT_LOG_I(TAG, "lvgl add display");
const lvgl_port_display_cfg_t disp_cfg = {
.io_handle = display->io_handle,
.panel_handle = display->display_handle,
.buffer_size = display->horizontal_resolution * display->draw_buffer_height * (display->bits_per_pixel / 8),
.double_buffer = display->double_buffering,
.hres = display->horizontal_resolution,
.vres = display->vertical_resolution,
.monochrome = display->monochrome,
.rotation = {
.swap_xy = display->swap_xy,
.mirror_x = display->mirror_x,
.mirror_y = display->mirror_y,
},
.flags = {
.buff_dma = true,
}
};
lv_disp_t _Nonnull* disp = lvgl_port_add_disp(&disp_cfg);
tt_check(disp != NULL, "failed to add display");
lv_indev_t _Nullable* touch_indev = NULL;
// Add touch
if (hardware->touch != NULL) {
const lvgl_port_touch_cfg_t touch_cfg = {
.disp = disp,
.handle = hardware->touch->touch_handle,
};
touch_indev = lvgl_port_add_touch(&touch_cfg);
tt_check(touch_indev != NULL, "failed to add touch to lvgl");
}
return (Lvgl) {
.disp = disp,
.touch_indev = touch_indev
};
}
-16
View File
@@ -1,16 +0,0 @@
#pragma once
#include "lvgl.h"
#ifdef __cplusplus
extern "C" {
#endif
typedef struct {
lv_disp_t* _Nonnull disp;
lv_indev_t* _Nullable touch_indev;
} Lvgl;
#ifdef __cplusplus
}
#endif
-14
View File
@@ -1,14 +0,0 @@
#pragma once
#include "devices.h"
#include "graphics.h"
#ifdef __cplusplus
extern "C" {
#endif
Lvgl tt_graphics_init(Hardware _Nonnull* hardware);
#ifdef __cplusplus
}
#endif
-66
View File
@@ -1,66 +0,0 @@
#include "partitions.h"
#include "esp_spiffs.h"
#include "log.h"
#include "nvs_flash.h"
static const char* TAG = "filesystem";
static esp_err_t nvs_flash_init_safely() {
esp_err_t result = nvs_flash_init();
if (result == ESP_ERR_NVS_NO_FREE_PAGES || result == ESP_ERR_NVS_NEW_VERSION_FOUND) {
ESP_ERROR_CHECK(nvs_flash_erase());
result = nvs_flash_init();
}
return result;
}
static esp_err_t spiffs_init(esp_vfs_spiffs_conf_t* conf) {
esp_err_t ret = esp_vfs_spiffs_register(conf);
if (ret != ESP_OK) {
if (ret == ESP_FAIL) {
TT_LOG_E(TAG, "Failed to mount or format filesystem %s", conf->base_path);
} else if (ret == ESP_ERR_NOT_FOUND) {
TT_LOG_E(TAG, "Failed to find SPIFFS partition %s", conf->base_path);
} else {
TT_LOG_E(TAG, "Failed to initialize SPIFFS %s (%s)", conf->base_path, esp_err_to_name(ret));
}
return ESP_FAIL;
}
size_t total = -1, used = 0;
ret = esp_spiffs_info(NULL, &total, &used);
if (ret != ESP_OK) {
TT_LOG_E(TAG, "Failed to get SPIFFS partition information for %s (%s)", conf->base_path, esp_err_to_name(ret));
} else {
TT_LOG_I(TAG, "Partition size for %s: total: %d, used: %d", conf->base_path, total, used);
}
return ESP_OK;
}
esp_err_t tt_partitions_init() {
ESP_ERROR_CHECK(nvs_flash_init_safely());
esp_vfs_spiffs_conf_t assets_spiffs = {
.base_path = MOUNT_POINT_ASSETS,
.partition_label = NULL,
.max_files = 100,
.format_if_mount_failed = false
};
if (spiffs_init(&assets_spiffs) != ESP_OK) {
return ESP_FAIL;
}
esp_vfs_spiffs_conf_t config_spiffs = {
.base_path = MOUNT_POINT_CONFIG,
.partition_label = "config",
.max_files = 100,
.format_if_mount_failed = false
};
if (spiffs_init(&config_spiffs) != ESP_OK) {
return ESP_FAIL;
}
return ESP_OK;
}
-8
View File
@@ -1,8 +0,0 @@
#pragma once
#include "esp_err.h"
#define MOUNT_POINT_ASSETS "/assets"
#define MOUNT_POINT_CONFIG "/config"
esp_err_t tt_partitions_init();
-172
View File
@@ -1,172 +0,0 @@
#include "gui_i.h"
#include "check.h"
#include "core_extra_defines.h"
#include "esp_lvgl_port.h"
#include "kernel.h"
#include "log.h"
#define TAG "gui"
// Forward declarations
void gui_redraw(Gui*);
static int32_t gui_main(void*);
static Gui* gui = NULL;
Gui* gui_alloc() {
Gui* instance = malloc(sizeof(Gui));
memset(instance, 0, sizeof(Gui));
tt_check(instance != NULL);
instance->thread = tt_thread_alloc_ex(
"gui",
4096, // Last known minimum was 2800 for launching desktop
&gui_main,
NULL
);
instance->mutex = tt_mutex_alloc(MutexTypeRecursive);
instance->keyboard = NULL;
tt_check(lvgl_port_lock(100));
instance->lvgl_parent = lv_scr_act();
lvgl_port_unlock();
return instance;
}
void gui_free(Gui* instance) {
tt_assert(instance != NULL);
tt_thread_free(instance->thread);
tt_mutex_free(instance->mutex);
free(instance);
}
void gui_lock() {
tt_assert(gui);
tt_assert(gui->mutex);
tt_check(tt_mutex_acquire(gui->mutex, 1000 / portTICK_PERIOD_MS) == TtStatusOk);
}
void gui_unlock() {
tt_assert(gui);
tt_assert(gui->mutex);
tt_check(tt_mutex_release(gui->mutex) == TtStatusOk);
}
void gui_request_draw() {
tt_assert(gui);
ThreadId thread_id = tt_thread_get_id(gui->thread);
tt_thread_flags_set(thread_id, GUI_THREAD_FLAG_DRAW);
}
void gui_show_app(App app, ViewPortShowCallback on_show, ViewPortHideCallback on_hide) {
gui_lock();
tt_check(gui->app_view_port == NULL);
gui->app_view_port = view_port_alloc(app, on_show, on_hide);
gui_unlock();
gui_request_draw();
}
void gui_keyboard_show(lv_obj_t* textarea) {
if (gui->keyboard) {
gui_lock();
lv_obj_clear_flag(gui->keyboard, LV_OBJ_FLAG_HIDDEN);
lv_keyboard_set_textarea(gui->keyboard, textarea);
if (gui->toolbar) {
lv_obj_add_flag(gui->toolbar, LV_OBJ_FLAG_HIDDEN);
}
gui_unlock();
}
}
void gui_keyboard_hide() {
if (gui->keyboard) {
gui_lock();
lv_obj_add_flag(gui->keyboard, LV_OBJ_FLAG_HIDDEN);
if (gui->toolbar) {
lv_obj_clear_flag(gui->toolbar, LV_OBJ_FLAG_HIDDEN);
}
gui_unlock();
}
}
void gui_hide_app() {
gui_lock();
ViewPort* view_port = gui->app_view_port;
tt_check(view_port != NULL);
// We must lock the LVGL port, because the viewport hide callbacks
// might call LVGL APIs (e.g. to remove the keyboard from the screen root)
tt_check(lvgl_port_lock(1000));
view_port_hide(view_port);
lvgl_port_unlock();
view_port_free(view_port);
gui->app_view_port = NULL;
gui_unlock();
}
static int32_t gui_main(void* p) {
UNUSED(p);
tt_check(gui);
Gui* local_gui = gui;
while (1) {
uint32_t flags = tt_thread_flags_wait(
GUI_THREAD_FLAG_ALL,
TtFlagWaitAny,
TtWaitForever
);
// Process and dispatch draw call
if (flags & GUI_THREAD_FLAG_DRAW) {
tt_thread_flags_clear(GUI_THREAD_FLAG_DRAW);
gui_redraw(local_gui);
}
if (flags & GUI_THREAD_FLAG_EXIT) {
tt_thread_flags_clear(GUI_THREAD_FLAG_EXIT);
break;
}
}
return 0;
}
// region AppManifest
static void gui_start(Service service) {
UNUSED(service);
gui = gui_alloc();
tt_thread_set_priority(gui->thread, ThreadPriorityNormal);
tt_thread_start(gui->thread);
}
static void gui_stop(Service service) {
UNUSED(service);
gui_lock();
ThreadId thread_id = tt_thread_get_id(gui->thread);
tt_thread_flags_set(thread_id, GUI_THREAD_FLAG_EXIT);
tt_thread_join(gui->thread);
gui_unlock();
gui_free(gui);
}
const ServiceManifest gui_service = {
.id = "gui",
.on_start = &gui_start,
.on_stop = &gui_stop
};
// endregion
@@ -1,23 +0,0 @@
#pragma once
#include "app.h"
#include "service_manifest.h"
#include "view_port.h"
#ifdef __cplusplus
extern "C" {
#endif
typedef struct Gui Gui;
void gui_show_app(App app, ViewPortShowCallback on_show, ViewPortHideCallback on_hide);
void gui_hide_app();
void gui_keyboard_show(lv_obj_t* textarea);
void gui_keyboard_hide();
#ifdef __cplusplus
}
#endif
@@ -1,83 +0,0 @@
#include "check.h"
#include "esp_lvgl_port.h"
#include "gui_i.h"
#include "log.h"
#include "services/gui/widgets/statusbar.h"
#include "services/loader/loader.h"
#include "ui/spacer.h"
#include "ui/style.h"
#include "ui/toolbar.h"
#define TAG "gui"
static lv_obj_t* create_app_views(Gui* gui, lv_obj_t* parent, App app) {
tt_lv_obj_set_style_bg_blacken(parent);
lv_obj_t* vertical_container = lv_obj_create(parent);
lv_obj_set_size(vertical_container, LV_PCT(100), LV_PCT(100));
lv_obj_set_flex_flow(vertical_container, LV_FLEX_FLOW_COLUMN);
tt_lv_obj_set_style_no_padding(vertical_container);
tt_lv_obj_set_style_bg_blacken(vertical_container);
// TODO: Move statusbar into separate ViewPort
AppFlags flags = tt_app_get_flags(app);
if (flags.show_statusbar) {
tt_lv_statusbar_create(vertical_container);
}
gui->toolbar = NULL;
if (flags.show_toolbar) {
const AppManifest* manifest = tt_app_get_manifest(app);
if (manifest != NULL) {
// TODO: Keep toolbar on app level so app can update it (app_set_toolbar() etc?)
Toolbar toolbar = {
.nav_action = &loader_stop_app,
.nav_icon = LV_SYMBOL_CLOSE,
.title = manifest->name
};
lv_obj_t* toolbar_widget = tt_lv_toolbar_create(vertical_container, &toolbar);
lv_obj_set_pos(toolbar_widget, 0, STATUSBAR_HEIGHT);
// Black area between toolbar and content below
lv_obj_t* spacer = tt_lv_spacer_create(vertical_container, 1, 2);
tt_lv_obj_set_style_bg_blacken(spacer);
gui->toolbar = toolbar_widget;
}
}
lv_obj_t* child_container = lv_obj_create(vertical_container);
lv_obj_set_width(child_container, LV_PCT(100));
lv_obj_set_flex_grow(child_container, 1);
gui->keyboard = lv_keyboard_create(vertical_container);
lv_obj_add_flag(gui->keyboard, LV_OBJ_FLAG_HIDDEN);
return child_container;
}
void gui_redraw(Gui* gui) {
tt_assert(gui);
// Lock GUI and LVGL
gui_lock();
if (lvgl_port_lock(1000)) {
lv_obj_clean(gui->lvgl_parent);
ViewPort* view_port = gui->app_view_port;
if (view_port != NULL) {
App app = view_port->app;
lv_obj_t* container = create_app_views(gui, gui->lvgl_parent, app);
view_port_show(view_port, container);
} else {
TT_LOG_W(TAG, "nothing to draw");
}
// Unlock GUI and LVGL
lvgl_port_unlock();
} else {
TT_LOG_E(TAG, "failed to lock lvgl");
}
gui_unlock();
}
@@ -1,42 +0,0 @@
#pragma once
#include "gui.h"
#include "message_queue.h"
#include "mutex.h"
#include "pubsub.h"
#include "view_port.h"
#include "view_port_i.h"
#include <stdio.h>
#define GUI_THREAD_FLAG_DRAW (1 << 0)
#define GUI_THREAD_FLAG_INPUT (1 << 1)
#define GUI_THREAD_FLAG_EXIT (1 << 2)
#define GUI_THREAD_FLAG_ALL (GUI_THREAD_FLAG_DRAW | GUI_THREAD_FLAG_INPUT | GUI_THREAD_FLAG_EXIT)
/** Gui structure */
struct Gui {
// Thread and lock
Thread* thread;
Mutex* mutex;
// Layers and Canvas
lv_obj_t* lvgl_parent;
// App-specific
ViewPort* app_view_port;
lv_obj_t* _Nullable toolbar;
lv_obj_t* _Nullable keyboard;
};
/** Update GUI, request redraw
*/
void gui_request_draw();
/** Lock GUI
*/
void gui_lock();
/** Unlock GUI
*/
void gui_unlock();
@@ -1,13 +0,0 @@
#include "icon_i.h"
uint8_t icon_get_width(const Icon* instance) {
return instance->width;
}
uint8_t icon_get_height(const Icon* instance) {
return instance->height;
}
const uint8_t* icon_get_data(const Icon* instance) {
return instance->frames[0];
}
@@ -1,11 +0,0 @@
#pragma once
#include <stdio.h>
struct Icon {
const uint8_t width;
const uint8_t height;
const uint8_t frame_count;
const uint8_t frame_rate;
const uint8_t* const* frames;
};
@@ -1,10 +0,0 @@
#pragma once
#include "icon.h"
typedef struct {
const uint8_t width;
const uint8_t height;
const uint8_t frame_count;
const uint8_t frame_rate;
const uint8_t* const* frames;
} Icon;
@@ -1,40 +0,0 @@
#include "view_port.h"
#include "check.h"
#include "ui/style.h"
#include "view_port_i.h"
#define TAG "viewport"
ViewPort* view_port_alloc(
App app,
ViewPortShowCallback on_show,
ViewPortHideCallback on_hide
) {
ViewPort* view_port = malloc(sizeof(ViewPort));
view_port->app = app;
view_port->on_show = on_show;
view_port->on_hide = on_hide;
return view_port;
}
void view_port_free(ViewPort* view_port) {
tt_assert(view_port);
free(view_port);
}
void view_port_show(ViewPort* view_port, lv_obj_t* parent) {
tt_assert(view_port);
tt_assert(parent);
if (view_port->on_show) {
tt_lv_obj_set_style_no_padding(parent);
view_port->on_show(view_port->app, parent);
}
}
void view_port_hide(ViewPort* view_port) {
tt_assert(view_port);
if (view_port->on_hide) {
view_port->on_hide(view_port->app);
}
}
@@ -1,50 +0,0 @@
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
#include "app.h"
#include "lvgl.h"
/** ViewPort Draw callback
* @warning called from GUI thread
*/
typedef void (*ViewPortShowCallback)(App app, lv_obj_t* parent);
typedef void (*ViewPortHideCallback)(App app);
// TODO: Move internally, use handle publicly
typedef struct {
App app;
ViewPortShowCallback on_show;
ViewPortHideCallback _Nullable on_hide;
bool app_toolbar;
} ViewPort;
/** ViewPort allocator
*
* always returns view_port or stops system if not enough memory.
* @param app
* @param on_show Called to create LVGL widgets
* @param on_hide Called before clearing the LVGL widget parent
*
* @return ViewPort instance
*/
ViewPort* view_port_alloc(
App app,
ViewPortShowCallback on_show,
ViewPortHideCallback on_hide
);
/** ViewPort deallocator
*
* Ensure that view_port was unregistered in GUI system before use.
*
* @param view_port ViewPort instance
*/
void view_port_free(ViewPort* view_port);
#ifdef __cplusplus
}
#endif
@@ -1,19 +0,0 @@
#pragma once
#include "view_port.h"
/** Process draw call. Calls on_show callback.
* To be used by GUI, called on redraw.
*
* @param view_port ViewPort instance
* @param canvas canvas to draw at
*/
void view_port_show(ViewPort* view_port, lv_obj_t* parent);
/**
* Process draw clearing call. Calls on_hdie callback.
* To be used by GUI, called on redraw.
*
* @param view_port
*/
void view_port_hide(ViewPort* view_port);
@@ -1,27 +0,0 @@
#include "statusbar.h"
#include "ui/spacer.h"
#include "ui/style.h"
lv_obj_t* tt_lv_statusbar_create(lv_obj_t* parent) {
lv_obj_t* wrapper = lv_obj_create(parent);
lv_obj_set_width(wrapper, LV_PCT(100));
lv_obj_set_height(wrapper, STATUSBAR_HEIGHT);
tt_lv_obj_set_style_no_padding(wrapper);
tt_lv_obj_set_style_bg_blacken(wrapper);
lv_obj_center(wrapper);
lv_obj_set_flex_flow(wrapper, LV_FLEX_FLOW_ROW);
lv_obj_t* left_spacer = tt_lv_spacer_create(wrapper, 1, 1);
lv_obj_set_flex_grow(left_spacer, 1);
lv_obj_t* wifi = lv_img_create(wrapper);
lv_obj_set_size(wifi, STATUSBAR_ICON_SIZE, STATUSBAR_ICON_SIZE);
tt_lv_obj_set_style_no_padding(wifi);
tt_lv_obj_set_style_bg_blacken(wifi);
lv_obj_set_style_img_recolor(wifi, lv_color_white(), 0);
lv_obj_set_style_img_recolor_opa(wifi, 255, 0);
lv_img_set_src(wifi, "A:/assets/ic_small_wifi_off.png");
return wrapper;
}
@@ -1,16 +0,0 @@
#pragma once
#include "lvgl.h"
#ifdef __cplusplus
extern "C" {
#endif
#define STATUSBAR_ICON_SIZE 18
#define STATUSBAR_HEIGHT (STATUSBAR_ICON_SIZE + 4) // 4 extra pixels for border and outline
lv_obj_t* tt_lv_statusbar_create(lv_obj_t* parent);
#ifdef __cplusplus
}
#endif
@@ -1,321 +0,0 @@
#include "app_i.h"
#include "app_manifest.h"
#include "app_manifest_registry.h"
#include "esp_heap_caps.h"
#include "freertos/FreeRTOS.h"
#include "freertos/semphr.h"
#include "loader_i.h"
#include "service_manifest.h"
#include "services/gui/gui.h"
#include <sys/cdefs.h>
#define TAG "Loader"
// Forward declarations
static int32_t loader_main(void* p);
static Loader* loader_singleton = NULL;
static Loader* loader_alloc() {
tt_check(loader_singleton == NULL);
loader_singleton = malloc(sizeof(Loader));
loader_singleton->pubsub = tt_pubsub_alloc();
loader_singleton->queue = tt_message_queue_alloc(1, sizeof(LoaderMessage));
loader_singleton->thread = tt_thread_alloc_ex(
"loader",
4096, // Last known minimum was 2400 for starting Hello World app
&loader_main,
NULL
);
loader_singleton->mutex = xSemaphoreCreateRecursiveMutex();
loader_singleton->app_stack_index = -1;
memset(loader_singleton->app_stack, 0, sizeof(App) * APP_STACK_SIZE);
return loader_singleton;
}
static void loader_free() {
tt_check(loader_singleton != NULL);
tt_thread_free(loader_singleton->thread);
tt_pubsub_free(loader_singleton->pubsub);
tt_message_queue_free(loader_singleton->queue);
tt_mutex_free(loader_singleton->mutex);
free(loader_singleton);
}
void loader_lock() {
tt_assert(loader_singleton);
tt_assert(loader_singleton->mutex);
tt_check(xSemaphoreTakeRecursive(loader_singleton->mutex, portMAX_DELAY) == pdPASS);
}
void loader_unlock() {
tt_assert(loader_singleton);
tt_assert(loader_singleton->mutex);
tt_check(xSemaphoreGiveRecursive(loader_singleton->mutex) == pdPASS);
}
LoaderStatus loader_start_app(const char* id, bool blocking, Bundle* _Nullable bundle) {
LoaderMessageLoaderStatusResult result = {
.value = LoaderStatusOk
};
LoaderMessage message = {
.type = LoaderMessageTypeAppStart,
.start.id = id,
.start.bundle = bundle,
.status_value = &result,
.api_lock = blocking ? tt_api_lock_alloc_locked() : NULL
};
tt_message_queue_put(loader_singleton->queue, &message, TtWaitForever);
if (blocking) {
tt_api_lock_wait_unlock_and_free(message.api_lock);
}
return result.value;
}
void loader_stop_app() {
LoaderMessage message = {.type = LoaderMessageTypeAppStop};
tt_message_queue_put(loader_singleton->queue, &message, TtWaitForever);
}
App _Nullable loader_get_current_app() {
loader_lock();
App app = (loader_singleton->app_stack_index >= 0)
? loader_singleton->app_stack[loader_singleton->app_stack_index]
: NULL;
loader_unlock();
return app;
}
PubSub* loader_get_pubsub() {
tt_assert(loader_singleton);
// it's safe to return pubsub without locking
// because it's never freed and loader is never exited
// also the loader instance cannot be obtained until the pubsub is created
return loader_singleton->pubsub;
}
static const char* app_state_to_string(AppState state) {
switch (state) {
case APP_STATE_INITIAL:
return "initial";
case APP_STATE_STARTED:
return "started";
case APP_STATE_SHOWING:
return "showing";
case APP_STATE_HIDING:
return "hiding";
case APP_STATE_STOPPED:
return "stopped";
default:
return "?";
}
}
static void app_transition_to_state(App app, AppState state) {
const AppManifest* manifest = tt_app_get_manifest(app);
const AppState old_state = tt_app_get_state(app);
TT_LOG_I(
TAG,
"app \"%s\" state: %s -> %s",
manifest->id,
app_state_to_string(old_state),
app_state_to_string(state)
);
switch (state) {
case APP_STATE_INITIAL:
tt_app_set_state(app, APP_STATE_INITIAL);
break;
case APP_STATE_STARTED:
if (manifest->on_start != NULL) {
manifest->on_start(app);
}
tt_app_set_state(app, APP_STATE_STARTED);
break;
case APP_STATE_SHOWING:
gui_show_app(
app,
manifest->on_show,
manifest->on_hide
);
tt_app_set_state(app, APP_STATE_SHOWING);
break;
case APP_STATE_HIDING:
gui_hide_app();
tt_app_set_state(app, APP_STATE_HIDING);
break;
case APP_STATE_STOPPED:
if (manifest->on_stop) {
manifest->on_stop(app);
}
tt_app_set_data(app, NULL);
tt_app_set_state(app, APP_STATE_STOPPED);
break;
}
}
LoaderStatus loader_do_start_app_with_manifest(
const AppManifest* _Nonnull manifest,
Bundle* _Nullable bundle
) {
TT_LOG_I(TAG, "start with manifest %s", manifest->id);
loader_lock();
if (loader_singleton->app_stack_index >= (APP_STACK_SIZE - 1)) {
TT_LOG_E(TAG, "failed to start app: stack limit of %d reached", APP_STACK_SIZE);
return LoaderStatusErrorInternal;
}
int8_t previous_index = loader_singleton->app_stack_index;
loader_singleton->app_stack_index++;
App app = tt_app_alloc(manifest, bundle);
tt_assert(loader_singleton->app_stack[loader_singleton->app_stack_index] == NULL);
loader_singleton->app_stack[loader_singleton->app_stack_index] = app;
app_transition_to_state(app, APP_STATE_INITIAL);
app_transition_to_state(app, APP_STATE_STARTED);
// We might have to hide the previous app first
if (previous_index != -1) {
App previous_app = loader_singleton->app_stack[previous_index];
app_transition_to_state(previous_app, APP_STATE_HIDING);
}
app_transition_to_state(app, APP_STATE_SHOWING);
loader_unlock();
LoaderEvent event = {.type = LoaderEventTypeApplicationStarted};
tt_pubsub_publish(loader_singleton->pubsub, &event);
return LoaderStatusOk;
}
static LoaderStatus loader_do_start_by_id(
const char* id,
Bundle* _Nullable bundle
) {
TT_LOG_I(TAG, "Start by id %s", id);
const AppManifest* manifest = tt_app_manifest_registry_find_by_id(id);
if (manifest == NULL) {
return LoaderStatusErrorUnknownApp;
} else {
return loader_do_start_app_with_manifest(manifest, bundle);
}
}
static void loader_do_stop_app() {
loader_lock();
int8_t current_app_index = loader_singleton->app_stack_index;
if (current_app_index == -1) {
loader_unlock();
TT_LOG_E(TAG, "Stop app: no app running");
return;
}
if (current_app_index == 0) {
loader_unlock();
TT_LOG_E(TAG, "Stop app: can't stop root app");
return;
}
// Stop current app
App app_to_stop = loader_singleton->app_stack[current_app_index];
app_transition_to_state(app_to_stop, APP_STATE_HIDING);
app_transition_to_state(app_to_stop, APP_STATE_STOPPED);
tt_app_free(app_to_stop);
loader_singleton->app_stack[current_app_index] = NULL;
loader_singleton->app_stack_index--;
TT_LOG_I(TAG, "Free heap: %zu", heap_caps_get_free_size(MALLOC_CAP_INTERNAL));
// Resume previous app
tt_assert(loader_singleton->app_stack[loader_singleton->app_stack_index] != NULL);
App app_to_resume = loader_singleton->app_stack[loader_singleton->app_stack_index];
app_transition_to_state(app_to_resume, APP_STATE_SHOWING);
loader_unlock();
LoaderEvent event = {.type = LoaderEventTypeApplicationStopped};
tt_pubsub_publish(loader_singleton->pubsub, &event);
}
static int32_t loader_main(void* p) {
UNUSED(p);
LoaderMessage message;
bool exit_requested = false;
while (!exit_requested) {
tt_check(loader_singleton != NULL);
if (tt_message_queue_get(loader_singleton->queue, &message, TtWaitForever) == TtStatusOk) {
TT_LOG_I(TAG, "Processing message of type %d", message.type);
switch (message.type) {
case LoaderMessageTypeAppStart:
// TODO: add bundle
message.status_value->value = loader_do_start_by_id(
message.start.id,
message.start.bundle
);
if (message.api_lock) {
tt_api_lock_unlock(message.api_lock);
}
break;
case LoaderMessageTypeAppStop:
loader_do_stop_app();
break;
case LoaderMessageTypeServiceStop:
exit_requested = true;
break;
}
}
}
return 0;
}
// region AppManifest
static void loader_start(Service service) {
UNUSED(service);
tt_check(loader_singleton == NULL);
loader_singleton = loader_alloc();
tt_thread_set_priority(loader_singleton->thread, ThreadPriorityNormal);
tt_thread_start(loader_singleton->thread);
}
static void loader_stop(Service service) {
UNUSED(service);
tt_check(loader_singleton != NULL);
// Send stop signal to thread and wait for thread to finish
LoaderMessage message = {
.api_lock = NULL,
.type = LoaderMessageTypeServiceStop
};
tt_message_queue_put(loader_singleton->queue, &message, TtWaitForever);
tt_thread_join(loader_singleton->thread);
loader_free();
loader_singleton = NULL;
}
const ServiceManifest loader_service = {
.id = "loader",
.on_start = &loader_start,
.on_stop = &loader_stop
};
// endregion
@@ -1,52 +0,0 @@
#pragma once
#include "app_manifest.h"
#include "bundle.h"
#include "pubsub.h"
#include "service_manifest.h"
#include "tactility_core.h"
#ifdef __cplusplus
extern "C" {
#endif
typedef struct Loader Loader;
typedef enum {
LoaderStatusOk,
LoaderStatusErrorAppStarted,
LoaderStatusErrorUnknownApp,
LoaderStatusErrorInternal,
} LoaderStatus;
typedef enum {
LoaderEventTypeApplicationStarted,
LoaderEventTypeApplicationStopped
} LoaderEventType;
typedef struct {
LoaderEventType type;
} LoaderEvent;
/**
* @brief Close any running app, then start new one. Blocking.
* @param[in] id application name or id
* @param[in] blocking application arguments
* @param[in] bundle optional bundle. Ownership is transferred to Loader.
* @return LoaderStatus
*/
LoaderStatus loader_start_app(const char* id, bool blocking, Bundle* _Nullable bundle);
void loader_stop_app();
App _Nullable loader_get_current_app();
/**
* @brief Get loader pubsub
* @return PubSub*
*/
PubSub* loader_get_pubsub();
#ifdef __cplusplus
}
#endif
@@ -1,58 +0,0 @@
#pragma once
#include "api_lock.h"
#include "app_manifest.h"
#include "freertos/FreeRTOS.h"
#include "freertos/semphr.h"
#include "loader.h"
#include "message_queue.h"
#include "pubsub.h"
#include "services/gui/view_port.h"
#include "thread.h"
#define APP_STACK_SIZE 32
struct Loader {
Thread* thread;
PubSub* pubsub;
MessageQueue* queue;
// TODO: replace with Mutex
SemaphoreHandle_t mutex;
int8_t app_stack_index;
App app_stack[APP_STACK_SIZE];
};
typedef enum {
LoaderMessageTypeAppStart,
LoaderMessageTypeAppStop,
LoaderMessageTypeServiceStop,
} LoaderMessageType;
typedef struct {
const char* id;
Bundle* _Nullable bundle;
} LoaderMessageAppStart;
typedef struct {
LoaderStatus value;
} LoaderMessageLoaderStatusResult;
typedef struct {
bool value;
} LoaderMessageBoolResult;
typedef struct {
// This lock blocks anyone from starting an app as long
// as an app is already running via loader_start()
ApiLock api_lock;
LoaderMessageType type;
union {
LoaderMessageAppStart start;
};
union {
LoaderMessageLoaderStatusResult* status_value;
LoaderMessageBoolResult* bool_value;
};
} LoaderMessage;
@@ -1,101 +0,0 @@
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
#include "pubsub.h"
#include <stdbool.h>
#include <stdio.h>
#ifdef ESP_PLATFORM
#include "esp_wifi.h"
#endif
typedef enum {
/** Radio was turned on */
WifiEventTypeRadioStateOn,
/** Radio is turning on. */
WifiEventTypeRadioStateOnPending,
/** Radio is turned off */
WifiEventTypeRadioStateOff,
/** Radio is turning off */
WifiEventTypeRadioStateOffPending,
/** Started scanning for access points */
WifiEventTypeScanStarted,
/** Finished scanning for access points */ // TODO: 1 second validity
WifiEventTypeScanFinished,
WifiEventTypeDisconnected,
WifiEventTypeConnectionPending,
WifiEventTypeConnectionSuccess,
WifiEventTypeConnectionFailed
} WifiEventType;
typedef enum {
WIFI_RADIO_ON_PENDING,
WIFI_RADIO_ON,
WIFI_RADIO_CONNECTION_PENDING,
WIFI_RADIO_CONNECTION_ACTIVE,
WIFI_RADIO_OFF_PENDING,
WIFI_RADIO_OFF
} WifiRadioState;
typedef struct {
WifiEventType type;
} WifiEvent;
typedef struct {
uint8_t ssid[33];
int8_t rssi;
wifi_auth_mode_t auth_mode;
} WifiApRecord;
/**
* @brief Get wifi pubsub
* @return PubSub*
*/
PubSub* wifi_get_pubsub();
WifiRadioState wifi_get_radio_state();
/**
* @brief Request scanning update. Returns immediately. Results are through pubsub.
*/
void wifi_scan();
bool wifi_is_scanning();
/**
* @brief Returns the access points from the last scan (if any). It only contains public APs.
* @param records the allocated buffer to store the records in
* @param limit the maximum amount of records to store
*/
void wifi_get_scan_results(WifiApRecord records[], uint16_t limit, uint16_t* result_count);
/**
* @brief Overrides the default scan result size of 16.
* @param records the record limit for the scan result (84 bytes per record!)
*/
void wifi_set_scan_records(uint16_t records);
/**
* @brief Enable/disable the radio. Ignores input if desired state matches current state.
* @param enabled
*/
void wifi_set_enabled(bool enabled);
/**
* @brief Connect to a network. Disconnects any existing connection.
* Returns immediately but runs in the background. Results are through pubsub.
* @param ssid
* @param password
*/
void wifi_connect(const char* ssid, const char _Nullable password[64]);
/**
* @brief Disconnect from the access point. Doesn't have any effect when not connected.
*/
void wifi_disconnect();
#ifdef __cplusplus
}
#endif
@@ -1,25 +0,0 @@
#pragma once
#include <stdbool.h>
#ifdef __cplusplus
extern "C" {
#endif
#define TT_WIFI_CREDENTIALS_PASSWORD_LIMIT 64 // Should be equal to wifi_sta_config_t.password
// TODO: Move to config file
#define TT_WIFI_CREDENTIALS_LIMIT 16
void tt_wifi_credentials_init();
bool tt_wifi_credentials_contains(const char* ssid);
bool tt_wifi_credentials_get(const char* ssid, char password[TT_WIFI_CREDENTIALS_PASSWORD_LIMIT]);
bool tt_wifi_credentials_set(const char* ssid, char password[TT_WIFI_CREDENTIALS_PASSWORD_LIMIT]);
bool tt_wifi_credentials_remove(const char* ssid);
#ifdef __cplusplus
}
#endif
@@ -1,240 +0,0 @@
#ifdef ESP_PLATFORM
#include "wifi_credentials.h"
#include "nvs_flash.h"
#include "log.h"
#include "hash.h"
#include "check.h"
#include "mutex.h"
#include "secure.h"
#define TAG "wifi_credentials"
#define TT_NVS_NAMESPACE "tt_wifi_cred" // limited by NVS_KEY_NAME_MAX_SIZE
#define TT_NVS_PARTITION "nvs"
static void tt_wifi_credentials_mutex_lock();
static void tt_wifi_credentials_mutex_unlock();
// region Hash
static Mutex* hash_mutex = NULL;
static int8_t ssid_hash_index = -1;
static uint32_t ssid_hashes[TT_WIFI_CREDENTIALS_LIMIT] = { 0 };
static int hash_find_value(uint32_t hash) {
tt_wifi_credentials_mutex_lock();
for (int i = 0; i < ssid_hash_index; ++i) {
if (ssid_hashes[i] == hash) {
return i;
}
}
tt_wifi_credentials_mutex_unlock();
return -1;
}
static int hash_find_string(const char* ssid) {
uint32_t hash = tt_hash_string_djb2(ssid);
return hash_find_value(hash);
}
static bool hash_contains_string(const char* ssid) {
return hash_find_string(ssid) != -1;
}
static bool hash_contains_value(uint32_t value) {
return hash_find_value(value) != -1;
}
static void hash_add(const char* ssid) {
uint32_t hash = tt_hash_string_djb2(ssid);
if (!hash_contains_value(hash)) {
tt_wifi_credentials_mutex_lock();
tt_check((ssid_hash_index + 1) < TT_WIFI_CREDENTIALS_LIMIT, "exceeding wifi credentials list size");
ssid_hash_index++;
ssid_hashes[ssid_hash_index] = hash;
tt_wifi_credentials_mutex_unlock();
}
}
static void hash_reset_all() {
ssid_hash_index = -1;
}
// endregion Hash
// region Wi-Fi Credentials - static
static void tt_wifi_credentials_mutex_lock() {
tt_mutex_acquire(hash_mutex, TtWaitForever);
}
static void tt_wifi_credentials_mutex_unlock() {
tt_mutex_release(hash_mutex);
}
static esp_err_t tt_wifi_credentials_nvs_open(nvs_handle_t* handle, nvs_open_mode_t mode) {
return nvs_open(TT_NVS_NAMESPACE, NVS_READWRITE, handle);
}
static void tt_wifi_credentials_nvs_close(nvs_handle_t handle) {
nvs_close(handle);
}
static bool tt_wifi_credentials_contains_in_flash(const char* ssid) {
nvs_handle_t handle;
esp_err_t result = tt_wifi_credentials_nvs_open(&handle, NVS_READONLY);
if (result != ESP_OK) {
TT_LOG_E(TAG, "Failed to open NVS handle: %s", esp_err_to_name(result));
return false;
}
hash_reset_all();
nvs_iterator_t iterator;
result = nvs_entry_find(TT_NVS_PARTITION, TT_NVS_NAMESPACE, NVS_TYPE_BLOB, &iterator);
bool contains_ssid = false;
while (result == ESP_OK) {
nvs_entry_info_t info;
nvs_entry_info(iterator, &info); // Can omit error check if parameters are guaranteed to be non-NULL
if (strcmp(info.key, ssid) == 0) {
contains_ssid = true;
break;
}
result = nvs_entry_next(&iterator);
}
nvs_release_iterator(iterator);
tt_wifi_credentials_nvs_close(handle);
return contains_ssid;
}
// endregion Wi-Fi Credentials - static
// region Wi-Fi Credentials - public
bool tt_wifi_credentials_contains(const char* ssid) {
uint32_t hash = tt_hash_string_djb2(ssid);
if (hash_contains_value(hash)) {
return tt_wifi_credentials_contains_in_flash(ssid);
} else {
return false;
}
}
void tt_wifi_credentials_init() {
hash_reset_all();
if (hash_mutex == NULL) {
hash_mutex = tt_mutex_alloc(MutexTypeRecursive);
}
nvs_handle_t handle;
esp_err_t result = tt_wifi_credentials_nvs_open(&handle, NVS_READWRITE);
if (result != ESP_OK) {
TT_LOG_E(TAG, "Failed to open NVS handle for init: %s", esp_err_to_name(result));
return;
}
nvs_iterator_t iterator;
result = nvs_entry_find(TT_NVS_PARTITION, TT_NVS_NAMESPACE, NVS_TYPE_BLOB, &iterator);
while (result == ESP_OK) {
nvs_entry_info_t info;
nvs_entry_info(iterator, &info); // Can omit error check if parameters are guaranteed to be non-NULL
hash_add(info.key);
result = nvs_entry_next(&iterator);
}
nvs_release_iterator(iterator);
tt_wifi_credentials_nvs_close(handle);
}
bool tt_wifi_credentials_get(const char* ssid, char password[TT_WIFI_CREDENTIALS_PASSWORD_LIMIT]) {
nvs_handle_t handle;
esp_err_t result = tt_wifi_credentials_nvs_open(&handle, NVS_READONLY);
if (result != ESP_OK) {
TT_LOG_E(TAG, "Failed to open NVS handle: %s", esp_err_to_name(result));
return false;
}
char password_encrypted[TT_WIFI_CREDENTIALS_PASSWORD_LIMIT];
size_t length = TT_WIFI_CREDENTIALS_PASSWORD_LIMIT;
result = nvs_get_blob(handle, ssid, password_encrypted, &length);
uint8_t iv[16];
tt_secure_get_iv_from_string(ssid, iv);
int decrypt_result = tt_secure_decrypt(
iv,
(uint8_t*)password_encrypted,
(uint8_t*)password,
TT_WIFI_CREDENTIALS_PASSWORD_LIMIT
);
if (decrypt_result != 0) {
result = ESP_FAIL;
TT_LOG_E(TAG, "Failed to decrypt credentials for \"%s\": %d", ssid, decrypt_result);
}
if (result != ESP_OK && result != ESP_ERR_NVS_NOT_FOUND) {
TT_LOG_E(TAG, "Failed to get credentials for \"%s\": %s", ssid, esp_err_to_name(result));
}
tt_wifi_credentials_nvs_close(handle);
return result == ESP_OK;
}
bool tt_wifi_credentials_set(const char* ssid, char password[TT_WIFI_CREDENTIALS_PASSWORD_LIMIT]) {
nvs_handle_t handle;
esp_err_t result = tt_wifi_credentials_nvs_open(&handle, NVS_READWRITE);
if (result != ESP_OK) {
TT_LOG_E(TAG, "Failed to open NVS handle: %s", esp_err_to_name(result));
return false;
}
char password_encrypted[TT_WIFI_CREDENTIALS_PASSWORD_LIMIT];
uint8_t iv[16];
tt_secure_get_iv_from_string(ssid, iv);
int encrypt_result = tt_secure_encrypt(
iv,
(uint8_t*)password,
(uint8_t*)password_encrypted,
TT_WIFI_CREDENTIALS_PASSWORD_LIMIT
);
if (encrypt_result != 0) {
result = ESP_FAIL;
TT_LOG_E(TAG, "Failed to encrypt credentials for \"%s\": %d", ssid, encrypt_result);
}
if (result == ESP_OK) {
result = nvs_set_blob(handle, ssid, password_encrypted, TT_WIFI_CREDENTIALS_PASSWORD_LIMIT);
if (result != ESP_OK) {
TT_LOG_E(TAG, "Failed to get credentials for \"%s\": %s", ssid, esp_err_to_name(result));
}
}
tt_wifi_credentials_nvs_close(handle);
return result == ESP_OK;
}
bool tt_wifi_credentials_remove(const char* ssid) {
nvs_handle_t handle;
esp_err_t result = tt_wifi_credentials_nvs_open(&handle, NVS_READWRITE);
if (result != ESP_OK) {
TT_LOG_E(TAG, "Failed to open NVS handle to store \"%s\": %s", ssid, esp_err_to_name(result));
return false;
}
result = nvs_erase_key(handle, ssid);
if (result != ESP_OK) {
TT_LOG_E(TAG, "Failed to erase credentials for \"%s\": %s", ssid, esp_err_to_name(result));
}
tt_wifi_credentials_nvs_close(handle);
return result == ESP_OK;
}
// end region Wi-Fi Credentials - public
#endif
@@ -1,37 +0,0 @@
#ifndef ESP_PLATFORM
#include "wifi_credentials.h"
#include "log.h"
#define TAG "wifi_credentials"
// region Wi-Fi Credentials - public
bool tt_wifi_credentials_contains(const char* ssid) {
// TODO: Implement
return false;
}
void tt_wifi_credentials_init() {
TT_LOG_W(TAG, "init mock implementation");
}
bool tt_wifi_credentials_get(const char* ssid, char password[TT_WIFI_CREDENTIALS_PASSWORD_LIMIT]) {
// TODO: Implement
return false;
}
bool tt_wifi_credentials_set(const char* ssid, char password[TT_WIFI_CREDENTIALS_PASSWORD_LIMIT]) {
// TODO: Implement
return false;
}
bool tt_wifi_credentials_remove(const char* ssid) {
// TODO: Implement
return false;
}
// end region Wi-Fi Credentials - public
#endif
@@ -1,596 +0,0 @@
#ifdef ESP_PLATFORM
#include "wifi.h"
#include "check.h"
#include "freertos/FreeRTOS.h"
#include "freertos/event_groups.h"
#include "log.h"
#include "message_queue.h"
#include "mutex.h"
#include "pubsub.h"
#include "service.h"
#include <sys/cdefs.h>
#define TAG "wifi"
#define WIFI_SCAN_RECORD_LIMIT 16 // default, can be overridden
#define WIFI_CONNECTED_BIT BIT0
#define WIFI_FAIL_BIT BIT1
typedef struct {
/** @brief Locking mechanism for modifying the Wifi instance */
Mutex* mutex;
/** @brief The public event bus */
PubSub* pubsub;
/** @brief The internal message queue */
MessageQueue* queue;
/** @brief The network interface when wifi is started */
esp_netif_t* _Nullable netif;
/** @brief Scanning results */
wifi_ap_record_t* _Nullable scan_list;
/** @brief The current item count in scan_list (-1 when scan_list is NULL) */
uint16_t scan_list_count;
/** @brief Maximum amount of records to scan (value > 0) */
uint16_t scan_list_limit;
bool scan_active;
esp_event_handler_instance_t event_handler_any_id;
esp_event_handler_instance_t event_handler_got_ip;
EventGroupHandle_t event_group;
WifiRadioState radio_state;
} Wifi;
typedef enum {
WifiMessageTypeRadioOn,
WifiMessageTypeRadioOff,
WifiMessageTypeScan,
WifiMessageTypeConnect,
WifiMessageTypeDisconnect
} WifiMessageType;
typedef struct {
uint8_t ssid[32];
uint8_t password[64];
} WifiConnectMessage;
typedef struct {
WifiMessageType type;
union {
WifiConnectMessage connect_message;
};
} WifiMessage;
static Wifi* wifi_singleton = NULL;
// Forward declarations
static void wifi_scan_list_free_safely(Wifi* wifi);
static void wifi_disconnect_internal(Wifi* wifi);
static void wifi_lock(Wifi* wifi);
static void wifi_unlock(Wifi* wifi);
// region Alloc
static Wifi* wifi_alloc() {
Wifi* instance = malloc(sizeof(Wifi));
instance->mutex = tt_mutex_alloc(MutexTypeRecursive);
instance->pubsub = tt_pubsub_alloc();
// TODO: Deal with messages that come in while an action is ongoing
// for example: when scanning and you turn off the radio, the scan should probably stop or turning off
// the radio should disable the on/off button in the app as it is pending.
instance->queue = tt_message_queue_alloc(1, sizeof(WifiMessage));
instance->netif = NULL;
instance->scan_active = false;
instance->scan_list = NULL;
instance->scan_list_count = 0;
instance->scan_list_limit = WIFI_SCAN_RECORD_LIMIT;
instance->event_handler_any_id = NULL;
instance->event_handler_got_ip = NULL;
instance->event_group = xEventGroupCreate();
instance->radio_state = WIFI_RADIO_OFF;
return instance;
}
static void wifi_free(Wifi* instance) {
tt_mutex_free(instance->mutex);
tt_pubsub_free(instance->pubsub);
tt_message_queue_free(instance->queue);
free(instance);
}
// endregion Alloc
// region Public functions
PubSub* wifi_get_pubsub() {
tt_assert(wifi_singleton);
return wifi_singleton->pubsub;
}
WifiRadioState wifi_get_radio_state() {
return wifi_singleton->radio_state;
}
void wifi_scan() {
tt_assert(wifi_singleton);
WifiMessage message = {.type = WifiMessageTypeScan};
// No need to lock for queue
tt_message_queue_put(wifi_singleton->queue, &message, 100 / portTICK_PERIOD_MS);
}
bool wifi_is_scanning() {
tt_assert(wifi_singleton);
return wifi_singleton->scan_active;
}
void wifi_connect(const char* ssid, const char _Nullable password[64]) {
tt_assert(wifi_singleton);
tt_check(strlen(ssid) <= 32);
WifiMessage message = {.type = WifiMessageTypeConnect};
memcpy(message.connect_message.ssid, ssid, 32);
if (password != NULL) {
memcpy(message.connect_message.password, password, 64);
} else {
message.connect_message.password[0] = 0;
}
tt_message_queue_put(wifi_singleton->queue, &message, 100 / portTICK_PERIOD_MS);
}
void wifi_disconnect() {
tt_assert(wifi_singleton);
WifiMessage message = {.type = WifiMessageTypeDisconnect};
tt_message_queue_put(wifi_singleton->queue, &message, 100 / portTICK_PERIOD_MS);
}
void wifi_set_scan_records(uint16_t records) {
tt_assert(wifi_singleton);
if (records != wifi_singleton->scan_list_limit) {
wifi_scan_list_free_safely(wifi_singleton);
wifi_singleton->scan_list_limit = records;
}
}
void wifi_get_scan_results(WifiApRecord records[], uint16_t limit, uint16_t* result_count) {
tt_check(wifi_singleton);
tt_check(result_count);
if (wifi_singleton->scan_list_count == 0) {
*result_count = 0;
} else {
uint16_t i = 0;
TT_LOG_I(TAG, "processing up to %d APs", wifi_singleton->scan_list_count);
uint16_t last_index = MIN(wifi_singleton->scan_list_count, limit);
for (; i < last_index; ++i) {
memcpy(records[i].ssid, wifi_singleton->scan_list[i].ssid, 33);
records[i].rssi = wifi_singleton->scan_list[i].rssi;
records[i].auth_mode = wifi_singleton->scan_list[i].authmode;
}
// The index already overflowed right before the for-loop was terminated,
// so it effectively became the list count:
*result_count = i;
}
}
void wifi_set_enabled(bool enabled) {
if (enabled) {
WifiMessage message = {.type = WifiMessageTypeRadioOn};
// No need to lock for queue
tt_message_queue_put(wifi_singleton->queue, &message, 100 / portTICK_PERIOD_MS);
} else {
WifiMessage message = {.type = WifiMessageTypeRadioOff};
// No need to lock for queue
tt_message_queue_put(wifi_singleton->queue, &message, 100 / portTICK_PERIOD_MS);
}
}
// endregion Public functions
static void wifi_lock(Wifi* wifi) {
tt_crash("this fails for now");
tt_assert(wifi);
tt_assert(wifi->mutex);
tt_check(xSemaphoreTakeRecursive(wifi->mutex, portMAX_DELAY) == pdPASS);
}
static void wifi_unlock(Wifi* wifi) {
tt_assert(wifi);
tt_assert(wifi->mutex);
tt_check(xSemaphoreGiveRecursive(wifi->mutex) == pdPASS);
}
static void wifi_scan_list_alloc(Wifi* wifi) {
tt_check(wifi->scan_list == NULL);
wifi->scan_list = malloc(sizeof(wifi_ap_record_t) * wifi->scan_list_limit);
wifi->scan_list_count = 0;
}
static void wifi_scan_list_alloc_safely(Wifi* wifi) {
if (wifi->scan_list == NULL) {
wifi_scan_list_alloc(wifi);
}
}
static void wifi_scan_list_free(Wifi* wifi) {
tt_check(wifi->scan_list != NULL);
free(wifi->scan_list);
wifi->scan_list = NULL;
wifi->scan_list_count = 0;
}
static void wifi_scan_list_free_safely(Wifi* wifi) {
if (wifi->scan_list != NULL) {
wifi_scan_list_free(wifi);
}
}
static void wifi_publish_event_simple(Wifi* wifi, WifiEventType type) {
WifiEvent turning_on_event = {.type = type};
tt_pubsub_publish(wifi->pubsub, &turning_on_event);
}
static void event_handler(void* arg, esp_event_base_t event_base, int32_t event_id, void* event_data) {
UNUSED(arg);
if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_START) {
TT_LOG_I(TAG, "event_handler: sta start");
if (wifi_singleton->radio_state == WIFI_RADIO_CONNECTION_PENDING) {
esp_wifi_connect();
}
} else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED) {
xEventGroupSetBits(wifi_singleton->event_group, WIFI_FAIL_BIT);
TT_LOG_I(TAG, "event_handler: disconnected");
} else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP) {
ip_event_got_ip_t* event = (ip_event_got_ip_t*)event_data;
TT_LOG_I(TAG, "event_handler: got ip:" IPSTR, IP2STR(&event->ip_info.ip));
xEventGroupSetBits(wifi_singleton->event_group, WIFI_CONNECTED_BIT);
}
}
static void wifi_enable(Wifi* wifi) {
WifiRadioState state = wifi->radio_state;
if (
state == WIFI_RADIO_ON ||
state == WIFI_RADIO_ON_PENDING ||
state == WIFI_RADIO_OFF_PENDING
) {
TT_LOG_W(TAG, "Can't enable from current state");
return;
}
TT_LOG_I(TAG, "Enabling");
wifi->radio_state = WIFI_RADIO_ON_PENDING;
wifi_publish_event_simple(wifi, WifiEventTypeRadioStateOnPending);
if (wifi->netif != NULL) {
esp_netif_destroy(wifi->netif);
}
wifi->netif = esp_netif_create_default_wifi_sta();
// Warning: this is the memory-intensive operation
// It uses over 117kB of RAM with default settings for S3 on IDF v5.1.2
wifi_init_config_t config = WIFI_INIT_CONFIG_DEFAULT();
esp_err_t init_result = esp_wifi_init(&config);
if (init_result != ESP_OK) {
TT_LOG_E(TAG, "Wifi init failed");
if (init_result == ESP_ERR_NO_MEM) {
TT_LOG_E(TAG, "Insufficient memory");
}
wifi->radio_state = WIFI_RADIO_OFF;
wifi_publish_event_simple(wifi, WifiEventTypeRadioStateOff);
return;
}
esp_wifi_set_storage(WIFI_STORAGE_RAM);
// TODO: don't crash on check failure
ESP_ERROR_CHECK(esp_event_handler_instance_register(
WIFI_EVENT,
ESP_EVENT_ANY_ID,
&event_handler,
NULL,
&wifi->event_handler_any_id
));
// TODO: don't crash on check failure
ESP_ERROR_CHECK(esp_event_handler_instance_register(
IP_EVENT,
IP_EVENT_STA_GOT_IP,
&event_handler,
NULL,
&wifi->event_handler_got_ip
));
if (esp_wifi_set_mode(WIFI_MODE_STA) != ESP_OK) {
TT_LOG_E(TAG, "Wifi mode setting failed");
wifi->radio_state = WIFI_RADIO_OFF;
esp_wifi_deinit();
wifi_publish_event_simple(wifi, WifiEventTypeRadioStateOff);
return;
}
esp_err_t start_result = esp_wifi_start();
if (start_result != ESP_OK) {
TT_LOG_E(TAG, "Wifi start failed");
if (start_result == ESP_ERR_NO_MEM) {
TT_LOG_E(TAG, "Insufficient memory");
}
wifi->radio_state = WIFI_RADIO_OFF;
esp_wifi_set_mode(WIFI_MODE_NULL);
esp_wifi_deinit();
wifi_publish_event_simple(wifi, WifiEventTypeRadioStateOff);
return;
}
wifi->radio_state = WIFI_RADIO_ON;
wifi_publish_event_simple(wifi, WifiEventTypeRadioStateOn);
TT_LOG_I(TAG, "Enabled");
}
static void wifi_disable(Wifi* wifi) {
WifiRadioState state = wifi->radio_state;
if (
state == WIFI_RADIO_OFF ||
state == WIFI_RADIO_OFF_PENDING ||
state == WIFI_RADIO_ON_PENDING
) {
TT_LOG_W(TAG, "Can't disable from current state");
return;
}
TT_LOG_I(TAG, "Disabling");
wifi->radio_state = WIFI_RADIO_OFF_PENDING;
wifi_publish_event_simple(wifi, WifiEventTypeRadioStateOffPending);
// Free up scan list memory
wifi_scan_list_free_safely(wifi_singleton);
if (esp_wifi_stop() != ESP_OK) {
TT_LOG_E(TAG, "Failed to stop radio");
wifi->radio_state = WIFI_RADIO_ON;
wifi_publish_event_simple(wifi, WifiEventTypeRadioStateOn);
return;
}
if (esp_wifi_set_mode(WIFI_MODE_NULL) != ESP_OK) {
TT_LOG_E(TAG, "Failed to unset mode");
}
if (esp_event_handler_instance_unregister(
WIFI_EVENT,
ESP_EVENT_ANY_ID,
wifi->event_handler_any_id
) != ESP_OK) {
TT_LOG_E(TAG, "Failed to unregister id event handler");
}
if (esp_event_handler_instance_unregister(
IP_EVENT,
IP_EVENT_STA_GOT_IP,
wifi->event_handler_got_ip
) != ESP_OK) {
TT_LOG_E(TAG, "Failed to unregister ip event handler");
}
if (esp_wifi_deinit() != ESP_OK) {
TT_LOG_E(TAG, "Failed to deinit");
}
tt_check(wifi->netif != NULL);
esp_netif_destroy(wifi->netif);
wifi->netif = NULL;
wifi->radio_state = WIFI_RADIO_OFF;
wifi_publish_event_simple(wifi, WifiEventTypeRadioStateOff);
TT_LOG_I(TAG, "Disabled");
}
static void wifi_scan_internal(Wifi* wifi) {
WifiRadioState state = wifi->radio_state;
if (state != WIFI_RADIO_ON && state != WIFI_RADIO_CONNECTION_ACTIVE) {
TT_LOG_W(TAG, "Scan unavailable: wifi not enabled");
return;
}
TT_LOG_I(TAG, "Starting scan");
wifi->scan_active = true;
wifi_publish_event_simple(wifi, WifiEventTypeScanStarted);
// Create scan list if it does not exist
wifi_scan_list_alloc_safely(wifi);
wifi->scan_list_count = 0;
esp_wifi_scan_start(NULL, true);
uint16_t record_count = wifi->scan_list_limit;
ESP_ERROR_CHECK(esp_wifi_scan_get_ap_records(&record_count, wifi->scan_list));
uint16_t safe_record_count = 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);
for (uint16_t i = 0; i < safe_record_count; i++) {
wifi_ap_record_t* record = &wifi->scan_list[i];
TT_LOG_I(TAG, " - SSID %s (RSSI %d, channel %d)", record->ssid, record->rssi, record->primary);
}
esp_wifi_scan_stop();
wifi_publish_event_simple(wifi, WifiEventTypeScanFinished);
wifi->scan_active = false;
TT_LOG_I(TAG, "Finished scan");
}
static void wifi_connect_internal(Wifi* wifi, WifiConnectMessage* connect_message) {
// TODO: only when connected!
wifi_disconnect_internal(wifi);
wifi->radio_state = WIFI_RADIO_CONNECTION_PENDING;
wifi_publish_event_simple(wifi, WifiEventTypeConnectionPending);
wifi_config_t wifi_config = {
.sta = {
/* Authmode threshold resets to WPA2 as default if password matches WPA2 standards (pasword len => 8).
* If you want to connect the device to deprecated WEP/WPA networks, Please set the threshold value
* to WIFI_AUTH_WEP/WIFI_AUTH_WPA_PSK and set the password with length and format matching to
* WIFI_AUTH_WEP/WIFI_AUTH_WPA_PSK standards.
*/
.threshold.authmode = WIFI_AUTH_WPA2_WPA3_PSK,
.sae_pwe_h2e = WPA3_SAE_PWE_BOTH,
.sae_h2e_identifier = {0},
},
};
memcpy(wifi_config.sta.ssid, connect_message->ssid, 32);
memcpy(wifi_config.sta.password, connect_message->password, 64);
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));
wifi_publish_event_simple(wifi, WifiEventTypeConnectionFailed);
return;
}
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));
wifi_publish_event_simple(wifi, WifiEventTypeConnectionFailed);
return;
}
/* Waiting until either the connection is established (WIFI_CONNECTED_BIT)
* or connection failed for the maximum number of re-tries (WIFI_FAIL_BIT).
* The bits are set by wifi_event_handler() */
EventBits_t bits = xEventGroupWaitBits(
wifi->event_group,
WIFI_CONNECTED_BIT | WIFI_FAIL_BIT,
pdFALSE,
pdFALSE,
portMAX_DELAY
);
if (bits & WIFI_CONNECTED_BIT) {
wifi->radio_state = WIFI_RADIO_CONNECTION_ACTIVE;
wifi_publish_event_simple(wifi, WifiEventTypeConnectionSuccess);
TT_LOG_I(TAG, "Connected to %s", connect_message->ssid);
} else if (bits & WIFI_FAIL_BIT) {
wifi->radio_state = WIFI_RADIO_ON;
wifi_publish_event_simple(wifi, WifiEventTypeConnectionFailed);
TT_LOG_I(TAG, "Failed to connect to %s", connect_message->ssid);
} else {
wifi->radio_state = WIFI_RADIO_ON;
wifi_publish_event_simple(wifi, WifiEventTypeConnectionFailed);
TT_LOG_E(TAG, "UNEXPECTED EVENT");
}
}
static void wifi_disconnect_internal(Wifi* wifi) {
esp_err_t stop_result = esp_wifi_stop();
if (stop_result != ESP_OK) {
TT_LOG_E(TAG, "Failed to disconnect (%s)", esp_err_to_name(stop_result));
} else {
wifi->radio_state = WIFI_RADIO_ON;
wifi_publish_event_simple(wifi, WifiEventTypeDisconnected);
TT_LOG_I(TAG, "Disconnected");
}
}
static void wifi_disconnect_internal_but_keep_active(Wifi* wifi) {
esp_err_t stop_result = esp_wifi_stop();
if (stop_result != ESP_OK) {
TT_LOG_E(TAG, "Failed to disconnect (%s)", esp_err_to_name(stop_result));
return;
}
wifi_config_t wifi_config = {
.sta = {
.ssid = {0},
.password = {0},
.threshold.authmode = WIFI_AUTH_OPEN,
.sae_pwe_h2e = WPA3_SAE_PWE_UNSPECIFIED,
.sae_h2e_identifier = {0},
},
};
esp_err_t set_config_result = esp_wifi_set_config(WIFI_IF_STA, &wifi_config);
if (set_config_result != ESP_OK) {
// TODO: disable radio, because radio state is in limbo between off and on
wifi->radio_state = WIFI_RADIO_OFF;
TT_LOG_E(TAG, "failed to set wifi config (%s)", esp_err_to_name(set_config_result));
wifi_publish_event_simple(wifi, WifiEventTypeRadioStateOff);
return;
}
esp_err_t wifi_start_result = esp_wifi_start();
if (wifi_start_result != ESP_OK) {
// TODO: disable radio, because radio state is in limbo between off and on
wifi->radio_state = WIFI_RADIO_OFF;
TT_LOG_E(TAG, "failed to start wifi to begin connecting (%s)", esp_err_to_name(wifi_start_result));
wifi_publish_event_simple(wifi, WifiEventTypeRadioStateOff);
return;
}
wifi->radio_state = WIFI_RADIO_ON;
wifi_publish_event_simple(wifi, WifiEventTypeDisconnected);
TT_LOG_I(TAG, "Disconnected");
}
// ESP wifi APIs need to run from the main task, so we can't just spawn a thread
_Noreturn int32_t wifi_main(void* p) {
UNUSED(p);
TT_LOG_I(TAG, "Started main loop");
tt_check(wifi_singleton != NULL);
Wifi* wifi = wifi_singleton;
MessageQueue* queue = wifi->queue;
WifiMessage message;
while (true) {
if (tt_message_queue_get(queue, &message, 1000 / portTICK_PERIOD_MS) == TtStatusOk) {
TT_LOG_I(TAG, "Processing message of type %d", message.type);
switch (message.type) {
case WifiMessageTypeRadioOn:
wifi_enable(wifi);
break;
case WifiMessageTypeRadioOff:
wifi_disable(wifi);
break;
case WifiMessageTypeScan:
wifi_scan_internal(wifi);
break;
case WifiMessageTypeConnect:
wifi_connect_internal(wifi, &message.connect_message);
break;
case WifiMessageTypeDisconnect:
wifi_disconnect_internal_but_keep_active(wifi);
break;
}
}
}
}
static void wifi_service_start(Service service) {
UNUSED(service);
tt_check(wifi_singleton == NULL);
wifi_singleton = wifi_alloc();
}
static void wifi_service_stop(Service service) {
UNUSED(service);
tt_check(wifi_singleton != NULL);
WifiRadioState state = wifi_singleton->radio_state;
if (state != WIFI_RADIO_OFF) {
wifi_disable(wifi_singleton);
}
wifi_free(wifi_singleton);
wifi_singleton = NULL;
// wifi_main() cannot be stopped yet as it runs in the main task.
// We could theoretically exit it, but then we wouldn't be able to restart the service.
tt_crash("not fully implemented");
}
const ServiceManifest wifi_service = {
.id = "wifi",
.on_start = &wifi_service_start,
.on_stop = &wifi_service_stop
};
#endif
@@ -1,361 +0,0 @@
#ifndef ESP_PLATFORM
#include "wifi.h"
#include "check.h"
#include "freertos/FreeRTOS.h"
#include "freertos/event_groups.h"
#include "log.h"
#include "message_queue.h"
#include "mutex.h"
#include "pubsub.h"
#include "service.h"
#include <sys/cdefs.h>
#define TAG "wifi"
#define WIFI_SCAN_RECORD_LIMIT 16 // default, can be overridden
#define WIFI_CONNECTED_BIT BIT0
#define WIFI_FAIL_BIT BIT1
typedef struct {
/** @brief Locking mechanism for modifying the Wifi instance */
Mutex* mutex;
/** @brief The public event bus */
PubSub* pubsub;
/** @brief The internal message queue */
MessageQueue* queue;
/** @brief Scanning results */
wifi_ap_record_t* _Nullable scan_list;
/** @brief The current item count in scan_list (-1 when scan_list is NULL) */
uint16_t scan_list_count;
/** @brief Maximum amount of records to scan (value > 0) */
uint16_t scan_list_limit;
bool scan_active;
EventGroupHandle_t event_group;
WifiRadioState radio_state;
} Wifi;
typedef enum {
WifiMessageTypeRadioOn,
WifiMessageTypeRadioOff,
WifiMessageTypeScan,
WifiMessageTypeConnect,
WifiMessageTypeDisconnect
} WifiMessageType;
typedef struct {
uint8_t ssid[32];
uint8_t password[64];
} WifiConnectMessage;
typedef struct {
WifiMessageType type;
union {
WifiConnectMessage connect_message;
};
} WifiMessage;
static Wifi* wifi_singleton = NULL;
// Forward declarations
static void wifi_scan_list_free_safely(Wifi* wifi);
static void wifi_disconnect_internal(Wifi* wifi);
static void wifi_lock(Wifi* wifi);
static void wifi_unlock(Wifi* wifi);
// region Alloc
static Wifi* wifi_alloc() {
Wifi* instance = malloc(sizeof(Wifi));
instance->mutex = tt_mutex_alloc(MutexTypeRecursive);
instance->pubsub = tt_pubsub_alloc();
// TODO: Deal with messages that come in while an action is ongoing
// for example: when scanning and you turn off the radio, the scan should probably stop or turning off
// the radio should disable the on/off button in the app as it is pending.
instance->queue = tt_message_queue_alloc(1, sizeof(WifiMessage));
instance->scan_active = false;
instance->scan_list = NULL;
instance->scan_list_count = 0;
instance->scan_list_limit = WIFI_SCAN_RECORD_LIMIT;
instance->event_group = xEventGroupCreate();
instance->radio_state = WIFI_RADIO_OFF;
return instance;
}
static void wifi_free(Wifi* instance) {
tt_mutex_free(instance->mutex);
tt_pubsub_free(instance->pubsub);
tt_message_queue_free(instance->queue);
free(instance);
}
// endregion Alloc
// region Public functions
PubSub* wifi_get_pubsub() {
tt_assert(wifi_singleton);
return wifi_singleton->pubsub;
}
WifiRadioState wifi_get_radio_state() {
return wifi_singleton->radio_state;
}
void wifi_scan() {
tt_assert(wifi_singleton);
WifiMessage message = {.type = WifiMessageTypeScan};
// No need to lock for queue
tt_message_queue_put(wifi_singleton->queue, &message, 100 / portTICK_PERIOD_MS);
}
bool wifi_is_scanning() {
tt_assert(wifi_singleton);
return wifi_singleton->scan_active;
}
void wifi_connect(const char* ssid, const char _Nullable password[64]) {
tt_assert(wifi_singleton);
tt_check(strlen(ssid) <= 32);
WifiMessage message = {.type = WifiMessageTypeConnect};
memcpy(message.connect_message.ssid, ssid, 32);
if (password != NULL) {
memcpy(message.connect_message.password, password, 64);
} else {
message.connect_message.password[0] = 0;
}
tt_message_queue_put(wifi_singleton->queue, &message, 100 / portTICK_PERIOD_MS);
}
void wifi_disconnect() {
tt_assert(wifi_singleton);
WifiMessage message = {.type = WifiMessageTypeDisconnect};
tt_message_queue_put(wifi_singleton->queue, &message, 100 / portTICK_PERIOD_MS);
}
void wifi_set_scan_records(uint16_t records) {
tt_assert(wifi_singleton);
if (records != wifi_singleton->scan_list_limit) {
wifi_scan_list_free_safely(wifi_singleton);
wifi_singleton->scan_list_limit = records;
}
}
void wifi_get_scan_results(WifiApRecord records[], uint16_t limit, uint16_t* result_count) {
tt_check(wifi_singleton);
tt_check(result_count);
if (wifi_singleton->scan_list_count == 0) {
*result_count = 0;
} else {
uint16_t i = 0;
TT_LOG_I(TAG, "processing up to %d APs", wifi_singleton->scan_list_count);
uint16_t last_index = MIN(wifi_singleton->scan_list_count, limit);
for (; i < last_index; ++i) {
memcpy(records[i].ssid, wifi_singleton->scan_list[i].ssid, 33);
records[i].rssi = wifi_singleton->scan_list[i].rssi;
records[i].auth_mode = wifi_singleton->scan_list[i].authmode;
}
// The index already overflowed right before the for-loop was terminated,
// so it effectively became the list count:
*result_count = i;
}
}
void wifi_set_enabled(bool enabled) {
if (enabled) {
WifiMessage message = {.type = WifiMessageTypeRadioOn};
// No need to lock for queue
tt_message_queue_put(wifi_singleton->queue, &message, 100 / portTICK_PERIOD_MS);
} else {
WifiMessage message = {.type = WifiMessageTypeRadioOff};
// No need to lock for queue
tt_message_queue_put(wifi_singleton->queue, &message, 100 / portTICK_PERIOD_MS);
}
}
// endregion Public functions
static void wifi_lock(Wifi* wifi) {
tt_crash("this fails for now");
tt_assert(wifi);
tt_assert(wifi->mutex);
tt_check(xSemaphoreTakeRecursive(wifi->mutex, portMAX_DELAY) == pdPASS);
}
static void wifi_unlock(Wifi* wifi) {
tt_assert(wifi);
tt_assert(wifi->mutex);
tt_check(xSemaphoreGiveRecursive(wifi->mutex) == pdPASS);
}
static void wifi_scan_list_alloc(Wifi* wifi) {
tt_check(wifi->scan_list == NULL);
wifi->scan_list = malloc(sizeof(wifi_ap_record_t) * wifi->scan_list_limit);
wifi->scan_list_count = 0;
}
static void wifi_scan_list_alloc_safely(Wifi* wifi) {
if (wifi->scan_list == NULL) {
wifi_scan_list_alloc(wifi);
}
}
static void wifi_scan_list_free(Wifi* wifi) {
tt_check(wifi->scan_list != NULL);
free(wifi->scan_list);
wifi->scan_list = NULL;
wifi->scan_list_count = 0;
}
static void wifi_scan_list_free_safely(Wifi* wifi) {
if (wifi->scan_list != NULL) {
wifi_scan_list_free(wifi);
}
}
static void wifi_publish_event_simple(Wifi* wifi, WifiEventType type) {
WifiEvent turning_on_event = {.type = type};
tt_pubsub_publish(wifi->pubsub, &turning_on_event);
}
static void wifi_enable(Wifi* wifi) {
WifiRadioState state = wifi->radio_state;
if (
state == WIFI_RADIO_ON ||
state == WIFI_RADIO_ON_PENDING ||
state == WIFI_RADIO_OFF_PENDING
) {
TT_LOG_W(TAG, "Can't enable from current state");
return;
}
wifi->radio_state = WIFI_RADIO_ON;
wifi_publish_event_simple(wifi, WifiEventTypeRadioStateOn);
TT_LOG_I(TAG, "Enabled");
}
static void wifi_disable(Wifi* wifi) {
WifiRadioState state = wifi->radio_state;
if (
state == WIFI_RADIO_OFF ||
state == WIFI_RADIO_OFF_PENDING ||
state == WIFI_RADIO_ON_PENDING
) {
TT_LOG_W(TAG, "Can't disable from current state");
return;
}
wifi->radio_state = WIFI_RADIO_OFF;
wifi_publish_event_simple(wifi, WifiEventTypeRadioStateOff);
TT_LOG_I(TAG, "Disabled");
}
static void wifi_scan_internal(Wifi* wifi) {
WifiRadioState state = wifi->radio_state;
if (state != WIFI_RADIO_ON && state != WIFI_RADIO_CONNECTION_ACTIVE) {
TT_LOG_W(TAG, "Scan unavailable: wifi not enabled");
return;
}
TT_LOG_I(TAG, "Starting scan");
wifi->scan_active = true;
wifi_publish_event_simple(wifi, WifiEventTypeScanStarted);
// TODO: fake entries
wifi_publish_event_simple(wifi, WifiEventTypeScanFinished);
wifi->scan_active = false;
TT_LOG_I(TAG, "Finished scan");
}
static void wifi_connect_internal(Wifi* wifi, WifiConnectMessage* connect_message) {
wifi_disconnect_internal(wifi);
wifi->radio_state = WIFI_RADIO_CONNECTION_PENDING;
wifi_publish_event_simple(wifi, WifiEventTypeConnectionPending);
// TODO: Sleep?
wifi->radio_state = WIFI_RADIO_CONNECTION_ACTIVE;
wifi_publish_event_simple(wifi, WifiEventTypeConnectionSuccess);
TT_LOG_I(TAG, "Connected to %s", connect_message->ssid);
}
static void wifi_disconnect_internal(Wifi* wifi) {
wifi->radio_state = WIFI_RADIO_ON;
wifi_publish_event_simple(wifi, WifiEventTypeDisconnected);
TT_LOG_I(TAG, "Disconnected");
}
static void wifi_disconnect_internal_but_keep_active(Wifi* wifi) {
wifi->radio_state = WIFI_RADIO_ON;
wifi_publish_event_simple(wifi, WifiEventTypeDisconnected);
TT_LOG_I(TAG, "Disconnected");
}
// ESP wifi APIs need to run from the main task, so we can't just spawn a thread
_Noreturn int32_t wifi_main(void* p) {
UNUSED(p);
TT_LOG_I(TAG, "Started main loop");
tt_check(wifi_singleton != NULL);
Wifi* wifi = wifi_singleton;
MessageQueue* queue = wifi->queue;
WifiMessage message;
while (true) {
if (tt_message_queue_get(queue, &message, 1000 / portTICK_PERIOD_MS) == TtStatusOk) {
TT_LOG_I(TAG, "Processing message of type %d", message.type);
switch (message.type) {
case WifiMessageTypeRadioOn:
wifi_enable(wifi);
break;
case WifiMessageTypeRadioOff:
wifi_disable(wifi);
break;
case WifiMessageTypeScan:
wifi_scan_internal(wifi);
break;
case WifiMessageTypeConnect:
wifi_connect_internal(wifi, &message.connect_message);
break;
case WifiMessageTypeDisconnect:
wifi_disconnect_internal_but_keep_active(wifi);
break;
}
}
}
}
static void wifi_service_start(Service service) {
UNUSED(service);
tt_check(wifi_singleton == NULL);
wifi_singleton = wifi_alloc();
}
static void wifi_service_stop(Service service) {
UNUSED(service);
tt_check(wifi_singleton != NULL);
WifiRadioState state = wifi_singleton->radio_state;
if (state != WIFI_RADIO_OFF) {
wifi_disable(wifi_singleton);
}
wifi_free(wifi_singleton);
wifi_singleton = NULL;
// wifi_main() cannot be stopped yet as it runs in the main task.
// We could theoretically exit it, but then we wouldn't be able to restart the service.
tt_crash("not fully implemented");
}
const ServiceManifest wifi_service = {
.id = "wifi",
.on_start = &wifi_service_start,
.on_stop = &wifi_service_stop
};
#endif
-121
View File
@@ -1,121 +0,0 @@
#include "tactility.h"
#include "app_manifest_registry.h"
#include "core.h"
#include "devices_i.h"
#include "esp_wifi.h"
#include "graphics_i.h"
#include "nvs_flash.h"
#include "partitions.h"
#include "service_registry.h"
#include "services/loader/loader.h"
#include "services/wifi/wifi_credentials.h"
#include <sys/cdefs.h>
#define TAG "tactility"
// System services
extern const ServiceManifest gui_service;
extern const ServiceManifest loader_service;
extern const ServiceManifest wifi_service;
// System apps
extern const AppManifest desktop_app;
extern const AppManifest system_info_app;
extern const AppManifest wifi_connect_app;
extern const AppManifest wifi_manage_app;
_Noreturn int32_t wifi_main(void* p);
static void register_system_apps() {
TT_LOG_I(TAG, "Registering default apps");
tt_app_manifest_registry_add(&desktop_app);
tt_app_manifest_registry_add(&system_info_app);
tt_app_manifest_registry_add(&wifi_connect_app);
tt_app_manifest_registry_add(&wifi_manage_app);
}
static void register_user_apps(const Config* _Nonnull config) {
TT_LOG_I(TAG, "Registering user apps");
for (size_t i = 0; i < CONFIG_APPS_LIMIT; i++) {
const AppManifest* manifest = config->apps[i];
if (manifest != NULL) {
tt_app_manifest_registry_add(manifest);
} else {
// reached end of list
break;
}
}
}
static void register_system_services() {
TT_LOG_I(TAG, "Registering system services");
tt_service_registry_add(&gui_service);
tt_service_registry_add(&loader_service);
tt_service_registry_add(&wifi_service);
}
static void start_system_services() {
TT_LOG_I(TAG, "Starting system services");
tt_service_registry_start(gui_service.id);
tt_service_registry_start(loader_service.id);
tt_service_registry_start(wifi_service.id);
}
static void register_and_start_user_services(const Config* _Nonnull config) {
TT_LOG_I(TAG, "Registering and starting user services");
for (size_t i = 0; i < CONFIG_SERVICES_LIMIT; i++) {
const ServiceManifest* manifest = config->services[i];
if (manifest != NULL) {
tt_service_registry_add(manifest);
tt_service_registry_start(manifest->id);
} else {
// reached end of list
break;
}
}
}
__attribute__((unused)) extern void tactility_start(const Config* _Nonnull config) {
tt_core_init();
// Initialize NVS
esp_err_t ret = nvs_flash_init();
if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
TT_LOG_I(TAG, "nvs erasing");
ESP_ERROR_CHECK(nvs_flash_erase());
ret = nvs_flash_init();
}
ESP_ERROR_CHECK(ret);
TT_LOG_I(TAG, "nvs initialized");
tt_partitions_init();
tt_wifi_credentials_init();
Hardware hardware = tt_hardware_init(config->hardware);
/*NbLvgl lvgl =*/tt_graphics_init(&hardware);
// Register all apps
register_system_services();
register_system_apps();
// TODO: move this after start_system_services, but desktop must subscribe to app registry events first.
register_user_apps(config);
// Start all services
start_system_services();
register_and_start_user_services(config);
// Network interface
ESP_ERROR_CHECK(esp_netif_init());
ESP_ERROR_CHECK(esp_event_loop_create_default());
loader_start_app(desktop_app.id, true, NULL);
if (config->auto_start_app_id != NULL) {
loader_start_app(config->auto_start_app_id, false, NULL);
}
// Wifi must run in the main task, or otherwise it will crash the app
// TODO: What if we need more functionality on the main task?
wifi_main(NULL);
}
-40
View File
@@ -1,40 +0,0 @@
#pragma once
#include "devices.h"
#include "tactility_core.h"
#ifdef __cplusplus
extern "C" {
#endif
#define CONFIG_APPS_LIMIT 32
#define CONFIG_SERVICES_LIMIT 32
// Forward declarations
typedef void* ThreadId;
typedef void (*Bootstrap)();
typedef TouchDriver (*CreateTouchDriver)();
typedef DisplayDriver (*CreateDisplayDriver)();
typedef struct {
// Optional bootstrapping method
const Bootstrap _Nullable bootstrap;
// Required driver for display
const CreateDisplayDriver _Nonnull display_driver;
// Optional driver for touch input
const CreateTouchDriver _Nullable touch_driver;
} HardwareConfig;
typedef struct {
const HardwareConfig* hardware;
// List of user applications
const AppManifest* const apps[CONFIG_APPS_LIMIT];
const ServiceManifest* const services[CONFIG_SERVICES_LIMIT];
const char* auto_start_app_id;
} Config;
__attribute__((unused)) extern void tactility_start(const Config _Nonnull* config);
#ifdef __cplusplus
}
#endif
-12
View File
@@ -1,12 +0,0 @@
#include "check.h"
#include "touch.h"
TouchDevice _Nonnull* tt_touch_alloc(TouchDriver _Nonnull* driver) {
TouchDevice _Nonnull* touch = malloc(sizeof(TouchDevice));
bool success = driver->create_touch_device(
&(touch->io_handle),
&(touch->touch_handle)
);
tt_check(success, "touch driver failed");
return touch;
}
-30
View File
@@ -1,30 +0,0 @@
#pragma once
#include "esp_lcd_panel_io.h"
#include "esp_lcd_touch.h"
#ifdef __cplusplus
extern "C" {
#endif
typedef bool (*CreateTouch)(esp_lcd_panel_io_handle_t* io_handle, esp_lcd_touch_handle_t* touch_handle);
typedef struct {
char name[32];
CreateTouch create_touch_device;
} TouchDriver;
typedef struct {
esp_lcd_panel_io_handle_t _Nonnull io_handle;
esp_lcd_touch_handle_t _Nonnull touch_handle;
} TouchDevice;
/**
* @param[in] driver
* @return a newly allocated instance
*/
TouchDevice _Nonnull* tt_touch_alloc(TouchDriver _Nonnull* driver);
#ifdef __cplusplus
}
#endif
-9
View File
@@ -1,9 +0,0 @@
#include "spacer.h"
#include "style.h"
lv_obj_t* tt_lv_spacer_create(lv_obj_t* parent, lv_coord_t width, lv_coord_t height) {
lv_obj_t* spacer = lv_obj_create(parent);
lv_obj_set_size(spacer, width, height);
tt_lv_obj_set_style_bg_invisible(spacer);
return spacer;
}
-13
View File
@@ -1,13 +0,0 @@
#pragma once
#include "lvgl.h"
#ifdef __cplusplus
extern "C" {
#endif
lv_obj_t* tt_lv_spacer_create(lv_obj_t* parent, lv_coord_t width, lv_coord_t height);
#ifdef __cplusplus
}
#endif
-16
View File
@@ -1,16 +0,0 @@
#include "style.h"
void tt_lv_obj_set_style_bg_blacken(lv_obj_t* obj) {
lv_obj_set_style_bg_color(obj, lv_color_black(), 0);
lv_obj_set_style_border_color(obj, lv_color_black(), 0);
}
void tt_lv_obj_set_style_bg_invisible(lv_obj_t* obj) {
lv_obj_set_style_bg_opa(obj, 0, 0);
lv_obj_set_style_border_width(obj, 0, 0);
}
void tt_lv_obj_set_style_no_padding(lv_obj_t* obj) {
lv_obj_set_style_pad_all(obj, LV_STATE_DEFAULT, 0);
lv_obj_set_style_pad_gap(obj, LV_STATE_DEFAULT, 0);
}
-15
View File
@@ -1,15 +0,0 @@
#pragma once
#include "lvgl.h"
#ifdef __cplusplus
extern "C" {
#endif
void tt_lv_obj_set_style_bg_blacken(lv_obj_t* obj);
void tt_lv_obj_set_style_bg_invisible(lv_obj_t* obj);
void tt_lv_obj_set_style_no_padding(lv_obj_t* obj);
#ifdef __cplusplus
}
#endif
-48
View File
@@ -1,48 +0,0 @@
#include "toolbar.h"
#include "services/loader/loader.h"
#include "ui/spacer.h"
#include "ui/style.h"
#define TOOLBAR_HEIGHT 40
#define TOOLBAR_FONT_HEIGHT 18
static void on_nav_pressed(lv_event_t* event) {
NavAction action = (NavAction)event->user_data;
action();
}
lv_obj_t* tt_lv_toolbar_create(lv_obj_t* parent, const Toolbar* toolbar) {
lv_obj_t* wrapper = lv_obj_create(parent);
lv_obj_set_width(wrapper, LV_PCT(100));
lv_obj_set_height(wrapper, TOOLBAR_HEIGHT);
tt_lv_obj_set_style_no_padding(wrapper);
lv_obj_center(wrapper);
lv_obj_set_flex_flow(wrapper, LV_FLEX_FLOW_ROW);
lv_obj_t* close_button = lv_btn_create(wrapper);
lv_obj_set_size(close_button, TOOLBAR_HEIGHT - 4, TOOLBAR_HEIGHT - 4);
tt_lv_obj_set_style_no_padding(close_button);
lv_obj_add_event_cb(close_button, &on_nav_pressed, LV_EVENT_CLICKED, toolbar->nav_action);
lv_obj_t* close_button_image = lv_img_create(close_button);
lv_img_set_src(close_button_image, toolbar->nav_icon); // e.g. LV_SYMBOL_CLOSE
lv_obj_align(close_button_image, LV_ALIGN_CENTER, 0, 0);
// Need spacer to avoid button press glitch animation
tt_lv_spacer_create(wrapper, 2, 1);
lv_obj_t* label_container = lv_obj_create(wrapper);
tt_lv_obj_set_style_no_padding(label_container);
lv_obj_set_style_border_width(label_container, 0, 0);
lv_obj_set_height(label_container, LV_PCT(100)); // 2% less due to 4px translate (it's not great, but it works)
lv_obj_set_flex_grow(label_container, 1);
lv_obj_t* title_label = lv_label_create(label_container);
lv_label_set_text(title_label, toolbar->title);
lv_obj_set_style_text_font(title_label, &lv_font_montserrat_18, 0);
lv_obj_set_size(title_label, LV_PCT(100), TOOLBAR_FONT_HEIGHT);
lv_obj_set_pos(title_label, 0, (TOOLBAR_HEIGHT - TOOLBAR_FONT_HEIGHT - 10) / 2);
lv_obj_set_style_text_align(title_label, LV_TEXT_ALIGN_CENTER, 0);
return wrapper;
}
-21
View File
@@ -1,21 +0,0 @@
#pragma once
#include "lvgl.h"
#ifdef __cplusplus
extern "C" {
#endif
typedef void(*NavAction)();
typedef struct {
const char* _Nullable title;
const char* _Nullable nav_icon; // LVGL compatible definition (e.g. local file or embedded icon from LVGL)
NavAction nav_action;
} Toolbar;
lv_obj_t* tt_lv_toolbar_create(lv_obj_t* parent, const Toolbar* toolbar);
#ifdef __cplusplus
}
#endif