Display brightness support (#26)

* cleanup

* brightness control and app

* cleanup

* persistant storage of display settings

* fix for missing include

* header cleanup

* fix pc build

* add docs

* move display app to tactility project
This commit is contained in:
Ken Van Hoeylandt 2024-01-31 20:39:12 +01:00 committed by GitHub
parent ae5c828f42
commit d171b9a231
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
33 changed files with 626 additions and 94 deletions

View File

@ -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]. (*) Note: Only the capacitive version is supported. See AliExpress [here][2432s024c_1] and [here][2432s024c_2].
[tdeck]: https://www.lilygo.cc/products/t-deck [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_1]: https://www.aliexpress.com/item/1005005902429049.html
[2432s024c_2]: https://www.aliexpress.com/item/1005005865107357.html [2432s024c_2]: https://www.aliexpress.com/item/1005005865107357.html

View File

@ -1,6 +1,5 @@
#include "hello_world.h" #include "hello_world.h"
#include "services/gui/gui.h" #include "lvgl.h"
#include "services/loader/loader.h"
static void app_show(TT_UNUSED App app, lv_obj_t* parent) { static void app_show(TT_UNUSED App app, lv_obj_t* parent) {
lv_obj_t* label = lv_label_create(parent); lv_obj_t* label = lv_label_create(parent);

View File

@ -1,13 +1,11 @@
#include "config.h" #include "config.h"
#include "kernel.h" #include "display_i.h"
#include "driver/spi_common.h"
#include "keyboard.h" #include "keyboard.h"
#include "log.h" #include "tactility_core.h"
#include <driver/spi_common.h>
#define TAG "tdeck_bootstrap" #define TAG "tdeck_bootstrap"
lv_disp_t* tdeck_display_init();
static bool tdeck_power_on() { static bool tdeck_power_on() {
gpio_config_t device_power_signal_config = { gpio_config_t device_power_signal_config = {
.pin_bit_mask = BIT64(TDECK_POWERON_GPIO), .pin_bit_mask = BIT64(TDECK_POWERON_GPIO),
@ -38,8 +36,7 @@ static bool init_i2c() {
.master.clk_speed = 400000 .master.clk_speed = 400000
}; };
return i2c_param_config(TDECK_I2C_BUS_HANDLE, &i2c_conf) == 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;
&& i2c_driver_install(TDECK_I2C_BUS_HANDLE, i2c_conf.mode, 0, 0, 0) == ESP_OK;
} }
static bool init_spi() { static bool init_spi() {
@ -89,6 +86,13 @@ bool tdeck_bootstrap() {
return false; 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(); keyboard_wait_for_response();
return true; return true;

View File

@ -9,7 +9,7 @@
#define TAG "tdeck_display" #define TAG "tdeck_display"
void tdeck_enable_backlight() { bool tdeck_backlight_init() {
ledc_timer_config_t ledc_timer = { ledc_timer_config_t ledc_timer = {
.speed_mode = TDECK_LCD_BACKLIGHT_LEDC_MODE, .speed_mode = TDECK_LCD_BACKLIGHT_LEDC_MODE,
.timer_num = TDECK_LCD_BACKLIGHT_LEDC_TIMER, .timer_num = TDECK_LCD_BACKLIGHT_LEDC_TIMER,
@ -17,20 +17,31 @@ void tdeck_enable_backlight() {
.freq_hz = TDECK_LCD_BACKLIGHT_LEDC_FREQUENCY, .freq_hz = TDECK_LCD_BACKLIGHT_LEDC_FREQUENCY,
.clk_cfg = LEDC_AUTO_CLK .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 = { ledc_channel_config_t ledc_channel = {
.speed_mode = TDECK_LCD_BACKLIGHT_LEDC_MODE, .speed_mode = TDECK_LCD_BACKLIGHT_LEDC_MODE,
.channel = TDECK_LCD_BACKLIGHT_LEDC_CHANNEL, .channel = TDECK_LCD_BACKLIGHT_LEDC_CHANNEL,
.timer_sel = TDECK_LCD_BACKLIGHT_LEDC_TIMER, .timer_sel = TDECK_LCD_BACKLIGHT_LEDC_TIMER,
.intr_type = LEDC_INTR_DISABLE, .intr_type = LEDC_INTR_DISABLE,
.gpio_num = TDECK_LCD_BACKLIGHT_LEDC_OUTPUT_IO, .gpio_num = TDECK_LCD_PIN_BACKLIGHT,
.duty = 0, // Set duty to 0% .duty = duty,
.hpoint = 0 .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() { lv_disp_t* tdeck_display_init() {

View File

@ -0,0 +1,18 @@
#pragma once
#include "hal/lv_hal_disp.h"
#include <stdbool.h>
#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

View File

@ -1,4 +1,5 @@
#include "lilygo_tdeck.h" #include "lilygo_tdeck.h"
#include "display_i.h"
#include <stdbool.h> #include <stdbool.h>
bool tdeck_bootstrap(); bool tdeck_bootstrap();
@ -8,6 +9,9 @@ extern const SdCard tdeck_sdcard;
const HardwareConfig lilygo_tdeck = { const HardwareConfig lilygo_tdeck = {
.bootstrap = &tdeck_bootstrap, .bootstrap = &tdeck_bootstrap,
.display = {
.set_backlight_duty = &tdeck_backlight_set
},
.init_lvgl = &tdeck_init_lvgl, .init_lvgl = &tdeck_init_lvgl,
.sdcard = &tdeck_sdcard .sdcard = &tdeck_sdcard
}; };

View File

@ -1,14 +1,13 @@
#include "config.h" #include "config.h"
#include "display_i.h"
#include "esp_lvgl_port.h" #include "esp_lvgl_port.h"
#include "keyboard.h" #include "keyboard.h"
#include "log.h" #include "log.h"
#include "ui/lvgl_sync.h" #include "ui/lvgl_sync.h"
#include <thread.h> #include "thread.h"
#define TAG "tdeck_lvgl" #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_touch(esp_lcd_panel_io_handle_t* io_handle, esp_lcd_touch_handle_t* touch_handle);
bool tdeck_init_lvgl() { 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_panel_io_handle_t touch_io_handle;
static esp_lcd_touch_handle_t touch_handle; static esp_lcd_touch_handle_t touch_handle;
// Init LVGL Port library
const lvgl_port_cfg_t lvgl_cfg = { const lvgl_port_cfg_t lvgl_cfg = {
.task_priority = THREAD_PRIORITY_RENDER, .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_affinity = -1, // core pinning
.task_max_sleep_ms = 500, .task_max_sleep_ms = 500,
.timer_period_ms = 5 .timer_period_ms = 5
@ -66,8 +63,5 @@ bool tdeck_init_lvgl() {
keyboard_alloc(display); keyboard_alloc(display);
TT_LOG_D(TAG, "enabling backlight");
tdeck_enable_backlight();
return true; return true;
} }

View File

@ -6,5 +6,8 @@ bool ws3t_bootstrap();
const HardwareConfig waveshare_s3_touch = { const HardwareConfig waveshare_s3_touch = {
.bootstrap = &ws3t_bootstrap, .bootstrap = &ws3t_bootstrap,
.display = {
.set_backlight_duty = NULL // TODO: This requires implementing the CH422G IO expander
},
.init_lvgl = &ws3t_init_lvgl .init_lvgl = &ws3t_init_lvgl
}; };

View File

@ -1,6 +1,6 @@
#include "config.h" #include "config.h"
#include "kernel.h" #include "tactility_core.h"
#include "log.h" #include "display_i.h"
#include <driver/spi_common.h> #include <driver/spi_common.h>
#define TAG "twodotfour_bootstrap" #define TAG "twodotfour_bootstrap"
@ -83,5 +83,12 @@ bool twodotfour_bootstrap() {
return false; 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; return true;
} }

View File

@ -1,28 +1,56 @@
#include "config.h" #include "config.h"
#include "log.h" #include "tactility_core.h"
#include "driver/gpio.h" #include "driver/gpio.h"
#include "driver/ledc.h"
#include "esp_err.h" #include "esp_err.h"
#include "esp_lcd_ili9341.h" #include "esp_lcd_ili9341.h"
#include "esp_lcd_panel_io.h"
#include "esp_lcd_panel_ops.h" #include "esp_lcd_panel_ops.h"
#include "esp_lvgl_port.h" #include "esp_lvgl_port.h"
#include "hal/lv_hal_disp.h" #include "hal/lv_hal_disp.h"
#include <esp_lcd_panel_io.h>
#define TAG "twodotfour_ili9341" #define TAG "twodotfour_ili9341"
static void twodotfour_backlight_on() { // Dipslay backlight (PWM)
gpio_config_t io_conf = { #define TWODOTFOUR_LCD_BACKLIGHT_LEDC_TIMER LEDC_TIMER_0
.pin_bit_mask = BIT64(TWODOTFOUR_LCD_PIN_BACKLIGHT), #define TWODOTFOUR_LCD_BACKLIGHT_LEDC_MODE LEDC_LOW_SPEED_MODE
.mode = GPIO_MODE_OUTPUT, #define TWODOTFOUR_LCD_BACKLIGHT_LEDC_CHANNEL LEDC_CHANNEL_0
.pull_up_en = GPIO_PULLUP_DISABLE, #define TWODOTFOUR_LCD_BACKLIGHT_LEDC_DUTY_RES LEDC_TIMER_8_BIT
.pull_down_en = GPIO_PULLDOWN_DISABLE, #define TWODOTFOUR_LCD_BACKLIGHT_LEDC_FREQUENCY (1000)
.intr_type = GPIO_INTR_DISABLE,
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) { return true;
TT_LOG_E(TAG, "Failed to turn backlight on"); }
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); lv_disp_t* display = lvgl_port_add_disp(&disp_cfg);
twodotfour_backlight_on();
return display; return display;
} }

View File

@ -0,0 +1,15 @@
#pragma once
#include <stdbool.h>
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
bool twodotfour_backlight_init();
void twodotfour_backlight_set(uint8_t duty);
#ifdef __cplusplus
}
#endif

View File

@ -1,4 +1,5 @@
#include "yellow_board.h" #include "yellow_board.h"
#include "display_i.h"
bool twodotfour_lvgl_init(); bool twodotfour_lvgl_init();
bool twodotfour_bootstrap(); bool twodotfour_bootstrap();
@ -7,6 +8,9 @@ extern const SdCard twodotfour_sdcard;
const HardwareConfig yellow_board_24inch_cap = { const HardwareConfig yellow_board_24inch_cap = {
.bootstrap = &twodotfour_bootstrap, .bootstrap = &twodotfour_bootstrap,
.display = {
.set_backlight_duty = &twodotfour_backlight_set
},
.init_lvgl = &twodotfour_lvgl_init, .init_lvgl = &twodotfour_lvgl_init,
.sdcard = &twodotfour_sdcard .sdcard = &twodotfour_sdcard
}; };

View File

@ -7,10 +7,22 @@
is not automatically called. This is normally done by a hook in `FreeRTOSConfig.h` 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. but that seems to not work with ESP32. I should investigate task cleanup hooks further.
- Set DPI in sdkconfig for Waveshare display - 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 # 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. - 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. - 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 # App Ideas
- Chip 8 emulator - Chip 8 emulator

View File

@ -8,7 +8,7 @@
typedef enum { typedef enum {
BUNDLE_ENTRY_TYPE_BOOL, BUNDLE_ENTRY_TYPE_BOOL,
BUNDLE_ENTRY_TYPE_INT, BUNDLE_ENTRY_TYPE_INT32,
BUNDLE_ENTRY_TYPE_STRING, BUNDLE_ENTRY_TYPE_STRING,
} BundleEntryType; } BundleEntryType;
@ -16,7 +16,7 @@ typedef struct {
BundleEntryType type; BundleEntryType type;
union { union {
bool bool_value; bool bool_value;
int int_value; int32_t int32_value;
char* string_ptr; char* string_ptr;
}; };
} BundleEntry; } BundleEntry;
@ -28,10 +28,10 @@ BundleEntry* bundle_entry_alloc_bool(bool value) {
return entry; return entry;
} }
BundleEntry* bundle_entry_alloc_int(int value) { BundleEntry* bundle_entry_alloc_int32(int32_t value) {
BundleEntry* entry = malloc(sizeof(BundleEntry)); BundleEntry* entry = malloc(sizeof(BundleEntry));
entry->type = BUNDLE_ENTRY_TYPE_INT; entry->type = BUNDLE_ENTRY_TYPE_INT32;
entry->int_value = value; entry->int32_value = value;
return entry; return entry;
} }
@ -50,7 +50,7 @@ BundleEntry* bundle_entry_alloc_copy(BundleEntry* source) {
entry->string_ptr = malloc(strlen(source->string_ptr) + 1); entry->string_ptr = malloc(strlen(source->string_ptr) + 1);
strcpy(entry->string_ptr, source->string_ptr); strcpy(entry->string_ptr, source->string_ptr);
} else { } else {
entry->int_value = source->int_value; entry->int32_value = source->int32_value;
} }
return entry; return entry;
} }
@ -112,11 +112,11 @@ bool tt_bundle_get_bool(Bundle bundle, const char* key) {
return (*entry)->bool_value; 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; BundleData* data = (BundleData*)bundle;
BundleEntry** entry = BundleDict_get(data->dict, key); BundleEntry** entry = BundleDict_get(data->dict, key);
tt_check(entry != NULL); tt_check(entry != NULL);
return (*entry)->int_value; return (*entry)->int32_value;
} }
const char* tt_bundle_get_string(Bundle bundle, const char* key) { 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; 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; BundleData* data = (BundleData*)bundle;
BundleEntry** entry = BundleDict_get(data->dict, key); BundleEntry** entry = BundleDict_get(data->dict, key);
if (entry != NULL) { return (entry != NULL) && ((*entry)->type == BUNDLE_ENTRY_TYPE_BOOL);
*out = (*entry)->bool_value; }
return true;
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 { } else {
return false; 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; BundleData* data = (BundleData*)bundle;
BundleEntry** entry = BundleDict_get(data->dict, key); BundleEntry** entry_ptr = BundleDict_get(data->dict, key);
if (entry != NULL) { if (entry_ptr != NULL) {
*out = (*entry)->int_value; BundleEntry* entry = *entry_ptr;
return true; if (entry->type == BUNDLE_ENTRY_TYPE_INT32) {
*out = entry->int32_value;
return true;
} else {
return false;
}
} else { } else {
return false; 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) { bool tt_bundle_opt_string(Bundle bundle, const char* key, char** out) {
BundleData* data = (BundleData*)bundle; BundleData* data = (BundleData*)bundle;
BundleEntry** entry = BundleDict_get(data->dict, key); BundleEntry** entry_ptr = BundleDict_get(data->dict, key);
if (entry != NULL) { if (entry_ptr != NULL) {
*out = (*entry)->string_ptr; BundleEntry* entry = *entry_ptr;
return true; if (entry->type == BUNDLE_ENTRY_TYPE_STRING) {
*out = entry->string_ptr;
return true;
} else {
return false;
}
} else { } else {
return false; 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; BundleData* data = (BundleData*)bundle;
BundleEntry** entry_handle = BundleDict_get(data->dict, key); BundleEntry** entry_handle = BundleDict_get(data->dict, key);
if (entry_handle != NULL) { if (entry_handle != NULL) {
BundleEntry* entry = *entry_handle; BundleEntry* entry = *entry_handle;
tt_assert(entry->type == BUNDLE_ENTRY_TYPE_INT); tt_assert(entry->type == BUNDLE_ENTRY_TYPE_INT32);
entry->int_value = value; entry->int32_value = value;
} else { } else {
BundleEntry* entry = bundle_entry_alloc_int(value); BundleEntry* entry = bundle_entry_alloc_int32(value);
BundleDict_set_at(data->dict, key, entry); BundleDict_set_at(data->dict, key, entry);
} }
} }

View File

@ -4,8 +4,9 @@
*/ */
#pragma once #pragma once
#include <stdio.h>
#include <stdbool.h> #include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#ifdef __cplusplus #ifdef __cplusplus
extern "C" { extern "C" {
@ -18,15 +19,19 @@ Bundle tt_bundle_alloc_copy(Bundle source);
void tt_bundle_free(Bundle bundle); void tt_bundle_free(Bundle bundle);
bool tt_bundle_get_bool(Bundle bundle, const char* key); 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); 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_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); 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_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); void tt_bundle_put_string(Bundle bundle, const char* key, const char* value);
#ifdef __cplusplus #ifdef __cplusplus

View File

@ -2,11 +2,10 @@ cmake_minimum_required(VERSION 3.16)
set(BOARD_COMPONENTS esp_wifi) set(BOARD_COMPONENTS esp_wifi)
file(GLOB_RECURSE SOURCE_FILES src/*.c)
idf_component_register( idf_component_register(
SRC_DIRS "src" SRCS ${SOURCE_FILES}
"src/apps/system/wifi_connect"
"src/apps/system/wifi_manage"
"src/services/wifi"
INCLUDE_DIRS "src/" INCLUDE_DIRS "src/"
REQUIRES esp_wifi nvs_flash spiffs REQUIRES esp_wifi nvs_flash spiffs
) )

View File

@ -115,7 +115,7 @@ AppManifest wifi_connect_app = {
.id = "wifi_connect", .id = "wifi_connect",
.name = "Wi-Fi Connect", .name = "Wi-Fi Connect",
.icon = NULL, .icon = NULL,
.type = AppTypeSystem, .type = AppTypeSettings,
.on_start = &app_start, .on_start = &app_start,
.on_stop = &app_stop, .on_stop = &app_stop,
.on_show = &app_show, .on_show = &app_show,

View File

@ -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) { void wifi_connect_view_create(App app, void* wifi, lv_obj_t* parent) {
WifiConnect* wifi_connect = (WifiConnect*)wifi; WifiConnect* wifi_connect = (WifiConnect*)wifi;
WifiConnectView* view = &wifi_connect->view; 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_flex_flow(parent, LV_FLEX_FLOW_COLUMN);
lv_obj_set_style_pad_top(parent, 8, 0); tt_lv_obj_set_style_auto_padding(parent);
lv_obj_set_style_pad_bottom(parent, 8, 0);
lv_obj_set_style_pad_left(parent, 16, 0);
lv_obj_set_style_pad_right(parent, 16, 0);
view->root = parent; view->root = parent;

View File

@ -158,7 +158,7 @@ AppManifest wifi_manage_app = {
.id = "wifi_manage", .id = "wifi_manage",
.name = "Wi-Fi", .name = "Wi-Fi",
.icon = NULL, .icon = NULL,
.type = AppTypeSystem, .type = AppTypeSettings,
.on_start = &app_start, .on_start = &app_start,
.on_stop = &app_stop, .on_stop = &app_stop,
.on_show = &app_show, .on_show = &app_show,

View File

@ -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) { void wifi_manage_view_create(WifiManageView* view, WifiManageBindings* bindings, lv_obj_t* parent) {
view->root = 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_flex_flow(parent, LV_FLEX_FLOW_COLUMN);
lv_obj_set_style_pad_top(parent, 8, 0); tt_lv_obj_set_style_auto_padding(parent);
lv_obj_set_style_pad_bottom(parent, 8, 0);
lv_obj_set_style_pad_left(parent, 16, 0);
lv_obj_set_style_pad_right(parent, 16, 0);
// Top row: enable/disable // Top row: enable/disable
lv_obj_t* switch_container = lv_obj_create(parent); lv_obj_t* switch_container = lv_obj_create(parent);

View File

@ -23,6 +23,7 @@ if (DEFINED ENV{ESP_IDF_VERSION})
PUBLIC idf::lvgl # libs/ PUBLIC idf::lvgl # libs/
PUBLIC idf::driver PUBLIC idf::driver
PUBLIC idf::spiffs PUBLIC idf::spiffs
PUBLIC idf::nvs_flash
) )
else() else()
add_definitions(-D_Nullable=) add_definitions(-D_Nullable=)

View File

@ -25,6 +25,8 @@ static void desktop_show(TT_UNUSED App app, TT_UNUSED lv_obj_t* parent) {
lv_list_add_text(list, "System"); lv_list_add_text(list, "System");
tt_app_manifest_registry_for_each_of_type(AppTypeSystem, list, create_app_widget); 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"); lv_list_add_text(list, "User");
tt_app_manifest_registry_for_each_of_type(AppTypeUser, list, create_app_widget); tt_app_manifest_registry_for_each_of_type(AppTypeUser, list, create_app_widget);
} }

View File

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

View File

@ -1,7 +1,5 @@
#include "app_manifest.h" #include "app.h"
#include "core_extra_defines.h"
#include "lvgl.h" #include "lvgl.h"
#include "thread.h"
static void app_show(TT_UNUSED App app, lv_obj_t* parent) { static void app_show(TT_UNUSED App app, lv_obj_t* parent) {
lv_obj_t* heap_info = lv_label_create(parent); lv_obj_t* heap_info = lv_label_create(parent);

View File

@ -5,10 +5,34 @@
typedef bool (*Bootstrap)(); typedef bool (*Bootstrap)();
typedef bool (*InitLvgl)(); typedef bool (*InitLvgl)();
typedef bool (*InitLvgl)();
typedef void (*SetBacklightDuty)(uint8_t);
typedef struct {
/** Set backlight duty */
SetBacklightDuty set_backlight_duty;
} Display;
typedef struct { 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; 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; 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; const SdCard* _Nullable sdcard;
} HardwareConfig; } HardwareConfig;

View File

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

View File

@ -0,0 +1,44 @@
#pragma once
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#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

View File

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

View File

@ -0,0 +1,109 @@
#ifndef ESP_PLATFOM
#include "bundle.h"
#include "preferences.h"
#include <string.h>
#include <tactility_core.h>
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

View File

@ -2,12 +2,15 @@
#include "app_manifest_registry.h" #include "app_manifest_registry.h"
#include "hardware_i.h" #include "hardware_i.h"
#include "preferences.h"
#include "service_registry.h" #include "service_registry.h"
#include "services/loader/loader.h" #include "services/loader/loader.h"
#define TAG "tactility" #define TAG "tactility"
// region System services static const Config* config_instance = NULL;
// region Default services
extern const ServiceManifest gui_service; extern const ServiceManifest gui_service;
extern const ServiceManifest loader_service; extern const ServiceManifest loader_service;
@ -19,13 +22,15 @@ static const ServiceManifest* const system_services[] = {
// endregion // endregion
// region System apps // region Default apps
extern const AppManifest desktop_app; extern const AppManifest desktop_app;
extern const AppManifest display_app;
extern const AppManifest system_info_app; extern const AppManifest system_info_app;
static const AppManifest* const system_apps[] = { static const AppManifest* const system_apps[] = {
&desktop_app, &desktop_app,
&display_app,
&system_info_app &system_info_app
}; };
@ -83,6 +88,16 @@ TT_UNUSED void tt_init(const Config* config) {
tt_hardware_init(config->hardware); 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! // 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
register_and_start_system_services(); register_and_start_system_services();
@ -103,4 +118,10 @@ TT_UNUSED void tt_init(const Config* config) {
} }
TT_LOG_I(TAG, "tt_init complete"); TT_LOG_I(TAG, "tt_init complete");
config_instance = config;
}
const Config* _Nullable tt_get_config() {
return config_instance;
} }

View File

@ -17,8 +17,18 @@ typedef struct {
const char* auto_start_app_id; const char* auto_start_app_id;
} Config; } Config;
/**
* Attempts to initialize Tactility and all configured hardware.
* @param config
*/
TT_UNUSED void tt_init(const Config* 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 #ifdef __cplusplus
} }
#endif #endif

View File

@ -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_all(obj, LV_STATE_DEFAULT, 0);
lv_obj_set_style_pad_gap(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);
}

View File

@ -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_bg_invisible(lv_obj_t* obj);
void tt_lv_obj_set_style_no_padding(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 #ifdef __cplusplus
} }
#endif #endif