fix(app-runtime): align runtime headers with implementation and restore PIO build

Unify AppRegistry/AppRuntimeManager APIs with current source, keep legacy workbench compatibility, and remove conflicting include causing IAppModule redefinition.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
L'électron rare
2026-03-11 00:53:01 +01:00
parent 8d874ae31b
commit 707877f206
5 changed files with 127 additions and 67 deletions
+24 -16
View File
@@ -1,8 +1,16 @@
// app_registry.h - App catalog loaded from registry.json.
// app_registry.h - app catalog registry with legacy AppEntry compatibility.
#pragma once
#include <Arduino.h>
#include <cstddef>
#include <cstdint>
#include <vector>
#include "app/app_runtime_types.h"
class StorageManager;
struct AppEntry {
char id[24] = {0};
char title[32] = {0};
@@ -10,37 +18,37 @@ struct AppEntry {
char icon_path[48] = {0};
char entry_screen[40] = {0};
bool enabled = true;
uint8_t required_capabilities = 0U; // bitmask (lightweight)
uint32_t required_capabilities = 0U;
};
// Capability bits for required_capabilities bitmask.
namespace AppCap {
constexpr uint8_t kAudioOut = 0x01U;
constexpr uint8_t kAudioIn = 0x02U;
constexpr uint8_t kStorageFs = 0x04U;
constexpr uint8_t kStorageSd = 0x08U;
constexpr uint8_t kCamera = 0x10U;
constexpr uint8_t kWifi = 0x20U;
constexpr uint8_t kGpuUi = 0x40U;
} // namespace AppCap
class AppRegistry {
public:
static constexpr uint8_t kMaxApps = 24U;
AppRegistry() = default;
bool loadFromJson(const char* json_path);
// New API used by runtime manager / web contracts.
bool loadFromFs(const StorageManager& storage, const char* registry_path = "/apps/registry.json");
bool loadFromJson(const char* json_text);
bool loadFromBuffer(const char* json_buffer, size_t len);
const AppDescriptor* find(const char* id) const;
std::vector<AppDescriptor> listByCategory(const char* category) const;
const std::vector<AppDescriptor>& descriptors() const;
// Legacy API used by workbench UI modules.
uint8_t count() const { return count_; }
const AppEntry* entry(uint8_t index) const;
const AppEntry* findById(const char* id) const;
uint8_t enabledCount() const;
const AppEntry* enabledEntry(uint8_t visible_index) const;
private:
AppEntry entries_[kMaxApps];
void loadFallbackCatalog();
static uint32_t parseCapabilityMask(const char* csv_caps);
void rebuildLegacyEntries();
std::vector<AppDescriptor> descriptors_;
AppEntry entries_[kMaxApps] = {};
uint8_t count_ = 0U;
};
@@ -1,67 +1,38 @@
// app_runtime_manager.h - App lifecycle: open/close/action state machine.
// app_runtime_manager.h - app lifecycle orchestration.
#pragma once
#include <Arduino.h>
#include "app/app_module.h"
#include "app/app_registry.h"
#include <cstdint>
#include <memory>
struct RuntimeServices;
#include "app/app_registry.h"
#include "app/app_runtime_types.h"
class AppRuntimeManager {
public:
enum class State : uint8_t {
kIdle = 0,
kStarting,
kRunning,
kClosing,
kError,
};
struct Snapshot {
State state = State::kIdle;
const char* app_id = "";
const char* entry_screen = "";
const char* last_error = "";
uint32_t opened_at_ms = 0U;
};
static constexpr uint8_t kMaxModules = 24U;
AppRuntimeManager() = default;
void begin(AppRegistry* registry, RuntimeServices* services);
// New runtime API.
void configure(AppRegistry* registry, const AppContext& context);
bool startApp(const AppStartRequest& request, uint32_t now_ms);
bool stopApp(const AppStopRequest& request, uint32_t now_ms);
bool handleAction(const AppAction& action, uint32_t now_ms);
void tick(uint32_t now_ms);
AppRuntimeStatus current() const;
const AppDescriptor* currentDescriptor() const;
// Register a module (call during setup, before any open).
bool registerModule(IAppModule* module);
// App lifecycle.
// Legacy compatibility API (used by workbench shell).
bool open(const char* app_id, const char* mode, uint32_t now_ms);
bool close(const char* reason, uint32_t now_ms);
bool action(const char* action_name, const char* payload);
void tick(uint32_t now_ms);
// Status.
State state() const { return state_; }
Snapshot snapshot() const;
bool isRunning() const { return state_ == State::kRunning; }
bool isIdle() const { return state_ == State::kIdle; }
const char* currentAppId() const { return current_app_id_; }
bool isRunning() const { return status_.state == AppRuntimeState::kRunning; }
bool isIdle() const { return status_.state == AppRuntimeState::kIdle; }
private:
IAppModule* findModule(const char* app_id) const;
void transitionTo(State next, const char* reason);
uint32_t evaluateMissingCapabilities(const AppDescriptor& descriptor) const;
AppRegistry* registry_ = nullptr;
RuntimeServices* services_ = nullptr;
IAppModule* modules_[kMaxModules] = {nullptr};
uint8_t module_count_ = 0U;
IAppModule* active_module_ = nullptr;
State state_ = State::kIdle;
char current_app_id_[24] = {0};
char current_entry_screen_[40] = {0};
char last_error_[64] = {0};
uint32_t opened_at_ms_ = 0U;
uint32_t close_requested_ms_ = 0U;
AppContext context_ = {};
std::unique_ptr<IAppModule> module_;
const AppDescriptor* current_descriptor_ = nullptr;
AppRuntimeStatus status_ = {};
};
@@ -2,7 +2,10 @@
#include "app/app_registry.h"
#include <ArduinoJson.h>
#include <cctype>
#include <cstring>
#include <cstdio>
#include <vector>
#include "core/str_utils.h"
#include "storage_manager.h"
@@ -102,6 +105,11 @@ bool AppRegistry::loadFromFs(const StorageManager& storage, const char* registry
return true;
}
bool AppRegistry::loadFromBuffer(const char* json_buffer, size_t len) {
(void)len;
return loadFromJson(json_buffer);
}
const AppDescriptor* AppRegistry::find(const char* id) const {
if (id == nullptr || id[0] == '\0') return nullptr;
for (const AppDescriptor& d : descriptors_) {
@@ -182,6 +190,7 @@ bool AppRegistry::loadFromJson(const char* json_text) {
descriptors_.push_back(d);
}
rebuildLegacyEntries();
return !descriptors_.empty();
}
@@ -208,6 +217,7 @@ void AppRegistry::loadFallbackCatalog() {
d.supports_streaming = f.streaming;
descriptors_.push_back(d);
}
rebuildLegacyEntries();
}
uint32_t AppRegistry::parseCapabilityMask(const char* csv_caps) {
@@ -234,3 +244,50 @@ uint32_t AppRegistry::parseCapabilityMask(const char* csv_caps) {
}
return mask;
}
const AppEntry* AppRegistry::entry(uint8_t index) const {
if (index >= count_) return nullptr;
return &entries_[index];
}
const AppEntry* AppRegistry::findById(const char* id) const {
if (id == nullptr || id[0] == '\0') return nullptr;
for (uint8_t i = 0U; i < count_; ++i) {
if (core::equalsIgnoreCase(entries_[i].id, id)) return &entries_[i];
}
return nullptr;
}
uint8_t AppRegistry::enabledCount() const {
uint8_t enabled = 0U;
for (uint8_t i = 0U; i < count_; ++i) {
if (entries_[i].enabled) enabled += 1U;
}
return enabled;
}
const AppEntry* AppRegistry::enabledEntry(uint8_t visible_index) const {
uint8_t seen = 0U;
for (uint8_t i = 0U; i < count_; ++i) {
if (!entries_[i].enabled) continue;
if (seen == visible_index) return &entries_[i];
seen += 1U;
}
return nullptr;
}
void AppRegistry::rebuildLegacyEntries() {
count_ = 0U;
std::memset(entries_, 0, sizeof(entries_));
for (const AppDescriptor& d : descriptors_) {
if (count_ >= kMaxApps) break;
AppEntry& out = entries_[count_++];
core::copyText(out.id, sizeof(out.id), d.id);
core::copyText(out.title, sizeof(out.title), d.title);
core::copyText(out.category, sizeof(out.category), d.category);
core::copyText(out.icon_path, sizeof(out.icon_path), d.icon_path);
core::copyText(out.entry_screen, sizeof(out.entry_screen), d.entry_screen);
out.enabled = d.enabled;
out.required_capabilities = d.required_capabilities;
}
}
@@ -205,6 +205,31 @@ void AppRuntimeManager::configure(AppRegistry* registry, const AppContext& conte
status_ = {};
}
bool AppRuntimeManager::open(const char* app_id, const char* mode, uint32_t now_ms) {
AppStartRequest request = {};
core::copyText(request.id, sizeof(request.id), (app_id != nullptr) ? app_id : "");
core::copyText(request.mode, sizeof(request.mode), (mode != nullptr) ? mode : "default");
core::copyText(request.source, sizeof(request.source), "workbench");
return startApp(request, now_ms);
}
bool AppRuntimeManager::close(const char* reason, uint32_t now_ms) {
AppStopRequest request = {};
core::copyText(request.id, sizeof(request.id), status_.id);
core::copyText(request.reason, sizeof(request.reason), (reason != nullptr) ? reason : "api");
return stopApp(request, now_ms);
}
bool AppRuntimeManager::action(const char* action_name, const char* payload) {
AppAction action_request = {};
core::copyText(action_request.id, sizeof(action_request.id), status_.id);
core::copyText(action_request.name, sizeof(action_request.name),
(action_name != nullptr) ? action_name : "");
core::copyText(action_request.payload, sizeof(action_request.payload),
(payload != nullptr) ? payload : "");
return handleAction(action_request, millis());
}
bool AppRuntimeManager::startApp(const AppStartRequest& request, uint32_t now_ms) {
if (registry_ == nullptr || request.id[0] == '\0') {
core::copyText(status_.last_error, sizeof(status_.last_error), "app_registry_unavailable");
@@ -3,7 +3,6 @@
#include <cstring>
#include "app/app_registry.h"
#include "runtime/runtime_services.h"
bool AudioPlayerModule::onOpen(const AppEntry& entry, const char* mode, RuntimeServices* services) {