Files
roro9stack/src/services/debug_console.h
T
twislaandClaude Opus 5.5 388e847cd4 Debug Console: files, screenshots and Update from SD over Wi-Fi
New commands everywhere: ls, rm and install <path> (Update from SD
without the Firmware page), all as Storage Service jobs. In Debug
Builds the console task answers get and put (one storage job per
transfer, file kept open, TCP flow control: about 300 KB/s, against
55 KB/s over serial) and screenshot (the RGB332 frame the UI composes
into). rdbg.py turns those into files and PNGs.

A failed put closes the connection: the rest of the file had been
parsed as commands. Card writes are retried 3 times after closing,
truncating to the last good byte and reopening, since FATFS keeps a
file in error after one failed write (seen once at 1.3 MB on this card).
onStorage() decides with one compare-and-swap whether the job or the
timeout wins, so an abandoned job can't touch a returned stack frame.

Verified on the device: screenshot; a get round trip byte-identical;
4 puts in a row; a tampered .ota refused by install; a good one put,
installed from SD, confirmed on Probation. The retry path itself has
not fired since it was added.

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

68 lines
2.3 KiB
C++

#pragma once
#ifdef RORO_DEBUG
#include <freertos/FreeRTOS.h>
#include <freertos/semphr.h>
#include <deque>
#include <functional>
#include <string>
#include "service.h"
#include "services/storage_service.h"
#include "services/wifi_service.h"
#include "ui/canvas.h"
class NetworkClient;
namespace roro {
// Debug Builds only (ADR 0004): the console over Wi-Fi, on TCP 2323 while Wi-Fi is Connected. A
// client sends the debug token as its first line, then gets the recent console backlog, every new
// console line, and runs the same commands as the serial port. One client at a time.
//
// The socket lives on this Service's own task; commands are handed to the main loop (takeCommand),
// which runs them where touching Apps and Services is safe. Their output reaches the client
// through the console ring like everything else.
class DebugConsole : public Service {
public:
static constexpr uint16_t kPort = 2323;
DebugConsole(WifiService& wifi, StorageService& storage);
// The off-screen frame the UI composes into: `screenshot` sends it as it stands.
void setFrame(Canvas& frame) { frame_ = &frame; }
const char* name() const override { return "debug"; }
void start() override;
// The next command line a client sent, for the main loop to run.
bool takeCommand(std::string& line);
bool clientConnected() const { return connected_; }
private:
static void taskEntry(void* self);
void listen();
void serve(::NetworkClient& client);
bool authenticate(::NetworkClient& client);
// Binary commands, answered on this task: true if `line` was one.
bool binaryCommand(::NetworkClient& client, const std::string& line);
// Runs `job` on the storage task (where card access is safe) and waits for it. False if the
// card isn't mounted, in which case the storage task never runs it.
bool onStorage(std::function<void()> job);
void get(::NetworkClient& client, const std::string& path);
void put(::NetworkClient& client, const std::string& args);
void screenshot(::NetworkClient& client);
WifiService& wifi_;
StorageService& storage_;
Canvas* frame_ = nullptr;
TaskHandle_t task_ = nullptr;
SemaphoreHandle_t lock_;
std::deque<std::string> commands_;
volatile bool connected_ = false;
};
} // namespace roro
#endif