mirror of
https://github.com/ByteWelder/Tactility.git
synced 2026-08-17 23:55:04 +00:00
Replace Tactility SystemEvents with new kernel implementation (#598)
TactilityKernel now has a system event API to replace the one from the Tactility subproject. It also implements several tests for it.
This commit is contained in:
parent
6e55e71e67
commit
acb2f1d4c7
@ -4,7 +4,8 @@
|
|||||||
#include <lvgl/lvgl.h>
|
#include <lvgl/lvgl.h>
|
||||||
#include <tactility/module.h>
|
#include <tactility/module.h>
|
||||||
|
|
||||||
#include <Tactility/SystemEvents.h>
|
#include <tactility/system_event.h>
|
||||||
|
|
||||||
#include <Tactility/LogMessages.h>
|
#include <Tactility/LogMessages.h>
|
||||||
#include <Tactility/settings/TrackballSettings.h>
|
#include <Tactility/settings/TrackballSettings.h>
|
||||||
|
|
||||||
@ -15,8 +16,6 @@ constexpr auto* TAG = "tdeck-plus";
|
|||||||
|
|
||||||
extern "C" {
|
extern "C" {
|
||||||
|
|
||||||
static tt::kernel::SystemEventSubscription tdeck_boot_splash_subscription = tt::kernel::NoSystemEventSubscription;
|
|
||||||
|
|
||||||
void init_trackball() {
|
void init_trackball() {
|
||||||
auto tbSettings = tt::settings::trackball::loadOrGetDefault();
|
auto tbSettings = tt::settings::trackball::loadOrGetDefault();
|
||||||
lvgl_lock();
|
lvgl_lock();
|
||||||
@ -31,6 +30,10 @@ void init_trackball() {
|
|||||||
lvgl_unlock();
|
lvgl_unlock();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void on_boot_completed(struct SystemEvent* /*event*/, void* /*context*/) {
|
||||||
|
init_trackball();
|
||||||
|
}
|
||||||
|
|
||||||
static error_t start() {
|
static error_t start() {
|
||||||
LOG_I(TAG, LOG_MESSAGE_POWER_ON_START);
|
LOG_I(TAG, LOG_MESSAGE_POWER_ON_START);
|
||||||
|
|
||||||
@ -42,16 +45,13 @@ static error_t start() {
|
|||||||
// Avoids crash when no SD card is inserted. It's unknown why, but likely is related to power draw.
|
// Avoids crash when no SD card is inserted. It's unknown why, but likely is related to power draw.
|
||||||
delay_millis(100);
|
delay_millis(100);
|
||||||
|
|
||||||
tdeck_boot_splash_subscription = tt::kernel::subscribeSystemEvent(tt::kernel::SystemEvent::BootSplash, [](tt::kernel::SystemEvent event) {
|
system_event_subscribe(KERNEL_EVENT_BOOT_COMPLETED, on_boot_completed, nullptr);
|
||||||
init_trackball();
|
|
||||||
});
|
|
||||||
|
|
||||||
return ERROR_NONE;
|
return ERROR_NONE;
|
||||||
}
|
}
|
||||||
|
|
||||||
static error_t stop() {
|
static error_t stop() {
|
||||||
tt::kernel::unsubscribeSystemEvent(tdeck_boot_splash_subscription);
|
system_event_unsubscribe(KERNEL_EVENT_BOOT_COMPLETED, on_boot_completed);
|
||||||
tdeck_boot_splash_subscription = tt::kernel::NoSystemEventSubscription;
|
|
||||||
return ERROR_NONE;
|
return ERROR_NONE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -5,7 +5,8 @@
|
|||||||
|
|
||||||
#include <lvgl/lvgl.h>
|
#include <lvgl/lvgl.h>
|
||||||
|
|
||||||
#include <Tactility/SystemEvents.h>
|
#include <tactility/system_event.h>
|
||||||
|
|
||||||
#include <Tactility/LogMessages.h>
|
#include <Tactility/LogMessages.h>
|
||||||
#include <Tactility/kernel/Kernel.h>
|
#include <Tactility/kernel/Kernel.h>
|
||||||
#include <Tactility/settings/TrackballSettings.h>
|
#include <Tactility/settings/TrackballSettings.h>
|
||||||
@ -17,22 +18,24 @@ constexpr auto* TAG = "tdeck";
|
|||||||
|
|
||||||
extern "C" {
|
extern "C" {
|
||||||
|
|
||||||
|
static void on_boot_completed(struct SystemEvent* /*event*/, void* /*context*/) {
|
||||||
|
auto tbSettings = tt::settings::trackball::loadOrGetDefault();
|
||||||
|
lvgl_lock();
|
||||||
|
if (trackball::init() != nullptr) {
|
||||||
|
trackball::setMode(tbSettings.trackballMode == tt::settings::trackball::TrackballMode::Pointer
|
||||||
|
? trackball::Mode::Pointer
|
||||||
|
: trackball::Mode::Encoder);
|
||||||
|
trackball::setEncoderSensitivity(tbSettings.encoderSensitivity);
|
||||||
|
trackball::setPointerSensitivity(tbSettings.pointerSensitivity);
|
||||||
|
trackball::setEnabled(tbSettings.trackballEnabled);
|
||||||
|
}
|
||||||
|
lvgl_unlock();
|
||||||
|
}
|
||||||
|
|
||||||
void subscribe_events() {
|
void subscribe_events() {
|
||||||
// The kernel trackball device is already started by kernel_init(); this just registers it as an
|
// The kernel trackball device is already started by kernel_init(); this just registers it as an
|
||||||
// LVGL input device and applies persisted settings, both of which require LVGL to be up first.
|
// LVGL input device and applies persisted settings, both of which require LVGL to be up first.
|
||||||
tt::kernel::subscribeSystemEvent(tt::kernel::SystemEvent::BootSplash, [](tt::kernel::SystemEvent event) {
|
system_event_subscribe(KERNEL_EVENT_BOOT_COMPLETED, on_boot_completed, nullptr);
|
||||||
auto tbSettings = tt::settings::trackball::loadOrGetDefault();
|
|
||||||
lvgl_lock();
|
|
||||||
if (trackball::init() != nullptr) {
|
|
||||||
trackball::setMode(tbSettings.trackballMode == tt::settings::trackball::TrackballMode::Pointer
|
|
||||||
? trackball::Mode::Pointer
|
|
||||||
: trackball::Mode::Encoder);
|
|
||||||
trackball::setEncoderSensitivity(tbSettings.encoderSensitivity);
|
|
||||||
trackball::setPointerSensitivity(tbSettings.pointerSensitivity);
|
|
||||||
trackball::setEnabled(tbSettings.trackballEnabled);
|
|
||||||
}
|
|
||||||
lvgl_unlock();
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static error_t start() {
|
static error_t start() {
|
||||||
|
|||||||
@ -1,7 +1,8 @@
|
|||||||
#include <lvgl/lvgl.h>
|
#include <lvgl/lvgl.h>
|
||||||
#include <tactility/module.h>
|
#include <tactility/module.h>
|
||||||
|
|
||||||
#include <Tactility/SystemEvents.h>
|
#include <tactility/system_event.h>
|
||||||
|
|
||||||
#include <Tactility/kernel/Kernel.h>
|
#include <Tactility/kernel/Kernel.h>
|
||||||
|
|
||||||
#include <lilygo/drivers/tpager_encoder_input.h>
|
#include <lilygo/drivers/tpager_encoder_input.h>
|
||||||
@ -10,22 +11,21 @@ constexpr auto* TAG = "T-Lora Pager";
|
|||||||
|
|
||||||
extern "C" {
|
extern "C" {
|
||||||
|
|
||||||
tt::kernel::SystemEventSubscription event_subscription = tt::kernel::NoSystemEventSubscription;
|
static void on_boot_completed(struct SystemEvent* /*event*/, void* /*context*/) {
|
||||||
|
// The kernel tpager_encoder device is already started by kernel_init(); this just
|
||||||
|
// registers it as an LVGL input device, which requires LVGL to be up first.
|
||||||
|
lvgl_lock();
|
||||||
|
tpager_encoder::init();
|
||||||
|
lvgl_unlock();
|
||||||
|
}
|
||||||
|
|
||||||
static error_t start() {
|
static error_t start() {
|
||||||
event_subscription = tt::kernel::subscribeSystemEvent(tt::kernel::SystemEvent::BootSplash, [](tt::kernel::SystemEvent) {
|
system_event_subscribe(KERNEL_EVENT_BOOT_COMPLETED, on_boot_completed, nullptr);
|
||||||
// The kernel tpager_encoder device is already started by kernel_init(); this just
|
|
||||||
// registers it as an LVGL input device, which requires LVGL to be up first.
|
|
||||||
lvgl_lock();
|
|
||||||
tpager_encoder::init();
|
|
||||||
lvgl_unlock();
|
|
||||||
});
|
|
||||||
return ERROR_NONE;
|
return ERROR_NONE;
|
||||||
}
|
}
|
||||||
|
|
||||||
static error_t stop() {
|
static error_t stop() {
|
||||||
tt::kernel::unsubscribeSystemEvent(event_subscription);
|
system_event_unsubscribe(KERNEL_EVENT_BOOT_COMPLETED, on_boot_completed);
|
||||||
event_subscription = tt::kernel::NoSystemEventSubscription;
|
|
||||||
return ERROR_NONE;
|
return ERROR_NONE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -21,6 +21,9 @@
|
|||||||
#include <tactility/drivers/esp32_esp_hosted_ota.h>
|
#include <tactility/drivers/esp32_esp_hosted_ota.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#include "tactility/system_event.h"
|
||||||
|
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include <new>
|
#include <new>
|
||||||
@ -114,6 +117,10 @@ void on_wifi_or_ip_event(void* arg, esp_event_base_t event_base, int32_t event_i
|
|||||||
result_event.type = WIFI_EVENT_TYPE_STATION_CONNECTION_RESULT;
|
result_event.type = WIFI_EVENT_TYPE_STATION_CONNECTION_RESULT;
|
||||||
result_event.connection_error = WIFI_STATION_CONNECTION_ERROR_TARGET_NOT_FOUND;
|
result_event.connection_error = WIFI_STATION_CONNECTION_ERROR_TARGET_NOT_FOUND;
|
||||||
fire_event(ctx, result_event);
|
fire_event(ctx, result_event);
|
||||||
|
NetworkDisconnectedEvent disconnected_event = {
|
||||||
|
.device = ctx->device
|
||||||
|
};
|
||||||
|
system_event_emit(KERNEL_EVENT_NETWORK_DISCONNECTED, &disconnected_event, sizeof(disconnected_event));
|
||||||
}
|
}
|
||||||
} else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP) {
|
} else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP) {
|
||||||
auto* got_ip = static_cast<ip_event_got_ip_t*>(event_data);
|
auto* got_ip = static_cast<ip_event_got_ip_t*>(event_data);
|
||||||
@ -132,6 +139,13 @@ void on_wifi_or_ip_event(void* arg, esp_event_base_t event_base, int32_t event_i
|
|||||||
result_event.type = WIFI_EVENT_TYPE_STATION_CONNECTION_RESULT;
|
result_event.type = WIFI_EVENT_TYPE_STATION_CONNECTION_RESULT;
|
||||||
result_event.connection_error = WIFI_STATION_CONNECTION_ERROR_NONE;
|
result_event.connection_error = WIFI_STATION_CONNECTION_ERROR_NONE;
|
||||||
fire_event(ctx, result_event);
|
fire_event(ctx, result_event);
|
||||||
|
|
||||||
|
NetworkConnectedEvent connected_event = {
|
||||||
|
.device = ctx->device,
|
||||||
|
.ipv4_addr = ctx->ipInfo.ip.addr,
|
||||||
|
.gateway = ctx->ipInfo.gw.addr,
|
||||||
|
};
|
||||||
|
system_event_emit(KERNEL_EVENT_NETWORK_CONNECTED, &connected_event, sizeof(connected_event));
|
||||||
} else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_SCAN_DONE) {
|
} else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_SCAN_DONE) {
|
||||||
mutex_lock(&ctx->mutex);
|
mutex_lock(&ctx->mutex);
|
||||||
ctx->scanning = false;
|
ctx->scanning = false;
|
||||||
|
|||||||
@ -1,28 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include <cstdint>
|
|
||||||
#include <functional>
|
|
||||||
|
|
||||||
namespace tt::kernel {
|
|
||||||
|
|
||||||
enum class SystemEvent {
|
|
||||||
BootSplash,
|
|
||||||
/** Gained IP address */
|
|
||||||
NetworkConnected,
|
|
||||||
NetworkDisconnected,
|
|
||||||
Time,
|
|
||||||
};
|
|
||||||
|
|
||||||
/** Value 0 mean "no subscription" */
|
|
||||||
typedef uint32_t SystemEventSubscription;
|
|
||||||
constexpr SystemEventSubscription NoSystemEventSubscription = 0U;
|
|
||||||
|
|
||||||
typedef std::function<void(SystemEvent)> OnSystemEvent;
|
|
||||||
|
|
||||||
void publishSystemEvent(SystemEvent event);
|
|
||||||
|
|
||||||
SystemEventSubscription subscribeSystemEvent(SystemEvent event, OnSystemEvent handler);
|
|
||||||
|
|
||||||
void unsubscribeSystemEvent(SystemEventSubscription subscription);
|
|
||||||
|
|
||||||
}
|
|
||||||
@ -1,21 +1,22 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <Tactility/SystemEvents.h>
|
|
||||||
#include <Tactility/service/Service.h>
|
#include <Tactility/service/Service.h>
|
||||||
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
|
||||||
struct Device;
|
struct Device;
|
||||||
|
struct SystemEvent;
|
||||||
|
|
||||||
namespace tt::service::rtctime {
|
namespace tt::service::rtctime {
|
||||||
|
|
||||||
class RtcTimeService final : public Service {
|
class RtcTimeService final : public Service {
|
||||||
|
|
||||||
kernel::SystemEventSubscription timeEventSubscription = 0;
|
bool timeEventSubscribed = false;
|
||||||
Device* rtcDevice = nullptr;
|
Device* rtcDevice = nullptr;
|
||||||
|
|
||||||
Device* findRtcDevice();
|
Device* findRtcDevice();
|
||||||
void onTimeChanged(kernel::SystemEvent event);
|
void onTimeChanged();
|
||||||
|
static void onTimeChangedTrampoline(struct SystemEvent* event, void* context);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
||||||
|
|||||||
@ -1,81 +0,0 @@
|
|||||||
#include <Tactility/CoreDefines.h>
|
|
||||||
#include <Tactility/Mutex.h>
|
|
||||||
#include <Tactility/SystemEvents.h>
|
|
||||||
|
|
||||||
#include <tactility/check.h>
|
|
||||||
#include <tactility/log.h>
|
|
||||||
#include <tactility/time.h>
|
|
||||||
|
|
||||||
#include <list>
|
|
||||||
|
|
||||||
namespace tt::kernel {
|
|
||||||
|
|
||||||
constexpr auto* TAG = "SystemEvents";
|
|
||||||
|
|
||||||
struct SubscriptionData {
|
|
||||||
SystemEventSubscription id;
|
|
||||||
SystemEvent event;
|
|
||||||
OnSystemEvent handler;
|
|
||||||
};
|
|
||||||
|
|
||||||
static Mutex mutex;
|
|
||||||
static SystemEventSubscription subscriptionCounter = 0;
|
|
||||||
static std::list<SubscriptionData> subscriptions;
|
|
||||||
|
|
||||||
static const char* getEventName(SystemEvent event) {
|
|
||||||
switch (event) {
|
|
||||||
using enum SystemEvent;
|
|
||||||
case BootSplash:
|
|
||||||
return TT_STRINGIFY(BootSplash);
|
|
||||||
case NetworkConnected:
|
|
||||||
return TT_STRINGIFY(NetworkConnected);
|
|
||||||
case NetworkDisconnected:
|
|
||||||
return TT_STRINGIFY(NetworkDisconnected);
|
|
||||||
case Time:
|
|
||||||
return TT_STRINGIFY(Time);
|
|
||||||
}
|
|
||||||
|
|
||||||
check(false); // Missing case above
|
|
||||||
}
|
|
||||||
|
|
||||||
void publishSystemEvent(SystemEvent event) {
|
|
||||||
LOG_I(TAG, "%s", getEventName(event));
|
|
||||||
|
|
||||||
if (mutex.lock(MAX_TICKS)) {
|
|
||||||
for (auto& subscription : subscriptions) {
|
|
||||||
if (subscription.event == event) {
|
|
||||||
subscription.handler(event);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
mutex.unlock();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
SystemEventSubscription subscribeSystemEvent(SystemEvent event, OnSystemEvent handler) {
|
|
||||||
if (mutex.lock(MAX_TICKS)) {
|
|
||||||
auto id = ++subscriptionCounter;
|
|
||||||
|
|
||||||
subscriptions.push_back({
|
|
||||||
.id = id,
|
|
||||||
.event = event,
|
|
||||||
.handler = handler
|
|
||||||
});
|
|
||||||
|
|
||||||
mutex.unlock();
|
|
||||||
return id;
|
|
||||||
} else {
|
|
||||||
check(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void unsubscribeSystemEvent(SystemEventSubscription subscription) {
|
|
||||||
if (mutex.lock(MAX_TICKS)) {
|
|
||||||
std::erase_if(subscriptions, [subscription](auto& item) {
|
|
||||||
return (item.id == subscription);
|
|
||||||
});
|
|
||||||
mutex.unlock();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@ -1,3 +1,6 @@
|
|||||||
|
#include "tactility/system_event.h"
|
||||||
|
|
||||||
|
|
||||||
#include <tactility/delay.h>
|
#include <tactility/delay.h>
|
||||||
#include <tactility/drivers/backlight.h>
|
#include <tactility/drivers/backlight.h>
|
||||||
#include <tactility/drivers/display.h>
|
#include <tactility/drivers/display.h>
|
||||||
@ -6,7 +9,6 @@
|
|||||||
|
|
||||||
#include <Tactility/CpuAffinity.h>
|
#include <Tactility/CpuAffinity.h>
|
||||||
#include <Tactility/Paths.h>
|
#include <Tactility/Paths.h>
|
||||||
#include <Tactility/SystemEvents.h>
|
|
||||||
#include <Tactility/TactilityPrivate.h>
|
#include <Tactility/TactilityPrivate.h>
|
||||||
#include <Tactility/app/AppContext.h>
|
#include <Tactility/app/AppContext.h>
|
||||||
#include <Tactility/app/AppPaths.h>
|
#include <Tactility/app/AppPaths.h>
|
||||||
@ -154,7 +156,7 @@ class BootApp : public App {
|
|||||||
// This event will likely block as other systems are initialized
|
// This event will likely block as other systems are initialized
|
||||||
// e.g. Wi-Fi reads AP configs from SD card
|
// e.g. Wi-Fi reads AP configs from SD card
|
||||||
LOG_I(TAG, "Publish event");
|
LOG_I(TAG, "Publish event");
|
||||||
kernel::publishSystemEvent(kernel::SystemEvent::BootSplash);
|
system_event_emit(KERNEL_EVENT_BOOT_COMPLETED, nullptr, 0);
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,5 @@
|
|||||||
#define LV_USE_PRIVATE_API 1 // For actual lv_obj_t declaration
|
#define LV_USE_PRIVATE_API 1 // For actual lv_obj_t declaration
|
||||||
|
|
||||||
#include <Tactility/SystemEvents.h>
|
|
||||||
#include <Tactility/PubSub.h>
|
#include <Tactility/PubSub.h>
|
||||||
#include <Tactility/RecursiveMutex.h>
|
#include <Tactility/RecursiveMutex.h>
|
||||||
#include <Tactility/Tactility.h>
|
#include <Tactility/Tactility.h>
|
||||||
@ -11,6 +10,7 @@
|
|||||||
|
|
||||||
#include <tactility/check.h>
|
#include <tactility/check.h>
|
||||||
#include <tactility/log.h>
|
#include <tactility/log.h>
|
||||||
|
#include <tactility/system_event.h>
|
||||||
#include <tactility/time.h>
|
#include <tactility/time.h>
|
||||||
|
|
||||||
#include <lvgl/fonts.h>
|
#include <lvgl/fonts.h>
|
||||||
@ -38,7 +38,6 @@ struct StatusbarData {
|
|||||||
uint8_t time_hours = 0;
|
uint8_t time_hours = 0;
|
||||||
uint8_t time_minutes = 0;
|
uint8_t time_minutes = 0;
|
||||||
bool time_set = false;
|
bool time_set = false;
|
||||||
kernel::SystemEventSubscription systemEventSubscription = 0;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
static StatusbarData statusbar_data;
|
static StatusbarData statusbar_data;
|
||||||
@ -115,7 +114,7 @@ static void statusbar_pubsub_event(Statusbar* statusbar) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void onTimeChanged(kernel::SystemEvent event) {
|
static void onTimeChanged(struct SystemEvent* /*event*/, void* /*context*/) {
|
||||||
if (statusbar_data.mutex.lock()) {
|
if (statusbar_data.mutex.lock()) {
|
||||||
statusbar_data.time_update_timer->reset(5);
|
statusbar_data.time_update_timer->reset(5);
|
||||||
statusbar_data.mutex.unlock();
|
statusbar_data.mutex.unlock();
|
||||||
@ -134,10 +133,7 @@ static void statusbar_constructor(const lv_obj_class_t* class_p, lv_obj_t* obj)
|
|||||||
|
|
||||||
if (!statusbar_data.time_update_timer->isRunning()) {
|
if (!statusbar_data.time_update_timer->isRunning()) {
|
||||||
statusbar_data.time_update_timer->start();
|
statusbar_data.time_update_timer->start();
|
||||||
statusbar_data.systemEventSubscription = kernel::subscribeSystemEvent(
|
system_event_subscribe(KERNEL_EVENT_TIME_CHANGED, onTimeChanged, nullptr);
|
||||||
kernel::SystemEvent::Time,
|
|
||||||
onTimeChanged
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -6,8 +6,8 @@
|
|||||||
#include <memory>
|
#include <memory>
|
||||||
|
|
||||||
#ifdef ESP_PLATFORM
|
#ifdef ESP_PLATFORM
|
||||||
#include <Tactility/SystemEvents.h>
|
|
||||||
#include <Tactility/TactilityCore.h>
|
#include <Tactility/TactilityCore.h>
|
||||||
|
#include <tactility/system_event.h>
|
||||||
#include <esp_netif_sntp.h>
|
#include <esp_netif_sntp.h>
|
||||||
#include <esp_sntp.h>
|
#include <esp_sntp.h>
|
||||||
#endif
|
#endif
|
||||||
@ -45,7 +45,7 @@ static void onTimeSynced(timeval* tv) {
|
|||||||
processedSyncEvent = true;
|
processedSyncEvent = true;
|
||||||
esp_netif_sntp_deinit();
|
esp_netif_sntp_deinit();
|
||||||
storeTimeInNvs();
|
storeTimeInNvs();
|
||||||
kernel::publishSystemEvent(kernel::SystemEvent::Time);
|
system_event_emit(KERNEL_EVENT_TIME_CHANGED, nullptr, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
void init() {
|
void init() {
|
||||||
|
|||||||
@ -8,6 +8,7 @@
|
|||||||
#include <tactility/device.h>
|
#include <tactility/device.h>
|
||||||
#include <tactility/drivers/rtc.h>
|
#include <tactility/drivers/rtc.h>
|
||||||
#include <tactility/log.h>
|
#include <tactility/log.h>
|
||||||
|
#include <tactility/system_event.h>
|
||||||
|
|
||||||
#include <cassert>
|
#include <cassert>
|
||||||
#include <ctime>
|
#include <ctime>
|
||||||
@ -96,15 +97,17 @@ static void writeRtcFromSystemTime(Device* rtc) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void RtcTimeService::onTimeChanged(kernel::SystemEvent event) {
|
void RtcTimeService::onTimeChanged() {
|
||||||
if (event == kernel::SystemEvent::Time) {
|
Device* rtc = findRtcDevice();
|
||||||
Device* rtc = findRtcDevice();
|
if (rtc) {
|
||||||
if (rtc) {
|
writeRtcFromSystemTime(rtc);
|
||||||
writeRtcFromSystemTime(rtc);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void RtcTimeService::onTimeChangedTrampoline(struct SystemEvent* /*event*/, void* context) {
|
||||||
|
static_cast<RtcTimeService*>(context)->onTimeChanged();
|
||||||
|
}
|
||||||
|
|
||||||
bool RtcTimeService::onStart(ServiceContext& serviceContext) {
|
bool RtcTimeService::onStart(ServiceContext& serviceContext) {
|
||||||
Device* rtc = findRtcDevice();
|
Device* rtc = findRtcDevice();
|
||||||
if (!rtc) {
|
if (!rtc) {
|
||||||
@ -113,22 +116,21 @@ bool RtcTimeService::onStart(ServiceContext& serviceContext) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (setSystemTimeFromRtc(rtc)) {
|
if (setSystemTimeFromRtc(rtc)) {
|
||||||
// Publish time event so other components know time is now valid
|
// Emit time event so other components know time is now valid
|
||||||
kernel::publishSystemEvent(kernel::SystemEvent::Time);
|
system_event_emit(KERNEL_EVENT_TIME_CHANGED, nullptr, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
timeEventSubscription = kernel::subscribeSystemEvent(
|
if (system_event_subscribe(KERNEL_EVENT_TIME_CHANGED, &RtcTimeService::onTimeChangedTrampoline, this) == ERROR_NONE) {
|
||||||
kernel::SystemEvent::Time,
|
timeEventSubscribed = true;
|
||||||
[this](kernel::SystemEvent event) { onTimeChanged(event); }
|
}
|
||||||
);
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void RtcTimeService::onStop(ServiceContext& serviceContext) {
|
void RtcTimeService::onStop(ServiceContext& serviceContext) {
|
||||||
if (timeEventSubscription != 0) {
|
if (timeEventSubscribed) {
|
||||||
kernel::unsubscribeSystemEvent(timeEventSubscription);
|
system_event_unsubscribe(KERNEL_EVENT_TIME_CHANGED, &RtcTimeService::onTimeChangedTrampoline);
|
||||||
timeEventSubscription = 0;
|
timeEventSubscribed = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (rtcDevice) {
|
if (rtcDevice) {
|
||||||
|
|||||||
@ -3,7 +3,6 @@
|
|||||||
#include <Tactility/CoreDefines.h>
|
#include <Tactility/CoreDefines.h>
|
||||||
#include <Tactility/LogMessages.h>
|
#include <Tactility/LogMessages.h>
|
||||||
#include <Tactility/RecursiveMutex.h>
|
#include <Tactility/RecursiveMutex.h>
|
||||||
#include <Tactility/SystemEvents.h>
|
|
||||||
#include <Tactility/Tactility.h>
|
#include <Tactility/Tactility.h>
|
||||||
#include <Tactility/Timer.h>
|
#include <Tactility/Timer.h>
|
||||||
#include <Tactility/service/Service.h>
|
#include <Tactility/service/Service.h>
|
||||||
@ -16,6 +15,7 @@
|
|||||||
#include <tactility/device.h>
|
#include <tactility/device.h>
|
||||||
#include <tactility/drivers/wifi.h>
|
#include <tactility/drivers/wifi.h>
|
||||||
#include <tactility/log.h>
|
#include <tactility/log.h>
|
||||||
|
#include <tactility/system_event.h>
|
||||||
#include <tactility/time.h>
|
#include <tactility/time.h>
|
||||||
#include <tactility/wifi_auto_scan.h>
|
#include <tactility/wifi_auto_scan.h>
|
||||||
|
|
||||||
@ -78,7 +78,7 @@ struct WifiServiceState {
|
|||||||
uint16_t scanRecordLimit = TT_WIFI_SCAN_RECORD_LIMIT;
|
uint16_t scanRecordLimit = TT_WIFI_SCAN_RECORD_LIMIT;
|
||||||
TickType_t lastScanTime = MAX_TICKS;
|
TickType_t lastScanTime = MAX_TICKS;
|
||||||
std::unique_ptr<Timer> autoConnectTimer;
|
std::unique_ptr<Timer> autoConnectTimer;
|
||||||
kernel::SystemEventSubscription bootEventSubscription = kernel::NoSystemEventSubscription;
|
bool bootEventSubscribed = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
WifiServiceState state;
|
WifiServiceState state;
|
||||||
@ -278,7 +278,7 @@ void onAutoConnectTimer() {
|
|||||||
|
|
||||||
// ---- Kernel driver event bridge ----
|
// ---- Kernel driver event bridge ----
|
||||||
|
|
||||||
void onWifiDeviceEvent(Device* /*device*/, void* /*context*/, ::WifiEvent event) {
|
void onWifiDeviceEvent(Device* device, void* /*context*/, ::WifiEvent event) {
|
||||||
switch (event.type) {
|
switch (event.type) {
|
||||||
case WIFI_EVENT_TYPE_SCAN_FINISHED:
|
case WIFI_EVENT_TYPE_SCAN_FINISHED:
|
||||||
getMainDispatcher().dispatch([] { dispatchAutoConnect(); });
|
getMainDispatcher().dispatch([] { dispatchAutoConnect(); });
|
||||||
@ -291,7 +291,8 @@ void onWifiDeviceEvent(Device* /*device*/, void* /*context*/, ::WifiEvent event)
|
|||||||
// Resetting it on every disconnect (including deliberate ones) would
|
// Resetting it on every disconnect (including deliberate ones) would
|
||||||
// let auto-connect immediately reconnect the user. Attempts that fail
|
// let auto-connect immediately reconnect the user. Attempts that fail
|
||||||
// while pending are unpaused via WIFI_EVENT_TYPE_STATION_CONNECTION_RESULT below.
|
// while pending are unpaused via WIFI_EVENT_TYPE_STATION_CONNECTION_RESULT below.
|
||||||
kernel::publishSystemEvent(kernel::SystemEvent::NetworkDisconnected);
|
NetworkDisconnectedEvent disconnected_event = { .device = device };
|
||||||
|
system_event_emit(KERNEL_EVENT_NETWORK_DISCONNECTED, &disconnected_event, sizeof(disconnected_event));
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@ -319,7 +320,6 @@ void onWifiDeviceEvent(Device* /*device*/, void* /*context*/, ::WifiEvent event)
|
|||||||
if (remember && !settings::save(target)) {
|
if (remember && !settings::save(target)) {
|
||||||
LOG_E(TAG, "Failed to store credentials");
|
LOG_E(TAG, "Failed to store credentials");
|
||||||
}
|
}
|
||||||
kernel::publishSystemEvent(kernel::SystemEvent::NetworkConnected);
|
|
||||||
} else {
|
} else {
|
||||||
// The pending connection attempt (which paused auto-connect via connect())
|
// The pending connection attempt (which paused auto-connect via connect())
|
||||||
// failed; unpause so auto-connect can try other saved APs.
|
// failed; unpause so auto-connect can try other saved APs.
|
||||||
@ -492,6 +492,10 @@ std::string getIp() {
|
|||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
|
void onBootCompleted(struct SystemEvent* /*event*/, void* /*context*/) {
|
||||||
|
bootSplashInit();
|
||||||
|
}
|
||||||
|
|
||||||
class WifiService final : public Service {
|
class WifiService final : public Service {
|
||||||
|
|
||||||
public:
|
public:
|
||||||
@ -506,9 +510,9 @@ public:
|
|||||||
LOG_W(TAG, "No WiFi device found");
|
LOG_W(TAG, "No WiFi device found");
|
||||||
}
|
}
|
||||||
|
|
||||||
state.bootEventSubscription = kernel::subscribeSystemEvent(kernel::SystemEvent::BootSplash, [](auto) {
|
if (system_event_subscribe(KERNEL_EVENT_BOOT_COMPLETED, onBootCompleted, nullptr) == ERROR_NONE) {
|
||||||
bootSplashInit();
|
state.bootEventSubscribed = true;
|
||||||
});
|
}
|
||||||
|
|
||||||
auto timer_interval = std::min(2000, AUTO_SCAN_INTERVAL);
|
auto timer_interval = std::min(2000, AUTO_SCAN_INTERVAL);
|
||||||
state.autoConnectTimer = std::make_unique<Timer>(Timer::Type::Periodic, timer_interval, [] { onAutoConnectTimer(); });
|
state.autoConnectTimer = std::make_unique<Timer>(Timer::Type::Periodic, timer_interval, [] { onAutoConnectTimer(); });
|
||||||
@ -526,8 +530,10 @@ public:
|
|||||||
state.autoConnectTimer->stop();
|
state.autoConnectTimer->stop();
|
||||||
state.autoConnectTimer = nullptr; // Must release as it holds a reference via its callback.
|
state.autoConnectTimer = nullptr; // Must release as it holds a reference via its callback.
|
||||||
|
|
||||||
kernel::unsubscribeSystemEvent(state.bootEventSubscription);
|
if (state.bootEventSubscribed) {
|
||||||
state.bootEventSubscription = kernel::NoSystemEventSubscription;
|
system_event_unsubscribe(KERNEL_EVENT_BOOT_COMPLETED, onBootCompleted);
|
||||||
|
state.bootEventSubscribed = false;
|
||||||
|
}
|
||||||
|
|
||||||
if (state.device != nullptr && device_is_ready(state.device)) {
|
if (state.device != nullptr && device_is_ready(state.device)) {
|
||||||
wifi_remove_event_callback(state.device, onWifiDeviceEvent);
|
wifi_remove_event_callback(state.device, onWifiDeviceEvent);
|
||||||
|
|||||||
@ -1,9 +1,10 @@
|
|||||||
#include <Tactility/settings/Time.h>
|
#include <Tactility/settings/Time.h>
|
||||||
|
|
||||||
#include <Tactility/SystemEvents.h>
|
|
||||||
#include <Tactility/Preferences.h>
|
#include <Tactility/Preferences.h>
|
||||||
#include <Tactility/settings/SystemSettings.h>
|
#include <Tactility/settings/SystemSettings.h>
|
||||||
|
|
||||||
|
#include <tactility/system_event.h>
|
||||||
|
|
||||||
#ifdef ESP_PLATFORM
|
#ifdef ESP_PLATFORM
|
||||||
#include <ctime>
|
#include <ctime>
|
||||||
#endif
|
#endif
|
||||||
@ -36,7 +37,7 @@ void setTimeZone(const std::string& name, const std::string& code) {
|
|||||||
tzset();
|
tzset();
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
kernel::publishSystemEvent(kernel::SystemEvent::Time);
|
system_event_emit(KERNEL_EVENT_TIME_CHANGED, nullptr, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string getTimeZoneName() {
|
std::string getTimeZoneName() {
|
||||||
|
|||||||
130
TactilityKernel/include/tactility/system_event.h
Normal file
130
TactilityKernel/include/tactility/system_event.h
Normal file
@ -0,0 +1,130 @@
|
|||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#include <tactility/error.h>
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
struct Device;
|
||||||
|
struct FileSystem;
|
||||||
|
|
||||||
|
/** Identifies a system-wide event */
|
||||||
|
enum SystemEventType {
|
||||||
|
KERNEL_EVENT_BOOT_COMPLETED, // No data
|
||||||
|
KERNEL_EVENT_NETWORK_CONNECTED, // struct NetworkConnectedEvent
|
||||||
|
KERNEL_EVENT_NETWORK_DISCONNECTED, // struct NetworkDisconnectedEvent
|
||||||
|
KERNEL_EVENT_FILE_SYSTEM_MOUNTED, // struct FileSystemMountedEvent
|
||||||
|
KERNEL_EVENT_FILE_SYSTEM_UNMOUNTED, // struct FileSystemUnmountedEvent
|
||||||
|
KERNEL_EVENT_SERVICE_STARTED, // ServiceStartedEvent
|
||||||
|
KERNEL_EVENT_SERVICE_STOPPED, // ServiceStoppedEvent
|
||||||
|
KERNEL_EVENT_TIME_CHANGED, // No data - fired whenever system time is set (NTP sync, RTC restore, manual change)
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A system-wide event as delivered to a system_event_callback_t.
|
||||||
|
* `data` points at the type-specific struct documented next to `type`'s enum value
|
||||||
|
* in SystemEventType (or is NULL when none is documented).
|
||||||
|
* It is only valid for the duration of the callback.
|
||||||
|
*/
|
||||||
|
struct SystemEvent {
|
||||||
|
enum SystemEventType type;
|
||||||
|
/** Microseconds since boot, from get_micros_since_boot(). */
|
||||||
|
uint64_t timestamp;
|
||||||
|
const void *data;
|
||||||
|
size_t data_len;
|
||||||
|
};
|
||||||
|
|
||||||
|
/** Data for KERNEL_EVENT_NETWORK_CONNECTED. */
|
||||||
|
struct NetworkConnectedEvent {
|
||||||
|
struct Device* device;
|
||||||
|
uint32_t ipv4_addr;
|
||||||
|
uint32_t gateway;
|
||||||
|
};
|
||||||
|
|
||||||
|
/** Data for KERNEL_EVENT_NETWORK_DISCONNECTED. */
|
||||||
|
struct NetworkDisconnectedEvent {
|
||||||
|
struct Device* device;
|
||||||
|
};
|
||||||
|
|
||||||
|
/** Data for KERNEL_EVENT_FILE_SYSTEM_MOUNTED. */
|
||||||
|
struct FileSystemMountedEvent {
|
||||||
|
struct FileSystem* file_system;
|
||||||
|
};
|
||||||
|
|
||||||
|
/** Data for KERNEL_EVENT_FILE_SYSTEM_UNMOUNTED. */
|
||||||
|
struct FileSystemUnmountedEvent {
|
||||||
|
struct FileSystem* file_system;
|
||||||
|
};
|
||||||
|
|
||||||
|
/** Data for KERNEL_EVENT_SERVICE_STARTED. */
|
||||||
|
struct ServiceStartedEvent {
|
||||||
|
const char* id;
|
||||||
|
};
|
||||||
|
|
||||||
|
/** Data for KERNEL_EVENT_SERVICE_STOPPED. */
|
||||||
|
struct ServiceStoppedEvent {
|
||||||
|
const char* id;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param[in] event the event being delivered; only valid for the duration of the call
|
||||||
|
* @param[in] context the context pointer passed to system_event_subscribe()
|
||||||
|
*/
|
||||||
|
typedef void (*system_event_callback_t)(struct SystemEvent* event, void* context);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Subscribe to system events of a given type.
|
||||||
|
* @warning Does not work in ISR context.
|
||||||
|
* @warning @a callback is invoked synchronously, on the caller's task, from within
|
||||||
|
* system_event_emit(). The internal subscription lock is not held during the call, so
|
||||||
|
* @a callback may itself call system_event_subscribe(), system_event_unsubscribe() or
|
||||||
|
* system_event_emit() without deadlocking - but a subscribe/unsubscribe made from within
|
||||||
|
* a callback only takes effect for events emitted after the current system_event_emit()
|
||||||
|
* call returns, since that call already snapshotted the subscriptions it will invoke.
|
||||||
|
* @param[in] type the event type to subscribe to
|
||||||
|
* @param[in] callback the callback to invoke when a matching event is emitted
|
||||||
|
* @param[in] context an opaque pointer passed back to @a callback unmodified
|
||||||
|
* @return ERROR_NONE on success
|
||||||
|
*/
|
||||||
|
error_t system_event_subscribe(
|
||||||
|
enum SystemEventType type,
|
||||||
|
system_event_callback_t callback,
|
||||||
|
void *context
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove a previously added subscription.
|
||||||
|
* @warning Does not work in ISR context.
|
||||||
|
* @param[in] type the event type passed to the matching system_event_subscribe() call
|
||||||
|
* @param[in] callback the callback passed to the matching system_event_subscribe() call
|
||||||
|
* @return ERROR_NONE on success, ERROR_NOT_FOUND if no matching subscription exists
|
||||||
|
*/
|
||||||
|
error_t system_event_unsubscribe(
|
||||||
|
enum SystemEventType type,
|
||||||
|
system_event_callback_t callback
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Emit a system event, synchronously invoking every subscription registered for @a type
|
||||||
|
* (in subscription order) on the calling task before returning.
|
||||||
|
* @warning Does not work in ISR context.
|
||||||
|
* @param[in] type the event type
|
||||||
|
* @param[in] data optional pointer to the type-specific event struct (see SystemEventType);
|
||||||
|
* only valid for the duration of this call, subscribers must not retain it
|
||||||
|
* @param[in] data_len size of @a data in bytes (0 if @a data is NULL)
|
||||||
|
* @return ERROR_NONE on success
|
||||||
|
*/
|
||||||
|
error_t system_event_emit(
|
||||||
|
enum SystemEventType type,
|
||||||
|
const void* data,
|
||||||
|
size_t data_len
|
||||||
|
);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
@ -64,17 +64,17 @@ static inline TickType_t get_timeout_remaining_ticks(TickType_t timeout, TickTyp
|
|||||||
uint32_t kernel_get_tick_frequency();
|
uint32_t kernel_get_tick_frequency();
|
||||||
|
|
||||||
/** @return the microseconds that have passed since boot */
|
/** @return the microseconds that have passed since boot */
|
||||||
static inline int64_t get_micros_since_boot() {
|
static inline uint64_t get_micros_since_boot() {
|
||||||
#ifdef ESP_PLATFORM
|
#ifdef ESP_PLATFORM
|
||||||
return esp_timer_get_time();
|
return esp_timer_get_time();
|
||||||
#else
|
#else
|
||||||
struct timespec ts;
|
struct timespec ts;
|
||||||
if (clock_gettime(CLOCK_MONOTONIC, &ts) == 0) {
|
if (clock_gettime(CLOCK_MONOTONIC, &ts) == 0) {
|
||||||
return ((int64_t)ts.tv_sec * 1000000LL) + (ts.tv_nsec / 1000);
|
return ((uint64_t)ts.tv_sec * 1000000LL) + (ts.tv_nsec / 1000);
|
||||||
}
|
}
|
||||||
struct timeval tv;
|
struct timeval tv;
|
||||||
gettimeofday(&tv, NULL);
|
gettimeofday(&tv, NULL);
|
||||||
return ((int64_t)tv.tv_sec * 1000000LL) + tv.tv_usec;
|
return ((uint64_t)tv.tv_sec * 1000000LL) + tv.tv_usec;
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,10 +1,10 @@
|
|||||||
// SPDX-License-Identifier: Apache-2.0
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <tactility/device.h>
|
|
||||||
#include <tactility/concurrent/mutex.h>
|
|
||||||
#include <tactility/concurrent/recursive_mutex.h>
|
#include <tactility/concurrent/recursive_mutex.h>
|
||||||
|
#include <tactility/device.h>
|
||||||
#include <tactility/filesystem/file_system.h>
|
#include <tactility/filesystem/file_system.h>
|
||||||
|
#include <tactility/system_event.h>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
// Define the internal FileSystem structure
|
// Define the internal FileSystem structure
|
||||||
@ -78,11 +78,21 @@ void file_system_for_each(void* callback_context, bool (*callback)(FileSystem* f
|
|||||||
error_t file_system_mount(FileSystem* fs) {
|
error_t file_system_mount(FileSystem* fs) {
|
||||||
// Assuming 'device' is accessible or passed via a different mechanism
|
// Assuming 'device' is accessible or passed via a different mechanism
|
||||||
// as it's required by the FileSystemApi signatures.
|
// as it's required by the FileSystemApi signatures.
|
||||||
return fs->api->mount(fs->data);
|
auto result = fs->api->mount(fs->data);
|
||||||
|
if (result == ERROR_NONE) {
|
||||||
|
FileSystemMountedEvent mounted_event = { .file_system = fs };
|
||||||
|
system_event_emit(KERNEL_EVENT_FILE_SYSTEM_MOUNTED, &mounted_event, sizeof(mounted_event));
|
||||||
|
}
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
error_t file_system_unmount(FileSystem* fs) {
|
error_t file_system_unmount(FileSystem* fs) {
|
||||||
return fs->api->unmount(fs->data);
|
auto result = fs->api->unmount(fs->data);
|
||||||
|
if (result == ERROR_NONE) {
|
||||||
|
FileSystemUnmountedEvent unmounted_event = { .file_system = fs };
|
||||||
|
system_event_emit(KERNEL_EVENT_FILE_SYSTEM_UNMOUNTED, &unmounted_event, sizeof(unmounted_event));
|
||||||
|
}
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool file_system_is_mounted(FileSystem* fs) {
|
bool file_system_is_mounted(FileSystem* fs) {
|
||||||
|
|||||||
@ -1,6 +1,9 @@
|
|||||||
// SPDX-License-Identifier: Apache-2.0
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
#include <tactility/service/service_manager.h>
|
#include <tactility/service/service_manager.h>
|
||||||
|
|
||||||
|
#include "tactility/system_event.h"
|
||||||
|
|
||||||
#include <tactility/concurrent/mutex.h>
|
#include <tactility/concurrent/mutex.h>
|
||||||
#include <tactility/log.h>
|
#include <tactility/log.h>
|
||||||
|
|
||||||
@ -122,6 +125,8 @@ error_t service_manager_start(const char* id) {
|
|||||||
|
|
||||||
if (error == ERROR_NONE) {
|
if (error == ERROR_NONE) {
|
||||||
service_instance_set_state(instance, SERVICE_STATE_STARTED);
|
service_instance_set_state(instance, SERVICE_STATE_STARTED);
|
||||||
|
ServiceStartedEvent start_event = { .id = id };
|
||||||
|
system_event_emit(KERNEL_EVENT_SERVICE_STARTED, &start_event, sizeof(start_event));
|
||||||
return ERROR_NONE;
|
return ERROR_NONE;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -165,6 +170,9 @@ error_t service_manager_stop(const char* id) {
|
|||||||
service_instance_destruct(instance);
|
service_instance_destruct(instance);
|
||||||
delete instance;
|
delete instance;
|
||||||
|
|
||||||
|
ServiceStoppedEvent stop_event = { .id = id };
|
||||||
|
system_event_emit(KERNEL_EVENT_SERVICE_STOPPED, &stop_event, sizeof(stop_event));
|
||||||
|
|
||||||
return ERROR_NONE;
|
return ERROR_NONE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
120
TactilityKernel/source/system_event.cpp
Normal file
120
TactilityKernel/source/system_event.cpp
Normal file
@ -0,0 +1,120 @@
|
|||||||
|
#include <tactility/system_event.h>
|
||||||
|
|
||||||
|
#include <tactility/concurrent/mutex.h>
|
||||||
|
#include <tactility/error.h>
|
||||||
|
#include <tactility/time.h>
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <new>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
struct KernelEventSubscription {
|
||||||
|
SystemEventType type;
|
||||||
|
system_event_callback_t callback;
|
||||||
|
void* callback_context;
|
||||||
|
};
|
||||||
|
|
||||||
|
static std::vector<KernelEventSubscription> subscriptions;
|
||||||
|
|
||||||
|
// Mutex is constructed/destructed via a static-lifetime wrapper because struct Mutex
|
||||||
|
// itself has no constructor: mutex_lock() on an unconstructed handle is undefined
|
||||||
|
// behaviour (the raw QueueHandle_t would be null).
|
||||||
|
struct KernelEventMutex {
|
||||||
|
Mutex handle {};
|
||||||
|
KernelEventMutex() { mutex_construct(&handle); }
|
||||||
|
~KernelEventMutex() { mutex_destruct(&handle); }
|
||||||
|
};
|
||||||
|
|
||||||
|
static KernelEventMutex subscriptions_mutex;
|
||||||
|
|
||||||
|
extern "C" {
|
||||||
|
|
||||||
|
error_t system_event_subscribe(
|
||||||
|
SystemEventType type,
|
||||||
|
system_event_callback_t callback,
|
||||||
|
void* context
|
||||||
|
) {
|
||||||
|
mutex_lock(&subscriptions_mutex.handle);
|
||||||
|
subscriptions.push_back(KernelEventSubscription { type, callback, context });
|
||||||
|
mutex_unlock(&subscriptions_mutex.handle);
|
||||||
|
|
||||||
|
return ERROR_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
error_t system_event_unsubscribe(
|
||||||
|
SystemEventType type,
|
||||||
|
system_event_callback_t callback
|
||||||
|
) {
|
||||||
|
mutex_lock(&subscriptions_mutex.handle);
|
||||||
|
const auto iterator = std::ranges::find_if(subscriptions, [type, callback](const KernelEventSubscription& subscription) {
|
||||||
|
return subscription.type == type && subscription.callback == callback;
|
||||||
|
});
|
||||||
|
error_t result = ERROR_NOT_FOUND;
|
||||||
|
if (iterator != subscriptions.end()) {
|
||||||
|
subscriptions.erase(iterator);
|
||||||
|
result = ERROR_NONE;
|
||||||
|
}
|
||||||
|
mutex_unlock(&subscriptions_mutex.handle);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
error_t system_event_emit(
|
||||||
|
enum SystemEventType type,
|
||||||
|
const void* data,
|
||||||
|
size_t data_len
|
||||||
|
) {
|
||||||
|
SystemEvent event = {
|
||||||
|
.type = type,
|
||||||
|
.timestamp = get_micros_since_boot(),
|
||||||
|
.data = data,
|
||||||
|
.data_len = data_len,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Snapshot matching subscriptions under the lock, then invoke after unlocking: a
|
||||||
|
// callback calling system_event_subscribe(), system_event_unsubscribe() or
|
||||||
|
// system_event_emit() would otherwise deadlock against this same (non-recursive)
|
||||||
|
// mutex, and a slow callback would block every other thread's subscribe/unsubscribe
|
||||||
|
// for the duration of this emit.
|
||||||
|
//
|
||||||
|
// Nothing between mutex_lock() and mutex_unlock() below may throw.
|
||||||
|
// Count first, then use new(std::nothrow) to allocate the exact size and report
|
||||||
|
// failure through the return value instead; the fill loop below is then a plain
|
||||||
|
// assignment of a trivially-copyable struct, which cannot throw or reallocate.
|
||||||
|
|
||||||
|
mutex_lock(&subscriptions_mutex.handle);
|
||||||
|
|
||||||
|
size_t match_count = 0;
|
||||||
|
for (const auto& subscription : subscriptions) {
|
||||||
|
if (subscription.type == type) {
|
||||||
|
match_count++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
KernelEventSubscription* matching = (match_count > 0)
|
||||||
|
? new (std::nothrow) KernelEventSubscription[match_count]
|
||||||
|
: nullptr;
|
||||||
|
if (match_count > 0 && matching == nullptr) {
|
||||||
|
mutex_unlock(&subscriptions_mutex.handle);
|
||||||
|
return ERROR_OUT_OF_MEMORY;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t matched_count = 0;
|
||||||
|
for (const auto& subscription : subscriptions) {
|
||||||
|
if (subscription.type == type) {
|
||||||
|
matching[matched_count++] = subscription;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mutex_unlock(&subscriptions_mutex.handle);
|
||||||
|
|
||||||
|
for (size_t i = 0; i < matched_count; i++) {
|
||||||
|
matching[i].callback(&event, matching[i].callback_context);
|
||||||
|
}
|
||||||
|
|
||||||
|
delete[] matching;
|
||||||
|
|
||||||
|
return ERROR_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // extern "C"
|
||||||
215
Tests/TactilityKernel/Source/SystemEventTest.cpp
Normal file
215
Tests/TactilityKernel/Source/SystemEventTest.cpp
Normal file
@ -0,0 +1,215 @@
|
|||||||
|
#include "doctest.h"
|
||||||
|
|
||||||
|
#include <tactility/system_event.h>
|
||||||
|
#include <tactility/time.h>
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
// system_event_emit() snapshots matching subscriptions under the lock, then invokes them
|
||||||
|
// after unlocking (see the @warning on system_event_subscribe() in system_event.h), so a
|
||||||
|
// callback calling system_event_subscribe()/_unsubscribe()/_emit() must not deadlock -
|
||||||
|
// covered below, mirroring DeviceListenerTest.cpp's reentrancy test.
|
||||||
|
|
||||||
|
struct RecordedCall {
|
||||||
|
void* context;
|
||||||
|
SystemEventType type;
|
||||||
|
const void* data;
|
||||||
|
size_t data_len;
|
||||||
|
uint64_t timestamp;
|
||||||
|
};
|
||||||
|
|
||||||
|
static std::vector<RecordedCall> calls_a;
|
||||||
|
static std::vector<RecordedCall> calls_b;
|
||||||
|
|
||||||
|
static void listener_a(SystemEvent* event, void* context) {
|
||||||
|
calls_a.push_back({ context, event->type, event->data, event->data_len, event->timestamp });
|
||||||
|
}
|
||||||
|
|
||||||
|
static void listener_b(SystemEvent* event, void* context) {
|
||||||
|
calls_b.push_back({ context, event->type, event->data, event->data_len, event->timestamp });
|
||||||
|
}
|
||||||
|
|
||||||
|
static void reset_calls() {
|
||||||
|
calls_a.clear();
|
||||||
|
calls_b.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("system_event_emit invokes every subscriber registered for that type") {
|
||||||
|
reset_calls();
|
||||||
|
int context_a = 1;
|
||||||
|
int context_b = 2;
|
||||||
|
|
||||||
|
CHECK_EQ(system_event_subscribe(KERNEL_EVENT_BOOT_COMPLETED, listener_a, &context_a), ERROR_NONE);
|
||||||
|
CHECK_EQ(system_event_subscribe(KERNEL_EVENT_BOOT_COMPLETED, listener_b, &context_b), ERROR_NONE);
|
||||||
|
|
||||||
|
CHECK_EQ(system_event_emit(KERNEL_EVENT_BOOT_COMPLETED, nullptr, 0), ERROR_NONE);
|
||||||
|
|
||||||
|
REQUIRE_EQ(calls_a.size(), 1);
|
||||||
|
CHECK_EQ(calls_a[0].context, &context_a);
|
||||||
|
CHECK_EQ(calls_a[0].type, KERNEL_EVENT_BOOT_COMPLETED);
|
||||||
|
|
||||||
|
REQUIRE_EQ(calls_b.size(), 1);
|
||||||
|
CHECK_EQ(calls_b[0].context, &context_b);
|
||||||
|
|
||||||
|
system_event_unsubscribe(KERNEL_EVENT_BOOT_COMPLETED, listener_a);
|
||||||
|
system_event_unsubscribe(KERNEL_EVENT_BOOT_COMPLETED, listener_b);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("system_event_emit only invokes subscribers registered for the emitted type") {
|
||||||
|
reset_calls();
|
||||||
|
int context_a = 1;
|
||||||
|
system_event_subscribe(KERNEL_EVENT_BOOT_COMPLETED, listener_a, &context_a);
|
||||||
|
|
||||||
|
system_event_emit(KERNEL_EVENT_TIME_CHANGED, nullptr, 0);
|
||||||
|
CHECK_EQ(calls_a.size(), 0);
|
||||||
|
|
||||||
|
system_event_emit(KERNEL_EVENT_BOOT_COMPLETED, nullptr, 0);
|
||||||
|
CHECK_EQ(calls_a.size(), 1);
|
||||||
|
|
||||||
|
system_event_unsubscribe(KERNEL_EVENT_BOOT_COMPLETED, listener_a);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("system_event_emit passes the data pointer and length through unchanged") {
|
||||||
|
reset_calls();
|
||||||
|
int context_a = 1;
|
||||||
|
struct Payload { int value; } payload { 42 };
|
||||||
|
|
||||||
|
system_event_subscribe(KERNEL_EVENT_TIME_CHANGED, listener_a, &context_a);
|
||||||
|
system_event_emit(KERNEL_EVENT_TIME_CHANGED, &payload, sizeof(payload));
|
||||||
|
|
||||||
|
REQUIRE_EQ(calls_a.size(), 1);
|
||||||
|
CHECK_EQ(calls_a[0].data, &payload);
|
||||||
|
CHECK_EQ(calls_a[0].data_len, sizeof(payload));
|
||||||
|
CHECK_EQ(static_cast<const Payload*>(calls_a[0].data)->value, 42);
|
||||||
|
|
||||||
|
system_event_unsubscribe(KERNEL_EVENT_TIME_CHANGED, listener_a);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("system_event_emit with no data passes a null pointer and zero length") {
|
||||||
|
reset_calls();
|
||||||
|
int context_a = 1;
|
||||||
|
system_event_subscribe(KERNEL_EVENT_BOOT_COMPLETED, listener_a, &context_a);
|
||||||
|
|
||||||
|
system_event_emit(KERNEL_EVENT_BOOT_COMPLETED, nullptr, 0);
|
||||||
|
|
||||||
|
REQUIRE_EQ(calls_a.size(), 1);
|
||||||
|
CHECK_EQ(calls_a[0].data, nullptr);
|
||||||
|
CHECK_EQ(calls_a[0].data_len, 0);
|
||||||
|
|
||||||
|
system_event_unsubscribe(KERNEL_EVENT_BOOT_COMPLETED, listener_a);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("system_event_unsubscribe stops further notifications for that callback only") {
|
||||||
|
reset_calls();
|
||||||
|
int context_a = 1;
|
||||||
|
int context_b = 2;
|
||||||
|
|
||||||
|
system_event_subscribe(KERNEL_EVENT_BOOT_COMPLETED, listener_a, &context_a);
|
||||||
|
system_event_subscribe(KERNEL_EVENT_BOOT_COMPLETED, listener_b, &context_b);
|
||||||
|
|
||||||
|
CHECK_EQ(system_event_unsubscribe(KERNEL_EVENT_BOOT_COMPLETED, listener_a), ERROR_NONE);
|
||||||
|
|
||||||
|
system_event_emit(KERNEL_EVENT_BOOT_COMPLETED, nullptr, 0);
|
||||||
|
|
||||||
|
CHECK_EQ(calls_a.size(), 0);
|
||||||
|
CHECK_EQ(calls_b.size(), 1);
|
||||||
|
|
||||||
|
system_event_unsubscribe(KERNEL_EVENT_BOOT_COMPLETED, listener_b);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("system_event_unsubscribe on an unregistered callback returns ERROR_NOT_FOUND and is a no-op") {
|
||||||
|
reset_calls();
|
||||||
|
int context_b = 2;
|
||||||
|
system_event_subscribe(KERNEL_EVENT_BOOT_COMPLETED, listener_b, &context_b);
|
||||||
|
|
||||||
|
// listener_a was never added for this type, so removing it must not disturb listener_b.
|
||||||
|
CHECK_EQ(system_event_unsubscribe(KERNEL_EVENT_BOOT_COMPLETED, listener_a), ERROR_NOT_FOUND);
|
||||||
|
|
||||||
|
system_event_emit(KERNEL_EVENT_BOOT_COMPLETED, nullptr, 0);
|
||||||
|
CHECK_EQ(calls_b.size(), 1);
|
||||||
|
|
||||||
|
system_event_unsubscribe(KERNEL_EVENT_BOOT_COMPLETED, listener_b);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("system_event_unsubscribe matches on (type, callback), not the callback alone") {
|
||||||
|
reset_calls();
|
||||||
|
int context_a = 1;
|
||||||
|
|
||||||
|
// Same callback subscribed for two different event types.
|
||||||
|
system_event_subscribe(KERNEL_EVENT_BOOT_COMPLETED, listener_a, &context_a);
|
||||||
|
system_event_subscribe(KERNEL_EVENT_TIME_CHANGED, listener_a, &context_a);
|
||||||
|
|
||||||
|
system_event_unsubscribe(KERNEL_EVENT_BOOT_COMPLETED, listener_a);
|
||||||
|
|
||||||
|
system_event_emit(KERNEL_EVENT_BOOT_COMPLETED, nullptr, 0);
|
||||||
|
CHECK_EQ(calls_a.size(), 0);
|
||||||
|
|
||||||
|
system_event_emit(KERNEL_EVENT_TIME_CHANGED, nullptr, 0);
|
||||||
|
CHECK_EQ(calls_a.size(), 1);
|
||||||
|
|
||||||
|
system_event_unsubscribe(KERNEL_EVENT_TIME_CHANGED, listener_a);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("system_event_emit with no subscribers for that type returns ERROR_NONE") {
|
||||||
|
CHECK_EQ(system_event_emit(KERNEL_EVENT_SERVICE_STOPPED, nullptr, 0), ERROR_NONE);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("system_event_emit stamps the event with the current boot-relative time") {
|
||||||
|
reset_calls();
|
||||||
|
int context_a = 1;
|
||||||
|
system_event_subscribe(KERNEL_EVENT_BOOT_COMPLETED, listener_a, &context_a);
|
||||||
|
|
||||||
|
auto before = static_cast<uint64_t>(get_micros_since_boot());
|
||||||
|
system_event_emit(KERNEL_EVENT_BOOT_COMPLETED, nullptr, 0);
|
||||||
|
auto after = static_cast<uint64_t>(get_micros_since_boot());
|
||||||
|
|
||||||
|
REQUIRE_EQ(calls_a.size(), 1);
|
||||||
|
CHECK_GE(calls_a[0].timestamp, before);
|
||||||
|
CHECK_LE(calls_a[0].timestamp, after);
|
||||||
|
|
||||||
|
system_event_unsubscribe(KERNEL_EVENT_BOOT_COMPLETED, listener_a);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool reentrant_add_triggered = false;
|
||||||
|
|
||||||
|
static void reentrant_listener(SystemEvent* event, void* context) {
|
||||||
|
calls_a.push_back({ context, event->type, event->data, event->data_len, event->timestamp });
|
||||||
|
if (!reentrant_add_triggered) {
|
||||||
|
reentrant_add_triggered = true;
|
||||||
|
// Subscribing from within a notification must not deadlock: emit() releases the
|
||||||
|
// lock before invoking callbacks, so this only blocks briefly on the (already
|
||||||
|
// unlocked) mutex.
|
||||||
|
system_event_subscribe(KERNEL_EVENT_BOOT_COMPLETED, listener_b, context);
|
||||||
|
// Also exercise unsubscribe() and a nested emit() of a different type from within
|
||||||
|
// a callback - all must complete without deadlocking.
|
||||||
|
system_event_unsubscribe(KERNEL_EVENT_BOOT_COMPLETED, reentrant_listener);
|
||||||
|
system_event_emit(KERNEL_EVENT_TIME_CHANGED, nullptr, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("system_event_emit is safe when a callback subscribes, unsubscribes and emits during notification") {
|
||||||
|
reset_calls();
|
||||||
|
reentrant_add_triggered = false;
|
||||||
|
int context_a = 1;
|
||||||
|
|
||||||
|
system_event_subscribe(KERNEL_EVENT_BOOT_COMPLETED, reentrant_listener, &context_a);
|
||||||
|
system_event_subscribe(KERNEL_EVENT_TIME_CHANGED, listener_b, &context_a);
|
||||||
|
|
||||||
|
system_event_emit(KERNEL_EVENT_BOOT_COMPLETED, nullptr, 0);
|
||||||
|
|
||||||
|
// reentrant_listener unsubscribed itself and triggered a nested TIME_CHANGED emit,
|
||||||
|
// which the pre-existing listener_b subscription picks up. The listener_b
|
||||||
|
// subscription added *during* this round wasn't part of this round's snapshot, so it
|
||||||
|
// wasn't invoked for BOOT_COMPLETED yet.
|
||||||
|
CHECK_EQ(calls_a.size(), 1);
|
||||||
|
CHECK_EQ(calls_b.size(), 1);
|
||||||
|
|
||||||
|
// A second BOOT_COMPLETED emit must not reach reentrant_listener again (it
|
||||||
|
// unsubscribed itself), but must reach the listener_b subscription added last round.
|
||||||
|
system_event_emit(KERNEL_EVENT_BOOT_COMPLETED, nullptr, 0);
|
||||||
|
CHECK_EQ(calls_a.size(), 1);
|
||||||
|
CHECK_EQ(calls_b.size(), 2);
|
||||||
|
|
||||||
|
system_event_unsubscribe(KERNEL_EVENT_BOOT_COMPLETED, listener_b);
|
||||||
|
system_event_unsubscribe(KERNEL_EVENT_TIME_CHANGED, listener_b);
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user