mirror of
https://github.com/markqvist/RNode_Firmware.git
synced 2026-04-28 09:43:08 +00:00
Add watchdog, serial file download, and bootloader-mode flash workflow
Watchdog: 30s ESP32 task watchdog auto-resets on lockup. Fed every main loop iteration. Disabled during deep sleep (CPU off). File download: 'D' debug command streams file contents by index over serial. screenshot.py 'dl <index>' downloads to local file. Flash workflow no longer needs BOOT+RST: screenshot.py z → esptool --before no_reset write_flash
This commit is contained in:
parent
d1daf8ca3e
commit
a45986f7a4
3 changed files with 103 additions and 2 deletions
17
Gui.h
17
Gui.h
|
|
@ -159,9 +159,11 @@ extern volatile uint32_t imu_step_count;
|
|||
// Sensor logger toggle — set by .ino after IMULogger.h is included
|
||||
typedef bool (*gui_log_toggle_fn_t)();
|
||||
static gui_log_toggle_fn_t gui_log_toggle_fn = NULL;
|
||||
// SD file listing — set by .ino after SD is available
|
||||
// SD file listing and download — set by .ino after SD is available
|
||||
typedef void (*gui_list_files_fn_t)();
|
||||
static gui_list_files_fn_t gui_list_files_fn = NULL;
|
||||
typedef void (*gui_download_file_fn_t)(uint8_t index);
|
||||
static gui_download_file_fn_t gui_download_file_fn = NULL;
|
||||
static bool gui_imu_logging = false;
|
||||
// Forward declarations for IMULogger.h variables (defined later in compilation)
|
||||
extern bool imu_logging;
|
||||
|
|
@ -1003,7 +1005,8 @@ void gui_process_serial_byte(uint8_t b) {
|
|||
switch (b) {
|
||||
case 'T': gui_cmd_payload_len = 5; break; // x(2) + y(2) + duration(1)
|
||||
case 'N': gui_cmd_payload_len = 2; break; // col(1) + row(1)
|
||||
default: gui_cmd_payload_len = 0; break; // S, M, I, F, L — no payload
|
||||
case 'D': gui_cmd_payload_len = 1; break; // file index(1)
|
||||
default: gui_cmd_payload_len = 0; break; // S, M, I, F, L, X, Z — no payload
|
||||
}
|
||||
gui_cmd_state++;
|
||||
if (gui_cmd_payload_len == 0) {
|
||||
|
|
@ -1336,6 +1339,16 @@ static void gui_cmd_execute() {
|
|||
Serial.flush();
|
||||
break;
|
||||
}
|
||||
case 'D': { // Download file by index
|
||||
Serial.write(hdr, 4);
|
||||
if (gui_download_file_fn) {
|
||||
gui_download_file_fn(gui_cmd_payload[0]);
|
||||
} else {
|
||||
Serial.println("{\"error\":\"no_sd\"}");
|
||||
}
|
||||
Serial.flush();
|
||||
break;
|
||||
}
|
||||
case 'X': { // Hard reset
|
||||
Serial.write(hdr, 4);
|
||||
Serial.println("{\"reset\":true}");
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@
|
|||
#include "Utilities.h"
|
||||
|
||||
#if BOARD_MODEL == BOARD_TWATCH_ULT
|
||||
#include "esp_task_wdt.h"
|
||||
#include "XL9555.h"
|
||||
#include "CO5300.h"
|
||||
#include "DRV2605.h"
|
||||
|
|
@ -117,6 +118,13 @@ void setup() {
|
|||
#if MCU_VARIANT == MCU_ESP32
|
||||
boot_seq();
|
||||
|
||||
// Hardware watchdog — auto-resets on lockup (30s timeout covers
|
||||
// BHI260AP firmware upload which takes ~10s at boot)
|
||||
#if BOARD_MODEL == BOARD_TWATCH_ULT
|
||||
esp_task_wdt_init(30, true); // 30s timeout, panic on expire
|
||||
esp_task_wdt_add(NULL); // subscribe current task (loopTask)
|
||||
#endif
|
||||
|
||||
// Init shared SPI bus mutex before any SPI users
|
||||
#if BOARD_MODEL == BOARD_TWATCH_ULT
|
||||
shared_spi_init();
|
||||
|
|
@ -2008,6 +2016,7 @@ void work_while_waiting() { loop(); }
|
|||
|
||||
void loop() {
|
||||
#if BOARD_MODEL == BOARD_TWATCH_ULT
|
||||
esp_task_wdt_reset(); // Feed watchdog
|
||||
uint32_t _prof_t0 = micros(), _prof_t1;
|
||||
#endif
|
||||
|
||||
|
|
@ -2258,6 +2267,38 @@ void loop() {
|
|||
}
|
||||
if (shared_spi_mutex) xSemaphoreGive(shared_spi_mutex);
|
||||
};
|
||||
gui_download_file_fn = [](uint8_t index) {
|
||||
if (shared_spi_mutex) xSemaphoreTake(shared_spi_mutex, portMAX_DELAY);
|
||||
SPI.begin(SD_CLK, SD_MISO, SD_MOSI, SD_CS);
|
||||
if (SD.begin(SD_CS, SPI, 4000000, "/sd", 5)) {
|
||||
File root = SD.open("/");
|
||||
File f;
|
||||
uint8_t i = 0;
|
||||
while ((f = root.openNextFile())) {
|
||||
if (i == index) {
|
||||
Serial.printf("{\"name\":\"%s\",\"size\":%lu}\n", f.name(), (unsigned long)f.size());
|
||||
uint8_t buf[512];
|
||||
while (f.available()) {
|
||||
int n = f.read(buf, sizeof(buf));
|
||||
Serial.write(buf, n);
|
||||
}
|
||||
f.close();
|
||||
root.close();
|
||||
SD.end();
|
||||
if (shared_spi_mutex) xSemaphoreGive(shared_spi_mutex);
|
||||
return;
|
||||
}
|
||||
f.close();
|
||||
i++;
|
||||
}
|
||||
root.close();
|
||||
SD.end();
|
||||
Serial.printf("{\"error\":\"index %d not found\"}\n", index);
|
||||
} else {
|
||||
Serial.println("{\"error\":\"sd_init_failed\"}");
|
||||
}
|
||||
if (shared_spi_mutex) xSemaphoreGive(shared_spi_mutex);
|
||||
};
|
||||
#endif
|
||||
|
||||
// Enable step counter (low power, always-on)
|
||||
|
|
|
|||
|
|
@ -241,6 +241,46 @@ def cmd_files(s):
|
|||
print(f"Timeout ({len(buf)} bytes)")
|
||||
|
||||
|
||||
def cmd_download(s, index, output):
|
||||
"""Download file from SD card by index"""
|
||||
s.write(PREFIX + b'D' + bytes([index]))
|
||||
s.flush()
|
||||
buf = b""
|
||||
# Wait for header line with filename and size
|
||||
deadline = time.time() + 10
|
||||
while time.time() < deadline:
|
||||
chunk = s.read(max(1, s.in_waiting or 1))
|
||||
if chunk:
|
||||
buf += chunk
|
||||
magic = PREFIX + b"D"
|
||||
idx = buf.find(magic)
|
||||
if idx >= 0:
|
||||
# Find the JSON header line
|
||||
hdr_start = idx + 4
|
||||
nl = buf.find(b"\n", hdr_start)
|
||||
if nl < 0:
|
||||
continue
|
||||
import json
|
||||
info = json.loads(buf[hdr_start:nl])
|
||||
if "error" in info:
|
||||
print(f"Error: {info['error']}")
|
||||
return
|
||||
fname = info["name"]
|
||||
fsize = info["size"]
|
||||
# Read the file data
|
||||
data = buf[nl + 1:]
|
||||
while len(data) < fsize and time.time() < deadline + 30:
|
||||
chunk = s.read(min(4096, fsize - len(data)))
|
||||
if chunk:
|
||||
data += chunk
|
||||
outname = output or fname.lstrip("/")
|
||||
with open(outname, "wb") as f:
|
||||
f.write(data[:fsize])
|
||||
print(f"Downloaded {fname} ({fsize} bytes) → {outname}")
|
||||
return
|
||||
print(f"Timeout ({len(buf)} bytes)")
|
||||
|
||||
|
||||
def cmd_simple(s, cmd_char, label):
|
||||
"""Send a command, print response, don't wait long"""
|
||||
send_cmd(s, ord(cmd_char))
|
||||
|
|
@ -309,6 +349,11 @@ def main():
|
|||
sub.add_parser("files", aliases=["f"],
|
||||
help="List files on SD card")
|
||||
|
||||
dl = sub.add_parser("download", aliases=["dl"],
|
||||
help="Download file from SD card by index")
|
||||
dl.add_argument("index", type=int, help="File index from 'files' listing")
|
||||
dl.add_argument("-o", "--output", help="Output filename (default: use SD name)")
|
||||
|
||||
sub.add_parser("reset", aliases=["x"],
|
||||
help="Hard reset the device")
|
||||
|
||||
|
|
@ -342,6 +387,8 @@ def main():
|
|||
cmd_log(s)
|
||||
elif args.command in ("files", "f"):
|
||||
cmd_files(s)
|
||||
elif args.command in ("download", "dl"):
|
||||
cmd_download(s, args.index, args.output)
|
||||
elif args.command in ("reset", "x"):
|
||||
cmd_simple(s, 'X', "Reset sent")
|
||||
elif args.command in ("bootloader", "z"):
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue