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"
11#include "cdc_core/UsbManager.h"
12#include "cdc_core/EventBus.h"
14
15#include "esp_system.h"
16#include "freertos/FreeRTOS.h"
17#include "freertos/task.h"
18#include "soc/rtc_cntl_reg.h"
19
20#include <cstdio>
21#include <cstring>
22
23namespace cdc::ui {
24
26
27static constexpr uint8_t EXPERT_MAX_ITEMS = 16;
28static constexpr uint8_t MODULES_VIEW_MAX = 16;
29
30// Forward declarations so the fixed-entry tables can name their handlers
31// (defined later in this file; showModulesView comes from AppUiInternal.h).
32static void runSystemTest();
33static void runTropicCacheRebuild();
34static void runTropicCacheCleanup();
35static void enterShipModeMenu();
37
41struct FixedExpertEntry { const char* key; void (*action)(); };
42static const FixedExpertEntry kExpertTop[] = {
43 {"core.hardware_info", runSystemTest},
44 {"core.firmware_check", showFirmwareCheck},
45 {"core.modules", showModulesView},
46 {"core.backup", showBackupMenu},
47 {"core.set_duress_pin", showDuressPinSetup},
48};
50 {"core.tr01_cache_rebuild", runTropicCacheRebuild},
51 {"core.tr01_cache_cleanup", runTropicCacheCleanup},
52 {"core.shipping_mode", enterShipModeMenu},
53 {"core.bootloader", rebootIntoBootloader},
54};
55static constexpr uint8_t EXPERT_TOP_COUNT = sizeof(kExpertTop) / sizeof(kExpertTop[0]);
56static constexpr uint8_t EXPERT_BOTTOM_COUNT = sizeof(kExpertBottom) / sizeof(kExpertBottom[0]);
58
60
61static ListView* s_expertMenu = nullptr;
64static uint8_t s_expertModuleCount = 0;
65
66static ListView* s_modulesView = nullptr;
69
71static void rebuildModulesView();
72
77static void onModuleRetryConfirm(void* userData) {
78 uint8_t index = static_cast<uint8_t>(reinterpret_cast<uintptr_t>(userData));
79 auto& moduleReg = core::ModuleRegistry::instance();
80
81 if (moduleReg.retryModule(index)) {
83 } else {
84 const char* error = moduleReg.getModuleSlotError(index);
85 showToastError(error ? error : ui::tr("core.failed"), TOAST_DURATION_MEDIUM_MS);
86 }
87
90}
91
97static void onModuleSelect(uint16_t index, void* userData) {
98 (void)userData;
99
100 auto& moduleReg = core::ModuleRegistry::instance();
101 if (index >= moduleReg.getModuleCount()) return;
102
103 core::IModule* module = moduleReg.getModuleAt(index);
104 if (!module) return;
105
106 uint8_t idx = static_cast<uint8_t>(index);
107
108 // If module has error, show retry dialog
109 if (moduleReg.hasModuleSlotError(idx)) {
110 const char* error = moduleReg.getModuleSlotError(idx);
111
112 static char confirmMsg[128];
113 snprintf(confirmMsg, sizeof(confirmMsg), "%s\n%s",
114 error ? error : ui::tr("core.module_error_generic"),
115 ui::tr("core.module_retry_prompt"));
116
117 showConfirm(confirmMsg, onModuleRetryConfirm, nullptr,
118 ConfirmView::Icon::ERROR, reinterpret_cast<void*>(static_cast<uintptr_t>(idx)));
119 return;
120 }
121
122 // Remember USB state before toggle
123 bool needsReplugBefore = core::UsbManager::instance().needsReplug();
124
125 // Normal toggle: enable/disable module
126 bool nowEnabled = moduleReg.toggleModuleEnabled(idx);
127
128 if (nowEnabled) {
129 if (!moduleReg.startModule(idx)) {
130 switch (moduleReg.classifyStartFailure(idx)) {
132 showToastError(moduleReg.getModuleSlotError(idx), TOAST_DURATION_MEDIUM_MS);
133 break;
135 showToastError(ui::tr("core.usb_no_free_slot"), TOAST_DURATION_MEDIUM_MS);
136 break;
139 break;
140 }
141 }
142 } else {
143 if (module->getState() == core::ServiceState::STARTED) {
144 module->stop();
145 }
146 }
147
148 // If USB config changed by THIS module toggle, show sticky alert
149 if (core::UsbManager::instance().newlyRequiresReplug(needsReplugBefore)) {
150 showToastAlertSticky(ui::tr("core.usb_replug_required"));
151 }
152
155}
156
160static void rebuildModulesView() {
161 auto& moduleReg = core::ModuleRegistry::instance();
162 uint8_t count = moduleReg.getModuleCount();
163
164 if (count == 0) {
165 s_modulesItems[0] = {"(none)", 0, false, nullptr};
166 count = 1;
167 } else {
168 for (uint8_t i = 0; i < count && i < MODULES_VIEW_MAX; i++) {
169 core::IModule* module = moduleReg.getModuleAt(i);
170 if (module) {
171 snprintf(s_moduleLabels[i], sizeof(s_moduleLabels[i]),
172 "%s %s", module->getName(),
173 moduleReg.getModuleStatusLabel(i));
174 s_modulesItems[i] = {s_moduleLabels[i], 0, false, nullptr};
175 }
176 }
177 if (count > MODULES_VIEW_MAX) count = MODULES_VIEW_MAX;
178 }
179
180 if (s_modulesView) {
181 s_modulesView->init(ui::tr("core.modules"), s_modulesItems, count);
182 }
183}
184
189 if (!s_modulesView) {
190 s_modulesView = new ListView();
191 }
192
194 s_modulesView->setOnSelect(onModuleSelect);
196}
197
201static void runSystemTest() {
203}
204
209 showToastTask(ui::tr("core.task_working"), 0);
212 if (ok) {
213 showToastSuccess(ui::tr("core.ok"));
214 } else {
215 showToastError(ui::tr("core.failed"));
216 }
217}
218
223 showToastTask(ui::tr("core.task_working"), 0);
226 if (ok) {
227 showToastSuccess(ui::tr("core.ok"));
228 } else {
229 showToastError(ui::tr("core.failed"));
230 }
231}
232
237static void onShipModeConfirm(void* /*userData*/) {
238 auto* power = hal::getPowerManagerInstance();
239 if (power) {
240 power->enterShipMode();
241 } else {
243 }
244}
245
247static void enterShipModeMenu() {
248 showConfirm(ui::tr("core.shipping_mode_q"), onShipModeConfirm, nullptr,
250}
251
253static void rebuildExpertMenu();
255static void onExpertMenuSelect(uint16_t index, void* userData);
256
261 // Duration 0: stays until the user dismisses it with Y/N, so the dismiss
262 // key is consumed by the modal and never selects the first list entry.
263 showToastInfo(ui::tr("core.expert_warning"), 0);
264 if (!s_expertMenu) {
265 s_expertMenu = new ListView();
266 s_expertMenu->setOnSelect(onExpertMenuSelect);
267 }
268
271}
272
276static void rebuildExpertMenu() {
277 auto& moduleReg = core::ModuleRegistry::instance();
278 uint8_t n = 0;
279
280 // Top fixed entries (system test, modules), then the dynamic module
281 // entries (e.g. vFAT), then the bottom fixed entries (TROPIC, bootloader).
282 for (uint8_t i = 0; i < EXPERT_TOP_COUNT; i++)
283 s_expertItems[n++] = {ui::tr(kExpertTop[i].key), 0, false, nullptr};
284
285 uint8_t rawModules = moduleReg.getMenuItems(
289 );
290 uint8_t visible = 0;
291 for (uint8_t i = 0; i < rawModules; i++) {
292 const auto& item = s_expertModuleItems[i];
293 if (item.isVisible && !item.isVisible()) continue;
294 s_expertModuleItems[visible] = item; // compact visible entries
295 s_expertItems[n++] = {item.label, 0, false, nullptr};
296 visible++;
297 }
298 s_expertModuleCount = visible;
299
300 for (uint8_t i = 0; i < EXPERT_BOTTOM_COUNT; i++)
301 s_expertItems[n++] = {ui::tr(kExpertBottom[i].key), 0, false, nullptr};
302
303 s_expertMenu->init(ui::tr("core.expert"), s_expertItems, n);
304}
305
319[[noreturn]] static void bootloaderResetTask(void*) {
320 vTaskDelay(pdMS_TO_TICKS(200));
321 REG_WRITE(RTC_CNTL_OPTION1_REG, RTC_CNTL_FORCE_DOWNLOAD_BOOT);
322 esp_restart();
323 while (true) { vTaskDelay(portMAX_DELAY); }
324}
325
337
338 xTaskCreate(bootloaderResetTask, "btldr_reset", 4096, nullptr,
339 configMAX_PRIORITIES - 1, nullptr);
340}
341
347static void onExpertMenuSelect(uint16_t index, void* userData) {
348 (void)userData;
349
350 if (index < EXPERT_TOP_COUNT) {
351 kExpertTop[index].action();
352 return;
353 }
354
355 uint16_t modIdx = index - EXPERT_TOP_COUNT;
356 if (modIdx < s_expertModuleCount) {
357 const auto& item = s_expertModuleItems[modIdx];
358 if (item.getView) {
359 IView* view = item.getView();
360 if (view) {
362 }
363 }
364 return;
365 }
366
367 uint16_t botIdx = modIdx - s_expertModuleCount;
368 if (botIdx < EXPERT_BOTTOM_COUNT) {
369 kExpertBottom[botIdx].action();
370 }
371}
372
378 if (evt.type != core::EventType::MODULE_ERROR) return;
379
380 auto& moduleReg = core::ModuleRegistry::instance();
381 uint8_t index = static_cast<uint8_t>(evt.data.value);
382
383 if (index >= moduleReg.getModuleCount()) return;
384
385 const char* error = moduleReg.getModuleSlotError(index);
386 core::IModule* module = moduleReg.getModuleAt(index);
387 const char* name = module ? module->getName() : "?";
388
389 static char errMsg[96];
390 snprintf(errMsg, sizeof(errMsg), "%s: %s", name, error ? error : "Fehler");
391
393}
394
395} // namespace cdc::ui
Expert-menu firmware/version screen with an optional upstream update check against the GitHub release...
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:53
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:209
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:403
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:1129
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:1117
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.
void showFirmwareCheck()
Opens the Firmware Check screen and pushes it onto the view stack.
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:70
union cdc::core::Event::@234350273243204124075032151001065005273232113040 data
EventType type
Definition EventBus.h:66
Menu item registered by a module.
Definition IModule.h:29