diff --git a/Boards/LilygoTLoraPager/Source/hal/TpagerKeyboard.cpp b/Boards/LilygoTLoraPager/Source/hal/TpagerKeyboard.cpp index 092073de..21f40397 100644 --- a/Boards/LilygoTLoraPager/Source/hal/TpagerKeyboard.cpp +++ b/Boards/LilygoTLoraPager/Source/hal/TpagerKeyboard.cpp @@ -239,13 +239,13 @@ void TpagerKeyboard::initEncoder(void) { } pcnt_chan_config_t chan_1_config = { - .edge_gpio_num = ENCODER_A, - .level_gpio_num = ENCODER_B, - }; - pcnt_chan_config_t chan_2_config = { .edge_gpio_num = ENCODER_B, .level_gpio_num = ENCODER_A, }; + pcnt_chan_config_t chan_2_config = { + .edge_gpio_num = ENCODER_A, + .level_gpio_num = ENCODER_B, + }; pcnt_channel_handle_t pcnt_chan_1 = NULL; pcnt_channel_handle_t pcnt_chan_2 = NULL; diff --git a/Data/data/settings.properties b/Data/data/settings.properties new file mode 100644 index 00000000..0b905db8 --- /dev/null +++ b/Data/data/settings.properties @@ -0,0 +1,2 @@ +language=en-US +timeFormat24h=true \ No newline at end of file diff --git a/Data/system/app/Launcher/i18n/en-GB.i18n b/Data/system/app/Launcher/i18n/en-GB.i18n new file mode 100644 index 00000000..5226a6e5 --- /dev/null +++ b/Data/system/app/Launcher/i18n/en-GB.i18n @@ -0,0 +1,3 @@ +Apps +Files +Settings diff --git a/Data/system/app/Launcher/i18n/en-US.i18n b/Data/system/app/Launcher/i18n/en-US.i18n new file mode 100644 index 00000000..5226a6e5 --- /dev/null +++ b/Data/system/app/Launcher/i18n/en-US.i18n @@ -0,0 +1,3 @@ +Apps +Files +Settings diff --git a/Data/system/app/Launcher/i18n/fr-FR.i18n b/Data/system/app/Launcher/i18n/fr-FR.i18n new file mode 100644 index 00000000..663a29ea --- /dev/null +++ b/Data/system/app/Launcher/i18n/fr-FR.i18n @@ -0,0 +1,3 @@ +Appli +Fichiers +Réglages diff --git a/Data/system/app/Launcher/i18n/nl-BE.i18n b/Data/system/app/Launcher/i18n/nl-BE.i18n new file mode 100644 index 00000000..89a1e21f --- /dev/null +++ b/Data/system/app/Launcher/i18n/nl-BE.i18n @@ -0,0 +1,3 @@ +Apps +Bestanden +Instellingen diff --git a/Data/system/app/Launcher/i18n/nl-NL.i18n b/Data/system/app/Launcher/i18n/nl-NL.i18n new file mode 100644 index 00000000..89a1e21f --- /dev/null +++ b/Data/system/app/Launcher/i18n/nl-NL.i18n @@ -0,0 +1,3 @@ +Apps +Bestanden +Instellingen diff --git a/Data/system/app/LocaleSettings/i18n/en-GB.i18n b/Data/system/app/LocaleSettings/i18n/en-GB.i18n new file mode 100644 index 00000000..ab750d45 --- /dev/null +++ b/Data/system/app/LocaleSettings/i18n/en-GB.i18n @@ -0,0 +1,7 @@ +Dutch (Netherlands) +Dutch (Belgium) +English (United States) +English (United Kingdom) +French (France) +Region +Language diff --git a/Data/system/app/LocaleSettings/i18n/en-US.i18n b/Data/system/app/LocaleSettings/i18n/en-US.i18n new file mode 100644 index 00000000..ab750d45 --- /dev/null +++ b/Data/system/app/LocaleSettings/i18n/en-US.i18n @@ -0,0 +1,7 @@ +Dutch (Netherlands) +Dutch (Belgium) +English (United States) +English (United Kingdom) +French (France) +Region +Language diff --git a/Data/system/app/LocaleSettings/i18n/fr-FR.i18n b/Data/system/app/LocaleSettings/i18n/fr-FR.i18n new file mode 100644 index 00000000..209b263a --- /dev/null +++ b/Data/system/app/LocaleSettings/i18n/fr-FR.i18n @@ -0,0 +1,7 @@ +Néerlandais (Pays-Bas) +Néerlandais (Belgique) +Anglais (États-Unis) +Anglais (Royaume-Uni) +Français (France) +Région +Langue diff --git a/Data/system/app/LocaleSettings/i18n/nl-BE.i18n b/Data/system/app/LocaleSettings/i18n/nl-BE.i18n new file mode 100644 index 00000000..39900b4b --- /dev/null +++ b/Data/system/app/LocaleSettings/i18n/nl-BE.i18n @@ -0,0 +1,7 @@ +Nederlands (Nederland) +Nederlands (België) +Engels (Verenigde Staten) +Engels (Verenigd Koninkrijk) +Frans (Frankrijk) +Regio +Taal diff --git a/Data/system/app/LocaleSettings/i18n/nl-NL.i18n b/Data/system/app/LocaleSettings/i18n/nl-NL.i18n new file mode 100644 index 00000000..39900b4b --- /dev/null +++ b/Data/system/app/LocaleSettings/i18n/nl-NL.i18n @@ -0,0 +1,7 @@ +Nederlands (Nederland) +Nederlands (België) +Engels (Verenigde Staten) +Engels (Verenigd Koninkrijk) +Frans (Frankrijk) +Regio +Taal diff --git a/Data/system/i18n/core/en-GB.i18n b/Data/system/i18n/core/en-GB.i18n new file mode 100644 index 00000000..a04e3142 --- /dev/null +++ b/Data/system/i18n/core/en-GB.i18n @@ -0,0 +1,7 @@ +OK +Yes +No +Cancel +Retry +Close +Open diff --git a/Data/system/i18n/core/en-US.i18n b/Data/system/i18n/core/en-US.i18n new file mode 100644 index 00000000..a04e3142 --- /dev/null +++ b/Data/system/i18n/core/en-US.i18n @@ -0,0 +1,7 @@ +OK +Yes +No +Cancel +Retry +Close +Open diff --git a/Data/system/i18n/core/fr-FR.i18n b/Data/system/i18n/core/fr-FR.i18n new file mode 100644 index 00000000..2f035013 --- /dev/null +++ b/Data/system/i18n/core/fr-FR.i18n @@ -0,0 +1,7 @@ +OK +Oui +Non +Annuler +Réessayer +Fermer +Ouvrir diff --git a/Data/system/i18n/core/nl-BE.i18n b/Data/system/i18n/core/nl-BE.i18n new file mode 100644 index 00000000..1a5002f4 --- /dev/null +++ b/Data/system/i18n/core/nl-BE.i18n @@ -0,0 +1,7 @@ +OK +Ja +Nee +Annuleren +Opnieuw +Sluiten +Openen diff --git a/Data/system/i18n/core/nl-NL.i18n b/Data/system/i18n/core/nl-NL.i18n new file mode 100644 index 00000000..1a5002f4 --- /dev/null +++ b/Data/system/i18n/core/nl-NL.i18n @@ -0,0 +1,7 @@ +OK +Ja +Nee +Annuleren +Opnieuw +Sluiten +Openen diff --git a/Documentation/ideas.md b/Documentation/ideas.md index 75719885..e055f691 100644 --- a/Documentation/ideas.md +++ b/Documentation/ideas.md @@ -4,15 +4,15 @@ - 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. - 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. +- Fix Development service: when no SD card is present, the app fails to install. Consider installing to `/data` +- Refactor `PropertiesFile.cpp` to use `tt::file::readLines()` (see TODO in code) +- Localize all apps ## Lower Priority diff --git a/Tactility/Include/Tactility/i18n/CoreTextResources.h b/Tactility/Include/Tactility/i18n/CoreTextResources.h new file mode 100644 index 00000000..ad389b83 --- /dev/null +++ b/Tactility/Include/Tactility/i18n/CoreTextResources.h @@ -0,0 +1,19 @@ +#pragma once + +#include "Tactility/i18n/TextResources.h" + +// WARNING: This file is auto-generated. Do not edit manually. + +namespace tt::i18n::core { + +enum class Text { + OK = 0, + YES = 1, + NO = 2, + CANCEL = 3, + RETRY = 4, + CLOSE = 5, + OPEN = 6, +}; + +} diff --git a/Tactility/Include/Tactility/i18n/TextResources.h b/Tactility/Include/Tactility/i18n/TextResources.h new file mode 100644 index 00000000..4e016443 --- /dev/null +++ b/Tactility/Include/Tactility/i18n/TextResources.h @@ -0,0 +1,49 @@ +#pragma once + +#include +#include + +namespace tt::i18n { + +/** + * Holds localized text data. + * + * It is used with data generated from Translations/ with the python generation scripts. + * It's used with a header file that specifies the indexes, and generated text files (.i18n) + */ +class TextResources { + + std::vector data; + std::string path; + static std::string ERROR_RESULT; + +public: + /** + * @param[in] path + */ + TextResources(const std::string& path) : path(path) {} + + const std::string& get(const int index) const { + if (index < data.size()) { + return data[index]; + } else { + return ERROR_RESULT; + } + } + + template + const std::string& get(EnumType value) const { return get(static_cast(value)); } + + const std::string& operator[](const int index) const { return get(index); } + + template + const std::string& operator[](const EnumType index) const { return get(index); } + + /** + * Load or reload an i18n file with the system's current locale settings. + * @return true on success + */ + bool load(); +}; + +} \ No newline at end of file diff --git a/Tactility/Include/Tactility/settings/Language.h b/Tactility/Include/Tactility/settings/Language.h new file mode 100644 index 00000000..953cb281 --- /dev/null +++ b/Tactility/Include/Tactility/settings/Language.h @@ -0,0 +1,24 @@ +#pragma once + +#include + +namespace tt::settings { + +enum class Language { + en_GB, + en_US, + fr_FR, + nl_BE, + nl_NL, + count +}; + +void setLanguage(Language language); + +Language getLanguage(); + +std::string toString(Language language); + +bool fromString(const std::string& text, Language& language); + +} diff --git a/Tactility/Include/Tactility/settings/SettingsProperties.h b/Tactility/Include/Tactility/settings/SettingsProperties.h new file mode 100644 index 00000000..3161c81d --- /dev/null +++ b/Tactility/Include/Tactility/settings/SettingsProperties.h @@ -0,0 +1,16 @@ +#pragma once + +#include "Language.h" + +namespace tt::settings { + +struct SettingsProperties { + Language language; + bool timeFormat24h; +}; + +bool loadSettingsProperties(SettingsProperties& properties); + +bool saveSettingsProperties(const SettingsProperties& properties); + +} diff --git a/Tactility/Include/Tactility/time/Time.h b/Tactility/Include/Tactility/settings/Time.h similarity index 96% rename from Tactility/Include/Tactility/time/Time.h rename to Tactility/Include/Tactility/settings/Time.h index 88448e4f..40ff450f 100644 --- a/Tactility/Include/Tactility/time/Time.h +++ b/Tactility/Include/Tactility/settings/Time.h @@ -2,7 +2,7 @@ #include -namespace tt::time { +namespace tt::settings { /** * Set the timezone diff --git a/Tactility/Private/Tactility/app/launcher/TextResources.h b/Tactility/Private/Tactility/app/launcher/TextResources.h new file mode 100644 index 00000000..efb5acd9 --- /dev/null +++ b/Tactility/Private/Tactility/app/launcher/TextResources.h @@ -0,0 +1,15 @@ +#pragma once + +#include "Tactility/i18n/TextResources.h" + +// WARNING: This file is auto-generated. Do not edit manually. + +namespace tt::app::launcher::i18n { + +enum class Text { + APPS = 0, + FILES = 1, + SETTINGS = 2, +}; + +} diff --git a/Tactility/Private/Tactility/app/localesettings/LocaleSettings.h b/Tactility/Private/Tactility/app/localesettings/LocaleSettings.h new file mode 100644 index 00000000..5fd746c0 --- /dev/null +++ b/Tactility/Private/Tactility/app/localesettings/LocaleSettings.h @@ -0,0 +1,7 @@ +#pragma once + +namespace tt::app::localesettings { + +void start(); + +} \ No newline at end of file diff --git a/Tactility/Private/Tactility/app/localesettings/TextResources.h b/Tactility/Private/Tactility/app/localesettings/TextResources.h new file mode 100644 index 00000000..08728900 --- /dev/null +++ b/Tactility/Private/Tactility/app/localesettings/TextResources.h @@ -0,0 +1,19 @@ +#pragma once + +#include "Tactility/i18n/TextResources.h" + +// WARNING: This file is auto-generated. Do not edit manually. + +namespace tt::app::localesettings::i18n { + +enum class Text { + NL_NL = 0, + NL_BE = 1, + EN_US = 2, + EN_GB = 3, + FR_FR = 4, + REGION = 5, + LANGUAGE = 6, +}; + +} diff --git a/Tactility/Private/Tactility/settings/TimePrivate.h b/Tactility/Private/Tactility/settings/TimePrivate.h new file mode 100644 index 00000000..1968e5e2 --- /dev/null +++ b/Tactility/Private/Tactility/settings/TimePrivate.h @@ -0,0 +1,7 @@ +#pragma once + +namespace tt::settings { + +void initTimeZone(); + +} diff --git a/Tactility/Private/Tactility/time/TimePrivate.h b/Tactility/Private/Tactility/time/TimePrivate.h deleted file mode 100644 index 92b13222..00000000 --- a/Tactility/Private/Tactility/time/TimePrivate.h +++ /dev/null @@ -1,7 +0,0 @@ -#pragma once - -namespace tt::time { - -void init(); - -} diff --git a/Tactility/Source/PartitionsEsp.cpp b/Tactility/Source/PartitionsEsp.cpp index cf9f7bbf..51225ba8 100644 --- a/Tactility/Source/PartitionsEsp.cpp +++ b/Tactility/Source/PartitionsEsp.cpp @@ -9,7 +9,7 @@ namespace tt { -static const char* TAG = "partitions"; +static const char* TAG = "Partitions"; static esp_err_t initNvsFlashSafely() { esp_err_t result = nvs_flash_init(); @@ -22,13 +22,27 @@ static esp_err_t initNvsFlashSafely() { static wl_handle_t data_wl_handle = WL_INVALID_HANDLE; +size_t getSectorSize() { +#if defined(CONFIG_FATFS_SECTOR_512) + return 512; +#elif defined(CONFIG_FATFS_SECTOR_1024) + return 1024; +#elif defined(CONFIG_FATFS_SECTOR_2048) + return 2048; +#elif defined(CONFIG_FATFS_SECTOR_4096) + return 4096; +#else +#error Not implemented +#endif +} + esp_err_t initPartitionsEsp() { ESP_ERROR_CHECK(initNvsFlashSafely()); const esp_vfs_fat_mount_config_t mount_config = { .format_if_mount_failed = false, .max_files = 4, - .allocation_unit_size = CONFIG_WL_SECTOR_SIZE, + .allocation_unit_size = getSectorSize(), .disk_status_check_enable = false, .use_one_fat = true, }; diff --git a/Tactility/Source/Tactility.cpp b/Tactility/Source/Tactility.cpp index 8cb64c69..5250348c 100644 --- a/Tactility/Source/Tactility.cpp +++ b/Tactility/Source/Tactility.cpp @@ -47,6 +47,7 @@ namespace app { namespace imageviewer { extern const AppManifest manifest; } namespace inputdialog { extern const AppManifest manifest; } namespace launcher { extern const AppManifest manifest; } + namespace localesettings { extern const AppManifest manifest; } namespace log { extern const AppManifest manifest; } namespace notes { extern const AppManifest manifest; } namespace power { extern const AppManifest manifest; } @@ -89,6 +90,7 @@ static void registerSystemApps() { addApp(app::imageviewer::manifest); addApp(app::inputdialog::manifest); addApp(app::launcher::manifest); + addApp(app::localesettings::manifest); addApp(app::log::manifest); addApp(app::notes::manifest); addApp(app::serialconsole::manifest); diff --git a/Tactility/Source/TactilityHeadless.cpp b/Tactility/Source/TactilityHeadless.cpp index 97570836..a827e5ab 100644 --- a/Tactility/Source/TactilityHeadless.cpp +++ b/Tactility/Source/TactilityHeadless.cpp @@ -6,7 +6,7 @@ #include "Tactility/service/ServiceRegistration.h" #include -#include +#include #ifdef ESP_PLATFORM #include "Tactility/InitEsp.h" @@ -45,7 +45,7 @@ void initHeadless(const hal::Configuration& config) { initEsp(); #endif hardwareConfig = &config; - time::init(); + settings::initTimeZone(); hal::init(config); network::ntp::init(); registerAndStartSystemServices(); diff --git a/Tactility/Source/app/ElfApp.cpp b/Tactility/Source/app/ElfApp.cpp index 3653d649..298ad7a4 100644 --- a/Tactility/Source/app/ElfApp.cpp +++ b/Tactility/Source/app/ElfApp.cpp @@ -12,6 +12,7 @@ #include #include +#include namespace tt::app { @@ -39,10 +40,18 @@ class ElfApp : public App { const std::string filePath; std::unique_ptr elfFileData; - esp_elf_t elf; + esp_elf_t elf { + .psegment = nullptr, + .svaddr = 0, + .ptext = nullptr, + .pdata = nullptr, + .sec = { }, + .entry = nullptr + }; bool shouldCleanupElf = false; // Whether we have to clean up the above "elf" object std::unique_ptr manifest; void* data = nullptr; + std::string lastError = ""; bool startElf() { TT_LOG_I(TAG, "Starting ELF %s", filePath.c_str()); @@ -58,14 +67,17 @@ class ElfApp : public App { } if (esp_elf_init(&elf) != ESP_OK) { - TT_LOG_E(TAG, "Failed to initialize"); + lastError = "Failed to initialize"; + TT_LOG_E(TAG, "%s", lastError.c_str()); elfFileData = nullptr; return false; } - if (esp_elf_relocate(&elf, elfFileData.get()) != ESP_OK) { - TT_LOG_E(TAG, "Failed to load executable"); - esp_elf_deinit(&elf); + auto relocate_result = esp_elf_relocate(&elf, elfFileData.get()); + if (relocate_result != 0) { + // Note: the result code mapes to values from cstdlib's errno.h + lastError = std::format("Failed to load executable (error code {})", -relocate_result); + TT_LOG_E(TAG, "%s", lastError.c_str()); elfFileData = nullptr; return false; } @@ -74,7 +86,8 @@ class ElfApp : public App { char* argv[] = {}; if (esp_elf_request(&elf, 0, argc, argv) != ESP_OK) { - TT_LOG_W(TAG, "Executable returned error code"); + lastError = "Executable returned error code"; + TT_LOG_E(TAG, "%s", lastError.c_str()); esp_elf_deinit(&elf); elfFileData = nullptr; return false; @@ -106,22 +119,29 @@ public: 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(); - } - - if (manifest->onCreate != nullptr) { - manifest->onCreate(&appContext, data); - } - } - } else { + elfManifestSetCount = 0; + if (!startElf()) { service::loader::stopApp(); + auto message = lastError.empty() ? "Application failed to start." : std::format("Application failed to start: {}", lastError); + alertdialog::start("Error", message); + return; + } + + if (elfManifestSetCount == 0) { + service::loader::stopApp(); + alertdialog::start("Error", "Application failed to start: application failed to register itself"); + return; + } + + manifest = std::make_unique(elfManifest); + lock.unlock(); + + if (manifest->createData != nullptr) { + data = manifest->createData(); + } + + if (manifest->onCreate != nullptr) { + manifest->onCreate(&appContext, data); } } diff --git a/Tactility/Source/app/launcher/Launcher.cpp b/Tactility/Source/app/launcher/Launcher.cpp index af2e8ff5..1c994cc7 100644 --- a/Tactility/Source/app/launcher/Launcher.cpp +++ b/Tactility/Source/app/launcher/Launcher.cpp @@ -1,4 +1,5 @@ #include "Tactility/app/AppContext.h" +#include "Tactility/app/launcher/TextResources.h" #include "Tactility/app/AppRegistration.h" #include "Tactility/service/loader/Loader.h" @@ -7,10 +8,10 @@ #include #include -#define TAG "launcher" - namespace tt::app::launcher { +constexpr auto* TAG = "Launcher"; + static void onAppPressed(TT_UNUSED lv_event_t* e) { auto* appId = (const char*)lv_event_get_user_data(e); service::loader::startApp(appId); @@ -52,6 +53,7 @@ static lv_obj_t* createAppButton(lv_obj_t* parent, const char* title, const char } class LauncherApp : public App { + tt::i18n::TextResources textResources = tt::i18n::TextResources("/system/app/Launcher/i18n"); void onCreate(TT_UNUSED AppContext& app) override { BootProperties boot_properties; @@ -62,6 +64,8 @@ class LauncherApp : public App { } void onShow(TT_UNUSED AppContext& app, lv_obj_t* parent) override { + textResources.load(); + auto* wrapper = lv_obj_create(parent); lv_obj_align(wrapper, LV_ALIGN_CENTER, 0, 0); @@ -87,9 +91,14 @@ class LauncherApp : public App { auto apps_icon_path = paths->getSystemPathLvgl("icon_apps.png"); auto files_icon_path = paths->getSystemPathLvgl("icon_files.png"); auto settings_icon_path = paths->getSystemPathLvgl("icon_settings.png"); - createAppButton(wrapper, "Apps", apps_icon_path.c_str(), "AppList", 0); - createAppButton(wrapper, "Files", files_icon_path.c_str(), "Files", padding); - createAppButton(wrapper, "Settings", settings_icon_path.c_str(), "Settings", padding); + + const auto& apps_title = textResources[i18n::Text::APPS]; + const auto& files_title = textResources[i18n::Text::FILES]; + const auto& settings_title = textResources[i18n::Text::SETTINGS]; + + createAppButton(wrapper, apps_title.c_str(), apps_icon_path.c_str(), "AppList", 0); + createAppButton(wrapper, files_title.c_str(), files_icon_path.c_str(), "Files", padding); + createAppButton(wrapper, settings_title.c_str(), settings_icon_path.c_str(), "Settings", padding); } }; diff --git a/Tactility/Source/app/localesettings/LocaleSettings.cpp b/Tactility/Source/app/localesettings/LocaleSettings.cpp new file mode 100644 index 00000000..cb4bb560 --- /dev/null +++ b/Tactility/Source/app/localesettings/LocaleSettings.cpp @@ -0,0 +1,168 @@ +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +namespace tt::app::localesettings { + +constexpr auto* TAG = "LocaleSettings"; + +extern const AppManifest manifest; + +class LocaleSettingsApp : public App { + tt::i18n::TextResources textResources = tt::i18n::TextResources("/system/app/LocaleSettings/i18n"); + Mutex mutex = Mutex(Mutex::Type::Recursive); + lv_obj_t* timeZoneLabel = nullptr; + lv_obj_t* regionLabel = nullptr; + lv_obj_t* languageDropdown = nullptr; + lv_obj_t* languageLabel = nullptr; + + static void onConfigureTimeZonePressed(TT_UNUSED lv_event_t* event) { + timezone::start(); + } + + std::map languageMap; + + std::string getLanguageOptions() const { + std::vector items; + for (int i = 0; i < static_cast(settings::Language::count); i++) { + switch (static_cast(i)) { + case settings::Language::en_GB: + items.push_back(textResources[i18n::Text::EN_GB]); + break; + case settings::Language::en_US: + items.push_back(textResources[i18n::Text::EN_US]); + break; + case settings::Language::fr_FR: + items.push_back(textResources[i18n::Text::FR_FR]); + break; + case settings::Language::nl_BE: + items.push_back(textResources[i18n::Text::NL_BE]); + break; + case settings::Language::nl_NL: + items.push_back(textResources[i18n::Text::NL_NL]); + break; + case settings::Language::count: + break; + } + } + return string::join(items, "\n"); + } + + void updateViews() { + textResources.load(); + + lv_label_set_text(regionLabel , textResources[i18n::Text::REGION].c_str()); + lv_label_set_text(languageLabel, textResources[i18n::Text::LANGUAGE].c_str()); + + std::string language_options = getLanguageOptions(); + lv_dropdown_set_options(languageDropdown, language_options.c_str()); + lv_dropdown_set_selected(languageDropdown, static_cast(settings::getLanguage())); + } + + static void onLanguageSet(lv_event_t* event) { + auto* dropdown = static_cast(lv_event_get_target(event)); + auto index = lv_dropdown_get_selected(dropdown); + auto language = static_cast(index); + settings::setLanguage(language); + + auto* self = static_cast(lv_event_get_user_data(event)); + self->updateViews(); + } + +public: + + void onShow(AppContext& app, lv_obj_t* parent) override { + textResources.load(); + + lv_obj_set_flex_flow(parent, LV_FLEX_FLOW_COLUMN); + + lvgl::toolbar_create(parent, app); + + auto* main_wrapper = lv_obj_create(parent); + lv_obj_set_flex_flow(main_wrapper, LV_FLEX_FLOW_COLUMN); + lv_obj_set_width(main_wrapper, LV_PCT(100)); + lv_obj_set_flex_grow(main_wrapper, 1); + + auto* region_wrapper = lv_obj_create(main_wrapper); + lv_obj_set_width(region_wrapper, LV_PCT(100)); + lv_obj_set_height(region_wrapper, LV_SIZE_CONTENT); + lv_obj_set_style_pad_all(region_wrapper, 0, 0); + lv_obj_set_style_border_width(region_wrapper, 0, 0); + + regionLabel = lv_label_create(region_wrapper); + lv_label_set_text(regionLabel , textResources[i18n::Text::REGION].c_str()); + lv_obj_align(regionLabel , LV_ALIGN_LEFT_MID, 0, 0); + + timeZoneLabel = lv_label_create(region_wrapper); + std::string timeZoneName = settings::getTimeZoneName(); + if (timeZoneName.empty()) { + timeZoneName = "not set"; + } + lv_label_set_text(timeZoneLabel, timeZoneName.c_str()); + // TODO: Find out why Y offset is needed + lv_obj_align_to(timeZoneLabel, regionLabel, LV_ALIGN_OUT_RIGHT_MID, 10, 8); + + auto* region_button = lv_button_create(region_wrapper); + lv_obj_align(region_button, LV_ALIGN_TOP_RIGHT, 0, 0); + auto* region_button_image = lv_image_create(region_button); + lv_obj_add_event_cb(region_button, onConfigureTimeZonePressed, LV_EVENT_SHORT_CLICKED, nullptr); + lv_image_set_src(region_button_image, LV_SYMBOL_SETTINGS); + + auto* language_wrapper = lv_obj_create(main_wrapper); + lv_obj_set_width(language_wrapper, LV_PCT(100)); + lv_obj_set_height(language_wrapper, LV_SIZE_CONTENT); + lv_obj_set_style_pad_all(language_wrapper, 0, 0); + lv_obj_set_style_border_width(language_wrapper, 0, 0); + + languageLabel = lv_label_create(language_wrapper); + lv_label_set_text(languageLabel, textResources[i18n::Text::LANGUAGE].c_str()); + lv_obj_align(languageLabel, LV_ALIGN_LEFT_MID, 0, 0); + + languageDropdown = lv_dropdown_create(language_wrapper); + lv_obj_align(languageDropdown, LV_ALIGN_RIGHT_MID, 0, 0); + std::string language_options = getLanguageOptions(); + lv_dropdown_set_options(languageDropdown, language_options.c_str()); + lv_dropdown_set_selected(languageDropdown, static_cast(settings::getLanguage())); + lv_obj_add_event_cb(languageDropdown, onLanguageSet, LV_EVENT_VALUE_CHANGED, this); + } + + void onResult(AppContext& app, TT_UNUSED LaunchId launchId, Result result, std::unique_ptr bundle) override { + if (result == Result::Ok && bundle != nullptr) { + const auto name = timezone::getResultName(*bundle); + const auto code = timezone::getResultCode(*bundle); + TT_LOG_I(TAG, "Result name=%s code=%s", name.c_str(), code.c_str()); + settings::setTimeZone(name, code); + + if (!name.empty()) { + if (lvgl::lock(100 / portTICK_PERIOD_MS)) { + lv_label_set_text(timeZoneLabel, name.c_str()); + lvgl::unlock(); + } + } + } + } +}; + +extern const AppManifest manifest = { + .id = "LocaleSettings", + .name = "Region & Language", + .icon = TT_ASSETS_APP_ICON_TIME_DATE_SETTINGS, + .type = Type::Settings, + .createApp = create +}; + +void start() { + service::loader::startApp(manifest.id); +} + +} // namespace diff --git a/Tactility/Source/app/timedatesettings/TimeDateSettings.cpp b/Tactility/Source/app/timedatesettings/TimeDateSettings.cpp index f9290374..95e4eb57 100644 --- a/Tactility/Source/app/timedatesettings/TimeDateSettings.cpp +++ b/Tactility/Source/app/timedatesettings/TimeDateSettings.cpp @@ -1,33 +1,25 @@ -#include "Tactility/app/timezone/TimeZone.h" -#include "Tactility/lvgl/Toolbar.h" -#include "Tactility/service/loader/Loader.h" -#include "Tactility/lvgl/LvglSync.h" - #include -#include +#include +#include +#include +#include + #include -#define TAG "text_viewer" - namespace tt::app::timedatesettings { +constexpr auto* TAG = "TimeDate"; + extern const AppManifest manifest; class TimeDateSettingsApp : public App { -private: - Mutex mutex = Mutex(Mutex::Type::Recursive); - lv_obj_t* regionLabelWidget = nullptr; - - static void onConfigureTimeZonePressed(TT_UNUSED lv_event_t* event) { - timezone::start(); - } static void onTimeFormatChanged(lv_event_t* event) { auto* widget = lv_event_get_target_obj(event); bool show_24 = lv_obj_has_state(widget, LV_STATE_CHECKED); - time::setTimeFormat24Hour(show_24); + settings::setTimeFormat24Hour(show_24); } public: @@ -42,32 +34,6 @@ public: lv_obj_set_width(main_wrapper, LV_PCT(100)); lv_obj_set_flex_grow(main_wrapper, 1); - auto* region_wrapper = lv_obj_create(main_wrapper); - lv_obj_set_width(region_wrapper, LV_PCT(100)); - lv_obj_set_height(region_wrapper, LV_SIZE_CONTENT); - lv_obj_set_style_pad_all(region_wrapper, 0, 0); - lv_obj_set_style_border_width(region_wrapper, 0, 0); - - auto* region_prefix_label = lv_label_create(region_wrapper); - lv_label_set_text(region_prefix_label, "Region: "); - lv_obj_align(region_prefix_label, LV_ALIGN_LEFT_MID, 0, 0); - - auto* region_label = lv_label_create(region_wrapper); - std::string timeZoneName = time::getTimeZoneName(); - if (timeZoneName.empty()) { - timeZoneName = "not set"; - } - regionLabelWidget = region_label; - lv_label_set_text(region_label, timeZoneName.c_str()); - // TODO: Find out why Y offset is needed - lv_obj_align_to(region_label, region_prefix_label, LV_ALIGN_OUT_RIGHT_MID, 0, 8); - - auto* region_button = lv_button_create(region_wrapper); - lv_obj_align(region_button, LV_ALIGN_TOP_RIGHT, 0, 0); - auto* region_button_image = lv_image_create(region_button); - lv_obj_add_event_cb(region_button, onConfigureTimeZonePressed, LV_EVENT_SHORT_CLICKED, nullptr); - lv_image_set_src(region_button_image, LV_SYMBOL_SETTINGS); - auto* time_format_wrapper = lv_obj_create(main_wrapper); lv_obj_set_width(time_format_wrapper, LV_PCT(100)); lv_obj_set_height(time_format_wrapper, LV_SIZE_CONTENT); @@ -81,28 +47,12 @@ public: auto* time_24h_switch = lv_switch_create(time_format_wrapper); lv_obj_align(time_24h_switch, LV_ALIGN_RIGHT_MID, 0, 0); lv_obj_add_event_cb(time_24h_switch, onTimeFormatChanged, LV_EVENT_VALUE_CHANGED, nullptr); - if (time::isTimeFormat24Hour()) { + if (settings::isTimeFormat24Hour()) { lv_obj_add_state(time_24h_switch, LV_STATE_CHECKED); } else { lv_obj_remove_state(time_24h_switch, LV_STATE_CHECKED); } } - - void onResult(AppContext& app, TT_UNUSED LaunchId launchId, Result result, std::unique_ptr bundle) override { - if (result == Result::Ok && bundle != nullptr) { - auto name = timezone::getResultName(*bundle); - auto code = timezone::getResultCode(*bundle); - TT_LOG_I(TAG, "Result name=%s code=%s", name.c_str(), code.c_str()); - time::setTimeZone(name, code); - - if (!name.empty()) { - if (lvgl::lock(100 / portTICK_PERIOD_MS)) { - lv_label_set_text(regionLabelWidget, name.c_str()); - lvgl::unlock(); - } - } - } - } }; extern const AppManifest manifest = { diff --git a/Tactility/Source/file/PropertiesFile.cpp b/Tactility/Source/file/PropertiesFile.cpp index 283fa903..088f44d8 100644 --- a/Tactility/Source/file/PropertiesFile.cpp +++ b/Tactility/Source/file/PropertiesFile.cpp @@ -31,6 +31,7 @@ bool loadPropertiesFile(const std::string& filePath, std::function + +#include +#include +#include +#include + +namespace tt::i18n { + +constexpr auto* TAG = "I18n"; + +static std::string getFallbackLocale() { + return "en-US"; +} + +static std::string getDesiredLocale() { + switch (settings::getLanguage()) { + case settings::Language::en_GB: + return "en-GB"; + case settings::Language::en_US: + return "en-US"; + case settings::Language::fr_FR: + return "fr-FR"; + case settings::Language::nl_BE: + return "nl-BE"; + case settings::Language::nl_NL: + return "nl-NL"; + default: + return getFallbackLocale(); + } +} + +static std::string getI18nDataFilePath(const std::string& path) { + auto locale = getDesiredLocale(); + auto desired_file_path = std::format("{}/{}.i18n", path, locale); + if (file::isFile(desired_file_path)) { + return desired_file_path; + } else { + TT_LOG_W(TAG, "Translations not found for %s at %s", locale.c_str(), desired_file_path.c_str()); + } + + auto fallback_locale = getFallbackLocale(); + auto fallback_file_path = std::format("{}/{}.i18n", path, getFallbackLocale()); + if (file::isFile(fallback_file_path)) { + return fallback_file_path; + } else { + TT_LOG_W(TAG, "Fallback translations not found for %s at %s", fallback_locale.c_str(), fallback_file_path.c_str()); + return ""; + } +} + +std::string TextResources::ERROR_RESULT = "TXT_RES_ERROR"; + +bool TextResources::load() { + std::vector new_data; + + // Resolve the language file that we need (depends on system language selection) + auto file_path = getI18nDataFilePath(path); + if (file_path.empty()) { + TT_LOG_E(TAG, "Couldn't find i18n data for %s", path.c_str()); + return false; + } + + file::withLock(file_path, [&file_path, &new_data] { + file::readLines(file_path, true, [&new_data](const char* line) { + new_data.push_back(line); + }); + }); + + if (new_data.empty()) { + TT_LOG_E(TAG, "Couldn't find i18n data for %s", path.c_str()); + return false; + } + + data = std::move(new_data); + return true; +} + +} diff --git a/Tactility/Source/lvgl/Statusbar.cpp b/Tactility/Source/lvgl/Statusbar.cpp index 7e9e39b1..68ca0deb 100644 --- a/Tactility/Source/lvgl/Statusbar.cpp +++ b/Tactility/Source/lvgl/Statusbar.cpp @@ -10,7 +10,7 @@ #include #include #include -#include +#include #include @@ -199,7 +199,7 @@ lv_obj_t* statusbar_create(lv_obj_t* parent) { static void update_time(Statusbar* statusbar) { if (statusbar_data.time_set) { - bool format24 = time::isTimeFormat24Hour(); + bool format24 = settings::isTimeFormat24Hour(); int hours = format24 ? statusbar_data.time_hours : statusbar_data.time_hours % 12; lv_label_set_text_fmt(statusbar->time, "%d:%02d", hours, statusbar_data.time_minutes); } else { diff --git a/Tactility/Source/service/development/DevelopmentService.cpp b/Tactility/Source/service/development/DevelopmentService.cpp index 959f8efb..6091816f 100644 --- a/Tactility/Source/service/development/DevelopmentService.cpp +++ b/Tactility/Source/service/development/DevelopmentService.cpp @@ -18,6 +18,7 @@ #include #include #include +#include namespace tt::service::development { @@ -198,6 +199,11 @@ esp_err_t DevelopmentService::handleAppRun(httpd_req_t* request) { auto app_id = id_key_pos->second; if (app_id.ends_with(".app.elf")) { + if (!file::isFile(app_id)) { + TT_LOG_W(TAG, "[400] /app/run cannot find app %s", app_id.c_str()); + httpd_resp_send_err(request, HTTPD_400_BAD_REQUEST, "app not found"); + return ESP_FAIL; + } app::registerElfApp(app_id); app_id = app::getElfAppId(app_id); } else if (!app::findAppById(app_id.c_str())) { diff --git a/Tactility/Source/service/wifi/WifiSettings.cpp b/Tactility/Source/service/wifi/WifiSettings.cpp index 4ce1a75a..d2bd369c 100644 --- a/Tactility/Source/service/wifi/WifiSettings.cpp +++ b/Tactility/Source/service/wifi/WifiSettings.cpp @@ -2,7 +2,7 @@ #include "Tactility/Preferences.h" #include "Tactility/file/PropertiesFile.h" -#include +#include #include namespace tt::service::wifi::settings { diff --git a/Tactility/Source/settings/Language.cpp b/Tactility/Source/settings/Language.cpp new file mode 100644 index 00000000..9a0b6dc8 --- /dev/null +++ b/Tactility/Source/settings/Language.cpp @@ -0,0 +1,65 @@ +#include +#include +#include +#include + +namespace tt::settings { + +constexpr auto* TAG = "Language"; + +void setLanguage(Language newLanguage) { + SettingsProperties properties; + if (!loadSettingsProperties(properties)) { + return; + } + + properties.language = newLanguage; + saveSettingsProperties(properties); +} + +Language getLanguage() { + SettingsProperties properties; + if (!loadSettingsProperties(properties)) { + return Language::en_US; + } else { + return properties.language; + } +} + +std::string toString(Language language) { + switch (language) { + case Language::en_GB: + return "en-GB"; + case Language::en_US: + return "en-US"; + case Language::fr_FR: + return "fr-FR"; + case Language::nl_BE: + return "nl-BE"; + case Language::nl_NL: + return "nl-NL"; + default: + TT_LOG_E(TAG, "Missing serialization for language %d", static_cast(language)); + std::unreachable(); + } +} + +bool fromString(const std::string& text, Language& language) { + if (text == "en-GB") { + language = Language::en_GB; + } else if (text == "en-US") { + language = Language::en_US; + } else if (text == "fr-FR") { + language = Language::fr_FR; + } else if (text == "nl-BE") { + language = Language::nl_BE; + } else if (text == "nl-NL") { + language = Language::nl_NL; + } else { + return false; + } + + return true; +} + +} diff --git a/Tactility/Source/settings/SettingsProperties.cpp b/Tactility/Source/settings/SettingsProperties.cpp new file mode 100644 index 00000000..05e56624 --- /dev/null +++ b/Tactility/Source/settings/SettingsProperties.cpp @@ -0,0 +1,77 @@ +#include +#include +#include +#include +#include + +namespace tt::settings { + +constexpr auto* TAG = "SettingsProperties"; +constexpr auto* FILE_PATH = "/data/settings.properties"; + +static Mutex mutex = Mutex(); +static bool cached = false; +static SettingsProperties cachedProperties; + +static bool loadSettingsPropertiesFromFile(SettingsProperties& properties) { + std::map map; + if (!file::withLock(FILE_PATH, [&map] { + return file::loadPropertiesFile(FILE_PATH, map); + })) { + TT_LOG_E(TAG, "Failed to load %s", FILE_PATH); + return false; + } + + auto language_entry = map.find("language"); + if (language_entry != map.end()) { + if (!fromString(language_entry->second, properties.language)) { + TT_LOG_W(TAG, "Unknown language \"%s\" in %s", language_entry->second.c_str(), FILE_PATH); + properties.language = Language::en_US; + } + } else { + properties.language = Language::en_US; + } + + auto time_format_entry = map.find("timeFormat24h"); + bool time_format_24h = time_format_entry == map.end() ? true : (time_format_entry->second == "true"); + properties.timeFormat24h = time_format_24h; + + return true; +} + +bool loadSettingsProperties(SettingsProperties& properties) { + auto scoped_lock = mutex.asScopedLock(); + scoped_lock.lock(); + + if (!cached) { + if (!loadSettingsPropertiesFromFile(cachedProperties)) { + return false; + } + cached = true; + } + + properties = cachedProperties; + return true; +} + +bool saveSettingsProperties(const SettingsProperties& properties) { + auto scoped_lock = mutex.asScopedLock(); + scoped_lock.lock(); + + return file::withLock(FILE_PATH, [&properties] { + std::map map; + map["language"] = toString(properties.language); + map["timeFormat24h"] = properties.timeFormat24h ? "true" : "false"; + + if (!file::savePropertiesFile(FILE_PATH, map)) { + TT_LOG_E(TAG, "Failed to save %s", FILE_PATH); + return false; + } + + cachedProperties = properties; + cached = true; + return true; + }); +} + +} \ No newline at end of file diff --git a/Tactility/Source/settings/Time.cpp b/Tactility/Source/settings/Time.cpp new file mode 100644 index 00000000..48ee837e --- /dev/null +++ b/Tactility/Source/settings/Time.cpp @@ -0,0 +1,81 @@ +#include + +#include +#include +#include + +#ifdef ESP_PLATFORM +#include +#endif + +namespace tt::settings { + +constexpr auto* TIME_SETTINGS_NAMESPACE = "time"; + +constexpr auto* TIMEZONE_PREFERENCES_KEY_NAME = "tz_name"; +constexpr auto* TIMEZONE_PREFERENCES_KEY_CODE = "tz_code"; +constexpr auto* TIMEZONE_PREFERENCES_KEY_TIME24 = "tz_time24"; + +void initTimeZone() { +#ifdef ESP_PLATFORM + auto code= getTimeZoneCode(); + if (!code.empty()) { + setenv("TZ", code.c_str(), 1); + tzset(); + } +#endif +} + +void setTimeZone(const std::string& name, const std::string& code) { + Preferences preferences(TIME_SETTINGS_NAMESPACE); + preferences.putString(TIMEZONE_PREFERENCES_KEY_NAME, name); + preferences.putString(TIMEZONE_PREFERENCES_KEY_CODE, code); + +#ifdef ESP_PLATFORM + setenv("TZ", code.c_str(), 1); + tzset(); +#endif + + kernel::publishSystemEvent(kernel::SystemEvent::Time); +} + +std::string getTimeZoneName() { + Preferences preferences(TIME_SETTINGS_NAMESPACE); + std::string result; + if (preferences.optString(TIMEZONE_PREFERENCES_KEY_NAME, result)) { + return result; + } else { + return {}; + } +} + +std::string getTimeZoneCode() { + Preferences preferences(TIME_SETTINGS_NAMESPACE); + std::string result; + if (preferences.optString(TIMEZONE_PREFERENCES_KEY_CODE, result)) { + return result; + } else { + return {}; + } +} + +bool isTimeFormat24Hour() { + SettingsProperties properties; + if (!loadSettingsProperties(properties)) { + return true; + } else { + return properties.timeFormat24h; + } +} + +void setTimeFormat24Hour(bool show24Hour) { + SettingsProperties properties; + if (!loadSettingsProperties(properties)) { + return; + } + + properties.timeFormat24h = show24Hour; + saveSettingsProperties(properties); +} + +} diff --git a/Tactility/Source/time/Time.cpp b/Tactility/Source/time/Time.cpp deleted file mode 100644 index 99d97f12..00000000 --- a/Tactility/Source/time/Time.cpp +++ /dev/null @@ -1,104 +0,0 @@ -#include "Tactility/time/Time.h" -#include "Tactility/kernel/SystemEvents.h" - -#ifdef ESP_PLATFORM -#include -#include "Tactility/Preferences.h" -#endif - -namespace tt::time { - -#ifdef ESP_PLATFORM - -#define TIME_SETTINGS_NAMESPACE "time" - -#define TIMEZONE_PREFERENCES_KEY_NAME "tz_name" -#define TIMEZONE_PREFERENCES_KEY_CODE "tz_code" -#define TIMEZONE_PREFERENCES_KEY_TIME24 "tz_time24" - -void init() { - auto code= getTimeZoneCode(); - if (!code.empty()) { - setenv("TZ", code.c_str(), 1); - tzset(); - } -} - -void setTimeZone(const std::string& name, const std::string& code) { - Preferences preferences(TIME_SETTINGS_NAMESPACE); - preferences.putString(TIMEZONE_PREFERENCES_KEY_NAME, name); - preferences.putString(TIMEZONE_PREFERENCES_KEY_CODE, code); - - setenv("TZ", code.c_str(), 1); - tzset(); - - kernel::publishSystemEvent(kernel::SystemEvent::Time); -} - -std::string getTimeZoneName() { - Preferences preferences(TIME_SETTINGS_NAMESPACE); - std::string result; - if (preferences.optString(TIMEZONE_PREFERENCES_KEY_NAME, result)) { - return result; - } else { - return {}; - } -} - -std::string getTimeZoneCode() { - Preferences preferences(TIME_SETTINGS_NAMESPACE); - std::string result; - if (preferences.optString(TIMEZONE_PREFERENCES_KEY_CODE, result)) { - return result; - } else { - return {}; - } -} - -bool isTimeFormat24Hour() { - Preferences preferences(TIME_SETTINGS_NAMESPACE); - bool show24Hour = true; - preferences.optBool(TIMEZONE_PREFERENCES_KEY_TIME24, show24Hour); - return show24Hour; -} - -void setTimeFormat24Hour(bool show24Hour) { - Preferences preferences(TIME_SETTINGS_NAMESPACE); - preferences.putBool(TIMEZONE_PREFERENCES_KEY_TIME24, show24Hour); - kernel::publishSystemEvent(kernel::SystemEvent::Time); -} - -#else - -static std::string timeZoneName; -static std::string timeZoneCode; -static bool show24Hour = true; - -void init() {} - -void setTimeZone(const std::string& name, const std::string& code) { - timeZoneName = name; - timeZoneCode = code; - kernel::publishSystemEvent(kernel::SystemEvent::Time); -} - -std::string getTimeZoneName() { - return timeZoneName; -} - -std::string getTimeZoneCode() { - return timeZoneCode; -} - -bool isTimeFormat24Hour() { - return show24Hour; -} - -void setTimeFormat24Hour(bool enabled) { - show24Hour = enabled; - kernel::publishSystemEvent(kernel::SystemEvent::Time); -} - -#endif - -} diff --git a/TactilityC/Source/tt_time.cpp b/TactilityC/Source/tt_time.cpp index 9a255e8f..ee97242b 100644 --- a/TactilityC/Source/tt_time.cpp +++ b/TactilityC/Source/tt_time.cpp @@ -1,6 +1,6 @@ #include "tt_time.h" -#include +#include #include using namespace tt; @@ -8,11 +8,11 @@ using namespace tt; extern "C" { void tt_timezone_set(const char* name, const char* code) { - time::setTimeZone(name, code); + settings::setTimeZone(name, code); } bool tt_timezone_get_name(char* buffer, size_t bufferSize) { - auto name = time::getTimeZoneName(); + auto name = settings::getTimeZoneName(); if (bufferSize < (name.length() + 1)) { return false; } else { @@ -22,7 +22,7 @@ bool tt_timezone_get_name(char* buffer, size_t bufferSize) { } bool tt_timezone_get_code(char* buffer, size_t bufferSize) { - auto code = time::getTimeZoneCode(); + auto code = settings::getTimeZoneCode(); if (bufferSize < (code.length() + 1)) { return false; } else { @@ -32,11 +32,11 @@ bool tt_timezone_get_code(char* buffer, size_t bufferSize) { } bool tt_timezone_is_format_24_hour() { - return time::isTimeFormat24Hour(); + return settings::isTimeFormat24Hour(); } void tt_timezone_set_format_24_hour(bool show24Hour) { - return time::setTimeFormat24Hour(show24Hour); + return settings::setTimeFormat24Hour(show24Hour); } } diff --git a/TactilityCore/Include/Tactility/file/File.h b/TactilityCore/Include/Tactility/file/File.h index 76808394..a96a9f0c 100644 --- a/TactilityCore/Include/Tactility/file/File.h +++ b/TactilityCore/Include/Tactility/file/File.h @@ -110,4 +110,6 @@ int scandir( ScandirSort _Nullable sort ); +bool readLines(const std::string& filePath, bool stripNewLine, std::function callback); + } diff --git a/TactilityCore/Source/file/File.cpp b/TactilityCore/Source/file/File.cpp index 3d778e61..b79fde56 100644 --- a/TactilityCore/Source/file/File.cpp +++ b/TactilityCore/Source/file/File.cpp @@ -222,4 +222,28 @@ bool isDirectory(const std::string& path) { return stat(path.c_str(), &stat_result) == 0 && S_ISDIR(stat_result.st_mode); } +bool readLines(const std::string& filepath, bool stripNewLine, std::function callback) { + auto* file = fopen(filepath.c_str(), "r"); + if (file == nullptr) { + return false; + } + + char line[1024]; + + while (fgets(line, sizeof(line), file) != nullptr) { + // Strip newline + if (stripNewLine) { + size_t line_length = strlen(line); + if (line_length > 0 && line[line_length - 1] == '\n') { + line[line_length - 1] = '\0'; + } + } + // Publish + callback(line); + } + + fclose(file); + return true; +} + } diff --git a/Translations/.gitignore b/Translations/.gitignore new file mode 100644 index 00000000..f54462c7 --- /dev/null +++ b/Translations/.gitignore @@ -0,0 +1,2 @@ +*.csv +*.ods# \ No newline at end of file diff --git a/Translations/README.md b/Translations/README.md new file mode 100644 index 00000000..535a2d52 --- /dev/null +++ b/Translations/README.md @@ -0,0 +1,25 @@ +# Translations + +## Requirements + +- Install [LibreOffice Calc](https://libreoffice.org/) +- Ensure you have Python 3 installed (Python 2 is not supported) + +## Steps + +To add new translations or edit existing ones, please follow these steps: + +1. Edit `Translations.ods` (see chapter below) +2. In LibreOffice Calc, select the relevant tab that you want to export +3. Click on `File` -> `Save a copy...` and save the file as `[tabname].csv` (without the "[]") +4. Repeat step 2 and 3 for all tabs that you updated +5. Run `python generate-all.py` + +## Notes + +- Do not commit the CSV files +- When editing the ODS file, make sure you don't paste in formatted data (use CTRL+Shift+V instead of CTRL+V) +- ODS export settings: + - Field delimiter: `,` + - String delimiter: `"` + - Encoding: `UTF-8` \ No newline at end of file diff --git a/Translations/Translations.ods b/Translations/Translations.ods new file mode 100644 index 00000000..3ce333cd Binary files /dev/null and b/Translations/Translations.ods differ diff --git a/Translations/generate-all.py b/Translations/generate-all.py new file mode 100644 index 00000000..477cc00e --- /dev/null +++ b/Translations/generate-all.py @@ -0,0 +1,37 @@ +import os +from pathlib import Path + +def get_project_root(): + return Path(__file__).parent.parent.resolve() + +def generate(csv_file, header_file, header_namespace, data_path): + csv_file_path = f"{get_project_root()}/Translations/{csv_file}" + if os.path.isfile(csv_file_path): + print(f"Processing {csv_file}") + script_path = f"{get_project_root()}/Translations/generate.py" + os.system(f"python {script_path} {csv_file} {header_file} {header_namespace} {data_path}") + else: + print(f"Skipping {csv_file} (not found)") + +if __name__ == "__main__": + # Core translations + generate( + "Core.csv", + "Tactility/Include/Tactility/i18n/CoreTextResources.h", + "tt::i18n::core", + "Data/system/i18n/core" + ) + # Launcher app + generate( + "Launcher.csv", + "Tactility/Private/Tactility/app/launcher/TextResources.h", + "tt::app::launcher::i18n", + "Data/system/app/Launcher/i18n" + ) + # LocaleSettings app + generate( + "LocaleSettings.csv", + "Tactility/Private/Tactility/app/localesettings/TextResources.h", + "tt::app::localesettings::i18n", + "Data/system/app/LocaleSettings/i18n" + ) diff --git a/Translations/generate.py b/Translations/generate.py new file mode 100644 index 00000000..175bbbb0 --- /dev/null +++ b/Translations/generate.py @@ -0,0 +1,96 @@ +import csv +import sys +from pathlib import Path +from typing import List + +def get_project_root(): + return Path(__file__).parent.parent.resolve() + +def load_csv(path: str, delimiter: str = ",", quotechar: str = '"', encoding: str = "utf-8", skip_header: bool = False) -> List[List[str]]: + """ + Load a CSV file into a list of rows, where each row is a list of strings. + + Args: + path: Path to the CSV file. + delimiter: Field delimiter character. + quotechar: Quote character. + encoding: File encoding. + skip_header: If True, skip the first row (header). + + Returns: + List of rows (list of lists of strings). + """ + rows: List[List[str]] = [] + with open(path, "r", encoding=encoding, newline="") as f: + reader = csv.reader(f, delimiter=delimiter, quotechar=quotechar) + if skip_header: + next(reader, None) + for row in reader: + rows.append(row) + return rows + +def print_help(): + print("Usage: python generate.py [csv_file] [header_file_path] [header_namespace] [i18n_directory]\n\n") + print("\t[csv_file] the CSV file containing the translations, exported from the .ods file") + print("\t[header_file_path] the path to the header file to be generated") + print("\t[header_namespace] the C++ namespace to use for the generated header file") + print("\t[i18n_directory] the directory where the .i18n files will be generated") + +def open_i18n_files(row, i18n_path): + result = [] + for i in range(1, len(row)): + filepath = f"{i18n_path}/{row[i]}.i18n" + print(f"Opening {filepath}") + file = open(filepath, "w") + result.append(file) + return result + +def close_i18n_files(files): + for file in files: + file.close() + +def generate_header(filepath, namespace, rows): + file = open(filepath, "w") + file.write("#pragma once\n\n") + file.write("#include \"Tactility/i18n/TextResources.h\"\n\n") + file.write("// WARNING: This file is auto-generated. Do not edit manually.\n\n") + file.write(f"namespace {namespace}") + file.write(" {\n\n") + file.write("enum class Text {\n") + for i in range(1, len(rows)): + key = rows[i][0].upper() + file.write(f" {key} = {i - 1},\n") + file.write("};\n") + file.write("\n}\n") + file.close() + +def translate(rows, language_index, file): + for i in range(1, len(rows)): + value = rows[i][language_index] + if value == "": + value = f"{rows[i][0]}_untranslated" + file.write(value) + file.write("\n") + +if __name__ == "__main__": + if "--help" in sys.argv: + print_help() + sys.exit() + if len(sys.argv) != 5: + print_help() + sys.exit() + project_root_path = get_project_root() + csv_file = f"{project_root_path}/Translations/{sys.argv[1]}" + header_path = f"{project_root_path}/{sys.argv[2]}" + header_namespace = sys.argv[3] + i18n_path = f"{project_root_path}/{sys.argv[4]}" + rows = load_csv(csv_file) + if len(rows) == 0: + print("Error: CSV file is empty.") + sys.exit(1) + generate_header(header_path, header_namespace, rows) + i18n_files = open_i18n_files(rows[0], i18n_path) + for i in range(0, len(i18n_files)): + i18n_file = i18n_files[i] + translate(rows, i + 1, i18n_file) + close_i18n_files(i18n_files) diff --git a/partitions-test.csv b/partitions-test.csv new file mode 100644 index 00000000..b2934bcb --- /dev/null +++ b/partitions-test.csv @@ -0,0 +1,7 @@ +# Name, Type, SubType, Offset, Size, Flags +# Note: if you have increased the bootloader size, make sure to update the offsets to avoid overlap +nvs, data, nvs, 0x9000, 0x6000, +phy_init, data, phy, 0xf000, 0x1000, +factory, app, factory, 0x10000, 3M, +system, data, fat, , 450k, +data, data, fat, , 450k, diff --git a/partitions.csv b/partitions.csv index 3bc4b6f9..00c39a9b 100644 --- a/partitions.csv +++ b/partitions.csv @@ -3,5 +3,5 @@ nvs, data, nvs, 0x9000, 0x6000, phy_init, data, phy, 0xf000, 0x1000, factory, app, factory, 0x10000, 3M, -system, data, fat, , 256k, -data, data, fat, , 256k, +system, data, fat, , 300k, +data, data, fat, , 600k, diff --git a/sdkconfig.board.cyd-2432s024c b/sdkconfig.board.cyd-2432s024c index be18fd74..9323e8c0 100644 --- a/sdkconfig.board.cyd-2432s024c +++ b/sdkconfig.board.cyd-2432s024c @@ -26,6 +26,11 @@ CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions.csv" CONFIG_PARTITION_TABLE_FILENAME="partitions.csv" CONFIG_FATFS_LFN_HEAP=y CONFIG_FATFS_VOLUME_COUNT=3 +CONFIG_FATFS_SECTOR_512=y +CONFIG_WL_SECTOR_SIZE_512=y +CONFIG_WL_SECTOR_SIZE=512 +CONFIG_WL_SECTOR_MODE_SAFE=y +CONFIG_WL_SECTOR_MODE=1 # Hardware: Main CONFIG_TT_BOARD_CYD_2432S024C=y diff --git a/sdkconfig.board.cyd-2432s032c b/sdkconfig.board.cyd-2432s032c index fbb5e695..2a32068c 100644 --- a/sdkconfig.board.cyd-2432s032c +++ b/sdkconfig.board.cyd-2432s032c @@ -26,6 +26,11 @@ CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions.csv" CONFIG_PARTITION_TABLE_FILENAME="partitions.csv" CONFIG_FATFS_LFN_HEAP=y CONFIG_FATFS_VOLUME_COUNT=3 +CONFIG_FATFS_SECTOR_512=y +CONFIG_WL_SECTOR_SIZE_512=y +CONFIG_WL_SECTOR_SIZE=512 +CONFIG_WL_SECTOR_MODE_SAFE=y +CONFIG_WL_SECTOR_MODE=1 # Hardware: Main CONFIG_TT_BOARD_CYD_2432S032C=y diff --git a/sdkconfig.board.cyd-4848s040c b/sdkconfig.board.cyd-4848s040c index 4cab54dd..85d0a54a 100644 --- a/sdkconfig.board.cyd-4848s040c +++ b/sdkconfig.board.cyd-4848s040c @@ -26,6 +26,11 @@ CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions.csv" CONFIG_PARTITION_TABLE_FILENAME="partitions.csv" CONFIG_FATFS_LFN_HEAP=y CONFIG_FATFS_VOLUME_COUNT=3 +CONFIG_FATFS_SECTOR_512=y +CONFIG_WL_SECTOR_SIZE_512=y +CONFIG_WL_SECTOR_SIZE=512 +CONFIG_WL_SECTOR_MODE_SAFE=y +CONFIG_WL_SECTOR_MODE=1 # Hardware: Main CONFIG_COMPILER_OPTIMIZATION_PERF=y diff --git a/sdkconfig.board.cyd-8048s043c b/sdkconfig.board.cyd-8048s043c index 70ad4211..be9659c4 100644 --- a/sdkconfig.board.cyd-8048s043c +++ b/sdkconfig.board.cyd-8048s043c @@ -26,6 +26,11 @@ CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions.csv" CONFIG_PARTITION_TABLE_FILENAME="partitions.csv" CONFIG_FATFS_LFN_HEAP=y CONFIG_FATFS_VOLUME_COUNT=3 +CONFIG_FATFS_SECTOR_512=y +CONFIG_WL_SECTOR_SIZE_512=y +CONFIG_WL_SECTOR_SIZE=512 +CONFIG_WL_SECTOR_MODE_SAFE=y +CONFIG_WL_SECTOR_MODE=1 # Hardware: Main CONFIG_COMPILER_OPTIMIZATION_PERF=y diff --git a/sdkconfig.board.cyd-jc2432w328c b/sdkconfig.board.cyd-jc2432w328c index 72668cbc..45ba3c90 100644 --- a/sdkconfig.board.cyd-jc2432w328c +++ b/sdkconfig.board.cyd-jc2432w328c @@ -26,6 +26,11 @@ CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions.csv" CONFIG_PARTITION_TABLE_FILENAME="partitions.csv" CONFIG_FATFS_LFN_HEAP=y CONFIG_FATFS_VOLUME_COUNT=3 +CONFIG_FATFS_SECTOR_512=y +CONFIG_WL_SECTOR_SIZE_512=y +CONFIG_WL_SECTOR_SIZE=512 +CONFIG_WL_SECTOR_MODE_SAFE=y +CONFIG_WL_SECTOR_MODE=1 # Hardware: Main CONFIG_TT_BOARD_CYD_JC2432W328C=y diff --git a/sdkconfig.board.cyd-jc8048w550c b/sdkconfig.board.cyd-jc8048w550c index d9dc67c9..fcb52cd5 100644 --- a/sdkconfig.board.cyd-jc8048w550c +++ b/sdkconfig.board.cyd-jc8048w550c @@ -26,6 +26,11 @@ CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions.csv" CONFIG_PARTITION_TABLE_FILENAME="partitions.csv" CONFIG_FATFS_LFN_HEAP=y CONFIG_FATFS_VOLUME_COUNT=3 +CONFIG_FATFS_SECTOR_512=y +CONFIG_WL_SECTOR_SIZE_512=y +CONFIG_WL_SECTOR_SIZE=512 +CONFIG_WL_SECTOR_MODE_SAFE=y +CONFIG_WL_SECTOR_MODE=1 # Hardware: Main CONFIG_COMPILER_OPTIMIZATION_PERF=y diff --git a/sdkconfig.board.elecrow-crowpanel-advance-28 b/sdkconfig.board.elecrow-crowpanel-advance-28 index 3f8de84c..a8a2eea1 100644 --- a/sdkconfig.board.elecrow-crowpanel-advance-28 +++ b/sdkconfig.board.elecrow-crowpanel-advance-28 @@ -26,6 +26,11 @@ CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions.csv" CONFIG_PARTITION_TABLE_FILENAME="partitions.csv" CONFIG_FATFS_LFN_HEAP=y CONFIG_FATFS_VOLUME_COUNT=3 +CONFIG_FATFS_SECTOR_512=y +CONFIG_WL_SECTOR_SIZE_512=y +CONFIG_WL_SECTOR_SIZE=512 +CONFIG_WL_SECTOR_MODE_SAFE=y +CONFIG_WL_SECTOR_MODE=1 # Hardware: Main CONFIG_TT_BOARD_ELECROW_CROWPANEL_ADVANCE_28=y diff --git a/sdkconfig.board.elecrow-crowpanel-advance-35 b/sdkconfig.board.elecrow-crowpanel-advance-35 index beba2c37..ee6d6403 100644 --- a/sdkconfig.board.elecrow-crowpanel-advance-35 +++ b/sdkconfig.board.elecrow-crowpanel-advance-35 @@ -26,6 +26,11 @@ CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions.csv" CONFIG_PARTITION_TABLE_FILENAME="partitions.csv" CONFIG_FATFS_LFN_HEAP=y CONFIG_FATFS_VOLUME_COUNT=3 +CONFIG_FATFS_SECTOR_512=y +CONFIG_WL_SECTOR_SIZE_512=y +CONFIG_WL_SECTOR_SIZE=512 +CONFIG_WL_SECTOR_MODE_SAFE=y +CONFIG_WL_SECTOR_MODE=1 # Hardware: Main CONFIG_TT_BOARD_ELECROW_CROWPANEL_ADVANCE_35=y diff --git a/sdkconfig.board.elecrow-crowpanel-advance-50 b/sdkconfig.board.elecrow-crowpanel-advance-50 index 34328ab4..73c1d1a8 100644 --- a/sdkconfig.board.elecrow-crowpanel-advance-50 +++ b/sdkconfig.board.elecrow-crowpanel-advance-50 @@ -26,6 +26,11 @@ CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions.csv" CONFIG_PARTITION_TABLE_FILENAME="partitions.csv" CONFIG_FATFS_LFN_HEAP=y CONFIG_FATFS_VOLUME_COUNT=3 +CONFIG_FATFS_SECTOR_512=y +CONFIG_WL_SECTOR_SIZE_512=y +CONFIG_WL_SECTOR_SIZE=512 +CONFIG_WL_SECTOR_MODE_SAFE=y +CONFIG_WL_SECTOR_MODE=1 # Hardware: Main CONFIG_TT_BOARD_ELECROW_CROWPANEL_ADVANCE_50=y diff --git a/sdkconfig.board.elecrow-crowpanel-basic-28 b/sdkconfig.board.elecrow-crowpanel-basic-28 index 69983a3d..b9e5395e 100644 --- a/sdkconfig.board.elecrow-crowpanel-basic-28 +++ b/sdkconfig.board.elecrow-crowpanel-basic-28 @@ -26,6 +26,11 @@ CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions.csv" CONFIG_PARTITION_TABLE_FILENAME="partitions.csv" CONFIG_FATFS_LFN_HEAP=y CONFIG_FATFS_VOLUME_COUNT=3 +CONFIG_FATFS_SECTOR_512=y +CONFIG_WL_SECTOR_SIZE_512=y +CONFIG_WL_SECTOR_SIZE=512 +CONFIG_WL_SECTOR_MODE_SAFE=y +CONFIG_WL_SECTOR_MODE=1 # Hardware: Main CONFIG_TT_BOARD_ELECROW_CROWPANEL_BASIC_28=y diff --git a/sdkconfig.board.elecrow-crowpanel-basic-35 b/sdkconfig.board.elecrow-crowpanel-basic-35 index e28b5048..3907565d 100644 --- a/sdkconfig.board.elecrow-crowpanel-basic-35 +++ b/sdkconfig.board.elecrow-crowpanel-basic-35 @@ -26,6 +26,11 @@ CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions.csv" CONFIG_PARTITION_TABLE_FILENAME="partitions.csv" CONFIG_FATFS_LFN_HEAP=y CONFIG_FATFS_VOLUME_COUNT=3 +CONFIG_FATFS_SECTOR_512=y +CONFIG_WL_SECTOR_SIZE_512=y +CONFIG_WL_SECTOR_SIZE=512 +CONFIG_WL_SECTOR_MODE_SAFE=y +CONFIG_WL_SECTOR_MODE=1 # Hardware: Main CONFIG_TT_BOARD_ELECROW_CROWPANEL_BASIC_35=y diff --git a/sdkconfig.board.elecrow-crowpanel-basic-50 b/sdkconfig.board.elecrow-crowpanel-basic-50 index 7c928797..8ea89fcd 100644 --- a/sdkconfig.board.elecrow-crowpanel-basic-50 +++ b/sdkconfig.board.elecrow-crowpanel-basic-50 @@ -26,6 +26,11 @@ CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions.csv" CONFIG_PARTITION_TABLE_FILENAME="partitions.csv" CONFIG_FATFS_LFN_HEAP=y CONFIG_FATFS_VOLUME_COUNT=3 +CONFIG_FATFS_SECTOR_512=y +CONFIG_WL_SECTOR_SIZE_512=y +CONFIG_WL_SECTOR_SIZE=512 +CONFIG_WL_SECTOR_MODE_SAFE=y +CONFIG_WL_SECTOR_MODE=1 # Hardware: Main CONFIG_TT_BOARD_ELECROW_CROWPANEL_BASIC_50=y diff --git a/sdkconfig.board.lilygo-tdeck b/sdkconfig.board.lilygo-tdeck index 6de01c9c..aa73175e 100644 --- a/sdkconfig.board.lilygo-tdeck +++ b/sdkconfig.board.lilygo-tdeck @@ -26,6 +26,11 @@ CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions.csv" CONFIG_PARTITION_TABLE_FILENAME="partitions.csv" CONFIG_FATFS_LFN_HEAP=y CONFIG_FATFS_VOLUME_COUNT=3 +CONFIG_FATFS_SECTOR_512=y +CONFIG_WL_SECTOR_SIZE_512=y +CONFIG_WL_SECTOR_SIZE=512 +CONFIG_WL_SECTOR_MODE_SAFE=y +CONFIG_WL_SECTOR_MODE=1 # Hardware: Main CONFIG_TT_BOARD_LILYGO_TDECK=y diff --git a/sdkconfig.board.lilygo-tlora-pager b/sdkconfig.board.lilygo-tlora-pager index 16fb1e30..f68156ed 100644 --- a/sdkconfig.board.lilygo-tlora-pager +++ b/sdkconfig.board.lilygo-tlora-pager @@ -26,6 +26,11 @@ CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions.csv" CONFIG_PARTITION_TABLE_FILENAME="partitions.csv" CONFIG_FATFS_LFN_HEAP=y CONFIG_FATFS_VOLUME_COUNT=3 +CONFIG_FATFS_SECTOR_512=y +CONFIG_WL_SECTOR_SIZE_512=y +CONFIG_WL_SECTOR_SIZE=512 +CONFIG_WL_SECTOR_MODE_SAFE=y +CONFIG_WL_SECTOR_MODE=1 # Hardware: Main CONFIG_TT_BOARD_LILYGO_TLORA_PAGER=y diff --git a/sdkconfig.board.m5stack-core2 b/sdkconfig.board.m5stack-core2 index c4e62cd3..3ad7fb7f 100644 --- a/sdkconfig.board.m5stack-core2 +++ b/sdkconfig.board.m5stack-core2 @@ -26,6 +26,11 @@ CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions.csv" CONFIG_PARTITION_TABLE_FILENAME="partitions.csv" CONFIG_FATFS_LFN_HEAP=y CONFIG_FATFS_VOLUME_COUNT=3 +CONFIG_FATFS_SECTOR_512=y +CONFIG_WL_SECTOR_SIZE_512=y +CONFIG_WL_SECTOR_SIZE=512 +CONFIG_WL_SECTOR_MODE_SAFE=y +CONFIG_WL_SECTOR_MODE=1 # Hardware: Main CONFIG_TT_BOARD_M5STACK_CORE2=y diff --git a/sdkconfig.board.m5stack-cores3 b/sdkconfig.board.m5stack-cores3 index 9f981363..27b2e87e 100644 --- a/sdkconfig.board.m5stack-cores3 +++ b/sdkconfig.board.m5stack-cores3 @@ -26,6 +26,11 @@ CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions.csv" CONFIG_PARTITION_TABLE_FILENAME="partitions.csv" CONFIG_FATFS_LFN_HEAP=y CONFIG_FATFS_VOLUME_COUNT=3 +CONFIG_FATFS_SECTOR_512=y +CONFIG_WL_SECTOR_SIZE_512=y +CONFIG_WL_SECTOR_SIZE=512 +CONFIG_WL_SECTOR_MODE_SAFE=y +CONFIG_WL_SECTOR_MODE=1 # Hardware: Main CONFIG_TT_BOARD_M5STACK_CORES3=y diff --git a/sdkconfig.board.unphone b/sdkconfig.board.unphone index c2202fb9..82359473 100644 --- a/sdkconfig.board.unphone +++ b/sdkconfig.board.unphone @@ -26,6 +26,11 @@ CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions.csv" CONFIG_PARTITION_TABLE_FILENAME="partitions.csv" CONFIG_FATFS_LFN_HEAP=y CONFIG_FATFS_VOLUME_COUNT=3 +CONFIG_FATFS_SECTOR_512=y +CONFIG_WL_SECTOR_SIZE_512=y +CONFIG_WL_SECTOR_SIZE=512 +CONFIG_WL_SECTOR_MODE_SAFE=y +CONFIG_WL_SECTOR_MODE=1 # Hardware: Main CONFIG_TT_BOARD_UNPHONE=y diff --git a/sdkconfig.board.waveshare-s3-touch-43 b/sdkconfig.board.waveshare-s3-touch-43 index 6e5babe4..c89e2b2c 100644 --- a/sdkconfig.board.waveshare-s3-touch-43 +++ b/sdkconfig.board.waveshare-s3-touch-43 @@ -26,6 +26,11 @@ CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions.csv" CONFIG_PARTITION_TABLE_FILENAME="partitions.csv" CONFIG_FATFS_LFN_HEAP=y CONFIG_FATFS_VOLUME_COUNT=3 +CONFIG_FATFS_SECTOR_512=y +CONFIG_WL_SECTOR_SIZE_512=y +CONFIG_WL_SECTOR_SIZE=512 +CONFIG_WL_SECTOR_MODE_SAFE=y +CONFIG_WL_SECTOR_MODE=1 # Hardware: Main CONFIG_TT_BOARD_WAVESHARE_S3_TOUCH_43=y diff --git a/sdkconfig.defaults b/sdkconfig.defaults index 199fed35..97d27e96 100644 --- a/sdkconfig.defaults +++ b/sdkconfig.defaults @@ -26,6 +26,11 @@ CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions.csv" CONFIG_PARTITION_TABLE_FILENAME="partitions.csv" CONFIG_FATFS_LFN_HEAP=y CONFIG_FATFS_VOLUME_COUNT=3 +CONFIG_FATFS_SECTOR_512=y +CONFIG_WL_SECTOR_SIZE_512=y +CONFIG_WL_SECTOR_SIZE=512 +CONFIG_WL_SECTOR_MODE_SAFE=y +CONFIG_WL_SECTOR_MODE=1 # Hardware defaults CONFIG_TT_BOARD_CUSTOM=y