some refactoring

This commit is contained in:
2025-10-22 14:46:20 +02:00
parent 5ddd38e5d7
commit 1ee132898b
22 changed files with 726 additions and 348 deletions

View File

@@ -4,7 +4,7 @@
#include "freertos/FreeRTOS.h"
#include "freertos/event_groups.h"
#include "freertos/timers.h"
#include "freertos/queue.h"
namespace cardboy::backend::esp {
@@ -16,12 +16,12 @@ public:
void signal(std::uint32_t bits) override;
void signalFromISR(std::uint32_t bits) override;
std::uint32_t wait(std::uint32_t mask, std::uint32_t timeout_ms) override;
void scheduleTimerSignal(std::uint32_t delay_ms) override;
void cancelTimerSignal() override;
void postEvent(const cardboy::sdk::AppEvent& event) override;
bool popEvent(cardboy::sdk::AppEvent& outEvent) override;
private:
EventGroupHandle_t group;
TimerHandle_t timer;
QueueHandle_t eventQueue;
};
} // namespace cardboy::backend::esp

View File

@@ -62,6 +62,7 @@ private:
class FilesystemService;
class LoopHooksService;
class NotificationService;
class TimerService;
std::unique_ptr<BuzzerService> buzzerService;
std::unique_ptr<BatteryService> batteryService;
@@ -70,6 +71,7 @@ private:
std::unique_ptr<HighResClockService> highResClockService;
std::unique_ptr<FilesystemService> filesystemService;
std::unique_ptr<EventBus> eventBus;
std::unique_ptr<TimerService> timerService;
std::unique_ptr<LoopHooksService> loopHooksService;
std::unique_ptr<NotificationService> notificationService;

View File

@@ -19,12 +19,15 @@
#include "esp_timer.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"
#include "freertos/timers.h"
#include "nvs.h"
#include "nvs_flash.h"
#include <algorithm>
#include <cstdint>
#include <ctime>
#include <list>
#include <mutex>
#include <string>
#include <string_view>
@@ -131,6 +134,63 @@ public:
void onLoopIteration() override { vTaskDelay(1); }
};
class EspRuntime::TimerService final : public cardboy::sdk::ITimerService {
public:
explicit TimerService(cardboy::sdk::IEventBus& bus);
~TimerService() override;
cardboy::sdk::TimerClientId acquireClient() override;
void releaseClient(cardboy::sdk::TimerClientId clientId) override;
cardboy::sdk::AppTimerHandle scheduleTimer(cardboy::sdk::TimerClientId clientId, std::uint32_t delay_ms,
bool repeat) override;
void cancelTimer(cardboy::sdk::TimerClientId clientId, cardboy::sdk::AppTimerHandle handle) override;
void cancelAllTimers(cardboy::sdk::TimerClientId clientId) override;
private:
struct TimerRecord {
TimerService* owner = nullptr;
TimerHandle_t timer = nullptr;
cardboy::sdk::TimerClientId clientId = cardboy::sdk::kInvalidTimerClient;
cardboy::sdk::AppTimerHandle handle = cardboy::sdk::kInvalidAppTimer;
bool repeat = false;
bool active = true;
};
class RecursiveLock {
public:
explicit RecursiveLock(SemaphoreHandle_t m) : mutex(m) { lock(); }
~RecursiveLock() { unlock(); }
void lock() {
if (mutex && !locked) {
if (xSemaphoreTakeRecursive(mutex, portMAX_DELAY) == pdTRUE)
locked = true;
}
}
void unlock() {
if (mutex && locked) {
xSemaphoreGiveRecursive(mutex);
locked = false;
}
}
private:
SemaphoreHandle_t mutex;
bool locked = false;
};
static void timerCallback(TimerHandle_t timer);
void handleTimer(TimerRecord* record);
void shutdown();
cardboy::sdk::IEventBus& eventBus;
SemaphoreHandle_t mutex = nullptr;
std::list<TimerRecord> timers;
cardboy::sdk::AppTimerHandle nextTimerHandle = 1;
cardboy::sdk::TimerClientId nextClientId = 1;
};
class EspRuntime::NotificationService final : public cardboy::sdk::INotificationCenter {
public:
void pushNotification(Notification notification) override {
@@ -247,6 +307,216 @@ private:
std::uint32_t revisionCounter = 0;
};
EspRuntime::TimerService::TimerService(cardboy::sdk::IEventBus& bus) : eventBus(bus) {
mutex = xSemaphoreCreateRecursiveMutex();
}
EspRuntime::TimerService::~TimerService() {
shutdown();
if (mutex)
vSemaphoreDelete(mutex);
}
cardboy::sdk::TimerClientId EspRuntime::TimerService::acquireClient() {
if (!mutex)
return cardboy::sdk::kInvalidTimerClient;
RecursiveLock lock(mutex);
cardboy::sdk::TimerClientId id = cardboy::sdk::kInvalidTimerClient;
do {
id = nextClientId++;
} while (id == cardboy::sdk::kInvalidTimerClient);
if (nextClientId == cardboy::sdk::kInvalidTimerClient)
++nextClientId;
return id;
}
void EspRuntime::TimerService::releaseClient(cardboy::sdk::TimerClientId clientId) { cancelAllTimers(clientId); }
cardboy::sdk::AppTimerHandle EspRuntime::TimerService::scheduleTimer(cardboy::sdk::TimerClientId clientId,
std::uint32_t delay_ms, bool repeat) {
if (!mutex || clientId == cardboy::sdk::kInvalidTimerClient)
return cardboy::sdk::kInvalidAppTimer;
TimerRecord* storedRecord = nullptr;
{
RecursiveLock lock(mutex);
TimerRecord record{};
record.owner = this;
record.clientId = clientId;
record.repeat = repeat;
record.active = true;
cardboy::sdk::AppTimerHandle newHandle = cardboy::sdk::kInvalidAppTimer;
do {
newHandle = nextTimerHandle++;
} while (newHandle == cardboy::sdk::kInvalidAppTimer);
if (nextTimerHandle == cardboy::sdk::kInvalidAppTimer)
++nextTimerHandle;
record.handle = newHandle;
timers.emplace_back(record);
storedRecord = &timers.back();
}
const std::uint32_t effectiveDelay = std::max<std::uint32_t>(1, delay_ms);
const TickType_t ticks = std::max<TickType_t>(pdMS_TO_TICKS(effectiveDelay), 1);
TimerHandle_t timerHandle =
xTimerCreate("AppSvcTimer", ticks, repeat ? pdTRUE : pdFALSE, storedRecord, &TimerService::timerCallback);
if (!timerHandle) {
RecursiveLock lock(mutex);
for (auto it = timers.begin(); it != timers.end(); ++it) {
if (&(*it) == storedRecord) {
timers.erase(it);
break;
}
}
return cardboy::sdk::kInvalidAppTimer;
}
{
RecursiveLock lock(mutex);
storedRecord->timer = timerHandle;
}
if (xTimerStart(timerHandle, 0) != pdPASS) {
cancelTimer(clientId, storedRecord->handle);
return cardboy::sdk::kInvalidAppTimer;
}
return storedRecord->handle;
}
void EspRuntime::TimerService::cancelTimer(cardboy::sdk::TimerClientId clientId,
cardboy::sdk::AppTimerHandle handle) {
if (!mutex || clientId == cardboy::sdk::kInvalidTimerClient || handle == cardboy::sdk::kInvalidAppTimer)
return;
TimerHandle_t timerHandle = nullptr;
{
RecursiveLock lock(mutex);
for (auto& record: timers) {
if (record.clientId == clientId && record.handle == handle) {
record.active = false;
timerHandle = record.timer;
break;
}
}
}
if (!timerHandle)
return;
xTimerStop(timerHandle, portMAX_DELAY);
xTimerDelete(timerHandle, portMAX_DELAY);
{
RecursiveLock lock(mutex);
for (auto it = timers.begin(); it != timers.end();) {
if (it->clientId == clientId && it->handle == handle)
it = timers.erase(it);
else
++it;
}
}
}
void EspRuntime::TimerService::cancelAllTimers(cardboy::sdk::TimerClientId clientId) {
if (!mutex || clientId == cardboy::sdk::kInvalidTimerClient)
return;
std::vector<TimerHandle_t> handles;
{
RecursiveLock lock(mutex);
for (auto& record: timers) {
if (record.clientId == clientId) {
record.active = false;
if (record.timer)
handles.push_back(record.timer);
}
}
}
for (auto timerHandle: handles) {
xTimerStop(timerHandle, portMAX_DELAY);
xTimerDelete(timerHandle, portMAX_DELAY);
}
{
RecursiveLock lock(mutex);
for (auto it = timers.begin(); it != timers.end();) {
if (it->clientId == clientId)
it = timers.erase(it);
else
++it;
}
}
}
void EspRuntime::TimerService::timerCallback(TimerHandle_t timer) {
auto* record = static_cast<TimerRecord*>(pvTimerGetTimerID(timer));
if (!record || !record->owner)
return;
record->owner->handleTimer(record);
}
void EspRuntime::TimerService::handleTimer(TimerRecord* record) {
if (!record || !record->active)
return;
const cardboy::sdk::TimerClientId clientId = record->clientId;
const cardboy::sdk::AppTimerHandle handle = record->handle;
const bool isRepeating = record->repeat;
TimerHandle_t timerHandle = record->timer;
if (!isRepeating) {
record->active = false;
{
RecursiveLock lock(mutex);
for (auto it = timers.begin(); it != timers.end();) {
if (&(*it) == record)
it = timers.erase(it);
else
++it;
}
}
if (timerHandle)
xTimerDelete(timerHandle, 0);
}
cardboy::sdk::AppTimerEvent timerEvent{};
timerEvent.clientId = clientId;
timerEvent.handle = handle;
cardboy::sdk::AppEvent event{};
event.timestamp_ms = static_cast<std::uint32_t>(esp_timer_get_time() / 1000ULL);
event.data = timerEvent;
eventBus.postEvent(event);
}
void EspRuntime::TimerService::shutdown() {
if (!mutex)
return;
std::vector<TimerHandle_t> handles;
{
RecursiveLock lock(mutex);
for (auto& record: timers) {
record.active = false;
if (record.timer)
handles.push_back(record.timer);
}
}
for (auto timerHandle: handles) {
xTimerStop(timerHandle, portMAX_DELAY);
xTimerDelete(timerHandle, portMAX_DELAY);
}
{
RecursiveLock lock(mutex);
timers.clear();
}
}
EspRuntime::EspRuntime() : framebuffer(), input(), clock() {
initializeHardware();
@@ -257,6 +527,7 @@ EspRuntime::EspRuntime() : framebuffer(), input(), clock() {
highResClockService = std::make_unique<HighResClockService>();
filesystemService = std::make_unique<FilesystemService>();
eventBus = std::make_unique<EventBus>();
timerService = std::make_unique<TimerService>(*eventBus);
loopHooksService = std::make_unique<LoopHooksService>();
notificationService = std::make_unique<NotificationService>();
@@ -267,6 +538,7 @@ EspRuntime::EspRuntime() : framebuffer(), input(), clock() {
services.highResClock = highResClockService.get();
services.filesystem = filesystemService.get();
services.eventBus = eventBus.get();
services.timer = timerService.get();
services.loopHooks = loopHooksService.get();
services.notifications = notificationService.get();

View File

@@ -14,22 +14,19 @@ namespace {
return portMAX_DELAY;
return pdMS_TO_TICKS(timeout_ms);
}
constexpr UBaseType_t kEventQueueLength = 16;
} // namespace
static void timerCallback(TimerHandle_t handle) {
auto* bus = static_cast<EventBus*>(pvTimerGetTimerID(handle));
if (bus)
bus->signal(cardboy::sdk::to_event_bits(cardboy::sdk::EventBusSignal::Timer));
}
EventBus::EventBus() :
group(xEventGroupCreate()), timer(xTimerCreate("EventBusTimer", pdMS_TO_TICKS(1), pdFALSE, this, timerCallback)) {}
group(xEventGroupCreate()),
eventQueue(xQueueCreate(kEventQueueLength, sizeof(cardboy::sdk::AppEvent))) {}
EventBus::~EventBus() {
if (timer)
xTimerDelete(timer, portMAX_DELAY);
if (group)
vEventGroupDelete(group);
if (eventQueue)
vQueueDelete(eventQueue);
}
void EventBus::signal(std::uint32_t bits) {
@@ -54,28 +51,23 @@ std::uint32_t EventBus::wait(std::uint32_t mask, std::uint32_t timeout_ms) {
return static_cast<std::uint32_t>(bits & mask);
}
void EventBus::scheduleTimerSignal(std::uint32_t delay_ms) {
if (!timer)
return;
xTimerStop(timer, 0);
if (delay_ms == cardboy::sdk::IEventBus::kWaitForever)
void EventBus::postEvent(const cardboy::sdk::AppEvent& event) {
if (!eventQueue)
return;
if (delay_ms == 0) {
signal(cardboy::sdk::to_event_bits(cardboy::sdk::EventBusSignal::Timer));
return;
if (xQueueSend(eventQueue, &event, 0) == errQUEUE_FULL) {
cardboy::sdk::AppEvent discarded{};
if (xQueueReceive(eventQueue, &discarded, 0) == pdTRUE)
xQueueSend(eventQueue, &event, 0);
}
const TickType_t ticks = std::max<TickType_t>(pdMS_TO_TICKS(delay_ms), 1);
if (xTimerChangePeriod(timer, ticks, 0) == pdPASS)
xTimerStart(timer, 0);
signal(cardboy::sdk::to_event_bits(cardboy::sdk::EventBusSignal::Timer));
}
void EventBus::cancelTimerSignal() {
if (!timer)
return;
xTimerStop(timer, 0);
bool EventBus::popEvent(cardboy::sdk::AppEvent& outEvent) {
if (!eventQueue)
return false;
return xQueueReceive(eventQueue, &outEvent, 0) == pdTRUE;
}
} // namespace cardboy::backend::esp