Add core runtime: event bus, services, app lifecycle

lib/core is hardware-independent and unit-tested on the host:
- EventBus: fixed-size thread-safe queue, delivered on the UI task by
  dispatch(); drops newest when full and counts drops
- Service / ServiceManager: cooperative ticking at per-service intervals,
  no catch-up bursts, safe across millis() wraparound
- App / AppManager: one foreground App, Home always to Launcher, Back
  offered to the App first, hidden Apps, redraw requests
- KeyEvent: logical keys for the upcoming input layer

Firmware main loop now ticks services and dispatches events.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EhqxQ49eCju4CzKYNjZzwT
This commit is contained in:
2026-10-02 09:10:10 +02:00
co-authored by Claude Opus 5.5
parent b293197ace
commit ae0726b7f9
14 changed files with 810 additions and 0 deletions
+9
View File
@@ -1,8 +1,13 @@
// roro9stack — boot stub: proves toolchain, display and keyboard.
#include <M5Cardputer.h>
#include "event_bus.h"
#include "service_manager.h"
#include "version.h"
static roro::EventBus bus;
static roro::ServiceManager services;
void setup() {
auto cfg = M5.config();
M5Cardputer.begin(cfg, true); // true = enable keyboard
@@ -19,6 +24,7 @@ void setup() {
d.drawString("Press keys...", 8, 56);
Serial.begin(115200);
services.startAll(millis());
}
static void printStatus() {
@@ -34,6 +40,9 @@ void loop() {
printStatus();
}
services.tick(millis());
bus.dispatch();
M5Cardputer.update();
if (M5Cardputer.Keyboard.isChange() && M5Cardputer.Keyboard.isPressed()) {
auto state = M5Cardputer.Keyboard.keysState();