diff --git a/README.md b/README.md index 3efdf925..fa71d41c 100644 --- a/README.md +++ b/README.md @@ -50,7 +50,7 @@ Predefined configurations are available for: (*) Note: Only the capacitive version is supported. See AliExpress [here][2432s024c_1] and [here][2432s024c_2]. [tdeck]: https://www.lilygo.cc/products/t-deck -[waveshare_s3_touch]: https://www.aliexpress.com/item/1005005692235592.html +[waveshare_s3_touch]: https://www.waveshare.com/wiki/ESP32-S3-Touch-LCD-4.3 [2432s024c_1]: https://www.aliexpress.com/item/1005005902429049.html [2432s024c_2]: https://www.aliexpress.com/item/1005005865107357.html diff --git a/app-esp/src/hello_world/hello_world.c b/app-esp/src/hello_world/hello_world.c index bb101e77..ef805f10 100644 --- a/app-esp/src/hello_world/hello_world.c +++ b/app-esp/src/hello_world/hello_world.c @@ -1,6 +1,5 @@ #include "hello_world.h" -#include "services/gui/gui.h" -#include "services/loader/loader.h" +#include "lvgl.h" static void app_show(TT_UNUSED App app, lv_obj_t* parent) { lv_obj_t* label = lv_label_create(parent); diff --git a/boards/lilygo_tdeck/bootstrap.c b/boards/lilygo_tdeck/bootstrap.c index 7dace3b1..ef9bc1c8 100644 --- a/boards/lilygo_tdeck/bootstrap.c +++ b/boards/lilygo_tdeck/bootstrap.c @@ -1,13 +1,11 @@ #include "config.h" -#include "kernel.h" +#include "display_i.h" +#include "driver/spi_common.h" #include "keyboard.h" -#include "log.h" -#include +#include "tactility_core.h" #define TAG "tdeck_bootstrap" -lv_disp_t* tdeck_display_init(); - static bool tdeck_power_on() { gpio_config_t device_power_signal_config = { .pin_bit_mask = BIT64(TDECK_POWERON_GPIO), @@ -38,8 +36,7 @@ static bool init_i2c() { .master.clk_speed = 400000 }; - return i2c_param_config(TDECK_I2C_BUS_HANDLE, &i2c_conf) == ESP_OK - && i2c_driver_install(TDECK_I2C_BUS_HANDLE, i2c_conf.mode, 0, 0, 0) == ESP_OK; + return i2c_param_config(TDECK_I2C_BUS_HANDLE, &i2c_conf) == ESP_OK && i2c_driver_install(TDECK_I2C_BUS_HANDLE, i2c_conf.mode, 0, 0, 0) == ESP_OK; } static bool init_spi() { @@ -89,6 +86,13 @@ bool tdeck_bootstrap() { return false; } + // Don't turn the backlight on yet - Tactility init will take care of it + TT_LOG_I(TAG, "Init backlight"); + if (!tdeck_backlight_init()) { + TT_LOG_E(TAG, "Init backlight failed"); + return false; + } + keyboard_wait_for_response(); return true; diff --git a/boards/lilygo_tdeck/display.c b/boards/lilygo_tdeck/display.c index 4e1a5ba7..cfde2fec 100644 --- a/boards/lilygo_tdeck/display.c +++ b/boards/lilygo_tdeck/display.c @@ -9,7 +9,7 @@ #define TAG "tdeck_display" -void tdeck_enable_backlight() { +bool tdeck_backlight_init() { ledc_timer_config_t ledc_timer = { .speed_mode = TDECK_LCD_BACKLIGHT_LEDC_MODE, .timer_num = TDECK_LCD_BACKLIGHT_LEDC_TIMER, @@ -17,20 +17,31 @@ void tdeck_enable_backlight() { .freq_hz = TDECK_LCD_BACKLIGHT_LEDC_FREQUENCY, .clk_cfg = LEDC_AUTO_CLK }; - ESP_ERROR_CHECK(ledc_timer_config(&ledc_timer)); + if (ledc_timer_config(&ledc_timer) != ESP_OK) { + TT_LOG_E(TAG, "Backlight led timer config failed"); + return false; + } + + return true; +} + +void tdeck_backlight_set(uint8_t duty) { ledc_channel_config_t ledc_channel = { .speed_mode = TDECK_LCD_BACKLIGHT_LEDC_MODE, .channel = TDECK_LCD_BACKLIGHT_LEDC_CHANNEL, .timer_sel = TDECK_LCD_BACKLIGHT_LEDC_TIMER, .intr_type = LEDC_INTR_DISABLE, - .gpio_num = TDECK_LCD_BACKLIGHT_LEDC_OUTPUT_IO, - .duty = 0, // Set duty to 0% + .gpio_num = TDECK_LCD_PIN_BACKLIGHT, + .duty = duty, .hpoint = 0 }; - ESP_ERROR_CHECK(ledc_channel_config(&ledc_channel)); - ESP_ERROR_CHECK(ledc_set_duty(TDECK_LCD_BACKLIGHT_LEDC_MODE, TDECK_LCD_BACKLIGHT_LEDC_CHANNEL, TDECK_LCD_BACKLIGHT_LEDC_DUTY)); + // Setting the config in the timer init and then calling ledc_set_duty() doesn't work when + // the app is running. For an unknown reason we have to call this config method every time: + if (ledc_channel_config(&ledc_channel) != ESP_OK) { + TT_LOG_E(TAG, "Failed to configure display backlight"); + } } lv_disp_t* tdeck_display_init() { diff --git a/boards/lilygo_tdeck/display_i.h b/boards/lilygo_tdeck/display_i.h new file mode 100644 index 00000000..279b904b --- /dev/null +++ b/boards/lilygo_tdeck/display_i.h @@ -0,0 +1,18 @@ +#pragma once + +#include "hal/lv_hal_disp.h" +#include + +#ifdef __cplusplus +extern "C" { +#endif + +lv_disp_t* tdeck_display_init(); + +bool tdeck_backlight_init(); + +void tdeck_backlight_set(uint8_t duty); + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/boards/lilygo_tdeck/lilygo_tdeck.c b/boards/lilygo_tdeck/lilygo_tdeck.c index 69ddb953..d7bb06f4 100644 --- a/boards/lilygo_tdeck/lilygo_tdeck.c +++ b/boards/lilygo_tdeck/lilygo_tdeck.c @@ -1,4 +1,5 @@ #include "lilygo_tdeck.h" +#include "display_i.h" #include bool tdeck_bootstrap(); @@ -8,6 +9,9 @@ extern const SdCard tdeck_sdcard; const HardwareConfig lilygo_tdeck = { .bootstrap = &tdeck_bootstrap, + .display = { + .set_backlight_duty = &tdeck_backlight_set + }, .init_lvgl = &tdeck_init_lvgl, .sdcard = &tdeck_sdcard }; diff --git a/boards/lilygo_tdeck/lvgl.c b/boards/lilygo_tdeck/lvgl.c index 67e3d363..ec98e421 100644 --- a/boards/lilygo_tdeck/lvgl.c +++ b/boards/lilygo_tdeck/lvgl.c @@ -1,14 +1,13 @@ #include "config.h" +#include "display_i.h" #include "esp_lvgl_port.h" #include "keyboard.h" #include "log.h" #include "ui/lvgl_sync.h" -#include +#include "thread.h" #define TAG "tdeck_lvgl" -lv_disp_t* tdeck_display_init(); -void tdeck_enable_backlight(); bool tdeck_init_touch(esp_lcd_panel_io_handle_t* io_handle, esp_lcd_touch_handle_t* touch_handle); bool tdeck_init_lvgl() { @@ -16,11 +15,9 @@ bool tdeck_init_lvgl() { static esp_lcd_panel_io_handle_t touch_io_handle; static esp_lcd_touch_handle_t touch_handle; - // Init LVGL Port library - const lvgl_port_cfg_t lvgl_cfg = { .task_priority = THREAD_PRIORITY_RENDER, - .task_stack = TDECK_LVGL_TASK_STACK_DEPTH , + .task_stack = TDECK_LVGL_TASK_STACK_DEPTH, .task_affinity = -1, // core pinning .task_max_sleep_ms = 500, .timer_period_ms = 5 @@ -66,8 +63,5 @@ bool tdeck_init_lvgl() { keyboard_alloc(display); - TT_LOG_D(TAG, "enabling backlight"); - tdeck_enable_backlight(); - return true; } diff --git a/boards/waveshare_s3_touch/waveshare_s3_touch.c b/boards/waveshare_s3_touch/waveshare_s3_touch.c index c75a65e8..2793ab47 100644 --- a/boards/waveshare_s3_touch/waveshare_s3_touch.c +++ b/boards/waveshare_s3_touch/waveshare_s3_touch.c @@ -6,5 +6,8 @@ bool ws3t_bootstrap(); const HardwareConfig waveshare_s3_touch = { .bootstrap = &ws3t_bootstrap, + .display = { + .set_backlight_duty = NULL // TODO: This requires implementing the CH422G IO expander + }, .init_lvgl = &ws3t_init_lvgl }; diff --git a/boards/yellow_board/bootstrap.c b/boards/yellow_board/bootstrap.c index 4a61e9ce..039332f7 100644 --- a/boards/yellow_board/bootstrap.c +++ b/boards/yellow_board/bootstrap.c @@ -1,6 +1,6 @@ #include "config.h" -#include "kernel.h" -#include "log.h" +#include "tactility_core.h" +#include "display_i.h" #include #define TAG "twodotfour_bootstrap" @@ -83,5 +83,12 @@ bool twodotfour_bootstrap() { return false; } + // Don't turn the backlight on yet - Tactility init will take care of it + TT_LOG_I(TAG, "Init backlight"); + if (!twodotfour_backlight_init()) { + TT_LOG_E(TAG, "Init backlight failed"); + return false; + } + return true; } diff --git a/boards/yellow_board/display.c b/boards/yellow_board/display.c index d095d6d4..ae626994 100644 --- a/boards/yellow_board/display.c +++ b/boards/yellow_board/display.c @@ -1,28 +1,56 @@ #include "config.h" -#include "log.h" +#include "tactility_core.h" + #include "driver/gpio.h" +#include "driver/ledc.h" #include "esp_err.h" #include "esp_lcd_ili9341.h" +#include "esp_lcd_panel_io.h" #include "esp_lcd_panel_ops.h" #include "esp_lvgl_port.h" #include "hal/lv_hal_disp.h" -#include #define TAG "twodotfour_ili9341" -static void twodotfour_backlight_on() { - gpio_config_t io_conf = { - .pin_bit_mask = BIT64(TWODOTFOUR_LCD_PIN_BACKLIGHT), - .mode = GPIO_MODE_OUTPUT, - .pull_up_en = GPIO_PULLUP_DISABLE, - .pull_down_en = GPIO_PULLDOWN_DISABLE, - .intr_type = GPIO_INTR_DISABLE, +// Dipslay backlight (PWM) +#define TWODOTFOUR_LCD_BACKLIGHT_LEDC_TIMER LEDC_TIMER_0 +#define TWODOTFOUR_LCD_BACKLIGHT_LEDC_MODE LEDC_LOW_SPEED_MODE +#define TWODOTFOUR_LCD_BACKLIGHT_LEDC_CHANNEL LEDC_CHANNEL_0 +#define TWODOTFOUR_LCD_BACKLIGHT_LEDC_DUTY_RES LEDC_TIMER_8_BIT +#define TWODOTFOUR_LCD_BACKLIGHT_LEDC_FREQUENCY (1000) + +bool twodotfour_backlight_init() { + ledc_timer_config_t ledc_timer = { + .speed_mode = TWODOTFOUR_LCD_BACKLIGHT_LEDC_MODE, + .timer_num = TWODOTFOUR_LCD_BACKLIGHT_LEDC_TIMER, + .duty_resolution = TWODOTFOUR_LCD_BACKLIGHT_LEDC_DUTY_RES, + .freq_hz = TWODOTFOUR_LCD_BACKLIGHT_LEDC_FREQUENCY, + .clk_cfg = LEDC_AUTO_CLK }; - gpio_config(&io_conf); + if (ledc_timer_config(&ledc_timer) != ESP_OK) { + TT_LOG_E(TAG, "Backlight led timer config failed"); + return false; + } - if (gpio_set_level(TWODOTFOUR_LCD_PIN_BACKLIGHT, 1) != ESP_OK) { - TT_LOG_E(TAG, "Failed to turn backlight on"); + return true; +} + +void twodotfour_backlight_set(uint8_t duty) { + ledc_channel_config_t ledc_channel = { + .speed_mode = TWODOTFOUR_LCD_BACKLIGHT_LEDC_MODE, + .channel = TWODOTFOUR_LCD_BACKLIGHT_LEDC_CHANNEL, + .timer_sel = TWODOTFOUR_LCD_BACKLIGHT_LEDC_TIMER, + .intr_type = LEDC_INTR_DISABLE, + .gpio_num = TWODOTFOUR_LCD_PIN_BACKLIGHT, + .duty = duty, + .hpoint = 0 + }; + + // Setting the config in the timer init and then calling ledc_set_duty() doesn't work when + // the app is running. For an unknown reason we have to call this config method every time: + if (ledc_channel_config(&ledc_channel) != ESP_OK) { + TT_LOG_E(TAG, "Failed to configure display backlight"); } } @@ -94,7 +122,5 @@ lv_disp_t* twodotfour_display_init() { lv_disp_t* display = lvgl_port_add_disp(&disp_cfg); - twodotfour_backlight_on(); - return display; } diff --git a/boards/yellow_board/display_i.h b/boards/yellow_board/display_i.h new file mode 100644 index 00000000..1700d88a --- /dev/null +++ b/boards/yellow_board/display_i.h @@ -0,0 +1,15 @@ +#pragma once + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +bool twodotfour_backlight_init(); +void twodotfour_backlight_set(uint8_t duty); + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/boards/yellow_board/yellow_board.c b/boards/yellow_board/yellow_board.c index 3ab71a41..03f08d9b 100644 --- a/boards/yellow_board/yellow_board.c +++ b/boards/yellow_board/yellow_board.c @@ -1,4 +1,5 @@ #include "yellow_board.h" +#include "display_i.h" bool twodotfour_lvgl_init(); bool twodotfour_bootstrap(); @@ -7,6 +8,9 @@ extern const SdCard twodotfour_sdcard; const HardwareConfig yellow_board_24inch_cap = { .bootstrap = &twodotfour_bootstrap, + .display = { + .set_backlight_duty = &twodotfour_backlight_set + }, .init_lvgl = &twodotfour_lvgl_init, .sdcard = &twodotfour_sdcard }; diff --git a/docs/ideas.md b/docs/ideas.md index 016240f4..21949a1d 100644 --- a/docs/ideas.md +++ b/docs/ideas.md @@ -7,10 +7,22 @@ is not automatically called. This is normally done by a hook in `FreeRTOSConfig.h` but that seems to not work with ESP32. I should investigate task cleanup hooks further. - Set DPI in sdkconfig for Waveshare display +- Try to drive Yellow Board backlight with PWM to reduce backlight strength +- Show a warning screen if firmware encryption or secure boot are off when saving WiFi credentials. +- Show a warning screen when a user plugs in the SD card on a device that only supports mounting at boot. +- Try out Waveshare S3 120MHz mode for PSRAM (see "enabling 120M PSRAM is necessary" in [docs](https://www.waveshare.com/wiki/ESP32-S3-Touch-LCD-4.3#Other_Notes)) +- Fix for dark theme: the wifi icons should use the colour of the theme (they remain black when dark theme is set) # Core Ideas - Make a HAL? It would mainly be there to support PC development. It's a lot of effort for supporting what's effectively a dev-only feature. - Support for displays with different DPI. Consider the layer-based system like on Android. +- Display orientation support for Display app +- If present, use LED to show boot status + +# App Improvement Ideas +- Make a Settings app to show all the apps that have a "settings" app type (and hide those in desktop) +- Sort desktop apps by name. +- Light/dark mode selection in Display settings app. # App Ideas - Chip 8 emulator diff --git a/tactility-core/src/bundle.c b/tactility-core/src/bundle.c index bac8e427..05e9ac81 100644 --- a/tactility-core/src/bundle.c +++ b/tactility-core/src/bundle.c @@ -8,7 +8,7 @@ typedef enum { BUNDLE_ENTRY_TYPE_BOOL, - BUNDLE_ENTRY_TYPE_INT, + BUNDLE_ENTRY_TYPE_INT32, BUNDLE_ENTRY_TYPE_STRING, } BundleEntryType; @@ -16,7 +16,7 @@ typedef struct { BundleEntryType type; union { bool bool_value; - int int_value; + int32_t int32_value; char* string_ptr; }; } BundleEntry; @@ -28,10 +28,10 @@ BundleEntry* bundle_entry_alloc_bool(bool value) { return entry; } -BundleEntry* bundle_entry_alloc_int(int value) { +BundleEntry* bundle_entry_alloc_int32(int32_t value) { BundleEntry* entry = malloc(sizeof(BundleEntry)); - entry->type = BUNDLE_ENTRY_TYPE_INT; - entry->int_value = value; + entry->type = BUNDLE_ENTRY_TYPE_INT32; + entry->int32_value = value; return entry; } @@ -50,7 +50,7 @@ BundleEntry* bundle_entry_alloc_copy(BundleEntry* source) { entry->string_ptr = malloc(strlen(source->string_ptr) + 1); strcpy(entry->string_ptr, source->string_ptr); } else { - entry->int_value = source->int_value; + entry->int32_value = source->int32_value; } return entry; } @@ -112,11 +112,11 @@ bool tt_bundle_get_bool(Bundle bundle, const char* key) { return (*entry)->bool_value; } -int tt_bundle_get_int(Bundle bundle, const char* key) { +int32_t tt_bundle_get_int32(Bundle bundle, const char* key) { BundleData* data = (BundleData*)bundle; BundleEntry** entry = BundleDict_get(data->dict, key); tt_check(entry != NULL); - return (*entry)->int_value; + return (*entry)->int32_value; } const char* tt_bundle_get_string(Bundle bundle, const char* key) { @@ -126,23 +126,51 @@ const char* tt_bundle_get_string(Bundle bundle, const char* key) { return (*entry)->string_ptr; } -bool tt_bundle_opt_bool(Bundle bundle, const char* key, bool* out) { +bool tt_bundle_has_bool(Bundle bundle, const char* key) { BundleData* data = (BundleData*)bundle; BundleEntry** entry = BundleDict_get(data->dict, key); - if (entry != NULL) { - *out = (*entry)->bool_value; - return true; + return (entry != NULL) && ((*entry)->type == BUNDLE_ENTRY_TYPE_BOOL); +} + +bool tt_bundle_has_int32(Bundle bundle, const char* key) { + BundleData* data = (BundleData*)bundle; + BundleEntry** entry = BundleDict_get(data->dict, key); + return (entry != NULL) && ((*entry)->type == BUNDLE_ENTRY_TYPE_INT32); +} + +bool tt_bundle_has_string(Bundle bundle, const char* key) { + BundleData* data = (BundleData*)bundle; + BundleEntry** entry = BundleDict_get(data->dict, key); + return (entry != NULL) && ((*entry)->type == BUNDLE_ENTRY_TYPE_STRING); +} + +bool tt_bundle_opt_bool(Bundle bundle, const char* key, bool* out) { + BundleData* data = (BundleData*)bundle; + BundleEntry** entry_ptr = BundleDict_get(data->dict, key); + if (entry_ptr != NULL) { + BundleEntry* entry = *entry_ptr; + if (entry->type == BUNDLE_ENTRY_TYPE_BOOL) { + *out = entry->bool_value; + return true; + } else { + return false; + } } else { return false; } } -bool tt_bundle_opt_int(Bundle bundle, const char* key, int* out) { +bool tt_bundle_opt_int32(Bundle bundle, const char* key, int32_t* out) { BundleData* data = (BundleData*)bundle; - BundleEntry** entry = BundleDict_get(data->dict, key); - if (entry != NULL) { - *out = (*entry)->int_value; - return true; + BundleEntry** entry_ptr = BundleDict_get(data->dict, key); + if (entry_ptr != NULL) { + BundleEntry* entry = *entry_ptr; + if (entry->type == BUNDLE_ENTRY_TYPE_INT32) { + *out = entry->int32_value; + return true; + } else { + return false; + } } else { return false; } @@ -150,10 +178,15 @@ bool tt_bundle_opt_int(Bundle bundle, const char* key, int* out) { bool tt_bundle_opt_string(Bundle bundle, const char* key, char** out) { BundleData* data = (BundleData*)bundle; - BundleEntry** entry = BundleDict_get(data->dict, key); - if (entry != NULL) { - *out = (*entry)->string_ptr; - return true; + BundleEntry** entry_ptr = BundleDict_get(data->dict, key); + if (entry_ptr != NULL) { + BundleEntry* entry = *entry_ptr; + if (entry->type == BUNDLE_ENTRY_TYPE_STRING) { + *out = entry->string_ptr; + return true; + } else { + return false; + } } else { return false; } @@ -172,15 +205,15 @@ void tt_bundle_put_bool(Bundle bundle, const char* key, bool value) { } } -void tt_bundle_put_int(Bundle bundle, const char* key, int value) { +void tt_bundle_put_int32(Bundle bundle, const char* key, int32_t value) { BundleData* data = (BundleData*)bundle; BundleEntry** entry_handle = BundleDict_get(data->dict, key); if (entry_handle != NULL) { BundleEntry* entry = *entry_handle; - tt_assert(entry->type == BUNDLE_ENTRY_TYPE_INT); - entry->int_value = value; + tt_assert(entry->type == BUNDLE_ENTRY_TYPE_INT32); + entry->int32_value = value; } else { - BundleEntry* entry = bundle_entry_alloc_int(value); + BundleEntry* entry = bundle_entry_alloc_int32(value); BundleDict_set_at(data->dict, key, entry); } } diff --git a/tactility-core/src/bundle.h b/tactility-core/src/bundle.h index fc8c3af1..cdcebc47 100644 --- a/tactility-core/src/bundle.h +++ b/tactility-core/src/bundle.h @@ -4,8 +4,9 @@ */ #pragma once -#include #include +#include +#include #ifdef __cplusplus extern "C" { @@ -18,15 +19,19 @@ Bundle tt_bundle_alloc_copy(Bundle source); void tt_bundle_free(Bundle bundle); bool tt_bundle_get_bool(Bundle bundle, const char* key); -int tt_bundle_get_int(Bundle bundle, const char* key); +int32_t tt_bundle_get_int32(Bundle bundle, const char* key); const char* tt_bundle_get_string(Bundle bundle, const char* key); +bool tt_bundle_has_bool(Bundle bundle, const char* key); +bool tt_bundle_has_int32(Bundle bundle, const char* key); +bool tt_bundle_has_string(Bundle bundle, const char* key); + bool tt_bundle_opt_bool(Bundle bundle, const char* key, bool* out); -bool tt_bundle_opt_int(Bundle bundle, const char* key, int* out); +bool tt_bundle_opt_int32(Bundle bundle, const char* key, int32_t* out); bool tt_bundle_opt_string(Bundle bundle, const char* key, char** out); void tt_bundle_put_bool(Bundle bundle, const char* key, bool value); -void tt_bundle_put_int(Bundle bundle, const char* key, int value); +void tt_bundle_put_int32(Bundle bundle, const char* key, int32_t value); void tt_bundle_put_string(Bundle bundle, const char* key, const char* value); #ifdef __cplusplus diff --git a/tactility-esp/CMakeLists.txt b/tactility-esp/CMakeLists.txt index ce4e778b..66214679 100644 --- a/tactility-esp/CMakeLists.txt +++ b/tactility-esp/CMakeLists.txt @@ -2,11 +2,10 @@ cmake_minimum_required(VERSION 3.16) set(BOARD_COMPONENTS esp_wifi) +file(GLOB_RECURSE SOURCE_FILES src/*.c) + idf_component_register( - SRC_DIRS "src" - "src/apps/system/wifi_connect" - "src/apps/system/wifi_manage" - "src/services/wifi" + SRCS ${SOURCE_FILES} INCLUDE_DIRS "src/" REQUIRES esp_wifi nvs_flash spiffs ) diff --git a/tactility-esp/src/apps/system/wifi_connect/wifi_connect.c b/tactility-esp/src/apps/system/wifi_connect/wifi_connect.c index 4fc56e22..876c3db1 100644 --- a/tactility-esp/src/apps/system/wifi_connect/wifi_connect.c +++ b/tactility-esp/src/apps/system/wifi_connect/wifi_connect.c @@ -115,7 +115,7 @@ AppManifest wifi_connect_app = { .id = "wifi_connect", .name = "Wi-Fi Connect", .icon = NULL, - .type = AppTypeSystem, + .type = AppTypeSettings, .on_start = &app_start, .on_stop = &app_stop, .on_show = &app_show, diff --git a/tactility-esp/src/apps/system/wifi_connect/wifi_connect_view.c b/tactility-esp/src/apps/system/wifi_connect/wifi_connect_view.c index 0b3e6399..c80fec31 100644 --- a/tactility-esp/src/apps/system/wifi_connect/wifi_connect_view.c +++ b/tactility-esp/src/apps/system/wifi_connect/wifi_connect_view.c @@ -67,13 +67,9 @@ void wifi_connect_view_create_bottom_buttons(WifiConnect* wifi, lv_obj_t* parent 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); + tt_lv_obj_set_style_auto_padding(parent); view->root = parent; diff --git a/tactility-esp/src/apps/system/wifi_manage/wifi_manage.c b/tactility-esp/src/apps/system/wifi_manage/wifi_manage.c index bafba89a..7b3d037a 100644 --- a/tactility-esp/src/apps/system/wifi_manage/wifi_manage.c +++ b/tactility-esp/src/apps/system/wifi_manage/wifi_manage.c @@ -158,7 +158,7 @@ AppManifest wifi_manage_app = { .id = "wifi_manage", .name = "Wi-Fi", .icon = NULL, - .type = AppTypeSystem, + .type = AppTypeSettings, .on_start = &app_start, .on_stop = &app_stop, .on_show = &app_show, diff --git a/tactility-esp/src/apps/system/wifi_manage/wifi_manage_view.c b/tactility-esp/src/apps/system/wifi_manage/wifi_manage_view.c index c613fd1e..32d3eabb 100644 --- a/tactility-esp/src/apps/system/wifi_manage/wifi_manage_view.c +++ b/tactility-esp/src/apps/system/wifi_manage/wifi_manage_view.c @@ -150,13 +150,8 @@ static void update_connected_ap(WifiManageView* view, WifiManageState* state, Wi 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); + tt_lv_obj_set_style_auto_padding(parent); // Top row: enable/disable lv_obj_t* switch_container = lv_obj_create(parent); diff --git a/tactility/CMakeLists.txt b/tactility/CMakeLists.txt index e0a43a8e..8f0d906f 100644 --- a/tactility/CMakeLists.txt +++ b/tactility/CMakeLists.txt @@ -23,6 +23,7 @@ if (DEFINED ENV{ESP_IDF_VERSION}) PUBLIC idf::lvgl # libs/ PUBLIC idf::driver PUBLIC idf::spiffs + PUBLIC idf::nvs_flash ) else() add_definitions(-D_Nullable=) diff --git a/tactility/src/apps/desktop/desktop.c b/tactility/src/apps/desktop/desktop.c index b9e552eb..3b240e87 100644 --- a/tactility/src/apps/desktop/desktop.c +++ b/tactility/src/apps/desktop/desktop.c @@ -25,6 +25,8 @@ static void desktop_show(TT_UNUSED App app, TT_UNUSED lv_obj_t* parent) { lv_list_add_text(list, "System"); tt_app_manifest_registry_for_each_of_type(AppTypeSystem, list, create_app_widget); + lv_list_add_text(list, "Settings"); + tt_app_manifest_registry_for_each_of_type(AppTypeSettings, list, create_app_widget); lv_list_add_text(list, "User"); tt_app_manifest_registry_for_each_of_type(AppTypeUser, list, create_app_widget); } diff --git a/tactility/src/apps/settings/display/display.c b/tactility/src/apps/settings/display/display.c new file mode 100644 index 00000000..f0127648 --- /dev/null +++ b/tactility/src/apps/settings/display/display.c @@ -0,0 +1,71 @@ +#include "app.h" +#include "lvgl.h" +#include "preferences.h" +#include "tactility.h" +#include "ui/spacer.h" +#include "ui/style.h" + +static bool backlight_duty_set = false; +static uint8_t backlight_duty = 255; + +static void slider_event_cb(lv_event_t* e) { + lv_obj_t* slider = lv_event_get_target(e); + const Config* config = tt_get_config(); + SetBacklightDuty set_backlight_duty = config->hardware->display.set_backlight_duty; + + if (set_backlight_duty != NULL) { + int32_t slider_value = lv_slider_get_value(slider); + + backlight_duty = (uint8_t)slider_value; + backlight_duty_set = true; + + set_backlight_duty(backlight_duty); + } +} + +static void app_show(TT_UNUSED App app, lv_obj_t* parent) { + lv_obj_set_flex_flow(parent, LV_FLEX_FLOW_COLUMN); + tt_lv_obj_set_style_auto_padding(parent); + + lv_obj_t* label = lv_label_create(parent); + lv_label_set_text(label, "Brightness"); + + tt_lv_spacer_create(parent, 1, 2); + + lv_obj_t* slider_container = lv_obj_create(parent); + lv_obj_set_size(slider_container, LV_PCT(100), LV_SIZE_CONTENT); + + lv_obj_t* slider = lv_slider_create(slider_container); + lv_obj_set_width(slider, LV_PCT(90)); + lv_obj_center(slider); + lv_slider_set_range(slider, 0, 255); + lv_obj_add_event_cb(slider, slider_event_cb, LV_EVENT_VALUE_CHANGED, NULL); + + const Config* config = tt_get_config(); + SetBacklightDuty set_backlight_duty = config->hardware->display.set_backlight_duty; + if (set_backlight_duty == NULL) { + lv_slider_set_value(slider, 255, LV_ANIM_OFF); + lv_obj_add_state(slider, LV_STATE_DISABLED); + } else { + int32_t value = 255; + tt_preferences()->opt_int32("display", "backlight_duty", &value); + lv_slider_set_value(slider, value, LV_ANIM_OFF); + } +} + +static void app_hide(App app) { + if (backlight_duty_set) { + tt_preferences()->put_int32("display", "backlight_duty", backlight_duty); + } +} + +const AppManifest display_app = { + .id = "display", + .name = "Display", + .icon = NULL, + .type = AppTypeSettings, + .on_start = NULL, + .on_stop = NULL, + .on_show = &app_show, + .on_hide = &app_hide +}; diff --git a/tactility/src/apps/system/system_info/system_info.c b/tactility/src/apps/system/system_info/system_info.c index f956320d..6e68a8a2 100644 --- a/tactility/src/apps/system/system_info/system_info.c +++ b/tactility/src/apps/system/system_info/system_info.c @@ -1,7 +1,5 @@ -#include "app_manifest.h" -#include "core_extra_defines.h" +#include "app.h" #include "lvgl.h" -#include "thread.h" static void app_show(TT_UNUSED App app, lv_obj_t* parent) { lv_obj_t* heap_info = lv_label_create(parent); diff --git a/tactility/src/hardware_config.h b/tactility/src/hardware_config.h index 5215dcfd..0247c43a 100644 --- a/tactility/src/hardware_config.h +++ b/tactility/src/hardware_config.h @@ -5,10 +5,34 @@ typedef bool (*Bootstrap)(); typedef bool (*InitLvgl)(); +typedef bool (*InitLvgl)(); + +typedef void (*SetBacklightDuty)(uint8_t); +typedef struct { + /** Set backlight duty */ + SetBacklightDuty set_backlight_duty; +} Display; typedef struct { - // Optional bootstrapping method (e.g. to turn peripherals on) + /** + * Optional bootstrapping method (e.g. to turn peripherals on) + * This is called after Tactility core init and before any other inits in the HardwareConfig. + * */ const Bootstrap _Nullable bootstrap; + + /** + * Initializes LVGL with all relevant hardware. + * This includes the display and optional pointer devices (such as touch) or a keyboard. + */ const InitLvgl init_lvgl; + + /** + * An interface for display features such as setting the backlight. + */ + const Display display; + + /** + * An optional SD card interface. + */ const SdCard* _Nullable sdcard; } HardwareConfig; diff --git a/tactility/src/preferences.c b/tactility/src/preferences.c new file mode 100644 index 00000000..103e87f9 --- /dev/null +++ b/tactility/src/preferences.c @@ -0,0 +1,15 @@ +#include "preferences.h" + +#ifdef ESP_PLATFORM +extern const Preferences preferences_esp; +#else +extern const Preferences preferences_memory; +#endif + +const Preferences* tt_preferences() { +#ifdef ESP_PLATFORM + return &preferences_esp; +#else + return &preferences_memory; +#endif +} \ No newline at end of file diff --git a/tactility/src/preferences.h b/tactility/src/preferences.h new file mode 100644 index 00000000..fb16cf5b --- /dev/null +++ b/tactility/src/preferences.h @@ -0,0 +1,44 @@ +#pragma once + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef bool (*PreferencesHasBool)(const char* namespace, const char* key); +typedef bool (*PreferencesHasInt32)(const char* namespace, const char* key); +typedef bool (*PreferencesHasString)(const char* namespace, const char* key); + +typedef bool (*PreferencesOptBool)(const char* namespace, const char* key, bool* out); +typedef bool (*PreferencesOptInt32)(const char* namespace, const char* key, int32_t* out); +typedef bool (*PreferencesOptString)(const char* namespace, const char* key, char* out, size_t* size); + +typedef void (*PreferencesPutBool)(const char* namespace, const char* key, bool value); +typedef void (*PreferencesPutInt32)(const char* namespace, const char* key, int32_t value); +typedef void (*PreferencesPutString)(const char* namespace, const char* key, const char* value); + +typedef struct { + PreferencesHasBool has_bool; + PreferencesHasInt32 has_int32; + PreferencesHasString has_string; + + PreferencesOptBool opt_bool; + PreferencesOptInt32 opt_int32; + PreferencesOptString opt_string; + + PreferencesPutBool put_bool; + PreferencesPutInt32 put_int32; + PreferencesPutString put_string; +} Preferences; + +/** + * @return an instance of Preferences. + */ +const Preferences* tt_preferences(); + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/tactility/src/preferences_esp.c b/tactility/src/preferences_esp.c new file mode 100644 index 00000000..140cc005 --- /dev/null +++ b/tactility/src/preferences_esp.c @@ -0,0 +1,96 @@ +#ifdef ESP_PLATFORM + +#include "preferences.h" +#include "nvs_flash.h" +#include "tactility_core.h" + +static bool opt_bool(const char* namespace, const char* key, bool* out) { + nvs_handle_t handle; + if (nvs_open(namespace, NVS_READWRITE, &handle) != ESP_OK) { + return false; + } else { + uint8_t out_number; + bool success = nvs_get_u8(handle, key, &out_number) == ESP_OK; + nvs_close(handle); + if (success) { + *out = (bool)out_number; + } + return success; + } +} + +static bool opt_int32(const char* namespace, const char* key, int32_t* out) { + nvs_handle_t handle; + if (nvs_open(namespace, NVS_READWRITE, &handle) != ESP_OK) { + return false; + } else { + bool success = nvs_get_i32(handle, key, out) == ESP_OK; + nvs_close(handle); + return success; + } +} + +static bool opt_string(const char* namespace, const char* key, char* out, size_t* size) { + nvs_handle_t handle; + if (nvs_open(namespace, NVS_READWRITE, &handle) != ESP_OK) { + return false; + } else { + bool success = nvs_get_str(handle, key, out, size) == ESP_OK; + nvs_close(handle); + return success; + } +} + +static bool has_bool(const char* namespace, const char* key) { + bool temp; + return opt_bool(namespace, key, &temp); +} + +static bool has_int32(const char* namespace, const char* key) { + int32_t temp; + return opt_int32(namespace, key, &temp); +} + +static bool has_string(const char* namespace, const char* key) { + char temp[128]; + size_t temp_size = 128; + return opt_string(namespace, key, temp, &temp_size); +} + +static void put_bool(const char* namespace, const char* key, bool value) { + nvs_handle_t handle; + if (nvs_open(namespace, NVS_READWRITE, &handle) == ESP_OK) { + nvs_set_u8(handle, key, (uint8_t)value) == ESP_OK; + nvs_close(handle); + } +} + +static void put_int32(const char* namespace, const char* key, int32_t value) { + nvs_handle_t handle; + if (nvs_open(namespace, NVS_READWRITE, &handle) == ESP_OK) { + nvs_set_i32(handle, key, value) == ESP_OK; + nvs_close(handle); + } +} + +static void put_string(const char* namespace, const char* key, const char* text) { + nvs_handle_t handle; + if (nvs_open(namespace, NVS_READWRITE, &handle) == ESP_OK) { + nvs_set_str(handle, key, text); + nvs_close(handle); + } +} + +const Preferences preferences_esp = { + .has_bool = &has_bool, + .has_int32 = &has_int32, + .has_string = &has_string, + .opt_bool = &opt_bool, + .opt_int32 = &opt_int32, + .opt_string = &opt_string, + .put_bool = &put_bool, + .put_int32 = &put_int32, + .put_string = &put_string +}; + +#endif \ No newline at end of file diff --git a/tactility/src/preferences_memory.c b/tactility/src/preferences_memory.c new file mode 100644 index 00000000..b0fbdeba --- /dev/null +++ b/tactility/src/preferences_memory.c @@ -0,0 +1,109 @@ +#ifndef ESP_PLATFOM + +#include "bundle.h" +#include "preferences.h" +#include +#include + +static Bundle* preferences_bundle; + +static Bundle* get_preferences_bundle() { + if (preferences_bundle == NULL) { + preferences_bundle = tt_bundle_alloc(); + } + return preferences_bundle; +} + +/** + * Creates a string that is effectively "namespace:key" so we can create a single map (bundle) + * to store all the key/value pairs. + * + * @param[in] namespace + * @param[in] key + * @param[out] out + */ +static void get_bundle_key(const char* namespace, const char* key, char* out) { + strcpy(out, namespace); + size_t namespace_len = strlen(namespace); + out[namespace_len] = ':'; + char* out_with_key_offset = &out[namespace_len + 1]; + strcpy(out_with_key_offset, key); +} + +static bool has_bool(const char* namespace, const char* key) { + char bundle_key[128]; + get_bundle_key(namespace, key, bundle_key); + return tt_bundle_has_bool(get_preferences_bundle(), bundle_key); +} + +static bool has_int32(const char* namespace, const char* key) { + char bundle_key[128]; + get_bundle_key(namespace, key, bundle_key); + return tt_bundle_has_int32(get_preferences_bundle(), bundle_key); +} + +static bool has_string(const char* namespace, const char* key) { + char bundle_key[128]; + get_bundle_key(namespace, key, bundle_key); + return tt_bundle_has_string(get_preferences_bundle(), bundle_key); +} + +static bool opt_bool(const char* namespace, const char* key, bool* out) { + char bundle_key[128]; + get_bundle_key(namespace, key, bundle_key); + return tt_bundle_opt_bool(get_preferences_bundle(), bundle_key, out); +} + +static bool opt_int32(const char* namespace, const char* key, int32_t* out) { + char bundle_key[128]; + get_bundle_key(namespace, key, bundle_key); + return tt_bundle_opt_int32(get_preferences_bundle(), bundle_key, out); +} + +static bool opt_string(const char* namespace, const char* key, char* out, size_t* size) { + char bundle_key[128]; + get_bundle_key(namespace, key, bundle_key); + char* bundle_out = NULL; + if (tt_bundle_opt_string(get_preferences_bundle(), bundle_key, &bundle_out)) { + tt_assert(bundle_out != NULL); + size_t found_length = strlen(bundle_out); + tt_check(found_length <= (*size + 1), "output buffer not large enough"); + *size = found_length; + strcpy(out, bundle_out); + return true; + } else { + return false; + } +} + +static void put_bool(const char* namespace, const char* key, bool value) { + char bundle_key[128]; + get_bundle_key(namespace, key, bundle_key); + return tt_bundle_put_bool(get_preferences_bundle(), bundle_key, value); +} + +static void put_int32(const char* namespace, const char* key, int32_t value) { + char bundle_key[128]; + get_bundle_key(namespace, key, bundle_key); + return tt_bundle_put_int32(get_preferences_bundle(), bundle_key, value); +} + +static void put_string(const char* namespace, const char* key, const char* text) { + char bundle_key[128]; + get_bundle_key(namespace, key, bundle_key); + return tt_bundle_put_string(get_preferences_bundle(), bundle_key, text); +} + +const Preferences preferences_memory = { + .has_bool = &has_bool, + .has_int32 = &has_int32, + .has_string = &has_string, + .opt_bool = &opt_bool, + .opt_int32 = &opt_int32, + .opt_string = &opt_string, + .put_bool = &put_bool, + .put_int32 = &put_int32, + .put_string = &put_string +}; + +#endif \ No newline at end of file diff --git a/tactility/src/tactility.c b/tactility/src/tactility.c index 9ec13b4e..ed2cc7ea 100644 --- a/tactility/src/tactility.c +++ b/tactility/src/tactility.c @@ -2,12 +2,15 @@ #include "app_manifest_registry.h" #include "hardware_i.h" +#include "preferences.h" #include "service_registry.h" #include "services/loader/loader.h" #define TAG "tactility" -// region System services +static const Config* config_instance = NULL; + +// region Default services extern const ServiceManifest gui_service; extern const ServiceManifest loader_service; @@ -19,13 +22,15 @@ static const ServiceManifest* const system_services[] = { // endregion -// region System apps +// region Default apps extern const AppManifest desktop_app; +extern const AppManifest display_app; extern const AppManifest system_info_app; static const AppManifest* const system_apps[] = { &desktop_app, + &display_app, &system_info_app }; @@ -83,6 +88,16 @@ TT_UNUSED void tt_init(const Config* config) { tt_hardware_init(config->hardware); + SetBacklightDuty set_backlight_duty = config->hardware->display.set_backlight_duty; + if (set_backlight_duty != NULL) { + int32_t backlight_duty = 200; + if (!tt_preferences()->opt_int32("display", "backlight_duty", &backlight_duty)) { + tt_preferences()->put_int32("display", "backlight_duty", backlight_duty); + } + int32_t safe_backlight_duty = TT_MIN(backlight_duty, 255); + set_backlight_duty((uint8_t)safe_backlight_duty); + } + // Note: the order of starting apps and services is critical! // System services are registered first so the apps below can use them register_and_start_system_services(); @@ -103,4 +118,10 @@ TT_UNUSED void tt_init(const Config* config) { } TT_LOG_I(TAG, "tt_init complete"); + + config_instance = config; +} + +const Config* _Nullable tt_get_config() { + return config_instance; } diff --git a/tactility/src/tactility.h b/tactility/src/tactility.h index 311d08ee..81ea139b 100644 --- a/tactility/src/tactility.h +++ b/tactility/src/tactility.h @@ -17,8 +17,18 @@ typedef struct { const char* auto_start_app_id; } Config; +/** + * Attempts to initialize Tactility and all configured hardware. + * @param config + */ TT_UNUSED void tt_init(const Config* config); +/** + * While technically nullable, this instance is always set if tt_init() succeeds. + * @return the Configuration instance that was passed on to tt_init() if init is successful + */ +const Config* _Nullable tt_get_config(); + #ifdef __cplusplus } #endif diff --git a/tactility/src/ui/style.c b/tactility/src/ui/style.c index bc71aeec..c2ed04a0 100644 --- a/tactility/src/ui/style.c +++ b/tactility/src/ui/style.c @@ -14,3 +14,10 @@ void tt_lv_obj_set_style_no_padding(lv_obj_t* obj) { lv_obj_set_style_pad_all(obj, LV_STATE_DEFAULT, 0); lv_obj_set_style_pad_gap(obj, LV_STATE_DEFAULT, 0); } + +void tt_lv_obj_set_style_auto_padding(lv_obj_t* obj) { + lv_obj_set_style_pad_top(obj, 8, 0); + lv_obj_set_style_pad_bottom(obj, 8, 0); + lv_obj_set_style_pad_left(obj, 16, 0); + lv_obj_set_style_pad_right(obj, 16, 0); +} diff --git a/tactility/src/ui/style.h b/tactility/src/ui/style.h index b1f77870..5816fa3d 100644 --- a/tactility/src/ui/style.h +++ b/tactility/src/ui/style.h @@ -10,6 +10,15 @@ void tt_lv_obj_set_style_bg_blacken(lv_obj_t* obj); void tt_lv_obj_set_style_bg_invisible(lv_obj_t* obj); void tt_lv_obj_set_style_no_padding(lv_obj_t* obj); +/** + * This is to create automatic padding depending on the screen size. + * The larger the screen, the more padding it gets. + * TODO: It currently only applies a single basic padding, but will be improved later. + * + * @param obj + */ +void tt_lv_obj_set_style_auto_padding(lv_obj_t* obj); + #ifdef __cplusplus } #endif