Add native narrow-band CW capture, ACK-first remote sessions, retained LoRa-readable pages, checked restoration and transport-safe progress. Add stock-compatible negotiated heartbeat/autosleep and recovery. Include host regressions and identity-bound HIL runners; keep unverified faster tuning HIL-only and document outstanding hardware validation.
138 lines
5.9 KiB
Python
138 lines
5.9 KiB
Python
#!/usr/bin/env python3
|
|
"""Execute the production ESP32 UART begin/end and bridge state checks."""
|
|
from pathlib import Path
|
|
import shutil
|
|
import subprocess
|
|
import tempfile
|
|
import unittest
|
|
|
|
from test_replay_reset_integration import extract_braced
|
|
|
|
ROOT = Path(__file__).resolve().parents[1]
|
|
|
|
FIXTURE = r'''
|
|
#include <cassert>
|
|
#include <cstdint>
|
|
#define ESP32 1
|
|
#define BRIDGE_DEBUG_PRINTLN(...) ((void)0)
|
|
struct NodePrefs { uint32_t bridge_baud = 57600; };
|
|
struct Stream { virtual ~Stream() = default; };
|
|
struct HardwareSerial : Stream {
|
|
bool pins_ok = true, begin_ok = true, installed = false;
|
|
unsigned pin_calls = 0, begin_calls = 0, end_calls = 0;
|
|
int16_t rx = -1, tx = -1;
|
|
uint32_t baud = 0;
|
|
bool setPins(int16_t selected_rx, int16_t selected_tx) {
|
|
++pin_calls; rx = selected_rx; tx = selected_tx; return pins_ok;
|
|
}
|
|
void begin(uint32_t selected_baud) { ++begin_calls; baud = selected_baud; installed = begin_ok; }
|
|
explicit operator bool() const { return installed; }
|
|
void end() { ++end_calls; installed = false; }
|
|
};
|
|
struct BridgeBase {
|
|
bool _initialized = false;
|
|
@RUNNING@
|
|
};
|
|
struct RS232Bridge : BridgeBase {
|
|
NodePrefs* _prefs;
|
|
Stream* _serial;
|
|
int16_t _rx_pin, _tx_pin;
|
|
uint16_t _rx_buffer_pos = 37;
|
|
bool _rx_packet_ready = true;
|
|
uint16_t _tx_size = 19, _tx_pos = 3;
|
|
bool _tx_wire_pending = true, _tx_is_ping = true;
|
|
uint32_t _tx_ping_challenge = 4, _outstanding_challenge = 5;
|
|
uint64_t _pong_target = 6;
|
|
uint32_t _pong_challenge = 7;
|
|
bool _ping_due = false, _partial_suspend_grace = true;
|
|
RS232Bridge(NodePrefs& prefs, HardwareSerial& serial)
|
|
: _prefs(&prefs), _serial(&serial), _rx_pin(5), _tx_pin(6) {}
|
|
void begin();
|
|
void end();
|
|
void resetTransportState();
|
|
};
|
|
@RESET@
|
|
@BEGIN@
|
|
@END@
|
|
int main() {
|
|
NodePrefs prefs;
|
|
HardwareSerial serial;
|
|
RS232Bridge bridge(prefs, serial);
|
|
auto dirty_parser = [&]() {
|
|
bridge._rx_buffer_pos = 37; bridge._rx_packet_ready = true;
|
|
bridge._tx_size = 19; bridge._tx_pos = 3;
|
|
bridge._tx_wire_pending = bridge._tx_is_ping = true;
|
|
bridge._tx_ping_challenge = 4; bridge._outstanding_challenge = 5;
|
|
bridge._pong_target = 6; bridge._pong_challenge = 7;
|
|
bridge._ping_due = false; bridge._partial_suspend_grace = true;
|
|
};
|
|
auto assert_parser_reset = [&]() {
|
|
assert(bridge._rx_buffer_pos == 0 && !bridge._rx_packet_ready);
|
|
assert(bridge._tx_size == 0 && bridge._tx_pos == 0);
|
|
assert(!bridge._tx_wire_pending && !bridge._tx_is_ping);
|
|
assert(bridge._tx_ping_challenge == 0 && bridge._outstanding_challenge == 0);
|
|
assert(bridge._pong_target == 0 && bridge._pong_challenge == 0);
|
|
assert(bridge._ping_due && !bridge._partial_suspend_grace);
|
|
};
|
|
assert(!bridge.isRunning());
|
|
serial.pins_ok = false;
|
|
bridge.begin();
|
|
assert(!bridge.isRunning() && serial.pin_calls == 1 && serial.begin_calls == 0);
|
|
assert_parser_reset();
|
|
serial.pins_ok = true; serial.begin_ok = false;
|
|
dirty_parser();
|
|
bridge.begin();
|
|
assert(!bridge.isRunning() && !serial.installed && serial.begin_calls == 1);
|
|
assert_parser_reset();
|
|
serial.begin_ok = true;
|
|
dirty_parser();
|
|
bridge.begin();
|
|
assert(bridge.isRunning() && serial.installed && serial.begin_calls == 2);
|
|
assert_parser_reset();
|
|
assert(serial.rx == 5 && serial.tx == 6 && serial.baud == 57600);
|
|
dirty_parser();
|
|
bridge.end();
|
|
assert(!bridge.isRunning() && !serial.installed && serial.end_calls == 1);
|
|
assert_parser_reset();
|
|
// A failed restart must clear its former state, and a later retry can recover.
|
|
dirty_parser(); bridge.begin(); assert(bridge.isRunning()); assert_parser_reset();
|
|
serial.pins_ok = false;
|
|
dirty_parser(); bridge.begin(); assert(!bridge.isRunning()); assert_parser_reset();
|
|
dirty_parser(); bridge.end(); assert_parser_reset();
|
|
serial.pins_ok = true; prefs.bridge_baud = 19200;
|
|
dirty_parser(); bridge.begin(); assert(bridge.isRunning() && serial.baud == 19200); assert_parser_reset();
|
|
for (unsigned cycle = 0; cycle < 100; ++cycle) {
|
|
dirty_parser(); bridge.end(); assert(!bridge.isRunning()); assert_parser_reset();
|
|
dirty_parser(); bridge.begin(); assert(bridge.isRunning()); assert_parser_reset();
|
|
}
|
|
dirty_parser(); bridge.end(); assert(!bridge.isRunning()); assert_parser_reset();
|
|
}
|
|
'''
|
|
|
|
|
|
class RS232DriverLifecycleTests(unittest.TestCase):
|
|
def test_failed_pins_or_driver_start_never_report_running_and_retry_recovers(self):
|
|
compiler = shutil.which("g++") or shutil.which("clang++")
|
|
self.assertIsNotNone(compiler, "a host C++ compiler is required")
|
|
driver = (ROOT / "src/helpers/bridges/RS232Bridge.cpp").read_text(encoding="ascii")
|
|
base = (ROOT / "src/helpers/bridges/BridgeBase.cpp").read_text(encoding="ascii")
|
|
code = FIXTURE.replace("@RUNNING@", extract_braced(base, "bool BridgeBase::isRunning() const")
|
|
.replace("BridgeBase::", "").replace(" override", ""))
|
|
code = code.replace("@BEGIN@", extract_braced(driver, "void RS232Bridge::begin()"))
|
|
code = code.replace("@END@", extract_braced(driver, "void RS232Bridge::end()"))
|
|
code = code.replace("@RESET@", extract_braced(driver, "void RS232Bridge::resetTransportState()"))
|
|
with tempfile.TemporaryDirectory(prefix="rs232-driver-lifecycle-") as directory:
|
|
work = Path(directory)
|
|
source, binary = work / "main.cpp", work / "test"
|
|
source.write_text(code, encoding="ascii")
|
|
built = subprocess.run([compiler, "-std=c++17", "-Wall", "-Wextra", "-Werror",
|
|
"-fsanitize=address,undefined", "-fno-sanitize-recover=all", "-fno-pie", "-no-pie",
|
|
str(source), "-o", str(binary)], capture_output=True, text=True, timeout=60)
|
|
self.assertEqual(built.returncode, 0, built.stdout + built.stderr)
|
|
checked = subprocess.run([str(binary)], capture_output=True, text=True, timeout=10)
|
|
self.assertEqual(checked.returncode, 0, checked.stdout + checked.stderr)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|