diff --git a/App/idf_component.yml b/App/idf_component.yml index 7b143f95..58f3ab69 100644 --- a/App/idf_component.yml +++ b/App/idf_component.yml @@ -16,7 +16,7 @@ dependencies: version: "1.3.2" espressif/esp_lcd_panel_io_additions: "1.0.1" espressif/esp_tinyusb: - version: "1.5.0" + version: "1.7.6~1" rules: - if: "target == esp32s3" idf: '5.4' diff --git a/Boards/Simulator/Source/hal/SimulatorSdCard.h b/Boards/Simulator/Source/hal/SimulatorSdCard.h index 246a09fa..6c8fc5a0 100644 --- a/Boards/Simulator/Source/hal/SimulatorSdCard.h +++ b/Boards/Simulator/Source/hal/SimulatorSdCard.h @@ -8,8 +8,6 @@ using tt::hal::sdcard::SdCardDevice; class SimulatorSdCard final : public SdCardDevice { -private: - State state; std::shared_ptr lock; std::string mountPath; @@ -21,10 +19,10 @@ public: lock(std::make_shared()) {} - std::string getName() const final { return "Mock SD Card"; } - std::string getDescription() const final { return ""; } + std::string getName() const override { return "Mock SD Card"; } + std::string getDescription() const override { return ""; } - bool mount(const std::string& newMountPath) final { + bool mount(const std::string& newMountPath) override { state = State::Mounted; mountPath = newMountPath; return true; @@ -36,9 +34,9 @@ public: return true; } - std::string getMountPath() const final { return mountPath; }; + std::string getMountPath() const override { return mountPath; } - tt::Lock& getLock() const final { return *lock; } + std::shared_ptr getLock() const override { return lock; } - State getState() const override { return state; } + State getState(TickType_t timeout) const override { return state; } }; diff --git a/Data/data/boot.properties b/Data/data/boot.properties new file mode 100644 index 00000000..bac5f5e3 --- /dev/null +++ b/Data/data/boot.properties @@ -0,0 +1,2 @@ +launcherAppId=Launcher +#autoStartAppId= \ No newline at end of file diff --git a/Data/data/service/Wifi/wifi.properties b/Data/data/service/Wifi/wifi.properties new file mode 100644 index 00000000..583f5735 --- /dev/null +++ b/Data/data/service/Wifi/wifi.properties @@ -0,0 +1 @@ +enableOnBoot=false \ No newline at end of file diff --git a/Data/data/test.txt b/Data/data/test.txt deleted file mode 100644 index ead4f5ba..00000000 --- a/Data/data/test.txt +++ /dev/null @@ -1,2 +0,0 @@ -This file exists to test the partition. -It can be deleted when the partition contains other files. diff --git a/Documentation/ideas.md b/Documentation/ideas.md index 899531c5..75719885 100644 --- a/Documentation/ideas.md +++ b/Documentation/ideas.md @@ -1,41 +1,47 @@ # TODOs +## Higher Priority + +- Move Development settings from flash to `/data/apps/development/development.properties` (just the "start on boot") +- Move Display settings from flash to `/data/apps/display/display.properties` +- App data directory should be automatically created (and then we can remove the custom code from Notes.cpp) - When an external app fails to load (e.g. due to mapping error) then show an error dialog. -- Revisit TinyUSB mouse idea: the bugs related to cleanup seem to be fixed in the library. -- Bug: When a Wi-Fi SSID is too long, then it fails to save the credentials -- Add a Keyboard setting app to override the behaviour of soft keyboard hiding (e.g. keyboard hardware is present, but the user wants to use a soft keyboard) -- HAL for display touch calibration +- Expose app::Paths to TactilityC +- Call tt::lvgl::isSyncSet after HAL init and show an error (and crash?) when it is not set. +- External app loading: Check the version of Tactility and check ESP target hardware to check for compatibility. +- Localization of texts (load in boot app from sd?) +- App packaging +- Create more unit tests for `tactility-core` +- Make a URL handler. Use it for handling local files. Match file types with apps. + +## Lower Priority + +- Support hot-plugging SD card +- Create more unit tests for `tactility` +- Explore LVGL9's FreeRTOS functionality +- CrashHandler: use "corrupted" flag +- CrashHandler: process other types of crashes (WDT?) +- Add a Keyboard setting in `keyboard.properties` to override the behaviour of soft keyboard hiding (e.g. keyboard hardware is present, but the user wants to use a soft keyboard) +- Use GPS time to set/update the current time +- All drivers (e.g. display, touch, etc.) should call stop() in their destructor, or at least assert that they should not be running. +- Fix bug in T-Deck/etc: esp_lvgl_port settings has a large stack size (~9kB) to fix an issue where the T-Deck would get a stackoverflow. This sometimes happens when WiFi is auto-enabled and you open the app while it is still connecting. - Start using non_null (either via MS GSL, or custom) - `hal/Configuration.h` defines C function types: Use C++ std::function instead - Fix system time to not be 1980 (use build year as a minimum). Consider keeping track of the last known time. - Use std::span or string_view in StringUtils https://youtu.be/FRkJCvHWdwQ?t=2754 -- Fix bug in T-Deck/etc: esp_lvgl_port settings has a large stack size (~9kB) to fix an issue where the T-Deck would get a stackoverflow. This sometimes happens when WiFi is auto-enabled and you open the app while it is still connecting. - Mutex: Implement give/take from ISR support (works only for non-recursive ones) - Extend unPhone power driver: add charging status, usb connection status, etc. -- Expose app::Paths to TactilityC -- Make a ledger for setting CPU affinity of various services and tasks -- CrashHandler: use "corrupted" flag -- CrashHandler: process other types of crashes (WDT?) -- Call tt::lvgl::isSyncSet after HAL init and show an error (and crash?) when it is not set. - Create different partition files for different ESP flash size targets (N4, N8, N16, N32) - T-Deck: Clear screen before turning on blacklight - T-Deck: Use knob for UI selection? -- Crash monitoring: Keep track of which system phase the app crashed in (e.g. which app in which state) -- App::onResult should pass the app id (or launch request id!) that was started, so we can differentiate between multiple types of apps being launched -- 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. -- Localisation of texts (load in boot app from sd?) -- Explore LVGL9's FreeRTOS functionality -- External app loading: Check version of Tactility and check ESP target hardware to check for compatibility. - Scanning SD card for external apps and auto-register them (in a temporary register?) -- Support hot-plugging SD card -- All drivers (e.g. display, touch, etc.) should call stop() in their destructor, or at least assert that they should not be running. -- Use GPS time to set/update the current time - Remove flex_flow from app_container in Gui.cpp # Nice-to-haves +- Considering the lack of callstack debugging for external apps: allow for some debugging to be exposed during a device crash. Apps could report their state (e.g. an integer value) which can be stored during app operation and retrieve after crash. The same can be done for various OS apps and states. We can keep an array of these numbers to keep track of the last X states, to get an idea of what's going on. - Give external app a different icon. Allow an external app to update their id, icon, type and name once they are running (and persist that info?). Loader will need to be able to find app by (external) location. - Audio player app - Audio recording app @@ -52,6 +58,7 @@ # App Ideas +- Revisit TinyUSB mouse idea: the bugs related to cleanup seem to be fixed in the library. - Map widget: https://github.com/portapack-mayhem/mayhem-firmware/blob/b66d8b1aa178d8a9cd06436fea788d5d58cb4c8d/firmware/application/ui/ui_geomap.cpp https://github.com/portapack-mayhem/mayhem-firmware/blob/b66d8b1aa178d8a9cd06436fea788d5d58cb4c8d/firmware/tools/generate_world_map.bin.py @@ -68,3 +75,4 @@ - Compile unix tools to ELF apps? - Todo list - Calendar +- Display touch calibration diff --git a/Tactility/Include/Tactility/BootProperties.h b/Tactility/Include/Tactility/BootProperties.h new file mode 100644 index 00000000..d94c5867 --- /dev/null +++ b/Tactility/Include/Tactility/BootProperties.h @@ -0,0 +1,24 @@ +#pragma once + +#include + +namespace tt { + +struct BootProperties { + /** App to start automatically after the splash screen. */ + std::string launcherAppId; + /** App to start automatically from the launcher screen. */ + std::string autoStartAppId; +}; + +/** + * Load the boot properties from the relevant file location(s). + * It will first attempt to load them from the SD card and if no file was found, + * then it will try to load the one from the data mount point. + * + * @param[out] properties the resulting properties + * @return true when the properties were successfully loaded and the result was set + */ +bool loadBootProperties(BootProperties& properties); + +} diff --git a/Tactility/Include/Tactility/MountPoints.h b/Tactility/Include/Tactility/MountPoints.h new file mode 100644 index 00000000..d2a3a9a8 --- /dev/null +++ b/Tactility/Include/Tactility/MountPoints.h @@ -0,0 +1,26 @@ +#pragma once + +#include +#include + +namespace tt::file { + +constexpr auto* SYSTEM_PARTITION_NAME = "system"; + +#ifdef ESP_PLATFORM +constexpr auto* MOUNT_POINT_SYSTEM = "/system"; +#else +constexpr auto* MOUNT_POINT_SYSTEM = "system"; +#endif + +constexpr auto* DATA_PARTITION_NAME = "data"; + +#ifdef ESP_PLATFORM +constexpr auto* MOUNT_POINT_DATA = "/data"; +#else +constexpr auto* MOUNT_POINT_DATA = "data"; +#endif + +std::vector getMountPoints(); + +} // namespace diff --git a/Tactility/Include/Tactility/Partitions.h b/Tactility/Include/Tactility/Partitions.h deleted file mode 100644 index 78f4eca0..00000000 --- a/Tactility/Include/Tactility/Partitions.h +++ /dev/null @@ -1,21 +0,0 @@ -#pragma once - -namespace tt { - -#define SYSTEM_PARTITION_NAME "system" - -#ifdef ESP_PLATFORM -#define MOUNT_POINT_SYSTEM "/system" -#else -#define MOUNT_POINT_SYSTEM "system" -#endif - -#define DATA_PARTITION_NAME "data" - -#ifdef ESP_PLATFORM -#define MOUNT_POINT_DATA "/data" -#else -#define MOUNT_POINT_DATA "data" -#endif - -} // namespace diff --git a/Tactility/Include/Tactility/Tactility.h b/Tactility/Include/Tactility/Tactility.h index a8c60973..f90e8bc5 100644 --- a/Tactility/Include/Tactility/Tactility.h +++ b/Tactility/Include/Tactility/Tactility.h @@ -4,7 +4,6 @@ #include #include -#include namespace tt { @@ -20,10 +19,6 @@ struct Configuration { const std::vector apps = {}; /** List of user services */ const std::vector services = {}; - /** Optional app to start automatically after the splash screen. */ - const std::string launcherAppId = app::launcher::manifest.id; - /** Optional app to start automatically after the splash screen. */ - const std::string autoStartAppId = {}; }; /** diff --git a/Tactility/Include/Tactility/app/AppManifest.h b/Tactility/Include/Tactility/app/AppManifest.h index 61dbf612..537cbc17 100644 --- a/Tactility/Include/Tactility/app/AppManifest.h +++ b/Tactility/Include/Tactility/app/AppManifest.h @@ -1,15 +1,9 @@ #pragma once -#include "Tactility/app/ManifestRegistry.h" - -#include -#include +#include "Tactility/app/AppRegistration.h" #include -// Forward declarations -typedef struct _lv_obj_t lv_obj_t; - namespace tt::app { class App; @@ -40,8 +34,6 @@ enum class Result { class Location { -private: - std::string path; Location() = default; explicit Location(const std::string& path) : path(path) {} @@ -54,7 +46,13 @@ public: return Location(path); } + /** Internal apps are all apps that are part of the firmware release. */ bool isInternal() const { return path.empty(); } + + /** + * External apps are all apps that are not part of the firmware release. + * e.g. an application on the sd card or one that is installed in /data + */ bool isExternal() const { return !path.empty(); } const std::string& getPath() const { return path; } }; diff --git a/Tactility/Include/Tactility/app/ManifestRegistry.h b/Tactility/Include/Tactility/app/AppRegistration.h similarity index 100% rename from Tactility/Include/Tactility/app/ManifestRegistry.h rename to Tactility/Include/Tactility/app/AppRegistration.h diff --git a/Tactility/Include/Tactility/app/notes/Notes.h b/Tactility/Include/Tactility/app/notes/Notes.h new file mode 100644 index 00000000..12a91814 --- /dev/null +++ b/Tactility/Include/Tactility/app/notes/Notes.h @@ -0,0 +1,11 @@ +#pragma once + +namespace tt::app::notes { + +/** + * Start the notes app with the specified text file. + * @param[in] filePath the path to the text file to open + */ +void start(const std::string& filePath); + +} diff --git a/Tactility/Include/Tactility/app/textviewer/TextViewer.h b/Tactility/Include/Tactility/app/textviewer/TextViewer.h deleted file mode 100644 index 0f7a72ed..00000000 --- a/Tactility/Include/Tactility/app/textviewer/TextViewer.h +++ /dev/null @@ -1,7 +0,0 @@ -#pragma once - -namespace tt::app::textviewer { - -void start(const std::string& file); - -} diff --git a/Tactility/Include/Tactility/file/FileLock.h b/Tactility/Include/Tactility/file/FileLock.h new file mode 100644 index 00000000..c9687694 --- /dev/null +++ b/Tactility/Include/Tactility/file/FileLock.h @@ -0,0 +1,32 @@ +#pragma once + +#include + +#include +#include + +/** + * Some file systems belong to devices on a shared bus (e.g. SPI SD card). + * Because of the shared bus, a lock is required for its operation. + */ +namespace tt::file { + +/** + * @param[in] path the path to find a lock for + * @return a non-null lock for the specified path. + */ +std::shared_ptr getLock(const std::string& path); + +/** + * Acquires a lock, calls the function, then releases the lock. + * @param[in] path the path to find a lock for + * @param[in] fn the code to execute while the lock is acquired + */ +template +ReturnType withLock(const std::string& path, std::function fn) { + const auto lock = getLock(path)->asScopedLock(); + lock.lock(); + return fn(); +} + +} diff --git a/TactilityCore/Include/Tactility/file/ObjectFile.h b/Tactility/Include/Tactility/file/ObjectFile.h similarity index 81% rename from TactilityCore/Include/Tactility/file/ObjectFile.h rename to Tactility/Include/Tactility/file/ObjectFile.h index 9dd6bcba..08d06a73 100644 --- a/TactilityCore/Include/Tactility/file/ObjectFile.h +++ b/Tactility/Include/Tactility/file/ObjectFile.h @@ -1,18 +1,18 @@ #pragma once -#include "File.h" +#include -#include -#include #include -#include +#include "FileLock.h" + +/** + * @warning The functionality below does NOT safely acquire file locks. Use file::getLock() or file::withLock() when using the functionality below. + */ namespace tt::file { class ObjectFileReader { -private: - const std::string filePath; const uint32_t recordSize = 0; @@ -41,12 +41,11 @@ public: class ObjectFileWriter { -private: - const std::string filePath; const uint32_t recordSize; const uint32_t recordVersion; const bool append; + const std::shared_ptr lock; std::unique_ptr file; uint32_t recordsWritten = 0; @@ -57,7 +56,8 @@ public: filePath(std::move(filePath)), recordSize(recordSize), recordVersion(recordVersion), - append(append) + append(append), + lock(getLock(filePath)) {} diff --git a/Tactility/Include/Tactility/file/PropertiesFile.h b/Tactility/Include/Tactility/file/PropertiesFile.h new file mode 100644 index 00000000..75000466 --- /dev/null +++ b/Tactility/Include/Tactility/file/PropertiesFile.h @@ -0,0 +1,36 @@ +#pragma once + +#include +#include +#include + +/** + * @note The functionality below safely acquires and releases any SD card device locks. Manual locking isn't needed. + */ +namespace tt::file { + +/** + * Load a properties file into a map + * @param[in] filePath the file to load + * @param[out] properties the resulting properties + * @return true when the properties file was opened successfully + */ +bool loadPropertiesFile(const std::string& filePath, std::map& properties); + +/** + * Load a properties file and report key-values to a function + * @param[in] filePath the file to load + * @param[in] callback the callback function that receives the key-values + * @return true when the properties file was opened successfully + */ +bool loadPropertiesFile(const std::string& filePath, std::function callback); + +/** + * Save properties to a file + * @param[in] filePath the file to save to + * @param[in] properties the properties to save + * @return true when the data was written to the file succesfully + */ +bool savePropertiesFile(const std::string& filePath, const std::map& properties); + +} diff --git a/Tactility/Include/Tactility/hal/sdcard/SdCardDevice.h b/Tactility/Include/Tactility/hal/sdcard/SdCardDevice.h index 8e753b22..648aff23 100644 --- a/Tactility/Include/Tactility/hal/sdcard/SdCardDevice.h +++ b/Tactility/Include/Tactility/hal/sdcard/SdCardDevice.h @@ -14,7 +14,7 @@ public: Mounted, Unmounted, Error, - Unknown + Timeout // Failed to retrieve state due to timeout }; enum class MountBehaviour { @@ -35,32 +35,28 @@ public: virtual bool mount(const std::string& mountPath) = 0; virtual bool unmount() = 0; - virtual State getState() const = 0; + virtual State getState(TickType_t timeout = portMAX_DELAY) const = 0; /** Return empty string when not mounted or the mount path if mounted */ virtual std::string getMountPath() const = 0; - virtual Lock& getLock() const = 0; + /** Non-null lock */ + virtual std::shared_ptr getLock() const = 0; virtual MountBehaviour getMountBehaviour() const { return mountBehaviour; } - bool isMounted() const { return getState() == State::Mounted; } + + /** @return true if the SD card was mounted, returns false when it was not or when a timeout happened. */ + bool isMounted(TickType_t timeout = portMAX_DELAY) const { return getState(timeout) == State::Mounted; } }; /** Return the SdCard device if the path is within the SdCard mounted path (path std::string::starts_with() check)*/ std::shared_ptr _Nullable find(const std::string& path); /** - * Acquires an SD card lock if the path is an SD card path. - * Always calls the function, but doesn't lock if the path is not an SD card path. + * Attempt to find an SD card that the specified belongs to, + * and returns its lock if the SD card is mounted. Otherwise it returns nullptr. + * @param[in] a path on a file system (e.g. file, directory, etc.) + * @return the lock of a mounted SD card or otherwise null */ -template - ReturnType withSdCardLock(const std::string& path, std::function fn) { - auto sdcard = find(path); - if (sdcard != nullptr) { - auto scoped_lockable = sdcard->getLock().asScopedLock(); - scoped_lockable.lock(portMAX_DELAY); - } - - return fn(); -} +std::shared_ptr findSdCardLock(const std::string& path); } // namespace tt::hal diff --git a/Tactility/Include/Tactility/hal/sdcard/SpiSdCardDevice.h b/Tactility/Include/Tactility/hal/sdcard/SpiSdCardDevice.h index 087a4b97..4ca1877e 100644 --- a/Tactility/Include/Tactility/hal/sdcard/SpiSdCardDevice.h +++ b/Tactility/Include/Tactility/hal/sdcard/SpiSdCardDevice.h @@ -28,7 +28,7 @@ public: gpio_num_t spiPinWp, gpio_num_t spiPinInt, MountBehaviour mountBehaviourAtBoot, - /** When custom lock is nullptr, use the SPI default one */ + /** When customLock is a nullptr, use the SPI default one */ std::shared_ptr _Nullable customLock = nullptr, std::vector csPinWorkAround = std::vector(), spi_host_device_t spiHost = SPI2_HOST, @@ -49,7 +49,7 @@ public: gpio_num_t spiPinCd; // Card detect gpio_num_t spiPinWp; // Write-protect gpio_num_t spiPinInt; // Interrupt - SdCardDevice::MountBehaviour mountBehaviourAtBoot; + MountBehaviour mountBehaviourAtBoot; std::shared_ptr _Nullable customLock; std::vector csPinWorkAround; spi_host_device_t spiHost; @@ -74,22 +74,22 @@ public: config(std::move(config)) {} - std::string getName() const final { return "SD Card"; } - std::string getDescription() const final { return "SD card via SPI interface"; } + std::string getName() const override { return "SD Card"; } + std::string getDescription() const override { return "SD card via SPI interface"; } - bool mount(const std::string& mountPath) final; - bool unmount() final; - std::string getMountPath() const final { return mountPath; } + bool mount(const std::string& mountPath) override; + bool unmount() override; + std::string getMountPath() const override { return mountPath; } - Lock& getLock() const final { - if (config->customLock) { - return *config->customLock; + std::shared_ptr getLock() const override { + if (config->customLock != nullptr) { + return config->customLock; } else { - return *spi::getLock(config->spiHost); + return spi::getLock(config->spiHost); } } - State getState() const override; + State getState(TickType_t timeout) const override; sdmmc_card_t* _Nullable getCard() { return card; } }; diff --git a/Tactility/Include/Tactility/kernel/SystemEvents.h b/Tactility/Include/Tactility/kernel/SystemEvents.h index 36302a77..9d53f9df 100644 --- a/Tactility/Include/Tactility/kernel/SystemEvents.h +++ b/Tactility/Include/Tactility/kernel/SystemEvents.h @@ -28,6 +28,7 @@ enum class SystemEvent { /** Value 0 mean "no subscription" */ typedef uint32_t SystemEventSubscription; +constexpr SystemEventSubscription NoSystemEventSubscription = 0U; typedef std::function OnSystemEvent; diff --git a/Tactility/Include/Tactility/service/ServiceRegistry.h b/Tactility/Include/Tactility/service/ServiceRegistration.h similarity index 100% rename from Tactility/Include/Tactility/service/ServiceRegistry.h rename to Tactility/Include/Tactility/service/ServiceRegistration.h diff --git a/Tactility/Include/Tactility/service/wifi/Wifi.h b/Tactility/Include/Tactility/service/wifi/Wifi.h index b41ad0a8..88cd3d6a 100644 --- a/Tactility/Include/Tactility/service/wifi/Wifi.h +++ b/Tactility/Include/Tactility/service/wifi/Wifi.h @@ -1,17 +1,14 @@ #pragma once -#include "./WifiGlobals.h" -#include "./WifiSettings.h" - #include -#include #include #include +#include "WifiApSettings.h" + #ifdef ESP_PLATFORM #include "esp_wifi.h" -#include "WifiSettings.h" #else #include // From esp_wifi_types.h in ESP-IDF 5.2 @@ -124,7 +121,7 @@ std::string getIp(); * @param[in] ap * @param[in] remember whether to save the ap data to the settings upon successful connection */ -void connect(const settings::WifiApSettings* ap, bool remember); +void connect(const settings::WifiApSettings& ap, bool remember); /** @brief Disconnect from the access point. Doesn't have any effect when not connected. */ void disconnect(); diff --git a/Tactility/Include/Tactility/service/wifi/WifiApSettings.h b/Tactility/Include/Tactility/service/wifi/WifiApSettings.h new file mode 100644 index 00000000..07fb4934 --- /dev/null +++ b/Tactility/Include/Tactility/service/wifi/WifiApSettings.h @@ -0,0 +1,58 @@ +#pragma once + +#include + +namespace tt::service::wifi::settings { + +/** + * This struct is stored as-is into NVS flash. + * + * The SSID and secret are increased by 1 byte to facilitate string null termination. + * This makes it easier to use the char array as a string in various places. + */ +struct WifiApSettings { + std::string ssid; + std::string password; + bool autoConnect; + int32_t channel; + + WifiApSettings( + std::string ssid, + std::string password, + bool autoConnect = true, + int32_t channel = 0 + ) : ssid(ssid), password(password), autoConnect(autoConnect), channel(channel) {} + + WifiApSettings() : ssid(""), password(""), autoConnect(true), channel(0) {} +}; + +/** + * Check if settings exist for the provided SSID + * @param[in] ssid the access point to look for + * @return true if the settings exist + */ +bool contains(const std::string& ssid); + +/** + * Load the settings for the provided SSID + * @param[in] ssid the access point to look for + * @param[out] settings the output settings + * @return true if the settings were loaded successfully + */ +bool load(const std::string& ssid, WifiApSettings& settings); + +/** + * Save settings + * @param settings the settings to save + * @return true when the settings were saved successfully + */ +bool save(const WifiApSettings& settings); + +/** + * Remove settings that were saved previously. + * @param ssid the name of the SSID for the settings to remove + * @return true when settings were found and removed + */ +bool remove(const std::string& ssid); + +} // namespace diff --git a/Tactility/Include/Tactility/service/wifi/WifiSettings.h b/Tactility/Include/Tactility/service/wifi/WifiSettings.h index 553eb47b..4ea9027f 100644 --- a/Tactility/Include/Tactility/service/wifi/WifiSettings.h +++ b/Tactility/Include/Tactility/service/wifi/WifiSettings.h @@ -1,31 +1,7 @@ #pragma once -#include "WifiGlobals.h" -#include - namespace tt::service::wifi::settings { -/** - * This struct is stored as-is into NVS flash. - * - * The SSID and secret are increased by 1 byte to facilitate string null termination. - * This makes it easier to use the char array as a string in various places. - */ -struct WifiApSettings { - char ssid[TT_WIFI_SSID_LIMIT + 1] = { 0 }; - char password[TT_WIFI_CREDENTIALS_PASSWORD_LIMIT + 1] = { 0 }; - int32_t channel = 0; - bool auto_connect = true; -}; - -bool contains(const char* ssid); - -bool load(const char* ssid, WifiApSettings* settings); - -bool save(const WifiApSettings* settings); - -bool remove(const char* ssid); - void setEnableOnBoot(bool enable); bool shouldEnableOnBoot(); diff --git a/Tactility/Private/Tactility/app/AppInstancePaths.h b/Tactility/Private/Tactility/app/AppInstancePaths.h index 7cd1e38c..46ca3b62 100644 --- a/Tactility/Private/Tactility/app/AppInstancePaths.h +++ b/Tactility/Private/Tactility/app/AppInstancePaths.h @@ -6,23 +6,21 @@ namespace tt::app { class AppInstancePaths final : public Paths { -private: - const AppManifest& manifest; public: explicit AppInstancePaths(const AppManifest& manifest) : manifest(manifest) {} - ~AppInstancePaths() final = default; + ~AppInstancePaths() override = default; - std::string getDataDirectory() const final; - std::string getDataDirectoryLvgl() const final; - std::string getDataPath(const std::string& childPath) const final; - std::string getDataPathLvgl(const std::string& childPath) const final; - std::string getSystemDirectory() const final; - std::string getSystemDirectoryLvgl() const final; - std::string getSystemPath(const std::string& childPath) const final; - std::string getSystemPathLvgl(const std::string& childPath) const final; + std::string getDataDirectory() const override; + std::string getDataDirectoryLvgl() const override; + std::string getDataPath(const std::string& childPath) const override; + std::string getDataPathLvgl(const std::string& childPath) const override; + std::string getSystemDirectory() const override; + std::string getSystemDirectoryLvgl() const override; + std::string getSystemPath(const std::string& childPath) const override; + std::string getSystemPathLvgl(const std::string& childPath) const override; }; } \ No newline at end of file diff --git a/Tactility/Private/Tactility/app/filebrowser/View.h b/Tactility/Private/Tactility/app/filebrowser/View.h index c6f24026..2d7f3729 100644 --- a/Tactility/Private/Tactility/app/filebrowser/View.h +++ b/Tactility/Private/Tactility/app/filebrowser/View.h @@ -20,7 +20,7 @@ class View { void showActionsForFile(); void viewFile(const std::string&path, const std::string&filename); - void createDirEntryWidget(lv_obj_t* parent, struct dirent& dir_entry); + void createDirEntryWidget(lv_obj_t* parent, dirent& dir_entry); void onNavigate(); public: diff --git a/Tactility/Private/Tactility/app/wificonnect/Bindings.h b/Tactility/Private/Tactility/app/wificonnect/Bindings.h index 193157e2..43e2cbea 100644 --- a/Tactility/Private/Tactility/app/wificonnect/Bindings.h +++ b/Tactility/Private/Tactility/app/wificonnect/Bindings.h @@ -1,10 +1,10 @@ #pragma once -#include "Tactility/service/wifi/WifiSettings.h" +#include namespace tt::app::wificonnect { -typedef void (*OnConnectSsid)(const service::wifi::settings::WifiApSettings* settings, bool store, void* context); +typedef void (*OnConnectSsid)(const service::wifi::settings::WifiApSettings& settings, bool store, void* context); typedef struct { OnConnectSsid onConnectSsid; diff --git a/Tactility/Private/Tactility/app/wificonnect/State.h b/Tactility/Private/Tactility/app/wificonnect/State.h index 0d95178f..e3e7ec55 100644 --- a/Tactility/Private/Tactility/app/wificonnect/State.h +++ b/Tactility/Private/Tactility/app/wificonnect/State.h @@ -1,18 +1,13 @@ #pragma once #include -#include -#include +#include namespace tt::app::wificonnect { class State { Mutex lock; - service::wifi::settings::WifiApSettings apSettings = { - .ssid = { 0 }, - .password = { 0 }, - .auto_connect = false - }; + service::wifi::settings::WifiApSettings apSettings; bool connectionError = false; bool connecting = false; public: @@ -20,7 +15,7 @@ public: void setConnectionError(bool error); bool hasConnectionError() const; - void setApSettings(const service::wifi::settings::WifiApSettings* newSettings); + void setApSettings(const service::wifi::settings::WifiApSettings& newSettings); void setConnecting(bool isConnecting); bool isConnecting() const; diff --git a/Tactility/Private/Tactility/app/wificonnect/View.h b/Tactility/Private/Tactility/app/wificonnect/View.h index 30492b6f..a8196d4f 100644 --- a/Tactility/Private/Tactility/app/wificonnect/View.h +++ b/Tactility/Private/Tactility/app/wificonnect/View.h @@ -13,8 +13,6 @@ class WifiConnect; class View { -private: - Bindings* bindings; State* state; diff --git a/Tactility/Private/Tactility/app/wificonnect/WifiConnect.h b/Tactility/Private/Tactility/app/wificonnect/WifiConnect.h index ed184e71..bbb17cc5 100644 --- a/Tactility/Private/Tactility/app/wificonnect/WifiConnect.h +++ b/Tactility/Private/Tactility/app/wificonnect/WifiConnect.h @@ -12,8 +12,6 @@ namespace tt::app::wificonnect { class WifiConnect : public App { -private: - Mutex mutex; State state; Bindings bindings = { diff --git a/TactilityCore/Private/Tactility/file/ObjectFilePrivate.h b/Tactility/Private/Tactility/file/ObjectFilePrivate.h similarity index 100% rename from TactilityCore/Private/Tactility/file/ObjectFilePrivate.h rename to Tactility/Private/Tactility/file/ObjectFilePrivate.h diff --git a/Tactility/Private/Tactility/service/wifi/WifiBootSplashInit.h b/Tactility/Private/Tactility/service/wifi/WifiBootSplashInit.h new file mode 100644 index 00000000..e96fdf97 --- /dev/null +++ b/Tactility/Private/Tactility/service/wifi/WifiBootSplashInit.h @@ -0,0 +1,10 @@ +#pragma once + +namespace tt::service::wifi { + +/** + * Called during boot, this function loads WiFi settings from SD card (when available). + */ +void bootSplashInit(); + +} \ No newline at end of file diff --git a/Tactility/Source/BootProperties.cpp b/Tactility/Source/BootProperties.cpp new file mode 100644 index 00000000..273dde0d --- /dev/null +++ b/Tactility/Source/BootProperties.cpp @@ -0,0 +1,48 @@ +#include "Tactility/BootProperties.h" +#include "Tactility/MountPoints.h" +#include "Tactility/file/PropertiesFile.h" +#include "Tactility/hal/sdcard/SdCardDevice.h" + +#include +#include + +#include +#include +#include +#include + +namespace tt { + +constexpr auto* TAG = "BootProperties"; +constexpr auto* PROPERTIES_FILE_FORMAT = "{}/boot.properties"; +constexpr auto* PROPERTIES_KEY_LAUNCHER_APP_ID = "launcherAppId"; +constexpr auto* PROPERTIES_KEY_AUTO_START_APP_ID = "autoStartAppId"; + +static std::string getPropertiesFilePath() { + const auto sdcards = hal::findDevices(hal::Device::Type::SdCard); + for (auto& sdcard : sdcards) { + std::string path = std::format(PROPERTIES_FILE_FORMAT, sdcard->getMountPath()); + if (file::isFile(path)) { + return path; + } + } + return std::format(PROPERTIES_FILE_FORMAT, file::MOUNT_POINT_DATA); +} + +bool loadBootProperties(BootProperties& properties) { + const std::string path = getPropertiesFilePath(); + if (!file::loadPropertiesFile(path, [&properties](auto& key, auto& value) { + if (key == PROPERTIES_KEY_AUTO_START_APP_ID) { + properties.autoStartAppId = value; + } else if (key == PROPERTIES_KEY_LAUNCHER_APP_ID) { + properties.launcherAppId = value; + } + })) { + TT_LOG_E(TAG, "Failed to load %s", path.c_str()); + return false; + } + + return !properties.launcherAppId.empty(); +} + +} diff --git a/Tactility/Source/MountPoints.cpp b/Tactility/Source/MountPoints.cpp new file mode 100644 index 00000000..1f5db3d4 --- /dev/null +++ b/Tactility/Source/MountPoints.cpp @@ -0,0 +1,55 @@ +#include "Tactility/MountPoints.h" +#include "Tactility/hal/Device.h" +#include "Tactility/hal/sdcard/SdCardDevice.h" + +#include + +#include +#include +#include + +namespace tt::file { + +std::vector getMountPoints() { + std::vector dir_entries; + dir_entries.clear(); + + // System partition + auto system_dirent = dirent{ + .d_ino = 0, + .d_type = TT_DT_DIR, + .d_name = { 0 } + }; + strcpy(system_dirent.d_name, SYSTEM_PARTITION_NAME); + dir_entries.push_back(system_dirent); + + // Data partition + auto data_dirent = dirent{ + .d_ino = 1, + .d_type = TT_DT_DIR, + .d_name = { 0 } + }; + strcpy(data_dirent.d_name, DATA_PARTITION_NAME); + dir_entries.push_back(data_dirent); + + // SD card partitions + auto sdcards = tt::hal::findDevices(hal::Device::Type::SdCard); + for (auto& sdcard : sdcards) { + auto state = sdcard->getState(); + if (state == hal::sdcard::SdCardDevice::State::Mounted) { + auto mount_name = sdcard->getMountPath().substr(1); + auto dir_entry = dirent { + .d_ino = 2, + .d_type = TT_DT_DIR, + .d_name = { 0 } + }; + assert(mount_name.length() < sizeof(dirent::d_name)); + strcpy(dir_entry.d_name, mount_name.c_str()); + dir_entries.push_back(dir_entry); + } + } + + return dir_entries; +} + +} diff --git a/Tactility/Source/Tactility.cpp b/Tactility/Source/Tactility.cpp index 5ee03dff..8cb64c69 100644 --- a/Tactility/Source/Tactility.cpp +++ b/Tactility/Source/Tactility.cpp @@ -1,11 +1,11 @@ #include "Tactility/Tactility.h" -#include "Tactility/app/ManifestRegistry.h" +#include "Tactility/app/AppRegistration.h" #include "Tactility/lvgl/LvglPrivate.h" #include "Tactility/service/ServiceManifest.h" #include -#include +#include #include namespace tt { @@ -54,7 +54,6 @@ namespace app { namespace serialconsole { extern const AppManifest manifest; } namespace settings { extern const AppManifest manifest; } namespace systeminfo { extern const AppManifest manifest; } - namespace textviewer { extern const AppManifest manifest; } namespace timedatesettings { extern const AppManifest manifest; } namespace timezone { extern const AppManifest manifest; } namespace usbsettings { extern const AppManifest manifest; } @@ -96,7 +95,6 @@ static void registerSystemApps() { addApp(app::settings::manifest); addApp(app::selectiondialog::manifest); addApp(app::systeminfo::manifest); - addApp(app::textviewer::manifest); addApp(app::timedatesettings::manifest); addApp(app::timezone::manifest); addApp(app::usbsettings::manifest); diff --git a/Tactility/Source/TactilityHeadless.cpp b/Tactility/Source/TactilityHeadless.cpp index 237b110c..97570836 100644 --- a/Tactility/Source/TactilityHeadless.cpp +++ b/Tactility/Source/TactilityHeadless.cpp @@ -3,7 +3,7 @@ #include "Tactility/hal/Hal_i.h" #include "Tactility/network/NtpPrivate.h" #include "Tactility/service/ServiceManifest.h" -#include "Tactility/service/ServiceRegistry.h" +#include "Tactility/service/ServiceRegistration.h" #include #include diff --git a/Tactility/Source/app/AppInstancePaths.cpp b/Tactility/Source/app/AppInstancePaths.cpp index 72ac5a70..26c073e2 100644 --- a/Tactility/Source/app/AppInstancePaths.cpp +++ b/Tactility/Source/app/AppInstancePaths.cpp @@ -1,6 +1,6 @@ #include "Tactility/app/AppInstancePaths.h" -#include +#include #define LVGL_PATH_PREFIX std::string("A:/") #ifdef ESP_PLATFORM @@ -12,38 +12,38 @@ namespace tt::app { std::string AppInstancePaths::getDataDirectory() const { - return PARTITION_PREFIX + DATA_PARTITION_NAME + "/app/" + manifest.id; + return PARTITION_PREFIX + file::DATA_PARTITION_NAME + "/app/" + manifest.id; } std::string AppInstancePaths::getDataDirectoryLvgl() const { - return LVGL_PATH_PREFIX + DATA_PARTITION_NAME + "/app/" + manifest.id; + return LVGL_PATH_PREFIX + file::DATA_PARTITION_NAME + "/app/" + manifest.id; } std::string AppInstancePaths::getDataPath(const std::string& childPath) const { assert(!childPath.starts_with('/')); - return PARTITION_PREFIX + DATA_PARTITION_NAME + "/app/" + manifest.id + '/' + childPath; + return PARTITION_PREFIX + file::DATA_PARTITION_NAME + "/app/" + manifest.id + '/' + childPath; } std::string AppInstancePaths::getDataPathLvgl(const std::string& childPath) const { assert(!childPath.starts_with('/')); - return LVGL_PATH_PREFIX + DATA_PARTITION_NAME + "/app/" + manifest.id + '/' + childPath; + return LVGL_PATH_PREFIX + file::DATA_PARTITION_NAME + "/app/" + manifest.id + '/' + childPath; } std::string AppInstancePaths::getSystemDirectory() const { - return PARTITION_PREFIX + SYSTEM_PARTITION_NAME + "/app/" + manifest.id; + return PARTITION_PREFIX + file::SYSTEM_PARTITION_NAME + "/app/" + manifest.id; } std::string AppInstancePaths::getSystemDirectoryLvgl() const { - return LVGL_PATH_PREFIX + SYSTEM_PARTITION_NAME + "/app/" + manifest.id; + return LVGL_PATH_PREFIX + file::SYSTEM_PARTITION_NAME + "/app/" + manifest.id; } std::string AppInstancePaths::getSystemPath(const std::string& childPath) const { assert(!childPath.starts_with('/')); - return PARTITION_PREFIX + SYSTEM_PARTITION_NAME + "/app/" + manifest.id + '/' + childPath; + return PARTITION_PREFIX + file::SYSTEM_PARTITION_NAME + "/app/" + manifest.id + '/' + childPath; } std::string AppInstancePaths::getSystemPathLvgl(const std::string& childPath) const { - return LVGL_PATH_PREFIX + SYSTEM_PARTITION_NAME + "/app/" + manifest.id + '/' + childPath; + return LVGL_PATH_PREFIX + file::SYSTEM_PARTITION_NAME + "/app/" + manifest.id + '/' + childPath; } } diff --git a/Tactility/Source/app/ManifestRegistry.cpp b/Tactility/Source/app/AppRegistration.cpp similarity index 94% rename from Tactility/Source/app/ManifestRegistry.cpp rename to Tactility/Source/app/AppRegistration.cpp index a708d79a..514dc04f 100644 --- a/Tactility/Source/app/ManifestRegistry.cpp +++ b/Tactility/Source/app/AppRegistration.cpp @@ -1,9 +1,10 @@ -#include "Tactility/app/ManifestRegistry.h" +#include "Tactility/app/AppRegistration.h" #include "Tactility/app/AppManifest.h" #include #include +#include #define TAG "app" diff --git a/Tactility/Source/app/ElfApp.cpp b/Tactility/Source/app/ElfApp.cpp index a868e6cd..3653d649 100644 --- a/Tactility/Source/app/ElfApp.cpp +++ b/Tactility/Source/app/ElfApp.cpp @@ -2,9 +2,9 @@ #include "Tactility/app/ElfApp.h" #include "Tactility/file/File.h" +#include "Tactility/file/FileLock.h" #include "Tactility/service/loader/Loader.h" -#include "Tactility/hal/sdcard/SdCardDevice.h" #include #include @@ -15,7 +15,7 @@ namespace tt::app { -#define TAG "elf_app" +constexpr auto* TAG = "ElfApp"; struct ElfManifest { /** The user-readable name of the app. Used in UI. */ @@ -33,6 +33,7 @@ struct ElfManifest { static size_t elfManifestSetCount = 0; static ElfManifest elfManifest; +static std::shared_ptr elfManifestLock = std::make_shared(); class ElfApp : public App { @@ -48,7 +49,7 @@ class ElfApp : public App { assert(elfFileData == nullptr); size_t size = 0; - hal::sdcard::withSdCardLock(filePath, [this, &size](){ + file::withLock(filePath, [this, &size]{ elfFileData = file::readBinary(filePath, size); }); @@ -56,25 +57,30 @@ class ElfApp : public App { return false; } - if (esp_elf_init(&elf) < 0) { + if (esp_elf_init(&elf) != ESP_OK) { TT_LOG_E(TAG, "Failed to initialize"); - shouldCleanupElf = true; + elfFileData = nullptr; return false; } - if (esp_elf_relocate(&elf, elfFileData.get()) < 0) { + if (esp_elf_relocate(&elf, elfFileData.get()) != ESP_OK) { TT_LOG_E(TAG, "Failed to load executable"); + esp_elf_deinit(&elf); + elfFileData = nullptr; return false; } int argc = 0; char* argv[] = {}; - if (esp_elf_request(&elf, 0, argc, argv) < 0) { + if (esp_elf_request(&elf, 0, argc, argv) != ESP_OK) { TT_LOG_W(TAG, "Executable returned error code"); + esp_elf_deinit(&elf); + elfFileData = nullptr; return false; } + shouldCleanupElf = true; return true; } @@ -95,10 +101,16 @@ public: explicit ElfApp(std::string filePath) : filePath(std::move(filePath)) {} void onCreate(AppContext& appContext) override { + // Because we use global variables, we have to ensure that we are not starting 2 apps in parallel + // We use a ScopedLock so we don't have to safeguard all branches + auto lock = elfManifestLock->asScopedLock(); + lock.lock(); + auto initial_count = elfManifestSetCount; if (startElf()) { if (elfManifestSetCount > initial_count) { manifest = std::make_unique(elfManifest); + lock.unlock(); if (manifest->createData != nullptr) { data = manifest->createData(); @@ -181,7 +193,7 @@ void registerElfApp(const std::string& filePath) { if (findAppById(filePath) == nullptr) { auto manifest = AppManifest { .id = getElfAppId(filePath), - .name = tt::string::removeFileExtension(tt::string::getLastPathSegment(filePath)), + .name = string::removeFileExtension(string::getLastPathSegment(filePath)), .type = Type::User, .location = Location::external(filePath) }; diff --git a/Tactility/Source/app/applist/AppList.cpp b/Tactility/Source/app/applist/AppList.cpp index 560b42a8..05c71c1a 100644 --- a/Tactility/Source/app/applist/AppList.cpp +++ b/Tactility/Source/app/applist/AppList.cpp @@ -1,4 +1,4 @@ -#include "Tactility/app/ManifestRegistry.h" +#include "Tactility/app/AppRegistration.h" #include "Tactility/service/loader/Loader.h" #include "Tactility/lvgl/Toolbar.h" diff --git a/Tactility/Source/app/boot/Boot.cpp b/Tactility/Source/app/boot/Boot.cpp index cb613c48..f5d46196 100644 --- a/Tactility/Source/app/boot/Boot.cpp +++ b/Tactility/Source/app/boot/Boot.cpp @@ -11,6 +11,8 @@ #include #include +#include +#include #ifdef ESP_PLATFORM #include "Tactility/app/crashdiagnostics/CrashDiagnostics.h" @@ -24,26 +26,25 @@ namespace tt::app::boot { -static std::shared_ptr getHalDisplay() { +static std::shared_ptr getHalDisplay() { return hal::findFirstDevice(hal::Device::Type::Display); } class BootApp : public App { -private: + Thread thread = Thread( + "boot", + 4096, + [] { return bootThreadCallback(); }, + getCpuAffinityConfiguration().system + ); - Thread thread = Thread("boot", 4096, [this]() { return bootThreadCallback(); }); - - int32_t bootThreadCallback() { - TickType_t start_time = kernel::getTicks(); - - kernel::publishSystemEvent(kernel::SystemEvent::BootSplash); - - auto hal_display = getHalDisplay(); + static void setupDisplay() { + const auto hal_display = getHalDisplay(); assert(hal_display != nullptr); if (hal_display->supportsBacklightDuty()) { uint8_t backlight_duty = 200; - app::display::getBacklightDuty(backlight_duty); + display::getBacklightDuty(backlight_duty); TT_LOG_I(TAG, "backlight %du", backlight_duty); hal_display->setBacklightDuty(backlight_duty); } else { @@ -52,27 +53,45 @@ private: if (hal_display->getGammaCurveCount() > 0) { uint8_t gamma_curve; - if (app::display::getGammaCurve(gamma_curve)) { + if (display::getGammaCurve(gamma_curve)) { hal_display->setGammaCurve(gamma_curve); TT_LOG_I(TAG, "gamma %du", gamma_curve); } } + } - if (hal::usb::isUsbBootMode()) { - TT_LOG_I(TAG, "Rebooting into mass storage device mode"); - hal::usb::resetUsbBootMode(); - hal::usb::startMassStorageWithSdmmc(); - } else { + static bool setupUsbBootMode() { + if (!hal::usb::isUsbBootMode()) { + return false; + } + + TT_LOG_I(TAG, "Rebooting into mass storage device mode"); + hal::usb::resetUsbBootMode(); + hal::usb::startMassStorageWithSdmmc(); + + return true; + } + + static void waitForMinimalSplashDuration(TickType_t startTime) { + const auto end_time = kernel::getTicks(); + const auto ticks_passed = end_time - startTime; + constexpr auto minimum_ticks = (CONFIG_TT_SPLASH_DURATION / portTICK_PERIOD_MS); + if (minimum_ticks > ticks_passed) { + kernel::delayTicks(minimum_ticks - ticks_passed); + } + } + + static int32_t bootThreadCallback() { + const auto start_time = kernel::getTicks(); + + kernel::publishSystemEvent(kernel::SystemEvent::BootSplash); + + setupDisplay(); + + if (!setupUsbBootMode()) { initFromBootApp(); - - TickType_t end_time = tt::kernel::getTicks(); - TickType_t ticks_passed = end_time - start_time; - TickType_t minimum_ticks = (CONFIG_TT_SPLASH_DURATION / portTICK_PERIOD_MS); - if (minimum_ticks > ticks_passed) { - kernel::delayTicks(minimum_ticks - ticks_passed); - } - - tt::service::loader::stopApp(); + waitForMinimalSplashDuration(start_time); + service::loader::stopApp(); startNextApp(); } @@ -81,16 +100,20 @@ private: static void startNextApp() { #ifdef ESP_PLATFORM - esp_reset_reason_t reason = esp_reset_reason(); - if (reason == ESP_RST_PANIC) { - app::crashdiagnostics::start(); + if (esp_reset_reason() == ESP_RST_PANIC) { + crashdiagnostics::start(); return; } #endif - auto* config = tt::getConfiguration(); - assert(!config->launcherAppId.empty()); - tt::service::loader::startApp(config->launcherAppId); + BootProperties boot_properties; + if (!loadBootProperties(boot_properties) || boot_properties.launcherAppId.empty()) { + TT_LOG_E(TAG, "Launcher not configured"); + stop(); + return; + } + + service::loader::startApp(boot_properties.launcherAppId); } public: @@ -99,9 +122,9 @@ public: auto* image = lv_image_create(parent); lv_obj_set_size(image, LV_PCT(100), LV_PCT(100)); - auto paths = app.getPaths(); + const auto paths = app.getPaths(); const char* logo = hal::usb::isUsbBootMode() ? "logo_usb.png" : "logo.png"; - auto logo_path = paths->getSystemPathLvgl(logo); + const auto logo_path = paths->getSystemPathLvgl(logo); TT_LOG_I(TAG, "%s", logo_path.c_str()); lv_image_set_src(image, logo_path.c_str()); diff --git a/Tactility/Source/app/filebrowser/FileBrowser.cpp b/Tactility/Source/app/filebrowser/FileBrowser.cpp index c39edcc2..e8ed7ebd 100644 --- a/Tactility/Source/app/filebrowser/FileBrowser.cpp +++ b/Tactility/Source/app/filebrowser/FileBrowser.cpp @@ -9,7 +9,7 @@ namespace tt::app::filebrowser { -#define TAG "filebrowser_app" +constexpr auto* TAG = "FileBrowser"; extern const AppManifest manifest; diff --git a/Tactility/Source/app/filebrowser/State.cpp b/Tactility/Source/app/filebrowser/State.cpp index e2dbe18d..352f274e 100644 --- a/Tactility/Source/app/filebrowser/State.cpp +++ b/Tactility/Source/app/filebrowser/State.cpp @@ -3,7 +3,7 @@ #include #include "Tactility/hal/sdcard/SdCardDevice.h" #include -#include +#include #include #include @@ -11,10 +11,10 @@ #include #include -#define TAG "filebrowser_app" - namespace tt::app::filebrowser { +constexpr auto* TAG = "FileBrowser"; + State::State() { if (kernel::getPlatform() == kernel::PlatformSimulator) { char cwd[PATH_MAX]; @@ -43,46 +43,20 @@ bool State::setEntriesForPath(const std::string& path) { 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. + * On PC, the root entry point ("/") is a folder. + * On ESP32, the root entry point contains the various mount points. */ - bool show_custom_root = (kernel::getPlatform() == kernel::PlatformEsp) && (path == "/"); - if (show_custom_root) { + bool get_mount_points = (kernel::getPlatform() == kernel::PlatformEsp) && (path == "/"); + if (get_mount_points) { TT_LOG_I(TAG, "Setting custom root"); - dir_entries.clear(); - dir_entries.push_back(dirent{ - .d_ino = 0, - .d_type = file::TT_DT_DIR, - .d_name = SYSTEM_PARTITION_NAME - }); - dir_entries.push_back(dirent{ - .d_ino = 1, - .d_type = file::TT_DT_DIR, - .d_name = DATA_PARTITION_NAME - }); - - auto sdcards = tt::hal::findDevices(hal::Device::Type::SdCard); - for (auto& sdcard : sdcards) { - auto state = sdcard->getState(); - if (state == hal::sdcard::SdCardDevice::State::Mounted) { - auto mount_name = sdcard->getMountPath().substr(1); - auto dir_entry = dirent { - .d_ino = 2, - .d_type = file::TT_DT_DIR, - .d_name = { 0 } - }; - assert(mount_name.length() < sizeof(dirent::d_name)); - strcpy(dir_entry.d_name, mount_name.c_str()); - dir_entries.push_back(dir_entry); - } - } - + dir_entries = file::getMountPoints(); current_path = path; selected_child_entry = ""; action = ActionNone; return true; } else { dir_entries.clear(); + // TODO: file Lock int count = file::scandir(path, dir_entries, &file::direntFilterDotEntries, file::direntSortAlphaAndType); if (count >= 0) { TT_LOG_I(TAG, "%s has %u entries", path.c_str(), count); diff --git a/Tactility/Source/app/filebrowser/SupportedFiles.cpp b/Tactility/Source/app/filebrowser/SupportedFiles.cpp index 3069ad08..953c9a86 100644 --- a/Tactility/Source/app/filebrowser/SupportedFiles.cpp +++ b/Tactility/Source/app/filebrowser/SupportedFiles.cpp @@ -3,7 +3,7 @@ namespace tt::app::filebrowser { -#define TAG "filebrowser_app" +constexpr auto* TAG = "FileBrowser"; bool isSupportedExecutableFile(const std::string& filename) { #ifdef ESP_PLATFORM diff --git a/Tactility/Source/app/filebrowser/View.cpp b/Tactility/Source/app/filebrowser/View.cpp index 5da95b85..1dc4f35e 100644 --- a/Tactility/Source/app/filebrowser/View.cpp +++ b/Tactility/Source/app/filebrowser/View.cpp @@ -4,13 +4,14 @@ #include "Tactility/app/alertdialog/AlertDialog.h" #include "Tactility/app/imageviewer/ImageViewer.h" #include "Tactility/app/inputdialog/InputDialog.h" -#include "Tactility/app/textviewer/TextViewer.h" +#include "Tactility/app/notes/Notes.h" #include "Tactility/app/ElfApp.h" #include "Tactility/lvgl/Toolbar.h" #include "Tactility/lvgl/LvglSync.h" #include -#include "Tactility/file/File.h" +#include +#include #include #include @@ -20,10 +21,10 @@ #include "Tactility/service/loader/Loader.h" #endif -#define TAG "filebrowser_app" - namespace tt::app::filebrowser { +constexpr auto* TAG = "FileBrowser"; + // region Callbacks static void dirEntryListScrollBeginCallback(lv_event_t* event) { @@ -95,10 +96,10 @@ void View::viewFile(const std::string& path, const std::string& filename) { imageviewer::start(processed_filepath); } else if (isSupportedTextFile(filename)) { if (kernel::getPlatform() == kernel::PlatformEsp) { - textviewer::start(processed_filepath); + notes::start(processed_filepath); } else { // Remove forward slash, because we need a relative path - textviewer::start(processed_filepath.substr(1)); + notes::start(processed_filepath.substr(1)); } } else { TT_LOG_W(TAG, "opening files of this type is not supported"); @@ -163,7 +164,6 @@ void View::onDirEntryLongPressed(int32_t index) { } } - void View::createDirEntryWidget(lv_obj_t* list, dirent& dir_entry) { tt_check(list); const char* symbol; diff --git a/Tactility/Source/app/fileselection/FileSelection.cpp b/Tactility/Source/app/fileselection/FileSelection.cpp index 0784d001..05534696 100644 --- a/Tactility/Source/app/fileselection/FileSelection.cpp +++ b/Tactility/Source/app/fileselection/FileSelection.cpp @@ -10,7 +10,7 @@ namespace tt::app::fileselection { -#define TAG "fileselection_app" +constexpr auto* TAG = "FileSelection"; extern const AppManifest manifest; diff --git a/Tactility/Source/app/fileselection/State.cpp b/Tactility/Source/app/fileselection/State.cpp index c648b6d9..b0e26fcc 100644 --- a/Tactility/Source/app/fileselection/State.cpp +++ b/Tactility/Source/app/fileselection/State.cpp @@ -3,7 +3,7 @@ #include #include "Tactility/hal/sdcard/SdCardDevice.h" #include -#include +#include #include #include @@ -11,10 +11,10 @@ #include #include -#define TAG "fileselection_app" - namespace tt::app::fileselection { +constexpr auto* TAG = "FileSelection"; + State::State() { if (kernel::getPlatform() == kernel::PlatformSimulator) { char cwd[PATH_MAX]; @@ -49,34 +49,7 @@ bool State::setEntriesForPath(const std::string& path) { bool show_custom_root = (kernel::getPlatform() == kernel::PlatformEsp) && (path == "/"); if (show_custom_root) { TT_LOG_I(TAG, "Setting custom root"); - dir_entries.clear(); - dir_entries.push_back(dirent{ - .d_ino = 0, - .d_type = file::TT_DT_DIR, - .d_name = SYSTEM_PARTITION_NAME - }); - dir_entries.push_back(dirent{ - .d_ino = 1, - .d_type = file::TT_DT_DIR, - .d_name = DATA_PARTITION_NAME - }); - - auto sdcards = tt::hal::findDevices(hal::Device::Type::SdCard); - for (auto& sdcard : sdcards) { - auto state = sdcard->getState(); - if (state == hal::sdcard::SdCardDevice::State::Mounted) { - auto mount_name = sdcard->getMountPath().substr(1); - auto dir_entry = dirent { - .d_ino = 2, - .d_type = file::TT_DT_DIR, - .d_name = { 0 } - }; - assert(mount_name.length() < sizeof(dirent::d_name)); - strcpy(dir_entry.d_name, mount_name.c_str()); - dir_entries.push_back(dir_entry); - } - } - + dir_entries = file::getMountPoints(); current_path = path; selected_child_entry = ""; return true; diff --git a/Tactility/Source/app/fileselection/View.cpp b/Tactility/Source/app/fileselection/View.cpp index 0d5043f2..dafa487a 100644 --- a/Tactility/Source/app/fileselection/View.cpp +++ b/Tactility/Source/app/fileselection/View.cpp @@ -15,10 +15,10 @@ #include "Tactility/service/loader/Loader.h" #endif -#define TAG "fileselection_app" - namespace tt::app::fileselection { +constexpr auto* TAG = "FileSelection"; + // region Callbacks static void onDirEntryPressedCallback(lv_event_t* event) { diff --git a/Tactility/Source/app/gpio/Gpio.cpp b/Tactility/Source/app/gpio/Gpio.cpp index 9524642f..88abddd4 100644 --- a/Tactility/Source/app/gpio/Gpio.cpp +++ b/Tactility/Source/app/gpio/Gpio.cpp @@ -148,7 +148,7 @@ void GpioApp::onShow(AppContext& app, lv_obj_t* parent) { // Add the GPIO number after the last item on a row auto* postfix = lv_label_create(row_wrapper); lv_label_set_text_fmt(postfix, "%02d", i); - lv_obj_set_pos(postfix, (int32_t)((column+1) * x_spacing + offset_from_left_label), 0); + lv_obj_set_pos(postfix, (column + 1) * x_spacing + offset_from_left_label, 0); // Add a new row wrapper underneath the last one auto* new_row_wrapper = createGpioRowWrapper(wrapper); diff --git a/Tactility/Source/app/i2cscanner/I2cScanner.cpp b/Tactility/Source/app/i2cscanner/I2cScanner.cpp index 67250b78..8c4c977e 100644 --- a/Tactility/Source/app/i2cscanner/I2cScanner.cpp +++ b/Tactility/Source/app/i2cscanner/I2cScanner.cpp @@ -24,8 +24,6 @@ extern const AppManifest manifest; class I2cScannerApp : public App { -private: - // Core Mutex mutex = Mutex(Mutex::Type::Recursive); std::unique_ptr scanTimer = nullptr; @@ -286,7 +284,7 @@ void I2cScannerApp::startScanning() { lv_obj_clean(scanListWidget); scanState = ScanStateScanning; - scanTimer = std::make_unique(Timer::Type::Once, [](){ + scanTimer = std::make_unique(Timer::Type::Once, []{ onScanTimerCallback(); }); scanTimer->start(10); diff --git a/Tactility/Source/app/launcher/Launcher.cpp b/Tactility/Source/app/launcher/Launcher.cpp index a61a89ae..af2e8ff5 100644 --- a/Tactility/Source/app/launcher/Launcher.cpp +++ b/Tactility/Source/app/launcher/Launcher.cpp @@ -1,11 +1,11 @@ #include "Tactility/app/AppContext.h" -#include "Tactility/app/ManifestRegistry.h" +#include "Tactility/app/AppRegistration.h" #include "Tactility/service/loader/Loader.h" -#include #include #include +#include #define TAG "launcher" @@ -54,10 +54,10 @@ static lv_obj_t* createAppButton(lv_obj_t* parent, const char* title, const char class LauncherApp : public App { void onCreate(TT_UNUSED AppContext& app) override { - auto* config = getConfiguration(); - if (!config->autoStartAppId.empty()) { - TT_LOG_I(TAG, "auto-starting %s", config->autoStartAppId.c_str()); - service::loader::startApp(config->autoStartAppId); + BootProperties boot_properties; + if (loadBootProperties(boot_properties) && !boot_properties.autoStartAppId.empty()) { + TT_LOG_I(TAG, "Starting %s", boot_properties.autoStartAppId.c_str()); + service::loader::startApp(boot_properties.autoStartAppId); } } diff --git a/Tactility/Source/app/notes/Notes.cpp b/Tactility/Source/app/notes/Notes.cpp index 985bcd94..e26e6bf6 100644 --- a/Tactility/Source/app/notes/Notes.cpp +++ b/Tactility/Source/app/notes/Notes.cpp @@ -1,17 +1,19 @@ -#include -#include -#include -#include -#include -#include +#include "Tactility/app/AppManifest.h" +#include "Tactility/app/fileselection/FileSelection.h" +#include "Tactility/file/FileLock.h" +#include "Tactility/lvgl/Toolbar.h" +#include "Tactility/lvgl/LvglSync.h" +#include "Tactility/service/loader/Loader.h" +#include "Tactility/Assets.h" -#include -#include -#include +#include + +#include namespace tt::app::notes { -constexpr const char* TAG = "Notes"; +constexpr auto* TAG = "Notes"; +constexpr auto* NOTES_FILE_ARGUMENT = "file"; class NotesApp : public App { @@ -81,7 +83,7 @@ class NotesApp : public App { void openFile(const std::string& path) { // We might be reading from the SD card, which could share a SPI bus with other devices (display) - hal::sdcard::withSdCardLock(path, [this, path]() { + file::withLock(path, [this, path] { auto data = file::readString(path); if (data != nullptr) { auto lock = lvgl::getSyncLock()->asScopedLock(); @@ -96,7 +98,7 @@ class NotesApp : public App { bool saveFile(const std::string& path) { // We might be writing to SD card, which could share a SPI bus with other devices (display) - return hal::sdcard::withSdCardLock(path, [this, path]() { + return file::withLock(path, [this, path] { if (file::writeString(path, saveBuffer.c_str())) { TT_LOG_I(TAG, "Saved to %s", path.c_str()); filePath = path; @@ -109,11 +111,20 @@ class NotesApp : public App { #pragma endregion Open_Events_Functions + void onCreate(AppContext& appContext) override { + auto parameters = appContext.getParameters(); + std::string file_path; + if (parameters != nullptr && parameters->optString(NOTES_FILE_ARGUMENT, file_path)) { + if (!file_path.empty()) { + filePath = file_path; + } + } + } void onShow(AppContext& context, lv_obj_t* parent) override { lv_obj_remove_flag(parent, LV_OBJ_FLAG_SCROLLABLE); lv_obj_set_flex_flow(parent, LV_FLEX_FLOW_COLUMN); - lv_obj_t* toolbar = tt::lvgl::toolbar_create(parent, context); + lv_obj_t* toolbar = lvgl::toolbar_create(parent, context); lv_obj_align(toolbar, LV_ALIGN_TOP_MID, 0, 0); uiDropDownMenu = lv_dropdown_create(toolbar); @@ -168,9 +179,8 @@ class NotesApp : public App { lv_label_set_text(uiCurrentFileName, "Untitled"); lv_obj_align(uiCurrentFileName, LV_ALIGN_CENTER, 0, 0); - //TODO: Move this to SD Card? - if (!file::findOrCreateDirectory(context.getPaths()->getDataDirectory(), 0777)) { - TT_LOG_E(TAG, "Failed to find or create path %s", context.getPaths()->getDataDirectory().c_str()); + if (!filePath.empty()) { + openFile(filePath); } } @@ -202,4 +212,9 @@ extern const AppManifest manifest = { .createApp = create }; +void start(const std::string& filePath) { + auto parameters = std::make_shared(); + parameters->putString(NOTES_FILE_ARGUMENT, filePath); + service::loader::startApp(manifest.id, parameters); +} } // namespace tt::app::notes \ No newline at end of file diff --git a/Tactility/Source/app/settings/Settings.cpp b/Tactility/Source/app/settings/Settings.cpp index 9171b753..b276b4c8 100644 --- a/Tactility/Source/app/settings/Settings.cpp +++ b/Tactility/Source/app/settings/Settings.cpp @@ -1,4 +1,4 @@ -#include "Tactility/app/ManifestRegistry.h" +#include "Tactility/app/AppRegistration.h" #include "Tactility/lvgl/Toolbar.h" #include "Tactility/service/loader/Loader.h" diff --git a/Tactility/Source/app/textviewer/TextViewer.cpp b/Tactility/Source/app/textviewer/TextViewer.cpp deleted file mode 100644 index 7db5d20c..00000000 --- a/Tactility/Source/app/textviewer/TextViewer.cpp +++ /dev/null @@ -1,61 +0,0 @@ -#include "Tactility/lvgl/LabelUtils.h" -#include "Tactility/lvgl/Style.h" -#include "Tactility/lvgl/Toolbar.h" -#include "Tactility/service/loader/Loader.h" - -#include - -#include - -#define TAG "text_viewer" -#define TEXT_VIEWER_FILE_ARGUMENT "file" - -namespace tt::app::textviewer { - -class TextViewerApp : public App { - - void onShow(AppContext& app, lv_obj_t* parent) override { - lv_obj_set_flex_flow(parent, LV_FLEX_FLOW_COLUMN); - lvgl::toolbar_create(parent, app); - - auto* wrapper = lv_obj_create(parent); - lv_obj_set_width(wrapper, LV_PCT(100)); - lv_obj_set_flex_grow(wrapper, 1); - lv_obj_set_flex_flow(wrapper, LV_FLEX_FLOW_COLUMN); - lv_obj_set_style_pad_all(wrapper, 0, 0); - lvgl::obj_set_style_bg_invisible(wrapper); - - auto* label = lv_label_create(wrapper); - lv_obj_align(label, LV_ALIGN_CENTER, 0, 0); - auto parameters = app.getParameters(); - tt_check(parameters != nullptr, "Parameters missing"); - bool success = false; - std::string file_argument; - if (parameters->optString(TEXT_VIEWER_FILE_ARGUMENT, file_argument)) { - TT_LOG_I(TAG, "Opening %s", file_argument.c_str()); - if (lvgl::label_set_text_file(label, file_argument.c_str())) { - success = true; - } - } - - if (!success) { - lv_label_set_text_fmt(label, "Failed to load %s", file_argument.c_str()); - } - } -}; - -extern const AppManifest manifest = { - .id = "TextViewer", - .name = "Text Viewer", - .type = Type::Hidden, - .createApp = create -}; - -void start(const std::string& file) { - auto parameters = std::make_shared(); - parameters->putString(TEXT_VIEWER_FILE_ARGUMENT, file); - service::loader::startApp(manifest.id, parameters); -} - - -} // namespace diff --git a/Tactility/Source/app/timezone/TimeZone.cpp b/Tactility/Source/app/timezone/TimeZone.cpp index c8372060..bbf54d0b 100644 --- a/Tactility/Source/app/timezone/TimeZone.cpp +++ b/Tactility/Source/app/timezone/TimeZone.cpp @@ -5,7 +5,7 @@ #include "Tactility/lvgl/LvglSync.h" #include "Tactility/service/loader/Loader.h" -#include +#include #include #include @@ -14,7 +14,7 @@ namespace tt::app::timezone { -#define TAG "timezone_select" +constexpr auto* TAG = "TimeZone"; #define RESULT_BUNDLE_CODE_INDEX "code" #define RESULT_BUNDLE_NAME_INDEX "name" @@ -62,8 +62,7 @@ void setResultCode(Bundle& bundle, const std::string& code) { // endregion - -class TimeZoneApp : public App { +class TimeZoneApp final : public App { Mutex mutex; std::vector entries; @@ -123,7 +122,7 @@ class TimeZoneApp : public App { } void readTimeZones(std::string filter) { - auto path = std::string(MOUNT_POINT_SYSTEM) + "/timezones.csv"; + auto path = std::string(file::MOUNT_POINT_SYSTEM) + "/timezones.csv"; auto* file = fopen(path.c_str(), "rb"); if (file == nullptr) { TT_LOG_E(TAG, "Failed to open %s", path.c_str()); @@ -136,7 +135,7 @@ class TimeZoneApp : public App { std::vector new_entries; while (fgets(line, 96, file)) { if (parseEntry(line, name, code)) { - if (tt::string::lowercase(name).find(filter) != std::string::npos) { + if (string::lowercase(name).find(filter) != std::string::npos) { count++; new_entries.push_back({.name = name, .code = code}); @@ -165,7 +164,7 @@ class TimeZoneApp : public App { void updateList() { if (lvgl::lock(100 / portTICK_PERIOD_MS)) { - std::string filter = tt::string::lowercase(std::string(lv_textarea_get_text(filterTextareaWidget))); + std::string filter = string::lowercase(std::string(lv_textarea_get_text(filterTextareaWidget))); readTimeZones(filter); lvgl::unlock(); } else { @@ -227,7 +226,7 @@ public: } void onCreate(AppContext& app) override { - updateTimer = std::make_unique(Timer::Type::Once, []() { updateTimerCallback(); }); + updateTimer = std::make_unique(Timer::Type::Once, [] { updateTimerCallback(); }); } }; diff --git a/Tactility/Source/app/wifiapsettings/WifiApSettings.cpp b/Tactility/Source/app/wifiapsettings/WifiApSettings.cpp index 4b18cd01..40f7f476 100644 --- a/Tactility/Source/app/wifiapsettings/WifiApSettings.cpp +++ b/Tactility/Source/app/wifiapsettings/WifiApSettings.cpp @@ -44,10 +44,10 @@ static void onToggleAutoConnect(lv_event_t* event) { bool is_on = lv_obj_has_state(enable_switch, LV_STATE_CHECKED); std::string ssid = parameters->getString("ssid"); - service::wifi::settings::WifiApSettings settings {}; - if (service::wifi::settings::load(ssid.c_str(), &settings)) { - settings.auto_connect = is_on; - if (!service::wifi::settings::save(&settings)) { + service::wifi::settings::WifiApSettings settings; + if (service::wifi::settings::load(ssid.c_str(), settings)) { + settings.autoConnect = is_on; + if (!service::wifi::settings::save(settings)) { TT_LOG_E(TAG, "Failed to save settings"); } } else { @@ -98,9 +98,9 @@ class WifiApSettings : public App { lv_obj_align(forget_button_label, LV_ALIGN_CENTER, 0, 0); lv_label_set_text(forget_button_label, "Forget"); - service::wifi::settings::WifiApSettings settings {}; - if (service::wifi::settings::load(ssid.c_str(), &settings)) { - if (settings.auto_connect) { + service::wifi::settings::WifiApSettings settings; + if (service::wifi::settings::load(ssid.c_str(), settings)) { + if (settings.autoConnect) { lv_obj_add_state(auto_connect_switch, LV_STATE_CHECKED); } else { lv_obj_remove_state(auto_connect_switch, LV_STATE_CHECKED); diff --git a/Tactility/Source/app/wificonnect/State.cpp b/Tactility/Source/app/wificonnect/State.cpp index 9fd13230..c32da4b0 100644 --- a/Tactility/Source/app/wificonnect/State.cpp +++ b/Tactility/Source/app/wificonnect/State.cpp @@ -17,9 +17,9 @@ bool State::hasConnectionError() const { return result; } -void State::setApSettings(const service::wifi::settings::WifiApSettings* newSettings) { +void State::setApSettings(const service::wifi::settings::WifiApSettings& newSettings) { lock.lock(); - memcpy(&this->apSettings, newSettings, sizeof(service::wifi::settings::WifiApSettings)); + this->apSettings = newSettings; lock.unlock(); } diff --git a/Tactility/Source/app/wificonnect/View.cpp b/Tactility/Source/app/wificonnect/View.cpp index 21f08976..8d977806 100644 --- a/Tactility/Source/app/wificonnect/View.cpp +++ b/Tactility/Source/app/wificonnect/View.cpp @@ -5,10 +5,11 @@ #include "Tactility/lvgl/Spinner.h" #include -#include #include #include +#include +#include namespace tt::app::wificonnect { @@ -50,14 +51,14 @@ static void onConnect(TT_UNUSED lv_event_t* event) { view.setLoading(true); service::wifi::settings::WifiApSettings settings; - strcpy((char*)settings.password, password); - strcpy((char*)settings.ssid, ssid); + settings.password = password; + settings.ssid = ssid; settings.channel = 0; - settings.auto_connect = TT_WIFI_AUTO_CONNECT; // No UI yet, so use global setting:w + settings.autoConnect = TT_WIFI_AUTO_CONNECT; // No UI yet, so use global setting:w auto* bindings = &wifi->getBindings(); bindings->onConnectSsid( - &settings, + settings, store, bindings->onConnectSsidContext ); diff --git a/Tactility/Source/app/wificonnect/WifiConnect.cpp b/Tactility/Source/app/wificonnect/WifiConnect.cpp index ff17d3bc..459714ed 100644 --- a/Tactility/Source/app/wificonnect/WifiConnect.cpp +++ b/Tactility/Source/app/wificonnect/WifiConnect.cpp @@ -6,7 +6,6 @@ #include "Tactility/lvgl/LvglSync.h" namespace tt::app::wificonnect { - #define TAG "wifi_connect" #define WIFI_CONNECT_PARAM_SSID "ssid" // String #define WIFI_CONNECT_PARAM_PASSWORD "password" // String @@ -37,7 +36,7 @@ static void eventCallback(const void* message, void* context) { wifi->requestViewUpdate(); } -static void onConnect(const service::wifi::settings::WifiApSettings* ap_settings, bool remember, TT_UNUSED void* parameter) { +static void onConnect(const service::wifi::settings::WifiApSettings& ap_settings, bool remember, TT_UNUSED void* parameter) { auto* wifi = static_cast(parameter); wifi->getState().setApSettings(ap_settings); wifi->getState().setConnecting(true); diff --git a/Tactility/Source/app/wifimanage/View.cpp b/Tactility/Source/app/wifimanage/View.cpp index 8b7b2497..2bb355c0 100644 --- a/Tactility/Source/app/wifimanage/View.cpp +++ b/Tactility/Source/app/wifimanage/View.cpp @@ -11,6 +11,7 @@ #include #include #include +#include namespace tt::app::wifimanage { diff --git a/Tactility/Source/app/wifimanage/WifiManage.cpp b/Tactility/Source/app/wifimanage/WifiManage.cpp index 5f914867..827250a4 100644 --- a/Tactility/Source/app/wifimanage/WifiManage.cpp +++ b/Tactility/Source/app/wifimanage/WifiManage.cpp @@ -16,9 +16,9 @@ extern const AppManifest manifest; static void onConnect(const char* ssid) { service::wifi::settings::WifiApSettings settings; - if (service::wifi::settings::load(ssid, &settings)) { + if (service::wifi::settings::load(ssid, settings)) { TT_LOG_I(TAG, "Connecting with known credentials"); - service::wifi::connect(&settings, false); + service::wifi::connect(settings, false); } else { TT_LOG_I(TAG, "Starting connection dialog"); wificonnect::start(ssid); diff --git a/Tactility/Source/file/FileLock.cpp b/Tactility/Source/file/FileLock.cpp new file mode 100644 index 00000000..6a01bdf3 --- /dev/null +++ b/Tactility/Source/file/FileLock.cpp @@ -0,0 +1,24 @@ +#include "Tactility/file/FileLock.h" + +#include +#include + +namespace tt::file { + +class NoLock : public Lock { + bool lock(TickType_t timeout) const override { return true; } + bool unlock() const override { return true; } +}; + +static std::shared_ptr noLock = std::make_shared(); + +std::shared_ptr getLock(const std::string& path) { + auto sdcard_lock = hal::sdcard::findSdCardLock(path); + if (sdcard_lock != nullptr) { + return sdcard_lock; + } else { + return noLock; + } +} + +} diff --git a/TactilityCore/Source/file/ObjectFileReader.cpp b/Tactility/Source/file/ObjectFileReader.cpp similarity index 97% rename from TactilityCore/Source/file/ObjectFileReader.cpp rename to Tactility/Source/file/ObjectFileReader.cpp index 94012901..12691264 100644 --- a/TactilityCore/Source/file/ObjectFileReader.cpp +++ b/Tactility/Source/file/ObjectFileReader.cpp @@ -6,7 +6,7 @@ namespace tt::file { -constexpr const char* TAG = "ObjectFileReader"; +constexpr auto* TAG = "ObjectFileReader"; bool ObjectFileReader::open() { auto opening_file = std::unique_ptr(fopen(filePath.c_str(), "r")); diff --git a/TactilityCore/Source/file/ObjectFileWriter.cpp b/Tactility/Source/file/ObjectFileWriter.cpp similarity index 97% rename from TactilityCore/Source/file/ObjectFileWriter.cpp rename to Tactility/Source/file/ObjectFileWriter.cpp index 293ff350..8bc9fbeb 100644 --- a/TactilityCore/Source/file/ObjectFileWriter.cpp +++ b/Tactility/Source/file/ObjectFileWriter.cpp @@ -1,4 +1,4 @@ -#include "Tactility/file/ObjectFile.h" +#include "../../Include/Tactility/file/ObjectFile.h" #include "Tactility/file/ObjectFilePrivate.h" #include @@ -7,7 +7,7 @@ namespace tt::file { -constexpr const char* TAG = "ObjectFileWriter"; +constexpr auto* TAG = "ObjectFileWriter"; bool ObjectFileWriter::open() { bool edit_existing = append && access(filePath.c_str(), F_OK) == 0; diff --git a/Tactility/Source/file/PropertiesFile.cpp b/Tactility/Source/file/PropertiesFile.cpp new file mode 100644 index 00000000..283fa903 --- /dev/null +++ b/Tactility/Source/file/PropertiesFile.cpp @@ -0,0 +1,74 @@ +#include "Tactility/file/PropertiesFile.h" + +#include +#include +#include + +namespace tt::file { + +static auto TAG = "PropertiesFile"; + +bool getKeyValuePair(const std::string& input, std::string& key, std::string& value) { + auto index = input.find('='); + if (index == std::string::npos) { + return false; + } + key = input.substr(0, index); + value = input.substr(index + 1); + return true; +} + +bool loadPropertiesFile(const std::string& filePath, std::function callback) { + return file::withLock(filePath, [&filePath, &callback] { + TT_LOG_I(TAG, "Reading properties file %s", filePath.c_str()); + const auto input = readString(filePath); + if (input == nullptr) { + TT_LOG_E(TAG, "Failed to read file contents of %s", filePath.c_str()); + return false; + } + + const auto* input_start = reinterpret_cast(input.get()); + const std::string input_string = input_start; + + uint16_t line_count = 0; + string::split(input_string, "\n", [&line_count, &filePath, &callback](auto token) { + line_count++; + std::string key, value; + auto trimmed_token = string::trim(token, " \t"); + if (!trimmed_token.starts_with("#")) { + if (getKeyValuePair(token, key, value)) { + std::string trimmed_key = string::trim(key, " \t"); + std::string trimmed_value = string::trim(value, " \t"); + callback(trimmed_key, trimmed_value); + } else { TT_LOG_E(TAG, "Failed to parse line %d of %s", line_count, filePath.c_str()); } + } + }); + + return true; + }); +} + +bool loadPropertiesFile(const std::string& filePath, std::map& outProperties) { + return loadPropertiesFile(filePath, [&outProperties](const std::string& key, const std::string& value) { + outProperties[key] = value; + }); +} + +bool savePropertiesFile(const std::string& filePath, const std::map& properties) { + return file::withLock(filePath, [filePath, &properties] { + TT_LOG_I(TAG, "Saving properties file %s", filePath.c_str()); + + FILE* file = fopen(filePath.c_str(), "w"); + if (file == nullptr) { + TT_LOG_E(TAG, "Failed to open %s", filePath.c_str()); + return false; + } + + for (const auto& [key, value]: properties) { fprintf(file, "%s=%s\n", key.c_str(), value.c_str()); } + + fclose(file); + return true; + }); +} + +} diff --git a/Tactility/Source/hal/gps/GpsConfiguration.cpp b/Tactility/Source/hal/gps/GpsConfiguration.cpp index a0a5ec8c..1f355dd0 100644 --- a/Tactility/Source/hal/gps/GpsConfiguration.cpp +++ b/Tactility/Source/hal/gps/GpsConfiguration.cpp @@ -1,8 +1,8 @@ #include "Tactility/hal/gps/GpsConfiguration.h" #include "Tactility/service/gps/GpsService.h" +#include "Tactility/file/ObjectFile.h" #include -#include namespace tt::hal::gps { diff --git a/Tactility/Source/hal/sdcard/SdCard.cpp b/Tactility/Source/hal/sdcard/SdCard.cpp index 9cce5391..c5e16c9c 100644 --- a/Tactility/Source/hal/sdcard/SdCard.cpp +++ b/Tactility/Source/hal/sdcard/SdCard.cpp @@ -14,4 +14,13 @@ std::shared_ptr _Nullable find(const std::string& path) { return nullptr; } +std::shared_ptr findSdCardLock(const std::string& path) { + auto sdcard = find(path); + if (sdcard != nullptr) { + return sdcard->getLock(); + } + + return nullptr; +} + } diff --git a/Tactility/Source/hal/sdcard/SpiSdCardDevice.cpp b/Tactility/Source/hal/sdcard/SpiSdCardDevice.cpp index 68dbd5df..1ef54b3b 100644 --- a/Tactility/Source/hal/sdcard/SpiSdCardDevice.cpp +++ b/Tactility/Source/hal/sdcard/SpiSdCardDevice.cpp @@ -8,10 +8,10 @@ #include #include -#define TAG "spi_sdcard" - namespace tt::hal::sdcard { +constexpr auto* TAG = "SpiSdCardDevice"; + /** * Before we can initialize the sdcard's SPI communications, we have to set all * other SPI pins on the board high. @@ -78,7 +78,7 @@ bool SpiSdCardDevice::mountInternal(const std::string& newMountPath) { esp_err_t result = esp_vfs_fat_sdspi_mount(newMountPath.c_str(), &host, &slot_config, &mount_config, &card); - if (result != ESP_OK) { + if (result != ESP_OK || card == nullptr) { if (result == ESP_FAIL) { TT_LOG_E(TAG, "Mounting failed. Ensure the card is formatted with FAT."); } else { @@ -99,6 +99,7 @@ bool SpiSdCardDevice::mount(const std::string& newMountPath) { } if (mountInternal(newMountPath)) { + TT_LOG_I(TAG, "Mounted at %s", newMountPath.c_str()); sdmmc_card_print_info(stdout, card); return true; } else { @@ -113,18 +114,19 @@ bool SpiSdCardDevice::unmount() { return false; } - if (esp_vfs_fat_sdcard_unmount(mountPath.c_str(), card) == ESP_OK) { - mountPath = ""; - card = nullptr; - return true; - } else { + if (esp_vfs_fat_sdcard_unmount(mountPath.c_str(), card) != ESP_OK) { TT_LOG_E(TAG, "Unmount failed for %s", mountPath.c_str()); return false; } + + TT_LOG_I(TAG, "Unmounted %s", mountPath.c_str()); + mountPath = ""; + card = nullptr; + return true; } // TODO: Refactor to "bool getStatus(Status* status)" method so that it can fail when the lvgl lock fails -SdCardDevice::State SpiSdCardDevice::getState() const { +SdCardDevice::State SpiSdCardDevice::getState(TickType_t timeout) const { if (card == nullptr) { return State::Unmounted; } @@ -134,20 +136,17 @@ SdCardDevice::State SpiSdCardDevice::getState() const { * 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. */ - auto lock = getLock().asScopedLock(); - bool locked = lock.lock(50); // TODO: Refactor to a more reliable locking mechanism + auto lock = getLock()->asScopedLock(); + bool locked = lock.lock(timeout); if (!locked) { - TT_LOG_E(TAG, LOG_MESSAGE_MUTEX_LOCK_FAILED_FMT, "LVGL"); - return State::Unknown; + return State::Timeout; } - bool result = sdmmc_get_status(card) == ESP_OK; - - if (result) { - return State::Mounted; - } else { + if (sdmmc_get_status(card) != ESP_OK) { return State::Error; } + + return State::Mounted; } } diff --git a/Tactility/Source/lvgl/EspLvglPort.cpp b/Tactility/Source/lvgl/EspLvglPort.cpp index f7f0f945..f4ce55e6 100644 --- a/Tactility/Source/lvgl/EspLvglPort.cpp +++ b/Tactility/Source/lvgl/EspLvglPort.cpp @@ -2,23 +2,23 @@ #include #include +#include #include // LVGL // The minimum task stack seems to be about 3500, but that crashes the wifi app in some scenarios // At 8192, it sometimes crashes when wifi-auto enables and is busy connecting and then you open WifiManage #define TDECK_LVGL_TASK_STACK_DEPTH 9216 -#define TAG "lvgl" +auto constexpr TAG = "lvgl"; namespace tt::lvgl { bool initEspLvglPort() { TT_LOG_D(TAG, "Port init"); - static lv_disp_t* display = nullptr; const lvgl_port_cfg_t lvgl_cfg = { .task_priority = static_cast(Thread::Priority::Critical), .task_stack = TDECK_LVGL_TASK_STACK_DEPTH, - .task_affinity = 1, // -1 = disabled, 0 = core 1, 1 = core 2 + .task_affinity = getCpuAffinityConfiguration().graphics, .task_max_sleep_ms = 500, .timer_period_ms = 5 }; @@ -28,7 +28,7 @@ bool initEspLvglPort() { return false; } - tt::lvgl::syncSet(&lvgl_port_lock, &lvgl_port_unlock); + syncSet(&lvgl_port_lock, &lvgl_port_unlock); return true; } diff --git a/Tactility/Source/lvgl/LabelUtils.cpp b/Tactility/Source/lvgl/LabelUtils.cpp index 90d6a4e7..0a9965f1 100644 --- a/Tactility/Source/lvgl/LabelUtils.cpp +++ b/Tactility/Source/lvgl/LabelUtils.cpp @@ -1,13 +1,13 @@ -#include -#include -#include +#include "Tactility/lvgl/LabelUtils.h" +#include "Tactility/file/File.h" +#include "Tactility/file/FileLock.h" namespace tt::lvgl { -#define TAG "tt_lv_label" +constexpr auto* TAG = "LabelUtils"; bool label_set_text_file(lv_obj_t* label, const char* filepath) { - auto text = hal::sdcard::withSdCardLock>(std::string(filepath), [filepath]() { + auto text = file::withLock>(std::string(filepath), [filepath] { return file::readString(filepath); }); diff --git a/Tactility/Source/lvgl/Lvgl.cpp b/Tactility/Source/lvgl/Lvgl.cpp index 4bdf8f22..f6b73d41 100644 --- a/Tactility/Source/lvgl/Lvgl.cpp +++ b/Tactility/Source/lvgl/Lvgl.cpp @@ -13,10 +13,9 @@ #endif #include -#include #include #include -#include +#include namespace tt::lvgl { diff --git a/Tactility/Source/service/ServiceInstancePaths.cpp b/Tactility/Source/service/ServiceInstancePaths.cpp index bc7097d0..c60ff123 100644 --- a/Tactility/Source/service/ServiceInstancePaths.cpp +++ b/Tactility/Source/service/ServiceInstancePaths.cpp @@ -1,6 +1,6 @@ #include "Tactility/service/ServiceInstancePaths.h" -#include "Tactility/Partitions.h" +#include "Tactility/MountPoints.h" #define LVGL_PATH_PREFIX std::string("A:/") @@ -13,38 +13,38 @@ namespace tt::service { std::string ServiceInstancePaths::getDataDirectory() const { - return PARTITION_PREFIX + DATA_PARTITION_NAME + "/service/" + manifest->id; + return PARTITION_PREFIX + file::DATA_PARTITION_NAME + "/service/" + manifest->id; } std::string ServiceInstancePaths::getDataDirectoryLvgl() const { - return LVGL_PATH_PREFIX + DATA_PARTITION_NAME + "/service/" + manifest->id; + return LVGL_PATH_PREFIX + file::DATA_PARTITION_NAME + "/service/" + manifest->id; } std::string ServiceInstancePaths::getDataPath(const std::string& childPath) const { assert(!childPath.starts_with('/')); - return PARTITION_PREFIX + DATA_PARTITION_NAME + "/service/" + manifest->id + '/' + childPath; + return PARTITION_PREFIX + file::DATA_PARTITION_NAME + "/service/" + manifest->id + '/' + childPath; } std::string ServiceInstancePaths::getDataPathLvgl(const std::string& childPath) const { assert(!childPath.starts_with('/')); - return LVGL_PATH_PREFIX + DATA_PARTITION_NAME + "/service/" + manifest->id + '/' + childPath; + return LVGL_PATH_PREFIX + file::DATA_PARTITION_NAME + "/service/" + manifest->id + '/' + childPath; } std::string ServiceInstancePaths::getSystemDirectory() const { - return PARTITION_PREFIX + SYSTEM_PARTITION_NAME + "/service/" + manifest->id; + return PARTITION_PREFIX + file::SYSTEM_PARTITION_NAME + "/service/" + manifest->id; } std::string ServiceInstancePaths::getSystemDirectoryLvgl() const { - return LVGL_PATH_PREFIX + SYSTEM_PARTITION_NAME + "/service/" + manifest->id; + return LVGL_PATH_PREFIX + file::SYSTEM_PARTITION_NAME + "/service/" + manifest->id; } std::string ServiceInstancePaths::getSystemPath(const std::string& childPath) const { assert(!childPath.starts_with('/')); - return PARTITION_PREFIX + SYSTEM_PARTITION_NAME + "/service/" + manifest->id + '/' + childPath; + return PARTITION_PREFIX + file::SYSTEM_PARTITION_NAME + "/service/" + manifest->id + '/' + childPath; } std::string ServiceInstancePaths::getSystemPathLvgl(const std::string& childPath) const { - return LVGL_PATH_PREFIX + SYSTEM_PARTITION_NAME + "/service/" + manifest->id + '/' + childPath; + return LVGL_PATH_PREFIX + file::SYSTEM_PARTITION_NAME + "/service/" + manifest->id + '/' + childPath; } } diff --git a/Tactility/Source/service/ServiceRegistry.cpp b/Tactility/Source/service/ServiceRegistration.cpp similarity index 96% rename from Tactility/Source/service/ServiceRegistry.cpp rename to Tactility/Source/service/ServiceRegistration.cpp index 80a893ac..b0580dfb 100644 --- a/Tactility/Source/service/ServiceRegistry.cpp +++ b/Tactility/Source/service/ServiceRegistration.cpp @@ -1,4 +1,4 @@ -#include "Tactility/service/ServiceRegistry.h" +#include "Tactility/service/ServiceRegistration.h" #include "Tactility/service/ServiceInstance.h" #include "Tactility/service/ServiceManifest.h" @@ -6,8 +6,6 @@ #include #include -#include -#include namespace tt::service { @@ -23,8 +21,9 @@ static Mutex manifest_mutex(Mutex::Type::Normal); static Mutex instance_mutex(Mutex::Type::Normal); void addService(std::shared_ptr manifest, bool autoStart) { + assert(manifest != nullptr); // We'll move the manifest pointer, but we'll need to id later - std::string id = manifest->id; + const auto& id = manifest->id; TT_LOG_I(TAG, "Adding %s", id.c_str()); diff --git a/Tactility/Source/service/development/DevelopmentService.cpp b/Tactility/Source/service/development/DevelopmentService.cpp index 27a0f0b4..959f8efb 100644 --- a/Tactility/Source/service/development/DevelopmentService.cpp +++ b/Tactility/Source/service/development/DevelopmentService.cpp @@ -6,7 +6,7 @@ #include "Tactility/network/Url.h" #include "Tactility/TactilityHeadless.h" #include "Tactility/service/ServiceManifest.h" -#include "Tactility/service/ServiceRegistry.h" +#include "Tactility/service/ServiceRegistration.h" #include "Tactility/service/wifi/Wifi.h" #include @@ -17,7 +17,7 @@ #include #include #include -#include +#include namespace tt::service::development { diff --git a/Tactility/Source/service/espnow/EspNowService.cpp b/Tactility/Source/service/espnow/EspNowService.cpp index d643a5ef..9c71850d 100644 --- a/Tactility/Source/service/espnow/EspNowService.cpp +++ b/Tactility/Source/service/espnow/EspNowService.cpp @@ -3,7 +3,7 @@ #include "Tactility/service/espnow/EspNowService.h" #include "Tactility/TactilityHeadless.h" #include "Tactility/service/ServiceManifest.h" -#include "Tactility/service/ServiceRegistry.h" +#include "Tactility/service/ServiceRegistration.h" #include "Tactility/service/espnow/EspNowWifi.h" #include #include diff --git a/Tactility/Source/service/gps/GpsConfiguration.cpp b/Tactility/Source/service/gps/GpsConfiguration.cpp index 14d03212..3caa65b8 100644 --- a/Tactility/Source/service/gps/GpsConfiguration.cpp +++ b/Tactility/Source/service/gps/GpsConfiguration.cpp @@ -1,6 +1,7 @@ #include "Tactility/service/gps/GpsService.h" -#include +#include "Tactility/file/ObjectFile.h" + #include #include diff --git a/Tactility/Source/service/gps/GpsService.cpp b/Tactility/Source/service/gps/GpsService.cpp index aa4a162f..f69ed1e6 100644 --- a/Tactility/Source/service/gps/GpsService.cpp +++ b/Tactility/Source/service/gps/GpsService.cpp @@ -1,6 +1,6 @@ #include "Tactility/service/gps/GpsService.h" #include "Tactility/service/ServiceManifest.h" -#include "Tactility/service/ServiceRegistry.h" +#include "Tactility/service/ServiceRegistration.h" #include #include diff --git a/Tactility/Source/service/gui/GuiService.cpp b/Tactility/Source/service/gui/GuiService.cpp index 1e886702..cb03babc 100644 --- a/Tactility/Source/service/gui/GuiService.cpp +++ b/Tactility/Source/service/gui/GuiService.cpp @@ -6,7 +6,7 @@ #include #include -#include +#include namespace tt::service::gui { diff --git a/Tactility/Source/service/loader/Loader.cpp b/Tactility/Source/service/loader/Loader.cpp index e7c343ab..87f62127 100644 --- a/Tactility/Source/service/loader/Loader.cpp +++ b/Tactility/Source/service/loader/Loader.cpp @@ -1,12 +1,12 @@ #include "Tactility/service/loader/Loader.h" #include "Tactility/app/AppInstance.h" #include "Tactility/app/AppManifest.h" -#include "Tactility/app/ManifestRegistry.h" +#include "Tactility/app/AppRegistration.h" #include #include #include -#include +#include #include diff --git a/Tactility/Source/service/screenshot/Screenshot.cpp b/Tactility/Source/service/screenshot/Screenshot.cpp index 368c2da6..37bb289b 100644 --- a/Tactility/Source/service/screenshot/Screenshot.cpp +++ b/Tactility/Source/service/screenshot/Screenshot.cpp @@ -5,7 +5,7 @@ #include "Tactility/service/screenshot/Screenshot.h" #include -#include +#include #include diff --git a/Tactility/Source/service/screenshot/ScreenshotTask.cpp b/Tactility/Source/service/screenshot/ScreenshotTask.cpp index 8ce2a361..4780c528 100644 --- a/Tactility/Source/service/screenshot/ScreenshotTask.cpp +++ b/Tactility/Source/service/screenshot/ScreenshotTask.cpp @@ -11,6 +11,7 @@ #include #include +#include namespace tt::service::screenshot { @@ -109,10 +110,11 @@ void ScreenshotTask::taskStart() { thread = new Thread( "screenshot", 8192, - [this]() { + [this] { this->taskMain(); return 0; - } + }, + getCpuAffinityConfiguration().graphics ); thread->start(); } diff --git a/Tactility/Source/service/sdcard/Sdcard.cpp b/Tactility/Source/service/sdcard/Sdcard.cpp index c6412d6b..685a73d6 100644 --- a/Tactility/Source/service/sdcard/Sdcard.cpp +++ b/Tactility/Source/service/sdcard/Sdcard.cpp @@ -1,6 +1,6 @@ #include "Tactility/service/ServiceContext.h" #include "Tactility/TactilityHeadless.h" -#include "Tactility/service/ServiceRegistry.h" +#include "Tactility/service/ServiceRegistration.h" #include #include @@ -13,8 +13,6 @@ extern const ServiceManifest manifest; class SdCardService final : public Service { -private: - Mutex mutex; std::unique_ptr updateTimer; hal::sdcard::SdCardDevice::State lastState = hal::sdcard::SdCardDevice::State::Unmounted; @@ -28,14 +26,14 @@ private: } void update() { - auto sdcard = tt::hal::getConfiguration()->sdcard; + auto sdcard = hal::getConfiguration()->sdcard; assert(sdcard); if (lock(50)) { auto new_state = sdcard->getState(); if (new_state == hal::sdcard::SdCardDevice::State::Error) { - TT_LOG_W(TAG, "Sdcard error - unmounting. Did you eject the card in an unsafe manner?"); + TT_LOG_E(TAG, "Sdcard error - unmounting. Did you eject the card in an unsafe manner?"); sdcard->unmount(); } @@ -51,7 +49,7 @@ private: public: - void onStart(ServiceContext& serviceContext) final { + void onStart(ServiceContext& serviceContext) override { if (hal::getConfiguration()->sdcard != nullptr) { auto service = findServiceById(manifest.id); updateTimer = std::make_unique(Timer::Type::Periodic, [service]() { @@ -64,7 +62,7 @@ public: } } - void onStop(ServiceContext& serviceContext) final { + void onStop(ServiceContext& serviceContext) override { if (updateTimer != nullptr) { // Stop thread updateTimer->stop(); diff --git a/Tactility/Source/service/statusbar/Statusbar.cpp b/Tactility/Source/service/statusbar/Statusbar.cpp index 06059cd6..68a37e4a 100644 --- a/Tactility/Source/service/statusbar/Statusbar.cpp +++ b/Tactility/Source/service/statusbar/Statusbar.cpp @@ -9,7 +9,7 @@ #include #include #include -#include +#include #include namespace tt::service::statusbar { @@ -81,7 +81,7 @@ static const char* getSdCardStatusIcon(hal::sdcard::SdCardDevice::State state) { return STATUSBAR_ICON_SDCARD; case Error: case Unmounted: - case Unknown: + case Timeout: return STATUSBAR_ICON_SDCARD_ALERT; default: tt_crash("Unhandled SdCard state"); @@ -199,8 +199,8 @@ class StatusbarService final : public Service { void updateSdCardIcon() { auto sdcard = hal::getConfiguration()->sdcard; if (sdcard != nullptr) { - auto state = sdcard->getState(); - if (state != hal::sdcard::SdCardDevice::State::Unknown) { + auto state = sdcard->getState(50 / portTICK_PERIOD_MS); + if (state != hal::sdcard::SdCardDevice::State::Timeout) { auto* desired_icon = getSdCardStatusIcon(state); if (sdcard_last_icon != desired_icon) { auto icon_path = paths->getSystemPathLvgl(desired_icon); diff --git a/Tactility/Source/service/wifi/WifiApSettings.cpp b/Tactility/Source/service/wifi/WifiApSettings.cpp new file mode 100644 index 00000000..68906b91 --- /dev/null +++ b/Tactility/Source/service/wifi/WifiApSettings.cpp @@ -0,0 +1,190 @@ +#include "Tactility/service/wifi/WifiApSettings.h" +#include "Tactility/file/PropertiesFile.h" + +#include +#include + +#include +#include +#include +#include +#include +#include + +namespace tt::service::wifi::settings { + +constexpr auto* TAG = "WifiApSettings"; + +constexpr auto* AP_SETTINGS_FORMAT = "/data/service/Wifi/{}.ap.properties"; + +constexpr auto* AP_PROPERTIES_KEY_SSID = "ssid"; +constexpr auto* AP_PROPERTIES_KEY_PASSWORD = "password"; +constexpr auto* AP_PROPERTIES_KEY_AUTO_CONNECT = "autoConnect"; +constexpr auto* AP_PROPERTIES_KEY_CHANNEL = "channel"; + + +std::string toHexString(const uint8_t *data, int length) { + std::stringstream stream; + stream << std::hex; + for( int i(0) ; i < length; ++i ) + stream << std::setw(2) << std::setfill('0') << static_cast(data[i]); + return stream.str(); +} + +bool readHex(const std::string& input, uint8_t* buffer, int length) { + if (input.size() / 2 != length) { + TT_LOG_E(TAG, "readHex() length mismatch"); + return false; + } + + char hex[3] = { 0 }; + for (int i = 0; i < length; i++) { + hex[0] = input[i * 2]; + hex[1] = input[i * 2 + 1]; + char* endptr; + buffer[i] = static_cast(strtoul(hex, &endptr, 16)); + } + + return true; +} + +static std::string getApPropertiesFilePath(const std::string& ssid) { + return std::format(AP_SETTINGS_FORMAT, ssid); +} + +static bool encrypt(const std::string& ssidInput, std::string& ssidOutput) { + uint8_t iv[16]; + const auto length = ssidInput.size(); + constexpr size_t chunk_size = 16; + const auto encrypted_length = ((length / chunk_size) + (length % chunk_size ? 1 : 0)) * chunk_size; + + auto* buffer = static_cast(malloc(encrypted_length)); + + crypt::getIv(ssidInput.c_str(), ssidInput.size(), iv); + if (crypt::encrypt(iv, reinterpret_cast(ssidInput.c_str()), buffer, encrypted_length) != 0) { + TT_LOG_E(TAG, "Failed to encrypt"); + free(buffer); + return false; + } + + ssidOutput = toHexString(buffer, encrypted_length); + free(buffer); + + return true; +} + +static bool decrypt(const std::string& ssidInput, std::string& ssidOutput) { + assert(!ssidInput.empty()); + assert(ssidInput.size() % 2 == 0); + auto* data = static_cast(malloc(ssidInput.size() / 2)); + if (!readHex(ssidInput, data, ssidInput.size() / 2)) { + TT_LOG_E(TAG, "Failed to read hex"); + return false; + } + + uint8_t iv[16]; + crypt::getIv(ssidInput.c_str(), ssidInput.size(), iv); + + auto result_length = ssidInput.size() / 2; + // Allocate correct length plus space for string null terminator + auto* result = static_cast(malloc(result_length + 1)); + result[result_length] = 0; + + int decrypt_result = crypt::decrypt( + iv, + data, + result, + ssidInput.size() / 2 + ); + + free(data); + + if (decrypt_result != 0) { + TT_LOG_E(TAG, "Failed to decrypt credentials for \"%s\": %d", ssidInput.c_str(), decrypt_result); + free(result); + return false; + } + + ssidOutput = reinterpret_cast(result); + free(result); + return true; +} + +bool contains(const std::string& ssid) { + const auto file_path = getApPropertiesFilePath(ssid); + return file::isFile(file_path); +} + +bool load(const std::string& ssid, WifiApSettings& apSettings) { + const auto file_path = getApPropertiesFilePath(ssid); + std::map map; + if (!file::loadPropertiesFile(file_path, map)) { + return false; + } + + // SSID is required + if (!map.contains(AP_PROPERTIES_KEY_SSID)) { + return false; + } + + apSettings.ssid = map[AP_PROPERTIES_KEY_SSID]; + assert(ssid == apSettings.ssid); + + if (map.contains(AP_PROPERTIES_KEY_PASSWORD)) { + std::string password_decrypted; + if (decrypt(map[AP_PROPERTIES_KEY_PASSWORD], password_decrypted)) { + apSettings.password = password_decrypted; + } else { + return false; + } + } else { + apSettings.password = ""; + } + + if (map.contains(AP_PROPERTIES_KEY_AUTO_CONNECT)) { + apSettings.autoConnect = (map[AP_PROPERTIES_KEY_AUTO_CONNECT] == "true"); + } else { + apSettings.autoConnect = true; + } + + if (map.contains(AP_PROPERTIES_KEY_CHANNEL)) { + apSettings.channel = std::stoi(map[AP_PROPERTIES_KEY_CHANNEL].c_str()); + } else { + apSettings.channel = 0; + } + + return true; + +} + +bool save(const WifiApSettings& apSettings) { + if (apSettings.ssid.empty()) { + return false; + } + + const auto file_path = getApPropertiesFilePath(apSettings.ssid); + + std::map map; + + std::string password_encrypted; + if (!encrypt(apSettings.password, password_encrypted)) { + return false; + } + + map[AP_PROPERTIES_KEY_PASSWORD] = password_encrypted; + map[AP_PROPERTIES_KEY_SSID] = apSettings.ssid; + map[AP_PROPERTIES_KEY_AUTO_CONNECT] = apSettings.autoConnect ? "true" : "false"; + map[AP_PROPERTIES_KEY_CHANNEL] = std::to_string(apSettings.channel); + + return file::savePropertiesFile(file_path, map); +} + +bool remove(const std::string& ssid) { + const auto path = getApPropertiesFilePath(ssid); + if (!file::isFile(path)) { + return false; + } + return ::remove(path.c_str()) == 0; +} + +} diff --git a/Tactility/Source/service/wifi/WifiBootSplashInit.cpp b/Tactility/Source/service/wifi/WifiBootSplashInit.cpp new file mode 100644 index 00000000..83e25471 --- /dev/null +++ b/Tactility/Source/service/wifi/WifiBootSplashInit.cpp @@ -0,0 +1,128 @@ +#include "Tactility/service/wifi/WifiBootSplashInit.h" +#include "Tactility/file/PropertiesFile.h" + +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +namespace tt::service::wifi { + +constexpr auto* TAG = "WifiBootSplashInit"; + +constexpr auto* AP_PROPERTIES_KEY_SSID = "ssid"; +constexpr auto* AP_PROPERTIES_KEY_PASSWORD = "password"; +constexpr auto* AP_PROPERTIES_KEY_AUTO_CONNECT = "autoConnect"; +constexpr auto* AP_PROPERTIES_KEY_CHANNEL = "channel"; +constexpr auto* AP_PROPERTIES_KEY_AUTO_REMOVE = "autoRemovePropertiesFile"; + +struct ApProperties { + std::string ssid; + std::string password; + bool autoConnect; + int32_t channel; + bool autoRemovePropertiesFile; +}; + +static void importWifiAp(const std::string& filePath) { + std::map map; + if (!file::loadPropertiesFile(filePath, map)) { + return; + } + + const auto ssid_iterator = map.find(AP_PROPERTIES_KEY_SSID); + if (ssid_iterator == map.end()) { + TT_LOG_E(TAG, "%s is missing ssid", filePath.c_str()); + return; + } + const auto ssid = ssid_iterator->second; + + if (!settings::contains(ssid)) { + const auto password_iterator = map.find(AP_PROPERTIES_KEY_PASSWORD); + const auto password = password_iterator == map.end() ? "" : password_iterator->second; + + const auto auto_connect_iterator = map.find(AP_PROPERTIES_KEY_AUTO_CONNECT); + const auto auto_connect = auto_connect_iterator == map.end() ? true : (auto_connect_iterator->second == "true"); + + const auto channel_iterator = map.find(AP_PROPERTIES_KEY_CHANNEL); + const auto channel = channel_iterator == map.end() ? 0 : std::stoi(channel_iterator->second); + + settings::WifiApSettings settings( + ssid, + password, + auto_connect, + channel + ); + + if (!settings::save(settings)) { + TT_LOG_E(TAG, "Failed to save settings for %s", ssid.c_str()); + } else { + TT_LOG_I(TAG, "Imported %s from %s", ssid.c_str(), filePath.c_str()); + } + } + + const auto auto_remove_iterator = map.find(AP_PROPERTIES_KEY_AUTO_REMOVE); + if (auto_remove_iterator != map.end() && auto_remove_iterator->second == "true") { + if (!remove(filePath.c_str())) { + TT_LOG_E(TAG, "Failed to auto-remove %s", filePath.c_str()); + } else { + TT_LOG_I(TAG, "Auto-removed %s", filePath.c_str()); + } + } +} + +static void importWifiApSettings(std::shared_ptr sdcard) { + // auto lock = sdcard->getLock()->asScopedLock(); + // lock.lock(); + auto path = sdcard->getMountPath(); + + std::vector dirent_list; + if (file::scandir(path, dirent_list, [](const dirent* entry) { + switch (entry->d_type) { + case file::TT_DT_DIR: + case file::TT_DT_CHR: + case file::TT_DT_LNK: + return -1; + case file::TT_DT_REG: + default: { + std::string name = entry->d_name; + if (name.ends_with(".ap.properties")) { + return 0; + } else { + return -1; + } + } + } + }, nullptr) == 0) { + return; + } + + if (dirent_list.empty()) { + TT_LOG_W(TAG, "No AP files found at %s", sdcard->getMountPath().c_str()); + return; + } + + for (auto& dirent : dirent_list) { + std::string absolute_path = std::format("{}/{}", path, dirent.d_name); + importWifiAp(absolute_path); + } +} + +void bootSplashInit() { + const auto sdcards = hal::findDevices(hal::Device::Type::SdCard); + for (auto& sdcard : sdcards) { + if (sdcard->isMounted()) { + importWifiApSettings(sdcard); + } else { + TT_LOG_W(TAG, "Skipping unmounted SD card %s", sdcard->getMountPath().c_str()); + } + } +} + +} diff --git a/Tactility/Source/service/wifi/WifiEsp.cpp b/Tactility/Source/service/wifi/WifiEsp.cpp index 8fa75838..6ec729ab 100644 --- a/Tactility/Source/service/wifi/WifiEsp.cpp +++ b/Tactility/Source/service/wifi/WifiEsp.cpp @@ -1,19 +1,20 @@ #ifdef ESP_PLATFORM -#include #include "Tactility/service/wifi/Wifi.h" #include "Tactility/TactilityHeadless.h" #include "Tactility/service/ServiceContext.h" +#include "Tactility/service/wifi/WifiGlobals.h" #include "Tactility/service/wifi/WifiSettings.h" +#include "Tactility/service/wifi/WifiBootSplashInit.h" +#include #include +#include #include - #include #include -#include #include namespace tt::service::wifi { @@ -36,8 +37,6 @@ static void dispatchDisconnectButKeepActive(std::shared_ptr wifi); class Wifi { -private: - std::atomic radio_state = RadioState::Off; bool scan_active = false; bool secure_connection = false; @@ -66,14 +65,11 @@ public: esp_event_handler_instance_t event_handler_any_id = nullptr; esp_event_handler_instance_t event_handler_got_ip = nullptr; EventFlag connection_wait_flags; - settings::WifiApSettings connection_target = { - .ssid = { 0 }, - .password = { 0 }, - .auto_connect = false - }; + settings::WifiApSettings connection_target; bool pause_auto_connect = false; // Pause when manually disconnecting until manually connecting again bool connection_target_remember = false; // Whether to store the connection_target on successful connection or not esp_netif_ip_info_t ip_info; + kernel::SystemEventSubscription bootEventSubscription = kernel::NoSystemEventSubscription; RadioState getRadioState() const { auto lock = dataMutex.asScopedLock(); @@ -187,8 +183,8 @@ bool isScanning() { } } -void connect(const settings::WifiApSettings* ap, bool remember) { - TT_LOG_I(TAG, "connect(%s, %d)", ap->ssid, remember); +void connect(const settings::WifiApSettings& ap, bool remember) { + TT_LOG_I(TAG, "connect(%s, %d)", ap.ssid.c_str(), remember); auto wifi = wifi_singleton; if (wifi == nullptr) { return; @@ -201,14 +197,14 @@ void connect(const settings::WifiApSettings* ap, bool remember) { // Manual connect (e.g. via app) should stop auto-connecting until the connection is established wifi->pause_auto_connect = true; - memcpy(&wifi->connection_target, ap, sizeof(settings::WifiApSettings)); + wifi->connection_target = ap; wifi->connection_target_remember = remember; if (wifi->getRadioState() == RadioState::Off) { - getMainDispatcher().dispatch([wifi]() { dispatchEnable(wifi); }); + getMainDispatcher().dispatch([wifi] { dispatchEnable(wifi); }); } - getMainDispatcher().dispatch([wifi]() { dispatchConnect(wifi); }); + getMainDispatcher().dispatch([wifi] { dispatchConnect(wifi); }); } void disconnect() { @@ -223,11 +219,7 @@ void disconnect() { return; } - wifi->connection_target = (settings::WifiApSettings) { - .ssid = { 0 }, - .password = { 0 }, - .auto_connect = false - }; + wifi->connection_target = settings::WifiApSettings("", ""); // Manual disconnect (e.g. via app) should stop auto-connecting until a new connection is established wifi->pause_auto_connect = true; getMainDispatcher().dispatch([wifi]() { dispatchDisconnectButKeepActive(wifi); }); @@ -307,9 +299,9 @@ void setEnabled(bool enabled) { } if (enabled) { - getMainDispatcher().dispatch([wifi]() { dispatchEnable(wifi); }); + getMainDispatcher().dispatch([wifi] { dispatchEnable(wifi); }); } else { - getMainDispatcher().dispatch([wifi]() { dispatchDisable(wifi); }); + getMainDispatcher().dispatch([wifi] { dispatchDisable(wifi); }); } wifi->pause_auto_connect = false; @@ -431,8 +423,8 @@ static bool find_auto_connect_ap(std::shared_ptr wifi, settings::WifiApSet auto ssid = reinterpret_cast(wifi->scan_list[i].ssid); if (settings::contains(ssid)) { static_assert(sizeof(wifi->scan_list[i].ssid) == (TT_WIFI_SSID_LIMIT + 1), "SSID size mismatch"); - if (settings::load(ssid, &settings)) { - if (settings.auto_connect) { + if (settings::load(ssid, settings)) { + if (settings.autoConnect) { return true; } } else { @@ -451,8 +443,8 @@ static void dispatchAutoConnect(std::shared_ptr wifi) { settings::WifiApSettings settings; if (find_auto_connect_ap(wifi, settings)) { - TT_LOG_I(TAG, "Auto-connecting to %s", settings.ssid); - connect(&settings, false); + TT_LOG_I(TAG, "Auto-connecting to %s", settings.ssid.c_str()); + connect(settings, false); // TODO: We currently have to manually reset it because connect() sets it. // connect() assumes it's only being called by the user and not internally, so it disables auto-connect wifi->pause_auto_connect = false; @@ -726,7 +718,7 @@ static void dispatchConnect(std::shared_ptr wifi) { return; } - TT_LOG_I(TAG, "Connecting to %s", wifi->connection_target.ssid); + TT_LOG_I(TAG, "Connecting to %s", wifi->connection_target.ssid.c_str()); // Stop radio first, if needed RadioState radio_state = wifi->getRadioState(); @@ -758,11 +750,10 @@ static void dispatchConnect(std::shared_ptr wifi) { config.sta.threshold.rssi = -127; config.sta.pmf_cfg.capable = true; - static_assert(sizeof(config.sta.ssid) == (sizeof(wifi_singleton->connection_target.ssid)-1), "SSID size mismatch"); - memcpy(config.sta.ssid, wifi_singleton->connection_target.ssid, sizeof(config.sta.ssid)); + memcpy(config.sta.ssid, wifi_singleton->connection_target.ssid.c_str(), wifi_singleton->connection_target.ssid.size()); if (wifi_singleton->connection_target.password[0] != 0x00) { - memcpy(config.sta.password, wifi_singleton->connection_target.password, sizeof(config.sta.password)); + memcpy(config.sta.password, wifi_singleton->connection_target.password.c_str(), wifi_singleton->connection_target.password.size()); config.sta.threshold.authmode = WIFI_AUTH_WPA2_PSK; } @@ -794,9 +785,9 @@ static void dispatchConnect(std::shared_ptr wifi) { wifi->setSecureConnection(config.sta.password[0] != 0x00U); wifi->setRadioState(RadioState::ConnectionActive); publish_event_simple(wifi, EventType::ConnectionSuccess); - TT_LOG_I(TAG, "Connected to %s", wifi->connection_target.ssid); + TT_LOG_I(TAG, "Connected to %s", wifi->connection_target.ssid.c_str()); if (wifi->connection_target_remember) { - if (!settings::save(&wifi->connection_target)) { + if (!settings::save(wifi->connection_target)) { TT_LOG_E(TAG, "Failed to store credentials"); } else { TT_LOG_I(TAG, "Stored credentials"); @@ -805,7 +796,7 @@ static void dispatchConnect(std::shared_ptr wifi) { } else if (bits & WIFI_FAIL_BIT) { wifi->setRadioState(RadioState::On); publish_event_simple(wifi, EventType::ConnectionFailed); - TT_LOG_I(TAG, "Failed to connect to %s", wifi->connection_target.ssid); + TT_LOG_I(TAG, "Failed to connect to %s", wifi->connection_target.ssid.c_str()); } else { wifi->setRadioState(RadioState::On); publish_event_simple(wifi, EventType::ConnectionFailed); @@ -911,13 +902,17 @@ public: assert(wifi_singleton == nullptr); wifi_singleton = std::make_shared(); + wifi_singleton->bootEventSubscription = kernel::subscribeSystemEvent(kernel::SystemEvent::BootSplash, [](auto) { + bootSplashInit(); + }); + wifi_singleton->autoConnectTimer = std::make_unique(Timer::Type::Periodic, []() { onAutoConnectTimer(); }); // We want to try and scan more often in case of startup or scan lock failure wifi_singleton->autoConnectTimer->start(std::min(2000, AUTO_SCAN_INTERVAL)); if (settings::shouldEnableOnBoot()) { TT_LOG_I(TAG, "Auto-enabling due to setting"); - getMainDispatcher().dispatch([]() { dispatchEnable(wifi_singleton); }); + getMainDispatcher().dispatch([] { dispatchEnable(wifi_singleton); }); } } diff --git a/Tactility/Source/service/wifi/WifiMock.cpp b/Tactility/Source/service/wifi/WifiMock.cpp index 9828ca92..3d3f7a41 100644 --- a/Tactility/Source/service/wifi/WifiMock.cpp +++ b/Tactility/Source/service/wifi/WifiMock.cpp @@ -66,7 +66,7 @@ bool isScanning() { return wifi->scan_active; } -void connect(const settings::WifiApSettings* ap, bool remember) { +void connect(const settings::WifiApSettings& ap, bool remember) { assert(wifi); // TODO: implement } diff --git a/Tactility/Source/service/wifi/WifiSettings.cpp b/Tactility/Source/service/wifi/WifiSettings.cpp index bc8874c9..4ce1a75a 100644 --- a/Tactility/Source/service/wifi/WifiSettings.cpp +++ b/Tactility/Source/service/wifi/WifiSettings.cpp @@ -1,18 +1,54 @@ +#include "Tactility/service/wifi/WifiSettings.h" #include "Tactility/Preferences.h" +#include "Tactility/file/PropertiesFile.h" -#define WIFI_PREFERENCES_NAMESPACE "wifi" -#define WIFI_PREFERENCES_KEY_ENABLE_ON_BOOT "enable_on_boot" +#include +#include namespace tt::service::wifi::settings { +constexpr auto* TAG = "WifiSettings"; +constexpr auto* SETTINGS_FILE = "/data/service/Wifi/wifi.properties"; +constexpr auto* SETTINGS_KEY_ENABLE_ON_BOOT = "enableOnBoot"; + +struct WifiProperties { + bool enableOnBoot; +}; + +static bool load(WifiProperties& properties) { + std::map map; + if (!file::loadPropertiesFile(SETTINGS_FILE, map)) { + return false; + } + + if (!map.contains(SETTINGS_KEY_ENABLE_ON_BOOT)) { + return false; + } + + auto enable_on_boot_string = map[SETTINGS_KEY_ENABLE_ON_BOOT]; + properties.enableOnBoot = (enable_on_boot_string == "true"); + return true; +} + +static bool save(const WifiProperties& properties) { + std::map map; + map[SETTINGS_KEY_ENABLE_ON_BOOT] = properties.enableOnBoot ? "true" : "false"; + return file::savePropertiesFile(SETTINGS_FILE, map); +} + void setEnableOnBoot(bool enable) { - Preferences(WIFI_PREFERENCES_NAMESPACE).putBool(WIFI_PREFERENCES_KEY_ENABLE_ON_BOOT, enable); + WifiProperties properties { .enableOnBoot = enable }; + if (!save(properties)) { + TT_LOG_E(TAG, "Failed to save %s", SETTINGS_FILE); + } } bool shouldEnableOnBoot() { - bool enable = false; - Preferences(WIFI_PREFERENCES_NAMESPACE).optBool(WIFI_PREFERENCES_KEY_ENABLE_ON_BOOT, enable); - return enable; + WifiProperties properties; + if (!load(properties)) { + return false; + } + return properties.enableOnBoot; } } // namespace diff --git a/Tactility/Source/service/wifi/WifiSettingsEsp.cpp b/Tactility/Source/service/wifi/WifiSettingsEsp.cpp deleted file mode 100644 index 51f427dc..00000000 --- a/Tactility/Source/service/wifi/WifiSettingsEsp.cpp +++ /dev/null @@ -1,148 +0,0 @@ -#ifdef ESP_PLATFORM - -#include "Tactility/service/wifi/WifiGlobals.h" -#include "Tactility/service/wifi/WifiSettings.h" - -#include -#include -#include - -#include -#include - -#define TAG "wifi_settings" -#define TT_NVS_NAMESPACE "wifi_settings" // limited by NVS_KEY_NAME_MAX_SIZE - -// region Wi-Fi Credentials - static - -static esp_err_t credentials_nvs_open(nvs_handle_t* handle, nvs_open_mode_t mode) { - return nvs_open(TT_NVS_NAMESPACE, NVS_READWRITE, handle); -} - -static void credentials_nvs_close(nvs_handle_t handle) { - nvs_close(handle); -} - -// endregion Wi-Fi Credentials - static - -// region Wi-Fi Credentials - public -namespace tt::service::wifi::settings { - -bool contains(const char* ssid) { - nvs_handle_t handle; - esp_err_t result = credentials_nvs_open(&handle, NVS_READONLY); - if (result != ESP_OK) { - TT_LOG_E(TAG, "Failed to open NVS handle: %s", esp_err_to_name(result)); - return false; - } - - bool key_exists = nvs_find_key(handle, ssid, nullptr) == ESP_OK; - credentials_nvs_close(handle); - - return key_exists; -} - -bool load(const char* ssid, WifiApSettings* settings) { - nvs_handle_t handle; - esp_err_t result = credentials_nvs_open(&handle, NVS_READONLY); - if (result != ESP_OK) { - TT_LOG_E(TAG, "Failed to open NVS handle: %s", esp_err_to_name(result)); - return false; - } - - WifiApSettings encrypted_settings; - size_t length = sizeof(WifiApSettings); - result = nvs_get_blob(handle, ssid, &encrypted_settings, &length); - - uint8_t iv[16]; - crypt::getIv(ssid, strlen(ssid), iv); - int decrypt_result = crypt::decrypt( - iv, - (uint8_t*)encrypted_settings.password, - (uint8_t*)settings->password, - TT_WIFI_CREDENTIALS_PASSWORD_LIMIT - ); - // Manually ensure null termination, because encryption must be a multiple of 16 bytes - encrypted_settings.password[TT_WIFI_CREDENTIALS_PASSWORD_LIMIT] = 0; - - if (decrypt_result != 0) { - result = ESP_FAIL; - TT_LOG_E(TAG, "Failed to decrypt credentials for \"%s\": %d", ssid, decrypt_result); - } - - if (result != ESP_OK && result != ESP_ERR_NVS_NOT_FOUND) { - TT_LOG_E(TAG, "Failed to get credentials for \"%s\": %s", ssid, esp_err_to_name(result)); - } - - credentials_nvs_close(handle); - - settings->auto_connect = encrypted_settings.auto_connect; - strcpy((char*)settings->ssid, encrypted_settings.ssid); - - return result == ESP_OK; -} - -bool save(const WifiApSettings* settings) { - nvs_handle_t handle; - esp_err_t result = credentials_nvs_open(&handle, NVS_READWRITE); - if (result != ESP_OK) { - TT_LOG_E(TAG, "Failed to open NVS handle: %s", esp_err_to_name(result)); - return false; - } - - WifiApSettings encrypted_settings = { - .ssid = { 0 }, - .password = { 0 }, - .auto_connect = settings->auto_connect, - }; - strcpy((char*)encrypted_settings.ssid, settings->ssid); - // We only decrypt multiples of 16, so we have to ensure the last byte is set to 0 - encrypted_settings.password[TT_WIFI_CREDENTIALS_PASSWORD_LIMIT] = 0; - - uint8_t iv[16]; - crypt::getIv(settings->ssid, strlen(settings->ssid), iv); - int encrypt_result = crypt::encrypt( - iv, - (uint8_t*)settings->password, - (uint8_t*)encrypted_settings.password, - TT_WIFI_CREDENTIALS_PASSWORD_LIMIT - ); - - if (encrypt_result != 0) { - result = ESP_FAIL; - TT_LOG_E(TAG, "Failed to encrypt credentials \"%s\": %d", settings->ssid, encrypt_result); - } - - if (result == ESP_OK) { - result = nvs_set_blob(handle, settings->ssid, &encrypted_settings, sizeof(WifiApSettings)); - if (result != ESP_OK) { - TT_LOG_E(TAG, "Failed to get credentials for \"%s\": %s", settings->ssid, esp_err_to_name(result)); - } - } - - credentials_nvs_close(handle); - return result == ESP_OK; -} - -bool remove(const char* ssid) { - nvs_handle_t handle; - esp_err_t result = credentials_nvs_open(&handle, NVS_READWRITE); - if (result != ESP_OK) { - TT_LOG_E(TAG, "Failed to open NVS handle to store \"%s\": %s", ssid, esp_err_to_name(result)); - return false; - } - - result = nvs_erase_key(handle, ssid); - if (result != ESP_OK) { - TT_LOG_E(TAG, "Failed to erase credentials for \"%s\": %s", ssid, esp_err_to_name(result)); - } - - credentials_nvs_close(handle); - return result == ESP_OK; -} - -// end region Wi-Fi Credentials - public - -} // nemespace - -#endif // ESP_PLATFORM \ No newline at end of file diff --git a/Tactility/Source/service/wifi/WifiSettingsMock.cpp b/Tactility/Source/service/wifi/WifiSettingsMock.cpp deleted file mode 100644 index 0de215b1..00000000 --- a/Tactility/Source/service/wifi/WifiSettingsMock.cpp +++ /dev/null @@ -1,27 +0,0 @@ -#ifndef ESP_PLATFORM - -#include "Tactility/service/wifi/WifiSettings.h" - -namespace tt::service::wifi::settings { - -#define TAG "wifi_settings_mock" - -bool contains(const char* ssid) { - return false; -} - -bool load(const char* ssid, WifiApSettings* settings) { - return false; -} - -bool save(const WifiApSettings* settings) { - return true; -} - -bool remove(const char* ssid) { - return true; -} - -} // namespace - -#endif // ESP_PLATFORM \ No newline at end of file diff --git a/TactilityC/Include/tt_app.h b/TactilityC/Include/tt_app.h index 1e5dbbb0..884bd5df 100644 --- a/TactilityC/Include/tt_app.h +++ b/TactilityC/Include/tt_app.h @@ -35,7 +35,7 @@ void tt_app_stop(); /** * Start an app by id and bundle. * @param[in] appId the app manifest id -* @param[in] parameters the parameters to pass onto the starting app + * @param[in] parameters the parameters to pass onto the starting app */ void tt_app_start_with_bundle(const char* appId, BundleHandle parameters); diff --git a/TactilityC/Source/tt_wifi.cpp b/TactilityC/Source/tt_wifi.cpp index b64441f7..20280993 100644 --- a/TactilityC/Source/tt_wifi.cpp +++ b/TactilityC/Source/tt_wifi.cpp @@ -34,10 +34,11 @@ void tt_wifi_set_enabled(bool enabled) { void tt_wifi_connect(const char* ssid, const char* password, int32_t channel, bool autoConnect, bool remember) { wifi::settings::WifiApSettings settings; - strcpy(settings.ssid, ssid); - strcpy(settings.password, password); + settings.ssid = ssid; + settings.password = password; settings.channel = channel; - settings.auto_connect = autoConnect; + settings.autoConnect = autoConnect; + wifi::connect(settings, remember); } void tt_wifi_disconnect() { diff --git a/TactilityCore/CMakeLists.txt b/TactilityCore/CMakeLists.txt index f8c64691..a55e7dab 100644 --- a/TactilityCore/CMakeLists.txt +++ b/TactilityCore/CMakeLists.txt @@ -9,7 +9,6 @@ if (DEFINED ENV{ESP_IDF_VERSION}) idf_component_register( SRCS ${SOURCE_FILES} INCLUDE_DIRS "Include/" - PRIV_INCLUDE_DIRS "Private/" REQUIRES mbedtls nvs_flash esp_rom esp_timer ) @@ -25,10 +24,6 @@ else() PRIVATE ${SOURCES} ) - include_directories( - PRIVATE Private/ - ) - target_include_directories(TactilityCore SYSTEM PUBLIC Include/ ) diff --git a/TactilityCore/Include/Tactility/CpuAffinity.h b/TactilityCore/Include/Tactility/CpuAffinity.h new file mode 100644 index 00000000..1408a3d5 --- /dev/null +++ b/TactilityCore/Include/Tactility/CpuAffinity.h @@ -0,0 +1,27 @@ +#pragma once + +#include "RtosCompat.h" + +namespace tt { + +typedef portBASE_TYPE CpuAffinity; + +constexpr static CpuAffinity None = -1; + +/** + * Determines the preferred affinity for certain (sub)systems. + */ +struct CpuAffinityConfiguration { + CpuAffinity system; + CpuAffinity graphics; // Display, LVGL + CpuAffinity wifi; + CpuAffinity mainDispatcher; + CpuAffinity apps; + CpuAffinity timer; // Tactility Timer (based on FreeRTOS) +}; + +void setCpuAffinityConfiguration(const CpuAffinityConfiguration& config); + +const CpuAffinityConfiguration& getCpuAffinityConfiguration(); + +} diff --git a/TactilityCore/Include/Tactility/Lock.h b/TactilityCore/Include/Tactility/Lock.h index d4988996..fdba4680 100644 --- a/TactilityCore/Include/Tactility/Lock.h +++ b/TactilityCore/Include/Tactility/Lock.h @@ -66,7 +66,7 @@ public: explicit ScopedLock(const Lock& lockable) : lockable(lockable) {} - ~ScopedLock() final { + ~ScopedLock() override { lockable.unlock(); // We don't care whether it succeeded or not } diff --git a/TactilityCore/Include/Tactility/Mutex.h b/TactilityCore/Include/Tactility/Mutex.h index 03b204f9..cfdf32aa 100644 --- a/TactilityCore/Include/Tactility/Mutex.h +++ b/TactilityCore/Include/Tactility/Mutex.h @@ -43,18 +43,18 @@ public: using Lock::lock; explicit Mutex(Type type = Type::Normal); - ~Mutex() final = default; + ~Mutex() override = default; /** Attempt to lock the mutex. Blocks until timeout passes or lock is acquired. * @param[in] timeout * @return success result */ - bool lock(TickType_t timeout) const final; + bool lock(TickType_t timeout) const override; /** Attempt to unlock the mutex. * @return success result */ - bool unlock() const final; + bool unlock() const override; /** @return the owner of the thread */ ThreadId getOwner() const; diff --git a/TactilityCore/Include/Tactility/StringUtils.h b/TactilityCore/Include/Tactility/StringUtils.h index b92c3196..5ad0713a 100644 --- a/TactilityCore/Include/Tactility/StringUtils.h +++ b/TactilityCore/Include/Tactility/StringUtils.h @@ -4,6 +4,7 @@ #include #include #include +#include namespace tt::string { @@ -30,6 +31,16 @@ std::string getLastPathSegment(const std::string& path); */ std::vector split(const std::string& input, const std::string& delimiter); +/** + * Splits the provided input into separate pieces with delimiter as separator text. + * When the input string is empty, the output list will be empty too. + * + * @param input the input to split up + * @param delimiter a non-empty string to recognize as separator + * @param callback the callback function that receives the split parts + */ +void split(const std::string& input, const std::string& delimiter, std::function callback); + /** * Join a set of tokens into a single string, given a delimiter (separator). * If the input is an empty list, the result will be an empty string. diff --git a/TactilityCore/Include/Tactility/Timer.h b/TactilityCore/Include/Tactility/Timer.h index 285b5ea3..2d58bf69 100644 --- a/TactilityCore/Include/Tactility/Timer.h +++ b/TactilityCore/Include/Tactility/Timer.h @@ -18,7 +18,7 @@ public: private: struct TimerHandleDeleter { - void operator()(TimerHandle_t handleToDelete) { + void operator()(TimerHandle_t handleToDelete) const { xTimerDelete(handleToDelete, portMAX_DELAY); } }; diff --git a/TactilityCore/Include/Tactility/crypt/Crypt.h b/TactilityCore/Include/Tactility/crypt/Crypt.h index 2b08cf54..b9dd37cf 100644 --- a/TactilityCore/Include/Tactility/crypt/Crypt.h +++ b/TactilityCore/Include/Tactility/crypt/Crypt.h @@ -44,7 +44,7 @@ void getIv(const void* data, size_t dataLength, uint8_t iv[16]); * @param[in] dataLength data length, a multiple of 16 (for both inData and outData) * @return the result of esp_aes_crypt_cbc() (MBEDTLS_ERR_*) */ -int encrypt(const uint8_t iv[16], uint8_t* inData, uint8_t* outData, size_t dataLength); +int encrypt(const uint8_t iv[16], const uint8_t* inData, uint8_t* outData, size_t dataLength); /** * @brief Decrypt data. @@ -58,6 +58,7 @@ int encrypt(const uint8_t iv[16], uint8_t* inData, uint8_t* outData, size_t data * @param[in] dataLength data length, a multiple of 16 (for both inData and outData) * @return the result of esp_aes_crypt_cbc() (MBEDTLS_ERR_*) */ -int decrypt(const uint8_t iv[16], uint8_t* inData, uint8_t* outData, size_t dataLength); +int decrypt(const uint8_t iv[16], const uint8_t* inData, uint8_t* outData, size_t dataLength); + } // namespace diff --git a/TactilityCore/Include/Tactility/file/File.h b/TactilityCore/Include/Tactility/file/File.h index 2aa83f1b..76808394 100644 --- a/TactilityCore/Include/Tactility/file/File.h +++ b/TactilityCore/Include/Tactility/file/File.h @@ -7,28 +7,23 @@ #include #include +/** + * @warning SD card access requires a locking mechanism: + * @warning When using this in the Tactility main project, use `file::getLock()` or `file::withLock()` + */ namespace tt::file { /** File types for `dirent`'s `d_type`. */ enum { - TT_DT_UNKNOWN = 0, -#define TT_DT_UNKNOWN TT_DT_UNKNOWN // Unknown type - TT_DT_FIFO = 1, -#define TT_DT_FIFO TT_DT_FIFO // Named pipe or FIFO - TT_DT_CHR = 2, -#define TT_DT_CHR TT_DT_CHR // Character device - TT_DT_DIR = 4, -#define TT_DT_DIR TT_DT_DIR // Directory - TT_DT_BLK = 6, -#define TT_DT_BLK TT_DT_BLK // Block device - TT_DT_REG = 8, -#define TT_DT_REG TT_DT_REG // Regular file - TT_DT_LNK = 10, -#define TT_DT_LNK TT_DT_LNK // Symbolic link - TT_DT_SOCK = 12, -#define TT_DT_SOCK TT_DT_SOCK // Local-domain socket - TT_DT_WHT = 14 -#define TT_DT_WHT TT_DT_WHT // Whiteout inodes + TT_DT_UNKNOWN = 0, // Unknown type + TT_DT_FIFO = 1, // Named pipe or FIFO + TT_DT_CHR = 2, // Character device + TT_DT_DIR = 4, // Directory + TT_DT_BLK = 6, // Block device + TT_DT_REG = 8, // Regular file + TT_DT_LNK = 10, // Symbolic link + TT_DT_SOCK = 12, // Local-domain socket + TT_DT_WHT = 14 // Whiteout inodes }; #ifdef _WIN32 @@ -92,6 +87,10 @@ bool direntSortAlphaAndType(const dirent& left, const dirent& right); /** A filter for filtering out "." and ".." */ int direntFilterDotEntries(const dirent* entry); +bool isFile(const std::string& path); + +bool isDirectory(const std::string& path); + /** * A scandir()-like implementation that works on ESP32. * It does not return "." and ".." items but otherwise functions the same. diff --git a/TactilityCore/Source/CpuAffinity.cpp b/TactilityCore/Source/CpuAffinity.cpp new file mode 100644 index 00000000..290c1df6 --- /dev/null +++ b/TactilityCore/Source/CpuAffinity.cpp @@ -0,0 +1,85 @@ +#include "Tactility/CpuAffinity.h" + +#include + +namespace tt { + +#ifdef ESP_PLATFORM + +static CpuAffinity getEspWifiAffinity() { +#ifdef CONFIG_ESP32_WIFI_TASK_PINNED_TO_CORE_0 + return 0; +#elif defined(CONFIG_ESP32_WIFI_TASK_PINNED_TO_CORE_1) + return 1; +#endif +} + +// Warning: Must watch ESP WiFi, as this task is used by WiFi +static CpuAffinity getEspMainSchedulerAffinity() { +#ifdef CONFIG_ESP32_WIFI_TASK_PINNED_TO_CORE_0 + return 0; +#elif defined(CONFIG_ESP32_WIFI_TASK_PINNED_TO_CORE_1) + return 1; +#endif +} + +static CpuAffinity getFreeRtosTimerAffinity() { +#if defined(CONFIG_FREERTOS_TIMER_TASK_NO_AFFINITY) + return None; +#elif defined(CONFIG_FREERTOS_TIMER_TASK_AFFINITY_CPU0) + return 0; +#elif defined(CONFIG_FREERTOS_TIMER_TASK_AFFINITY_CPU1) + return 1; +#else + static_assert(false); +#endif +} + +#if CONFIG_FREERTOS_NUMBER_OF_CORES == 1 +static const CpuAffinityConfiguration esp = { + .system = 0, + .graphics = 0, + .wifi = 0, + .mainDispatcher = 0, + .apps = 0, + .timer = getFreeRtosTimerAffinity() +}; +#elif CONFIG_FREERTOS_NUMBER_OF_CORES == 2 +static const CpuAffinityConfiguration esp = { + .system = 0, + .graphics = 1, + .wifi = getEspWifiAffinity(), + .mainDispatcher = getEspMainSchedulerAffinity(), + .apps = 1, + .timer = getFreeRtosTimerAffinity() +}; +#endif + +#else + +static const CpuAffinityConfiguration simulator = { + .system = None, + .graphics = None, + .wifi = None, + .mainDispatcher = 0, + .apps = None, + .timer = None +}; + +#endif + +const CpuAffinityConfiguration& getCpuAffinityConfiguration() { +#ifdef ESP_PLATFORM + +#if CONFIG_FREERTOS_NUMBER_OF_CORES == 2 + // WiFi uses the main dispatcher to defer operations in the background + assert(esp.wifi == esp.mainDispatcher); +#endif // CORES + return esp; + +#else + return simulator; +#endif +} + +} diff --git a/TactilityCore/Source/StringUtils.cpp b/TactilityCore/Source/StringUtils.cpp index e7f8969f..43afa563 100644 --- a/TactilityCore/Source/StringUtils.cpp +++ b/TactilityCore/Source/StringUtils.cpp @@ -29,24 +29,28 @@ std::string getLastPathSegment(const std::string& path) { } } -std::vector split(const std::string&input, const std::string&delimiter) { +void split(const std::string& input, const std::string& delimiter, std::function callback) { size_t token_index = 0; size_t delimiter_index; const size_t delimiter_length = delimiter.length(); - std::string token; - std::vector result; while ((delimiter_index = input.find(delimiter, token_index)) != std::string::npos) { - token = input.substr(token_index, delimiter_index - token_index); + std::string token = input.substr(token_index, delimiter_index - token_index); token_index = delimiter_index + delimiter_length; - result.push_back(token); + callback(token); } auto end_token = input.substr(token_index); if (!end_token.empty()) { - result.push_back(end_token); + callback(end_token); } +} +std::vector split(const std::string&input, const std::string&delimiter) { + std::vector result; + split(input, delimiter, [&result](const std::string& token) { + result.push_back(token); + }); return result; } diff --git a/TactilityCore/Source/Timer.cpp b/TactilityCore/Source/Timer.cpp index 1ce15502..7478ee0c 100644 --- a/TactilityCore/Source/Timer.cpp +++ b/TactilityCore/Source/Timer.cpp @@ -13,7 +13,7 @@ void Timer::onCallback(TimerHandle_t hTimer) { } } -static inline TimerHandle_t createTimer(Timer::Type type, void* timerId, TimerCallbackFunction_t callback) { +static TimerHandle_t createTimer(Timer::Type type, void* timerId, TimerCallbackFunction_t callback) { assert(timerId != nullptr); assert(callback != nullptr); diff --git a/TactilityCore/Source/crypt/Crypt.cpp b/TactilityCore/Source/crypt/Crypt.cpp index 05daafc5..d8d8741c 100644 --- a/TactilityCore/Source/crypt/Crypt.cpp +++ b/TactilityCore/Source/crypt/Crypt.cpp @@ -127,7 +127,7 @@ static void getKey(uint8_t key[32]) { } void getIv(const void* data, size_t dataLength, uint8_t iv[16]) { - memset((void*)iv, 0, 16); + memset(iv, 0, 16); auto* data_bytes = (uint8_t*)data; for (int i = 0; i < dataLength; ++i) { size_t safe_index = i % 16; @@ -161,7 +161,7 @@ static int aes256CryptCbc( return result; } -int encrypt(const uint8_t iv[16], uint8_t* inData, uint8_t* outData, size_t dataLength) { +int encrypt(const uint8_t iv[16], const uint8_t* inData, uint8_t* outData, size_t dataLength) { tt_check(dataLength % 16 == 0, "Length is not a multiple of 16 bytes (for AES 256"); uint8_t key[32]; getKey(key); @@ -173,7 +173,7 @@ int encrypt(const uint8_t iv[16], uint8_t* inData, uint8_t* outData, size_t data return aes256CryptCbc(key, MBEDTLS_AES_ENCRYPT, dataLength, iv_copy, inData, outData); } -int decrypt(const uint8_t iv[16], uint8_t* inData, uint8_t* outData, size_t dataLength) { +int decrypt(const uint8_t iv[16], const uint8_t* inData, uint8_t* outData, size_t dataLength) { tt_check(dataLength % 16 == 0, "Length is not a multiple of 16 bytes (for AES 256"); uint8_t key[32]; getKey(key); diff --git a/TactilityCore/Source/file/File.cpp b/TactilityCore/Source/file/File.cpp index 8ca16dbc..3d778e61 100644 --- a/TactilityCore/Source/file/File.cpp +++ b/TactilityCore/Source/file/File.cpp @@ -2,6 +2,11 @@ #include #include +#include + +namespace tt::hal::sdcard { +class SdCardDevice; +} namespace tt::file { @@ -208,4 +213,13 @@ bool findOrCreateDirectory(std::string path, mode_t mode) { return true; } +bool isFile(const std::string& path) { + return access(path.c_str(), F_OK) == 0; +} + +bool isDirectory(const std::string& path) { + struct stat stat_result; + return stat(path.c_str(), &stat_result) == 0 && S_ISDIR(stat_result.st_mode); +} + } diff --git a/Tests/TactilityCore/ObjectFileTest.cpp b/Tests/Tactility/ObjectFileTest.cpp similarity index 100% rename from Tests/TactilityCore/ObjectFileTest.cpp rename to Tests/Tactility/ObjectFileTest.cpp diff --git a/Tests/Tactility/PropertiesFileTest.cpp b/Tests/Tactility/PropertiesFileTest.cpp new file mode 100644 index 00000000..2face09b --- /dev/null +++ b/Tests/Tactility/PropertiesFileTest.cpp @@ -0,0 +1,30 @@ +#include "../TactilityCore/TestFile.h" +#include "doctest.h" + +#include <../../Tactility/Include/Tactility/file/PropertiesFile.h> + +using namespace tt; + +TEST_CASE("loadPropertiesFile() should return false when the file does not exist") { + std::map properties; + CHECK_EQ(file::loadPropertiesFile("does_not_exist.properties", properties), false); +} + +TEST_CASE("PropertiesFile should parse a valid file properly") { + TestFile file("test.properties"); + file.writeData( + "# Comment\n" // Regular comment + " \t# Comment\n" // Prefixed comment + "key1=value1\n" // Regular property + " \tkey 2\t = \tvalue 2\t " // Property with empty space + ); + + std::map properties; + + // Load data + CHECK_EQ(file::loadPropertiesFile(file.getPath(), properties), true); + + CHECK_EQ(properties.size(), 2); + CHECK_EQ(properties["key1"], "value1"); + CHECK_EQ(properties["key 2"], "value 2"); +} diff --git a/Tests/TactilityCore/FileTest.cpp b/Tests/TactilityCore/FileTest.cpp index 94fa75f3..5e68585e 100644 --- a/Tests/TactilityCore/FileTest.cpp +++ b/Tests/TactilityCore/FileTest.cpp @@ -9,6 +9,6 @@ TEST_CASE("findOrCreateDirectory can create a directory tree without prefix") { } TEST_CASE("findOrCreateDirectory can create a directory tree with prefix") { - CHECK_EQ(file::findOrCreateDirectory("/test2/test2", 0777), true); + CHECK_EQ(file::findOrCreateDirectory("/tmp/test2", 0777), true); // TODO: delete dirs } diff --git a/Tests/TactilityCore/TestFile.h b/Tests/TactilityCore/TestFile.h new file mode 100644 index 00000000..0ad32035 --- /dev/null +++ b/Tests/TactilityCore/TestFile.h @@ -0,0 +1,42 @@ +#pragma once + +#include "doctest.h" +#include + +#include + +/** + * A class for creating test files that can automatically clean themselves up. + */ +class TestFile { + const char* path; + bool autoClean; + +public: + + TestFile(const char* path, bool autoClean = true) : path(path), autoClean(autoClean) { + if (autoClean && exists()) { + remove(); + } + } + + ~TestFile() { + if (autoClean && exists()) { + remove(); + } + } + + const char* getPath() const { return path; } + + void writeData(const char* data) const { + CHECK_EQ(tt::file::writeString(path, data), true); + } + + bool exists() const { + return access(path, F_OK) == 0; + } + + void remove() const { + ::remove(path); + } +}; \ No newline at end of file