Various improvements and fixes (#128)

- Fix for display orientation assumption in Display app
- Update logo (added colours)
- Fix for double stopping the Files app
- Deny registration of apps and services that are already registered
- Updated `ideas.md` for these changes
- Other cleanup
This commit is contained in:
Ken Van Hoeylandt 2024-12-15 21:15:54 +01:00 committed by GitHub
parent 1b89065c99
commit 49bf8e824c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 162 additions and 100 deletions

View File

@ -111,6 +111,7 @@ bool YellowDisplay::start() {
.panel_handle = panelHandle, .panel_handle = panelHandle,
.buffer_size = TWODOTFOUR_LCD_DRAW_BUFFER_SIZE, .buffer_size = TWODOTFOUR_LCD_DRAW_BUFFER_SIZE,
.double_buffer = false, .double_buffer = false,
.trans_size = 0,
.hres = TWODOTFOUR_LCD_HORIZONTAL_RESOLUTION, .hres = TWODOTFOUR_LCD_HORIZONTAL_RESOLUTION,
.vres = TWODOTFOUR_LCD_VERTICAL_RESOLUTION, .vres = TWODOTFOUR_LCD_VERTICAL_RESOLUTION,
.monochrome = false, .monochrome = false,

44
Buildscripts/logo.cmake Normal file
View File

@ -0,0 +1,44 @@
get_filename_component(VERSION_TEXT_FILE version.txt ABSOLUTE)
file(READ ${VERSION_TEXT_FILE} TACTILITY_VERSION)
if (DEFINED ENV{ESP_IDF_VERSION})
set(TACTILITY_TARGET " @ ESP-IDF")
else()
set(TACTILITY_TARGET " @ Simulator")
endif()
if(NOT WIN32)
string(ASCII 27 Esc)
set(ColourReset "${Esc}[m")
set(Cyan "${Esc}[36m")
set(Grey "${Esc}[37m")
set(LightPurple "${Esc}[1;35m")
set(White "${Esc}[1;37m")
else()
set(ColourReset "")
set(Cyan "")
set(Grey "")
set(LightPurple "")
set(White "")
endif()
# Some terminals (e.g. GitHub Actions) reset colour for every in a multiline message(),
# so we add the colour to each line instead of assuming it would automatically be re-used.
message("\n\n\
${LightPurple}@@\n\
${LightPurple}@@@\n\
${LightPurple}@@@\n\
${LightPurple}@@@\n\
${LightPurple}@@@\n\
${Cyan}@@@@@@@@@@@@@@@@${LightPurple}@@@\n\
${Cyan}@@@@@@@@@@@@@@@@@${LightPurple}@@@\n\
${Cyan}@@@ ${LightPurple}@@@ ${White}Tactility ${TACTILITY_VERSION}\n\
${Cyan}@@@ ${LightPurple}@@@ ${Grey}${TACTILITY_TARGET}\n\
${Cyan}@@@${LightPurple}@@@@@@@@@@@@@@@@@\n\
${Cyan}@@@${LightPurple}@@@@@@@@@@@@@@@@\n\
${Cyan}@@@\n\
${Cyan}@@@\n\
${Cyan}@@@\n\
${Cyan}@@@\n\
${Cyan}@@\n\n${ColourReset}")

View File

@ -6,6 +6,8 @@ set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_ASM_COMPILE_OBJECT "${CMAKE_CXX_COMPILER_TARGET}") set(CMAKE_ASM_COMPILE_OBJECT "${CMAKE_CXX_COMPILER_TARGET}")
include("Buildscripts/logo.cmake")
if (DEFINED ENV{ESP_IDF_VERSION}) if (DEFINED ENV{ESP_IDF_VERSION})
message("Building with ESP-IDF v$ENV{ESP_IDF_VERSION}") message("Building with ESP-IDF v$ENV{ESP_IDF_VERSION}")
include($ENV{IDF_PATH}/tools/cmake/project.cmake) include($ENV{IDF_PATH}/tools/cmake/project.cmake)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 2.3 KiB

View File

@ -9,9 +9,9 @@
id="svg1" id="svg1"
inkscape:version="1.4 (e7c3feb100, 2024-10-09)" inkscape:version="1.4 (e7c3feb100, 2024-10-09)"
sodipodi:docname="Tactility.svg" sodipodi:docname="Tactility.svg"
inkscape:export-filename="Tactility.png" inkscape:export-filename="../assets/boot_logo.png"
inkscape:export-xdpi="96" inkscape:export-xdpi="22.013334"
inkscape:export-ydpi="96" inkscape:export-ydpi="22.013334"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
@ -27,14 +27,14 @@
inkscape:deskcolor="#d1d1d1" inkscape:deskcolor="#d1d1d1"
inkscape:document-units="mm" inkscape:document-units="mm"
inkscape:zoom="1" inkscape:zoom="1"
inkscape:cx="410.5" inkscape:cx="411"
inkscape:cy="336.5" inkscape:cy="336"
inkscape:window-width="2115" inkscape:window-width="1503"
inkscape:window-height="1295" inkscape:window-height="930"
inkscape:window-x="26" inkscape:window-x="0"
inkscape:window-y="23" inkscape:window-y="0"
inkscape:window-maximized="0" inkscape:window-maximized="1"
inkscape:current-layer="svg1" inkscape:current-layer="g29"
showgrid="false" /> showgrid="false" />
<defs <defs
id="defs1"> id="defs1">
@ -221,53 +221,55 @@
inkscape:label="Glyph"> inkscape:label="Glyph">
<g <g
id="g28" id="g28"
inkscape:label="Upright T"> inkscape:label="Upright T"
<path style="fill:#7376ff;fill-opacity:1" />
style="fill:#ffffff;stroke-width:0.289836;paint-order:stroke fill markers"
id="rect26"
width="60"
height="10"
x="10"
y="150"
sodipodi:type="rect"
inkscape:path-effect="#path-effect1"
d="M 20.022406,150 H 70 v 10 H 10 a 10.011209,10.011209 135.06412 0 1 10.022406,-10 z" />
<path
style="fill:#ffffff;stroke-width:0.264583;paint-order:stroke fill markers"
id="rect28"
width="10"
height="60"
x="40"
y="150"
sodipodi:type="rect"
inkscape:path-effect="#path-effect4"
d="m 40,150 h 10 v 60 h -0.01471 A 9.9852864,9.9852864 45 0 1 40,200.01471 Z" />
</g>
<g <g
id="g28-0" id="g28-0"
transform="rotate(180,55,165)" transform="rotate(180,55,165)"
inkscape:label="Upside-down T"> inkscape:label="Upside-down T"
<path style="fill:#f876e9;fill-opacity:1" />
style="fill:#ffffff;stroke-width:0.289836;paint-order:stroke fill markers" <path
id="rect26-4" style="fill:#00bbff;fill-opacity:1;stroke-width:0.289836;paint-order:stroke fill markers"
width="60" id="rect26"
height="10" width="60"
x="10" height="10"
y="150" x="10"
sodipodi:type="rect" y="150"
inkscape:path-effect="#path-effect3" sodipodi:type="rect"
d="M 19.96875,150 H 70 v 10 H 10 v -0.0312 A 9.96875,9.96875 135 0 1 19.96875,150 Z" /> inkscape:path-effect="#path-effect1"
<path d="M 20.022406,150 H 70 v 10 H 10 a 10.011209,10.011209 135.06412 0 1 10.022406,-10 z" />
style="fill:#ffffff;stroke-width:0.264583;paint-order:stroke fill markers" <path
id="rect28-8" style="fill:#ee81c3;fill-opacity:1;stroke-width:0.289836;paint-order:stroke fill markers"
width="10" id="rect26-4"
height="60" width="60"
x="40" height="10"
y="150" x="10"
sodipodi:type="rect" y="150"
inkscape:path-effect="#path-effect2" sodipodi:type="rect"
d="m 40,150 h 10 v 60 A 10.011209,10.011209 45.064117 0 1 40,199.97759 Z" /> inkscape:path-effect="#path-effect3"
</g> d="M 19.96875,150 H 70 v 10 H 10 v -0.0312 A 9.96875,9.96875 135 0 1 19.96875,150 Z"
transform="rotate(180,55,165)" />
<path
style="fill:#00bbff;fill-opacity:1;stroke-width:0.264583;paint-order:stroke fill markers"
id="rect28"
width="10"
height="60"
x="40"
y="150"
sodipodi:type="rect"
inkscape:path-effect="#path-effect4"
d="m 40,150 h 10 v 60 h -0.01471 A 9.9852864,9.9852864 45 0 1 40,200.01471 Z" />
<path
style="fill:#ee81c3;fill-opacity:1;stroke-width:0.264583;paint-order:stroke fill markers"
id="rect28-8"
width="10"
height="60"
x="40"
y="150"
sodipodi:type="rect"
inkscape:path-effect="#path-effect2"
d="m 40,150 h 10 v 60 A 10.011209,10.011209 45.064117 0 1 40,199.97759 Z"
transform="rotate(180,55,165)" />
</g> </g>
<text <text
xml:space="preserve" xml:space="preserve"

Before

Width:  |  Height:  |  Size: 8.9 KiB

After

Width:  |  Height:  |  Size: 9.0 KiB

View File

@ -1,5 +1,12 @@
# Bugs
- I2C Scanner is on M5Stack devices is broken
- Fix screenshot app on ESP32: it currently blocks when allocating memory (its cmakelists.txt also needs a fix, see TODO in there)
- In LVGL9 with M5Core2, crash when bottom item is clicked without scrolling first
- Commit fix to esp_lvgl_port to have `esp_lvgl_port_disp.c` user driver_data instead of user_data
- WiFi bug: when pressing disconnect while between `WIFI_EVENT_STA_START` and `IP_EVENT_STA_GOT_IP`, then auto-connect becomes activate again.
# TODOs # TODOs
- Release process should bundle all libs and TactilityC headers and generate an SDK project - Rewrite `sdcard` HAL to class
- Attach ELF data to wrapper app (as app data) (check that app state is "running"!) so you can run more than 1 external apps at a time. - Attach ELF data to wrapper app (as app data) (check that app state is "running"!) so you can run more than 1 external apps at a time.
We'll need to keep track of all manifest instances, so that the wrapper can look up the relevant manifest for the relevant callbacks. We'll need to keep track of all manifest instances, so that the wrapper can look up the relevant manifest for the relevant callbacks.
- T-Deck: Clear screen before turning on blacklight - T-Deck: Clear screen before turning on blacklight
@ -11,37 +18,28 @@
- Crash monitoring: Keep track of which system phase the app crashed in (e.g. which app in which state) - Crash monitoring: Keep track of which system phase the app crashed in (e.g. which app in which state)
- AppContext's onResult should pass the app id (or launch request id!) that was started, so we can differentiate between multiple types of apps being launched - AppContext's onResult should pass the app id (or launch request id!) that was started, so we can differentiate between multiple types of apps being launched
- Loader: Use main dispatcher instead of Thread, and move API to `tt::app::` - Loader: Use main dispatcher instead of Thread, and move API to `tt::app::`
- Bug: I2C Scanner is on M5Stack devices is broken
- Make firmwares available via release process
- Make firmwares available via web serial website
- Bug: When closing a top level app, there's often an error "can't stop root app"
- Create more unit tests for `tactility-core` and `tactility` (PC-only for now) - Create more unit tests for `tactility-core` and `tactility` (PC-only for now)
- Show a warning screen if firmware encryption or secure boot are off when saving WiFi credentials. - 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. - Show a warning screen when a user plugs in the SD card on a device that only supports mounting at boot.
- Check service/app id on registration to see if it is a duplicate id
- Fix screenshot app on ESP32: it currently blocks when allocating memory (its cmakelists.txt also needs a fix, see TODO in there)
- Localisation of texts (load in boot app from sd?) - Localisation of texts (load in boot app from sd?)
- Portrait support for GPIO app - Portrait support for GPIO app
- Explore LVGL9's FreeRTOS functionality - Explore LVGL9's FreeRTOS functionality
- Explore LVGL9's ILI93414 driver for 2.4" Yellow Board - Explore LVGL9's ILI93414 driver for 2.4" Yellow Board
- Bug: in LVGL9 with M5Core2, crash when bottom item is clicked without scrolling first
- Replace M5Unified and M5GFX with custom drivers (so we can fix the Core2 SD card mounting bug, and so we regain some firmware space) - Replace M5Unified and M5GFX with custom drivers (so we can fix the Core2 SD card mounting bug, and so we regain some firmware space)
- Commit fix to esp_lvgl_port to have `esp_lvgl_port_disp.c` user driver_data instead of user_data
- Wifi bug: when pressing disconnect while between `WIFI_EVENT_STA_START` and `IP_EVENT_STA_GOT_IP`, then auto-connect becomes activate again.
- T-Deck Plus: Create separate board config
- External app loading: Check version of Tactility and check ESP target hardware, to check for compatibility. - External app loading: Check version of Tactility and check ESP target hardware, to check for compatibility.
- hal::Configuration: Replace CreateX fields with plain instances - Scanning SD card for external apps and auto-register them (in a temporary register?)
- T-Deck Power: capacity estimation uses linear voltage curve, but it should use some sort of battery discharge curve.
- Consider scanning SD card for external apps and auto-register them (in a temporary register?)
# Core Ideas
- Support for displays with different DPI. Consider the layer-based system like on Android.
- If present, use LED to show boot status
- 2 wire speaker support
- tt::app::start() and similar functions as proxies for Loader app start/stop/etc. - tt::app::start() and similar functions as proxies for Loader app start/stop/etc.
- USB implementation to make device act as mass storage device. - Support hot-plugging SD card
# Nice-to-haves
- T-Deck Plus: Create separate board config?
- Support for displays with different DPI. Consider the layer-based system like on Android.
- Make firmwares available via web serial website
- If present, use LED to show boot status
- T-Deck Power: capacity estimation uses linear voltage curve, but it should use some sort of battery discharge curve.
# App Ideas # App Ideas
- USB implementation to make device act as mass storage device.
- System logger - System logger
- BlueTooth keyboard app - BlueTooth keyboard app
- Chip 8 emulator - Chip 8 emulator

View File

@ -16,14 +16,19 @@ void addApp(const AppManifest* manifest) {
TT_LOG_I(TAG, "Registering manifest %s", manifest->id.c_str()); TT_LOG_I(TAG, "Registering manifest %s", manifest->id.c_str());
hash_mutex.acquire(TtWaitForever); hash_mutex.acquire(TtWaitForever);
app_manifest_map[manifest->id] = manifest;
if (app_manifest_map[manifest->id] == nullptr) {
app_manifest_map[manifest->id] = manifest;
} else {
TT_LOG_E(TAG, "App id in use: %s", manifest->id.c_str());
}
hash_mutex.release(); hash_mutex.release();
} }
_Nullable const AppManifest * findAppById(const std::string& id) { _Nullable const AppManifest * findAppById(const std::string& id) {
hash_mutex.acquire(TtWaitForever); hash_mutex.acquire(TtWaitForever);
auto iterator = app_manifest_map.find(id); _Nullable const AppManifest* result = app_manifest_map[id.c_str()];
_Nullable const AppManifest* result = iterator != app_manifest_map.end() ? iterator->second : nullptr;
hash_mutex.release(); hash_mutex.release();
return result; return result;
} }

View File

@ -49,8 +49,10 @@ static void onShow(TT_UNUSED AppContext& app, lv_obj_t* parent) {
lv_obj_set_flex_grow(wrapper, 1); lv_obj_set_flex_grow(wrapper, 1);
auto* display = lv_obj_get_display(parent); auto* display = lv_obj_get_display(parent);
auto orientation = lv_display_get_rotation(display); auto horizontal_px = lv_display_get_horizontal_resolution(display);
if (orientation == LV_DISPLAY_ROTATION_0 || orientation == LV_DISPLAY_ROTATION_180) { auto vertical_px = lv_display_get_vertical_resolution(display);
bool is_landscape_display = horizontal_px > vertical_px;
if (is_landscape_display) {
lv_obj_set_flex_flow(wrapper, LV_FLEX_FLOW_ROW); lv_obj_set_flex_flow(wrapper, LV_FLEX_FLOW_ROW);
} else { } else {
lv_obj_set_flex_flow(wrapper, LV_FLEX_FLOW_COLUMN); lv_obj_set_flex_flow(wrapper, LV_FLEX_FLOW_COLUMN);

View File

@ -30,17 +30,17 @@ static void onSliderEvent(lv_event_t* event) {
} }
} }
#define ORIENTATION_LANDSCAPE 0 #define ROTATION_DEFAULT 0
#define ORIENTATION_LANDSCAPE_FLIPPED 1 #define ROTATION_180 1
#define ORIENTATION_PORTRAIT_LEFT 2 #define ROTATION_270 2
#define ORIENTATION_PORTRAIT_RIGHT 3 #define ROTATION_90 3
static lv_display_rotation_t orientationSettingToDisplayOrientation(uint32_t setting) { static lv_display_rotation_t orientationSettingToDisplayRotation(uint32_t setting) {
if (setting == ORIENTATION_LANDSCAPE_FLIPPED) { if (setting == ROTATION_180) {
return LV_DISPLAY_ROTATION_180; return LV_DISPLAY_ROTATION_180;
} else if (setting == ORIENTATION_PORTRAIT_LEFT) { } else if (setting == ROTATION_270) {
return LV_DISPLAY_ROTATION_270; return LV_DISPLAY_ROTATION_270;
} else if (setting == ORIENTATION_PORTRAIT_RIGHT) { } else if (setting == ROTATION_90) {
return LV_DISPLAY_ROTATION_90; return LV_DISPLAY_ROTATION_90;
} else { } else {
return LV_DISPLAY_ROTATION_0; return LV_DISPLAY_ROTATION_0;
@ -49,13 +49,13 @@ static lv_display_rotation_t orientationSettingToDisplayOrientation(uint32_t set
static uint32_t dipslayOrientationToOrientationSetting(lv_display_rotation_t orientation) { static uint32_t dipslayOrientationToOrientationSetting(lv_display_rotation_t orientation) {
if (orientation == LV_DISPLAY_ROTATION_90) { if (orientation == LV_DISPLAY_ROTATION_90) {
return ORIENTATION_PORTRAIT_RIGHT; return ROTATION_90;
} else if (orientation == LV_DISPLAY_ROTATION_180) { } else if (orientation == LV_DISPLAY_ROTATION_180) {
return ORIENTATION_LANDSCAPE_FLIPPED; return ROTATION_180;
} else if (orientation == LV_DISPLAY_ROTATION_270) { } else if (orientation == LV_DISPLAY_ROTATION_270) {
return ORIENTATION_PORTRAIT_LEFT; return ROTATION_270;
} else { } else {
return ORIENTATION_LANDSCAPE; return ROTATION_DEFAULT;
} }
} }
@ -63,7 +63,7 @@ static void onOrientationSet(lv_event_t* event) {
auto* dropdown = static_cast<lv_obj_t*>(lv_event_get_target(event)); auto* dropdown = static_cast<lv_obj_t*>(lv_event_get_target(event));
uint32_t selected = lv_dropdown_get_selected(dropdown); uint32_t selected = lv_dropdown_get_selected(dropdown);
TT_LOG_I(TAG, "Selected %ld", selected); TT_LOG_I(TAG, "Selected %ld", selected);
lv_display_rotation_t rotation = orientationSettingToDisplayOrientation(selected); lv_display_rotation_t rotation = orientationSettingToDisplayRotation(selected);
if (lv_display_get_rotation(lv_display_get_default()) != rotation) { if (lv_display_get_rotation(lv_display_get_default()) != rotation) {
lv_display_set_rotation(lv_display_get_default(), rotation); lv_display_set_rotation(lv_display_get_default(), rotation);
setRotation(rotation); setRotation(rotation);
@ -111,8 +111,17 @@ static void onShow(AppContext& app, lv_obj_t* parent) {
lv_label_set_text(orientation_label, "Orientation"); lv_label_set_text(orientation_label, "Orientation");
lv_obj_align(orientation_label, LV_ALIGN_TOP_LEFT, 0, 40); lv_obj_align(orientation_label, LV_ALIGN_TOP_LEFT, 0, 40);
auto horizontal_px = lv_display_get_horizontal_resolution(lvgl_display);
auto vertical_px = lv_display_get_vertical_resolution(lvgl_display);
bool is_landscape_display = horizontal_px > vertical_px;
lv_obj_t* orientation_dropdown = lv_dropdown_create(wrapper); lv_obj_t* orientation_dropdown = lv_dropdown_create(wrapper);
lv_dropdown_set_options(orientation_dropdown, "Landscape\nLandscape (flipped)\nPortrait Left\nPortrait Right"); if (is_landscape_display) {
lv_dropdown_set_options(orientation_dropdown, "Landscape\nLandscape (flipped)\nPortrait Left\nPortrait Right");
} else {
lv_dropdown_set_options(orientation_dropdown, "Portrait\nPortrait (flipped)\nLandscape Left\nLandscape Right");
}
lv_obj_align(orientation_dropdown, LV_ALIGN_TOP_RIGHT, 0, 32); lv_obj_align(orientation_dropdown, LV_ALIGN_TOP_RIGHT, 0, 32);
lv_obj_add_event_cb(orientation_dropdown, onOrientationSet, LV_EVENT_VALUE_CHANGED, nullptr); lv_obj_add_event_cb(orientation_dropdown, onOrientationSet, LV_EVENT_VALUE_CHANGED, nullptr);
uint32_t orientation_selected = dipslayOrientationToOrientationSetting( uint32_t orientation_selected = dipslayOrientationToOrientationSetting(

View File

@ -100,10 +100,6 @@ static void onNavigateUpPressed(TT_UNUSED lv_event_t* event) {
updateViews(files_data); updateViews(files_data);
} }
static void onExitAppPressed(TT_UNUSED lv_event_t* event) {
service::loader::stopApp();
}
static void viewFile(const char* path, const char* filename) { static void viewFile(const char* path, const char* filename) {
size_t path_len = strlen(path); size_t path_len = strlen(path);
size_t filename_len = strlen(filename); size_t filename_len = strlen(filename);
@ -222,7 +218,6 @@ static void onShow(AppContext& app, lv_obj_t* parent) {
lv_obj_set_flex_flow(parent, LV_FLEX_FLOW_COLUMN); lv_obj_set_flex_flow(parent, LV_FLEX_FLOW_COLUMN);
lv_obj_t* toolbar = lvgl::toolbar_create(parent, "Files"); lv_obj_t* toolbar = lvgl::toolbar_create(parent, "Files");
lvgl::toolbar_set_nav_action(toolbar, LV_SYMBOL_CLOSE, &onExitAppPressed, nullptr);
lvgl::toolbar_add_action(toolbar, LV_SYMBOL_UP, &onNavigateUpPressed, nullptr); lvgl::toolbar_add_action(toolbar, LV_SYMBOL_UP, &onNavigateUpPressed, nullptr);
data->list = lv_list_create(parent); data->list = lv_list_create(parent);

View File

@ -24,7 +24,11 @@ void addService(const ServiceManifest* manifest) {
TT_LOG_I(TAG, "Adding %s", manifest->id.c_str()); TT_LOG_I(TAG, "Adding %s", manifest->id.c_str());
manifest_mutex.acquire(TtWaitForever); manifest_mutex.acquire(TtWaitForever);
service_manifest_map[manifest->id] = manifest; if (service_manifest_map[manifest->id] == nullptr) {
service_manifest_map[manifest->id] = manifest;
} else {
TT_LOG_E(TAG, "Service id in use: %s", manifest->id.c_str());
}
manifest_mutex.release(); manifest_mutex.release();
} }