Various improvements (#547)

- Fixed crash when returning to Setup app (from any of the steps)
- Fixed crash when returning to TimeDateSettings app (after locale selection)
- Fixed GuiService inconsistent behaviour: now always perform operations async (e.g. to show/hide apps). This fixes crashes in some onHide() calls where onHide() might be called before onShow() was called.
- Fix for incorrect WebServService path
- Remove CYD-4848S040C SD card functionality, but added a GPIO fix for releasing 3-wire SPI pin sharing.
- Fix for saving/loading settings for various apps
This commit is contained in:
Ken Van Hoeylandt
2026-07-04 21:26:43 +02:00
committed by GitHub
parent a323f8e148
commit ecad2248d9
33 changed files with 278 additions and 146 deletions
@@ -1,4 +1,5 @@
#ifdef ESP_PLATFORM
#include <Tactility/file/File.h>
#include <Tactility/file/PropertiesFile.h>
#include <Tactility/Logger.h>
#include <Tactility/Paths.h>
@@ -21,8 +22,13 @@ struct DevelopmentSettings {
};
static bool load(DevelopmentSettings& settings) {
auto settings_path = getSettingsFilePath();
if (!file::isFile(settings_path)) {
return false;
}
std::map<std::string, std::string> map;
if (!file::loadPropertiesFile(getSettingsFilePath(), map)) {
if (!file::loadPropertiesFile(settings_path, map)) {
return false;
}
@@ -38,7 +44,12 @@ static bool load(DevelopmentSettings& settings) {
static bool save(const DevelopmentSettings& settings) {
std::map<std::string, std::string> map;
map[SETTINGS_KEY_ENABLE_ON_BOOT] = settings.enableOnBoot ? "true" : "false";
return file::savePropertiesFile(getSettingsFilePath(), map);
auto settings_path = getSettingsFilePath();
if (!file::findOrCreateParentDirectory(settings_path, 0755)) {
LOGGER.error("Failed to create parent dir for {}", settings_path);
return false;
}
return file::savePropertiesFile(settings_path, map);
}
void setEnableOnBoot(bool enable) {
+52 -28
View File
@@ -27,14 +27,49 @@ void warnIfRunningOnGuiTask(const char* context) {
}
}
namespace {
enum class GuiDispatchType { Show, Hide, Exit };
struct GuiDispatchItem {
GuiService* service;
GuiDispatchType type;
std::shared_ptr<app::AppInstance> appInstance; // only used for Show
};
} // namespace
// region AppManifest
void GuiService::onGuiDispatch(void* context) {
std::unique_ptr<GuiDispatchItem> item(static_cast<GuiDispatchItem*>(context));
switch (item->type) {
case GuiDispatchType::Show:
item->service->showApp(item->appInstance);
break;
case GuiDispatchType::Hide:
item->service->hideApp();
break;
case GuiDispatchType::Exit:
item->service->exitRequested = true;
break;
}
}
void GuiService::onLoaderEvent(LoaderService::Event event) {
GuiDispatchItem* item;
if (event == LoaderService::Event::ApplicationShowing) {
auto app_instance = std::static_pointer_cast<app::AppInstance>(app::getCurrentAppContext());
showApp(app_instance);
item = new GuiDispatchItem{this, GuiDispatchType::Show, app_instance};
} else if (event == LoaderService::Event::ApplicationHiding) {
hideApp();
item = new GuiDispatchItem{this, GuiDispatchType::Hide, nullptr};
} else {
return;
}
if (dispatcher_dispatch(dispatcher, item, onGuiDispatch) != ERROR_NONE) {
LOGGER.error("Failed to dispatch gui event");
delete item;
}
}
@@ -82,26 +117,8 @@ int32_t GuiService::guiMain() {
lvgl::unlock();
while (true) {
uint32_t flags = 0;
if (service->threadFlags.wait(GUI_THREAD_FLAG_ALL, false, true, &flags, portMAX_DELAY)) {
// When service not started or starting -> exit
State service_state = getState(manifest.id);
if (service_state != State::Started && service_state != State::Starting) {
break;
}
// Process and dispatch draw call
if (flags & GUI_THREAD_FLAG_DRAW) {
service->threadFlags.clear(GUI_THREAD_FLAG_DRAW);
service->redraw();
}
if (flags & GUI_THREAD_FLAG_EXIT) {
service->threadFlags.clear(GUI_THREAD_FLAG_EXIT);
break;
}
}
while (!service->exitRequested) {
dispatcher_consume(service->dispatcher);
}
service->appRootWidget = nullptr;
@@ -177,6 +194,9 @@ void GuiService::redraw() {
}
bool GuiService::onStart(ServiceContext& service) {
exitRequested = false;
dispatcher = dispatcher_alloc();
thread = new Thread(
GUI_TASK_NAME,
4096, // Last known minimum was 2800 for launching desktop
@@ -211,8 +231,14 @@ void GuiService::onStop(ServiceContext& service) {
appToRender = nullptr;
isStarted = false;
threadFlags.set(GUI_THREAD_FLAG_EXIT);
unlock();
auto* exit_item = new GuiDispatchItem{this, GuiDispatchType::Exit, nullptr};
if (dispatcher_dispatch(dispatcher, exit_item, onGuiDispatch) != ERROR_NONE) {
LOGGER.error("Failed to dispatch gui exit event");
check(false, "Failed to dispatch exit signal to thread.");
delete exit_item;
}
thread->join();
if (lvgl::lock()) {
@@ -226,10 +252,8 @@ void GuiService::onStop(ServiceContext& service) {
}
delete thread;
}
void GuiService::requestDraw() {
threadFlags.set(GUI_THREAD_FLAG_DRAW);
dispatcher_free(dispatcher);
dispatcher = nullptr;
}
void GuiService::showApp(std::shared_ptr<app::AppInstance> app) {
@@ -253,7 +277,7 @@ void GuiService::showApp(std::shared_ptr<app::AppInstance> app) {
}
appToRender = std::move(app);
requestDraw();
redraw();
}
void GuiService::hideApp() {
@@ -1454,7 +1454,7 @@ esp_err_t WebServerService::handleApiScreenshot(httpd_req_t* request) {
#if TT_FEATURE_SCREENSHOT_ENABLED
// Determine save location: prefer SD card root if mounted, otherwise /data
std::string save_path = getUserDataRootPath();
std::string save_path = getUserDataPath();
// Find next available filename with incrementing number
std::string screenshot_path;
@@ -127,6 +127,10 @@ bool load(const std::string& ssid, WifiApSettings& apSettings) {
return false;
}
const auto file_path = getApPropertiesFilePath(service_context->getPaths(), ssid);
if (!file::isFile(file_path)) {
return false;
}
std::map<std::string, std::string> map;
if (!file::loadPropertiesFile(file_path, map)) {
return false;
@@ -120,21 +120,25 @@ static void importWifiApSettingsFromDir(const std::string& path) {
}
void bootSplashInit() {
LOGGER.info("bootSplashInit begin");
LOGGER.info("bootSplashInit dispatch");
getMainDispatcher().dispatch([] {
LOGGER.info("bootSplashInit dispatch begin");
// First import any provisioning files placed on the system data partition.
const std::string data_settings_path = file::getChildPath(getUserDataPath(), "provisioning");
importWifiApSettingsFromDir(data_settings_path);
// Import any provisioning files placed on the system data partition.
const std::string provisioning_path = file::getChildPath(getUserDataPath(), "provisioning");
if (file::isDirectory(provisioning_path)) {
importWifiApSettingsFromDir(provisioning_path);
} else {
LOGGER.info("Skip provisioning: no files at {}", provisioning_path);
}
// Dispatch WiFi on
if (settings::shouldEnableOnBoot()) {
LOGGER.info("Auto-enabling due to setting");
LOGGER.info("Auto-enabling WiFi");
getMainDispatcher().dispatch([] -> void { setEnabled(true); });
}
LOGGER.info("bootSplashInit dispatch end");
});
LOGGER.info("bootSplashInit end");
}
}