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