From 50bd6e8bf6c885ac00e50363be68f69f36e45a33 Mon Sep 17 00:00:00 2001 From: Ken Van Hoeylandt Date: Fri, 27 Dec 2024 22:12:39 +0000 Subject: [PATCH] Merge develop into main branch (#137) * SdCard HAL refactored (#135) - Refactor SdCard HAL - introduce Lockable * Screenshot and FatFS improvements (#136) - Fix screenshots on ESP32 - Improve Screenshot service - Convert Screenshot app to class-based instead of structs - Screenshot app now automatically updates when task is finished - Enable FatFS long filename support * Re-use common log messages (#138) For consistency and binary size reduction * Toolbar spinner should get margin to the right * More TactilityC features (#139) * Rewrote Loader - Simplified Loader by removing custom threa - Created DispatcherThread - Move auto-starting apps to Boot app - Fixed Dispatcher bug where it could get stuck not processing new messages * Hide AP settings if the AP is not saved * Missing from previous commit * Replace LV_EVENT_CLICKED with LV_EVENT_SHORT_CLICKED * Refactored files app and created InputDialog (#140) - Changed Files app so that it has a View and State - Files app now allows for long-pressing on files to perform actions - Files app now has rename and delete actions - Created InputDialog app - Improved AlertDialog app layout --- App/Source/Main.cpp | 2 +- Boards/LilygoTdeck/CMakeLists.txt | 2 +- Boards/LilygoTdeck/Source/InitHardware.cpp | 25 +- Boards/LilygoTdeck/Source/LilygoTdeck.cpp | 6 +- Boards/LilygoTdeck/Source/PowerOn.cpp | 4 +- Boards/LilygoTdeck/Source/Sdcard.cpp | 166 --------- Boards/LilygoTdeck/Source/hal/TdeckSdCard.cpp | 34 ++ Boards/LilygoTdeck/Source/hal/TdeckSdCard.h | 7 + Boards/M5stackCore2/Source/M5stackCore2.cpp | 3 +- Boards/M5stackCoreS3/Source/M5stackCoreS3.cpp | 3 +- Boards/M5stackShared/Source/M5stackShared.h | 5 +- Boards/M5stackShared/Source/Sdcard.cpp | 137 -------- .../Source/hal/M5stackSdCard.cpp | 31 ++ .../M5stackShared/Source/hal/M5stackSdCard.h | 7 + Boards/Simulator/Source/hal/Configuration.cpp | 5 +- Boards/Simulator/Source/hal/SimulatorSdCard.h | 26 ++ .../Simulator/Source/hal/SimulatorSdcard.cpp | 26 -- Boards/YellowBoard/Source/Boot.cpp | 69 ++-- Boards/YellowBoard/Source/Sdcard.cpp | 79 ----- Boards/YellowBoard/Source/YellowBoard.cpp | 5 +- .../Source/{Config.h => YellowConfig.h} | 9 - .../YellowBoard/Source/hal/YellowSdCard.cpp | 31 ++ Boards/YellowBoard/Source/hal/YellowSdCard.h | 8 + Buildscripts/release-sdk.sh | 1 + Documentation/ideas.md | 12 +- ExternalApps/HelloWorld/main/Source/main.c | 7 +- Libraries/lv_screenshot/CMakeLists.txt | 15 +- .../{private => Private}/save_bmp.h | 0 .../{private => Private}/save_png.h | 0 .../{src => Source}/lv_screenshot.c | 33 +- .../{src => Source}/lv_screenshot.h | 4 +- Libraries/lv_screenshot/Source/save_bmp.c | 96 +++++ .../lv_screenshot/{src => Source}/save_png.c | 8 +- Tactility/Private/service/loader/Loader_i.h | 84 +---- Tactility/Source/Tactility.cpp | 32 +- Tactility/Source/TactilityConfig.h | 10 + Tactility/Source/app/ElfApp.cpp | 6 +- Tactility/Source/app/ElfApp.h | 2 +- .../Source/app/alertdialog/AlertDialog.cpp | 19 +- Tactility/Source/app/applist/AppList.cpp | 9 +- Tactility/Source/app/boot/Boot.cpp | 35 +- .../app/crashdiagnostics/CrashDiagnostics.cpp | 2 +- Tactility/Source/app/desktop/Desktop.cpp | 2 +- Tactility/Source/app/files/FileUtils.cpp | 130 +++---- Tactility/Source/app/files/FileUtils.h | 26 +- Tactility/Source/app/files/Files.cpp | 242 +------------ Tactility/Source/app/files/Files.h | 33 ++ Tactility/Source/app/files/FilesData.cpp | 96 ----- Tactility/Source/app/files/FilesData.h | 36 -- Tactility/Source/app/files/State.cpp | 106 ++++++ Tactility/Source/app/files/State.h | 74 ++++ Tactility/Source/app/files/View.cpp | 329 ++++++++++++++++++ Tactility/Source/app/files/View.h | 40 +++ .../Source/app/i2cscanner/I2cScanner.cpp | 8 +- .../app/i2cscanner/I2cScannerThread.cpp | 12 +- .../Source/app/inputdialog/InputDialog.cpp | 120 +++++++ .../Source/app/inputdialog/InputDialog.h | 20 ++ .../Source/app/screenshot/Screenshot.cpp | 12 +- .../Source/app/screenshot/ScreenshotUi.cpp | 214 ++++++++---- .../Source/app/screenshot/ScreenshotUi.h | 40 ++- .../app/selectiondialog/SelectionDialog.cpp | 19 +- Tactility/Source/app/settings/Settings.cpp | 9 +- .../app/wifiapsettings/WifiApSettings.cpp | 7 +- Tactility/Source/app/wificonnect/View.cpp | 2 +- .../Source/app/wificonnect/WifiConnect.cpp | 2 +- Tactility/Source/app/wifimanage/View.cpp | 4 +- .../Source/app/wifimanage/WifiManage.cpp | 2 +- Tactility/Source/lvgl/LvglSync.cpp | 20 ++ Tactility/Source/lvgl/LvglSync.h | 6 +- Tactility/Source/lvgl/Toolbar.cpp | 9 +- Tactility/Source/service/gui/GuiDraw.cpp | 2 +- Tactility/Source/service/loader/Loader.cpp | 180 ++++------ Tactility/Source/service/loader/Loader.h | 3 +- .../Source/service/screenshot/Screenshot.cpp | 130 +++---- .../Source/service/screenshot/Screenshot.h | 33 +- .../service/screenshot/ScreenshotTask.cpp | 236 ++++++------- .../service/screenshot/ScreenshotTask.h | 77 ++-- .../Source/service/statusbar/Statusbar.cpp | 29 +- TactilityC/Source/tt_app_alertdialog.cpp | 19 + TactilityC/Source/tt_app_alertdialog.h | 16 + TactilityC/Source/tt_app_context.cpp | 36 ++ TactilityC/Source/tt_app_context.h | 19 + .../app/App.cpp => tt_app_manifest.cpp} | 15 +- .../app/App.h => tt_app_manifest.h} | 8 +- ...nDialog.cpp => tt_app_selectiondialog.cpp} | 7 +- ...ctionDialog.h => tt_app_selectiondialog.h} | 4 + TactilityC/Source/tt_bundle.cpp | 51 +++ TactilityC/Source/tt_bundle.h | 29 ++ .../TactilityC.cpp => tt_init.cpp} | 75 +++- .../{TactilityC/TactilityC.h => tt_init.h} | 0 .../lvgl/Spinner.cpp => tt_lvgl_spinner.cpp} | 2 +- .../lvgl/Spinner.h => tt_lvgl_spinner.h} | 2 +- .../lvgl/Toolbar.cpp => tt_lvgl_toolbar.cpp} | 4 +- .../lvgl/Toolbar.h => tt_lvgl_toolbar.h} | 4 +- TactilityC/Source/tt_message_queue.cpp | 40 +++ TactilityC/Source/tt_message_queue.h | 23 ++ TactilityC/Source/tt_mutex.cpp | 31 ++ TactilityC/Source/tt_mutex.h | 24 ++ TactilityC/Source/tt_semaphore.cpp | 28 ++ TactilityC/Source/tt_semaphore.h | 20 ++ TactilityC/Source/tt_service_loader.cpp | 20 ++ TactilityC/Source/tt_service_loader.h | 21 ++ TactilityC/Source/tt_thread.cpp | 78 +++++ TactilityC/Source/tt_thread.h | 74 ++++ TactilityC/Source/tt_timer.cpp | 65 ++++ TactilityC/Source/tt_timer.h | 37 ++ TactilityCore/Source/CoreTypes.h | 3 + TactilityCore/Source/Dispatcher.cpp | 66 +++- TactilityCore/Source/DispatcherThread.cpp | 46 +++ TactilityCore/Source/DispatcherThread.h | 34 ++ TactilityCore/Source/Lockable.cpp | 10 + TactilityCore/Source/Lockable.h | 41 +++ TactilityCore/Source/Log.h | 2 + TactilityCore/Source/LogMessages.h | 22 ++ TactilityCore/Source/MessageQueue.cpp | 108 ++---- TactilityCore/Source/MessageQueue.h | 72 ++-- TactilityCore/Source/Mutex.cpp | 5 - TactilityCore/Source/Mutex.h | 35 +- TactilityCore/Source/Semaphore.cpp | 31 +- TactilityCore/Source/Semaphore.h | 24 +- TactilityCore/Source/StreamBuffer.cpp | 8 +- TactilityCore/Source/StreamBuffer.h | 2 +- TactilityCore/Source/StringUtils.cpp | 10 +- TactilityCore/Source/StringUtils.h | 2 +- TactilityCore/Source/Timer.cpp | 39 +-- TactilityCore/Source/Timer.h | 19 +- TactilityCore/Source/file/File.cpp | 12 +- TactilityCore/Source/file/File.h | 4 +- TactilityHeadless/CMakeLists.txt | 2 +- TactilityHeadless/Source/hal/Configuration.h | 4 +- TactilityHeadless/Source/hal/Hal.cpp | 2 +- TactilityHeadless/Source/hal/SdCard.h | 38 ++ TactilityHeadless/Source/hal/SpiSdCard.cpp | 159 +++++++++ TactilityHeadless/Source/hal/SpiSdCard.h | 80 +++++ .../Source/hal/sdcard/Sdcard.cpp | 85 ----- TactilityHeadless/Source/hal/sdcard/Sdcard.h | 35 -- .../Source/service/sdcard/Sdcard.cpp | 21 +- .../Source/service/wifi/WifiEsp.cpp | 122 +++---- Tests/TactilityCore/MessageQueueTest.cpp | 2 +- sdkconfig.board.lilygo-tdeck | 3 + sdkconfig.board.m5stack-core2 | 2 + sdkconfig.board.m5stack-cores3 | 2 + sdkconfig.board.yellow-board | 2 + sdkconfig.defaults | 1 + 144 files changed, 3244 insertions(+), 2038 deletions(-) delete mode 100644 Boards/LilygoTdeck/Source/Sdcard.cpp create mode 100644 Boards/LilygoTdeck/Source/hal/TdeckSdCard.cpp create mode 100644 Boards/LilygoTdeck/Source/hal/TdeckSdCard.h delete mode 100644 Boards/M5stackShared/Source/Sdcard.cpp create mode 100644 Boards/M5stackShared/Source/hal/M5stackSdCard.cpp create mode 100644 Boards/M5stackShared/Source/hal/M5stackSdCard.h create mode 100644 Boards/Simulator/Source/hal/SimulatorSdCard.h delete mode 100644 Boards/Simulator/Source/hal/SimulatorSdcard.cpp delete mode 100644 Boards/YellowBoard/Source/Sdcard.cpp rename Boards/YellowBoard/Source/{Config.h => YellowConfig.h} (60%) create mode 100644 Boards/YellowBoard/Source/hal/YellowSdCard.cpp create mode 100644 Boards/YellowBoard/Source/hal/YellowSdCard.h rename Libraries/lv_screenshot/{private => Private}/save_bmp.h (100%) rename Libraries/lv_screenshot/{private => Private}/save_png.h (100%) rename Libraries/lv_screenshot/{src => Source}/lv_screenshot.c (70%) rename Libraries/lv_screenshot/{src => Source}/lv_screenshot.h (64%) create mode 100644 Libraries/lv_screenshot/Source/save_bmp.c rename Libraries/lv_screenshot/{src => Source}/save_png.c (56%) create mode 100644 Tactility/Source/app/files/Files.h delete mode 100644 Tactility/Source/app/files/FilesData.cpp delete mode 100644 Tactility/Source/app/files/FilesData.h create mode 100644 Tactility/Source/app/files/State.cpp create mode 100644 Tactility/Source/app/files/State.h create mode 100644 Tactility/Source/app/files/View.cpp create mode 100644 Tactility/Source/app/files/View.h create mode 100644 Tactility/Source/app/inputdialog/InputDialog.cpp create mode 100644 Tactility/Source/app/inputdialog/InputDialog.h create mode 100644 TactilityC/Source/tt_app_alertdialog.cpp create mode 100644 TactilityC/Source/tt_app_alertdialog.h create mode 100644 TactilityC/Source/tt_app_context.cpp create mode 100644 TactilityC/Source/tt_app_context.h rename TactilityC/Source/{TactilityC/app/App.cpp => tt_app_manifest.cpp} (94%) rename TactilityC/Source/{TactilityC/app/App.h => tt_app_manifest.h} (93%) rename TactilityC/Source/{TactilityC/app/SelectionDialog.cpp => tt_app_selectiondialog.cpp} (56%) rename TactilityC/Source/{TactilityC/app/SelectionDialog.h => tt_app_selectiondialog.h} (61%) create mode 100644 TactilityC/Source/tt_bundle.cpp create mode 100644 TactilityC/Source/tt_bundle.h rename TactilityC/Source/{TactilityC/TactilityC.cpp => tt_init.cpp} (65%) rename TactilityC/Source/{TactilityC/TactilityC.h => tt_init.h} (100%) rename TactilityC/Source/{TactilityC/lvgl/Spinner.cpp => tt_lvgl_spinner.cpp} (83%) rename TactilityC/Source/{TactilityC/lvgl/Spinner.h => tt_lvgl_spinner.h} (84%) rename TactilityC/Source/{TactilityC/lvgl/Toolbar.cpp => tt_lvgl_toolbar.cpp} (84%) rename TactilityC/Source/{TactilityC/lvgl/Toolbar.h => tt_lvgl_toolbar.h} (80%) create mode 100644 TactilityC/Source/tt_message_queue.cpp create mode 100644 TactilityC/Source/tt_message_queue.h create mode 100644 TactilityC/Source/tt_mutex.cpp create mode 100644 TactilityC/Source/tt_mutex.h create mode 100644 TactilityC/Source/tt_semaphore.cpp create mode 100644 TactilityC/Source/tt_semaphore.h create mode 100644 TactilityC/Source/tt_service_loader.cpp create mode 100644 TactilityC/Source/tt_service_loader.h create mode 100644 TactilityC/Source/tt_thread.cpp create mode 100644 TactilityC/Source/tt_thread.h create mode 100644 TactilityC/Source/tt_timer.cpp create mode 100644 TactilityC/Source/tt_timer.h create mode 100644 TactilityCore/Source/DispatcherThread.cpp create mode 100644 TactilityCore/Source/DispatcherThread.h create mode 100644 TactilityCore/Source/Lockable.cpp create mode 100644 TactilityCore/Source/Lockable.h create mode 100644 TactilityCore/Source/LogMessages.h create mode 100644 TactilityHeadless/Source/hal/SdCard.h create mode 100644 TactilityHeadless/Source/hal/SpiSdCard.cpp create mode 100644 TactilityHeadless/Source/hal/SpiSdCard.h delete mode 100644 TactilityHeadless/Source/hal/sdcard/Sdcard.cpp delete mode 100644 TactilityHeadless/Source/hal/sdcard/Sdcard.h diff --git a/App/Source/Main.cpp b/App/Source/Main.cpp index 74bf91fe..34486635 100644 --- a/App/Source/Main.cpp +++ b/App/Source/Main.cpp @@ -2,7 +2,7 @@ // Apps #include "Tactility.h" -#include "TactilityC/TactilityC.h" +#include "tt_init.h" namespace tt::service::wifi { extern void wifi_task(void*); diff --git a/Boards/LilygoTdeck/CMakeLists.txt b/Boards/LilygoTdeck/CMakeLists.txt index 05ae1a26..d0df65f2 100644 --- a/Boards/LilygoTdeck/CMakeLists.txt +++ b/Boards/LilygoTdeck/CMakeLists.txt @@ -1,5 +1,5 @@ idf_component_register( SRC_DIRS "Source" "Source/hal" INCLUDE_DIRS "Source" - REQUIRES Tactility esp_lvgl_port esp_lcd esp_lcd_touch_gt911 driver vfs fatfs esp_adc + REQUIRES Tactility esp_lvgl_port esp_lcd esp_lcd_touch_gt911 driver esp_adc ) diff --git a/Boards/LilygoTdeck/Source/InitHardware.cpp b/Boards/LilygoTdeck/Source/InitHardware.cpp index 292f114e..2725bed6 100644 --- a/Boards/LilygoTdeck/Source/InitHardware.cpp +++ b/Boards/LilygoTdeck/Source/InitHardware.cpp @@ -19,25 +19,32 @@ #define TDECK_LCD_BACKLIGHT_LEDC_FREQUENCY (4000) static bool init_spi() { + TT_LOG_I(TAG, LOG_MESSAGE_SPI_INIT_START_FMT, TDECK_SPI_HOST); + spi_bus_config_t bus_config = { .mosi_io_num = TDECK_SPI_PIN_MOSI, .miso_io_num = TDECK_SPI_PIN_MISO, .sclk_io_num = TDECK_SPI_PIN_SCLK, .quadwp_io_num = -1, // Quad SPI LCD driver is not yet supported .quadhd_io_num = -1, // Quad SPI LCD driver is not yet supported + .data4_io_num = 0, + .data5_io_num = 0, + .data6_io_num = 0, + .data7_io_num = 0, .max_transfer_sz = TDECK_SPI_TRANSFER_SIZE_LIMIT, + .flags = 0, + .isr_cpu_id = ESP_INTR_CPU_AFFINITY_AUTO, + .intr_flags = 0 }; - return spi_bus_initialize(TDECK_SPI_HOST, &bus_config, SPI_DMA_CH_AUTO) == ESP_OK; -} - -bool tdeck_init_hardware() { - TT_LOG_I(TAG, "Init SPI"); - - if (!init_spi()) { - TT_LOG_E(TAG, "Init SPI failed"); + if (spi_bus_initialize(TDECK_SPI_HOST, &bus_config, SPI_DMA_CH_AUTO) != ESP_OK) { + TT_LOG_E(TAG, LOG_MESSAGE_SPI_INIT_FAILED_FMT, TDECK_SPI_HOST); return false; } return true; -} \ No newline at end of file +} + +bool tdeck_init_hardware() { + return init_spi(); +} diff --git a/Boards/LilygoTdeck/Source/LilygoTdeck.cpp b/Boards/LilygoTdeck/Source/LilygoTdeck.cpp index 7d8297f2..f4193696 100644 --- a/Boards/LilygoTdeck/Source/LilygoTdeck.cpp +++ b/Boards/LilygoTdeck/Source/LilygoTdeck.cpp @@ -2,21 +2,19 @@ #include "hal/TdeckDisplay.h" #include "hal/TdeckKeyboard.h" #include "hal/TdeckPower.h" -#include "hal/sdcard/Sdcard.h" +#include "hal/TdeckSdCard.h" bool tdeck_init_power(); bool tdeck_init_hardware(); bool tdeck_init_lvgl(); -extern const tt::hal::sdcard::SdCard tdeck_sdcard; - extern const tt::hal::Configuration lilygo_tdeck = { .initBoot = tdeck_init_power, .initHardware = tdeck_init_hardware, .initLvgl = tdeck_init_lvgl, .createDisplay = createDisplay, .createKeyboard = createKeyboard, - .sdcard = &tdeck_sdcard, + .sdcard = createTdeckSdCard(), .power = tdeck_get_power, .i2c = { tt::hal::i2c::Configuration { diff --git a/Boards/LilygoTdeck/Source/PowerOn.cpp b/Boards/LilygoTdeck/Source/PowerOn.cpp index 990cd230..bd21099e 100644 --- a/Boards/LilygoTdeck/Source/PowerOn.cpp +++ b/Boards/LilygoTdeck/Source/PowerOn.cpp @@ -27,9 +27,9 @@ static bool tdeck_power_on() { } bool tdeck_init_power() { - ESP_LOGI(TAG, "Power on"); + ESP_LOGI(TAG, LOG_MESSAGE_POWER_ON_START); if (!tdeck_power_on()) { - TT_LOG_E(TAG, "Power on failed"); + TT_LOG_E(TAG, LOG_MESSAGE_POWER_ON_FAILED); return false; } diff --git a/Boards/LilygoTdeck/Source/Sdcard.cpp b/Boards/LilygoTdeck/Source/Sdcard.cpp deleted file mode 100644 index d5664302..00000000 --- a/Boards/LilygoTdeck/Source/Sdcard.cpp +++ /dev/null @@ -1,166 +0,0 @@ -#include "hal/sdcard/Sdcard.h" -#include "Check.h" -#include "Log.h" - -#include "esp_vfs_fat.h" -#include "sdmmc_cmd.h" -#include "lvgl/LvglSync.h" - -#define TAG "tdeck_sdcard" - -#define TDECK_SDCARD_SPI_HOST SPI2_HOST -#define TDECK_SDCARD_PIN_CS GPIO_NUM_39 -#define TDECK_SDCARD_SPI_FREQUENCY 800000U -#define TDECK_SDCARD_FORMAT_ON_MOUNT_FAILED false -#define TDECK_SDCARD_MAX_OPEN_FILES 4 -#define TDECK_SDCARD_ALLOC_UNIT_SIZE (16 * 1024) -#define TDECK_SDCARD_STATUS_CHECK_ENABLED false - -// Other -#define TDECK_LCD_PIN_CS GPIO_NUM_12 -#define TDECK_RADIO_PIN_CS GPIO_NUM_9 - -typedef struct { - const char* mount_point; - sdmmc_card_t* card; -} MountData; - -/** - * Before we can initialize the sdcard's SPI communications, we have to set all - * other SPI pins on the board high. - * See https://github.com/espressif/esp-idf/issues/1597 - * See https://github.com/Xinyuan-LilyGO/T-Deck/blob/master/examples/UnitTest/UnitTest.ino - * @return success result - */ -static bool sdcard_init() { - TT_LOG_D(TAG, "init"); - - gpio_config_t config = { - .pin_bit_mask = BIT64(TDECK_SDCARD_PIN_CS) | BIT64(TDECK_RADIO_PIN_CS) | BIT64(TDECK_LCD_PIN_CS), - .mode = GPIO_MODE_OUTPUT, - .pull_up_en = GPIO_PULLUP_DISABLE, - .pull_down_en = GPIO_PULLDOWN_DISABLE, - .intr_type = GPIO_INTR_DISABLE, - }; - - if (gpio_config(&config) != ESP_OK) { - TT_LOG_E(TAG, "GPIO init failed"); - return false; - } - - if (gpio_set_level(TDECK_SDCARD_PIN_CS, 1) != ESP_OK) { - TT_LOG_E(TAG, "Failed to set board CS pin high"); - return false; - } - - if (gpio_set_level(TDECK_RADIO_PIN_CS, 1) != ESP_OK) { - TT_LOG_E(TAG, "Failed to set radio CS pin high"); - return false; - } - - if (gpio_set_level(TDECK_LCD_PIN_CS, 1) != ESP_OK) { - TT_LOG_E(TAG, "Failed to set TFT CS pin high"); - return false; - } - - return true; -} - -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 = { - .format_if_mount_failed = TDECK_SDCARD_FORMAT_ON_MOUNT_FAILED, - .max_files = TDECK_SDCARD_MAX_OPEN_FILES, - .allocation_unit_size = TDECK_SDCARD_ALLOC_UNIT_SIZE, - .disk_status_check_enable = TDECK_SDCARD_STATUS_CHECK_ENABLED - }; - - // 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; - slot_config.host_id = TDECK_SDCARD_SPI_HOST; - - sdmmc_host_t host = SDSPI_HOST_DEFAULT(); - // The following value is from T-Deck repo's UnitTest.ino project: - // 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) { - if (ret == ESP_FAIL) { - TT_LOG_E(TAG, "Mounting failed. Ensure the card is formatted with FAT."); - } else { - TT_LOG_E(TAG, "Mounting failed (%s)", esp_err_to_name(ret)); - } - return nullptr; - } - - auto* data = static_cast(malloc(sizeof(MountData))); - *data = (MountData) { - .mount_point = mount_point, - .card = card, - }; - - return data; -} - -static void* sdcard_init_and_mount(const char* mount_point) { - if (!sdcard_init()) { - TT_LOG_E(TAG, "Failed to set SPI CS pins high. This is a pre-requisite for mounting."); - return nullptr; - } - auto* data = static_cast(sdcard_mount(mount_point)); - if (data == nullptr) { - TT_LOG_E(TAG, "Mount failed for %s", mount_point); - return nullptr; - } - - sdmmc_card_print_info(stdout, data->card); - - return data; -} - -static void sdcard_unmount(void* context) { - auto* data = static_cast(context); - TT_LOG_I(TAG, "Unmounting %s", data->mount_point); - - tt_assert(data != nullptr); - if (esp_vfs_fat_sdcard_unmount(data->mount_point, data->card) != ESP_OK) { - TT_LOG_E(TAG, "Unmount failed for %s", data->mount_point); - } - - free(data); -} - -// TODO: Refactor to "bool getStatus(Status* status)" method so that it can fail when the lvgl lock fails -static bool sdcard_is_mounted(void* context) { - auto* data = static_cast(context); - /** - * The SD card and the screen are on the same SPI bus. - * Writing and reading to the bus from 2 devices at the same time causes crashes. - * This work-around ensures that this check is only happening when LVGL isn't rendering. - */ - bool locked = tt::lvgl::lock(50); // TODO: Refactor to a more reliable locking mechanism - if (!locked) { - TT_LOG_W(TAG, "Failed to get LVGL lock"); - } - - bool result = (data != nullptr) && (sdmmc_get_status(data->card) == ESP_OK); - - if (locked) { - tt::lvgl::unlock(); - } - - return result; -} - -extern const tt::hal::sdcard::SdCard tdeck_sdcard = { - .mount = &sdcard_init_and_mount, - .unmount = &sdcard_unmount, - .is_mounted = &sdcard_is_mounted, - .mount_behaviour = tt::hal::sdcard::MountBehaviourAtBoot -}; diff --git a/Boards/LilygoTdeck/Source/hal/TdeckSdCard.cpp b/Boards/LilygoTdeck/Source/hal/TdeckSdCard.cpp new file mode 100644 index 00000000..26589583 --- /dev/null +++ b/Boards/LilygoTdeck/Source/hal/TdeckSdCard.cpp @@ -0,0 +1,34 @@ +#include "TdeckSdCard.h" + +#include "lvgl/LvglSync.h" +#include "hal/SpiSdCard.h" + +#include +#include + +#define TDECK_SDCARD_SPI_FREQUENCY 800000U +#define TDECK_SDCARD_PIN_CS GPIO_NUM_39 +#define TDECK_LCD_PIN_CS GPIO_NUM_12 +#define TDECK_RADIO_PIN_CS GPIO_NUM_9 + +std::shared_ptr createTdeckSdCard() { + auto* configuration = new tt::hal::SpiSdCard::Config( + TDECK_SDCARD_SPI_FREQUENCY, + TDECK_SDCARD_PIN_CS, + GPIO_NUM_NC, + GPIO_NUM_NC, + GPIO_NUM_NC, + SdCard::MountBehaviourAtBoot, + tt::lvgl::getLvglSyncLockable(), + { + TDECK_RADIO_PIN_CS, + TDECK_LCD_PIN_CS + } + ); + + auto* sdcard = (SdCard*) new SpiSdCard( + std::unique_ptr(configuration) + ); + + return std::shared_ptr(sdcard); +} diff --git a/Boards/LilygoTdeck/Source/hal/TdeckSdCard.h b/Boards/LilygoTdeck/Source/hal/TdeckSdCard.h new file mode 100644 index 00000000..0cfdb0de --- /dev/null +++ b/Boards/LilygoTdeck/Source/hal/TdeckSdCard.h @@ -0,0 +1,7 @@ +#pragma once + +#include "hal/SdCard.h" + +using namespace tt::hal; + +std::shared_ptr createTdeckSdCard(); diff --git a/Boards/M5stackCore2/Source/M5stackCore2.cpp b/Boards/M5stackCore2/Source/M5stackCore2.cpp index 5a289e61..686bbba9 100644 --- a/Boards/M5stackCore2/Source/M5stackCore2.cpp +++ b/Boards/M5stackCore2/Source/M5stackCore2.cpp @@ -1,12 +1,11 @@ #include "M5stackCore2.h" #include "M5stackShared.h" -#include "hal/M5stackPower.h" extern const tt::hal::Configuration m5stack_core2 = { .initBoot = m5stack_bootstrap, .initLvgl = m5stack_lvgl_init, .createDisplay = createDisplay, - .sdcard = &m5stack_sdcard, + .sdcard = createM5SdCard(), .power = m5stack_get_power, .i2c = { tt::hal::i2c::Configuration { diff --git a/Boards/M5stackCoreS3/Source/M5stackCoreS3.cpp b/Boards/M5stackCoreS3/Source/M5stackCoreS3.cpp index 5f3115a2..72850afc 100644 --- a/Boards/M5stackCoreS3/Source/M5stackCoreS3.cpp +++ b/Boards/M5stackCoreS3/Source/M5stackCoreS3.cpp @@ -1,12 +1,11 @@ #include "M5stackCoreS3.h" #include "M5stackShared.h" -#include "hal/M5stackPower.h" const tt::hal::Configuration m5stack_cores3 = { .initBoot = m5stack_bootstrap, .initLvgl = m5stack_lvgl_init, .createDisplay = createDisplay, - .sdcard = &m5stack_sdcard, + .sdcard = createM5SdCard(), .power = m5stack_get_power, .i2c = { tt::hal::i2c::Configuration { diff --git a/Boards/M5stackShared/Source/M5stackShared.h b/Boards/M5stackShared/Source/M5stackShared.h index 6ef643dd..7a91cdc1 100644 --- a/Boards/M5stackShared/Source/M5stackShared.h +++ b/Boards/M5stackShared/Source/M5stackShared.h @@ -3,9 +3,8 @@ #include "hal/Power.h" #include "hal/M5stackTouch.h" #include "hal/M5stackDisplay.h" -#include "hal/sdcard/Sdcard.h" +#include "hal/M5stackPower.h" +#include "hal/M5stackSdCard.h" extern bool m5stack_bootstrap(); extern bool m5stack_lvgl_init(); - -extern const tt::hal::sdcard::SdCard m5stack_sdcard; diff --git a/Boards/M5stackShared/Source/Sdcard.cpp b/Boards/M5stackShared/Source/Sdcard.cpp deleted file mode 100644 index f4fd5654..00000000 --- a/Boards/M5stackShared/Source/Sdcard.cpp +++ /dev/null @@ -1,137 +0,0 @@ -#include "Check.h" -#include "Log.h" -#include "hal/sdcard/Sdcard.h" - -#include "esp_vfs_fat.h" -#include "sdmmc_cmd.h" - -#define TAG "m5stack_sdcard" - -#define SDCARD_SPI_HOST SPI2_HOST -#define SDCARD_PIN_CS GPIO_NUM_4 -#define SDCARD_SPI_FREQUENCY 800000U -#define SDCARD_FORMAT_ON_MOUNT_FAILED false -#define SDCARD_MAX_OPEN_FILES 4 -#define SDCARD_ALLOC_UNIT_SIZE (16 * 1024) -#define SDCARD_STATUS_CHECK_ENABLED false - -typedef struct { - const char* mount_point; - sdmmc_card_t* card; -} MountData; - - -/** - * Before we can initialize the sdcard's SPI communications, we have to set all - * other SPI pins on the board high. - * See https://github.com/espressif/esp-idf/issues/1597 - * See https://github.com/Xinyuan-LilyGO/T-Deck/blob/master/examples/UnitTest/UnitTest.ino - * @return success result - */ -static bool sdcard_init() { - TT_LOG_D(TAG, "init"); - - gpio_config_t config = { - .pin_bit_mask = BIT64(GPIO_NUM_4) | BIT64(GPIO_NUM_5), - .mode = GPIO_MODE_OUTPUT, - .pull_up_en = GPIO_PULLUP_DISABLE, - .pull_down_en = GPIO_PULLDOWN_DISABLE, - .intr_type = GPIO_INTR_DISABLE, - }; - - if (gpio_config(&config) != ESP_OK) { - TT_LOG_E(TAG, "GPIO init failed"); - return false; - } - - if (gpio_set_level(GPIO_NUM_4, 1) != ESP_OK) { - TT_LOG_E(TAG, "Failed to set board CS pin high"); - return false; - } - - if (gpio_set_level(GPIO_NUM_5, 1) != ESP_OK) { - TT_LOG_E(TAG, "Failed to set board CS pin high"); - return false; - } - - return true; -} - -static void* sdcard_mount(const char* mount_point) { - TT_LOG_I(TAG, "Mounting %s", mount_point); - - esp_vfs_fat_sdmmc_mount_config_t mount_config = { - .format_if_mount_failed = SDCARD_FORMAT_ON_MOUNT_FAILED, - .max_files = SDCARD_MAX_OPEN_FILES, - .allocation_unit_size = SDCARD_ALLOC_UNIT_SIZE, - .disk_status_check_enable = SDCARD_STATUS_CHECK_ENABLED, - .use_one_fat = false - }; - - 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 = SDCARD_PIN_CS; - slot_config.host_id = SDCARD_SPI_HOST; - - sdmmc_host_t host = SDSPI_HOST_DEFAULT(); - host.max_freq_khz = SDCARD_SPI_FREQUENCY; - esp_err_t ret = esp_vfs_fat_sdspi_mount(mount_point, &host, &slot_config, &mount_config, &card); - - if (ret != ESP_OK) { - if (ret == ESP_FAIL) { - TT_LOG_E(TAG, "Mounting failed. Ensure the card is formatted with FAT."); - } else { - TT_LOG_E(TAG, "Mounting failed (%s)", esp_err_to_name(ret)); - } - return nullptr; - } - - auto* data = static_cast(malloc(sizeof(MountData))); - *data = (MountData) { - .mount_point = mount_point, - .card = card, - }; - - return data; -} - -static void* sdcard_init_and_mount(const char* mount_point) { - if (!sdcard_init()) { - TT_LOG_E(TAG, "Failed to set SPI CS pins high. This is a pre-requisite for mounting."); - return NULL; - } - auto* data = static_cast(sdcard_mount(mount_point)); - if (data == nullptr) { - TT_LOG_E(TAG, "Mount failed for %s", mount_point); - return nullptr; - } - - sdmmc_card_print_info(stdout, data->card); - - return data; -} -static void sdcard_unmount(void* context) { - auto* data = static_cast(context); - TT_LOG_I(TAG, "Unmounting %s", data->mount_point); - - tt_assert(data != nullptr); - if (esp_vfs_fat_sdcard_unmount(data->mount_point, data->card) != ESP_OK) { - TT_LOG_E(TAG, "Unmount failed for %s", data->mount_point); - } - - free(data); -} - -static bool sdcard_is_mounted(void* context) { - auto* data = static_cast(context); - return (data != nullptr) && (sdmmc_get_status(data->card) == ESP_OK); -} - -extern const tt::hal::sdcard::SdCard m5stack_sdcard = { - .mount = &sdcard_init_and_mount, - .unmount = &sdcard_unmount, - .is_mounted = &sdcard_is_mounted, - .mount_behaviour = tt::hal::sdcard::MountBehaviourAnytime -}; diff --git a/Boards/M5stackShared/Source/hal/M5stackSdCard.cpp b/Boards/M5stackShared/Source/hal/M5stackSdCard.cpp new file mode 100644 index 00000000..f97401f2 --- /dev/null +++ b/Boards/M5stackShared/Source/hal/M5stackSdCard.cpp @@ -0,0 +1,31 @@ +#include "M5stackSdCard.h" + +#include "lvgl/LvglSync.h" +#include "hal/SpiSdCard.h" + +#include +#include + +#define SDCARD_PIN_CS GPIO_NUM_4 +#define SDCARD_SPI_FREQUENCY 800000U + +std::shared_ptr createM5SdCard() { + auto* configuration = new tt::hal::SpiSdCard::Config( + SDCARD_SPI_FREQUENCY, + SDCARD_PIN_CS, + GPIO_NUM_NC, + GPIO_NUM_NC, + GPIO_NUM_NC, + SdCard::MountBehaviourAtBoot, + tt::lvgl::getLvglSyncLockable(), + { + GPIO_NUM_5 + } + ); + + auto* sdcard = (SdCard*) new SpiSdCard( + std::unique_ptr(configuration) + ); + + return std::shared_ptr(sdcard); +} diff --git a/Boards/M5stackShared/Source/hal/M5stackSdCard.h b/Boards/M5stackShared/Source/hal/M5stackSdCard.h new file mode 100644 index 00000000..b9239dbf --- /dev/null +++ b/Boards/M5stackShared/Source/hal/M5stackSdCard.h @@ -0,0 +1,7 @@ +#pragma once + +#include "hal/SdCard.h" + +using namespace tt::hal; + +std::shared_ptr createM5SdCard(); diff --git a/Boards/Simulator/Source/hal/Configuration.cpp b/Boards/Simulator/Source/hal/Configuration.cpp index dea0172d..831de905 100644 --- a/Boards/Simulator/Source/hal/Configuration.cpp +++ b/Boards/Simulator/Source/hal/Configuration.cpp @@ -5,11 +5,10 @@ #include "src/lv_init.h" #include "SdlDisplay.h" #include "SdlKeyboard.h" +#include "SimulatorSdCard.h" #define TAG "hardware" -extern const tt::hal::sdcard::SdCard simulatorSdcard; - static bool initBoot() { lv_init(); lvgl_task_start(); @@ -31,7 +30,7 @@ extern const tt::hal::Configuration hardware = { .initBoot = initBoot, .createDisplay = createDisplay, .createKeyboard = createKeyboard, - .sdcard = &simulatorSdcard, + .sdcard = std::make_shared(), .power = simulatorPower, .i2c = { tt::hal::i2c::Configuration { diff --git a/Boards/Simulator/Source/hal/SimulatorSdCard.h b/Boards/Simulator/Source/hal/SimulatorSdCard.h new file mode 100644 index 00000000..23d8d7f4 --- /dev/null +++ b/Boards/Simulator/Source/hal/SimulatorSdCard.h @@ -0,0 +1,26 @@ +#pragma once + +#include "hal/SdCard.h" + +using namespace tt::hal; + +class SimulatorSdCard : public SdCard { +private: + State state; +public: + SimulatorSdCard() : SdCard(MountBehaviourAtBoot), state(StateUnmounted) {} + + bool mount(const char* mountPath) override { + state = StateMounted; + return true; + } + bool unmount() override { + state = StateUnmounted; + return true; + } + + State getState() const override { + return state; + } +}; + diff --git a/Boards/Simulator/Source/hal/SimulatorSdcard.cpp b/Boards/Simulator/Source/hal/SimulatorSdcard.cpp deleted file mode 100644 index 42de03fa..00000000 --- a/Boards/Simulator/Source/hal/SimulatorSdcard.cpp +++ /dev/null @@ -1,26 +0,0 @@ -#include "Tactility.h" -#include "hal/sdcard/Sdcard.h" - -static uint32_t fake_handle = 0; - -static void* _Nullable sdcard_mount(TT_UNUSED const char* mount_point) { - return &fake_handle; -} - -static void* sdcard_init_and_mount(TT_UNUSED const char* mount_point) { - return &fake_handle; -} - -static void sdcard_unmount(TT_UNUSED void* context) { -} - -static bool sdcard_is_mounted(TT_UNUSED void* context) { - return TT_SCREENSHOT_MODE; -} - -extern const tt::hal::sdcard::SdCard simulatorSdcard = { - .mount = &sdcard_init_and_mount, - .unmount = &sdcard_unmount, - .is_mounted = &sdcard_is_mounted, - .mount_behaviour = tt::hal::sdcard::MountBehaviourAtBoot -}; diff --git a/Boards/YellowBoard/Source/Boot.cpp b/Boards/YellowBoard/Source/Boot.cpp index 64907cf9..a8351726 100644 --- a/Boards/YellowBoard/Source/Boot.cpp +++ b/Boards/YellowBoard/Source/Boot.cpp @@ -1,4 +1,4 @@ -#include "Config.h" +#include "YellowConfig.h" #include "TactilityCore.h" #include "hal/YellowTouchConstants.h" #include @@ -6,6 +6,8 @@ #define TAG "twodotfour_bootstrap" static bool init_i2c() { + TT_LOG_I(TAG, LOG_MESSAGE_I2C_INIT_START); + const i2c_config_t i2c_conf = { .mode = I2C_MODE_MASTER, .sda_io_num = GPIO_NUM_33, @@ -18,12 +20,12 @@ static bool init_i2c() { }; if (i2c_param_config(TWODOTFOUR_TOUCH_I2C_PORT, &i2c_conf) != ESP_OK) { - TT_LOG_E(TAG, "i2c config failed"); + TT_LOG_E(TAG, LOG_MESSAGE_I2C_INIT_CONFIG_FAILED ); return false; } if (i2c_driver_install(TWODOTFOUR_TOUCH_I2C_PORT, i2c_conf.mode, 0, 0, 0) != ESP_OK) { - TT_LOG_E(TAG, "i2c driver install failed"); + TT_LOG_E(TAG, LOG_MESSAGE_I2C_INIT_DRIVER_INSTALL_FAILED); return false; } @@ -31,35 +33,46 @@ static bool init_i2c() { } static bool init_spi2() { - const spi_bus_config_t bus_config = { - .mosi_io_num = TWODOTFOUR_SPI2_PIN_MOSI, - .miso_io_num = GPIO_NUM_NC, - .sclk_io_num = TWODOTFOUR_SPI2_PIN_SCLK, - .quadwp_io_num = GPIO_NUM_NC, - .quadhd_io_num = GPIO_NUM_NC, - .max_transfer_sz = TWODOTFOUR_SPI2_TRANSACTION_LIMIT - }; + TT_LOG_I(TAG, LOG_MESSAGE_SPI_INIT_START_FMT, SPI2_HOST); - if (spi_bus_initialize(SPI2_HOST, &bus_config, SPI_DMA_CH_AUTO) != ESP_OK) { - TT_LOG_E(TAG, "SPI bus init failed"); - return false; - } + const spi_bus_config_t bus_config = { + .mosi_io_num = TWODOTFOUR_SPI2_PIN_MOSI, + .miso_io_num = GPIO_NUM_NC, + .sclk_io_num = TWODOTFOUR_SPI2_PIN_SCLK, + .quadwp_io_num = GPIO_NUM_NC, + .quadhd_io_num = GPIO_NUM_NC, + .max_transfer_sz = TWODOTFOUR_SPI2_TRANSACTION_LIMIT + }; - return true; + if (spi_bus_initialize(SPI2_HOST, &bus_config, SPI_DMA_CH_AUTO) != ESP_OK) { + TT_LOG_E(TAG, LOG_MESSAGE_SPI_INIT_FAILED_FMT, SPI2_HOST); + return false; + } + + return true; } static bool init_spi3() { + TT_LOG_I(TAG, LOG_MESSAGE_SPI_INIT_START_FMT, SPI3_HOST); + const spi_bus_config_t bus_config = { .mosi_io_num = TWODOTFOUR_SPI3_PIN_MOSI, .miso_io_num = TWODOTFOUR_SPI3_PIN_MISO, .sclk_io_num = TWODOTFOUR_SPI3_PIN_SCLK, .quadwp_io_num = GPIO_NUM_NC, .quadhd_io_num = GPIO_NUM_NC, - .max_transfer_sz = TWODOTFOUR_SPI3_TRANSACTION_LIMIT + .data4_io_num = 0, + .data5_io_num = 0, + .data6_io_num = 0, + .data7_io_num = 0, + .max_transfer_sz = TWODOTFOUR_SPI3_TRANSACTION_LIMIT, + .flags = 0, + .isr_cpu_id = ESP_INTR_CPU_AFFINITY_AUTO, + .intr_flags = 0 }; if (spi_bus_initialize(SPI3_HOST, &bus_config, SPI_DMA_CH_AUTO) != ESP_OK) { - TT_LOG_E(TAG, "SPI bus init failed"); + TT_LOG_E(TAG, LOG_MESSAGE_SPI_INIT_FAILED_FMT, SPI3_HOST); return false; } @@ -67,23 +80,5 @@ static bool init_spi3() { } bool twodotfour_boot() { - TT_LOG_I(TAG, "Init I2C"); - if (!init_i2c()) { - TT_LOG_E(TAG, "Init I2C failed"); - return false; - } - - TT_LOG_I(TAG, "Init SPI2"); - if (!init_spi2()) { - TT_LOG_E(TAG, "Init SPI2 failed"); - return false; - } - - TT_LOG_I(TAG, "Init SPI3"); - if (!init_spi3()) { - TT_LOG_E(TAG, "Init SPI3 failed"); - return false; - } - - return true; + return init_i2c() && init_spi2() && init_spi3(); } diff --git a/Boards/YellowBoard/Source/Sdcard.cpp b/Boards/YellowBoard/Source/Sdcard.cpp deleted file mode 100644 index 0208117c..00000000 --- a/Boards/YellowBoard/Source/Sdcard.cpp +++ /dev/null @@ -1,79 +0,0 @@ -#include "hal/sdcard/Sdcard.h" -#include "Check.h" -#include "Log.h" -#include "Config.h" - -#include "esp_vfs_fat.h" -#include "sdmmc_cmd.h" - -#define TAG "twodotfour_sdcard" - -typedef struct { - const char* mount_point; - sdmmc_card_t* card; -} MountData; - -static void* sdcard_mount(const char* mount_point) { - TT_LOG_I(TAG, "Mounting %s", mount_point); - - esp_vfs_fat_sdmmc_mount_config_t mount_config = { - .format_if_mount_failed = TWODOTFOUR_SDCARD_FORMAT_ON_MOUNT_FAILED, - .max_files = TWODOTFOUR_SDCARD_MAX_OPEN_FILES, - .allocation_unit_size = TWODOTFOUR_SDCARD_ALLOC_UNIT_SIZE, - .disk_status_check_enable = TWODOTFOUR_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 = TWODOTFOUR_SDCARD_PIN_CS; - slot_config.host_id = TWODOTFOUR_SDCARD_SPI_HOST; - - sdmmc_host_t host = SDSPI_HOST_DEFAULT(); - host.max_freq_khz = TWODOTFOUR_SDCARD_SPI_FREQUENCY; - esp_err_t ret = esp_vfs_fat_sdspi_mount(mount_point, &host, &slot_config, &mount_config, &card); - - if (ret != ESP_OK) { - if (ret == ESP_FAIL) { - TT_LOG_E(TAG, "Mounting failed. Ensure the card is formatted with FAT."); - } else { - TT_LOG_E(TAG, "Mounting failed (%s)", esp_err_to_name(ret)); - } - return nullptr; - } - - auto* data = static_cast(malloc(sizeof(MountData))); - *data = (MountData) { - .mount_point = mount_point, - .card = card - }; - - sdmmc_card_print_info(stdout, data->card); - - return data; -} - -static void sdcard_unmount(void* context) { - auto* data = static_cast(context); - TT_LOG_I(TAG, "Unmounting %s", data->mount_point); - - tt_assert(data != nullptr); - if (esp_vfs_fat_sdcard_unmount(data->mount_point, data->card) != ESP_OK) { - TT_LOG_E(TAG, "Unmount failed for %s", data->mount_point); - } - - free(data); -} - -static bool sdcard_is_mounted(void* context) { - auto* data = static_cast(context); - return (data != nullptr) && (sdmmc_get_status(data->card) == ESP_OK); -} - -extern const tt::hal::sdcard::SdCard twodotfour_sdcard = { - .mount = &sdcard_mount, - .unmount = &sdcard_unmount, - .is_mounted = &sdcard_is_mounted, - .mount_behaviour = tt::hal::sdcard::MountBehaviourAnytime -}; diff --git a/Boards/YellowBoard/Source/YellowBoard.cpp b/Boards/YellowBoard/Source/YellowBoard.cpp index d8775894..c6f6161b 100644 --- a/Boards/YellowBoard/Source/YellowBoard.cpp +++ b/Boards/YellowBoard/Source/YellowBoard.cpp @@ -1,16 +1,15 @@ #include "YellowBoard.h" #include "hal/YellowDisplay.h" +#include "hal/YellowSdCard.h" bool twodotfour_lvgl_init(); bool twodotfour_boot(); -extern const tt::hal::sdcard::SdCard twodotfour_sdcard; - const tt::hal::Configuration yellow_board_24inch_cap = { .initBoot = &twodotfour_boot, .initLvgl = &twodotfour_lvgl_init, .createDisplay = createDisplay, - .sdcard = &twodotfour_sdcard, + .sdcard = createYellowSdCard(), .power = nullptr, .i2c = { tt::hal::i2c::Configuration { diff --git a/Boards/YellowBoard/Source/Config.h b/Boards/YellowBoard/Source/YellowConfig.h similarity index 60% rename from Boards/YellowBoard/Source/Config.h rename to Boards/YellowBoard/Source/YellowConfig.h index 247d3f2f..7530ac94 100644 --- a/Boards/YellowBoard/Source/Config.h +++ b/Boards/YellowBoard/Source/YellowConfig.h @@ -15,12 +15,3 @@ #define TWODOTFOUR_SPI3_PIN_MOSI GPIO_NUM_23 #define TWODOTFOUR_SPI3_PIN_MISO GPIO_NUM_19 #define TWODOTFOUR_SPI3_TRANSACTION_LIMIT 8192 // TODO: Determine proper limit - -// SD Card -#define TWODOTFOUR_SDCARD_SPI_HOST SPI3_HOST -#define TWODOTFOUR_SDCARD_PIN_CS GPIO_NUM_5 -#define TWODOTFOUR_SDCARD_SPI_FREQUENCY 800000U -#define TWODOTFOUR_SDCARD_FORMAT_ON_MOUNT_FAILED false -#define TWODOTFOUR_SDCARD_MAX_OPEN_FILES 4 -#define TWODOTFOUR_SDCARD_ALLOC_UNIT_SIZE (16 * 1024) -#define TWODOTFOUR_SDCARD_STATUS_CHECK_ENABLED false diff --git a/Boards/YellowBoard/Source/hal/YellowSdCard.cpp b/Boards/YellowBoard/Source/hal/YellowSdCard.cpp new file mode 100644 index 00000000..5f8db8f5 --- /dev/null +++ b/Boards/YellowBoard/Source/hal/YellowSdCard.cpp @@ -0,0 +1,31 @@ +#include "YellowSdCard.h" + +#define TAG "twodotfour_sdcard" + +#include "lvgl/LvglSync.h" +#include "hal/SpiSdCard.h" + +#define SDCARD_SPI_HOST SPI3_HOST +#define SDCARD_PIN_CS GPIO_NUM_5 +#define SDCARD_SPI_FREQUENCY 800000U + +std::shared_ptr createYellowSdCard() { + auto* configuration = new tt::hal::SpiSdCard::Config( + SDCARD_SPI_FREQUENCY, + SDCARD_PIN_CS, + GPIO_NUM_NC, + GPIO_NUM_NC, + GPIO_NUM_NC, + SdCard::MountBehaviourAtBoot, + nullptr, + std::vector(), + SDCARD_SPI_HOST + ); + + auto* sdcard = (SdCard*) new SpiSdCard( + std::unique_ptr(configuration) + ); + + return std::shared_ptr(sdcard); +} + diff --git a/Boards/YellowBoard/Source/hal/YellowSdCard.h b/Boards/YellowBoard/Source/hal/YellowSdCard.h new file mode 100644 index 00000000..4a162bb5 --- /dev/null +++ b/Boards/YellowBoard/Source/hal/YellowSdCard.h @@ -0,0 +1,8 @@ +#pragma once + +#include "hal/SdCard.h" + +using namespace tt::hal; + +std::shared_ptr createYellowSdCard(); + diff --git a/Buildscripts/release-sdk.sh b/Buildscripts/release-sdk.sh index c07e6446..4e6adc91 100755 --- a/Buildscripts/release-sdk.sh +++ b/Buildscripts/release-sdk.sh @@ -36,6 +36,7 @@ cd Libraries/lvgl find src/ -name '*.h' | cpio -pdm $find_target_dir cd - cp Libraries/lvgl/lvgl.h $find_target_dir +cp Libraries/lvgl/lv_version.h $find_target_dir cp Libraries/lvgl/LICENCE.txt $lvgl_library_path/LICENSE.txt cp Libraries/lvgl_conf/lv_conf_kconfig.h $lvgl_library_path/Include/lv_conf.h diff --git a/Documentation/ideas.md b/Documentation/ideas.md index a20b089e..2d8f2640 100644 --- a/Documentation/ideas.md +++ b/Documentation/ideas.md @@ -1,6 +1,5 @@ # 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) - WiFi bug: when pressing disconnect while between `WIFI_EVENT_STA_START` and `IP_EVENT_STA_GOT_IP`, then auto-connect becomes active again. - ESP32 (CYD) memory issues (or any device without PSRAM): - Boot app doesn't show logo @@ -10,22 +9,21 @@ - When no PSRAM is available, use simplified desktop buttons - Add statusbar icon for memory pressure. - Show error in WiFi screen (e.g. AlertDialog when SPI is not enabled and available memory is below a certain amount) -- WiFi details "forget" button should be hidden when WiFi credentials are not stores yet. +- Clean up static_cast when casting to base class. # TODOs +- Call tt::lvgl::isSyncSet after HAL init and show error (and crash?) when it is not set. - Create different partitions files for different ESP flash size targets (N4, N8, N16, N32) -- 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. 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 - Audio player app - Audio recording app -- Files app: file operations: rename, delete, copy, paste (long press?), create folder - T-Deck: Use knob for UI selection - Logging to disk/etc. - 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 -- Loader: Use main dispatcher instead of Thread, and move API to `tt::app::` +- Loader: Use main dispatcher instead of Thread - 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 when a user plugs in the SD card on a device that only supports mounting at boot. @@ -43,6 +41,10 @@ - Make firmwares available via web serial website - If present, use LED to show boot/wifi status - T-Deck Power: capacity estimation uses linear voltage curve, but it should use some sort of battery discharge curve. +- Statusbar widget to show how much memory is in use? +- Wrapper for Slider that shows "+" and "-" buttons, and also the value in a label. +- Display app: Add toggle to display performance measurement overlay (consider showing FPS in statusbar!) +- Files app: copy/paste actions # App Ideas - USB implementation to make device act as mass storage device. diff --git a/ExternalApps/HelloWorld/main/Source/main.c b/ExternalApps/HelloWorld/main/Source/main.c index 7dbf6862..c1ab0443 100644 --- a/ExternalApps/HelloWorld/main/Source/main.c +++ b/ExternalApps/HelloWorld/main/Source/main.c @@ -1,9 +1,8 @@ -#include -#include "TactilityC/app/App.h" -#include "TactilityC/lvgl/Toolbar.h" +#include "tt_app_manifest.h" +#include "tt_lvgl_toolbar.h" /** - * Note: LVGL and Tactility methods need to be exposed manually from TactilityC/Source/TactilityC.cpp + * Note: LVGL and Tactility methods need to be exposed manually from TactilityC/Source/tt_init.cpp * Only C is supported for now (C++ symbols fail to link) */ static void onShow(AppContextHandle context, lv_obj_t* parent) { diff --git a/Libraries/lv_screenshot/CMakeLists.txt b/Libraries/lv_screenshot/CMakeLists.txt index 1895aa7f..9282f359 100644 --- a/Libraries/lv_screenshot/CMakeLists.txt +++ b/Libraries/lv_screenshot/CMakeLists.txt @@ -5,18 +5,17 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_EXTENSIONS OFF) if (DEFINED ENV{ESP_IDF_VERSION}) - file(GLOB_RECURSE SOURCE_FILES Source/*.c*) # TODO: Fix - idf_component_register( - SRCS ${SOURCE_FILES} - INCLUDE_DIRS "src/" + SRC_DIRS "Source/" + INCLUDE_DIRS "Source/" + PRIV_INCLUDE_DIRS "Private/" REQUIRES lvgl ) add_definitions(-DESP_PLATFORM) else() - file(GLOB SOURCES "src/*.c*") - file(GLOB HEADERS "src/*.h*") + file(GLOB SOURCES "Source/*.c*") + file(GLOB HEADERS "Source/*.h*") add_library(lv_screenshot STATIC) @@ -26,8 +25,8 @@ else() ) target_include_directories(lv_screenshot - PRIVATE private - PUBLIC src + PRIVATE Private + PUBLIC Source ) target_link_libraries(lv_screenshot diff --git a/Libraries/lv_screenshot/private/save_bmp.h b/Libraries/lv_screenshot/Private/save_bmp.h similarity index 100% rename from Libraries/lv_screenshot/private/save_bmp.h rename to Libraries/lv_screenshot/Private/save_bmp.h diff --git a/Libraries/lv_screenshot/private/save_png.h b/Libraries/lv_screenshot/Private/save_png.h similarity index 100% rename from Libraries/lv_screenshot/private/save_png.h rename to Libraries/lv_screenshot/Private/save_png.h diff --git a/Libraries/lv_screenshot/src/lv_screenshot.c b/Libraries/lv_screenshot/Source/lv_screenshot.c similarity index 70% rename from Libraries/lv_screenshot/src/lv_screenshot.c rename to Libraries/lv_screenshot/Source/lv_screenshot.c index 4c19139b..1160c5bb 100644 --- a/Libraries/lv_screenshot/src/lv_screenshot.c +++ b/Libraries/lv_screenshot/Source/lv_screenshot.c @@ -1,25 +1,29 @@ #include "lv_screenshot.h" #include "save_png.h" +#include "save_bmp.h" + +#ifdef __cplusplus +extern "C" { +#endif static void data_pre_processing(lv_draw_buf_t* snapshot, uint16_t bpp, lv_100ask_screenshot_sv_t screenshot_sv); -bool lv_screenshot_create(lv_obj_t* obj, lv_color_format_t cf, lv_100ask_screenshot_sv_t screenshot_sv, const char* filename) { - lv_draw_buf_t* snapshot = lv_snapshot_take(obj, cf); +bool lv_screenshot_create(lv_obj_t* obj, lv_100ask_screenshot_sv_t screenshot_sv, const char* filename) { + lv_draw_buf_t* snapshot = lv_snapshot_take(obj, LV_COLOR_FORMAT_RGB888); if (snapshot) { - data_pre_processing(snapshot, LV_COLOR_DEPTH, screenshot_sv); - + bool success = false; if (screenshot_sv == LV_100ASK_SCREENSHOT_SV_PNG) { - if (LV_COLOR_DEPTH == 16) { - lv_screenshot_save_png_file(snapshot->data, snapshot->header.w, snapshot->header.h, 24, filename); - } else if (LV_COLOR_DEPTH == 32) { - lv_screenshot_save_png_file(snapshot->data, snapshot->header.w, snapshot->header.h, 32, filename); - } + data_pre_processing(snapshot, 24, screenshot_sv); + success = lv_screenshot_save_png_file(snapshot->data, snapshot->header.w, snapshot->header.h, 24, filename); + } else if (screenshot_sv == LV_100ASK_SCREENSHOT_SV_BMP) { + data_pre_processing(snapshot, 24, screenshot_sv); + success = lve_screenshot_save_bmp_file(snapshot->data, snapshot->header.w, snapshot->header.h, 24, filename); } lv_draw_buf_destroy(snapshot); - return true; + return success; } return false; @@ -45,16 +49,21 @@ static void data_pre_processing(lv_draw_buf_t* snapshot, uint16_t bpp, lv_100ask count += 3; } } - } else if ((screenshot_sv == LV_100ASK_SCREENSHOT_SV_PNG) && (bpp == 32)) { + } else if ((screenshot_sv == LV_100ASK_SCREENSHOT_SV_PNG) && (bpp == 32 || bpp == 24)) { uint8_t tmp_data = 0; uint32_t count = 0; + uint32_t pixel_byte_gap = bpp / 8; for (int w = 0; w < snapshot->header.w; w++) { for (int h = 0; h < snapshot->header.h; h++) { tmp_data = *(snapshot->data + count); *(uint8_t*)(snapshot->data + count) = *(snapshot->data + count + 2); *(uint8_t*)(snapshot->data + count + 2) = tmp_data; - count += 4; + count += pixel_byte_gap; } } } } + +#ifdef __cplusplus +} +#endif diff --git a/Libraries/lv_screenshot/src/lv_screenshot.h b/Libraries/lv_screenshot/Source/lv_screenshot.h similarity index 64% rename from Libraries/lv_screenshot/src/lv_screenshot.h rename to Libraries/lv_screenshot/Source/lv_screenshot.h index efaf72bf..23e7fcb8 100644 --- a/Libraries/lv_screenshot/src/lv_screenshot.h +++ b/Libraries/lv_screenshot/Source/lv_screenshot.h @@ -1,8 +1,6 @@ #pragma once #include "lvgl.h" -#include - #ifdef __cplusplus extern "C" { @@ -14,7 +12,7 @@ typedef enum { LV_100ASK_SCREENSHOT_SV_LAST } lv_100ask_screenshot_sv_t; -bool lv_screenshot_create(lv_obj_t* obj, lv_color_format_t cf, lv_100ask_screenshot_sv_t screenshot_sv, const char* filename); +bool lv_screenshot_create(lv_obj_t* obj, lv_100ask_screenshot_sv_t screenshot_sv, const char* filename); #ifdef __cplusplus } /*extern "C"*/ diff --git a/Libraries/lv_screenshot/Source/save_bmp.c b/Libraries/lv_screenshot/Source/save_bmp.c new file mode 100644 index 00000000..3ab90381 --- /dev/null +++ b/Libraries/lv_screenshot/Source/save_bmp.c @@ -0,0 +1,96 @@ +#include +#include +#include +#include "lvgl.h" + +typedef struct tagBITMAPFILEHEADER { + uint16_t bfType; + uint32_t bfSize; + uint16_t bfReserved1; + uint16_t bfReserved2; + uint32_t bfOffBits; +} __attribute__((packed)) BITMAPFILEHEADER, *PBITMAPFILEHEADER; + +typedef struct tagBITMAPINFOHEADER { + uint32_t biSize; + uint32_t biwidth; + uint32_t biheight; + uint16_t biPlanes; + uint16_t biBitCount; + uint32_t biCompression; + uint32_t biSizeImage; + uint32_t biXPelsPerMeter; + uint32_t biYPelsPerMeter; + uint32_t biClrUsed; + uint32_t biClrImportant; +} __attribute__((packed)) BITMAPINFOHEADER, *PBITMAPINFOHEADER; + +typedef struct tagRGBQUAD { + uint8_t rgbBlue; + uint8_t rgbGreen; + uint8_t rgbRed; + uint8_t rgbReserved; +} __attribute__((packed)) RGBQUAD; + +bool lve_screenshot_save_bmp_file(const uint8_t* image, uint32_t w, uint32_t h, uint32_t bpp, const char* filename) { + BITMAPFILEHEADER tBmpFileHead; + BITMAPINFOHEADER tBmpInfoHead; + + uint32_t dwSize; + + uint32_t bw; + lv_fs_file_t f; + + memset(&tBmpFileHead, 0, sizeof(BITMAPFILEHEADER)); + memset(&tBmpInfoHead, 0, sizeof(BITMAPINFOHEADER)); + + lv_fs_res_t res = lv_fs_open(&f, filename, LV_FS_MODE_WR); + if (res != LV_FS_RES_OK) { + LV_LOG_USER("Can't create output file %s", filename); + return false; + } + + tBmpFileHead.bfType = 0x4d42; + tBmpFileHead.bfSize = 0x36 + w * h * (bpp / 8); + tBmpFileHead.bfOffBits = 0x00000036; + + tBmpInfoHead.biSize = 0x00000028; + tBmpInfoHead.biwidth = w; + tBmpInfoHead.biheight = h; + tBmpInfoHead.biPlanes = 0x0001; + tBmpInfoHead.biBitCount = bpp; + tBmpInfoHead.biCompression = 0; + tBmpInfoHead.biSizeImage = w * h * (bpp / 8); + tBmpInfoHead.biXPelsPerMeter = 0; + tBmpInfoHead.biYPelsPerMeter = 0; + tBmpInfoHead.biClrUsed = 0; + tBmpInfoHead.biClrImportant = 0; + + res = lv_fs_write(&f, &tBmpFileHead, sizeof(tBmpFileHead), &bw); + if (bw != sizeof(tBmpFileHead)) { + LV_LOG_USER("Can't write BMP File Head to %s", filename); + return false; + } + + res = lv_fs_write(&f, &tBmpInfoHead, sizeof(tBmpInfoHead), &bw); + if (bw != sizeof(tBmpInfoHead)) { + LV_LOG_USER("Can't write BMP File Info Head to %s", filename); + return false; + } + + dwSize = w * bpp / 8; + const uint8_t* pPos = image + (h - 1) * dwSize; + + while (pPos >= image) { + res = lv_fs_write(&f, pPos, dwSize, &bw); + if (bw != dwSize) { + LV_LOG_USER("Can't write date to BMP File %s", filename); + return false; + } + pPos -= dwSize; + } + + lv_fs_close(&f); + + return true; +} diff --git a/Libraries/lv_screenshot/src/save_png.c b/Libraries/lv_screenshot/Source/save_png.c similarity index 56% rename from Libraries/lv_screenshot/src/save_png.c rename to Libraries/lv_screenshot/Source/save_png.c index d7a5eaac..3be555f7 100644 --- a/Libraries/lv_screenshot/src/save_png.c +++ b/Libraries/lv_screenshot/Source/save_png.c @@ -1,11 +1,11 @@ -#include "save_png.h" #include "src/libs/lodepng/lodepng.h" bool lv_screenshot_save_png_file(const uint8_t* image, uint32_t w, uint32_t h, uint32_t bpp, const char* filename) { if (bpp == 32) { - return lodepng_encode32_file(filename, image, w, h); + return lodepng_encode32_file(filename, image, w, h) == 0; } else if (bpp == 24) { - return lodepng_encode24_file(filename, image, w, h); + return lodepng_encode24_file(filename, image, w, h) == 0; + } else { + return false; } - return false; } diff --git a/Tactility/Private/service/loader/Loader_i.h b/Tactility/Private/service/loader/Loader_i.h index 51bd74c9..f0af6ea3 100644 --- a/Tactility/Private/service/loader/Loader_i.h +++ b/Tactility/Private/service/loader/Loader_i.h @@ -2,6 +2,7 @@ #include "app/AppManifest.h" #include "app/AppInstance.h" +#include "EventFlag.h" #include "MessageQueue.h" #include "Pubsub.h" #include "Thread.h" @@ -10,6 +11,7 @@ #include "RtosCompatSemaphore.h" #include #include +#include namespace tt::service::loader { @@ -53,98 +55,46 @@ typedef struct { // region LoaderMessage -typedef enum { - LoaderMessageTypeNone, - LoaderMessageTypeAppStart, - LoaderMessageTypeAppStop, - LoaderMessageTypeServiceStop, -} LoaderMessageType; - class LoaderMessageAppStart { public: + // This lock blocks anyone from starting an app as long + // as an app is already running via loader_start() + // This lock's lifecycle is not owned by this class. + std::shared_ptr api_lock = std::make_shared(); std::string id; std::shared_ptr _Nullable parameters; LoaderMessageAppStart() = default; + LoaderMessageAppStart(LoaderMessageAppStart& other) : + api_lock(other.api_lock), + id(other.id), + parameters(other.parameters) {} + LoaderMessageAppStart(const std::string& id, std::shared_ptr parameters) : id(id), parameters(std::move(parameters)) {} -}; -typedef struct { - LoaderStatus value; -} LoaderMessageLoaderStatusResult; + ~LoaderMessageAppStart() = default; -typedef struct { - bool value; -} LoaderMessageBoolResult; + std::shared_ptr getApiLockEventFlag() { return api_lock; } -class LoaderMessage { -public: - // This lock blocks anyone from starting an app as long - // as an app is already running via loader_start() - // This lock's lifecycle is not owned by this class. - EventFlag* _Nullable api_lock; - LoaderMessageType type; + uint32_t getApiLockEventFlagValue() { return 1; } - struct { - union { - // TODO: Convert to smart pointer - const LoaderMessageAppStart* start; - }; - } payload; - - struct { - union { - LoaderMessageLoaderStatusResult status_value; - LoaderMessageBoolResult bool_value; - void* raw_value; - }; - } result; - - LoaderMessage() { - api_lock = nullptr; - type = LoaderMessageTypeNone; - payload = { .start = nullptr }; - result = { .raw_value = nullptr }; - } - - LoaderMessage(const LoaderMessageAppStart* start, const LoaderMessageLoaderStatusResult& statusResult) { - api_lock = nullptr; - type = LoaderMessageTypeAppStart; - payload.start = start; - result.status_value = statusResult; - } - - LoaderMessage(LoaderMessageType messageType) { - api_lock = nullptr; - type = messageType; - payload = { .start = nullptr }; - result = { .raw_value = nullptr }; - } - - void setApiLock(EventFlag* eventFlag) { - api_lock = eventFlag; - } - - void cleanup() { - if (type == LoaderMessageTypeAppStart) { - delete payload.start; - } + void onProcessed() { + api_lock->set(1); } }; // endregion LoaderMessage struct Loader { - Thread* thread; std::shared_ptr pubsub_internal = std::make_shared(); std::shared_ptr pubsub_external = std::make_shared(); - MessageQueue queue = MessageQueue(2, sizeof(LoaderMessage)); // 2 entries, so you can stop the current app while starting a new one without blocking Mutex mutex = Mutex(Mutex::TypeRecursive); std::stack app_stack; + std::unique_ptr dispatcherThread = std::make_unique("loader_dispatcher", 6144); // Files app requires ~5k }; } // namespace diff --git a/Tactility/Source/Tactility.cpp b/Tactility/Source/Tactility.cpp index d49d136f..19ae635b 100644 --- a/Tactility/Source/Tactility.cpp +++ b/Tactility/Source/Tactility.cpp @@ -17,19 +17,19 @@ static const Configuration* config_instance = nullptr; namespace service { namespace gui { extern const ServiceManifest manifest; } namespace loader { extern const ServiceManifest manifest; } -#ifndef ESP_PLATFORM // Screenshots don't work yet on ESP32 + namespace statusbar { extern const ServiceManifest manifest; } +#if TT_FEATURE_SCREENSHOT_ENABLED namespace screenshot { extern const ServiceManifest manifest; } #endif - namespace statusbar { extern const ServiceManifest manifest; } } static const std::vector system_services = { &service::loader::manifest, &service::gui::manifest, // depends on loader service -#ifndef ESP_PLATFORM // Screenshots don't work yet on ESP32 - &service::screenshot::manifest, + &service::statusbar::manifest, +#if TT_FEATURE_SCREENSHOT_ENABLED + &service::screenshot::manifest #endif - &service::statusbar::manifest }; // endregion @@ -44,23 +44,24 @@ namespace app { namespace files { extern const AppManifest manifest; } namespace gpio { extern const AppManifest manifest; } namespace imageviewer { extern const AppManifest manifest; } - namespace screenshot { extern const AppManifest manifest; } - namespace settings { extern const AppManifest manifest; } namespace display { extern const AppManifest manifest; } namespace i2cscanner { extern const AppManifest manifest; } namespace i2csettings { extern const AppManifest manifest; } + namespace inputdialog { extern const AppManifest manifest; } namespace power { extern const AppManifest manifest; } namespace selectiondialog { extern const AppManifest manifest; } + namespace settings { extern const AppManifest manifest; } namespace systeminfo { extern const AppManifest manifest; } namespace textviewer { extern const AppManifest manifest; } namespace wifiapsettings { extern const AppManifest manifest; } namespace wificonnect { extern const AppManifest manifest; } namespace wifimanage { extern const AppManifest manifest; } +#if TT_FEATURE_SCREENSHOT_ENABLED + namespace screenshot { extern const AppManifest manifest; } +#endif #ifdef ESP_PLATFORM extern const AppManifest elfWrapperManifest; namespace crashdiagnostics { extern const AppManifest manifest; } -#else - namespace app::screenshot { extern const AppManifest manifest; } #endif } @@ -77,6 +78,7 @@ static const std::vector system_apps = { &app::gpio::manifest, &app::i2cscanner::manifest, &app::i2csettings::manifest, + &app::inputdialog::manifest, &app::imageviewer::manifest, &app::settings::manifest, &app::selectiondialog::manifest, @@ -85,11 +87,12 @@ static const std::vector system_apps = { &app::wifiapsettings::manifest, &app::wificonnect::manifest, &app::wifimanage::manifest, +#if TT_FEATURE_SCREENSHOT_ENABLED + &app::screenshot::manifest, +#endif #ifdef ESP_PLATFORM &app::crashdiagnostics::manifest, &app::elfWrapperManifest, // For hot-loading ELF apps -#else - &app::screenshot::manifest, // Screenshots don't work yet on ESP32 #endif }; @@ -166,12 +169,7 @@ void run(const Configuration& config) { register_user_apps(config.apps); TT_LOG_I(TAG, "init starting desktop app"); - service::loader::startApp(app::boot::manifest.id, true); - - if (config.autoStartAppId) { - TT_LOG_I(TAG, "init auto-starting %s", config.autoStartAppId); - service::loader::startApp(config.autoStartAppId, true); - } + service::loader::startApp(app::boot::manifest.id); TT_LOG_I(TAG, "init complete"); diff --git a/Tactility/Source/TactilityConfig.h b/Tactility/Source/TactilityConfig.h index 00f7ac0e..d554942e 100644 --- a/Tactility/Source/TactilityConfig.h +++ b/Tactility/Source/TactilityConfig.h @@ -1,7 +1,17 @@ #pragma once +#ifdef ESP_PLATFORM +#include "sdkconfig.h" +#endif + #include "TactilityHeadlessConfig.h" #define TT_CONFIG_APPS_LIMIT 32 #define TT_CONFIG_FORCE_ONSCREEN_KEYBOARD false // for development/debug purposes #define TT_SCREENSHOT_MODE false // for taking screenshots (e.g. forces SD card presence and Files tree on simulator) + +#ifdef ESP_PLATFORM +#define TT_FEATURE_SCREENSHOT_ENABLED (CONFIG_LV_USE_SNAPSHOT == 1 && CONFIG_SPIRAM_USE_MALLOC == 1) +#else // Sim +#define TT_FEATURE_SCREENSHOT_ENABLED true +#endif diff --git a/Tactility/Source/app/ElfApp.cpp b/Tactility/Source/app/ElfApp.cpp index 0bf63a90..26bec482 100644 --- a/Tactility/Source/app/ElfApp.cpp +++ b/Tactility/Source/app/ElfApp.cpp @@ -16,13 +16,13 @@ static size_t elfManifestSetCount = 0; std::unique_ptr elfFileData; esp_elf_t elf; -bool startElfApp(const char* filePath) { - TT_LOG_I(TAG, "Starting ELF %s", filePath); +bool startElfApp(const std::string& filePath) { + TT_LOG_I(TAG, "Starting ELF %s", filePath.c_str()); assert(elfFileData == nullptr); size_t size = 0; - elfFileData = file::readBinary(filePath, size); + elfFileData = file::readBinary(filePath.c_str(), size); if (elfFileData == nullptr) { return false; } diff --git a/Tactility/Source/app/ElfApp.h b/Tactility/Source/app/ElfApp.h index 3a3a3a42..55a9e4c3 100644 --- a/Tactility/Source/app/ElfApp.h +++ b/Tactility/Source/app/ElfApp.h @@ -6,7 +6,7 @@ namespace tt::app { -bool startElfApp(const char* filePath); +bool startElfApp(const std::string& filePath); void setElfAppManifest(const AppManifest& manifest); diff --git a/Tactility/Source/app/alertdialog/AlertDialog.cpp b/Tactility/Source/app/alertdialog/AlertDialog.cpp index 427774e7..a2901653 100644 --- a/Tactility/Source/app/alertdialog/AlertDialog.cpp +++ b/Tactility/Source/app/alertdialog/AlertDialog.cpp @@ -49,15 +49,13 @@ static std::string getTitleParameter(std::shared_ptr bundle) { static void onButtonClicked(lv_event_t* e) { lv_event_code_t code = lv_event_get_code(e); - if (code == LV_EVENT_CLICKED) { - auto index = reinterpret_cast(lv_event_get_user_data(e)); - TT_LOG_I(TAG, "Selected item at index %d", index); - tt::app::AppContext* app = service::loader::getCurrentApp(); - auto bundle = std::make_shared(); - setResultIndex(bundle, (int32_t)index); - app->setResult(app::ResultOk, bundle); - service::loader::stopApp(); - } + auto index = reinterpret_cast(lv_event_get_user_data(e)); + TT_LOG_I(TAG, "Selected item at index %d", index); + tt::app::AppContext* app = service::loader::getCurrentApp(); + auto bundle = std::make_shared(); + setResultIndex(bundle, (int32_t)index); + app->setResult(app::ResultOk, bundle); + service::loader::stopApp(); } static void createButton(lv_obj_t* parent, const std::string& text, size_t index) { @@ -65,7 +63,7 @@ static void createButton(lv_obj_t* parent, const std::string& text, size_t index lv_obj_t* button_label = lv_label_create(button); lv_obj_align(button_label, LV_ALIGN_CENTER, 0, 0); lv_label_set_text(button_label, text.c_str()); - lv_obj_add_event_cb(button, &onButtonClicked, LV_EVENT_CLICKED, (void*)index); + lv_obj_add_event_cb(button, &onButtonClicked, LV_EVENT_SHORT_CLICKED, (void*)index); } static void onShow(AppContext& app, lv_obj_t* parent) { @@ -78,6 +76,7 @@ static void onShow(AppContext& app, lv_obj_t* parent) { lv_obj_t* message_label = lv_label_create(parent); lv_obj_align(message_label, LV_ALIGN_CENTER, 0, 0); + lv_obj_set_width(message_label, LV_PCT(80)); std::string message; if (parameters->optString(PARAMETER_BUNDLE_KEY_MESSAGE, message)) { diff --git a/Tactility/Source/app/applist/AppList.cpp b/Tactility/Source/app/applist/AppList.cpp index b38eea4d..61ae1938 100644 --- a/Tactility/Source/app/applist/AppList.cpp +++ b/Tactility/Source/app/applist/AppList.cpp @@ -9,11 +9,8 @@ namespace tt::app::applist { static void onAppPressed(lv_event_t* e) { - lv_event_code_t code = lv_event_get_code(e); - if (code == LV_EVENT_CLICKED) { - const auto* manifest = static_cast(lv_event_get_user_data(e)); - service::loader::startApp(manifest->id, false); - } + const auto* manifest = static_cast(lv_event_get_user_data(e)); + service::loader::startApp(manifest->id, false); } static void createAppWidget(const AppManifest* manifest, void* parent) { @@ -21,7 +18,7 @@ static void createAppWidget(const AppManifest* manifest, void* parent) { auto* list = reinterpret_cast(parent); const void* icon = !manifest->icon.empty() ? manifest->icon.c_str() : TT_ASSETS_APP_ICON_FALLBACK; lv_obj_t* btn = lv_list_add_button(list, icon, manifest->name.c_str()); - lv_obj_add_event_cb(btn, &onAppPressed, LV_EVENT_CLICKED, (void*)manifest); + lv_obj_add_event_cb(btn, &onAppPressed, LV_EVENT_SHORT_CLICKED, (void*)manifest); } static void onShow(TT_UNUSED AppContext& app, lv_obj_t* parent) { diff --git a/Tactility/Source/app/boot/Boot.cpp b/Tactility/Source/app/boot/Boot.cpp index 0c7fb49f..5317f53b 100644 --- a/Tactility/Source/app/boot/Boot.cpp +++ b/Tactility/Source/app/boot/Boot.cpp @@ -4,29 +4,33 @@ #include "app/AppContext.h" #include "app/display/DisplaySettings.h" #include "hal/Display.h" -#include "kernel/PanicHandler.h" #include "service/loader/Loader.h" #include "lvgl/Style.h" #include "lvgl.h" +#include "Tactility.h" #ifdef ESP_PLATFORM +#include "kernel/PanicHandler.h" #include "sdkconfig.h" #else #define CONFIG_TT_SPLASH_DURATION 0 #endif +#define TAG "Boot" + namespace tt::app::boot { -static int32_t threadCallback(void* context); +static int32_t bootThreadCallback(void* context); +static void startNextApp(); struct Data { - Data() : thread("boot", 4096, threadCallback, this) {} + Data() : thread("boot", 4096, bootThreadCallback, this) {} Thread thread; }; -static int32_t threadCallback(TT_UNUSED void* context) { +static int32_t bootThreadCallback(TT_UNUSED void* context) { TickType_t start_time = tt::kernel::getTicks(); auto* lvgl_display = lv_display_get_default(); @@ -46,18 +50,33 @@ static int32_t threadCallback(TT_UNUSED void* context) { } tt::service::loader::stopApp(); + + + startNextApp(); + + return 0; +} + +static void startNextApp() { + auto config = tt::getConfiguration(); + std::string next_app; + if (config->autoStartAppId) { + TT_LOG_I(TAG, "init auto-starting %s", config->autoStartAppId); + next_app = config->autoStartAppId; + } else { + next_app = "Desktop"; + } + #ifdef ESP_PLATFORM esp_reset_reason_t reason = esp_reset_reason(); if (reason == ESP_RST_PANIC) { tt::service::loader::startApp("CrashDiagnostics"); } else { - tt::service::loader::startApp("Desktop"); + tt::service::loader::startApp(next_app); } #else - tt::service::loader::startApp("Desktop"); + tt::service::loader::startApp(next_app); #endif - - return 0; } static void onShow(TT_UNUSED AppContext& app, lv_obj_t* parent) { diff --git a/Tactility/Source/app/crashdiagnostics/CrashDiagnostics.cpp b/Tactility/Source/app/crashdiagnostics/CrashDiagnostics.cpp index 65d3453a..31618d10 100644 --- a/Tactility/Source/app/crashdiagnostics/CrashDiagnostics.cpp +++ b/Tactility/Source/app/crashdiagnostics/CrashDiagnostics.cpp @@ -21,7 +21,7 @@ static void onShow(TT_UNUSED AppContext& app, lv_obj_t* parent) { auto* display = lv_obj_get_display(parent); int32_t parent_height = lv_display_get_vertical_resolution(display) - STATUSBAR_HEIGHT; - lv_obj_add_event_cb(parent, onContinuePressed, LV_EVENT_CLICKED, nullptr); + lv_obj_add_event_cb(parent, onContinuePressed, LV_EVENT_SHORT_CLICKED, nullptr); auto* top_label = lv_label_create(parent); lv_label_set_text(top_label, "Oops! We've crashed ..."); // TODO: Funny messages lv_obj_align(top_label, LV_ALIGN_TOP_MID, 0, 2); diff --git a/Tactility/Source/app/desktop/Desktop.cpp b/Tactility/Source/app/desktop/Desktop.cpp index 7c86fefb..ac0decdb 100644 --- a/Tactility/Source/app/desktop/Desktop.cpp +++ b/Tactility/Source/app/desktop/Desktop.cpp @@ -28,7 +28,7 @@ static lv_obj_t* createAppButton(lv_obj_t* parent, const char* title, const char auto* button_image = lv_image_create(apps_button); lv_image_set_src(button_image, imageFile); - lv_obj_add_event_cb(apps_button, onAppPressed, LV_EVENT_CLICKED, (void*)appId); + lv_obj_add_event_cb(apps_button, onAppPressed, LV_EVENT_SHORT_CLICKED, (void*)appId); lv_obj_set_style_image_recolor(button_image, lv_theme_get_color_primary(parent), 0); lv_obj_set_style_image_recolor_opa(button_image, LV_OPA_COVER, 0); diff --git a/Tactility/Source/app/files/FileUtils.cpp b/Tactility/Source/app/files/FileUtils.cpp index 291d7946..7937d0ca 100644 --- a/Tactility/Source/app/files/FileUtils.cpp +++ b/Tactility/Source/app/files/FileUtils.cpp @@ -1,97 +1,99 @@ #include "FileUtils.h" #include "TactilityCore.h" -#include #include +#include namespace tt::app::files { #define TAG "file_utils" -#define SCANDIR_LIMIT 128 +std::string getChildPath(const std::string& basePath, const std::string& childPath) { + // Postfix with "/" when the current path isn't "/" + if (basePath.length() != 1) { + return basePath + "/" + childPath; + } else { + return "/" + childPath; + } +} int dirent_filter_dot_entries(const struct dirent* entry) { return (strcmp(entry->d_name, "..") == 0 || strcmp(entry->d_name, ".") == 0) ? -1 : 0; } -int dirent_sort_alpha_and_type(const struct dirent** left, const struct dirent** right) { - bool left_is_dir = (*left)->d_type == TT_DT_DIR || (*left)->d_type == TT_DT_CHR; - bool right_is_dir = (*right)->d_type == TT_DT_DIR || (*right)->d_type == TT_DT_CHR; +bool dirent_sort_alpha_and_type(const struct dirent& left, const struct dirent& right) { + bool left_is_dir = left.d_type == TT_DT_DIR || left.d_type == TT_DT_CHR; + bool right_is_dir = right.d_type == TT_DT_DIR || right.d_type == TT_DT_CHR; if (left_is_dir == right_is_dir) { - return strcmp((*left)->d_name, (*right)->d_name); + return strcmp(left.d_name, right.d_name) < 0; } else { - return (left_is_dir < right_is_dir) ? 1 : -1; + return left_is_dir > right_is_dir; } } -int dirent_sort_alpha(const struct dirent** left, const struct dirent** right) { - return strcmp((*left)->d_name, (*right)->d_name); -} - int scandir( - const char* path, - struct dirent*** output, - ScandirFilter _Nullable filter, - ScandirSort _Nullable sort + const std::string& path, + std::vector& outList, + ScandirFilter _Nullable filterMethod, + ScandirSort _Nullable sortMethod ) { - DIR* dir = opendir(path); + DIR* dir = opendir(path.c_str()); if (dir == nullptr) { - TT_LOG_E(TAG, "Failed to open dir %s", path); + TT_LOG_E(TAG, "Failed to open dir %s", path.c_str()); return -1; } - *output = static_cast(malloc(sizeof(void*) * SCANDIR_LIMIT)); - if (*output == nullptr) { - TT_LOG_E(TAG, "Out of memory"); - closedir(dir); - return -1; - } - - struct dirent** dirent_array = *output; - int next_dirent_index = 0; - struct dirent* current_entry; - bool out_of_memory = false; while ((current_entry = readdir(dir)) != nullptr) { - if (filter(current_entry) == 0) { - dirent_array[next_dirent_index] = static_cast(malloc(sizeof(struct dirent))); - if (dirent_array[next_dirent_index] != nullptr) { - memcpy(dirent_array[next_dirent_index], current_entry, sizeof(struct dirent)); - - next_dirent_index++; - if (next_dirent_index >= SCANDIR_LIMIT) { - TT_LOG_E(TAG, "Directory has more than %d files", SCANDIR_LIMIT); - break; - } - } else { - TT_LOG_E(TAG, "Alloc failed. Aborting and cleaning up."); - out_of_memory = true; - break; - } - } - } - - // Out-of-memory clean-up - if (out_of_memory && next_dirent_index > 0) { - for (int i = 0; i < next_dirent_index; ++i) { - TT_LOG_I(TAG, "Cleanup item %d", i); - free(dirent_array[i]); - } - TT_LOG_I(TAG, "Free"); - free(*output); - closedir(dir); - return -1; - // Empty directory - } else if (next_dirent_index == 0) { - free(*output); - *output = nullptr; - } else { - if (sort) { - qsort(dirent_array, next_dirent_index, sizeof(struct dirent*), (__compar_fn_t)sort); + if (filterMethod(current_entry) == 0) { + outList.push_back(*current_entry); } } closedir(dir); - return next_dirent_index; + + if (sortMethod != nullptr) { + sort(outList.begin(), outList.end(), sortMethod); + } + + return (int)outList.size(); }; +bool isSupportedExecutableFile(const std::string& filename) { +#ifdef ESP_PLATFORM + // Currently only the PNG library is built into Tactility + return filename.ends_with(".elf"); +#else + return false; +#endif +} + +template +std::basic_string lowercase(const std::basic_string& input) { + std::basic_string output = input; + std::transform( + output.begin(), + output.end(), + output.begin(), + [](const T character) { return static_cast(std::tolower(character)); } + ); + return std::move(output); +} + +bool isSupportedImageFile(const std::string& filename) { + // Currently only the PNG library is built into Tactility + return lowercase(filename).ends_with(".png"); +} + +bool isSupportedTextFile(const std::string& filename) { + std::string filename_lower = lowercase(filename); + return filename_lower.ends_with(".txt") || + filename_lower.ends_with(".ini") || + filename_lower.ends_with(".json") || + filename_lower.ends_with(".yaml") || + filename_lower.ends_with(".yml") || + filename_lower.ends_with(".lua") || + filename_lower.ends_with(".js") || + filename_lower.ends_with(".properties"); +} + } diff --git a/Tactility/Source/app/files/FileUtils.h b/Tactility/Source/app/files/FileUtils.h index 054ed4da..c384de63 100644 --- a/Tactility/Source/app/files/FileUtils.h +++ b/Tactility/Source/app/files/FileUtils.h @@ -1,6 +1,8 @@ #pragma once #include +#include +#include namespace tt::app::files { @@ -26,19 +28,13 @@ enum { #define TT_DT_WHT TT_DT_WHT // Whiteout inodes }; +std::string getChildPath(const std::string& basePath, const std::string& childPath); + typedef int (*ScandirFilter)(const struct dirent*); -typedef int (*ScandirSort)(const struct dirent**, const struct dirent**); +typedef bool (*ScandirSort)(const struct dirent&, const struct dirent&); -/** - * Alphabetic sorting function for tt_scandir() - * @param left left-hand side part for comparison - * @param right right-hand side part for comparison - * @return 0, -1 or 1 - */ -int dirent_sort_alpha(const struct dirent** left, const struct dirent** right); - -int dirent_sort_alpha_and_type(const struct dirent** left, const struct dirent** right); +bool dirent_sort_alpha_and_type(const struct dirent& left, const struct dirent& right); int dirent_filter_dot_entries(const struct dirent* entry); @@ -49,16 +45,20 @@ int dirent_filter_dot_entries(const struct dirent* entry); * The caller is responsible for free-ing the memory of these. * * @param[in] path path the scan for files and directories - * @param[out] output a pointer to an array of dirent* + * @param[out] outList a pointer to vector of dirent * @param[in] filter an optional filter to filter out specific items * @param[in] sort an optional sorting function * @return the amount of items that were stored in "output" or -1 when an error occurred */ int scandir( - const char* path, - struct dirent*** output, + const std::string& path, + std::vector& outList, ScandirFilter _Nullable filter, ScandirSort _Nullable sort ); +bool isSupportedExecutableFile(const std::string& filename); +bool isSupportedImageFile(const std::string& filename); +bool isSupportedTextFile(const std::string& filename); + } // namespace diff --git a/Tactility/Source/app/files/Files.cpp b/Tactility/Source/app/files/Files.cpp index 1640465c..3149a282 100644 --- a/Tactility/Source/app/files/Files.cpp +++ b/Tactility/Source/app/files/Files.cpp @@ -1,21 +1,8 @@ -#include "FilesData.h" +#include "Files.h" #include "app/AppContext.h" -#include "Tactility.h" #include "Assets.h" -#include "Check.h" -#include "FileUtils.h" -#include "StringUtils.h" -#include "app/ElfApp.h" -#include "app/imageviewer/ImageViewer.h" -#include "app/textviewer/TextViewer.h" -#include "lvgl.h" -#include "service/loader/Loader.h" -#include "lvgl/Toolbar.h" -#include -#include #include -#include namespace tt::app::files { @@ -24,231 +11,21 @@ namespace tt::app::files { extern const AppManifest manifest; /** Returns the app data if the app is active. Note that this could clash if the same app is started twice and a background thread is slow. */ -std::shared_ptr _Nullable optData() { - app::AppContext* app = service::loader::getCurrentApp(); - if (app->getManifest().id == manifest.id) { - return std::static_pointer_cast(app->getData()); - } else { - return nullptr; - } -} - -/** - * Case-insensitive check to see if the given file matches the provided file extension. - * @param path the full path to the file - * @param extension the extension to look for, including the period symbol, in lower case - * @return true on match - */ -static bool hasFileExtension(const char* path, const char* extension) { - size_t postfix_len = strlen(extension); - size_t base_len = strlen(path); - if (base_len < postfix_len) { - return false; - } - - for (int i = (int)postfix_len - 1; i >= 0; i--) { - if (tolower(path[base_len - postfix_len + i]) != tolower(extension[i])) { - return false; - } - } - - return true; -} - -static bool isSupportedExecutableFile(const char* filename) { -#ifdef ESP_PLATFORM - // Currently only the PNG library is built into Tactility - return hasFileExtension(filename, ".elf"); -#else - return false; -#endif -} - -static bool isSupportedImageFile(const char* filename) { - // Currently only the PNG library is built into Tactility - return hasFileExtension(filename, ".png"); -} - -static bool isSupportedTextFile(const char* filename) { - return hasFileExtension(filename, ".txt") || - hasFileExtension(filename, ".ini") || - hasFileExtension(filename, ".json") || - hasFileExtension(filename, ".yaml") || - hasFileExtension(filename, ".yml") || - hasFileExtension(filename, ".lua") || - hasFileExtension(filename, ".js") || - hasFileExtension(filename, ".properties"); -} - -// region Views - -static void updateViews(std::shared_ptr data); - -static void onNavigateUpPressed(TT_UNUSED lv_event_t* event) { - auto files_data = optData(); - if (files_data == nullptr) { - return; - } - - if (strcmp(files_data->current_path, "/") != 0) { - TT_LOG_I(TAG, "Navigating upwards"); - char new_absolute_path[MAX_PATH_LENGTH]; - if (string::getPathParent(files_data->current_path, new_absolute_path)) { - data_set_entries_for_path(files_data, new_absolute_path); - } - } - - updateViews(files_data); -} - -static void viewFile(const char* path, const char* filename) { - size_t path_len = strlen(path); - size_t filename_len = strlen(filename); - char* filepath = static_cast(malloc(path_len + filename_len + 2)); - sprintf(filepath, "%s/%s", path, filename); - - // For PC we need to make the path relative to the current work directory, - // because that's how LVGL maps its 'drive letter' to the file system. - char* processed_filepath; - if (kernel::getPlatform() == kernel::PlatformSimulator) { - char cwd[PATH_MAX]; - if (getcwd(cwd, sizeof(cwd)) == nullptr) { - TT_LOG_E(TAG, "Failed to get current working directory"); - return; - } - if (!strstr(filepath, cwd)) { - TT_LOG_E(TAG, "Can only work with files in working directory %s", cwd); - return; - } - char* substr = filepath + strlen(cwd); - processed_filepath = substr; - } else { - processed_filepath = filepath; - } - - TT_LOG_I(TAG, "Clicked %s", filepath); - - if (isSupportedExecutableFile(filename)) { -#ifdef ESP_PLATFORM - app::startElfApp(processed_filepath); -#endif - } else if (isSupportedImageFile(filename)) { - auto bundle = std::make_shared(); - bundle->putString(IMAGE_VIEWER_FILE_ARGUMENT, processed_filepath); - service::loader::startApp("ImageViewer", false, bundle); - } else if (isSupportedTextFile(filename)) { - auto bundle = std::make_shared(); - if (kernel::getPlatform() == kernel::PlatformEsp) { - bundle->putString(TEXT_VIEWER_FILE_ARGUMENT, processed_filepath); - } else { - // Remove forward slash, because we need a relative path - bundle->putString(TEXT_VIEWER_FILE_ARGUMENT, processed_filepath + 1); - } - service::loader::startApp("TextViewer", false, bundle); - } else { - TT_LOG_W(TAG, "opening files of this type is not supported"); - } - - free(filepath); -} - -static void onFilePressed(lv_event_t* event) { - auto files_data = optData(); - if (files_data == nullptr) { - return; - } - - lv_event_code_t code = lv_event_get_code(event); - if (code == LV_EVENT_CLICKED) { - auto* dir_entry = static_cast(lv_event_get_user_data(event)); - TT_LOG_I(TAG, "Pressed %s %d", dir_entry->d_name, dir_entry->d_type); - - switch (dir_entry->d_type) { - case TT_DT_DIR: - case TT_DT_CHR: - data_set_entries_for_child_path(files_data, dir_entry->d_name); - updateViews(files_data); - break; - case TT_DT_LNK: - TT_LOG_W(TAG, "opening links is not supported"); - break; - case TT_DT_REG: - viewFile(files_data->current_path, dir_entry->d_name); - break; - default: - // Assume it's a file - // TODO: Find a better way to identify a file - viewFile(files_data->current_path, dir_entry->d_name); - break; - } - } -} - -static void createFileWidget(lv_obj_t* parent, struct dirent* dir_entry) { - tt_check(parent); - auto* list = (lv_obj_t*)parent; - const char* symbol; - if (dir_entry->d_type == TT_DT_DIR || dir_entry->d_type == TT_DT_CHR) { - symbol = LV_SYMBOL_DIRECTORY; - } else if (isSupportedImageFile(dir_entry->d_name)) { - symbol = LV_SYMBOL_IMAGE; - } else if (dir_entry->d_type == TT_DT_LNK) { - symbol = LV_SYMBOL_LOOP; - } else { - symbol = LV_SYMBOL_FILE; - } - lv_obj_t* button = lv_list_add_button(list, symbol, dir_entry->d_name); - lv_obj_add_event_cb(button, &onFilePressed, LV_EVENT_CLICKED, (void*)dir_entry); -} - -static void updateViews(std::shared_ptr data) { - lv_obj_clean(data->list); - for (int i = 0; i < data->dir_entries_count; ++i) { - TT_LOG_D(TAG, "Entry: %s %d", data->dir_entries[i]->d_name, data->dir_entries[i]->d_type); - createFileWidget(data->list, data->dir_entries[i]); - } -} - -// endregion Views - -// region Lifecycle static void onShow(AppContext& app, lv_obj_t* parent) { - auto data = std::static_pointer_cast(app.getData()); - - lv_obj_set_flex_flow(parent, LV_FLEX_FLOW_COLUMN); - - lv_obj_t* toolbar = lvgl::toolbar_create(parent, "Files"); - lvgl::toolbar_add_button_action(toolbar, LV_SYMBOL_UP, &onNavigateUpPressed, nullptr); - - data->list = lv_list_create(parent); - lv_obj_set_width(data->list, LV_PCT(100)); - lv_obj_set_flex_grow(data->list, 1); - - updateViews(data); + auto files = std::static_pointer_cast(app.getData()); + files->onShow(parent); } static void onStart(AppContext& app) { - auto* test = new uint32_t; - delete test; - auto data = std::make_shared(); - // PC platform is bound to current work directory because of the LVGL file system mapping - if (kernel::getPlatform() == kernel::PlatformSimulator) { - char cwd[PATH_MAX]; - if (getcwd(cwd, sizeof(cwd)) != nullptr) { - data_set_entries_for_path(data, cwd); - } else { - TT_LOG_E(TAG, "Failed to get current work directory files"); - data_set_entries_for_path(data, "/"); - } - } else { - data_set_entries_for_path(data, "/"); - } - - app.setData(data); + auto files = std::make_shared(); + app.setData(files); } -// endregion Lifecycle +static void onResult(AppContext& app, Result result, const Bundle& bundle) { + auto files = std::static_pointer_cast(app.getData()); + files->onResult(result, bundle); +} extern const AppManifest manifest = { .id = "Files", @@ -257,6 +34,7 @@ extern const AppManifest manifest = { .type = TypeHidden, .onStart = onStart, .onShow = onShow, + .onResult = onResult }; } // namespace diff --git a/Tactility/Source/app/files/Files.h b/Tactility/Source/app/files/Files.h new file mode 100644 index 00000000..eda259f8 --- /dev/null +++ b/Tactility/Source/app/files/Files.h @@ -0,0 +1,33 @@ +#pragma once + +#include "View.h" +#include "State.h" +#include "app/AppManifest.h" + +#include +#include +#include + +namespace tt::app::files { + +class Files { + std::unique_ptr view; + std::shared_ptr state; + +public: + Files() { + state = std::make_shared(); + view = std::make_unique(state); + } + + void onShow(lv_obj_t* parent) { + view->init(parent); + } + + void onResult(Result result, const Bundle& bundle) { + view->onResult(result, bundle); + } +}; + + +} // namespace diff --git a/Tactility/Source/app/files/FilesData.cpp b/Tactility/Source/app/files/FilesData.cpp deleted file mode 100644 index 99aa510a..00000000 --- a/Tactility/Source/app/files/FilesData.cpp +++ /dev/null @@ -1,96 +0,0 @@ -#include -#include "FilesData.h" -#include "FileUtils.h" -#include "StringUtils.h" -#include "Tactility.h" - -namespace tt::app::files { - -#define TAG "files_app" - -static bool get_child_path(char* base_path, const char* child_path, char* out_path, size_t max_chars) { - size_t current_path_length = strlen(base_path); - size_t added_path_length = strlen(child_path); - size_t total_path_length = current_path_length + added_path_length + 1; // two paths with `/` - - if (total_path_length >= max_chars) { - TT_LOG_E(TAG, "Path limit reached (%d chars)", MAX_PATH_LENGTH); - return false; - } else { - // Postfix with "/" when the current path isn't "/" - if (current_path_length != 1) { - sprintf(out_path, "%s/%s", base_path, child_path); - } else { - sprintf(out_path, "/%s", child_path); - } - return true; - } -} - -static void data_set_entries(std::shared_ptr data, struct dirent** entries, int count) { - if (data->dir_entries != nullptr) { - data->freeEntries(); - } - - data->dir_entries = entries; - data->dir_entries_count = count; -} - -bool data_set_entries_for_path(std::shared_ptr data, const char* path) { - TT_LOG_I(TAG, "Changing path: %s -> %s", data->current_path, path); - - /** - * ESP32 does not have a root directory, so we have to create it manually. - * We'll add the NVS Flash partitions and the binding for the sdcard. - */ -#if TT_SCREENSHOT_MODE - bool show_custom_root = true; -#else - bool show_custom_root = (kernel::getPlatform() == kernel::PlatformEsp && strcmp(path, "/") == 0); -#endif - if (show_custom_root) { - int dir_entries_count = 3; - auto** dir_entries = (dirent**)malloc(sizeof(struct dirent*) * 3); - - dir_entries[0] = (dirent*)malloc(sizeof(struct dirent)); - dir_entries[0]->d_type = TT_DT_DIR; - strcpy(dir_entries[0]->d_name, "assets"); - - dir_entries[1] = (dirent*)malloc(sizeof(struct dirent)); - dir_entries[1]->d_type = TT_DT_DIR; - strcpy(dir_entries[1]->d_name, "config"); - - dir_entries[2] = (dirent*)malloc(sizeof(struct dirent)); - dir_entries[2]->d_type = TT_DT_DIR; - strcpy(dir_entries[2]->d_name, "sdcard"); - - data_set_entries(data, dir_entries, dir_entries_count); - strcpy(data->current_path, path); - return true; - } else { - struct dirent** entries = nullptr; - int count = tt::app::files::scandir(path, &entries, &dirent_filter_dot_entries, &dirent_sort_alpha_and_type); - if (count >= 0) { - TT_LOG_I(TAG, "%s has %u entries", path, count); - data_set_entries(data, entries, count); - strcpy(data->current_path, path); - return true; - } else { - TT_LOG_E(TAG, "Failed to fetch entries for %s", path); - return false; - } - } -} - -bool data_set_entries_for_child_path(std::shared_ptr data, const char* child_path) { - char new_absolute_path[MAX_PATH_LENGTH + 1]; - if (get_child_path(data->current_path, child_path, new_absolute_path, MAX_PATH_LENGTH)) { - TT_LOG_I(TAG, "Navigating from %s to %s", data->current_path, new_absolute_path); - return data_set_entries_for_path(data, new_absolute_path); - } else { - TT_LOG_I(TAG, "Failed to get child path for %s/%s", data->current_path, child_path); - return false; - } -} - -} // namespace diff --git a/Tactility/Source/app/files/FilesData.h b/Tactility/Source/app/files/FilesData.h deleted file mode 100644 index 43d14160..00000000 --- a/Tactility/Source/app/files/FilesData.h +++ /dev/null @@ -1,36 +0,0 @@ -#pragma once - -#include "lvgl.h" -#include -#include - -namespace tt::app::files { - -#define MAX_PATH_LENGTH 256 - -struct Data { - char current_path[MAX_PATH_LENGTH] = { 0 }; - struct dirent** dir_entries = nullptr; - int dir_entries_count = 0; - lv_obj_t* list = nullptr; - - void freeEntries() { - for (int i = 0; i < dir_entries_count; ++i) { - free(dir_entries[i]); - } - free(dir_entries); - dir_entries = nullptr; - dir_entries_count = 0; - } - - ~Data() { - freeEntries(); - } -}; - -void data_free(std::shared_ptr data); -void data_free_entries(std::shared_ptr data); -bool data_set_entries_for_child_path(std::shared_ptr data, const char* child_path); -bool data_set_entries_for_path(std::shared_ptr data, const char* path); - -} // namespace diff --git a/Tactility/Source/app/files/State.cpp b/Tactility/Source/app/files/State.cpp new file mode 100644 index 00000000..2ff288cf --- /dev/null +++ b/Tactility/Source/app/files/State.cpp @@ -0,0 +1,106 @@ +#include "State.h" +#include "kernel/Kernel.h" +#include "Log.h" +#include "FileUtils.h" + +#include + +#define TAG "files_app" + +namespace tt::app::files { + +State::State() { + if (kernel::getPlatform() == kernel::PlatformSimulator) { + char cwd[PATH_MAX]; + if (getcwd(cwd, sizeof(cwd)) != nullptr) { + setEntriesForPath(cwd); + } else { + TT_LOG_E(TAG, "Failed to get current work directory files"); + setEntriesForPath("/"); + } + } else { + setEntriesForPath("/"); + } +} + +std::string State::getSelectedChildPath() const { + return getChildPath(current_path, selected_child_entry); +} + +bool State::setEntriesForPath(const std::string& path) { + auto scoped_lock = mutex.scoped(); + if (!scoped_lock->lock(100)) { + TT_LOG_E(TAG, LOG_MESSAGE_MUTEX_LOCK_FAILED_FMT, "setEntriesForPath"); + return false; + } + + TT_LOG_I(TAG, "Changing path: %s -> %s", current_path.c_str(), path.c_str()); + + /** + * ESP32 does not have a root directory, so we have to create it manually. + * We'll add the NVS Flash partitions and the binding for the sdcard. + */ +#if TT_SCREENSHOT_MODE + bool show_custom_root = true; +#else + bool show_custom_root = (kernel::getPlatform() == kernel::PlatformEsp) && (path == "/"); +#endif + if (show_custom_root) { + TT_LOG_I(TAG, "Setting custom root"); + dir_entries.clear(); + dir_entries.push_back({ + .d_ino = 0, + .d_type = TT_DT_DIR, + .d_name = "assets" + }); + dir_entries.push_back({ + .d_ino = 1, + .d_type = TT_DT_DIR, + .d_name = "config" + }); + dir_entries.push_back({ + .d_ino = 2, + .d_type = TT_DT_DIR, + .d_name = "sdcard" + }); + + current_path = path; + selected_child_entry = ""; + action = ActionNone; + return true; + } else { + dir_entries.clear(); + int count = tt::app::files::scandir(path, dir_entries, &dirent_filter_dot_entries, dirent_sort_alpha_and_type); + if (count >= 0) { + TT_LOG_I(TAG, "%s has %u entries", path.c_str(), count); + current_path = path; + selected_child_entry = ""; + action = ActionNone; + return true; + } else { + TT_LOG_E(TAG, "Failed to fetch entries for %s", path.c_str()); + return false; + } + } +} + +bool State::setEntriesForChildPath(const std::string& child_path) { + auto path = getChildPath(current_path, child_path); + TT_LOG_I(TAG, "Navigating from %s to %s", current_path.c_str(), path.c_str()); + return setEntriesForPath(path); +} + +bool State::getDirent(uint32_t index, dirent& dirent) { + auto scoped_mutex = mutex.scoped(); + if (!scoped_mutex->lock(50 / portTICK_PERIOD_MS)) { + return false; + } + + if (index < dir_entries.size()) { + dirent = dir_entries[index]; + return true; + } else { + return false; + } +} +} diff --git a/Tactility/Source/app/files/State.h b/Tactility/Source/app/files/State.h new file mode 100644 index 00000000..d39a09c6 --- /dev/null +++ b/Tactility/Source/app/files/State.h @@ -0,0 +1,74 @@ +#pragma once + +#include +#include +#include +#include "Mutex.h" + +namespace tt::app::files { + +class State { + +public: + + enum PendingAction { + ActionNone, + ActionDelete, + ActionRename + }; + +private: + + Mutex mutex = Mutex(Mutex::TypeRecursive); + std::vector dir_entries; + std::string current_path; + std::string selected_child_entry; + PendingAction action = ActionNone; + +public: + + + State(); + + void freeEntries() { + dir_entries.clear(); + } + + ~State() { + freeEntries(); + } + + bool setEntriesForChildPath(const std::string& child_path); + bool setEntriesForPath(const std::string& path); + + const std::vector& lockEntries() const { + mutex.lock(TtWaitForever); + return dir_entries; + } + + void unlockEntries() { + mutex.unlock(); + } + + bool getDirent(uint32_t index, dirent& dirent); + + void setSelectedChildEntry(const std::string& newFile) { + selected_child_entry = newFile; + action = ActionNone; + } + + std::string getSelectedChildEntry() const { return selected_child_entry; } + std::string getCurrentPath() const { return current_path; } + + std::string getSelectedChildPath() const; + + PendingAction getPendingAction() const { + return action; + } + + void setPendingAction(PendingAction newAction) { + action = newAction; + } +}; + +} diff --git a/Tactility/Source/app/files/View.cpp b/Tactility/Source/app/files/View.cpp new file mode 100644 index 00000000..8bbb0296 --- /dev/null +++ b/Tactility/Source/app/files/View.cpp @@ -0,0 +1,329 @@ +#include +#include "app/alertdialog/AlertDialog.h" +#include "app/imageviewer/ImageViewer.h" +#include "app/inputdialog/InputDialog.h" +#include "app/textviewer/TextViewer.h" +#include "app/ElfApp.h" +#include "lvgl/Toolbar.h" +#include "lvgl/LvglSync.h" +#include "service/loader/Loader.h" +#include "FileUtils.h" +#include "Tactility.h" +#include "View.h" +#include "StringUtils.h" +#include + +#define TAG "files_app" + +namespace tt::app::files { + +// region Callbacks + +static void dirEntryListScrollBeginCallback(lv_event_t* event) { + auto* view = (View*)lv_event_get_user_data(event); + view->onDirEntryListScrollBegin(); +} + +static void onDirEntryPressedCallback(lv_event_t* event) { + auto* view = (View*)lv_event_get_user_data(event); + auto* button = lv_event_get_target_obj(event); + auto index = lv_obj_get_index(button); + view->onDirEntryPressed(index); +} + +static void onDirEntryLongPressedCallback(lv_event_t* event) { + auto* view = (View*)lv_event_get_user_data(event); + auto* button = lv_event_get_target_obj(event); + auto index = lv_obj_get_index(button); + view->onDirEntryLongPressed(index); +} + +static void onRenamePressedCallback(lv_event_t* event) { + auto* view = (View*)lv_event_get_user_data(event); + view->onRenamePressed(); +} + +static void onDeletePressedCallback(lv_event_t* event) { + auto* view = (View*)lv_event_get_user_data(event); + view->onDeletePressed(); +} + +static void onNavigateUpPressedCallback(TT_UNUSED lv_event_t* event) { + auto* view = (View*)lv_event_get_user_data(event); + view->onNavigateUpPressed(); +} + +// endregion + +void View::viewFile(const std::string& path, const std::string& filename) { + std::string file_path = path + "/" + filename; + + // For PC we need to make the path relative to the current work directory, + // because that's how LVGL maps its 'drive letter' to the file system. + std::string processed_filepath; + if (kernel::getPlatform() == kernel::PlatformSimulator) { + char cwd[PATH_MAX]; + if (getcwd(cwd, sizeof(cwd)) == nullptr) { + TT_LOG_E(TAG, "Failed to get current working directory"); + return; + } + if (!file_path.starts_with(cwd)) { + TT_LOG_E(TAG, "Can only work with files in working directory %s", cwd); + return; + } + processed_filepath = file_path.substr(strlen(cwd)); + } else { + processed_filepath = file_path; + } + + TT_LOG_I(TAG, "Clicked %s", file_path.c_str()); + + if (isSupportedExecutableFile(filename)) { +#ifdef ESP_PLATFORM + app::startElfApp(processed_filepath); +#endif + } else if (isSupportedImageFile(filename)) { + auto bundle = std::make_shared(); + bundle->putString(IMAGE_VIEWER_FILE_ARGUMENT, processed_filepath); + service::loader::startApp("ImageViewer", false, bundle); + } else if (isSupportedTextFile(filename)) { + auto bundle = std::make_shared(); + if (kernel::getPlatform() == kernel::PlatformEsp) { + bundle->putString(TEXT_VIEWER_FILE_ARGUMENT, processed_filepath); + } else { + // Remove forward slash, because we need a relative path + bundle->putString(TEXT_VIEWER_FILE_ARGUMENT, processed_filepath.substr(1)); + } + service::loader::startApp("TextViewer", false, bundle); + } else { + TT_LOG_W(TAG, "opening files of this type is not supported"); + } + + onNavigate(); +} + +void View::onDirEntryPressed(uint32_t index) { + dirent dir_entry; + if (state->getDirent(index, dir_entry)) { + TT_LOG_I(TAG, "Pressed %s %d", dir_entry.d_name, dir_entry.d_type); + state->setSelectedChildEntry(dir_entry.d_name); + switch (dir_entry.d_type) { + case TT_DT_DIR: + case TT_DT_CHR: + state->setEntriesForChildPath(dir_entry.d_name); + onNavigate(); + update(); + break; + case TT_DT_LNK: + TT_LOG_W(TAG, "opening links is not supported"); + break; + case TT_DT_REG: + viewFile(state->getCurrentPath(), dir_entry.d_name); + onNavigate(); + break; + default: + // Assume it's a file + // TODO: Find a better way to identify a file + viewFile(state->getCurrentPath(), dir_entry.d_name); + onNavigate(); + break; + } + } +} + +void View::onDirEntryLongPressed(int32_t index) { + dirent dir_entry; + if (state->getDirent(index, dir_entry)) { + TT_LOG_I(TAG, "Pressed %s %d", dir_entry.d_name, dir_entry.d_type); + state->setSelectedChildEntry(dir_entry.d_name); + switch (dir_entry.d_type) { + case TT_DT_DIR: + case TT_DT_CHR: + showActionsForDirectory(); + break; + case TT_DT_LNK: + TT_LOG_W(TAG, "opening links is not supported"); + break; + case TT_DT_REG: + showActionsForFile(); + break; + default: + // Assume it's a file + // TODO: Find a better way to identify a file + showActionsForFile(); + break; + } + } +} + + +void View::createDirEntryWidget(lv_obj_t* parent, struct dirent& dir_entry) { + tt_check(parent); + auto* list = (lv_obj_t*)parent; + const char* symbol; + if (dir_entry.d_type == TT_DT_DIR || dir_entry.d_type == TT_DT_CHR) { + symbol = LV_SYMBOL_DIRECTORY; + } else if (isSupportedImageFile(dir_entry.d_name)) { + symbol = LV_SYMBOL_IMAGE; + } else if (dir_entry.d_type == TT_DT_LNK) { + symbol = LV_SYMBOL_LOOP; + } else { + symbol = LV_SYMBOL_FILE; + } + lv_obj_t* button = lv_list_add_button(list, symbol, dir_entry.d_name); + lv_obj_add_event_cb(button, &onDirEntryPressedCallback, LV_EVENT_SHORT_CLICKED, this); + lv_obj_add_event_cb(button, &onDirEntryLongPressedCallback, LV_EVENT_LONG_PRESSED, this); +} + +void View::onNavigateUpPressed() { + if (state->getCurrentPath() != "/") { + TT_LOG_I(TAG, "Navigating upwards"); + std::string new_absolute_path; + if (string::getPathParent(state->getCurrentPath(), new_absolute_path)) { + state->setEntriesForPath(new_absolute_path); + } + onNavigate(); + update(); + } +} + +void View::onRenamePressed() { + std::string entry_name = state->getSelectedChildEntry(); + TT_LOG_I(TAG, "Pending rename %s", entry_name.c_str()); + state->setPendingAction(State::ActionRename); + app::inputdialog::start("Rename", "", entry_name); +} + +void View::onDeletePressed() { + std::string file_path = state->getSelectedChildPath(); + TT_LOG_I(TAG, "Pending delete %s", file_path.c_str()); + state->setPendingAction(State::ActionDelete); + std::string message = "Do you want to delete this?\n" + file_path; + const std::vector choices = { "Yes", "No" }; + app::alertdialog::start("Are you sure?", message, choices); +} + +void View::showActionsForDirectory() { + lv_obj_clean(action_list); + + auto* rename_button = lv_list_add_button(action_list, LV_SYMBOL_EDIT, "Rename"); + lv_obj_add_event_cb(rename_button, onRenamePressedCallback, LV_EVENT_SHORT_CLICKED, this); + + lv_obj_remove_flag(action_list, LV_OBJ_FLAG_HIDDEN); +} + +void View::showActionsForFile() { + lv_obj_clean(action_list); + + auto* rename_button = lv_list_add_button(action_list, LV_SYMBOL_EDIT, "Rename"); + lv_obj_add_event_cb(rename_button, onRenamePressedCallback, LV_EVENT_SHORT_CLICKED, this); + auto* delete_button = lv_list_add_button(action_list, LV_SYMBOL_TRASH, "Delete"); + lv_obj_add_event_cb(delete_button, onDeletePressedCallback, LV_EVENT_SHORT_CLICKED, this); + + lv_obj_remove_flag(action_list, LV_OBJ_FLAG_HIDDEN); +} + +void View::update() { + auto scoped_lockable = lvgl::getLvglSyncLockable()->scoped(); + if (scoped_lockable->lock(100 / portTICK_PERIOD_MS)) { + lv_obj_clean(dir_entry_list); + auto entries = state->lockEntries(); + for (auto entry : entries) { + TT_LOG_D(TAG, "Entry: %s %d", entry.d_name, entry.d_type); + createDirEntryWidget(dir_entry_list, entry); + } + state->unlockEntries(); + + if (state->getCurrentPath() == "/") { + lv_obj_add_flag(navigate_up_button, LV_OBJ_FLAG_HIDDEN); + } else { + lv_obj_remove_flag(navigate_up_button, LV_OBJ_FLAG_HIDDEN); + } + } else { + TT_LOG_E(TAG, LOG_MESSAGE_MUTEX_LOCK_FAILED_FMT, "lvgl"); + } +} + +void View::init(lv_obj_t* parent) { + lv_obj_set_flex_flow(parent, LV_FLEX_FLOW_COLUMN); + + lv_obj_t* toolbar = lvgl::toolbar_create(parent, "Files"); + navigate_up_button = lvgl::toolbar_add_button_action(toolbar, LV_SYMBOL_UP, &onNavigateUpPressedCallback, this); + + lv_obj_t* wrapper = lv_obj_create(parent); + lv_obj_set_width(wrapper, LV_PCT(100)); + lv_obj_set_style_border_width(wrapper, 0, 0); + lv_obj_set_style_pad_all(wrapper, 0, 0); + lv_obj_set_flex_grow(wrapper, 1); + lv_obj_set_flex_flow(wrapper, LV_FLEX_FLOW_ROW); + + dir_entry_list = lv_list_create(wrapper); + lv_obj_set_height(dir_entry_list, LV_PCT(100)); + lv_obj_set_flex_grow(dir_entry_list, 1); + + lv_obj_add_event_cb(dir_entry_list, dirEntryListScrollBeginCallback, LV_EVENT_SCROLL_BEGIN, this); + + action_list = lv_list_create(wrapper); + lv_obj_set_height(action_list, LV_PCT(100)); + lv_obj_set_flex_grow(action_list, 1); + lv_obj_add_flag(action_list, LV_OBJ_FLAG_HIDDEN); + + update(); +} + +void View::onDirEntryListScrollBegin() { + auto scoped_lockable = lvgl::getLvglSyncLockable()->scoped(); + if (scoped_lockable->lock(100 / portTICK_PERIOD_MS)) { + lv_obj_add_flag(action_list, LV_OBJ_FLAG_HIDDEN); + } +} + +void View::onNavigate() { + auto scoped_lockable = lvgl::getLvglSyncLockable()->scoped(); + if (scoped_lockable->lock(100 / portTICK_PERIOD_MS)) { + lv_obj_add_flag(action_list, LV_OBJ_FLAG_HIDDEN); + } +} + +void View::onResult(Result result, const Bundle& bundle) { + if (result != ResultOk) { + return; + } + + std::string filepath = state->getSelectedChildPath(); + TT_LOG_I(TAG, "Result for %s", filepath.c_str()); + + switch (state->getPendingAction()) { + case State::ActionDelete: { + if (alertdialog::getResultIndex(bundle) == 0) { + int delete_count = (int)remove(filepath.c_str()); + if (delete_count > 0) { + TT_LOG_I(TAG, "Deleted %d items", delete_count); + } else { + TT_LOG_W(TAG, "Failed to delete %s", filepath.c_str()); + } + state->setEntriesForPath(state->getCurrentPath()); + update(); + } + break; + } + case State::ActionRename: { + auto new_name = app::inputdialog::getResult(bundle); + if (!new_name.empty() && new_name != state->getSelectedChildEntry()) { + std::string rename_to = getChildPath(state->getCurrentPath(), new_name); + if (rename(filepath.c_str(), rename_to.c_str())) { + TT_LOG_I(TAG, "Renamed \"%s\" to \"%s\"", filepath.c_str(), rename_to.c_str()); + } else { + TT_LOG_E(TAG, "Failed to rename \"%s\" to \"%s\"", filepath.c_str(), rename_to.c_str()); + } + state->setEntriesForPath(state->getCurrentPath()); + update(); + } + break; + } + default: + break; + } +} + +} diff --git a/Tactility/Source/app/files/View.h b/Tactility/Source/app/files/View.h new file mode 100644 index 00000000..862b7c7f --- /dev/null +++ b/Tactility/Source/app/files/View.h @@ -0,0 +1,40 @@ +#pragma once + +#include "State.h" +#include "app/AppManifest.h" +#include +#include + +namespace tt::app::files { + +class View { + std::shared_ptr state; + + lv_obj_t* dir_entry_list = nullptr; + lv_obj_t* action_list = nullptr; + lv_obj_t* navigate_up_button = nullptr; + + void showActionsForDirectory(); + void showActionsForFile(); + + void viewFile(const std::string&path, const std::string&filename); + void createDirEntryWidget(lv_obj_t* parent, struct dirent& dir_entry); + void onNavigate(); + +public: + + explicit View(const std::shared_ptr& state) : state(state) {} + + void init(lv_obj_t* parent); + void update(); + + void onNavigateUpPressed(); + void onDirEntryPressed(uint32_t index); + void onDirEntryLongPressed(int32_t index); + void onRenamePressed(); + void onDeletePressed(); + void onDirEntryListScrollBegin(); + void onResult(Result result, const Bundle& bundle); +}; + +} diff --git a/Tactility/Source/app/i2cscanner/I2cScanner.cpp b/Tactility/Source/app/i2cscanner/I2cScanner.cpp index 7128eb77..002defeb 100644 --- a/Tactility/Source/app/i2cscanner/I2cScanner.cpp +++ b/Tactility/Source/app/i2cscanner/I2cScanner.cpp @@ -89,7 +89,7 @@ static void updateViews(std::shared_ptr data) { tt_check(data->mutex.release() == TtStatusOk); } else { - TT_LOG_W(TAG, "updateViews lock"); + TT_LOG_W(TAG, LOG_MESSAGE_MUTEX_LOCK_FAILED_FMT, "updateViews"); } } @@ -98,7 +98,7 @@ static void updateViewsSafely(std::shared_ptr data) { updateViews(data); lvgl::unlock(); } else { - TT_LOG_W(TAG, "updateViewsSafely lock LVGL"); + TT_LOG_W(TAG, LOG_MESSAGE_MUTEX_LOCK_FAILED_FMT, "updateViewsSafely"); } } @@ -110,7 +110,7 @@ void onScanTimerFinished(std::shared_ptr data) { } tt_check(data->mutex.release() == TtStatusOk); } else { - TT_LOG_W(TAG, "onScanTimerFinished lock"); + TT_LOG_W(TAG, LOG_MESSAGE_MUTEX_LOCK_FAILED_FMT, "onScanTimerFinished"); } } @@ -134,7 +134,7 @@ static void onShow(AppContext& app, lv_obj_t* parent) { lv_obj_t* scan_button = lv_button_create(wrapper); lv_obj_set_width(scan_button, LV_PCT(48)); lv_obj_align(scan_button, LV_ALIGN_TOP_LEFT, 0, 1); // Shift 1 pixel to align with selection box - lv_obj_add_event_cb(scan_button, &onPressScan, LV_EVENT_CLICKED, nullptr); + lv_obj_add_event_cb(scan_button, &onPressScan, LV_EVENT_SHORT_CLICKED, nullptr); lv_obj_t* scan_button_label = lv_label_create(scan_button); lv_obj_align(scan_button_label, LV_ALIGN_CENTER, 0, 0); lv_label_set_text(scan_button_label, START_SCAN_TEXT); diff --git a/Tactility/Source/app/i2cscanner/I2cScannerThread.cpp b/Tactility/Source/app/i2cscanner/I2cScannerThread.cpp index cba72b14..996442bd 100644 --- a/Tactility/Source/app/i2cscanner/I2cScannerThread.cpp +++ b/Tactility/Source/app/i2cscanner/I2cScannerThread.cpp @@ -22,7 +22,7 @@ static bool getPort(std::shared_ptr data, i2c_port_t* port) { tt_assert(data->mutex.release() == TtStatusOk); return true; } else { - TT_LOG_W(TAG, "getPort lock"); + TT_LOG_W(TAG, LOG_MESSAGE_MUTEX_LOCK_FAILED_FMT, "getPort"); return false; } } @@ -33,7 +33,7 @@ static bool addAddressToList(std::shared_ptr data, uint8_t address) { tt_assert(data->mutex.release() == TtStatusOk); return true; } else { - TT_LOG_W(TAG, "addAddressToList lock"); + TT_LOG_W(TAG, LOG_MESSAGE_MUTEX_LOCK_FAILED_FMT, "addAddressToList"); return false; } } @@ -58,7 +58,7 @@ static void onScanTimer(TT_UNUSED std::shared_ptr context) { } } } else { - TT_LOG_W(TAG, "onScanTimer lock"); + TT_LOG_W(TAG, LOG_MESSAGE_MUTEX_LOCK_FAILED_FMT, "onScanTimer"); break; } @@ -82,7 +82,7 @@ bool hasScanThread(std::shared_ptr data) { return has_thread; } else { // Unsafe way - TT_LOG_W(TAG, "hasScanTimer lock"); + TT_LOG_W(TAG, LOG_MESSAGE_MUTEX_LOCK_FAILED_FMT, "hasScanTimer"); return data->scanTimer != nullptr; } } @@ -107,7 +107,7 @@ void startScanning(std::shared_ptr data) { data->scanTimer->start(10); tt_check(data->mutex.release() == TtStatusOk); } else { - TT_LOG_W(TAG, "startScanning lock"); + TT_LOG_W(TAG, LOG_MESSAGE_MUTEX_LOCK_FAILED_FMT, "startScanning"); } } @@ -117,7 +117,7 @@ void stopScanning(std::shared_ptr data) { data->scanState = ScanStateStopped; tt_check(data->mutex.release() == TtStatusOk); } else { - TT_LOG_E(TAG, "Acquire mutex failed"); + TT_LOG_E(TAG, LOG_MESSAGE_MUTEX_LOCK_FAILED); } } diff --git a/Tactility/Source/app/inputdialog/InputDialog.cpp b/Tactility/Source/app/inputdialog/InputDialog.cpp new file mode 100644 index 00000000..01a2caa7 --- /dev/null +++ b/Tactility/Source/app/inputdialog/InputDialog.cpp @@ -0,0 +1,120 @@ +#include "InputDialog.h" +#include "lvgl.h" +#include "lvgl/Toolbar.h" +#include "service/loader/Loader.h" +#include "service/gui/Gui.h" +#include +#include + +namespace tt::app::inputdialog { + +#define PARAMETER_BUNDLE_KEY_TITLE "title" +#define PARAMETER_BUNDLE_KEY_MESSAGE "message" +#define PARAMETER_BUNDLE_KEY_PREFILLED "prefilled" +#define RESULT_BUNDLE_KEY_RESULT "result" + +#define DEFAULT_TITLE "Input" + +#define TAG "input_dialog" + +extern const AppManifest manifest; + +void start(const std::string& title, const std::string& message, const std::string& prefilled) { + auto bundle = std::make_shared(); + bundle->putString(PARAMETER_BUNDLE_KEY_TITLE, title); + bundle->putString(PARAMETER_BUNDLE_KEY_MESSAGE, message); + bundle->putString(PARAMETER_BUNDLE_KEY_PREFILLED, prefilled); + service::loader::startApp(manifest.id, false, bundle); +} + +std::string getResult(const Bundle& bundle) { + std::string result; + bundle.optString(RESULT_BUNDLE_KEY_RESULT, result); + return result; +} + +void setResult(const std::shared_ptr& bundle, const std::string& result) { + bundle->putString(RESULT_BUNDLE_KEY_RESULT, result); +} + +static std::string getTitleParameter(const std::shared_ptr& bundle) { + std::string result; + if (bundle->optString(PARAMETER_BUNDLE_KEY_TITLE, result)) { + return result; + } else { + return DEFAULT_TITLE; + } +} + +static void onButtonClicked(lv_event_t* e) { + auto user_data = lv_event_get_user_data(e); + int index = (user_data != 0) ? 0 : 1; + TT_LOG_I(TAG, "Selected item at index %d", index); + tt::app::AppContext* app = service::loader::getCurrentApp(); + auto bundle = std::make_shared(); + if (index == 0) { + const char* text = lv_textarea_get_text((lv_obj_t*)user_data); + setResult(bundle, text); + app->setResult(app::ResultOk, bundle); + } else { + app->setResult(app::ResultCancelled, bundle); + + } + service::loader::stopApp(); +} + +static void createButton(lv_obj_t* parent, const std::string& text, void* callbackContext) { + lv_obj_t* button = lv_button_create(parent); + lv_obj_t* button_label = lv_label_create(button); + lv_obj_align(button_label, LV_ALIGN_CENTER, 0, 0); + lv_label_set_text(button_label, text.c_str()); + lv_obj_add_event_cb(button, &onButtonClicked, LV_EVENT_SHORT_CLICKED, callbackContext); +} + +static void onShow(AppContext& app, lv_obj_t* parent) { + auto parameters = app.getParameters(); + tt_check(parameters != nullptr, "Parameters missing"); + + std::string title = getTitleParameter(app.getParameters()); + auto* toolbar = lvgl::toolbar_create(parent, title); + lv_obj_align(toolbar, LV_ALIGN_TOP_MID, 0, 0); + + auto* message_label = lv_label_create(parent); + lv_obj_align(message_label, LV_ALIGN_CENTER, 0, -20); + lv_obj_set_width(message_label, LV_PCT(80)); + + std::string message; + if (parameters->optString(PARAMETER_BUNDLE_KEY_MESSAGE, message)) { + lv_label_set_text(message_label, message.c_str()); + lv_label_set_long_mode(message_label, LV_LABEL_LONG_WRAP); + } + + auto* textarea = lv_textarea_create(parent); + lv_obj_align_to(textarea, message_label, LV_ALIGN_OUT_BOTTOM_MID, 0, 4); + lv_textarea_set_one_line(textarea, true); + std::string prefilled; + if (parameters->optString(PARAMETER_BUNDLE_KEY_PREFILLED, prefilled)) { + lv_textarea_set_text(textarea, prefilled.c_str()); + } + service::gui::keyboardAddTextArea(textarea); + + auto* button_wrapper = lv_obj_create(parent); + lv_obj_set_flex_flow(button_wrapper, LV_FLEX_FLOW_ROW); + lv_obj_set_size(button_wrapper, LV_PCT(100), LV_SIZE_CONTENT); + lv_obj_set_style_pad_all(button_wrapper, 0, 0); + lv_obj_set_flex_align(button_wrapper, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER); + lv_obj_set_style_border_width(button_wrapper, 0, 0); + lv_obj_align(button_wrapper, LV_ALIGN_BOTTOM_MID, 0, -4); + + createButton(button_wrapper, "OK", textarea); + createButton(button_wrapper, "Cancel", nullptr); +} + +extern const AppManifest manifest = { + .id = "InputDialog", + .name = "Input Dialog", + .type = TypeHidden, + .onShow = onShow +}; + +} diff --git a/Tactility/Source/app/inputdialog/InputDialog.h b/Tactility/Source/app/inputdialog/InputDialog.h new file mode 100644 index 00000000..94ce7785 --- /dev/null +++ b/Tactility/Source/app/inputdialog/InputDialog.h @@ -0,0 +1,20 @@ +#pragma once + +#include +#include +#include "Bundle.h" + +/** + * Start the app by its ID and provide: + * - a title + * - a text + */ +namespace tt::app::inputdialog { + + void start(const std::string& title, const std::string& message, const std::string& prefilled = ""); + + /** + * @return the text that was in the field when OK was pressed, or otherwise empty string + */ + std::string getResult(const Bundle& bundle); +} diff --git a/Tactility/Source/app/screenshot/Screenshot.cpp b/Tactility/Source/app/screenshot/Screenshot.cpp index df3c6bee..e3f9c1d9 100644 --- a/Tactility/Source/app/screenshot/Screenshot.cpp +++ b/Tactility/Source/app/screenshot/Screenshot.cpp @@ -1,3 +1,7 @@ +#include "TactilityConfig.h" + +#if TT_FEATURE_SCREENSHOT_ENABLED + #include "ScreenshotUi.h" #include @@ -5,17 +9,17 @@ namespace tt::app::screenshot { static void onShow(AppContext& app, lv_obj_t* parent) { auto ui = std::static_pointer_cast(app.getData()); - create_ui(app, ui, parent); + ui->createWidgets(app, parent); } static void onStart(AppContext& app) { - auto ui = std::shared_ptr(new ScreenshotUi()); + auto ui = std::make_shared(); app.setData(ui); // Ensure data gets deleted when no more in use } extern const AppManifest manifest = { .id = "Screenshot", - .name = "_Screenshot", // So it gets put at the bottom of the desktop and becomes less visible on small screen devices + .name = "Screenshot", .icon = LV_SYMBOL_IMAGE, .type = TypeSystem, .onStart = onStart, @@ -23,3 +27,5 @@ extern const AppManifest manifest = { }; } // namespace + +#endif diff --git a/Tactility/Source/app/screenshot/ScreenshotUi.cpp b/Tactility/Source/app/screenshot/ScreenshotUi.cpp index 4bbf507c..07cc69b6 100644 --- a/Tactility/Source/app/screenshot/ScreenshotUi.cpp +++ b/Tactility/Source/app/screenshot/ScreenshotUi.cpp @@ -1,18 +1,23 @@ +#include "TactilityConfig.h" + +#if TT_FEATURE_SCREENSHOT_ENABLED + #include "ScreenshotUi.h" #include "TactilityCore.h" -#include "hal/sdcard/Sdcard.h" +#include "hal/SdCard.h" #include "service/gui/Gui.h" #include "service/loader/Loader.h" #include "service/screenshot/Screenshot.h" #include "lvgl/Toolbar.h" +#include "TactilityHeadless.h" +#include "lvgl/LvglSync.h" namespace tt::app::screenshot { #define TAG "screenshot_ui" extern AppManifest manifest; -static void update_mode(std::shared_ptr ui); /** Returns the app data if the app is active. Note that this could clash if the same app is started twice and a background thread is slow. */ std::shared_ptr _Nullable optScreenshotUi() { @@ -24,173 +29,228 @@ std::shared_ptr _Nullable optScreenshotUi() { } } -static void on_start_pressed(lv_event_t* event) { +static void onStartPressedCallback(TT_UNUSED lv_event_t* event) { auto ui = optScreenshotUi(); - if (ui == nullptr) { + if (ui != nullptr) { + ui->onStartPressed(); + } +} + +static void onModeSetCallback(TT_UNUSED lv_event_t* event) { + auto ui = optScreenshotUi(); + if (ui != nullptr) { + ui->onModeSet(); + } +} + +static void onTimerCallback(TT_UNUSED std::shared_ptr context) { + auto screenshot_ui = optScreenshotUi(); + if (screenshot_ui != nullptr) { + screenshot_ui->onTimerTick(); + } +} + +ScreenshotUi::ScreenshotUi() { + updateTimer = std::make_unique(Timer::TypePeriodic, onTimerCallback, nullptr); +} + +ScreenshotUi::~ScreenshotUi() { + if (updateTimer->isRunning()) { + updateTimer->stop(); + } +} + +void ScreenshotUi::onTimerTick() { + auto lvgl_lock = lvgl::getLvglSyncLockable()->scoped(); + if (lvgl_lock->lock(50 / portTICK_PERIOD_MS)) { + updateScreenshotMode(); + } +} + +void ScreenshotUi::onModeSet() { + updateScreenshotMode(); +} + +void ScreenshotUi::onStartPressed() { + auto service = service::screenshot::optScreenshotService(); + if (service == nullptr) { + TT_LOG_E(TAG, "Service not found/running"); return; } - if (service::screenshot::isStarted()) { + if (service->isTaskStarted()) { TT_LOG_I(TAG, "Stop screenshot"); - service::screenshot::stop(); + service->stop(); } else { - uint32_t selected = lv_dropdown_get_selected(ui->mode_dropdown); - const char* path = lv_textarea_get_text(ui->path_textarea); + uint32_t selected = lv_dropdown_get_selected(modeDropdown); + const char* path = lv_textarea_get_text(pathTextArea); if (selected == 0) { TT_LOG_I(TAG, "Start timed screenshots"); - const char* delay_text = lv_textarea_get_text(ui->delay_textarea); + const char* delay_text = lv_textarea_get_text(delayTextArea); int delay = atoi(delay_text); if (delay > 0) { - service::screenshot::startTimed(path, delay, 1); + service->startTimed(path, delay, 1); } else { TT_LOG_W(TAG, "Ignored screenshot start because delay was 0"); } } else { TT_LOG_I(TAG, "Start app screenshots"); - service::screenshot::startApps(path); + service->startApps(path); } } - update_mode(ui); + updateScreenshotMode(); } -static void update_mode(std::shared_ptr ui) { - lv_obj_t* label = ui->start_stop_button_label; - if (service::screenshot::isStarted()) { +void ScreenshotUi::updateScreenshotMode() { + auto service = service::screenshot::optScreenshotService(); + if (service == nullptr) { + TT_LOG_E(TAG, "Service not found/running"); + return; + } + + lv_obj_t* label = startStopButtonLabel; + if (service->isTaskStarted()) { lv_label_set_text(label, "Stop"); } else { lv_label_set_text(label, "Start"); } - uint32_t selected = lv_dropdown_get_selected(ui->mode_dropdown); + uint32_t selected = lv_dropdown_get_selected(modeDropdown); if (selected == 0) { // Timer - lv_obj_remove_flag(ui->timer_wrapper, LV_OBJ_FLAG_HIDDEN); + lv_obj_remove_flag(timerWrapper, LV_OBJ_FLAG_HIDDEN); } else { - lv_obj_add_flag(ui->timer_wrapper, LV_OBJ_FLAG_HIDDEN); + lv_obj_add_flag(timerWrapper, LV_OBJ_FLAG_HIDDEN); } } -static void on_mode_set(lv_event_t* event) { - auto ui = optScreenshotUi(); - if (ui != nullptr) { - update_mode(ui); - } -} -static void create_mode_setting_ui(std::shared_ptr ui, lv_obj_t* parent) { - lv_obj_t* mode_wrapper = lv_obj_create(parent); +void ScreenshotUi::createModeSettingWidgets(lv_obj_t* parent) { + auto service = service::screenshot::optScreenshotService(); + if (service == nullptr) { + TT_LOG_E(TAG, "Service not found/running"); + return; + } + + auto* mode_wrapper = lv_obj_create(parent); lv_obj_set_size(mode_wrapper, LV_PCT(100), LV_SIZE_CONTENT); lv_obj_set_style_pad_all(mode_wrapper, 0, 0); lv_obj_set_style_border_width(mode_wrapper, 0, 0); - lv_obj_t* mode_label = lv_label_create(mode_wrapper); + auto* mode_label = lv_label_create(mode_wrapper); lv_label_set_text(mode_label, "Mode:"); lv_obj_align(mode_label, LV_ALIGN_LEFT_MID, 0, 0); - lv_obj_t* mode_dropdown = lv_dropdown_create(mode_wrapper); - lv_dropdown_set_options(mode_dropdown, "Timer\nApp start"); - lv_obj_align_to(mode_dropdown, mode_label, LV_ALIGN_OUT_RIGHT_MID, 8, 0); - lv_obj_add_event_cb(mode_dropdown, on_mode_set, LV_EVENT_VALUE_CHANGED, nullptr); - ui->mode_dropdown = mode_dropdown; - service::screenshot::Mode mode = service::screenshot::getMode(); + modeDropdown = lv_dropdown_create(mode_wrapper); + lv_dropdown_set_options(modeDropdown, "Timer\nApp start"); + lv_obj_align_to(modeDropdown, mode_label, LV_ALIGN_OUT_RIGHT_MID, 8, 0); + lv_obj_add_event_cb(modeDropdown, onModeSetCallback, LV_EVENT_VALUE_CHANGED, nullptr); + service::screenshot::Mode mode = service->getMode(); if (mode == service::screenshot::ScreenshotModeApps) { - lv_dropdown_set_selected(mode_dropdown, 1); + lv_dropdown_set_selected(modeDropdown, 1); } - lv_obj_t* button = lv_button_create(mode_wrapper); + auto* button = lv_button_create(mode_wrapper); lv_obj_align(button, LV_ALIGN_RIGHT_MID, 0, 0); - lv_obj_t* button_label = lv_label_create(button); - lv_obj_align(button_label, LV_ALIGN_CENTER, 0, 0); - ui->start_stop_button_label = button_label; - lv_obj_add_event_cb(button, &on_start_pressed, LV_EVENT_CLICKED, nullptr); + lv_obj_add_event_cb(button, &onStartPressedCallback, LV_EVENT_SHORT_CLICKED, nullptr); + startStopButtonLabel = lv_label_create(button); + lv_obj_align(startStopButtonLabel, LV_ALIGN_CENTER, 0, 0); } -static void create_path_ui(std::shared_ptr ui, lv_obj_t* parent) { - lv_obj_t* path_wrapper = lv_obj_create(parent); +void ScreenshotUi::createFilePathWidgets(lv_obj_t* parent) { + auto* path_wrapper = lv_obj_create(parent); lv_obj_set_size(path_wrapper, LV_PCT(100), LV_SIZE_CONTENT); lv_obj_set_style_pad_all(path_wrapper, 0, 0); lv_obj_set_style_border_width(path_wrapper, 0, 0); lv_obj_set_flex_flow(path_wrapper, LV_FLEX_FLOW_ROW); - lv_obj_t* label_wrapper = lv_obj_create(path_wrapper); + auto* label_wrapper = lv_obj_create(path_wrapper); lv_obj_set_style_border_width(label_wrapper, 0, 0); lv_obj_set_style_pad_all(label_wrapper, 0, 0); lv_obj_set_size(label_wrapper, 44, 36); - lv_obj_t* path_label = lv_label_create(label_wrapper); + auto* path_label = lv_label_create(label_wrapper); lv_label_set_text(path_label, "Path:"); lv_obj_align(path_label, LV_ALIGN_LEFT_MID, 0, 0); - lv_obj_t* path_textarea = lv_textarea_create(path_wrapper); - lv_textarea_set_one_line(path_textarea, true); - lv_obj_set_flex_grow(path_textarea, 1); - ui->path_textarea = path_textarea; + pathTextArea = lv_textarea_create(path_wrapper); + lv_textarea_set_one_line(pathTextArea, true); + lv_obj_set_flex_grow(pathTextArea, 1); if (kernel::getPlatform() == kernel::PlatformEsp) { - if (hal::sdcard::getState() == hal::sdcard::StateMounted) { - lv_textarea_set_text(path_textarea, "A:/sdcard"); + auto sdcard = tt::hal::getConfiguration().sdcard; + if (sdcard != nullptr && sdcard->getState() == hal::SdCard::StateMounted) { + lv_textarea_set_text(pathTextArea, "A:/sdcard"); } else { - lv_textarea_set_text(path_textarea, "Error: no SD card"); + lv_textarea_set_text(pathTextArea, "Error: no SD card"); } } else { // PC - lv_textarea_set_text(path_textarea, "A:"); + lv_textarea_set_text(pathTextArea, "A:"); } } -static void create_timer_settings_ui(std::shared_ptr ui, lv_obj_t* parent) { - lv_obj_t* timer_wrapper = lv_obj_create(parent); - lv_obj_set_size(timer_wrapper, LV_PCT(100), LV_SIZE_CONTENT); - lv_obj_set_style_pad_all(timer_wrapper, 0, 0); - lv_obj_set_style_border_width(timer_wrapper, 0, 0); - ui->timer_wrapper = timer_wrapper; +void ScreenshotUi::createTimerSettingsWidgets(lv_obj_t* parent) { + timerWrapper = lv_obj_create(parent); + lv_obj_set_size(timerWrapper, LV_PCT(100), LV_SIZE_CONTENT); + lv_obj_set_style_pad_all(timerWrapper, 0, 0); + lv_obj_set_style_border_width(timerWrapper, 0, 0); - lv_obj_t* delay_wrapper = lv_obj_create(timer_wrapper); + auto* delay_wrapper = lv_obj_create(timerWrapper); lv_obj_set_size(delay_wrapper, LV_PCT(100), LV_SIZE_CONTENT); lv_obj_set_style_pad_all(delay_wrapper, 0, 0); lv_obj_set_style_border_width(delay_wrapper, 0, 0); lv_obj_set_flex_flow(delay_wrapper, LV_FLEX_FLOW_ROW); - lv_obj_t* delay_label_wrapper = lv_obj_create(delay_wrapper); + auto* delay_label_wrapper = lv_obj_create(delay_wrapper); lv_obj_set_style_border_width(delay_label_wrapper, 0, 0); lv_obj_set_style_pad_all(delay_label_wrapper, 0, 0); lv_obj_set_size(delay_label_wrapper, 44, 36); - lv_obj_t* delay_label = lv_label_create(delay_label_wrapper); + auto* delay_label = lv_label_create(delay_label_wrapper); lv_label_set_text(delay_label, "Delay:"); lv_obj_align(delay_label, LV_ALIGN_LEFT_MID, 0, 0); - lv_obj_t* delay_textarea = lv_textarea_create(delay_wrapper); - lv_textarea_set_one_line(delay_textarea, true); - lv_textarea_set_accepted_chars(delay_textarea, "0123456789"); - lv_textarea_set_text(delay_textarea, "10"); - lv_obj_set_flex_grow(delay_textarea, 1); - ui->delay_textarea = delay_textarea; + delayTextArea = lv_textarea_create(delay_wrapper); + lv_textarea_set_one_line(delayTextArea, true); + lv_textarea_set_accepted_chars(delayTextArea, "0123456789"); + lv_textarea_set_text(delayTextArea, "10"); + lv_obj_set_flex_grow(delayTextArea, 1); - lv_obj_t* delay_unit_label_wrapper = lv_obj_create(delay_wrapper); + auto* delay_unit_label_wrapper = lv_obj_create(delay_wrapper); lv_obj_set_style_border_width(delay_unit_label_wrapper, 0, 0); lv_obj_set_style_pad_all(delay_unit_label_wrapper, 0, 0); lv_obj_set_size(delay_unit_label_wrapper, LV_SIZE_CONTENT, 36); - lv_obj_t* delay_unit_label = lv_label_create(delay_unit_label_wrapper); + auto* delay_unit_label = lv_label_create(delay_unit_label_wrapper); lv_obj_align(delay_unit_label, LV_ALIGN_LEFT_MID, 0, 0); lv_label_set_text(delay_unit_label, "seconds"); } -void create_ui(const AppContext& app, std::shared_ptr ui, lv_obj_t* parent) { +void ScreenshotUi::createWidgets(const AppContext& app, lv_obj_t* parent) { + if (updateTimer->isRunning()) { + updateTimer->stop(); + } + lv_obj_set_flex_flow(parent, LV_FLEX_FLOW_COLUMN); - lv_obj_t* toolbar = lvgl::toolbar_create(parent, app); + auto* toolbar = lvgl::toolbar_create(parent, app); lv_obj_align(toolbar, LV_ALIGN_TOP_MID, 0, 0); - lv_obj_t* wrapper = lv_obj_create(parent); + auto* wrapper = lv_obj_create(parent); lv_obj_set_width(wrapper, LV_PCT(100)); lv_obj_set_flex_grow(wrapper, 1); lv_obj_set_style_border_width(wrapper, 0, 0); lv_obj_set_flex_flow(wrapper, LV_FLEX_FLOW_COLUMN); - create_mode_setting_ui(ui, wrapper); - create_path_ui(ui, wrapper); - create_timer_settings_ui(ui, wrapper); + createModeSettingWidgets(wrapper); + createFilePathWidgets(wrapper); + createTimerSettingsWidgets(wrapper); - service::gui::keyboardAddTextArea(ui->delay_textarea); - service::gui::keyboardAddTextArea(ui->path_textarea); + service::gui::keyboardAddTextArea(delayTextArea); + service::gui::keyboardAddTextArea(pathTextArea); - update_mode(ui); + updateScreenshotMode(); + + if (!updateTimer->isRunning()) { + updateTimer->start(500 / portTICK_PERIOD_MS); + } } } // namespace + +#endif \ No newline at end of file diff --git a/Tactility/Source/app/screenshot/ScreenshotUi.h b/Tactility/Source/app/screenshot/ScreenshotUi.h index 64be0a99..36919d61 100644 --- a/Tactility/Source/app/screenshot/ScreenshotUi.h +++ b/Tactility/Source/app/screenshot/ScreenshotUi.h @@ -1,3 +1,8 @@ +#include "Timer.h" +#include "TactilityConfig.h" + +#if TT_FEATURE_SCREENSHOT_ENABLED + #pragma once #include "app/AppContext.h" @@ -5,14 +10,33 @@ namespace tt::app::screenshot { -typedef struct { - lv_obj_t* mode_dropdown; - lv_obj_t* path_textarea; - lv_obj_t* start_stop_button_label; - lv_obj_t* timer_wrapper; - lv_obj_t* delay_textarea; -} ScreenshotUi; +class ScreenshotUi { + + lv_obj_t* modeDropdown = nullptr; + lv_obj_t* pathTextArea = nullptr; + lv_obj_t* startStopButtonLabel = nullptr; + lv_obj_t* timerWrapper = nullptr; + lv_obj_t* delayTextArea = nullptr; + std::unique_ptr updateTimer; + + void createTimerSettingsWidgets(lv_obj_t* parent); + void createModeSettingWidgets(lv_obj_t* parent); + void createFilePathWidgets(lv_obj_t* parent); + + void updateScreenshotMode(); + +public: + + ScreenshotUi(); + ~ScreenshotUi(); + + void createWidgets(const AppContext& app, lv_obj_t* parent); + void onStartPressed(); + void onModeSet(); + void onTimerTick(); +}; -void create_ui(const AppContext& app, std::shared_ptr ui, lv_obj_t* parent); } // namespace + +#endif diff --git a/Tactility/Source/app/selectiondialog/SelectionDialog.cpp b/Tactility/Source/app/selectiondialog/SelectionDialog.cpp index 7d89af47..c88f152e 100644 --- a/Tactility/Source/app/selectiondialog/SelectionDialog.cpp +++ b/Tactility/Source/app/selectiondialog/SelectionDialog.cpp @@ -46,22 +46,19 @@ static std::string getTitleParameter(std::shared_ptr bundle) { } static void onListItemSelected(lv_event_t* e) { - lv_event_code_t code = lv_event_get_code(e); - if (code == LV_EVENT_CLICKED) { - size_t index = reinterpret_cast(lv_event_get_user_data(e)); - TT_LOG_I(TAG, "Selected item at index %d", index); - tt::app::AppContext* app = service::loader::getCurrentApp(); - auto bundle = std::make_shared(); - setResultIndex(bundle, (int32_t)index); - app->setResult(app::ResultOk, bundle); - service::loader::stopApp(); - } + size_t index = reinterpret_cast(lv_event_get_user_data(e)); + TT_LOG_I(TAG, "Selected item at index %d", index); + tt::app::AppContext* app = service::loader::getCurrentApp(); + auto bundle = std::make_shared(); + setResultIndex(bundle, (int32_t)index); + app->setResult(app::ResultOk, bundle); + service::loader::stopApp(); } static void createChoiceItem(void* parent, const std::string& title, size_t index) { auto* list = static_cast(parent); lv_obj_t* btn = lv_list_add_button(list, nullptr, title.c_str()); - lv_obj_add_event_cb(btn, &onListItemSelected, LV_EVENT_CLICKED, (void*)index); + lv_obj_add_event_cb(btn, &onListItemSelected, LV_EVENT_SHORT_CLICKED, (void*)index); } static void onShow(AppContext& app, lv_obj_t* parent) { diff --git a/Tactility/Source/app/settings/Settings.cpp b/Tactility/Source/app/settings/Settings.cpp index 19a6fac3..c06c8df1 100644 --- a/Tactility/Source/app/settings/Settings.cpp +++ b/Tactility/Source/app/settings/Settings.cpp @@ -9,11 +9,8 @@ namespace tt::app::settings { static void onAppPressed(lv_event_t* e) { - lv_event_code_t code = lv_event_get_code(e); - if (code == LV_EVENT_CLICKED) { - const auto* manifest = static_cast(lv_event_get_user_data(e)); - service::loader::startApp(manifest->id); - } + const auto* manifest = static_cast(lv_event_get_user_data(e)); + service::loader::startApp(manifest->id); } static void createWidget(const AppManifest* manifest, void* parent) { @@ -21,7 +18,7 @@ static void createWidget(const AppManifest* manifest, void* parent) { auto* list = (lv_obj_t*)parent; const void* icon = !manifest->icon.empty() ? manifest->icon.c_str() : TT_ASSETS_APP_ICON_FALLBACK; lv_obj_t* btn = lv_list_add_button(list, icon, manifest->name.c_str()); - lv_obj_add_event_cb(btn, &onAppPressed, LV_EVENT_CLICKED, (void*)manifest); + lv_obj_add_event_cb(btn, &onAppPressed, LV_EVENT_SHORT_CLICKED, (void*)manifest); } static void onShow(AppContext& app, lv_obj_t* parent) { diff --git a/Tactility/Source/app/wifiapsettings/WifiApSettings.cpp b/Tactility/Source/app/wifiapsettings/WifiApSettings.cpp index 1da662d4..f18e40d9 100644 --- a/Tactility/Source/app/wifiapsettings/WifiApSettings.cpp +++ b/Tactility/Source/app/wifiapsettings/WifiApSettings.cpp @@ -100,7 +100,7 @@ static void onShow(AppContext& app, lv_obj_t* parent) { lv_obj_t* forget_button = lv_button_create(wrapper); lv_obj_set_width(forget_button, LV_PCT(100)); lv_obj_align_to(forget_button, auto_connect_wrapper, LV_ALIGN_OUT_BOTTOM_MID, 0, 10); - lv_obj_add_event_cb(forget_button, onPressForget, LV_EVENT_CLICKED, nullptr); + lv_obj_add_event_cb(forget_button, onPressForget, LV_EVENT_SHORT_CLICKED, nullptr); lv_obj_t* forget_button_label = lv_label_create(forget_button); lv_obj_align(forget_button_label, LV_ALIGN_CENTER, 0, 0); lv_label_set_text(forget_button_label, "Forget"); @@ -112,9 +112,10 @@ static void onShow(AppContext& app, lv_obj_t* parent) { } else { lv_obj_remove_state(auto_connect_switch, LV_STATE_CHECKED); } - } else { - TT_LOG_E(TAG, "Failed to load settings"); + TT_LOG_W(TAG, "No settings found"); + lv_obj_add_flag(forget_button, LV_OBJ_FLAG_HIDDEN); + lv_obj_add_flag(auto_connect_wrapper, LV_OBJ_FLAG_HIDDEN); } } diff --git a/Tactility/Source/app/wificonnect/View.cpp b/Tactility/Source/app/wificonnect/View.cpp index 169a40dd..33aad340 100644 --- a/Tactility/Source/app/wificonnect/View.cpp +++ b/Tactility/Source/app/wificonnect/View.cpp @@ -106,7 +106,7 @@ void View::createBottomButtons(lv_obj_t* parent) { lv_obj_t* connect_label = lv_label_create(connect_button); lv_label_set_text(connect_label, "Connect"); lv_obj_align(connect_button, LV_ALIGN_RIGHT_MID, 0, 0); - lv_obj_add_event_cb(connect_button, &onConnect, LV_EVENT_CLICKED, nullptr); + lv_obj_add_event_cb(connect_button, &onConnect, LV_EVENT_SHORT_CLICKED, nullptr); } // TODO: Standardize dialogs diff --git a/Tactility/Source/app/wificonnect/WifiConnect.cpp b/Tactility/Source/app/wificonnect/WifiConnect.cpp index d8873b4f..959570fd 100644 --- a/Tactility/Source/app/wificonnect/WifiConnect.cpp +++ b/Tactility/Source/app/wificonnect/WifiConnect.cpp @@ -82,7 +82,7 @@ void WifiConnect::requestViewUpdate() { view.update(); lvgl::unlock(); } else { - TT_LOG_E(TAG, "Failed to lock lvgl"); + TT_LOG_E(TAG, LOG_MESSAGE_MUTEX_LOCK_FAILED_FMT, "LVGL"); } } unlock(); diff --git a/Tactility/Source/app/wifimanage/View.cpp b/Tactility/Source/app/wifimanage/View.cpp index 9a6fe69c..e12deb0c 100644 --- a/Tactility/Source/app/wifimanage/View.cpp +++ b/Tactility/Source/app/wifimanage/View.cpp @@ -76,7 +76,7 @@ static void showDetails(lv_event_t* event) { void View::createSsidListItem(const service::wifi::WifiApRecord& record, bool isConnecting) { lv_obj_t* wrapper = lv_obj_create(networks_list); - lv_obj_add_event_cb(wrapper, &connect, LV_EVENT_CLICKED, bindings); + lv_obj_add_event_cb(wrapper, &connect, LV_EVENT_SHORT_CLICKED, bindings); lv_obj_set_user_data(wrapper, bindings); lv_obj_set_size(wrapper, LV_PCT(100), LV_SIZE_CONTENT); lvgl::obj_set_style_no_padding(wrapper); @@ -94,7 +94,7 @@ void View::createSsidListItem(const service::wifi::WifiApRecord& record, bool is lv_obj_set_style_margin_all(info_wrapper, 0, 0); lv_obj_set_size(info_wrapper, 36, 36); lv_obj_set_style_border_color(info_wrapper, lv_theme_get_color_primary(info_wrapper), 0); - lv_obj_add_event_cb(info_wrapper, &showDetails, LV_EVENT_CLICKED, bindings); + lv_obj_add_event_cb(info_wrapper, &showDetails, LV_EVENT_SHORT_CLICKED, bindings); lv_obj_align(info_wrapper, LV_ALIGN_RIGHT_MID, 0, 0); lv_obj_t* info_label = lv_label_create(info_wrapper); diff --git a/Tactility/Source/app/wifimanage/WifiManage.cpp b/Tactility/Source/app/wifimanage/WifiManage.cpp index 64807aa5..f91a2e25 100644 --- a/Tactility/Source/app/wifimanage/WifiManage.cpp +++ b/Tactility/Source/app/wifimanage/WifiManage.cpp @@ -76,7 +76,7 @@ void WifiManage::requestViewUpdate() { view.update(); lvgl::unlock(); } else { - TT_LOG_E(TAG, "failed to lock lvgl"); + TT_LOG_E(TAG, LOG_MESSAGE_MUTEX_LOCK_FAILED_FMT, "LVGL"); } } unlock(); diff --git a/Tactility/Source/lvgl/LvglSync.cpp b/Tactility/Source/lvgl/LvglSync.cpp index 32a1f08d..d09cfc1d 100644 --- a/Tactility/Source/lvgl/LvglSync.cpp +++ b/Tactility/Source/lvgl/LvglSync.cpp @@ -29,4 +29,24 @@ void unlock() { unlock_singleton(); } +class LvglSync : public Lockable { +public: + ~LvglSync() override = default; + + bool lock(uint32_t timeoutTicks) const override { + return tt::lvgl::lock(timeoutTicks); + } + + bool unlock() const override { + tt::lvgl::unlock(); + return true; + } +}; + +static std::shared_ptr lvglSync = std::make_shared(); + +std::shared_ptr getLvglSyncLockable() { + return lvglSync; +} + } // namespace diff --git a/Tactility/Source/lvgl/LvglSync.h b/Tactility/Source/lvgl/LvglSync.h index 66e8d746..5aadfa3f 100644 --- a/Tactility/Source/lvgl/LvglSync.h +++ b/Tactility/Source/lvgl/LvglSync.h @@ -1,6 +1,8 @@ #pragma once -#include +#include "Lockable.h" + +#include namespace tt::lvgl { @@ -12,4 +14,6 @@ bool isSyncSet(); bool lock(uint32_t timeout_ticks); void unlock(); +std::shared_ptr getLvglSyncLockable(); + } // namespace diff --git a/Tactility/Source/lvgl/Toolbar.cpp b/Tactility/Source/lvgl/Toolbar.cpp index 3a22f124..cfa50312 100644 --- a/Tactility/Source/lvgl/Toolbar.cpp +++ b/Tactility/Source/lvgl/Toolbar.cpp @@ -1,4 +1,5 @@ #define LV_USE_PRIVATE_API 1 // For actual lv_obj_t declaration + #include "Toolbar.h" #include "service/loader/Loader.h" @@ -6,8 +7,6 @@ #include "lvgl/Style.h" #include "Spinner.h" -#define SPINNER_HEIGHT TOOLBAR_HEIGHT - namespace tt::lvgl { typedef struct { @@ -100,20 +99,19 @@ void toolbar_set_title(lv_obj_t* obj, const std::string& title) { void toolbar_set_nav_action(lv_obj_t* obj, const char* icon, lv_event_cb_t callback, void* user_data) { auto* toolbar = (Toolbar*)obj; - lv_obj_add_event_cb(toolbar->close_button, callback, LV_EVENT_CLICKED, user_data); + lv_obj_add_event_cb(toolbar->close_button, callback, LV_EVENT_SHORT_CLICKED, user_data); lv_image_set_src(toolbar->close_button_image, icon); // e.g. LV_SYMBOL_CLOSE } lv_obj_t* toolbar_add_button_action(lv_obj_t* obj, const char* icon, lv_event_cb_t callback, void* user_data) { auto* toolbar = (Toolbar*)obj; - uint8_t id = toolbar->action_count; tt_check(toolbar->action_count < TOOLBAR_ACTION_LIMIT, "max actions reached"); toolbar->action_count++; lv_obj_t* action_button = lv_button_create(toolbar->action_container); lv_obj_set_size(action_button, TOOLBAR_HEIGHT - 4, TOOLBAR_HEIGHT - 4); obj_set_style_no_padding(action_button); - lv_obj_add_event_cb(action_button, callback, LV_EVENT_CLICKED, user_data); + lv_obj_add_event_cb(action_button, callback, LV_EVENT_SHORT_CLICKED, user_data); lv_obj_t* action_button_image = lv_image_create(action_button); lv_image_set_src(action_button_image, icon); lv_obj_align(action_button_image, LV_ALIGN_CENTER, 0, 0); @@ -125,6 +123,7 @@ lv_obj_t* toolbar_add_switch_action(lv_obj_t* obj) { auto* toolbar = (Toolbar*)obj; lv_obj_t* widget = lv_switch_create(toolbar->action_container); lv_obj_set_style_margin_top(widget, 4, 0); // Because aligning doesn't work + lv_obj_set_style_margin_right(widget, 4, 0); return widget; } diff --git a/Tactility/Source/service/gui/GuiDraw.cpp b/Tactility/Source/service/gui/GuiDraw.cpp index 876cdd51..026abdec 100644 --- a/Tactility/Source/service/gui/GuiDraw.cpp +++ b/Tactility/Source/service/gui/GuiDraw.cpp @@ -59,7 +59,7 @@ void redraw(Gui* gui) { // Unlock GUI and LVGL lvgl::unlock(); } else { - TT_LOG_E(TAG, "failed to lock lvgl"); + TT_LOG_E(TAG, LOG_MESSAGE_MUTEX_LOCK_FAILED_FMT, "LVGL"); } unlock(); diff --git a/Tactility/Source/service/loader/Loader.cpp b/Tactility/Source/service/loader/Loader.cpp index 3fa97cd6..bc8f6c9f 100644 --- a/Tactility/Source/service/loader/Loader.cpp +++ b/Tactility/Source/service/loader/Loader.cpp @@ -1,5 +1,3 @@ -#include "Tactility.h" -#include #include "app/AppManifest.h" #include "app/ManifestRegistry.h" #include "service/ServiceManifest.h" @@ -9,95 +7,72 @@ #ifdef ESP_PLATFORM #include "esp_heap_caps.h" +#include "TactilityHeadless.h" + #else #include "lvgl/LvglSync.h" +#include "TactilityHeadless.h" #endif namespace tt::service::loader { #define TAG "loader" -#define LOADER_EVENT_FLAG 1 typedef struct { LoaderEventType type; } LoaderEventInternal; // Forward declarations -static int32_t loader_main(void* p); +static void onStartAppMessage(std::shared_ptr message); +static void onStopAppMessage(TT_UNUSED std::shared_ptr message); +static void stopAppInternal(); +static LoaderStatus startAppInternal(const std::string& id, std::shared_ptr _Nullable parameters); static Loader* loader_singleton = nullptr; static Loader* loader_alloc() { assert(loader_singleton == nullptr); loader_singleton = new Loader(); - loader_singleton->thread = new Thread( - "loader", - 4096, // Last known minimum was 2400 for starting Hello World app - &loader_main, - nullptr - ); return loader_singleton; } static void loader_free() { tt_assert(loader_singleton != nullptr); - delete loader_singleton->thread; delete loader_singleton; loader_singleton = nullptr; } -static void loader_lock() { - tt_assert(loader_singleton); - tt_check(loader_singleton->mutex.acquire(TtWaitForever) == TtStatusOk); -} - -static void loader_unlock() { - tt_assert(loader_singleton); - tt_check(loader_singleton->mutex.release() == TtStatusOk); -} - -LoaderStatus startApp(const std::string& id, bool blocking, std::shared_ptr parameters) { +void startApp(const std::string& id, bool blocking, std::shared_ptr parameters) { TT_LOG_I(TAG, "Start app %s", id.c_str()); tt_assert(loader_singleton); - LoaderMessageLoaderStatusResult result = { - .value = LoaderStatusOk - }; + auto message = std::make_shared(id, parameters); + loader_singleton->dispatcherThread->dispatch(onStartAppMessage, message); - auto* start_message = new LoaderMessageAppStart(id, parameters); - LoaderMessage message(start_message, result); - - EventFlag* event_flag = blocking ? new EventFlag() : nullptr; - if (event_flag != nullptr) { - message.setApiLock(event_flag); - } - - loader_singleton->queue.put(&message, TtWaitForever); - - if (event_flag != nullptr) { + auto event_flag = message->getApiLockEventFlag(); + if (blocking) { /* TODO: Check if task id is not the LVGL one, because otherwise this fails as the apps starting logic will try to lock lvgl to update the UI and fail. */ - event_flag->wait(LOADER_EVENT_FLAG); - delete event_flag; + event_flag->wait(message->getApiLockEventFlagValue()); } - - return result.value; } void stopApp() { TT_LOG_I(TAG, "Stop app"); tt_check(loader_singleton); - LoaderMessage message(LoaderMessageTypeAppStop); - loader_singleton->queue.put(&message, TtWaitForever); + loader_singleton->dispatcherThread->dispatch(onStopAppMessage, nullptr); } app::AppContext* _Nullable getCurrentApp() { tt_assert(loader_singleton); - loader_lock(); - app::AppInstance* app = loader_singleton->app_stack.top(); - loader_unlock(); - return dynamic_cast(app); + if (loader_singleton->mutex.lock(10 / portTICK_PERIOD_MS)) { + app::AppInstance* app = loader_singleton->app_stack.top(); + loader_singleton->mutex.unlock(); + return dynamic_cast(app); + } else { + return nullptr; + } } std::shared_ptr getPubsub() { @@ -108,7 +83,7 @@ std::shared_ptr getPubsub() { return loader_singleton->pubsub_external; } -static const char* app_state_to_string(app::State state) { +static const char* appStateToString(app::State state) { switch (state) { case app::StateInitial: return "initial"; @@ -125,16 +100,16 @@ static const char* app_state_to_string(app::State state) { } } -static void app_transition_to_state(app::AppInstance& app, app::State state) { +static void transitionAppToState(app::AppInstance& app, app::State state) { const app::AppManifest& manifest = app.getManifest(); const app::State old_state = app.getState(); TT_LOG_I( TAG, - "app \"%s\" state: %s -> %s", + "App \"%s\" state: %s -> %s", manifest.id.c_str(), - app_state_to_string(old_state), - app_state_to_string(state) + appStateToString(old_state), + appStateToString(state) ); switch (state) { @@ -179,30 +154,33 @@ static void app_transition_to_state(app::AppInstance& app, app::State state) { } } -static LoaderStatus loader_do_start_app_with_manifest( +static LoaderStatus startAppWithManifestInternal( const app::AppManifest* manifest, std::shared_ptr _Nullable parameters ) { - TT_LOG_I(TAG, "start with manifest %s", manifest->id.c_str()); + tt_check(loader_singleton != nullptr); - loader_lock(); + TT_LOG_I(TAG, "Start with manifest %s", manifest->id.c_str()); + + auto scoped_lock = loader_singleton->mutex.scoped(); + if (!scoped_lock->lock(50 / portTICK_PERIOD_MS)) { + return LoaderStatusErrorInternal; + } auto previous_app = !loader_singleton->app_stack.empty() ? loader_singleton->app_stack.top() : nullptr; auto new_app = new app::AppInstance(*manifest, parameters); new_app->mutableFlags().showStatusbar = (manifest->type != app::TypeBoot); loader_singleton->app_stack.push(new_app); - app_transition_to_state(*new_app, app::StateInitial); - app_transition_to_state(*new_app, app::StateStarted); + transitionAppToState(*new_app, app::StateInitial); + transitionAppToState(*new_app, app::StateStarted); // We might have to hide the previous app first if (previous_app != nullptr) { - app_transition_to_state(*previous_app, app::StateHiding); + transitionAppToState(*previous_app, app::StateHiding); } - app_transition_to_state(*new_app, app::StateShowing); - - loader_unlock(); + transitionAppToState(*new_app, app::StateShowing); LoaderEventInternal event_internal = {.type = LoaderEventTypeApplicationStarted}; tt_pubsub_publish(loader_singleton->pubsub_internal, &event_internal); @@ -218,7 +196,16 @@ static LoaderStatus loader_do_start_app_with_manifest( return LoaderStatusOk; } -static LoaderStatus do_start_by_id( +static void onStartAppMessage(std::shared_ptr message) { + auto start_message = std::reinterpret_pointer_cast(message); + startAppInternal(start_message->id, start_message->parameters); +} + +static void onStopAppMessage(TT_UNUSED std::shared_ptr message) { + stopAppInternal(); +} + +static LoaderStatus startAppInternal( const std::string& id, std::shared_ptr _Nullable parameters ) { @@ -229,18 +216,21 @@ static LoaderStatus do_start_by_id( TT_LOG_E(TAG, "App not found: %s", id.c_str()); return LoaderStatusErrorUnknownApp; } else { - return loader_do_start_app_with_manifest(manifest, parameters); + return startAppWithManifestInternal(manifest, parameters); } } +static void stopAppInternal() { + tt_check(loader_singleton != nullptr); -static void do_stop_app() { - loader_lock(); + auto scoped_lock = loader_singleton->mutex.scoped(); + if (!scoped_lock->lock(50 / portTICK_PERIOD_MS)) { + return; + } size_t original_stack_size = loader_singleton->app_stack.size(); if (original_stack_size == 0) { - loader_unlock(); TT_LOG_E(TAG, "Stop app: no app running"); return; } @@ -249,7 +239,6 @@ static void do_stop_app() { app::AppInstance* app_to_stop = loader_singleton->app_stack.top(); if (original_stack_size == 1 && app_to_stop->getManifest().type != app::TypeBoot) { - loader_unlock(); TT_LOG_E(TAG, "Stop app: can't stop root app"); return; } @@ -257,8 +246,8 @@ static void do_stop_app() { std::unique_ptr result_holder = std::move(app_to_stop->getResult()); const app::AppManifest& manifest = app_to_stop->getManifest(); - app_transition_to_state(*app_to_stop, app::StateHiding); - app_transition_to_state(*app_to_stop, app::StateStopped); + transitionAppToState(*app_to_stop, app::StateHiding); + transitionAppToState(*app_to_stop, app::StateStopped); loader_singleton->app_stack.pop(); delete app_to_stop; @@ -273,12 +262,14 @@ static void do_stop_app() { if (!loader_singleton->app_stack.empty()) { app_to_resume = loader_singleton->app_stack.top(); tt_assert(app_to_resume); - app_transition_to_state(*app_to_resume, app::StateShowing); + transitionAppToState(*app_to_resume, app::StateShowing); on_result = app_to_resume->getManifest().onResult; } - loader_unlock(); + // Unlock so that we can send results to app and they can also start/stop new apps while processing these results + scoped_lock->unlock(); + // WARNING: After this point we cannot change the app states from this method directly anymore as we don't have a lock! LoaderEventInternal event_internal = {.type = LoaderEventTypeApplicationStopped}; tt_pubsub_publish(loader_singleton->pubsub_internal, &event_internal); @@ -319,61 +310,24 @@ static void do_stop_app() { } } - -static int32_t loader_main(TT_UNUSED void* parameter) { - LoaderMessage message; - bool exit_requested = false; - while (!exit_requested) { - tt_assert(loader_singleton != nullptr); - if (loader_singleton->queue.get(&message, TtWaitForever) == TtStatusOk) { - TT_LOG_I(TAG, "Processing message of type %d", message.type); - switch (message.type) { - case LoaderMessageTypeAppStart: - message.result.status_value.value = do_start_by_id( - message.payload.start->id, - message.payload.start->parameters - ); - if (message.api_lock != nullptr) { - message.api_lock->set(LOADER_EVENT_FLAG); - } - message.cleanup(); - break; - case LoaderMessageTypeAppStop: - do_stop_app(); - break; - case LoaderMessageTypeServiceStop: - exit_requested = true; - break; - case LoaderMessageTypeNone: - break; - } - } - } - - return 0; -} - // region AppManifest static void loader_start(TT_UNUSED ServiceContext& service) { tt_check(loader_singleton == nullptr); loader_singleton = loader_alloc(); - - loader_singleton->thread->setPriority(THREAD_PRIORITY_SERVICE); - loader_singleton->thread->start(); + loader_singleton->dispatcherThread->start(); } static void loader_stop(TT_UNUSED ServiceContext& service) { tt_check(loader_singleton != nullptr); // Send stop signal to thread and wait for thread to finish - loader_lock(); - LoaderMessage message(LoaderMessageTypeServiceStop); - loader_singleton->queue.put(&message, TtWaitForever); - loader_unlock(); + if (!loader_singleton->mutex.lock(2000 / portTICK_PERIOD_MS)) { + TT_LOG_W(TAG, LOG_MESSAGE_MUTEX_LOCK_FAILED_FMT, "loader_stop"); + } + loader_singleton->dispatcherThread->stop(); - loader_singleton->thread->join(); - delete loader_singleton->thread; + loader_singleton->mutex.unlock(); loader_free(); loader_singleton = nullptr; diff --git a/Tactility/Source/service/loader/Loader.h b/Tactility/Source/service/loader/Loader.h index d3df1f73..1a75311e 100644 --- a/Tactility/Source/service/loader/Loader.h +++ b/Tactility/Source/service/loader/Loader.h @@ -23,9 +23,8 @@ typedef enum { * @param[in] id application name or id * @param[in] blocking whether this call is blocking or not. You cannot call this from an LVGL thread. * @param[in] parameters optional parameters to pass onto the application - * @return LoaderStatus */ -LoaderStatus startApp(const std::string& id, bool blocking = false, std::shared_ptr _Nullable parameters = nullptr); +void startApp(const std::string& id, bool blocking = false, std::shared_ptr _Nullable parameters = nullptr); /** * @brief Stop the currently showing app. Show the previous app if any app was still running. diff --git a/Tactility/Source/service/screenshot/Screenshot.cpp b/Tactility/Source/service/screenshot/Screenshot.cpp index d74bd577..8d1ef97f 100644 --- a/Tactility/Source/service/screenshot/Screenshot.cpp +++ b/Tactility/Source/service/screenshot/Screenshot.cpp @@ -1,9 +1,10 @@ +#include "TactilityConfig.h" + +#if TT_FEATURE_SCREENSHOT_ENABLED + #include "Screenshot.h" -#include #include -#include "Mutex.h" -#include "ScreenshotTask.h" #include "service/ServiceContext.h" #include "service/ServiceRegistry.h" @@ -13,105 +14,84 @@ namespace tt::service::screenshot { extern const ServiceManifest manifest; -struct ServiceData { - Mutex mutex; - task::ScreenshotTask* task = nullptr; - Mode mode = ScreenshotModeNone; - - ~ServiceData() { - if (task) { - task::free(task); - } +std::shared_ptr _Nullable optScreenshotService() { + ServiceContext* context = service::findServiceById(manifest.id); + if (context != nullptr) { + return std::static_pointer_cast(context->getData()); + } else { + return nullptr; } +} - void lock() { - tt_check(mutex.acquire(TtWaitForever) == TtStatusOk); - } - - void unlock() { - tt_check(mutex.release() == TtStatusOk); - } -}; - -void startApps(const char* path) { - _Nullable auto* service = findServiceById(manifest.id); - if (service == nullptr) { - TT_LOG_E(TAG, "Service not found"); +void ScreenshotService::startApps(const char* path) { + auto scoped_lockable = mutex.scoped(); + if (!scoped_lockable->lock(50 / portTICK_PERIOD_MS)) { + TT_LOG_W(TAG, LOG_MESSAGE_MUTEX_LOCK_FAILED); return; } - auto data = std::static_pointer_cast(service->getData()); - data->lock(); - if (data->task == nullptr) { - data->task = task::alloc(); - data->mode = ScreenshotModeApps; - task::startApps(data->task, path); + if (task == nullptr || task->isFinished()) { + task = std::make_unique(); + mode = ScreenshotModeApps; + task->startApps(path); } else { - TT_LOG_E(TAG, "Screenshot task already running"); + TT_LOG_W(TAG, "Screenshot task already running"); } - data->unlock(); } -void startTimed(const char* path, uint8_t delay_in_seconds, uint8_t amount) { - _Nullable auto* service = findServiceById(manifest.id); - if (service == nullptr) { - TT_LOG_E(TAG, "Service not found"); +void ScreenshotService::startTimed(const char* path, uint8_t delay_in_seconds, uint8_t amount) { + auto scoped_lockable = mutex.scoped(); + if (!scoped_lockable->lock(50 / portTICK_PERIOD_MS)) { + TT_LOG_W(TAG, LOG_MESSAGE_MUTEX_LOCK_FAILED); return; } - auto data = std::static_pointer_cast(service->getData()); - data->lock(); - if (data->task == nullptr) { - data->task = task::alloc(); - data->mode = ScreenshotModeTimed; - task::startTimed(data->task, path, delay_in_seconds, amount); + if (task == nullptr || task->isFinished()) { + task = std::make_unique(); + mode = ScreenshotModeTimed; + task->startTimed(path, delay_in_seconds, amount); } else { - TT_LOG_E(TAG, "Screenshot task already running"); + TT_LOG_W(TAG, "Screenshot task already running"); } - data->unlock(); } -void stop() { - _Nullable ServiceContext* service = findServiceById(manifest.id); - if (service == nullptr) { - TT_LOG_E(TAG, "Service not found"); +void ScreenshotService::stop() { + auto scoped_lockable = mutex.scoped(); + if (!scoped_lockable->lock(50 / portTICK_PERIOD_MS)) { + TT_LOG_W(TAG, LOG_MESSAGE_MUTEX_LOCK_FAILED); return; } - auto data = std::static_pointer_cast(service->getData()); - data->lock(); - if (data->task != nullptr) { - task::stop(data->task); - task::free(data->task); - data->task = nullptr; - data->mode = ScreenshotModeNone; + if (task != nullptr) { + task = nullptr; + mode = ScreenshotModeNone; } else { - TT_LOG_E(TAG, "Screenshot task not running"); + TT_LOG_W(TAG, "Screenshot task not running"); } - data->unlock(); } -Mode getMode() { - _Nullable auto* service = findServiceById(manifest.id); - if (service == nullptr) { - TT_LOG_E(TAG, "Service not found"); +Mode ScreenshotService::getMode() { + auto scoped_lockable = mutex.scoped(); + if (!scoped_lockable->lock(50 / portTICK_PERIOD_MS)) { + TT_LOG_W(TAG, LOG_MESSAGE_MUTEX_LOCK_FAILED); return ScreenshotModeNone; + } + + return mode; +} + +bool ScreenshotService::isTaskStarted() { + auto* current_task = task.get(); + if (current_task == nullptr) { + return false; } else { - auto data = std::static_pointer_cast(service->getData()); - data->lock(); - Mode mode = data->mode; - data->unlock(); - return mode; + return !current_task->isFinished(); } } -bool isStarted() { - return getMode() != ScreenshotModeNone; -} - -static void onStart(ServiceContext& service) { - auto data = std::make_shared(); - service.setData(data); +static void onStart(ServiceContext& serviceContext) { + auto service = std::make_shared(); + serviceContext.setData(service); } extern const ServiceManifest manifest = { @@ -120,3 +100,5 @@ extern const ServiceManifest manifest = { }; } // namespace + +#endif \ No newline at end of file diff --git a/Tactility/Source/service/screenshot/Screenshot.h b/Tactility/Source/service/screenshot/Screenshot.h index a911584f..b1ca5a8c 100644 --- a/Tactility/Source/service/screenshot/Screenshot.h +++ b/Tactility/Source/service/screenshot/Screenshot.h @@ -1,5 +1,11 @@ #pragma once +#include "Mutex.h" +#include "ScreenshotTask.h" +#include "TactilityConfig.h" + +#if TT_FEATURE_SCREENSHOT_ENABLED + #include namespace tt::service::screenshot { @@ -10,22 +16,23 @@ typedef enum { ScreenshotModeApps } Mode; -/** @brief Starts taking screenshot with a timer - * @param path the path to store the screenshots in - * @param delay_in_seconds the delay before starting (and between successive screenshots) - * @param amount 0 = indefinite, >0 for a specific - */ -void startTimed(const char* path, uint8_t delay_in_seconds, uint8_t amount); -/** @brief Starts taking screenshot when an app is started - * @param path the path to store the screenshots in - */ -void startApps(const char* path); +class ScreenshotService { + Mutex mutex; + std::unique_ptr task; + Mode mode = ScreenshotModeNone; -void stop(); +public: -Mode getMode(); + bool isTaskStarted(); + Mode getMode(); + void startApps(const char* path); + void startTimed(const char* path, uint8_t delay_in_seconds, uint8_t amount); + void stop(); +}; -bool isStarted(); +std::shared_ptr _Nullable optScreenshotService(); } // namespace + +#endif diff --git a/Tactility/Source/service/screenshot/ScreenshotTask.cpp b/Tactility/Source/service/screenshot/ScreenshotTask.cpp index 00dfa40e..d3e40d10 100644 --- a/Tactility/Source/service/screenshot/ScreenshotTask.cpp +++ b/Tactility/Source/service/screenshot/ScreenshotTask.cpp @@ -1,184 +1,184 @@ +#include "TactilityConfig.h" + +#if TT_FEATURE_SCREENSHOT_ENABLED + #include #include "ScreenshotTask.h" #include "lv_screenshot.h" #include "app/AppContext.h" -#include "Mutex.h" #include "TactilityCore.h" -#include "Thread.h" #include "service/loader/Loader.h" #include "lvgl/LvglSync.h" -namespace tt::service::screenshot::task { +namespace tt::service::screenshot { #define TAG "screenshot_task" -#define TASK_WORK_TYPE_DELAY 1 -#define TASK_WORK_TYPE_APPS 2 - -#define SCREENSHOT_PATH_LIMIT 128 - - -struct ScreenshotTaskWork { - int type = TASK_WORK_TYPE_DELAY ; - uint8_t delay_in_seconds = 0; - uint8_t amount = 0; - char path[SCREENSHOT_PATH_LIMIT] = { 0 }; -}; - -struct ScreenshotTaskData { - Thread* thread = nullptr; - Mutex mutex = Mutex(Mutex::TypeRecursive); - bool interrupted = false; - ScreenshotTaskWork work; -}; - -static void task_lock(ScreenshotTaskData* data) { - tt_check(data->mutex.acquire(TtWaitForever) == TtStatusOk); -} - -static void task_unlock(ScreenshotTaskData* data) { - tt_check(data->mutex.release() == TtStatusOk); -} - -ScreenshotTask* alloc() { - return new ScreenshotTaskData(); -} - -void free(ScreenshotTask* task) { - auto* data = static_cast(task); - if (data->thread) { - stop(data); +ScreenshotTask::~ScreenshotTask() { + if (thread) { + stop(); } - delete data; } -static bool is_interrupted(ScreenshotTaskData* data) { - task_lock(data); - bool interrupted = data->interrupted; - task_unlock(data); +bool ScreenshotTask::isInterrupted() { + auto scoped_lockable = mutex.scoped(); + if (!scoped_lockable->lock(50 / portTICK_PERIOD_MS)) { + TT_LOG_W(TAG, LOG_MESSAGE_MUTEX_LOCK_FAILED); + return true; + } return interrupted; } -static int32_t screenshot_task(void* context) { - auto* data = static_cast(context); +bool ScreenshotTask::isFinished() { + auto scoped_lockable = mutex.scoped(); + if (!scoped_lockable->lock(50 / portTICK_PERIOD_MS)) { + TT_LOG_W(TAG, LOG_MESSAGE_MUTEX_LOCK_FAILED); + return false; + } + return finished; +} - bool interrupted = false; +void ScreenshotTask::setFinished() { + auto scoped_lockable = mutex.scoped(); + scoped_lockable->lock(TtWaitForever); + finished = true; +} + +static void makeScreenshot(const char* filename) { + if (lvgl::lock(50 / portTICK_PERIOD_MS)) { + if (lv_screenshot_create(lv_scr_act(), LV_100ASK_SCREENSHOT_SV_PNG, filename)) { + TT_LOG_I(TAG, "Screenshot saved to %s", filename); + } else { + TT_LOG_E(TAG, "Screenshot not saved to %s", filename); + } + lvgl::unlock(); + } else { + TT_LOG_E(TAG, LOG_MESSAGE_MUTEX_LOCK_FAILED_FMT, "LVGL"); + } +} + +static int32_t screenshotTaskCallback(void* context) { + auto* data = static_cast(context); + assert(data != nullptr); + data->taskMain(); + return 0; +} + +void ScreenshotTask::taskMain() { uint8_t screenshots_taken = 0; std::string last_app_id; - while (!interrupted) { - interrupted = is_interrupted(data); - - if (data->work.type == TASK_WORK_TYPE_DELAY) { + while (!isInterrupted()) { + if (work.type == TASK_WORK_TYPE_DELAY) { // Splitting up the delays makes it easier to stop the service - for (int i = 0; i < (data->work.delay_in_seconds * 10) && !is_interrupted(data); ++i){ + for (int i = 0; i < (work.delay_in_seconds * 10) && !isInterrupted(); ++i){ kernel::delayMillis(100); } - if (is_interrupted(data)) { - break; - } + if (!isInterrupted()) { + screenshots_taken++; + char filename[SCREENSHOT_PATH_LIMIT + 32]; + sprintf(filename, "%s/screenshot-%d.png", work.path, screenshots_taken); + makeScreenshot(filename); - screenshots_taken++; - char filename[SCREENSHOT_PATH_LIMIT + 32]; - sprintf(filename, "%s/screenshot-%d.png", data->work.path, screenshots_taken); - lvgl::lock(TtWaitForever); - if (lv_screenshot_create(lv_scr_act(), LV_COLOR_FORMAT_NATIVE, LV_100ASK_SCREENSHOT_SV_PNG, filename)){ - TT_LOG_I(TAG, "Screenshot saved to %s", filename); - } else { - TT_LOG_E(TAG, "Screenshot not saved to %s", filename); + if (work.amount > 0 && screenshots_taken >= work.amount) { + break; // Interrupted loop + } } - lvgl::unlock(); - - if (data->work.amount > 0 && screenshots_taken >= data->work.amount) { - break; // Interrupted loop - } - } else if (data->work.type == TASK_WORK_TYPE_APPS) { + } else if (work.type == TASK_WORK_TYPE_APPS) { app::AppContext* _Nullable app = loader::getCurrentApp(); if (app) { const app::AppManifest& manifest = app->getManifest(); if (manifest.id != last_app_id) { kernel::delayMillis(100); last_app_id = manifest.id; - char filename[SCREENSHOT_PATH_LIMIT + 32]; - sprintf(filename, "%s/screenshot-%s.png", data->work.path, manifest.id.c_str()); - lvgl::lock(TtWaitForever); - if (lv_screenshot_create(lv_scr_act(), LV_COLOR_FORMAT_NATIVE, LV_100ASK_SCREENSHOT_SV_PNG, filename)){ - TT_LOG_I(TAG, "Screenshot saved to %s", filename); - } else { - TT_LOG_E(TAG, "Screenshot not saved to %s", filename); - } - lvgl::unlock(); + sprintf(filename, "%s/screenshot-%s.png", work.path, manifest.id.c_str()); + makeScreenshot(filename); } } + // Ensure the LVGL widgets are rendered as the app just started kernel::delayMillis(250); } } - return 0; + setFinished(); } -static void task_start(ScreenshotTaskData* data) { - task_lock(data); - tt_check(data->thread == nullptr); - data->thread = new Thread( +void ScreenshotTask::taskStart() { + auto scoped_lockable = mutex.scoped(); + if (!scoped_lockable->lock(50 / portTICK_PERIOD_MS)) { + TT_LOG_E(TAG, LOG_MESSAGE_MUTEX_LOCK_FAILED); + return; + } + + tt_check(thread == nullptr); + thread = new Thread( "screenshot", 8192, - &screenshot_task, - data + &screenshotTaskCallback, + this ); - data->thread->start(); - task_unlock(data); + thread->start(); } -void startApps(ScreenshotTask* task, const char* path) { +void ScreenshotTask::startApps(const char* path) { tt_check(strlen(path) < (SCREENSHOT_PATH_LIMIT - 1)); - auto* data = static_cast(task); - task_lock(data); - if (data->thread == nullptr) { - data->interrupted = false; - data->work.type = TASK_WORK_TYPE_APPS; - strcpy(data->work.path, path); - task_start(data); + + auto scoped_lockable = mutex.scoped(); + if (!scoped_lockable->lock(50 / portTICK_PERIOD_MS)) { + TT_LOG_E(TAG, LOG_MESSAGE_MUTEX_LOCK_FAILED); + return; + } + + if (thread == nullptr) { + interrupted = false; + work.type = TASK_WORK_TYPE_APPS; + strcpy(work.path, path); + taskStart(); } else { TT_LOG_E(TAG, "Task was already running"); } - task_unlock(data); } -void startTimed(ScreenshotTask* task, const char* path, uint8_t delay_in_seconds, uint8_t amount) { +void ScreenshotTask::startTimed(const char* path, uint8_t delay_in_seconds, uint8_t amount) { tt_check(strlen(path) < (SCREENSHOT_PATH_LIMIT - 1)); - auto* data = static_cast(task); - task_lock(data); - if (data->thread == nullptr) { - data->interrupted = false; - data->work.type = TASK_WORK_TYPE_DELAY; - data->work.delay_in_seconds = delay_in_seconds; - data->work.amount = amount; - strcpy(data->work.path, path); - task_start(data); + auto scoped_lockable = mutex.scoped(); + if (!scoped_lockable->lock(50 / portTICK_PERIOD_MS)) { + TT_LOG_E(TAG, LOG_MESSAGE_MUTEX_LOCK_FAILED); + return; + } + + if (thread == nullptr) { + interrupted = false; + work.type = TASK_WORK_TYPE_DELAY; + work.delay_in_seconds = delay_in_seconds; + work.amount = amount; + strcpy(work.path, path); + taskStart(); } else { TT_LOG_E(TAG, "Task was already running"); } - task_unlock(data); } -void stop(ScreenshotTask* task) { - auto* data = static_cast(task); - if (data->thread != nullptr) { - task_lock(data); - data->interrupted = true; - task_unlock(data); +void ScreenshotTask::stop() { + if (thread != nullptr) { + if (mutex.lock(50 / portTICK_PERIOD_MS)) { + interrupted = true; + tt_check(mutex.unlock()); + } - data->thread->join(); + thread->join(); - task_lock(data); - delete data->thread; - data->thread = nullptr; - task_unlock(data); + if (mutex.lock(50 / portTICK_PERIOD_MS)) { + delete thread; + thread = nullptr; + tt_check(mutex.unlock()); + } } } } // namespace + +#endif diff --git a/Tactility/Source/service/screenshot/ScreenshotTask.h b/Tactility/Source/service/screenshot/ScreenshotTask.h index 8a6de042..5fbd2d12 100644 --- a/Tactility/Source/service/screenshot/ScreenshotTask.h +++ b/Tactility/Source/service/screenshot/ScreenshotTask.h @@ -1,32 +1,69 @@ +#include "TactilityConfig.h" + +#if TT_FEATURE_SCREENSHOT_ENABLED + #pragma once #include +#include +#include -namespace tt::service::screenshot::task { +namespace tt::service::screenshot { -typedef void ScreenshotTask; +#define TASK_WORK_TYPE_DELAY 1 +#define TASK_WORK_TYPE_APPS 2 -ScreenshotTask* alloc(); +#define SCREENSHOT_PATH_LIMIT 128 -void free(ScreenshotTask* task); +class ScreenshotTask { -/** @brief Start taking screenshots after a certain delay - * @param task the screenshot task - * @param path the path to store the screenshots at - * @param delay_in_seconds the delay before starting (and between successive screenshots) - * @param amount 0 = indefinite, >0 for a specific - */ -void startTimed(ScreenshotTask* task, const char* path, uint8_t delay_in_seconds, uint8_t amount); + struct ScreenshotTaskWork { + int type = TASK_WORK_TYPE_DELAY ; + uint8_t delay_in_seconds = 0; + uint8_t amount = 0; + char path[SCREENSHOT_PATH_LIMIT] = { 0 }; + }; -/** @brief Start taking screenshot whenever an app is started - * @param task the screenshot task - * @param path the path to store the screenshots at - */ -void startApps(ScreenshotTask* task, const char* path); + Thread* thread = nullptr; + Mutex mutex = Mutex(Mutex::TypeRecursive); + bool interrupted = false; + bool finished = false; + ScreenshotTaskWork work; -/** @brief Stop taking screenshots - * @param task the screenshot task - */ -void stop(ScreenshotTask* task); +public: + ScreenshotTask() = default; + ~ScreenshotTask(); + + /** @brief Start taking screenshots after a certain delay + * @param task the screenshot task + * @param path the path to store the screenshots at + * @param delay_in_seconds the delay before starting (and between successive screenshots) + * @param amount 0 = indefinite, >0 for a specific + */ + void startTimed(const char* path, uint8_t delay_in_seconds, uint8_t amount); + + /** @brief Start taking screenshot whenever an app is started + * @param task the screenshot task + * @param path the path to store the screenshots at + */ + void startApps(const char* path); + + /** @brief Stop taking screenshots + * @param task the screenshot task + */ + void stop(); + + void taskMain(); + + bool isFinished(); + +private: + + bool isInterrupted(); + void setFinished(); + void taskStart(); +}; } + +#endif diff --git a/Tactility/Source/service/statusbar/Statusbar.cpp b/Tactility/Source/service/statusbar/Statusbar.cpp index 53b96eb0..a77a1be9 100644 --- a/Tactility/Source/service/statusbar/Statusbar.cpp +++ b/Tactility/Source/service/statusbar/Statusbar.cpp @@ -4,11 +4,12 @@ #include "Tactility.h" #include "hal/Power.h" -#include "hal/sdcard/Sdcard.h" +#include "hal/SdCard.h" #include "lvgl/Statusbar.h" #include "service/ServiceContext.h" #include "service/wifi/Wifi.h" #include "service/ServiceRegistry.h" +#include "TactilityHeadless.h" namespace tt::service::statusbar { @@ -85,25 +86,29 @@ static void update_wifi_icon(std::shared_ptr data) { // region sdcard -static _Nullable const char* sdcard_get_status_icon(hal::sdcard::State state) { +static const char* sdcard_get_status_icon(hal::SdCard::State state) { switch (state) { - case hal::sdcard::StateMounted: + case hal::SdCard::StateMounted: return TT_ASSETS_ICON_SDCARD; - case hal::sdcard::StateError: - case hal::sdcard::StateUnmounted: + case hal::SdCard::StateError: + case hal::SdCard::StateUnmounted: + case hal::SdCard::StateUnknown: return TT_ASSETS_ICON_SDCARD_ALERT; default: - return nullptr; + tt_crash("Unhandled SdCard state"); } } static void update_sdcard_icon(std::shared_ptr data) { - hal::sdcard::State state = hal::sdcard::getState(); - const char* desired_icon = sdcard_get_status_icon(state); - if (data->sdcard_last_icon != desired_icon) { - lvgl::statusbar_icon_set_image(data->sdcard_icon_id, desired_icon); - lvgl::statusbar_icon_set_visibility(data->sdcard_icon_id, desired_icon != nullptr); - data->sdcard_last_icon = desired_icon; + auto sdcard = tt::hal::getConfiguration().sdcard; + if (sdcard != nullptr) { + auto state = sdcard->getState(); + const char* desired_icon = sdcard_get_status_icon(state); + if (data->sdcard_last_icon != desired_icon) { + lvgl::statusbar_icon_set_image(data->sdcard_icon_id, desired_icon); + lvgl::statusbar_icon_set_visibility(data->sdcard_icon_id, desired_icon != nullptr); + data->sdcard_last_icon = desired_icon; + } } } diff --git a/TactilityC/Source/tt_app_alertdialog.cpp b/TactilityC/Source/tt_app_alertdialog.cpp new file mode 100644 index 00000000..ade85d35 --- /dev/null +++ b/TactilityC/Source/tt_app_alertdialog.cpp @@ -0,0 +1,19 @@ +#include "tt_app_alertdialog.h" +#include + +extern "C" { + +void tt_app_alertdialog_start(const char* title, const char* message, const char* buttonLabels[], uint32_t buttonLabelCount) { + std::vector list; + for (int i = 0; i < buttonLabelCount; i++) { + const char* item = buttonLabels[i]; + list.push_back(item); + } + tt::app::alertdialog::start(title, message, list); +} + +int32_t tt_app_alertdialog_get_result_index(BundleHandle handle) { + return tt::app::alertdialog::getResultIndex(*(tt::Bundle*)handle); +} + +} diff --git a/TactilityC/Source/tt_app_alertdialog.h b/TactilityC/Source/tt_app_alertdialog.h new file mode 100644 index 00000000..ac135034 --- /dev/null +++ b/TactilityC/Source/tt_app_alertdialog.h @@ -0,0 +1,16 @@ +#pragma once + +#include "tt_bundle.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +void tt_app_alertdialog_start(const char* title, const char* message, const char* buttonLabels[], uint32_t buttonLabelCount); +int32_t tt_app_alertdialog_get_result_index(BundleHandle handle); + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/TactilityC/Source/tt_app_context.cpp b/TactilityC/Source/tt_app_context.cpp new file mode 100644 index 00000000..01bb1961 --- /dev/null +++ b/TactilityC/Source/tt_app_context.cpp @@ -0,0 +1,36 @@ +#include "tt_app_context.h" +#include + +struct AppContextDataWrapper { + void* _Nullable data; +}; + +extern "C" { + +#define HANDLE_AS_APP_CONTEXT(handle) ((tt::app::AppContext*)(handle)) + +void* _Nullable tt_app_context_get_data(AppContextHandle handle) { + auto wrapper = std::reinterpret_pointer_cast(HANDLE_AS_APP_CONTEXT(handle)->getData()); + return wrapper ? wrapper->data : nullptr; +} + +void tt_app_context_set_data(AppContextHandle handle, void* _Nullable data) { + auto wrapper = std::make_shared(); + wrapper->data = data; + HANDLE_AS_APP_CONTEXT(handle)->setData(std::move(wrapper)); +} + +BundleHandle _Nullable tt_app_context_get_parameters(AppContextHandle handle) { + return (BundleHandle)HANDLE_AS_APP_CONTEXT(handle)->getParameters().get(); +} + +void tt_app_context_set_result(AppContextHandle handle, Result result, BundleHandle _Nullable bundle) { + auto shared_bundle = std::shared_ptr((tt::Bundle*)bundle); + HANDLE_AS_APP_CONTEXT(handle)->setResult((tt::app::Result)result, std::move(shared_bundle)); +} + +bool tt_app_context_has_result(AppContextHandle handle) { + return HANDLE_AS_APP_CONTEXT(handle)->hasResult(); +} + +} \ No newline at end of file diff --git a/TactilityC/Source/tt_app_context.h b/TactilityC/Source/tt_app_context.h new file mode 100644 index 00000000..1b609331 --- /dev/null +++ b/TactilityC/Source/tt_app_context.h @@ -0,0 +1,19 @@ +#pragma once + +#include "tt_app_manifest.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef void* AppContextHandle; + +void* _Nullable tt_app_context_get_data(AppContextHandle handle); +void tt_app_context_set_data(AppContextHandle handle, void* _Nullable data); +BundleHandle _Nullable tt_app_context_get_parameters(AppContextHandle handle); +void tt_app_context_set_result(AppContextHandle handle, Result result, BundleHandle _Nullable bundle); +bool tt_app_context_has_result(AppContextHandle handle); + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/TactilityC/Source/TactilityC/app/App.cpp b/TactilityC/Source/tt_app_manifest.cpp similarity index 94% rename from TactilityC/Source/TactilityC/app/App.cpp rename to TactilityC/Source/tt_app_manifest.cpp index 68f07cc3..912b27dc 100644 --- a/TactilityC/Source/TactilityC/app/App.cpp +++ b/TactilityC/Source/tt_app_manifest.cpp @@ -1,9 +1,10 @@ -#include -#include "App.h" -#include "Log.h" -#include "app/ElfApp.h" +#include "tt_app_manifest.h" -#define TAG "tactilityc_app" +#include +#include +#include + +#define TAG "tt_app" AppOnStart elfOnStart = nullptr; AppOnStop elfOnStop = nullptr; @@ -100,8 +101,8 @@ void tt_set_app_manifest( elfOnResult = onResult; tt::app::setElfAppManifest(manifest); #else - tt_crash("Not intended for PC"); + tt_crash("TactilityC is intended for PC/Simulator"); #endif } -} \ No newline at end of file +} diff --git a/TactilityC/Source/TactilityC/app/App.h b/TactilityC/Source/tt_app_manifest.h similarity index 93% rename from TactilityC/Source/TactilityC/app/App.h rename to TactilityC/Source/tt_app_manifest.h index a3488365..4cee8716 100644 --- a/TactilityC/Source/TactilityC/app/App.h +++ b/TactilityC/Source/tt_app_manifest.h @@ -1,20 +1,20 @@ #pragma once -#include "lvgl.h" +#include "tt_bundle.h" +#include #ifdef __cplusplus extern "C" { #endif -typedef void* AppContextHandle; -typedef void* BundleHandle; - typedef enum { AppResultOk, AppResultCancelled, AppResultError } Result; +typedef void* AppContextHandle; + typedef void (*AppOnStart)(AppContextHandle app); typedef void (*AppOnStop)(AppContextHandle app); typedef void (*AppOnShow)(AppContextHandle app, lv_obj_t* parent); diff --git a/TactilityC/Source/TactilityC/app/SelectionDialog.cpp b/TactilityC/Source/tt_app_selectiondialog.cpp similarity index 56% rename from TactilityC/Source/TactilityC/app/SelectionDialog.cpp rename to TactilityC/Source/tt_app_selectiondialog.cpp index 64471080..7c122554 100644 --- a/TactilityC/Source/TactilityC/app/SelectionDialog.cpp +++ b/TactilityC/Source/tt_app_selectiondialog.cpp @@ -1,4 +1,5 @@ -#include "app/selectiondialog/SelectionDialog.h" +#include "tt_app_selectiondialog.h" +#include extern "C" { @@ -11,4 +12,8 @@ void tt_app_selectiondialog_start(const char* title, int argc, const char* argv[ tt::app::selectiondialog::start(title, list); } +int32_t tt_app_selectiondialog_get_result_index(BundleHandle handle) { + return tt::app::selectiondialog::getResultIndex(*(tt::Bundle*)handle); +} + } diff --git a/TactilityC/Source/TactilityC/app/SelectionDialog.h b/TactilityC/Source/tt_app_selectiondialog.h similarity index 61% rename from TactilityC/Source/TactilityC/app/SelectionDialog.h rename to TactilityC/Source/tt_app_selectiondialog.h index 6c8a9b53..9aed6c35 100644 --- a/TactilityC/Source/TactilityC/app/SelectionDialog.h +++ b/TactilityC/Source/tt_app_selectiondialog.h @@ -1,11 +1,15 @@ #pragma once +#include "tt_bundle.h" + #ifdef __cplusplus extern "C" { #endif void tt_app_selectiondialog_start(const char* title, int argc, const char* argv[]); +int32_t tt_app_selectiondialog_get_result_index(BundleHandle handle); + #ifdef __cplusplus } #endif \ No newline at end of file diff --git a/TactilityC/Source/tt_bundle.cpp b/TactilityC/Source/tt_bundle.cpp new file mode 100644 index 00000000..54846d64 --- /dev/null +++ b/TactilityC/Source/tt_bundle.cpp @@ -0,0 +1,51 @@ +#include +#include "tt_bundle.h" +#include "Bundle.h" + +#define HANDLE_AS_BUNDLE(handle) ((tt::Bundle*)(handle)) + +extern "C" { + +BundleHandle tt_bundle_alloc() { + return new tt::Bundle(); +} + +void tt_bundle_free(BundleHandle handle) { + delete HANDLE_AS_BUNDLE(handle); +} + +bool tt_bundle_opt_bool(BundleHandle handle, const char* key, bool* out) { + return HANDLE_AS_BUNDLE(handle)->optBool(key, *out); +} + +bool tt_bundle_opt_int32(BundleHandle handle, const char* key, int32_t* out) { + return HANDLE_AS_BUNDLE(handle)->optInt32(key, *out); +} +bool tt_bundle_opt_string(BundleHandle handle, const char* key, char* out, uint32_t outSize) { + std::string out_string; + if (HANDLE_AS_BUNDLE(handle)->optString(key, out_string)) { + if (out_string.length() < outSize) { // Need 1 byte to add 0 at the end + memcpy(out, out_string.c_str(), out_string.length()); + out[out_string.length()] = 0x00; + return true; + } else { + return false; + } + } else { + return false; + } +} + +void tt_bundle_put_bool(BundleHandle handle, const char* key, bool value) { + HANDLE_AS_BUNDLE(handle)->putBool(key, value); +} + +void tt_bundle_put_int32(BundleHandle handle, const char* key, int32_t value) { + HANDLE_AS_BUNDLE(handle)->putInt32(key, value); +} + +void tt_bundle_put_string(BundleHandle handle, const char* key, const char* value) { + HANDLE_AS_BUNDLE(handle)->putString(key, value); +} + +} \ No newline at end of file diff --git a/TactilityC/Source/tt_bundle.h b/TactilityC/Source/tt_bundle.h new file mode 100644 index 00000000..a611d5bb --- /dev/null +++ b/TactilityC/Source/tt_bundle.h @@ -0,0 +1,29 @@ +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include + +typedef void* BundleHandle; + +BundleHandle tt_bundle_alloc(); +void tt_bundle_free(BundleHandle handle); + +bool tt_bundle_opt_bool(BundleHandle handle, const char* key, bool* out); +bool tt_bundle_opt_int32(BundleHandle handle, const char* key, int32_t* out); +/** + * Note that outSize must be large enough to include null terminator. + * This means that your string has to be the expected text length + 1 extra character. + */ +bool tt_bundle_opt_string(BundleHandle handle, const char* key, char* out, uint32_t outSize); + +void tt_bundle_put_bool(BundleHandle handle, const char* key, bool value); +void tt_bundle_put_int32(BundleHandle handle, const char* key, int32_t value); +void tt_bundle_put_string(BundleHandle handle, const char* key, const char* value); + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/TactilityC/Source/TactilityC/TactilityC.cpp b/TactilityC/Source/tt_init.cpp similarity index 65% rename from TactilityC/Source/TactilityC/TactilityC.cpp rename to TactilityC/Source/tt_init.cpp index de14c968..a7d49e64 100644 --- a/TactilityC/Source/TactilityC/TactilityC.cpp +++ b/TactilityC/Source/tt_init.cpp @@ -2,21 +2,86 @@ #include "elf_symbol.h" -#include "app/App.h" -#include "app/SelectionDialog.h" -#include "lvgl/Toolbar.h" -#include "TactilityC/lvgl/Spinner.h" +#include "tt_app_context.h" +#include "tt_app_manifest.h" +#include "tt_app_alertdialog.h" +#include "tt_app_selectiondialog.h" +#include "tt_bundle.h" +#include "tt_lvgl_spinner.h" +#include "tt_lvgl_toolbar.h" +#include "tt_message_queue.h" +#include "tt_mutex.h" +#include "tt_semaphore.h" +#include "tt_thread.h" +#include "tt_timer.h" -#include "lvgl.h" +#include extern "C" { const struct esp_elfsym elf_symbols[] { // Tactility + ESP_ELFSYM_EXPORT(tt_app_context_get_data), + ESP_ELFSYM_EXPORT(tt_app_context_set_data), + ESP_ELFSYM_EXPORT(tt_app_context_get_parameters), + ESP_ELFSYM_EXPORT(tt_app_context_set_result), + ESP_ELFSYM_EXPORT(tt_app_context_has_result), ESP_ELFSYM_EXPORT(tt_app_selectiondialog_start), + ESP_ELFSYM_EXPORT(tt_app_selectiondialog_get_result_index), + ESP_ELFSYM_EXPORT(tt_app_alertdialog_start), + ESP_ELFSYM_EXPORT(tt_app_alertdialog_get_result_index), + ESP_ELFSYM_EXPORT(tt_bundle_alloc), + ESP_ELFSYM_EXPORT(tt_bundle_free), + ESP_ELFSYM_EXPORT(tt_bundle_opt_bool), + ESP_ELFSYM_EXPORT(tt_bundle_opt_int32), + ESP_ELFSYM_EXPORT(tt_bundle_opt_string), + ESP_ELFSYM_EXPORT(tt_bundle_put_bool), + ESP_ELFSYM_EXPORT(tt_bundle_put_int32), + ESP_ELFSYM_EXPORT(tt_bundle_put_string), ESP_ELFSYM_EXPORT(tt_set_app_manifest), ESP_ELFSYM_EXPORT(tt_lvgl_toolbar_create), ESP_ELFSYM_EXPORT(tt_lvgl_toolbar_create_simple), + ESP_ELFSYM_EXPORT(tt_message_queue_alloc), + ESP_ELFSYM_EXPORT(tt_message_queue_free), + ESP_ELFSYM_EXPORT(tt_message_queue_put), + ESP_ELFSYM_EXPORT(tt_message_queue_get), + ESP_ELFSYM_EXPORT(tt_message_queue_get_capacity), + ESP_ELFSYM_EXPORT(tt_message_queue_get_message_size), + ESP_ELFSYM_EXPORT(tt_message_queue_get_count), + ESP_ELFSYM_EXPORT(tt_message_queue_reset), + ESP_ELFSYM_EXPORT(tt_mutex_alloc), + ESP_ELFSYM_EXPORT(tt_mutex_free), + ESP_ELFSYM_EXPORT(tt_mutex_lock), + ESP_ELFSYM_EXPORT(tt_mutex_unlock), + ESP_ELFSYM_EXPORT(tt_semaphore_alloc), + ESP_ELFSYM_EXPORT(tt_semaphore_free), + ESP_ELFSYM_EXPORT(tt_semaphore_acquire), + ESP_ELFSYM_EXPORT(tt_semaphore_release), + ESP_ELFSYM_EXPORT(tt_semaphore_get_count), + ESP_ELFSYM_EXPORT(tt_thread_alloc), + ESP_ELFSYM_EXPORT(tt_thread_alloc_ext), + ESP_ELFSYM_EXPORT(tt_thread_free), + ESP_ELFSYM_EXPORT(tt_thread_set_name), + ESP_ELFSYM_EXPORT(tt_thread_mark_as_static), + ESP_ELFSYM_EXPORT(tt_thread_is_marked_as_static), + ESP_ELFSYM_EXPORT(tt_thread_set_stack_size), + ESP_ELFSYM_EXPORT(tt_thread_set_callback), + ESP_ELFSYM_EXPORT(tt_thread_set_priority), + ESP_ELFSYM_EXPORT(tt_thread_set_state_callback), + ESP_ELFSYM_EXPORT(tt_thread_get_state), + ESP_ELFSYM_EXPORT(tt_thread_start), + ESP_ELFSYM_EXPORT(tt_thread_join), + ESP_ELFSYM_EXPORT(tt_thread_get_id), + ESP_ELFSYM_EXPORT(tt_thread_get_return_code), + ESP_ELFSYM_EXPORT(tt_timer_alloc), + ESP_ELFSYM_EXPORT(tt_timer_free), + ESP_ELFSYM_EXPORT(tt_timer_start), + ESP_ELFSYM_EXPORT(tt_timer_restart), + ESP_ELFSYM_EXPORT(tt_timer_stop), + ESP_ELFSYM_EXPORT(tt_timer_is_running), + ESP_ELFSYM_EXPORT(tt_timer_get_expire_time), + ESP_ELFSYM_EXPORT(tt_timer_set_pending_callback), + ESP_ELFSYM_EXPORT(tt_timer_set_thread_priority), // tt::lvgl ESP_ELFSYM_EXPORT(tt_lvgl_spinner_create), // lv_obj diff --git a/TactilityC/Source/TactilityC/TactilityC.h b/TactilityC/Source/tt_init.h similarity index 100% rename from TactilityC/Source/TactilityC/TactilityC.h rename to TactilityC/Source/tt_init.h diff --git a/TactilityC/Source/TactilityC/lvgl/Spinner.cpp b/TactilityC/Source/tt_lvgl_spinner.cpp similarity index 83% rename from TactilityC/Source/TactilityC/lvgl/Spinner.cpp rename to TactilityC/Source/tt_lvgl_spinner.cpp index 6d7fcdd6..a18dcf5c 100644 --- a/TactilityC/Source/TactilityC/lvgl/Spinner.cpp +++ b/TactilityC/Source/tt_lvgl_spinner.cpp @@ -1,4 +1,4 @@ -#include "Spinner.h" +#include "tt_lvgl_spinner.h" #include "lvgl/Spinner.h" extern "C" { diff --git a/TactilityC/Source/TactilityC/lvgl/Spinner.h b/TactilityC/Source/tt_lvgl_spinner.h similarity index 84% rename from TactilityC/Source/TactilityC/lvgl/Spinner.h rename to TactilityC/Source/tt_lvgl_spinner.h index e3e3c2e3..325aa95b 100644 --- a/TactilityC/Source/TactilityC/lvgl/Spinner.h +++ b/TactilityC/Source/tt_lvgl_spinner.h @@ -1,6 +1,6 @@ #pragma once -#include "lvgl.h" +#include #ifdef __cplusplus extern "C" { diff --git a/TactilityC/Source/TactilityC/lvgl/Toolbar.cpp b/TactilityC/Source/tt_lvgl_toolbar.cpp similarity index 84% rename from TactilityC/Source/TactilityC/lvgl/Toolbar.cpp rename to TactilityC/Source/tt_lvgl_toolbar.cpp index bc9ccc86..b761ffbb 100644 --- a/TactilityC/Source/TactilityC/lvgl/Toolbar.cpp +++ b/TactilityC/Source/tt_lvgl_toolbar.cpp @@ -1,5 +1,5 @@ -#include "Toolbar.h" -#include "lvgl/Toolbar.h" +#include "tt_lvgl_toolbar.h" +#include extern "C" { diff --git a/TactilityC/Source/TactilityC/lvgl/Toolbar.h b/TactilityC/Source/tt_lvgl_toolbar.h similarity index 80% rename from TactilityC/Source/TactilityC/lvgl/Toolbar.h rename to TactilityC/Source/tt_lvgl_toolbar.h index f472a50f..0c282c5e 100644 --- a/TactilityC/Source/TactilityC/lvgl/Toolbar.h +++ b/TactilityC/Source/tt_lvgl_toolbar.h @@ -1,7 +1,7 @@ #pragma once -#include "lvgl.h" -#include "TactilityC/app/App.h" +#include +#include "tt_app_context.h" #ifdef __cplusplus extern "C" { diff --git a/TactilityC/Source/tt_message_queue.cpp b/TactilityC/Source/tt_message_queue.cpp new file mode 100644 index 00000000..814cbd2f --- /dev/null +++ b/TactilityC/Source/tt_message_queue.cpp @@ -0,0 +1,40 @@ +#include "tt_message_queue.h" +#include + +#define HANDLE_TO_MESSAGE_QUEUE(handle) ((tt::MessageQueue*)(handle)) + +extern "C" { + +MessageQueueHandle tt_message_queue_alloc(uint32_t capacity, uint32_t messageSize) { + return new tt::MessageQueue(capacity, messageSize); +} + +void tt_message_queue_free(MessageQueueHandle handle) { + delete HANDLE_TO_MESSAGE_QUEUE(handle); +} + +bool tt_message_queue_put(MessageQueueHandle handle, const void* message, uint32_t timeout) { + return HANDLE_TO_MESSAGE_QUEUE(handle)->put(message, timeout); +} + +bool tt_message_queue_get(MessageQueueHandle handle, void* message, uint32_t timeout) { + return HANDLE_TO_MESSAGE_QUEUE(handle)->get(message, timeout); +} + +uint32_t tt_message_queue_get_capacity(MessageQueueHandle handle) { + return HANDLE_TO_MESSAGE_QUEUE(handle)->getCapacity(); +} + +uint32_t tt_message_queue_get_message_size(MessageQueueHandle handle) { + return HANDLE_TO_MESSAGE_QUEUE(handle)->getMessageSize(); +} + +uint32_t tt_message_queue_get_count(MessageQueueHandle handle) { + return HANDLE_TO_MESSAGE_QUEUE(handle)->getCount(); +} + +bool tt_message_queue_reset(MessageQueueHandle handle) { + return HANDLE_TO_MESSAGE_QUEUE(handle)->reset(); +} + +} diff --git a/TactilityC/Source/tt_message_queue.h b/TactilityC/Source/tt_message_queue.h new file mode 100644 index 00000000..c0aac3cf --- /dev/null +++ b/TactilityC/Source/tt_message_queue.h @@ -0,0 +1,23 @@ +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include + +typedef void* MessageQueueHandle; + +MessageQueueHandle tt_message_queue_alloc(uint32_t capacity, uint32_t messageSize); +void tt_message_queue_free(MessageQueueHandle handle); +bool tt_message_queue_put(MessageQueueHandle handle, const void* message, uint32_t timeout); +bool tt_message_queue_get(MessageQueueHandle handle, void* message, uint32_t timeout); +uint32_t tt_message_queue_get_capacity(MessageQueueHandle handle); +uint32_t tt_message_queue_get_message_size(MessageQueueHandle handle); +uint32_t tt_message_queue_get_count(MessageQueueHandle handle); +bool tt_message_queue_reset(MessageQueueHandle handle); + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/TactilityC/Source/tt_mutex.cpp b/TactilityC/Source/tt_mutex.cpp new file mode 100644 index 00000000..82a00450 --- /dev/null +++ b/TactilityC/Source/tt_mutex.cpp @@ -0,0 +1,31 @@ +#include "tt_mutex.h" +#include "Mutex.h" + +extern "C" { + +#define HANDLE_AS_MUTEX(handle) ((tt::Mutex*)(handle)) + +MutexHandle tt_mutex_alloc(enum TtMutexType type) { + switch (type) { + case TtMutexType::MUTEX_TYPE_NORMAL: + return new tt::Mutex(tt::Mutex::TypeNormal); + case TtMutexType::MUTEX_TYPE_RECURSIVE: + return new tt::Mutex(tt::Mutex::TypeRecursive); + default: + tt_crash("Type not supported"); + } +} + +void tt_mutex_free(MutexHandle handle) { + delete HANDLE_AS_MUTEX(handle); +} + +bool tt_mutex_lock(MutexHandle handle, uint32_t timeoutTicks) { + return HANDLE_AS_MUTEX(handle)->lock(timeoutTicks); +} + +bool tt_mutex_unlock(MutexHandle handle) { + return HANDLE_AS_MUTEX(handle)->unlock(); +} + +} \ No newline at end of file diff --git a/TactilityC/Source/tt_mutex.h b/TactilityC/Source/tt_mutex.h new file mode 100644 index 00000000..b4c69c22 --- /dev/null +++ b/TactilityC/Source/tt_mutex.h @@ -0,0 +1,24 @@ +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include + +typedef void* MutexHandle; + +enum TtMutexType { + MUTEX_TYPE_NORMAL, + MUTEX_TYPE_RECURSIVE +}; + +MutexHandle tt_mutex_alloc(enum TtMutexType); +void tt_mutex_free(MutexHandle handle); +bool tt_mutex_lock(MutexHandle handle, uint32_t timeoutTicks); +bool tt_mutex_unlock(MutexHandle handle); + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/TactilityC/Source/tt_semaphore.cpp b/TactilityC/Source/tt_semaphore.cpp new file mode 100644 index 00000000..8921267a --- /dev/null +++ b/TactilityC/Source/tt_semaphore.cpp @@ -0,0 +1,28 @@ +#include "tt_semaphore.h" +#include "Semaphore.h" + +extern "C" { + +#define HANDLE_AS_SEMAPHORE(handle) ((tt::Semaphore*)(handle)) + +SemaphoreHandle tt_semaphore_alloc(uint32_t maxCount, uint32_t initialCount) { + return new tt::Semaphore(maxCount, initialCount); +} + +void tt_semaphore_free(SemaphoreHandle handle) { + delete HANDLE_AS_SEMAPHORE(handle); +} + +bool tt_semaphore_acquire(SemaphoreHandle handle, uint32_t timeoutTicks) { + return HANDLE_AS_SEMAPHORE(handle)->acquire(timeoutTicks); +} + +bool tt_semaphore_release(SemaphoreHandle handle) { + return HANDLE_AS_SEMAPHORE(handle)->release(); +} + +uint32_t tt_semaphore_get_count(SemaphoreHandle handle) { + return HANDLE_AS_SEMAPHORE(handle)->getCount(); +} + +} diff --git a/TactilityC/Source/tt_semaphore.h b/TactilityC/Source/tt_semaphore.h new file mode 100644 index 00000000..1958a0aa --- /dev/null +++ b/TactilityC/Source/tt_semaphore.h @@ -0,0 +1,20 @@ +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include + +typedef void* SemaphoreHandle; + +SemaphoreHandle tt_semaphore_alloc(uint32_t maxCount, uint32_t initialCount); +void tt_semaphore_free(SemaphoreHandle handle); +bool tt_semaphore_acquire(SemaphoreHandle handle, uint32_t timeoutTicks); +bool tt_semaphore_release(SemaphoreHandle handle); +uint32_t tt_semaphore_get_count(SemaphoreHandle handle); + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/TactilityC/Source/tt_service_loader.cpp b/TactilityC/Source/tt_service_loader.cpp new file mode 100644 index 00000000..5f1fdbd7 --- /dev/null +++ b/TactilityC/Source/tt_service_loader.cpp @@ -0,0 +1,20 @@ +#include "tt_service_loader.h" +#include +#include + +extern "C" { + +void tt_service_loader_start_app(const char* id, bool blocking, BundleHandle _Nullable bundle) { + auto shared_bundle = std::shared_ptr((tt::Bundle*)bundle); + tt::service::loader::startApp(id, blocking, std::move(shared_bundle)); +} + +void tt_service_loader_stop_app() { + tt::service::loader::stopApp(); +} + +AppContextHandle tt_service_loader_get_current_app() { + return tt::service::loader::getCurrentApp(); +} + +} diff --git a/TactilityC/Source/tt_service_loader.h b/TactilityC/Source/tt_service_loader.h new file mode 100644 index 00000000..f8b4a8ca --- /dev/null +++ b/TactilityC/Source/tt_service_loader.h @@ -0,0 +1,21 @@ +#pragma once + +#include "tt_bundle.h" +#include "tt_app_context.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @param[in] id application manifest id + * @param[in] blocking whether this operation blocks until the application is started + * @param[in] bundle an allocated bundle (or NULL) of which the memory ownership is handed over to this function + */ +void tt_service_loader_start_app(const char* id, bool blocking, BundleHandle _Nullable bundle); +void tt_service_loader_stop_app(); +AppContextHandle tt_service_loader_get_current_app(); + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/TactilityC/Source/tt_thread.cpp b/TactilityC/Source/tt_thread.cpp new file mode 100644 index 00000000..1263c4fb --- /dev/null +++ b/TactilityC/Source/tt_thread.cpp @@ -0,0 +1,78 @@ +#include "tt_thread.h" +#include + +extern "C" { + +#define HANDLE_AS_THREAD(handle) ((tt::Thread*)(handle)) + +ThreadHandle tt_thread_alloc() { + return new tt::Thread(); +} + +ThreadHandle tt_thread_alloc_ext( + const char* name, + uint32_t stackSize, + ThreadCallback callback, + void* _Nullable callbackContext +) { + return new tt::Thread( + name, + stackSize, + callback, + callbackContext + ); +} + +void tt_thread_free(ThreadHandle handle) { + delete HANDLE_AS_THREAD(handle); +} + +void tt_thread_set_name(ThreadHandle handle, const char* name) { + HANDLE_AS_THREAD(handle)->setName(name); +} + +void tt_thread_mark_as_static(ThreadHandle handle) { + HANDLE_AS_THREAD(handle)->markAsStatic(); +} + +bool tt_thread_is_marked_as_static(ThreadHandle handle) { + return HANDLE_AS_THREAD(handle)->isMarkedAsStatic(); +} + +void tt_thread_set_stack_size(ThreadHandle handle, size_t size) { + HANDLE_AS_THREAD(handle)->setStackSize(size); +} + +void tt_thread_set_callback(ThreadHandle handle, ThreadCallback callback, void* _Nullable callbackContext) { + HANDLE_AS_THREAD(handle)->setCallback(callback, callbackContext); +} + +void tt_thread_set_priority(ThreadHandle handle, ThreadPriority priority) { + HANDLE_AS_THREAD(handle)->setPriority((tt::Thread::Priority)priority); +} + +void tt_thread_set_state_callback(ThreadHandle handle, ThreadStateCallback callback, void* _Nullable callbackContext) { + HANDLE_AS_THREAD(handle)->setStateCallback((tt::Thread::StateCallback)callback, callbackContext); +} + +ThreadState tt_thread_get_state(ThreadHandle handle) { + return (ThreadState)HANDLE_AS_THREAD(handle)->getState(); +} + +void tt_thread_start(ThreadHandle handle) { + HANDLE_AS_THREAD(handle)->start(); +} + +bool tt_thread_join(ThreadHandle handle) { + return HANDLE_AS_THREAD(handle)->join(); +} + +ThreadId tt_thread_get_id(ThreadHandle handle) { + return HANDLE_AS_THREAD(handle)->getId(); +} + +int32_t tt_thread_get_return_code(ThreadHandle handle) { + return HANDLE_AS_THREAD(handle)->getReturnCode(); +} + +} \ No newline at end of file diff --git a/TactilityC/Source/tt_thread.h b/TactilityC/Source/tt_thread.h new file mode 100644 index 00000000..d21cc7e3 --- /dev/null +++ b/TactilityC/Source/tt_thread.h @@ -0,0 +1,74 @@ +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef ESP_PLATFORM +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#else +#include "FreeRTOS.h" +#include "task.h" +#endif + +#include +#include +#include + +typedef void* ThreadHandle; + +typedef enum { + ThreadStateStopped, + ThreadStateStarting, + ThreadStateRunning, +} ThreadState; + +typedef TaskHandle_t ThreadId; + +/** ThreadCallback Your callback to run in new thread + * @warning never use osThreadExit in Thread + */ +typedef int32_t (*ThreadCallback)(void* context); + +/** Thread state change callback called upon thread state change + * @param state new thread state + * @param context callback context + */ +typedef void (*ThreadStateCallback)(ThreadState state, void* context); + +typedef enum { + ThreadPriorityNone = 0, /**< Uninitialized, choose system default */ + ThreadPriorityIdle = 1, + ThreadPriorityLowest = 2, + ThreadPriorityLow = 3, + ThreadPriorityNormal = 4, + ThreadPriorityHigh = 5, + ThreadPriorityHigher = 6, + ThreadPriorityHighest = 7 +} ThreadPriority; + +ThreadHandle tt_thread_alloc(); +ThreadHandle tt_thread_alloc_ext( + const char* name, + uint32_t stackSize, + ThreadCallback callback, + void* _Nullable callbackContext +); +void tt_thread_free(ThreadHandle handle); +void tt_thread_set_name(ThreadHandle handle, const char* name); +void tt_thread_mark_as_static(ThreadHandle handle); +bool tt_thread_is_marked_as_static(ThreadHandle handle); +void tt_thread_set_stack_size(ThreadHandle handle, size_t size); +void tt_thread_set_callback(ThreadHandle handle, ThreadCallback callback, void* _Nullable callbackContext); +void tt_thread_set_priority(ThreadHandle handle, ThreadPriority priority); +void tt_thread_set_state_callback(ThreadHandle handle, ThreadStateCallback callback, void* _Nullable callbackContext); +ThreadState tt_thread_get_state(ThreadHandle handle); +void tt_thread_start(ThreadHandle handle); +bool tt_thread_join(ThreadHandle handle); +ThreadId tt_thread_get_id(ThreadHandle handle); +int32_t tt_thread_get_return_code(ThreadHandle handle); + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/TactilityC/Source/tt_timer.cpp b/TactilityC/Source/tt_timer.cpp new file mode 100644 index 00000000..a593c570 --- /dev/null +++ b/TactilityC/Source/tt_timer.cpp @@ -0,0 +1,65 @@ +#include "tt_timer.h" +#include + +struct TimerWrapper { + std::unique_ptr timer; + TimerCallback callback; + void* _Nullable callbackContext; +}; + +extern "C" { + + +static void callbackWrapper(std::shared_ptr wrapper) { + auto timer_wrapper = (TimerWrapper*)wrapper.get(); + timer_wrapper->callback(timer_wrapper->callbackContext); +} + +TimerHandle tt_timer_alloc(TimerType type, TimerCallback callback, void* callbackContext) { + auto wrapper = std::make_shared(); + wrapper->callback = callback; + wrapper->callbackContext = callbackContext; + wrapper->timer = std::make_unique((tt::Timer::Type)type, callbackWrapper, wrapper); + return wrapper.get(); +} + +void tt_timer_free(TimerHandle handle) { + auto* wrapper = (TimerWrapper*)handle; + wrapper->timer = nullptr; + delete wrapper; +} + +bool tt_timer_start(TimerHandle handle, uint32_t intervalTicks) { + return ((TimerWrapper*)handle)->timer->start(intervalTicks); +} + +bool tt_timer_restart(TimerHandle handle, uint32_t intervalTicks) { + return ((TimerWrapper*)handle)->timer->restart(intervalTicks); +} + +bool tt_timer_stop(TimerHandle handle) { + return ((TimerWrapper*)handle)->timer->stop(); +} + +bool tt_timer_is_running(TimerHandle handle) { + return ((TimerWrapper*)handle)->timer->isRunning(); +} + +uint32_t tt_timer_get_expire_time(TimerHandle handle) { + return ((TimerWrapper*)handle)->timer->getExpireTime(); +} + +bool tt_timer_set_pending_callback(TimerHandle handle, TimerPendingCallback callback, void* callbackContext, uint32_t arg) { + return ((TimerWrapper*)handle)->timer->setPendingCallback( + callback, + callbackContext, + arg + ); +} + +void tt_timer_set_thread_priority(TimerHandle handle, TimerThreadPriority priority) { + ((TimerWrapper*)handle)->timer->setThreadPriority((tt::Timer::ThreadPriority)priority); +} + +} + diff --git a/TactilityC/Source/tt_timer.h b/TactilityC/Source/tt_timer.h new file mode 100644 index 00000000..eba26d30 --- /dev/null +++ b/TactilityC/Source/tt_timer.h @@ -0,0 +1,37 @@ +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include + +typedef void* TimerHandle; + +typedef enum { + TimerTypeOnce = 0, ///< One-shot timer. + TimerTypePeriodic = 1 ///< Repeating timer. +} TimerType; + +typedef enum { + TimerThreadPriorityNormal, /**< Lower then other threads */ + TimerThreadPriorityElevated, /**< Same as other threads */ +} TimerThreadPriority; + +typedef void (*TimerCallback)(void* context); +typedef void (*TimerPendingCallback)(void* context, uint32_t arg); + +TimerHandle tt_timer_alloc(TimerType type, TimerCallback callback, void* callbackContext); +void tt_timer_free(TimerHandle handle); +bool tt_timer_start(TimerHandle handle, uint32_t intervalTicks); +bool tt_timer_restart(TimerHandle handle, uint32_t intervalTicks); +bool tt_timer_stop(TimerHandle handle); +bool tt_timer_is_running(TimerHandle handle); +uint32_t tt_timer_get_expire_time(TimerHandle handle); +bool tt_timer_set_pending_callback(TimerHandle handle, TimerPendingCallback callback, void* callbackContext, uint32_t arg); +void tt_timer_set_thread_priority(TimerHandle handle, TimerThreadPriority priority); + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/TactilityCore/Source/CoreTypes.h b/TactilityCore/Source/CoreTypes.h index 0b9647ec..d265dcb1 100644 --- a/TactilityCore/Source/CoreTypes.h +++ b/TactilityCore/Source/CoreTypes.h @@ -5,10 +5,12 @@ namespace tt { +[[deprecated("Using this is poor software design in most scenarios")]] typedef enum { TtWaitForever = 0xFFFFFFFFU, } TtWait; +[[deprecated("Define flags as needed")]] typedef enum { TtFlagWaitAny = 0x00000000U, ///< Wait for any flag (default). TtFlagWaitAll = 0x00000001U, ///< Wait for all flags. @@ -22,6 +24,7 @@ typedef enum { TtFlagErrorISR = 0xFFFFFFFAU, ///< TtStatusErrorISR (-6). } TtFlag; +[[deprecated("Use bool or specific type")]] typedef enum { TtStatusOk = 0, ///< Operation completed successfully. TtStatusError = diff --git a/TactilityCore/Source/Dispatcher.cpp b/TactilityCore/Source/Dispatcher.cpp index db497c23..dabf0d7e 100644 --- a/TactilityCore/Source/Dispatcher.cpp +++ b/TactilityCore/Source/Dispatcher.cpp @@ -1,3 +1,4 @@ +#include #include "Dispatcher.h" #include "Check.h" @@ -19,32 +20,59 @@ Dispatcher::~Dispatcher() { void Dispatcher::dispatch(Callback callback, std::shared_ptr context) { auto message = std::make_shared(callback, std::move(context)); // Mutate - mutex.acquire(TtWaitForever); - queue.push(std::move(message)); - if (queue.size() == BACKPRESSURE_WARNING_COUNT) { - TT_LOG_W(TAG, "Backpressure: You're not consuming fast enough (100 queued)"); + if (mutex.lock(1000 / portTICK_PERIOD_MS)) { + queue.push(std::move(message)); + TT_LOG_I(TAG, "dispatch"); + if (queue.size() == BACKPRESSURE_WARNING_COUNT) { + TT_LOG_W(TAG, "Backpressure: You're not consuming fast enough (100 queued)"); + } + mutex.unlock(); + // Signal + eventFlag.set(1); + } else { + TT_LOG_E(TAG, LOG_MESSAGE_MUTEX_LOCK_FAILED); } - mutex.release(); - // Signal - eventFlag.set(1); } uint32_t Dispatcher::consume(uint32_t timeout_ticks) { // Wait for signal and clear - eventFlag.wait(1, TtFlagWaitAny, timeout_ticks); - eventFlag.clear(1); - - // Mutate - if (mutex.acquire(1 / portTICK_PERIOD_MS) == TtStatusOk) { - auto item = queue.front(); - queue.pop(); - // Don't keep lock as callback might be slow - tt_check(mutex.release() == TtStatusOk); - - item->callback(item->context); + TickType_t start_ticks = kernel::getTicks(); + if (eventFlag.wait(1, TtFlagWaitAny, timeout_ticks) == TtStatusErrorTimeout) { + return 0; } - return true; + TickType_t ticks_remaining = TT_MAX(timeout_ticks - (kernel::getTicks() - start_ticks), 0); + + eventFlag.clear(1); + + TT_LOG_I(TAG, "Dispatcher continuing"); + + // Mutate + bool processing = true; + uint32_t consumed = 0; + do { + if (mutex.lock(ticks_remaining / portTICK_PERIOD_MS)) { + if (!queue.empty()) { + TT_LOG_I(TAG, "Dispatcher popping from queue"); + auto item = queue.front(); + queue.pop(); + consumed++; + processing = !queue.empty(); + // Don't keep lock as callback might be slow + + tt_check(mutex.unlock()); + item->callback(item->context); + } else { + tt_check(mutex.unlock()); + } + + } else { + TT_LOG_E(TAG, LOG_MESSAGE_MUTEX_LOCK_FAILED); + } + + } while (processing); + + return consumed; } } // namespace diff --git a/TactilityCore/Source/DispatcherThread.cpp b/TactilityCore/Source/DispatcherThread.cpp new file mode 100644 index 00000000..506fe748 --- /dev/null +++ b/TactilityCore/Source/DispatcherThread.cpp @@ -0,0 +1,46 @@ +#include "DispatcherThread.h" + +namespace tt { + +int32_t dispatcherThreadMain(void* context) { + auto* dispatcherThread = (DispatcherThread*)context; + dispatcherThread->_threadMain(); + return 0; +} + +DispatcherThread::DispatcherThread(const std::string& threadName, size_t threadStackSize) { + thread = std::make_unique( + threadName, + threadStackSize, + dispatcherThreadMain, + this + ); +} + +DispatcherThread::~DispatcherThread() { + if (thread->getState() != Thread::StateStopped) { + stop(); + } +} + +void DispatcherThread::_threadMain() { + do { + dispatcher.consume(1000); + } while (!interruptThread); +} + +void DispatcherThread::dispatch(Callback callback, std::shared_ptr context) { + dispatcher.dispatch(callback, std::move(context)); +} + +void DispatcherThread::start() { + interruptThread = false; + thread->start(); +} + +void DispatcherThread::stop() { + interruptThread = true; + thread->join(); +} + +} \ No newline at end of file diff --git a/TactilityCore/Source/DispatcherThread.h b/TactilityCore/Source/DispatcherThread.h new file mode 100644 index 00000000..76499b96 --- /dev/null +++ b/TactilityCore/Source/DispatcherThread.h @@ -0,0 +1,34 @@ +#pragma once + +#include "Dispatcher.h" + +namespace tt { + +/** Starts a Thread to process dispatched messages */ +class DispatcherThread { + + Dispatcher dispatcher; + std::unique_ptr thread; + bool interruptThread = false; + +public: + + explicit DispatcherThread(const std::string& threadName, size_t threadStackSize = 4096); + ~DispatcherThread(); + + /** + * Dispatch a message. + */ + void dispatch(Callback callback, std::shared_ptr context); + + /** Start the thread (blocking). */ + void start(); + + /** Stop the thread (blocking). */ + void stop(); + + /** Internal method */ + void _threadMain(); +}; + +} \ No newline at end of file diff --git a/TactilityCore/Source/Lockable.cpp b/TactilityCore/Source/Lockable.cpp new file mode 100644 index 00000000..4e2c9f0d --- /dev/null +++ b/TactilityCore/Source/Lockable.cpp @@ -0,0 +1,10 @@ +#include "Lockable.h" + +namespace tt { + +std::unique_ptr Lockable::scoped() const { + auto* scoped = new ScopedLockableUsage(*this); + return std::unique_ptr(scoped); +} + +} diff --git a/TactilityCore/Source/Lockable.h b/TactilityCore/Source/Lockable.h new file mode 100644 index 00000000..07b7c53c --- /dev/null +++ b/TactilityCore/Source/Lockable.h @@ -0,0 +1,41 @@ +#pragma once + +#include "Check.h" +#include + +namespace tt { + +class ScopedLockableUsage; + +class Lockable { +public: + virtual ~Lockable() = default; + + virtual bool lock(uint32_t timeoutTicks) const = 0; + virtual bool unlock() const = 0; + + std::unique_ptr scoped() const; +}; + +class ScopedLockableUsage final : public Lockable { + + const Lockable& lockable; + +public: + + explicit ScopedLockableUsage(const Lockable& lockable) : lockable(lockable) {} + + ~ScopedLockableUsage() final { + lockable.unlock(); // We don't care whether it succeeded or not + } + + bool lock(uint32_t timeout) const override { + return lockable.lock(timeout); + } + + bool unlock() const override { + return lockable.unlock(); + } +}; + +} diff --git a/TactilityCore/Source/Log.h b/TactilityCore/Source/Log.h index 84eb23ee..8a8e354d 100644 --- a/TactilityCore/Source/Log.h +++ b/TactilityCore/Source/Log.h @@ -1,5 +1,7 @@ #pragma once +#include "LogMessages.h" + #ifdef ESP_TARGET #include "esp_log.h" #else diff --git a/TactilityCore/Source/LogMessages.h b/TactilityCore/Source/LogMessages.h new file mode 100644 index 00000000..48178061 --- /dev/null +++ b/TactilityCore/Source/LogMessages.h @@ -0,0 +1,22 @@ +/** + * Contains common log messages. + * This helps to keep the binary smaller. + */ +#pragma once + +// Mutex +#define LOG_MESSAGE_MUTEX_LOCK_FAILED "Mutex acquisition timeout" +#define LOG_MESSAGE_MUTEX_LOCK_FAILED_FMT "Mutex acquisition timeout (%s)" + +// SPI +#define LOG_MESSAGE_SPI_INIT_START_FMT "SPI %d init" +#define LOG_MESSAGE_SPI_INIT_FAILED_FMT "SPI %d init failed" + +// I2C +#define LOG_MESSAGE_I2C_INIT_START "I2C init" +#define LOG_MESSAGE_I2C_INIT_CONFIG_FAILED "I2C config failed" +#define LOG_MESSAGE_I2C_INIT_DRIVER_INSTALL_FAILED "I2C driver install failed" + +// Power on +#define LOG_MESSAGE_POWER_ON_START "Power on" +#define LOG_MESSAGE_POWER_ON_FAILED "Power on failed" diff --git a/TactilityCore/Source/MessageQueue.cpp b/TactilityCore/Source/MessageQueue.cpp index d62dc7f1..1cb8767e 100644 --- a/TactilityCore/Source/MessageQueue.cpp +++ b/TactilityCore/Source/MessageQueue.cpp @@ -4,9 +4,9 @@ namespace tt { -MessageQueue::MessageQueue(uint32_t msg_count, uint32_t msg_size) { - tt_assert((kernel::isIrq() == 0U) && (msg_count > 0U) && (msg_size > 0U)); - queue_handle = xQueueCreate(msg_count, msg_size); +MessageQueue::MessageQueue(uint32_t capacity, uint32_t msg_size) { + tt_assert((kernel::isIrq() == 0U) && (capacity > 0U) && (msg_size > 0U)); + queue_handle = xQueueCreate(capacity, msg_size); tt_check(queue_handle); } @@ -15,106 +15,75 @@ MessageQueue::~MessageQueue() { vQueueDelete(queue_handle); } -TtStatus MessageQueue::put(const void* msg_ptr, uint32_t timeout) { - TtStatus stat; +bool MessageQueue::put(const void* message, uint32_t timeout) { + bool result = true; BaseType_t yield; - stat = TtStatusOk; - if (kernel::isIrq() != 0U) { - if ((queue_handle == nullptr) || (msg_ptr == nullptr) || (timeout != 0U)) { - stat = TtStatusErrorParameter; + if ((queue_handle == nullptr) || (message == nullptr) || (timeout != 0U)) { + result = false; } else { yield = pdFALSE; - if (xQueueSendToBackFromISR(queue_handle, msg_ptr, &yield) != pdTRUE) { - stat = TtStatusErrorResource; + if (xQueueSendToBackFromISR(queue_handle, message, &yield) != pdTRUE) { + result = false; } else { portYIELD_FROM_ISR(yield); } } - } else { - if ((queue_handle == nullptr) || (msg_ptr == nullptr)) { - stat = TtStatusErrorParameter; - } else { - if (xQueueSendToBack(queue_handle, msg_ptr, (TickType_t)timeout) != pdPASS) { - if (timeout != 0U) { - stat = TtStatusErrorTimeout; - } else { - stat = TtStatusErrorResource; - } - } - } + } else if ((queue_handle == nullptr) || (message == nullptr)) { + result = false; + } else if (xQueueSendToBack(queue_handle, message, (TickType_t)timeout) != pdPASS) { + result = false; } - /* Return execution status */ - return (stat); + return result; } -TtStatus MessageQueue::get(void* msg_ptr, uint32_t timeout_ticks) { - TtStatus stat; +bool MessageQueue::get(void* msg_ptr, uint32_t timeout_ticks) { + bool result = true; BaseType_t yield; - stat = TtStatusOk; - if (kernel::isIrq() != 0U) { + if (kernel::isIrq()) { if ((queue_handle == nullptr) || (msg_ptr == nullptr) || (timeout_ticks != 0U)) { - stat = TtStatusErrorParameter; + result = false; } else { yield = pdFALSE; if (xQueueReceiveFromISR(queue_handle, msg_ptr, &yield) != pdPASS) { - stat = TtStatusErrorResource; + result = false; } else { portYIELD_FROM_ISR(yield); } } } else { if ((queue_handle == nullptr) || (msg_ptr == nullptr)) { - stat = TtStatusErrorParameter; - } else { - if (xQueueReceive(queue_handle, msg_ptr, (TickType_t)timeout_ticks) != pdPASS) { - if (timeout_ticks != 0U) { - stat = TtStatusErrorTimeout; - } else { - stat = TtStatusErrorResource; - } - } + result = false; + } else if (xQueueReceive(queue_handle, msg_ptr, (TickType_t)timeout_ticks) != pdPASS) { + result = false; } } - /* Return execution status */ - return (stat); + return result; } uint32_t MessageQueue::getCapacity() const { auto* mq = (StaticQueue_t*)(queue_handle); - uint32_t capacity; - if (mq == nullptr) { - capacity = 0U; + return 0U; } else { - /* capacity = pxQueue->uxLength */ - capacity = mq->uxDummy4[1]; + return mq->uxDummy4[1]; } - - /* Return maximum number of messages */ - return (capacity); } uint32_t MessageQueue::getMessageSize() const { auto* mq = (StaticQueue_t*)(queue_handle); - uint32_t size; - if (mq == nullptr) { - size = 0U; + return 0U; } else { - /* size = pxQueue->uxItemSize */ - size = mq->uxDummy4[2]; + return mq->uxDummy4[2]; } - - /* Return maximum message size */ - return (size); } uint32_t MessageQueue::getCount() const { @@ -129,7 +98,7 @@ uint32_t MessageQueue::getCount() const { } /* Return number of queued messages */ - return ((uint32_t)count); + return (uint32_t)count; } uint32_t MessageQueue::getSpace() const { @@ -150,24 +119,17 @@ uint32_t MessageQueue::getSpace() const { space = (uint32_t)uxQueueSpacesAvailable((QueueHandle_t)mq); } - /* Return number of available slots */ - return (space); + return space; } -TtStatus MessageQueue::reset() { - TtStatus stat; - - if (kernel::isIrq() != 0U) { - stat = TtStatusErrorISR; - } else if (queue_handle == nullptr) { - stat = TtStatusErrorParameter; +bool MessageQueue::reset() { + tt_check(!kernel::isIrq()); + if (queue_handle == nullptr) { + return false; } else { - stat = TtStatusOk; - (void)xQueueReset(queue_handle); + xQueueReset(queue_handle); + return true; } - - /* Return execution status */ - return (stat); } } // namespace diff --git a/TactilityCore/Source/MessageQueue.h b/TactilityCore/Source/MessageQueue.h index 30af0f4b..8bbe7a32 100644 --- a/TactilityCore/Source/MessageQueue.h +++ b/TactilityCore/Source/MessageQueue.h @@ -19,81 +19,61 @@ namespace tt { +/** + * Message Queue implementation. + * Calls can be done from ISR/IRQ mode unless otherwise specified. + */ class MessageQueue { private: QueueHandle_t queue_handle; public: /** Allocate message queue - * - * @param[in] msg_count The message count - * @param[in] msg_size The message size + * @param[in] capacity Maximum messages in queue + * @param[in] messageSize The size in bytes of a single message */ - MessageQueue(uint32_t msg_count, uint32_t msg_size); + MessageQueue(uint32_t capacity, uint32_t messageSize); ~MessageQueue(); /** Put message into queue - * - * @param instance pointer to MessageQueue instance - * @param[in] msg_ptr The message pointer - * @param[in] timeout The timeout - * @param[in] msg_prio The message prio - * - * @return The status. + * @param[in] message A pointer to a message. The message will be copied into a buffer. + * @param[in] timeoutTicks + * @return success result */ - TtStatus put(const void* msg_ptr, uint32_t timeout); + bool put(const void* message, uint32_t timeoutTicks); /** Get message from queue - * - * @param instance pointer to MessageQueue instance - * @param msg_ptr The message pointer - * @param msg_prio The message prioority - * @param[in] timeout_ticks The timeout - * - * @return The status. + * @param message A pointer to an already allocated message object + * @param[in] timeoutTicks + * @return success result */ - TtStatus get(void* msg_ptr, uint32_t timeout_ticks); + bool get(void* message, uint32_t timeoutTicks); - /** Get queue capacity - * - * @param instance pointer to MessageQueue instance - * - * @return capacity in object count + /** + * @return The maximum amount of messages that can be in the queue at any given time. */ uint32_t getCapacity() const; - /** Get message size - * - * @param instance pointer to MessageQueue instance - * - * @return Message size in bytes + /** + * @return The size of a single message in bytes */ uint32_t getMessageSize() const; - /** Get message count in queue - * - * @param instance pointer to MessageQueue instance - * - * @return Message count + /** + * @return How many messages are currently in the queue. */ uint32_t getCount() const; - /** Get queue available space - * - * @param instance pointer to MessageQueue instance - * - * @return Message count + /** + * @return How many messages can be added to the queue before the put() method starts blocking. */ uint32_t getSpace() const; - /** Reset queue - * - * @param instance pointer to MessageQueue instance - * - * @return The status. + /** Reset queue (cannot be called in ISR/IRQ mode) + * @return success result */ - TtStatus reset(); + bool reset(); }; } // namespace diff --git a/TactilityCore/Source/Mutex.cpp b/TactilityCore/Source/Mutex.cpp index 49f80a9c..085cce89 100644 --- a/TactilityCore/Source/Mutex.cpp +++ b/TactilityCore/Source/Mutex.cpp @@ -110,11 +110,6 @@ ThreadId Mutex::getOwner() const { return (ThreadId)xSemaphoreGetMutexHolder(semaphore); } - -std::unique_ptr Mutex::scoped() const { - return std::make_unique(*this); -} - Mutex* tt_mutex_alloc(Mutex::Type type) { return new Mutex(type); } diff --git a/TactilityCore/Source/Mutex.h b/TactilityCore/Source/Mutex.h index 26b79d43..030650fc 100644 --- a/TactilityCore/Source/Mutex.h +++ b/TactilityCore/Source/Mutex.h @@ -8,6 +8,7 @@ #include "Thread.h" #include "RtosCompatSemaphore.h" #include "Check.h" +#include "Lockable.h" #include namespace tt { @@ -18,7 +19,7 @@ class ScopedMutexUsage; * Wrapper for FreeRTOS xSemaphoreCreateMutex and xSemaphoreCreateRecursiveMutex * Can be used in IRQ mode (within ISR context) */ -class Mutex { +class Mutex : public Lockable { public: @@ -35,35 +36,15 @@ private: public: explicit Mutex(Type type = TypeNormal); - ~Mutex(); + ~Mutex() override; - TtStatus acquire(uint32_t timeout) const; + TtStatus acquire(uint32_t timeoutTicks) const; TtStatus release() const; + + bool lock(uint32_t timeoutTicks) const override { return acquire(timeoutTicks) == TtStatusOk; } + bool unlock() const override { return release() == TtStatusOk; } + ThreadId getOwner() const; - - std::unique_ptr scoped() const; -}; - -class ScopedMutexUsage { - - const Mutex& mutex; - bool acquired = false; - -public: - - ScopedMutexUsage(const Mutex& mutex) : mutex(mutex) {} - - ~ScopedMutexUsage() { - if (acquired) { - tt_check(mutex.release() == TtStatusOk); - } - } - - bool acquire(uint32_t timeout) { - TtStatus result = mutex.acquire(timeout); - acquired = (result == TtStatusOk); - return acquired; - } }; /** Allocate Mutex diff --git a/TactilityCore/Source/Semaphore.cpp b/TactilityCore/Source/Semaphore.cpp index 2cfa398c..f54a6624 100644 --- a/TactilityCore/Source/Semaphore.cpp +++ b/TactilityCore/Source/Semaphore.cpp @@ -28,49 +28,36 @@ Semaphore::~Semaphore() { vSemaphoreDelete(handle); } -TtStatus Semaphore::acquire(uint32_t timeout) const { +bool Semaphore::acquire(uint32_t timeout) const { if (TT_IS_IRQ_MODE()) { if (timeout != 0U) { - return TtStatusErrorParameter; + return false; } else { BaseType_t yield = pdFALSE; if (xSemaphoreTakeFromISR(handle, &yield) != pdPASS) { - return TtStatusErrorResource; + return false; } else { portYIELD_FROM_ISR(yield); - return TtStatusOk; + return true; } } } else { - if (xSemaphoreTake(handle, (TickType_t)timeout) != pdPASS) { - if (timeout != 0U) { - return TtStatusErrorTimeout; - } else { - return TtStatusErrorResource; - } - } else { - return TtStatusOk; - } + return xSemaphoreTake(handle, (TickType_t)timeout) == pdPASS; } } -TtStatus Semaphore::release() const { +bool Semaphore::release() const { if (TT_IS_IRQ_MODE()) { BaseType_t yield = pdFALSE; - if (xSemaphoreGiveFromISR(handle, &yield) != pdTRUE) { - return TtStatusErrorResource; + return false; } else { portYIELD_FROM_ISR(yield); - return TtStatusOk; + return true; } } else { - if (xSemaphoreGive(handle) != pdPASS) { - return TtStatusErrorResource; - } else { - return TtStatusOk; - } + return xSemaphoreGive(handle) == pdPASS; } } diff --git a/TactilityCore/Source/Semaphore.h b/TactilityCore/Source/Semaphore.h index 79bd675c..da7f0497 100644 --- a/TactilityCore/Source/Semaphore.h +++ b/TactilityCore/Source/Semaphore.h @@ -15,37 +15,29 @@ namespace tt { /** * Wrapper for xSemaphoreCreateBinary (max count == 1) and xSemaphoreCreateCounting (max count > 1) - * Can be used in IRQ mode (within ISR context) + * Can be used from IRQ/ISR mode, but cannot be created/destroyed from such a context. */ class Semaphore { private: SemaphoreHandle_t handle; public: /** + * Cannot be called from IRQ/ISR mode. * @param[in] maxCount The maximum count * @param[in] initialCount The initial count */ Semaphore(uint32_t maxCount, uint32_t initialCount); - /** - * @param instance The pointer to Semaphore instance - */ + /** Cannot be called from IRQ/ISR mode. */ ~Semaphore(); - /** Acquire semaphore - * @param[in] timeout The timeout - * @return the status - */ - TtStatus acquire(uint32_t timeout) const; + /** Acquire semaphore */ + bool acquire(uint32_t timeoutTicks) const; - /** Release semaphore - * @return the status - */ - TtStatus release() const; + /** Release semaphore */ + bool release() const; - /** Get semaphore count - * @return semaphore count - */ + /** @return semaphore count */ uint32_t getCount() const; }; diff --git a/TactilityCore/Source/StreamBuffer.cpp b/TactilityCore/Source/StreamBuffer.cpp index 7108b338..47acde64 100644 --- a/TactilityCore/Source/StreamBuffer.cpp +++ b/TactilityCore/Source/StreamBuffer.cpp @@ -66,12 +66,8 @@ bool StreamBuffer::isEmpty() const { return xStreamBufferIsEmpty(handle) == pdTRUE; }; -TtStatus StreamBuffer::reset() const { - if (xStreamBufferReset(handle) == pdPASS) { - return TtStatusOk; - } else { - return TtStatusError; - } +bool StreamBuffer::reset() const { + return xStreamBufferReset(handle) == pdPASS; } } // namespace diff --git a/TactilityCore/Source/StreamBuffer.h b/TactilityCore/Source/StreamBuffer.h index 187cda9a..94caf2eb 100644 --- a/TactilityCore/Source/StreamBuffer.h +++ b/TactilityCore/Source/StreamBuffer.h @@ -141,7 +141,7 @@ public: * @return TtStatusError if there was a task blocked waiting to send to or read * from the stream buffer then the stream buffer is not reset. */ - TtStatus reset() const; + bool reset() const; }; diff --git a/TactilityCore/Source/StringUtils.cpp b/TactilityCore/Source/StringUtils.cpp index 2316f18f..c8168271 100644 --- a/TactilityCore/Source/StringUtils.cpp +++ b/TactilityCore/Source/StringUtils.cpp @@ -13,17 +13,15 @@ int findLastIndex(const char* text, size_t from_index, char find) { return -1; } -bool getPathParent(const char* path, char* output) { - int index = findLastIndex(path, strlen(path) - 1, '/'); +bool getPathParent(const std::string& path, std::string& output) { + int index = findLastIndex(path.c_str(), path.length() - 1, '/'); if (index == -1) { return false; } else if (index == 0) { - output[0] = '/'; - output[1] = 0x00; + output = "/"; return true; } else { - memcpy(output, path, index); - output[index] = 0x00; + output = path.substr(0, index); return true; } } diff --git a/TactilityCore/Source/StringUtils.h b/TactilityCore/Source/StringUtils.h index 0d2f9555..694ff1fb 100644 --- a/TactilityCore/Source/StringUtils.h +++ b/TactilityCore/Source/StringUtils.h @@ -21,7 +21,7 @@ int findLastIndex(const char* text, size_t from_index, char find); * @param[out] output an output buffer that is allocated to at least the size of "current" * @return true when successful */ -bool getPathParent(const char* path, char* output); +bool getPathParent(const std::string& path, std::string& output); /** * Given a filesystem path as input, get the last segment of a path diff --git a/TactilityCore/Source/Timer.cpp b/TactilityCore/Source/Timer.cpp index 59a7eff8..4146ba91 100644 --- a/TactilityCore/Source/Timer.cpp +++ b/TactilityCore/Source/Timer.cpp @@ -37,33 +37,22 @@ Timer::~Timer() { tt_check(xTimerDelete(timerHandle, portMAX_DELAY) == pdPASS); } -TtStatus Timer::start(uint32_t ticks) { +bool Timer::start(uint32_t intervalTicks) { tt_assert(!kernel::isIrq()); - tt_assert(ticks < portMAX_DELAY); - - if (xTimerChangePeriod(timerHandle, ticks, portMAX_DELAY) == pdPASS) { - return TtStatusOk; - } else { - return TtStatusErrorResource; - } + tt_assert(intervalTicks < portMAX_DELAY); + return xTimerChangePeriod(timerHandle, intervalTicks, portMAX_DELAY) == pdPASS; } -TtStatus Timer::restart(uint32_t ticks) { +bool Timer::restart(uint32_t intervalTicks) { tt_assert(!kernel::isIrq()); - tt_assert(ticks < portMAX_DELAY); - - if (xTimerChangePeriod(timerHandle, ticks, portMAX_DELAY) == pdPASS && - xTimerReset(timerHandle, portMAX_DELAY) == pdPASS) { - return TtStatusOk; - } else { - return TtStatusErrorResource; - } + tt_assert(intervalTicks < portMAX_DELAY); + return xTimerChangePeriod(timerHandle, intervalTicks, portMAX_DELAY) == pdPASS && + xTimerReset(timerHandle, portMAX_DELAY) == pdPASS; } -TtStatus Timer::stop() { +bool Timer::stop() { tt_assert(!kernel::isIrq()); - tt_check(xTimerStop(timerHandle, portMAX_DELAY) == pdPASS); - return TtStatusOk; + return xTimerStop(timerHandle, portMAX_DELAY) == pdPASS; } bool Timer::isRunning() { @@ -76,17 +65,15 @@ uint32_t Timer::getExpireTime() { return (uint32_t)xTimerGetExpiryTime(timerHandle); } -void Timer::pendingCallback(PendingCallback callback, void* callbackContext, uint32_t arg) { - BaseType_t ret = pdFAIL; +bool Timer::setPendingCallback(PendingCallback callback, void* callbackContext, uint32_t arg) { if (kernel::isIrq()) { - ret = xTimerPendFunctionCallFromISR(callback, callbackContext, arg, nullptr); + return xTimerPendFunctionCallFromISR(callback, callbackContext, arg, nullptr) == pdPASS; } else { - ret = xTimerPendFunctionCall(callback, callbackContext, arg, TtWaitForever); + return xTimerPendFunctionCall(callback, callbackContext, arg, TtWaitForever) == pdPASS; } - tt_assert(ret == pdPASS); } -void Timer::setThreadPriority(TimerThreadPriority priority) { +void Timer::setThreadPriority(ThreadPriority priority) { tt_assert(!kernel::isIrq()); TaskHandle_t task_handle = xTimerGetTimerDaemonTaskHandle(); diff --git a/TactilityCore/Source/Timer.h b/TactilityCore/Source/Timer.h index 1a6d92a0..54fe6f4a 100644 --- a/TactilityCore/Source/Timer.h +++ b/TactilityCore/Source/Timer.h @@ -15,7 +15,6 @@ public: typedef void (*Callback)(std::shared_ptr context); typedef void (*PendingCallback)(void* context, uint32_t arg); - Callback callback; std::shared_ptr callbackContext; @@ -39,9 +38,9 @@ public: * timer service process this request. * * @param[in] ticks The interval in ticks - * @return The status. + * @return success result */ - TtStatus start(uint32_t ticks); + bool start(uint32_t intervalTicks); /** Restart timer with previous timeout value * @@ -50,9 +49,9 @@ public: * * @param[in] ticks The interval in ticks * - * @return The status. + * @return success result */ - TtStatus restart(uint32_t ticks); + bool restart(uint32_t intervalTicks); /** Stop timer @@ -60,9 +59,9 @@ public: * @warning This is asynchronous call, real operation will happen as soon as * timer service process this request. * - * @return The status. + * @return success result */ - TtStatus stop(); + bool stop(); /** Is timer running * @@ -82,18 +81,18 @@ public: */ uint32_t getExpireTime(); - void pendingCallback(PendingCallback callback, void* callbackContext, uint32_t arg); + bool setPendingCallback(PendingCallback callback, void* callbackContext, uint32_t arg); typedef enum { TimerThreadPriorityNormal, /**< Lower then other threads */ TimerThreadPriorityElevated, /**< Same as other threads */ - } TimerThreadPriority; + } ThreadPriority; /** Set Timer thread priority * * @param[in] priority The priority */ - void setThreadPriority(TimerThreadPriority priority); + void setThreadPriority(ThreadPriority priority); }; } // namespace diff --git a/TactilityCore/Source/file/File.cpp b/TactilityCore/Source/file/File.cpp index 4f843fed..2c1bf40c 100644 --- a/TactilityCore/Source/file/File.cpp +++ b/TactilityCore/Source/file/File.cpp @@ -26,17 +26,17 @@ long getSize(FILE* file) { return file_size; } -static std::unique_ptr readBinaryInternal(const char* filepath, size_t& outSize, size_t sizePadding = 0) { - FILE* file = fopen(filepath, "rb"); +static std::unique_ptr readBinaryInternal(const std::string& filepath, size_t& outSize, size_t sizePadding = 0) { + FILE* file = fopen(filepath.c_str(), "rb"); if (file == nullptr) { - TT_LOG_E(TAG, "Failed to open %s", filepath); + TT_LOG_E(TAG, "Failed to open %s", filepath.c_str()); return nullptr; } long content_length = getSize(file); if (content_length == -1) { - TT_LOG_E(TAG, "Failed to determine content length for %s", filepath); + TT_LOG_E(TAG, "Failed to determine content length for %s", filepath.c_str()); return nullptr; } @@ -64,11 +64,11 @@ static std::unique_ptr readBinaryInternal(const char* filepath, size_ return data; } -std::unique_ptr readBinary(const char* filepath, size_t& outSize) { +std::unique_ptr readBinary(const std::string& filepath, size_t& outSize) { return readBinaryInternal(filepath, outSize); } -std::unique_ptr readString(const char* filepath) { +std::unique_ptr readString(const std::string& filepath) { size_t size = 0; auto data = readBinaryInternal(filepath, size, 1); if (size > 0) { diff --git a/TactilityCore/Source/file/File.h b/TactilityCore/Source/file/File.h index be9f760f..ee835794 100644 --- a/TactilityCore/Source/file/File.h +++ b/TactilityCore/Source/file/File.h @@ -7,7 +7,7 @@ namespace tt::file { long getSize(FILE* file); -std::unique_ptr readBinary(const char* filepath, size_t& outSize); -std::unique_ptr readString(const char* filepath); +std::unique_ptr readBinary(const std::string& filepath, size_t& outSize); +std::unique_ptr readString(const std::string& filepath); } diff --git a/TactilityHeadless/CMakeLists.txt b/TactilityHeadless/CMakeLists.txt index 7a2be4eb..0a092d7d 100644 --- a/TactilityHeadless/CMakeLists.txt +++ b/TactilityHeadless/CMakeLists.txt @@ -10,7 +10,7 @@ if (DEFINED ENV{ESP_IDF_VERSION}) SRCS ${SOURCE_FILES} INCLUDE_DIRS "Source/" PRIV_INCLUDE_DIRS "Private/" - REQUIRES TactilityCore esp_wifi nvs_flash spiffs driver + REQUIRES TactilityCore esp_wifi nvs_flash driver spiffs vfs fatfs ) if (NOT DEFINED TACTILITY_SKIP_SPIFFS) diff --git a/TactilityHeadless/Source/hal/Configuration.h b/TactilityHeadless/Source/hal/Configuration.h index 240ea1db..7ce0df3d 100644 --- a/TactilityHeadless/Source/hal/Configuration.h +++ b/TactilityHeadless/Source/hal/Configuration.h @@ -1,8 +1,8 @@ #pragma once #include "Power.h" -#include "hal/sdcard/Sdcard.h" #include "hal/i2c/I2c.h" +#include "SdCard.h" namespace tt::hal { @@ -49,7 +49,7 @@ struct Configuration { /** * An optional SD card interface. */ - const sdcard::SdCard* _Nullable sdcard = nullptr; + const std::shared_ptr _Nullable sdcard = nullptr; /** * An optional power interface for battery or other power delivery. diff --git a/TactilityHeadless/Source/hal/Hal.cpp b/TactilityHeadless/Source/hal/Hal.cpp index 93a59fc6..14f42185 100644 --- a/TactilityHeadless/Source/hal/Hal.cpp +++ b/TactilityHeadless/Source/hal/Hal.cpp @@ -19,7 +19,7 @@ void init(const Configuration& configuration) { if (configuration.sdcard != nullptr) { TT_LOG_I(TAG, "Mounting sdcard"); - if (!sdcard::mount(configuration.sdcard)) { + if (!configuration.sdcard->mount(TT_SDCARD_MOUNT_POINT )) { TT_LOG_W(TAG, "SD card mount failed (init can continue)"); } } diff --git a/TactilityHeadless/Source/hal/SdCard.h b/TactilityHeadless/Source/hal/SdCard.h new file mode 100644 index 00000000..48736396 --- /dev/null +++ b/TactilityHeadless/Source/hal/SdCard.h @@ -0,0 +1,38 @@ +#pragma once + +#include "TactilityCore.h" + +namespace tt::hal { + +#define TT_SDCARD_MOUNT_POINT "/sdcard" + +class SdCard { +public: + enum State { + StateMounted, + StateUnmounted, + StateError, + StateUnknown + }; + + enum MountBehaviour { + MountBehaviourAtBoot, /** Only mount at boot */ + MountBehaviourAnytime /** Mount/dismount any time */ + }; + +private: + MountBehaviour mountBehaviour; + +public: + explicit SdCard(MountBehaviour mountBehaviour) : mountBehaviour(mountBehaviour) {} + virtual ~SdCard() = default; + + virtual bool mount(const char* mountPath) = 0; + virtual bool unmount() = 0; + virtual State getState() const = 0; + + virtual MountBehaviour getMountBehaviour() const { return mountBehaviour; } + bool isMounted() const { return getState() == StateMounted; } +}; + +} // namespace diff --git a/TactilityHeadless/Source/hal/SpiSdCard.cpp b/TactilityHeadless/Source/hal/SpiSdCard.cpp new file mode 100644 index 00000000..f7218a66 --- /dev/null +++ b/TactilityHeadless/Source/hal/SpiSdCard.cpp @@ -0,0 +1,159 @@ +#ifdef ESP_PLATFORM + +#include "SpiSdCard.h" + +#include "Check.h" +#include "Log.h" + +#include +#include +#include + +#define TAG "spi_sdcard" + +namespace tt::hal { + +/** + * Before we can initialize the sdcard's SPI communications, we have to set all + * other SPI pins on the board high. + * See https://github.com/espressif/esp-idf/issues/1597 + * See https://github.com/Xinyuan-LilyGO/T-Deck/blob/master/examples/UnitTest/UnitTest.ino + * @return success result + */ +bool SpiSdCard::applyGpioWorkAround() { + TT_LOG_D(TAG, "init"); + + uint64_t pin_bit_mask = BIT64(config->spiPinCs); + for (auto const& pin: config->csPinWorkAround) { + pin_bit_mask |= BIT64(pin); + } + + gpio_config_t sd_gpio_config = { + .pin_bit_mask = pin_bit_mask, + .mode = GPIO_MODE_OUTPUT, + .pull_up_en = GPIO_PULLUP_DISABLE, + .pull_down_en = GPIO_PULLDOWN_DISABLE, + .intr_type = GPIO_INTR_DISABLE, + }; + + if (gpio_config(&sd_gpio_config) != ESP_OK) { + TT_LOG_E(TAG, "GPIO init failed"); + return false; + } + + for (auto const& pin: config->csPinWorkAround) { + if (gpio_set_level(pin, 1) != ESP_OK) { + TT_LOG_E(TAG, "Failed to set board CS pin high"); + return false; + } + } + + return true; +} + +bool SpiSdCard::mountInternal(const char* mountPoint) { + TT_LOG_I(TAG, "Mounting %s", mountPoint); + + esp_vfs_fat_sdmmc_mount_config_t mount_config = { + .format_if_mount_failed = config->formatOnMountFailed, + .max_files = config->maxOpenFiles, + .allocation_unit_size = config->allocUnitSize, + .disk_status_check_enable = config->statusCheckEnabled, + .use_one_fat = false + }; + + // Init without card detect (CD) and write protect (WD) + sdspi_device_config_t slot_config = SDSPI_DEVICE_CONFIG_DEFAULT(); + slot_config.gpio_cs = config->spiPinCs; + slot_config.gpio_cd = config->spiPinCd; + slot_config.gpio_wp = config->spiPinWp; + slot_config.gpio_int = config->spiPinInt; + + sdmmc_host_t host = SDSPI_HOST_DEFAULT(); + // The following value is from T-Deck repo's UnitTest.ino project: + // 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 = config->spiFrequency; + + esp_err_t result = esp_vfs_fat_sdspi_mount(mountPoint, &host, &slot_config, &mount_config, &card); + + if (result != ESP_OK) { + if (result == ESP_FAIL) { + TT_LOG_E(TAG, "Mounting failed. Ensure the card is formatted with FAT."); + } else { + TT_LOG_E(TAG, "Mounting failed (%s)", esp_err_to_name(result)); + } + return false; + } + + this->mountPoint = mountPoint; + + return true; +} + +bool SpiSdCard::mount(const char* mount_point) { + if (!applyGpioWorkAround()) { + TT_LOG_E(TAG, "Failed to set SPI CS pins high. This is a pre-requisite for mounting."); + return false; + } + + if (mountInternal(mount_point)) { + sdmmc_card_print_info(stdout, card); + return true; + } else { + TT_LOG_E(TAG, "Mount failed for %s", mount_point); + return false; + } +} + +bool SpiSdCard::unmount() { + if (card == nullptr) { + TT_LOG_E(TAG, "Can't unmount: not mounted"); + return false; + } + + if (esp_vfs_fat_sdcard_unmount(mountPoint.c_str(), card) == ESP_OK) { + mountPoint = ""; + card = nullptr; + return true; + } else { + TT_LOG_E(TAG, "Unmount failed for %s", mountPoint.c_str()); + return false; + } +} + +// TODO: Refactor to "bool getStatus(Status* status)" method so that it can fail when the lvgl lock fails +tt::hal::SdCard::State SpiSdCard::getState() const { + if (card == nullptr) { + return StateUnmounted; + } + + /** + * The SD card and the screen are on the same SPI bus. + * Writing and reading to the bus from 2 devices at the same time causes crashes. + * This work-around ensures that this check is only happening when LVGL isn't rendering. + */ + if (config->lockable) { + bool locked = config->lockable->lock(50); // TODO: Refactor to a more reliable locking mechanism + if (!locked) { + TT_LOG_E(TAG, LOG_MESSAGE_MUTEX_LOCK_FAILED_FMT, "LVGL"); + return StateUnknown; + } + } + + bool result = sdmmc_get_status(card) == ESP_OK; + + if (config->lockable) { + config->lockable->unlock(); + } + + if (result) { + return StateMounted; + } else { + return StateError; + } +} + +} + +#endif \ No newline at end of file diff --git a/TactilityHeadless/Source/hal/SpiSdCard.h b/TactilityHeadless/Source/hal/SpiSdCard.h new file mode 100644 index 00000000..eae1aaaa --- /dev/null +++ b/TactilityHeadless/Source/hal/SpiSdCard.h @@ -0,0 +1,80 @@ +#ifdef ESP_PLATFORM + +#pragma once + +#include "hal/SdCard.h" + +#include +#include +#include +#include +#include + +namespace tt::hal { + +/** + * SD card interface at the default SPI interface + */ +class SpiSdCard : public tt::hal::SdCard { +public: + struct Config { + Config( + int spiFrequency, + gpio_num_t spiPinCs, + gpio_num_t spiPinCd, + gpio_num_t spiPinWp, + gpio_num_t spiPinInt, + MountBehaviour mountBehaviourAtBoot, + std::shared_ptr lockable = nullptr, + std::vector csPinWorkAround = std::vector(), + spi_host_device_t spiHost = SPI2_HOST + ) : spiFrequency(spiFrequency), + spiPinCs(spiPinCs), + spiPinCd(spiPinCd), + spiPinWp(spiPinWp), + spiPinInt(spiPinInt), + mountBehaviourAtBoot(mountBehaviourAtBoot), + lockable(std::move(lockable)), + csPinWorkAround(std::move(csPinWorkAround)), + spiHost(spiHost) + {} + + int spiFrequency; + gpio_num_t spiPinCs; // Clock + gpio_num_t spiPinCd; // Card detect + gpio_num_t spiPinWp; // Write-protect + gpio_num_t spiPinInt; // Interrupt + SdCard::MountBehaviour mountBehaviourAtBoot; + std::shared_ptr _Nullable lockable; + std::vector csPinWorkAround; + spi_host_device_t spiHost; + bool formatOnMountFailed = false; + uint16_t maxOpenFiles = 4; + uint16_t allocUnitSize = 16 * 1024; + bool statusCheckEnabled = false; + }; + +private: + + std::string mountPoint; + sdmmc_card_t* card = nullptr; + std::shared_ptr config; + + bool applyGpioWorkAround(); + bool mountInternal(const char* mount_point); + +public: + + explicit SpiSdCard(std::unique_ptr config) : + SdCard(config->mountBehaviourAtBoot), + config(std::move(config)) + {} + + bool mount(const char* mountPath) override; + bool unmount() override; + State getState() const override; +}; + +} + +#endif diff --git a/TactilityHeadless/Source/hal/sdcard/Sdcard.cpp b/TactilityHeadless/Source/hal/sdcard/Sdcard.cpp deleted file mode 100644 index 20622ed9..00000000 --- a/TactilityHeadless/Source/hal/sdcard/Sdcard.cpp +++ /dev/null @@ -1,85 +0,0 @@ -#include "Sdcard.h" - -#include "Mutex.h" -#include "TactilityCore.h" - -namespace tt::hal::sdcard { - -#define TAG "sdcard" - -static Mutex mutex(Mutex::TypeRecursive); - -typedef struct { - const SdCard* sdcard; - void* context; -} MountData; - -static MountData data = { - .sdcard = nullptr, - .context = nullptr -}; - -static bool lock(uint32_t timeout_ticks) { - return mutex.acquire(timeout_ticks) == TtStatusOk; -} - -static void unlock() { - mutex.release(); -} - -bool mount(const SdCard* sdcard) { - TT_LOG_I(TAG, "Mounting"); - - if (data.sdcard != nullptr) { - TT_LOG_E(TAG, "Failed to mount: already mounted"); - return false; - } - - if (lock(100)) { - void* context = sdcard->mount(TT_SDCARD_MOUNT_POINT); - data = (MountData) { - .sdcard = sdcard, - .context = context - }; - unlock(); - return (data.context != nullptr); - } else { - TT_LOG_E(TAG, "Failed to lock"); - return false; - } -} - -State getState() { - if (data.context == nullptr) { - return StateUnmounted; - } else if (data.sdcard->is_mounted(data.context)) { - return StateMounted; - } else { - return StateError; - } -} - -bool unmount(uint32_t timeout_ticks) { - TT_LOG_I(TAG, "Unmounting"); - bool result = false; - - if (lock(timeout_ticks)) { - if (data.sdcard != nullptr) { - data.sdcard->unmount(data.context); - data = (MountData) { - .sdcard = nullptr, - .context = nullptr - }; - result = true; - } else { - TT_LOG_E(TAG, "Can't unmount: nothing mounted"); - } - unlock(); - } else { - TT_LOG_E(TAG, "Failed to lock in %lu ticks", timeout_ticks); - } - - return result; -} - -} // namespace diff --git a/TactilityHeadless/Source/hal/sdcard/Sdcard.h b/TactilityHeadless/Source/hal/sdcard/Sdcard.h deleted file mode 100644 index 33ad1ff8..00000000 --- a/TactilityHeadless/Source/hal/sdcard/Sdcard.h +++ /dev/null @@ -1,35 +0,0 @@ -#pragma once - -#include "TactilityCore.h" - -namespace tt::hal::sdcard { - -#define TT_SDCARD_MOUNT_POINT "/sdcard" - -typedef void* (*Mount)(const char* mount_path); -typedef void (*Unmount)(void* context); -typedef bool (*IsMounted)(void* context); - -typedef enum { - StateMounted, - StateUnmounted, - StateError, -} State; - -typedef enum { - MountBehaviourAtBoot, /** Only mount at boot */ - MountBehaviourAnytime /** Mount/dismount any time */ -} MountBehaviour; - -typedef struct { - Mount mount; - Unmount unmount; - IsMounted is_mounted; - MountBehaviour mount_behaviour; -} SdCard; - -bool mount(const SdCard* sdcard); -State getState(); -bool unmount(uint32_t timeout_ticks); - -} // namespace diff --git a/TactilityHeadless/Source/service/sdcard/Sdcard.cpp b/TactilityHeadless/Source/service/sdcard/Sdcard.cpp index 7f00bf32..be1b7c79 100644 --- a/TactilityHeadless/Source/service/sdcard/Sdcard.cpp +++ b/TactilityHeadless/Source/service/sdcard/Sdcard.cpp @@ -17,7 +17,7 @@ extern const ServiceManifest manifest; struct ServiceData { Mutex mutex; std::unique_ptr updateTimer; - hal::sdcard::State lastState = hal::sdcard::StateUnmounted; + hal::SdCard::State lastState = hal::SdCard::StateUnmounted; bool lock(TickType_t timeout) const { return mutex.acquire(timeout) == TtStatusOk; @@ -30,18 +30,23 @@ struct ServiceData { static void onUpdate(std::shared_ptr context) { - auto data = std::static_pointer_cast(context); - - if (!data->lock(50)) { - TT_LOG_W(TAG, "Failed to acquire lock"); + auto sdcard = tt::hal::getConfiguration().sdcard; + if (sdcard == nullptr) { return; } - hal::sdcard::State new_state = hal::sdcard::getState(); + auto data = std::static_pointer_cast(context); - if (new_state == hal::sdcard::StateError) { + if (!data->lock(50)) { + TT_LOG_W(TAG, LOG_MESSAGE_MUTEX_LOCK_FAILED); + return; + } + + auto new_state = sdcard->getState(); + + if (new_state == hal::SdCard::StateError) { TT_LOG_W(TAG, "Sdcard error - unmounting. Did you eject the card in an unsafe manner?"); - hal::sdcard::unmount(kernel::millisToTicks(1000)); + sdcard->unmount(); } if (new_state != data->lastState) { diff --git a/TactilityHeadless/Source/service/wifi/WifiEsp.cpp b/TactilityHeadless/Source/service/wifi/WifiEsp.cpp index 328caf7a..622d2dcc 100644 --- a/TactilityHeadless/Source/service/wifi/WifiEsp.cpp +++ b/TactilityHeadless/Source/service/wifi/WifiEsp.cpp @@ -72,50 +72,54 @@ public: bool connection_target_remember = false; // Whether to store the connection_target on successful connection or not WifiRadioState getRadioState() const { - auto lock = dataMutex.scoped(); - lock->acquire(TtWaitForever); + auto lockable = dataMutex.scoped(); + lockable->lock(TtWaitForever); + // TODO: Handle lock failure return radio_state; } void setRadioState(WifiRadioState newState) { - auto lock = dataMutex.scoped(); - lock->acquire(TtWaitForever); + auto lockable = dataMutex.scoped(); + lockable->lock(TtWaitForever); + // TODO: Handle lock failure radio_state = newState; } bool isScanning() const { - auto lock = dataMutex.scoped(); - lock->acquire(TtWaitForever); + auto lockable = dataMutex.scoped(); + lockable->lock(TtWaitForever); + // TODO: Handle lock failure return radio_state; } void setScanning(bool newState) { - auto lock = dataMutex.scoped(); - lock->acquire(TtWaitForever); + auto lockable = dataMutex.scoped(); + lockable->lock(TtWaitForever); + // TODO: Handle lock failure scan_active = newState; } bool isScanActive() const { - auto lock = dataMutex.scoped(); - lock->acquire(TtWaitForever); + auto lcokable = dataMutex.scoped(); + lcokable->lock(TtWaitForever); return scan_active; } void setScanActive(bool newState) { - auto lock = dataMutex.scoped(); - lock->acquire(TtWaitForever); + auto lockable = dataMutex.scoped(); + lockable->lock(TtWaitForever); scan_active = newState; } bool isSecureConnection() const { - auto lock = dataMutex.scoped(); - lock->acquire(TtWaitForever); + auto lockable = dataMutex.scoped(); + lockable->lock(TtWaitForever); return secure_connection; } void setSecureConnection(bool newState) { - auto lock = dataMutex.scoped(); - lock->acquire(TtWaitForever); + auto lockable = dataMutex.scoped(); + lockable->lock(TtWaitForever); secure_connection = newState; } }; @@ -186,8 +190,8 @@ void connect(const settings::WifiApSettings* ap, bool remember) { return; } - auto lock = wifi->dataMutex.scoped(); - if (!lock->acquire(10 / portTICK_PERIOD_MS)) { + auto lockable = wifi->dataMutex.scoped(); + if (!lockable->lock(10 / portTICK_PERIOD_MS)) { return; } @@ -205,8 +209,8 @@ void disconnect() { return; } - auto lock = wifi->dataMutex.scoped(); - if (!lock->acquire(10 / portTICK_PERIOD_MS)) { + auto lockable = wifi->dataMutex.scoped(); + if (!lockable->lock(10 / portTICK_PERIOD_MS)) { return; } @@ -227,8 +231,8 @@ void setScanRecords(uint16_t records) { return; } - auto lock = wifi->dataMutex.scoped(); - if (!lock->acquire(10 / portTICK_PERIOD_MS)) { + auto lockable = wifi->dataMutex.scoped(); + if (!lockable->lock(10 / portTICK_PERIOD_MS)) { return; } @@ -248,8 +252,8 @@ std::vector getScanResults() { return records; } - auto lock = wifi->dataMutex.scoped(); - if (!lock->acquire(10 / portTICK_PERIOD_MS)) { + auto lockable = wifi->dataMutex.scoped(); + if (!lockable->lock(10 / portTICK_PERIOD_MS)) { return records; } @@ -274,8 +278,8 @@ void setEnabled(bool enabled) { return; } - auto lock = wifi->dataMutex.scoped(); - if (!lock->acquire(10 / portTICK_PERIOD_MS)) { + auto lockable = wifi->dataMutex.scoped(); + if (!lockable->lock(10 / portTICK_PERIOD_MS)) { return; } @@ -294,8 +298,8 @@ bool isConnectionSecure() { return false; } - auto lock = wifi->dataMutex.scoped(); - if (!lock->acquire(10 / portTICK_PERIOD_MS)) { + auto lockable = wifi->dataMutex.scoped(); + if (!lockable->lock(10 / portTICK_PERIOD_MS)) { return false; } @@ -315,8 +319,8 @@ int getRssi() { // endregion Public functions static void scan_list_alloc(std::shared_ptr wifi) { - auto lock = wifi->dataMutex.scoped(); - if (lock->acquire(TtWaitForever)) { + auto lockable = wifi->dataMutex.scoped(); + if (lockable->lock(TtWaitForever)) { tt_assert(wifi->scan_list == nullptr); wifi->scan_list = static_cast(malloc(sizeof(wifi_ap_record_t) * wifi->scan_list_limit)); wifi->scan_list_count = 0; @@ -324,8 +328,8 @@ static void scan_list_alloc(std::shared_ptr wifi) { } static void scan_list_alloc_safely(std::shared_ptr wifi) { - auto lock = wifi->dataMutex.scoped(); - if (lock->acquire(TtWaitForever)) { + auto lockable = wifi->dataMutex.scoped(); + if (lockable->lock(TtWaitForever)) { if (wifi->scan_list == nullptr) { scan_list_alloc(wifi); } @@ -333,8 +337,8 @@ static void scan_list_alloc_safely(std::shared_ptr wifi) { } static void scan_list_free(std::shared_ptr wifi) { - auto lock = wifi->dataMutex.scoped(); - if (lock->acquire(TtWaitForever)) { + auto lockable = wifi->dataMutex.scoped(); + if (lockable->lock(TtWaitForever)) { tt_assert(wifi->scan_list != nullptr); free(wifi->scan_list); wifi->scan_list = nullptr; @@ -343,8 +347,8 @@ static void scan_list_free(std::shared_ptr wifi) { } static void scan_list_free_safely(std::shared_ptr wifi) { - auto lock = wifi->dataMutex.scoped(); - if (lock->acquire(TtWaitForever)) { + auto lockable = wifi->dataMutex.scoped(); + if (lockable->lock(TtWaitForever)) { if (wifi->scan_list != nullptr) { scan_list_free(wifi); } @@ -352,8 +356,8 @@ static void scan_list_free_safely(std::shared_ptr wifi) { } static void publish_event_simple(std::shared_ptr wifi, WifiEventType type) { - auto lock = wifi->dataMutex.scoped(); - if (lock->acquire(TtWaitForever)) { + auto lockable = wifi->dataMutex.scoped(); + if (lockable->lock(TtWaitForever)) { WifiEvent turning_on_event = {.type = type}; tt_pubsub_publish(wifi->pubsub, &turning_on_event); } @@ -369,8 +373,8 @@ static bool copy_scan_list(std::shared_ptr wifi) { return false; } - auto lock = wifi->dataMutex.scoped(); - if (!lock->acquire(TtWaitForever)) { + auto lockable = wifi->dataMutex.scoped(); + if (!lockable->lock(TtWaitForever)) { return false; } @@ -396,9 +400,9 @@ static bool copy_scan_list(std::shared_ptr wifi) { static bool find_auto_connect_ap(std::shared_ptr context, settings::WifiApSettings& settings) { auto wifi = std::static_pointer_cast(context); - auto lock = wifi->dataMutex.scoped(); + auto lockable = wifi->dataMutex.scoped(); - if (lock->acquire(10 / portTICK_PERIOD_MS)) { + if (lockable->lock(10 / portTICK_PERIOD_MS)) { TT_LOG_I(TAG, "auto_connect()"); for (int i = 0; i < wifi->scan_list_count; ++i) { auto ssid = reinterpret_cast(wifi->scan_list[i].ssid); @@ -495,9 +499,9 @@ static void dispatchEnable(std::shared_ptr context) { return; } - auto lock = std::make_unique(wifi->radioMutex); + auto lockable = wifi->radioMutex.scoped(); - if (lock->acquire(50 / portTICK_PERIOD_MS)) { + if (lockable->lock(50 / portTICK_PERIOD_MS)) { TT_LOG_I(TAG, "Enabling"); wifi->setRadioState(WIFI_RADIO_ON_PENDING); publish_event_simple(wifi, WifiEventTypeRadioStateOnPending); @@ -566,17 +570,17 @@ static void dispatchEnable(std::shared_ptr context) { publish_event_simple(wifi, WifiEventTypeRadioStateOn); TT_LOG_I(TAG, "Enabled"); } else { - TT_LOG_E(TAG, "enable() mutex timeout"); + TT_LOG_E(TAG, LOG_MESSAGE_MUTEX_LOCK_FAILED); } } static void dispatchDisable(std::shared_ptr context) { TT_LOG_I(TAG, "dispatchDisable()"); auto wifi = std::static_pointer_cast(context); - auto lock = wifi->radioMutex.scoped(); + auto lockable = wifi->radioMutex.scoped(); - if (!lock->acquire(50 / portTICK_PERIOD_MS)) { - TT_LOG_E(TAG, "disable() mutex timeout"); + if (!lockable->lock(50 / portTICK_PERIOD_MS)) { + TT_LOG_E(TAG, LOG_MESSAGE_MUTEX_LOCK_FAILED_FMT, "disable()"); return; } @@ -640,10 +644,10 @@ static void dispatchDisable(std::shared_ptr context) { static void dispatchScan(std::shared_ptr context) { TT_LOG_I(TAG, "dispatchScan()"); auto wifi = std::static_pointer_cast(context); - auto lock = wifi->radioMutex.scoped(); + auto lockable = wifi->radioMutex.scoped(); - if (!lock->acquire(10 / portTICK_PERIOD_MS)) { - TT_LOG_E(TAG, "dispatchScan() mutex timeout"); + if (!lockable->lock(10 / portTICK_PERIOD_MS)) { + TT_LOG_E(TAG, LOG_MESSAGE_MUTEX_LOCK_FAILED); return; } @@ -674,10 +678,10 @@ static void dispatchScan(std::shared_ptr context) { static void dispatchConnect(std::shared_ptr context) { TT_LOG_I(TAG, "dispatchConnect()"); auto wifi = std::static_pointer_cast(context); - auto lock = wifi->radioMutex.scoped(); + auto lockable = wifi->radioMutex.scoped(); - if (!lock->acquire(50 / portTICK_PERIOD_MS)) { - TT_LOG_E(TAG, "dispatchConnect() mutex timeout"); + if (!lockable->lock(50 / portTICK_PERIOD_MS)) { + TT_LOG_E(TAG, LOG_MESSAGE_MUTEX_LOCK_FAILED_FMT, "dispatchConnect()"); return; } @@ -803,10 +807,10 @@ static void dispatchConnect(std::shared_ptr context) { static void dispatchDisconnectButKeepActive(std::shared_ptr context) { TT_LOG_I(TAG, "dispatchDisconnectButKeepActive()"); auto wifi = std::static_pointer_cast(context); - auto lock = wifi->radioMutex.scoped(); + auto lockable = wifi->radioMutex.scoped(); - if (!lock->acquire(50 / portTICK_PERIOD_MS)) { - TT_LOG_E(TAG, "disconnect_internal_but_keep_active() mutex timeout"); + if (!lockable->lock(50 / portTICK_PERIOD_MS)) { + TT_LOG_E(TAG, LOG_MESSAGE_MUTEX_LOCK_FAILED); return; } @@ -881,9 +885,9 @@ static void dispatchDisconnectButKeepActive(std::shared_ptr context) { } static bool shouldScanForAutoConnect(std::shared_ptr wifi) { - auto lock = wifi->dataMutex.scoped(); + auto lockable = wifi->dataMutex.scoped(); - if (!lock->acquire(100)) { + if (!lockable->lock(100)) { return false; } diff --git a/Tests/TactilityCore/MessageQueueTest.cpp b/Tests/TactilityCore/MessageQueueTest.cpp index 5c3fc6fb..f1452166 100644 --- a/Tests/TactilityCore/MessageQueueTest.cpp +++ b/Tests/TactilityCore/MessageQueueTest.cpp @@ -80,7 +80,7 @@ TEST_CASE("message queue should make copy of data") { // We want to verify that the value was copied into the queue and retrieved properly int32_t queue_number = 0; - CHECK_EQ(queue.get(&queue_number, 100), TtStatusOk); + CHECK_EQ(queue.get(&queue_number, 100), true); CHECK_EQ(queue_number, test_value); } diff --git a/sdkconfig.board.lilygo-tdeck b/sdkconfig.board.lilygo-tdeck index ffc26040..41a00457 100644 --- a/sdkconfig.board.lilygo-tdeck +++ b/sdkconfig.board.lilygo-tdeck @@ -12,6 +12,7 @@ CONFIG_LV_USE_CLIB_MALLOC=y CONFIG_LV_USE_MSGBOX=n CONFIG_LV_USE_SPINNER=n CONFIG_LV_USE_WIN=n +CONFIG_LV_USE_SNAPSHOT=y CONFIG_FREERTOS_HZ=1000 CONFIG_FREERTOS_TASK_NOTIFICATION_ARRAY_ENTRIES=2 CONFIG_FREERTOS_SMP=n @@ -22,6 +23,7 @@ CONFIG_PARTITION_TABLE_CUSTOM=y CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions.csv" CONFIG_PARTITION_TABLE_FILENAME="partitions.csv" CONFIG_ELF_LOADER_CUSTOMER_SYMBOLS=y +CONFIG_FATFS_LFN_HEAP=y # Hardware: Main CONFIG_TT_BOARD_LILYGO_TDECK=y @@ -34,6 +36,7 @@ CONFIG_FLASHMODE_QIO=y CONFIG_ESP32S3_SPIRAM_SUPPORT=y CONFIG_SPIRAM_MODE_OCT=y CONFIG_SPIRAM_SPEED_80M=y +CONFIG_SPIRAM_USE_MALLOC=y CONFIG_SPIRAM_TRY_ALLOCATE_WIFI_LWIP=y # LVGL CONFIG_LV_DISP_DEF_REFR_PERIOD=17 diff --git a/sdkconfig.board.m5stack-core2 b/sdkconfig.board.m5stack-core2 index 421268bb..2e593b50 100644 --- a/sdkconfig.board.m5stack-core2 +++ b/sdkconfig.board.m5stack-core2 @@ -12,6 +12,7 @@ CONFIG_LV_USE_CLIB_MALLOC=y CONFIG_LV_USE_MSGBOX=n CONFIG_LV_USE_SPINNER=n CONFIG_LV_USE_WIN=n +CONFIG_LV_USE_SNAPSHOT=y CONFIG_FREERTOS_HZ=1000 CONFIG_FREERTOS_TASK_NOTIFICATION_ARRAY_ENTRIES=2 CONFIG_FREERTOS_SMP=n @@ -22,6 +23,7 @@ CONFIG_PARTITION_TABLE_CUSTOM=y CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions.csv" CONFIG_PARTITION_TABLE_FILENAME="partitions.csv" CONFIG_ELF_LOADER_CUSTOMER_SYMBOLS=y +CONFIG_FATFS_LFN_HEAP=y # Hardware: Main CONFIG_TT_BOARD_M5STACK_CORE2=y diff --git a/sdkconfig.board.m5stack-cores3 b/sdkconfig.board.m5stack-cores3 index ba2d7f1a..667b98e2 100644 --- a/sdkconfig.board.m5stack-cores3 +++ b/sdkconfig.board.m5stack-cores3 @@ -12,6 +12,7 @@ CONFIG_LV_USE_CLIB_MALLOC=y CONFIG_LV_USE_MSGBOX=n CONFIG_LV_USE_SPINNER=n CONFIG_LV_USE_WIN=n +CONFIG_LV_USE_SNAPSHOT=y CONFIG_FREERTOS_HZ=1000 CONFIG_FREERTOS_TASK_NOTIFICATION_ARRAY_ENTRIES=2 CONFIG_FREERTOS_SMP=n @@ -22,6 +23,7 @@ CONFIG_PARTITION_TABLE_CUSTOM=y CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions.csv" CONFIG_PARTITION_TABLE_FILENAME="partitions.csv" CONFIG_ELF_LOADER_CUSTOMER_SYMBOLS=y +CONFIG_FATFS_LFN_HEAP=y # Hardware: Main CONFIG_TT_BOARD_M5STACK_CORES3=y diff --git a/sdkconfig.board.yellow-board b/sdkconfig.board.yellow-board index 8e00e31f..a41eb57a 100644 --- a/sdkconfig.board.yellow-board +++ b/sdkconfig.board.yellow-board @@ -12,6 +12,7 @@ CONFIG_LV_USE_CLIB_MALLOC=y CONFIG_LV_USE_MSGBOX=n CONFIG_LV_USE_SPINNER=n CONFIG_LV_USE_WIN=n +CONFIG_LV_USE_SNAPSHOT=y CONFIG_FREERTOS_HZ=1000 CONFIG_FREERTOS_TASK_NOTIFICATION_ARRAY_ENTRIES=2 CONFIG_FREERTOS_SMP=n @@ -22,6 +23,7 @@ CONFIG_PARTITION_TABLE_CUSTOM=y CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions.csv" CONFIG_PARTITION_TABLE_FILENAME="partitions.csv" CONFIG_ELF_LOADER_CUSTOMER_SYMBOLS=y +CONFIG_FATFS_LFN_HEAP=y # Hardware: Main CONFIG_TT_BOARD_YELLOW_BOARD_24_CAP=y diff --git a/sdkconfig.defaults b/sdkconfig.defaults index 9722087f..94e17271 100644 --- a/sdkconfig.defaults +++ b/sdkconfig.defaults @@ -12,6 +12,7 @@ CONFIG_LV_USE_CLIB_MALLOC=y CONFIG_LV_USE_MSGBOX=n CONFIG_LV_USE_SPINNER=n CONFIG_LV_USE_WIN=n +CONFIG_LV_USE_SNAPSHOT=y CONFIG_FREERTOS_HZ=1000 CONFIG_FREERTOS_TASK_NOTIFICATION_ARRAY_ENTRIES=2 CONFIG_FREERTOS_SMP=n