Various improvements (#547)

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