969 lines
33 KiB
C++
969 lines
33 KiB
C++
/**
|
|
* @file debugger.cpp
|
|
* @brief Implementation of debugging functions for monitoring and diagnostics.
|
|
*
|
|
* This file contains the implementation of various debugging functions to monitor
|
|
* and diagnose the system. It includes functions to print system information, WiFi
|
|
* details, EEPROM status, dump configuration settings, dump persistence data, show
|
|
* Diagnostic Trouble Codes (DTCs), and more.
|
|
*
|
|
* @author Marcel Peterkau
|
|
* @date 24.08.2025
|
|
*/
|
|
|
|
#include "debugger.h"
|
|
#include "common.h"
|
|
#include "globals.h"
|
|
|
|
#include <map>
|
|
#include <vector>
|
|
#include <functional>
|
|
#include <type_traits>
|
|
|
|
void Debug_log(LogLevel, const char *format, ...);
|
|
const char *uint32_to_binary_string(uint32_t num);
|
|
|
|
// -------------------------------------------------------------------
|
|
// Forward declarations for functions used by command handlers
|
|
// (Implementations are further below)
|
|
void Debug_CheckEEPROM(bool autocorrect);
|
|
void Debug_dumpConfig();
|
|
void Debug_dumpPersistence();
|
|
void Debug_ShowDTCs();
|
|
void Debug_dumpGlobals();
|
|
void Debug_PurgeN(uint16_t pulses);
|
|
void Debug_refillTank();
|
|
void Debug_UpdateWatches();
|
|
void processCmdDebug(String command);
|
|
|
|
// -------------------------------------------------------------------
|
|
// Helpers
|
|
static inline uint8_t clamp_u8(int v, int lo, int hi) { return (uint8_t)(v < lo ? lo : (v > hi ? hi : v)); }
|
|
static inline uint16_t clamp_u16(int v, int lo, int hi) { return (uint16_t)(v < lo ? lo : (v > hi ? hi : v)); }
|
|
static inline uint32_t clamp_u32(int v, int lo, int hi) { return (uint32_t)(v < lo ? lo : (v > hi ? hi : v)); }
|
|
|
|
static int parseIndexOrName(const String &token, const char *const *table, int count)
|
|
{
|
|
if (token.length() == 0)
|
|
return -1;
|
|
bool numeric = true;
|
|
for (size_t i = 0; i < token.length(); ++i)
|
|
{
|
|
char c = token.charAt(i);
|
|
if (c < '0' || c > '9')
|
|
{
|
|
numeric = false;
|
|
break;
|
|
}
|
|
}
|
|
if (numeric)
|
|
{
|
|
int idx = token.toInt();
|
|
return (idx >= 0 && idx < count) ? idx : -1;
|
|
}
|
|
for (int i = 0; i < count; ++i)
|
|
{
|
|
if (table[i] && token.equalsIgnoreCase(table[i]))
|
|
return i;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
static inline const char *safeName(const char *const *table, int count, int idx)
|
|
{
|
|
return (idx >= 0 && idx < count && table[idx]) ? table[idx] : "?";
|
|
}
|
|
|
|
static std::vector<String> splitArgs(const String &input)
|
|
{
|
|
std::vector<String> tokens;
|
|
size_t start = 0;
|
|
while (true)
|
|
{
|
|
int end = input.indexOf(' ', start);
|
|
if (end == -1)
|
|
break;
|
|
tokens.push_back(input.substring(start, end));
|
|
start = static_cast<size_t>(end) + 1;
|
|
}
|
|
if (start < input.length())
|
|
tokens.push_back(input.substring(start));
|
|
return tokens;
|
|
}
|
|
|
|
// -------------------------------------------------------------------
|
|
// Debug watches (place before handlers so templates are visible)
|
|
DebugStatus_t DebuggerStatus[dbg_cntElements];
|
|
|
|
struct DebugWatchEntry
|
|
{
|
|
const void *ptr;
|
|
String name;
|
|
uint32_t interval_ms;
|
|
uint32_t duration_ms;
|
|
uint32_t lastPrint_ms;
|
|
uint32_t start_ms;
|
|
std::function<void(const void *, const String &)> printer;
|
|
};
|
|
|
|
#define MAX_DEBUG_WATCHES 8
|
|
static DebugWatchEntry debugWatches[MAX_DEBUG_WATCHES];
|
|
|
|
template <typename T>
|
|
static void debugPrinterImpl(const void *ptr, const String &name)
|
|
{
|
|
const T *typed = static_cast<const T *>(ptr);
|
|
if constexpr (std::is_same<T, bool>::value)
|
|
Debug_pushMessage("%s = %s\n", name.c_str(), *typed ? "true" : "false");
|
|
else if constexpr (std::is_floating_point<T>::value)
|
|
Debug_pushMessage("%s = %.3f\n", name.c_str(), *typed);
|
|
else if constexpr (std::is_signed<T>::value)
|
|
Debug_pushMessage("%s = %ld\n", name.c_str(), static_cast<long>(*typed));
|
|
else if constexpr (std::is_unsigned<T>::value)
|
|
Debug_pushMessage("%s = %lu\n", name.c_str(), static_cast<unsigned long>(*typed));
|
|
}
|
|
|
|
template <typename T>
|
|
void RegisterDebugPrintAuto(const T *ptr, const String &name, uint32_t interval_ms, uint32_t duration_ms)
|
|
{
|
|
for (int i = 0; i < MAX_DEBUG_WATCHES; ++i)
|
|
{
|
|
if (debugWatches[i].ptr == nullptr)
|
|
{
|
|
debugWatches[i] = {ptr, name, interval_ms, duration_ms, 0, millis(), debugPrinterImpl<T>};
|
|
Debug_pushMessage("Registered Watch: %s\n", name.c_str());
|
|
return;
|
|
}
|
|
}
|
|
Debug_pushMessage("Debug Watch list full!\n");
|
|
}
|
|
|
|
void Debug_UpdateWatches()
|
|
{
|
|
uint32_t now = millis();
|
|
for (int i = 0; i < MAX_DEBUG_WATCHES; ++i)
|
|
{
|
|
auto &w = debugWatches[i];
|
|
if (!w.ptr)
|
|
continue;
|
|
|
|
if (now - w.start_ms >= w.duration_ms)
|
|
{
|
|
Debug_pushMessage("Watch expired: %s\n", w.name.c_str());
|
|
w.ptr = nullptr;
|
|
continue;
|
|
}
|
|
|
|
if (now - w.lastPrint_ms >= w.interval_ms)
|
|
{
|
|
w.lastPrint_ms = now;
|
|
if (w.printer)
|
|
w.printer(w.ptr, w.name);
|
|
}
|
|
}
|
|
}
|
|
|
|
// -------------------------------------------------------------------
|
|
// X-Macro command list (name + short description)
|
|
// Consistent, guessable CLI command names + clear help strings
|
|
#define DEBUG_COMMANDS \
|
|
/* help */ \
|
|
CMD(help, "Show this help") \
|
|
/* info */ \
|
|
CMD(info_sys, "Print system information") \
|
|
CMD(info_net, "Print WiFi/network information") \
|
|
/* EEPROM / config */ \
|
|
CMD(ee_format_cfg, "Format Config-EEPROM") \
|
|
CMD(ee_format_pds, "Format Persistence-EEPROM") \
|
|
CMD(ee_check, "Check EEPROM (no fix)") \
|
|
CMD(ee_check_fix, "Check EEPROM and autocorrect") \
|
|
CMD(ee_dump_1k, "Dump EEPROM [0..1023]") \
|
|
CMD(ee_dump, "Dump EEPROM <start> <len>") \
|
|
CMD(ee_reinit, "Reinitialize EEPROM") \
|
|
CMD(ee_page_reset, "Reset persistence page to start") \
|
|
CMD(cfg_dump, "Dump configuration struct") \
|
|
CMD(pds_dump, "Dump persistence struct") \
|
|
CMD(ee_save_all, "Save both CFG and PDS") \
|
|
/* dumps / globals */ \
|
|
CMD(globals_dump, "Dump globals struct") \
|
|
/* debug port control */ \
|
|
CMD(debug_serial_on, "Enable Serial debug") \
|
|
CMD(debug_webui_on, "Enable WebUI debug") \
|
|
CMD(debug_serial_off, "Disable Serial debug") \
|
|
CMD(debug_webui_off, "Disable WebUI debug") \
|
|
/* DTC + notifications */ \
|
|
CMD(dtc_show, "Show DTCs") \
|
|
CMD(dtc_clear, "Clear all DTCs") \
|
|
CMD(dtc_fake_crit, "Raise a fake CRIT DTC") \
|
|
CMD(dtc_fake_warn, "Raise a fake WARN DTC") \
|
|
CMD(dtc_fake_info, "Raise a fake INFO DTC") \
|
|
CMD(notify_error, "WebUI toast: error") \
|
|
CMD(notify_warning, "WebUI toast: warning") \
|
|
CMD(notify_success, "WebUI toast: success") \
|
|
CMD(notify_info, "WebUI toast: info") \
|
|
/* actions */ \
|
|
CMD(purge, "Purge <n pulses> (default 10)") \
|
|
CMD(wifi_toggle, "Toggle WiFi on/off") \
|
|
CMD(dtc_add, "Add DTC <code>") \
|
|
CMD(tank_refill, "Set tank to 100%") \
|
|
CMD(watch_isr, "Watch globals.isr_debug for 20s") \
|
|
/* setters */ \
|
|
CMD(set_speed_source, "Set speed source <index|name>") \
|
|
CMD(set_can_source, "Set CAN source <index|name>") \
|
|
CMD(set_gps_baud, "Set GPS baud <index|name>") \
|
|
CMD(set_system_status, "Set system status <index|name>") \
|
|
CMD(tank_set_pct, "Set tank fill <0..100> %")
|
|
|
|
// Prototypes for all handlers (linker will error if you add a CMD but forget the function)
|
|
#define CMD(name, desc) static void processCmd_##name(const String &args);
|
|
DEBUG_COMMANDS
|
|
#undef CMD
|
|
|
|
using DebugCmdHandler = void (*)(const String &args);
|
|
struct CmdEntry
|
|
{
|
|
const char *name;
|
|
DebugCmdHandler fn;
|
|
const char *desc;
|
|
};
|
|
|
|
static const std::vector<CmdEntry> &getCmdRegistry()
|
|
{
|
|
static const std::vector<CmdEntry> reg = {
|
|
#define CMD(name, desc) {#name, &processCmd_##name, desc},
|
|
DEBUG_COMMANDS
|
|
#undef CMD
|
|
};
|
|
return reg;
|
|
}
|
|
|
|
static const std::map<String, DebugCmdHandler> &getCmdMap()
|
|
{
|
|
static std::map<String, DebugCmdHandler> m;
|
|
if (m.empty())
|
|
for (const auto &e : getCmdRegistry())
|
|
m.emplace(String(e.name), e.fn);
|
|
return m;
|
|
}
|
|
|
|
// --- compile-time width for help column ---------------------------------
|
|
#ifndef HELP_NAME_PADDING
|
|
#define HELP_NAME_PADDING 2 // extra spaces after longest command
|
|
#endif
|
|
#ifndef HELP_MIN_COLW
|
|
#define HELP_MIN_COLW 14 // optional floor (can be 0)
|
|
#endif
|
|
|
|
namespace dbgmeta
|
|
{
|
|
// 1) Build a constexpr array of command-name lengths via X-macro expansion
|
|
constexpr size_t name_lengths[] = {
|
|
#define CMD(name, desc) (sizeof(#name) - 1),
|
|
DEBUG_COMMANDS
|
|
#undef CMD
|
|
};
|
|
|
|
// 2) constexpr recursion to compute max of the array (C++11-friendly)
|
|
template <size_t N>
|
|
constexpr size_t array_max(const size_t (&a)[N], size_t i = 0, size_t m = 0)
|
|
{
|
|
return (i == N) ? m
|
|
: array_max(a, i + 1, (a[i] > m ? a[i] : m));
|
|
}
|
|
|
|
constexpr size_t longest = array_max(name_lengths);
|
|
}
|
|
|
|
// Final compile-time column width
|
|
constexpr int HELP_COLW_RAW = static_cast<int>(dbgmeta::longest) + HELP_NAME_PADDING;
|
|
constexpr int HELP_COLW = (HELP_COLW_RAW < HELP_MIN_COLW) ? HELP_MIN_COLW : HELP_COLW_RAW;
|
|
|
|
// -------------------------------------------------------------------
|
|
// Dispatcher
|
|
void processCmdDebug(String input)
|
|
{
|
|
input.trim();
|
|
int splitIndex = input.indexOf(' ');
|
|
String command = (splitIndex == -1) ? input : input.substring(0, splitIndex);
|
|
String args = (splitIndex == -1) ? "" : input.substring(splitIndex + 1);
|
|
|
|
auto &cmdMap = getCmdMap();
|
|
auto it = cmdMap.find(command);
|
|
if (it != cmdMap.end())
|
|
it->second(args);
|
|
else
|
|
Debug_log(LOG_WARN, "Unknown command: '%s'\n", command.c_str());
|
|
}
|
|
|
|
// -------------------------------------------------------------------
|
|
// Lifecycle / I/O
|
|
void initDebugger()
|
|
{
|
|
DebuggerStatus[dbg_Serial] = disabled;
|
|
DebuggerStatus[dbg_Webui] = disabled;
|
|
Serial.setDebugOutput(false);
|
|
}
|
|
|
|
void Debug_Process()
|
|
{
|
|
// === States ===
|
|
typedef enum
|
|
{
|
|
IDLE,
|
|
CMD_COMPLETE,
|
|
CMD_ABORT,
|
|
CMD_OVERFLOW
|
|
} InputProcessed_t;
|
|
enum EscState
|
|
{
|
|
ESC_NONE,
|
|
ESC_GOT_ESC,
|
|
ESC_GOT_BRACKET
|
|
};
|
|
|
|
// === Config ===
|
|
#ifndef DBG_HISTORY_CAP
|
|
#define DBG_HISTORY_CAP 4 // wie viele letzte Commands gemerkt werden
|
|
#endif
|
|
#ifndef DBG_ESC_TIMEOUT_MS
|
|
#define DBG_ESC_TIMEOUT_MS 100 // Timeout, um lone-ESC von Arrow-ESC zu unterscheiden
|
|
#endif
|
|
|
|
// === Line buffer / history ===
|
|
static char inputBuffer[32];
|
|
static unsigned int inputCnt = 0;
|
|
|
|
static char history[DBG_HISTORY_CAP][sizeof(inputBuffer)];
|
|
static uint8_t histSize = 0; // wie viele Slots belegt (<= CAP)
|
|
static uint8_t histHead = 0; // nächste Schreibposition (Ringpuffer)
|
|
static uint8_t histOffset = 0; // 0: live editing, >0: n zurück (1 = letzter cmd)
|
|
|
|
static char editBackup[sizeof(inputBuffer)]; // was vor dem ersten ↑ im Eingabefeld stand
|
|
|
|
static EscState escState = ESC_NONE;
|
|
static uint32_t escTs = 0;
|
|
|
|
auto replaceInputLine = [&](const char *newData, unsigned int newLen)
|
|
{
|
|
// Cursor ans Zeilenende => lösche aktuelle Eingabe „sichtbar“ und schreibe neuen Inhalt
|
|
for (unsigned int i = 0; i < inputCnt; ++i)
|
|
Serial.write('\b');
|
|
for (unsigned int i = 0; i < inputCnt; ++i)
|
|
Serial.write(' ');
|
|
for (unsigned int i = 0; i < inputCnt; ++i)
|
|
Serial.write('\b');
|
|
|
|
// kopieren + echo
|
|
newLen = (newLen >= sizeof(inputBuffer)) ? (sizeof(inputBuffer) - 1) : newLen;
|
|
memcpy(inputBuffer, newData, newLen);
|
|
inputBuffer[newLen] = '\0';
|
|
inputCnt = newLen;
|
|
Serial.write((const uint8_t *)inputBuffer, inputCnt);
|
|
};
|
|
|
|
auto recallHistory = [&](int direction) // +1 = UP (älter), -1 = DOWN (neuer)
|
|
{
|
|
if (histSize == 0)
|
|
return;
|
|
|
|
if (direction > 0)
|
|
{ // UP
|
|
if (histOffset == 0)
|
|
{
|
|
// ersten Sprung in die History -> aktuellen Edit-Stand sichern
|
|
memcpy(editBackup, inputBuffer, sizeof(editBackup));
|
|
}
|
|
if (histOffset < histSize)
|
|
{
|
|
histOffset++;
|
|
uint8_t idx = (uint8_t)((histHead + DBG_HISTORY_CAP - histOffset) % DBG_HISTORY_CAP);
|
|
replaceInputLine(history[idx], strlen(history[idx]));
|
|
}
|
|
}
|
|
else if (direction < 0)
|
|
{ // DOWN
|
|
if (histOffset > 1)
|
|
{
|
|
histOffset--;
|
|
uint8_t idx = (uint8_t)((histHead + DBG_HISTORY_CAP - histOffset) % DBG_HISTORY_CAP);
|
|
replaceInputLine(history[idx], strlen(history[idx]));
|
|
}
|
|
else if (histOffset == 1)
|
|
{
|
|
// zurück in den Live-Edit-Puffer
|
|
histOffset = 0;
|
|
replaceInputLine(editBackup, strlen(editBackup));
|
|
}
|
|
}
|
|
};
|
|
|
|
auto commitToHistory = [&](const char *line)
|
|
{
|
|
if (!line || !line[0])
|
|
return; // leer -> nicht speichern
|
|
// optional: Duplikatfilter (gleiche wie letzte nicht erneut speichern)
|
|
if (histSize > 0)
|
|
{
|
|
uint8_t lastIdx = (uint8_t)((histHead + DBG_HISTORY_CAP - 1) % DBG_HISTORY_CAP);
|
|
if (strncmp(history[lastIdx], line, sizeof(history[0])) == 0)
|
|
return;
|
|
}
|
|
strncpy(history[histHead], line, sizeof(history[0]) - 1);
|
|
history[histHead][sizeof(history[0]) - 1] = '\0';
|
|
histHead = (uint8_t)((histHead + 1) % DBG_HISTORY_CAP);
|
|
if (histSize < DBG_HISTORY_CAP)
|
|
histSize++;
|
|
};
|
|
|
|
InputProcessed_t InputProcessed = IDLE;
|
|
|
|
// Lone-ESC Timeout behandeln (ESC ohne Folge -> Abort)
|
|
if (escState != ESC_NONE && (millis() - escTs) > DBG_ESC_TIMEOUT_MS)
|
|
{
|
|
InputProcessed = CMD_ABORT;
|
|
escState = ESC_NONE;
|
|
inputCnt = 0;
|
|
inputBuffer[0] = '\0';
|
|
}
|
|
|
|
// Alle verfügbaren Bytes verarbeiten (reaktiver auf Escape-Sequenzen)
|
|
while (Serial.available())
|
|
{
|
|
char c = Serial.read();
|
|
|
|
// CR zu LF normalisieren (einige Terminals senden \r oder \r\n)
|
|
if (c == '\r')
|
|
c = '\n';
|
|
|
|
// Escape-Sequenzen (Pfeiltasten) erkennen: ESC [ A / B
|
|
if (escState == ESC_NONE)
|
|
{
|
|
if ((uint8_t)c == 0x1B)
|
|
{ // ESC
|
|
escState = ESC_GOT_ESC;
|
|
escTs = millis();
|
|
continue;
|
|
}
|
|
}
|
|
else if (escState == ESC_GOT_ESC)
|
|
{
|
|
if (c == '[')
|
|
{
|
|
escState = ESC_GOT_BRACKET;
|
|
continue;
|
|
}
|
|
else
|
|
{
|
|
// ESC allein (kein '[') -> Abort
|
|
InputProcessed = CMD_ABORT;
|
|
escState = ESC_NONE;
|
|
inputCnt = 0;
|
|
inputBuffer[0] = '\0';
|
|
continue;
|
|
}
|
|
}
|
|
else if (escState == ESC_GOT_BRACKET)
|
|
{
|
|
// UP/DOWN
|
|
if (c == 'A')
|
|
{ // Up
|
|
recallHistory(+1);
|
|
}
|
|
else if (c == 'B')
|
|
{ // Down
|
|
recallHistory(-1);
|
|
}
|
|
escState = ESC_NONE;
|
|
continue;
|
|
}
|
|
|
|
switch ((uint8_t)c)
|
|
{
|
|
case '\n':
|
|
// Abschluss der Eingabe
|
|
inputBuffer[inputCnt] = '\0';
|
|
Serial.write('\n');
|
|
InputProcessed = CMD_COMPLETE;
|
|
// History-Context verlassen
|
|
histOffset = 0;
|
|
break;
|
|
|
|
case 0x08: // Backspace
|
|
case 0x7F: // DEL
|
|
if (inputCnt > 0)
|
|
{
|
|
// Puffer kürzen + visuell löschen
|
|
inputCnt--;
|
|
inputBuffer[inputCnt] = '\0';
|
|
Serial.write('\b');
|
|
Serial.write(' ');
|
|
Serial.write('\b');
|
|
}
|
|
// Falls wir gerade in der History sind, zurück in Live-Edit wechseln?
|
|
// (optional: hier NICHT automatisch, damit man editierten History-Text senden kann)
|
|
break;
|
|
|
|
default:
|
|
// printable ASCII (inkl. Leerzeichen): 0x20..0x7E
|
|
if (c >= 0x20 && c <= 0x7E)
|
|
{
|
|
if (inputCnt < sizeof(inputBuffer) - 1)
|
|
{
|
|
inputBuffer[inputCnt++] = c;
|
|
Serial.write(c);
|
|
// Sobald der Nutzer tippt, verlassen wir ggf. den History-View
|
|
if (histOffset != 0)
|
|
{
|
|
histOffset = 0;
|
|
// editBackup war unser Startzustand vor History — jetzt tippt der Nutzer aktiv;
|
|
// nichts weiter zu tun, da inputBuffer bereits der sichtbare Stand ist.
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// overflow -> sofort melden und zurücksetzen
|
|
inputBuffer[sizeof(inputBuffer) - 1] = '\0';
|
|
inputCnt = 0;
|
|
InputProcessed = CMD_OVERFLOW;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
// Wenn schon eine komplette Aktion erkannt wurde, beenden wir die Schleife
|
|
if (InputProcessed == CMD_COMPLETE || InputProcessed == CMD_OVERFLOW || InputProcessed == CMD_ABORT)
|
|
break;
|
|
}
|
|
|
|
// Post-Processing
|
|
switch (InputProcessed)
|
|
{
|
|
case CMD_ABORT:
|
|
Debug_pushMessage("Abort\n");
|
|
break;
|
|
|
|
case CMD_COMPLETE:
|
|
// in History legen (vor Ausführung)
|
|
commitToHistory(inputBuffer);
|
|
processCmdDebug(String(inputBuffer));
|
|
inputCnt = 0;
|
|
inputBuffer[0] = '\0';
|
|
break;
|
|
|
|
case CMD_OVERFLOW:
|
|
Debug_pushMessage("Input buffer overflow\n");
|
|
inputCnt = 0;
|
|
inputBuffer[0] = '\0';
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
if (InputProcessed != IDLE)
|
|
Serial.print(">");
|
|
|
|
Debug_UpdateWatches();
|
|
}
|
|
|
|
// -------------------------------------------------------------------
|
|
// Command handlers
|
|
|
|
// Info
|
|
static void processCmd_help(const String &)
|
|
{
|
|
Debug_log(LOG_INFO, "Available commands:\n");
|
|
for (const auto &e : getCmdRegistry())
|
|
{
|
|
// %-*s nimmt die Breite zur Laufzeit entgegen — hier ist sie aber ein constexpr
|
|
Debug_log(LOG_INFO, " - %-*s : %s\n", HELP_COLW, e.name, e.desc);
|
|
}
|
|
}
|
|
|
|
static void processCmd_info_sys(const String &)
|
|
{
|
|
|
|
Debug_pushMessage("Souko's ChainOiler Mk1\n");
|
|
Debug_pushMessage("Hostname: %s\n", globals.DeviceName);
|
|
|
|
FlashMode_t ideMode = ESP.getFlashChipMode();
|
|
Debug_pushMessage("Sdk version: %s\n", ESP.getSdkVersion());
|
|
Debug_pushMessage("Core Version: %s\n", ESP.getCoreVersion().c_str());
|
|
Debug_pushMessage("Boot Version: %u\n", ESP.getBootVersion());
|
|
Debug_pushMessage("Boot Mode: %u\n", ESP.getBootMode());
|
|
Debug_pushMessage("CPU Frequency: %u MHz\n", ESP.getCpuFreqMHz());
|
|
Debug_pushMessage("Reset reason: %s\n", ESP.getResetReason().c_str());
|
|
Debug_pushMessage("Flash Size: %d\n", ESP.getFlashChipRealSize());
|
|
Debug_pushMessage("Flash Size IDE: %d\n", ESP.getFlashChipSize());
|
|
Debug_pushMessage("Flash ide mode: %s\n", (ideMode == FM_QIO ? "QIO" : ideMode == FM_QOUT ? "QOUT"
|
|
: ideMode == FM_DIO ? "DIO"
|
|
: ideMode == FM_DOUT ? "DOUT"
|
|
: "UNKNOWN"));
|
|
Debug_pushMessage("OTA-Pass: %s\n", QUOTE(ADMIN_PASSWORD));
|
|
Debug_pushMessage("Git-Revision: %s\n", constants.GitHash);
|
|
Debug_pushMessage("Sw-Version: %d.%02d\n", constants.FW_Version_major, constants.FW_Version_minor);
|
|
}
|
|
|
|
static void processCmd_info_net(const String &)
|
|
{
|
|
Debug_pushMessage("IP Address: %s\n", WiFi.localIP().toString().c_str());
|
|
}
|
|
|
|
// EEPROM
|
|
static void processCmd_ee_format_cfg(const String &)
|
|
{
|
|
Debug_pushMessage("Formatting Config-EEPROM and resetting to default\n");
|
|
FormatConfig_EEPROM();
|
|
}
|
|
|
|
static void processCmd_ee_format_pds(const String &)
|
|
{
|
|
Debug_pushMessage("Formatting Persistence-EEPROM and resetting to default\n");
|
|
FormatPersistence_EEPROM();
|
|
}
|
|
|
|
static void processCmd_ee_check(const String &) { Debug_CheckEEPROM(false); }
|
|
static void processCmd_ee_check_fix(const String &) { Debug_CheckEEPROM(true); }
|
|
static void processCmd_ee_dump_1k(const String &) { dumpEEPROM(0, 1024); }
|
|
static void processCmd_ee_dump(const String &args)
|
|
{
|
|
int start = 0, len = EEPROM_SIZE_BYTES;
|
|
auto tokens = splitArgs(args);
|
|
if (tokens.size() >= 2)
|
|
{
|
|
start = tokens[0].toInt();
|
|
len = tokens[1].toInt();
|
|
}
|
|
dumpEEPROM(start, len);
|
|
}
|
|
|
|
static void processCmd_ee_reinit(const String &) { globals.requestEEAction = EE_REINITIALIZE; }
|
|
static void processCmd_ee_page_reset(const String &) { MovePersistencePage_EEPROM(true); }
|
|
static void processCmd_cfg_dump(const String &)
|
|
{
|
|
Debug_pushMessage("DistancePerLube_Default: %d\n", LubeConfig.DistancePerLube_Default);
|
|
Debug_pushMessage("DistancePerLube_Rain: %d\n", LubeConfig.DistancePerLube_Rain);
|
|
Debug_pushMessage("tankCapacity_ml: %d\n", LubeConfig.tankCapacity_ml);
|
|
Debug_pushMessage("amountPerDose_microL: %d\n", LubeConfig.amountPerDose_microL);
|
|
Debug_pushMessage("TankRemindAtPercentage: %d\n", LubeConfig.TankRemindAtPercentage);
|
|
Debug_pushMessage("PulsePerRevolution: %d\n", LubeConfig.PulsePerRevolution);
|
|
Debug_pushMessage("TireWidth_mm: %d\n", LubeConfig.TireWidth_mm);
|
|
Debug_pushMessage("TireWidthHeight_Ratio: %d\n", LubeConfig.TireWidthHeight_Ratio);
|
|
Debug_pushMessage("RimDiameter_Inch: %d\n", LubeConfig.RimDiameter_Inch);
|
|
Debug_pushMessage("DistancePerRevolution_mm: %d\n", LubeConfig.DistancePerRevolution_mm);
|
|
Debug_pushMessage("BleedingPulses: %d\n", LubeConfig.BleedingPulses);
|
|
Debug_pushMessage("SpeedSource: %d\n", LubeConfig.SpeedSource);
|
|
Debug_pushMessage("GPSBaudRate: %d\n", LubeConfig.GPSBaudRate);
|
|
Debug_pushMessage("CANSource: %d\n", LubeConfig.CANSource);
|
|
Debug_pushMessage("checksum: 0x%08X\n", LubeConfig.checksum);
|
|
}
|
|
|
|
static void processCmd_pds_dump(const String &)
|
|
{
|
|
Debug_pushMessage("writeCycleCounter: %d\n", PersistenceData.writeCycleCounter);
|
|
Debug_pushMessage("tankRemain_microL: %d\n", PersistenceData.tankRemain_microL);
|
|
Debug_pushMessage("TravelDistance_highRes_mm: %d\n", PersistenceData.TravelDistance_highRes_mm);
|
|
Debug_pushMessage("checksum: %d\n", PersistenceData.checksum);
|
|
Debug_pushMessage("PSD Address: 0x%04X\n", globals.eePersistenceAddress);
|
|
}
|
|
|
|
static void processCmd_ee_save_all(const String &) { globals.requestEEAction = EE_ALL_SAVE; }
|
|
|
|
static void processCmd_globals_dump(const String &)
|
|
{
|
|
Debug_pushMessage("systemStatus: %d\n", globals.systemStatus);
|
|
Debug_pushMessage("resumeStatus: %d\n", globals.resumeStatus);
|
|
Debug_pushMessage("purgePulses: %d\n", globals.purgePulses);
|
|
Debug_pushMessage("requestEEAction: %d\n", globals.requestEEAction);
|
|
Debug_pushMessage("DeviceName: %s\n", globals.DeviceName);
|
|
Debug_pushMessage("FlashVersion: %s\n", globals.FlashVersion);
|
|
Debug_pushMessage("eePersistenceAddress: %d\n", globals.eePersistenceAddress);
|
|
Debug_pushMessage("TankPercentage: %d\n", globals.TankPercentage);
|
|
Debug_pushMessage("hasDTC: %d\n", globals.hasDTC);
|
|
}
|
|
|
|
// Debug port toggles
|
|
static void processCmd_debug_serial_on(const String &) { SetDebugportStatus(dbg_Serial, enabled); }
|
|
static void processCmd_debug_webui_on(const String &) { SetDebugportStatus(dbg_Webui, enabled); }
|
|
static void processCmd_debug_serial_off(const String &) { SetDebugportStatus(dbg_Serial, disabled); }
|
|
static void processCmd_debug_webui_off(const String &) { SetDebugportStatus(dbg_Webui, disabled); }
|
|
|
|
// DTC / notifications
|
|
static void processCmd_dtc_show(const String &)
|
|
{
|
|
char buff_timestamp[16]; // DD-hh:mm:ss:xxx
|
|
char buff_active[9];
|
|
Debug_pushMessage("\n timestamp | DTC-Nr. | status | severity\n");
|
|
for (uint32_t i = 0; i < MAX_DTC_STORAGE; i++)
|
|
{
|
|
if (DTCStorage[i].Number < DTC_LAST_DTC)
|
|
{
|
|
sprintf(buff_timestamp, "%02d-%02d:%02d:%02d:%03d",
|
|
DTCStorage[i].timestamp / 86400000,
|
|
DTCStorage[i].timestamp / 360000 % 24,
|
|
DTCStorage[i].timestamp / 60000 % 60,
|
|
DTCStorage[i].timestamp / 1000 % 60,
|
|
DTCStorage[i].timestamp % 1000);
|
|
|
|
if (DTCStorage[i].active == DTC_ACTIVE)
|
|
strcpy(buff_active, "active");
|
|
else if (DTCStorage[i].active == DTC_PREVIOUS)
|
|
strcpy(buff_active, "previous");
|
|
else
|
|
strcpy(buff_active, "none");
|
|
|
|
Debug_pushMessage("%s %7d %8s %8d\n",
|
|
buff_timestamp,
|
|
DTCStorage[i].Number,
|
|
buff_active,
|
|
getSeverityForDTC(DTCStorage[i].Number));
|
|
}
|
|
}
|
|
}
|
|
|
|
static void processCmd_dtc_clear(const String &) { ClearAllDTC(); }
|
|
static void processCmd_dtc_fake_crit(const String &) { MaintainDTC(DTC_FAKE_DTC_CRIT, true, millis()); }
|
|
static void processCmd_dtc_fake_warn(const String &) { MaintainDTC(DTC_FAKE_DTC_WARN, true, millis()); }
|
|
static void processCmd_dtc_fake_info(const String &) { MaintainDTC(DTC_FAKE_DTC_INFO, true, millis()); }
|
|
|
|
static void processCmd_notify_error(const String &) { Websocket_PushNotification("Debug Error Notification", error); }
|
|
static void processCmd_notify_warning(const String &) { Websocket_PushNotification("Debug Warning Notification", warning); }
|
|
static void processCmd_notify_success(const String &) { Websocket_PushNotification("Debug Success Notification", success); }
|
|
static void processCmd_notify_info(const String &) { Websocket_PushNotification("Debug Info Notification", info); }
|
|
|
|
// Purge / WiFi / Tank / ISR
|
|
static void processCmd_purge(const String &args)
|
|
{
|
|
auto tokens = splitArgs(args);
|
|
uint16_t n = tokens.empty() ? 10 : (uint16_t)tokens[0].toInt();
|
|
n = clamp_u16(n, 1, 1000);
|
|
globals.purgePulses = n;
|
|
globals.resumeStatus = globals.systemStatus;
|
|
globals.systemStatus = sysStat_Purge;
|
|
Debug_pushMessage("Purging %u pulses\n", n);
|
|
}
|
|
|
|
static void processCmd_wifi_toggle(const String &)
|
|
{
|
|
globals.toggle_wifi = true;
|
|
}
|
|
|
|
static void processCmd_dtc_add(const String &args)
|
|
{
|
|
auto tokens = splitArgs(args);
|
|
if (tokens.empty())
|
|
{
|
|
Debug_log(LOG_WARN, "Usage: dtc_add <code>\n");
|
|
return;
|
|
}
|
|
int code = tokens[0].toInt();
|
|
MaintainDTC((DTCNum_t)code, true, millis());
|
|
}
|
|
|
|
static void processCmd_tank_refill(const String &)
|
|
{
|
|
PersistenceData.tankRemain_microL = LubeConfig.tankCapacity_ml * 1000;
|
|
globals.requestEEAction = EE_PDS_SAVE;
|
|
Debug_pushMessage("Setting Tank to 100%\n");
|
|
}
|
|
|
|
static void processCmd_watch_isr(const String &)
|
|
{
|
|
RegisterDebugPrintAuto(&globals.isr_debug, "watch_isr", 100, 20000);
|
|
}
|
|
|
|
// Setters with bounds/string lookup
|
|
static void processCmd_set_speed_source(const String &args)
|
|
{
|
|
auto tokens = splitArgs(args);
|
|
if (tokens.empty())
|
|
{
|
|
Debug_log(LOG_WARN, "Usage: set_speed_source <index|name>\n");
|
|
return;
|
|
}
|
|
int idx = parseIndexOrName(tokens[0], SpeedSourceString, SPEEDSOURCE_COUNT);
|
|
if (idx < 0)
|
|
{
|
|
Debug_log(LOG_WARN, "Invalid speed source '%s'\n", tokens[0].c_str());
|
|
return;
|
|
}
|
|
LubeConfig.SpeedSource = (SpeedSource_t)idx;
|
|
Debug_log(LOG_INFO, "SpeedSource = %d (%s)\n", idx, safeName(SpeedSourceString, SPEEDSOURCE_COUNT, idx));
|
|
}
|
|
|
|
static void processCmd_set_can_source(const String &args)
|
|
{
|
|
auto tokens = splitArgs(args);
|
|
if (tokens.empty())
|
|
{
|
|
Debug_log(LOG_WARN, "Usage: set_can_source <index|name>\n");
|
|
return;
|
|
}
|
|
int idx = parseIndexOrName(tokens[0], CANSourceString, CANSOURCE_COUNT);
|
|
if (idx < 0)
|
|
{
|
|
Debug_log(LOG_WARN, "Invalid CAN source '%s'\n", tokens[0].c_str());
|
|
return;
|
|
}
|
|
LubeConfig.CANSource = (CANSource_t)idx;
|
|
Debug_log(LOG_INFO, "CANSource = %d (%s)\n", idx, safeName(CANSourceString, CANSOURCE_COUNT, idx));
|
|
}
|
|
|
|
static void processCmd_set_gps_baud(const String &args)
|
|
{
|
|
auto tokens = splitArgs(args);
|
|
if (tokens.empty())
|
|
{
|
|
Debug_log(LOG_WARN, "Usage: set_gps_baud <index|name>\n");
|
|
return;
|
|
}
|
|
int idx = parseIndexOrName(tokens[0], GPSBaudRateString, GPSBAUDRATE_COUNT);
|
|
if (idx < 0)
|
|
{
|
|
Debug_log(LOG_WARN, "Invalid GPS baud '%s'\n", tokens[0].c_str());
|
|
return;
|
|
}
|
|
LubeConfig.GPSBaudRate = (GPSBaudRate_t)idx;
|
|
Debug_log(LOG_INFO, "GPSBaudRate = %d (%s)\n", idx, safeName(GPSBaudRateString, GPSBAUDRATE_COUNT, idx));
|
|
}
|
|
|
|
static void processCmd_set_system_status(const String &args)
|
|
{
|
|
auto tokens = splitArgs(args);
|
|
if (tokens.empty())
|
|
{
|
|
Debug_log(LOG_WARN, "Usage: set_system_status <index|name>\n");
|
|
return;
|
|
}
|
|
int idx = parseIndexOrName(tokens[0], SystemStatusString, SYSSTAT_COUNT);
|
|
if (idx < 0)
|
|
{
|
|
Debug_log(LOG_WARN, "Invalid status '%s'\n", tokens[0].c_str());
|
|
return;
|
|
}
|
|
globals.systemStatus = (tSystem_Status)idx;
|
|
Debug_log(LOG_INFO, "SystemStatus = %d (%s)\n", idx, safeName(SystemStatusString, SYSSTAT_COUNT, idx));
|
|
}
|
|
|
|
static void processCmd_tank_set_pct(const String &args)
|
|
{
|
|
auto tokens = splitArgs(args);
|
|
if (tokens.empty())
|
|
{
|
|
Debug_log(LOG_WARN, "Usage: tank_pct <0..100>\n");
|
|
return;
|
|
}
|
|
int pct = clamp_u16(tokens[0].toInt(), 0, 100);
|
|
PersistenceData.tankRemain_microL = (uint32_t)LubeConfig.tankCapacity_ml * (uint32_t)pct * 10u; // ml * 1000 * pct/100
|
|
globals.requestEEAction = EE_PDS_SAVE;
|
|
Debug_log(LOG_INFO, "Tank set to %d%% (%lu uL)\n", pct, (unsigned long)PersistenceData.tankRemain_microL);
|
|
}
|
|
|
|
// -------------------------------------------------------------------
|
|
// Debug plumbing
|
|
|
|
void SetDebugportStatus(DebugPorts_t port, DebugStatus_t status)
|
|
{
|
|
if (status == disabled)
|
|
Debug_pushMessage("Disable DebugPort %s\n", sDebugPorts[port]);
|
|
DebuggerStatus[port] = status;
|
|
if (status == enabled)
|
|
Debug_pushMessage("Enabled DebugPort %s\n", sDebugPorts[port]);
|
|
}
|
|
|
|
void Debug_log(LogLevel, const char *format, ...)
|
|
{
|
|
if ((DebuggerStatus[dbg_Serial] != enabled) && (DebuggerStatus[dbg_Webui] != enabled))
|
|
return;
|
|
|
|
char buff[128];
|
|
va_list arg;
|
|
va_start(arg, format);
|
|
vsnprintf(buff, sizeof(buff), format, arg);
|
|
va_end(arg);
|
|
|
|
if (DebuggerStatus[dbg_Serial] == enabled)
|
|
Serial.print(buff);
|
|
if (DebuggerStatus[dbg_Webui] == enabled)
|
|
Websocket_PushLiveDebug(String(buff));
|
|
}
|
|
|
|
void Debug_pushMessage(const char *format, ...)
|
|
{
|
|
if ((DebuggerStatus[dbg_Serial] != enabled) && (DebuggerStatus[dbg_Webui] != enabled))
|
|
return;
|
|
|
|
char buff[128];
|
|
va_list arg;
|
|
va_start(arg, format);
|
|
vsnprintf(buff, sizeof(buff), format, arg);
|
|
va_end(arg);
|
|
|
|
if (DebuggerStatus[dbg_Serial] == enabled)
|
|
Serial.print(buff);
|
|
if (DebuggerStatus[dbg_Webui] == enabled)
|
|
Websocket_PushLiveDebug(String(buff));
|
|
}
|
|
|
|
void pushCANDebug(uint32_t id, uint8_t dlc, uint8_t *data)
|
|
{
|
|
if ((DebuggerStatus[dbg_Serial] != enabled) && (DebuggerStatus[dbg_Webui] != enabled))
|
|
return;
|
|
|
|
char buff[100];
|
|
char *p = buff;
|
|
|
|
p += snprintf(p, sizeof(buff), "CAN: 0x%08X | %d | ", id, dlc);
|
|
for (int i = 0; i < dlc; i++)
|
|
p += snprintf(p, sizeof(buff) - (p - buff), "%02X ", data[i]);
|
|
*(p++) = '\n';
|
|
*p = '\0';
|
|
|
|
if (DebuggerStatus[dbg_Serial] == enabled)
|
|
Serial.print(buff);
|
|
if (DebuggerStatus[dbg_Webui] == enabled)
|
|
Websocket_PushLiveDebug(String(buff));
|
|
}
|
|
|
|
// -------------------------------------------------------------------
|
|
// Debug utilities
|
|
|
|
void Debug_CheckEEPROM(bool autocorrect)
|
|
{
|
|
uint32_t checksum = PersistenceData.checksum;
|
|
PersistenceData.checksum = 0;
|
|
if (Checksum_EEPROM((uint8_t *)&PersistenceData, sizeof(PersistenceData)) == checksum)
|
|
Debug_pushMessage("PersistenceData EEPROM Checksum OK\n");
|
|
else
|
|
Debug_pushMessage("PersistenceData EEPROM Checksum BAD\n");
|
|
PersistenceData.checksum = checksum;
|
|
|
|
checksum = LubeConfig.checksum;
|
|
LubeConfig.checksum = 0;
|
|
if (Checksum_EEPROM((uint8_t *)&LubeConfig, sizeof(LubeConfig)) == checksum)
|
|
Debug_pushMessage("LubeConfig EEPROM Checksum OK\n");
|
|
else
|
|
Debug_pushMessage("LubeConfig EEPROM Checksum BAD\n");
|
|
LubeConfig.checksum = checksum;
|
|
|
|
uint32_t sanitycheck = ConfigSanityCheck(autocorrect);
|
|
if (sanitycheck == 0)
|
|
Debug_pushMessage("LubeConfig Sanity Check OK\n");
|
|
else
|
|
Debug_pushMessage("LubeConfig Sanity Check BAD: %s\n", uint32_to_binary_string(sanitycheck));
|
|
}
|
|
|
|
const char *uint32_to_binary_string(uint32_t num)
|
|
{
|
|
static char binary_str[65]; // 32 bits + 31 spaces + '\0'
|
|
int i, j;
|
|
for (i = 31, j = 0; i >= 0; i--, j++)
|
|
{
|
|
binary_str[j] = ((num >> i) & 1) ? '1' : '0';
|
|
if (i % 4 == 0 && i != 0)
|
|
binary_str[++j] = ' ';
|
|
}
|
|
binary_str[j] = '\0';
|
|
return binary_str;
|
|
}
|