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.
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 synchronizethe clock remotely.
Proposed Solution
Add a
DualConsolewrapper class that forwards CLI I/O to both the primary USB serial portand 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 aDualConsoleinsteadof
&Serialis a non-breaking change that requires no modification toCommonCLIor anyother core code.
Implementation sketch
In main.cpp, conditionally replace Serial with DualConsole:
Use Case
With a small ESP32 running MicroPython acting as a TCP-to-UART bridge:
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.