33#include "freertos/FreeRTOS.h"
34#include "freertos/task.h"
35#include "esp_memory_utils.h"
42static const char*
TAG =
"SERIAL";
96#if FEATURE_SECURE_SERIAL
101static void resetAuthTimer() {
114 if (!cmd || !*cmd)
return;
142static void redrawLine(
const char* newContent,
size_t& bufferPos) {
143 while (bufferPos > 0) {
179 if (!args || !*args) {
184 char* endptr =
nullptr;
185 long slotVal = strtol(args, &endptr, 10);
187 if (endptr == args || *endptr !=
'\0' || slotVal < 0) {
192 if (slotVal >= maxSlot) {
193 Console::printf(
"ERROR: Invalid %s (0-%d)\r\n", slotTypeName, maxSlot - 1);
198 result.
value = slotVal;
229static void printHexDump(
const uint8_t* data,
size_t len,
size_t maxBytes) {
230 for (
size_t i = 0; i < len && i < maxBytes; i +=
HEX_DUMP_WIDTH) {
237 if (len > maxBytes) {
249 case NVS_TYPE_U8:
return "u8";
250 case NVS_TYPE_I8:
return "i8";
251 case NVS_TYPE_U16:
return "u16";
252 case NVS_TYPE_I16:
return "i16";
253 case NVS_TYPE_U32:
return "u32";
254 case NVS_TYPE_I32:
return "i32";
255 case NVS_TYPE_U64:
return "u64";
256 case NVS_TYPE_I64:
return "i64";
257 case NVS_TYPE_STR:
return "str";
258 case NVS_TYPE_BLOB:
return "blob";
270 nvs_iterator_t it =
nullptr;
271 esp_err_t err = nvs_entry_find(
"nvs", ns, NVS_TYPE_ANY, &it);
272 nvs_type_t keyType = NVS_TYPE_ANY;
274 while (it !=
nullptr) {
275 nvs_entry_info_t info;
276 nvs_entry_info(it, &info);
277 if (strcmp(info.key, key) == 0) {
281 err = nvs_entry_next(&it);
282 if (err != ESP_OK)
break;
284 nvs_release_iterator(it);
295static void printNvsValue(nvs_handle_t nvs,
const char* key, nvs_type_t type) {
299 if (nvs_get_u8(nvs, key, &val) == ESP_OK) {
306 if (nvs_get_i8(nvs, key, &val) == ESP_OK) {
313 if (nvs_get_u16(nvs, key, &val) == ESP_OK) {
320 if (nvs_get_i16(nvs, key, &val) == ESP_OK) {
327 if (nvs_get_u32(nvs, key, &val) == ESP_OK) {
328 Console::printf(
"%lu (0x%08lX)\r\n", (
unsigned long)val, (
unsigned long)val);
334 if (nvs_get_i32(nvs, key, &val) == ESP_OK) {
341 if (nvs_get_u64(nvs, key, &val) == ESP_OK) {
348 if (nvs_get_i64(nvs, key, &val) == ESP_OK) {
355 if (nvs_get_str(nvs, key,
nullptr, &len) == ESP_OK && len > 0) {
356 char* buf =
static_cast<char*
>(malloc(len));
358 LOG_E(
TAG,
"Failed to allocate %d bytes for NVS string", (
int)len);
362 if (nvs_get_str(nvs, key, buf, &len) == ESP_OK) {
369 case NVS_TYPE_BLOB: {
371 if (nvs_get_blob(nvs, key,
nullptr, &len) == ESP_OK && len > 0) {
373 uint8_t* buf =
static_cast<uint8_t*
>(malloc(len));
375 LOG_E(
TAG,
"Failed to allocate %d bytes for NVS blob", (
int)len);
379 if (nvs_get_blob(nvs, key, buf, &len) == ESP_OK) {
403 gettimeofday(&tv,
nullptr);
404 return localtime_r(&tv.tv_sec, &tm) !=
nullptr;
414 tv.tv_sec = mktime(tm);
416 return settimeofday(&tv,
nullptr) == 0;
466 Console::printf(
"Free heap: %lu bytes\r\n", (
unsigned long)esp_get_free_heap_size());
467 Console::printf(
"Min free heap: %lu bytes\r\n", (
unsigned long)esp_get_minimum_free_heap_size());
468 Console::printf(
"Uptime: %llu ms\r\n", esp_timer_get_time() / 1000ULL);
490 multi_heap_info_t info;
491 heap_caps_get_info(&info, caps);
492 if (info.total_free_bytes + info.total_allocated_bytes == 0)
return;
494 Console::printf(
" total free : %lu\r\n", (
unsigned long)info.total_free_bytes);
495 Console::printf(
" total allocated : %lu\r\n", (
unsigned long)info.total_allocated_bytes);
496 Console::printf(
" largest free : %lu\r\n", (
unsigned long)info.largest_free_block);
497 Console::printf(
" min ever free : %lu\r\n", (
unsigned long)info.minimum_free_bytes);
498 Console::printf(
" free blocks : %lu\r\n", (
unsigned long)info.free_blocks);
499 Console::printf(
" alloc blocks : %lu\r\n", (
unsigned long)info.allocated_blocks);
507 printHeapRegion(
"Internal DRAM (8-bit)", MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT);
508 printHeapRegion(
"Internal 32-bit only", MALLOC_CAP_INTERNAL | MALLOC_CAP_32BIT);
514 Console::printf(
"\r\n-- Heap totals (heap_caps_get_total_size) --\r\n");
515 Console::printf(
" INTERNAL : %u\r\n", (
unsigned)heap_caps_get_total_size(MALLOC_CAP_INTERNAL));
516 Console::printf(
" EXEC : %u\r\n", (
unsigned)heap_caps_get_total_size(MALLOC_CAP_EXEC));
517 Console::printf(
" SPIRAM : %u\r\n", (
unsigned)heap_caps_get_total_size(MALLOC_CAP_SPIRAM));
518 Console::printf(
" DMA : %u\r\n", (
unsigned)heap_caps_get_total_size(MALLOC_CAP_DMA));
520 (
unsigned)heap_caps_get_free_size(MALLOC_CAP_INTERNAL | MALLOC_CAP_EXEC),
521 (
unsigned)heap_caps_get_largest_free_block(MALLOC_CAP_INTERNAL | MALLOC_CAP_EXEC),
522 (
unsigned)heap_caps_get_total_size(MALLOC_CAP_INTERNAL | MALLOC_CAP_EXEC));
524 (
unsigned)heap_caps_get_free_size(MALLOC_CAP_32BIT),
525 (
unsigned)heap_caps_get_largest_free_block(MALLOC_CAP_32BIT));
527#if CONFIG_FREERTOS_USE_TRACE_FACILITY
528 UBaseType_t numTasks = uxTaskGetNumberOfTasks();
529 TaskStatus_t* tasks = (TaskStatus_t*)calloc(numTasks,
sizeof(TaskStatus_t));
531 numTasks = uxTaskGetSystemState(tasks, numTasks,
nullptr);
534 uint32_t totalStackFree = 0;
535 for (UBaseType_t i = 0; i < numTasks; i++) {
536 const char* st =
"?";
537 switch (tasks[i].eCurrentState) {
538 case eRunning: st =
"RUN";
break;
539 case eReady: st =
"RDY";
break;
540 case eBlocked: st =
"BLK";
break;
541 case eSuspended: st =
"SUS";
break;
542 case eDeleted: st =
"DEL";
break;
543 case eInvalid: st =
"INV";
break;
545 BaseType_t coreId = -1;
546#if INCLUDE_xTaskGetCoreID
547 coreId = xTaskGetCoreID(tasks[i].xHandle);
549 const char* stackLoc =
"DRAM";
550 if (tasks[i].pxStackBase !=
nullptr &&
551 esp_ptr_external_ram(tasks[i].pxStackBase)) {
556 (
unsigned)tasks[i].uxCurrentPriority,
557 (
unsigned long)tasks[i].usStackHighWaterMark,
561 totalStackFree += tasks[i].usStackHighWaterMark;
564 (
unsigned long)totalStackFree);
568 Console::printf(
"\r\nTask list unavailable (FREERTOS_USE_TRACE_FACILITY=n)\r\n");
578 (
unsigned long)esp_get_free_heap_size(),
579 (
unsigned long)heap_caps_get_total_size(MALLOC_CAP_DEFAULT));
581 size_t intFree = heap_caps_get_free_size(MALLOC_CAP_INTERNAL);
582 size_t intTotal = heap_caps_get_total_size(MALLOC_CAP_INTERNAL);
583 size_t intLargest = heap_caps_get_largest_free_block(MALLOC_CAP_INTERNAL);
584 Console::printf(
"Internal DRAM: %lu / %lu free (largest block %lu)\r\n",
585 (
unsigned long)intFree,
586 (
unsigned long)intTotal,
587 (
unsigned long)intLargest);
589 size_t dmaFree = heap_caps_get_free_size(MALLOC_CAP_DMA);
590 size_t dmaLargest = heap_caps_get_largest_free_block(MALLOC_CAP_DMA);
592 (
unsigned long)dmaFree,
593 (
unsigned long)dmaLargest);
595 size_t psramFree = heap_caps_get_free_size(MALLOC_CAP_SPIRAM);
596 size_t psramTotal = heap_caps_get_total_size(MALLOC_CAP_SPIRAM);
597 if (psramTotal > 0) {
599 (
unsigned long)psramFree,
600 (
unsigned long)psramTotal);
613 vTaskDelay(pdMS_TO_TICKS(100));
641 power->enterShipMode();
646 if (!args || !*args) {
651 if (!top || strcmp(top->
getName(),
"T9InputView") != 0) {
665 if (args && strcmp(args,
"CLEAR") == 0) {
682 if (!args || strcmp(args,
"YES") != 0) {
695 Console::printf(
"ERROR: NVS wipe failed (%s)\r\n", esp_err_to_name(err));
706 const char* nsFilter = (args && *args) ? args :
nullptr;
708 nvs_iterator_t it =
nullptr;
709 esp_err_t err = nvs_entry_find(
"nvs", nsFilter, NVS_TYPE_ANY, &it);
711 if (err == ESP_ERR_NVS_NOT_FOUND) {
721 Console::printf(
"ERROR: nvs_entry_find failed (%s)\r\n", esp_err_to_name(err));
733 while (it !=
nullptr) {
734 nvs_entry_info_t info;
735 nvs_entry_info(it, &info);
737 if (!nsFilter && strcmp(lastNs, info.namespace_name) != 0) {
738 strncpy(lastNs, info.namespace_name,
sizeof(lastNs) - 1);
745 err = nvs_entry_next(&it);
746 if (err != ESP_OK)
break;
749 nvs_release_iterator(it);
758 if (!args || !*args) {
765 if (sscanf(args,
"%15s %15s", ns, key) != 2) {
771 esp_err_t err = nvs_open(ns, NVS_READONLY, &nvs);
773 Console::printf(
"ERROR: Cannot open namespace '%s' (%s)\r\n", ns, esp_err_to_name(err));
778 if (keyType == NVS_TYPE_ANY) {
779 Console::printf(
"ERROR: Key '%s' not found in namespace '%s'\r\n", key, ns);
794 if (!args || !*args) {
802 int parsed = sscanf(args,
"%15s %15s", ns, key);
810 esp_err_t err = nvs_open(ns, NVS_READWRITE, &nvs);
812 Console::printf(
"ERROR: Cannot open namespace '%s' (%s)\r\n", ns, esp_err_to_name(err));
816 if (parsed == 1 || key[0] ==
'\0') {
817 err = nvs_erase_all(nvs);
825 err = nvs_erase_key(nvs, key);
829 }
else if (err == ESP_ERR_NVS_NOT_FOUND) {
832 Console::printf(
"ERROR: Delete failed (%s)\r\n", esp_err_to_name(err));
852 Console::printf(
"%02d:%02d:%02d\r\n", tm.tm_hour, tm.tm_min, tm.tm_sec);
867 Console::printf(
"%02d.%02d.%04d\r\n", tm.tm_mday, tm.tm_mon + 1, tm.tm_year + 1900);
878 if (!args || !*args) {
883 if (sscanf(args,
"%d:%d:%d", &h, &m, &s) != 3) {
887 if (h < 0 || h > 23 || m < 0 || m > 59 || s < 0 || s > 59) {
916 if (!args || !*args) {
922 if (sscanf(args,
"%d.%d.%d", &d, &m, &y) == 3) {
923 if (d < 1 || d > 31 || m < 1 || m > 12 || y < YEAR_MIN || y >
YEAR_MAX) {
932 tm.tm_year = y - 1900;
948 if (sscanf(args,
"%lld", &ts) != 1 || ts < 0) {
949 Console::printf(
"ERROR: Invalid format. Use DD.MM.YYYY or a Unix timestamp\r\n");
952 time_t secs =
static_cast<time_t
>(ts);
954 if (!gmtime_r(&secs, &tm)) {
958 int year = tm.tm_year + 1900;
959 if (year < YEAR_MIN || year >
YEAR_MAX) {
966 if (settimeofday(&tv,
nullptr) == 0) {
967 Console::printf(
"OK: Time set to %lld (%04d-%02d-%02d %02d:%02d:%02d UTC)\r\n",
968 ts, year, tm.tm_mon + 1, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec);
986 if (!args) args =
"";
998 if (!args) args =
"";
1010 if (!args) args =
"";
1021#if FEATURE_SECURE_SERIAL
1026static void cmdAuth(
const char* args) {
1029 if (
pm.isBadgeBlocked()) {
1030 if (
pm.isLockoutActive()) {
1031 uint32_t remainingSec =
pm.getLockoutRemainingMs() / 1000;
1032 Console::printf(
"ERROR: PIN locked. Wait %lu seconds.\r\n", (
unsigned long)remainingSec);
1039 if (!args || !*args) {
1059 uint8_t retries = pm.getBadgeRetries();
1061 if (pm.isLockoutActive()) {
1062 uint32_t remainingSec = pm.getLockoutRemainingMs() / 1000;
1063 Console::printf(
"ERROR: Wrong PIN. Locked for %lu seconds.\r\n", (
unsigned long)remainingSec);
1068 Console::printf(
"ERROR: Wrong PIN. %d retries remaining.\r\n", retries);
1077static void cmdLogout(
const char* args) {
1107 pm.getBadgeRetries(),
1108 pm.isBadgeBlocked() ?
"yes" :
"no",
1109 pm.isPinSet() ?
"yes" :
"no");
1117 if (!args || !args[0]) {
1122 const char* space = strchr(args,
' ');
1131 size_t curLen =
static_cast<size_t>(space - args);
1138 memcpy(currentPin, args, curLen);
1139 currentPin[curLen] =
'\0';
1141 const char* p = space + 1;
1142 while (*p ==
' ') p++;
1143 size_t newLen = strlen(p);
1150 memcpy(newPin, p, newLen);
1151 newPin[newLen] =
'\0';
1153 auto isDigits = [](
const char* s) {
1154 for (; *s; ++s)
if (!isdigit(
static_cast<unsigned char>(*s)))
return false;
1157 if (!isDigits(currentPin) || !isDigits(newPin)) {
1163 if (
pm.isBadgeBlocked()) {
1164 Console::printf(
"ERROR: PIN entry blocked (lockout active or no retries left)\r\n");
1168 if (!
pm.changeBadgePin(currentPin, newPin)) {
1169 Console::printf(
"ERROR: PIN change failed (current PIN wrong or new PIN invalid)\r\n");
1185 if (!args || !args[0]) {
1191 size_t len = strlen(args);
1198 memcpy(duressPin, args, len);
1199 duressPin[len] =
'\0';
1202 Console::printf(
"ERROR: Duress PIN rejected (invalid length, non-digit, or equal to badge PIN)\r\n");
1233 Console::printf(
" Session: %s\r\n", se->isSessionActive() ?
"active" :
"inactive");
1246 uint8_t riscvVer[4] = {0};
1247 uint8_t spectVer[4] = {0};
1251 if (se->getChipId(chipId,
sizeof(chipId))) {
1253 for (
int i = 0; i < 8; i++) {
1261 if (se->getFwVersion(riscvVer, spectVer)) {
1263 riscvVer[3], riscvVer[2], riscvVer[1], riscvVer[0]);
1265 spectVer[3], spectVer[2], spectVer[1], spectVer[0]);
1280 if (se->isSessionActive()) {
1285 if (se->sessionStart()) {
1304 if (se->eccSlotUsed(i)) {
1309 if (eccCount == 0) {
1330 if (!result.valid) {
1338 uint16_t slot =
static_cast<uint16_t
>(result.value);
1340 uint16_t actualLen = 0;
1342 hal::SeResult seResult = se->rmemRead(slot, data,
sizeof(data), &actualLen);
1358 if (!result.valid) {
1366 uint8_t slot =
static_cast<uint8_t
>(result.value);
1381 if (!result.valid) {
1389 uint16_t slot =
static_cast<uint16_t
>(result.value);
1409 if (se->isSessionActive()) {
1413 if (se->sessionStart()) {
1429 auto logFn = [](uint16_t slot,
const char* message,
void* ctx) {
1431 if (!message)
return;
1432 if (strcmp(message,
"invalid header") == 0 ||
1433 strcmp(message,
"mismatched module") == 0 ||
1434 strcmp(message,
"nvs write failed") == 0 ||
1435 strcmp(message,
"session start failed") == 0 ||
1436 strcmp(message,
"read failed") == 0) {
1443 if (storage.rebuildVerbose(logFn,
nullptr)) {
1458 if (storage.cleanup()) {
1473 if (!args || strcmp(args,
"CONFIRM") != 0) {
1474 Console::printf(
"WARNING: This will ERASE ALL data on TROPIC01!\r\n");
1484 if (!se->isSessionActive()) {
1485 if (!se->sessionStart()) {
1491 Console::printf(
"Erasing ECC keys and R-Memory (this may take a while)...\r\n");
1494 [](uint16_t current, uint16_t total) {
1499 if (!result.sessionReady) {
1506 result.eccDeleted, result.rmemDeleted);
1521#if FEATURE_SECURE_SERIAL
1537 LOG_I(
TAG,
"Serial command processor initialized");
1540#if FEATURE_SECURE_SERIAL
1556void SerialCmd::handleHistoryNav(HistoryDirection dir) {
1557 if (dir == HistoryDirection::OLDER) {
1591bool SerialCmd::handleEscape(
int c) {
1605 handleHistoryNav(HistoryDirection::OLDER);
1608 handleHistoryNav(HistoryDirection::NEWER);
1629void SerialCmd::handleSpecialChar(
int c,
bool& commandReady) {
1630 commandReady =
false;
1636 bi(
static_cast<uint8_t
>(c));
1656 commandReady =
true;
1682 static uint8_t utf8Pending = 0;
1683 static uint32_t utf8Cp = 0;
1685 auto appendByte = [](uint8_t b) {
1693 if ((c & 0xC0) == 0x80) {
1694 utf8Cp = (utf8Cp << 6) | (c & 0x3F);
1695 if (--utf8Pending == 0) {
1697 if (cp437) appendByte(cp437);
1705 if ((c & 0xE0) == 0xC0) {
1710 if ((c & 0xF0) == 0xE0) {
1715 if ((c & 0xF8) == 0xF0) {
1721 if (c >= 0x20 && c < 0x7F) {
1722 appendByte(
static_cast<uint8_t
>(c));
1723 }
else if (c >= 0x80 && c <= 0xFF) {
1725 appendByte(
static_cast<uint8_t
>(c));
1737 bool anyCommandReady =
false;
1741 for (
int i = 0; i < 4096; ++i) {
1745 if (handleEscape(c))
continue;
1747 bool commandReady =
false;
1748 handleSpecialChar(c, commandReady);
1749 if (commandReady) anyCommandReady =
true;
1751 return anyCommandReady;
1783#if FEATURE_SECURE_SERIAL
1786 uint64_t now = esp_timer_get_time();
1810 if (
pm.isBadgeBlocked()) {
1811 if (
pm.isLockoutActive()) {
1812 uint32_t remainingSec =
pm.getLockoutRemainingMs() / 1000;
1813 LOG_W(
TAG,
"PIN locked, %lu seconds remaining", (
unsigned long)remainingSec);
1815 LOG_W(
TAG,
"PIN permanently blocked (retries exhausted)");
1820 if (!pin || !*pin) {
1825 if (!
pm.verifyBadgePin(pin)) {
1826 LOG_W(
TAG,
"Authentication failed, %d retries remaining",
pm.getBadgeRetries());
1835 LOG_I(
TAG,
"Authenticated via serial");
1843#if FEATURE_SECURE_SERIAL
1866void SerialCmd::executeCommand(
char* cmd) {
1870 char first_token[24];
1872 while (cmd[i] && !isspace(
static_cast<unsigned char>(cmd[i])) &&
1873 i <
sizeof(first_token) - 1) {
1874 first_token[i] = cmd[i];
1877 first_token[i] =
'\0';
1878 LOG_D(
TAG,
"Executing: %s", first_token);
1887char* SerialCmd::trim(
char* str) {
1888 if (!str)
return str;
1890 while (*str && isspace(
static_cast<unsigned char>(*str))) str++;
1891 if (*str ==
'\0')
return str;
1893 char* end = str + strlen(str) - 1;
1894 while (end > str && isspace(
static_cast<unsigned char>(*end))) end--;
1919 default:
return "?";
1930 default:
return "?";
1940 default:
return "?";
1949 if (count <= 1)
return count;
1952 for (uint8_t i = 0; i < count; i++) {
1954 for (uint8_t j = 0; j < unique; j++) {
1955 if (strcmp(results[i].ssid, results[j].ssid) == 0) {
1957 if (results[i].rssi > results[j].rssi) results[j] = results[i];
1961 if (!seen && results[i].ssid[0] !=
'\0') {
1962 if (unique != i) results[unique] = results[i];
1967 for (uint8_t i = 0; i < unique; i++) {
1968 for (uint8_t j = i + 1; j < unique; j++) {
1969 if (results[j].rssi > results[i].rssi) {
1971 results[i] = results[j];
2000 if (!wifi->startScan()) {
2005 uint32_t elapsed = 0;
2011 if (!wifi->isScanComplete()) {
2023 Console::printf(
"# %-32s %5s %3s %s\r\n",
"SSID",
"RSSI",
"Ch",
"Security");
2024 for (uint8_t i = 0; i < count; i++) {
2026 static_cast<unsigned>(i + 1),
2029 static_cast<unsigned>(results[i].channel),
2053 if (wifi->isConnected()) {
2057 if (wifi->getIpAddress(ip,
sizeof(ip))) {
2060 uint8_t mac[6] = {};
2061 if (wifi->getMacAddress(mac)) {
2063 mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
2065 int8_t rssi = wifi->getRssi();
2073 const auto& cfg = wh.config();
2080 static_cast<unsigned long>(wh.getConnectTimeoutMs()));
2096 if (args && args[0]) {
2097 if (strcasecmp(args,
"ap") == 0) {
2099 }
else if (strcasecmp(args,
"sta_ap") == 0) {
2101 }
else if (strcasecmp(args,
"sta") != 0) {
2113 if (!wifi->enable(mode)) {
2124 if (!wh.config().valid)
return;
2127 if (!wh.setUserEnabled(
true)) {
2129 const char* err = wh.getLastError();
2135 wifi->getIpAddress(ip,
sizeof(ip));
2150 if (!wifi->isEnabled()) {
2162 if (!args || !args[0]) {
2167 const char* space = strchr(args,
' ');
2174 char password[65] = {};
2176 size_t ssidLen =
static_cast<size_t>(space - args);
2177 if (ssidLen >=
sizeof(ssid)) ssidLen =
sizeof(ssid) - 1;
2178 memcpy(ssid, args, ssidLen);
2179 ssid[ssidLen] =
'\0';
2181 const char* pw = space + 1;
2182 while (*pw ==
' ') pw++;
2183 size_t pwLen = strlen(pw);
2184 if (pwLen >=
sizeof(password)) pwLen =
sizeof(password) - 1;
2185 memcpy(password, pw, pwLen);
2186 password[pwLen] =
'\0';
2188 if (ssid[0] ==
'\0') {
2194 wh.saveCredentials(ssid, password);
2197 ssid,
static_cast<unsigned long>(wh.getConnectTimeoutMs()));
2199 if (!wh.setUserEnabled(
true)) {
2200 const char* err = wh.getLastError();
2201 Console::printf(
"ERROR: Connection failed (%s)\r\n", err ? err :
"?");
2207 if (wifi) wifi->getIpAddress(ip,
sizeof(ip));
2216 if (!args || !args[0]) {
2218 static_cast<unsigned long>(wh.getConnectTimeoutMs()));
2222 char* end =
nullptr;
2223 long val = strtol(args, &end, 10);
2224 if (end == args || *end !=
'\0'
2233 if (!wh.setConnectTimeoutMs(
static_cast<uint32_t
>(val))) {
2264 uint8_t count = reg.getModuleCount();
2265 for (uint8_t i = 0; i < count; i++) {
2267 if (module && module->
getName() &&
2282 uint8_t count = reg.getModuleCount();
2284 Console::printf(
"=== Modules (%u) ===\r\n",
static_cast<unsigned>(count));
2285 for (uint8_t i = 0; i < count; i++) {
2287 if (!module)
continue;
2289 const char* error = reg.getModuleSlotError(i);
2291 static_cast<unsigned>(i),
2293 reg.isModuleEnabled(i) ?
"enabled" :
"disabled",
2294 reg.getModuleStatusLabel(i),
2295 error ? error :
"");
2304 if (!args || !*args) {
2316 uint8_t idx =
static_cast<uint8_t
>(index);
2318 if (reg.isModuleEnabled(idx)) {
2325 reg.setModuleEnabled(idx,
true);
2326 if (!reg.startModule(idx)) {
2327 switch (reg.classifyStartFailure(idx)) {
2332 Console::printf(
"ERROR: No free USB slot - disable a USB module (e.g. GPG) first\r\n");
2353 if (!args || !*args) {
2365 uint8_t idx =
static_cast<uint8_t
>(index);
2367 if (!reg.isModuleEnabled(idx)) {
2374 reg.setModuleEnabled(idx,
false);
2392 {
"LIST",
"[namespace]",
"List entries (optional namespace filter)",
cmdNvsList},
2393 {
"READ",
"<ns> <key>",
"Read key value",
cmdNvsRead},
2394 {
"DEL",
"<ns> [key]",
"Delete key, or entire namespace if omitted",
cmdNvsDel},
2395 {
"CLEAR",
"YES",
"Erase entire NVS (confirmation required)",
cmdNvsClear},
2396 {
nullptr,
nullptr,
nullptr,
nullptr},
2401 {
"STATUS",
"",
"Show PIN retries / lockout state",
cmdPinStatus},
2402 {
"RESET",
"",
"Reset PIN retries (debug)",
cmdPinReset},
2403 {
"CHANGE",
"<currentPin> <newPin>",
"Change badge PIN (4-8 digits)",
cmdPinChange},
2404 {
"DURESS",
"<pin>",
"Arm self-destruct PIN (wipes on entry)",
cmdPinDuress},
2406 {
nullptr,
nullptr,
nullptr,
nullptr},
2411 {
"STATUS",
"",
"Show TR01 connection status",
cmdTr01Status},
2412 {
"INFO",
"",
"Show TR01 chip info (ID, firmware)",
cmdTr01Info},
2414 {
"SLOTS",
"",
"Show TR01 slot usage summary",
cmdTr01Slots},
2415 {
"RMEM_READ",
"<slot>",
"Read and dump R-Memory slot",
cmdTr01RmemRead},
2416 {
"ECC_DEL",
"<slot>",
"Delete ECC key slot",
cmdTr01EccDel},
2418 {
"RESYNC",
"",
"Resync TR01 session and cache",
cmdTr01Resync},
2420 {
"CLEANUP",
"",
"Cleanup mismatched slots and rebuild cache",
cmdTr01Cleanup},
2421 {
"WIPE",
"CONFIRM",
"Factory reset all TR01 data",
cmdTr01Wipe},
2422 {
nullptr,
nullptr,
nullptr,
nullptr},
2427 {
"SCAN",
"",
"Scan for available networks",
cmdWifiScan},
2428 {
"STATUS",
"",
"Show WiFi state and saved configuration",
cmdWifiStatus},
2429 {
"ON",
"[sta|ap|sta_ap]",
"Enable WiFi radio (default STA, auto-reconnect)",
cmdWifiOn},
2430 {
"OFF",
"",
"Disable WiFi radio",
cmdWifiOff},
2431 {
"CONNECT",
"<ssid> <password>",
"Connect to network and persist credentials",
cmdWifiConnect},
2432 {
"TIMEOUT",
"[ms]",
"Get or set connect timeout (3000-60000 ms)",
cmdWifiTimeout},
2433 {
"FORGET",
"",
"Clear saved WiFi configuration",
cmdWifiForget},
2434 {
nullptr,
nullptr,
nullptr,
nullptr},
2439 {
"LIST",
"",
"List modules with state and errors",
cmdModuleList},
2440 {
"ENABLE",
"<name>",
"Enable a module (persistent)",
cmdModuleEnable},
2442 {
nullptr,
nullptr,
nullptr,
nullptr},
2452 reg.registerCommand({
"HELP",
"Show available commands",
cmdHelp,
"system",
false});
2453 reg.registerCommand({
"PING",
"Check if device is responsive",
cmdPing,
"system",
false});
2454 reg.registerCommand({
"VERSION",
"Show firmware version and API level",
cmdVersion,
"system",
false});
2455 reg.registerCommand({
"STATUS",
"Show system status",
cmdStatus,
"system",
false});
2456 reg.registerCommand({
"MEM",
"Show memory usage",
cmdMem,
"system",
false});
2457 reg.registerCommand({
"MEMINFO",
"Show detailed memory + task info",
cmdMemInfo,
"system",
false});
2458 reg.registerCommand({
"CPU",
"Measure aggregate CPU load (~250 ms)",
cmdCpu,
"system",
false});
2459 reg.registerCommand({
"ERROR_LOG",
"Show error log (CLEAR to reset)",
cmdErrorLog,
"system",
false});
2460 reg.registerCommand({
"REBOOT",
"Restart the device",
cmdReboot,
"system",
true});
2461 reg.registerCommand({
"BOOTLOADER",
"Reboot into USB download mode",
cmdBootloader,
"system",
true});
2462 reg.registerCommand({
"SHIPMODE",
"Enter ship mode (disconnect battery)",
cmdShipMode,
"system",
true});
2463 reg.registerCommand({
"PASTE",
"Paste text into the active T9 input",
cmdPaste,
"system",
true});
2465 reg.registerCommand({
"NVS",
"NVS storage: LIST/READ/DEL/CLEAR",
cmdNvs,
"nvs",
true,
kNvsSubs});
2467 reg.registerCommand({
"GET_TIME",
"Show current time",
cmdGetTime,
"time",
false});
2468 reg.registerCommand({
"GET_DATE",
"Show current date",
cmdGetDate,
"time",
false});
2469 reg.registerCommand({
"SET_TIME",
"Set time (HH:MM:SS)",
cmdSetTime,
"time",
false});
2470 reg.registerCommand({
"SET_DATE",
"Set date (DD.MM.YYYY or Unix timestamp)",
cmdSetDate,
"time",
false});
2472 reg.registerCommand({
"SET_NAME",
"Set display name",
cmdSetName,
"display",
false});
2473 reg.registerCommand({
"SET_INFO",
"Set info line 1",
cmdSetInfo,
"display",
false});
2474 reg.registerCommand({
"SET_INFO2",
"Set info line 2",
cmdSetInfo2,
"display",
false});
2476 reg.registerCommand({
"PIN",
"PIN management: STATUS/RESET/CHANGE/DURESS",
cmdPin,
"pin",
true,
kPinSubs});
2478 reg.registerCommand({
"TR01",
"TROPIC01 secure element: STATUS/INFO/SESSION/SLOTS/RMEM_*/ECC_DEL/...",
2481#if FEATURE_SECURE_SERIAL
2482 reg.registerCommand({
"AUTH",
"Authenticate with PIN", cmdAuth,
"auth",
false});
2483 reg.registerCommand({
"LOGOUT",
"End authenticated session", cmdLogout,
"auth",
false});
2486 reg.registerCommand({
"WIFI",
"WiFi control: SCAN/STATUS/ON/OFF/CONNECT/TIMEOUT/FORGET",
2489 reg.registerCommand({
"MODULE",
"Module control: LIST/ENABLE/DISABLE",
Canonical CP437 <-> Unicode/UTF-8 codec.
Expert-menu firmware/version screen with an optional upstream update check against the GitHub release...
char name[cdc::hal::ISecureElement::RMEM_NAME_LEN]
CDC Log: logging over TinyUSB CDC and UART.
#define LOG_W(tag, fmt,...)
#define LOG_D(tag, fmt,...)
#define LOG_I(tag, fmt,...)
void error_log_dump(void)
Dumps buffered error-log entries to console.
void log_set_level(log_level_t level)
Sets runtime log verbosity threshold.
void log_register_authgate_hook(log_authgate_hook_t hook)
Registers (or clears) the auth-gate hook for INFO/DEBUG/VERBOSE.
void error_log_clear(void)
Clears error-log ring buffer state.
#define LOG_E(tag, fmt,...)
static uint8_t loadOverWindow(uint32_t windowMs=250)
Measure aggregate CPU load over a blocking window.
Module interface that extends IService with module-specific features.
virtual ServiceState getState() const =0
virtual const char * getName() const =0
static ModuleRegistry & instance()
Returns the singleton module registry instance.
void resetBadgeRetries()
Resets badge retry counter to maximum.
static constexpr uint8_t BADGE_PIN_MAX
static constexpr uint8_t BADGE_PIN_MIN
bool clearDuressPin()
Clears the duress PIN, disarming the self-destruct trigger.
static PinManager & instance()
Returns singleton PIN manager instance.
static TropicSlotMap & instance()
Returns singleton Tropic slot-map instance.
void forEachRange(SlotType type, RangeCallback cb, void *user) const
Iterates configured slot ranges of the given type in declaration order.
static TropicStorage & instance()
Returns singleton instance of TROPIC metadata cache manager.
static UsbManager & instance()
Returns singleton USB manager instance.
static constexpr uint16_t RMEM_SLOT_COUNT
static constexpr uint8_t ECC_SLOT_COUNT
static constexpr uint8_t MAX_SCAN_RESULTS
static void print(const char *str)
Prints raw string to console.
static void showPrompt()
Prints standard shell prompt.
static void flush()
Flushes pending console output.
static void printf(const char *format,...) __attribute__((format(printf
Prints formatted text to console.
static void putchar(char c)
Writes a single character to console.
static int getchar()
Reads one character from console input.
static void init()
Initializes console wrapper state.
virtual void setAuthProvider(bool(*authCheck)())=0
virtual bool processCommand(const char *line)=0
virtual void showHelp()=0
virtual void setOnCommandExecuted(void(*callback)())=0
static bool isAuthenticated()
Returns whether the serial session is currently authenticated.
static void registerBuiltinCommands()
Registers all built-in serial commands.
static void init()
Public SerialCmd interface implementation.
static bool process()
Processes one pending input character from the serial console.
static ICommandRegistry & getRegistry()
Returns the shared command registry instance.
static void logout()
Logs out the current serial session.
static void setTextCallback(TextChangeCallback callback)
Sets the callback used by text-setting commands.
static bool authenticate(const char *pin)
Attempts to authenticate the serial session with a PIN.
static void setTimeCallback(TimeChangeCallback callback)
Sets the callback invoked after successful date/time updates.
static constexpr uint32_t AUTH_TIMEOUT_MS
static constexpr size_t CMD_BUFFER_SIZE
static void touchAuthSession()
Keeps the auth session alive during a long-running serial activity.
virtual const char * getName() const =0
static ViewStack & instance()
Returns singleton view-stack instance.
bool setUserEnabled(bool enabled)
Sets the user/system WiFi intent and applies it immediately.
static WifiHandlers & instance()
Returns singleton Wi-Fi handlers instance.
CDC Badge OS plugin host API - canonical C ABI contract.
#define HOST_API_LEVEL_STR
uint8_t fromUnicode(uint32_t cp)
Map a Unicode codepoint to its CP437 byte, or 0 if it has none.
esp_err_t wipeNvs()
Erases the NVS partition and re-initializes it blank.
TropicWipeResult wipeTropic(hal::ISecureElement *se, uint16_t progressEvery=0, void(*onRmemProgress)(uint16_t current, uint16_t total)=nullptr)
Iterates every TROPIC01 ECC slot (0..ECC_SLOT_COUNT-1) and R-Memory slot (0..RMEM_SLOT_COUNT-1),...
@ UsbBudgetFull
HID interface budget is exhausted.
@ Generic
Start failed for an unspecified reason.
@ SlotError
Module reported a slot-map error.
IWifiController * getWifiControllerInstance()
Returns the singleton Wi-Fi controller service instance.
IPowerManager * getPowerManagerInstance()
Returns the singleton power manager instance.
ISecureElement * getSecureElementInstance()
Returns singleton secure-element stub instance.
static hal::ISecureElement * getSecureElementWithCheck()
Secure-element access helpers.
static void cmdNvsRead(const char *args)
Reads and prints a single NVS key value.
static void cmdTr01Session(const char *args)
Starts a secure-element session, restarting it if already active.
static void cmdTr01Cleanup(const char *args)
Cleans up slot metadata inconsistencies and rebuilds cache state.
static void printNvsValue(nvs_handle_t nvs, const char *key, nvs_type_t type)
Prints an NVS value according to its stored type.
void(*)() TimeChangeCallback
static void cmdTr01Slots(const char *args)
Prints usage information for ECC and R-Memory slots.
static void printHexDump(const uint8_t *data, size_t len, size_t maxBytes)
NVS utility helpers used by command handlers.
static void historyAdd(const char *cmd)
General-purpose helper functions.
static const SubCommand kTr01Subs[]
static constexpr int YEAR_MIN
static void cmdSetInfo2(const char *args)
Sets the second info line through the text callback.
static EscState s_escState
static void redrawLine(const char *newContent, size_t &bufferPos)
Clears the current console line and redraws it with new content.
static void cmdWifi(const char *args)
static void cmdSetDate(const char *args)
Updates the system clock date component.
static void cmdMem(const char *args)
static void cmdPing(const char *args)
Replies with a liveness check response.
static void cmdWifiOn(const char *args)
WIFI_ON [sta|ap|sta_ap] - enable WiFi radio.
static SlotParseResult parseSlotArg(const char *args, uint16_t maxSlot, const char *slotTypeName)
Parses a slot number from a string argument.
static const char * wifiSecurityName(hal::WifiSecurity sec)
static void cmdTr01Status(const char *args)
TROPIC01 secure-element maintenance and diagnostic handlers.
static size_t s_historyHead
static void cmdSetTime(const char *args)
Updates the system clock time component.
static void cmdBootloader(const char *args)
Reboots the device into USB download (bootloader) mode.
static size_t s_historyPos
static void cmdHelp(const char *args)
System command handlers.
static void cmdWifiOff(const char *args)
WIFI_OFF - disconnect and disable WiFi radio.
static void cmdPin(const char *args)
static uint64_t s_authTimestamp
static void cmdTr01Resync(const char *args)
Restarts the secure-element session to resynchronize state.
static void cmdTr01Info(const char *args)
Prints secure-element chip and firmware information.
static constexpr uint32_t WIPE_PROGRESS_INTERVAL
static void cmdTr01RmemRead(const char *args)
Reads and dumps one secure-element R-Memory slot.
static void cmdNvs(const char *args)
static void cmdGetDate(const char *args)
Prints the current local date.
static void cmdCpu(const char *args)
static void cmdSetName(const char *args)
Display text command handlers.
static void cmdWifiStatus(const char *args)
WIFI_STATUS - show runtime state and saved configuration.
static void cmdGetTime(const char *args)
Date/time command handlers.
static void cmdTr01CacheRebuild(const char *args)
Rebuilds the Tropic slot cache and prints per-slot diagnostics.
static bool s_authenticated
Session authentication flags and timeout baseline.
static constexpr size_t HISTORY_MAX
Internal constants used by serial command processing.
static constexpr uint8_t WIFI_MAX_SCAN_RESULTS
static void cmdPinStatus(const char *args)
Prints the current badge PIN status snapshot.
static void cmdTr01EccDel(const char *args)
Deletes one ECC key slot.
static void cmdTr01Wipe(const char *args)
Performs a destructive secure-element factory wipe after confirmation.
static void cmdPinDuress(const char *args)
Arms the duress / self-destruct PIN.
static void cmdModuleList(const char *args)
MODULE LIST - list registered modules with state and errors.
static void cmdShipMode(const char *args)
Enters ship mode (disconnects the battery via BATFET).
static const SubCommand kWifiSubs[]
static constexpr size_t HEX_DUMP_WIDTH
static void cmdPinReset(const char *args)
Authentication command handlers for secure serial mode.
static void cmdStatus(const char *args)
Prints runtime status information for the device.
static constexpr size_t NVS_KEY_MAX_LEN
static nvs_type_t findNvsKeyType(const char *ns, const char *key)
Finds the stored NVS type of a key by namespace iteration.
ICommandRegistry & getCommandRegistry()
Returns singleton command-registry interface.
EscState
Escape-sequence parser state for ANSI key handling.
static constexpr int YEAR_MAX
static void cmdWifiForget(const char *args)
WIFI_FORGET - disable WiFi and erase the saved configuration.
static const char * wifiStateName(hal::WifiState st)
static TimeChangeCallback s_timeCallback
static void cmdModuleDisable(const char *args)
MODULE DISABLE <name> - disable a module by name (persistent).
static bool setSystemTime(struct tm *tm)
Sets system time from a populated local tm structure.
static const SubCommand kModuleSubs[]
void(*)(const char *field, const char *value) TextChangeCallback
static void cmdWifiConnect(const char *args)
WIFI_CONNECT <ssid> <password> - connect and persist credentials.
static void cmdWifiTimeout(const char *args)
WIFI_TIMEOUT [ms] - get or set the connect timeout.
static void cmdNvsClear(const char *args)
NVS command handlers.
static const SubCommand kPinSubs[]
static void cmdTr01RmemDel(const char *args)
Erases one R-Memory slot.
static const char * wifiModeName(hal::WifiMode m)
static const SubCommand kNvsSubs[]
Sub-command tables and dispatchers for grouped commands.
static void cmdWifiScan(const char *args)
WIFI_SCAN - scan and print networks (deduplicated, sorted by RSSI).
static size_t s_cmdBufferPos
static bool getCurrentTime(struct timeval &tv, struct tm &tm)
Date/time parsing and validation helpers.
static void cmdVersion(const char *args)
Prints the firmware version, the plugin host API level and the last upstream firmware-check result (i...
static void cmdNvsList(const char *args)
Lists NVS entries, optionally filtered by namespace.
static void cmdPaste(const char *args)
static void printHeapRegion(const char *label, uint32_t caps)
Prints heap and PSRAM usage statistics.
static constexpr uint32_t WIFI_SCAN_POLL_MS
WiFi serial command handlers.
static char s_cmdBuffer[SerialCmd::CMD_BUFFER_SIZE]
Global static state for line editing and command dispatch.
static void cmdReboot(const char *args)
Reboots the device after flushing serial output.
static const char * getNvsTypeName(nvs_type_t type)
Returns a human-readable name for an NVS type value.
static void cmdPinChange(const char *args)
Changes the badge PIN after verifying the current one.
static const char * historyGet(size_t idx)
Returns a history entry by reverse index (0 = newest).
static void cmdModule(const char *args)
static int findModuleIndex(const char *name)
Module management serial command handlers.
static size_t s_historyCount
static constexpr size_t NVS_NAMESPACE_MAX_LEN
static void cmdModuleEnable(const char *args)
MODULE ENABLE <name> - enable a module by name (persistent).
static void cmdNvsDel(const char *args)
Deletes an NVS key or an entire namespace.
static void cmdSetInfo(const char *args)
Sets the first info line through the text callback.
static bool s_initialized
void dispatchSubCommand(const char *parent, const char *args, const SubCommand *table)
Routes a sub-command line to its handler.
static uint8_t wifiDedupAndSort(hal::WifiScanResult *results, uint8_t count)
Deduplicates scan results by SSID (keeping strongest RSSI) and sorts the survivors descending by RSSI...
static void cmdMemInfo(const char *args)
static void cmdTr01(const char *args)
static TextChangeCallback s_textCallback
Optional callbacks injected by higher-level modules.
static char s_historyBuffer[HISTORY_MAX][SerialCmd::CMD_BUFFER_SIZE]
Command history ring buffer allocated in PSRAM.
static void cmdPinDuressClear(const char *args)
Disarms the duress / self-destruct PIN.
static void cmdErrorLog(const char *args)
Displays the error log or clears it when CLEAR is passed.
void rebootIntoBootloader()
Reboots the device into USB download (bootloader) mode.
static constexpr uint32_t WIFI_SCAN_TIMEOUT_MS
static constexpr uint32_t WIFI_CONNECT_TIMEOUT_MIN_MS
static constexpr uint32_t WIFI_CONNECT_TIMEOUT_MAX_MS
bool firmwareCheckLastResult(char *out, size_t cap)
Formats the last persisted upstream check into out.
Slot parsing helpers for secure-element commands.