Quick Actions
- Sync Assets
${data.features_enabled?.screenshot ? 'Screenshot ' : ''}
Reboot
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 fa4ab4c7e..000000000
--- 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 5dfe44db2..000000000
--- a/Data/webserver/version.json
+++ /dev/null
@@ -1,3 +0,0 @@
-{
- "version": 0
-}
diff --git a/Tactility/CMakeLists.txt b/Tactility/CMakeLists.txt
index ace461036..af6a8bd26 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 c3930db97..2d0093272 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 a504c9ac4..f9e5ab073 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/Source/Paths.cpp b/Tactility/Source/Paths.cpp
index 3e57255b0..6857f706e 100644
--- a/Tactility/Source/Paths.cpp
+++ b/Tactility/Source/Paths.cpp
@@ -5,6 +5,8 @@
#include
#include
+#include
+#include
#include
namespace tt {
@@ -21,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;
@@ -36,28 +36,38 @@ FileSystem* findSdcardFileSystem(bool mustBeMounted) {
return found;
}
-std::string getSystemRootPath() {
+std::string getUserDataRootPath() {
#ifdef CONFIG_TT_USER_DATA_LOCATION_INTERNAL
return file::MOUNT_POINT_DATA;
#elif CONFIG_TT_USER_DATA_LOCATION_SD
- std::string root_path;
- check(findFirstMountedSdCardPath(root_path), "No SD card mounted");
- return root_path;
+ 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) {
@@ -67,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 6907934c0..ad79924e4 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,10 +287,11 @@ 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) {
LOGGER.info("Created {}", temp_path);
@@ -295,7 +299,7 @@ void createTempDirectory(const std::string& rootPath) {
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 +307,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 39a38c356..5dc0d958b 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 f4ac84179..b05020111 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 5fc63f999..cfbf643ea 100644
--- a/Tactility/Source/app/webserversettings/WebServerSettings.cpp
+++ b/Tactility/Source/app/webserversettings/WebServerSettings.cpp
@@ -131,30 +131,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;
@@ -341,32 +317,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);
diff --git a/Tactility/Source/bluetooth/BluetoothSettings.cpp b/Tactility/Source/bluetooth/BluetoothSettings.cpp
index f9f23dc64..822370482 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 eb5db903d..0cf6d4306 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 04b2fccf8..4cdcd89a3 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
#include
@@ -9,7 +10,10 @@ namespace tt::service::development {
static const auto LOGGER = Logger("DevSettings");
-constexpr auto* SETTINGS_FILE = "/data/settings/development.properties";
+static std::string getSettingsFilePath() {
+ return getUserDataPath() + "/settings/development.properties";
+}
+
constexpr auto* SETTINGS_KEY_ENABLE_ON_BOOT = "enableOnBoot";
struct DevelopmentSettings {
@@ -18,7 +22,7 @@ struct DevelopmentSettings {
static bool load(DevelopmentSettings& settings) {
std::map map;
- if (!file::loadPropertiesFile(SETTINGS_FILE, map)) {
+ if (!file::loadPropertiesFile(getSettingsFilePath(), map)) {
return false;
}
@@ -34,13 +38,13 @@ static bool load(DevelopmentSettings& settings) {
static bool save(const DevelopmentSettings& settings) {
std::map map;
map[SETTINGS_KEY_ENABLE_ON_BOOT] = settings.enableOnBoot ? "true" : "false";
- return file::savePropertiesFile(SETTINGS_FILE, map);
+ return file::savePropertiesFile(getSettingsFilePath(), map);
}
void setEnableOnBoot(bool enable) {
DevelopmentSettings properties { .enableOnBoot = enable };
if (!save(properties)) {
- LOGGER.error("Failed to save {}", SETTINGS_FILE);
+ LOGGER.error("Failed to save {}", getSettingsFilePath());
}
}
diff --git a/Tactility/Source/service/webserver/AssetVersion.cpp b/Tactility/Source/service/webserver/AssetVersion.cpp
index e06d41e78..37cd4460e 100644
--- a/Tactility/Source/service/webserver/AssetVersion.cpp
+++ b/Tactility/Source/service/webserver/AssetVersion.cpp
@@ -15,9 +15,9 @@
namespace tt::service::webserver {
static const auto LOGGER = tt::Logger("AssetVersion");
-constexpr auto* DATA_VERSION_FILE = "/data/webserver/version.json";
+constexpr auto* DATA_VERSION_FILE = "/system/app/WebServer/version.json";
constexpr auto* SD_VERSION_FILE = "/sdcard/tactility/webserver/version.json";
-constexpr auto* DATA_ASSETS_DIR = "/data/webserver";
+constexpr auto* DATA_ASSETS_DIR = "/system/app/WebServer";
constexpr auto* SD_ASSETS_DIR = "/sdcard/tactility/webserver";
static bool loadVersionFromFile(const char* path, AssetVersion& version) {
@@ -349,17 +349,6 @@ bool syncAssets() {
return true;
}
- // POST-FLASH RECOVERY: Data empty but SD card exists
- if (!dataExists) {
- LOGGER.info("Data partition empty - copying from SD card (recovery mode)");
- if (!copyDirectory(SD_ASSETS_DIR, DATA_ASSETS_DIR)) {
- LOGGER.error("Failed to copy assets from SD card to Data");
- return false;
- }
- LOGGER.info("Recovery complete - assets restored from SD card");
- return true;
- }
-
// NORMAL OPERATION: Both exist - compare versions
AssetVersion dataVersion, sdVersion;
bool hasDataVer = loadDataVersion(dataVersion);
diff --git a/Tactility/Source/service/webserver/WebServerService.cpp b/Tactility/Source/service/webserver/WebServerService.cpp
index 5675f689b..0206de5b9 100644
--- a/Tactility/Source/service/webserver/WebServerService.cpp
+++ b/Tactility/Source/service/webserver/WebServerService.cpp
@@ -207,11 +207,6 @@ bool WebServerService::onStart(ServiceContext& service) {
statusbarIconId = lvgl::statusbar_icon_add();
lvgl::statusbar_icon_set_visibility(statusbarIconId, false);
- // Run asset synchronization on startup
- if (!syncAssets()) {
- LOGGER.warn("Asset sync failed, but continuing with available assets");
- }
-
// Load and cache settings once at boot
bool serverEnabled;
{
@@ -619,7 +614,7 @@ static bool isAllowedBasePath(const std::string& path, bool allowRoot = false) {
return false;
}
if (allowRoot && path == "/") return true;
- return path == "/data" || path.starts_with("/data/") || path == "/sdcard" || path.starts_with("/sdcard/");
+ return path.starts_with("/data") || path.starts_with("/system/app/WebServer") || path.starts_with("/sdcard");
}
// Normalize client-supplied path: URL-decode, trim quotes/control chars, ensure leading slash, collapse duplicate slashes
@@ -990,7 +985,6 @@ esp_err_t WebServerService::handleAdminPost(httpd_req_t* request) {
}
const char* uri = request->uri;
- if (strncmp(uri, "/admin/sync", 11) == 0) return handleSync(request);
if (strncmp(uri, "/admin/reboot", 13) == 0) return handleReboot(request);
LOGGER.info("POST {} - not found in admin dispatcher", uri);
httpd_resp_send_err(request, HTTPD_404_NOT_FOUND, "not found");
@@ -1460,10 +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;
- if (!findFirstMountedSdCardPath(save_path)) {
- save_path = file::MOUNT_POINT_DATA;
- }
+ std::string save_path = getUserDataRootPath();
// Find next available filename with incrementing number
std::string screenshot_path;
@@ -1683,25 +1674,9 @@ esp_err_t WebServerService::handleFsRename(httpd_req_t* request) {
// endregion
-esp_err_t WebServerService::handleSync(httpd_req_t* request) {
-
- LOGGER.info("POST /sync");
-
- bool success = syncAssets();
-
- if (success) {
- httpd_resp_sendstr(request, "Assets synchronized successfully");
- } else {
- httpd_resp_send_err(request, HTTPD_500_INTERNAL_SERVER_ERROR, "Asset sync failed");
- }
-
- return success ? ESP_OK : ESP_FAIL;
-}
-
esp_err_t WebServerService::handleReboot(httpd_req_t* request) {
LOGGER.info("POST /reboot");
-
httpd_resp_sendstr(request, "Rebooting...");
// Reboot after a short delay to allow response to be sent
@@ -1724,7 +1699,7 @@ esp_err_t WebServerService::handleAssets(httpd_req_t* request) {
// Special case: serve favicon from system assets
if (strcmp(uri, "/favicon.ico") == 0) {
- const char* faviconPath = "/data/system/spinner.png";
+ const char* faviconPath = "/system/spinner.png";
if (file::isFile(faviconPath)) {
httpd_resp_set_type(request, "image/png");
httpd_resp_set_hdr(request, "Cache-Control", "public, max-age=86400");
@@ -1767,11 +1742,9 @@ esp_err_t WebServerService::handleAssets(httpd_req_t* request) {
return ESP_FAIL;
}
- std::string dataPath = std::string("/data/webserver") + requestedPath;
+ std::string dataPath = std::string("/system/app/WebServer") + requestedPath;
if (requestedPath == "/dashboard.html" && !file::isFile(dataPath.c_str())) {
- // Dashboard doesn't exist, try default.html
- dataPath = "/data/webserver/default.html";
LOGGER.info("dashboard.html not found, serving default.html");
}
diff --git a/Tactility/Source/service/wifi/WifiApSettings.cpp b/Tactility/Source/service/wifi/WifiApSettings.cpp
index 292062ad3..c1c49270a 100644
--- a/Tactility/Source/service/wifi/WifiApSettings.cpp
+++ b/Tactility/Source/service/wifi/WifiApSettings.cpp
@@ -181,6 +181,10 @@ bool save(const WifiApSettings& apSettings) {
}
const auto file_path = getApPropertiesFilePath(service_context->getPaths(), apSettings.ssid);
+ if (!file::findOrCreateParentDirectory(file_path, 0755)) {
+ LOGGER.error("Failed to create {}", file_path);
+ return false;
+ }
std::map map;
diff --git a/Tactility/Source/service/wifi/WifiBootSplashInit.cpp b/Tactility/Source/service/wifi/WifiBootSplashInit.cpp
index d4ea00ebf..23523e227 100644
--- a/Tactility/Source/service/wifi/WifiBootSplashInit.cpp
+++ b/Tactility/Source/service/wifi/WifiBootSplashInit.cpp
@@ -124,16 +124,9 @@ void bootSplashInit() {
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(file::MOUNT_POINT_DATA, "settings");
+ const std::string data_settings_path = file::getChildPath(getUserDataPath(), "provisioning");
importWifiApSettingsFromDir(data_settings_path);
- // Then scan attached SD cards as before.
- std::string sdcard_path;
- if (findFirstMountedSdCardPath((sdcard_path))) {
- const std::string sd_settings_path = file::getChildPath(sdcard_path, "settings");
- importWifiApSettingsFromDir(sd_settings_path);
- }
-
if (settings::shouldEnableOnBoot()) {
LOGGER.info("Auto-enabling due to setting");
getMainDispatcher().dispatch([] -> void { setEnabled(true); });
diff --git a/Tactility/Source/service/wifi/WifiSettings.cpp b/Tactility/Source/service/wifi/WifiSettings.cpp
index e712ffc9d..716b7a4ca 100644
--- a/Tactility/Source/service/wifi/WifiSettings.cpp
+++ b/Tactility/Source/service/wifi/WifiSettings.cpp
@@ -21,14 +21,14 @@ static WifiSettings cachedSettings {
static bool cached = false;
-static bool load(WifiSettings& settings) {
- auto service_context = findServiceContext();
- if (service_context == nullptr) {
- return false;
- }
+static bool hasWifiSettingsFile(std::shared_ptr context) {
+ std::string settings_path = context->getPaths()->getUserDataPath("settings.properties");
+ return file::isFile(settings_path);
+}
+static bool load(std::shared_ptr context, WifiSettings& settings) {
std::map map;
- std::string settings_path = service_context->getPaths()->getUserDataPath("settings.properties");
+ std::string settings_path = context->getPaths()->getUserDataPath("settings.properties");
if (!file::loadPropertiesFile(settings_path, map)) {
return false;
}
@@ -42,23 +42,22 @@ static bool load(WifiSettings& settings) {
return true;
}
-static bool save(const WifiSettings& settings) {
- auto service_context = findServiceContext();
- if (service_context == nullptr) {
- return false;
- }
+static bool save(std::shared_ptr context, const WifiSettings& settings) {
std::map map;
map[SETTINGS_KEY_ENABLE_ON_BOOT] = settings.enableOnBoot ? "true" : "false";
- std::string settings_path = service_context->getPaths()->getUserDataPath("settings.properties");
+ std::string settings_path = context->getPaths()->getUserDataPath("settings.properties");
return file::savePropertiesFile(settings_path, map);
}
WifiSettings getCachedOrLoad() {
if (!cached) {
- if (!load(cachedSettings)) {
- LOGGER.error("Failed to load");
- } else {
- cached = true;
+ auto context = findServiceContext();
+ if (context && hasWifiSettingsFile(context)) {
+ if (load(context, cachedSettings)) {
+ cached = true;
+ } else {
+ LOGGER.info("No settings found, using defaults");
+ }
}
}
@@ -67,8 +66,9 @@ WifiSettings getCachedOrLoad() {
void setEnableOnBoot(bool enable) {
cachedSettings.enableOnBoot = enable;
- if (!save(cachedSettings)) {
- LOGGER.error("Failed to save");
+ auto context = findServiceContext();
+ if (context && !save(context, cachedSettings)) {
+ LOGGER.error("Failed to save settings");
}
}
diff --git a/Tactility/Source/settings/BootSettings.cpp b/Tactility/Source/settings/BootSettings.cpp
index 061e35933..042016069 100644
--- a/Tactility/Source/settings/BootSettings.cpp
+++ b/Tactility/Source/settings/BootSettings.cpp
@@ -7,7 +7,6 @@
#include
#include
#include
-#include
namespace tt::settings {
@@ -18,14 +17,7 @@ constexpr auto* PROPERTIES_KEY_LAUNCHER_APP_ID = "launcherAppId";
constexpr auto* PROPERTIES_KEY_AUTO_START_APP_ID = "autoStartAppId";
static std::string getPropertiesFilePath() {
- std::string sdcard_path;
- if (findFirstMountedSdCardPath(sdcard_path)) {
- std::string path = std::format(PROPERTIES_FILE_FORMAT, sdcard_path);
- if (file::isFile(path)) {
- return path;
- }
- }
- return std::format(PROPERTIES_FILE_FORMAT, file::MOUNT_POINT_DATA);
+ return std::format(PROPERTIES_FILE_FORMAT, getUserDataPath());
}
bool loadBootSettings(BootSettings& properties) {
diff --git a/Tactility/Source/settings/DisplaySettings.cpp b/Tactility/Source/settings/DisplaySettings.cpp
index ab3a94461..f00f3b7d9 100644
--- a/Tactility/Source/settings/DisplaySettings.cpp
+++ b/Tactility/Source/settings/DisplaySettings.cpp
@@ -1,6 +1,7 @@
#include
#include
+#include
#include
#include
@@ -10,7 +11,10 @@
namespace tt::settings::display {
-constexpr auto* SETTINGS_FILE = "/data/settings/display.properties";
+static std::string getSettingsFilePath() {
+ return getUserDataPath() + "/settings/display.properties";
+}
+
constexpr auto* SETTINGS_KEY_ORIENTATION = "orientation";
constexpr auto* SETTINGS_KEY_GAMMA_CURVE = "gammaCurve";
constexpr auto* SETTINGS_KEY_BACKLIGHT_DUTY = "backlightDuty";
@@ -106,7 +110,7 @@ static bool fromString(const std::string& str, ScreensaverType& type) {
bool load(DisplaySettings& settings) {
std::map map;
- if (!file::loadPropertiesFile(SETTINGS_FILE, map)) {
+ if (!file::loadPropertiesFile(getSettingsFilePath(), map)) {
return false;
}
@@ -186,7 +190,7 @@ 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(SETTINGS_FILE, map);
+ return file::savePropertiesFile(getSettingsFilePath(), map);
}
lv_display_rotation_t toLvglDisplayRotation(Orientation orientation) {
diff --git a/Tactility/Source/settings/KeyboardSettings.cpp b/Tactility/Source/settings/KeyboardSettings.cpp
index af2811a61..f900db2ce 100644
--- a/Tactility/Source/settings/KeyboardSettings.cpp
+++ b/Tactility/Source/settings/KeyboardSettings.cpp
@@ -1,12 +1,16 @@
#include
#include
+#include
#include
#include
namespace tt::settings::keyboard {
-constexpr auto* SETTINGS_FILE = "/data/settings/keyboard.properties";
+static std::string getSettingsFilePath() {
+ return getUserDataPath() + "/settings/keyboard.properties";
+}
+
constexpr auto* KEY_BACKLIGHT_ENABLED = "backlightEnabled";
constexpr auto* KEY_BACKLIGHT_BRIGHTNESS = "backlightBrightness";
constexpr auto* KEY_BACKLIGHT_TIMEOUT_ENABLED = "backlightTimeoutEnabled";
@@ -14,7 +18,7 @@ constexpr auto* KEY_BACKLIGHT_TIMEOUT_MS = "backlightTimeoutMs";
bool load(KeyboardSettings& settings) {
std::map map;
- if (!file::loadPropertiesFile(SETTINGS_FILE, map)) {
+ if (!file::loadPropertiesFile(getSettingsFilePath(), map)) {
return false;
}
@@ -54,7 +58,7 @@ 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(SETTINGS_FILE, map);
+ return file::savePropertiesFile(getSettingsFilePath(), map);
}
}
diff --git a/Tactility/Source/settings/SystemSettings.cpp b/Tactility/Source/settings/SystemSettings.cpp
index 77ec5b353..7001b1bb0 100644
--- a/Tactility/Source/settings/SystemSettings.cpp
+++ b/Tactility/Source/settings/SystemSettings.cpp
@@ -1,28 +1,35 @@
#include
#include
#include
+#include
#include
#include
#include
#include
+#include "Tactility/Paths.h"
+
#include
namespace tt::settings {
static const auto LOGGER = Logger("SystemSettings");
-constexpr auto* FILE_PATH_FORMAT = "{}/settings/system.properties";
+constexpr auto* FILE_PATH_FORMAT = "{}/provisioning/system.properties";
static bool cached = false;
static SystemSettings cachedSettings;
+static bool hasSystemSettingsFile() {
+ auto file_path = std::format(FILE_PATH_FORMAT, getUserDataPath());
+ return file::isFile(file_path);
+}
+
static bool loadSystemSettingsFromFile(SystemSettings& properties) {
- auto file_path = std::format(FILE_PATH_FORMAT, file::MOUNT_POINT_DATA);
+ auto file_path = std::format(FILE_PATH_FORMAT, getUserDataPath());
LOGGER.info("System settings loading from {}", file_path);
std::map map;
if (!file::loadPropertiesFile(file_path, map)) {
- LOGGER.error("Failed to load {}", file_path);
return false;
}
@@ -55,11 +62,12 @@ static bool loadSystemSettingsFromFile(SystemSettings& properties) {
}
bool loadSystemSettings(SystemSettings& properties) {
- if (!cached) {
- if (!loadSystemSettingsFromFile(cachedSettings)) {
- return false;
+ if (!cached && hasSystemSettingsFile()) {
+ if (loadSystemSettingsFromFile(cachedSettings)) {
+ cached = true;
+ } else {
+ LOGGER.error("Failed to load");
}
- cached = true;
}
properties = cachedSettings;
diff --git a/Tactility/Source/settings/TouchCalibrationSettings.cpp b/Tactility/Source/settings/TouchCalibrationSettings.cpp
index 9621d1633..1c1da3da9 100644
--- a/Tactility/Source/settings/TouchCalibrationSettings.cpp
+++ b/Tactility/Source/settings/TouchCalibrationSettings.cpp
@@ -2,6 +2,7 @@
#include
#include
+#include
#include
#include
@@ -12,7 +13,10 @@
namespace tt::settings::touch {
-constexpr auto* SETTINGS_FILE = "/data/settings/touch-calibration.properties";
+static std::string getSettingsFilePath() {
+ return getUserDataPath() + "/settings/touch-calibration.properties";
+}
+
constexpr auto* SETTINGS_KEY_ENABLED = "enabled";
constexpr auto* SETTINGS_KEY_X_MIN = "xMin";
constexpr auto* SETTINGS_KEY_X_MAX = "xMax";
@@ -61,7 +65,7 @@ bool isValid(const TouchCalibrationSettings& settings) {
bool load(TouchCalibrationSettings& settings) {
std::map map;
- if (!file::loadPropertiesFile(SETTINGS_FILE, map)) {
+ if (!file::loadPropertiesFile(getSettingsFilePath(), map)) {
return false;
}
@@ -112,7 +116,7 @@ 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(SETTINGS_FILE, map)) {
+ if (!file::savePropertiesFile(getSettingsFilePath(), map)) {
return false;
}
diff --git a/Tactility/Source/settings/TrackballSettings.cpp b/Tactility/Source/settings/TrackballSettings.cpp
index 7478b948d..1cb68d21d 100644
--- a/Tactility/Source/settings/TrackballSettings.cpp
+++ b/Tactility/Source/settings/TrackballSettings.cpp
@@ -1,5 +1,6 @@
#include
#include
+#include
#include
#include
@@ -7,7 +8,10 @@
namespace tt::settings::trackball {
-constexpr auto* SETTINGS_FILE = "/data/settings/trackball.properties";
+static std::string getSettingsFilePath() {
+ return getUserDataPath() + "/settings/trackball.properties";
+}
+
constexpr auto* KEY_TRACKBALL_ENABLED = "trackballEnabled";
constexpr auto* KEY_TRACKBALL_MODE = "trackballMode";
constexpr auto* KEY_ENCODER_SENSITIVITY = "encoderSensitivity";
@@ -20,7 +24,7 @@ constexpr uint8_t MAX_POINTER_SENSITIVITY = 10;
bool load(TrackballSettings& settings) {
std::map map;
- if (!file::loadPropertiesFile(SETTINGS_FILE, map)) {
+ if (!file::loadPropertiesFile(getSettingsFilePath(), map)) {
return false;
}
@@ -80,7 +84,7 @@ 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(SETTINGS_FILE, map);
+ return file::savePropertiesFile(getSettingsFilePath(), map);
}
}
diff --git a/Tactility/Source/settings/WebServerSettings.cpp b/Tactility/Source/settings/WebServerSettings.cpp
index 6b9ecff89..4bfcee4aa 100644
--- a/Tactility/Source/settings/WebServerSettings.cpp
+++ b/Tactility/Source/settings/WebServerSettings.cpp
@@ -2,6 +2,7 @@
#include
#include
#include
+#include
#include
#include
@@ -17,8 +18,11 @@
namespace tt::settings::webserver {
-static const auto LOGGER = tt::Logger("WebServerSettings");
-constexpr auto* SETTINGS_FILE = "/data/service/webserver/settings.properties";
+static const auto LOGGER = Logger("WebServerSettings");
+
+static std::string getSettingsFilePath() {
+ return getUserDataPath() + "/settings/webserver.properties";
+}
// Property keys
constexpr auto* KEY_WIFI_ENABLED = "wifiEnabled";
@@ -87,7 +91,7 @@ static bool isEmptyCredential(const std::string& value) {
bool load(WebServerSettings& settings) {
std::map map;
- if (!file::loadPropertiesFile(SETTINGS_FILE, map)) {
+ if (!file::loadPropertiesFile(getSettingsFilePath(), map)) {
return false;
}
@@ -146,7 +150,7 @@ bool load(WebServerSettings& settings) {
// Persist the generated password immediately
map[KEY_AP_PASSWORD] = settings.apPassword;
- if (file::savePropertiesFile(SETTINGS_FILE, map)) {
+ if (file::savePropertiesFile(getSettingsFilePath(), map)) {
LOGGER.info("Generated and saved new secure AP password");
} else {
LOGGER.error("Failed to save generated AP password");
@@ -187,7 +191,7 @@ bool load(WebServerSettings& settings) {
// We need to save these to the file so they're consistent across reboots
map[KEY_WEBSERVER_USERNAME] = settings.webServerUsername;
map[KEY_WEBSERVER_PASSWORD] = settings.webServerPassword;
- if (file::savePropertiesFile(SETTINGS_FILE, map)) {
+ if (file::savePropertiesFile(getSettingsFilePath(), map)) {
LOGGER.info("Generated and saved new secure credentials");
} else {
LOGGER.error("Failed to save generated credentials - auth may be inconsistent across reboots");
@@ -254,7 +258,7 @@ bool save(const WebServerSettings& settings) {
map[KEY_WEBSERVER_PASSWORD] = settings.webServerPassword;
// Save to flash storage only (no SD backup - settings sync at boot handles restore)
- return file::savePropertiesFile(SETTINGS_FILE, map);
+ return file::savePropertiesFile(getSettingsFilePath(), map);
}
}
diff --git a/device.py b/device.py
index 89c739e99..052464aef 100644
--- a/device.py
+++ b/device.py
@@ -94,18 +94,28 @@ def write_defaults(output_file):
default_properties = read_file(default_properties_path)
output_file.write(default_properties)
+def get_user_data_location(device_properties: dict):
+ user_data_location = get_property_or_exit(device_properties, "storage", "userDataLocation")
+ if user_data_location not in ("SD", "Internal"):
+ exit_with_error(f"storage.userDataLocation must be 'SD' or 'Internal', but was: '{user_data_location}'")
+ return user_data_location
+
def write_partition_table(output_file, device_properties: dict, is_dev: bool):
+ flash_size = get_property_or_exit(device_properties, "hardware", "flashSize")
+ if not flash_size.endswith("MB"):
+ exit_with_error("Flash size should be written as xMB or xxMB (e.g. 4MB, 16MB)")
+ flash_size_number = flash_size[:-2]
+ variant = "with-sd" if get_user_data_location(device_properties) == "SD" else "no-sd"
+ partition_filename = f"partitions-{flash_size_number}mb-{variant}.csv"
if is_dev:
- flash_size_number = 4
- else:
- flash_size = get_property_or_exit(device_properties, "hardware", "flashSize")
- if not flash_size.endswith("MB"):
- exit_with_error("Flash size should be written as xMB or xxMB (e.g. 4MB, 16MB)")
- flash_size_number = flash_size[:-2]
+ dev_partition_filename = f"partitions-{flash_size_number}mb-{variant}-dev.csv"
+ if os.path.isfile(dev_partition_filename):
+ partition_filename = dev_partition_filename
+ print(f"Using partition table: {partition_filename}")
output_file.write("# Partition Table\n")
output_file.write("CONFIG_PARTITION_TABLE_CUSTOM=y\n")
- output_file.write(f"CONFIG_PARTITION_TABLE_CUSTOM_FILENAME=\"partitions-{flash_size_number}mb.csv\"\n")
- output_file.write(f"CONFIG_PARTITION_TABLE_FILENAME=\"partitions-{flash_size_number}mb.csv\"\n")
+ output_file.write(f"CONFIG_PARTITION_TABLE_CUSTOM_FILENAME=\"{partition_filename}\"\n")
+ output_file.write(f"CONFIG_PARTITION_TABLE_FILENAME=\"{partition_filename}\"\n")
def write_tactility_variables(output_file, device_properties: dict, device_id: str):
# Board and vendor
@@ -129,13 +139,10 @@ def write_tactility_variables(output_file, device_properties: dict, device_id: s
safe_auto_start_app_id = auto_start_app_id.replace("\"", "\\\"")
output_file.write(f"CONFIG_TT_AUTO_START_APP_ID=\"{safe_auto_start_app_id}\"\n")
# User data location
- user_data_location = get_property_or_exit(device_properties, "storage", "userDataLocation")
- if user_data_location == "SD":
+ if get_user_data_location(device_properties) == "SD":
output_file.write("CONFIG_TT_USER_DATA_LOCATION_SD=y\n")
- elif user_data_location == "Internal":
- output_file.write("CONFIG_TT_USER_DATA_LOCATION_INTERNAL=y\n")
else:
- exit_with_error(f"storage.userDataLocation must be 'SD' or 'Internal', but was: '{user_data_location}'")
+ output_file.write("CONFIG_TT_USER_DATA_LOCATION_INTERNAL=y\n")
def write_core_variables(output_file, device_properties: dict):
idf_target = get_property_or_exit(device_properties, "hardware", "target").lower()
diff --git a/partitions-8mb.csv b/partitions-16mb-no-sd-dev.csv
similarity index 76%
rename from partitions-8mb.csv
rename to partitions-16mb-no-sd-dev.csv
index 6051c95d2..497ffa1ef 100644
--- a/partitions-8mb.csv
+++ b/partitions-16mb-no-sd-dev.csv
@@ -3,5 +3,5 @@
nvs, data, nvs, 0x9000, 0x6000,
phy_init, data, phy, 0xf000, 0x1000,
factory, app, factory, 0x10000, 4M,
-system, data, fat, , 300k,
-data, data, fat, , 3600k,
+system, data, fat, , 100k,
+data, data, fat, , 1000k,
diff --git a/partitions-16mb.csv b/partitions-16mb-no-sd.csv
similarity index 76%
rename from partitions-16mb.csv
rename to partitions-16mb-no-sd.csv
index 89987213c..daeee992d 100644
--- a/partitions-16mb.csv
+++ b/partitions-16mb-no-sd.csv
@@ -3,5 +3,5 @@
nvs, data, nvs, 0x9000, 0x6000,
phy_init, data, phy, 0xf000, 0x1000,
factory, app, factory, 0x10000, 4M,
-system, data, fat, , 300k,
-data, data, fat, , 11600k,
+system, data, fat, , 100k,
+data, data, fat, , 11800k,
diff --git a/partitions-4mb.csv b/partitions-16mb-with-sd.csv
similarity index 66%
rename from partitions-4mb.csv
rename to partitions-16mb-with-sd.csv
index 00c39a9b5..262186df8 100644
--- a/partitions-4mb.csv
+++ b/partitions-16mb-with-sd.csv
@@ -2,6 +2,5 @@
# Note: if you have increased the bootloader size, make sure to update the offsets to avoid overlap
nvs, data, nvs, 0x9000, 0x6000,
phy_init, data, phy, 0xf000, 0x1000,
-factory, app, factory, 0x10000, 3M,
-system, data, fat, , 300k,
-data, data, fat, , 600k,
+factory, app, factory, 0x10000, 4M,
+system, data, fat, , 100k,
diff --git a/partitions-4mb-with-sd.csv b/partitions-4mb-with-sd.csv
new file mode 100644
index 000000000..59d1243b4
--- /dev/null
+++ b/partitions-4mb-with-sd.csv
@@ -0,0 +1,6 @@
+# Name, Type, SubType, Offset, Size, Flags
+# Note: if you have increased the bootloader size, make sure to update the offsets to avoid overlap
+nvs, data, nvs, 0x9000, 0x6000,
+phy_init, data, phy, 0xf000, 0x1000,
+factory, app, factory, 0x10000, 3800k,
+system, data, fat, , 100k,
diff --git a/partitions-8mb-no-sd-dev.csv b/partitions-8mb-no-sd-dev.csv
new file mode 100644
index 000000000..497ffa1ef
--- /dev/null
+++ b/partitions-8mb-no-sd-dev.csv
@@ -0,0 +1,7 @@
+# Name, Type, SubType, Offset, Size, Flags
+# Note: if you have increased the bootloader size, make sure to update the offsets to avoid overlap
+nvs, data, nvs, 0x9000, 0x6000,
+phy_init, data, phy, 0xf000, 0x1000,
+factory, app, factory, 0x10000, 4M,
+system, data, fat, , 100k,
+data, data, fat, , 1000k,
diff --git a/partitions-8mb-no-sd.csv b/partitions-8mb-no-sd.csv
new file mode 100644
index 000000000..79544b13d
--- /dev/null
+++ b/partitions-8mb-no-sd.csv
@@ -0,0 +1,7 @@
+# Name, Type, SubType, Offset, Size, Flags
+# Note: if you have increased the bootloader size, make sure to update the offsets to avoid overlap
+nvs, data, nvs, 0x9000, 0x6000,
+phy_init, data, phy, 0xf000, 0x1000,
+factory, app, factory, 0x10000, 4M,
+system, data, fat, , 100k,
+data, data, fat, , 3800k,
diff --git a/partitions-8mb-with-sd.csv b/partitions-8mb-with-sd.csv
new file mode 100644
index 000000000..262186df8
--- /dev/null
+++ b/partitions-8mb-with-sd.csv
@@ -0,0 +1,6 @@
+# Name, Type, SubType, Offset, Size, Flags
+# Note: if you have increased the bootloader size, make sure to update the offsets to avoid overlap
+nvs, data, nvs, 0x9000, 0x6000,
+phy_init, data, phy, 0xf000, 0x1000,
+factory, app, factory, 0x10000, 4M,
+system, data, fat, , 100k,