Quick Actions
-
${data.features_enabled?.screenshot ? '' : ''}
diff --git a/Data/data/webserver/version.json b/Data/system/app/WebServer/version.json
similarity index 100%
rename from Data/data/webserver/version.json
rename to Data/system/app/WebServer/version.json
diff --git a/Data/webserver/default.html b/Data/webserver/default.html
deleted file mode 100644
index fa4ab4c7..00000000
--- a/Data/webserver/default.html
+++ /dev/null
@@ -1,63 +0,0 @@
-
-
-
-
-
-
Tactility Dashboard
-
-
-
-
Tactility Default Dashboard
-
-
-
Version 0 - Default Placeholder
-
This is the default dashboard bundled with firmware.
-
To customize this interface:
-
- - Create your custom dashboard HTML/CSS/JS files
- - Add them to
/sdcard/tactility/webserver/
- - Create
version.json with {"version": 1} or higher
- - Reboot or click "Sync Assets" on the Core Interface
-
-
Your custom assets will automatically replace this page!
-
-
-
-
-
diff --git a/Data/webserver/version.json b/Data/webserver/version.json
deleted file mode 100644
index 5dfe44db..00000000
--- a/Data/webserver/version.json
+++ /dev/null
@@ -1,3 +0,0 @@
-{
- "version": 0
-}
diff --git a/Devices/btt-panda-touch/device.properties b/Devices/btt-panda-touch/device.properties
index b34ee6e4..8e65fa6d 100644
--- a/Devices/btt-panda-touch/device.properties
+++ b/Devices/btt-panda-touch/device.properties
@@ -12,6 +12,8 @@ hardware.esptoolFlashFreq=120M
hardware.bluetooth=true
hardware.usbHostEnabled=true
+storage.userDataLocation=Internal
+
display.size=5"
display.shape=rectangle
display.dpi=187
diff --git a/Devices/cyd-2432s024c/device.properties b/Devices/cyd-2432s024c/device.properties
index 8c41c3e8..0f2a5c1d 100644
--- a/Devices/cyd-2432s024c/device.properties
+++ b/Devices/cyd-2432s024c/device.properties
@@ -7,6 +7,8 @@ hardware.target=ESP32
hardware.flashSize=4MB
hardware.spiRam=false
+storage.userDataLocation=SD
+
display.size=2.4"
display.shape=rectangle
display.dpi=167
diff --git a/Devices/cyd-2432s024r/device.properties b/Devices/cyd-2432s024r/device.properties
index b54d29d5..b2e05d04 100644
--- a/Devices/cyd-2432s024r/device.properties
+++ b/Devices/cyd-2432s024r/device.properties
@@ -7,6 +7,8 @@ hardware.target=ESP32
hardware.flashSize=4MB
hardware.spiRam=false
+storage.userDataLocation=SD
+
display.size=2.4"
display.shape=rectangle
display.dpi=167
diff --git a/Devices/cyd-2432s028r/device.properties b/Devices/cyd-2432s028r/device.properties
index ff1db8b3..c0047a33 100644
--- a/Devices/cyd-2432s028r/device.properties
+++ b/Devices/cyd-2432s028r/device.properties
@@ -7,6 +7,8 @@ hardware.target=ESP32
hardware.flashSize=4MB
hardware.spiRam=false
+storage.userDataLocation=SD
+
display.size=2.8"
display.shape=rectangle
display.dpi=143
diff --git a/Devices/cyd-2432s028rv3/device.properties b/Devices/cyd-2432s028rv3/device.properties
index c35b6414..ae3e926f 100644
--- a/Devices/cyd-2432s028rv3/device.properties
+++ b/Devices/cyd-2432s028rv3/device.properties
@@ -7,6 +7,8 @@ hardware.target=ESP32
hardware.flashSize=4MB
hardware.spiRam=false
+storage.userDataLocation=SD
+
display.size=2.8"
display.shape=rectangle
display.dpi=143
diff --git a/Devices/cyd-2432s032c/device.properties b/Devices/cyd-2432s032c/device.properties
index 239afa35..e483bd8c 100644
--- a/Devices/cyd-2432s032c/device.properties
+++ b/Devices/cyd-2432s032c/device.properties
@@ -7,6 +7,8 @@ hardware.target=ESP32
hardware.flashSize=4MB
hardware.spiRam=false
+storage.userDataLocation=SD
+
display.size=3.2"
display.shape=rectangle
display.dpi=125
diff --git a/Devices/cyd-3248s035c/device.properties b/Devices/cyd-3248s035c/device.properties
index fedfa096..c8fc929d 100644
--- a/Devices/cyd-3248s035c/device.properties
+++ b/Devices/cyd-3248s035c/device.properties
@@ -7,6 +7,8 @@ hardware.target=ESP32
hardware.flashSize=4MB
hardware.spiRam=false
+storage.userDataLocation=SD
+
display.size=3.5"
display.shape=rectangle
display.dpi=165
diff --git a/Devices/cyd-4848s040c/device.properties b/Devices/cyd-4848s040c/device.properties
index 6dc01205..45c66c54 100644
--- a/Devices/cyd-4848s040c/device.properties
+++ b/Devices/cyd-4848s040c/device.properties
@@ -10,6 +10,8 @@ hardware.spiRamMode=OCT
hardware.spiRamSpeed=80M
hardware.bluetooth=true
+storage.userDataLocation=SD
+
display.size=4"
display.shape=rectangle
display.dpi=170
diff --git a/Devices/cyd-8048s043c/device.properties b/Devices/cyd-8048s043c/device.properties
index 94659bab..8056421d 100644
--- a/Devices/cyd-8048s043c/device.properties
+++ b/Devices/cyd-8048s043c/device.properties
@@ -12,6 +12,8 @@ hardware.spiRamSpeed=80M
hardware.esptoolFlashFreq=80M
hardware.bluetooth=true
+storage.userDataLocation=SD
+
display.size=4.3"
display.shape=rectangle
display.dpi=217
diff --git a/Devices/cyd-e32r28t/device.properties b/Devices/cyd-e32r28t/device.properties
index f9b582a9..0adcd93d 100644
--- a/Devices/cyd-e32r28t/device.properties
+++ b/Devices/cyd-e32r28t/device.properties
@@ -7,6 +7,8 @@ hardware.target=ESP32
hardware.flashSize=4MB
hardware.spiRam=false
+storage.userDataLocation=SD
+
display.size=2.8"
display.shape=rectangle
display.dpi=143
diff --git a/Devices/cyd-e32r32p/device.properties b/Devices/cyd-e32r32p/device.properties
index 1895aa40..3c10c3e8 100644
--- a/Devices/cyd-e32r32p/device.properties
+++ b/Devices/cyd-e32r32p/device.properties
@@ -7,6 +7,8 @@ hardware.target=ESP32
hardware.flashSize=4MB
hardware.spiRam=false
+storage.userDataLocation=SD
+
display.size=2.8"
display.shape=rectangle
display.dpi=125
diff --git a/Devices/elecrow-crowpanel-advance-28/device.properties b/Devices/elecrow-crowpanel-advance-28/device.properties
index 797e53f9..9d2fd794 100644
--- a/Devices/elecrow-crowpanel-advance-28/device.properties
+++ b/Devices/elecrow-crowpanel-advance-28/device.properties
@@ -12,6 +12,8 @@ hardware.tinyUsb=true
hardware.esptoolFlashFreq=120M
hardware.bluetooth=true
+storage.userDataLocation=SD
+
display.size=2.8"
display.shape=rectangle
display.dpi=143
diff --git a/Devices/elecrow-crowpanel-advance-35/device.properties b/Devices/elecrow-crowpanel-advance-35/device.properties
index 0bc352e6..83374439 100644
--- a/Devices/elecrow-crowpanel-advance-35/device.properties
+++ b/Devices/elecrow-crowpanel-advance-35/device.properties
@@ -12,6 +12,8 @@ hardware.tinyUsb=true
hardware.esptoolFlashFreq=120M
hardware.bluetooth=true
+storage.userDataLocation=SD
+
display.size=3.5"
display.shape=rectangle
display.dpi=165
diff --git a/Devices/elecrow-crowpanel-advance-50/device.properties b/Devices/elecrow-crowpanel-advance-50/device.properties
index f1a00e28..9fa787d6 100644
--- a/Devices/elecrow-crowpanel-advance-50/device.properties
+++ b/Devices/elecrow-crowpanel-advance-50/device.properties
@@ -12,6 +12,8 @@ hardware.tinyUsb=true
hardware.esptoolFlashFreq=120M
hardware.bluetooth=true
+storage.userDataLocation=SD
+
display.size=5"
display.shape=rectangle
display.dpi=187
diff --git a/Devices/elecrow-crowpanel-basic-28/device.properties b/Devices/elecrow-crowpanel-basic-28/device.properties
index 7843d829..61b71556 100644
--- a/Devices/elecrow-crowpanel-basic-28/device.properties
+++ b/Devices/elecrow-crowpanel-basic-28/device.properties
@@ -7,6 +7,8 @@ hardware.target=ESP32
hardware.flashSize=4MB
hardware.spiRam=false
+storage.userDataLocation=SD
+
display.size=2.8"
display.shape=rectangle
display.dpi=143
diff --git a/Devices/elecrow-crowpanel-basic-35/device.properties b/Devices/elecrow-crowpanel-basic-35/device.properties
index b768e157..c9bb6094 100644
--- a/Devices/elecrow-crowpanel-basic-35/device.properties
+++ b/Devices/elecrow-crowpanel-basic-35/device.properties
@@ -7,6 +7,8 @@ hardware.target=ESP32
hardware.flashSize=4MB
hardware.spiRam=false
+storage.userDataLocation=SD
+
display.size=3.5"
display.shape=rectangle
display.dpi=165
diff --git a/Devices/elecrow-crowpanel-basic-50/device.properties b/Devices/elecrow-crowpanel-basic-50/device.properties
index 5f71e02e..69633d2a 100644
--- a/Devices/elecrow-crowpanel-basic-50/device.properties
+++ b/Devices/elecrow-crowpanel-basic-50/device.properties
@@ -12,6 +12,8 @@ hardware.tinyUsb=true
hardware.esptoolFlashFreq=120M
hardware.bluetooth=true
+storage.userDataLocation=SD
+
display.size=5.0"
display.shape=rectangle
display.dpi=187
diff --git a/Devices/generic-esp32/device.properties b/Devices/generic-esp32/device.properties
index 4dd70ea6..8b803a84 100644
--- a/Devices/generic-esp32/device.properties
+++ b/Devices/generic-esp32/device.properties
@@ -4,6 +4,7 @@ general.name=ESP32
apps.launcherAppId=Launcher
hardware.target=ESP32
-hardware.flashSize=4MB
+hardware.flashSize=8MB
hardware.spiRam=false
+storage.userDataLocation=Internal
diff --git a/Devices/generic-esp32c6/device.properties b/Devices/generic-esp32c6/device.properties
index c8941ee3..bd332c25 100644
--- a/Devices/generic-esp32c6/device.properties
+++ b/Devices/generic-esp32c6/device.properties
@@ -4,6 +4,7 @@ general.name=ESP32-C6
apps.launcherAppId=Launcher
hardware.target=ESP32C6
-hardware.flashSize=4MB
+hardware.flashSize=8MB
hardware.spiRam=false
+storage.userDataLocation=Internal
diff --git a/Devices/generic-esp32p4/device.properties b/Devices/generic-esp32p4/device.properties
index d2fefc16..4a050b39 100644
--- a/Devices/generic-esp32p4/device.properties
+++ b/Devices/generic-esp32p4/device.properties
@@ -4,6 +4,7 @@ general.name=ESP32-P4
apps.launcherAppId=Launcher
hardware.target=ESP32P4
-hardware.flashSize=4MB
+hardware.flashSize=8MB
hardware.spiRam=false
+storage.userDataLocation=Internal
diff --git a/Devices/generic-esp32s3/device.properties b/Devices/generic-esp32s3/device.properties
index 3bb4a9d4..8512a450 100644
--- a/Devices/generic-esp32s3/device.properties
+++ b/Devices/generic-esp32s3/device.properties
@@ -4,6 +4,7 @@ general.name=ESP32-S3
apps.launcherAppId=Launcher
hardware.target=ESP32S3
-hardware.flashSize=4MB
+hardware.flashSize=8MB
hardware.spiRam=false
+storage.userDataLocation=Internal
diff --git a/Devices/guition-jc1060p470ciwy/device.properties b/Devices/guition-jc1060p470ciwy/device.properties
index 4ad803c6..fcc7421e 100644
--- a/Devices/guition-jc1060p470ciwy/device.properties
+++ b/Devices/guition-jc1060p470ciwy/device.properties
@@ -11,6 +11,8 @@ hardware.spiRamSpeed=200M
hardware.esptoolFlashFreq=80M
hardware.bluetooth=true
+storage.userDataLocation=SD
+
display.size=7"
display.shape=rectangle
display.dpi=187
diff --git a/Devices/guition-jc2432w328c/device.properties b/Devices/guition-jc2432w328c/device.properties
index a938ef23..7ef40633 100644
--- a/Devices/guition-jc2432w328c/device.properties
+++ b/Devices/guition-jc2432w328c/device.properties
@@ -7,6 +7,8 @@ hardware.target=ESP32
hardware.flashSize=4MB
hardware.spiRam=false
+storage.userDataLocation=SD
+
display.size=2.8"
display.shape=rectangle
display.dpi=143
diff --git a/Devices/guition-jc3248w535c/device.properties b/Devices/guition-jc3248w535c/device.properties
index dabe1b87..88497b41 100644
--- a/Devices/guition-jc3248w535c/device.properties
+++ b/Devices/guition-jc3248w535c/device.properties
@@ -12,6 +12,8 @@ hardware.tinyUsb=true
hardware.esptoolFlashFreq=120M
hardware.bluetooth=true
+storage.userDataLocation=SD
+
display.size=3.5"
display.shape=rectangle
display.dpi=165
diff --git a/Devices/guition-jc8048w550c/device.properties b/Devices/guition-jc8048w550c/device.properties
index 46c912bd..b0450a3c 100644
--- a/Devices/guition-jc8048w550c/device.properties
+++ b/Devices/guition-jc8048w550c/device.properties
@@ -11,6 +11,8 @@ hardware.spiRamSpeed=80M
hardware.esptoolFlashFreq=80M
hardware.bluetooth=true
+storage.userDataLocation=SD
+
display.size=5"
display.shape=rectangle
display.dpi=187
diff --git a/Devices/heltec-wifi-lora-32-v3/device.properties b/Devices/heltec-wifi-lora-32-v3/device.properties
index 84afa8f0..729c4873 100644
--- a/Devices/heltec-wifi-lora-32-v3/device.properties
+++ b/Devices/heltec-wifi-lora-32-v3/device.properties
@@ -12,6 +12,8 @@ hardware.tinyUsb=true
hardware.esptoolFlashFreq=120M
hardware.bluetooth=true
+storage.userDataLocation=Internal
+
display.size=0.96"
display.shape=rectangle
display.dpi=149
diff --git a/Devices/lilygo-tdeck/device.properties b/Devices/lilygo-tdeck/device.properties
index cba55320..04207e46 100644
--- a/Devices/lilygo-tdeck/device.properties
+++ b/Devices/lilygo-tdeck/device.properties
@@ -12,6 +12,8 @@ hardware.tinyUsb=true
hardware.esptoolFlashFreq=120M
hardware.bluetooth=true
+storage.userDataLocation=SD
+
display.size=2.8"
display.shape=rectangle
display.dpi=143
diff --git a/Devices/lilygo-tdisplay-s3/device.properties b/Devices/lilygo-tdisplay-s3/device.properties
index 006ae739..759018c9 100644
--- a/Devices/lilygo-tdisplay-s3/device.properties
+++ b/Devices/lilygo-tdisplay-s3/device.properties
@@ -13,6 +13,8 @@ hardware.tinyUsb=true
hardware.esptoolFlashFreq=120M
hardware.bluetooth=true
+storage.userDataLocation=Internal
+
display.size=1.9"
display.shape=rectangle
display.dpi=191
diff --git a/Devices/lilygo-tdisplay/device.properties b/Devices/lilygo-tdisplay/device.properties
index 0dd37c33..59008613 100644
--- a/Devices/lilygo-tdisplay/device.properties
+++ b/Devices/lilygo-tdisplay/device.properties
@@ -10,6 +10,8 @@ hardware.flashSize=16MB
hardware.spiRam=false
hardware.esptoolFlashFreq=80M
+storage.userDataLocation=Internal
+
display.size=1.14"
display.shape=rectangle
display.dpi=242
diff --git a/Devices/lilygo-tdongle-s3/device.properties b/Devices/lilygo-tdongle-s3/device.properties
index 62d1e218..f92ed6eb 100644
--- a/Devices/lilygo-tdongle-s3/device.properties
+++ b/Devices/lilygo-tdongle-s3/device.properties
@@ -12,6 +12,8 @@ hardware.tinyUsb=true
hardware.esptoolFlashFreq=120M
hardware.bluetooth=true
+storage.userDataLocation=SD
+
display.size=0.96"
display.shape=rectangle
display.dpi=186
diff --git a/Devices/lilygo-thmi/device.properties b/Devices/lilygo-thmi/device.properties
index 4da5dd34..d58efc9f 100644
--- a/Devices/lilygo-thmi/device.properties
+++ b/Devices/lilygo-thmi/device.properties
@@ -12,6 +12,8 @@ hardware.tinyUsb=true
hardware.esptoolFlashFreq=120M
hardware.bluetooth=true
+storage.userDataLocation=SD
+
display.size=2.8"
display.shape=rectangle
display.dpi=125
diff --git a/Devices/lilygo-tlora-pager/device.properties b/Devices/lilygo-tlora-pager/device.properties
index a74d00ca..1e363302 100644
--- a/Devices/lilygo-tlora-pager/device.properties
+++ b/Devices/lilygo-tlora-pager/device.properties
@@ -13,6 +13,8 @@ hardware.tinyUsb=true
hardware.esptoolFlashFreq=40M
hardware.bluetooth=true
+storage.userDataLocation=SD
+
display.size=2.33"
display.shape=rectangle
display.dpi=227
diff --git a/Devices/m5stack-cardputer-adv/device.properties b/Devices/m5stack-cardputer-adv/device.properties
index e500bcf0..ab63ce70 100644
--- a/Devices/m5stack-cardputer-adv/device.properties
+++ b/Devices/m5stack-cardputer-adv/device.properties
@@ -10,6 +10,8 @@ hardware.tinyUsb=true
hardware.esptoolFlashFreq=120M
hardware.bluetooth=true
+storage.userDataLocation=SD
+
display.size=1.14"
display.shape=rectangle
# TODO: dps is actually 242, but this breaks UI (button selection becomes invisible and switch visibility is reduced)
diff --git a/Devices/m5stack-cardputer/device.properties b/Devices/m5stack-cardputer/device.properties
index c2419c32..f2545fab 100644
--- a/Devices/m5stack-cardputer/device.properties
+++ b/Devices/m5stack-cardputer/device.properties
@@ -10,6 +10,8 @@ hardware.tinyUsb=true
hardware.esptoolFlashFreq=120M
hardware.bluetooth=true
+storage.userDataLocation=SD
+
display.size=1.14"
display.shape=rectangle
# TODO: dps is actually 242, but this breaks UI (button selection becomes invisible and switch visibility is reduced)
diff --git a/Devices/m5stack-core2/device.properties b/Devices/m5stack-core2/device.properties
index eafb7c19..17156143 100644
--- a/Devices/m5stack-core2/device.properties
+++ b/Devices/m5stack-core2/device.properties
@@ -10,6 +10,8 @@ hardware.spiRamMode=QUAD
hardware.spiRamSpeed=80M
hardware.esptoolFlashFreq=80M
+storage.userDataLocation=SD
+
display.size=2"
display.shape=rectangle
display.dpi=200
diff --git a/Devices/m5stack-cores3/device.properties b/Devices/m5stack-cores3/device.properties
index 8fbb6768..28e15bcd 100644
--- a/Devices/m5stack-cores3/device.properties
+++ b/Devices/m5stack-cores3/device.properties
@@ -12,8 +12,10 @@ hardware.tinyUsb=true
hardware.esptoolFlashFreq=120M
hardware.bluetooth=true
+storage.userDataLocation=SD
+
display.size=2"
display.shape=rectangle
display.dpi=200
-lvgl.colorDepth=16
\ No newline at end of file
+lvgl.colorDepth=16
diff --git a/Devices/m5stack-papers3/device.properties b/Devices/m5stack-papers3/device.properties
index 80ce7247..91cc994f 100644
--- a/Devices/m5stack-papers3/device.properties
+++ b/Devices/m5stack-papers3/device.properties
@@ -13,6 +13,8 @@ hardware.esptoolFlashFreq=80M
hardware.tinyUsb=true
hardware.bluetooth=true
+storage.userDataLocation=SD
+
display.size=4.7"
display.shape=rectangle
display.dpi=235
@@ -22,5 +24,3 @@ lvgl.fontSize=24
lvgl.theme=Mono
sdkconfig.CONFIG_EPD_DISPLAY_TYPE_ED047TC2=y
-
-
diff --git a/Devices/m5stack-stackchan/device.properties b/Devices/m5stack-stackchan/device.properties
index ad8c15c1..fa5fcaf9 100644
--- a/Devices/m5stack-stackchan/device.properties
+++ b/Devices/m5stack-stackchan/device.properties
@@ -12,6 +12,8 @@ hardware.tinyUsb=true
hardware.esptoolFlashFreq=120M
hardware.bluetooth=true
+storage.userDataLocation=SD
+
display.size=2"
display.shape=rectangle
display.dpi=200
diff --git a/Devices/m5stack-stickc-plus/CMakeLists.txt b/Devices/m5stack-stickc-plus/CMakeLists.txt
deleted file mode 100644
index b103142f..00000000
--- a/Devices/m5stack-stickc-plus/CMakeLists.txt
+++ /dev/null
@@ -1,7 +0,0 @@
-file(GLOB_RECURSE SOURCE_FILES Source/*.c*)
-
-idf_component_register(
- SRCS ${SOURCE_FILES}
- INCLUDE_DIRS "Source"
- REQUIRES Tactility esp_lvgl_port esp_lcd AXP192 ST7789 ButtonControl
-)
diff --git a/Devices/m5stack-stickc-plus/Source/Configuration.cpp b/Devices/m5stack-stickc-plus/Source/Configuration.cpp
deleted file mode 100644
index 15e09bfd..00000000
--- a/Devices/m5stack-stickc-plus/Source/Configuration.cpp
+++ /dev/null
@@ -1,29 +0,0 @@
-#include "devices/Display.h"
-#include "devices/Power.h"
-
-#include
-#include
-
-using namespace tt::hal;
-
-static bool initBoot() {
- // CH552 applies 4 V to GPIO 0, which reduces Wi-Fi sensitivity
- // Setting output to high adds a bias of 3.3 V and suppresses over-voltage:
- gpio::configure(0, gpio::Mode::Output, false, false);
- gpio::setLevel(0, true);
-
- return initAxp();
-}
-
-static DeviceVector createDevices() {
- return {
- getAxp192(),
- ButtonControl::createTwoButtonControl(37, 39),
- createDisplay()
- };
-}
-
-extern const Configuration hardwareConfiguration = {
- .initBoot = initBoot,
- .createDevices = createDevices
-};
diff --git a/Devices/m5stack-stickc-plus/Source/devices/Display.cpp b/Devices/m5stack-stickc-plus/Source/devices/Display.cpp
deleted file mode 100644
index f6fba81a..00000000
--- a/Devices/m5stack-stickc-plus/Source/devices/Display.cpp
+++ /dev/null
@@ -1,67 +0,0 @@
-#include "Display.h"
-#include "Power.h"
-
-#include
-
-#include
-#include
-
-static const auto LOGGER = tt::Logger("StickCPlus");
-
-static void setBacklightOn(bool on) {
- const auto axp = getAxp192();
- const auto* driver = axp->getAxp192();
- uint8_t state;
- if (axp192_read(driver, AXP192_DCDC13_LDO23_CONTROL, &state) != AXP192_OK) {
- LOGGER.info("Failed to read LCD brightness state");
- return;
- }
- std::bitset<8> new_state = state;
- if (new_state[2] != on) {
- new_state[2] = on;
- const auto new_state_long = new_state.to_ulong();
- axp192_write(driver, AXP192_DCDC13_LDO23_CONTROL, static_cast(new_state_long)); // Display on/off
- }
-}
-
-static void setBrightness(uint8_t brightness) {
- const auto axp = getAxp192();
- if (brightness)
- {
- brightness = (((brightness >> 1) + 8) / 13) + 5;
- setBacklightOn(true);
- axp192_write(axp->getAxp192(), AXP192_LDO23_VOLTAGE, brightness << 4); // Display brightness
- }
- else
- {
- setBacklightOn(false);
- }
-}
-
-std::shared_ptr createDisplay() {
- St7789Display::Configuration panel_configuration = {
- .horizontalResolution = LCD_HORIZONTAL_RESOLUTION,
- .verticalResolution = LCD_VERTICAL_RESOLUTION,
- .gapX = 52,
- .gapY = 40,
- .swapXY = false,
- .mirrorX = false,
- .mirrorY = false,
- .invertColor = true,
- .bufferSize = LCD_BUFFER_SIZE,
- .touch = nullptr,
- .backlightDutyFunction = setBrightness,
- .resetPin = LCD_PIN_RESET,
- .lvglSwapBytes = false
- };
-
- auto spi_configuration = std::make_shared(St7789Display::SpiConfiguration {
- .spiHostDevice = LCD_SPI_HOST,
- .csPin = LCD_PIN_CS,
- .dcPin = LCD_PIN_DC,
- .pixelClockFrequency = 40'000'000,
- .transactionQueueDepth = 10
- });
-
- return std::make_shared(panel_configuration, spi_configuration);
-}
diff --git a/Devices/m5stack-stickc-plus/Source/devices/Display.h b/Devices/m5stack-stickc-plus/Source/devices/Display.h
deleted file mode 100644
index bcab89c0..00000000
--- a/Devices/m5stack-stickc-plus/Source/devices/Display.h
+++ /dev/null
@@ -1,18 +0,0 @@
-#pragma once
-
-#include
-#include
-#include
-#include
-
-constexpr auto LCD_SPI_HOST = SPI2_HOST;
-constexpr auto LCD_PIN_CS = GPIO_NUM_5;
-constexpr auto LCD_PIN_DC = GPIO_NUM_23;
-constexpr auto LCD_PIN_RESET = GPIO_NUM_18;
-constexpr auto LCD_HORIZONTAL_RESOLUTION = 135;
-constexpr auto LCD_VERTICAL_RESOLUTION = 240;
-constexpr auto LCD_BUFFER_HEIGHT = LCD_VERTICAL_RESOLUTION / 3;
-constexpr auto LCD_BUFFER_SIZE = LCD_HORIZONTAL_RESOLUTION * LCD_BUFFER_HEIGHT;
-constexpr auto LCD_SPI_TRANSFER_SIZE_LIMIT = LCD_BUFFER_SIZE * LV_COLOR_DEPTH / 8;
-
-std::shared_ptr createDisplay();
diff --git a/Devices/m5stack-stickc-plus/Source/devices/Power.cpp b/Devices/m5stack-stickc-plus/Source/devices/Power.cpp
deleted file mode 100644
index 3efd322c..00000000
--- a/Devices/m5stack-stickc-plus/Source/devices/Power.cpp
+++ /dev/null
@@ -1,39 +0,0 @@
-#include
-#include
-
-static std::shared_ptr axp192 = nullptr;
-
-std::shared_ptr createAxp192() {
- assert(axp192 == nullptr);
- auto configuration = std::make_unique(device_find_by_name("i2c_internal"));
- axp192 = std::make_shared(std::move(configuration));
- return axp192;
-}
-
-std::shared_ptr getAxp192() {
- assert(axp192 != nullptr);
- return axp192;
-}
-
-bool initAxp() {
- const auto axp = createAxp192();
- return axp->init([](auto* driver) {
- // Reference: https://github.com/pr3y/Bruce/blob/main/lib/utility/AXP192.cpp
- axp192_ioctl(driver, AXP192_LDO23_VOLTAGE, 3300); // LCD backlight
- axp192_ioctl(driver, AXP192_ADC_ENABLE_1, 0xff); // Set all ADC enabled
- axp192_ioctl(driver, AXP192_CHARGE_CONTROL_1, 0xc0); // Battery charging at 4.2 V and 100 mA
- axp192_ioctl(driver, AXP192_ADC_ENABLE_1, 0xff); // Enable battery, AC in, Vbus, APS ADC
- uint8_t buffer;
- axp192_read(driver, AXP192_DCDC13_LDO23_CONTROL, &buffer);
- axp192_ioctl(driver, AXP192_DCDC13_LDO23_CONTROL, buffer | 0x4D); // Enable Ext, LDO2, LDO3, DCDC1
- axp192_ioctl(driver, AXP192_PEK, 0x0C); // 128 ms power on, 4 s power off
- axp192_ioctl(driver, AXP192_GPIO0_LDOIO0_VOLTAGE, 0xF0); // 3.3 V RTC voltage
- axp192_ioctl(driver, AXP192_GPIO0_CONTROL, 0x02); // Set GPIO0 to LDO
- axp192_ioctl(driver, AXP192_VBUS_IPSOUT_CHANNEL, 0x80); // Disable Vbus hold limit
- axp192_ioctl(driver, AXP192_BATTERY_CHARGE_HIGH_TEMP, 0xFC); // Set temperature protection
- axp192_ioctl(driver, AXP192_BATTERY_CHARGE_CONTROL, 0xA2); // Enable RTC BAT charge
- axp192_ioctl(driver, AXP192_SHUTDOWN_BATTERY_CHGLED_CONTROL, 0x46); // Enable bat detection
- return true;
- });
-
-}
diff --git a/Devices/m5stack-stickc-plus/Source/devices/Power.h b/Devices/m5stack-stickc-plus/Source/devices/Power.h
deleted file mode 100644
index 14a00889..00000000
--- a/Devices/m5stack-stickc-plus/Source/devices/Power.h
+++ /dev/null
@@ -1,9 +0,0 @@
-#pragma once
-
-#include
-
-bool initAxp();
-
-// Must call initAxp() first before this returns a non-nullptr response
-std::shared_ptr getAxp192();
-
diff --git a/Devices/m5stack-stickc-plus/Source/module.cpp b/Devices/m5stack-stickc-plus/Source/module.cpp
deleted file mode 100644
index caaf12df..00000000
--- a/Devices/m5stack-stickc-plus/Source/module.cpp
+++ /dev/null
@@ -1,23 +0,0 @@
-#include
-
-extern "C" {
-
-static error_t start() {
- // Empty for now
- return ERROR_NONE;
-}
-
-static error_t stop() {
- // Empty for now
- return ERROR_NONE;
-}
-
-struct Module m5stack_stickc_plus_module = {
- .name = "m5stack-stickc-plus",
- .start = start,
- .stop = stop,
- .symbols = nullptr,
- .internal = nullptr
-};
-
-}
diff --git a/Devices/m5stack-stickc-plus/device.properties b/Devices/m5stack-stickc-plus/device.properties
deleted file mode 100644
index 181cd242..00000000
--- a/Devices/m5stack-stickc-plus/device.properties
+++ /dev/null
@@ -1,18 +0,0 @@
-general.vendor=M5Stack
-general.name=StickC Plus
-general.incubating=true
-
-apps.launcherAppId=Launcher
-apps.autoStartAppId=ApWebServer
-
-hardware.target=ESP32
-hardware.flashSize=4MB
-hardware.spiRam=false
-hardware.esptoolFlashFreq=80M
-
-display.size=1.14"
-display.shape=rectangle
-display.dpi=242
-
-lvgl.colorDepth=16
-lvgl.uiDensity=compact
diff --git a/Devices/m5stack-stickc-plus/devicetree.yaml b/Devices/m5stack-stickc-plus/devicetree.yaml
deleted file mode 100644
index 287869e9..00000000
--- a/Devices/m5stack-stickc-plus/devicetree.yaml
+++ /dev/null
@@ -1,5 +0,0 @@
-dependencies:
-- Platforms/platform-esp32
-- Drivers/mpu6886-module
-- Drivers/bm8563-module
-dts: m5stack,stickc-plus.dts
diff --git a/Devices/m5stack-stickc-plus/m5stack,stickc-plus.dts b/Devices/m5stack-stickc-plus/m5stack,stickc-plus.dts
deleted file mode 100644
index 4946efdc..00000000
--- a/Devices/m5stack-stickc-plus/m5stack,stickc-plus.dts
+++ /dev/null
@@ -1,66 +0,0 @@
-/dts-v1/;
-
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-
-/ {
- compatible = "root";
- model = "M5Stack StickC Plus";
-
- gpio0 {
- compatible = "espressif,esp32-gpio";
- gpio-count = <40>;
- };
-
- i2c_internal {
- compatible = "espressif,esp32-i2c";
- port = ;
- clock-frequency = <400000>;
- pin-sda = <&gpio0 21 GPIO_FLAG_NONE>;
- pin-scl = <&gpio0 22 GPIO_FLAG_NONE>;
-
- mpu6886 {
- compatible = "invensense,mpu6886";
- reg = <0x68>;
- };
-
- bm8563 {
- compatible = "belling,bm8563";
- reg = <0x51>;
- };
- };
-
- i2c_grove {
- compatible = "espressif,esp32-i2c";
- port = ;
- clock-frequency = <400000>;
- pin-sda = <&gpio0 32 GPIO_FLAG_NONE>;
- pin-scl = <&gpio0 33 GPIO_FLAG_NONE>;
- };
-
- spi0 {
- compatible = "espressif,esp32-spi";
- host = ;
- cs-gpios = <&gpio0 5 GPIO_FLAG_NONE>;
- pin-mosi = <&gpio0 15 GPIO_FLAG_NONE>;
- pin-sclk = <&gpio0 13 GPIO_FLAG_NONE>;
-
- display {
- compatible = "display-placeholder";
- };
- };
-
- uart_grove: uart1 {
- compatible = "espressif,esp32-uart";
- status = "disabled";
- port = ;
- pin-tx = <&gpio0 33 GPIO_FLAG_NONE>;
- pin-rx = <&gpio0 32 GPIO_FLAG_NONE>;
- };
-};
diff --git a/Devices/m5stack-stickc-plus2/device.properties b/Devices/m5stack-stickc-plus2/device.properties
index f763c2ff..0dddc076 100644
--- a/Devices/m5stack-stickc-plus2/device.properties
+++ b/Devices/m5stack-stickc-plus2/device.properties
@@ -12,6 +12,8 @@ hardware.spiRamMode=QUAD
hardware.spiRamSpeed=80M
hardware.esptoolFlashFreq=80M
+storage.userDataLocation=Internal
+
display.size=1.14"
display.shape=rectangle
display.dpi=242
diff --git a/Devices/m5stack-sticks3/device.properties b/Devices/m5stack-sticks3/device.properties
index a055ee35..685147d1 100644
--- a/Devices/m5stack-sticks3/device.properties
+++ b/Devices/m5stack-sticks3/device.properties
@@ -12,6 +12,8 @@ hardware.esptoolFlashFreq=80M
hardware.tinyUsb=true
hardware.bluetooth=true
+storage.userDataLocation=SD
+
display.size=1.14"
display.shape=rectangle
display.dpi=242
diff --git a/Devices/m5stack-tab5/device.properties b/Devices/m5stack-tab5/device.properties
index dbb15512..8c11c4b6 100644
--- a/Devices/m5stack-tab5/device.properties
+++ b/Devices/m5stack-tab5/device.properties
@@ -13,6 +13,8 @@ hardware.bluetooth=true
hardware.usbHostEnabled=true
hardware.tinyUsb=true
+storage.userDataLocation=SD
+
display.size=5"
display.shape=rectangle
display.dpi=294
diff --git a/Devices/simulator/device.properties b/Devices/simulator/device.properties
index 1c66595e..8406fffb 100644
--- a/Devices/simulator/device.properties
+++ b/Devices/simulator/device.properties
@@ -5,5 +5,7 @@ apps.launcherAppId=Launcher
hardware.target=POSIX
+storage.userDataLocation=Internal
+
lvgl.colorDepth=16
lvgl.fontSize=14
diff --git a/Devices/unphone/device.properties b/Devices/unphone/device.properties
index 82ecc658..3b2f5c7a 100644
--- a/Devices/unphone/device.properties
+++ b/Devices/unphone/device.properties
@@ -10,6 +10,8 @@ hardware.spiRamMode=OCT
hardware.spiRamSpeed=80M
hardware.bluetooth=true
+storage.userDataLocation=SD
+
display.size=3.5"
display.shape=rectangle
display.dpi=165
diff --git a/Devices/waveshare-esp32-s3-geek/device.properties b/Devices/waveshare-esp32-s3-geek/device.properties
index f17c7f40..19b06a31 100644
--- a/Devices/waveshare-esp32-s3-geek/device.properties
+++ b/Devices/waveshare-esp32-s3-geek/device.properties
@@ -14,6 +14,8 @@ hardware.tinyUsb=true
hardware.esptoolFlashFreq=120M
hardware.bluetooth=true
+storage.userDataLocation=SD
+
display.size=1.14"
display.shape=rectangle
display.dpi=143
diff --git a/Devices/waveshare-s3-lcd-13/device.properties b/Devices/waveshare-s3-lcd-13/device.properties
index 6e7e5705..ed688994 100644
--- a/Devices/waveshare-s3-lcd-13/device.properties
+++ b/Devices/waveshare-s3-lcd-13/device.properties
@@ -14,6 +14,8 @@ hardware.tinyUsb=true
hardware.esptoolFlashFreq=120M
hardware.bluetooth=true
+storage.userDataLocation=SD
+
display.size=1.3"
display.shape=rectangle
display.dpi=261
diff --git a/Devices/waveshare-s3-touch-lcd-128/device.properties b/Devices/waveshare-s3-touch-lcd-128/device.properties
index 5f03f80a..071fa9f9 100644
--- a/Devices/waveshare-s3-touch-lcd-128/device.properties
+++ b/Devices/waveshare-s3-touch-lcd-128/device.properties
@@ -14,6 +14,8 @@ hardware.tinyUsb=true
hardware.esptoolFlashFreq=120M
hardware.bluetooth=true
+storage.userDataLocation=SD
+
display.size=1.28"
display.shape=circle
display.dpi=265
@@ -24,4 +26,4 @@ lvgl.uiDensity=compact
# Fix error "PSRAM space not enough for the Flash instructions" on boot:
sdkconfig.CONFIG_SPIRAM_FETCH_INSTRUCTIONS=n
sdkconfig.CONFIG_SPIRAM_RODATA=n
-sdkconfig.CONFIG_SPIRAM_XIP_FROM_PSRAM=n
\ No newline at end of file
+sdkconfig.CONFIG_SPIRAM_XIP_FROM_PSRAM=n
diff --git a/Devices/waveshare-s3-touch-lcd-147/device.properties b/Devices/waveshare-s3-touch-lcd-147/device.properties
index c4f263b2..092fa342 100644
--- a/Devices/waveshare-s3-touch-lcd-147/device.properties
+++ b/Devices/waveshare-s3-touch-lcd-147/device.properties
@@ -14,6 +14,8 @@ hardware.tinyUsb=true
hardware.esptoolFlashFreq=120M
hardware.bluetooth=true
+storage.userDataLocation=SD
+
display.size=1.47"
display.shape=rectangle
display.dpi=247
diff --git a/Devices/waveshare-s3-touch-lcd-43/device.properties b/Devices/waveshare-s3-touch-lcd-43/device.properties
index 5bf22b8c..c72986ac 100644
--- a/Devices/waveshare-s3-touch-lcd-43/device.properties
+++ b/Devices/waveshare-s3-touch-lcd-43/device.properties
@@ -4,7 +4,8 @@ general.name=S3 Touch LCD 4.3"
apps.launcherAppId=Launcher
hardware.target=ESP32S3
-hardware.flashSize=4MB
+# Some models have 16MB flash, but some have 8MB. We only support the latter to avoid making more builds.
+hardware.flashSize=8MB
hardware.spiRam=true
hardware.spiRamMode=OCT
hardware.spiRamSpeed=120M
@@ -12,6 +13,8 @@ hardware.tinyUsb=true
hardware.esptoolFlashFreq=120M
hardware.bluetooth=true
+storage.userDataLocation=Internal
+
display.size=4.3"
display.shape=rectangle
display.dpi=217
diff --git a/Devices/wireless-tag-wt32-sc01-plus/device.properties b/Devices/wireless-tag-wt32-sc01-plus/device.properties
index beecefe5..18f88ac7 100644
--- a/Devices/wireless-tag-wt32-sc01-plus/device.properties
+++ b/Devices/wireless-tag-wt32-sc01-plus/device.properties
@@ -12,6 +12,8 @@ hardware.tinyUsb=true
hardware.esptoolFlashFreq=80M
hardware.bluetooth=true
+storage.userDataLocation=SD
+
display.size=3.5"
display.shape=rectangle
display.dpi=165
diff --git a/Firmware/Kconfig b/Firmware/Kconfig
index 9ec78134..4ddbf8ef 100644
--- a/Firmware/Kconfig
+++ b/Firmware/Kconfig
@@ -77,6 +77,18 @@ menu "Tactility App"
config TT_TDECK_WORKAROUND
bool "Temporary work-around until we fix the T-Deck keyboard and trackball settings"
default n
+ choice TT_USER_DATA_LOCATION
+ prompt "User Data Location"
+ default TT_USER_DATA_LOCATION_INTERNAL
+ help
+ Where user data is stored on this device.
+ "Internal Flash" assumes /data always exists.
+ "SD Card" looks for the first mounted SD card at runtime and requires one to be present.
+ config TT_USER_DATA_LOCATION_SD
+ bool "SD Card"
+ config TT_USER_DATA_LOCATION_INTERNAL
+ bool "Internal Flash"
+ endchoice
config TT_SPLASH_DURATION
int "Splash Duration (ms)"
default 1000
diff --git a/Tactility/CMakeLists.txt b/Tactility/CMakeLists.txt
index ace46103..af6a8bd2 100644
--- a/Tactility/CMakeLists.txt
+++ b/Tactility/CMakeLists.txt
@@ -67,8 +67,10 @@ if (DEFINED ENV{ESP_IDF_VERSION})
if (NOT DEFINED TACTILITY_SKIP_SPIFFS)
# Read-only
fatfs_create_rawflash_image(system "${CMAKE_CURRENT_SOURCE_DIR}/../Data/system" FLASH_IN_PROJECT PRESERVE_TIME)
- # Read-write
- fatfs_create_spiflash_image(data "${CMAKE_CURRENT_SOURCE_DIR}/../Data/data" FLASH_IN_PROJECT PRESERVE_TIME)
+ # Read-write (skipped when user data lives on the SD card instead of internal flash)
+ if (NOT CONFIG_TT_USER_DATA_LOCATION_SD)
+ fatfs_create_spiflash_image(data "${CMAKE_CURRENT_SOURCE_DIR}/../Data/data" FLASH_IN_PROJECT PRESERVE_TIME)
+ endif ()
endif ()
endif ()
diff --git a/Tactility/Include/Tactility/Paths.h b/Tactility/Include/Tactility/Paths.h
index c3930db9..2d009327 100644
--- a/Tactility/Include/Tactility/Paths.h
+++ b/Tactility/Include/Tactility/Paths.h
@@ -11,7 +11,9 @@ bool findFirstMountedSdCardPath(std::string& path);
FileSystem* findSdcardFileSystem(bool mustBeMounted);
-std::string getSystemRootPath();
+std::string getUserDataRootPath();
+
+std::string getUserDataPath();
std::string getTempPath();
@@ -19,7 +21,7 @@ std::string getAppInstallPath();
std::string getAppInstallPath(const std::string& appId);
-std::string getUserPath();
+std::string getUserHomePath();
std::string getAppUserPath(const std::string& appId);
diff --git a/Tactility/Private/Tactility/app/chat/ChatSettings.h b/Tactility/Private/Tactility/app/chat/ChatSettings.h
index a504c9ac..f9e5ab07 100644
--- a/Tactility/Private/Tactility/app/chat/ChatSettings.h
+++ b/Tactility/Private/Tactility/app/chat/ChatSettings.h
@@ -14,8 +14,6 @@
namespace tt::app::chat {
-constexpr auto* CHAT_SETTINGS_FILE = "/data/settings/chat.properties";
-
struct ChatSettingsData {
uint32_t senderId = 0; // Unique device ID (randomly generated on first launch)
std::string nickname = "Device";
diff --git a/Tactility/Private/Tactility/service/webserver/WebServerService.h b/Tactility/Private/Tactility/service/webserver/WebServerService.h
index a27dc525..058c1600 100644
--- a/Tactility/Private/Tactility/service/webserver/WebServerService.h
+++ b/Tactility/Private/Tactility/service/webserver/WebServerService.h
@@ -97,6 +97,9 @@ public:
// Global accessor for controlling the WebServer service
void setWebServerEnabled(bool enabled);
+// Returns whether the HTTP server is actually running right now (not just the persisted setting)
+bool isWebServerEnabled();
+
// Get the pubsub for subscribing to WebServer events
std::shared_ptr> getPubsub();
diff --git a/Tactility/Source/Paths.cpp b/Tactility/Source/Paths.cpp
index 8affa3c0..6857f706 100644
--- a/Tactility/Source/Paths.cpp
+++ b/Tactility/Source/Paths.cpp
@@ -4,6 +4,9 @@
#include
#include
+#include
+#include
+#include
#include
namespace tt {
@@ -20,14 +23,12 @@ bool findFirstMountedSdCardPath(std::string& path) {
FileSystem* findSdcardFileSystem(bool mustBeMounted) {
FileSystem* found = nullptr;
file_system_for_each(&found, [](auto* fs, void* context) {
- char path[128];
- if (file_system_get_path(fs, path, sizeof(path)) != ERROR_NONE) return true;
- // TODO: Find a better way to identify SD card paths
- if (std::string(path).starts_with("/sdcard")) {
- *static_cast(context) = fs;
- return false;
+ auto* owner = file_system_get_owner(fs);
+ if (owner == nullptr || device_get_type(owner) != &SDCARD_TYPE) {
+ return true;
}
- return true;
+ *static_cast(context) = fs;
+ return false;
});
if (found && mustBeMounted && !file_system_is_mounted(found)) {
return nullptr;
@@ -35,24 +36,38 @@ FileSystem* findSdcardFileSystem(bool mustBeMounted) {
return found;
}
-std::string getSystemRootPath() {
- std::string root_path;
- if (!findFirstMountedSdCardPath(root_path)) {
- root_path = file::MOUNT_POINT_DATA;
- }
- return root_path;
+std::string getUserDataRootPath() {
+#ifdef CONFIG_TT_USER_DATA_LOCATION_INTERNAL
+ return file::MOUNT_POINT_DATA;
+#elif CONFIG_TT_USER_DATA_LOCATION_SD
+ auto* fs = findSdcardFileSystem(false);
+ check(fs);
+ char fs_path[32];
+ check(file_system_get_path(fs, fs_path, sizeof(fs_path)) == ERROR_NONE);
+ return std::string(fs_path);
+#else
+#error CONFIG_TT_USER_DATA_* not set or unsupported
+#endif
+}
+
+std::string getUserDataPath() {
+#ifdef ESP_PLATFORM
+ return getUserDataRootPath() + "/tactility";
+#else
+ return "data";
+#endif
}
std::string getTempPath() {
- return getSystemRootPath() + "/tmp";
+ return getUserDataPath() + "/tmp";
}
std::string getAppInstallPath() {
- return getSystemRootPath() + "/app";
+ return getUserDataPath() + "/app";
}
-std::string getUserPath() {
- return getSystemRootPath() + "/user";
+std::string getUserHomePath() {
+ return getUserDataPath() + "/user";
}
std::string getAppInstallPath(const std::string& appId) {
@@ -62,7 +77,7 @@ std::string getAppInstallPath(const std::string& appId) {
std::string getAppUserPath(const std::string& appId) {
assert(app::isValidId(appId));
- return std::format("{}/app/{}", getUserPath(), appId);
+ return std::format("{}/app/{}", getUserHomePath(), appId);
}
}
\ No newline at end of file
diff --git a/Tactility/Source/Tactility.cpp b/Tactility/Source/Tactility.cpp
index 6907934c..2530b932 100644
--- a/Tactility/Source/Tactility.cpp
+++ b/Tactility/Source/Tactility.cpp
@@ -35,6 +35,9 @@
#include
#endif
+#include "Tactility/Paths.h"
+
+
#include
namespace tt {
@@ -242,7 +245,7 @@ static void registerInstalledAppsFromFileSystems() {
if (!file_system_is_mounted(fs)) return true;
char path[128];
if (file_system_get_path(fs, path, sizeof(path)) != ERROR_NONE) return true;
- const auto app_path = std::format("{}/app", path);
+ const auto app_path = std::format("{}/tactility/app", path);
if (!app_path.starts_with(file::MOUNT_POINT_SYSTEM) && file::isDirectory(app_path)) {
LOGGER.info("Registering apps from {}", app_path);
registerInstalledApps(app_path);
@@ -284,18 +287,21 @@ static void registerAndStartPrimaryServices() {
#endif
}
-void createTempDirectory(const std::string& rootPath) {
- auto temp_path = std::format("{}/tmp", rootPath);
+void createTempDirectory() {
+ auto data_path = getUserDataPath();
+ auto temp_path = std::format("{}/tmp", data_path);
if (!file::isDirectory(temp_path)) {
- auto lock = file::getLock(rootPath)->asScopedLock();
+ auto lock = file::getLock(data_path)->asScopedLock();
if (lock.lock(1000 / portTICK_PERIOD_MS)) {
- if (mkdir(temp_path.c_str(), 0777) == 0) {
+ if (!file::findOrCreateParentDirectory(temp_path, 0777)) {
+ LOGGER.error("Failed to create {}", data_path);
+ } else if (mkdir(temp_path.c_str(), 0777) == 0) {
LOGGER.info("Created {}", temp_path);
} else {
LOGGER.error("Failed to create {}", temp_path);
}
} else {
- LOGGER.error(LOG_MESSAGE_MUTEX_LOCK_FAILED_FMT, rootPath);
+ LOGGER.error(LOG_MESSAGE_MUTEX_LOCK_FAILED_FMT, data_path);
}
} else {
LOGGER.info("Found existing {}", temp_path);
@@ -303,14 +309,7 @@ void createTempDirectory(const std::string& rootPath) {
}
void prepareFileSystems() {
- file_system_for_each(nullptr, [](auto* fs, void* context) {
- if (!file_system_is_mounted(fs)) return true;
- char path[128];
- if (file_system_get_path(fs, path, sizeof(path)) != ERROR_NONE) return true;
- if (std::string(path) == file::MOUNT_POINT_SYSTEM) return true;
- createTempDirectory(path);
- return true;
- });
+ createTempDirectory();
}
void registerApps() {
diff --git a/Tactility/Source/app/boot/Boot.cpp b/Tactility/Source/app/boot/Boot.cpp
index 39a38c35..5dc0d958 100644
--- a/Tactility/Source/app/boot/Boot.cpp
+++ b/Tactility/Source/app/boot/Boot.cpp
@@ -2,6 +2,7 @@
#include
#include
+#include
#include
#include
#include
@@ -10,6 +11,7 @@
#include
#include
#include
+#include
#include
#include
#include
@@ -44,6 +46,10 @@ class BootApp : public App {
// onShow() reads this instead of the live flag to avoid a race between the two.
static std::atomic isUsbBootSplash;
+ // Set by bootThreadCallback() when CONFIG_TT_USER_DATA_LOCATION_SD is defined but no SD card is mounted.
+ // onShow() reads this to show an error instead of the normal splash, and boot halts instead of starting the launcher.
+ static std::atomic sdCardMissing;
+
Thread thread = Thread(
"boot",
5120,
@@ -122,11 +128,20 @@ class BootApp : public App {
setupDisplay(); // Set backlight
prepareFileSystems();
+#ifdef CONFIG_TT_USER_DATA_LOCATION_SD
+ std::string sd_path;
+ if (!findFirstMountedSdCardPath(sd_path)) {
+ LOGGER.error("SD card not found");
+ sdCardMissing = true;
+ }
+#endif
+
if (!setupUsbBootMode()) {
LOGGER.info("initFromBootApp");
registerApps();
waitForMinimalSplashDuration(start_time);
- stop(manifest.appId);
+ // When SD card is missing, wait for dialog result
+ if (!sdCardMissing) stop(manifest.appId);
startNextApp();
}
@@ -162,6 +177,11 @@ class BootApp : public App {
}
static void startNextApp() {
+ if (sdCardMissing) {
+ alertdialog::start("Error", "SD card not found.\nPlease insert one and reboot.", std::vector { "Reboot" });
+ return;
+ }
+
#ifdef ESP_PLATFORM
if (esp_reset_reason() == ESP_RST_PANIC) {
crashdiagnostics::start();
@@ -195,6 +215,12 @@ public:
thread.join();
}
+ void onResult(AppContext& /*app*/, LaunchId /*launchId*/, Result /*result*/, std::unique_ptr /*bundle*/) override {
+#ifdef ESP_PLATFORM
+ esp_restart();
+#endif
+ }
+
void onShow(AppContext& app, lv_obj_t* parent) override {
lvgl::obj_set_style_bg_blacken(parent);
lv_obj_set_style_border_width(parent, 0, LV_STATE_DEFAULT);
@@ -232,6 +258,7 @@ public:
};
std::atomic BootApp::isUsbBootSplash = false;
+std::atomic BootApp::sdCardMissing = false;
extern const AppManifest manifest = {
.appId = "Boot",
diff --git a/Tactility/Source/app/chat/ChatSettings.cpp b/Tactility/Source/app/chat/ChatSettings.cpp
index f4ac8417..b0502011 100644
--- a/Tactility/Source/app/chat/ChatSettings.cpp
+++ b/Tactility/Source/app/chat/ChatSettings.cpp
@@ -10,6 +10,7 @@
#include
#include
#include
+#include
#include
@@ -24,6 +25,10 @@ namespace tt::app::chat {
static const auto LOGGER = Logger("ChatSettings");
+static std::string getSettingsFilePath() {
+ return getUserDataPath() + "/settings/chat.properties";
+}
+
constexpr auto* KEY_SENDER_ID = "senderId";
constexpr auto* KEY_NICKNAME = "nickname";
constexpr auto* KEY_ENCRYPTION_KEY = "encryptionKey";
@@ -120,7 +125,7 @@ ChatSettingsData loadSettings() {
ChatSettingsData settings = getDefaultSettings();
std::map map;
- if (!file::loadPropertiesFile(CHAT_SETTINGS_FILE, map)) {
+ if (!file::loadPropertiesFile(getSettingsFilePath(), map)) {
settings.senderId = generateSenderId();
return settings;
}
@@ -171,11 +176,11 @@ bool saveSettings(const ChatSettingsData& settings) {
map[KEY_ENCRYPTION_KEY] = "";
}
- return file::savePropertiesFile(CHAT_SETTINGS_FILE, map);
+ return file::savePropertiesFile(getSettingsFilePath(), map);
}
bool settingsFileExists() {
- return access(CHAT_SETTINGS_FILE, F_OK) == 0;
+ return access(getSettingsFilePath().c_str(), F_OK) == 0;
}
} // namespace tt::app::chat
diff --git a/Tactility/Source/app/webserversettings/WebServerSettings.cpp b/Tactility/Source/app/webserversettings/WebServerSettings.cpp
index 5fc63f99..b9ae51bb 100644
--- a/Tactility/Source/app/webserversettings/WebServerSettings.cpp
+++ b/Tactility/Source/app/webserversettings/WebServerSettings.cpp
@@ -29,7 +29,6 @@ class WebServerSettingsApp final : public App {
settings::webserver::WebServerSettings originalSettings;
bool updated = false;
bool wifiSettingsChanged = false;
- bool webServerEnabledChanged = false;
lv_obj_t* dropdownWifiMode = nullptr;
lv_obj_t* textAreaApPassword = nullptr;
lv_obj_t* switchApOpenNetwork = nullptr;
@@ -61,11 +60,19 @@ class WebServerSettingsApp final : public App {
getMainDispatcher().dispatch([app, enabled] {
app->wsSettings.webServerEnabled = enabled;
app->updated = true;
- app->webServerEnabledChanged = true;
if (lvgl::lock(100)) {
app->updateUrlDisplay();
lvgl::unlock();
}
+
+ // Apply immediately instead of waiting for app exit
+ const auto copy = app->wsSettings;
+ if (!settings::webserver::save(copy)) {
+ LOGGER.warn("Failed to persist WebServer settings; changes may be lost on reboot");
+ }
+ service::webserver::getPubsub()->publish(service::webserver::WebServerEvent::WebServerSettingsChanged);
+ LOGGER.info("WebServer {}", enabled ? "enabling..." : "disabling...");
+ service::webserver::setWebServerEnabled(enabled);
});
}
@@ -131,30 +138,6 @@ class WebServerSettingsApp final : public App {
});
}
- static void onSyncAssets(lv_event_t* e) {
- auto* app = static_cast(lv_event_get_user_data(e));
- auto* btn = static_cast(lv_event_get_target_obj(e));
- lv_obj_add_state(btn, LV_STATE_DISABLED);
- LOGGER.info("Manual asset sync triggered");
-
- getMainDispatcher().dispatch([app, btn]{
- bool success = service::webserver::syncAssets();
- if (success) {
- LOGGER.info("Asset sync completed successfully");
- } else {
- LOGGER.error("Asset sync failed");
- }
- // Only re-enable if button still exists (user hasn't navigated away)
- // Must acquire LVGL lock since we're not in an LVGL event callback context
- if (lvgl::lock(1000)) {
- if (lv_obj_is_valid(btn)) {
- lv_obj_remove_state(btn, LV_STATE_DISABLED);
- }
- lvgl::unlock();
- }
- });
- }
-
void updateUrlDisplay() {
if (!labelUrlValue) return;
@@ -197,6 +180,8 @@ class WebServerSettingsApp final : public App {
public:
void onCreate(AppContext& app) override {
wsSettings = settings::webserver::loadOrGetDefault();
+ // Reflect the server's actual running state, in case it differs from the persisted setting
+ wsSettings.webServerEnabled = service::webserver::isWebServerEnabled();
originalSettings = wsSettings;
}
@@ -341,32 +326,6 @@ public:
updateUrlDisplay();
- // Sync Assets button
- auto* sync_wrapper = lv_obj_create(main_wrapper);
- lv_obj_set_size(sync_wrapper, LV_PCT(100), LV_SIZE_CONTENT);
- lv_obj_set_style_pad_all(sync_wrapper, 10, LV_STATE_DEFAULT);
- lv_obj_set_style_border_width(sync_wrapper, 1, LV_STATE_DEFAULT);
- lv_obj_set_flex_flow(sync_wrapper, LV_FLEX_FLOW_COLUMN);
- lv_obj_set_style_flex_cross_place(sync_wrapper, LV_FLEX_ALIGN_START, 0);
-
- auto* sync_label = lv_label_create(sync_wrapper);
- lv_label_set_text(sync_label, "Asset Synchronization");
-
- auto* sync_info = lv_label_create(sync_wrapper);
- lv_label_set_long_mode(sync_info, LV_LABEL_LONG_WRAP);
- lv_obj_set_width(sync_info, LV_PCT(95));
- if (lv_display_get_color_format(lv_obj_get_display(parent)) != LV_COLOR_FORMAT_L8) {
- lv_obj_set_style_text_color(sync_info, lv_palette_main(LV_PALETTE_GREY), 0);
- }
- lv_label_set_text(sync_info, "Sync web assets between Data partition and SD card backup");
-
- auto* sync_button = lv_btn_create(sync_wrapper);
- lv_obj_set_width(sync_button, LV_SIZE_CONTENT);
- auto* sync_button_label = lv_label_create(sync_button);
- lv_label_set_text(sync_button_label, "Sync Assets Now");
- lv_obj_center(sync_button_label);
- lv_obj_add_event_cb(sync_button, onSyncAssets, LV_EVENT_CLICKED, this);
-
// Info text
auto* info_label = lv_label_create(main_wrapper);
lv_label_set_long_mode(info_label, LV_LABEL_LONG_WRAP);
@@ -394,11 +353,11 @@ public:
}
// Save to flash only (settings sync at boot handles SD restore)
+ // Note: the enable/disable toggle already saved and applied itself immediately
const auto copy = wsSettings;
const bool wifiChanged = wifiSettingsChanged;
- const bool webServerChanged = webServerEnabledChanged;
- getMainDispatcher().dispatch([copy, wifiChanged, webServerChanged]{
+ getMainDispatcher().dispatch([copy, wifiChanged]{
// Save to flash (fast, low memory pressure)
if (!settings::webserver::save(copy)) {
LOGGER.warn("Failed to persist WebServer settings; changes may be lost on reboot");
@@ -411,12 +370,6 @@ public:
if (wifiChanged) {
LOGGER.info("WiFi mode changed to {}", copy.wifiMode == settings::webserver::WiFiMode::AccessPoint ? "AP" : "Station");
}
-
- // Control WebServer service immediately
- if (webServerChanged) {
- LOGGER.info("WebServer {}", copy.webServerEnabled ? "enabling..." : "disabling...");
- service::webserver::setWebServerEnabled(copy.webServerEnabled);
- }
});
}
}
diff --git a/Tactility/Source/bluetooth/BluetoothSettings.cpp b/Tactility/Source/bluetooth/BluetoothSettings.cpp
index f9f23dc6..82237048 100644
--- a/Tactility/Source/bluetooth/BluetoothSettings.cpp
+++ b/Tactility/Source/bluetooth/BluetoothSettings.cpp
@@ -4,13 +4,16 @@
#include
#include
#include
+#include
namespace tt::bluetooth::settings {
static const auto LOGGER = Logger("BluetoothSettings");
-// Use the same path as the old service so existing settings survive migration.
-constexpr auto* SETTINGS_PATH = "/data/service/bluetooth/settings.properties";
+static std::string getSettingsPath() {
+ return getUserDataPath() + "/settings/bluetooth.settings";
+}
+
constexpr auto* KEY_ENABLE_ON_BOOT = "enableOnBoot";
constexpr auto* KEY_SPP_AUTO_START = "sppAutoStart";
constexpr auto* KEY_MIDI_AUTO_START = "midiAutoStart";
@@ -27,7 +30,7 @@ static bool cached_valid = false;
static bool load(BluetoothSettings& out) {
std::map map;
- if (!file::loadPropertiesFile(SETTINGS_PATH, map)) {
+ if (!file::loadPropertiesFile(getSettingsPath(), map)) {
return false;
}
auto it = map.find(KEY_ENABLE_ON_BOOT);
@@ -44,11 +47,11 @@ static bool load(BluetoothSettings& out) {
static bool save(const BluetoothSettings& s) {
std::map map;
- file::loadPropertiesFile(SETTINGS_PATH, map); // ignore failure — may not exist yet
+ file::loadPropertiesFile(getSettingsPath(), map); // ignore failure — may not exist yet
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(SETTINGS_PATH, map);
+ return file::savePropertiesFile(getSettingsPath(), map);
}
static BluetoothSettings getCachedOrLoad() {
diff --git a/Tactility/Source/service/ServicePaths.cpp b/Tactility/Source/service/ServicePaths.cpp
index eb5db903..0cf6d430 100644
--- a/Tactility/Source/service/ServicePaths.cpp
+++ b/Tactility/Source/service/ServicePaths.cpp
@@ -1,21 +1,15 @@
#include
#include
-#include
+#include
#include
#include
-#ifdef ESP_PLATFORM
-constexpr auto PARTITION_PREFIX = std::string("/");
-#else
-constexpr auto PARTITION_PREFIX = std::string("");
-#endif
-
namespace tt::service {
std::string ServicePaths::getUserDataDirectory() const {
- return std::format("{}{}/service/{}", PARTITION_PREFIX, file::DATA_PARTITION_NAME, manifest->id);
+ return std::format("{}/service/{}", tt::getUserDataPath(), manifest->id);
}
std::string ServicePaths::getUserDataPath(const std::string& childPath) const {
@@ -24,7 +18,7 @@ std::string ServicePaths::getUserDataPath(const std::string& childPath) const {
}
std::string ServicePaths::getAssetsDirectory() const {
- return std::format("{}{}/service/{}/assets", PARTITION_PREFIX, file::SYSTEM_PARTITION_NAME, manifest->id);
+ return std::format("{}/service/{}/assets", tt::getUserDataPath(), manifest->id);
}
std::string ServicePaths::getAssetsPath(const std::string& childPath) const {
diff --git a/Tactility/Source/service/development/DevelopmentSettings.cpp b/Tactility/Source/service/development/DevelopmentSettings.cpp
index 04b2fccf..4cdcd89a 100644
--- a/Tactility/Source/service/development/DevelopmentSettings.cpp
+++ b/Tactility/Source/service/development/DevelopmentSettings.cpp
@@ -1,6 +1,7 @@
#ifdef ESP_PLATFORM
#include
#include
+#include
#include
#include