some refactoring 2

This commit is contained in:
2025-10-25 12:34:53 +02:00
parent 1ee132898b
commit f8735d4bce
32 changed files with 816 additions and 818 deletions

View File

@@ -4,7 +4,6 @@ idf_component_register(
"src/buttons.cpp" "src/buttons.cpp"
"src/buzzer.cpp" "src/buzzer.cpp"
"src/esp_backend.cpp" "src/esp_backend.cpp"
"src/event_bus.cpp"
"src/display.cpp" "src/display.cpp"
"src/fs_helper.cpp" "src/fs_helper.cpp"
"src/i2c_global.cpp" "src/i2c_global.cpp"

View File

@@ -5,9 +5,10 @@
#ifndef BUTTONS_HPP #ifndef BUTTONS_HPP
#define BUTTONS_HPP #define BUTTONS_HPP
#include "cardboy/sdk/event_bus.hpp"
#include <cstdint> #include <cstdint>
#include "cardboy/sdk/input_state.hpp"
#include "cardboy/sdk/services.hpp"
#include "freertos/FreeRTOS.h" #include "freertos/FreeRTOS.h"
#include "freertos/task.h" #include "freertos/task.h"
@@ -24,20 +25,19 @@ typedef enum {
class Buttons { class Buttons {
public: public:
static Buttons& get(); static Buttons& get();
void pooler(); // FIXME: void pooler(); // FIXME:
uint8_t get_pressed(); uint8_t get_pressed();
void install_isr(); cardboy::sdk::InputState get_state();
void register_listener(TaskHandle_t task); void install_isr();
void setEventBus(cardboy::sdk::IEventBus* bus); void setEventBus(cardboy::sdk::IEventBus* bus);
TaskHandle_t _pooler_task; TaskHandle_t _pooler_task;
private: private:
Buttons(); Buttons();
uint8_t _previous;
volatile uint8_t _current; volatile uint8_t _current;
volatile TaskHandle_t _listener = nullptr;
cardboy::sdk::IEventBus* _eventBus = nullptr; cardboy::sdk::IEventBus* _eventBus = nullptr;
}; };

View File

@@ -1,27 +0,0 @@
#pragma once
#include <cardboy/sdk/event_bus.hpp>
#include "freertos/FreeRTOS.h"
#include "freertos/event_groups.h"
#include "freertos/queue.h"
namespace cardboy::backend::esp {
class EventBus final : public cardboy::sdk::IEventBus {
public:
EventBus();
~EventBus() override;
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 postEvent(const cardboy::sdk::AppEvent& event) override;
bool popEvent(cardboy::sdk::AppEvent& outEvent) override;
private:
EventGroupHandle_t group;
QueueHandle_t eventQueue;
};
} // namespace cardboy::backend::esp

View File

@@ -2,7 +2,6 @@
#include <cardboy/sdk/display_spec.hpp> #include <cardboy/sdk/display_spec.hpp>
#include "cardboy/backend/esp/display.hpp" #include "cardboy/backend/esp/display.hpp"
#include "cardboy/backend/esp/event_bus.hpp"
#include "cardboy/sdk/platform.hpp" #include "cardboy/sdk/platform.hpp"
#include "cardboy/sdk/services.hpp" #include "cardboy/sdk/services.hpp"
@@ -63,19 +62,22 @@ private:
class LoopHooksService; class LoopHooksService;
class NotificationService; class NotificationService;
class TimerService; class TimerService;
class EventBus;
class AppScopedServices;
class AppServiceProvider;
std::unique_ptr<BuzzerService> buzzerService; std::unique_ptr<BuzzerService> _buzzerService;
std::unique_ptr<BatteryService> batteryService; std::unique_ptr<BatteryService> _batteryService;
std::unique_ptr<StorageService> storageService; std::unique_ptr<StorageService> _storageService;
std::unique_ptr<RandomService> randomService; std::unique_ptr<RandomService> _randomService;
std::unique_ptr<HighResClockService> highResClockService; std::unique_ptr<HighResClockService> _highResClockService;
std::unique_ptr<FilesystemService> filesystemService; std::unique_ptr<FilesystemService> _filesystemService;
std::unique_ptr<EventBus> eventBus; std::unique_ptr<EventBus> _eventBus;
std::unique_ptr<TimerService> timerService; std::unique_ptr<LoopHooksService> _loopHooksService;
std::unique_ptr<LoopHooksService> loopHooksService; std::unique_ptr<NotificationService> _notificationService;
std::unique_ptr<NotificationService> notificationService; std::unique_ptr<AppServiceProvider> _appServiceProvider;
cardboy::sdk::Services services{}; cardboy::sdk::Services _services{};
}; };
struct Backend { struct Backend {

View File

@@ -13,7 +13,6 @@
#include "cardboy/backend/esp/config.hpp" #include "cardboy/backend/esp/config.hpp"
#include "cardboy/backend/esp/i2c_global.hpp" #include "cardboy/backend/esp/i2c_global.hpp"
#include "cardboy/sdk/event_bus.hpp"
static i2c_master_dev_handle_t dev_handle; static i2c_master_dev_handle_t dev_handle;
static inline i2c_device_config_t dev_cfg = { static inline i2c_device_config_t dev_cfg = {
@@ -76,6 +75,28 @@ static void delay(unsigned long long loop) {
} }
} }
static cardboy::sdk::InputState buttons_to_input_state(uint8_t pressed) {
cardboy::sdk::InputState state{};
if (pressed & BTN_UP)
state.up = true;
if (pressed & BTN_LEFT)
state.left = true;
if (pressed & BTN_RIGHT)
state.right = true;
if (pressed & BTN_DOWN)
state.down = true;
if (pressed & BTN_A)
state.a = true;
if (pressed & BTN_B)
state.b = true;
if (pressed & BTN_SELECT)
state.select = true;
if (pressed & BTN_START)
state.start = true;
return state;
}
void Buttons::pooler() { void Buttons::pooler() {
while (true) { while (true) {
BaseType_t xResult = xTaskNotifyWait(pdFALSE, ULONG_MAX, nullptr, portMAX_DELAY); BaseType_t xResult = xTaskNotifyWait(pdFALSE, ULONG_MAX, nullptr, portMAX_DELAY);
@@ -88,15 +109,27 @@ void Buttons::pooler() {
reg = 1; reg = 1;
ESP_ERROR_CHECK( ESP_ERROR_CHECK(
i2c_master_transmit_receive(dev_handle, &reg, sizeof(reg), reinterpret_cast<uint8_t*>(&buffer), 1, -1)); i2c_master_transmit_receive(dev_handle, &reg, sizeof(reg), reinterpret_cast<uint8_t*>(&buffer), 1, -1));
if (_listener)
xTaskNotifyGive(_listener);
if (_eventBus) if (_eventBus) {
_eventBus->signal(cardboy::sdk::to_event_bits(cardboy::sdk::EventBusSignal::Input)); cardboy::sdk::AppButtonEvent button{};
button.current = buttons_to_input_state(_current);
button.previous = buttons_to_input_state(_previous);
_previous = _current;
cardboy::sdk::AppEvent evt{};
// TODO: dedup?
TickType_t ticks = xTaskGetTickCount();
auto now = static_cast<std::uint32_t>((static_cast<std::uint64_t>(ticks) * 1000ULL) / configTICK_RATE_HZ);
evt.timestamp_ms = now;
evt.data = button;
_eventBus->post(evt);
}
} }
} }
uint8_t Buttons::get_pressed() { return _current; } uint8_t Buttons::get_pressed() { return _current; }
void Buttons::install_isr() { gpio_isr_handler_add(EXP_INT, wakeup, nullptr); } void Buttons::install_isr() { gpio_isr_handler_add(EXP_INT, wakeup, nullptr); }
cardboy::sdk::InputState Buttons::get_state() { return buttons_to_input_state(get_pressed()); }
void Buttons::register_listener(TaskHandle_t task) { _listener = task; } void Buttons::setEventBus(cardboy::sdk::IEventBus* bus) { _eventBus = bus; }
void Buttons::setEventBus(cardboy::sdk::IEventBus* bus) { _eventBus = bus; }

View File

@@ -18,15 +18,17 @@
#include "esp_random.h" #include "esp_random.h"
#include "esp_timer.h" #include "esp_timer.h"
#include "freertos/FreeRTOS.h" #include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h" #include "freertos/semphr.h"
#include "freertos/task.h"
#include "freertos/timers.h" #include "freertos/timers.h"
#include "nvs.h" #include "nvs.h"
#include "nvs_flash.h" #include "nvs_flash.h"
#include <algorithm> #include <algorithm>
#include <atomic>
#include <cstdint> #include <cstdint>
#include <ctime> #include <ctime>
#include <deque>
#include <list> #include <list>
#include <mutex> #include <mutex>
#include <string> #include <string>
@@ -134,61 +136,96 @@ public:
void onLoopIteration() override { vTaskDelay(1); } void onLoopIteration() override { vTaskDelay(1); }
}; };
class EspRuntime::TimerService final : public cardboy::sdk::ITimerService { class EspRuntime::EventBus final : public cardboy::sdk::IEventBus {
public: public:
explicit TimerService(cardboy::sdk::IEventBus& bus); explicit EventBus() {
~TimerService() override; _queueHandle =
xQueueCreateStatic(_kMaxQueueSize, sizeof(cardboy::sdk::AppEvent), _queueStorage.data(), &_queue);
}
cardboy::sdk::TimerClientId acquireClient() override; ~EventBus() override { vQueueDelete(_queueHandle); }
void releaseClient(cardboy::sdk::TimerClientId clientId) override; void post(const sdk::AppEvent& event) override { xQueueSendToBack(_queueHandle, &event, portMAX_DELAY); }
cardboy::sdk::AppTimerHandle scheduleTimer(cardboy::sdk::TimerClientId clientId, std::uint32_t delay_ms, sdk::AppEvent pop() override {
bool repeat) override; sdk::AppEvent out;
void cancelTimer(cardboy::sdk::TimerClientId clientId, cardboy::sdk::AppTimerHandle handle) override; xQueueReceive(_queueHandle, &out, portMAX_DELAY);
void cancelAllTimers(cardboy::sdk::TimerClientId clientId) override; return out;
}
private: private:
struct TimerRecord { static constexpr std::uint32_t _kMaxQueueSize = 32;
TimerService* owner = nullptr; StaticQueue_t _queue;
TimerHandle_t timer = nullptr; std::array<std::uint8_t, 32 * sizeof(cardboy::sdk::AppEvent)> _queueStorage{};
cardboy::sdk::TimerClientId clientId = cardboy::sdk::kInvalidTimerClient; QueueHandle_t _queueHandle;
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() { class EspRuntime::TimerService final : public cardboy::sdk::ITimerService {
if (mutex && !locked) { public:
if (xSemaphoreTakeRecursive(mutex, portMAX_DELAY) == pdTRUE) explicit TimerService(cardboy::sdk::IEventBus& appBus);
locked = true; ~TimerService() override;
}
cardboy::sdk::AppTimerHandle scheduleTimer(std::uint32_t delay_ms, bool repeat) override;
void cancelTimer(cardboy::sdk::AppTimerHandle handle) override;
void cancelAllTimers() override;
private:
static inline EspRuntime::TimerService* _current = nullptr;
struct InitializedSemaphore {
InitializedSemaphore() {
_handle = xSemaphoreCreateBinary();
xSemaphoreGive(_handle);
} }
~InitializedSemaphore() { vSemaphoreDelete(_handle); }
void unlock() { SemaphoreHandle_t operator*() { return _handle; }
if (mutex && locked) { SemaphoreHandle_t operator->() { return _handle; }
xSemaphoreGiveRecursive(mutex);
locked = false;
}
}
private: private:
SemaphoreHandle_t mutex; SemaphoreHandle_t _handle;
bool locked = false; };
static inline InitializedSemaphore _currentSemaphore;
struct TimerRecord {
TimerService* owner = nullptr;
TimerHandle_t timer = nullptr;
cardboy::sdk::AppTimerHandle handle = cardboy::sdk::kInvalidAppTimer;
bool repeat = false;
}; };
static void timerCallback(TimerHandle_t timer); static void timerCallback(TimerHandle_t timer);
void handleTimer(TimerRecord* record); void handleTimer(sdk::AppTimerHandle record);
void shutdown();
cardboy::sdk::IEventBus& eventBus; cardboy::sdk::IEventBus& _appEventBus;
SemaphoreHandle_t mutex = nullptr; SemaphoreHandle_t _mutex;
std::list<TimerRecord> timers; StaticSemaphore_t _mutexStatic;
cardboy::sdk::AppTimerHandle nextTimerHandle = 1; std::list<TimerRecord> _timers;
cardboy::sdk::TimerClientId nextClientId = 1; std::atomic<sdk::AppTimerHandle> _nextTimerHandle = 1;
static_assert(std::atomic<sdk::AppTimerHandle>::is_always_lock_free);
};
class EspRuntime::AppScopedServices final : public cardboy::sdk::AppScopedServices {
public:
AppScopedServices(std::unique_ptr<TimerService> timer) : _ownedTimer(std::move(timer)) {
this->timer = _ownedTimer.get();
}
private:
std::unique_ptr<TimerService> _ownedTimer;
};
class EspRuntime::AppServiceProvider final : public cardboy::sdk::IAppServiceProvider {
public:
explicit AppServiceProvider(cardboy::sdk::IEventBus& bus) : eventBus(bus) {}
[[nodiscard]] std::unique_ptr<cardboy::sdk::AppScopedServices>
createScopedServices(std::uint64_t generation) override {
(void) generation;
auto timer = std::make_unique<TimerService>(eventBus);
return std::make_unique<AppScopedServices>(std::move(timer));
}
private:
cardboy::sdk::IEventBus& eventBus;
}; };
class EspRuntime::NotificationService final : public cardboy::sdk::INotificationCenter { class EspRuntime::NotificationService final : public cardboy::sdk::INotificationCenter {
@@ -262,8 +299,8 @@ public:
bool removed = false; bool removed = false;
for (auto it = entries.begin(); it != entries.end();) { for (auto it = entries.begin(); it != entries.end();) {
if (it->id == id) { if (it->id == id) {
it = entries.erase(it); it = entries.erase(it);
removed = true; removed = true;
} else { } else {
++it; ++it;
} }
@@ -279,8 +316,8 @@ public:
bool removed = false; bool removed = false;
for (auto it = entries.begin(); it != entries.end();) { for (auto it = entries.begin(); it != entries.end();) {
if (it->externalId == externalId) { if (it->externalId == externalId) {
it = entries.erase(it); it = entries.erase(it);
removed = true; removed = true;
} else { } else {
++it; ++it;
} }
@@ -307,99 +344,69 @@ private:
std::uint32_t revisionCounter = 0; std::uint32_t revisionCounter = 0;
}; };
EspRuntime::TimerService::TimerService(cardboy::sdk::IEventBus& bus) : eventBus(bus) { EspRuntime::TimerService::TimerService(cardboy::sdk::IEventBus& appBus) : _appEventBus(appBus) {
mutex = xSemaphoreCreateRecursiveMutex(); xSemaphoreTake(*_currentSemaphore, portMAX_DELAY);
assert(_current == nullptr);
_mutex = xSemaphoreCreateBinaryStatic(&_mutexStatic);
assert(_mutex);
xSemaphoreGive(_mutex);
_current = this;
xSemaphoreGive(*_currentSemaphore);
} }
EspRuntime::TimerService::~TimerService() { EspRuntime::TimerService::~TimerService() {
shutdown(); xSemaphoreTake(*_currentSemaphore, portMAX_DELAY);
if (mutex) assert(_current == this);
vSemaphoreDelete(mutex); _current = nullptr;
cancelAllTimers();
vSemaphoreDelete(_mutex);
xSemaphoreGive(*_currentSemaphore);
} }
cardboy::sdk::TimerClientId EspRuntime::TimerService::acquireClient() { cardboy::sdk::AppTimerHandle EspRuntime::TimerService::scheduleTimer(std::uint32_t delay_ms, bool repeat) {
if (!mutex) TimerRecord record{};
return cardboy::sdk::kInvalidTimerClient; record.owner = this;
RecursiveLock lock(mutex); record.repeat = repeat;
cardboy::sdk::TimerClientId id = cardboy::sdk::kInvalidTimerClient;
cardboy::sdk::AppTimerHandle newHandle = cardboy::sdk::kInvalidAppTimer;
do { do {
id = nextClientId++; newHandle = _nextTimerHandle++;
} while (id == cardboy::sdk::kInvalidTimerClient); } while (newHandle == cardboy::sdk::kInvalidAppTimer);
if (nextClientId == cardboy::sdk::kInvalidTimerClient) if (_nextTimerHandle == cardboy::sdk::kInvalidAppTimer)
++nextClientId; ++_nextTimerHandle;
return id; record.handle = newHandle;
} xSemaphoreTake(_mutex, portMAX_DELAY);
TimerRecord* storedRecord = &_timers.emplace_back(record);
xSemaphoreGive(_mutex);
void EspRuntime::TimerService::releaseClient(cardboy::sdk::TimerClientId clientId) { cancelAllTimers(clientId); } const TickType_t ticks = std::max<TickType_t>(pdMS_TO_TICKS(delay_ms), 1);
cardboy::sdk::AppTimerHandle EspRuntime::TimerService::scheduleTimer(cardboy::sdk::TimerClientId clientId, static_assert(sizeof(void*) >= sizeof(cardboy::sdk::AppTimerHandle));
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 = TimerHandle_t timerHandle =
xTimerCreate("AppSvcTimer", ticks, repeat ? pdTRUE : pdFALSE, storedRecord, &TimerService::timerCallback); xTimerCreate("AppSvcTimer", ticks, repeat ? pdTRUE : pdFALSE, reinterpret_cast<void*>(storedRecord->handle),
if (!timerHandle) { &TimerService::timerCallback);
RecursiveLock lock(mutex); storedRecord->timer = timerHandle;
for (auto it = timers.begin(); it != timers.end(); ++it) {
if (&(*it) == storedRecord) {
timers.erase(it);
break;
}
}
return cardboy::sdk::kInvalidAppTimer;
}
{ if (xTimerStart(timerHandle, portMAX_DELAY) != pdPASS) {
RecursiveLock lock(mutex); assert(false);
storedRecord->timer = timerHandle;
} }
if (xTimerStart(timerHandle, 0) != pdPASS) {
cancelTimer(clientId, storedRecord->handle);
return cardboy::sdk::kInvalidAppTimer;
}
return storedRecord->handle; return storedRecord->handle;
} }
void EspRuntime::TimerService::cancelTimer(cardboy::sdk::TimerClientId clientId, void EspRuntime::TimerService::cancelTimer(cardboy::sdk::AppTimerHandle handle) {
cardboy::sdk::AppTimerHandle handle) { assert(handle != sdk::kInvalidAppTimer);
if (!mutex || clientId == cardboy::sdk::kInvalidTimerClient || handle == cardboy::sdk::kInvalidAppTimer)
return;
TimerHandle_t timerHandle = nullptr; TimerHandle_t timerHandle = nullptr;
{ {
RecursiveLock lock(mutex); xSemaphoreTake(_mutex, portMAX_DELAY);
for (auto& record: timers) { for (auto it = _timers.begin(); it != _timers.end(); ++it) {
if (record.clientId == clientId && record.handle == handle) { if (it->handle == handle) {
record.active = false; timerHandle = it->timer;
timerHandle = record.timer; it = _timers.erase(it);
break;
} }
} }
xSemaphoreGive(_mutex);
} }
if (!timerHandle) if (!timerHandle)
@@ -407,143 +414,106 @@ void EspRuntime::TimerService::cancelTimer(cardboy::sdk::TimerClientId clientId,
xTimerStop(timerHandle, portMAX_DELAY); xTimerStop(timerHandle, portMAX_DELAY);
xTimerDelete(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) { void EspRuntime::TimerService::cancelAllTimers() {
if (!mutex || clientId == cardboy::sdk::kInvalidTimerClient) if (!_mutex)
return; return;
std::vector<TimerHandle_t> handles; std::vector<TimerHandle_t> handles;
handles.resize(_timers.size());
{ {
RecursiveLock lock(mutex); xSemaphoreTake(_mutex, portMAX_DELAY);
for (auto& record: timers) { size_t i = 0;
if (record.clientId == clientId) { for (auto& record: _timers) {
record.active = false; if (record.timer) {
if (record.timer) assert(record.timer);
handles.push_back(record.timer); handles[i] = record.timer;
} }
i++;
} }
_timers.clear();
xSemaphoreGive(_mutex);
} }
for (auto timerHandle: handles) { for (auto timerHandle: handles) {
xTimerStop(timerHandle, portMAX_DELAY); xTimerStop(timerHandle, portMAX_DELAY);
xTimerDelete(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) { void EspRuntime::TimerService::timerCallback(TimerHandle_t timer) {
auto* record = static_cast<TimerRecord*>(pvTimerGetTimerID(timer)); auto handle = reinterpret_cast<sdk::AppTimerHandle>(pvTimerGetTimerID(timer));
if (!record || !record->owner) xSemaphoreTake(*_currentSemaphore, portMAX_DELAY);
if (!_current)
return; return;
record->owner->handleTimer(record); _current->handleTimer(handle);
xSemaphoreGive(*_currentSemaphore);
} }
void EspRuntime::TimerService::handleTimer(TimerRecord* record) { void EspRuntime::TimerService::handleTimer(sdk::AppTimerHandle handle) {
if (!record || !record->active) TimerRecord* record = nullptr;
return; TimerHandle_t timerHandle = nullptr;
bool repeat = false;
const cardboy::sdk::TimerClientId clientId = record->clientId; {
const cardboy::sdk::AppTimerHandle handle = record->handle; xSemaphoreTake(_mutex, portMAX_DELAY);
const bool isRepeating = record->repeat; for (auto it = _timers.begin(); it != _timers.end(); ++it) {
TimerHandle_t timerHandle = record->timer; if (it->handle == handle) {
record = &(*it);
if (!isRepeating) { timerHandle = it->timer;
record->active = false; if (!it->repeat) {
{ _timers.erase(it);
RecursiveLock lock(mutex); } else {
for (auto it = timers.begin(); it != timers.end();) { repeat = true;
if (&(*it) == record) }
it = timers.erase(it); break;
else
++it;
} }
} }
if (timerHandle) xSemaphoreGive(_mutex);
xTimerDelete(timerHandle, 0);
} }
if (!record)
return;
if (!repeat && timerHandle)
xTimerDelete(timerHandle, portMAX_DELAY);
cardboy::sdk::AppTimerEvent timerEvent{}; cardboy::sdk::AppTimerEvent timerEvent{};
timerEvent.clientId = clientId; timerEvent.handle = handle;
timerEvent.handle = handle;
cardboy::sdk::AppEvent event{}; cardboy::sdk::AppEvent event{};
event.timestamp_ms = static_cast<std::uint32_t>(esp_timer_get_time() / 1000ULL); event.timestamp_ms = static_cast<std::uint32_t>(esp_timer_get_time() / 1000ULL);
event.data = timerEvent; event.data = timerEvent;
eventBus.postEvent(event); _appEventBus.post(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() { EspRuntime::EspRuntime() : framebuffer(), input(), clock() {
initializeHardware(); initializeHardware();
buzzerService = std::make_unique<BuzzerService>(); _buzzerService = std::make_unique<BuzzerService>();
batteryService = std::make_unique<BatteryService>(); _batteryService = std::make_unique<BatteryService>();
storageService = std::make_unique<StorageService>(); _storageService = std::make_unique<StorageService>();
randomService = std::make_unique<RandomService>(); _randomService = std::make_unique<RandomService>();
highResClockService = std::make_unique<HighResClockService>(); _highResClockService = std::make_unique<HighResClockService>();
filesystemService = std::make_unique<FilesystemService>(); _filesystemService = std::make_unique<FilesystemService>();
eventBus = std::make_unique<EventBus>(); _eventBus = std::make_unique<EventBus>();
timerService = std::make_unique<TimerService>(*eventBus); _appServiceProvider = std::make_unique<AppServiceProvider>(*_eventBus);
loopHooksService = std::make_unique<LoopHooksService>(); _loopHooksService = std::make_unique<LoopHooksService>();
notificationService = std::make_unique<NotificationService>(); _notificationService = std::make_unique<NotificationService>();
services.buzzer = buzzerService.get(); _services.buzzer = _buzzerService.get();
services.battery = batteryService.get(); _services.battery = _batteryService.get();
services.storage = storageService.get(); _services.storage = _storageService.get();
services.random = randomService.get(); _services.random = _randomService.get();
services.highResClock = highResClockService.get(); _services.highResClock = _highResClockService.get();
services.filesystem = filesystemService.get(); _services.filesystem = _filesystemService.get();
services.eventBus = eventBus.get(); _services.eventBus = _eventBus.get();
services.timer = timerService.get(); _services.appServices = _appServiceProvider.get();
services.loopHooks = loopHooksService.get(); _services.loopHooks = _loopHooksService.get();
services.notifications = notificationService.get(); _services.notifications = _notificationService.get();
Buttons::get().setEventBus(eventBus.get()); Buttons::get().setEventBus(_eventBus.get());
set_notification_center(notificationService.get()); set_notification_center(_notificationService.get());
} }
EspRuntime::~EspRuntime() { EspRuntime::~EspRuntime() {
@@ -551,7 +521,7 @@ EspRuntime::~EspRuntime() {
shutdown_time_sync_service(); shutdown_time_sync_service();
} }
cardboy::sdk::Services& EspRuntime::serviceRegistry() { return services; } cardboy::sdk::Services& EspRuntime::serviceRegistry() { return _services; }
void EspRuntime::initializeHardware() { void EspRuntime::initializeHardware() {
static bool initialized = false; static bool initialized = false;
@@ -595,27 +565,7 @@ void EspFramebuffer::sendFrame_impl(bool clearAfterSend) { SMD::send_frame(clear
bool EspFramebuffer::frameInFlight_impl() const { return SMD::frame_transfer_in_flight(); } bool EspFramebuffer::frameInFlight_impl() const { return SMD::frame_transfer_in_flight(); }
cardboy::sdk::InputState EspInput::readState_impl() { cardboy::sdk::InputState EspInput::readState_impl() { return Buttons::get().get_state(); }
cardboy::sdk::InputState state{};
const uint8_t pressed = Buttons::get().get_pressed();
if (pressed & BTN_UP)
state.up = true;
if (pressed & BTN_LEFT)
state.left = true;
if (pressed & BTN_RIGHT)
state.right = true;
if (pressed & BTN_DOWN)
state.down = true;
if (pressed & BTN_A)
state.a = true;
if (pressed & BTN_B)
state.b = true;
if (pressed & BTN_SELECT)
state.select = true;
if (pressed & BTN_START)
state.start = true;
return state;
}
std::uint32_t EspClock::millis_impl() { std::uint32_t EspClock::millis_impl() {
TickType_t ticks = xTaskGetTickCount(); TickType_t ticks = xTaskGetTickCount();

View File

@@ -1,73 +0,0 @@
#include "cardboy/backend/esp/event_bus.hpp"
#include "cardboy/sdk/event_bus.hpp"
#include "freertos/portmacro.h"
#include <algorithm>
namespace cardboy::backend::esp {
namespace {
[[nodiscard]] TickType_t toTicks(std::uint32_t timeout_ms) {
if (timeout_ms == cardboy::sdk::IEventBus::kWaitForever)
return portMAX_DELAY;
return pdMS_TO_TICKS(timeout_ms);
}
constexpr UBaseType_t kEventQueueLength = 16;
} // namespace
EventBus::EventBus() :
group(xEventGroupCreate()),
eventQueue(xQueueCreate(kEventQueueLength, sizeof(cardboy::sdk::AppEvent))) {}
EventBus::~EventBus() {
if (group)
vEventGroupDelete(group);
if (eventQueue)
vQueueDelete(eventQueue);
}
void EventBus::signal(std::uint32_t bits) {
if (!group || bits == 0)
return;
xEventGroupSetBits(group, bits);
}
void EventBus::signalFromISR(std::uint32_t bits) {
if (!group || bits == 0)
return;
BaseType_t higherPriorityTaskWoken = pdFALSE;
xEventGroupSetBitsFromISR(group, bits, &higherPriorityTaskWoken);
if (higherPriorityTaskWoken == pdTRUE)
portYIELD_FROM_ISR(higherPriorityTaskWoken);
}
std::uint32_t EventBus::wait(std::uint32_t mask, std::uint32_t timeout_ms) {
if (!group || mask == 0)
return 0;
const EventBits_t bits = xEventGroupWaitBits(group, mask, pdTRUE, pdFALSE, toTicks(timeout_ms));
return static_cast<std::uint32_t>(bits & mask);
}
void EventBus::postEvent(const cardboy::sdk::AppEvent& event) {
if (!eventQueue)
return;
if (xQueueSend(eventQueue, &event, 0) == errQUEUE_FULL) {
cardboy::sdk::AppEvent discarded{};
if (xQueueReceive(eventQueue, &discarded, 0) == pdTRUE)
xQueueSend(eventQueue, &event, 0);
}
signal(cardboy::sdk::to_event_bits(cardboy::sdk::EventBusSignal::Timer));
}
bool EventBus::popEvent(cardboy::sdk::AppEvent& outEvent) {
if (!eventQueue)
return false;
return xQueueReceive(eventQueue, &outEvent, 0) == pdTRUE;
}
} // namespace cardboy::backend::esp

View File

@@ -241,7 +241,7 @@ extern "C" void app_main() {
system.registerApp(apps::createTetrisAppFactory()); system.registerApp(apps::createTetrisAppFactory());
system.registerApp(apps::createGameboyAppFactory()); system.registerApp(apps::createGameboyAppFactory());
// start_task_usage_monitor(); start_task_usage_monitor();
system.run(); system.run();
} }

View File

@@ -4,6 +4,11 @@ project(cardboy_sdk LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED YES) set(CMAKE_CXX_STANDARD_REQUIRED YES)
set(CMAKE_CXX_EXTENSIONS NO) set(CMAKE_CXX_EXTENSIONS NO)
# add_compile_options(-Werror -O0 -Wall -Wextra -pedantic -Wno-unused-parameter -Wno-unused-variable
# -Wno-error=unused-function
# -Wshadow -Wformat=2 -Wfloat-equal -D_GLIBCXX_DEBUG -Wconversion)
#add_compile_options(-fsanitize=address -fno-sanitize-recover -D_GLIBCXX_DEBUG)
#add_link_options(-fsanitize=address -fno-sanitize-recover -D_GLIBCXX_DEBUG)
add_subdirectory(utils) add_subdirectory(utils)

View File

@@ -49,7 +49,8 @@ public:
const auto snap = captureTime(); const auto snap = captureTime();
renderIfNeeded(snap); renderIfNeeded(snap);
lastSnapshot = snap; lastSnapshot = snap;
refreshTimer = context.scheduleRepeatingTimer(200); if (auto* timer = context.timer())
refreshTimer = timer->scheduleTimer(200, true);
} }
void onStop() override { cancelRefreshTimer(); } void onStop() override { cancelRefreshTimer(); }
@@ -75,10 +76,11 @@ private:
TimeSnapshot lastSnapshot{}; TimeSnapshot lastSnapshot{};
void cancelRefreshTimer() { void cancelRefreshTimer() {
if (refreshTimer != cardboy::sdk::kInvalidAppTimer) { if (refreshTimer == cardboy::sdk::kInvalidAppTimer)
context.cancelTimer(refreshTimer); return;
refreshTimer = cardboy::sdk::kInvalidAppTimer; if (auto* timer = context.timer())
} timer->cancelTimer(refreshTimer);
refreshTimer = cardboy::sdk::kInvalidAppTimer;
} }
void handleButtonEvent(const cardboy::sdk::AppButtonEvent& button) { void handleButtonEvent(const cardboy::sdk::AppButtonEvent& button) {

View File

@@ -1156,15 +1156,17 @@ public:
uint32_t stableFrames = 0; uint32_t stableFrames = 0;
void cancelTick() { void cancelTick() {
if (tickTimer != kInvalidAppTimer) { if (tickTimer == kInvalidAppTimer)
context.cancelTimer(tickTimer); return;
tickTimer = kInvalidAppTimer; if (auto* timer = context.timer())
} timer->cancelTimer(tickTimer);
tickTimer = kInvalidAppTimer;
} }
void scheduleNextTick(uint32_t delayMs) { void scheduleNextTick(uint32_t delayMs) {
cancelTick(); cancelTick();
tickTimer = context.scheduleTimer(delayMs, false); if (auto* timer = context.timer())
tickTimer = timer->scheduleTimer(delayMs, false);
} }
uint32_t idleDelayMs() const { return browserDirty ? 50 : 140; } uint32_t idleDelayMs() const { return browserDirty ? 50 : 140; }

View File

@@ -69,7 +69,8 @@ public:
const auto snap = captureTime(); const auto snap = captureTime();
renderIfNeeded(snap); renderIfNeeded(snap);
lastSnapshot = snap; lastSnapshot = snap;
refreshTimer = context.scheduleRepeatingTimer(kRefreshIntervalMs); if (auto* timer = context.timer())
refreshTimer = timer->scheduleTimer(kRefreshIntervalMs, true);
} }
void onStop() override { cancelRefreshTimer(); } void onStop() override { cancelRefreshTimer(); }
@@ -104,10 +105,11 @@ private:
std::uint32_t lastNotificationInteractionMs = 0; std::uint32_t lastNotificationInteractionMs = 0;
void cancelRefreshTimer() { void cancelRefreshTimer() {
if (refreshTimer != cardboy::sdk::kInvalidAppTimer) { if (refreshTimer == cardboy::sdk::kInvalidAppTimer)
context.cancelTimer(refreshTimer); return;
refreshTimer = cardboy::sdk::kInvalidAppTimer; if (auto* timer = context.timer())
} timer->cancelTimer(refreshTimer);
refreshTimer = cardboy::sdk::kInvalidAppTimer;
} }
static bool comboPressed(const cardboy::sdk::InputState& state) { return state.a && state.select; } static bool comboPressed(const cardboy::sdk::InputState& state) { return state.a && state.select; }

View File

@@ -189,15 +189,17 @@ private:
} }
void cancelInactivityTimer() { void cancelInactivityTimer() {
if (inactivityTimer != cardboy::sdk::kInvalidAppTimer) { if (inactivityTimer == cardboy::sdk::kInvalidAppTimer)
context.cancelTimer(inactivityTimer); return;
inactivityTimer = cardboy::sdk::kInvalidAppTimer; if (auto* timer = context.timer())
} timer->cancelTimer(inactivityTimer);
inactivityTimer = cardboy::sdk::kInvalidAppTimer;
} }
void resetInactivityTimer() { void resetInactivityTimer() {
cancelInactivityTimer(); cancelInactivityTimer();
inactivityTimer = context.scheduleTimer(kIdleTimeoutMs); if (auto* timer = context.timer())
inactivityTimer = timer->scheduleTimer(kIdleTimeoutMs, false);
} }
}; };

View File

@@ -248,14 +248,16 @@ private:
void scheduleMoveTimer() { void scheduleMoveTimer() {
cancelMoveTimer(); cancelMoveTimer();
const std::uint32_t interval = currentInterval(); const std::uint32_t interval = currentInterval();
moveTimer = context.scheduleRepeatingTimer(interval); if (auto* timer = context.timer())
moveTimer = timer->scheduleTimer(interval, true);
} }
void cancelMoveTimer() { void cancelMoveTimer() {
if (moveTimer != cardboy::sdk::kInvalidAppTimer) { if (moveTimer == cardboy::sdk::kInvalidAppTimer)
context.cancelTimer(moveTimer); return;
moveTimer = cardboy::sdk::kInvalidAppTimer; if (auto* timer = context.timer())
} timer->cancelTimer(moveTimer);
moveTimer = cardboy::sdk::kInvalidAppTimer;
} }
[[nodiscard]] std::uint32_t currentInterval() const { [[nodiscard]] std::uint32_t currentInterval() const {

View File

@@ -235,35 +235,40 @@ private:
void cancelTimers() { void cancelTimers() {
if (dropTimer != cardboy::sdk::kInvalidAppTimer) { if (dropTimer != cardboy::sdk::kInvalidAppTimer) {
context.cancelTimer(dropTimer); if (auto* timer = context.timer())
timer->cancelTimer(dropTimer);
dropTimer = cardboy::sdk::kInvalidAppTimer; dropTimer = cardboy::sdk::kInvalidAppTimer;
} }
cancelSoftDropTimer(); cancelSoftDropTimer();
} }
void cancelSoftDropTimer() { void cancelSoftDropTimer() {
if (softTimer != cardboy::sdk::kInvalidAppTimer) { if (softTimer == cardboy::sdk::kInvalidAppTimer)
context.cancelTimer(softTimer); return;
softTimer = cardboy::sdk::kInvalidAppTimer; if (auto* timer = context.timer())
} timer->cancelTimer(softTimer);
softTimer = cardboy::sdk::kInvalidAppTimer;
} }
void scheduleDropTimer() { void scheduleDropTimer() {
cancelDropTimer(); cancelDropTimer();
const std::uint32_t interval = dropIntervalMs(); const std::uint32_t interval = dropIntervalMs();
dropTimer = context.scheduleRepeatingTimer(interval); if (auto* timer = context.timer())
dropTimer = timer->scheduleTimer(interval, true);
} }
void cancelDropTimer() { void cancelDropTimer() {
if (dropTimer != cardboy::sdk::kInvalidAppTimer) { if (dropTimer == cardboy::sdk::kInvalidAppTimer)
context.cancelTimer(dropTimer); return;
dropTimer = cardboy::sdk::kInvalidAppTimer; if (auto* timer = context.timer())
} timer->cancelTimer(dropTimer);
dropTimer = cardboy::sdk::kInvalidAppTimer;
} }
void scheduleSoftDropTimer() { void scheduleSoftDropTimer() {
cancelSoftDropTimer(); cancelSoftDropTimer();
softTimer = context.scheduleRepeatingTimer(60); if (auto* timer = context.timer())
softTimer = timer->scheduleTimer(60, true);
} }
[[nodiscard]] std::uint32_t dropIntervalMs() const { [[nodiscard]] std::uint32_t dropIntervalMs() const {

View File

@@ -16,7 +16,6 @@ target_sources(cardboy_backend_interface
${CMAKE_CURRENT_SOURCE_DIR}/include/cardboy/backend/backend_interface.hpp ${CMAKE_CURRENT_SOURCE_DIR}/include/cardboy/backend/backend_interface.hpp
${CMAKE_CURRENT_SOURCE_DIR}/include/cardboy/sdk/backend.hpp ${CMAKE_CURRENT_SOURCE_DIR}/include/cardboy/sdk/backend.hpp
${CMAKE_CURRENT_SOURCE_DIR}/include/cardboy/sdk/display_spec.hpp ${CMAKE_CURRENT_SOURCE_DIR}/include/cardboy/sdk/display_spec.hpp
${CMAKE_CURRENT_SOURCE_DIR}/include/cardboy/sdk/event_bus.hpp
${CMAKE_CURRENT_SOURCE_DIR}/include/cardboy/sdk/loop_hooks.hpp ${CMAKE_CURRENT_SOURCE_DIR}/include/cardboy/sdk/loop_hooks.hpp
${CMAKE_CURRENT_SOURCE_DIR}/include/cardboy/sdk/input_state.hpp ${CMAKE_CURRENT_SOURCE_DIR}/include/cardboy/sdk/input_state.hpp
${CMAKE_CURRENT_SOURCE_DIR}/include/cardboy/sdk/platform.hpp ${CMAKE_CURRENT_SOURCE_DIR}/include/cardboy/sdk/platform.hpp

View File

@@ -12,17 +12,13 @@ namespace cardboy::sdk {
using AppTimerHandle = std::uint32_t; using AppTimerHandle = std::uint32_t;
constexpr AppTimerHandle kInvalidAppTimer = 0; constexpr AppTimerHandle kInvalidAppTimer = 0;
using TimerClientId = std::uint32_t;
constexpr TimerClientId kInvalidTimerClient = 0;
struct AppButtonEvent { struct AppButtonEvent {
InputState current{}; InputState current{};
InputState previous{}; InputState previous{};
}; };
struct AppTimerEvent { struct AppTimerEvent {
TimerClientId clientId = kInvalidTimerClient; AppTimerHandle handle = kInvalidAppTimer;
AppTimerHandle handle = kInvalidAppTimer;
}; };
struct AppEvent { struct AppEvent {
@@ -51,6 +47,8 @@ struct AppEvent {
} }
}; };
static_assert(std::is_trivially_copyable_v<AppEvent>);
template<typename... Ts> template<typename... Ts>
struct Overload : Ts... { struct Overload : Ts... {
using Ts::operator()...; using Ts::operator()...;

View File

@@ -1,44 +0,0 @@
#pragma once
#include "cardboy/sdk/app_events.hpp"
#include <cstdint>
namespace cardboy::sdk {
enum class EventBusSignal : std::uint32_t {
None = 0,
Input = 1u << 0,
Timer = 1u << 1,
External = 1u << 2,
};
inline EventBusSignal operator|(EventBusSignal lhs, EventBusSignal rhs) {
return static_cast<EventBusSignal>(static_cast<std::uint32_t>(lhs) | static_cast<std::uint32_t>(rhs));
}
inline EventBusSignal& operator|=(EventBusSignal& lhs, EventBusSignal rhs) {
lhs = lhs | rhs;
return lhs;
}
inline EventBusSignal operator&(EventBusSignal lhs, EventBusSignal rhs) {
return static_cast<EventBusSignal>(static_cast<std::uint32_t>(lhs) & static_cast<std::uint32_t>(rhs));
}
inline std::uint32_t to_event_bits(EventBusSignal signal) { return static_cast<std::uint32_t>(signal); }
class IEventBus {
public:
static constexpr std::uint32_t kWaitForever = 0xFFFFFFFFu;
virtual ~IEventBus() = default;
virtual void signal(std::uint32_t bits) = 0;
virtual void signalFromISR(std::uint32_t bits) = 0;
virtual std::uint32_t wait(std::uint32_t mask, std::uint32_t timeout_ms) = 0;
virtual void postEvent(const AppEvent& event) = 0;
virtual bool popEvent(AppEvent& outEvent) = 0;
};
} // namespace cardboy::sdk

View File

@@ -11,8 +11,8 @@ public:
static void invokePreSend(void* framebuffer); static void invokePreSend(void* framebuffer);
private: private:
static PreSendHook hook_; static PreSendHook _hook;
static void* userData_; static void* _userData;
}; };
} // namespace cardboy::sdk } // namespace cardboy::sdk

View File

@@ -1,10 +1,10 @@
#pragma once #pragma once
#include "cardboy/sdk/event_bus.hpp"
#include "cardboy/sdk/loop_hooks.hpp" #include "cardboy/sdk/loop_hooks.hpp"
#include "cardboy/sdk/timer_service.hpp" #include "cardboy/sdk/timer_service.hpp"
#include <cstdint> #include <cstdint>
#include <memory>
#include <string> #include <string>
#include <string_view> #include <string_view>
#include <vector> #include <vector>
@@ -74,36 +74,56 @@ public:
class INotificationCenter { class INotificationCenter {
public: public:
struct Notification { struct Notification {
std::uint64_t id = 0; std::uint64_t id = 0;
std::uint64_t timestamp = 0; std::uint64_t timestamp = 0;
std::uint64_t externalId = 0; std::uint64_t externalId = 0;
std::string title; std::string title;
std::string body; std::string body;
bool unread = true; bool unread = true;
}; };
virtual ~INotificationCenter() = default; virtual ~INotificationCenter() = default;
virtual void pushNotification(Notification notification) = 0; virtual void pushNotification(Notification notification) = 0;
[[nodiscard]] virtual std::uint32_t revision() const = 0; [[nodiscard]] virtual std::uint32_t revision() const = 0;
[[nodiscard]] virtual std::vector<Notification> recent(std::size_t limit) const = 0; [[nodiscard]] virtual std::vector<Notification> recent(std::size_t limit) const = 0;
virtual void markAllRead() = 0; virtual void markAllRead() = 0;
virtual void clear() = 0; virtual void clear() = 0;
virtual void removeById(std::uint64_t id) = 0; virtual void removeById(std::uint64_t id) = 0;
virtual void removeByExternalId(std::uint64_t externalId) = 0; virtual void removeByExternalId(std::uint64_t externalId) = 0;
};
class IEventBus {
public:
virtual ~IEventBus() = default;
virtual void post(const AppEvent& event) = 0;
virtual AppEvent pop() = 0;
};
struct AppScopedServices {
ITimerService* timer = nullptr;
virtual ~AppScopedServices() = default;
};
class IAppServiceProvider {
public:
virtual ~IAppServiceProvider() = default;
[[nodiscard]] virtual std::unique_ptr<AppScopedServices> createScopedServices(std::uint64_t generation) = 0;
}; };
struct Services { struct Services {
IBuzzer* buzzer = nullptr; IBuzzer* buzzer = nullptr;
IBatteryMonitor* battery = nullptr; IBatteryMonitor* battery = nullptr;
IStorage* storage = nullptr; IStorage* storage = nullptr;
IRandom* random = nullptr; IRandom* random = nullptr;
IHighResClock* highResClock = nullptr; IHighResClock* highResClock = nullptr;
IFilesystem* filesystem = nullptr; IFilesystem* filesystem = nullptr;
IEventBus* eventBus = nullptr; IEventBus* eventBus = nullptr;
ITimerService* timer = nullptr; ILoopHooks* loopHooks = nullptr;
ILoopHooks* loopHooks = nullptr;
INotificationCenter* notifications = nullptr; INotificationCenter* notifications = nullptr;
IAppServiceProvider* appServices = nullptr;
}; };
} // namespace cardboy::sdk } // namespace cardboy::sdk

View File

@@ -10,12 +10,9 @@ class ITimerService {
public: public:
virtual ~ITimerService() = default; virtual ~ITimerService() = default;
virtual TimerClientId acquireClient() = 0; virtual AppTimerHandle scheduleTimer(std::uint32_t delay_ms, bool repeat) = 0;
virtual void releaseClient(TimerClientId clientId) = 0; virtual void cancelTimer(AppTimerHandle handle) = 0;
virtual AppTimerHandle scheduleTimer(TimerClientId clientId, std::uint32_t delay_ms, bool repeat) = 0; virtual void cancelAllTimers() = 0;
virtual void cancelTimer(TimerClientId clientId, AppTimerHandle handle) = 0;
virtual void cancelAllTimers(TimerClientId clientId) = 0;
}; };
} // namespace cardboy::sdk } // namespace cardboy::sdk

View File

@@ -1,6 +1,5 @@
#pragma once #pragma once
#include "cardboy/sdk/event_bus.hpp"
#include "cardboy/sdk/platform.hpp" #include "cardboy/sdk/platform.hpp"
#include "cardboy/sdk/services.hpp" #include "cardboy/sdk/services.hpp"
@@ -12,6 +11,7 @@
#include <cstdint> #include <cstdint>
#include <filesystem> #include <filesystem>
#include <limits> #include <limits>
#include <memory>
#include <mutex> #include <mutex>
#include <random> #include <random>
#include <string> #include <string>
@@ -113,33 +113,39 @@ public:
void signal(std::uint32_t bits) override; void signal(std::uint32_t bits) override;
void signalFromISR(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; std::uint32_t wait(std::uint32_t mask, std::uint32_t timeout_ms) override;
void postEvent(const cardboy::sdk::AppEvent& event) override;
bool popEvent(cardboy::sdk::AppEvent& outEvent) override;
private: private:
DesktopRuntime& runtime; DesktopRuntime& runtime;
std::mutex mutex; std::mutex mutex;
std::condition_variable cv; std::condition_variable cv;
std::uint32_t pendingBits = 0; std::uint32_t pendingBits = 0;
std::mutex eventMutex; };
std::deque<cardboy::sdk::AppEvent> eventQueue;
class DesktopScopedEventBus final : public cardboy::sdk::IAppEventBus {
public:
explicit DesktopScopedEventBus(cardboy::sdk::IEventBus& bus);
void post(const cardboy::sdk::AppEvent& event) override;
bool pop(cardboy::sdk::AppEvent& outEvent) override;
void clear() override;
private:
cardboy::sdk::IEventBus& globalBus;
std::mutex mutex;
std::deque<cardboy::sdk::AppEvent> queue;
}; };
class DesktopTimerService final : public cardboy::sdk::ITimerService { class DesktopTimerService final : public cardboy::sdk::ITimerService {
public: public:
DesktopTimerService(DesktopRuntime& owner, cardboy::sdk::IEventBus& bus); DesktopTimerService(DesktopRuntime& owner, cardboy::sdk::IAppEventBus& appBus);
~DesktopTimerService() override; ~DesktopTimerService() override;
cardboy::sdk::TimerClientId acquireClient() override; cardboy::sdk::AppTimerHandle scheduleTimer(std::uint32_t delay_ms, bool repeat) override;
void releaseClient(cardboy::sdk::TimerClientId clientId) override; void cancelTimer(cardboy::sdk::AppTimerHandle handle) override;
cardboy::sdk::AppTimerHandle scheduleTimer(cardboy::sdk::TimerClientId clientId, std::uint32_t delay_ms, void cancelAllTimers() override;
bool repeat) override;
void cancelTimer(cardboy::sdk::TimerClientId clientId, cardboy::sdk::AppTimerHandle handle) override;
void cancelAllTimers(cardboy::sdk::TimerClientId clientId) override;
private: private:
struct TimerRecord { struct TimerRecord {
cardboy::sdk::TimerClientId clientId = cardboy::sdk::kInvalidTimerClient;
cardboy::sdk::AppTimerHandle handle = cardboy::sdk::kInvalidAppTimer; cardboy::sdk::AppTimerHandle handle = cardboy::sdk::kInvalidAppTimer;
std::chrono::steady_clock::time_point due; std::chrono::steady_clock::time_point due;
std::chrono::milliseconds interval{0}; std::chrono::milliseconds interval{0};
@@ -151,15 +157,30 @@ private:
void wakeWorker(); void wakeWorker();
void cleanupInactive(); void cleanupInactive();
DesktopRuntime& runtime; DesktopRuntime& runtime;
cardboy::sdk::IEventBus& eventBus; cardboy::sdk::IAppEventBus& appEventBus;
std::mutex mutex; std::mutex mutex;
std::condition_variable cv; std::condition_variable cv;
std::vector<TimerRecord> timers; std::vector<TimerRecord> timers;
bool stopWorker = false; bool stopWorker = false;
std::thread worker; std::thread worker;
cardboy::sdk::AppTimerHandle nextHandle = 1; cardboy::sdk::AppTimerHandle nextHandle = 1;
cardboy::sdk::TimerClientId nextClient = 1; };
class DesktopAppServiceProvider final : public cardboy::sdk::IAppServiceProvider {
public:
DesktopAppServiceProvider(DesktopRuntime& owner, cardboy::sdk::IEventBus& bus);
[[nodiscard]] std::unique_ptr<cardboy::sdk::AppScopedServices> createScopedServices(std::uint64_t generation) override;
private:
struct ScopedServices final : cardboy::sdk::AppScopedServices {
std::unique_ptr<DesktopScopedEventBus> ownedEventBus;
std::unique_ptr<DesktopTimerService> ownedTimer;
};
DesktopRuntime& runtime;
cardboy::sdk::IEventBus& eventBus;
}; };
class DesktopFramebuffer final : public cardboy::sdk::FramebufferFacade<DesktopFramebuffer> { class DesktopFramebuffer final : public cardboy::sdk::FramebufferFacade<DesktopFramebuffer> {
@@ -240,7 +261,7 @@ private:
DesktopHighResClock highResService; DesktopHighResClock highResService;
DesktopFilesystem filesystemService; DesktopFilesystem filesystemService;
DesktopEventBus eventBusService; DesktopEventBus eventBusService;
DesktopTimerService timerService; DesktopAppServiceProvider appServiceProvider;
DesktopNotificationCenter notificationService; DesktopNotificationCenter notificationService;
cardboy::sdk::Services services{}; cardboy::sdk::Services services{};
}; };

View File

@@ -63,26 +63,34 @@ std::uint32_t DesktopEventBus::wait(std::uint32_t mask, std::uint32_t timeout_ms
} }
} }
void DesktopEventBus::postEvent(const cardboy::sdk::AppEvent& event) { DesktopScopedEventBus::DesktopScopedEventBus(cardboy::sdk::IEventBus& bus) : globalBus(bus) {}
void DesktopScopedEventBus::post(const cardboy::sdk::AppEvent& event) {
{ {
std::lock_guard<std::mutex> lock(eventMutex); std::lock_guard<std::mutex> lock(mutex);
if (eventQueue.size() >= kDesktopEventQueueLimit) if (queue.size() >= kDesktopEventQueueLimit)
eventQueue.pop_front(); queue.pop_front();
eventQueue.push_back(event); queue.push_back(event);
} }
signal(cardboy::sdk::to_event_bits(cardboy::sdk::EventBusSignal::Timer)); globalBus.signal(cardboy::sdk::to_event_bits(cardboy::sdk::EventBusSignal::Timer));
} }
bool DesktopEventBus::popEvent(cardboy::sdk::AppEvent& outEvent) { bool DesktopScopedEventBus::pop(cardboy::sdk::AppEvent& outEvent) {
std::lock_guard<std::mutex> lock(eventMutex); std::lock_guard<std::mutex> lock(mutex);
if (eventQueue.empty()) if (queue.empty())
return false; return false;
outEvent = eventQueue.front(); outEvent = queue.front();
eventQueue.pop_front(); queue.pop_front();
return true; return true;
} }
DesktopTimerService::DesktopTimerService(DesktopRuntime& owner, cardboy::sdk::IEventBus& bus) : runtime(owner), eventBus(bus) { void DesktopScopedEventBus::clear() {
std::lock_guard<std::mutex> lock(mutex);
queue.clear();
}
DesktopTimerService::DesktopTimerService(DesktopRuntime& owner, cardboy::sdk::IAppEventBus& appBus) :
runtime(owner), appEventBus(appBus) {
worker = std::thread(&DesktopTimerService::workerLoop, this); worker = std::thread(&DesktopTimerService::workerLoop, this);
} }
@@ -94,33 +102,17 @@ DesktopTimerService::~DesktopTimerService() {
cv.notify_all(); cv.notify_all();
if (worker.joinable()) if (worker.joinable())
worker.join(); worker.join();
cancelAllTimers();
} }
cardboy::sdk::TimerClientId DesktopTimerService::acquireClient() { cardboy::sdk::AppTimerHandle DesktopTimerService::scheduleTimer(std::uint32_t delay_ms, bool repeat) {
std::lock_guard<std::mutex> lock(mutex);
cardboy::sdk::TimerClientId id = cardboy::sdk::kInvalidTimerClient;
do {
id = nextClient++;
} while (id == cardboy::sdk::kInvalidTimerClient);
if (nextClient == cardboy::sdk::kInvalidTimerClient)
++nextClient;
return id;
}
void DesktopTimerService::releaseClient(cardboy::sdk::TimerClientId clientId) { cancelAllTimers(clientId); }
cardboy::sdk::AppTimerHandle DesktopTimerService::scheduleTimer(cardboy::sdk::TimerClientId clientId,
std::uint32_t delay_ms, bool repeat) {
if (clientId == cardboy::sdk::kInvalidTimerClient)
return cardboy::sdk::kInvalidAppTimer;
const auto now = std::chrono::steady_clock::now(); const auto now = std::chrono::steady_clock::now();
const auto effectiveDelayMs = std::chrono::milliseconds(delay_ms); const auto effectiveDelayMs = std::chrono::milliseconds(delay_ms);
const auto dueTime = delay_ms == 0 ? now : now + effectiveDelayMs; const auto dueTime = delay_ms == 0 ? now : now + effectiveDelayMs;
const auto interval = std::chrono::milliseconds(std::max<std::uint32_t>(1, repeat ? delay_ms : std::max(delay_ms, 1u))); const auto interval = std::chrono::milliseconds(std::max<std::uint32_t>(1, repeat ? std::max(delay_ms, 1u)
: std::max(delay_ms, 1u)));
TimerRecord record{}; TimerRecord record{};
record.clientId = clientId;
record.repeat = repeat; record.repeat = repeat;
record.interval = interval; record.interval = interval;
record.due = dueTime; record.due = dueTime;
@@ -141,25 +133,22 @@ cardboy::sdk::AppTimerHandle DesktopTimerService::scheduleTimer(cardboy::sdk::Ti
} }
} }
void DesktopTimerService::cancelTimer(cardboy::sdk::TimerClientId clientId, cardboy::sdk::AppTimerHandle handle) { void DesktopTimerService::cancelTimer(cardboy::sdk::AppTimerHandle handle) {
if (clientId == cardboy::sdk::kInvalidTimerClient || handle == cardboy::sdk::kInvalidAppTimer) if (handle == cardboy::sdk::kInvalidAppTimer)
return; return;
std::lock_guard<std::mutex> lock(mutex); std::lock_guard<std::mutex> lock(mutex);
for (auto& record: timers) { for (auto& record: timers) {
if (record.clientId == clientId && record.handle == handle) if (record.handle == handle)
record.active = false; record.active = false;
} }
cleanupInactive(); cleanupInactive();
wakeWorker(); wakeWorker();
} }
void DesktopTimerService::cancelAllTimers(cardboy::sdk::TimerClientId clientId) { void DesktopTimerService::cancelAllTimers() {
if (clientId == cardboy::sdk::kInvalidTimerClient)
return;
std::lock_guard<std::mutex> lock(mutex); std::lock_guard<std::mutex> lock(mutex);
for (auto& record: timers) { for (auto& record: timers) {
if (record.clientId == clientId) record.active = false;
record.active = false;
} }
cleanupInactive(); cleanupInactive();
wakeWorker(); wakeWorker();
@@ -195,13 +184,12 @@ void DesktopTimerService::workerLoop() {
lock.unlock(); lock.unlock();
cardboy::sdk::AppTimerEvent timerEvent{}; cardboy::sdk::AppTimerEvent timerEvent{};
timerEvent.clientId = record.clientId; timerEvent.handle = record.handle;
timerEvent.handle = record.handle;
cardboy::sdk::AppEvent event{}; cardboy::sdk::AppEvent event{};
event.timestamp_ms = runtime.clock.millis(); event.timestamp_ms = runtime.clock.millis();
event.data = timerEvent; event.data = timerEvent;
eventBus.postEvent(event); appEventBus.post(event);
lock.lock(); lock.lock();
continue; continue;
@@ -218,6 +206,19 @@ void DesktopTimerService::cleanupInactive() {
timers.end()); timers.end());
} }
DesktopAppServiceProvider::DesktopAppServiceProvider(DesktopRuntime& owner, cardboy::sdk::IEventBus& bus) :
runtime(owner), eventBus(bus) {}
std::unique_ptr<cardboy::sdk::AppScopedServices> DesktopAppServiceProvider::createScopedServices(std::uint64_t generation) {
(void)generation;
auto scoped = std::make_unique<ScopedServices>();
scoped->ownedEventBus = std::make_unique<DesktopScopedEventBus>(eventBus);
scoped->events = scoped->ownedEventBus.get();
scoped->ownedTimer = std::make_unique<DesktopTimerService>(runtime, *scoped->ownedEventBus);
scoped->timer = scoped->ownedTimer.get();
return scoped;
}
bool DesktopStorage::readUint32(std::string_view ns, std::string_view key, std::uint32_t& out) { bool DesktopStorage::readUint32(std::string_view ns, std::string_view key, std::uint32_t& out) {
auto it = data.find(composeKey(ns, key)); auto it = data.find(composeKey(ns, key));
if (it == data.end()) if (it == data.end())
@@ -441,7 +442,7 @@ DesktopRuntime::DesktopRuntime() :
"Cardboy Desktop"), "Cardboy Desktop"),
texture(), sprite(texture), texture(), sprite(texture),
pixels(static_cast<std::size_t>(cardboy::sdk::kDisplayWidth * cardboy::sdk::kDisplayHeight) * 4, 0), pixels(static_cast<std::size_t>(cardboy::sdk::kDisplayWidth * cardboy::sdk::kDisplayHeight) * 4, 0),
framebuffer(*this), input(*this), clock(*this), eventBusService(*this), timerService(*this, eventBusService) { framebuffer(*this), input(*this), clock(*this), eventBusService(*this), appServiceProvider(*this, eventBusService) {
window.setFramerateLimit(60); window.setFramerateLimit(60);
if (!texture.resize(sf::Vector2u{cardboy::sdk::kDisplayWidth, cardboy::sdk::kDisplayHeight})) if (!texture.resize(sf::Vector2u{cardboy::sdk::kDisplayWidth, cardboy::sdk::kDisplayHeight}))
throw std::runtime_error("Failed to allocate texture for desktop framebuffer"); throw std::runtime_error("Failed to allocate texture for desktop framebuffer");
@@ -457,7 +458,7 @@ DesktopRuntime::DesktopRuntime() :
services.highResClock = &highResService; services.highResClock = &highResService;
services.filesystem = &filesystemService; services.filesystem = &filesystemService;
services.eventBus = &eventBusService; services.eventBus = &eventBusService;
services.timer = &timerService; services.appServices = &appServiceProvider;
services.loopHooks = nullptr; services.loopHooks = nullptr;
services.notifications = &notificationService; services.notifications = &notificationService;
} }

View File

@@ -33,66 +33,44 @@ struct AppContext {
[[nodiscard]] Services* getServices() const { return services; } [[nodiscard]] Services* getServices() const { return services; }
[[nodiscard]] IBuzzer* buzzer() const { return services ? services->buzzer : nullptr; } [[nodiscard]] IBuzzer* buzzer() const { return services ? services->buzzer : nullptr; }
[[nodiscard]] IBatteryMonitor* battery() const { return services ? services->battery : nullptr; } [[nodiscard]] IBatteryMonitor* battery() const { return services ? services->battery : nullptr; }
[[nodiscard]] IStorage* storage() const { return services ? services->storage : nullptr; } [[nodiscard]] IStorage* storage() const { return services ? services->storage : nullptr; }
[[nodiscard]] IRandom* random() const { return services ? services->random : nullptr; } [[nodiscard]] IRandom* random() const { return services ? services->random : nullptr; }
[[nodiscard]] IHighResClock* highResClock() const { return services ? services->highResClock : nullptr; } [[nodiscard]] IHighResClock* highResClock() const { return services ? services->highResClock : nullptr; }
[[nodiscard]] IFilesystem* filesystem() const { return services ? services->filesystem : nullptr; } [[nodiscard]] IFilesystem* filesystem() const { return services ? services->filesystem : nullptr; }
[[nodiscard]] IEventBus* eventBus() const { return services ? services->eventBus : nullptr; } [[nodiscard]] AppScopedServices* appServices() const { return _scopedServices; }
[[nodiscard]] ITimerService* timerService() const { return services ? services->timer : nullptr; } [[nodiscard]] IEventBus* eventBus() const { return services ? services->eventBus : nullptr; }
[[nodiscard]] ILoopHooks* loopHooks() const { return services ? services->loopHooks : nullptr; } [[nodiscard]] ITimerService* timer() const { return _scopedServices ? _scopedServices->timer : nullptr; }
[[nodiscard]] ILoopHooks* loopHooks() const { return services ? services->loopHooks : nullptr; }
[[nodiscard]] INotificationCenter* notificationCenter() const { [[nodiscard]] INotificationCenter* notificationCenter() const {
return services ? services->notifications : nullptr; return services ? services->notifications : nullptr;
} }
void requestAppSwitchByIndex(std::size_t index) { void requestAppSwitchByIndex(std::size_t index) {
pendingAppIndex = index; _pendingAppIndex = index;
pendingAppName.clear(); _pendingAppName.clear();
pendingSwitchByName = false; _pendingSwitchByName = false;
pendingSwitch = true; _pendingSwitch = true;
} }
void requestAppSwitchByName(std::string_view name) { void requestAppSwitchByName(std::string_view name) {
pendingAppName.assign(name.begin(), name.end()); _pendingAppName.assign(name.begin(), name.end());
pendingSwitchByName = true; _pendingSwitchByName = true;
pendingSwitch = true; _pendingSwitch = true;
} }
[[nodiscard]] bool hasPendingAppSwitch() const { return pendingSwitch; } [[nodiscard]] bool hasPendingAppSwitch() const { return _pendingSwitch; }
AppTimerHandle scheduleTimer(std::uint32_t delay_ms, bool repeat = false) {
auto* timer = timerService();
if (!timer || timerClientId == kInvalidTimerClient)
return kInvalidAppTimer;
return timer->scheduleTimer(timerClientId, delay_ms, repeat);
}
AppTimerHandle scheduleRepeatingTimer(std::uint32_t interval_ms) {
return scheduleTimer(interval_ms, true);
}
void cancelTimer(AppTimerHandle handle) {
auto* timer = timerService();
if (!timer || timerClientId == kInvalidTimerClient)
return;
timer->cancelTimer(timerClientId, handle);
}
void cancelAllTimers() {
auto* timer = timerService();
if (!timer || timerClientId == kInvalidTimerClient)
return;
timer->cancelAllTimers(timerClientId);
}
private: private:
friend class AppSystem; friend class AppSystem;
bool pendingSwitch = false; bool _pendingSwitch = false;
bool pendingSwitchByName = false; bool _pendingSwitchByName = false;
std::size_t pendingAppIndex = 0; std::size_t _pendingAppIndex = 0;
std::string pendingAppName; std::string _pendingAppName;
TimerClientId timerClientId = kInvalidTimerClient; AppScopedServices* _scopedServices = nullptr;
void setScopedServices(AppScopedServices* services) { _scopedServices = services; }
}; };
class IApp { class IApp {

View File

@@ -1,6 +1,5 @@
#pragma once #pragma once
#include <cardboy/sdk/event_bus.hpp>
#include "app_framework.hpp" #include "app_framework.hpp"
#include <cstdint> #include <cstdint>
@@ -16,36 +15,30 @@ public:
~AppSystem(); ~AppSystem();
void registerApp(std::unique_ptr<IAppFactory> factory); void registerApp(std::unique_ptr<IAppFactory> factory);
bool startApp(const std::string& name); void startApp(const std::string& name);
bool startAppByIndex(std::size_t index); void startAppByIndex(std::size_t index);
void run(); void run();
[[nodiscard]] std::size_t appCount() const { return factories.size(); } [[nodiscard]] std::size_t appCount() const { return _factories.size(); }
[[nodiscard]] const IAppFactory* factoryAt(std::size_t index) const; [[nodiscard]] const IAppFactory* factoryAt(std::size_t index) const;
[[nodiscard]] std::size_t indexOfFactory(const IAppFactory* factory) const; [[nodiscard]] std::size_t indexOfFactory(const IAppFactory* factory) const;
[[nodiscard]] std::size_t currentFactoryIndex() const { return activeIndex; } [[nodiscard]] std::size_t currentFactoryIndex() const { return _activeIndex; }
[[nodiscard]] const IApp* currentApp() const { return current.get(); } [[nodiscard]] const IApp* currentApp() const { return _current.get(); }
[[nodiscard]] const IAppFactory* currentFactory() const { return activeFactory; } [[nodiscard]] const IAppFactory* currentFactory() const { return _activeFactory; }
private: private:
friend struct AppContext; void handlePendingSwitchRequest();
std::unique_ptr<AppScopedServices> createAppScopedServices(std::uint64_t generation);
void dispatchEvent(const AppEvent& event); AppContext _context;
bool handlePendingSwitchRequest(); std::vector<std::unique_ptr<IAppFactory>> _factories;
void attachTimerClient(); std::unique_ptr<IApp> _current;
void detachTimerClient(); IAppFactory* _activeFactory = nullptr;
void processEventBusEvents(); std::size_t _activeIndex = static_cast<std::size_t>(-1);
std::unique_ptr<AppScopedServices> _scopedServices;
AppContext context; std::uint64_t _nextScopedGeneration = 1;
std::vector<std::unique_ptr<IAppFactory>> factories;
std::unique_ptr<IApp> current;
IAppFactory* activeFactory = nullptr;
std::size_t activeIndex = static_cast<std::size_t>(-1);
TimerClientId timerClientId = kInvalidTimerClient;
InputState lastInputState{};
bool suppressInputs = false;
}; };
} // namespace cardboy::sdk } // namespace cardboy::sdk

View File

@@ -13,8 +13,8 @@ public:
static void invokePreSend(void* framebuffer); static void invokePreSend(void* framebuffer);
private: private:
static PreSendHook hook_; static PreSendHook _hook;
static void* userData_; static void* _userData;
}; };
} // namespace cardboy::sdk } // namespace cardboy::sdk

View File

@@ -17,11 +17,11 @@ class StatusBar {
public: public:
static StatusBar& instance(); static StatusBar& instance();
void setServices(Services* services) { services_ = services; } void setServices(Services* services) { _services = services; }
void setEnabled(bool value); void setEnabled(bool value);
void toggle(); void toggle();
[[nodiscard]] bool isEnabled() const { return enabled_; } [[nodiscard]] bool isEnabled() const { return _enabled; }
void setCurrentAppName(std::string_view name); void setCurrentAppName(std::string_view name);
@@ -29,7 +29,7 @@ public:
template<typename Framebuffer> template<typename Framebuffer>
void renderIfEnabled(Framebuffer& fb) { void renderIfEnabled(Framebuffer& fb) {
if (!enabled_) if (!_enabled)
return; return;
renderBar(fb); renderBar(fb);
} }
@@ -84,9 +84,9 @@ private:
[[nodiscard]] std::string prepareLeftText(int displayWidth) const; [[nodiscard]] std::string prepareLeftText(int displayWidth) const;
[[nodiscard]] std::string prepareRightText() const; [[nodiscard]] std::string prepareRightText() const;
bool enabled_ = false; bool _enabled = false;
Services* services_ = nullptr; Services* _services = nullptr;
std::string appName_{}; std::string _appName{};
}; };
} // namespace cardboy::sdk } // namespace cardboy::sdk

View File

@@ -7,14 +7,6 @@
namespace cardboy::sdk { namespace cardboy::sdk {
namespace { namespace {
[[nodiscard]] bool inputsDiffer(const InputState& a, const InputState& b) {
return a.up != b.up || a.down != b.down || a.left != b.left || a.right != b.right || a.a != b.a || a.b != b.b ||
a.select != b.select || a.start != b.start;
}
[[nodiscard]] bool anyButtonPressed(const InputState& state) {
return state.up || state.down || state.left || state.right || state.a || state.b || state.select || state.start;
}
template<typename Framebuffer> template<typename Framebuffer>
void statusBarPreSendHook(void* framebuffer, void* userData) { void statusBarPreSendHook(void* framebuffer, void* userData) {
@@ -25,191 +17,120 @@ void statusBarPreSendHook(void* framebuffer, void* userData) {
} }
} // namespace } // namespace
AppSystem::AppSystem(AppContext ctx) : context(std::move(ctx)) { AppSystem::AppSystem(AppContext ctx) : _context(std::move(ctx)) {
context.system = this; _context.system = this;
auto& statusBar = StatusBar::instance(); auto& statusBar = StatusBar::instance();
statusBar.setServices(context.services); statusBar.setServices(_context.services);
using FBType = typename AppContext::Framebuffer; FramebufferHooks::setPreSendHook(&statusBarPreSendHook<AppContext::Framebuffer>, &statusBar);
FramebufferHooks::setPreSendHook(&statusBarPreSendHook<FBType>, &statusBar);
} }
AppSystem::~AppSystem() { AppSystem::~AppSystem() { FramebufferHooks::clearPreSendHook(); }
detachTimerClient();
FramebufferHooks::clearPreSendHook();
}
void AppSystem::registerApp(std::unique_ptr<IAppFactory> factory) { void AppSystem::registerApp(std::unique_ptr<IAppFactory> factory) {
if (!factory) assert(factory);
return; _factories.emplace_back(std::move(factory));
factories.emplace_back(std::move(factory));
} }
bool AppSystem::startApp(const std::string& name) { void AppSystem::startApp(const std::string& name) {
for (std::size_t i = 0; i < factories.size(); ++i) { for (std::size_t i = 0; i < _factories.size(); ++i) {
if (factories[i]->name() == name) if (_factories[i]->name() == name)
return startAppByIndex(i); startAppByIndex(i);
} }
return false;
} }
bool AppSystem::startAppByIndex(std::size_t index) { void AppSystem::startAppByIndex(std::size_t index) {
if (index >= factories.size()) assert(index < _factories.size());
return false;
context.system = this; if (_current) {
auto& factory = factories[index]; _current->onStop();
auto app = factory->create(context); _current.reset();
if (!app)
return false;
if (current) {
current->onStop();
current.reset();
detachTimerClient();
} }
activeFactory = factory.get(); _context.system = this;
activeIndex = index;
context.pendingSwitch = false; auto& factory = _factories[index];
context.pendingSwitchByName = false;
context.pendingAppName.clear(); const std::uint64_t newGeneration = _nextScopedGeneration++;
current = std::move(app);
attachTimerClient(); auto app = factory->create(_context);
lastInputState = context.input.readState(); assert(app);
suppressInputs = true;
StatusBar::instance().setServices(context.services); _scopedServices.reset();
StatusBar::instance().setCurrentAppName(activeFactory ? activeFactory->name() : ""); auto scoped = createAppScopedServices(newGeneration);
current->onStart(); _scopedServices = std::move(scoped);
return true; _context.setScopedServices(_scopedServices.get());
_activeFactory = factory.get();
_activeIndex = index;
_context._pendingSwitch = false;
_context._pendingSwitchByName = false;
_context._pendingAppName.clear();
_current = std::move(app);
StatusBar::instance().setServices(_context.services);
StatusBar::instance().setCurrentAppName(_activeFactory ? _activeFactory->name() : "");
_current->onStart();
} }
void AppSystem::run() { void AppSystem::run() {
if (!current) { if (!_current) {
if (factories.empty() || !startAppByIndex(0)) assert(!_factories.empty());
return; startAppByIndex(0);
} }
while (true) { while (true) {
auto* eventBus = context.eventBus(); if (auto* hooks = _context.loopHooks())
if (!eventBus)
return;
if (auto* hooks = context.loopHooks())
hooks->onLoopIteration(); hooks->onLoopIteration();
processEventBusEvents(); auto event = _context.eventBus()->pop();
if (handlePendingSwitchRequest()) if (const auto* btn = event.button()) {
continue; const bool consumedByStatusToggle = StatusBar::instance().handleToggleInput(btn->current, btn->previous);
if (consumedByStatusToggle) {
const std::uint32_t now = context.clock.millis(); continue;
const InputState inputNow = context.input.readState(); }
const bool consumedByStatusToggle = StatusBar::instance().handleToggleInput(inputNow, lastInputState);
if (suppressInputs) {
lastInputState = inputNow;
if (!anyButtonPressed(inputNow))
suppressInputs = false;
} else if (!consumedByStatusToggle && inputsDiffer(inputNow, lastInputState)) {
AppButtonEvent button{};
button.current = inputNow;
button.previous = lastInputState;
AppEvent evt{};
evt.timestamp_ms = now;
evt.data = button;
lastInputState = inputNow;
dispatchEvent(evt);
} else if (consumedByStatusToggle) {
lastInputState = inputNow;
} }
_current->handleEvent(event);
if (handlePendingSwitchRequest()) if (_context._pendingSwitch) {
handlePendingSwitchRequest();
continue; continue;
}
const std::uint32_t mask = to_event_bits(EventBusSignal::Input) | to_event_bits(EventBusSignal::Timer);
eventBus->wait(mask, IEventBus::kWaitForever);
} }
} }
const IAppFactory* AppSystem::factoryAt(std::size_t index) const { const IAppFactory* AppSystem::factoryAt(std::size_t index) const {
if (index >= factories.size()) if (index >= _factories.size())
return nullptr; return nullptr;
return factories[index].get(); return _factories[index].get();
} }
std::size_t AppSystem::indexOfFactory(const IAppFactory* factory) const { std::size_t AppSystem::indexOfFactory(const IAppFactory* factory) const {
if (!factory) if (!factory)
return static_cast<std::size_t>(-1); return static_cast<std::size_t>(-1);
for (std::size_t i = 0; i < factories.size(); ++i) { for (std::size_t i = 0; i < _factories.size(); ++i) {
if (factories[i].get() == factory) if (_factories[i].get() == factory)
return i; return i;
} }
return static_cast<std::size_t>(-1); return static_cast<std::size_t>(-1);
} }
void AppSystem::dispatchEvent(const AppEvent& event) { void AppSystem::handlePendingSwitchRequest() {
if (!current) assert(_context._pendingSwitch);
return; const bool byName = _context._pendingSwitchByName;
const std::size_t reqIndex = _context._pendingAppIndex;
if (const auto* timer = event.timer()) { const std::string reqName = _context._pendingAppName;
if (timer->clientId != timerClientId) _context._pendingSwitch = false;
return; _context._pendingSwitchByName = false;
} _context._pendingAppName.clear();
current->handleEvent(event);
}
bool AppSystem::handlePendingSwitchRequest() {
if (!context.pendingSwitch)
return false;
const bool byName = context.pendingSwitchByName;
const std::size_t reqIndex = context.pendingAppIndex;
const std::string reqName = context.pendingAppName;
context.pendingSwitch = false;
context.pendingSwitchByName = false;
context.pendingAppName.clear();
bool switched = false; bool switched = false;
if (byName) if (byName)
switched = startApp(reqName); startApp(reqName);
else else
switched = startAppByIndex(reqIndex); startAppByIndex(reqIndex);
return switched;
} }
void AppSystem::attachTimerClient() { std::unique_ptr<AppScopedServices> AppSystem::createAppScopedServices(std::uint64_t generation) {
auto* timer = context.timerService(); if (!_context.services || !_context.services->appServices)
if (!timer) { return nullptr;
timerClientId = kInvalidTimerClient; return _context.services->appServices->createScopedServices(generation);
context.timerClientId = kInvalidTimerClient;
return;
}
timerClientId = timer->acquireClient();
context.timerClientId = timerClientId;
}
void AppSystem::detachTimerClient() {
auto* timer = context.timerService();
if (!timer || timerClientId == kInvalidTimerClient)
return;
timer->releaseClient(timerClientId);
timerClientId = kInvalidTimerClient;
context.timerClientId = kInvalidTimerClient;
}
void AppSystem::processEventBusEvents() {
auto* eventBus = context.eventBus();
if (!eventBus)
return;
AppEvent event{};
while (eventBus->popEvent(event)) {
dispatchEvent(event);
if (context.pendingSwitch)
break;
}
} }
} // namespace cardboy::sdk } // namespace cardboy::sdk

View File

@@ -2,22 +2,22 @@
namespace cardboy::sdk { namespace cardboy::sdk {
FramebufferHooks::PreSendHook FramebufferHooks::hook_ = nullptr; FramebufferHooks::PreSendHook FramebufferHooks::_hook = nullptr;
void* FramebufferHooks::userData_ = nullptr; void* FramebufferHooks::_userData = nullptr;
void FramebufferHooks::setPreSendHook(PreSendHook hook, void* userData) { void FramebufferHooks::setPreSendHook(PreSendHook hook, void* userData) {
hook_ = hook; _hook = hook;
userData_ = userData; _userData = userData;
} }
void FramebufferHooks::clearPreSendHook() { void FramebufferHooks::clearPreSendHook() {
hook_ = nullptr; _hook = nullptr;
userData_ = nullptr; _userData = nullptr;
} }
void FramebufferHooks::invokePreSend(void* framebuffer) { void FramebufferHooks::invokePreSend(void* framebuffer) {
if (hook_) if (_hook)
hook_(framebuffer, userData_); _hook(framebuffer, _userData);
} }
} // namespace cardboy::sdk } // namespace cardboy::sdk

View File

@@ -12,17 +12,17 @@ StatusBar& StatusBar::instance() {
return bar; return bar;
} }
void StatusBar::setEnabled(bool value) { enabled_ = value; } void StatusBar::setEnabled(bool value) { _enabled = value; }
void StatusBar::toggle() { void StatusBar::toggle() {
enabled_ = !enabled_; _enabled = !_enabled;
if (services_ && services_->buzzer) if (_services && _services->buzzer)
services_->buzzer->beepMove(); _services->buzzer->beepMove();
} }
void StatusBar::setCurrentAppName(std::string_view name) { void StatusBar::setCurrentAppName(std::string_view name) {
appName_.assign(name.begin(), name.end()); _appName.assign(name.begin(), name.end());
std::transform(appName_.begin(), appName_.end(), appName_.begin(), std::transform(_appName.begin(), _appName.end(), _appName.begin(),
[](unsigned char ch) { return static_cast<char>(std::toupper(ch)); }); [](unsigned char ch) { return static_cast<char>(std::toupper(ch)); });
} }
@@ -37,7 +37,7 @@ bool StatusBar::handleToggleInput(const InputState& current, const InputState& p
} }
std::string StatusBar::prepareLeftText(int displayWidth) const { std::string StatusBar::prepareLeftText(int displayWidth) const {
std::string text = appName_.empty() ? std::string("CARDBOY") : appName_; std::string text = _appName.empty() ? std::string("CARDBOY") : _appName;
int maxWidth = std::max(0, displayWidth - 32); int maxWidth = std::max(0, displayWidth - 32);
while (!text.empty() && font16x8::measureText(text, 1, 1) > maxWidth) while (!text.empty() && font16x8::measureText(text, 1, 1) > maxWidth)
text.pop_back(); text.pop_back();
@@ -45,14 +45,14 @@ std::string StatusBar::prepareLeftText(int displayWidth) const {
} }
std::string StatusBar::prepareRightText() const { std::string StatusBar::prepareRightText() const {
if (!services_) if (!_services)
return {}; return {};
std::string right; std::string right;
if (services_->battery && services_->battery->hasData()) { if (_services->battery && _services->battery->hasData()) {
const float current = services_->battery->current(); const float current = _services->battery->current();
const float chargeMah = services_->battery->charge(); const float chargeMah = _services->battery->charge();
const float fallbackV = services_->battery->voltage(); const float fallbackV = _services->battery->voltage();
char buf[64]; char buf[64];
if (std::isfinite(current) && std::isfinite(chargeMah)) { if (std::isfinite(current) && std::isfinite(chargeMah)) {
std::snprintf(buf, sizeof(buf), "cur %.2fmA chr %.2fmAh", static_cast<double>(current), std::snprintf(buf, sizeof(buf), "cur %.2fmA chr %.2fmAh", static_cast<double>(current),
@@ -63,7 +63,7 @@ std::string StatusBar::prepareRightText() const {
right.assign(buf); right.assign(buf);
} }
if (services_->buzzer && services_->buzzer->isMuted()) { if (_services->buzzer && _services->buzzer->isMuted()) {
if (!right.empty()) if (!right.empty())
right.append(" "); right.append(" ");
right.append("MUTE"); right.append("MUTE");

View File

@@ -613,9 +613,9 @@ CONFIG_PARTITION_TABLE_MD5=y
# #
# Compiler options # Compiler options
# #
# CONFIG_COMPILER_OPTIMIZATION_DEBUG is not set CONFIG_COMPILER_OPTIMIZATION_DEBUG=y
# CONFIG_COMPILER_OPTIMIZATION_SIZE is not set # CONFIG_COMPILER_OPTIMIZATION_SIZE is not set
CONFIG_COMPILER_OPTIMIZATION_PERF=y # CONFIG_COMPILER_OPTIMIZATION_PERF is not set
# CONFIG_COMPILER_OPTIMIZATION_NONE is not set # CONFIG_COMPILER_OPTIMIZATION_NONE is not set
CONFIG_COMPILER_OPTIMIZATION_ASSERTIONS_ENABLE=y CONFIG_COMPILER_OPTIMIZATION_ASSERTIONS_ENABLE=y
# CONFIG_COMPILER_OPTIMIZATION_ASSERTIONS_SILENT is not set # CONFIG_COMPILER_OPTIMIZATION_ASSERTIONS_SILENT is not set
@@ -1714,6 +1714,7 @@ CONFIG_FREERTOS_IDLE_TIME_BEFORE_SLEEP=3
# #
# Port # Port
# #
CONFIG_FREERTOS_TASK_FUNCTION_WRAPPER=y
# CONFIG_FREERTOS_WATCHPOINT_END_OF_STACK is not set # CONFIG_FREERTOS_WATCHPOINT_END_OF_STACK is not set
CONFIG_FREERTOS_TLSP_DELETION_CALLBACKS=y CONFIG_FREERTOS_TLSP_DELETION_CALLBACKS=y
# CONFIG_FREERTOS_TASK_PRE_DELETION_HOOK is not set # CONFIG_FREERTOS_TASK_PRE_DELETION_HOOK is not set
@@ -1761,16 +1762,16 @@ CONFIG_HAL_WDT_USE_ROM_IMPL=y
# #
# Heap memory debugging # Heap memory debugging
# #
CONFIG_HEAP_POISONING_DISABLED=y # CONFIG_HEAP_POISONING_DISABLED is not set
# CONFIG_HEAP_POISONING_LIGHT is not set # CONFIG_HEAP_POISONING_LIGHT is not set
# CONFIG_HEAP_POISONING_COMPREHENSIVE is not set CONFIG_HEAP_POISONING_COMPREHENSIVE=y
CONFIG_HEAP_TRACING_OFF=y CONFIG_HEAP_TRACING_OFF=y
# CONFIG_HEAP_TRACING_STANDALONE is not set # CONFIG_HEAP_TRACING_STANDALONE is not set
# CONFIG_HEAP_TRACING_TOHOST is not set # CONFIG_HEAP_TRACING_TOHOST is not set
# CONFIG_HEAP_USE_HOOKS is not set # CONFIG_HEAP_USE_HOOKS is not set
# CONFIG_HEAP_TASK_TRACKING is not set # CONFIG_HEAP_TASK_TRACKING is not set
# CONFIG_HEAP_ABORT_WHEN_ALLOCATION_FAILS is not set # CONFIG_HEAP_ABORT_WHEN_ALLOCATION_FAILS is not set
CONFIG_HEAP_TLSF_USE_ROM_IMPL=y # CONFIG_HEAP_TLSF_USE_ROM_IMPL is not set
# CONFIG_HEAP_PLACE_FUNCTION_INTO_FLASH is not set # CONFIG_HEAP_PLACE_FUNCTION_INTO_FLASH is not set
# end of Heap memory debugging # end of Heap memory debugging
@@ -2463,9 +2464,9 @@ CONFIG_LOG_BOOTLOADER_LEVEL=3
CONFIG_FLASHMODE_DIO=y CONFIG_FLASHMODE_DIO=y
# CONFIG_FLASHMODE_DOUT is not set # CONFIG_FLASHMODE_DOUT is not set
CONFIG_MONITOR_BAUD=115200 CONFIG_MONITOR_BAUD=115200
# CONFIG_OPTIMIZATION_LEVEL_DEBUG is not set CONFIG_OPTIMIZATION_LEVEL_DEBUG=y
# CONFIG_COMPILER_OPTIMIZATION_LEVEL_DEBUG is not set CONFIG_COMPILER_OPTIMIZATION_LEVEL_DEBUG=y
# CONFIG_COMPILER_OPTIMIZATION_DEFAULT is not set CONFIG_COMPILER_OPTIMIZATION_DEFAULT=y
# CONFIG_OPTIMIZATION_LEVEL_RELEASE is not set # CONFIG_OPTIMIZATION_LEVEL_RELEASE is not set
# CONFIG_COMPILER_OPTIMIZATION_LEVEL_RELEASE is not set # CONFIG_COMPILER_OPTIMIZATION_LEVEL_RELEASE is not set
CONFIG_OPTIMIZATION_ASSERTIONS_ENABLED=y CONFIG_OPTIMIZATION_ASSERTIONS_ENABLED=y

View File

@@ -599,13 +599,13 @@ CONFIG_ESPTOOLPY_MONITOR_BAUD=115200
# #
# Partition Table # Partition Table
# #
CONFIG_PARTITION_TABLE_SINGLE_APP=y # CONFIG_PARTITION_TABLE_SINGLE_APP is not set
# CONFIG_PARTITION_TABLE_SINGLE_APP_LARGE is not set # CONFIG_PARTITION_TABLE_SINGLE_APP_LARGE is not set
# CONFIG_PARTITION_TABLE_TWO_OTA is not set # CONFIG_PARTITION_TABLE_TWO_OTA is not set
# CONFIG_PARTITION_TABLE_TWO_OTA_LARGE is not set # CONFIG_PARTITION_TABLE_TWO_OTA_LARGE is not set
# CONFIG_PARTITION_TABLE_CUSTOM is not set CONFIG_PARTITION_TABLE_CUSTOM=y
CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions.csv" CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions.csv"
CONFIG_PARTITION_TABLE_FILENAME="partitions_singleapp.csv" CONFIG_PARTITION_TABLE_FILENAME="partitions.csv"
CONFIG_PARTITION_TABLE_OFFSET=0x8000 CONFIG_PARTITION_TABLE_OFFSET=0x8000
CONFIG_PARTITION_TABLE_MD5=y CONFIG_PARTITION_TABLE_MD5=y
# end of Partition Table # end of Partition Table
@@ -613,9 +613,9 @@ CONFIG_PARTITION_TABLE_MD5=y
# #
# Compiler options # Compiler options
# #
CONFIG_COMPILER_OPTIMIZATION_DEBUG=y # CONFIG_COMPILER_OPTIMIZATION_DEBUG is not set
# CONFIG_COMPILER_OPTIMIZATION_SIZE is not set # CONFIG_COMPILER_OPTIMIZATION_SIZE is not set
# CONFIG_COMPILER_OPTIMIZATION_PERF is not set CONFIG_COMPILER_OPTIMIZATION_PERF=y
# CONFIG_COMPILER_OPTIMIZATION_NONE is not set # CONFIG_COMPILER_OPTIMIZATION_NONE is not set
CONFIG_COMPILER_OPTIMIZATION_ASSERTIONS_ENABLE=y CONFIG_COMPILER_OPTIMIZATION_ASSERTIONS_ENABLE=y
# CONFIG_COMPILER_OPTIMIZATION_ASSERTIONS_SILENT is not set # CONFIG_COMPILER_OPTIMIZATION_ASSERTIONS_SILENT is not set
@@ -644,7 +644,7 @@ CONFIG_COMPILER_RT_LIB_GCCLIB=y
CONFIG_COMPILER_RT_LIB_NAME="gcc" CONFIG_COMPILER_RT_LIB_NAME="gcc"
CONFIG_COMPILER_ORPHAN_SECTIONS_WARNING=y CONFIG_COMPILER_ORPHAN_SECTIONS_WARNING=y
# CONFIG_COMPILER_ORPHAN_SECTIONS_PLACE is not set # CONFIG_COMPILER_ORPHAN_SECTIONS_PLACE is not set
CONFIG_COMPILER_STATIC_ANALYZER=y # CONFIG_COMPILER_STATIC_ANALYZER is not set
# end of Compiler options # end of Compiler options
# #
@@ -695,7 +695,7 @@ CONFIG_BT_NIMBLE_ROLE_BROADCASTER=y
CONFIG_BT_NIMBLE_ROLE_OBSERVER=y CONFIG_BT_NIMBLE_ROLE_OBSERVER=y
CONFIG_BT_NIMBLE_GATT_CLIENT=y CONFIG_BT_NIMBLE_GATT_CLIENT=y
CONFIG_BT_NIMBLE_GATT_SERVER=y CONFIG_BT_NIMBLE_GATT_SERVER=y
# CONFIG_BT_NIMBLE_NVS_PERSIST is not set CONFIG_BT_NIMBLE_NVS_PERSIST=y
# CONFIG_BT_NIMBLE_SMP_ID_RESET is not set # CONFIG_BT_NIMBLE_SMP_ID_RESET is not set
CONFIG_BT_NIMBLE_SECURITY_ENABLE=y CONFIG_BT_NIMBLE_SECURITY_ENABLE=y
CONFIG_BT_NIMBLE_SM_LEGACY=y CONFIG_BT_NIMBLE_SM_LEGACY=y
@@ -1699,9 +1699,13 @@ CONFIG_FREERTOS_TIMER_TASK_STACK_DEPTH=2048
CONFIG_FREERTOS_TIMER_QUEUE_LENGTH=10 CONFIG_FREERTOS_TIMER_QUEUE_LENGTH=10
CONFIG_FREERTOS_QUEUE_REGISTRY_SIZE=0 CONFIG_FREERTOS_QUEUE_REGISTRY_SIZE=0
CONFIG_FREERTOS_TASK_NOTIFICATION_ARRAY_ENTRIES=1 CONFIG_FREERTOS_TASK_NOTIFICATION_ARRAY_ENTRIES=1
# CONFIG_FREERTOS_USE_TRACE_FACILITY is not set CONFIG_FREERTOS_USE_TRACE_FACILITY=y
CONFIG_FREERTOS_USE_STATS_FORMATTING_FUNCTIONS=y
# CONFIG_FREERTOS_USE_LIST_DATA_INTEGRITY_CHECK_BYTES is not set # CONFIG_FREERTOS_USE_LIST_DATA_INTEGRITY_CHECK_BYTES is not set
# CONFIG_FREERTOS_GENERATE_RUN_TIME_STATS is not set # CONFIG_FREERTOS_VTASKLIST_INCLUDE_COREID is not set
CONFIG_FREERTOS_GENERATE_RUN_TIME_STATS=y
CONFIG_FREERTOS_RUN_TIME_COUNTER_TYPE_U32=y
# CONFIG_FREERTOS_RUN_TIME_COUNTER_TYPE_U64 is not set
CONFIG_FREERTOS_USE_TICKLESS_IDLE=y CONFIG_FREERTOS_USE_TICKLESS_IDLE=y
CONFIG_FREERTOS_IDLE_TIME_BEFORE_SLEEP=3 CONFIG_FREERTOS_IDLE_TIME_BEFORE_SLEEP=3
# CONFIG_FREERTOS_USE_APPLICATION_TASK_TAG is not set # CONFIG_FREERTOS_USE_APPLICATION_TASK_TAG is not set
@@ -1710,7 +1714,6 @@ CONFIG_FREERTOS_IDLE_TIME_BEFORE_SLEEP=3
# #
# Port # Port
# #
CONFIG_FREERTOS_TASK_FUNCTION_WRAPPER=y
# CONFIG_FREERTOS_WATCHPOINT_END_OF_STACK is not set # CONFIG_FREERTOS_WATCHPOINT_END_OF_STACK is not set
CONFIG_FREERTOS_TLSP_DELETION_CALLBACKS=y CONFIG_FREERTOS_TLSP_DELETION_CALLBACKS=y
# CONFIG_FREERTOS_TASK_PRE_DELETION_HOOK is not set # CONFIG_FREERTOS_TASK_PRE_DELETION_HOOK is not set
@@ -1722,6 +1725,7 @@ CONFIG_FREERTOS_TICK_SUPPORT_SYSTIMER=y
CONFIG_FREERTOS_CORETIMER_SYSTIMER_LVL1=y CONFIG_FREERTOS_CORETIMER_SYSTIMER_LVL1=y
# CONFIG_FREERTOS_CORETIMER_SYSTIMER_LVL3 is not set # CONFIG_FREERTOS_CORETIMER_SYSTIMER_LVL3 is not set
CONFIG_FREERTOS_SYSTICK_USES_SYSTIMER=y CONFIG_FREERTOS_SYSTICK_USES_SYSTIMER=y
CONFIG_FREERTOS_RUN_TIME_STATS_USING_ESP_TIMER=y
# CONFIG_FREERTOS_PLACE_FUNCTIONS_INTO_FLASH is not set # CONFIG_FREERTOS_PLACE_FUNCTIONS_INTO_FLASH is not set
# CONFIG_FREERTOS_CHECK_PORT_CRITICAL_COMPLIANCE is not set # CONFIG_FREERTOS_CHECK_PORT_CRITICAL_COMPLIANCE is not set
# end of Port # end of Port
@@ -2410,6 +2414,211 @@ CONFIG_WL_SECTOR_SIZE=4096
CONFIG_WIFI_PROV_SCAN_MAX_ENTRIES=16 CONFIG_WIFI_PROV_SCAN_MAX_ENTRIES=16
CONFIG_WIFI_PROV_AUTOSTOP_TIMEOUT=30 CONFIG_WIFI_PROV_AUTOSTOP_TIMEOUT=30
CONFIG_WIFI_PROV_BLE_SEC_CONN=y CONFIG_WIFI_PROV_BLE_SEC_CONN=y
#
# LittleFS
#
# CONFIG_LITTLEFS_SDMMC_SUPPORT is not set
CONFIG_LITTLEFS_MAX_PARTITIONS=3
CONFIG_LITTLEFS_PAGE_SIZE=256
CONFIG_LITTLEFS_OBJ_NAME_LEN=64
CONFIG_LITTLEFS_READ_SIZE=128
CONFIG_LITTLEFS_WRITE_SIZE=128
CONFIG_LITTLEFS_LOOKAHEAD_SIZE=128
CONFIG_LITTLEFS_CACHE_SIZE=512
CONFIG_LITTLEFS_BLOCK_CYCLES=512
CONFIG_LITTLEFS_USE_MTIME=y
# CONFIG_LITTLEFS_USE_ONLY_HASH is not set
# CONFIG_LITTLEFS_HUMAN_READABLE is not set
CONFIG_LITTLEFS_MTIME_USE_SECONDS=y
# CONFIG_LITTLEFS_MTIME_USE_NONCE is not set
# CONFIG_LITTLEFS_SPIFFS_COMPAT is not set
# CONFIG_LITTLEFS_FLUSH_FILE_EVERY_WRITE is not set
# CONFIG_LITTLEFS_FCNTL_GET_PATH is not set
# CONFIG_LITTLEFS_MULTIVERSION is not set
# CONFIG_LITTLEFS_MALLOC_STRATEGY_DISABLE is not set
CONFIG_LITTLEFS_MALLOC_STRATEGY_DEFAULT=y
# CONFIG_LITTLEFS_MALLOC_STRATEGY_INTERNAL is not set
CONFIG_LITTLEFS_ASSERTS=y
# CONFIG_LITTLEFS_MMAP_PARTITION is not set
# end of LittleFS
# end of Component config # end of Component config
# CONFIG_IDF_EXPERIMENTAL_FEATURES is not set # CONFIG_IDF_EXPERIMENTAL_FEATURES is not set
# Deprecated options for backward compatibility
# CONFIG_APP_BUILD_TYPE_ELF_RAM is not set
# CONFIG_NO_BLOBS is not set
# CONFIG_APP_ROLLBACK_ENABLE is not set
# CONFIG_LOG_BOOTLOADER_LEVEL_NONE is not set
# CONFIG_LOG_BOOTLOADER_LEVEL_ERROR is not set
# CONFIG_LOG_BOOTLOADER_LEVEL_WARN is not set
CONFIG_LOG_BOOTLOADER_LEVEL_INFO=y
# CONFIG_LOG_BOOTLOADER_LEVEL_DEBUG is not set
# CONFIG_LOG_BOOTLOADER_LEVEL_VERBOSE is not set
CONFIG_LOG_BOOTLOADER_LEVEL=3
# CONFIG_FLASH_ENCRYPTION_ENABLED is not set
# CONFIG_FLASHMODE_QIO is not set
# CONFIG_FLASHMODE_QOUT is not set
CONFIG_FLASHMODE_DIO=y
# CONFIG_FLASHMODE_DOUT is not set
CONFIG_MONITOR_BAUD=115200
# CONFIG_OPTIMIZATION_LEVEL_DEBUG is not set
# CONFIG_COMPILER_OPTIMIZATION_LEVEL_DEBUG is not set
# CONFIG_COMPILER_OPTIMIZATION_DEFAULT is not set
# CONFIG_OPTIMIZATION_LEVEL_RELEASE is not set
# CONFIG_COMPILER_OPTIMIZATION_LEVEL_RELEASE is not set
CONFIG_OPTIMIZATION_ASSERTIONS_ENABLED=y
# CONFIG_OPTIMIZATION_ASSERTIONS_SILENT is not set
# CONFIG_OPTIMIZATION_ASSERTIONS_DISABLED is not set
CONFIG_OPTIMIZATION_ASSERTION_LEVEL=2
# CONFIG_CXX_EXCEPTIONS is not set
# CONFIG_STACK_CHECK_NONE is not set
# CONFIG_STACK_CHECK_NORM is not set
CONFIG_STACK_CHECK_STRONG=y
# CONFIG_STACK_CHECK_ALL is not set
CONFIG_STACK_CHECK=y
CONFIG_WARN_WRITE_STRINGS=y
# CONFIG_ESP32_APPTRACE_DEST_TRAX is not set
CONFIG_ESP32_APPTRACE_DEST_NONE=y
CONFIG_ESP32_APPTRACE_LOCK_ENABLE=y
# CONFIG_BLUEDROID_ENABLED is not set
CONFIG_NIMBLE_ENABLED=y
CONFIG_NIMBLE_MEM_ALLOC_MODE_INTERNAL=y
# CONFIG_NIMBLE_MEM_ALLOC_MODE_DEFAULT is not set
CONFIG_NIMBLE_MAX_CONNECTIONS=3
CONFIG_NIMBLE_MAX_BONDS=3
CONFIG_NIMBLE_MAX_CCCDS=8
CONFIG_NIMBLE_L2CAP_COC_MAX_NUM=0
CONFIG_NIMBLE_PINNED_TO_CORE=0
CONFIG_NIMBLE_TASK_STACK_SIZE=4096
CONFIG_BT_NIMBLE_TASK_STACK_SIZE=4096
CONFIG_NIMBLE_ROLE_CENTRAL=y
CONFIG_NIMBLE_ROLE_PERIPHERAL=y
CONFIG_NIMBLE_ROLE_BROADCASTER=y
CONFIG_NIMBLE_ROLE_OBSERVER=y
CONFIG_NIMBLE_NVS_PERSIST=y
CONFIG_NIMBLE_SM_LEGACY=y
CONFIG_NIMBLE_SM_SC=y
# CONFIG_NIMBLE_SM_SC_DEBUG_KEYS is not set
CONFIG_BT_NIMBLE_SM_SC_LVL=0
# CONFIG_NIMBLE_DEBUG is not set
CONFIG_NIMBLE_SVC_GAP_DEVICE_NAME="nimble"
CONFIG_NIMBLE_GAP_DEVICE_NAME_MAX_LEN=31
CONFIG_NIMBLE_ATT_PREFERRED_MTU=256
CONFIG_NIMBLE_SVC_GAP_APPEARANCE=0
CONFIG_BT_NIMBLE_MSYS1_BLOCK_COUNT=24
CONFIG_BT_NIMBLE_ACL_BUF_COUNT=24
CONFIG_BT_NIMBLE_ACL_BUF_SIZE=255
CONFIG_BT_NIMBLE_HCI_EVT_BUF_SIZE=70
CONFIG_BT_NIMBLE_HCI_EVT_HI_BUF_COUNT=30
CONFIG_BT_NIMBLE_HCI_EVT_LO_BUF_COUNT=8
CONFIG_NIMBLE_RPA_TIMEOUT=900
# CONFIG_NIMBLE_MESH is not set
CONFIG_NIMBLE_CRYPTO_STACK_MBEDTLS=y
# CONFIG_BT_NIMBLE_COEX_PHY_CODED_TX_RX_TLIM_EN is not set
CONFIG_BT_NIMBLE_COEX_PHY_CODED_TX_RX_TLIM_DIS=y
CONFIG_SW_COEXIST_ENABLE=y
CONFIG_ESP32_WIFI_SW_COEXIST_ENABLE=y
CONFIG_ESP_WIFI_SW_COEXIST_ENABLE=y
# CONFIG_ANA_CMPR_ISR_IRAM_SAFE is not set
# CONFIG_GPTIMER_ISR_IRAM_SAFE is not set
# CONFIG_MCPWM_ISR_IRAM_SAFE is not set
# CONFIG_EVENT_LOOP_PROFILING is not set
CONFIG_POST_EVENTS_FROM_ISR=y
CONFIG_POST_EVENTS_FROM_IRAM_ISR=y
CONFIG_GDBSTUB_SUPPORT_TASKS=y
CONFIG_GDBSTUB_MAX_TASKS=32
# CONFIG_OTA_ALLOW_HTTP is not set
CONFIG_ESP_SYSTEM_PD_FLASH=y
CONFIG_PERIPH_CTRL_FUNC_IN_IRAM=y
CONFIG_BROWNOUT_DET=y
# CONFIG_BROWNOUT_DET_LVL_SEL_7 is not set
# CONFIG_BROWNOUT_DET_LVL_SEL_6 is not set
# CONFIG_BROWNOUT_DET_LVL_SEL_5 is not set
# CONFIG_BROWNOUT_DET_LVL_SEL_4 is not set
CONFIG_BROWNOUT_DET_LVL_SEL_3=y
# CONFIG_BROWNOUT_DET_LVL_SEL_2 is not set
# CONFIG_BROWNOUT_DET_LVL_SEL_1 is not set
# CONFIG_BROWNOUT_DET_LVL_SEL_0 is not set
CONFIG_BROWNOUT_DET_LVL=3
CONFIG_ESP_SYSTEM_BROWNOUT_INTR=y
CONFIG_ESP32_PHY_CALIBRATION_AND_DATA_STORAGE=y
CONFIG_ESP32_PHY_MAX_WIFI_TX_POWER=20
CONFIG_ESP32_PHY_MAX_TX_POWER=20
# CONFIG_REDUCE_PHY_TX_POWER is not set
# CONFIG_ESP32_REDUCE_PHY_TX_POWER is not set
CONFIG_ESP_SYSTEM_PM_POWER_DOWN_CPU=y
CONFIG_ESP32_RTC_XTAL_BOOTSTRAP_CYCLES=0
CONFIG_SYSTEM_EVENT_QUEUE_SIZE=32
CONFIG_SYSTEM_EVENT_TASK_STACK_SIZE=2304
CONFIG_MAIN_TASK_STACK_SIZE=3584
CONFIG_CONSOLE_UART_DEFAULT=y
# CONFIG_CONSOLE_UART_CUSTOM is not set
# CONFIG_CONSOLE_UART_NONE is not set
# CONFIG_ESP_CONSOLE_UART_NONE is not set
CONFIG_CONSOLE_UART=y
CONFIG_CONSOLE_UART_NUM=0
CONFIG_CONSOLE_UART_BAUDRATE=115200
CONFIG_INT_WDT=y
CONFIG_INT_WDT_TIMEOUT_MS=300
CONFIG_TASK_WDT=y
CONFIG_ESP_TASK_WDT=y
# CONFIG_TASK_WDT_PANIC is not set
CONFIG_TASK_WDT_TIMEOUT_S=5
CONFIG_TASK_WDT_CHECK_IDLE_TASK_CPU0=y
# CONFIG_ESP32_DEBUG_STUBS_ENABLE is not set
CONFIG_IPC_TASK_STACK_SIZE=1024
CONFIG_TIMER_TASK_STACK_SIZE=3584
# CONFIG_ESP32_ENABLE_COREDUMP_TO_FLASH is not set
# CONFIG_ESP32_ENABLE_COREDUMP_TO_UART is not set
CONFIG_ESP32_ENABLE_COREDUMP_TO_NONE=y
CONFIG_TIMER_TASK_PRIORITY=1
CONFIG_TIMER_TASK_STACK_DEPTH=2048
CONFIG_TIMER_QUEUE_LENGTH=10
# CONFIG_ENABLE_STATIC_TASK_CLEAN_UP_HOOK is not set
# CONFIG_HAL_ASSERTION_SILIENT is not set
# CONFIG_L2_TO_L3_COPY is not set
CONFIG_ESP_GRATUITOUS_ARP=y
CONFIG_GARP_TMR_INTERVAL=60
CONFIG_TCPIP_RECVMBOX_SIZE=32
CONFIG_TCP_MAXRTX=12
CONFIG_TCP_SYNMAXRTX=12
CONFIG_TCP_MSS=1440
CONFIG_TCP_MSL=60000
CONFIG_TCP_SND_BUF_DEFAULT=5760
CONFIG_TCP_WND_DEFAULT=5760
CONFIG_TCP_RECVMBOX_SIZE=6
CONFIG_TCP_QUEUE_OOSEQ=y
CONFIG_TCP_OVERSIZE_MSS=y
# CONFIG_TCP_OVERSIZE_QUARTER_MSS is not set
# CONFIG_TCP_OVERSIZE_DISABLE is not set
CONFIG_UDP_RECVMBOX_SIZE=6
CONFIG_TCPIP_TASK_STACK_SIZE=3072
CONFIG_TCPIP_TASK_AFFINITY_NO_AFFINITY=y
# CONFIG_TCPIP_TASK_AFFINITY_CPU0 is not set
CONFIG_TCPIP_TASK_AFFINITY=0x7FFFFFFF
# CONFIG_PPP_SUPPORT is not set
CONFIG_NEWLIB_STDOUT_LINE_ENDING_CRLF=y
# CONFIG_NEWLIB_STDOUT_LINE_ENDING_LF is not set
# CONFIG_NEWLIB_STDOUT_LINE_ENDING_CR is not set
# CONFIG_NEWLIB_STDIN_LINE_ENDING_CRLF is not set
# CONFIG_NEWLIB_STDIN_LINE_ENDING_LF is not set
CONFIG_NEWLIB_STDIN_LINE_ENDING_CR=y
# CONFIG_NEWLIB_NANO_FORMAT is not set
CONFIG_NEWLIB_TIME_SYSCALL_USE_RTC_HRT=y
# CONFIG_NEWLIB_TIME_SYSCALL_USE_RTC is not set
# CONFIG_NEWLIB_TIME_SYSCALL_USE_HRT is not set
# CONFIG_NEWLIB_TIME_SYSCALL_USE_NONE is not set
CONFIG_ESP32_PTHREAD_TASK_PRIO_DEFAULT=5
CONFIG_ESP32_PTHREAD_TASK_STACK_SIZE_DEFAULT=3072
CONFIG_ESP32_PTHREAD_STACK_MIN=768
CONFIG_ESP32_PTHREAD_TASK_CORE_DEFAULT=-1
CONFIG_ESP32_PTHREAD_TASK_NAME_DEFAULT="pthread"
CONFIG_SPI_FLASH_WRITING_DANGEROUS_REGIONS_ABORTS=y
# CONFIG_SPI_FLASH_WRITING_DANGEROUS_REGIONS_FAILS is not set
# CONFIG_SPI_FLASH_WRITING_DANGEROUS_REGIONS_ALLOWED is not set
CONFIG_SUPPRESS_SELECT_DEBUG_OUTPUT=y
CONFIG_SUPPORT_TERMIOS=y
CONFIG_SEMIHOSTFS_MAX_MOUNT_POINTS=1
# End of deprecated options