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].
[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

View File

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

View File

@ -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 <driver/spi_common.h>
#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;

View File

@ -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() {

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 "display_i.h"
#include <stdbool.h>
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
};

View File

@ -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 <thread.h>
#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;
}

View File

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

View File

@ -1,6 +1,6 @@
#include "config.h"
#include "kernel.h"
#include "log.h"
#include "tactility_core.h"
#include "display_i.h"
#include <driver/spi_common.h>
#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;
}

View File

@ -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 <esp_lcd_panel_io.h>
#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;
}

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

View File

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

View File

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

View File

@ -4,8 +4,9 @@
*/
#pragma once
#include <stdio.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#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

View File

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

View File

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

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) {
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;

View File

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

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) {
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);

View File

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

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");
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);
}

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 "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);

View File

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

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

View File

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

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