Skip to content

[Feature Request] Mirror CLI to secondary UART to enable remote console access (e.g. WiFi bridge) #2645

@gagorski

Description

@gagorski

Problem

When a repeater is deployed in the field (no USB access), there is no way to interact with
the serial CLI without physically retrieving the device. This makes it impossible to run
diagnostic commands (log, stats-radio, neighbors, etc.), change settings, or synchronize
the clock remotely.

Proposed Solution

Add a DualConsole wrapper class that forwards CLI I/O to both the primary USB serial port
and a secondary hardware UART simultaneously. When a second UART is available, any device
connected to it (e.g. a WiFi bridge, BLE module, or another MCU) can relay CLI commands
over the network.

The key idea is that the firmware's CLI accepts a Stream* - passing a DualConsole instead
of &Serial is a non-breaking change that requires no modification to CommonCLI or any
other core code.

Implementation sketch

class DualConsole : public Stream {
    Stream &_a, &_b;
public:
    DualConsole(Stream &a, Stream &b) : _a(a), _b(b) {}

    // Input: read from whichever port has data
    int available() override { return _a.available() || _b.available(); }
    int read() override {
        if (_a.available()) return _a.read();
        if (_b.available()) return _b.read();
        return -1;
    }
    int peek() override {
        if (_a.available()) return _a.peek();
        if (_b.available()) return _b.peek();
        return -1;
    }

    // Output: write to both
    size_t write(uint8_t b) override { _a.write(b); _b.write(b); return 1; }
    void flush() override { _a.flush(); _b.flush(); }
};

In main.cpp, conditionally replace Serial with DualConsole:

#if defined(SERIAL_TX) && defined(SERIAL_RX)
    Serial1.begin(115200, SERIAL_8N1, SERIAL_RX, SERIAL_TX);
    static DualConsole dual_console(Serial, Serial1);
    // pass dual_console to CLI instead of Serial
#endif
The #if guard ensures the change only compiles on boards that define secondary UART pins.

Use Case
With a small ESP32 running MicroPython acting as a TCP-to-UART bridge:

 Remote operator → TCP socket → ESP32 bridge → UART (Serial1) → XIAO S3 CLI

This enables fully remote CLI access with no hardware changes to the repeater itself -
just adding a low-cost WiFi module wired to the secondary UART pins.

On XIAO ESP32S3, D6/D7 are available as SERIAL_TX/SERIAL_RX in the PlatformIO
environment and are conveniently accessible on the board's edge pads.

Benefits
No changes to CommonCLI or any shared firmware code
Fully backward-compatible - boards without a secondary UART compile and behave identically
Enables remote diagnostics, clock sync, and config changes for deployed repeaters
Works with any bridge device (WiFi, BLE, RS-485, etc.)
Notes
The DualConsole class reads from whichever port has data first. If both send data
simultaneously, input from the slower port may be lost - acceptable for interactive CLI use.
The bridge device will receive an echo of its own transmitted characters (TCP echo behavior);
this should be handled by the client software.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions