16static const char*
TAG =
"ModuleReg";
24#define MODULE_DEFAULT_ENTRY(n, en) { n, en },
25constexpr ModuleDefault kModuleDefaults[] = {
28#undef MODULE_DEFAULT_ENTRY
30const ModuleDefault* findModuleDefault(
const char*
name) {
31 for (
const auto& e : kModuleDefaults) {
32 if (strcmp(e.name,
name) == 0)
return &e;
45 static ModuleRegistry*
instance =
new ModuleRegistry();
54 if (!initFunc)
return;
57 LOG_E(
TAG,
"Module initializer registry full");
61 initializers_[initCount_++] = initFunc;
73 LOG_E(
TAG,
"TropicStorage not STARTED (state=%d) before module init - aborting",
74 static_cast<int>(storageState));
78 LOG_I(
TAG,
"Running %d module initializers", initCount_);
80 for (uint8_t i = 0; i < initCount_; i++) {
81 if (initializers_[i]) {
88 for (uint8_t i = 0; i < count_; i++) {
89 if (!findModuleDefault(modules_[i]->getName())) {
90 LOG_E(
TAG,
"Module '%s' missing from MODULE_DEFAULT_MAP (module_defaults.h)",
91 modules_[i]->getName());
102 for (uint8_t i = 0; i < count_; i++) {
104 LOG_W(
TAG,
"Stopping disabled module '%s' (started during init)", modules_[i]->getName());
108 for (uint8_t i = 0; i < count_; i++) {
110 LOG_W(
TAG,
"Enabled module '%s' failed to start", modules_[i]->getName());
115 cleanupOrphanedModuleData();
128 LOG_E(
TAG,
"Cannot register null module");
133 LOG_E(
TAG,
"Module registry full, cannot register '%s'", module->
getName());
138 for (uint8_t i = 0; i < count_; i++) {
139 if (strcmp(modules_[i]->getName(), module->
getName()) == 0) {
145 modules_[count_++] =
module;
146 clearModuleError(count_ - 1);
147 (void)applySlotRequest(module, count_ - 1);
159 for (uint8_t i = 0; i < count_; i++) {
160 if (strcmp(modules_[i]->getName(),
name) == 0) {
162 for (uint8_t j = i; j < count_ - 1; j++) {
163 modules_[j] = modules_[j + 1];
165 modules_[--count_] =
nullptr;
178 if (!
name)
return nullptr;
180 for (uint8_t i = 0; i < count_; i++) {
181 if (strcmp(modules_[i]->getName(),
name) == 0) {
194 if (index >= count_)
return nullptr;
195 return modules_[index];
204 for (uint8_t i = 0; i < count_; i++) {
205 if (!modules_[i]->init()) {
206 LOG_E(
TAG,
"Failed to init module '%s'", modules_[i]->getName());
219 for (uint8_t i = 0; i < count_; i++) {
222 LOG_I(
TAG,
"Module '%s' is disabled, skipping start", modules_[i]->getName());
239 if (index >= count_)
return false;
240 IModule* module = modules_[index];
241 if (!module)
return false;
251 if (!module->
start()) {
281 for (uint8_t i = 0; i < count_; i++) {
296 if (!items || maxItems == 0)
return 0;
298 uint8_t totalCount = 0;
301 for (uint8_t i = 0; i < count_; i++) {
305 uint8_t count = modules_[i]->getMenuItems(moduleItems, 8);
307 for (uint8_t j = 0; j < count && totalCount < maxItems; j++) {
308 if (moduleItems[j].location == location) {
310 if (moduleItems[j].isVisible && !moduleItems[j].isVisible()) {
314 moduleItems[j].
moduleName = modules_[i]->getName();
315 items[totalCount++] = moduleItems[j];
321 for (uint8_t i = 0; i < totalCount; i++) {
322 for (uint8_t j = i + 1; j < totalCount; j++) {
323 if (items[j].priority < items[i].priority) {
338 for (uint8_t i = 0; i < count_; i++) {
340 modules_[i]->onUnlock();
349 for (uint8_t i = 0; i < count_; i++) {
351 modules_[i]->onLock();
360 for (uint8_t i = 0; i < count_; i++) {
362 modules_[i]->onUsbConnect();
371 for (uint8_t i = 0; i < count_; i++) {
373 modules_[i]->onUsbDisconnect();
383 for (uint8_t i = 0; i < count_; i++) {
385 modules_[i]->onTick(nowMs);
397 if (!items || maxItems == 0)
return 0;
399 uint8_t totalCount = 0;
402 for (uint8_t i = 0; i < count_; i++) {
406 uint8_t count = modules_[i]->getLockScreenContextItems(moduleItems, 4);
408 for (uint8_t j = 0; j < count && totalCount < maxItems; j++) {
410 moduleItems[j].
moduleName = modules_[i]->getName();
411 items[totalCount++] = moduleItems[j];
416 for (uint8_t i = 0; i < totalCount; i++) {
417 for (uint8_t j = i + 1; j < totalCount; j++) {
418 if (items[j].priority < items[i].priority) {
441void ModuleRegistry::cleanupOrphanedModuleData() {
444 LOG_I(
TAG,
"No previous module list found (first boot)");
449 size_t len =
sizeof(savedList);
453 if (err != ESP_OK || len == 0) {
458 LOG_I(
TAG,
"Checking for orphaned module data...");
461 char* saveptr =
nullptr;
462 char* token = strtok_r(savedList,
",", &saveptr);
466 if (strlen(token) == 0) {
467 token = strtok_r(
nullptr,
",", &saveptr);
473 for (uint8_t i = 0; i < count_; i++) {
474 if (strcmp(modules_[i]->getName(), token) == 0) {
483 snprintf(nsName,
sizeof(nsName),
"%s%s",
NVS_PREFIX, token);
485 LOG_W(
TAG,
"Module '%s' removed - erasing NVS namespace '%s'", token, nsName);
487 NvsScope modHandle(nsName, NVS_READWRITE);
489 nvs_erase_all(modHandle);
491 LOG_I(
TAG,
"Erased NVS data for removed module '%s'", token);
495 token = strtok_r(
nullptr,
",", &saveptr);
502void ModuleRegistry::saveModuleList() {
512 for (uint8_t i = 0; i < count_; i++) {
513 const char*
name = modules_[i]->getName();
514 size_t nameLen = strlen(
name);
517 if (offset + nameLen + 2 >
sizeof(moduleList)) {
518 LOG_W(
TAG,
"Module list too long, truncating");
523 moduleList[offset++] =
',';
525 memcpy(moduleList + offset,
name, nameLen);
528 moduleList[offset] =
'\0';
534 LOG_I(
TAG,
"Saved module list: %s", moduleList);
536 LOG_E(
TAG,
"Failed to save module list");
547void ModuleRegistry::loadDisabledList() {
552 size_t len =
sizeof(disabledModules_);
554 LOG_I(
TAG,
"Loaded disabled modules: %s", disabledModules_);
562 disabledModules_[0] =
'\0';
564 for (uint8_t i = 0; i < count_; i++) {
565 const ModuleDefault* def = findModuleDefault(modules_[i]->getName());
566 bool defaultEnabled = def ? def->enabled :
true;
567 if (defaultEnabled)
continue;
568 const char*
name = modules_[i]->getName();
569 size_t nameLen = strlen(
name);
570 size_t needed = (offset > 0 ? 1 : 0) + nameLen + 1;
571 if (offset + needed >
sizeof(disabledModules_))
break;
572 if (offset > 0) disabledModules_[offset++] =
',';
573 memcpy(disabledModules_ + offset,
name, nameLen);
575 disabledModules_[offset] =
'\0';
577 if (disabledModules_[0] !=
'\0') {
578 LOG_I(
TAG,
"Applied factory-default disabled list: %s", disabledModules_);
586void ModuleRegistry::saveDisabledList() {
591 LOG_I(
TAG,
"Saved disabled modules: %s", disabledModules_);
593 LOG_E(
TAG,
"Failed to save disabled modules list");
603 if (!
name ||
name[0] ==
'\0')
return true;
604 if (disabledModules_[0] ==
'\0')
return true;
607 size_t nameLen = strlen(
name);
608 const char* ptr = disabledModules_;
612 while (*ptr ==
',') ptr++;
613 if (*ptr ==
'\0')
break;
616 const char* end = ptr;
617 while (*end && *end !=
',') end++;
618 size_t tokenLen = end - ptr;
621 if (tokenLen == nameLen && strncmp(ptr,
name, nameLen) == 0) {
637 if (index >= count_)
return "[--]";
649 if (index >= count_)
return false;
667 char* dest,
size_t capacity) {
668 if (capacity == 0)
return;
670 auto tmp = std::make_unique<char[]>(capacity);
672 size_t newOffset = 0;
673 const size_t nameLen = strlen(
name);
674 const size_t maxOffset = capacity;
676 const char* ptr = list;
678 while (*ptr ==
',') ptr++;
679 if (*ptr ==
'\0')
break;
681 const char* end = ptr;
682 while (*end && *end !=
',') end++;
683 size_t tokenLen =
static_cast<size_t>(end - ptr);
685 const bool matches = (tokenLen == nameLen &&
686 strncmp(ptr,
name, nameLen) == 0);
688 if (newOffset > 0 && newOffset + 1 < maxOffset) {
689 tmp[newOffset++] =
',';
691 if (newOffset + tokenLen < maxOffset) {
692 memcpy(tmp.get() + newOffset, ptr, tokenLen);
693 newOffset += tokenLen;
699 tmp[newOffset] =
'\0';
700 memcpy(dest, tmp.get(), newOffset + 1);
701 dest[capacity - 1] =
'\0';
716 const size_t currentLen = strlen(list);
717 const size_t nameLen = strlen(
name);
719 if (currentLen + nameLen + 2 >= capacity) {
723 size_t writePos = currentLen;
724 if (currentLen > 0) {
725 list[writePos++] =
',';
727 memcpy(list + writePos,
name, nameLen + 1);
737 if (index >= count_)
return;
739 const char*
name = modules_[index]->getName();
742 if (enabled == currentlyEnabled)
return;
746 disabledModules_,
sizeof(disabledModules_));
749 LOG_W(
TAG,
"Disabled list full, cannot add '%s'",
name);
763 if (index >= count_)
return false;
776 if (index >= count_)
return false;
777 return moduleErrors_[index].hasError;
786 if (index >= count_)
return nullptr;
787 return moduleErrors_[index].hasError ? moduleErrors_[index].message :
nullptr;
795void ModuleRegistry::setModuleError(uint8_t index,
const char* message) {
797 moduleErrors_[index].hasError =
true;
799 strncpy(moduleErrors_[index].message, message,
sizeof(moduleErrors_[index].message) - 1);
800 moduleErrors_[index].message[
sizeof(moduleErrors_[index].message) - 1] =
'\0';
802 moduleErrors_[index].message[0] =
'\0';
810void ModuleRegistry::clearModuleError(uint8_t index) {
812 moduleErrors_[index].hasError =
false;
813 moduleErrors_[index].message[0] =
'\0';
825 for (uint8_t i = 0; i < count_; i++) {
826 if (strcmp(modules_[i]->getName(),
name) == 0) {
832 setModuleError(i, message);
833 LOG_E(
TAG,
"Module '%s' error: %s",
name, message ? message :
"(null)");
843 LOG_W(
TAG,
"reportModuleError: module '%s' not found",
name);
853 for (uint8_t i = 0; i < count_; i++) {
854 if (strcmp(modules_[i]->getName(),
name) == 0) {
867 if (index >= count_)
return false;
869 IModule* module = modules_[index];
870 if (!module)
return false;
872 const char*
name =
module->getName();
876 clearModuleError(index);
881 if (!module->
init()) {
888 if (!module->
start()) {
908void ModuleRegistry::buildSlotErrorMessage(
char* buffer,
size_t bufSize,
909 const char* errorType,
const char* mapName) {
910 snprintf(buffer, bufSize,
"%s for %s", errorType, mapName);
918bool ModuleRegistry::validateSlotMap(
const char* moduleName) {
920 if (!slotMap.isValid()) {
921 const char* errMsg = slotMap.errorMessage();
937bool ModuleRegistry::validateEccRange(
const char* mapName,
const char* moduleName,
941 TropicSlotMap::SlotRange ecc = {};
945 buildSlotErrorMessage(msg,
sizeof(msg),
"missing ECC slot map", mapName);
950 uint16_t count =
static_cast<uint16_t
>(ecc.end - ecc.start + 1);
951 if (count < minSlots) {
953 buildSlotErrorMessage(msg,
sizeof(msg),
"not enough ECC slots", mapName);
959 range.eccStart =
static_cast<uint8_t
>(ecc.start);
960 range.eccEnd =
static_cast<uint8_t
>(ecc.end);
974bool ModuleRegistry::validateRmemRange(
const char* mapName,
const char* moduleName,
978 TropicSlotMap::SlotRange rmem = {};
982 buildSlotErrorMessage(msg,
sizeof(msg),
"missing RMEM slot map", mapName);
987 uint16_t count =
static_cast<uint16_t
>(rmem.end - rmem.start + 1);
988 if (count < minSlots) {
990 buildSlotErrorMessage(msg,
sizeof(msg),
"not enough RMEM slots", mapName);
998 buildSlotErrorMessage(msg,
sizeof(msg),
"module id mismatch", mapName);
1003 range.hasRmem =
true;
1004 range.rmemStart = rmem.start;
1005 range.rmemEnd = rmem.end;
1020bool ModuleRegistry::applySlotRequest(
IModule* module, uint8_t index) {
1021 if (!module)
return false;
1023 const char* moduleName =
module->getName();
1026 if (!validateSlotMap(moduleName)) {
1031 IModule::SlotRequest req =
module->getSlotRequest();
1032 const char* mapName = (req.mapName && req.mapName[0] !=
'\0') ? req.mapName : moduleName;
1035 if (!mapName || (req.minEccSlots == 0 && req.minRmemSlots == 0)) {
1039 IModule::SlotRange range = {};
1043 if (req.minEccSlots > 0) {
1044 if (!validateEccRange(mapName, moduleName, req.minEccSlots, range,
moduleId)) {
1050 if (req.minRmemSlots > 0) {
1051 if (!validateRmemRange(mapName, moduleName, req.minRmemSlots, range,
moduleId)) {
1058 module->setSlotRange(range);
#define MODULE_DEFAULT_ENTRY(n, en)
char name[cdc::hal::ISecureElement::RMEM_NAME_LEN]
Shared RAII wrappers for firmware resources.
CDC Log: logging over TinyUSB CDC and UART.
#define LOG_W(tag, fmt,...)
#define LOG_I(tag, fmt,...)
#define LOG_E(tag, fmt,...)
static EventBus & instance()
Returns singleton event-bus instance.
bool publish(const Event &event, bool fromISR=false)
Publishes an event to the queue.
Module interface that extends IService with module-specific features.
virtual const char * getVersion() const =0
Returns the module version string.
virtual ServiceState getState() const =0
virtual const char * getName() const =0
void runAllInitializers()
Executes all registered initializers and post-registration housekeeping.
bool retryModule(uint8_t index)
Attempts to recover a failed module by re-initializing and restarting it.
void reportModuleError(const char *name, const char *message)
Records and publishes an operational module error by module name.
bool registerModule(IModule *module)
Registers a module instance in the runtime registry.
void dispatchUnlock()
Dispatches unlock lifecycle event to started modules.
void dispatchUsbConnect()
Dispatches USB-connect lifecycle event to started modules.
const char * getModuleSlotError(uint8_t index) const
Returns stored slot-error message for module index.
void dispatchUsbDisconnect()
Dispatches USB-disconnect lifecycle event to started modules.
const char * getModuleStatusLabel(uint8_t index) const
Returns a short status marker combining enabled flag and run state.
bool startAll()
Starts all enabled modules.
static constexpr const char * NVS_PREFIX
bool hasModuleSlotError(uint8_t index) const
Reports whether a module currently has a slot-validation error.
bool isModuleEnabledByName(const char *name) const
Checks whether a module name is currently enabled.
IModule * getModuleAt(uint8_t index)
Returns module pointer at registry index.
void stopAll()
Stops all currently started modules.
static ModuleRegistry & instance()
Returns the singleton module registry instance.
bool initAll()
Calls init() on all registered modules.
static constexpr uint8_t MAX_MODULES
uint8_t getLockScreenContextItems(LockScreenContextItem *items, uint8_t maxItems)
Collects lock-screen context actions from started modules.
bool isModuleEnabled(uint8_t index) const
Checks whether module at index is enabled.
void registerInitializer(ModuleInitFunc initFunc)
Registers a deferred module initializer callback.
bool toggleModuleEnabled(uint8_t index)
Toggles enabled state for a module.
bool startModule(uint8_t index)
Starts a single module by index.
static constexpr uint8_t MAX_INITIALIZERS
uint8_t getMenuItems(MenuLocation location, ModuleMenuItem *items, uint8_t maxItems)
Collects menu items from started modules for a given location.
void setModuleEnabled(uint8_t index, bool enabled)
Enables or disables a module by updating persisted disabled list.
void unregisterModule(const char *name)
Unregisters a module by name.
void clearModuleErrorByName(const char *name)
Clears stored module error by module name.
ModuleStartFailure classifyStartFailure(uint8_t index) const
Classifies why a preceding startModule() call failed.
void dispatchTick(uint32_t nowMs)
Dispatches periodic tick callback to started modules.
IModule * getModule(const char *name)
Looks up a module by name.
void dispatchLock()
Dispatches lock lifecycle event to started modules.
RAII wrapper for an NVS handle.
void close() noexcept
Manually close before destruction. Idempotent.
esp_err_t commit() noexcept
Commit pending writes. Caller checks the return value if needed.
static TropicSlotMap & instance()
Returns singleton Tropic slot-map instance.
static TropicStorage & instance()
Returns singleton instance of TROPIC metadata cache manager.
ServiceState getState() const override
static UsbManager & instance()
Returns singleton USB manager instance.
#define MODULE_DEFAULT_MAP(X)
static constexpr const char * MODULES_NVS_KEY_DISABLED
MenuLocation
Menu location for module registration.
static constexpr size_t MAX_MODULE_LIST_SIZE
static void removeNameFromList(const char *list, const char *name, char *dest, size_t capacity)
Removes a name from a comma-separated list, in place.
ModuleStartFailure
Classified cause of a failed startModule() call.
@ UsbBudgetFull
HID interface budget is exhausted.
@ Generic
Start failed for an unspecified reason.
@ SlotError
Module reported a slot-map error.
static constexpr const char * MODULES_NVS_NAMESPACE
NVS garbage collection for removed modules.
static constexpr const char * MODULES_NVS_KEY
static bool addNameToList(char *list, const char *name, size_t capacity)
Appends a name to a comma-separated list, in place.
union cdc::core::Event::@234350273243204124075032151001065005273232113040 data
Lock screen context menu item registered by a module.