|
| 1 | +#include "sp140/crash_log.h" |
| 2 | + |
| 3 | +#include <Arduino.h> |
| 4 | +#include <Preferences.h> |
| 5 | +#include <esp_system.h> |
| 6 | +#include <rom/rtc.h> |
| 7 | + |
| 8 | +#include "version.h" |
| 9 | +#include "sp140/device_state.h" |
| 10 | + |
| 11 | +#define DEBUG_SERIAL USBSerial |
| 12 | + |
| 13 | +// NVS keys (max 15 chars, all under namespace "openppg") |
| 14 | +static const char* RST_REASON = "rst_reason"; |
| 15 | +static const char* RST_COUNT = "rst_count"; |
| 16 | +static const char* RST_ARMED = "rst_armed"; |
| 17 | +static const char* RST_UPTIME = "rst_uptime"; |
| 18 | +static const char* RST_VER_MAJ = "rst_ver_maj"; |
| 19 | +static const char* RST_VER_MIN = "rst_ver_min"; |
| 20 | +static const char* RST_BOOTS = "rst_boots"; |
| 21 | + |
| 22 | +extern DeviceState currentState; |
| 23 | + |
| 24 | +static const char* resetReasonToString(int reason) { |
| 25 | + // ESP-IDF reset reasons (0-15) |
| 26 | + switch (reason) { |
| 27 | + case ESP_RST_POWERON: return "POWERON"; |
| 28 | + case ESP_RST_SW: return "SW_RESET"; |
| 29 | + case ESP_RST_PANIC: return "PANIC"; |
| 30 | + case ESP_RST_INT_WDT: return "INT_WDT"; |
| 31 | + case ESP_RST_TASK_WDT: return "TASK_WDT"; |
| 32 | + case ESP_RST_WDT: return "WDT"; |
| 33 | + case ESP_RST_DEEPSLEEP: return "DEEPSLEEP"; |
| 34 | + case ESP_RST_BROWNOUT: return "BROWNOUT"; |
| 35 | + case ESP_RST_SDIO: return "SDIO"; |
| 36 | + case ESP_RST_EXT: return "EXTERNAL"; |
| 37 | + default: break; |
| 38 | + } |
| 39 | + // RTC reset reasons (stored as value + 100) |
| 40 | + if (reason >= 100) { |
| 41 | + switch (reason - 100) { |
| 42 | + case 1: return "RTC_POWERON"; |
| 43 | + case 3: return "RTC_SW_RESET"; |
| 44 | + case 12: return "RTC_SW_CPU_RST"; |
| 45 | + case 15: return "RTC_BROWNOUT"; |
| 46 | + case 16: return "RTC_SDIO_RST"; |
| 47 | + case 9: return "RTC_DEEPSLEEP"; |
| 48 | + case 7: return "RTC_TG0WDT"; |
| 49 | + case 8: return "RTC_TG1WDT"; |
| 50 | + case 11: return "RTC_INT_WDT"; |
| 51 | + default: return "RTC_OTHER"; |
| 52 | + } |
| 53 | + } |
| 54 | + return "UNKNOWN"; |
| 55 | +} |
| 56 | + |
| 57 | +static const char* stateToString(uint8_t state) { |
| 58 | + switch (state) { |
| 59 | + case DISARMED: return "DISARMED"; |
| 60 | + case ARMED: return "ARMED"; |
| 61 | + case ARMED_CRUISING: return "ARMED_CRUISING"; |
| 62 | + default: return "UNKNOWN"; |
| 63 | + } |
| 64 | +} |
| 65 | + |
| 66 | +void crashLogReadAndReport() { |
| 67 | + esp_reset_reason_t currentReason = esp_reset_reason(); |
| 68 | + RESET_REASON rtcReason = rtc_get_reset_reason(0); // Core 0 |
| 69 | + |
| 70 | + // Read previous crash data from NVS |
| 71 | + Preferences prefs; |
| 72 | + prefs.begin("crashlog", true); // read-only |
| 73 | + |
| 74 | + uint8_t prevReason = prefs.getUChar(RST_REASON, 0); |
| 75 | + uint16_t prevCount = prefs.getUShort(RST_COUNT, 0); |
| 76 | + uint8_t prevArmed = prefs.getUChar(RST_ARMED, 0); |
| 77 | + uint32_t prevUptime = prefs.getULong(RST_UPTIME, 0); |
| 78 | + uint8_t prevVerMaj = prefs.getUChar(RST_VER_MAJ, 0); |
| 79 | + uint8_t prevVerMin = prefs.getUChar(RST_VER_MIN, 0); |
| 80 | + uint8_t prevBoots = prefs.getUChar(RST_BOOTS, 0); |
| 81 | + |
| 82 | + prefs.end(); |
| 83 | + |
| 84 | + // Print previous reset info |
| 85 | + DEBUG_SERIAL.println("=== Previous Reset Info ==="); |
| 86 | + |
| 87 | + if (prevCount == 0 && prevReason == 0) { |
| 88 | + DEBUG_SERIAL.println("No previous crash data stored."); |
| 89 | + } else { |
| 90 | + DEBUG_SERIAL.print("Reset reason: "); |
| 91 | + DEBUG_SERIAL.print(resetReasonToString(prevReason)); |
| 92 | + DEBUG_SERIAL.print(" ("); |
| 93 | + DEBUG_SERIAL.print(prevReason); |
| 94 | + DEBUG_SERIAL.println(")"); |
| 95 | + |
| 96 | + DEBUG_SERIAL.print("Crash count: "); |
| 97 | + DEBUG_SERIAL.println(prevCount); |
| 98 | + |
| 99 | + DEBUG_SERIAL.print("Last state: "); |
| 100 | + DEBUG_SERIAL.println(stateToString(prevArmed)); |
| 101 | + |
| 102 | + DEBUG_SERIAL.print("Last uptime: "); |
| 103 | + DEBUG_SERIAL.print(prevUptime); |
| 104 | + DEBUG_SERIAL.print(" ms ("); |
| 105 | + DEBUG_SERIAL.print(prevUptime / 60000.0, 1); |
| 106 | + DEBUG_SERIAL.println(" min)"); |
| 107 | + |
| 108 | + DEBUG_SERIAL.print("Last firmware: "); |
| 109 | + DEBUG_SERIAL.print(prevVerMaj); |
| 110 | + DEBUG_SERIAL.print("."); |
| 111 | + DEBUG_SERIAL.println(prevVerMin); |
| 112 | + } |
| 113 | + |
| 114 | + // Boot loop detection |
| 115 | + uint8_t newBoots = 0; |
| 116 | + if (prevUptime > 0 && prevUptime < BOOT_LOOP_UPTIME_MS) { |
| 117 | + newBoots = prevBoots + 1; |
| 118 | + } |
| 119 | + |
| 120 | + if (newBoots >= BOOT_LOOP_THRESHOLD) { |
| 121 | + DEBUG_SERIAL.println("!!! BOOT LOOP DETECTED !!!"); |
| 122 | + DEBUG_SERIAL.print("Device has crashed "); |
| 123 | + DEBUG_SERIAL.print(newBoots); |
| 124 | + DEBUG_SERIAL.println(" times with <30s uptime each."); |
| 125 | + DEBUG_SERIAL.println("!!!!!!!!!!!!!!!!!!!!!!!!!!!"); |
| 126 | + } |
| 127 | + |
| 128 | + DEBUG_SERIAL.print("Current reset: "); |
| 129 | + DEBUG_SERIAL.print(resetReasonToString(currentReason)); |
| 130 | + DEBUG_SERIAL.print(" (esp:"); |
| 131 | + DEBUG_SERIAL.print((int)currentReason); |
| 132 | + DEBUG_SERIAL.print(" rtc:"); |
| 133 | + DEBUG_SERIAL.print((int)rtcReason); |
| 134 | + DEBUG_SERIAL.println(")"); |
| 135 | + DEBUG_SERIAL.println("=== End Reset Info ==="); |
| 136 | + |
| 137 | + // Increment crash count for non-poweron resets |
| 138 | + uint16_t newCount = prevCount; |
| 139 | + if (currentReason != ESP_RST_POWERON && rtcReason != POWERON_RESET) { |
| 140 | + newCount++; |
| 141 | + } |
| 142 | + |
| 143 | + // Store RTC reason if esp_reset_reason returns UNKNOWN |
| 144 | + uint8_t reasonToStore = (uint8_t)currentReason; |
| 145 | + if (currentReason == ESP_RST_UNKNOWN && rtcReason != NO_MEAN) { |
| 146 | + reasonToStore = (uint8_t)rtcReason + 100; // Offset to distinguish from esp_reset_reason values |
| 147 | + } |
| 148 | + |
| 149 | + // Write current boot info to NVS |
| 150 | + prefs.begin("crashlog", false); // read-write |
| 151 | + |
| 152 | + prefs.putUChar(RST_REASON, reasonToStore); |
| 153 | + prefs.putUShort(RST_COUNT, newCount); |
| 154 | + prefs.putUChar(RST_ARMED, (uint8_t)DISARMED); |
| 155 | + prefs.putULong(RST_UPTIME, 0); |
| 156 | + prefs.putUChar(RST_VER_MAJ, VERSION_MAJOR); |
| 157 | + prefs.putUChar(RST_VER_MIN, VERSION_MINOR); |
| 158 | + prefs.putUChar(RST_BOOTS, newBoots); |
| 159 | + |
| 160 | + prefs.end(); |
| 161 | +} |
| 162 | + |
| 163 | +void crashLogHeartbeat() { |
| 164 | + Preferences prefs; |
| 165 | + prefs.begin("crashlog", false); // read-write |
| 166 | + |
| 167 | + prefs.putULong(RST_UPTIME, millis()); |
| 168 | + prefs.putUChar(RST_ARMED, (uint8_t)currentState); |
| 169 | + |
| 170 | + prefs.end(); |
| 171 | +} |
| 172 | + |
| 173 | +void crashLogUpdateArmedState(DeviceState state) { |
| 174 | + Preferences prefs; |
| 175 | + prefs.begin("crashlog", false); // read-write |
| 176 | + |
| 177 | + prefs.putUChar(RST_ARMED, (uint8_t)state); |
| 178 | + |
| 179 | + prefs.end(); |
| 180 | +} |
| 181 | + |
| 182 | +void sendCrashLogData() { |
| 183 | + Preferences prefs; |
| 184 | + prefs.begin("crashlog", true); // read-only |
| 185 | + |
| 186 | + uint8_t reason = prefs.getUChar(RST_REASON, 0); |
| 187 | + uint16_t count = prefs.getUShort(RST_COUNT, 0); |
| 188 | + uint8_t armed = prefs.getUChar(RST_ARMED, 0); |
| 189 | + uint32_t uptime = prefs.getULong(RST_UPTIME, 0); |
| 190 | + uint8_t verMaj = prefs.getUChar(RST_VER_MAJ, 0); |
| 191 | + uint8_t verMin = prefs.getUChar(RST_VER_MIN, 0); |
| 192 | + uint8_t boots = prefs.getUChar(RST_BOOTS, 0); |
| 193 | + |
| 194 | + prefs.end(); |
| 195 | + |
| 196 | + // Manual JSON to avoid ArduinoJson stack usage on WebSerial task |
| 197 | + char buf[256]; |
| 198 | + snprintf(buf, sizeof(buf), |
| 199 | + "{\"crash_log\":{\"reason\":\"%s\",\"code\":%d,\"count\":%d," |
| 200 | + "\"state\":\"%s\",\"uptime_ms\":%lu,\"fw\":\"%d.%d\"," |
| 201 | + "\"rapid_boots\":%d,\"boot_loop\":%s}}", |
| 202 | + resetReasonToString(reason), |
| 203 | + reason, count, stateToString(armed), |
| 204 | + (unsigned long)uptime, verMaj, verMin, |
| 205 | + boots, boots >= BOOT_LOOP_THRESHOLD ? "true" : "false"); |
| 206 | + |
| 207 | + DEBUG_SERIAL.println(buf); |
| 208 | +} |
0 commit comments