Statusbar improvements (#36)

- Added sdcard service: it updates a statusbar icon and it can auto-unmount sdcards that were physically ejected by the user.
- Added `is_mounted` check for sdcard drivers
- Refactored assets: moved them from `tactility-esp/` to `data/`
- Made assets work with sim build
- Refactored wifi statusbar icons
- Refactored wifi_manage app access point icons
- Support not having an initial image icon when registering a new icon in statusbars. When a statusbar icon is added, it is now visible/invisible depending on whether an image was specified.
- Keep track of app memory on app start to find memory leaks (needs fine-tuning in the future)
- `tt_init()` assigns `config_instance` earlier, so it can be used during service init
This commit is contained in:
Ken Van Hoeylandt 2024-02-09 23:53:29 +01:00 committed by GitHub
parent 29ea47a7ba
commit 5558edccce
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
67 changed files with 321 additions and 86 deletions

View File

@ -572,8 +572,8 @@
/*API for fopen, fread, etc*/
#define LV_USE_FS_STDIO 1
#if LV_USE_FS_STDIO
#define LV_FS_STDIO_LETTER 'A' /*Set an upper cased letter on which the drive will accessible (e.g. 'A')*/
#define LV_FS_STDIO_PATH "" /*Set the working directory. File/directory paths will be appended to it.*/
#define LV_FS_STDIO_LETTER 'A' /*Set an upper cased letter on which the drive will accessible (e.g. 'A')*/
#define LV_FS_STDIO_PATH "." /*Set the working directory. File/directory paths will be appended to it.*/
#define LV_FS_STDIO_CACHE_SIZE 0 /*>0 to cache this number of bytes in lv_fs_read()*/
#endif

View File

@ -54,7 +54,7 @@ static bool sdcard_init() {
return true;
}
static void* sdcard_mount(const char* mount_point) {
static void* _Nullable sdcard_mount(const char* mount_point) {
TT_LOG_I(TAG, "Mounting %s", mount_point);
esp_vfs_fat_sdmmc_mount_config_t mount_config = {
@ -64,8 +64,6 @@ static void* sdcard_mount(const char* mount_point) {
.disk_status_check_enable = TDECK_SDCARD_STATUS_CHECK_ENABLED
};
sdmmc_card_t* card;
// Init without card detect (CD) and write protect (WD)
sdspi_device_config_t slot_config = SDSPI_DEVICE_CONFIG_DEFAULT();
slot_config.gpio_cs = TDECK_SDCARD_PIN_CS;
@ -76,6 +74,8 @@ static void* sdcard_mount(const char* mount_point) {
// https://github.com/Xinyuan-LilyGO/T-Deck/blob/master/examples/UnitTest/UnitTest.ino
// Observation: Using this automatically sets the bus to 20MHz
host.max_freq_khz = TDECK_SDCARD_SPI_FREQUENCY;
sdmmc_card_t* card;
esp_err_t ret = esp_vfs_fat_sdspi_mount(mount_point, &host, &slot_config, &mount_config, &card);
if (ret != ESP_OK) {
@ -101,7 +101,6 @@ static void* sdcard_init_and_mount(const char* mount_point) {
TT_LOG_E(TAG, "Failed to set SPI CS pins high. This is a pre-requisite for mounting.");
return NULL;
}
MountData* data = sdcard_mount(mount_point);
if (data == NULL) {
TT_LOG_E(TAG, "Mount failed for %s", mount_point);
@ -125,8 +124,14 @@ static void sdcard_unmount(void* context) {
free(data);
}
static bool sdcard_is_mounted(void* context) {
MountData* data = (MountData*)context;
return (data != NULL) && (sdmmc_get_status(data->card) == ESP_OK);
}
const SdCard tdeck_sdcard = {
.mount = &sdcard_init_and_mount,
.unmount = &sdcard_unmount,
.is_mounted = &sdcard_is_mounted,
.mount_behaviour = SDCARD_MOUNT_BEHAVIOUR_AT_BOOT
};

View File

@ -66,8 +66,14 @@ static void sdcard_unmount(void* context) {
free(data);
}
static bool sdcard_is_mounted(void* context) {
MountData* data = (MountData*)context;
return (data != NULL) && (sdmmc_get_status(data->card) == ESP_OK);
}
const SdCard twodotfour_sdcard = {
.mount = &sdcard_mount,
.unmount = &sdcard_unmount,
.is_mounted = &sdcard_is_mounted,
.mount_behaviour = SDCARD_MOUNT_BEHAVIOUR_ANYTIME
};

BIN
data/assets/sdcard.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 287 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 309 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 376 B

BIN
data/assets/wifi_find.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 464 B

BIN
data/assets/wifi_off.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 411 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 435 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 404 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 451 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 415 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 473 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 419 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 473 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 415 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 478 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 406 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 466 B

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="20" viewBox="0 -960 960 960" width="20"><path d="M360-528h72v-144h-72v144Zm120 0h72v-144h-72v144Zm120 0h72v-144h-72v144ZM263.717-96Q234-96 213-117.15T192-168v-504l192-192h312q29.7 0 50.85 21.15Q768-821.7 768-792v624q0 29.7-21.162 50.85Q725.676-96 695.96-96H263.717Zm.283-72h432v-624H414L264-642v474Zm0 0h432-432Z"/></svg>

After

Width:  |  Height:  |  Size: 369 B

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="20" viewBox="0 -960 960 960" width="20"><path d="M263.717-96Q234-96 213-117.15T192-168v-504l192-192h312q29.7 0 50.85 21.15Q768-821.7 768-792v624q0 29.7-21.162 50.85Q725.676-96 695.96-96H263.717Zm.283-72h432v-624H414L264-642v474Zm215.789-120Q495-288 505.5-298.289q10.5-10.29 10.5-25.5Q516-339 505.711-349.5q-10.29-10.5-25.5-10.5Q465-360 454.5-349.711q-10.5 10.29-10.5 25.5Q444-309 454.289-298.5q10.29 10.5 25.5 10.5ZM444-432h72v-192h-72v192ZM264-168h432-432Z"/></svg>

After

Width:  |  Height:  |  Size: 514 B

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="20" viewBox="0 -960 960 960" width="20"><path d="M480-144 0-624q98-94 221.5-143T480-816q135 0 258.5 49T960-624l-97 97q-24-24-54-36.5T744-576q-70 0-119 49t-49 119q0 35 12.5 65t36.5 54L480-144Zm264 0q-17 0-28.5-11.5T704-184q0-17 11.5-28.5T744-224q17 0 28.5 11.5T784-184q0 17-11.5 28.5T744-144Zm-28-122q0-42 7.5-59t38.5-46q13-12 24-24t11-31q0-21-14.5-34T745-473q-20 0-35 12.5T687-426l-52-22q12-37 41.5-58.5T744-528q49 0 79.5 28t30.5 73q0 23-11 41.5T805-341q-21 20-26 32.5t-5 42.5h-58Z"/></svg>

After

Width:  |  Height:  |  Size: 538 B

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="20" viewBox="0 -960 960 960" width="20"><path d="M480-144 0-624q97-94 221-143t259-49q135 0 259 49t221 143l-51 52q-86-85-197-128.5T480-744q-102 0-197.5 32T106-619l427 422-53 53Zm381-48L745-308q-17 10-35 15t-38 5q-60 0-102-42t-42-102q0-60 42-102t102-42q60 0 102 42t42 102q0 20-4.5 38.5T796-359l116 116-51 51ZM672-360q30 0 51-21t21-51q0-30-21-51t-51-21q-30 0-51 21t-21 51q0 30 21 51t51 21ZM480-196Z"/></svg>

After

Width:  |  Height:  |  Size: 452 B

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="20" viewBox="0 -960 960 960" width="20"><path d="m699-363-51-51 206-205q-81-61-176-93t-198-32q-36.667 0-73.333 4.5Q370-735 335-727l-59-59q48-15 99.241-22.5T480-816q140 0 263 51t217 141L699-363ZM480-246l66-66-359-359q-21 11-41 24.5T106-619l374 373ZM813-45 597-261 480-144 0-624q30-29 63.533-54.224Q97.065-703.447 134-724l-89-89 51-51L864-96l-51 51ZM492-571Zm-125 79Z"/></svg>

After

Width:  |  Height:  |  Size: 422 B

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="20" viewBox="0 -960 960 960" width="20"><path d="M480-144 0-624q94-90 217-141t263-51q140 0 263 51t217 141L480-144Zm0-102 374-373q-81-61-176-93t-198-32q-103 0-198 32t-176 93l374 373Zm-36-138h72v-168h-72v168Zm35.789-216Q495-600 505.5-610.289q10.5-10.29 10.5-25.5Q516-651 505.711-661.5q-10.29-10.5-25.5-10.5Q465-672 454.5-661.711q-10.5 10.29-10.5 25.5Q444-621 454.289-610.5q10.29 10.5 25.5 10.5ZM480-246Z"/></svg>

After

Width:  |  Height:  |  Size: 458 B

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="20" viewBox="0 -960 960 960" width="20"><path d="M480-144 0-624q94-90 217-141t263-51q140 0 263 51t217 141L480-144Zm0-102 374-373q-81-61-176-93t-198-32q-103 0-198 32t-176 93l374 373Z"/></svg>

After

Width:  |  Height:  |  Size: 238 B

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="20" viewBox="0 -960 960 960" width="20"><path d="M648-144q-10.2 0-17.1-6.9-6.9-6.9-6.9-17.1v-144q0-10.2 6.9-17.1 6.9-6.9 17.1-6.9h24v-48q0-29.7 21.212-50.85 21.213-21.15 51-21.15Q774-456 795-434.85q21 21.15 21 50.85v48h24q10.2 0 17.1 6.9 6.9 6.9 6.9 17.1v144q0 10.2-6.9 17.1-6.9 6.9-17.1 6.9H648Zm60-192h72v-48q0-15.3-10.289-25.65-10.29-10.35-25.5-10.35Q729-420 718.5-409.65 708-399.3 708-384v48ZM480-144 0-624q94-90 217-141t263-51q140 0 263 51t217 141L814-477q-13-13-25.5-25T763-527l91-92q-81-61-176-93t-198-32q-103 0-198 32t-176 93l374 373 48-48 25.5 25.5L579-243l-99 99Zm0-351Z"/></svg>

After

Width:  |  Height:  |  Size: 637 B

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="20" viewBox="0 -960 960 960" width="20"><path d="M480-144 0-624q94-90 217-141t263-51q140 0 263 51t217 141L480-144ZM357-369q28-20 59.5-29t63.5-9q32 0 64 9t59 29l251-250q-81-61-176-93t-198-32q-103 0-198 32t-176 93l251 250Z"/></svg>

After

Width:  |  Height:  |  Size: 277 B

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="20" viewBox="0 -960 960 960" width="20"><path d="M480-144 0-624q94-90 217-141t263-51q140 0 263 51t217 141L850-514q-20-7-40-10.5t-42-3.5h-5l91-91q-81-61-176-93t-198-32q-103 0-198 32t-176 93l251 250q28-20 59.5-29t63.5-9q18 0 36.5 3t35.5 9q-12 24-18.5 50t-6.5 55q0 22 4 42.5t12 40.5l-63 63Zm192 0q-10 0-17-7t-7-17v-144q0-10 7-17t17-7h24v-48q0-30 21-51t51-21q30 0 51 21t21 51v48h24q10 0 17 7t7 17v144q0 10-7 17t-17 7H672Zm60-192h72v-48q0-15-10.5-25.5T768-420q-15 0-25.5 10.5T732-384v48Z"/></svg>

After

Width:  |  Height:  |  Size: 539 B

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="20" viewBox="0 -960 960 960" width="20"><path d="M480-144 0-624q94-90 217-141t263-51q140 0 263 51t217 141L480-144ZM305-420q38-29 82.5-44.5T480-480q48 0 92.5 15.5T655-420l199-199q-81-61-176-93t-198-32q-103 0-198 32t-176 93l199 199Z"/></svg>

After

Width:  |  Height:  |  Size: 287 B

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="20" viewBox="0 -960 960 960" width="20"><path d="M480-144 0-624q97-93 220.853-142.5Q344.706-816 480-816q135 0 259 49.5T960-624L850-514q-20-7-40.5-10.5T768-528h-5l91-91q-81.087-61.475-176.543-93.238Q582-744 480.164-744q-101.836 0-197.5 32T106-619l199 199q38-29 82.5-44.5T480-480q29.652 0 58.826 6T595-456q-31.901 32.539-49.951 75.77Q527-337 527-290q0 21 3.81 42.097Q534.619-226.806 543-207l-63 63Zm192 0q-10.2 0-17.1-6.9-6.9-6.9-6.9-17.1v-144q0-10.2 6.9-17.1 6.9-6.9 17.1-6.9h24v-48q0-29.7 21.212-50.85 21.213-21.15 51-21.15Q798-456 819-434.85q21 21.15 21 50.85v48h24q10.2 0 17.1 6.9 6.9 6.9 6.9 17.1v144q0 10.2-6.9 17.1-6.9 6.9-17.1 6.9H672Zm60-192h72v-48q0-15.3-10.289-25.65-10.29-10.35-25.5-10.35Q753-420 742.5-409.65 732-399.3 732-384v48Z"/></svg>

After

Width:  |  Height:  |  Size: 798 B

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="20" viewBox="0 -960 960 960" width="20"><path d="M480-144 0-624q94-90 217-141t263-51q140 0 263 51t217 141L480-144ZM254-471q49-40 106-60.5T480-552q63 0 120 20.5T706-471l148-148q-81-61-176-93t-198-32q-103 0-198 32t-176 93l148 148Z"/></svg>

After

Width:  |  Height:  |  Size: 285 B

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="20" viewBox="0 -960 960 960" width="20"><path d="M480-144 0-624q94-90 217-141t263-51q140 0 263 51t217 141L850-514q-20-7-40-10.5t-42-3.5h-5l91-91q-81-61-175.764-93Q583.472-744 480-744t-198.236 32Q187-680 106-619l148 148q49-40 106.185-60.5Q417.369-552 480-552q48.3 0 93.15 12.5Q618-527 660-503q-60 29-96.5 86T527-290.355q0 22.355 4 42.855 4 20.5 12 40.5l-63 63Zm192 0q-10.2 0-17.1-6.9-6.9-6.9-6.9-17.1v-144q0-10.2 6.9-17.1 6.9-6.9 17.1-6.9h24v-48q0-29.7 21.212-50.85 21.213-21.15 51-21.15Q798-456 819-434.85q21 21.15 21 50.85v48h24q10.2 0 17.1 6.9 6.9 6.9 6.9 17.1v144q0 10.2-6.9 17.1-6.9 6.9-17.1 6.9H672Zm60-192h72v-48q0-15.3-10.289-25.65-10.29-10.35-25.5-10.35Q753-420 742.5-409.65 732-399.3 732-384v48Z"/></svg>

After

Width:  |  Height:  |  Size: 761 B

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="20" viewBox="0 -960 960 960" width="20"><path d="M480-144 0-624q97-93 221-142.5T480-816q135 0 259 49.5T960-624L480-144ZM165-561q66-56 147-83.5T480-672q87 0 168 27.5T795-561l59-58q-81-61-176.5-93T480-744q-102 0-197.5 32T106-619l59 58Z"/></svg>

After

Width:  |  Height:  |  Size: 290 B

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="20" viewBox="0 -960 960 960" width="20"><path d="M732-336h72v-48q0-15.3-10.289-25.65-10.29-10.35-25.5-10.35Q753-420 742.5-409.65 732-399.3 732-384v48ZM480-144 0-624q97-93 220.783-142.5 123.783-49.5 259-49.5Q615-816 739-766.5 863-717 960-624L849-513q-20-8-40.066-11.5Q788.868-528 768-528q-95 0-161.5 66.5T540-300q0 20.868 3.5 40.934T555-219l-75 75Zm192 0q-10.2 0-17.1-6.9-6.9-6.9-6.9-17.1v-144q0-10.2 6.9-17.1 6.9-6.9 17.1-6.9h24v-48q0-29.7 21.212-50.85 21.213-21.15 51-21.15Q798-456 819-434.85q21 21.15 21 50.85v48h24q10.2 0 17.1 6.9 6.9 6.9 6.9 17.1v144q0 10.2-6.9 17.1-6.9 6.9-17.1 6.9H672ZM165-561q66-56 147-83.5T480-672q87 0 168 27.5T795-561l59-58q-81.087-61.475-176.543-93.238Q582-744 480.164-744q-101.836 0-197.5 32T106-619l59 58Z"/></svg>

After

Width:  |  Height:  |  Size: 793 B

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="20" viewBox="0 -960 960 960" width="20"><path d="M480-144 0-624q94-90 217-141t263-51q140 0 263 51t217 141L480-144Z"/></svg>

After

Width:  |  Height:  |  Size: 171 B

View File

@ -13,7 +13,7 @@ LIST_DEF(PubSubSubscriptionList, PubSubSubscription, M_POD_OPLIST);
struct PubSub {
PubSubSubscriptionList_t items;
Mutex* mutex;
Mutex mutex;
};
PubSub* tt_pubsub_alloc() {

View File

@ -80,7 +80,6 @@ static void tt_thread_body(void* context) {
tt_thread_set_state(thread, ThreadStateRunning);
thread->ret = thread->callback(thread->context);
TT_LOG_I(TAG, "thread returned: %s", thread->name ?: "[no name]");
tt_assert(thread->state == ThreadStateRunning);
@ -170,7 +169,7 @@ void tt_thread_mark_as_static(Thread* thread) {
thread->is_static = true;
}
bool tt_thread_mark_is_service(ThreadId thread_id) {
bool tt_thread_mark_is_static(ThreadId thread_id) {
TaskHandle_t hTask = (TaskHandle_t)thread_id;
assert(!TT_IS_IRQ_MODE() && (hTask != NULL));
Thread* thread = (Thread*)pvTaskGetThreadLocalStoragePointer(hTask, 0);

View File

@ -275,7 +275,12 @@ void tt_thread_resume(ThreadId thread_id);
*/
bool tt_thread_is_suspended(ThreadId thread_id);
bool tt_thread_mark_is_service(ThreadId thread_id);
/** Check if the thread was created with static memory
*
* @param thread_id thread id
* @return true if thread memory is static
*/
bool tt_thread_mark_is_static(ThreadId thread_id);
#ifdef __cplusplus
}

View File

@ -10,10 +10,10 @@ idf_component_register(
REQUIRES esp_wifi nvs_flash spiffs
)
set(ASSETS_SRC_DIR "${CMAKE_CURRENT_SOURCE_DIR}/assets")
set(ASSETS_SRC_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../data/assets")
spiffs_create_partition_image(assets ${ASSETS_SRC_DIR} FLASH_IN_PROJECT)
set(CONFIG_SRC_DIR "${CMAKE_CURRENT_SOURCE_DIR}/config")
set(CONFIG_SRC_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../data/config")
spiffs_create_partition_image(config ${CONFIG_SRC_DIR} FLASH_IN_PROJECT)
target_link_libraries(${COMPONENT_LIB} ${IDF_TARGET_NAME} tactility)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 274 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 224 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 312 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 309 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 336 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 307 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 344 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 312 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 353 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 370 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 220 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 224 B

View File

@ -26,30 +26,6 @@ static void on_disconnect_pressed(lv_event_t* event) {
// region Secondary updates
static const char* get_network_icon(int8_t rssi, wifi_auth_mode_t auth_mode) {
if (rssi > -67) {
if (auth_mode == WIFI_AUTH_OPEN)
return "A:/assets/network_wifi.png";
else
return "A:/assets/network_wifi_locked.png";
} else if (rssi > -70) {
if (auth_mode == WIFI_AUTH_OPEN)
return "A:/assets/network_wifi_3_bar.png";
else
return "A:/assets/network_wifi_3_bar_locked.png";
} else if (rssi > -80) {
if (auth_mode == WIFI_AUTH_OPEN)
return "A:/assets/network_wifi_2_bar.png";
else
return "A:/assets/network_wifi_2_bar_locked.png";
} else {
if (auth_mode == WIFI_AUTH_OPEN)
return "A:/assets/network_wifi_1_bar.png";
else
return "A:/assets/network_wifi_1_bar_locked.png";
}
}
static void connect(lv_event_t* event) {
lv_obj_t* button = event->current_target;
// Assumes that the second child of the button is a label ... risky
@ -64,7 +40,7 @@ static void connect(lv_event_t* event) {
static void create_network_button(WifiManageView* view, WifiManageBindings* bindings, WifiApRecord* record) {
const char* ssid = (const char*)record->ssid;
const char* icon = get_network_icon(record->rssi, record->auth_mode);
const char* icon = wifi_get_status_icon_for_rssi(record->rssi, record->auth_mode != WIFI_AUTH_OPEN);
lv_obj_t* ap_button = lv_list_add_btn(
view->networks_list,
icon,

View File

@ -1,5 +1,6 @@
#include "wifi.h"
#include "assets.h"
#include "check.h"
#include "freertos/FreeRTOS.h"
#include "freertos/event_groups.h"
@ -33,10 +34,12 @@ typedef struct {
uint16_t scan_list_limit;
int8_t statusbar_icon_id;
bool scan_active;
bool secure_connection;
esp_event_handler_instance_t event_handler_any_id;
esp_event_handler_instance_t event_handler_got_ip;
EventGroupHandle_t event_group;
WifiRadioState radio_state;
const char* _Nullable last_statusbar_icon;
} Wifi;
typedef enum {
@ -86,7 +89,9 @@ static Wifi* wifi_alloc() {
instance->event_handler_got_ip = NULL;
instance->event_group = xEventGroupCreate();
instance->radio_state = WIFI_RADIO_OFF;
instance->statusbar_icon_id = tt_statusbar_icon_add("A:/assets/ic_small_wifi_off.png");
instance->statusbar_icon_id = tt_statusbar_icon_add(TT_ASSETS_ICON_WIFI_OFF);
instance->last_statusbar_icon = NULL;
instance->secure_connection = false;
return instance;
}
@ -225,35 +230,6 @@ static void wifi_scan_list_free_safely(Wifi* wifi) {
static void wifi_publish_event_simple(Wifi* wifi, WifiEventType type) {
WifiEvent turning_on_event = {.type = type};
switch (type) {
case WifiEventTypeRadioStateOn:
break;
case WifiEventTypeRadioStateOnPending:
break;
case WifiEventTypeRadioStateOff:
tt_statusbar_icon_set_image(wifi->statusbar_icon_id, "A:/assets/ic_small_wifi_off.png");
break;
case WifiEventTypeRadioStateOffPending:
break;
case WifiEventTypeScanStarted:
break;
case WifiEventTypeScanFinished:
break;
case WifiEventTypeDisconnected:
tt_statusbar_icon_set_image(wifi->statusbar_icon_id, "A:/assets/ic_small_wifi_off.png");
break;
case WifiEventTypeConnectionPending:
tt_statusbar_icon_set_image(wifi->statusbar_icon_id, "A:/assets/network_wifi_1_bar.png");
break;
case WifiEventTypeConnectionSuccess:
// TODO: update with actual bars
tt_statusbar_icon_set_image(wifi->statusbar_icon_id, "A:/assets/network_wifi.png");
break;
case WifiEventTypeConnectionFailed:
tt_statusbar_icon_set_image(wifi->statusbar_icon_id, "A:/assets/ic_small_wifi_off.png");
break;
}
tt_pubsub_publish(wifi->pubsub, &turning_on_event);
}
@ -266,6 +242,8 @@ static void event_handler(TT_UNUSED void* arg, esp_event_base_t event_base, int3
} else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED) {
xEventGroupSetBits(wifi_singleton->event_group, WIFI_FAIL_BIT);
TT_LOG_I(TAG, "event_handler: disconnected");
wifi_singleton->radio_state = WIFI_RADIO_ON;
wifi_publish_event_simple(wifi_singleton, WifiEventTypeDisconnected);
} else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP) {
ip_event_got_ip_t* event = (ip_event_got_ip_t*)event_data;
TT_LOG_I(TAG, "event_handler: got ip:" IPSTR, IP2STR(&event->ip_info.ip));
@ -444,6 +422,47 @@ static void wifi_scan_internal(Wifi* wifi) {
TT_LOG_I(TAG, "Finished scan");
}
const char* wifi_get_status_icon_for_rssi(int rssi, bool secured) {
if (rssi > -67) {
return secured ? TT_ASSETS_ICON_WIFI_SIGNAL_4_LOCKED : TT_ASSETS_ICON_WIFI_SIGNAL_4;
} else if (rssi > -70) {
return secured ? TT_ASSETS_ICON_WIFI_SIGNAL_3_LOCKED : TT_ASSETS_ICON_WIFI_SIGNAL_3;
} else if (rssi > -80) {
return secured ? TT_ASSETS_ICON_WIFI_SIGNAL_2_LOCKED : TT_ASSETS_ICON_WIFI_SIGNAL_2;
} else {
return secured ? TT_ASSETS_ICON_WIFI_SIGNAL_1_LOCKED : TT_ASSETS_ICON_WIFI_SIGNAL_1;
}
}
static const char* wifi_get_status_icon(WifiRadioState state, bool secure) {
static int rssi = 0;
switch (state) {
case WIFI_RADIO_ON_PENDING:
case WIFI_RADIO_ON:
case WIFI_RADIO_OFF_PENDING:
case WIFI_RADIO_OFF:
return TT_ASSETS_ICON_WIFI_OFF;
case WIFI_RADIO_CONNECTION_PENDING:
return TT_ASSETS_ICON_WIFI_FIND;
case WIFI_RADIO_CONNECTION_ACTIVE:
if (esp_wifi_sta_get_rssi(&rssi) == ESP_OK) {
return wifi_get_status_icon_for_rssi(rssi, secure);
} else {
return secure ? TT_ASSETS_ICON_WIFI_SIGNAL_0_LOCKED : TT_ASSETS_ICON_WIFI_SIGNAL_0;
}
default:
tt_crash_implementation("not implemented");
}
}
static void wifi_update_statusbar(Wifi* wifi) {
const char* icon = wifi_get_status_icon(wifi->radio_state, wifi->secure_connection);
if (icon != wifi->last_statusbar_icon) {
tt_statusbar_icon_set_image(wifi->statusbar_icon_id, icon);
wifi->last_statusbar_icon = icon;
}
}
static void wifi_connect_internal(Wifi* wifi, WifiConnectMessage* connect_message) {
// TODO: only when connected!
wifi_disconnect_internal(wifi);
@ -467,6 +486,8 @@ static void wifi_connect_internal(Wifi* wifi, WifiConnectMessage* connect_messag
memcpy(wifi_config.sta.ssid, connect_message->ssid, 32);
memcpy(wifi_config.sta.password, connect_message->password, 64);
wifi->secure_connection = (wifi_config.sta.password[0] != 0x00);
esp_err_t set_config_result = esp_wifi_set_config(WIFI_IF_STA, &wifi_config);
if (set_config_result != ESP_OK) {
wifi->radio_state = WIFI_RADIO_ON;
@ -589,6 +610,8 @@ _Noreturn int32_t wifi_main(TT_UNUSED void* parameter) {
break;
}
}
wifi_update_statusbar(wifi);
}
}

View File

@ -62,6 +62,9 @@ WifiRadioState wifi_get_radio_state();
*/
void wifi_scan();
/**
* @return true if wifi is actively scanning
*/
bool wifi_is_scanning();
/**
@ -96,6 +99,14 @@ void wifi_connect(const char* ssid, const char _Nullable password[64]);
*/
void wifi_disconnect();
/**
* Return the relevant icon asset from assets.h for the given inputs
* @param rssi the rssi value
* @param secured whether the access point is a secured one (as in: not an open one)
* @return
*/
const char* wifi_get_status_icon_for_rssi(int rssi, bool secured);
#ifdef __cplusplus
}
#endif

View File

@ -1,7 +1,10 @@
#include "app_i.h"
#include "log.h"
#include <stdlib.h>
#define TAG "app"
static AppFlags tt_app_get_flags_default(AppType type);
static const AppFlags DEFAULT_FLAGS = {
@ -11,6 +14,11 @@ static const AppFlags DEFAULT_FLAGS = {
// region Alloc/free
App tt_app_alloc(const AppManifest* manifest, Bundle* _Nullable parameters) {
#ifdef ESP_PLATFORM
size_t memory_before = heap_caps_get_free_size(MALLOC_CAP_INTERNAL);
#else
size_t memory_before = 0;
#endif
AppData* data = malloc(sizeof(AppData));
*data = (AppData) {
.mutex = tt_mutex_alloc(MutexTypeNormal),
@ -18,6 +26,7 @@ App tt_app_alloc(const AppManifest* manifest, Bundle* _Nullable parameters) {
.flags = tt_app_get_flags_default(manifest->type),
.manifest = manifest,
.parameters = parameters,
.memory = memory_before,
.data = NULL
};
return (App*)data;
@ -25,11 +34,25 @@ App tt_app_alloc(const AppManifest* manifest, Bundle* _Nullable parameters) {
void tt_app_free(App app) {
AppData* data = (AppData*)app;
size_t memory_before = data->memory;
if (data->parameters) {
tt_bundle_free(data->parameters);
}
tt_mutex_free(data->mutex);
free(data);
#ifdef ESP_PLATFORM
size_t memory_after = heap_caps_get_free_size(MALLOC_CAP_INTERNAL);
#else
size_t memory_after = 0;
#endif
if (memory_after < memory_before) {
TT_LOG_W(TAG, "Potential memory leak: gained %u bytes after closing app", memory_before - memory_after);
TT_LOG_W(TAG, "Note that WiFi service frees up memory asynchronously and that the leak can be caused by an app that was launched by this app.");
}
}
// endregion

View File

@ -14,6 +14,8 @@ typedef struct {
Mutex mutex;
const AppManifest* manifest;
AppState state;
/** @brief Memory marker at start of app, to detect memory leaks */
size_t memory;
AppFlags flags;
/** @brief Optional parameters to start the app with
* When these are stored in the app struct, the struct takes ownership.

22
tactility/src/assets.h Normal file
View File

@ -0,0 +1,22 @@
#pragma once
#define TT_ASSET_FOLDER "A:/assets/"
#define TT_ASSET(file) TT_ASSET_FOLDER file
#define TT_ASSETS_ICON_SDCARD TT_ASSET("sdcard.png")
#define TT_ASSETS_ICON_SDCARD_ALERT TT_ASSET("sdcard_alert.png")
#define TT_ASSETS_ICON_WIFI_CONNECTION_ISSUE TT_ASSET("wifi_connection_issue.png")
#define TT_ASSETS_ICON_WIFI_FIND TT_ASSET("wifi_find.png")
#define TT_ASSETS_ICON_WIFI_OFF TT_ASSET("wifi_off.png")
#define TT_ASSETS_ICON_WIFI_PERM_SCAN TT_ASSET("wifi_perm_scan.png")
#define TT_ASSETS_ICON_WIFI_SIGNAL_0 TT_ASSET("wifi_signal_0.png")
#define TT_ASSETS_ICON_WIFI_SIGNAL_0_LOCKED TT_ASSET("wifi_signal_0_locked.png")
#define TT_ASSETS_ICON_WIFI_SIGNAL_1 TT_ASSET("wifi_signal_1.png")
#define TT_ASSETS_ICON_WIFI_SIGNAL_1_LOCKED TT_ASSET("wifi_signal_1_locked.png")
#define TT_ASSETS_ICON_WIFI_SIGNAL_2 TT_ASSET("wifi_signal_2.png")
#define TT_ASSETS_ICON_WIFI_SIGNAL_2_LOCKED TT_ASSET("wifi_signal_2_locked.png")
#define TT_ASSETS_ICON_WIFI_SIGNAL_3 TT_ASSET("wifi_signal_3.png")
#define TT_ASSETS_ICON_WIFI_SIGNAL_3_LOCKED TT_ASSET("wifi_signal_3_locked.png")
#define TT_ASSETS_ICON_WIFI_SIGNAL_4 TT_ASSET("wifi_signal_4.png")
#define TT_ASSETS_ICON_WIFI_SIGNAL_4_LOCKED TT_ASSET("wifi_signal_4_locked.png")

View File

@ -1,4 +1,5 @@
#include "hardware_i.h"
#include "sdcard_i.h"
#define TAG "hardware"
@ -12,6 +13,7 @@ void tt_hardware_init(const HardwareConfig* config) {
tt_check(config->bootstrap(), "bootstrap failed");
}
tt_sdcard_init();
if (config->sdcard != NULL) {
TT_LOG_I(TAG, "Mounting sdcard");
tt_sdcard_mount(config->sdcard);

View File

@ -2,12 +2,10 @@
#include "mutex.h"
#include "tactility_core.h"
#include "ui/statusbar.h"
#define TAG "sdcard"
static Mutex* mutex = NULL;
static int8_t statusbar_icon = -1;
static Mutex mutex = NULL;
typedef struct {
const SdCard* sdcard;
@ -19,15 +17,13 @@ static MountData data = {
.context = NULL
};
static void sdcard_ensure_initialized() {
void tt_sdcard_init() {
if (mutex == NULL) {
mutex = tt_mutex_alloc(MutexTypeRecursive);
statusbar_icon = tt_statusbar_icon_add("A:/assets/sdcard_unmounted.png");
}
}
static bool sdcard_lock(uint32_t timeout_ticks) {
sdcard_ensure_initialized();
return tt_mutex_acquire(mutex, timeout_ticks) == TtStatusOk;
}
@ -51,7 +47,6 @@ bool tt_sdcard_mount(const SdCard* sdcard) {
};
sdcard_unlock();
if (data.context != NULL) {
tt_statusbar_icon_set_image(statusbar_icon, "A:/assets/sdcard_mounted.png");
return true;
} else {
return false;
@ -62,8 +57,19 @@ bool tt_sdcard_mount(const SdCard* sdcard) {
}
}
bool tt_sdcard_is_mounted() {
return data.context != NULL;
SdcardState tt_sdcard_get_state() {
if (data.context == NULL) {
return SDCARD_STATE_UNMOUNTED;
} else {
// TODO: Side-effects are not great - consider refactoring this, so:
// Consider making tt_sdcard_get_status() that can return an error state
// The sdcard service can then auto-dismount
if (data.sdcard->is_mounted(data.context)) {
return SDCARD_STATE_MOUNTED;
} else {
return SDCARD_STATE_ERROR;
}
}
}
bool tt_sdcard_unmount(uint32_t timeout_ticks) {
@ -78,7 +84,6 @@ bool tt_sdcard_unmount(uint32_t timeout_ticks) {
.sdcard = NULL
};
result = true;
tt_statusbar_icon_set_image(statusbar_icon, "A:/assets/sdcard_unmounted.png");
} else {
TT_LOG_E(TAG, "Can't unmount: nothing mounted");
}

View File

@ -9,7 +9,14 @@ extern "C" {
#endif
typedef void* (*SdcardMount)(const char* mount_path);
typedef void (*SdcardUnmount)(void*);
typedef void (*SdcardUnmount)(void* context);
typedef bool (*SdcardIsMounted)(void* context);
typedef enum {
SDCARD_STATE_MOUNTED,
SDCARD_STATE_UNMOUNTED,
SDCARD_STATE_ERROR,
} SdcardState;
typedef enum {
SDCARD_MOUNT_BEHAVIOUR_AT_BOOT, /** Only mount at boot */
@ -19,11 +26,12 @@ typedef enum {
typedef struct {
SdcardMount mount;
SdcardUnmount unmount;
SdcardIsMounted is_mounted;
SdcardMountBehaviour mount_behaviour;
} SdCard;
bool tt_sdcard_mount(const SdCard* sdcard);
bool tt_sdcard_is_mounted();
SdcardState tt_sdcard_get_state();
bool tt_sdcard_unmount();
#ifdef __cplusplus

11
tactility/src/sdcard_i.h Normal file
View File

@ -0,0 +1,11 @@
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
void tt_sdcard_init();
#ifdef __cplusplus
}
#endif

View File

@ -0,0 +1,117 @@
#include <dirent.h>
#include "assets.h"
#include "mutex.h"
#include "service.h"
#include "tactility.h"
#include "tactility_core.h"
#include "ui/statusbar.h"
#define TAG "sdcard_service"
static int32_t sdcard_task(TT_UNUSED void* context);
typedef struct {
Mutex mutex;
Thread* thread;
SdcardState last_state;
int8_t statusbar_icon_id;
bool interrupted;
} ServiceData;
static ServiceData* service_data_alloc() {
ServiceData* data = malloc(sizeof(ServiceData));
*data = (ServiceData) {
.mutex = tt_mutex_alloc(MutexTypeNormal),
.thread = tt_thread_alloc_ex(
"sdcard",
3000, // Minimum is ~2800 @ ESP-IDF 5.1.2 when ejecting sdcard
&sdcard_task,
data
),
.last_state = -1,
.statusbar_icon_id = tt_statusbar_icon_add(NULL),
.interrupted = false
};
return data;
}
static void service_data_free(ServiceData* data) {
tt_mutex_free(data->mutex);
tt_statusbar_icon_remove(data->statusbar_icon_id);
tt_thread_free(data->thread);
}
static void service_data_lock(ServiceData* data) {
tt_check(tt_mutex_acquire(data->mutex, TtWaitForever) == TtStatusOk);
}
static void service_data_unlock(ServiceData* data) {
tt_check(tt_mutex_release(data->mutex) == TtStatusOk);
}
static int32_t sdcard_task(void* context) {
ServiceData* data = (ServiceData*)context;
bool interrupted = false;
// We set NULL as statusbar image by default, so it's hidden by default
tt_statusbar_icon_set_visibility(data->statusbar_icon_id, true);
do {
service_data_lock(data);
interrupted = data->interrupted;
SdcardState new_state = tt_sdcard_get_state();
if (new_state == SDCARD_STATE_ERROR) {
TT_LOG_W(TAG, "Sdcard error - unmounting. Did you eject the card in an unsafe manner?");
tt_sdcard_unmount();
}
if (new_state != data->last_state) {
TT_LOG_I(TAG, "State change %d -> %d", data->last_state, new_state);
if (new_state == SDCARD_STATE_MOUNTED) {
tt_statusbar_icon_set_image(data->statusbar_icon_id, TT_ASSETS_ICON_SDCARD);
} else {
tt_statusbar_icon_set_image(data->statusbar_icon_id, TT_ASSETS_ICON_SDCARD_ALERT);
}
data->last_state = new_state;
}
service_data_unlock(data);
tt_delay_ms(2000);
} while (!interrupted);
return 0;
}
static void on_start(Service service) {
if (tt_get_config()->hardware->sdcard != NULL) {
ServiceData* data = service_data_alloc();
tt_service_set_data(service, data);
tt_thread_start(data->thread);
} else {
TT_LOG_I(TAG, "task not started due to config");
}
}
static void on_stop(Service service) {
ServiceData* data = tt_service_get_data(service);
if (data != NULL) {
service_data_lock(data);
data->interrupted = true;
service_data_unlock(data);
tt_thread_join(data->thread);
service_data_free(data);
}
}
const ServiceManifest sdcard_service = {
.id = "sdcard",
.on_start = &on_start,
.on_stop = &on_stop
};

View File

@ -14,10 +14,12 @@ static const Config* config_instance = NULL;
extern const ServiceManifest gui_service;
extern const ServiceManifest loader_service;
extern const ServiceManifest sdcard_service;
static const ServiceManifest* const system_services[] = {
&gui_service,
&loader_service // depends on gui service
&loader_service, // depends on gui service
&sdcard_service
};
// endregion
@ -87,6 +89,9 @@ static void register_and_start_user_services(const ServiceManifest* const servic
TT_UNUSED void tt_init(const Config* config) {
TT_LOG_I(TAG, "tt_init started");
// Assign early so starting services can use it
config_instance = config;
tt_service_registry_init();
tt_app_manifest_registry_init();
@ -122,8 +127,6 @@ TT_UNUSED void tt_init(const Config* config) {
}
TT_LOG_I(TAG, "tt_init complete");
config_instance = config;
}
const Config* _Nullable tt_get_config() {

View File

@ -18,7 +18,7 @@ typedef struct {
} StatusbarIcon;
typedef struct {
Mutex* mutex;
Mutex mutex;
PubSub* pubsub;
StatusbarIcon icons[STATUSBAR_ICON_LIMIT];
} StatusbarData;
@ -166,13 +166,13 @@ static void statusbar_event(TT_UNUSED const lv_obj_class_t* class_p, lv_event_t*
}
}
int8_t tt_statusbar_icon_add(const char* image) {
int8_t tt_statusbar_icon_add(const char* _Nullable image) {
statusbar_lock();
int8_t result = -1;
for (int8_t i = 0; i < STATUSBAR_ICON_LIMIT; ++i) {
if (!statusbar_data.icons[i].claimed) {
statusbar_data.icons[i].claimed = true;
statusbar_data.icons[i].visible = true;
statusbar_data.icons[i].visible = (image != NULL);
statusbar_data.icons[i].image = image;
result = i;
TT_LOG_I(TAG, "id %d: added", i);

View File

@ -12,7 +12,7 @@ extern "C" {
#define STATUSBAR_HEIGHT (STATUSBAR_ICON_SIZE + 4) // 4 extra pixels for border and outline
lv_obj_t* tt_statusbar_create(lv_obj_t* parent);
int8_t tt_statusbar_icon_add(const char* image);
int8_t tt_statusbar_icon_add(const char* _Nullable image);
void tt_statusbar_icon_remove(int8_t id);
void tt_statusbar_icon_set_image(int8_t id, const char* image);
void tt_statusbar_icon_set_visibility(int8_t id, bool visible);