31#include "freertos/FreeRTOS.h"
32#include "freertos/task.h"
33#include "esp_memory_utils.h"
40static const char*
TAG =
"SERIAL";
94#if FEATURE_SECURE_SERIAL
99static void resetAuthTimer() {
112 if (!cmd || !*cmd)
return;
140static void redrawLine(
const char* newContent,
size_t& bufferPos) {
141 while (bufferPos > 0) {
177 if (!args || !*args) {
182 char* endptr =
nullptr;
183 long slotVal = strtol(args, &endptr, 10);
185 if (endptr == args || *endptr !=
'\0' || slotVal < 0) {
190 if (slotVal >= maxSlot) {
191 Console::printf(
"ERROR: Invalid %s (0-%d)\r\n", slotTypeName, maxSlot - 1);
196 result.
value = slotVal;
227static void printHexDump(
const uint8_t* data,
size_t len,
size_t maxBytes) {
228 for (
size_t i = 0; i < len && i < maxBytes; i +=
HEX_DUMP_WIDTH) {
235 if (len > maxBytes) {
247 case NVS_TYPE_U8:
return "u8";
248 case NVS_TYPE_I8:
return "i8";
249 case NVS_TYPE_U16:
return "u16";
250 case NVS_TYPE_I16:
return "i16";
251 case NVS_TYPE_U32:
return "u32";
252 case NVS_TYPE_I32:
return "i32";
253 case NVS_TYPE_U64:
return "u64";
254 case NVS_TYPE_I64:
return "i64";
255 case NVS_TYPE_STR:
return "str";
256 case NVS_TYPE_BLOB:
return "blob";
268 nvs_iterator_t it =
nullptr;
269 esp_err_t err = nvs_entry_find(
"nvs", ns, NVS_TYPE_ANY, &it);
270 nvs_type_t keyType = NVS_TYPE_ANY;
272 while (it !=
nullptr) {
273 nvs_entry_info_t info;
274 nvs_entry_info(it, &info);
275 if (strcmp(info.key, key) == 0) {
279 err = nvs_entry_next(&it);
280 if (err != ESP_OK)
break;
282 nvs_release_iterator(it);
293static void printNvsValue(nvs_handle_t nvs,
const char* key, nvs_type_t type) {
297 if (nvs_get_u8(nvs, key, &val) == ESP_OK) {
304 if (nvs_get_i8(nvs, key, &val) == ESP_OK) {
311 if (nvs_get_u16(nvs, key, &val) == ESP_OK) {
318 if (nvs_get_i16(nvs, key, &val) == ESP_OK) {
325 if (nvs_get_u32(nvs, key, &val) == ESP_OK) {
326 Console::printf(
"%lu (0x%08lX)\r\n", (
unsigned long)val, (
unsigned long)val);
332 if (nvs_get_i32(nvs, key, &val) == ESP_OK) {
339 if (nvs_get_u64(nvs, key, &val) == ESP_OK) {
346 if (nvs_get_i64(nvs, key, &val) == ESP_OK) {
353 if (nvs_get_str(nvs, key,
nullptr, &len) == ESP_OK && len > 0) {
354 char* buf =
static_cast<char*
>(malloc(len));
356 LOG_E(
TAG,
"Failed to allocate %d bytes for NVS string", (
int)len);
360 if (nvs_get_str(nvs, key, buf, &len) == ESP_OK) {
367 case NVS_TYPE_BLOB: {
369 if (nvs_get_blob(nvs, key,
nullptr, &len) == ESP_OK && len > 0) {
371 uint8_t* buf =
static_cast<uint8_t*
>(malloc(len));
373 LOG_E(
TAG,
"Failed to allocate %d bytes for NVS blob", (
int)len);
377 if (nvs_get_blob(nvs, key, buf, &len) == ESP_OK) {
401 gettimeofday(&tv,
nullptr);
402 return localtime_r(&tv.tv_sec, &tm) !=
nullptr;
412 tv.tv_sec = mktime(tm);
414 return settimeofday(&tv,
nullptr) == 0;
446 Console::printf(
"Free heap: %lu bytes\r\n", (
unsigned long)esp_get_free_heap_size());
447 Console::printf(
"Min free heap: %lu bytes\r\n", (
unsigned long)esp_get_minimum_free_heap_size());
448 Console::printf(
"Uptime: %llu ms\r\n", esp_timer_get_time() / 1000ULL);
470 multi_heap_info_t info;
471 heap_caps_get_info(&info, caps);
472 if (info.total_free_bytes + info.total_allocated_bytes == 0)
return;
474 Console::printf(
" total free : %lu\r\n", (
unsigned long)info.total_free_bytes);
475 Console::printf(
" total allocated : %lu\r\n", (
unsigned long)info.total_allocated_bytes);
476 Console::printf(
" largest free : %lu\r\n", (
unsigned long)info.largest_free_block);
477 Console::printf(
" min ever free : %lu\r\n", (
unsigned long)info.minimum_free_bytes);
478 Console::printf(
" free blocks : %lu\r\n", (
unsigned long)info.free_blocks);
479 Console::printf(
" alloc blocks : %lu\r\n", (
unsigned long)info.allocated_blocks);
487 printHeapRegion(
"Internal DRAM (8-bit)", MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT);
488 printHeapRegion(
"Internal 32-bit only", MALLOC_CAP_INTERNAL | MALLOC_CAP_32BIT);
494 Console::printf(
"\r\n-- Heap totals (heap_caps_get_total_size) --\r\n");
495 Console::printf(
" INTERNAL : %u\r\n", (
unsigned)heap_caps_get_total_size(MALLOC_CAP_INTERNAL));
496 Console::printf(
" EXEC : %u\r\n", (
unsigned)heap_caps_get_total_size(MALLOC_CAP_EXEC));
497 Console::printf(
" SPIRAM : %u\r\n", (
unsigned)heap_caps_get_total_size(MALLOC_CAP_SPIRAM));
498 Console::printf(
" DMA : %u\r\n", (
unsigned)heap_caps_get_total_size(MALLOC_CAP_DMA));
500 (
unsigned)heap_caps_get_free_size(MALLOC_CAP_INTERNAL | MALLOC_CAP_EXEC),
501 (
unsigned)heap_caps_get_largest_free_block(MALLOC_CAP_INTERNAL | MALLOC_CAP_EXEC),
502 (
unsigned)heap_caps_get_total_size(MALLOC_CAP_INTERNAL | MALLOC_CAP_EXEC));
504 (
unsigned)heap_caps_get_free_size(MALLOC_CAP_32BIT),
505 (
unsigned)heap_caps_get_largest_free_block(MALLOC_CAP_32BIT));
507#if CONFIG_FREERTOS_USE_TRACE_FACILITY
508 UBaseType_t numTasks = uxTaskGetNumberOfTasks();
509 TaskStatus_t* tasks = (TaskStatus_t*)calloc(numTasks,
sizeof(TaskStatus_t));
511 numTasks = uxTaskGetSystemState(tasks, numTasks,
nullptr);
514 uint32_t totalStackFree = 0;
515 for (UBaseType_t i = 0; i < numTasks; i++) {
516 const char* st =
"?";
517 switch (tasks[i].eCurrentState) {
518 case eRunning: st =
"RUN";
break;
519 case eReady: st =
"RDY";
break;
520 case eBlocked: st =
"BLK";
break;
521 case eSuspended: st =
"SUS";
break;
522 case eDeleted: st =
"DEL";
break;
523 case eInvalid: st =
"INV";
break;
525 BaseType_t coreId = -1;
526#if INCLUDE_xTaskGetCoreID
527 coreId = xTaskGetCoreID(tasks[i].xHandle);
529 const char* stackLoc =
"DRAM";
530 if (tasks[i].pxStackBase !=
nullptr &&
531 esp_ptr_external_ram(tasks[i].pxStackBase)) {
536 (
unsigned)tasks[i].uxCurrentPriority,
537 (
unsigned long)tasks[i].usStackHighWaterMark,
541 totalStackFree += tasks[i].usStackHighWaterMark;
544 (
unsigned long)totalStackFree);
548 Console::printf(
"\r\nTask list unavailable (FREERTOS_USE_TRACE_FACILITY=n)\r\n");
558 (
unsigned long)esp_get_free_heap_size(),
559 (
unsigned long)heap_caps_get_total_size(MALLOC_CAP_DEFAULT));
561 size_t intFree = heap_caps_get_free_size(MALLOC_CAP_INTERNAL);
562 size_t intTotal = heap_caps_get_total_size(MALLOC_CAP_INTERNAL);
563 size_t intLargest = heap_caps_get_largest_free_block(MALLOC_CAP_INTERNAL);
564 Console::printf(
"Internal DRAM: %lu / %lu free (largest block %lu)\r\n",
565 (
unsigned long)intFree,
566 (
unsigned long)intTotal,
567 (
unsigned long)intLargest);
569 size_t dmaFree = heap_caps_get_free_size(MALLOC_CAP_DMA);
570 size_t dmaLargest = heap_caps_get_largest_free_block(MALLOC_CAP_DMA);
572 (
unsigned long)dmaFree,
573 (
unsigned long)dmaLargest);
575 size_t psramFree = heap_caps_get_free_size(MALLOC_CAP_SPIRAM);
576 size_t psramTotal = heap_caps_get_total_size(MALLOC_CAP_SPIRAM);
577 if (psramTotal > 0) {
579 (
unsigned long)psramFree,
580 (
unsigned long)psramTotal);
593 vTaskDelay(pdMS_TO_TICKS(100));
621 power->enterShipMode();
626 if (!args || !*args) {
631 if (!top || strcmp(top->
getName(),
"T9InputView") != 0) {
645 if (args && strcmp(args,
"CLEAR") == 0) {
662 if (!args || strcmp(args,
"YES") != 0) {
675 Console::printf(
"ERROR: NVS wipe failed (%s)\r\n", esp_err_to_name(err));
686 const char* nsFilter = (args && *args) ? args :
nullptr;
688 nvs_iterator_t it =
nullptr;
689 esp_err_t err = nvs_entry_find(
"nvs", nsFilter, NVS_TYPE_ANY, &it);
691 if (err == ESP_ERR_NVS_NOT_FOUND) {
701 Console::printf(
"ERROR: nvs_entry_find failed (%s)\r\n", esp_err_to_name(err));
713 while (it !=
nullptr) {
714 nvs_entry_info_t info;
715 nvs_entry_info(it, &info);
717 if (!nsFilter && strcmp(lastNs, info.namespace_name) != 0) {
718 strncpy(lastNs, info.namespace_name,
sizeof(lastNs) - 1);
725 err = nvs_entry_next(&it);
726 if (err != ESP_OK)
break;
729 nvs_release_iterator(it);
738 if (!args || !*args) {
745 if (sscanf(args,
"%15s %15s", ns, key) != 2) {
751 esp_err_t err = nvs_open(ns, NVS_READONLY, &nvs);
753 Console::printf(
"ERROR: Cannot open namespace '%s' (%s)\r\n", ns, esp_err_to_name(err));
758 if (keyType == NVS_TYPE_ANY) {
759 Console::printf(
"ERROR: Key '%s' not found in namespace '%s'\r\n", key, ns);
774 if (!args || !*args) {
782 int parsed = sscanf(args,
"%15s %15s", ns, key);
790 esp_err_t err = nvs_open(ns, NVS_READWRITE, &nvs);
792 Console::printf(
"ERROR: Cannot open namespace '%s' (%s)\r\n", ns, esp_err_to_name(err));
796 if (parsed == 1 || key[0] ==
'\0') {
797 err = nvs_erase_all(nvs);
805 err = nvs_erase_key(nvs, key);
809 }
else if (err == ESP_ERR_NVS_NOT_FOUND) {
812 Console::printf(
"ERROR: Delete failed (%s)\r\n", esp_err_to_name(err));
832 Console::printf(
"%02d:%02d:%02d\r\n", tm.tm_hour, tm.tm_min, tm.tm_sec);
847 Console::printf(
"%02d.%02d.%04d\r\n", tm.tm_mday, tm.tm_mon + 1, tm.tm_year + 1900);
858 if (!args || !*args) {
863 if (sscanf(args,
"%d:%d:%d", &h, &m, &s) != 3) {
867 if (h < 0 || h > 23 || m < 0 || m > 59 || s < 0 || s > 59) {
896 if (!args || !*args) {
902 if (sscanf(args,
"%d.%d.%d", &d, &m, &y) == 3) {
903 if (d < 1 || d > 31 || m < 1 || m > 12 || y < YEAR_MIN || y >
YEAR_MAX) {
912 tm.tm_year = y - 1900;
928 if (sscanf(args,
"%lld", &ts) != 1 || ts < 0) {
929 Console::printf(
"ERROR: Invalid format. Use DD.MM.YYYY or a Unix timestamp\r\n");
932 time_t secs =
static_cast<time_t
>(ts);
934 if (!gmtime_r(&secs, &tm)) {
938 int year = tm.tm_year + 1900;
939 if (year < YEAR_MIN || year >
YEAR_MAX) {
946 if (settimeofday(&tv,
nullptr) == 0) {
947 Console::printf(
"OK: Time set to %lld (%04d-%02d-%02d %02d:%02d:%02d UTC)\r\n",
948 ts, year, tm.tm_mon + 1, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec);
966 if (!args) args =
"";
978 if (!args) args =
"";
990 if (!args) args =
"";
1001#if FEATURE_SECURE_SERIAL
1006static void cmdAuth(
const char* args) {
1009 if (
pm.isBadgeBlocked()) {
1010 if (
pm.isLockoutActive()) {
1011 uint32_t remainingSec =
pm.getLockoutRemainingMs() / 1000;
1012 Console::printf(
"ERROR: PIN locked. Wait %lu seconds.\r\n", (
unsigned long)remainingSec);
1019 if (!args || !*args) {
1039 uint8_t retries = pm.getBadgeRetries();
1041 if (pm.isLockoutActive()) {
1042 uint32_t remainingSec = pm.getLockoutRemainingMs() / 1000;
1043 Console::printf(
"ERROR: Wrong PIN. Locked for %lu seconds.\r\n", (
unsigned long)remainingSec);
1048 Console::printf(
"ERROR: Wrong PIN. %d retries remaining.\r\n", retries);
1057static void cmdLogout(
const char* args) {
1087 pm.getBadgeRetries(),
1088 pm.isBadgeBlocked() ?
"yes" :
"no",
1089 pm.isPinSet() ?
"yes" :
"no");
1097 if (!args || !args[0]) {
1102 const char* space = strchr(args,
' ');
1111 size_t curLen =
static_cast<size_t>(space - args);
1118 memcpy(currentPin, args, curLen);
1119 currentPin[curLen] =
'\0';
1121 const char* p = space + 1;
1122 while (*p ==
' ') p++;
1123 size_t newLen = strlen(p);
1130 memcpy(newPin, p, newLen);
1131 newPin[newLen] =
'\0';
1133 auto isDigits = [](
const char* s) {
1134 for (; *s; ++s)
if (!isdigit(
static_cast<unsigned char>(*s)))
return false;
1137 if (!isDigits(currentPin) || !isDigits(newPin)) {
1143 if (
pm.isBadgeBlocked()) {
1144 Console::printf(
"ERROR: PIN entry blocked (lockout active or no retries left)\r\n");
1148 if (!
pm.changeBadgePin(currentPin, newPin)) {
1149 Console::printf(
"ERROR: PIN change failed (current PIN wrong or new PIN invalid)\r\n");
1165 if (!args || !args[0]) {
1171 size_t len = strlen(args);
1178 memcpy(duressPin, args, len);
1179 duressPin[len] =
'\0';
1182 Console::printf(
"ERROR: Duress PIN rejected (invalid length, non-digit, or equal to badge PIN)\r\n");
1213 Console::printf(
" Session: %s\r\n", se->isSessionActive() ?
"active" :
"inactive");
1226 uint8_t riscvVer[4] = {0};
1227 uint8_t spectVer[4] = {0};
1231 if (se->getChipId(chipId,
sizeof(chipId))) {
1233 for (
int i = 0; i < 8; i++) {
1241 if (se->getFwVersion(riscvVer, spectVer)) {
1243 riscvVer[3], riscvVer[2], riscvVer[1], riscvVer[0]);
1245 spectVer[3], spectVer[2], spectVer[1], spectVer[0]);
1260 if (se->isSessionActive()) {
1265 if (se->sessionStart()) {
1284 if (se->eccSlotUsed(i)) {
1289 if (eccCount == 0) {
1310 if (!result.valid) {
1318 uint16_t slot =
static_cast<uint16_t
>(result.value);
1320 uint16_t actualLen = 0;
1322 hal::SeResult seResult = se->rmemRead(slot, data,
sizeof(data), &actualLen);
1338 if (!result.valid) {
1346 uint8_t slot =
static_cast<uint8_t
>(result.value);
1361 if (!result.valid) {
1369 uint16_t slot =
static_cast<uint16_t
>(result.value);
1389 if (se->isSessionActive()) {
1393 if (se->sessionStart()) {
1409 auto logFn = [](uint16_t slot,
const char* message,
void* ctx) {
1411 if (!message)
return;
1412 if (strcmp(message,
"invalid header") == 0 ||
1413 strcmp(message,
"mismatched module") == 0 ||
1414 strcmp(message,
"nvs write failed") == 0 ||
1415 strcmp(message,
"session start failed") == 0 ||
1416 strcmp(message,
"read failed") == 0) {
1423 if (storage.rebuildVerbose(logFn,
nullptr)) {
1438 if (storage.cleanup()) {
1453 if (!args || strcmp(args,
"CONFIRM") != 0) {
1454 Console::printf(
"WARNING: This will ERASE ALL data on TROPIC01!\r\n");
1464 if (!se->isSessionActive()) {
1465 if (!se->sessionStart()) {
1471 Console::printf(
"Erasing ECC keys and R-Memory (this may take a while)...\r\n");
1474 [](uint16_t current, uint16_t total) {
1479 if (!result.sessionReady) {
1486 result.eccDeleted, result.rmemDeleted);
1501#if FEATURE_SECURE_SERIAL
1517 LOG_I(
TAG,
"Serial command processor initialized");
1520#if FEATURE_SECURE_SERIAL
1536void SerialCmd::handleHistoryNav(HistoryDirection dir) {
1537 if (dir == HistoryDirection::OLDER) {
1571bool SerialCmd::handleEscape(
int c) {
1585 handleHistoryNav(HistoryDirection::OLDER);
1588 handleHistoryNav(HistoryDirection::NEWER);
1609void SerialCmd::handleSpecialChar(
int c,
bool& commandReady) {
1610 commandReady =
false;
1616 bi(
static_cast<uint8_t
>(c));
1636 commandReady =
true;
1662 static uint8_t utf8Pending = 0;
1663 static uint32_t utf8Cp = 0;
1665 auto appendByte = [](uint8_t b) {
1673 if ((c & 0xC0) == 0x80) {
1674 utf8Cp = (utf8Cp << 6) | (c & 0x3F);
1675 if (--utf8Pending == 0) {
1677 if (cp437) appendByte(cp437);
1685 if ((c & 0xE0) == 0xC0) {
1690 if ((c & 0xF0) == 0xE0) {
1695 if ((c & 0xF8) == 0xF0) {
1701 if (c >= 0x20 && c < 0x7F) {
1702 appendByte(
static_cast<uint8_t
>(c));
1703 }
else if (c >= 0x80 && c <= 0xFF) {
1705 appendByte(
static_cast<uint8_t
>(c));
1717 bool anyCommandReady =
false;
1721 for (
int i = 0; i < 4096; ++i) {
1725 if (handleEscape(c))
continue;
1727 bool commandReady =
false;
1728 handleSpecialChar(c, commandReady);
1729 if (commandReady) anyCommandReady =
true;
1731 return anyCommandReady;
1763#if FEATURE_SECURE_SERIAL
1766 uint64_t now = esp_timer_get_time();
1790 if (
pm.isBadgeBlocked()) {
1791 if (
pm.isLockoutActive()) {
1792 uint32_t remainingSec =
pm.getLockoutRemainingMs() / 1000;
1793 LOG_W(
TAG,
"PIN locked, %lu seconds remaining", (
unsigned long)remainingSec);
1795 LOG_W(
TAG,
"PIN permanently blocked (retries exhausted)");
1800 if (!pin || !*pin) {
1805 if (!
pm.verifyBadgePin(pin)) {
1806 LOG_W(
TAG,
"Authentication failed, %d retries remaining",
pm.getBadgeRetries());
1815 LOG_I(
TAG,
"Authenticated via serial");
1823#if FEATURE_SECURE_SERIAL
1846void SerialCmd::executeCommand(
char* cmd) {
1850 char first_token[24];
1852 while (cmd[i] && !isspace(
static_cast<unsigned char>(cmd[i])) &&
1853 i <
sizeof(first_token) - 1) {
1854 first_token[i] = cmd[i];
1857 first_token[i] =
'\0';
1858 LOG_D(
TAG,
"Executing: %s", first_token);
1867char* SerialCmd::trim(
char* str) {
1868 if (!str)
return str;
1870 while (*str && isspace(
static_cast<unsigned char>(*str))) str++;
1871 if (*str ==
'\0')
return str;
1873 char* end = str + strlen(str) - 1;
1874 while (end > str && isspace(
static_cast<unsigned char>(*end))) end--;
1899 default:
return "?";
1910 default:
return "?";
1920 default:
return "?";
1929 if (count <= 1)
return count;
1932 for (uint8_t i = 0; i < count; i++) {
1934 for (uint8_t j = 0; j < unique; j++) {
1935 if (strcmp(results[i].ssid, results[j].ssid) == 0) {
1937 if (results[i].rssi > results[j].rssi) results[j] = results[i];
1941 if (!seen && results[i].ssid[0] !=
'\0') {
1942 if (unique != i) results[unique] = results[i];
1947 for (uint8_t i = 0; i < unique; i++) {
1948 for (uint8_t j = i + 1; j < unique; j++) {
1949 if (results[j].rssi > results[i].rssi) {
1951 results[i] = results[j];
1980 if (!wifi->startScan()) {
1985 uint32_t elapsed = 0;
1991 if (!wifi->isScanComplete()) {
2003 Console::printf(
"# %-32s %5s %3s %s\r\n",
"SSID",
"RSSI",
"Ch",
"Security");
2004 for (uint8_t i = 0; i < count; i++) {
2006 static_cast<unsigned>(i + 1),
2009 static_cast<unsigned>(results[i].channel),
2033 if (wifi->isConnected()) {
2037 if (wifi->getIpAddress(ip,
sizeof(ip))) {
2040 uint8_t mac[6] = {};
2041 if (wifi->getMacAddress(mac)) {
2043 mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
2045 int8_t rssi = wifi->getRssi();
2053 const auto& cfg = wh.config();
2060 static_cast<unsigned long>(wh.getConnectTimeoutMs()));
2076 if (args && args[0]) {
2077 if (strcasecmp(args,
"ap") == 0) {
2079 }
else if (strcasecmp(args,
"sta_ap") == 0) {
2081 }
else if (strcasecmp(args,
"sta") != 0) {
2093 if (!wifi->enable(mode)) {
2104 if (!wh.config().valid)
return;
2107 if (!wh.setUserEnabled(
true)) {
2109 const char* err = wh.getLastError();
2115 wifi->getIpAddress(ip,
sizeof(ip));
2130 if (!wifi->isEnabled()) {
2142 if (!args || !args[0]) {
2147 const char* space = strchr(args,
' ');
2154 char password[65] = {};
2156 size_t ssidLen =
static_cast<size_t>(space - args);
2157 if (ssidLen >=
sizeof(ssid)) ssidLen =
sizeof(ssid) - 1;
2158 memcpy(ssid, args, ssidLen);
2159 ssid[ssidLen] =
'\0';
2161 const char* pw = space + 1;
2162 while (*pw ==
' ') pw++;
2163 size_t pwLen = strlen(pw);
2164 if (pwLen >=
sizeof(password)) pwLen =
sizeof(password) - 1;
2165 memcpy(password, pw, pwLen);
2166 password[pwLen] =
'\0';
2168 if (ssid[0] ==
'\0') {
2174 wh.saveCredentials(ssid, password);
2177 ssid,
static_cast<unsigned long>(wh.getConnectTimeoutMs()));
2179 if (!wh.setUserEnabled(
true)) {
2180 const char* err = wh.getLastError();
2181 Console::printf(
"ERROR: Connection failed (%s)\r\n", err ? err :
"?");
2187 if (wifi) wifi->getIpAddress(ip,
sizeof(ip));
2196 if (!args || !args[0]) {
2198 static_cast<unsigned long>(wh.getConnectTimeoutMs()));
2202 char* end =
nullptr;
2203 long val = strtol(args, &end, 10);
2204 if (end == args || *end !=
'\0'
2213 if (!wh.setConnectTimeoutMs(
static_cast<uint32_t
>(val))) {
2244 uint8_t count = reg.getModuleCount();
2245 for (uint8_t i = 0; i < count; i++) {
2247 if (module && module->
getName() &&
2262 uint8_t count = reg.getModuleCount();
2264 Console::printf(
"=== Modules (%u) ===\r\n",
static_cast<unsigned>(count));
2265 for (uint8_t i = 0; i < count; i++) {
2267 if (!module)
continue;
2269 const char* error = reg.getModuleSlotError(i);
2271 static_cast<unsigned>(i),
2273 reg.isModuleEnabled(i) ?
"enabled" :
"disabled",
2274 reg.getModuleStatusLabel(i),
2275 error ? error :
"");
2284 if (!args || !*args) {
2296 uint8_t idx =
static_cast<uint8_t
>(index);
2298 if (reg.isModuleEnabled(idx)) {
2305 reg.setModuleEnabled(idx,
true);
2306 if (!reg.startModule(idx)) {
2307 switch (reg.classifyStartFailure(idx)) {
2312 Console::printf(
"ERROR: No free USB slot - disable a USB module (e.g. GPG) first\r\n");
2333 if (!args || !*args) {
2345 uint8_t idx =
static_cast<uint8_t
>(index);
2347 if (!reg.isModuleEnabled(idx)) {
2354 reg.setModuleEnabled(idx,
false);
2372 {
"LIST",
"[namespace]",
"List entries (optional namespace filter)",
cmdNvsList},
2373 {
"READ",
"<ns> <key>",
"Read key value",
cmdNvsRead},
2374 {
"DEL",
"<ns> [key]",
"Delete key, or entire namespace if omitted",
cmdNvsDel},
2375 {
"CLEAR",
"YES",
"Erase entire NVS (confirmation required)",
cmdNvsClear},
2376 {
nullptr,
nullptr,
nullptr,
nullptr},
2381 {
"STATUS",
"",
"Show PIN retries / lockout state",
cmdPinStatus},
2382 {
"RESET",
"",
"Reset PIN retries (debug)",
cmdPinReset},
2383 {
"CHANGE",
"<currentPin> <newPin>",
"Change badge PIN (4-8 digits)",
cmdPinChange},
2384 {
"DURESS",
"<pin>",
"Arm self-destruct PIN (wipes on entry)",
cmdPinDuress},
2386 {
nullptr,
nullptr,
nullptr,
nullptr},
2391 {
"STATUS",
"",
"Show TR01 connection status",
cmdTr01Status},
2392 {
"INFO",
"",
"Show TR01 chip info (ID, firmware)",
cmdTr01Info},
2394 {
"SLOTS",
"",
"Show TR01 slot usage summary",
cmdTr01Slots},
2395 {
"RMEM_READ",
"<slot>",
"Read and dump R-Memory slot",
cmdTr01RmemRead},
2396 {
"ECC_DEL",
"<slot>",
"Delete ECC key slot",
cmdTr01EccDel},
2398 {
"RESYNC",
"",
"Resync TR01 session and cache",
cmdTr01Resync},
2400 {
"CLEANUP",
"",
"Cleanup mismatched slots and rebuild cache",
cmdTr01Cleanup},
2401 {
"WIPE",
"CONFIRM",
"Factory reset all TR01 data",
cmdTr01Wipe},
2402 {
nullptr,
nullptr,
nullptr,
nullptr},
2407 {
"SCAN",
"",
"Scan for available networks",
cmdWifiScan},
2408 {
"STATUS",
"",
"Show WiFi state and saved configuration",
cmdWifiStatus},
2409 {
"ON",
"[sta|ap|sta_ap]",
"Enable WiFi radio (default STA, auto-reconnect)",
cmdWifiOn},
2410 {
"OFF",
"",
"Disable WiFi radio",
cmdWifiOff},
2411 {
"CONNECT",
"<ssid> <password>",
"Connect to network and persist credentials",
cmdWifiConnect},
2412 {
"TIMEOUT",
"[ms]",
"Get or set connect timeout (3000-60000 ms)",
cmdWifiTimeout},
2413 {
"FORGET",
"",
"Clear saved WiFi configuration",
cmdWifiForget},
2414 {
nullptr,
nullptr,
nullptr,
nullptr},
2419 {
"LIST",
"",
"List modules with state and errors",
cmdModuleList},
2420 {
"ENABLE",
"<name>",
"Enable a module (persistent)",
cmdModuleEnable},
2422 {
nullptr,
nullptr,
nullptr,
nullptr},
2432 reg.registerCommand({
"HELP",
"Show available commands",
cmdHelp,
"system",
false});
2433 reg.registerCommand({
"PING",
"Check if device is responsive",
cmdPing,
"system",
false});
2434 reg.registerCommand({
"STATUS",
"Show system status",
cmdStatus,
"system",
false});
2435 reg.registerCommand({
"MEM",
"Show memory usage",
cmdMem,
"system",
false});
2436 reg.registerCommand({
"MEMINFO",
"Show detailed memory + task info",
cmdMemInfo,
"system",
false});
2437 reg.registerCommand({
"CPU",
"Measure aggregate CPU load (~250 ms)",
cmdCpu,
"system",
false});
2438 reg.registerCommand({
"ERROR_LOG",
"Show error log (CLEAR to reset)",
cmdErrorLog,
"system",
false});
2439 reg.registerCommand({
"REBOOT",
"Restart the device",
cmdReboot,
"system",
true});
2440 reg.registerCommand({
"BOOTLOADER",
"Reboot into USB download mode",
cmdBootloader,
"system",
true});
2441 reg.registerCommand({
"SHIPMODE",
"Enter ship mode (disconnect battery)",
cmdShipMode,
"system",
true});
2442 reg.registerCommand({
"PASTE",
"Paste text into the active T9 input",
cmdPaste,
"system",
true});
2444 reg.registerCommand({
"NVS",
"NVS storage: LIST/READ/DEL/CLEAR",
cmdNvs,
"nvs",
true,
kNvsSubs});
2446 reg.registerCommand({
"GET_TIME",
"Show current time",
cmdGetTime,
"time",
false});
2447 reg.registerCommand({
"GET_DATE",
"Show current date",
cmdGetDate,
"time",
false});
2448 reg.registerCommand({
"SET_TIME",
"Set time (HH:MM:SS)",
cmdSetTime,
"time",
false});
2449 reg.registerCommand({
"SET_DATE",
"Set date (DD.MM.YYYY or Unix timestamp)",
cmdSetDate,
"time",
false});
2451 reg.registerCommand({
"SET_NAME",
"Set display name",
cmdSetName,
"display",
false});
2452 reg.registerCommand({
"SET_INFO",
"Set info line 1",
cmdSetInfo,
"display",
false});
2453 reg.registerCommand({
"SET_INFO2",
"Set info line 2",
cmdSetInfo2,
"display",
false});
2455 reg.registerCommand({
"PIN",
"PIN management: STATUS/RESET/CHANGE/DURESS",
cmdPin,
"pin",
true,
kPinSubs});
2457 reg.registerCommand({
"TR01",
"TROPIC01 secure element: STATUS/INFO/SESSION/SLOTS/RMEM_*/ECC_DEL/...",
2460#if FEATURE_SECURE_SERIAL
2461 reg.registerCommand({
"AUTH",
"Authenticate with PIN", cmdAuth,
"auth",
false});
2462 reg.registerCommand({
"LOGOUT",
"End authenticated session", cmdLogout,
"auth",
false});
2465 reg.registerCommand({
"WIFI",
"WiFi control: SCAN/STATUS/ON/OFF/CONNECT/TIMEOUT/FORGET",
2468 reg.registerCommand({
"MODULE",
"Module control: LIST/ENABLE/DISABLE",
Canonical CP437 <-> Unicode/UTF-8 codec.
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.
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 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
Slot parsing helpers for secure-element commands.