CDC Badge OS
Firmware for the CDC Badge v1.0 hardware security key
Loading...
Searching...
No Matches
ExpertMenuUi.cpp
Go to the documentation of this file.
1
5
6#include "AppUiInternal.h"
7#include "cdc_os_ui/AppUi.h"
10#include "cdc_core/UsbManager.h"
11#include "cdc_core/EventBus.h"
13
14#include "esp_system.h"
15#include "freertos/FreeRTOS.h"
16#include "freertos/task.h"
17#include "soc/rtc_cntl_reg.h"
18
19#include <cstdio>
20#include <cstring>
21
22namespace cdc::ui {
23
25
26static constexpr uint8_t EXPERT_MAX_ITEMS = 16;
27static constexpr uint8_t MODULES_VIEW_MAX = 16;
28
29// Forward declarations so the fixed-entry tables can name their handlers
30// (defined later in this file; showModulesView comes from AppUiInternal.h).
31static void runSystemTest();
32static void runTropicCacheRebuild();
33static void runTropicCacheCleanup();
34static void enterShipModeMenu();
36
40struct FixedExpertEntry { const char* key; void (*action)(); };
41static const FixedExpertEntry kExpertTop[] = {
42 {"core.hardware_info", runSystemTest},
43 {"core.modules", showModulesView},
44 {"core.backup", showBackupMenu},
45 {"core.set_duress_pin", showDuressPinSetup},
46};
48 {"core.tr01_cache_rebuild", runTropicCacheRebuild},
49 {"core.tr01_cache_cleanup", runTropicCacheCleanup},
50 {"core.shipping_mode", enterShipModeMenu},
51 {"core.bootloader", rebootIntoBootloader},
52};
53static constexpr uint8_t EXPERT_TOP_COUNT = sizeof(kExpertTop) / sizeof(kExpertTop[0]);
54static constexpr uint8_t EXPERT_BOTTOM_COUNT = sizeof(kExpertBottom) / sizeof(kExpertBottom[0]);
56
58
59static ListView* s_expertMenu = nullptr;
62static uint8_t s_expertModuleCount = 0;
63
64static ListView* s_modulesView = nullptr;
67
69static void rebuildModulesView();
70
75static void onModuleRetryConfirm(void* userData) {
76 uint8_t index = static_cast<uint8_t>(reinterpret_cast<uintptr_t>(userData));
77 auto& moduleReg = core::ModuleRegistry::instance();
78
79 if (moduleReg.retryModule(index)) {
81 } else {
82 const char* error = moduleReg.getModuleSlotError(index);
83 showToastError(error ? error : ui::tr("core.failed"), TOAST_DURATION_MEDIUM_MS);
84 }
85
88}
89
95static void onModuleSelect(uint16_t index, void* userData) {
96 (void)userData;
97
98 auto& moduleReg = core::ModuleRegistry::instance();
99 if (index >= moduleReg.getModuleCount()) return;
100
101 core::IModule* module = moduleReg.getModuleAt(index);
102 if (!module) return;
103
104 uint8_t idx = static_cast<uint8_t>(index);
105
106 // If module has error, show retry dialog
107 if (moduleReg.hasModuleSlotError(idx)) {
108 const char* error = moduleReg.getModuleSlotError(idx);
109
110 static char confirmMsg[128];
111 snprintf(confirmMsg, sizeof(confirmMsg), "%s\n%s",
112 error ? error : ui::tr("core.module_error_generic"),
113 ui::tr("core.module_retry_prompt"));
114
115 showConfirm(confirmMsg, onModuleRetryConfirm, nullptr,
116 ConfirmView::Icon::ERROR, reinterpret_cast<void*>(static_cast<uintptr_t>(idx)));
117 return;
118 }
119
120 // Remember USB state before toggle
121 bool needsReplugBefore = core::UsbManager::instance().needsReplug();
122
123 // Normal toggle: enable/disable module
124 bool nowEnabled = moduleReg.toggleModuleEnabled(idx);
125
126 if (nowEnabled) {
127 if (!moduleReg.startModule(idx)) {
128 switch (moduleReg.classifyStartFailure(idx)) {
130 showToastError(moduleReg.getModuleSlotError(idx), TOAST_DURATION_MEDIUM_MS);
131 break;
133 showToastError(ui::tr("core.usb_no_free_slot"), TOAST_DURATION_MEDIUM_MS);
134 break;
137 break;
138 }
139 }
140 } else {
141 if (module->getState() == core::ServiceState::STARTED) {
142 module->stop();
143 }
144 }
145
146 // If USB config changed by THIS module toggle, show sticky alert
147 if (core::UsbManager::instance().newlyRequiresReplug(needsReplugBefore)) {
148 showToastAlertSticky(ui::tr("core.usb_replug_required"));
149 }
150
153}
154
158static void rebuildModulesView() {
159 auto& moduleReg = core::ModuleRegistry::instance();
160 uint8_t count = moduleReg.getModuleCount();
161
162 if (count == 0) {
163 s_modulesItems[0] = {"(none)", 0, false, nullptr};
164 count = 1;
165 } else {
166 for (uint8_t i = 0; i < count && i < MODULES_VIEW_MAX; i++) {
167 core::IModule* module = moduleReg.getModuleAt(i);
168 if (module) {
169 snprintf(s_moduleLabels[i], sizeof(s_moduleLabels[i]),
170 "%s %s", module->getName(),
171 moduleReg.getModuleStatusLabel(i));
172 s_modulesItems[i] = {s_moduleLabels[i], 0, false, nullptr};
173 }
174 }
175 if (count > MODULES_VIEW_MAX) count = MODULES_VIEW_MAX;
176 }
177
178 if (s_modulesView) {
179 s_modulesView->init(ui::tr("core.modules"), s_modulesItems, count);
180 }
181}
182
187 if (!s_modulesView) {
188 s_modulesView = new ListView();
189 }
190
192 s_modulesView->setOnSelect(onModuleSelect);
194}
195
199static void runSystemTest() {
201}
202
207 showToastTask(ui::tr("core.task_working"), 0);
210 if (ok) {
211 showToastSuccess(ui::tr("core.ok"));
212 } else {
213 showToastError(ui::tr("core.failed"));
214 }
215}
216
221 showToastTask(ui::tr("core.task_working"), 0);
224 if (ok) {
225 showToastSuccess(ui::tr("core.ok"));
226 } else {
227 showToastError(ui::tr("core.failed"));
228 }
229}
230
235static void onShipModeConfirm(void* /*userData*/) {
236 auto* power = hal::getPowerManagerInstance();
237 if (power) {
238 power->enterShipMode();
239 } else {
241 }
242}
243
245static void enterShipModeMenu() {
246 showConfirm(ui::tr("core.shipping_mode_q"), onShipModeConfirm, nullptr,
248}
249
251static void rebuildExpertMenu();
253static void onExpertMenuSelect(uint16_t index, void* userData);
254
259 // Duration 0: stays until the user dismisses it with Y/N, so the dismiss
260 // key is consumed by the modal and never selects the first list entry.
261 showToastInfo(ui::tr("core.expert_warning"), 0);
262 if (!s_expertMenu) {
263 s_expertMenu = new ListView();
264 s_expertMenu->setOnSelect(onExpertMenuSelect);
265 }
266
269}
270
274static void rebuildExpertMenu() {
275 auto& moduleReg = core::ModuleRegistry::instance();
276 uint8_t n = 0;
277
278 // Top fixed entries (system test, modules), then the dynamic module
279 // entries (e.g. vFAT), then the bottom fixed entries (TROPIC, bootloader).
280 for (uint8_t i = 0; i < EXPERT_TOP_COUNT; i++)
281 s_expertItems[n++] = {ui::tr(kExpertTop[i].key), 0, false, nullptr};
282
283 uint8_t rawModules = moduleReg.getMenuItems(
287 );
288 uint8_t visible = 0;
289 for (uint8_t i = 0; i < rawModules; i++) {
290 const auto& item = s_expertModuleItems[i];
291 if (item.isVisible && !item.isVisible()) continue;
292 s_expertModuleItems[visible] = item; // compact visible entries
293 s_expertItems[n++] = {item.label, 0, false, nullptr};
294 visible++;
295 }
296 s_expertModuleCount = visible;
297
298 for (uint8_t i = 0; i < EXPERT_BOTTOM_COUNT; i++)
299 s_expertItems[n++] = {ui::tr(kExpertBottom[i].key), 0, false, nullptr};
300
301 s_expertMenu->init(ui::tr("core.expert"), s_expertItems, n);
302}
303
317[[noreturn]] static void bootloaderResetTask(void*) {
318 vTaskDelay(pdMS_TO_TICKS(200));
319 REG_WRITE(RTC_CNTL_OPTION1_REG, RTC_CNTL_FORCE_DOWNLOAD_BOOT);
320 esp_restart();
321 while (true) { vTaskDelay(portMAX_DELAY); }
322}
323
335
336 xTaskCreate(bootloaderResetTask, "btldr_reset", 4096, nullptr,
337 configMAX_PRIORITIES - 1, nullptr);
338}
339
345static void onExpertMenuSelect(uint16_t index, void* userData) {
346 (void)userData;
347
348 if (index < EXPERT_TOP_COUNT) {
349 kExpertTop[index].action();
350 return;
351 }
352
353 uint16_t modIdx = index - EXPERT_TOP_COUNT;
354 if (modIdx < s_expertModuleCount) {
355 const auto& item = s_expertModuleItems[modIdx];
356 if (item.getView) {
357 IView* view = item.getView();
358 if (view) {
360 }
361 }
362 return;
363 }
364
365 uint16_t botIdx = modIdx - s_expertModuleCount;
366 if (botIdx < EXPERT_BOTTOM_COUNT) {
367 kExpertBottom[botIdx].action();
368 }
369}
370
376 if (evt.type != core::EventType::MODULE_ERROR) return;
377
378 auto& moduleReg = core::ModuleRegistry::instance();
379 uint8_t index = static_cast<uint8_t>(evt.data.value);
380
381 if (index >= moduleReg.getModuleCount()) return;
382
383 const char* error = moduleReg.getModuleSlotError(index);
384 core::IModule* module = moduleReg.getModuleAt(index);
385 const char* name = module ? module->getName() : "?";
386
387 static char errMsg[96];
388 snprintf(errMsg, sizeof(errMsg), "%s: %s", name, error ? error : "Fehler");
389
391}
392
393} // namespace cdc::ui
char name[cdc::hal::ISecureElement::RMEM_NAME_LEN]
Module interface that extends IService with module-specific features.
Definition IModule.h:55
virtual ServiceState getState() const =0
virtual const char * getName() const =0
static ModuleRegistry & instance()
Returns the singleton module registry instance.
bool rebuild()
Rebuilds cache contents from secure-element R-MEM without verbose logging.
static TropicStorage & instance()
Returns singleton instance of TROPIC metadata cache manager.
bool cleanup()
Removes cache entries and chip records that violate slot/module mapping.
bool needsReplug() const
Definition UsbManager.h:69
static UsbManager & instance()
Returns singleton USB manager instance.
static ViewStack & instance()
Returns singleton view-stack instance.
Definition ViewStack.cpp:34
void push(IView *view, void *context=nullptr)
@ UsbBudgetFull
HID interface budget is exhausted.
@ Generic
Start failed for an unspecified reason.
@ SlotError
Module reported a slot-map error.
IPowerManager * getPowerManagerInstance()
Returns the singleton power manager instance.
Centralized key-code constants for cdc_views.
Definition IModule.h:8
const char * tr(const char *key)
Look up a translation by string key.
Definition I18n.h:208
static void onModuleSelect(uint16_t index, void *userData)
Handles module list selection for retry/toggle behavior.
static const FixedExpertEntry kExpertTop[]
static void onModuleRetryConfirm(void *userData)
Retries failed module initialization after user confirmation.
static ListItem s_expertItems[EXPERT_MAX_ITEMS]
void showDuressPinSetup()
Opens the duress / self-destruct PIN setup wizard.
Definition AppUi.cpp:389
static ListView * s_modulesView
static constexpr uint8_t EXPERT_FIXED_COUNT
void prepareForBootloaderReset()
Puts the badge into a quiet pre-reset state.
Definition AppUi.cpp:1052
static void onExpertMenuSelect(uint16_t index, void *userData)
Handles expert menu selection actions.
static constexpr uint32_t TOAST_DURATION_LONG_MS
static constexpr uint8_t EXPERT_MAX_ITEMS
Expert menu sizing constants.
static void runTropicCacheCleanup()
Cleans cached TROPIC metadata and reports operation result.
static constexpr uint8_t EXPERT_BOTTOM_COUNT
void showToastTask(const char *message, uint16_t durationMs=0)
Shows a task/progress toast message.
void ui_rebuild_menus()
Rebuilds dynamic UI menus.
Definition AppUi.cpp:1040
void rebootIntoBootloader()
Reboots the device into USB download (bootloader) mode.
static core::ModuleMenuItem s_expertModuleItems[EXPERT_MAX_ITEMS - EXPERT_FIXED_COUNT]
static constexpr uint8_t MODULES_VIEW_MAX
static constexpr uint32_t TOAST_DURATION_MEDIUM_MS
void showModulesView()
Shows module management list view.
void onModuleErrorEvent(const core::Event &evt)
Displays toast notification for module error events.
static void onShipModeConfirm(void *)
Disconnects the battery (ship mode) after user confirmation.
void showToastAlertSticky(const char *message)
Shows a non-dismissible alert toast.
static const FixedExpertEntry kExpertBottom[]
void showExpertMenu()
Shows expert menu and initial warning toast.
static ListItem s_modulesItems[MODULES_VIEW_MAX]
static constexpr uint8_t EXPERT_TOP_COUNT
static ListView * s_expertMenu
Static view pointers and menu item storage for expert/module views.
static void bootloaderResetTask(void *)
Worker that detaches USB, arms the download-boot bit and triggers a hard system reset.
void showConfirm(const char *message, ConfirmView::ConfirmCallback onConfirm, ConfirmView::CancelCallback onCancel=nullptr, ConfirmView::Icon icon=ConfirmView::Icon::QUESTION, void *userData=nullptr)
Shows a shared modal confirmation dialog instance.
static uint8_t s_expertModuleCount
static void runTropicCacheRebuild()
Rebuilds cached TROPIC metadata and reports operation result.
void showToastSuccess(const char *message, uint16_t durationMs=1500)
Shows a success toast message.
static void runSystemTest()
Opens hardware information/system-test screen.
static constexpr uint32_t TOAST_DURATION_SHORT_MS
static void rebuildExpertMenu()
Rebuilds expert menu item list including module-provided entries.
void showToastInfo(const char *message, uint16_t durationMs=1500)
Shows an informational toast message.
static void enterShipModeMenu()
Prompts to confirm ship-mode entry from the expert menu.
void showToastError(const char *message, uint16_t durationMs=1500)
Shows an error toast message.
static void rebuildModulesView()
Rebuilds module status list view content.
static char s_moduleLabels[MODULES_VIEW_MAX][48]
void showHardwareInfo()
Opens hardware info screen using shared info view.
void showBackupMenu()
Shows the Backup submenu (Export / Import / Delete).
uint8_t value
Definition EventBus.h:58
union cdc::core::Event::@234350273243204124075032151001065005273232113040 data
EventType type
Definition EventBus.h:54
Menu item registered by a module.
Definition IModule.h:29