Merge develop into main (#313)

- Add app path get() functions to `TactilityC`
- Improved `Dispatcher` and `DispatcherThread`
- Improved `PubSub` (type safety)
- Created test for `DispatcherThread` and `PubSub`
- Save properties files on app exit (various apps) by posting it to the main dispatcher (fixes UI hanging briefly on app exit)
- Fixed bug with `SystemSettings` being read from the wrong file path.
- `loadPropertiesFile()` now uses `file::readLines()` instead of doing that manually
- Increased timer task stack size (required due to issues when reading a properties file for the very first time)
- General cleanup
- Created `EstimatedPower` driver that uses an ADC pin to measure voltage and estimate the battery charge that is left.
- Cleanup of T-Deck board (updated to new style)
This commit is contained in:
Ken Van Hoeylandt
2025-09-01 23:07:00 +02:00
committed by GitHub
parent 5cc5b50694
commit 0f8380e8fe
96 changed files with 766 additions and 682 deletions
@@ -13,7 +13,6 @@
#include <Tactility/service/ServiceRegistration.h>
#include <Tactility/service/wifi/Wifi.h>
#include <Tactility/StringUtils.h>
#include <Tactility/TactilityHeadless.h>
#include <cstring>
#include <esp_wifi.h>
@@ -1,10 +1,11 @@
#ifdef ESP_PLATFORM
#include "Tactility/service/espnow/EspNowService.h"
#include "Tactility/TactilityHeadless.h"
#include "Tactility/service/ServiceManifest.h"
#include "Tactility/service/ServiceRegistration.h"
#include "Tactility/service/espnow/EspNowWifi.h"
#include <Tactility/Tactility.h>
#include <Tactility/service/espnow/EspNowService.h>
#include <Tactility/service/ServiceManifest.h>
#include <Tactility/service/ServiceRegistration.h>
#include <Tactility/service/espnow/EspNowWifi.h>
#include <cstring>
#include <esp_now.h>
#include <esp_random.h>
+1 -1
View File
@@ -208,7 +208,7 @@ void GpsService::setState(State newState) {
lock.lock();
state = newState;
lock.unlock();
statePubSub->publish(&state);
statePubSub->publish(state);
}
bool GpsService::hasCoordinates() const {
+12 -13
View File
@@ -12,22 +12,16 @@ namespace tt::service::gui {
extern const ServiceManifest manifest;
constexpr const char* TAG = "gui";
constexpr auto* TAG = "GuiService";
// region AppManifest
void GuiService::onLoaderMessage(const void* message, TT_UNUSED void* context) {
auto service = findService();
if (service == nullptr) {
return;
}
auto* event = static_cast<const loader::LoaderEvent*>(message);
if (event->type == loader::LoaderEventTypeApplicationShowing) {
void GuiService::onLoaderEvent(loader::LoaderEvent event) {
if (event == loader::LoaderEvent::ApplicationShowing) {
auto app_instance = app::getCurrentAppContext();
service->showApp(app_instance);
} else if (event->type == loader::LoaderEventTypeApplicationHiding) {
service->hideApp();
showApp(app_instance);
} else if (event == loader::LoaderEvent::ApplicationHiding) {
hideApp();
}
}
@@ -124,7 +118,12 @@ void GuiService::onStart(TT_UNUSED ServiceContext& service) {
4096, // Last known minimum was 2800 for launching desktop
[]() { return guiMain(); }
);
loader_pubsub_subscription = loader::getPubsub()->subscribe(&onLoaderMessage, nullptr);
loader_pubsub_subscription = loader::getPubsub()->subscribe([this](auto event) {
onLoaderEvent(event);
});
tt_check(lvgl::lock(1000 / portTICK_PERIOD_MS));
keyboardGroup = lv_group_create();
auto* screen_root = lv_screen_active();
+12 -18
View File
@@ -3,7 +3,6 @@
#include "Tactility/app/AppManifest.h"
#include "Tactility/app/AppRegistration.h"
#include <Tactility/RtosCompat.h>
#include <Tactility/DispatcherThread.h>
#include <Tactility/service/ServiceManifest.h>
#include <Tactility/service/ServiceRegistration.h>
@@ -11,7 +10,6 @@
#include <stack>
#ifdef ESP_PLATFORM
#include <Tactility/TactilityHeadless.h>
#include <esp_heap_caps.h>
#include <utility>
#else
@@ -20,8 +18,8 @@
namespace tt::service::loader {
#define TAG "loader"
#define LOADER_TIMEOUT (100 / portTICK_PERIOD_MS)
constexpr auto* TAG = "Loader";
constexpr auto LOADER_TIMEOUT = (100 / portTICK_PERIOD_MS);
extern const ServiceManifest manifest;
@@ -47,7 +45,7 @@ static const char* appStateToString(app::State state) {
class LoaderService final : public Service {
std::shared_ptr<PubSub> pubsubExternal = std::make_shared<PubSub>();
std::shared_ptr<PubSub<LoaderEvent>> pubsubExternal = std::make_shared<PubSub<LoaderEvent>>();
Mutex mutex = Mutex(Mutex::Type::Recursive);
std::stack<std::shared_ptr<app::AppInstance>> appStack;
app::LaunchId nextLaunchId = 0;
@@ -64,13 +62,13 @@ class LoaderService final : public Service {
public:
void onStart(TT_UNUSED ServiceContext& service) final {
void onStart(TT_UNUSED ServiceContext& service) override {
dispatcherThread->start();
}
void onStop(TT_UNUSED ServiceContext& service) final {
void onStop(TT_UNUSED ServiceContext& service) override {
// Send stop signal to thread and wait for thread to finish
mutex.withLock([this]() {
mutex.withLock([this] {
dispatcherThread->stop();
});
}
@@ -79,7 +77,7 @@ public:
void stopApp();
std::shared_ptr<app::AppContext> _Nullable getCurrentAppContext();
std::shared_ptr<PubSub> getPubsub() const { return pubsubExternal; }
std::shared_ptr<PubSub<LoaderEvent>> getPubsub() const { return pubsubExternal; }
};
std::shared_ptr<LoaderService> _Nullable optScreenshotService() {
@@ -117,8 +115,7 @@ void LoaderService::onStartAppMessage(const std::string& id, app::LaunchId launc
transitionAppToState(new_app, app::State::Showing);
LoaderEvent event_external = { .type = LoaderEventTypeApplicationStarted };
pubsubExternal->publish(&event_external);
pubsubExternal->publish(LoaderEvent::ApplicationStarted);
}
void LoaderService::onStopAppMessage(const std::string& id) {
@@ -188,8 +185,7 @@ void LoaderService::onStopAppMessage(const std::string& id) {
lock.unlock();
// WARNING: After this point we cannot change the app states from this method directly anymore as we don't have a lock!
LoaderEvent event_external = { .type = LoaderEventTypeApplicationStopped };
pubsubExternal->publish(&event_external);
pubsubExternal->publish(LoaderEvent::ApplicationStopped);
if (instance_to_resume != nullptr) {
if (result_set) {
@@ -240,13 +236,11 @@ void LoaderService::transitionAppToState(const std::shared_ptr<app::AppInstance>
app->getApp()->onCreate(*app);
break;
case Showing: {
LoaderEvent event_showing = { .type = LoaderEventTypeApplicationShowing };
pubsubExternal->publish(&event_showing);
pubsubExternal->publish(LoaderEvent::ApplicationShowing);
break;
}
case Hiding: {
LoaderEvent event_hiding = { .type = LoaderEventTypeApplicationHiding };
pubsubExternal->publish(&event_hiding);
pubsubExternal->publish(LoaderEvent::ApplicationHiding);
break;
}
case Stopped:
@@ -307,7 +301,7 @@ std::shared_ptr<app::App> _Nullable getCurrentApp() {
return app_context != nullptr ? app_context->getApp() : nullptr;
}
std::shared_ptr<PubSub> getPubsub() {
std::shared_ptr<PubSub<LoaderEvent>> getPubsub() {
auto service = optScreenshotService();
assert(service);
return service->getPubsub();
+5 -5
View File
@@ -1,14 +1,14 @@
#include "Tactility/service/ServiceContext.h"
#include "Tactility/TactilityHeadless.h"
#include "Tactility/service/ServiceRegistration.h"
#include <Tactility/service/ServiceContext.h>
#include <Tactility/service/ServiceRegistration.h>
#include <Tactility/Mutex.h>
#include <Tactility/Timer.h>
#define TAG "sdcard_service"
#include <Tactility/hal/sdcard/SdCardDevice.h>
namespace tt::service::sdcard {
constexpr auto* TAG = "SdcardService";
extern const ServiceManifest manifest;
class SdCardService final : public Service {
@@ -6,7 +6,6 @@
#include "Tactility/service/gps/GpsService.h"
#include <Tactility/Mutex.h>
#include <Tactility/Tactility.h>
#include <Tactility/TactilityHeadless.h>
#include <Tactility/Timer.h>
#include <Tactility/service/ServiceContext.h>
#include <Tactility/service/ServiceRegistration.h>
@@ -14,7 +13,7 @@
namespace tt::service::statusbar {
#define TAG "statusbar_service"
constexpr auto* TAG = "StatusbarService";
// SD card status
#define STATUSBAR_ICON_SDCARD "sdcard.png"
+35 -36
View File
@@ -1,14 +1,14 @@
#ifdef ESP_PLATFORM
#include "Tactility/service/wifi/Wifi.h"
#include "Tactility/TactilityHeadless.h"
#include "Tactility/service/ServiceContext.h"
#include "Tactility/service/wifi/WifiGlobals.h"
#include "Tactility/service/wifi/WifiSettings.h"
#include "Tactility/service/wifi/WifiBootSplashInit.h"
#include <Tactility/service/wifi/Wifi.h>
#include <Tactility/EventFlag.h>
#include <Tactility/Tactility.h>
#include <Tactility/kernel/SystemEvents.h>
#include <Tactility/service/ServiceContext.h>
#include <Tactility/service/wifi/WifiGlobals.h>
#include <Tactility/service/wifi/WifiSettings.h>
#include <Tactility/service/wifi/WifiBootSplashInit.h>
#include <Tactility/Timer.h>
#include <lwip/esp_netif_net_stack.h>
@@ -19,10 +19,10 @@
namespace tt::service::wifi {
#define TAG "wifi_service"
#define WIFI_CONNECTED_BIT BIT0
#define WIFI_FAIL_BIT BIT1
#define AUTO_SCAN_INTERVAL 10000 // ms
constexpr auto* TAG = "WifiService";
constexpr auto WIFI_CONNECTED_BIT = BIT0;
constexpr auto WIFI_FAIL_BIT = BIT1;
constexpr auto AUTO_SCAN_INTERVAL = 10000; // ms
// Forward declarations
class Wifi;
@@ -48,7 +48,7 @@ public:
Mutex dataMutex = Mutex(Mutex::Type::Recursive);
std::unique_ptr<Timer> autoConnectTimer;
/** @brief The public event bus */
std::shared_ptr<PubSub> pubsub = std::make_shared<PubSub>();
std::shared_ptr<PubSub<WifiEvent>> pubsub = std::make_shared<PubSub<WifiEvent>>();
// TODO: Deal with messages that come in while an action is ongoing
// for example: when scanning and you turn off the radio, the scan should probably stop or turning off
// the radio should disable the on/off button in the app as it is pending.
@@ -129,7 +129,7 @@ static std::shared_ptr<Wifi> wifi_singleton;
// region Public functions
std::shared_ptr<PubSub> getPubsub() {
std::shared_ptr<PubSub<WifiEvent>> getPubsub() {
auto wifi = wifi_singleton;
if (wifi == nullptr) {
tt_crash("Service not running");
@@ -371,11 +371,10 @@ static void scan_list_free_safely(std::shared_ptr<Wifi> wifi) {
}
}
static void publish_event_simple(std::shared_ptr<Wifi> wifi, EventType type) {
static void publish_event(std::shared_ptr<Wifi> wifi, WifiEvent event) {
auto lock = wifi->dataMutex.asScopedLock();
if (lock.lock()) {
Event turning_on_event = {.type = type};
wifi->pubsub->publish(&turning_on_event);
wifi->pubsub->publish(event);
}
}
@@ -484,7 +483,7 @@ static void eventHandler(TT_UNUSED void* arg, esp_event_base_t event_base, int32
break;
}
wifi->setRadioState(RadioState::On);
publish_event_simple(wifi, EventType::Disconnected);
publish_event(wifi, WifiEvent::Disconnected);
kernel::publishSystemEvent(kernel::SystemEvent::NetworkDisconnected);
} else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP) {
auto* event = static_cast<ip_event_got_ip_t*>(event_data);
@@ -511,7 +510,7 @@ static void eventHandler(TT_UNUSED void* arg, esp_event_base_t event_base, int32
esp_wifi_scan_stop();
}
publish_event_simple(wifi_singleton, EventType::ScanFinished);
publish_event(wifi_singleton, WifiEvent::ScanFinished);
TT_LOG_I(TAG, "eventHandler: Finished scan");
if (copied_list && wifi_singleton->getRadioState() == RadioState::On && !wifi->pause_auto_connect) {
@@ -537,7 +536,7 @@ static void dispatchEnable(std::shared_ptr<Wifi> wifi) {
if (lock.lock(50 / portTICK_PERIOD_MS)) {
TT_LOG_I(TAG, "Enabling");
wifi->setRadioState(RadioState::OnPending);
publish_event_simple(wifi, EventType::RadioStateOnPending);
publish_event(wifi, WifiEvent::RadioStateOnPending);
if (wifi->netif != nullptr) {
esp_netif_destroy(wifi->netif);
@@ -554,7 +553,7 @@ static void dispatchEnable(std::shared_ptr<Wifi> wifi) {
TT_LOG_E(TAG, "Insufficient memory");
}
wifi->setRadioState(RadioState::Off);
publish_event_simple(wifi, EventType::RadioStateOff);
publish_event(wifi, WifiEvent::RadioStateOff);
return;
}
@@ -582,7 +581,7 @@ static void dispatchEnable(std::shared_ptr<Wifi> wifi) {
TT_LOG_E(TAG, "Wifi mode setting failed");
wifi->setRadioState(RadioState::Off);
esp_wifi_deinit();
publish_event_simple(wifi, EventType::RadioStateOff);
publish_event(wifi, WifiEvent::RadioStateOff);
return;
}
@@ -595,12 +594,12 @@ static void dispatchEnable(std::shared_ptr<Wifi> wifi) {
wifi->setRadioState(RadioState::Off);
esp_wifi_set_mode(WIFI_MODE_NULL);
esp_wifi_deinit();
publish_event_simple(wifi, EventType::RadioStateOff);
publish_event(wifi, WifiEvent::RadioStateOff);
return;
}
wifi->setRadioState(RadioState::On);
publish_event_simple(wifi, EventType::RadioStateOn);
publish_event(wifi, WifiEvent::RadioStateOn);
wifi->pause_auto_connect = false;
@@ -631,7 +630,7 @@ static void dispatchDisable(std::shared_ptr<Wifi> wifi) {
TT_LOG_I(TAG, "Disabling");
wifi->setRadioState(RadioState::OffPending);
publish_event_simple(wifi, EventType::RadioStateOffPending);
publish_event(wifi, WifiEvent::RadioStateOffPending);
// Free up scan list memory
scan_list_free_safely(wifi_singleton);
@@ -639,7 +638,7 @@ static void dispatchDisable(std::shared_ptr<Wifi> wifi) {
if (esp_wifi_stop() != ESP_OK) {
TT_LOG_E(TAG, "Failed to stop radio");
wifi->setRadioState(RadioState::On);
publish_event_simple(wifi, EventType::RadioStateOn);
publish_event(wifi, WifiEvent::RadioStateOn);
return;
}
@@ -672,7 +671,7 @@ static void dispatchDisable(std::shared_ptr<Wifi> wifi) {
wifi->netif = nullptr;
wifi->setScanActive(false);
wifi->setRadioState(RadioState::Off);
publish_event_simple(wifi, EventType::RadioStateOff);
publish_event(wifi, WifiEvent::RadioStateOff);
TT_LOG_I(TAG, "Disabled");
}
@@ -706,7 +705,7 @@ static void dispatchScan(std::shared_ptr<Wifi> wifi) {
TT_LOG_I(TAG, "Starting scan");
wifi->setScanActive(true);
publish_event_simple(wifi, EventType::ScanStarted);
publish_event(wifi, WifiEvent::ScanStarted);
}
static void dispatchConnect(std::shared_ptr<Wifi> wifi) {
@@ -740,7 +739,7 @@ static void dispatchConnect(std::shared_ptr<Wifi> wifi) {
wifi->setRadioState(RadioState::ConnectionPending);
publish_event_simple(wifi, EventType::ConnectionPending);
publish_event(wifi, WifiEvent::ConnectionPending);
wifi_config_t config;
memset(&config, 0, sizeof(wifi_config_t));
@@ -762,7 +761,7 @@ static void dispatchConnect(std::shared_ptr<Wifi> wifi) {
if (set_config_result != ESP_OK) {
wifi->setRadioState(RadioState::On);
TT_LOG_E(TAG, "Failed to set wifi config (%s)", esp_err_to_name(set_config_result));
publish_event_simple(wifi, EventType::ConnectionFailed);
publish_event(wifi, WifiEvent::ConnectionFailed);
return;
}
@@ -771,7 +770,7 @@ static void dispatchConnect(std::shared_ptr<Wifi> wifi) {
if (wifi_start_result != ESP_OK) {
wifi->setRadioState(RadioState::On);
TT_LOG_E(TAG, "Failed to start wifi to begin connecting (%s)", esp_err_to_name(wifi_start_result));
publish_event_simple(wifi, EventType::ConnectionFailed);
publish_event(wifi, WifiEvent::ConnectionFailed);
return;
}
@@ -784,7 +783,7 @@ static void dispatchConnect(std::shared_ptr<Wifi> wifi) {
if (bits & WIFI_CONNECTED_BIT) {
wifi->setSecureConnection(config.sta.password[0] != 0x00U);
wifi->setRadioState(RadioState::ConnectionActive);
publish_event_simple(wifi, EventType::ConnectionSuccess);
publish_event(wifi, WifiEvent::ConnectionSuccess);
TT_LOG_I(TAG, "Connected to %s", wifi->connection_target.ssid.c_str());
if (wifi->connection_target_remember) {
if (!settings::save(wifi->connection_target)) {
@@ -795,11 +794,11 @@ static void dispatchConnect(std::shared_ptr<Wifi> wifi) {
}
} else if (bits & WIFI_FAIL_BIT) {
wifi->setRadioState(RadioState::On);
publish_event_simple(wifi, EventType::ConnectionFailed);
publish_event(wifi, WifiEvent::ConnectionFailed);
TT_LOG_I(TAG, "Failed to connect to %s", wifi->connection_target.ssid.c_str());
} else {
wifi->setRadioState(RadioState::On);
publish_event_simple(wifi, EventType::ConnectionFailed);
publish_event(wifi, WifiEvent::ConnectionFailed);
TT_LOG_E(TAG, "UNEXPECTED EVENT");
}
@@ -834,7 +833,7 @@ static void dispatchDisconnectButKeepActive(std::shared_ptr<Wifi> wifi) {
// TODO: disable radio, because radio state is in limbo between off and on
wifi->setRadioState(RadioState::Off);
TT_LOG_E(TAG, "failed to set wifi config (%s)", esp_err_to_name(set_config_result));
publish_event_simple(wifi, EventType::RadioStateOff);
publish_event(wifi, WifiEvent::RadioStateOff);
return;
}
@@ -843,12 +842,12 @@ static void dispatchDisconnectButKeepActive(std::shared_ptr<Wifi> wifi) {
// TODO: disable radio, because radio state is in limbo between off and on
wifi->setRadioState(RadioState::Off);
TT_LOG_E(TAG, "failed to start wifi to begin connecting (%s)", esp_err_to_name(wifi_start_result));
publish_event_simple(wifi, EventType::RadioStateOff);
publish_event(wifi, WifiEvent::RadioStateOff);
return;
}
wifi->setRadioState(RadioState::On);
publish_event_simple(wifi, EventType::Disconnected);
publish_event(wifi, WifiEvent::Disconnected);
TT_LOG_I(TAG, "Disconnected");
}
+4 -5
View File
@@ -22,7 +22,7 @@ struct Wifi {
/** @brief Locking mechanism for modifying the Wifi instance */
Mutex mutex = Mutex(Mutex::Type::Recursive);
/** @brief The public event bus */
std::shared_ptr<PubSub> pubsub = std::make_shared<PubSub>();
std::shared_ptr<PubSub<WifiEvent>> pubsub = std::make_shared<PubSub<WifiEvent>>();
/** @brief The internal message queue */
bool scan_active = false;
bool secure_connection = false;
@@ -34,16 +34,15 @@ static Wifi* wifi = nullptr;
// region Static
static void publish_event_simple(Wifi* wifi, EventType type) {
Event turning_on_event = { .type = type };
wifi->pubsub->publish(&turning_on_event);
static void publish_event(WifiEvent event) {
wifi->pubsub->publish(event);
}
// endregion Static
// region Public functions
std::shared_ptr<PubSub> getPubsub() {
std::shared_ptr<PubSub<WifiEvent>> getPubsub() {
assert(wifi);
return wifi->pubsub;
}
+24 -11
View File
@@ -1,9 +1,8 @@
#include "Tactility/service/wifi/WifiSettings.h"
#include "Tactility/Preferences.h"
#include "Tactility/file/PropertiesFile.h"
#include <Tactility/service/wifi/WifiSettings.h>
#include <Tactility/Log.h>
#include <Tactility/file/File.h>
#include <Tactility/file/PropertiesFile.h>
#include <Tactility/Log.h>
namespace tt::service::wifi::settings {
@@ -15,6 +14,12 @@ struct WifiSettings {
bool enableOnBoot;
};
static WifiSettings cachedSettings {
.enableOnBoot = false
};
static bool cached = false;
static bool load(WifiSettings& settings) {
std::map<std::string, std::string> map;
if (!file::loadPropertiesFile(SETTINGS_FILE, map)) {
@@ -36,19 +41,27 @@ static bool save(const WifiSettings& settings) {
return file::savePropertiesFile(SETTINGS_FILE, map);
}
WifiSettings getCachedOrLoad() {
if (!cached) {
if (!load(cachedSettings)) {
TT_LOG_E(TAG, "Failed to load %s", SETTINGS_FILE);
} else {
cached = true;
}
}
return cachedSettings;
}
void setEnableOnBoot(bool enable) {
WifiSettings settings { .enableOnBoot = enable };
if (!save(settings)) {
cachedSettings.enableOnBoot = enable;
if (!save(cachedSettings)) {
TT_LOG_E(TAG, "Failed to save %s", SETTINGS_FILE);
}
}
bool shouldEnableOnBoot() {
WifiSettings settings;
if (!load(settings)) {
return false;
}
return settings.enableOnBoot;
return getCachedOrLoad().enableOnBoot;
}
} // namespace