diff --git a/App/Source/Main.cpp b/App/Source/Main.cpp index 6042c859..b4f1096e 100644 --- a/App/Source/Main.cpp +++ b/App/Source/Main.cpp @@ -4,7 +4,7 @@ #include "Tactility.h" namespace tt::service::wifi { - extern void wifi_main(void*); + extern void wifi_task(void*); } extern const tt::app::AppManifest hello_world_app; @@ -25,9 +25,7 @@ void app_main() { .auto_start_app_id = nullptr }; - tt::init(config); - - tt::service::wifi::wifi_main(nullptr); + tt::run(config); } } // extern diff --git a/Boards/LilygoTdeck/Source/Sdcard.cpp b/Boards/LilygoTdeck/Source/Sdcard.cpp index b0f975fa..d5664302 100644 --- a/Boards/LilygoTdeck/Source/Sdcard.cpp +++ b/Boards/LilygoTdeck/Source/Sdcard.cpp @@ -144,7 +144,7 @@ static bool sdcard_is_mounted(void* context) { * Writing and reading to the bus from 2 devices at the same time causes crashes. * This work-around ensures that this check is only happening when LVGL isn't rendering. */ - bool locked = tt::lvgl::lock(100); // TODO: Refactor to a more reliable locking mechanism + bool locked = tt::lvgl::lock(50); // TODO: Refactor to a more reliable locking mechanism if (!locked) { TT_LOG_W(TAG, "Failed to get LVGL lock"); } diff --git a/Documentation/app-lifecycle.puml b/Documentation/app-lifecycle.puml index 2ed42a27..a88b3f6c 100644 --- a/Documentation/app-lifecycle.puml +++ b/Documentation/app-lifecycle.puml @@ -1,9 +1,9 @@ @startuml -[*] --> on_create : app is started -on_create --> on_show : app becomes visible -on_show --> on_hide : app is no longer visible -on_hide --> on_destroy : app is being closed -on_destroy --> [*] +[*] --> onStart : app is created +onStart --> onShow : app becomes visible +onShow --> onHide : app is no longer visible +onHide --> onStop : app is preparing to be destroyed +onStop --> [*] : app is destroyed skinparam ranksep 25 skinparam padding 2 @enduml \ No newline at end of file diff --git a/Documentation/ideas.md b/Documentation/ideas.md index 52143817..c0139738 100644 --- a/Documentation/ideas.md +++ b/Documentation/ideas.md @@ -1,32 +1,30 @@ # TODOs -- Bug: sdcard file reading fails (due to `A:/` prefix?) -- Publish firmwares with upload tool -- Bug: When closing a top level app, there's often an error "can't stop root app" - Bug: I2C Scanner is on M5Stack devices is broken +- WiFi AP Connect app: add "Forget" option. +- T-Deck Plus: Implement battery status +- Make firmwares available via release process +- Make firmwares available via web serial website +- Bug: When closing a top level app, there's often an error "can't stop root app" - Create more unit tests for `tactility-core` and `tactility` (PC-only for now) -- WiFi on-at-boot should be a setting in its app -- Create app to edit WiFi settings (e.g. "forget" and "auto-connect" option) - Show a warning screen if firmware encryption or secure boot are off when saving WiFi credentials. - Show a warning screen when a user plugs in the SD card on a device that only supports mounting at boot. -- T-Deck has random sdcard SPI crashes due to sharing bus with screen SPI: make it use the LVGL lock for sdcard operations? - Check service/app id on registration to see if it is a duplicate id - Fix screenshot app on ESP32: it currently blocks when allocating memory -- Localisation of texts +- Localisation of texts (load in boot app from sd?) - Portrait support for GPIO app -- App lifecycle docs mention on_create/on_destroy but app lifecycle is on_start/on_stop - Explore LVGL9's FreeRTOS functionality - Explore LVGL9's ILI93414 driver for 2.4" Yellow Board - Bug: in LVGL9 with M5Core2, crash when bottom item is clicked without scrolling first - Replace M5Unified and M5GFX with custom drivers (so we can fix the Core2 SD card mounting bug, and so we regain some firmware space) - Commit fix to esp_lvgl_port to have `esp_lvgl_port_disp.c` user driver_data instead of user_data - Wifi bug: when pressing disconnect while between `WIFI_EVENT_STA_START` and `IP_EVENT_STA_GOT_IP`, then auto-connect becomes activate again. +- T-Deck Plus: Create separate board config # Core Ideas - Support for displays with different DPI. Consider the layer-based system like on Android. - If present, use LED to show boot status - 2 wire speaker support - tt::app::start() and similar functions as proxies for Loader app start/stop/etc. -- Wi-Fi using dispatcher to dispatch its main functionality to the dedicated Wi-Fi CPU core (to avoid main loop hack) # App Ideas - System logger diff --git a/Documentation/pics/app-lifecycle.png b/Documentation/pics/app-lifecycle.png index 9b5edf02..669df49b 100644 Binary files a/Documentation/pics/app-lifecycle.png and b/Documentation/pics/app-lifecycle.png differ diff --git a/Documentation/pics/app-lifecycle.png~ b/Documentation/pics/app-lifecycle.png~ new file mode 100644 index 00000000..874e863c Binary files /dev/null and b/Documentation/pics/app-lifecycle.png~ differ diff --git a/Tactility/Private/service/loader/Loader_i.h b/Tactility/Private/service/loader/Loader_i.h index fdcdc65b..60bd988b 100644 --- a/Tactility/Private/service/loader/Loader_i.h +++ b/Tactility/Private/service/loader/Loader_i.h @@ -140,10 +140,10 @@ public: struct Loader { Thread* thread; - PubSub* pubsub_internal; - PubSub* pubsub_external; + std::shared_ptr pubsub_internal = std::make_shared(); + std::shared_ptr pubsub_external = std::make_shared(); MessageQueue queue = MessageQueue(2, sizeof(LoaderMessage)); // 2 entries, so you can stop the current app while starting a new one without blocking - Mutex* mutex; + Mutex mutex = Mutex(MutexTypeRecursive); std::stack app_stack; }; diff --git a/Tactility/Source/Tactility.cpp b/Tactility/Source/Tactility.cpp index 4af506e2..bda82b4c 100644 --- a/Tactility/Source/Tactility.cpp +++ b/Tactility/Source/Tactility.cpp @@ -1,3 +1,4 @@ +#include #include "Tactility.h" #include "app/ManifestRegistry.h" @@ -127,7 +128,7 @@ static void register_and_start_user_services(const service::ServiceManifest* con } } -void init(const Configuration& config) { +void run(const Configuration& config) { TT_LOG_I(TAG, "init started"); tt_assert(config.hardware); @@ -160,6 +161,11 @@ void init(const Configuration& config) { } TT_LOG_I(TAG, "init complete"); + + TT_LOG_I(TAG, "Processing main dispatcher"); + while (true) { + getMainDispatcher().consume(TtWaitForever); + } } const Configuration* _Nullable getConfiguration() { diff --git a/Tactility/Source/Tactility.h b/Tactility/Source/Tactility.h index 521616d5..5dfa5e0f 100644 --- a/Tactility/Source/Tactility.h +++ b/Tactility/Source/Tactility.h @@ -19,7 +19,7 @@ typedef struct { * Attempts to initialize Tactility and all configured hardware. * @param config */ -void init(const Configuration& config); +void run(const Configuration& config); /** * While technically nullable, this instance is always set if tt_init() succeeds. diff --git a/Tactility/Source/app/power/Power.cpp b/Tactility/Source/app/power/Power.cpp index 1168ea41..5d518c99 100644 --- a/Tactility/Source/app/power/Power.cpp +++ b/Tactility/Source/app/power/Power.cpp @@ -13,7 +13,7 @@ namespace tt::app::power { #define TAG "power" extern const AppManifest manifest; -static void on_timer(TT_UNUSED void* context); +static void on_timer(TT_UNUSED std::shared_ptr context); struct Data { std::unique_ptr update_timer = std::unique_ptr(new Timer(Timer::TypePeriodic, &on_timer, nullptr)); diff --git a/Tactility/Source/app/wificonnect/WifiConnect.cpp b/Tactility/Source/app/wificonnect/WifiConnect.cpp index 71784c89..d8873b4f 100644 --- a/Tactility/Source/app/wificonnect/WifiConnect.cpp +++ b/Tactility/Source/app/wificonnect/WifiConnect.cpp @@ -54,7 +54,7 @@ static void onConnect(const service::wifi::settings::WifiApSettings* ap_settings } WifiConnect::WifiConnect() { - PubSub* wifi_pubsub = service::wifi::getPubsub(); + auto wifi_pubsub = service::wifi::getPubsub(); wifiSubscription = tt_pubsub_subscribe(wifi_pubsub, &eventCallback, this); bindings = (Bindings) { .onConnectSsid = onConnect, @@ -63,7 +63,7 @@ WifiConnect::WifiConnect() { } WifiConnect::~WifiConnect() { - PubSub* pubsub = service::wifi::getPubsub(); + auto pubsub = service::wifi::getPubsub(); tt_pubsub_unsubscribe(pubsub, wifiSubscription); } diff --git a/Tactility/Source/app/wifimanage/WifiManage.cpp b/Tactility/Source/app/wifimanage/WifiManage.cpp index 986bf6f9..64807aa5 100644 --- a/Tactility/Source/app/wifimanage/WifiManage.cpp +++ b/Tactility/Source/app/wifimanage/WifiManage.cpp @@ -108,7 +108,7 @@ static void wifiManageEventCallback(const void* message, void* context) { } void WifiManage::onShow(AppContext& app, lv_obj_t* parent) { - PubSub* wifi_pubsub = service::wifi::getPubsub(); + auto wifi_pubsub = service::wifi::getPubsub(); wifiSubscription = tt_pubsub_subscribe(wifi_pubsub, &wifiManageEventCallback, this); // State update (it has its own locking) @@ -128,6 +128,7 @@ void WifiManage::onShow(AppContext& app, lv_obj_t* parent) { bool can_scan = radio_state == service::wifi::WIFI_RADIO_ON || radio_state == service::wifi::WIFI_RADIO_CONNECTION_PENDING || radio_state == service::wifi::WIFI_RADIO_CONNECTION_ACTIVE; + TT_LOG_I(TAG, "%d %d", radio_state, service::wifi::isScanning()); if (can_scan && !service::wifi::isScanning()) { service::wifi::scan(); } @@ -135,7 +136,7 @@ void WifiManage::onShow(AppContext& app, lv_obj_t* parent) { void WifiManage::onHide(TT_UNUSED AppContext& app) { lock(); - PubSub* wifi_pubsub = service::wifi::getPubsub(); + auto wifi_pubsub = service::wifi::getPubsub(); tt_pubsub_unsubscribe(wifi_pubsub, wifiSubscription); wifiSubscription = nullptr; isViewEnabled = false; diff --git a/Tactility/Source/lvgl/Statusbar.cpp b/Tactility/Source/lvgl/Statusbar.cpp index 8e9e6ff2..0c72d5db 100644 --- a/Tactility/Source/lvgl/Statusbar.cpp +++ b/Tactility/Source/lvgl/Statusbar.cpp @@ -22,7 +22,7 @@ typedef struct { typedef struct { Mutex* mutex; - PubSub* pubsub; + std::shared_ptr pubsub; StatusbarIcon icons[STATUSBAR_ICON_LIMIT]; } StatusbarData; @@ -40,7 +40,7 @@ typedef struct { static void statusbar_init() { statusbar_data.mutex = tt_mutex_alloc(MutexTypeRecursive); - statusbar_data.pubsub = tt_pubsub_alloc(); + statusbar_data.pubsub = std::make_shared(); for (int i = 0; i < STATUSBAR_ICON_LIMIT; i++) { statusbar_data.icons[i].image = nullptr; statusbar_data.icons[i].visible = false; diff --git a/Tactility/Source/service/loader/Loader.cpp b/Tactility/Source/service/loader/Loader.cpp index 75892c95..dad3d873 100644 --- a/Tactility/Source/service/loader/Loader.cpp +++ b/Tactility/Source/service/loader/Loader.cpp @@ -29,38 +29,30 @@ static Loader* loader_singleton = nullptr; static Loader* loader_alloc() { assert(loader_singleton == nullptr); loader_singleton = new Loader(); - loader_singleton->pubsub_internal = tt_pubsub_alloc(); - loader_singleton->pubsub_external = tt_pubsub_alloc(); loader_singleton->thread = new Thread( "loader", 4096, // Last known minimum was 2400 for starting Hello World app &loader_main, nullptr ); - loader_singleton->mutex = tt_mutex_alloc(MutexTypeRecursive); return loader_singleton; } static void loader_free() { tt_assert(loader_singleton != nullptr); delete loader_singleton->thread; - tt_pubsub_free(loader_singleton->pubsub_internal); - tt_pubsub_free(loader_singleton->pubsub_external); - tt_mutex_free(loader_singleton->mutex); delete loader_singleton; loader_singleton = nullptr; } static void loader_lock() { tt_assert(loader_singleton); - tt_assert(loader_singleton->mutex); - tt_check(tt_mutex_acquire(loader_singleton->mutex, TtWaitForever) == TtStatusOk); + tt_check(loader_singleton->mutex.acquire(TtWaitForever) == TtStatusOk); } static void loader_unlock() { tt_assert(loader_singleton); - tt_assert(loader_singleton->mutex); - tt_check(tt_mutex_release(loader_singleton->mutex) == TtStatusOk); + tt_check(loader_singleton->mutex.release() == TtStatusOk); } LoaderStatus startApp(const std::string& id, bool blocking, std::shared_ptr parameters) { @@ -107,7 +99,7 @@ app::AppContext* _Nullable getCurrentApp() { return dynamic_cast(app); } -PubSub* getPubsub() { +std::shared_ptr getPubsub() { tt_assert(loader_singleton); // it's safe to return pubsub without locking // because it's never freed and loader is never exited diff --git a/Tactility/Source/service/loader/Loader.h b/Tactility/Source/service/loader/Loader.h index f3303c01..d3df1f73 100644 --- a/Tactility/Source/service/loader/Loader.h +++ b/Tactility/Source/service/loader/Loader.h @@ -37,6 +37,6 @@ app::AppContext* _Nullable getCurrentApp(); /** * @brief PubSub for LoaderEvent */ -PubSub* getPubsub(); +std::shared_ptr getPubsub(); } // namespace diff --git a/Tactility/Source/service/statusbar/Statusbar.cpp b/Tactility/Source/service/statusbar/Statusbar.cpp index d6fad481..15ed97da 100644 --- a/Tactility/Source/service/statusbar/Statusbar.cpp +++ b/Tactility/Source/service/statusbar/Statusbar.cpp @@ -1,11 +1,13 @@ #include "Assets.h" +#include "Mutex.h" +#include "Timer.h" +#include "Tactility.h" + #include "hal/Power.h" #include "hal/sdcard/Sdcard.h" -#include "Mutex.h" +#include "lvgl/Statusbar.h" #include "service/ServiceContext.h" #include "service/wifi/Wifi.h" -#include "Tactility.h" -#include "lvgl/Statusbar.h" #include "service/ServiceRegistry.h" namespace tt::service::statusbar { @@ -16,8 +18,7 @@ extern const ServiceManifest manifest; struct ServiceData { Mutex mutex; - Thread thread; - bool service_interrupted = false; + std::unique_ptr updateTimer; int8_t wifi_icon_id = lvgl::statusbar_icon_add(nullptr); const char* wifi_last_icon = nullptr; int8_t sdcard_icon_id = lvgl::statusbar_icon_add(nullptr); @@ -153,51 +154,35 @@ static void service_data_free(ServiceData* data) { free(data); } -int32_t serviceMain(TT_UNUSED void* parameter) { - TT_LOG_I(TAG, "Started main loop"); - delay_ms(20); // TODO: Make service instance findable earlier on (but expose "starting" state?) - auto context = tt::service::findServiceById(manifest.id); - if (context == nullptr) { - TT_LOG_E(TAG, "Service not found"); - return -1; - } - - auto data = std::static_pointer_cast(context->getData()); - - while (!data->service_interrupted) { - update_wifi_icon(data); - update_sdcard_icon(data); - update_power_icon(data); - delay_ms(1000); - } - return 0; +static void onUpdate(std::shared_ptr parameter) { + auto data = std::static_pointer_cast(parameter); + // TODO: Make thread-safe for LVGL + update_wifi_icon(data); + update_sdcard_icon(data); + update_power_icon(data); } static void onStart(ServiceContext& service) { auto data = std::make_shared(); service.setData(data); + // TODO: Make thread-safe for LVGL lvgl::statusbar_icon_set_visibility(data->wifi_icon_id, true); update_wifi_icon(data); update_sdcard_icon(data); // also updates visibility update_power_icon(data); - - data->thread.setCallback(serviceMain, nullptr); - data->thread.setPriority(Thread::PriorityLow); - data->thread.setStackSize(3000); - data->thread.setName("statusbar"); - data->thread.start(); + data->updateTimer = std::make_unique(Timer::TypePeriodic, onUpdate, data); + // We want to try and scan more often in case of startup or scan lock failure + data->updateTimer->start(1000); } static void onStop(ServiceContext& service) { auto data = std::static_pointer_cast(service.getData()); // Stop thread - data->lock(); - data->service_interrupted = true; - data->unlock(); - data->thread.join(); + data->updateTimer->stop(); + data->updateTimer = nullptr; } extern const ServiceManifest manifest = { diff --git a/TactilityCore/Source/Dispatcher.cpp b/TactilityCore/Source/Dispatcher.cpp index e06f0aff..db2d1bf7 100644 --- a/TactilityCore/Source/Dispatcher.cpp +++ b/TactilityCore/Source/Dispatcher.cpp @@ -1,39 +1,50 @@ #include "Dispatcher.h" +#include "Check.h" namespace tt { -Dispatcher::Dispatcher(size_t queueLimit) : - queue(queueLimit, sizeof(DispatcherMessage)), - mutex(MutexTypeNormal), - buffer({ .callback = nullptr, .context = nullptr }) { } +#define TAG "Dispatcher" +#define BACKPRESSURE_WARNING_COUNT 100 + +Dispatcher::Dispatcher() : + mutex(MutexTypeNormal) +{} Dispatcher::~Dispatcher() { - queue.reset(); // Wait for Mutex usage mutex.acquire(TtWaitForever); mutex.release(); } -void Dispatcher::dispatch(Callback callback, void* context) { - DispatcherMessage message = { - .callback = callback, - .context = context - }; +void Dispatcher::dispatch(Callback callback, std::shared_ptr context) { + auto message = std::make_shared(callback, std::move(context)); + // Mutate mutex.acquire(TtWaitForever); - queue.put(&message, TtWaitForever); + queue.push(std::move(message)); + if (queue.size() == BACKPRESSURE_WARNING_COUNT) { + TT_LOG_W(TAG, "Backpressure: You're not consuming fast enough (100 queued)"); + } mutex.release(); + // Signal + eventFlag.set(1); } -bool Dispatcher::consume(uint32_t timeout_ticks) { - mutex.acquire(TtWaitForever); - if (queue.get(&buffer, timeout_ticks) == TtStatusOk) { - buffer.callback(buffer.context); - mutex.release(); - return true; - } else { - mutex.release(); - return false; +uint32_t Dispatcher::consume(uint32_t timeout_ticks) { + // Wait for signal and clear + eventFlag.wait(1, TtFlagWaitAny, timeout_ticks); + eventFlag.clear(1); + + // Mutate + if (mutex.acquire(1 / portTICK_PERIOD_MS) == TtStatusOk) { + auto item = queue.front(); + queue.pop(); + // Don't keep lock as callback might be slow + tt_check(mutex.release() == TtStatusOk); + + item->callback(item->context); } + + return true; } } // namespace diff --git a/TactilityCore/Source/Dispatcher.h b/TactilityCore/Source/Dispatcher.h index dd0dd095..522674f4 100644 --- a/TactilityCore/Source/Dispatcher.h +++ b/TactilityCore/Source/Dispatcher.h @@ -7,29 +7,39 @@ #include "MessageQueue.h" #include "Mutex.h" +#include "EventFlag.h" +#include +#include namespace tt { -typedef void (*Callback)(void* data); +typedef void (*Callback)(std::shared_ptr data); class Dispatcher { private: - typedef struct { + struct DispatcherMessage { Callback callback; - void* context; - } DispatcherMessage; + std::shared_ptr context; // Can't use unique_ptr with void, so we use shared_ptr + + DispatcherMessage(Callback callback, std::shared_ptr context) : + callback(callback), + context(std::move(context)) + {} + + ~DispatcherMessage() = default; + }; - MessageQueue queue; Mutex mutex; - DispatcherMessage buffer; // Buffer for consuming a message + std::queue> queue; + EventFlag eventFlag; public: - explicit Dispatcher(size_t queueLimit = 8); + explicit Dispatcher(); ~Dispatcher(); - void dispatch(Callback callback, void* context); - bool consume(uint32_t timeout_ticks); + void dispatch(Callback callback, std::shared_ptr context); + uint32_t consume(uint32_t timeout_ticks); }; } // namespace diff --git a/TactilityCore/Source/Mutex.cpp b/TactilityCore/Source/Mutex.cpp index ff887aad..5df8e39c 100644 --- a/TactilityCore/Source/Mutex.cpp +++ b/TactilityCore/Source/Mutex.cpp @@ -111,6 +111,10 @@ ThreadId Mutex::getOwner() const { } +std::unique_ptr Mutex::scoped() const { + return std::move(std::make_unique(*this)); +} + Mutex* tt_mutex_alloc(MutexType type) { return new Mutex(type); } @@ -125,7 +129,6 @@ TtStatus tt_mutex_acquire(Mutex* mutex, uint32_t timeout) { TtStatus tt_mutex_release(Mutex* mutex) { return mutex->release(); - } ThreadId tt_mutex_get_owner(Mutex* mutex) { diff --git a/TactilityCore/Source/Mutex.h b/TactilityCore/Source/Mutex.h index da9de57f..5c823268 100644 --- a/TactilityCore/Source/Mutex.h +++ b/TactilityCore/Source/Mutex.h @@ -7,9 +7,13 @@ #include "CoreTypes.h" #include "Thread.h" #include "RtosCompatSemaphore.h" +#include "Check.h" +#include namespace tt { +class ScopedMutexUsage; + typedef enum { MutexTypeNormal, MutexTypeRecursive, @@ -30,6 +34,30 @@ public: TtStatus acquire(uint32_t timeout) const; TtStatus release() const; ThreadId getOwner() const; + + std::unique_ptr scoped() const; +}; + +class ScopedMutexUsage { + + const Mutex& mutex; + bool acquired = false; + +public: + + ScopedMutexUsage(const Mutex& mutex) : mutex(mutex) {} + + ~ScopedMutexUsage() { + if (acquired) { + tt_check(mutex.release() == TtStatusOk); + } + } + + bool acquire(uint32_t timeout) { + TtStatus result = mutex.acquire(timeout); + acquired = (result == TtStatusOk); + return acquired; + } }; /** Allocate Mutex diff --git a/TactilityCore/Source/Pubsub.cpp b/TactilityCore/Source/Pubsub.cpp index 220cde86..73f8567f 100644 --- a/TactilityCore/Source/Pubsub.cpp +++ b/TactilityCore/Source/Pubsub.cpp @@ -1,42 +1,11 @@ #include "Pubsub.h" #include "Check.h" -#include "Mutex.h" #include namespace tt { -struct PubSubSubscription { - uint64_t id; - PubSubCallback callback; - void* callback_context; -}; - -typedef std::list Subscriptions; - -struct PubSub { - uint64_t last_id = 0; - Subscriptions items; - Mutex* mutex; -}; - -PubSub* tt_pubsub_alloc() { - auto* pubsub = new PubSub(); - - pubsub->mutex = tt_mutex_alloc(MutexTypeNormal); - tt_assert(pubsub->mutex); - - return pubsub; -} - -void tt_pubsub_free(PubSub* pubsub) { - tt_assert(pubsub); - tt_check(pubsub->items.empty()); - tt_mutex_free(pubsub->mutex); - delete pubsub; -} - -PubSubSubscription* tt_pubsub_subscribe(PubSub* pubsub, PubSubCallback callback, void* callback_context) { - tt_check(tt_mutex_acquire(pubsub->mutex, TtWaitForever) == TtStatusOk); +PubSubSubscription* tt_pubsub_subscribe(std::shared_ptr pubsub, PubSubCallback callback, void* callback_context) { + tt_check(pubsub->mutex.acquire(TtWaitForever) == TtStatusOk); PubSubSubscription subscription = { .id = (++pubsub->last_id), .callback = callback, @@ -46,16 +15,16 @@ PubSubSubscription* tt_pubsub_subscribe(PubSub* pubsub, PubSubCallback callback, subscription ); - tt_check(tt_mutex_release(pubsub->mutex) == TtStatusOk); + tt_check(pubsub->mutex.release() == TtStatusOk); return (PubSubSubscription*)pubsub->last_id; } -void tt_pubsub_unsubscribe(PubSub* pubsub, PubSubSubscription* pubsub_subscription) { +void tt_pubsub_unsubscribe(std::shared_ptr pubsub, PubSubSubscription* pubsub_subscription) { tt_assert(pubsub); tt_assert(pubsub_subscription); - tt_check(tt_mutex_acquire(pubsub->mutex, TtWaitForever) == TtStatusOk); + tt_check(pubsub->mutex.acquire(TtWaitForever) == TtStatusOk); bool result = false; auto id = (uint64_t)pubsub_subscription; for (auto it = pubsub->items.begin(); it != pubsub->items.end(); it++) { @@ -66,19 +35,19 @@ void tt_pubsub_unsubscribe(PubSub* pubsub, PubSubSubscription* pubsub_subscripti } } - tt_check(tt_mutex_release(pubsub->mutex) == TtStatusOk); + tt_check(pubsub->mutex.release() == TtStatusOk); tt_check(result); } -void tt_pubsub_publish(PubSub* pubsub, void* message) { - tt_check(tt_mutex_acquire(pubsub->mutex, TtWaitForever) == TtStatusOk); +void tt_pubsub_publish(std::shared_ptr pubsub, void* message) { + tt_check(pubsub->mutex.acquire(TtWaitForever) == TtStatusOk); // Iterate over subscribers for (auto& it : pubsub->items) { it.callback(message, it.callback_context); } - tt_check(tt_mutex_release(pubsub->mutex) == TtStatusOk); + tt_check(pubsub->mutex.release() == TtStatusOk); } } // namespace diff --git a/TactilityCore/Source/Pubsub.h b/TactilityCore/Source/Pubsub.h index 6db6bb36..134e892c 100644 --- a/TactilityCore/Source/Pubsub.h +++ b/TactilityCore/Source/Pubsub.h @@ -4,30 +4,30 @@ */ #pragma once +#include "Mutex.h" +#include + namespace tt { /** PubSub Callback type */ typedef void (*PubSubCallback)(const void* message, void* context); -/** PubSub type */ -typedef struct PubSub PubSub; +struct PubSubSubscription { + uint64_t id; + PubSubCallback callback; + void* callback_context; +}; -/** PubSubSubscription type */ -typedef struct PubSubSubscription PubSubSubscription; +struct PubSub { + typedef std::list Subscriptions; + uint64_t last_id = 0; + Subscriptions items; + Mutex mutex; -/** Allocate PubSub - * - * Reentrable, Not threadsafe, one owner - * - * @return pointer to PubSub instance - */ -PubSub* tt_pubsub_alloc(); - -/** Free PubSub - * - * @param pubsub PubSub instance - */ -void tt_pubsub_free(PubSub* pubsub); + ~PubSub() { + tt_check(items.empty()); + } +}; /** Subscribe to PubSub * @@ -40,7 +40,7 @@ void tt_pubsub_free(PubSub* pubsub); * @return pointer to PubSubSubscription instance */ PubSubSubscription* -tt_pubsub_subscribe(PubSub* pubsub, PubSubCallback callback, void* callback_context); +tt_pubsub_subscribe(std::shared_ptr pubsub, PubSubCallback callback, void* callback_context); /** Unsubscribe from PubSub * @@ -50,7 +50,7 @@ tt_pubsub_subscribe(PubSub* pubsub, PubSubCallback callback, void* callback_cont * @param pubsub pointer to PubSub instance * @param pubsub_subscription pointer to PubSubSubscription instance */ -void tt_pubsub_unsubscribe(PubSub* pubsub, PubSubSubscription* pubsub_subscription); +void tt_pubsub_unsubscribe(std::shared_ptr pubsub, PubSubSubscription* pubsub_subscription); /** Publish message to PubSub * @@ -59,6 +59,6 @@ void tt_pubsub_unsubscribe(PubSub* pubsub, PubSubSubscription* pubsub_subscripti * @param pubsub pointer to PubSub instance * @param message message pointer to publish */ -void tt_pubsub_publish(PubSub* pubsub, void* message); +void tt_pubsub_publish(std::shared_ptr pubsub, void* message); } // namespace diff --git a/TactilityCore/Source/Timer.cpp b/TactilityCore/Source/Timer.cpp index 6207ac0f..b17fd1e5 100644 --- a/TactilityCore/Source/Timer.cpp +++ b/TactilityCore/Source/Timer.cpp @@ -1,4 +1,6 @@ #include "Timer.h" + +#include #include "Check.h" #include "Kernel.h" #include "RtosCompat.h" @@ -13,11 +15,11 @@ static void timer_callback(TimerHandle_t hTimer) { } } -Timer::Timer(Type type, Callback callback, void* callbackContext) { +Timer::Timer(Type type, Callback callback, std::shared_ptr callbackContext) { tt_assert((kernel_is_irq() == 0U) && (callback != nullptr)); this->callback = callback; - this->callbackContext = callbackContext; + this->callbackContext = std::move(callbackContext); UBaseType_t reload; if (type == TypeOnce) { diff --git a/TactilityCore/Source/Timer.h b/TactilityCore/Source/Timer.h index 1310ced4..1a6d92a0 100644 --- a/TactilityCore/Source/Timer.h +++ b/TactilityCore/Source/Timer.h @@ -3,6 +3,7 @@ #include "CoreTypes.h" #include "RtosCompatTimers.h" +#include namespace tt { @@ -11,12 +12,12 @@ private: TimerHandle_t timerHandle; public: - typedef void (*Callback)(void* context); + typedef void (*Callback)(std::shared_ptr context); typedef void (*PendingCallback)(void* context, uint32_t arg); Callback callback; - void* callbackContext; + std::shared_ptr callbackContext; typedef enum { TypeOnce = 0, ///< One-shot timer. @@ -28,7 +29,7 @@ public: * @param[in] callback The callback function * @param callbackContext The callback context */ - Timer(Type type, Callback callback, void* callbackContext); + Timer(Type type, Callback callback, std::shared_ptr callbackContext); ~Timer(); diff --git a/TactilityHeadless/Source/TactilityHeadless.cpp b/TactilityHeadless/Source/TactilityHeadless.cpp index 93f6db13..3ad9a787 100644 --- a/TactilityHeadless/Source/TactilityHeadless.cpp +++ b/TactilityHeadless/Source/TactilityHeadless.cpp @@ -1,3 +1,4 @@ +#include #include "TactilityHeadless.h" #include "hal/Configuration.h" #include "hal/Hal_i.h" @@ -15,6 +16,8 @@ namespace tt { namespace service::wifi { extern const ServiceManifest manifest; } namespace service::sdcard { extern const ServiceManifest manifest; } +static Dispatcher mainDispatcher; + static const service::ServiceManifest* const system_services[] = { &service::sdcard::manifest, &service::wifi::manifest @@ -40,6 +43,11 @@ void initHeadless(const hal::Configuration& config) { register_and_start_system_services(); } + +Dispatcher& getMainDispatcher() { + return mainDispatcher; +} + namespace hal { const Configuration& getConfiguration() { diff --git a/TactilityHeadless/Source/TactilityHeadless.h b/TactilityHeadless/Source/TactilityHeadless.h index 2b117fb5..bf92b5f2 100644 --- a/TactilityHeadless/Source/TactilityHeadless.h +++ b/TactilityHeadless/Source/TactilityHeadless.h @@ -2,11 +2,14 @@ #include "hal/Configuration.h" #include "TactilityHeadlessConfig.h" +#include "Dispatcher.h" namespace tt { void initHeadless(const hal::Configuration& config); +Dispatcher& getMainDispatcher(); + } // namespace namespace tt::hal { diff --git a/TactilityHeadless/Source/service/sdcard/Sdcard.cpp b/TactilityHeadless/Source/service/sdcard/Sdcard.cpp index 5d4ad9e9..8a7b1707 100644 --- a/TactilityHeadless/Source/service/sdcard/Sdcard.cpp +++ b/TactilityHeadless/Source/service/sdcard/Sdcard.cpp @@ -1,11 +1,13 @@ -#include - #include "Mutex.h" +#include "Timer.h" + #include "service/ServiceContext.h" #include "TactilityCore.h" #include "TactilityHeadless.h" #include "service/ServiceRegistry.h" +#include + #define TAG "sdcard_service" namespace tt::service::sdcard { @@ -15,21 +17,11 @@ extern const ServiceManifest manifest; struct ServiceData { Mutex mutex; - Thread thread = Thread( - "sdcard", - 3000, // Minimum is ~2800 @ ESP-IDF 5.1.2 when ejecting sdcard - &sdcard_task, - nullptr - ); + std::unique_ptr updateTimer; hal::sdcard::State lastState = hal::sdcard::StateUnmounted; - bool interrupted = false; - ServiceData() { - thread.setPriority(Thread::PriorityLow); - } - - void lock() const { - tt_check(mutex.acquire(TtWaitForever) == TtStatusOk); + bool lock(TickType_t timeout) const { + return mutex.acquire(timeout) == TtStatusOk; } void unlock() const { @@ -38,46 +30,36 @@ struct ServiceData { }; -static int32_t sdcard_task(TT_UNUSED void* context) { - delay_ms(20); // TODO: Make service instance findable earlier on (but expose "starting" state?) - auto service = findServiceById(manifest.id); - if (service == nullptr) { - TT_LOG_E(TAG, "Service not found"); - return -1; +static void onUpdate(std::shared_ptr context) { + auto data = std::static_pointer_cast(context); + + if (!data->lock(50)) { + TT_LOG_W(TAG, "Failed to acquire lock"); + return; } - auto data = std::static_pointer_cast(service->getData()); + hal::sdcard::State new_state = hal::sdcard::getState(); - bool interrupted = false; + if (new_state == hal::sdcard::StateError) { + TT_LOG_W(TAG, "Sdcard error - unmounting. Did you eject the card in an unsafe manner?"); + hal::sdcard::unmount(ms_to_ticks(1000)); + } - do { - data->lock(); + if (new_state != data->lastState) { + data->lastState = new_state; + } - interrupted = data->interrupted; - - hal::sdcard::State new_state = hal::sdcard::getState(); - - if (new_state == hal::sdcard::StateError) { - TT_LOG_W(TAG, "Sdcard error - unmounting. Did you eject the card in an unsafe manner?"); - hal::sdcard::unmount(ms_to_ticks(1000)); - } - - if (new_state != data->lastState) { - data->lastState = new_state; - } - - data->lock(); - delay_ms(2000); - } while (!interrupted); - - return 0; + data->unlock(); } static void onStart(ServiceContext& service) { if (hal::getConfiguration().sdcard != nullptr) { auto data = std::make_shared(); service.setData(data); - data->thread.start(); + + data->updateTimer = std::make_unique(Timer::TypePeriodic, onUpdate, data); + // We want to try and scan more often in case of startup or scan lock failure + data->updateTimer->start(1000); } else { TT_LOG_I(TAG, "task not started due to config"); } @@ -85,12 +67,10 @@ static void onStart(ServiceContext& service) { static void onStop(ServiceContext& service) { auto data = std::static_pointer_cast(service.getData()); - if (data != nullptr) { - data->lock(); - data->interrupted = true; - data->unlock(); - - data->thread.join(); + if (data->updateTimer != nullptr) { + // Stop thread + data->updateTimer->stop(); + data->updateTimer = nullptr; } } diff --git a/TactilityHeadless/Source/service/wifi/Wifi.h b/TactilityHeadless/Source/service/wifi/Wifi.h index 5ca51b80..ddc7ca34 100644 --- a/TactilityHeadless/Source/service/wifi/Wifi.h +++ b/TactilityHeadless/Source/service/wifi/Wifi.h @@ -59,7 +59,7 @@ enum WifiRadioState { WIFI_RADIO_CONNECTION_PENDING, WIFI_RADIO_CONNECTION_ACTIVE, WIFI_RADIO_OFF_PENDING, - WIFI_RADIO_OFF + WIFI_RADIO_OFF, }; struct WifiEvent { @@ -74,9 +74,9 @@ struct WifiApRecord { /** * @brief Get wifi pubsub - * @return PubSub* + * @return PubSub */ -PubSub* getPubsub(); +std::shared_ptr getPubsub(); WifiRadioState getRadioState(); /** diff --git a/TactilityHeadless/Source/service/wifi/WifiEsp.cpp b/TactilityHeadless/Source/service/wifi/WifiEsp.cpp index 9b7806df..2e991968 100644 --- a/TactilityHeadless/Source/service/wifi/WifiEsp.cpp +++ b/TactilityHeadless/Source/service/wifi/WifiEsp.cpp @@ -5,15 +5,18 @@ #include "MessageQueue.h" #include "Mutex.h" #include "Check.h" -#include "freertos/FreeRTOS.h" #include "Log.h" -#include "Pubsub.h" +#include "Timer.h" #include "service/ServiceContext.h" #include "WifiSettings.h" +#include "TactilityCore.h" +#include "TactilityHeadless.h" + +#include "freertos/FreeRTOS.h" + #include #include #include -#include namespace tt::service::wifi { @@ -22,37 +25,33 @@ namespace tt::service::wifi { #define WIFI_FAIL_BIT BIT1 #define AUTO_SCAN_INTERVAL 10000 // ms -typedef enum { - WifiMessageTypeRadioOn, - WifiMessageTypeRadioOff, - WifiMessageTypeScan, - WifiMessageTypeConnect, - WifiMessageTypeDisconnect, - WifiMessageTypeAutoConnect, -} WifiMessageType; - -typedef struct { -} WifiConnectMessage; - -typedef struct { - WifiMessageType type; - union { - WifiConnectMessage connect_message; - }; -} WifiMessage; +// Forward declarations +class Wifi; +static void scan_list_free_safely(std::shared_ptr wifi); +// Methods for main thread dispatcher +static void dispatchAutoConnect(std::shared_ptr context); +static void dispatchEnable(std::shared_ptr context); +static void dispatchDisable(std::shared_ptr context); +static void dispatchScan(std::shared_ptr context); +static void dispatchConnect(std::shared_ptr context); +static void dispatchDisconnectButKeepActive(std::shared_ptr context); class Wifi { -public: - Wifi(); - ~Wifi(); - std::atomic radio_state; +private: + + std::atomic radio_state = WIFI_RADIO_OFF; + bool scan_active = false; + bool secure_connection = false; + +public: + /** @brief Locking mechanism for modifying the Wifi instance */ - Mutex mutex = Mutex(MutexTypeRecursive); + Mutex radioMutex = Mutex(MutexTypeRecursive); + Mutex dataMutex = Mutex(MutexTypeRecursive); + std::unique_ptr autoConnectTimer; /** @brief The public event bus */ - PubSub* pubsub = nullptr; - /** @brief The internal message queue */ - MessageQueue queue = MessageQueue(1, sizeof(WifiMessage)); + std::shared_ptr pubsub = std::make_shared(); // 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. @@ -64,10 +63,8 @@ public: uint16_t scan_list_count = 0; /** @brief Maximum amount of records to scan (value > 0) */ uint16_t scan_list_limit = TT_WIFI_SCAN_RECORD_LIMIT; - bool scan_active = false; /** @brief when we last requested a scan. Loops around every 50 days. */ - TickType_t last_scan_time; - bool secure_connection = false; + TickType_t last_scan_time = portMAX_DELAY; esp_event_handler_instance_t event_handler_any_id = nullptr; esp_event_handler_instance_t event_handler_got_ip = nullptr; EventFlag connection_wait_flags; @@ -78,167 +75,236 @@ public: }; bool pause_auto_connect = false; // Pause when manually disconnecting until manually connecting again bool connection_target_remember = false; // Whether to store the connection_target on successful connection or not + + WifiRadioState getRadioState() const { + auto lock = dataMutex.scoped(); + lock->acquire(TtWaitForever); + return radio_state; + } + + void setRadioState(WifiRadioState newState) { + auto lock = dataMutex.scoped(); + lock->acquire(TtWaitForever); + radio_state = newState; + } + + bool isScanning() const { + auto lock = dataMutex.scoped(); + lock->acquire(TtWaitForever); + return radio_state; + } + + void setScanning(bool newState) { + auto lock = dataMutex.scoped(); + lock->acquire(TtWaitForever); + scan_active = newState; + } + + bool isScanActive() const { + auto lock = dataMutex.scoped(); + lock->acquire(TtWaitForever); + return scan_active; + } + + void setScanActive(bool newState) { + auto lock = dataMutex.scoped(); + lock->acquire(TtWaitForever); + scan_active = newState; + } + + bool isSecureConnection() const { + auto lock = dataMutex.scoped(); + lock->acquire(TtWaitForever); + return secure_connection; + } + + void setSecureConnection(bool newState) { + auto lock = dataMutex.scoped(); + lock->acquire(TtWaitForever); + secure_connection = newState; + } }; static std::shared_ptr wifi_singleton; -// Forward declarations -static void scan_list_free_safely(std::shared_ptr wifi); -static void disconnect_internal_but_keep_active(std::shared_ptr wifi); -static void lock(std::shared_ptr wifi); -static void unlock(std::shared_ptr wifi); - -// region Alloc - -Wifi::Wifi() : radio_state(WIFI_RADIO_OFF) { - pubsub = tt_pubsub_alloc(); -} - -Wifi::~Wifi() { - tt_pubsub_free(pubsub); -} - -// endregion Alloc // region Public functions -PubSub* getPubsub() { - tt_assert(wifi_singleton); - return wifi_singleton->pubsub; +std::shared_ptr getPubsub() { + auto wifi = wifi_singleton; + if (wifi == nullptr) { + tt_crash("Service not running"); + } + + return wifi->pubsub; } WifiRadioState getRadioState() { - tt_assert(wifi_singleton); - lock(wifi_singleton); - WifiRadioState state = wifi_singleton->radio_state; - unlock(wifi_singleton); - return state; + auto wifi = wifi_singleton; + if (wifi != nullptr) { + return wifi->getRadioState(); + } else { + return WIFI_RADIO_OFF; + } } std::string getConnectionTarget() { - lock(wifi_singleton); - std::string result; - switch (wifi_singleton->radio_state) { - case WIFI_RADIO_CONNECTION_PENDING: - case WIFI_RADIO_CONNECTION_ACTIVE: - result = wifi_singleton->connection_target.ssid; - break; - case WIFI_RADIO_ON: - case WIFI_RADIO_ON_PENDING: - case WIFI_RADIO_OFF_PENDING: - case WIFI_RADIO_OFF: - result = ""; - break; + auto wifi = wifi_singleton; + if (wifi == nullptr) { + return ""; } - unlock(wifi_singleton); - return result; + + WifiRadioState state = wifi->getRadioState(); + if ( + state != WIFI_RADIO_CONNECTION_PENDING && + state != WIFI_RADIO_CONNECTION_ACTIVE + ) { + return ""; + } + + return wifi->connection_target.ssid; } void scan() { TT_LOG_I(TAG, "scan()"); - tt_assert(wifi_singleton); - lock(wifi_singleton); - WifiMessage message = {.type = WifiMessageTypeScan}; - // No need to lock for queue - wifi_singleton->queue.put(&message, 100 / portTICK_PERIOD_MS); - unlock(wifi_singleton); + auto wifi = wifi_singleton; + if (wifi == nullptr) { + return; + } + + getMainDispatcher().dispatch(dispatchScan, wifi); } bool isScanning() { - tt_assert(wifi_singleton); - lock(wifi_singleton); - bool is_scanning = wifi_singleton->scan_active; - unlock(wifi_singleton); - return is_scanning; + auto wifi = wifi_singleton; + if (wifi == nullptr) { + return false; + } else { + return wifi->isScanActive(); + } } void connect(const settings::WifiApSettings* ap, bool remember) { TT_LOG_I(TAG, "connect(%s, %d)", ap->ssid, remember); - tt_assert(wifi_singleton); + auto wifi = wifi_singleton; + if (wifi == nullptr) { + return; + } + + auto lock = wifi->dataMutex.scoped(); + if (!lock->acquire(10 / portTICK_PERIOD_MS)) { + return; + } + // Manual connect (e.g. via app) should stop auto-connecting until the connection is established - wifi_singleton->pause_auto_connect = true; - lock(wifi_singleton); - memcpy(&wifi_singleton->connection_target, ap, sizeof(settings::WifiApSettings)); - wifi_singleton->connection_target_remember = remember; - WifiMessage message = {.type = WifiMessageTypeConnect}; - wifi_singleton->queue.put(&message, 100 / portTICK_PERIOD_MS); - unlock(wifi_singleton); + wifi->pause_auto_connect = true; + memcpy(&wifi->connection_target, ap, sizeof(settings::WifiApSettings)); + wifi->connection_target_remember = remember; + getMainDispatcher().dispatch(dispatchConnect, wifi); } void disconnect() { TT_LOG_I(TAG, "disconnect()"); - tt_assert(wifi_singleton); - lock(wifi_singleton); - wifi_singleton->connection_target = (settings::WifiApSettings) { + auto wifi = wifi_singleton; + if (wifi == nullptr) { + return; + } + + auto lock = wifi->dataMutex.scoped(); + if (!lock->acquire(10 / portTICK_PERIOD_MS)) { + return; + } + + wifi->connection_target = (settings::WifiApSettings) { .ssid = { 0 }, .password = { 0 }, .auto_connect = false }; // Manual disconnect (e.g. via app) should stop auto-connecting until a new connection is established - wifi_singleton->pause_auto_connect = true; - WifiMessage message = {.type = WifiMessageTypeDisconnect}; - wifi_singleton->queue.put(&message, 100 / portTICK_PERIOD_MS); - unlock(wifi_singleton); + wifi->pause_auto_connect = true; + getMainDispatcher().dispatch(dispatchDisconnectButKeepActive, wifi); } void setScanRecords(uint16_t records) { TT_LOG_I(TAG, "setScanRecords(%d)", records); - tt_assert(wifi_singleton); - lock(wifi_singleton); - if (records != wifi_singleton->scan_list_limit) { - scan_list_free_safely(wifi_singleton); - wifi_singleton->scan_list_limit = records; + auto wifi = wifi_singleton; + if (wifi == nullptr) { + return; + } + + auto lock = wifi->dataMutex.scoped(); + if (!lock->acquire(10 / portTICK_PERIOD_MS)) { + return; + } + + if (records != wifi->scan_list_limit) { + scan_list_free_safely(wifi); + wifi->scan_list_limit = records; } - unlock(wifi_singleton); } std::vector getScanResults() { TT_LOG_I(TAG, "getScanResults()"); - tt_assert(wifi_singleton); + auto wifi = wifi_singleton; std::vector records; - lock(wifi_singleton); - if (wifi_singleton->scan_list_count > 0) { + if (wifi == nullptr) { + return records; + } + + auto lock = wifi->dataMutex.scoped(); + if (!lock->acquire(10 / portTICK_PERIOD_MS)) { + return records; + } + + if (wifi->scan_list_count > 0) { uint16_t i = 0; - for (; i < wifi_singleton->scan_list_count; ++i) { + for (; i < wifi->scan_list_count; ++i) { records.push_back((WifiApRecord) { - .ssid = (const char*)wifi_singleton->scan_list[i].ssid, - .rssi = wifi_singleton->scan_list[i].rssi, - .auth_mode = wifi_singleton->scan_list[i].authmode + .ssid = (const char*)wifi->scan_list[i].ssid, + .rssi = wifi->scan_list[i].rssi, + .auth_mode = wifi->scan_list[i].authmode }); } } - unlock(wifi_singleton); return records; } void setEnabled(bool enabled) { TT_LOG_I(TAG, "setEnabled(%d)", enabled); - tt_assert(wifi_singleton); - lock(wifi_singleton); - if (enabled) { - WifiMessage message = {.type = WifiMessageTypeRadioOn}; - // No need to lock for queue - wifi_singleton->queue.put(&message, 100 / portTICK_PERIOD_MS); - } else { - WifiMessage message = {.type = WifiMessageTypeRadioOff}; - // No need to lock for queue - wifi_singleton->queue.put(&message, 100 / portTICK_PERIOD_MS); - // Reset pause state + auto wifi = wifi_singleton; + if (wifi == nullptr) { + return; } - wifi_singleton->pause_auto_connect = false; - wifi_singleton->last_scan_time = 0; - unlock(wifi_singleton); + + auto lock = wifi->dataMutex.scoped(); + if (!lock->acquire(10 / portTICK_PERIOD_MS)) { + return; + } + + if (enabled) { + getMainDispatcher().dispatch(dispatchEnable, wifi); + } else { + getMainDispatcher().dispatch(dispatchDisable, wifi); + } + wifi->pause_auto_connect = false; + wifi->last_scan_time = 0; } bool isConnectionSecure() { - tt_assert(wifi_singleton); - lock(wifi_singleton); - bool is_secure = wifi_singleton->secure_connection; - unlock(wifi_singleton); - return is_secure; + auto wifi = wifi_singleton; + if (wifi == nullptr) { + return false; + } + + auto lock = wifi->dataMutex.scoped(); + if (!lock->acquire(10 / portTICK_PERIOD_MS)) { + return false; + } + + return wifi->isSecureConnection(); } int getRssi() { @@ -253,55 +319,66 @@ int getRssi() { // endregion Public functions -static void lock(std::shared_ptr wifi) { - tt_assert(wifi); - wifi->mutex.acquire(ms_to_ticks(100)); -} - -static void unlock(std::shared_ptr wifi) { - tt_assert(wifi); - wifi->mutex.release(); -} - static void scan_list_alloc(std::shared_ptr wifi) { - tt_assert(wifi->scan_list == nullptr); - wifi->scan_list = static_cast(malloc(sizeof(wifi_ap_record_t) * wifi->scan_list_limit)); - wifi->scan_list_count = 0; + auto lock = wifi->dataMutex.scoped(); + if (lock->acquire(TtWaitForever)) { + tt_assert(wifi->scan_list == nullptr); + wifi->scan_list = static_cast(malloc(sizeof(wifi_ap_record_t) * wifi->scan_list_limit)); + wifi->scan_list_count = 0; + } } static void scan_list_alloc_safely(std::shared_ptr wifi) { - if (wifi->scan_list == nullptr) { - scan_list_alloc(wifi); + auto lock = wifi->dataMutex.scoped(); + if (lock->acquire(TtWaitForever)) { + if (wifi->scan_list == nullptr) { + scan_list_alloc(wifi); + } } } static void scan_list_free(std::shared_ptr wifi) { - tt_assert(wifi->scan_list != nullptr); - free(wifi->scan_list); - wifi->scan_list = nullptr; - wifi->scan_list_count = 0; + auto lock = wifi->dataMutex.scoped(); + if (lock->acquire(TtWaitForever)) { + tt_assert(wifi->scan_list != nullptr); + free(wifi->scan_list); + wifi->scan_list = nullptr; + wifi->scan_list_count = 0; + } } static void scan_list_free_safely(std::shared_ptr wifi) { - if (wifi->scan_list != nullptr) { - scan_list_free(wifi); + auto lock = wifi->dataMutex.scoped(); + if (lock->acquire(TtWaitForever)) { + if (wifi->scan_list != nullptr) { + scan_list_free(wifi); + } } } static void publish_event_simple(std::shared_ptr wifi, WifiEventType type) { - WifiEvent turning_on_event = {.type = type}; - tt_pubsub_publish(wifi->pubsub, &turning_on_event); + auto lock = wifi->dataMutex.scoped(); + if (lock->acquire(TtWaitForever)) { + WifiEvent turning_on_event = {.type = type}; + tt_pubsub_publish(wifi->pubsub, &turning_on_event); + } } static bool copy_scan_list(std::shared_ptr wifi) { - bool can_fetch_results = (wifi->radio_state == WIFI_RADIO_ON || wifi->radio_state == WIFI_RADIO_CONNECTION_ACTIVE) && - wifi->scan_active; + auto state = wifi->getRadioState(); + bool can_fetch_results = (state == WIFI_RADIO_ON || state == WIFI_RADIO_CONNECTION_ACTIVE) && + wifi->isScanActive(); if (!can_fetch_results) { TT_LOG_I(TAG, "Skip scan result fetching"); return false; } + auto lock = wifi->dataMutex.scoped(); + if (!lock->acquire(TtWaitForever)) { + return false; + } + // Create scan list if it does not exist scan_list_alloc_safely(wifi); wifi->scan_list_count = 0; @@ -322,166 +399,204 @@ static bool copy_scan_list(std::shared_ptr wifi) { } } -static void auto_connect(std::shared_ptr wifi) { - TT_LOG_I(TAG, "auto_connect()"); - for (int i = 0; i < wifi->scan_list_count; ++i) { - auto ssid = reinterpret_cast(wifi->scan_list[i].ssid); - if (settings::contains(ssid)) { - static_assert(sizeof(wifi->scan_list[i].ssid) == (TT_WIFI_SSID_LIMIT + 1), "SSID size mismatch"); - settings::WifiApSettings ap_settings; - if (settings::load(ssid, &ap_settings)) { - if (ap_settings.auto_connect) { - TT_LOG_I(TAG, "Auto-connecting to %s", ap_settings.ssid); - connect(&ap_settings, false); +static bool find_auto_connect_ap(std::shared_ptr context, settings::WifiApSettings& settings) { + auto wifi = std::static_pointer_cast(context); + auto lock = wifi->dataMutex.scoped(); + + if (lock->acquire(10 / portTICK_PERIOD_MS)) { + TT_LOG_I(TAG, "auto_connect()"); + for (int i = 0; i < wifi->scan_list_count; ++i) { + auto ssid = reinterpret_cast(wifi->scan_list[i].ssid); + if (settings::contains(ssid)) { + static_assert(sizeof(wifi->scan_list[i].ssid) == (TT_WIFI_SSID_LIMIT + 1), "SSID size mismatch"); + if (settings::load(ssid, &settings)) { + if (settings.auto_connect) { + return true; + } + } else { + TT_LOG_E(TAG, "Failed to load credentials for ssid %s", ssid); } - } else { - TT_LOG_E(TAG, "Failed to load credentials for ssid %s", ssid); + break; } - break; } } + + return false; +} + +static void dispatchAutoConnect(std::shared_ptr context) { + TT_LOG_I(TAG, "dispatchAutoConnect()"); + auto wifi = std::static_pointer_cast(context); + + settings::WifiApSettings settings; + if (find_auto_connect_ap(context, settings)) { + TT_LOG_I(TAG, "Auto-connecting to %s", settings.ssid); + connect(&settings, false); + } } -static void event_handler(TT_UNUSED void* arg, esp_event_base_t event_base, int32_t event_id, void* event_data) { - lock(wifi_singleton); +static void eventHandler(TT_UNUSED void* arg, esp_event_base_t event_base, int32_t event_id, void* event_data) { + auto wifi = wifi_singleton; + if (wifi == nullptr) { + TT_LOG_E(TAG, "eventHandler: no wifi instance"); + return; + } + if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_START) { - TT_LOG_I(TAG, "event_handler: sta start"); - if (wifi_singleton->radio_state == WIFI_RADIO_CONNECTION_PENDING) { + TT_LOG_I(TAG, "eventHandler: sta start"); + if (wifi->getRadioState() == WIFI_RADIO_CONNECTION_PENDING) { esp_wifi_connect(); } } else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED) { - TT_LOG_I(TAG, "event_handler: disconnected"); - if (wifi_singleton->radio_state == WIFI_RADIO_CONNECTION_PENDING) { - wifi_singleton->connection_wait_flags.set(WIFI_FAIL_BIT); + TT_LOG_I(TAG, "eventHandler: disconnected"); + if (wifi->getRadioState() == WIFI_RADIO_CONNECTION_PENDING) { + wifi->connection_wait_flags.set(WIFI_FAIL_BIT); } - wifi_singleton->radio_state = WIFI_RADIO_ON; - publish_event_simple(wifi_singleton, WifiEventTypeDisconnected); + wifi->setRadioState(WIFI_RADIO_ON); + publish_event_simple(wifi, WifiEventTypeDisconnected); } else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP) { auto* event = static_cast(event_data); - TT_LOG_I(TAG, "event_handler: got ip:" IPSTR, IP2STR(&event->ip_info.ip)); - if (wifi_singleton->radio_state == WIFI_RADIO_CONNECTION_PENDING) { - wifi_singleton->connection_wait_flags.set(WIFI_CONNECTED_BIT); + TT_LOG_I(TAG, "eventHandler: got ip:" IPSTR, IP2STR(&event->ip_info.ip)); + if (wifi->getRadioState() == WIFI_RADIO_CONNECTION_PENDING) { + wifi->connection_wait_flags.set(WIFI_CONNECTED_BIT); // We resume auto-connecting only when there was an explicit request by the user for the connection - wifi_singleton->pause_auto_connect = false; // Resume auto-connection + // TODO: Make thread-safe + wifi->pause_auto_connect = false; // Resume auto-connection } } else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_SCAN_DONE) { auto* event = static_cast(event_data); - TT_LOG_I(TAG, "event_handler: wifi scanning done (scan id %u)", event->scan_id); - bool copied_list = copy_scan_list(wifi_singleton); + TT_LOG_I(TAG, "eventHandler: wifi scanning done (scan id %u)", event->scan_id); + bool copied_list = copy_scan_list(wifi); + auto state = wifi->getRadioState(); if ( - wifi_singleton->radio_state != WIFI_RADIO_OFF && - wifi_singleton->radio_state != WIFI_RADIO_OFF_PENDING + state != WIFI_RADIO_OFF && + state != WIFI_RADIO_OFF_PENDING ) { - wifi_singleton->scan_active = false; + wifi->setScanActive(false); esp_wifi_scan_stop(); } publish_event_simple(wifi_singleton, WifiEventTypeScanFinished); - TT_LOG_I(TAG, "Finished scan"); + TT_LOG_I(TAG, "eventHandler: Finished scan"); - if (copied_list && wifi_singleton->radio_state == WIFI_RADIO_ON) { - WifiMessage message = {.type = WifiMessageTypeAutoConnect}; - // No need to lock for queue - wifi_singleton->queue.put(&message, 100 / portTICK_PERIOD_MS); + if (copied_list && wifi_singleton->getRadioState() == WIFI_RADIO_ON && !wifi->pause_auto_connect) { + getMainDispatcher().dispatch(dispatchAutoConnect, wifi); } } - unlock(wifi_singleton); } -static void enable(std::shared_ptr wifi) { - WifiRadioState state = wifi->radio_state; +static void dispatchEnable(std::shared_ptr context) { + TT_LOG_I(TAG, "dispatchEnable()"); + auto wifi = std::static_pointer_cast(context); + + WifiRadioState state = wifi->getRadioState(); if ( state == WIFI_RADIO_ON || state == WIFI_RADIO_ON_PENDING || state == WIFI_RADIO_OFF_PENDING - ) { + ) { TT_LOG_W(TAG, "Can't enable from current state"); return; } - TT_LOG_I(TAG, "Enabling"); - wifi->radio_state = WIFI_RADIO_ON_PENDING; - publish_event_simple(wifi, WifiEventTypeRadioStateOnPending); + auto lock = std::make_unique(wifi->radioMutex); - if (wifi->netif != nullptr) { - esp_netif_destroy(wifi->netif); - } - wifi->netif = esp_netif_create_default_wifi_sta(); + if (lock->acquire(50 / portTICK_PERIOD_MS)) { + TT_LOG_I(TAG, "Enabling"); + wifi->setRadioState(WIFI_RADIO_ON_PENDING); + publish_event_simple(wifi, WifiEventTypeRadioStateOnPending); - // Warning: this is the memory-intensive operation - // It uses over 117kB of RAM with default settings for S3 on IDF v5.1.2 - wifi_init_config_t config = WIFI_INIT_CONFIG_DEFAULT(); - esp_err_t init_result = esp_wifi_init(&config); - if (init_result != ESP_OK) { - TT_LOG_E(TAG, "Wifi init failed"); - if (init_result == ESP_ERR_NO_MEM) { - TT_LOG_E(TAG, "Insufficient memory"); + if (wifi->netif != nullptr) { + esp_netif_destroy(wifi->netif); } - wifi->radio_state = WIFI_RADIO_OFF; - publish_event_simple(wifi, WifiEventTypeRadioStateOff); - return; - } + wifi->netif = esp_netif_create_default_wifi_sta(); - esp_wifi_set_storage(WIFI_STORAGE_RAM); - - // TODO: don't crash on check failure - ESP_ERROR_CHECK(esp_event_handler_instance_register( - WIFI_EVENT, - ESP_EVENT_ANY_ID, - &event_handler, - nullptr, - &wifi->event_handler_any_id - )); - - // TODO: don't crash on check failure - ESP_ERROR_CHECK(esp_event_handler_instance_register( - IP_EVENT, - IP_EVENT_STA_GOT_IP, - &event_handler, - nullptr, - &wifi->event_handler_got_ip - )); - - if (esp_wifi_set_mode(WIFI_MODE_STA) != ESP_OK) { - TT_LOG_E(TAG, "Wifi mode setting failed"); - wifi->radio_state = WIFI_RADIO_OFF; - esp_wifi_deinit(); - publish_event_simple(wifi, WifiEventTypeRadioStateOff); - return; - } - - esp_err_t start_result = esp_wifi_start(); - if (start_result != ESP_OK) { - TT_LOG_E(TAG, "Wifi start failed"); - if (start_result == ESP_ERR_NO_MEM) { - TT_LOG_E(TAG, "Insufficient memory"); + // Warning: this is the memory-intensive operation + // It uses over 117kB of RAM with default settings for S3 on IDF v5.1.2 + wifi_init_config_t config = WIFI_INIT_CONFIG_DEFAULT(); + esp_err_t init_result = esp_wifi_init(&config); + if (init_result != ESP_OK) { + TT_LOG_E(TAG, "Wifi init failed"); + if (init_result == ESP_ERR_NO_MEM) { + TT_LOG_E(TAG, "Insufficient memory"); + } + wifi->setRadioState(WIFI_RADIO_OFF); + publish_event_simple(wifi, WifiEventTypeRadioStateOff); + return; } - wifi->radio_state = WIFI_RADIO_OFF; - esp_wifi_set_mode(WIFI_MODE_NULL); - esp_wifi_deinit(); - publish_event_simple(wifi, WifiEventTypeRadioStateOff); - return; - } - wifi->radio_state = WIFI_RADIO_ON; - publish_event_simple(wifi, WifiEventTypeRadioStateOn); - TT_LOG_I(TAG, "Enabled"); + esp_wifi_set_storage(WIFI_STORAGE_RAM); + + // TODO: don't crash on check failure + ESP_ERROR_CHECK(esp_event_handler_instance_register( + WIFI_EVENT, + ESP_EVENT_ANY_ID, + &eventHandler, + nullptr, + &wifi->event_handler_any_id + )); + + // TODO: don't crash on check failure + ESP_ERROR_CHECK(esp_event_handler_instance_register( + IP_EVENT, + IP_EVENT_STA_GOT_IP, + &eventHandler, + nullptr, + &wifi->event_handler_got_ip + )); + + if (esp_wifi_set_mode(WIFI_MODE_STA) != ESP_OK) { + TT_LOG_E(TAG, "Wifi mode setting failed"); + wifi->setRadioState(WIFI_RADIO_OFF); + esp_wifi_deinit(); + publish_event_simple(wifi, WifiEventTypeRadioStateOff); + return; + } + + esp_err_t start_result = esp_wifi_start(); + if (start_result != ESP_OK) { + TT_LOG_E(TAG, "Wifi start failed"); + if (start_result == ESP_ERR_NO_MEM) { + TT_LOG_E(TAG, "Insufficient memory"); + } + wifi->setRadioState(WIFI_RADIO_OFF); + esp_wifi_set_mode(WIFI_MODE_NULL); + esp_wifi_deinit(); + publish_event_simple(wifi, WifiEventTypeRadioStateOff); + return; + } + + wifi->setRadioState(WIFI_RADIO_ON); + publish_event_simple(wifi, WifiEventTypeRadioStateOn); + TT_LOG_I(TAG, "Enabled"); + } else { + TT_LOG_E(TAG, "enable() mutex timeout"); + } } -static void disable(std::shared_ptr wifi) { - WifiRadioState state = wifi->radio_state; +static void dispatchDisable(std::shared_ptr context) { + TT_LOG_I(TAG, "dispatchDisable()"); + auto wifi = std::static_pointer_cast(context); + auto lock = wifi->radioMutex.scoped(); + + if (!lock->acquire(50 / portTICK_PERIOD_MS)) { + TT_LOG_E(TAG, "disable() mutex timeout"); + return; + } + + WifiRadioState state = wifi->getRadioState(); if ( state == WIFI_RADIO_OFF || state == WIFI_RADIO_OFF_PENDING || state == WIFI_RADIO_ON_PENDING - ) { + ) { TT_LOG_W(TAG, "Can't disable from current state"); return; } TT_LOG_I(TAG, "Disabling"); - wifi->radio_state = WIFI_RADIO_OFF_PENDING; + wifi->setRadioState(WIFI_RADIO_OFF_PENDING); publish_event_simple(wifi, WifiEventTypeRadioStateOffPending); // Free up scan list memory @@ -489,7 +604,7 @@ static void disable(std::shared_ptr wifi) { if (esp_wifi_stop() != ESP_OK) { TT_LOG_E(TAG, "Failed to stop radio"); - wifi->radio_state = WIFI_RADIO_ON; + wifi->setRadioState(WIFI_RADIO_ON); publish_event_simple(wifi, WifiEventTypeRadioStateOn); return; } @@ -499,18 +614,18 @@ static void disable(std::shared_ptr wifi) { } if (esp_event_handler_instance_unregister( - WIFI_EVENT, - ESP_EVENT_ANY_ID, - wifi->event_handler_any_id - ) != ESP_OK) { + WIFI_EVENT, + ESP_EVENT_ANY_ID, + wifi->event_handler_any_id + ) != ESP_OK) { TT_LOG_E(TAG, "Failed to unregister id event handler"); } if (esp_event_handler_instance_unregister( - IP_EVENT, - IP_EVENT_STA_GOT_IP, - wifi->event_handler_got_ip - ) != ESP_OK) { + IP_EVENT, + IP_EVENT_STA_GOT_IP, + wifi->event_handler_got_ip + ) != ESP_OK) { TT_LOG_E(TAG, "Failed to unregister ip event handler"); } @@ -521,38 +636,60 @@ static void disable(std::shared_ptr wifi) { tt_assert(wifi->netif != nullptr); esp_netif_destroy(wifi->netif); wifi->netif = nullptr; - wifi->scan_active = false; - wifi->radio_state = WIFI_RADIO_OFF; + wifi->setScanActive(false); + wifi->setRadioState(WIFI_RADIO_OFF); publish_event_simple(wifi, WifiEventTypeRadioStateOff); TT_LOG_I(TAG, "Disabled"); } -static void scan_internal(std::shared_ptr wifi) { - WifiRadioState state = wifi->radio_state; +static void dispatchScan(std::shared_ptr context) { + TT_LOG_I(TAG, "dispatchScan()"); + auto wifi = std::static_pointer_cast(context); + auto lock = wifi->radioMutex.scoped(); + + if (!lock->acquire(10 / portTICK_PERIOD_MS)) { + TT_LOG_E(TAG, "dispatchScan() mutex timeout"); + return; + } + + WifiRadioState state = wifi->getRadioState(); if (state != WIFI_RADIO_ON && state != WIFI_RADIO_CONNECTION_ACTIVE && state != WIFI_RADIO_CONNECTION_PENDING) { TT_LOG_W(TAG, "Scan unavailable: wifi not enabled"); return; } - if (!wifi->scan_active) { - wifi->last_scan_time = tt::get_ticks(); - if (esp_wifi_scan_start(nullptr, false) == ESP_OK) { - TT_LOG_I(TAG, "Starting scan"); - wifi->scan_active = true; - publish_event_simple(wifi, WifiEventTypeScanStarted); - } else { - TT_LOG_I(TAG, "Can't start scan"); - } - } else { + if (wifi->isScanActive()) { TT_LOG_W(TAG, "Scan already pending"); + return; } + + // TODO: Thread safety + wifi->last_scan_time = tt::get_ticks(); + + if (esp_wifi_scan_start(nullptr, false) != ESP_OK) { + TT_LOG_I(TAG, "Can't start scan"); + return; + } + + TT_LOG_I(TAG, "Starting scan"); + wifi->setScanActive(true); + publish_event_simple(wifi, WifiEventTypeScanStarted); } -static void connect_internal(std::shared_ptr wifi) { +static void dispatchConnect(std::shared_ptr context) { + TT_LOG_I(TAG, "dispatchConnect()"); + auto wifi = std::static_pointer_cast(context); + auto lock = wifi->radioMutex.scoped(); + + if (!lock->acquire(50 / portTICK_PERIOD_MS)) { + TT_LOG_E(TAG, "dispatchConnect() mutex timeout"); + return; + } + TT_LOG_I(TAG, "Connecting to %s", wifi->connection_target.ssid); // Stop radio first, if needed - WifiRadioState radio_state = wifi->radio_state; + WifiRadioState radio_state = wifi->getRadioState(); if ( radio_state == WIFI_RADIO_ON || radio_state == WIFI_RADIO_CONNECTION_ACTIVE || @@ -560,14 +697,14 @@ static void connect_internal(std::shared_ptr wifi) { ) { TT_LOG_I(TAG, "Connecting: Stopping radio first"); esp_err_t stop_result = esp_wifi_stop(); - wifi->scan_active = false; + wifi->setScanActive(false); if (stop_result != ESP_OK) { TT_LOG_E(TAG, "Connecting: Failed to disconnect (%s)", esp_err_to_name(stop_result)); return; } } - wifi->radio_state = WIFI_RADIO_CONNECTION_PENDING; + wifi->setRadioState(WIFI_RADIO_CONNECTION_PENDING); publish_event_simple(wifi, WifiEventTypeConnectionPending); @@ -621,11 +758,9 @@ static void connect_internal(std::shared_ptr wifi) { memcpy(wifi_config.sta.ssid, wifi_singleton->connection_target.ssid, sizeof(wifi_config.sta.ssid)); memcpy(wifi_config.sta.password, wifi_singleton->connection_target.password, sizeof(wifi_config.sta.password)); - wifi->secure_connection = (wifi_config.sta.password[0] != 0x00); - esp_err_t set_config_result = esp_wifi_set_config(WIFI_IF_STA, &wifi_config); if (set_config_result != ESP_OK) { - wifi->radio_state = WIFI_RADIO_ON; + wifi->setRadioState(WIFI_RADIO_ON); TT_LOG_E(TAG, "Failed to set wifi config (%s)", esp_err_to_name(set_config_result)); publish_event_simple(wifi, WifiEventTypeConnectionFailed); return; @@ -633,7 +768,7 @@ static void connect_internal(std::shared_ptr wifi) { esp_err_t wifi_start_result = esp_wifi_start(); if (wifi_start_result != ESP_OK) { - wifi->radio_state = WIFI_RADIO_ON; + wifi->setRadioState(WIFI_RADIO_ON); TT_LOG_E(TAG, "Failed to start wifi to begin connecting (%s)", esp_err_to_name(wifi_start_result)); publish_event_simple(wifi, WifiEventTypeConnectionFailed); return; @@ -643,9 +778,11 @@ static void connect_internal(std::shared_ptr wifi) { * or connection failed for the maximum number of re-tries (WIFI_FAIL_BIT). * The bits are set by wifi_event_handler() */ uint32_t bits = wifi_singleton->connection_wait_flags.wait(WIFI_FAIL_BIT | WIFI_CONNECTED_BIT); + TT_LOG_I(TAG, "Waiting for EventFlag by event_handler()"); if (bits & WIFI_CONNECTED_BIT) { - wifi->radio_state = WIFI_RADIO_CONNECTION_ACTIVE; + wifi->setSecureConnection(wifi_config.sta.password[0] != 0x00); + wifi->setRadioState(WIFI_RADIO_CONNECTION_ACTIVE); publish_event_simple(wifi, WifiEventTypeConnectionSuccess); TT_LOG_I(TAG, "Connected to %s", wifi->connection_target.ssid); if (wifi->connection_target_remember) { @@ -656,11 +793,11 @@ static void connect_internal(std::shared_ptr wifi) { } } } else if (bits & WIFI_FAIL_BIT) { - wifi->radio_state = WIFI_RADIO_ON; + wifi->setRadioState(WIFI_RADIO_ON); publish_event_simple(wifi, WifiEventTypeConnectionFailed); TT_LOG_I(TAG, "Failed to connect to %s", wifi->connection_target.ssid); } else { - wifi->radio_state = WIFI_RADIO_ON; + wifi->setRadioState(WIFI_RADIO_ON); publish_event_simple(wifi, WifiEventTypeConnectionFailed); TT_LOG_E(TAG, "UNEXPECTED EVENT"); } @@ -668,7 +805,16 @@ static void connect_internal(std::shared_ptr wifi) { wifi_singleton->connection_wait_flags.clear(WIFI_FAIL_BIT | WIFI_CONNECTED_BIT); } -static void disconnect_internal_but_keep_active(std::shared_ptr wifi) { +static void dispatchDisconnectButKeepActive(std::shared_ptr context) { + TT_LOG_I(TAG, "dispatchDisconnectButKeepActive()"); + auto wifi = std::static_pointer_cast(context); + auto lock = wifi->radioMutex.scoped(); + + if (!lock->acquire(50 / portTICK_PERIOD_MS)) { + TT_LOG_E(TAG, "disconnect_internal_but_keep_active() mutex timeout"); + return; + } + esp_err_t stop_result = esp_wifi_stop(); if (stop_result != ESP_OK) { TT_LOG_E(TAG, "Failed to disconnect (%s)", esp_err_to_name(stop_result)); @@ -691,7 +837,7 @@ static void disconnect_internal_but_keep_active(std::shared_ptr wifi) { esp_err_t set_config_result = esp_wifi_set_config(WIFI_IF_STA, &wifi_config); if (set_config_result != ESP_OK) { // TODO: disable radio, because radio state is in limbo between off and on - wifi->radio_state = WIFI_RADIO_OFF; + wifi->setRadioState(WIFI_RADIO_OFF); TT_LOG_E(TAG, "failed to set wifi config (%s)", esp_err_to_name(set_config_result)); publish_event_simple(wifi, WifiEventTypeRadioStateOff); return; @@ -700,122 +846,96 @@ static void disconnect_internal_but_keep_active(std::shared_ptr wifi) { esp_err_t wifi_start_result = esp_wifi_start(); if (wifi_start_result != ESP_OK) { // TODO: disable radio, because radio state is in limbo between off and on - wifi->radio_state = WIFI_RADIO_OFF; + wifi->setRadioState(WIFI_RADIO_OFF); TT_LOG_E(TAG, "failed to start wifi to begin connecting (%s)", esp_err_to_name(wifi_start_result)); publish_event_simple(wifi, WifiEventTypeRadioStateOff); return; } - wifi->radio_state = WIFI_RADIO_ON; + wifi->setRadioState(WIFI_RADIO_ON); publish_event_simple(wifi, WifiEventTypeDisconnected); TT_LOG_I(TAG, "Disconnected"); } static bool shouldScanForAutoConnect(std::shared_ptr wifi) { - bool is_radio_in_scannable_state = wifi->radio_state == WIFI_RADIO_ON && - !wifi->scan_active && - !wifi->pause_auto_connect; + auto lock = wifi->dataMutex.scoped(); - if (is_radio_in_scannable_state) { - TickType_t current_time = tt::get_ticks(); - bool scan_time_has_looped = (current_time < wifi->last_scan_time); - bool no_recent_scan = (current_time - wifi->last_scan_time) > (AUTO_SCAN_INTERVAL / portTICK_PERIOD_MS); - return scan_time_has_looped || no_recent_scan; - } else { + if (!lock->acquire(100)) { return false; } + + bool is_radio_in_scannable_state = wifi->getRadioState() == WIFI_RADIO_ON && + !wifi->isScanActive() && + !wifi->pause_auto_connect; + + if (!is_radio_in_scannable_state) { + return false; + } + + TickType_t current_time = tt::get_ticks(); + bool scan_time_has_looped = (current_time < wifi->last_scan_time); + bool no_recent_scan = (current_time - wifi->last_scan_time) > (AUTO_SCAN_INTERVAL / portTICK_PERIOD_MS); + + return scan_time_has_looped || no_recent_scan; +} + +void onAutoConnectTimer(std::shared_ptr context) { + auto wifi = std::static_pointer_cast(wifi_singleton); + // Automatic scanning is done so we can automatically connect to access points + bool should_auto_scan = shouldScanForAutoConnect(wifi); + if (should_auto_scan) { + getMainDispatcher().dispatch(dispatchScan, wifi); + } } -// ESP Wi-Fi APIs need to run from the main task, so we can't just spawn a thread -_Noreturn int32_t wifi_main(TT_UNUSED void* parameter) { - TT_LOG_I(TAG, "Started main loop"); - tt_assert(wifi_singleton != nullptr); - auto wifi = wifi_singleton; - MessageQueue& queue = wifi->queue; +static void onStart(ServiceContext& service) { + tt_assert(wifi_singleton == nullptr); + wifi_singleton = std::make_shared(); + + service.setData(wifi_singleton); + + wifi_singleton->autoConnectTimer = std::make_unique(Timer::TypePeriodic, onAutoConnectTimer, wifi_singleton); + // We want to try and scan more often in case of startup or scan lock failure + wifi_singleton->autoConnectTimer->start(TT_MIN(2000, AUTO_SCAN_INTERVAL)); if (settings::shouldEnableOnBoot()) { TT_LOG_I(TAG, "Auto-enabling due to setting"); - enable(wifi); - scan_internal(wifi); - } - - WifiMessage message; - while (true) { - if (queue.get(&message, 10000 / portTICK_PERIOD_MS) == TtStatusOk) { - TT_LOG_I(TAG, "Processing message of type %d", message.type); - switch (message.type) { - case WifiMessageTypeRadioOn: - lock(wifi); - enable(wifi); - unlock(wifi); - break; - case WifiMessageTypeRadioOff: - lock(wifi); - disable(wifi); - unlock(wifi); - break; - case WifiMessageTypeScan: - lock(wifi); - scan_internal(wifi); - unlock(wifi); - break; - case WifiMessageTypeConnect: - lock(wifi); - connect_internal(wifi); - unlock(wifi); - break; - case WifiMessageTypeDisconnect: - lock(wifi); - disconnect_internal_but_keep_active(wifi); - unlock(wifi); - break; - case WifiMessageTypeAutoConnect: - lock(wifi); - if (!wifi->pause_auto_connect) { - auto_connect(wifi_singleton); - } - unlock(wifi); - break; - } - } - - // Automatic scanning is done so we can automatically connect to access points - lock(wifi); - bool should_auto_scan = shouldScanForAutoConnect(wifi); - unlock(wifi); - if (should_auto_scan) { - scan_internal(wifi); - } + getMainDispatcher().dispatch(dispatchEnable, wifi_singleton); } } -static void service_start(ServiceContext& service) { - tt_assert(wifi_singleton == nullptr); - wifi_singleton = std::make_shared(); - service.setData(wifi_singleton); -} +static void onStop(ServiceContext& service) { + auto wifi = wifi_singleton; + tt_assert(wifi != nullptr); -static void service_stop(ServiceContext& service) { - tt_assert(wifi_singleton != nullptr); - - WifiRadioState state = wifi_singleton->radio_state; + WifiRadioState state = wifi->getRadioState(); if (state != WIFI_RADIO_OFF) { - disable(wifi_singleton); + dispatchDisable(wifi); } + wifi->autoConnectTimer->stop(); + wifi->autoConnectTimer = nullptr; // Must release as it holds a reference to this Wifi instance + + // Acquire all mutexes + wifi->dataMutex.acquire(TtWaitForever); + wifi->radioMutex.acquire(TtWaitForever); + + // Detach wifi_singleton = nullptr; - // wifi_main() cannot be stopped yet as it runs in the main task. - // We could theoretically exit it, but then we wouldn't be able to restart the service. - tt_crash("not fully implemented"); + // Release mutexes + wifi->dataMutex.release(); + wifi->radioMutex.release(); + + // Release (hopefully) last Wifi instance by scope } extern const ServiceManifest manifest = { .id = "Wifi", - .onStart = &service_start, - .onStop = &service_stop + .onStart = onStart, + .onStop = onStop }; } // namespace -#endif // ESP_TARGET \ No newline at end of file +#endif // ESP_TARGET diff --git a/TactilityHeadless/Source/service/wifi/WifiMock.cpp b/TactilityHeadless/Source/service/wifi/WifiMock.cpp index 96d5f0d2..a98e457e 100644 --- a/TactilityHeadless/Source/service/wifi/WifiMock.cpp +++ b/TactilityHeadless/Source/service/wifi/WifiMock.cpp @@ -21,7 +21,7 @@ typedef struct { /** @brief Locking mechanism for modifying the Wifi instance */ Mutex* mutex; /** @brief The public event bus */ - PubSub* pubsub; + std::shared_ptr pubsub; /** @brief The internal message queue */ MessageQueue queue; bool scan_active; @@ -50,7 +50,7 @@ static void publish_event_simple(Wifi* wifi, WifiEventType type) { static Wifi* wifi_alloc() { auto* instance = static_cast(malloc(sizeof(Wifi))); instance->mutex = tt_mutex_alloc(MutexTypeRecursive); - instance->pubsub = tt_pubsub_alloc(); + instance->pubsub = std::make_shared(); instance->scan_active = false; instance->radio_state = WIFI_RADIO_CONNECTION_ACTIVE; instance->secure_connection = false; @@ -59,7 +59,6 @@ static Wifi* wifi_alloc() { static void wifi_free(Wifi* instance) { tt_mutex_free(instance->mutex); - tt_pubsub_free(instance->pubsub); free(instance); } @@ -67,7 +66,7 @@ static void wifi_free(Wifi* instance) { // region Public functions -PubSub* getPubsub() { +std::shared_ptr getPubsub() { tt_assert(wifi); return wifi->pubsub; } diff --git a/Tests/TactilityCore/DispatcherTest.cpp b/Tests/TactilityCore/DispatcherTest.cpp index c6c9e9b6..f6045431 100644 --- a/Tests/TactilityCore/DispatcherTest.cpp +++ b/Tests/TactilityCore/DispatcherTest.cpp @@ -4,16 +4,26 @@ using namespace tt; -void increment_callback(void* context) { - auto* counter = (uint32_t*)context; - (*counter)++; +static uint32_t counter = 0; +static const uint32_t value_chacker_expected = 123; + +void increment_callback(TT_UNUSED std::shared_ptr context) { + counter++; +} + +void value_checker(std::shared_ptr context) { + auto value = std::static_pointer_cast(context); + if (*value != value_chacker_expected) { + tt_crash_implementation(); + } } TEST_CASE("dispatcher should not call callback if consume isn't called") { + counter = 0; Dispatcher dispatcher; - uint32_t counter = 0; - dispatcher.dispatch(&increment_callback, &counter); + auto context = std::make_shared(); + dispatcher.dispatch(&increment_callback, std::move(context)); delay_ticks(10); CHECK_EQ(counter, 0); @@ -21,16 +31,25 @@ TEST_CASE("dispatcher should not call callback if consume isn't called") { TEST_CASE("dispatcher should be able to dealloc when message is not consumed") { auto* dispatcher = new Dispatcher(); - uint32_t counter = 0; - dispatcher->dispatch(increment_callback, &counter); + auto context = std::make_shared(); + dispatcher->dispatch(increment_callback, std::move(context)); delete dispatcher; } TEST_CASE("dispatcher should call callback when consume is called") { + counter = 0; Dispatcher dispatcher; - uint32_t counter = 0; - dispatcher.dispatch(increment_callback, &counter); + auto context = std::make_shared(); + dispatcher.dispatch(increment_callback, std::move(context)); dispatcher.consume(100); CHECK_EQ(counter, 1); } + +TEST_CASE("message should be passed on correctly") { + Dispatcher dispatcher; + + auto context = std::make_shared(value_chacker_expected); + dispatcher.dispatch(value_checker, std::move(context)); + dispatcher.consume(100); +} diff --git a/Tests/TactilityCore/TimerTest.cpp b/Tests/TactilityCore/TimerTest.cpp index 602d4cc5..8c8d1b84 100644 --- a/Tests/TactilityCore/TimerTest.cpp +++ b/Tests/TactilityCore/TimerTest.cpp @@ -2,32 +2,34 @@ #include "TactilityCore.h" #include "Timer.h" +#include + using namespace tt; -void* timer_callback_context = NULL; -static void timer_callback_with_context(void* context) { - timer_callback_context = context; +std::shared_ptr timer_callback_context = NULL; +static void timer_callback_with_context(std::shared_ptr context) { + timer_callback_context = std::move(context); } -static void timer_callback_with_counter(void* context) { - int* int_ptr = (int*)context; +static void timer_callback_with_counter(std::shared_ptr context) { + auto int_ptr = std::static_pointer_cast(context); (*int_ptr)++; } TEST_CASE("a timer passes the context correctly") { - int foo = 1; - auto* timer = new Timer(Timer::TypeOnce, &timer_callback_with_context, &foo); + auto foo = std::make_shared(1); + auto* timer = new Timer(Timer::TypeOnce, &timer_callback_with_context, foo); timer->start(1); delay_ticks(10); timer->stop(); delete timer; - CHECK_EQ(timer_callback_context, &foo); + CHECK_EQ(*std::static_pointer_cast(timer_callback_context), *foo); } TEST_CASE("TimerTypePeriodic timers can be stopped and restarted") { - int counter = 0; - auto* timer = new Timer(Timer::TypePeriodic, &timer_callback_with_counter, &counter); + auto counter = std::make_shared(0); + auto* timer = new Timer(Timer::TypePeriodic, &timer_callback_with_counter, counter); timer->start(1); delay_ticks(10); timer->stop(); @@ -36,24 +38,24 @@ TEST_CASE("TimerTypePeriodic timers can be stopped and restarted") { timer->stop(); delete timer; - CHECK_GE(counter, 2); + CHECK_GE(*counter, 2); } TEST_CASE("TimerTypePeriodic calls the callback periodically") { - int counter = 0; + auto counter = std::make_shared(0); int ticks_to_run = 10; - auto* timer = new Timer(Timer::TypePeriodic, &timer_callback_with_counter, &counter); + auto* timer = new Timer(Timer::TypePeriodic, &timer_callback_with_counter, counter); timer->start(1); delay_ticks(ticks_to_run); timer->stop(); delete timer; - CHECK_EQ(counter, ticks_to_run); + CHECK_EQ(*counter, ticks_to_run); } TEST_CASE("restarting TimerTypeOnce timers calls the callback again") { - int counter = 0; - auto* timer = new Timer(Timer::TypeOnce, &timer_callback_with_counter, &counter); + auto counter = std::make_shared(0); + auto* timer = new Timer(Timer::TypeOnce, &timer_callback_with_counter, counter); timer->start(1); delay_ticks(10); timer->stop(); @@ -62,5 +64,5 @@ TEST_CASE("restarting TimerTypeOnce timers calls the callback again") { timer->stop(); delete timer; - CHECK_EQ(counter, 2); + CHECK_EQ(*counter, 2); }