diff --git a/Data/data/tactility/service/bluetooth/settings.properties b/Data/data/tactility/service/bluetooth/settings.properties deleted file mode 100644 index 583f5735..00000000 --- a/Data/data/tactility/service/bluetooth/settings.properties +++ /dev/null @@ -1 +0,0 @@ -enableOnBoot=false \ No newline at end of file diff --git a/Data/data/tactility/service/wifi/settings.properties b/Data/data/tactility/service/wifi/settings.properties deleted file mode 100644 index 583f5735..00000000 --- a/Data/data/tactility/service/wifi/settings.properties +++ /dev/null @@ -1 +0,0 @@ -enableOnBoot=false \ No newline at end of file diff --git a/Data/data/tactility/settings/development.properties b/Data/data/tactility/settings/development.properties deleted file mode 100644 index 583f5735..00000000 --- a/Data/data/tactility/settings/development.properties +++ /dev/null @@ -1 +0,0 @@ -enableOnBoot=false \ No newline at end of file diff --git a/Data/data/tactility/settings/system.properties b/Data/data/tactility/settings/system.properties deleted file mode 100644 index 8f251bec..00000000 --- a/Data/data/tactility/settings/system.properties +++ /dev/null @@ -1,5 +0,0 @@ -language=en-US -timeFormat24h=true -dateFormat=DD/MM/YYYY -region=EU -timezone=Europe/Amsterdam \ No newline at end of file diff --git a/Devices/cyd-4848s040c/Source/devices/St7701Display.cpp b/Devices/cyd-4848s040c/Source/devices/St7701Display.cpp index 154849a2..7a292c2a 100644 --- a/Devices/cyd-4848s040c/Source/devices/St7701Display.cpp +++ b/Devices/cyd-4848s040c/Source/devices/St7701Display.cpp @@ -12,9 +12,25 @@ #include #include #include +#include +#include +#include static const auto LOGGER = tt::Logger("St7701Display"); +// GPIO47/48 are physically shared between this bit-banged 3-wire command bus +// and the SD card's real SPI2 bus (no alternate pins exist on this PCB). +// esp_lcd_new_panel_io_3wire_spi() reconfigures them as plain GPIO via +// gpio_config(), severing their SPI2 matrix routing. Reconnect them here once +// the vendor init sequence is done, so SD card reads/writes keep working. +// Safe only because nothing else calls into the ST7701 IO handle after boot. +static void reclaimSpiPinsForSdCard() { + esp_rom_gpio_connect_out_signal(GPIO_NUM_47, spi_periph_signal[SPI2_HOST].spid_out, false, false); + esp_rom_gpio_connect_out_signal(GPIO_NUM_48, spi_periph_signal[SPI2_HOST].spiclk_out, false, false); + gpio_set_direction(GPIO_NUM_47, GPIO_MODE_OUTPUT); + gpio_set_direction(GPIO_NUM_48, GPIO_MODE_OUTPUT); +} + static const st7701_lcd_init_cmd_t st7701_lcd_init_cmds[] = { // {cmd, { data }, data_size, delay_ms} {0xFF, (uint8_t[]) {0x77, 0x01, 0x00, 0x00, 0x10}, 5, 0}, @@ -181,6 +197,8 @@ bool St7701Display::createPanelHandle(esp_lcd_panel_io_handle_t ioHandle, esp_lc return false; } + reclaimSpiPinsForSdCard(); + return true; } diff --git a/Devices/cyd-4848s040c/Source/module.cpp b/Devices/cyd-4848s040c/Source/module.cpp index 164c2fce..6ec9afb2 100644 --- a/Devices/cyd-4848s040c/Source/module.cpp +++ b/Devices/cyd-4848s040c/Source/module.cpp @@ -12,7 +12,7 @@ static error_t stop() { return ERROR_NONE; } -struct Module cyd_4848s040c_module = { +Module cyd_4848s040c_module = { .name = "cyd-4848s040c", .start = start, .stop = stop, diff --git a/Devices/cyd-4848s040c/cyd,4848s040c.dts b/Devices/cyd-4848s040c/cyd,4848s040c.dts index 63b7fda7..96e31374 100644 --- a/Devices/cyd-4848s040c/cyd,4848s040c.dts +++ b/Devices/cyd-4848s040c/cyd,4848s040c.dts @@ -28,18 +28,4 @@ pin-sda = <&gpio0 19 GPIO_FLAG_NONE>; pin-scl = <&gpio0 45 GPIO_FLAG_NONE>; }; - - spi0 { - compatible = "espressif,esp32-spi"; - host = ; - cs-gpios = <&gpio0 42 GPIO_FLAG_NONE>; - pin-mosi = <&gpio0 47 GPIO_FLAG_NONE>; - pin-miso = <&gpio0 41 GPIO_FLAG_NONE>; - pin-sclk = <&gpio0 48 GPIO_FLAG_NONE>; - - sdcard@0 { - compatible = "espressif,esp32-sdspi"; - frequency-khz = <20000>; - }; - }; }; diff --git a/Documentation/ideas.md b/Documentation/ideas.md index 1af68702..e5540bee 100644 --- a/Documentation/ideas.md +++ b/Documentation/ideas.md @@ -12,8 +12,6 @@ ## Higher Priority -- Require either SD card present or at least 8MB of flash. This way we can increase firmware size, ensure performance, and have a single location for storing data. -- Consider storing data on SD card in `/tactility` folder (for usage with Launcher) - Add font design tokens such as "regular", "title" and "smaller". Perhaps via the LVGL kernel module. - Add kernel listening mechanism so that the root device init can be notified when a device becomes available: Callback for device/start stop with filtering on device type: @@ -21,13 +19,10 @@ - on_after_start: e.g. to initialize an I2S device via its I2C connection, once I2C becomes available - on_before_stop: e.g. to stop using a device that was started before - on_after_stop: ? -- DTS: support for #defines -- DTS: support for aliases - SPI kernel driver - Kernel concepts for ELF loading (generic approach for GUI apps, console apps, libraries). - Fix glitches when installing app via App Hub with 4.3" Waveshare - TCA9534 keyboards should use interrupts -- GT911 drivers should use interrupts if it's stable - Fix Cardputer (original): use LV_KEY_NEXT and _PREV in keyboard mapping instead of encoder driver hack (and check GPIO app if it then hangs too) - Expose http::download() and main dispatcher to TactiltyC. - External app loading: Check the version of Tactility and check ESP target hardware to check for compatibility @@ -36,15 +31,9 @@ Create some kind of "intent" handler like on Android. The intent can have an action (e.g. view), a URL and an optional bundle. The manifest can provide the intent handler -- When an SD card is detected, check if it has been initialized and assigned as data partition. - If the user choses to select it, then copy files from /data over to it. - Write the user choice to a file on the card. - File contains 3 statuses: ignore, data, .. initdata? - The latter is used for auto-selecting it as data partition. - Support direct installation of an `.app` file with `tactility.py install helloworld.app ` - Support `tactility.py target ` to remember the device IP address. - minitar/untarFile(): "entry->metadata.path" can escape its confined path (e.g. "../something") -- Refactor elf loader code to make it multi-platform and to support multiple types of executables ## Medium Priority @@ -91,7 +80,7 @@ - Audio player app - Audio recording app - OTA updates -- T-Deck Plus: Create separate device config? +- T-Deck Plus: Create separate device config (pre-requisite: needs more kernel drivers so the driver count in the device project is reduced) - If present, use LED to show boot/wifi status - Capacity based on voltage: estimation for various devices uses a linear voltage curve, but it should use a battery discharge curve. - Wrapper for lvgl slider widget that shows "+" and "-" buttons, and also the value in a label. diff --git a/Tactility/Include/Tactility/lvgl/LvglSync.h b/Tactility/Include/Tactility/lvgl/LvglSync.h index f579358d..c3c9841e 100644 --- a/Tactility/Include/Tactility/lvgl/LvglSync.h +++ b/Tactility/Include/Tactility/lvgl/LvglSync.h @@ -12,9 +12,11 @@ constexpr TickType_t defaultLockTime = 500 / portTICK_PERIOD_MS; * LVGL locking function * @param[in] timeout as ticks * @warning when passing zero, we wait forever, as this is the default behaviour for esp_lvgl_port, and we want it to remain consistent + * @deprecated Use lvgl_lock() or lvgl_try_lock() from lvgl-module instead. */ bool lock(TickType_t timeout = portMAX_DELAY); +/** @deprecated Use lvgl_unlock() from lvgl-module instead. */ void unlock(); std::shared_ptr getSyncLock(); diff --git a/Tactility/Include/Tactility/settings/SystemSettings.h b/Tactility/Include/Tactility/settings/SystemSettings.h index 0339b718..366107be 100644 --- a/Tactility/Include/Tactility/settings/SystemSettings.h +++ b/Tactility/Include/Tactility/settings/SystemSettings.h @@ -5,9 +5,9 @@ namespace tt::settings { struct SystemSettings { - Language language; - bool timeFormat24h; - std::string dateFormat; // MM/DD/YYYY, DD/MM/YYYY, YYYY-MM-DD, YYYY/MM/DD + Language language = Language::en_US; + bool timeFormat24h = true; + std::string dateFormat = std::string("DD/MM/YYYY"); // MM/DD/YYYY, DD/MM/YYYY, YYYY-MM-DD, YYYY/MM/DD }; bool loadSystemSettings(SystemSettings& properties); diff --git a/Tactility/Private/Tactility/PartitionsEsp.h b/Tactility/Private/Tactility/PartitionsEsp.h index d504272d..958155aa 100644 --- a/Tactility/Private/Tactility/PartitionsEsp.h +++ b/Tactility/Private/Tactility/PartitionsEsp.h @@ -7,7 +7,7 @@ namespace tt { -esp_err_t initPartitionsEsp(); +bool initPartitionsEsp(); wl_handle_t getDataPartitionWlHandle(); } // namespace diff --git a/Tactility/Private/Tactility/app/chat/ChatSettings.h b/Tactility/Private/Tactility/app/chat/ChatSettings.h index f9e5ab07..6e4e5b1d 100644 --- a/Tactility/Private/Tactility/app/chat/ChatSettings.h +++ b/Tactility/Private/Tactility/app/chat/ChatSettings.h @@ -24,7 +24,6 @@ struct ChatSettingsData { ChatSettingsData loadSettings(); bool saveSettings(const ChatSettingsData& settings); -ChatSettingsData getDefaultSettings(); bool settingsFileExists(); } // namespace tt::app::chat diff --git a/Tactility/Private/Tactility/service/gui/GuiService.h b/Tactility/Private/Tactility/service/gui/GuiService.h index 9ae0f3da..a1116e93 100644 --- a/Tactility/Private/Tactility/service/gui/GuiService.h +++ b/Tactility/Private/Tactility/service/gui/GuiService.h @@ -7,16 +7,13 @@ #include #include +#include + #include #include namespace tt::service::gui { -constexpr auto GUI_THREAD_FLAG_DRAW = (1 << 0); -constexpr auto GUI_THREAD_FLAG_INPUT = (1 << 1); -constexpr auto GUI_THREAD_FLAG_EXIT = (1 << 2); -constexpr auto GUI_THREAD_FLAG_ALL = (GUI_THREAD_FLAG_DRAW | GUI_THREAD_FLAG_INPUT | GUI_THREAD_FLAG_EXIT); - /** * Output a log warning if the current task is the GUI task. * This is meant for code that should either create their own task or use a different task to execute on. @@ -28,7 +25,8 @@ class GuiService final : public Service { // Thread and lock Thread* thread = nullptr; - EventGroup threadFlags; + DispatcherHandle_t dispatcher = nullptr; + bool exitRequested = false; RecursiveMutex mutex; PubSub::SubscriptionHandle loader_pubsub_subscription = nullptr; @@ -46,6 +44,8 @@ class GuiService final : public Service { static int32_t guiMain(); + static void onGuiDispatch(void* context); + void onLoaderEvent(loader::LoaderService::Event event); lv_obj_t* createAppViews(lv_obj_t* parent); @@ -70,8 +70,6 @@ public: void onStop(ServiceContext& service) override; - void requestDraw(); - /** * Show the on-screen keyboard. * @param[in] textarea the textarea to focus the input for diff --git a/Tactility/Source/PartitionsEsp.cpp b/Tactility/Source/PartitionsEsp.cpp index 73f0f11c..78555593 100644 --- a/Tactility/Source/PartitionsEsp.cpp +++ b/Tactility/Source/PartitionsEsp.cpp @@ -47,6 +47,7 @@ FileSystemApi partition_fs_api = { // endregion file_system stub static esp_err_t initNvsFlashSafely() { + LOGGER.info("Init NVS"); esp_err_t result = nvs_flash_init(); if (result == ESP_ERR_NVS_NO_FREE_PAGES || result == ESP_ERR_NVS_NEW_VERSION_FOUND) { ESP_ERROR_CHECK(nvs_flash_erase()); @@ -75,7 +76,7 @@ size_t getSectorSize() { #endif } -esp_err_t initPartitionsEsp() { +bool initPartitionsEsp() { LOGGER.info("Init partitions"); ESP_ERROR_CHECK(initNvsFlashSafely()); @@ -90,22 +91,25 @@ esp_err_t initPartitionsEsp() { auto system_result = esp_vfs_fat_spiflash_mount_ro("/system", "system", &mount_config); if (system_result != ESP_OK) { LOGGER.error("Failed to mount /system ({})", esp_err_to_name(system_result)); - } else { - LOGGER.info("Mounted /system"); - static auto system_fs_data = PartitionFsData("/system"); - file_system_add(&partition_fs_api, &system_fs_data); + return false; } + LOGGER.info("Mounted /system"); + static auto system_fs_data = PartitionFsData("/system"); + file_system_add(&partition_fs_api, &system_fs_data); +#ifdef CONFIG_TT_USER_DATA_LOCATION_INTERNAL auto data_result = esp_vfs_fat_spiflash_mount_rw_wl("/data", "data", &mount_config, &data_wl_handle); if (data_result != ESP_OK) { LOGGER.error("Failed to mount /data ({})", esp_err_to_name(data_result)); - } else { - LOGGER.info("Mounted /data"); - static auto data_fs_data = PartitionFsData("/data"); - file_system_add(&partition_fs_api, &data_fs_data); + return false; } - return system_result == ESP_OK && data_result == ESP_OK; + LOGGER.info("Mounted /data"); + static auto data_fs_data = PartitionFsData("/data"); + file_system_add(&partition_fs_api, &data_fs_data); +#endif + + return true; } } // namespace diff --git a/Tactility/Source/TactilityEsp.cpp b/Tactility/Source/TactilityEsp.cpp index 44111c9d..85cb4a22 100644 --- a/Tactility/Source/TactilityEsp.cpp +++ b/Tactility/Source/TactilityEsp.cpp @@ -1,38 +1,25 @@ #ifdef ESP_PLATFORM -#include +#include +#include + #include -#include #include "esp_event.h" #include "esp_netif.h" -#include "nvs_flash.h" + +constexpr auto* TAG = "Tactility"; namespace tt { -static auto LOGGER = Logger("Tactility"); - -// Initialize NVS -static void initNvs() { - LOGGER.info("Init NVS"); - esp_err_t ret = nvs_flash_init(); - if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) { - LOGGER.info("NVS erasing"); - ESP_ERROR_CHECK(nvs_flash_erase()); - ret = nvs_flash_init(); - } - ESP_ERROR_CHECK(ret); -} - static void initNetwork() { - LOGGER.info("Init network"); + LOG_I(TAG, "Init network"); ESP_ERROR_CHECK(esp_netif_init()); ESP_ERROR_CHECK(esp_event_loop_create_default()); } void initEsp() { - initNvs(); - initPartitionsEsp(); + check(initPartitionsEsp(), "Failed to init partitions"); initNetwork(); } diff --git a/Tactility/Source/app/chat/ChatSettings.cpp b/Tactility/Source/app/chat/ChatSettings.cpp index b0502011..dabe1787 100644 --- a/Tactility/Source/app/chat/ChatSettings.cpp +++ b/Tactility/Source/app/chat/ChatSettings.cpp @@ -8,6 +8,7 @@ #include #include +#include #include #include #include @@ -34,6 +35,8 @@ constexpr auto* KEY_NICKNAME = "nickname"; constexpr auto* KEY_ENCRYPTION_KEY = "encryptionKey"; constexpr auto* KEY_CHAT_CHANNEL = "chatChannel"; +uint32_t defaultSenderId = 0; + // IV_SEED provides basic obfuscation for stored encryption keys, not strong encryption. // The device master key (from crypt::getIv) provides the actual security. static constexpr auto* IV_SEED = "chat_key"; @@ -112,8 +115,9 @@ static uint32_t generateSenderId() { } ChatSettingsData getDefaultSettings() { + if (defaultSenderId == 0) defaultSenderId = generateSenderId(); return ChatSettingsData{ - .senderId = 0, + .senderId = defaultSenderId, .nickname = "Device", .encryptionKey = {}, .hasEncryptionKey = false, @@ -124,8 +128,14 @@ ChatSettingsData getDefaultSettings() { ChatSettingsData loadSettings() { ChatSettingsData settings = getDefaultSettings(); + auto settings_path = getSettingsFilePath(); + if (!file::isFile(settings_path)) { + settings.senderId = generateSenderId(); + return settings; + } + std::map map; - if (!file::loadPropertiesFile(getSettingsFilePath(), map)) { + if (!file::loadPropertiesFile(settings_path, map)) { settings.senderId = generateSenderId(); return settings; } @@ -176,7 +186,12 @@ bool saveSettings(const ChatSettingsData& settings) { map[KEY_ENCRYPTION_KEY] = ""; } - return file::savePropertiesFile(getSettingsFilePath(), map); + auto settings_path = getSettingsFilePath(); + if (!file::findOrCreateParentDirectory(settings_path, 0755)) { + LOGGER.error("Failed to create parent dir for {}", settings_path); + return false; + } + return file::savePropertiesFile(settings_path, map); } bool settingsFileExists() { diff --git a/Tactility/Source/app/setup/Setup.cpp b/Tactility/Source/app/setup/Setup.cpp index 0d50b846..d9bfff49 100644 --- a/Tactility/Source/app/setup/Setup.cpp +++ b/Tactility/Source/app/setup/Setup.cpp @@ -53,6 +53,7 @@ class SetupApp final : public App { Phase phase = Phase::Welcome; size_t stepIndex = 0; std::vector steps; + bool isShown = false; lv_obj_t* titleLabel = nullptr; lv_obj_t* descriptionLabel = nullptr; @@ -105,7 +106,12 @@ class SetupApp final : public App { phase = Phase::Done; } - renderCurrent(); + // Widgets may not exist yet: onShow() runs asynchronously on the GUI task and + // may not have (re)created them by the time onResult() advances the state. + // onShow() calls renderCurrent() itself once the widgets are ready. + if (isShown) { + renderCurrent(); + } } void onSkipClicked() { @@ -180,9 +186,14 @@ public: lv_obj_align(continueButton, LV_ALIGN_BOTTOM_RIGHT, -12, -12); lv_obj_add_event_cb(continueButton, onContinueClickedCallback, LV_EVENT_SHORT_CLICKED, this); + isShown = true; renderCurrent(); } + void onHide(AppContext& app) override { + isShown = false; + } + void onResult(AppContext& app, LaunchId launchId, Result result, std::unique_ptr bundle) override { lvgl_lock(); advanceTo(stepIndex + 1); diff --git a/Tactility/Source/app/timedatesettings/TimeDateSettings.cpp b/Tactility/Source/app/timedatesettings/TimeDateSettings.cpp index ab8b825f..1c2520d0 100644 --- a/Tactility/Source/app/timedatesettings/TimeDateSettings.cpp +++ b/Tactility/Source/app/timedatesettings/TimeDateSettings.cpp @@ -1,12 +1,15 @@ +#include "tactility/lvgl_module.h" + + +#include +#include #include #include -#include -#include #include -#include +#include #include -#include #include +#include #include @@ -23,6 +26,7 @@ class TimeDateSettingsApp final : public App { RecursiveMutex mutex; lv_obj_t* timeZoneLabel = nullptr; lv_obj_t* dateFormatDropdown = nullptr; + bool isShown = false; static void onTimeFormatChanged(lv_event_t* event) { auto* widget = lv_event_get_target_obj(event); @@ -133,6 +137,12 @@ public: } lv_obj_center(timeZoneLabel); lv_label_set_text(timeZoneLabel, timeZoneName.c_str()); + + isShown = true; + } + + void onHide(AppContext& app) override { + isShown = false; } void onResult(AppContext& app, LaunchId launchId, Result result, std::unique_ptr bundle) override { @@ -141,11 +151,13 @@ public: const auto code = timezone::getResultCode(*bundle); LOGGER.info("Result name={} code={}", name, code); - if (!name.empty()) { - if (lvgl::lock(100 / portTICK_PERIOD_MS)) { + // onShow() may not have (re)created the widgets yet: onResult() runs synchronously + // on the loader thread and can race ahead of the async gui-task redraw. + if (!name.empty() && lvgl_try_lock(100 / portTICK_PERIOD_MS)) { + if (isShown) { lv_label_set_text(timeZoneLabel, name.c_str()); - lvgl::unlock(); } + lvgl_unlock(); } } } diff --git a/Tactility/Source/bluetooth/BluetoothPairedDevice.cpp b/Tactility/Source/bluetooth/BluetoothPairedDevice.cpp index 38dd04be..deb3cdee 100644 --- a/Tactility/Source/bluetooth/BluetoothPairedDevice.cpp +++ b/Tactility/Source/bluetooth/BluetoothPairedDevice.cpp @@ -60,8 +60,11 @@ bool hasFileForDevice(const std::string& addr_hex) { } bool load(const std::string& addr_hex, PairedDevice& device) { + auto file_path = getFilePath(addr_hex); + if (!file::isFile(file_path)) return false; + std::map map; - if (!file::loadPropertiesFile(getFilePath(addr_hex), map)) return false; + if (!file::loadPropertiesFile(file_path, map)) return false; if (!map.contains(KEY_ADDR)) return false; if (!hexToAddr(map[KEY_ADDR], device.addr)) return false; @@ -83,7 +86,12 @@ bool save(const PairedDevice& device) { map[KEY_ADDR] = addr_hex; map[KEY_AUTO_CONNECT] = device.autoConnect ? "true" : "false"; map[KEY_PROFILE_ID] = std::to_string(device.profileId); - return file::savePropertiesFile(getFilePath(addr_hex), map); + auto file_path = getFilePath(addr_hex); + if (!file::findOrCreateParentDirectory(file_path, 0755)) { + LOGGER.error("Failed to create parent dir for {}", file_path); + return false; + } + return file::savePropertiesFile(file_path, map); } bool remove(const std::string& addr_hex) { diff --git a/Tactility/Source/bluetooth/BluetoothSettings.cpp b/Tactility/Source/bluetooth/BluetoothSettings.cpp index 82237048..aa192874 100644 --- a/Tactility/Source/bluetooth/BluetoothSettings.cpp +++ b/Tactility/Source/bluetooth/BluetoothSettings.cpp @@ -29,8 +29,13 @@ static BluetoothSettings cached; static bool cached_valid = false; static bool load(BluetoothSettings& out) { + auto settings_path = getSettingsPath(); + if (!file::isFile(settings_path)) { + return false; + } + std::map map; - if (!file::loadPropertiesFile(getSettingsPath(), map)) { + if (!file::loadPropertiesFile(settings_path, map)) { return false; } auto it = map.find(KEY_ENABLE_ON_BOOT); @@ -47,11 +52,18 @@ static bool load(BluetoothSettings& out) { static bool save(const BluetoothSettings& s) { std::map map; - file::loadPropertiesFile(getSettingsPath(), map); // ignore failure — may not exist yet + if (file::isFile(getSettingsPath())) { + file::loadPropertiesFile(getSettingsPath(), map); + } map[KEY_ENABLE_ON_BOOT] = s.enableOnBoot ? "true" : "false"; map[KEY_SPP_AUTO_START] = s.sppAutoStart ? "true" : "false"; map[KEY_MIDI_AUTO_START] = s.midiAutoStart ? "true" : "false"; - return file::savePropertiesFile(getSettingsPath(), map); + auto settings_path = getSettingsPath(); + if (!file::findOrCreateParentDirectory(settings_path, 0755)) { + LOGGER.error("Failed to create parent dir for {}", settings_path); + return false; + } + return file::savePropertiesFile(settings_path, map); } static BluetoothSettings getCachedOrLoad() { diff --git a/Tactility/Source/service/development/DevelopmentSettings.cpp b/Tactility/Source/service/development/DevelopmentSettings.cpp index 4cdcd89a..15f7d0ed 100644 --- a/Tactility/Source/service/development/DevelopmentSettings.cpp +++ b/Tactility/Source/service/development/DevelopmentSettings.cpp @@ -1,4 +1,5 @@ #ifdef ESP_PLATFORM +#include #include #include #include @@ -21,8 +22,13 @@ struct DevelopmentSettings { }; static bool load(DevelopmentSettings& settings) { + auto settings_path = getSettingsFilePath(); + if (!file::isFile(settings_path)) { + return false; + } + std::map map; - if (!file::loadPropertiesFile(getSettingsFilePath(), map)) { + if (!file::loadPropertiesFile(settings_path, map)) { return false; } @@ -38,7 +44,12 @@ static bool load(DevelopmentSettings& settings) { static bool save(const DevelopmentSettings& settings) { std::map map; map[SETTINGS_KEY_ENABLE_ON_BOOT] = settings.enableOnBoot ? "true" : "false"; - return file::savePropertiesFile(getSettingsFilePath(), map); + auto settings_path = getSettingsFilePath(); + if (!file::findOrCreateParentDirectory(settings_path, 0755)) { + LOGGER.error("Failed to create parent dir for {}", settings_path); + return false; + } + return file::savePropertiesFile(settings_path, map); } void setEnableOnBoot(bool enable) { diff --git a/Tactility/Source/service/gui/GuiService.cpp b/Tactility/Source/service/gui/GuiService.cpp index bfd255c5..235cdb59 100644 --- a/Tactility/Source/service/gui/GuiService.cpp +++ b/Tactility/Source/service/gui/GuiService.cpp @@ -27,14 +27,49 @@ void warnIfRunningOnGuiTask(const char* context) { } } +namespace { + +enum class GuiDispatchType { Show, Hide, Exit }; + +struct GuiDispatchItem { + GuiService* service; + GuiDispatchType type; + std::shared_ptr appInstance; // only used for Show +}; + +} // namespace + // region AppManifest +void GuiService::onGuiDispatch(void* context) { + std::unique_ptr item(static_cast(context)); + switch (item->type) { + case GuiDispatchType::Show: + item->service->showApp(item->appInstance); + break; + case GuiDispatchType::Hide: + item->service->hideApp(); + break; + case GuiDispatchType::Exit: + item->service->exitRequested = true; + break; + } +} + void GuiService::onLoaderEvent(LoaderService::Event event) { + GuiDispatchItem* item; if (event == LoaderService::Event::ApplicationShowing) { auto app_instance = std::static_pointer_cast(app::getCurrentAppContext()); - showApp(app_instance); + item = new GuiDispatchItem{this, GuiDispatchType::Show, app_instance}; } else if (event == LoaderService::Event::ApplicationHiding) { - hideApp(); + item = new GuiDispatchItem{this, GuiDispatchType::Hide, nullptr}; + } else { + return; + } + + if (dispatcher_dispatch(dispatcher, item, onGuiDispatch) != ERROR_NONE) { + LOGGER.error("Failed to dispatch gui event"); + delete item; } } @@ -82,26 +117,8 @@ int32_t GuiService::guiMain() { lvgl::unlock(); - while (true) { - uint32_t flags = 0; - if (service->threadFlags.wait(GUI_THREAD_FLAG_ALL, false, true, &flags, portMAX_DELAY)) { - // When service not started or starting -> exit - State service_state = getState(manifest.id); - if (service_state != State::Started && service_state != State::Starting) { - break; - } - - // Process and dispatch draw call - if (flags & GUI_THREAD_FLAG_DRAW) { - service->threadFlags.clear(GUI_THREAD_FLAG_DRAW); - service->redraw(); - } - - if (flags & GUI_THREAD_FLAG_EXIT) { - service->threadFlags.clear(GUI_THREAD_FLAG_EXIT); - break; - } - } + while (!service->exitRequested) { + dispatcher_consume(service->dispatcher); } service->appRootWidget = nullptr; @@ -177,6 +194,9 @@ void GuiService::redraw() { } bool GuiService::onStart(ServiceContext& service) { + exitRequested = false; + dispatcher = dispatcher_alloc(); + thread = new Thread( GUI_TASK_NAME, 4096, // Last known minimum was 2800 for launching desktop @@ -211,8 +231,14 @@ void GuiService::onStop(ServiceContext& service) { appToRender = nullptr; isStarted = false; - threadFlags.set(GUI_THREAD_FLAG_EXIT); unlock(); + + auto* exit_item = new GuiDispatchItem{this, GuiDispatchType::Exit, nullptr}; + if (dispatcher_dispatch(dispatcher, exit_item, onGuiDispatch) != ERROR_NONE) { + LOGGER.error("Failed to dispatch gui exit event"); + check(false, "Failed to dispatch exit signal to thread."); + delete exit_item; + } thread->join(); if (lvgl::lock()) { @@ -226,10 +252,8 @@ void GuiService::onStop(ServiceContext& service) { } delete thread; -} - -void GuiService::requestDraw() { - threadFlags.set(GUI_THREAD_FLAG_DRAW); + dispatcher_free(dispatcher); + dispatcher = nullptr; } void GuiService::showApp(std::shared_ptr app) { @@ -253,7 +277,7 @@ void GuiService::showApp(std::shared_ptr app) { } appToRender = std::move(app); - requestDraw(); + redraw(); } void GuiService::hideApp() { diff --git a/Tactility/Source/service/webserver/WebServerService.cpp b/Tactility/Source/service/webserver/WebServerService.cpp index 78b1416f..c0289a01 100644 --- a/Tactility/Source/service/webserver/WebServerService.cpp +++ b/Tactility/Source/service/webserver/WebServerService.cpp @@ -1454,7 +1454,7 @@ esp_err_t WebServerService::handleApiScreenshot(httpd_req_t* request) { #if TT_FEATURE_SCREENSHOT_ENABLED // Determine save location: prefer SD card root if mounted, otherwise /data - std::string save_path = getUserDataRootPath(); + std::string save_path = getUserDataPath(); // Find next available filename with incrementing number std::string screenshot_path; diff --git a/Tactility/Source/service/wifi/WifiApSettings.cpp b/Tactility/Source/service/wifi/WifiApSettings.cpp index c1c49270..71d32536 100644 --- a/Tactility/Source/service/wifi/WifiApSettings.cpp +++ b/Tactility/Source/service/wifi/WifiApSettings.cpp @@ -127,6 +127,10 @@ bool load(const std::string& ssid, WifiApSettings& apSettings) { return false; } const auto file_path = getApPropertiesFilePath(service_context->getPaths(), ssid); + if (!file::isFile(file_path)) { + return false; + } + std::map map; if (!file::loadPropertiesFile(file_path, map)) { return false; diff --git a/Tactility/Source/service/wifi/WifiBootSplashInit.cpp b/Tactility/Source/service/wifi/WifiBootSplashInit.cpp index 23523e22..50431dcb 100644 --- a/Tactility/Source/service/wifi/WifiBootSplashInit.cpp +++ b/Tactility/Source/service/wifi/WifiBootSplashInit.cpp @@ -120,21 +120,25 @@ static void importWifiApSettingsFromDir(const std::string& path) { } void bootSplashInit() { - LOGGER.info("bootSplashInit begin"); + LOGGER.info("bootSplashInit dispatch"); getMainDispatcher().dispatch([] { LOGGER.info("bootSplashInit dispatch begin"); - // First import any provisioning files placed on the system data partition. - const std::string data_settings_path = file::getChildPath(getUserDataPath(), "provisioning"); - importWifiApSettingsFromDir(data_settings_path); + // Import any provisioning files placed on the system data partition. + const std::string provisioning_path = file::getChildPath(getUserDataPath(), "provisioning"); + if (file::isDirectory(provisioning_path)) { + importWifiApSettingsFromDir(provisioning_path); + } else { + LOGGER.info("Skip provisioning: no files at {}", provisioning_path); + } + // Dispatch WiFi on if (settings::shouldEnableOnBoot()) { - LOGGER.info("Auto-enabling due to setting"); + LOGGER.info("Auto-enabling WiFi"); getMainDispatcher().dispatch([] -> void { setEnabled(true); }); } LOGGER.info("bootSplashInit dispatch end"); }); - LOGGER.info("bootSplashInit end"); } } diff --git a/Tactility/Source/settings/BootSettings.cpp b/Tactility/Source/settings/BootSettings.cpp index 04201606..1933afd9 100644 --- a/Tactility/Source/settings/BootSettings.cpp +++ b/Tactility/Source/settings/BootSettings.cpp @@ -1,7 +1,6 @@ #include #include #include -#include #include #include @@ -10,8 +9,6 @@ namespace tt::settings { -constexpr auto* TAG = "BootSettings"; - constexpr auto* PROPERTIES_FILE_FORMAT = "{}/settings/boot.properties"; constexpr auto* PROPERTIES_KEY_LAUNCHER_APP_ID = "launcherAppId"; constexpr auto* PROPERTIES_KEY_AUTO_START_APP_ID = "autoStartAppId"; @@ -22,6 +19,10 @@ static std::string getPropertiesFilePath() { bool loadBootSettings(BootSettings& properties) { const std::string path = getPropertiesFilePath(); + if (!file::isFile(path)) { + return false; + } + if (!file::loadPropertiesFile(path, [&properties](auto& key, auto& value) { if (key == PROPERTIES_KEY_AUTO_START_APP_ID) { properties.autoStartAppId = value; @@ -29,7 +30,6 @@ bool loadBootSettings(BootSettings& properties) { properties.launcherAppId = value; } })) { - LOG_I(TAG, "No settings at %s", path.c_str()); return false; } diff --git a/Tactility/Source/settings/DisplaySettings.cpp b/Tactility/Source/settings/DisplaySettings.cpp index f00f3b7d..1e62ca4e 100644 --- a/Tactility/Source/settings/DisplaySettings.cpp +++ b/Tactility/Source/settings/DisplaySettings.cpp @@ -1,5 +1,6 @@ #include +#include #include #include #include @@ -109,8 +110,13 @@ static bool fromString(const std::string& str, ScreensaverType& type) { } bool load(DisplaySettings& settings) { + auto settings_path = getSettingsFilePath(); + if (!file::isFile(settings_path)) { + return false; + } + std::map map; - if (!file::loadPropertiesFile(getSettingsFilePath(), map)) { + if (!file::loadPropertiesFile(settings_path, map)) { return false; } @@ -190,7 +196,11 @@ bool save(const DisplaySettings& settings) { map[SETTINGS_KEY_TIMEOUT_ENABLED] = settings.backlightTimeoutEnabled ? "1" : "0"; map[SETTINGS_KEY_TIMEOUT_MS] = std::to_string(settings.backlightTimeoutMs); map[SETTINGS_KEY_SCREENSAVER_TYPE] = toString(settings.screensaverType); - return file::savePropertiesFile(getSettingsFilePath(), map); + auto settings_path = getSettingsFilePath(); + if (!file::findOrCreateParentDirectory(settings_path, 0755)) { + return false; + } + return file::savePropertiesFile(settings_path, map); } lv_display_rotation_t toLvglDisplayRotation(Orientation orientation) { diff --git a/Tactility/Source/settings/KeyboardSettings.cpp b/Tactility/Source/settings/KeyboardSettings.cpp index f900db2c..ff77e1ea 100644 --- a/Tactility/Source/settings/KeyboardSettings.cpp +++ b/Tactility/Source/settings/KeyboardSettings.cpp @@ -1,4 +1,5 @@ #include +#include #include #include @@ -17,8 +18,13 @@ constexpr auto* KEY_BACKLIGHT_TIMEOUT_ENABLED = "backlightTimeoutEnabled"; constexpr auto* KEY_BACKLIGHT_TIMEOUT_MS = "backlightTimeoutMs"; bool load(KeyboardSettings& settings) { + auto settings_path = getSettingsFilePath(); + if (!file::isFile(settings_path)) { + return false; + } + std::map map; - if (!file::loadPropertiesFile(getSettingsFilePath(), map)) { + if (!file::loadPropertiesFile(settings_path, map)) { return false; } @@ -58,7 +64,11 @@ bool save(const KeyboardSettings& settings) { map[KEY_BACKLIGHT_BRIGHTNESS] = std::to_string(settings.backlightBrightness); map[KEY_BACKLIGHT_TIMEOUT_ENABLED] = settings.backlightTimeoutEnabled ? "1" : "0"; map[KEY_BACKLIGHT_TIMEOUT_MS] = std::to_string(settings.backlightTimeoutMs); - return file::savePropertiesFile(getSettingsFilePath(), map); + auto settings_path = getSettingsFilePath(); + if (!file::findOrCreateParentDirectory(settings_path, 0755)) { + return false; + } + return file::savePropertiesFile(settings_path, map); } } diff --git a/Tactility/Source/settings/SystemSettings.cpp b/Tactility/Source/settings/SystemSettings.cpp index 488c671b..98c04ac5 100644 --- a/Tactility/Source/settings/SystemSettings.cpp +++ b/Tactility/Source/settings/SystemSettings.cpp @@ -80,6 +80,11 @@ bool saveSystemSettings(const SystemSettings& properties) { map["timeFormat24h"] = properties.timeFormat24h ? "true" : "false"; map["dateFormat"] = properties.dateFormat; + if (!file::findOrCreateParentDirectory(file_path, 0755)) { + LOGGER.error("Failed to create parent dir for {}", file_path); + return false; + } + if (!file::savePropertiesFile(file_path, map)) { LOGGER.error("Failed to save {}", file_path); return false; diff --git a/Tactility/Source/settings/TouchCalibrationSettings.cpp b/Tactility/Source/settings/TouchCalibrationSettings.cpp index 1c1da3da..dab64f86 100644 --- a/Tactility/Source/settings/TouchCalibrationSettings.cpp +++ b/Tactility/Source/settings/TouchCalibrationSettings.cpp @@ -1,5 +1,6 @@ #include +#include #include #include #include @@ -64,8 +65,13 @@ bool isValid(const TouchCalibrationSettings& settings) { } bool load(TouchCalibrationSettings& settings) { + auto settings_path = getSettingsFilePath(); + if (!file::isFile(settings_path)) { + return false; + } + std::map map; - if (!file::loadPropertiesFile(getSettingsFilePath(), map)) { + if (!file::loadPropertiesFile(settings_path, map)) { return false; } @@ -116,7 +122,12 @@ bool save(const TouchCalibrationSettings& settings) { map[SETTINGS_KEY_Y_MIN] = std::to_string(settings.yMin); map[SETTINGS_KEY_Y_MAX] = std::to_string(settings.yMax); - if (!file::savePropertiesFile(getSettingsFilePath(), map)) { + auto settings_path = getSettingsFilePath(); + if (!file::findOrCreateParentDirectory(settings_path, 0755)) { + return false; + } + + if (!file::savePropertiesFile(settings_path, map)) { return false; } diff --git a/Tactility/Source/settings/TrackballSettings.cpp b/Tactility/Source/settings/TrackballSettings.cpp index 1cb68d21..fa3518a6 100644 --- a/Tactility/Source/settings/TrackballSettings.cpp +++ b/Tactility/Source/settings/TrackballSettings.cpp @@ -1,4 +1,5 @@ #include +#include #include #include @@ -23,8 +24,13 @@ constexpr uint8_t MIN_POINTER_SENSITIVITY = 1; constexpr uint8_t MAX_POINTER_SENSITIVITY = 10; bool load(TrackballSettings& settings) { + auto settings_path = getSettingsFilePath(); + if (!file::isFile(settings_path)) { + return false; + } + std::map map; - if (!file::loadPropertiesFile(getSettingsFilePath(), map)) { + if (!file::loadPropertiesFile(settings_path, map)) { return false; } @@ -84,7 +90,11 @@ bool save(const TrackballSettings& settings) { map[KEY_TRACKBALL_MODE] = (settings.trackballMode == TrackballMode::Pointer) ? "1" : "0"; map[KEY_ENCODER_SENSITIVITY] = std::to_string(std::clamp(settings.encoderSensitivity, MIN_ENCODER_SENSITIVITY, MAX_ENCODER_SENSITIVITY)); map[KEY_POINTER_SENSITIVITY] = std::to_string(std::clamp(settings.pointerSensitivity, MIN_POINTER_SENSITIVITY, MAX_POINTER_SENSITIVITY)); - return file::savePropertiesFile(getSettingsFilePath(), map); + auto settings_path = getSettingsFilePath(); + if (!file::findOrCreateParentDirectory(settings_path, 0755)) { + return false; + } + return file::savePropertiesFile(settings_path, map); } } diff --git a/Tactility/Source/settings/WebServerSettings.cpp b/Tactility/Source/settings/WebServerSettings.cpp index eb8ab34d..a45f67c5 100644 --- a/Tactility/Source/settings/WebServerSettings.cpp +++ b/Tactility/Source/settings/WebServerSettings.cpp @@ -90,8 +90,13 @@ static bool isEmptyCredential(const std::string& value) { } bool load(WebServerSettings& settings) { + auto settings_path = getSettingsFilePath(); + if (!file::isFile(settings_path)) { + return false; + } + std::map map; - if (!file::loadPropertiesFile(getSettingsFilePath(), map)) { + if (!file::loadPropertiesFile(settings_path, map)) { return false; } @@ -257,7 +262,12 @@ bool save(const WebServerSettings& settings) { map[KEY_WEBSERVER_USERNAME] = settings.webServerUsername; map[KEY_WEBSERVER_PASSWORD] = settings.webServerPassword; - return file::savePropertiesFile(getSettingsFilePath(), map); + auto settings_path = getSettingsFilePath(); + if (!file::findOrCreateParentDirectory(settings_path, 0755)) { + LOGGER.error("Failed to create parent dir for {}", settings_path); + return false; + } + return file::savePropertiesFile(settings_path, map); } } diff --git a/TactilityKernel/source/drivers/root.cpp b/TactilityKernel/source/drivers/root.cpp index c6dc5349..0e2d21ad 100644 --- a/TactilityKernel/source/drivers/root.cpp +++ b/TactilityKernel/source/drivers/root.cpp @@ -7,7 +7,7 @@ extern "C" { -bool root_is_model(const struct Device* device, const char* buffer) { +bool root_is_model(const Device* device, const char* buffer) { auto* config = static_cast(device->config); return strcmp(config->model, buffer) == 0; }