Files
roro9stack/src/platform/system_info.cpp
T
twislaandClaude Opus 5.5 0fb7f4e9d5 Debug Builds: the console over Wi-Fi (Debug Console, TCP 2323)
cardputer-adv-debug (-DRORO_DEBUG, version +debug) adds a Debug Console:
after a token line, a client gets the last 6 KB of console output, live
lines (ESP-IDF logs included) and the serial commands. The socket task
only queues lines; the main loop runs them. Release builds compile none
of it. The token lives in ~/.config/roro9stack/debug-token, created by
_docker.sh and passed into the container.

All output now goes through `console`, which never waits for USB: a host
that was attached but not reading stalled the main loop up to 2 s per
line. New commands everywhere: info (slots with their versions from NVS,
since the framework stamps its own into each image), tasks, reboot,
boot other, log level, help. scripts/rdbg.py is the client; flash.sh
--debug builds it; CI builds both variants. ADR 0004.

Verified on the device: USB-flashed, then updated over Wi-Fi to a Debug
Build that confirmed on Probation; both slots hold Debug Builds.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EhqxQ49eCju4CzKYNjZzwT
2026-10-04 02:44:14 +02:00

119 lines
4.8 KiB
C++

#include "platform/system_info.h"
#include <Arduino.h>
#include <esp_heap_caps.h>
#include <esp_ota_ops.h>
#include <esp_system.h>
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>
#include <string>
#include <vector>
#include "version.h"
namespace roro::system_info {
const char* resetReason() {
switch (esp_reset_reason()) {
case ESP_RST_POWERON: return "power-on";
case ESP_RST_EXT: return "reset pin";
case ESP_RST_SW: return "restart";
case ESP_RST_PANIC: return "panic";
case ESP_RST_INT_WDT: return "interrupt watchdog";
case ESP_RST_TASK_WDT: return "task watchdog";
case ESP_RST_WDT: return "watchdog";
case ESP_RST_DEEPSLEEP: return "deep sleep";
case ESP_RST_BROWNOUT: return "brownout";
case ESP_RST_SDIO: return "sdio";
case ESP_RST_USB: return "usb";
case ESP_RST_JTAG: return "jtag";
default: return "unknown";
}
}
bool resetWasCrash() {
switch (esp_reset_reason()) {
case ESP_RST_PANIC:
case ESP_RST_INT_WDT:
case ESP_RST_TASK_WDT:
case ESP_RST_WDT: return true;
default: return false;
}
}
static const char* stateName(esp_ota_img_states_t s) {
switch (s) {
case ESP_OTA_IMG_NEW: return "new";
case ESP_OTA_IMG_PENDING_VERIFY: return "pending verify";
case ESP_OTA_IMG_VALID: return "valid";
case ESP_OTA_IMG_INVALID: return "invalid";
case ESP_OTA_IMG_ABORTED: return "aborted";
default: return "undefined";
}
}
void printSystem(Print& out) {
uint32_t s = millis() / 1000;
out.printf("firmware: %s %s\n", kProductName, versionString());
out.printf("uptime: %luh%02lum%02lus, last start: %s\n", (unsigned long)(s / 3600), (unsigned long)(s / 60 % 60),
(unsigned long)(s % 60), resetReason());
out.printf("heap: %u free, %u lowest, %u largest block\n", ESP.getFreeHeap(), ESP.getMinFreeHeap(),
(unsigned)heap_caps_get_largest_free_block(MALLOC_CAP_8BIT));
out.printf("chip: %s rev %d, %lu MHz, %.1f C\n", ESP.getChipModel(), ESP.getChipRevision(),
(unsigned long)ESP.getCpuFreqMHz(), temperatureRead());
}
static std::string slotKey(const esp_partition_t* slot) { return std::string("ver_") + slot->label; }
void recordSlotVersion(KeyValueStore& store, const esp_partition_t* slot, const char* version) {
std::string known;
if (slot && !(store.getString(slotKey(slot).c_str(), known) && known == version))
store.putString(slotKey(slot).c_str(), version);
}
void printSlots(Print& out, KeyValueStore& store) {
const esp_partition_t* running = esp_ota_get_running_partition();
const esp_partition_t* next = esp_ota_get_boot_partition();
esp_partition_iterator_t it = esp_partition_find(ESP_PARTITION_TYPE_APP, ESP_PARTITION_SUBTYPE_ANY, nullptr);
for (; it; it = esp_partition_next(it)) { // the last next() releases the iterator
const esp_partition_t* p = esp_partition_get(it);
esp_app_desc_t desc;
bool hasApp = esp_ota_get_partition_description(p, &desc) == ESP_OK;
std::string version = "(unknown version)";
store.getString(slotKey(p).c_str(), version);
esp_ota_img_states_t state;
bool hasState = esp_ota_get_state_partition(p, &state) == ESP_OK;
out.printf("slot %s: %s%s%s, %s\n", p->label, hasApp ? version.c_str() : "(empty)", p == running ? ", running" : "",
p == next ? ", boots next" : "", hasState ? stateName(state) : "no OTA state");
}
}
void printTasks(Print& out) {
UBaseType_t n = uxTaskGetNumberOfTasks();
std::vector<TaskStatus_t> tasks(n + 4);
uint32_t total = 0;
n = uxTaskGetSystemState(tasks.data(), tasks.size(), &total);
out.printf("%-16s %-4s %3s %6s %5s %s\n", "task", "st", "pri", "stack", "cpu%", "core");
static const char kStates[] = "RrBSD"; // running, ready, blocked, suspended, deleted
for (UBaseType_t i = 0; i < n; i++) {
const TaskStatus_t& t = tasks[i];
unsigned cpu = total ? (unsigned)((uint64_t)t.ulRunTimeCounter * 100 / total) : 0;
int core = t.xCoreID == tskNO_AFFINITY ? -1 : (int)t.xCoreID;
out.printf("%-16s %-4c %3u %6u %5u %d\n", t.pcTaskName, t.eCurrentState < 5 ? kStates[t.eCurrentState] : '?',
(unsigned)t.uxCurrentPriority, (unsigned)t.usStackHighWaterMark, cpu, core);
}
}
const char* bootOtherSlot() {
const esp_partition_t* other = esp_ota_get_next_update_partition(nullptr);
esp_app_desc_t desc;
if (!other || esp_ota_get_partition_description(other, &desc) != ESP_OK) return "the other slot is empty";
if (esp_ota_set_boot_partition(other) != ESP_OK) return "the other slot doesn't hold a valid image";
delay(200);
esp_restart();
return "";
}
} // namespace roro::system_info