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
@@ -0,0 +1,22 @@
|
||||
cmake_minimum_required(VERSION 3.16)
|
||||
|
||||
set(BOARD_COMPONENTS esp_wifi)
|
||||
|
||||
idf_component_register(
|
||||
SRC_DIRS "src"
|
||||
"src/apps/system/wifi_connect"
|
||||
"src/apps/system/wifi_manage"
|
||||
"src/services/wifi"
|
||||
INCLUDE_DIRS "src/"
|
||||
REQUIRES esp_wifi esp_lvgl_port nvs_flash esp_lcd esp_lcd_touch 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)
|
||||
|
||||
target_link_libraries(${COMPONENT_LIB} ${IDF_TARGET_NAME} tactility)
|
||||
target_link_libraries(${COMPONENT_LIB} ${IDF_TARGET_NAME} tactility-core)
|
||||
|
||||
|
After Width: | Height: | Size: 274 B |
|
After Width: | Height: | Size: 224 B |
|
After Width: | Height: | Size: 312 B |
|
After Width: | Height: | Size: 309 B |
|
After Width: | Height: | Size: 336 B |
|
After Width: | Height: | Size: 307 B |
|
After Width: | Height: | Size: 344 B |
|
After Width: | Height: | Size: 312 B |
|
After Width: | Height: | Size: 353 B |
|
After Width: | Height: | Size: 370 B |
@@ -0,0 +1,125 @@
|
||||
#include "wifi_connect.h"
|
||||
|
||||
#include "app.h"
|
||||
#include "esp_lvgl_port.h"
|
||||
#include "services/wifi/wifi.h"
|
||||
#include "tactility_core.h"
|
||||
#include "ui/lvgl_sync.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 (tt_lvgl_lock(1000)) {
|
||||
wifi_connect_view_update(&wifi->view, &wifi->bindings, &wifi->state);
|
||||
tt_lvgl_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
|
||||
};
|
||||
@@ -0,0 +1,30 @@
|
||||
#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
|
||||
@@ -0,0 +1,10 @@
|
||||
#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;
|
||||
@@ -0,0 +1,12 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#define WIFI_CONNECT_PARAM_SSID "ssid" // String
|
||||
#define WIFI_CONNECT_PARAM_PASSWORD "password" // String
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,21 @@
|
||||
#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
|
||||
@@ -0,0 +1,7 @@
|
||||
#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);
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
#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
|
||||
@@ -0,0 +1,136 @@
|
||||
#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);
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
#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
|
||||
@@ -0,0 +1,167 @@
|
||||
#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 "ui/lvgl_sync.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 (tt_lvgl_lock(1000)) {
|
||||
wifi_manage_view_update(&wifi->view, &wifi->bindings, &wifi->state);
|
||||
tt_lvgl_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
|
||||
};
|
||||
@@ -0,0 +1,28 @@
|
||||
#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
|
||||
@@ -0,0 +1,13 @@
|
||||
#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;
|
||||
@@ -0,0 +1,25 @@
|
||||
#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
|
||||
@@ -0,0 +1,23 @@
|
||||
#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);
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
#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
|
||||
@@ -0,0 +1,222 @@
|
||||
#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);
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
#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
|
||||
@@ -0,0 +1,32 @@
|
||||
#include "check.h"
|
||||
#include "devices_i.h"
|
||||
#include "touch.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
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
#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
|
||||
@@ -0,0 +1,13 @@
|
||||
#pragma once
|
||||
|
||||
#include "tactility-esp.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
Hardware tt_hardware_init(const HardwareConfig _Nonnull* config);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,15 @@
|
||||
#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;
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
#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
|
||||
@@ -0,0 +1,59 @@
|
||||
#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 = 8096,
|
||||
.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
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
#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
|
||||
@@ -0,0 +1,14 @@
|
||||
#pragma once
|
||||
|
||||
#include "devices.h"
|
||||
#include "graphics.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
Lvgl tt_graphics_init(Hardware _Nonnull* hardware);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,66 @@
|
||||
#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;
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
#pragma once
|
||||
|
||||
#include "esp_err.h"
|
||||
|
||||
#define MOUNT_POINT_ASSETS "/assets"
|
||||
#define MOUNT_POINT_CONFIG "/config"
|
||||
|
||||
esp_err_t tt_partitions_init();
|
||||
|
||||
@@ -0,0 +1,593 @@
|
||||
#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
|
||||
};
|
||||
|
||||
@@ -0,0 +1,101 @@
|
||||
#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
|
||||
@@ -0,0 +1,237 @@
|
||||
#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
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
#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
|
||||
@@ -0,0 +1,50 @@
|
||||
#include "tactility.h"
|
||||
|
||||
#include "core.h"
|
||||
#include "graphics_i.h"
|
||||
#include "devices_i.h" // TODO: Rename to hardware*.*
|
||||
#include "nvs_flash.h"
|
||||
#include "partitions.h"
|
||||
#include "esp_lvgl_port.h"
|
||||
#include "ui/lvgl_sync.h"
|
||||
#include "services/wifi/wifi_credentials.h"
|
||||
#include "services/loader/loader.h"
|
||||
#include <sys/cdefs.h>
|
||||
#include "esp_netif.h"
|
||||
#include "esp_event.h"
|
||||
|
||||
#define TAG "tactility"
|
||||
|
||||
static bool lvgl_lock_impl(int timeout_ticks) {
|
||||
return lvgl_port_lock(timeout_ticks);
|
||||
}
|
||||
|
||||
static void lvgl_unlock_impl() {
|
||||
lvgl_port_unlock();
|
||||
}
|
||||
|
||||
void tt_esp_init(const Config* _Nonnull config) {
|
||||
// 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");
|
||||
|
||||
// Network interface
|
||||
ESP_ERROR_CHECK(esp_netif_init());
|
||||
ESP_ERROR_CHECK(esp_event_loop_create_default());
|
||||
|
||||
tt_partitions_init();
|
||||
|
||||
tt_wifi_credentials_init();
|
||||
|
||||
tt_lvgl_sync_set(&lvgl_lock_impl, &lvgl_unlock_impl);
|
||||
|
||||
Hardware hardware = tt_hardware_init(config->hardware);
|
||||
/*NbLvgl lvgl =*/tt_graphics_init(&hardware);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
#pragma once
|
||||
|
||||
#include "tactility.h"
|
||||
#include "devices.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;
|
||||
|
||||
void tt_esp_init(const Config* _Nonnull config);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,12 @@
|
||||
#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;
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
#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
|
||||