Project restructuring: add tactility-headless (#55)

- Created `tactility-headless` to support ESP32 firmwares that don't require graphics
- `tactility` subproject now contains both PC and ESP32 code (to avoid having to split up `tactility` and `tactility-headless` into separate projects, which would result in a very complex dependency tree)
- `tactility` subproject is now defined as component for ESP32 and as regular module for PC
- Improvements for dispatcher
- Added `project-structure.puml` to docs
- `Gui` service now depends on `Loader` service instead of the reverse
- Added `statusbar_updater` service for updating Wi-Fi and SD card icons
This commit is contained in:
Ken Van Hoeylandt 2024-08-31 17:56:28 +02:00 committed by GitHub
parent b659d5b940
commit 27730260e0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
85 changed files with 841 additions and 275 deletions

View File

@ -15,8 +15,9 @@ if (DEFINED ENV{ESP_IDF_VERSION})
set(COMPONENTS app-esp) set(COMPONENTS app-esp)
set(EXTRA_COMPONENT_DIRS set(EXTRA_COMPONENT_DIRS
"boards" "boards"
"tactility-esp"
"app-esp" "app-esp"
"tactility"
"tactility-headless"
"libs/esp_lvgl_port" "libs/esp_lvgl_port"
"libs/lvgl" "libs/lvgl"
"libs/M5Unified" "libs/M5Unified"
@ -40,7 +41,12 @@ endif()
project(tactility-root) project(tactility-root)
add_subdirectory(tactility) # Defined as regular project for PC and component for ESP
if (NOT DEFINED ENV{ESP_IDF_VERSION})
add_subdirectory(tactility)
add_subdirectory(tactility-headless)
endif()
add_subdirectory(tactility-core) add_subdirectory(tactility-core)
add_subdirectory(libs/mlib) add_subdirectory(libs/mlib)

View File

@ -1,6 +1,6 @@
cmake_minimum_required(VERSION 3.16) cmake_minimum_required(VERSION 3.16)
set(BOARD_COMPONENTS tactility-esp) set(BOARD_COMPONENTS tactility)
if("${IDF_TARGET}" STREQUAL "esp32") if("${IDF_TARGET}" STREQUAL "esp32")
list(APPEND BOARD_COMPONENTS list(APPEND BOARD_COMPONENTS
@ -22,7 +22,3 @@ idf_component_register(
"src/hello_world" "src/hello_world"
REQUIRES ${BOARD_COMPONENTS} REQUIRES ${BOARD_COMPONENTS}
) )
# TODO Remove?
target_link_libraries(${COMPONENT_LIB} ${IDF_TARGET_NAME} tactility)

View File

@ -1,16 +1,11 @@
#include "board_config.h" #include "board_config.h"
#include "tactility_esp.h"
// Apps // Apps
#include "hello_world/hello_world.h" #include "hello_world/hello_world.h"
#include "tactility.h"
extern void wifi_main(void*); extern void wifi_main(void*);
extern const ServiceManifest wifi_service;
extern const AppManifest gpio_app;
extern const AppManifest wifi_connect_app;
extern const AppManifest wifi_manage_app;
void app_main(void) { void app_main(void) {
static const Config config = { static const Config config = {
/** /**
@ -19,19 +14,12 @@ void app_main(void) {
*/ */
.hardware = TT_BOARD_HARDWARE, .hardware = TT_BOARD_HARDWARE,
.apps = { .apps = {
&gpio_app,
&hello_world_app, &hello_world_app,
&wifi_connect_app,
&wifi_manage_app
},
.services = {
&wifi_service
}, },
.services = {},
.auto_start_app_id = NULL .auto_start_app_id = NULL
}; };
tt_esp_init();
tt_init(&config); tt_init(&config);
wifi_main(NULL); wifi_main(NULL);

View File

@ -5,6 +5,7 @@ add_executable(app-sim ${SOURCES})
target_link_libraries(app-sim target_link_libraries(app-sim
PRIVATE tactility PRIVATE tactility
PRIVATE tactility-core PRIVATE tactility-core
PRIVATE tactility-headless
PRIVATE lvgl PRIVATE lvgl
) )

View File

@ -24,5 +24,5 @@ TT_UNUSED static void lvgl_deinit() {
HardwareConfig sim_hardware = { HardwareConfig sim_hardware = {
.bootstrap = NULL, .bootstrap = NULL,
.init_lvgl = &lvgl_init, .init_graphics = &lvgl_init,
}; };

View File

@ -1,11 +1,5 @@
#include "hello_world/hello_world.h" #include "hello_world/hello_world.h"
#include "tactility.h" #include "tactility.h"
#include "assets.h"
#include "FreeRTOS.h"
#include "ui/statusbar.h"
#define TAG "main"
extern HardwareConfig sim_hardware; extern HardwareConfig sim_hardware;
@ -20,9 +14,4 @@ void app_main() {
}; };
tt_init(&config); tt_init(&config);
// Note: this is just to test the statusbar as Wi-Fi
// and sd card apps are not available for PC
tt_statusbar_icon_add(TT_ASSETS_ICON_SDCARD_ALERT);
tt_statusbar_icon_add(TT_ASSETS_ICON_WIFI_OFF);
} }

View File

@ -1,7 +1,5 @@
idf_component_register( idf_component_register(
SRC_DIRS "." SRC_DIRS "."
INCLUDE_DIRS "." INCLUDE_DIRS "."
REQUIRES esp_lvgl_port esp_lcd esp_lcd_touch_gt911 driver vfs fatfs REQUIRES tactility esp_lvgl_port esp_lcd esp_lcd_touch_gt911 driver vfs fatfs
) )
target_link_libraries(${COMPONENT_LIB} ${IDF_TARGET_NAME} tactility)

View File

@ -12,6 +12,6 @@ const HardwareConfig lilygo_tdeck = {
.display = { .display = {
.set_backlight_duty = &tdeck_backlight_set .set_backlight_duty = &tdeck_backlight_set
}, },
.init_lvgl = &tdeck_init_lvgl, .init_graphics = &tdeck_init_lvgl,
.sdcard = &tdeck_sdcard .sdcard = &tdeck_sdcard
}; };

View File

@ -2,7 +2,5 @@ idf_component_register(
SRC_DIRS "source" SRC_DIRS "source"
INCLUDE_DIRS "include" INCLUDE_DIRS "include"
PRIV_INCLUDE_DIRS "private" PRIV_INCLUDE_DIRS "private"
REQUIRES esp_lvgl_port esp_lcd_ili9341 driver vfs fatfs M5Unified REQUIRES tactility esp_lvgl_port esp_lcd_ili9341 driver vfs fatfs M5Unified
) )
target_link_libraries(${COMPONENT_LIB} ${IDF_TARGET_NAME} tactility)

View File

@ -9,6 +9,6 @@ const HardwareConfig m5stack_core2 = {
.display = { .display = {
.set_backlight_duty = NULL .set_backlight_duty = NULL
}, },
.init_lvgl = &core2_lvgl_init, .init_graphics = &core2_lvgl_init,
.sdcard = &core2_sdcard .sdcard = &core2_sdcard
}; };

View File

@ -1,7 +1,5 @@
idf_component_register( idf_component_register(
SRC_DIRS "." SRC_DIRS "."
INCLUDE_DIRS "." INCLUDE_DIRS "."
REQUIRES lvgl esp_lcd esp_lcd_touch_gt911 REQUIRES tactility lvgl esp_lcd esp_lcd_touch_gt911
) )
target_link_libraries(${COMPONENT_LIB} ${IDF_TARGET_NAME} tactility)

View File

@ -9,5 +9,5 @@ const HardwareConfig waveshare_s3_touch = {
.display = { .display = {
.set_backlight_duty = NULL // TODO: This requires implementing the CH422G IO expander .set_backlight_duty = NULL // TODO: This requires implementing the CH422G IO expander
}, },
.init_lvgl = &ws3t_init_lvgl .init_graphics = &ws3t_init_lvgl
}; };

View File

@ -1,7 +1,5 @@
idf_component_register( idf_component_register(
SRC_DIRS "." SRC_DIRS "."
INCLUDE_DIRS "." INCLUDE_DIRS "."
REQUIRES esp_lvgl_port esp_lcd_touch_cst816s esp_lcd_ili9341 driver vfs fatfs REQUIRES tactility esp_lvgl_port esp_lcd_touch_cst816s esp_lcd_ili9341 driver vfs fatfs
) )
target_link_libraries(${COMPONENT_LIB} ${IDF_TARGET_NAME} tactility)

View File

@ -11,6 +11,6 @@ const HardwareConfig yellow_board_24inch_cap = {
.display = { .display = {
.set_backlight_duty = &twodotfour_backlight_set .set_backlight_duty = &twodotfour_backlight_set
}, },
.init_lvgl = &twodotfour_lvgl_init, .init_graphics = &twodotfour_lvgl_init,
.sdcard = &twodotfour_sdcard .sdcard = &twodotfour_sdcard
}; };

View File

@ -22,7 +22,7 @@
- 2 wire speaker support - 2 wire speaker support
- tt_app_start() and similar functions as proxies for Loader app start/stop/etc. - tt_app_start() and similar functions as proxies for Loader app start/stop/etc.
- tt_app_set_result() for apps that need to return data to other apps (e.g. file selection) - tt_app_set_result() for apps that need to return data to other apps (e.g. file selection)
- Introduce co-routines for calling wifi/lvgl/etc functionality. - Wi-Fi using dispatcher to dispatch its main functionality to the dedicated Wi-Fi CPU core (to avoid main loop hack)
# App Improvement Ideas # App Improvement Ideas
- Sort desktop apps by name. - Sort desktop apps by name.
@ -30,7 +30,6 @@
# App Ideas # App Ideas
- File viewer (images, text... binary?) - File viewer (images, text... binary?)
- GPIO status viewer
- BlueTooth keyboard app - BlueTooth keyboard app
- Chip 8 emulator - Chip 8 emulator
- BadUSB - BadUSB

View File

@ -0,0 +1,23 @@
@startuml
skinparam componentStyle uml1
[tactility] as t
note right of t : to build and use graphical apps
[tactility-headless] as theadless
note right of theadless : to build and use background services
[tactility-core] as tcore
note right of tcore : defines, data types, logging, async, etc.
package "Applications" {
[app-sim] as appsim
[app-esp] as appesp
}
note right of appesp : app-esp depends on the board \n projects for configuration
[t] ..> [theadless]
[theadless] ..> [tcore]
[appsim] ..> [t]
[appesp] ..> [t]
@enduml

View File

@ -8,6 +8,7 @@ file(GLOB SOURCES "src/*.c")
file(GLOB HEADERS "src/*.h") file(GLOB HEADERS "src/*.h")
add_library(tactility-core OBJECT) add_library(tactility-core OBJECT)
target_sources(tactility-core target_sources(tactility-core
PRIVATE ${SOURCES} PRIVATE ${SOURCES}
PUBLIC ${HEADERS} PUBLIC ${HEADERS}

View File

@ -2,9 +2,20 @@
#include "tactility_core.h" #include "tactility_core.h"
typedef struct {
Callback callback;
void* context;
} DispatcherMessage;
typedef struct {
MessageQueue* queue;
Mutex* mutex;
DispatcherMessage buffer; // Buffer for consuming a message
} DispatcherData;
Dispatcher* tt_dispatcher_alloc(uint32_t message_count) { Dispatcher* tt_dispatcher_alloc(uint32_t message_count) {
Dispatcher* dispatcher = malloc(sizeof(Dispatcher)); DispatcherData* data = malloc(sizeof(DispatcherData));
*dispatcher = (Dispatcher) { *data = (DispatcherData) {
.queue = tt_message_queue_alloc(message_count, sizeof(DispatcherMessage)), .queue = tt_message_queue_alloc(message_count, sizeof(DispatcherMessage)),
.mutex = tt_mutex_alloc(MutexTypeNormal), .mutex = tt_mutex_alloc(MutexTypeNormal),
.buffer = { .buffer = {
@ -12,37 +23,40 @@ Dispatcher* tt_dispatcher_alloc(uint32_t message_count) {
.context = NULL .context = NULL
} }
}; };
return dispatcher; return data;
} }
void tt_dispatcher_free(Dispatcher* dispatcher) { void tt_dispatcher_free(Dispatcher* dispatcher) {
tt_mutex_acquire(dispatcher->mutex, TtWaitForever); DispatcherData* data = (DispatcherData*)dispatcher;
tt_message_queue_reset(dispatcher->queue); tt_mutex_acquire(data->mutex, TtWaitForever);
tt_message_queue_free(dispatcher->queue); tt_message_queue_reset(data->queue);
tt_mutex_release(dispatcher->mutex); tt_message_queue_free(data->queue);
tt_mutex_free(dispatcher->mutex); tt_mutex_release(data->mutex);
free(dispatcher); tt_mutex_free(data->mutex);
free(data);
} }
void tt_dispatcher_dispatch(Dispatcher* dispatcher, Callback callback, void* context) { void tt_dispatcher_dispatch(Dispatcher* dispatcher, Callback callback, void* context) {
DispatcherData* data = (DispatcherData*)dispatcher;
DispatcherMessage message = { DispatcherMessage message = {
.callback = callback, .callback = callback,
.context = context .context = context
}; };
tt_mutex_acquire(dispatcher->mutex, TtWaitForever); tt_mutex_acquire(data->mutex, TtWaitForever);
tt_message_queue_put(dispatcher->queue, &message, TtWaitForever); tt_message_queue_put(data->queue, &message, TtWaitForever);
tt_mutex_release(dispatcher->mutex); tt_mutex_release(data->mutex);
} }
bool tt_dispatcher_consume(Dispatcher* dispatcher, uint32_t timeout_ticks) { bool tt_dispatcher_consume(Dispatcher* dispatcher, uint32_t timeout_ticks) {
tt_mutex_acquire(dispatcher->mutex, TtWaitForever); DispatcherData* data = (DispatcherData*)dispatcher;
if (tt_message_queue_get(dispatcher->queue, &(dispatcher->buffer), timeout_ticks) == TtStatusOk) { tt_mutex_acquire(data->mutex, TtWaitForever);
DispatcherMessage* message = &(dispatcher->buffer); if (tt_message_queue_get(data->queue, &(data->buffer), timeout_ticks) == TtStatusOk) {
DispatcherMessage* message = &(data->buffer);
message->callback(message->context); message->callback(message->context);
tt_mutex_release(dispatcher->mutex); tt_mutex_release(data->mutex);
return true; return true;
} else { } else {
tt_mutex_release(dispatcher->mutex); tt_mutex_release(data->mutex);
return false; return false;
} }
} }

View File

@ -1,7 +1,7 @@
/** /**
* @file message_queue.h * @file dispatcher.h
* *
* Dispatcher is a thread-safe message queue implementation for callbacks. * Dispatcher is a thread-safe code execution queue.
*/ */
#pragma once #pragma once
@ -15,21 +15,12 @@ extern "C" {
typedef void (*Callback)(void* data); typedef void (*Callback)(void* data);
typedef struct { typedef void Dispatcher;
Callback callback;
void* context;
} DispatcherMessage;
typedef struct {
MessageQueue* queue;
Mutex* mutex;
DispatcherMessage buffer; // Buffer for consuming a message
} Dispatcher;
Dispatcher* tt_dispatcher_alloc(uint32_t message_count); Dispatcher* tt_dispatcher_alloc(uint32_t message_count);
void tt_dispatcher_free(Dispatcher* dispatcher); void tt_dispatcher_free(Dispatcher* dispatcher);
void tt_dispatcher_dispatch(Dispatcher* dispatcher, Callback callback, void* context); void tt_dispatcher_dispatch(Dispatcher* data, Callback callback, void* context);
bool tt_dispatcher_consume(Dispatcher* dispatcher, uint32_t timeout_ticks); bool tt_dispatcher_consume(Dispatcher* data, uint32_t timeout_ticks);
#ifdef __cplusplus #ifdef __cplusplus
} }

View File

@ -1,6 +1,6 @@
#pragma once #pragma once
#ifdef ESP_PLATFORM #ifdef ESP_TARGET
#include "esp_log.h" #include "esp_log.h"
#else #else
#include <stdarg.h> #include <stdarg.h>
@ -11,7 +11,7 @@
extern "C" { extern "C" {
#endif #endif
#ifdef ESP_PLATFORM #ifdef ESP_TARGET
#define TT_LOG_E(tag, format, ...) \ #define TT_LOG_E(tag, format, ...) \
ESP_LOGE(tag, format, ##__VA_ARGS__) ESP_LOGE(tag, format, ##__VA_ARGS__)
@ -47,7 +47,7 @@ void tt_log(LogLevel level, const char* tag, const char* format, ...);
#define TT_LOG_T(tag, format, ...) \ #define TT_LOG_T(tag, format, ...) \
tt_log(LOG_LEVEL_TRACE, tag, format, ##__VA_ARGS__) tt_log(LOG_LEVEL_TRACE, tag, format, ##__VA_ARGS__)
#endif #endif // ESP_TARGET
#ifdef __cplusplus #ifdef __cplusplus
} }

View File

@ -1,21 +0,0 @@
cmake_minimum_required(VERSION 3.16)
set(BOARD_COMPONENTS esp_wifi)
file(GLOB_RECURSE SOURCE_FILES src/*.c)
idf_component_register(
SRCS ${SOURCE_FILES}
INCLUDE_DIRS "src/"
REQUIRES esp_wifi nvs_flash spiffs
)
set(ASSETS_SRC_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../data/assets")
spiffs_create_partition_image(assets ${ASSETS_SRC_DIR} FLASH_IN_PROJECT)
set(CONFIG_SRC_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../data/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)

View File

@ -0,0 +1,48 @@
cmake_minimum_required(VERSION 3.16)
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
if (DEFINED ENV{ESP_IDF_VERSION})
file(GLOB_RECURSE SOURCE_FILES src/*.c)
idf_component_register(
SRCS ${SOURCE_FILES}
INCLUDE_DIRS "src/"
REQUIRES esp_wifi nvs_flash spiffs driver newlib
)
set(ASSETS_SRC_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../data/assets")
spiffs_create_partition_image(assets ${ASSETS_SRC_DIR} FLASH_IN_PROJECT)
set(CONFIG_SRC_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../data/config")
spiffs_create_partition_image(config ${CONFIG_SRC_DIR} FLASH_IN_PROJECT)
target_link_libraries(${COMPONENT_LIB}
PUBLIC tactility-core
)
add_definitions(-DESP_PLATFORM)
else()
file(GLOB_RECURSE SOURCES "src/*.c")
file(GLOB_RECURSE HEADERS "src/*.h")
add_library(tactility-headless OBJECT)
target_sources(tactility-headless
PRIVATE ${SOURCES}
PUBLIC ${HEADERS}
)
target_include_directories(tactility-headless
PRIVATE src/
INTERFACE src/
)
add_definitions(-D_Nullable=)
add_definitions(-D_Nonnull=)
target_link_libraries(tactility-headless
PUBLIC tactility-core
PUBLIC freertos_kernel
)
endif()

View File

@ -1,14 +1,18 @@
#include "tactility_core.h" #include "tactility_core.h"
#ifdef ESP_TARGET
#include "esp_partitions.h"
#include "services/wifi/wifi_credentials.h"
#include "esp_event.h" #include "esp_event.h"
#include "esp_netif.h" #include "esp_netif.h"
#include "nvs_flash.h" #include "nvs_flash.h"
#include "partitions.h"
#include "services/wifi/wifi_credentials.h"
#define TAG "tactility" #define TAG "tactility"
void tt_esp_init() {
// Initialize NVS // Initialize NVS
static void tt_esp_nvs_init() {
esp_err_t ret = nvs_flash_init(); esp_err_t ret = nvs_flash_init();
if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) { if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
TT_LOG_I(TAG, "nvs erasing"); TT_LOG_I(TAG, "nvs erasing");
@ -17,12 +21,20 @@ void tt_esp_init() {
} }
ESP_ERROR_CHECK(ret); ESP_ERROR_CHECK(ret);
TT_LOG_I(TAG, "nvs initialized"); TT_LOG_I(TAG, "nvs initialized");
}
// Network interface static void tt_esp_network_init() {
ESP_ERROR_CHECK(esp_netif_init()); ESP_ERROR_CHECK(esp_netif_init());
ESP_ERROR_CHECK(esp_event_loop_create_default()); ESP_ERROR_CHECK(esp_event_loop_create_default());
}
tt_partitions_init(); void tt_esp_init() {
tt_esp_nvs_init();
tt_esp_partitions_init();
tt_esp_network_init();
tt_wifi_credentials_init(); tt_wifi_credentials_init();
} }
#endif

View File

@ -1,7 +1,6 @@
#pragma once #pragma once
#include "hardware_config.h" #ifdef ESP_TARGET
#include "tactility.h"
#ifdef __cplusplus #ifdef __cplusplus
extern "C" { extern "C" {
@ -12,3 +11,5 @@ void tt_esp_init();
#ifdef __cplusplus #ifdef __cplusplus
} }
#endif #endif
#endif // ESP_TARGET

View File

@ -1,4 +1,6 @@
#include "partitions.h" #ifdef ESP_TARGET
#include "esp_partitions.h"
#include "esp_spiffs.h" #include "esp_spiffs.h"
#include "log.h" #include "log.h"
#include "nvs_flash.h" #include "nvs_flash.h"
@ -37,7 +39,7 @@ static esp_err_t spiffs_init(esp_vfs_spiffs_conf_t* conf) {
return ESP_OK; return ESP_OK;
} }
esp_err_t tt_partitions_init() { esp_err_t tt_esp_partitions_init() {
ESP_ERROR_CHECK(nvs_flash_init_safely()); ESP_ERROR_CHECK(nvs_flash_init_safely());
esp_vfs_spiffs_conf_t assets_spiffs = { esp_vfs_spiffs_conf_t assets_spiffs = {
@ -64,3 +66,5 @@ esp_err_t tt_partitions_init() {
return ESP_OK; return ESP_OK;
} }
#endif // ESP_TARGET

View File

@ -1,8 +1,12 @@
#pragma once #pragma once
#ifdef ESP_TARGET
#include "esp_err.h" #include "esp_err.h"
#define MOUNT_POINT_ASSETS "/assets" #define MOUNT_POINT_ASSETS "/assets"
#define MOUNT_POINT_CONFIG "/config" #define MOUNT_POINT_CONFIG "/config"
esp_err_t tt_partitions_init(); esp_err_t tt_esp_partitions_init();
#endif // ESP_TARGET

View File

@ -0,0 +1,22 @@
#include "hardware_i.h"
#include "preferences.h"
#include "sdcard_i.h"
#define TAG "hardware"
void tt_hardware_init(const HardwareConfig* config) {
if (config->bootstrap != NULL) {
TT_LOG_I(TAG, "Bootstrapping");
tt_check(config->bootstrap(), "bootstrap failed");
}
tt_sdcard_init();
if (config->sdcard != NULL) {
TT_LOG_I(TAG, "Mounting sdcard");
tt_sdcard_mount(config->sdcard);
}
tt_check(config->init_graphics, "lvlg init not set");
tt_check(config->init_graphics(), "lvgl init failed");
}

View File

@ -4,8 +4,7 @@
#include "sdcard.h" #include "sdcard.h"
typedef bool (*Bootstrap)(); typedef bool (*Bootstrap)();
typedef bool (*InitLvgl)(); typedef bool (*InitGraphics)();
typedef bool (*InitLvgl)();
typedef void (*SetBacklightDuty)(uint8_t); typedef void (*SetBacklightDuty)(uint8_t);
typedef struct { typedef struct {
@ -24,10 +23,11 @@ typedef struct {
* Initializes LVGL with all relevant hardware. * Initializes LVGL with all relevant hardware.
* This includes the display and optional pointer devices (such as touch) or a keyboard. * This includes the display and optional pointer devices (such as touch) or a keyboard.
*/ */
const InitLvgl init_lvgl; const InitGraphics init_graphics;
/** /**
* An interface for display features such as setting the backlight. * An interface for display features such as setting the backlight.
* This does nothing when a display isn't present.
*/ */
const Display display; const Display display;

View File

@ -4,6 +4,8 @@
#include "nvs_flash.h" #include "nvs_flash.h"
#include "tactility_core.h" #include "tactility_core.h"
#define TAG "preferences"
static bool opt_bool(const char* namespace, const char* key, bool* out) { static bool opt_bool(const char* namespace, const char* key, bool* out) {
nvs_handle_t handle; nvs_handle_t handle;
if (nvs_open(namespace, NVS_READWRITE, &handle) != ESP_OK) { if (nvs_open(namespace, NVS_READWRITE, &handle) != ESP_OK) {
@ -60,16 +62,24 @@ static bool has_string(const char* namespace, const char* key) {
static void put_bool(const char* namespace, const char* key, bool value) { static void put_bool(const char* namespace, const char* key, bool value) {
nvs_handle_t handle; nvs_handle_t handle;
if (nvs_open(namespace, NVS_READWRITE, &handle) == ESP_OK) { if (nvs_open(namespace, NVS_READWRITE, &handle) == ESP_OK) {
nvs_set_u8(handle, key, (uint8_t)value) == ESP_OK; if (nvs_set_u8(handle, key, (uint8_t)value) != ESP_OK) {
TT_LOG_E(TAG, "failed to write %s:%s", namespace, key);
}
nvs_close(handle); nvs_close(handle);
} else {
TT_LOG_E(TAG, "failed to open namespace %s for writing", namespace);
} }
} }
static void put_int32(const char* namespace, const char* key, int32_t value) { static void put_int32(const char* namespace, const char* key, int32_t value) {
nvs_handle_t handle; nvs_handle_t handle;
if (nvs_open(namespace, NVS_READWRITE, &handle) == ESP_OK) { if (nvs_open(namespace, NVS_READWRITE, &handle) == ESP_OK) {
nvs_set_i32(handle, key, value) == ESP_OK; if (nvs_set_i32(handle, key, value) != ESP_OK) {
TT_LOG_E(TAG, "failed to write %s:%s", namespace, key);
}
nvs_close(handle); nvs_close(handle);
} else {
TT_LOG_E(TAG, "failed to open namespace %s for writing", namespace);
} }
} }
@ -78,6 +88,8 @@ static void put_string(const char* namespace, const char* key, const char* text)
if (nvs_open(namespace, NVS_READWRITE, &handle) == ESP_OK) { if (nvs_open(namespace, NVS_READWRITE, &handle) == ESP_OK) {
nvs_set_str(handle, key, text); nvs_set_str(handle, key, text);
nvs_close(handle); nvs_close(handle);
} else {
TT_LOG_E(TAG, "failed to open namespace %s for writing", namespace);
} }
} }

View File

@ -1,11 +1,9 @@
#include <dirent.h> #include <dirent.h>
#include "assets.h"
#include "mutex.h" #include "mutex.h"
#include "service.h" #include "service.h"
#include "tactility.h"
#include "tactility_core.h" #include "tactility_core.h"
#include "ui/statusbar.h" #include "tactility_headless.h"
#define TAG "sdcard_service" #define TAG "sdcard_service"
@ -15,7 +13,6 @@ typedef struct {
Mutex* mutex; Mutex* mutex;
Thread* thread; Thread* thread;
SdcardState last_state; SdcardState last_state;
int8_t statusbar_icon_id;
bool interrupted; bool interrupted;
} ServiceData; } ServiceData;
@ -30,7 +27,6 @@ static ServiceData* service_data_alloc() {
data data
), ),
.last_state = -1, .last_state = -1,
.statusbar_icon_id = tt_statusbar_icon_add(NULL),
.interrupted = false .interrupted = false
}; };
tt_thread_set_priority(data->thread, ThreadPriorityLow); tt_thread_set_priority(data->thread, ThreadPriorityLow);
@ -39,7 +35,6 @@ static ServiceData* service_data_alloc() {
static void service_data_free(ServiceData* data) { static void service_data_free(ServiceData* data) {
tt_mutex_free(data->mutex); tt_mutex_free(data->mutex);
tt_statusbar_icon_remove(data->statusbar_icon_id);
tt_thread_free(data->thread); tt_thread_free(data->thread);
} }
@ -56,9 +51,6 @@ static int32_t sdcard_task(void* context) {
bool interrupted = false; bool interrupted = false;
// We set NULL as statusbar image by default, so it's hidden by default
tt_statusbar_icon_set_visibility(data->statusbar_icon_id, true);
do { do {
service_data_lock(data); service_data_lock(data);
@ -72,12 +64,6 @@ static int32_t sdcard_task(void* context) {
} }
if (new_state != data->last_state) { if (new_state != data->last_state) {
TT_LOG_I(TAG, "State change %d -> %d", data->last_state, new_state);
if (new_state == SdcardStateMounted) {
tt_statusbar_icon_set_image(data->statusbar_icon_id, TT_ASSETS_ICON_SDCARD);
} else {
tt_statusbar_icon_set_image(data->statusbar_icon_id, TT_ASSETS_ICON_SDCARD_ALERT);
}
data->last_state = new_state; data->last_state = new_state;
} }
@ -89,7 +75,7 @@ static int32_t sdcard_task(void* context) {
} }
static void on_start(Service service) { static void on_start(Service service) {
if (tt_get_config()->hardware->sdcard != NULL) { if (tt_get_hardware_config()->sdcard != NULL) {
ServiceData* data = service_data_alloc(); ServiceData* data = service_data_alloc();
tt_service_set_data(service, data); tt_service_set_data(service, data);
tt_thread_start(data->thread); tt_thread_start(data->thread);

View File

@ -0,0 +1,9 @@
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
#ifdef __cplusplus
}
#endif

View File

@ -10,6 +10,26 @@ extern "C" {
#ifdef ESP_PLATFORM #ifdef ESP_PLATFORM
#include "esp_wifi.h" #include "esp_wifi.h"
#else
#include <stdint.h>
// From esp_wifi_types.h in ESP-IDF 5.2
typedef enum {
WIFI_AUTH_OPEN = 0, /**< authenticate mode : open */
WIFI_AUTH_WEP, /**< authenticate mode : WEP */
WIFI_AUTH_WPA_PSK, /**< authenticate mode : WPA_PSK */
WIFI_AUTH_WPA2_PSK, /**< authenticate mode : WPA2_PSK */
WIFI_AUTH_WPA_WPA2_PSK, /**< authenticate mode : WPA_WPA2_PSK */
WIFI_AUTH_ENTERPRISE, /**< authenticate mode : WiFi EAP security */
WIFI_AUTH_WPA2_ENTERPRISE = WIFI_AUTH_ENTERPRISE, /**< authenticate mode : WiFi EAP security */
WIFI_AUTH_WPA3_PSK, /**< authenticate mode : WPA3_PSK */
WIFI_AUTH_WPA2_WPA3_PSK, /**< authenticate mode : WPA2_WPA3_PSK */
WIFI_AUTH_WAPI_PSK, /**< authenticate mode : WAPI_PSK */
WIFI_AUTH_OWE, /**< authenticate mode : OWE */
WIFI_AUTH_WPA3_ENT_192, /**< authenticate mode : WPA3_ENT_SUITE_B_192_BIT */
WIFI_AUTH_WPA3_EXT_PSK, /**< authenticate mode : WPA3_PSK_EXT_KEY */
WIFI_AUTH_WPA3_EXT_PSK_MIXED_MODE, /**< authenticate mode: WPA3_PSK + WPA3_PSK_EXT_KEY */
WIFI_AUTH_MAX
} wifi_auth_mode_t;
#endif #endif
typedef enum { typedef enum {
@ -100,12 +120,14 @@ void wifi_connect(const char* ssid, const char _Nullable password[64]);
void wifi_disconnect(); void wifi_disconnect();
/** /**
* Return the relevant icon asset from assets.h for the given inputs * Return true if the connection isn't unencrypted.
* @param rssi the rssi value
* @param secured whether the access point is a secured one (as in: not an open one)
* @return
*/ */
const char* wifi_get_status_icon_for_rssi(int rssi, bool secured); bool wifi_is_connection_secure();
/**
* Returns the RSSI value (negative number) or return 1 when not connected
*/
int wifi_get_rssi();
#ifdef __cplusplus #ifdef __cplusplus
} }

View File

@ -1,3 +1,5 @@
#ifdef ESP_TARGET
#include "wifi_credentials.h" #include "wifi_credentials.h"
#include "nvs_flash.h" #include "nvs_flash.h"
@ -230,3 +232,4 @@ bool tt_wifi_credentials_remove(const char* ssid) {
// end region Wi-Fi Credentials - public // end region Wi-Fi Credentials - public
#endif // ESP_TARGET

View File

@ -0,0 +1,30 @@
#ifndef ESP_TARGET
#include "wifi_credentials.h"
#include "log.h"
#define TAG "wifi_credentials_mock"
static void hash_reset_all();
bool tt_wifi_credentials_contains(const char* ssid) {
return false;
}
void tt_wifi_credentials_init() {
TT_LOG_I(TAG, "init");
}
bool tt_wifi_credentials_get(const char* ssid, char password[TT_WIFI_CREDENTIALS_PASSWORD_LIMIT]) {
return false;
}
bool tt_wifi_credentials_set(const char* ssid, char password[TT_WIFI_CREDENTIALS_PASSWORD_LIMIT]) {
return false;
}
bool tt_wifi_credentials_remove(const char* ssid) {
return false;
}
#endif // ESP_TARGET

View File

@ -1,3 +1,5 @@
#ifdef ESP_TARGET
#include "wifi.h" #include "wifi.h"
#include "assets.h" #include "assets.h"
@ -9,7 +11,6 @@
#include "mutex.h" #include "mutex.h"
#include "pubsub.h" #include "pubsub.h"
#include "service.h" #include "service.h"
#include "ui/statusbar.h"
#include <sys/cdefs.h> #include <sys/cdefs.h>
#define TAG "wifi" #define TAG "wifi"
@ -32,14 +33,12 @@ typedef struct {
uint16_t scan_list_count; uint16_t scan_list_count;
/** @brief Maximum amount of records to scan (value > 0) */ /** @brief Maximum amount of records to scan (value > 0) */
uint16_t scan_list_limit; uint16_t scan_list_limit;
int8_t statusbar_icon_id;
bool scan_active; bool scan_active;
bool secure_connection; bool secure_connection;
esp_event_handler_instance_t event_handler_any_id; esp_event_handler_instance_t event_handler_any_id;
esp_event_handler_instance_t event_handler_got_ip; esp_event_handler_instance_t event_handler_got_ip;
EventGroupHandle_t event_group; EventGroupHandle_t event_group;
WifiRadioState radio_state; WifiRadioState radio_state;
const char* _Nullable last_statusbar_icon;
} Wifi; } Wifi;
typedef enum { typedef enum {
@ -89,14 +88,11 @@ static Wifi* wifi_alloc() {
instance->event_handler_got_ip = NULL; instance->event_handler_got_ip = NULL;
instance->event_group = xEventGroupCreate(); instance->event_group = xEventGroupCreate();
instance->radio_state = WIFI_RADIO_OFF; instance->radio_state = WIFI_RADIO_OFF;
instance->statusbar_icon_id = tt_statusbar_icon_add(TT_ASSETS_ICON_WIFI_OFF);
instance->last_statusbar_icon = NULL;
instance->secure_connection = false; instance->secure_connection = false;
return instance; return instance;
} }
static void wifi_free(Wifi* instance) { static void wifi_free(Wifi* instance) {
tt_statusbar_icon_remove(instance->statusbar_icon_id);
tt_mutex_free(instance->mutex); tt_mutex_free(instance->mutex);
tt_pubsub_free(instance->pubsub); tt_pubsub_free(instance->pubsub);
tt_message_queue_free(instance->queue); tt_message_queue_free(instance->queue);
@ -188,10 +184,24 @@ void wifi_set_enabled(bool enabled) {
} }
} }
bool wifi_is_connection_secure() {
tt_check(wifi_singleton);
return wifi_singleton->secure_connection;
}
int wifi_get_rssi() {
static int rssi = 0;
if (esp_wifi_sta_get_rssi(&rssi) == ESP_OK) {
return rssi;
} else {
return 1;
}
}
// endregion Public functions // endregion Public functions
static void wifi_lock(Wifi* wifi) { static void wifi_lock(Wifi* wifi) {
tt_crash("this fails for now"); tt_crash("this fails for now"); // TODO: Fix
tt_assert(wifi); tt_assert(wifi);
tt_assert(wifi->mutex); tt_assert(wifi->mutex);
tt_check(xSemaphoreTakeRecursive(wifi->mutex, portMAX_DELAY) == pdPASS); tt_check(xSemaphoreTakeRecursive(wifi->mutex, portMAX_DELAY) == pdPASS);
@ -422,47 +432,6 @@ static void wifi_scan_internal(Wifi* wifi) {
TT_LOG_I(TAG, "Finished scan"); TT_LOG_I(TAG, "Finished scan");
} }
const char* wifi_get_status_icon_for_rssi(int rssi, bool secured) {
if (rssi > -67) {
return secured ? TT_ASSETS_ICON_WIFI_SIGNAL_4_LOCKED : TT_ASSETS_ICON_WIFI_SIGNAL_4;
} else if (rssi > -70) {
return secured ? TT_ASSETS_ICON_WIFI_SIGNAL_3_LOCKED : TT_ASSETS_ICON_WIFI_SIGNAL_3;
} else if (rssi > -80) {
return secured ? TT_ASSETS_ICON_WIFI_SIGNAL_2_LOCKED : TT_ASSETS_ICON_WIFI_SIGNAL_2;
} else {
return secured ? TT_ASSETS_ICON_WIFI_SIGNAL_1_LOCKED : TT_ASSETS_ICON_WIFI_SIGNAL_1;
}
}
static const char* wifi_get_status_icon(WifiRadioState state, bool secure) {
static int rssi = 0;
switch (state) {
case WIFI_RADIO_ON_PENDING:
case WIFI_RADIO_ON:
case WIFI_RADIO_OFF_PENDING:
case WIFI_RADIO_OFF:
return TT_ASSETS_ICON_WIFI_OFF;
case WIFI_RADIO_CONNECTION_PENDING:
return TT_ASSETS_ICON_WIFI_FIND;
case WIFI_RADIO_CONNECTION_ACTIVE:
if (esp_wifi_sta_get_rssi(&rssi) == ESP_OK) {
return wifi_get_status_icon_for_rssi(rssi, secure);
} else {
return secure ? TT_ASSETS_ICON_WIFI_SIGNAL_0_LOCKED : TT_ASSETS_ICON_WIFI_SIGNAL_0;
}
default:
tt_crash_implementation("not implemented");
}
}
static void wifi_update_statusbar(Wifi* wifi) {
const char* icon = wifi_get_status_icon(wifi->radio_state, wifi->secure_connection);
if (icon != wifi->last_statusbar_icon) {
tt_statusbar_icon_set_image(wifi->statusbar_icon_id, icon);
wifi->last_statusbar_icon = icon;
}
}
static void wifi_connect_internal(Wifi* wifi, WifiConnectMessage* connect_message) { static void wifi_connect_internal(Wifi* wifi, WifiConnectMessage* connect_message) {
// TODO: only when connected! // TODO: only when connected!
wifi_disconnect_internal(wifi); wifi_disconnect_internal(wifi);
@ -536,6 +505,7 @@ static void wifi_disconnect_internal(Wifi* wifi) {
TT_LOG_E(TAG, "Failed to disconnect (%s)", esp_err_to_name(stop_result)); TT_LOG_E(TAG, "Failed to disconnect (%s)", esp_err_to_name(stop_result));
} else { } else {
wifi->radio_state = WIFI_RADIO_ON; wifi->radio_state = WIFI_RADIO_ON;
wifi->secure_connection = false;
wifi_publish_event_simple(wifi, WifiEventTypeDisconnected); wifi_publish_event_simple(wifi, WifiEventTypeDisconnected);
TT_LOG_I(TAG, "Disconnected"); TT_LOG_I(TAG, "Disconnected");
} }
@ -610,8 +580,6 @@ _Noreturn int32_t wifi_main(TT_UNUSED void* parameter) {
break; break;
} }
} }
wifi_update_statusbar(wifi);
} }
} }
@ -642,3 +610,4 @@ const ServiceManifest wifi_service = {
.on_stop = &wifi_service_stop .on_stop = &wifi_service_stop
}; };
#endif // ESP_TARGET

View File

@ -0,0 +1,157 @@
#include "wifi.h"
#ifndef ESP_TARGET
#include "assets.h"
#include "check.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;
bool scan_active;
bool secure_connection;
WifiRadioState radio_state;
} Wifi;
static Wifi* wifi_singleton = NULL;
// Forward declarations
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();
instance->scan_active = false;
instance->radio_state = WIFI_RADIO_OFF;
instance->secure_connection = false;
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);
wifi_singleton->scan_active = false; // TODO: enable and then later disable automatically
}
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);
// TODO: implement
}
void wifi_disconnect() {
tt_assert(wifi_singleton);
// TODO: implement
}
void wifi_set_scan_records(uint16_t records) {
tt_assert(wifi_singleton);
// TODO: implement
}
void wifi_get_scan_results(WifiApRecord records[], uint16_t limit, uint16_t* result_count) {
tt_check(wifi_singleton);
tt_check(result_count);
// TODO: implement
*result_count = 0;
}
void wifi_set_enabled(bool enabled) {
tt_assert(wifi_singleton != NULL);
if (enabled) {
wifi_singleton->radio_state = WIFI_RADIO_CONNECTION_ACTIVE;
wifi_singleton->secure_connection = true;
} else {
wifi_singleton->radio_state = WIFI_RADIO_OFF;
}
}
bool wifi_is_connection_secure() {
return wifi_singleton->secure_connection;
}
int wifi_get_rssi() {
// TODO: implement
return -10;
}
// endregion Public functions
static void wifi_lock(Wifi* wifi) {
tt_crash("this fails for now");
tt_assert(wifi);
tt_assert(wifi->mutex);
tt_mutex_acquire(wifi->mutex, 100);
}
static void wifi_unlock(Wifi* wifi) {
tt_assert(wifi);
tt_assert(wifi->mutex);
tt_mutex_release(wifi->mutex);
}
static void wifi_service_start(TT_UNUSED Service service) {
tt_check(wifi_singleton == NULL);
wifi_singleton = wifi_alloc();
}
static void wifi_service_stop(TT_UNUSED Service service) {
tt_check(wifi_singleton != NULL);
wifi_free(wifi_singleton);
wifi_singleton = NULL;
}
const ServiceManifest wifi_service = {
.id = "wifi",
.on_start = &wifi_service_start,
.on_stop = &wifi_service_stop
};
#endif // ESP_TARGET

View File

@ -0,0 +1,16 @@
#include "tactility_headless.h"
#include "hardware_config.h"
#include "hardware_i.h"
#include "service_registry.h"
static const HardwareConfig* hardwareConfig = NULL;
void tt_tactility_headless_init(const HardwareConfig* config, const ServiceManifest* const services[32]) {
tt_service_registry_init();
tt_hardware_init(config);
hardwareConfig = config;
}
const HardwareConfig* tt_get_hardware_config() {
return hardwareConfig;
}

View File

@ -0,0 +1,17 @@
#pragma once
#include "hardware_config.h"
#include "service_manifest.h"
#include "tactility_headless_config.h"
#ifdef __cplusplus
extern "C" {
#endif
void tt_tactility_headless_init(const HardwareConfig* config, const ServiceManifest* const services[32]);
const HardwareConfig* tt_get_hardware_config();
#ifdef __cplusplus
}
#endif

View File

@ -0,0 +1,3 @@
#pragma once
#define TT_CONFIG_SERVICES_LIMIT 32

View File

@ -3,35 +3,39 @@ set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF) set(CMAKE_CXX_EXTENSIONS OFF)
file(GLOB_RECURSE SOURCES "src/*.c")
file(GLOB_RECURSE HEADERS "src/*.h")
add_library(tactility OBJECT)
target_sources(tactility
PRIVATE ${SOURCES}
PUBLIC ${HEADERS}
)
target_include_directories(tactility
PRIVATE src/
INTERFACE src/
)
if (DEFINED ENV{ESP_IDF_VERSION}) if (DEFINED ENV{ESP_IDF_VERSION})
add_definitions(-DESP_PLATFORM) file(GLOB_RECURSE SOURCE_FILES src/*.c)
target_link_libraries(tactility
PUBLIC tactility-core idf_component_register(
PUBLIC idf::lvgl # libs/ SRCS ${SOURCE_FILES}
PUBLIC idf::driver INCLUDE_DIRS "src/"
PUBLIC idf::spiffs REQUIRES tactility-headless lvgl
PUBLIC idf::nvs_flash )
PUBLIC idf::newlib # for scandir() and related
target_link_libraries(${COMPONENT_LIB}
PUBLIC lv_screenshot PUBLIC lv_screenshot
) )
add_definitions(-DESP_PLATFORM)
else() else()
file(GLOB_RECURSE SOURCES "src/*.c")
file(GLOB_RECURSE HEADERS "src/*.h")
add_library(tactility OBJECT)
target_sources(tactility
PRIVATE ${SOURCES}
PUBLIC ${HEADERS}
)
target_include_directories(tactility
PRIVATE src/
INTERFACE src/
)
add_definitions(-D_Nullable=) add_definitions(-D_Nullable=)
add_definitions(-D_Nonnull=) add_definitions(-D_Nonnull=)
target_link_libraries(tactility target_link_libraries(tactility
PUBLIC tactility-core PUBLIC tactility-headless
PUBLIC lvgl PUBLIC lvgl
PUBLIC freertos_kernel PUBLIC freertos_kernel
PUBLIC lv_screenshot PUBLIC lv_screenshot

View File

@ -1,3 +1,5 @@
#ifdef ESP_TARGET
#include "services/loader/loader.h" #include "services/loader/loader.h"
#include "ui/toolbar.h" #include "ui/toolbar.h"
#include "thread.h" #include "thread.h"
@ -203,3 +205,5 @@ const AppManifest gpio_app = {
.on_show = &app_show, .on_show = &app_show,
.on_hide = &on_hide .on_hide = &on_hide
}; };
#endif // ESP_TARGET

View File

@ -1,7 +1,7 @@
#include "wifi_manage.h" #include "wifi_manage.h"
#include "app.h" #include "app.h"
#include "apps/system/wifi_connect/wifi_connect_bundle.h" #include "apps/wifi_connect/wifi_connect_bundle.h"
#include "services/loader/loader.h" #include "services/loader/loader.h"
#include "tactility_core.h" #include "tactility_core.h"
#include "ui/lvgl_sync.h" #include "ui/lvgl_sync.h"

View File

@ -1,6 +1,7 @@
#include "wifi_manage_view.h" #include "wifi_manage_view.h"
#include "log.h" #include "log.h"
#include "services/statusbar_updater/statusbar_updater.h"
#include "services/wifi/wifi.h" #include "services/wifi/wifi.h"
#include "ui/style.h" #include "ui/style.h"
#include "ui/toolbar.h" #include "ui/toolbar.h"

View File

@ -1,12 +1,8 @@
#include "hardware_i.h" #include "lvgl_init_i.h"
#include "lvgl.h"
#include "preferences.h" #include "preferences.h"
#include "sdcard_i.h" #include "lvgl.h"
#define TAG "hardware" void tt_lvgl_init(const HardwareConfig* config) {
static void init_display_settings(const HardwareConfig* config) {
SetBacklightDuty set_backlight_duty = config->display.set_backlight_duty; SetBacklightDuty set_backlight_duty = config->display.set_backlight_duty;
if (set_backlight_duty != NULL) { if (set_backlight_duty != NULL) {
int32_t backlight_duty = 200; int32_t backlight_duty = 200;
@ -24,21 +20,3 @@ static void init_display_settings(const HardwareConfig* config) {
} }
} }
} }
void tt_hardware_init(const HardwareConfig* config) {
if (config->bootstrap != NULL) {
TT_LOG_I(TAG, "Bootstrapping");
tt_check(config->bootstrap(), "bootstrap failed");
}
tt_sdcard_init();
if (config->sdcard != NULL) {
TT_LOG_I(TAG, "Mounting sdcard");
tt_sdcard_mount(config->sdcard);
}
tt_check(config->init_lvgl, "lvlg init not set");
tt_check(config->init_lvgl(), "lvgl init failed");
init_display_settings(config);
}

View File

@ -0,0 +1,13 @@
#pragma once
#include "hardware_config.h"
#ifdef __cplusplus
extern "C" {
#endif
void tt_lvgl_init(const HardwareConfig* config);
#ifdef __cplusplus
}
#endif

View File

@ -1,6 +1,7 @@
#include "gui_i.h" #include "gui_i.h"
#include "tactility.h" #include "tactility.h"
#include "services/loader/loader.h"
#include "ui/lvgl_sync.h" #include "ui/lvgl_sync.h"
#include "ui/lvgl_keypad.h" #include "ui/lvgl_keypad.h"
@ -18,6 +19,19 @@ static int32_t gui_main(void*);
Gui* gui = NULL; Gui* gui = NULL;
typedef void (*PubSubCallback)(const void* message, void* context);
void gui_loader_callback(const void* message, void* context) {
Gui* gui = (Gui*)context;
LoaderEvent* event = (LoaderEvent*)message;
if (event->type == LoaderEventTypeApplicationShowing) {
App* app = event->app_showing.app;
AppManifest* app_manifest = tt_app_get_manifest(app);
gui_show_app(app, app_manifest->on_show, app_manifest->on_hide);
} else if (event->type == LoaderEventTypeApplicationHiding) {
gui_hide_app();
}
}
Gui* gui_alloc() { Gui* gui_alloc() {
Gui* instance = malloc(sizeof(Gui)); Gui* instance = malloc(sizeof(Gui));
memset(instance, 0, sizeof(Gui)); memset(instance, 0, sizeof(Gui));
@ -30,7 +44,7 @@ Gui* gui_alloc() {
); );
instance->mutex = tt_mutex_alloc(MutexTypeRecursive); instance->mutex = tt_mutex_alloc(MutexTypeRecursive);
instance->keyboard = NULL; instance->keyboard = NULL;
instance->loader_pubsub_subscription = tt_pubsub_subscribe(loader_get_pubsub(), &gui_loader_callback, instance);
tt_check(tt_lvgl_lock(1000 / portTICK_PERIOD_MS)); tt_check(tt_lvgl_lock(1000 / portTICK_PERIOD_MS));
instance->keyboard_group = lv_group_create(); instance->keyboard_group = lv_group_create();
instance->lvgl_parent = lv_scr_act(); instance->lvgl_parent = lv_scr_act();

View File

@ -18,6 +18,7 @@ struct Gui {
// Thread and lock // Thread and lock
Thread* thread; Thread* thread;
Mutex* mutex; Mutex* mutex;
PubSubSubscription* loader_pubsub_subscription;
// Layers and Canvas // Layers and Canvas
lv_obj_t* lvgl_parent; lv_obj_t* lvgl_parent;

View File

@ -17,6 +17,10 @@
#define TAG "loader" #define TAG "loader"
typedef struct {
LoaderEventType type;
} LoaderEventInternal;
// Forward declarations // Forward declarations
static int32_t loader_main(void* p); static int32_t loader_main(void* p);
@ -25,7 +29,8 @@ static Loader* loader_singleton = NULL;
static Loader* loader_alloc() { static Loader* loader_alloc() {
tt_check(loader_singleton == NULL); tt_check(loader_singleton == NULL);
loader_singleton = malloc(sizeof(Loader)); loader_singleton = malloc(sizeof(Loader));
loader_singleton->pubsub = tt_pubsub_alloc(); loader_singleton->pubsub_internal = tt_pubsub_alloc();
loader_singleton->pubsub_external = tt_pubsub_alloc();
loader_singleton->queue = tt_message_queue_alloc(1, sizeof(LoaderMessage)); loader_singleton->queue = tt_message_queue_alloc(1, sizeof(LoaderMessage));
loader_singleton->thread = tt_thread_alloc_ex( loader_singleton->thread = tt_thread_alloc_ex(
"loader", "loader",
@ -42,7 +47,8 @@ static Loader* loader_alloc() {
static void loader_free() { static void loader_free() {
tt_check(loader_singleton != NULL); tt_check(loader_singleton != NULL);
tt_thread_free(loader_singleton->thread); tt_thread_free(loader_singleton->thread);
tt_pubsub_free(loader_singleton->pubsub); tt_pubsub_free(loader_singleton->pubsub_internal);
tt_pubsub_free(loader_singleton->pubsub_external);
tt_message_queue_free(loader_singleton->queue); tt_message_queue_free(loader_singleton->queue);
tt_mutex_free(loader_singleton->mutex); tt_mutex_free(loader_singleton->mutex);
free(loader_singleton); free(loader_singleton);
@ -104,7 +110,7 @@ PubSub* loader_get_pubsub() {
// it's safe to return pubsub without locking // it's safe to return pubsub without locking
// because it's never freed and loader is never exited // because it's never freed and loader is never exited
// also the loader instance cannot be obtained until the pubsub is created // also the loader instance cannot be obtained until the pubsub is created
return loader_singleton->pubsub; return loader_singleton->pubsub_external;
} }
static const char* app_state_to_string(AppState state) { static const char* app_state_to_string(AppState state) {
@ -147,15 +153,23 @@ static void app_transition_to_state(App app, AppState state) {
tt_app_set_state(app, AppStateStarted); tt_app_set_state(app, AppStateStarted);
break; break;
case AppStateShowing: case AppStateShowing:
gui_show_app( LoaderEvent event_showing = {
app, .type = LoaderEventTypeApplicationShowing,
manifest->on_show, .app_showing = {
manifest->on_hide .app = app
); }
};
tt_pubsub_publish(loader_singleton->pubsub_external, &event_showing);
tt_app_set_state(app, AppStateShowing); tt_app_set_state(app, AppStateShowing);
break; break;
case AppStateHiding: case AppStateHiding:
gui_hide_app(); LoaderEvent event_hiding = {
.type = LoaderEventTypeApplicationHiding,
.app_hiding = {
.app = app
}
};
tt_pubsub_publish(loader_singleton->pubsub_external, &event_hiding);
tt_app_set_state(app, AppStateHiding); tt_app_set_state(app, AppStateHiding);
break; break;
case AppStateStopped: case AppStateStopped:
@ -200,8 +214,16 @@ LoaderStatus loader_do_start_app_with_manifest(
loader_unlock(); loader_unlock();
LoaderEvent event = {.type = LoaderEventTypeApplicationStarted}; LoaderEventInternal event_internal = {.type = LoaderEventTypeApplicationStarted};
tt_pubsub_publish(loader_singleton->pubsub, &event); tt_pubsub_publish(loader_singleton->pubsub_internal, &event_internal);
LoaderEvent event_external = {
.type = LoaderEventTypeApplicationStarted,
.app_started = {
.app = app
}
};
tt_pubsub_publish(loader_singleton->pubsub_external, &event_external);
return LoaderStatusOk; return LoaderStatusOk;
} }
@ -240,6 +262,7 @@ static void loader_do_stop_app() {
// Stop current app // Stop current app
App app_to_stop = loader_singleton->app_stack[current_app_index]; App app_to_stop = loader_singleton->app_stack[current_app_index];
AppManifest* manifest = tt_app_get_manifest(app_to_stop);
app_transition_to_state(app_to_stop, AppStateHiding); app_transition_to_state(app_to_stop, AppStateHiding);
app_transition_to_state(app_to_stop, AppStateStopped); app_transition_to_state(app_to_stop, AppStateStopped);
@ -258,8 +281,16 @@ static void loader_do_stop_app() {
loader_unlock(); loader_unlock();
LoaderEvent event = {.type = LoaderEventTypeApplicationStopped}; LoaderEventInternal event_internal = {.type = LoaderEventTypeApplicationStopped};
tt_pubsub_publish(loader_singleton->pubsub, &event); tt_pubsub_publish(loader_singleton->pubsub_internal, &event_internal);
LoaderEvent event_external = {
.type = LoaderEventTypeApplicationStopped,
.app_stopped = {
.manifest = manifest
}
};
tt_pubsub_publish(loader_singleton->pubsub_external, &event_external);
} }

View File

@ -21,15 +21,39 @@ typedef enum {
typedef enum { typedef enum {
LoaderEventTypeApplicationStarted, LoaderEventTypeApplicationStarted,
LoaderEventTypeApplicationShowing,
LoaderEventTypeApplicationHiding,
LoaderEventTypeApplicationStopped LoaderEventTypeApplicationStopped
} LoaderEventType; } LoaderEventType;
typedef struct {
App* app;
} LoaderEventAppStarted;
typedef struct {
App* app;
} LoaderEventAppShowing;
typedef struct {
App* app;
} LoaderEventAppHiding;
typedef struct {
AppManifest* manifest;
} LoaderEventAppStopped;
typedef struct { typedef struct {
LoaderEventType type; LoaderEventType type;
union {
LoaderEventAppStarted app_started;
LoaderEventAppShowing app_showing;
LoaderEventAppHiding app_hiding;
LoaderEventAppStopped app_stopped;
};
} LoaderEvent; } LoaderEvent;
/** /**
* @brief Close any running app, then start new one. Blocking. * @brief Start an app
* @param[in] id application name or id * @param[in] id application name or id
* @param[in] blocking application arguments * @param[in] blocking application arguments
* @param[in] bundle optional bundle. Ownership is transferred to Loader. * @param[in] bundle optional bundle. Ownership is transferred to Loader.
@ -37,13 +61,15 @@ typedef struct {
*/ */
LoaderStatus loader_start_app(const char* id, bool blocking, Bundle* _Nullable bundle); LoaderStatus loader_start_app(const char* id, bool blocking, Bundle* _Nullable bundle);
/**
* @brief Stop the currently showing app. Show the previous app if any app was still running.
*/
void loader_stop_app(); void loader_stop_app();
App _Nullable loader_get_current_app(); App _Nullable loader_get_current_app();
/** /**
* @brief Get loader pubsub * @brief PubSub for LoaderEvent
* @return PubSub*
*/ */
PubSub* loader_get_pubsub(); PubSub* loader_get_pubsub();

View File

@ -20,7 +20,8 @@
struct Loader { struct Loader {
Thread* thread; Thread* thread;
PubSub* pubsub; PubSub* pubsub_internal;
PubSub* pubsub_external;
MessageQueue* queue; MessageQueue* queue;
Mutex* mutex; Mutex* mutex;
int8_t app_stack_index; int8_t app_stack_index;

View File

@ -0,0 +1,160 @@
#include "mutex.h"
#include "service.h"
#include "ui/statusbar.h"
#define TAG "wifi_statusbar_service"
#include "assets.h"
#include "sdcard.h"
#include "services/wifi/wifi.h"
typedef struct {
Mutex* mutex;
Thread* thread;
bool service_interrupted;
int8_t wifi_icon_id;
const char* wifi_last_icon;
int8_t sdcard_icon_id;
const char* sdcard_last_icon;
} ServiceData;
const char* wifi_get_status_icon_for_rssi(int rssi, bool secured) {
if (rssi > 0) {
return TT_ASSETS_ICON_WIFI_CONNECTION_ISSUE;
} else if (rssi > -67) {
return secured ? TT_ASSETS_ICON_WIFI_SIGNAL_4_LOCKED : TT_ASSETS_ICON_WIFI_SIGNAL_4;
} else if (rssi > -70) {
return secured ? TT_ASSETS_ICON_WIFI_SIGNAL_3_LOCKED : TT_ASSETS_ICON_WIFI_SIGNAL_3;
} else if (rssi > -80) {
return secured ? TT_ASSETS_ICON_WIFI_SIGNAL_2_LOCKED : TT_ASSETS_ICON_WIFI_SIGNAL_2;
} else {
return secured ? TT_ASSETS_ICON_WIFI_SIGNAL_1_LOCKED : TT_ASSETS_ICON_WIFI_SIGNAL_1;
}
}
static const char* wifi_get_status_icon(WifiRadioState state, bool secure) {
int rssi;
switch (state) {
case WIFI_RADIO_ON_PENDING:
case WIFI_RADIO_ON:
case WIFI_RADIO_OFF_PENDING:
case WIFI_RADIO_OFF:
return TT_ASSETS_ICON_WIFI_OFF;
case WIFI_RADIO_CONNECTION_PENDING:
return TT_ASSETS_ICON_WIFI_FIND;
case WIFI_RADIO_CONNECTION_ACTIVE:
rssi = wifi_get_rssi();
return wifi_get_status_icon_for_rssi(rssi, secure);
default:
tt_crash_implementation("not implemented");
}
}
static _Nullable const char* sdcard_get_status_icon(SdcardState state) {
switch (state) {
case SdcardStateMounted:
return TT_ASSETS_ICON_SDCARD;
case SdcardStateError:
case SdcardStateUnmounted:
return TT_ASSETS_ICON_SDCARD_ALERT;
default:
return NULL;
}
}
static void update_wifi_icon(ServiceData* data) {
WifiRadioState radio_state = wifi_get_radio_state();
bool is_secure = wifi_is_connection_secure();
const char* desired_icon = wifi_get_status_icon(radio_state, is_secure);
if (data->wifi_last_icon != desired_icon) {
tt_statusbar_icon_set_image(data->wifi_icon_id, desired_icon);
data->wifi_last_icon = desired_icon;
}
}
static void update_sdcard_icon(ServiceData* data) {
SdcardState state = tt_sdcard_get_state();
const char* desired_icon = sdcard_get_status_icon(state);
if (data->sdcard_last_icon != desired_icon) {
tt_statusbar_icon_set_image(data->sdcard_icon_id, desired_icon);
tt_statusbar_icon_set_visibility(data->sdcard_icon_id, desired_icon != NULL);
data->sdcard_last_icon = desired_icon;
}
}
static ServiceData* service_data_alloc() {
ServiceData* data = malloc(sizeof(ServiceData));
*data = (ServiceData) {
.mutex = tt_mutex_alloc(MutexTypeNormal),
.thread = tt_thread_alloc(),
.service_interrupted = false,
.wifi_icon_id = tt_statusbar_icon_add(NULL),
.wifi_last_icon = NULL,
.sdcard_icon_id = tt_statusbar_icon_add(NULL),
.sdcard_last_icon = NULL,
};
tt_statusbar_icon_set_visibility(data->wifi_icon_id, true);
update_wifi_icon(data);
update_sdcard_icon(data); // also updates visibility
return data;
}
static void service_data_free(ServiceData* data) {
tt_mutex_free(data->mutex);
tt_thread_free(data->thread);
tt_statusbar_icon_remove(data->wifi_icon_id);
free(data);
}
static void service_data_lock(ServiceData* data) {
tt_check(tt_mutex_acquire(data->mutex, TtWaitForever) == TtStatusOk);
}
static void service_data_unlock(ServiceData* data) {
tt_check(tt_mutex_release(data->mutex) == TtStatusOk);
}
int32_t service_main(TT_UNUSED void* parameter) {
TT_LOG_I(TAG, "Started main loop");
ServiceData* data = (ServiceData*)parameter;
tt_check(data != NULL);
while (!data->service_interrupted) {
update_wifi_icon(data);
update_sdcard_icon(data);
tt_delay_ms(1000);
}
return 0;
}
static void on_start(Service service) {
ServiceData* data = service_data_alloc();
tt_service_set_data(service, data);
tt_thread_set_callback(data->thread, service_main);
tt_thread_set_current_priority(ThreadPriorityLow);
tt_thread_set_stack_size(data->thread, 2048); // 2048 was the minimum when last tested
tt_thread_set_context(data->thread, data);
tt_thread_start(data->thread);
}
static void on_stop(Service service) {
ServiceData* data = tt_service_get_data(service);
// Stop thread
service_data_lock(data);
data->service_interrupted = true;
service_data_unlock(data);
tt_mutex_release(data->mutex);
tt_thread_join(data->thread);
service_data_free(data);
}
const ServiceManifest statusbar_updater_service = {
.id = "statusbar_updater",
.on_start = &on_start,
.on_stop = &on_stop
};

View File

@ -0,0 +1,19 @@
#pragma once
#include <stdbool.h>
#ifdef __cplusplus
extern "C" {
#endif
/**
* Return the relevant icon asset from assets.h for the given inputs
* @param rssi the rssi value
* @param secured whether the access point is a secured one (as in: not an open one)
* @return
*/
const char* wifi_get_status_icon_for_rssi(int rssi, bool secured);
#ifdef __cplusplus
}
#endif

View File

@ -1,9 +1,11 @@
#include "tactility.h" #include "tactility.h"
#include "app_manifest_registry.h" #include "app_manifest_registry.h"
#include "hardware_i.h" #include "esp_init.h"
#include "lvgl_init_i.h"
#include "service_registry.h" #include "service_registry.h"
#include "services/loader/loader.h" #include "services/loader/loader.h"
#include "tactility_headless.h"
#define TAG "tactility" #define TAG "tactility"
@ -15,14 +17,18 @@ extern const ServiceManifest gui_service;
extern const ServiceManifest loader_service; extern const ServiceManifest loader_service;
extern const ServiceManifest screenshot_service; extern const ServiceManifest screenshot_service;
extern const ServiceManifest sdcard_service; extern const ServiceManifest sdcard_service;
extern const ServiceManifest wifi_service;
extern const ServiceManifest statusbar_updater_service;
static const ServiceManifest* const system_services[] = { static const ServiceManifest* const system_services[] = {
&gui_service, &loader_service,
&loader_service, // depends on gui service &gui_service, // depends on loader service
#ifndef ESP_PLATFORM // Screenshots don't work yet on ESP32 #ifndef ESP_PLATFORM // Screenshots don't work yet on ESP32
&screenshot_service, &screenshot_service,
#endif #endif
&sdcard_service &sdcard_service,
&wifi_service,
&statusbar_updater_service
}; };
// endregion // endregion
@ -32,19 +38,28 @@ static const ServiceManifest* const system_services[] = {
extern const AppManifest desktop_app; extern const AppManifest desktop_app;
extern const AppManifest display_app; extern const AppManifest display_app;
extern const AppManifest files_app; extern const AppManifest files_app;
extern const AppManifest screenshot_app;
extern const AppManifest settings_app; extern const AppManifest settings_app;
extern const AppManifest system_info_app; extern const AppManifest system_info_app;
extern const AppManifest wifi_connect_app;
extern const AppManifest wifi_manage_app;
#ifdef ESP_PLATFORM
extern const AppManifest screenshot_app;
extern const AppManifest gpio_app;
#endif
static const AppManifest* const system_apps[] = { static const AppManifest* const system_apps[] = {
&desktop_app, &desktop_app,
&display_app, &display_app,
&files_app, &files_app,
#ifndef ESP_PLATFORM // Screenshots don't work yet on ESP32 &settings_app,
&system_info_app,
&wifi_connect_app,
&wifi_manage_app,
#ifdef ESP_PLATFORM // Screenshots don't work yet on ESP32
&gpio_app,
&screenshot_app, &screenshot_app,
#endif #endif
&settings_app,
&system_info_app
}; };
// endregion // endregion
@ -96,13 +111,18 @@ static void register_and_start_user_services(const ServiceManifest* const servic
void tt_init(const Config* config) { void tt_init(const Config* config) {
TT_LOG_I(TAG, "tt_init started"); TT_LOG_I(TAG, "tt_init started");
#ifdef ESP_PLATFORM
tt_esp_init();
#endif
// Assign early so starting services can use it // Assign early so starting services can use it
config_instance = config; config_instance = config;
tt_service_registry_init(); tt_tactility_headless_init(config->hardware, config->services);
tt_app_manifest_registry_init();
tt_hardware_init(config->hardware); tt_lvgl_init(config->hardware);
tt_app_manifest_registry_init();
// Note: the order of starting apps and services is critical! // Note: the order of starting apps and services is critical!
// System services are registered first so the apps below can use them // System services are registered first so the apps below can use them

View File

@ -1,6 +1,6 @@
#pragma once #pragma once
#define TT_CONFIG_APPS_LIMIT 32 #include "tactility_headless_config.h"
#define TT_CONFIG_SERVICES_LIMIT 32
#define TT_CONFIG_APPS_LIMIT 32
#define TT_CONFIG_FORCE_ONSCREEN_KEYBOARD false #define TT_CONFIG_FORCE_ONSCREEN_KEYBOARD false

View File

@ -25,7 +25,7 @@ typedef struct {
static StatusbarData statusbar_data = { static StatusbarData statusbar_data = {
.mutex = NULL, .mutex = NULL,
.icons = {0} .icons = {}
}; };
typedef struct { typedef struct {