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.
469 lines
21 KiB
C++
469 lines
21 KiB
C++
// Real repeater methods and CLI dispatch are inserted by the Python harness.
|
|
#include <cassert>
|
|
#include <cstdio>
|
|
#include <cstring>
|
|
#include <cstdint>
|
|
#include <initializer_list>
|
|
#include <new>
|
|
#include <helpers/CLICommandUtils.h>
|
|
#include <helpers/bridges/ESPNowBridgeFormat.h>
|
|
#include <helpers/bridges/RS232PowerSettings.h>
|
|
struct FakeRng { void random(uint8_t* out, size_t size) { memset(out, 0x42, size); } };
|
|
#define BRIDGE_MAX_BAUD 115200
|
|
#define PUB_KEY_SIZE 32
|
|
#define MESH_DEBUG_PRINTLN(...) ((void)0)
|
|
|
|
namespace mesh {
|
|
struct Packet { bool allowed = true; };
|
|
struct LocalIdentity { uint8_t pub_key[32] = {}; };
|
|
struct Utils { static void toHex(char* out, const uint8_t*, unsigned) { strcpy(out, "mock-node"); } };
|
|
}
|
|
struct StrHelper {
|
|
static void strncpy(char* out, const char* in, size_t size) {
|
|
assert(size); std::strncpy(out, in, size - 1); out[size - 1] = 0;
|
|
}
|
|
};
|
|
struct NodePrefs {
|
|
uint8_t bridge_enabled = 0, espnow_bridge_enabled = 0, rs232_bridge_enabled = 0;
|
|
uint16_t bridge_delay = 0;
|
|
uint8_t powersaving_enabled = 1, rs232_autosleep = 1;
|
|
uint8_t rs232_heartbeat = 1;
|
|
uint16_t rs232_idle_seconds = 300;
|
|
uint8_t bridge_pkt_src = 0, bridge_uart = 0, bridge_channel = 0, bridge_format = 0;
|
|
uint32_t bridge_baud = 0;
|
|
float freq = 915, bw = 125;
|
|
uint8_t sf = 7, cr = 5, disable_fwd = 0;
|
|
char bridge_secret[32] = {}, node_name[32] = "triple bridge";
|
|
};
|
|
struct FakeUart {
|
|
unsigned begins = 0, ends = 0;
|
|
int rx = -1, tx = -1;
|
|
uint32_t baud = 0;
|
|
bool running = false;
|
|
} Serial2;
|
|
struct FakeSensors {
|
|
unsigned yield_calls = 0;
|
|
bool gpsUsesSerialUart(uint8_t uart) const { return uart == 1; }
|
|
bool gpsSerialTransportMayConflict(uint8_t uart) const { return uart == 1; }
|
|
bool gpsSerialTransportCanYield(uint8_t) const { return true; }
|
|
bool setGpsSerialTransportBlocked(uint8_t, bool) { ++yield_calls; return true; }
|
|
};
|
|
class AbstractBridge {
|
|
bool (*filter)(void*, const mesh::Packet*) = nullptr;
|
|
void* filter_context = nullptr;
|
|
protected:
|
|
bool allow(mesh::Packet* packet) { return !filter || filter(filter_context, packet); }
|
|
public:
|
|
unsigned filters = 0;
|
|
virtual ~AbstractBridge() = default;
|
|
virtual void begin() = 0;
|
|
virtual void end() = 0;
|
|
virtual bool isRunning() const = 0;
|
|
virtual void loop() = 0;
|
|
virtual void sendPacket(mesh::Packet*) = 0;
|
|
void setPacketFilter(bool (*check)(void*, const mesh::Packet*), void* context) {
|
|
filter = check; filter_context = context; ++filters;
|
|
}
|
|
};
|
|
class RS232Bridge : public AbstractBridge {
|
|
NodePrefs* prefs;
|
|
FakeUart& uart;
|
|
int rx, tx;
|
|
bool running = false;
|
|
public:
|
|
using PowerAction = mesh::bridge::RS232PowerPolicy::Action;
|
|
mesh::bridge::RS232PowerPolicy power;
|
|
void setHeartbeatSession(uint64_t) {}
|
|
void setHeartbeatEnabled(bool) {}
|
|
PowerAction servicePowerPolicy(bool allowed, uint32_t idle, uint32_t now, bool force = false) {
|
|
return power.service(allowed && force, idle, now, running, false);
|
|
}
|
|
void completePowerAction(PowerAction action, bool success, uint32_t now) { power.complete(action, success, now); }
|
|
uint32_t powerWakeDelayMs(uint32_t now) const { return power.wakeDelay(now); }
|
|
void formatHeartbeatStatus(char* out, size_t size, uint32_t) const { snprintf(out, size, "state=active peer=unknown"); }
|
|
inline static unsigned live = 0, fail_starts = 0, fail_allocations = 0;
|
|
static void* operator new(size_t size, const std::nothrow_t&) noexcept {
|
|
if (fail_allocations) { --fail_allocations; return nullptr; }
|
|
return ::operator new(size, std::nothrow);
|
|
}
|
|
static void operator delete(void* pointer) noexcept { ::operator delete(pointer); }
|
|
unsigned loops = 0, sent = 0;
|
|
RS232Bridge(NodePrefs* p, FakeUart& u, int r, int t, void*, void*)
|
|
: prefs(p), uart(u), rx(r), tx(t) { ++live; }
|
|
~RS232Bridge() override { --live; }
|
|
void begin() override {
|
|
assert(filters); ++uart.begins;
|
|
if (fail_starts) { --fail_starts; return; }
|
|
uart.rx = rx; uart.tx = tx; uart.baud = prefs->bridge_baud;
|
|
running = uart.running = true;
|
|
}
|
|
void end() override { ++uart.ends; running = uart.running = false; }
|
|
bool isRunning() const override { return running; }
|
|
void loop() override { ++loops; }
|
|
void sendPacket(mesh::Packet* p) override { if (allow(p)) ++sent; }
|
|
};
|
|
class ESPNowBridge : public AbstractBridge {
|
|
bool running = false;
|
|
public:
|
|
unsigned begins = 0, ends = 0, fail_starts = 0, loops = 0, sent = 0;
|
|
void begin() override {
|
|
assert(filters); ++begins;
|
|
if (fail_starts) { --fail_starts; return; }
|
|
running = true;
|
|
}
|
|
void end() override { ++ends; running = false; }
|
|
bool isRunning() const override { return running; }
|
|
void loop() override { ++loops; }
|
|
void sendPacket(mesh::Packet* p) override { if (allow(p)) ++sent; }
|
|
};
|
|
struct MQTTNodeInfo {
|
|
const char* node_name;
|
|
float* freq;
|
|
float* bw;
|
|
uint8_t* sf;
|
|
uint8_t* cr;
|
|
uint8_t* repeat_flag;
|
|
bool repeat_when_nonzero;
|
|
};
|
|
class MQTTBridge : public AbstractBridge {
|
|
bool running = false;
|
|
bool stopping = false;
|
|
bool network_suspended = false;
|
|
public:
|
|
inline static unsigned live = 0, fail_starts = 0;
|
|
unsigned begins = 0, ends = 0, loops = 0, sent = 0, received = 0;
|
|
MQTTBridge(MQTTNodeInfo, void*, void*, mesh::LocalIdentity*) { ++live; }
|
|
~MQTTBridge() override { --live; }
|
|
void setDeviceID(const char*) {}
|
|
void setFirmwareVersion(const char*) {}
|
|
void setBoardModel(const char*) {}
|
|
void setBuildDate(const char*) {}
|
|
void setStatsSources(void*, void*, void*, void*) {}
|
|
void begin() override {
|
|
assert(filters); ++begins;
|
|
if (fail_starts) { --fail_starts; return; }
|
|
running = true;
|
|
network_suspended = false;
|
|
}
|
|
void end() override { ++ends; running = stopping = network_suspended = false; }
|
|
void requestStop() { stopping = running; }
|
|
bool isStopping() const { return stopping; }
|
|
bool isRunning() const override { return running; }
|
|
bool isNetworkSuspended() const { return network_suspended; }
|
|
void setNetworkSuspended(bool paused) {
|
|
assert(running && !stopping);
|
|
network_suspended = paused;
|
|
}
|
|
void loop() override {
|
|
++loops;
|
|
if (stopping) { ++ends; running = stopping = network_suspended = false; }
|
|
}
|
|
void sendPacket(mesh::Packet* p) override { if (allow(p)) ++sent; }
|
|
void onPacketReceived(mesh::Packet* p) { if (allow(p)) ++received; }
|
|
};
|
|
static uint32_t fake_millis = 100;
|
|
static uint32_t millis() { return fake_millis; }
|
|
static bool millisHasNowPassed(uint32_t deadline) {
|
|
return static_cast<int32_t>(fake_millis - deadline) >= 0;
|
|
}
|
|
struct FakeWiFi { bool connected = true; bool isConnected() const { return connected; } } WiFi;
|
|
struct FakeBoard {
|
|
bool ota = false, allow_start = true, allow_stop = true;
|
|
bool isOTAUpdateRunning() const { return ota; }
|
|
const char* getManufacturerName() const { return "synthetic ESP32"; }
|
|
bool startOTAUpdate(const char*, char* reply, bool) {
|
|
ota = allow_start; strcpy(reply, allow_start ? "Started: http://192.168.4.1/update" : "ERR: OTA failed");
|
|
return allow_start;
|
|
}
|
|
bool stopOTAUpdate(char* reply) {
|
|
if (allow_stop) ota = false;
|
|
strcpy(reply, allow_stop ? "OK - OTA stopped" : "ERR: upload active"); return allow_stop;
|
|
}
|
|
};
|
|
struct FakeCLI {
|
|
FakeBoard board;
|
|
FakeBoard* getBoard() { return &board; }
|
|
void* getObserverPrefs() { return nullptr; }
|
|
};
|
|
struct FakeAlerter { void setBridge(MQTTBridge*) {} };
|
|
struct Callbacks {
|
|
virtual ~Callbacks() = default;
|
|
virtual bool isBridgeRunning() const = 0;
|
|
virtual bool setBridgeState(bool) = 0;
|
|
virtual bool restartBridge() = 0;
|
|
virtual bool isRs232BridgeRunning() const = 0;
|
|
virtual bool setRs232BridgeState(bool) = 0;
|
|
virtual bool restartRs232Bridge() = 0;
|
|
virtual void formatRs232Status(char*) = 0;
|
|
virtual bool isEspNowBridgeRunning() = 0;
|
|
virtual bool setEspNowBridgeState(bool) = 0;
|
|
virtual bool restartEspNowBridge() = 0;
|
|
virtual bool isMqttBridgeRunning() = 0;
|
|
virtual bool setMqttBridgeState(bool) = 0;
|
|
virtual bool requestMqttBridgeStop() = 0;
|
|
virtual bool isMqttBridgeStopping() = 0;
|
|
virtual bool restartMqttBridge() = 0;
|
|
};
|
|
class MyMesh : public Callbacks {
|
|
public:
|
|
NodePrefs _prefs;
|
|
RS232Bridge* bridge = nullptr;
|
|
MQTTBridge* mqtt_bridge = nullptr;
|
|
ESPNowBridge espnow_bridge;
|
|
uint8_t active_rs232_bridge_uart = 0;
|
|
uint32_t shared_espnow_retry_at = 0;
|
|
unsigned filter_checks = 0;
|
|
FakeSensors sensors;
|
|
FakeCLI _cli;
|
|
FakeAlerter _alerter;
|
|
mesh::LocalIdentity self_id;
|
|
void* _mgr = nullptr; void* _radio = nullptr; void* _ms = nullptr;
|
|
void* getRTCClock() { return nullptr; }
|
|
FakeRng rng;
|
|
FakeRng* getRNG() { return &rng; }
|
|
mesh::LocalIdentity getSelfId() { return self_id; }
|
|
const char* getFirmwareVer() { return "test"; }
|
|
const char* getBuildDate() { return "test"; }
|
|
bool allowTransportPacket(const mesh::Packet* p, unsigned mode) {
|
|
assert(mode == 2); ++filter_checks; return p->allowed;
|
|
}
|
|
@FILTER@
|
|
void defaults() {
|
|
@DEFAULTS@
|
|
}
|
|
void boot() {
|
|
@BOOT@
|
|
}
|
|
void routeRx(mesh::Packet* pkt) {
|
|
@ROUTE_RX@
|
|
}
|
|
void routeTx(mesh::Packet* pkt) {
|
|
@ROUTE_TX@
|
|
}
|
|
void serviceBridges() {
|
|
@SERVICE@
|
|
}
|
|
bool bridgesPreventSleep() const {
|
|
@PENDING@
|
|
return false;
|
|
}
|
|
~MyMesh() override {
|
|
assert(setRs232BridgeState(false));
|
|
assert(setBridgeState(false));
|
|
delete mqtt_bridge;
|
|
}
|
|
@LIFECYCLE@
|
|
};
|
|
@PARSERS@
|
|
class CommonCLI {
|
|
public:
|
|
NodePrefs* _prefs;
|
|
MyMesh* _callbacks;
|
|
FakeSensors* _sensors;
|
|
FakeBoard* _board;
|
|
@OTA_STATE@
|
|
NodePrefs stored;
|
|
unsigned saves = 0;
|
|
bool fail_save = false;
|
|
explicit CommonCLI(MyMesh& mesh) : _prefs(&mesh._prefs), _callbacks(&mesh),
|
|
_sensors(&mesh.sensors), _board(mesh._cli.getBoard()) {}
|
|
void savePrefs() { ++saves; stored = *_prefs; }
|
|
bool trySavePrefs() { if (fail_save) return false; savePrefs(); return true; }
|
|
void set(const char* input, char* reply) {
|
|
char config[256] = {}; assert(strlen(input) < sizeof(config)); strcpy(config, input);
|
|
@SET_BRANCHES@
|
|
}
|
|
bool get(const char* config, char* reply) {
|
|
@GET_BRANCHES@
|
|
return false;
|
|
}
|
|
void run(const char* command, char* reply) {
|
|
if (false) {
|
|
@OTA@
|
|
}
|
|
}
|
|
};
|
|
static void expect_get(CommonCLI& cli, const char* key, const char* expected) {
|
|
char reply[256] = {}; cli.get(key, reply); assert(strcmp(reply, expected) == 0);
|
|
}
|
|
int main() {
|
|
{
|
|
MyMesh fresh; fresh.defaults(); CommonCLI cli(fresh);
|
|
assert(fresh._prefs.bridge_enabled && fresh._prefs.espnow_bridge_enabled);
|
|
assert(!fresh._prefs.rs232_bridge_enabled);
|
|
const unsigned before = Serial2.begins;
|
|
fresh.boot();
|
|
assert(fresh.isMqttBridgeRunning() && fresh.isEspNowBridgeRunning());
|
|
assert(!fresh.isRs232BridgeRunning() && !fresh.bridge && Serial2.begins == before);
|
|
expect_get(cli, "bridge.type", "> mqtt+rs232+espnow");
|
|
expect_get(cli, "rs232.enabled", "> off");
|
|
expect_get(cli, "rs232.running", "> off");
|
|
fresh.mqtt_bridge->setNetworkSuspended(true);
|
|
assert(fresh.isMqttBridgeRunning() && fresh._prefs.bridge_enabled);
|
|
assert(fresh.bridgesPreventSleep()); // ESP-NOW retains its radio owner.
|
|
assert(fresh.setEspNowBridgeState(false));
|
|
assert(!fresh.bridgesPreventSleep()); // Paused MQTT is still configured.
|
|
assert(fresh.setRs232BridgeState(true));
|
|
assert(fresh.bridgesPreventSleep()); // An active UART remains independent.
|
|
assert(fresh.setRs232BridgeState(false));
|
|
assert(!fresh.bridgesPreventSleep());
|
|
fresh.mqtt_bridge->setNetworkSuspended(false);
|
|
assert(fresh.bridgesPreventSleep()); // The recovery probe owns the radio.
|
|
char pause_reply[256] = {};
|
|
cli.set("espnow.enabled off", pause_reply);
|
|
assert(strcmp(pause_reply, "OK") == 0);
|
|
fresh.mqtt_bridge->setNetworkSuspended(true);
|
|
assert(fresh.requestMqttBridgeStop());
|
|
assert(fresh.isMqttBridgeStopping() && fresh.bridgesPreventSleep());
|
|
fresh.serviceBridges();
|
|
assert(!fresh.isMqttBridgeRunning() && !fresh.bridgesPreventSleep());
|
|
assert(fresh.setBridgeState(true));
|
|
assert(fresh.isMqttBridgeRunning() && fresh.bridgesPreventSleep());
|
|
cli.set("espnow.enabled on", pause_reply);
|
|
assert(strcmp(pause_reply, "OK") == 0 && fresh.isEspNowBridgeRunning());
|
|
}
|
|
MyMesh mesh; mesh.defaults(); mesh.boot(); CommonCLI cli(mesh);
|
|
char reply[256] = {};
|
|
cli.set("rs232.enabled on", reply);
|
|
assert(strcmp(reply, "OK") == 0 && mesh.isRs232BridgeRunning());
|
|
assert(Serial2.rx == 5 && Serial2.tx == 6 && Serial2.baud == 115200);
|
|
assert(mesh.sensors.yield_calls == 0 && mesh.bridgesPreventSleep());
|
|
expect_get(cli, "rs232.enabled", "> on"); expect_get(cli, "rs232.running", "> on");
|
|
mesh::Packet packet;
|
|
mesh.routeRx(&packet); mesh.routeTx(&packet); mesh.serviceBridges();
|
|
assert(mesh.bridge->sent == 1 && mesh.bridge->loops == 1);
|
|
assert(mesh.espnow_bridge.sent == 1 && mesh.espnow_bridge.loops == 1);
|
|
assert(mesh.mqtt_bridge->received == 1 && mesh.mqtt_bridge->sent == 1);
|
|
assert(mesh.mqtt_bridge->loops == 1); // Core 1 services stop acknowledgement only.
|
|
assert(mesh.filter_checks == 4);
|
|
packet.allowed = false;
|
|
mesh.routeRx(&packet); mesh.routeTx(&packet);
|
|
assert(mesh.filter_checks == 8 && mesh.bridge->sent == 1 && mesh.espnow_bridge.sent == 1);
|
|
assert(mesh.mqtt_bridge->received == 1 && mesh.mqtt_bridge->sent == 1);
|
|
packet.allowed = true;
|
|
const unsigned wifi_starts = mesh.mqtt_bridge->begins, wireless_starts = mesh.espnow_bridge.begins;
|
|
cli.set("bridge.baud 57600", reply);
|
|
assert(strcmp(reply, "OK") == 0 && Serial2.baud == 57600);
|
|
cli.set("bridge.uart 2", reply);
|
|
assert(strcmp(reply, "OK") == 0 && mesh.isRs232BridgeRunning());
|
|
assert(mesh.mqtt_bridge->begins == wifi_starts && mesh.espnow_bridge.begins == wireless_starts);
|
|
const unsigned saves = cli.saves;
|
|
for (const char* invalid : {"rs232.enabled onjunk", "bridge.uart 1", "bridge.uart 2junk",
|
|
"bridge.baud 9599", "bridge.baud 115201", "bridge.baud 57600junk"}) {
|
|
cli.set(invalid, reply); assert(strncmp(reply, "Error", 5) == 0 && cli.saves == saves);
|
|
assert(mesh.isRs232BridgeRunning() && mesh.isMqttBridgeRunning() && mesh.isEspNowBridgeRunning());
|
|
}
|
|
RS232Bridge::fail_starts = 1;
|
|
cli.set("bridge.baud 19200", reply);
|
|
assert(strstr(reply, "baud unchanged") && cli.saves == saves);
|
|
assert(mesh._prefs.bridge_baud == 57600 && Serial2.baud == 57600 && mesh.isRs232BridgeRunning());
|
|
assert(mesh.mqtt_bridge->begins == wifi_starts && mesh.espnow_bridge.begins == wireless_starts);
|
|
cli.set("rs232.enabled off", reply); assert(strcmp(reply, "OK") == 0 && !mesh.bridge);
|
|
const unsigned disabled_saves = cli.saves;
|
|
RS232Bridge::fail_starts = 1;
|
|
cli.set("rs232.enabled on", reply);
|
|
assert(strstr(reply, "setting unchanged") && cli.saves == disabled_saves && !mesh.bridge);
|
|
assert(!mesh._prefs.rs232_bridge_enabled && mesh.isMqttBridgeRunning() && mesh.isEspNowBridgeRunning());
|
|
RS232Bridge::fail_allocations = 1;
|
|
cli.set("rs232.enabled on", reply);
|
|
assert(strstr(reply, "setting unchanged") && !mesh.bridge && !mesh._prefs.rs232_bridge_enabled);
|
|
assert(cli.saves == disabled_saves && mesh.isMqttBridgeRunning() && mesh.isEspNowBridgeRunning());
|
|
cli.fail_save = true;
|
|
cli.set("rs232.enabled on", reply);
|
|
assert(strstr(reply, "setting unchanged") && !mesh.bridge && !mesh._prefs.rs232_bridge_enabled);
|
|
assert(cli.saves == disabled_saves && mesh.isMqttBridgeRunning() && mesh.isEspNowBridgeRunning());
|
|
cli.fail_save = false;
|
|
cli.set("rs232.enabled on", reply); assert(strcmp(reply, "OK") == 0);
|
|
const unsigned saved_after_enable = cli.saves;
|
|
cli.fail_save = true;
|
|
cli.set("rs232.enabled off", reply);
|
|
assert(strstr(reply, "setting unchanged") && mesh.isRs232BridgeRunning() && mesh._prefs.rs232_bridge_enabled);
|
|
cli.set("bridge.baud 19200", reply);
|
|
assert(strstr(reply, "baud unchanged") && mesh._prefs.bridge_baud == 57600 && Serial2.baud == 57600);
|
|
cli.set("bridge.uart 2", reply);
|
|
assert(strstr(reply, "UART unchanged") && mesh._prefs.bridge_uart == 2 && mesh.isRs232BridgeRunning());
|
|
assert(cli.saves == saved_after_enable && mesh.mqtt_bridge->begins == wifi_starts);
|
|
assert(mesh.espnow_bridge.begins == wireless_starts);
|
|
cli.fail_save = false;
|
|
// Saved UART settings recreate a running independent bridge after reboot.
|
|
{
|
|
MyMesh restarted; restarted.defaults(); restarted._prefs = cli.stored; restarted.boot();
|
|
assert(restarted.isRs232BridgeRunning() && restarted.isMqttBridgeRunning());
|
|
assert(restarted._prefs.bridge_baud == 57600 && restarted.isEspNowBridgeRunning());
|
|
}
|
|
// ESP-NOW format/channel changes and its legacy alias never restart the UART.
|
|
const unsigned uart_before_wireless = Serial2.begins;
|
|
cli.set("bridge.channel 6", reply); cli.set("bridge.secret three-transports", reply);
|
|
cli.set("bridge.format raw", reply); assert(strncmp(reply, "OK", 2) == 0);
|
|
assert(Serial2.begins == uart_before_wireless && mesh.mqtt_bridge->begins == wifi_starts);
|
|
cli.set("bridge.enabled off", reply);
|
|
assert(strcmp(reply, "OK") == 0 && !mesh.isEspNowBridgeRunning());
|
|
assert(mesh.isMqttBridgeRunning() && mesh.isRs232BridgeRunning());
|
|
// A failed intent save must not signal an uncancellable worker stop.
|
|
cli.fail_save = true;
|
|
cli.set("mqtt.enabled off", reply);
|
|
assert(strstr(reply, "not saved; unchanged") && mesh._prefs.bridge_enabled);
|
|
assert(mesh.isMqttBridgeRunning() && !mesh.isMqttBridgeStopping());
|
|
cli.fail_save = false;
|
|
cli.set("mqtt.enabled off", reply);
|
|
assert(strcmp(reply, "OK") == 0 && mesh.isMqttBridgeRunning());
|
|
assert(mesh.isMqttBridgeStopping() && !mesh._prefs.bridge_enabled);
|
|
expect_get(cli, "mqtt.running", "> on");
|
|
expect_get(cli, "mqtt.stopping", "> on");
|
|
const unsigned stopping_saves = cli.saves;
|
|
cli.set("mqtt.enabled on", reply);
|
|
assert(strstr(reply, "MQTT is stopping") && !mesh._prefs.bridge_enabled);
|
|
assert(cli.saves == stopping_saves && !mesh.setMqttBridgeState(true));
|
|
cli.set("mqtt.enabled off", reply); // a repeated request is accepted
|
|
assert(strcmp(reply, "OK") == 0 && mesh.isMqttBridgeStopping());
|
|
assert(mesh.isRs232BridgeRunning() && mesh.bridgesPreventSleep());
|
|
const unsigned uart_tx_before = mesh.bridge->sent;
|
|
mesh.routeRx(&packet); mesh.serviceBridges();
|
|
assert(mesh.bridge->sent == uart_tx_before + 1);
|
|
assert(!mesh.isMqttBridgeRunning() && !mesh.isMqttBridgeStopping());
|
|
expect_get(cli, "mqtt.stopping", "> off");
|
|
cli.fail_save = true;
|
|
cli.set("mqtt.enabled on", reply);
|
|
assert(strstr(reply, "not saved; unchanged") && !mesh._prefs.bridge_enabled);
|
|
assert(!mesh.isMqttBridgeRunning());
|
|
cli.fail_save = false;
|
|
cli.set("espnow.enabled on", reply); assert(strcmp(reply, "OK") == 0);
|
|
cli.set("mqtt.enabled on", reply); assert(strcmp(reply, "OK") == 0);
|
|
mesh.serviceBridges(); assert(mesh.isEspNowBridgeRunning());
|
|
// Browser OTA releases only WiFi users and leaves the enabled UART running.
|
|
const unsigned uart_before_ota = Serial2.begins;
|
|
cli.run("start ota ap", reply);
|
|
assert(mesh._cli.board.ota && !mesh.isMqttBridgeRunning() && !mesh.isEspNowBridgeRunning());
|
|
assert(mesh.isRs232BridgeRunning() && Serial2.begins == uart_before_ota);
|
|
for (unsigned tick = 0; tick < 5; ++tick) { fake_millis += 5001; mesh.serviceBridges(); }
|
|
assert(!mesh.isEspNowBridgeRunning() && mesh.isRs232BridgeRunning());
|
|
mesh._cli.board.allow_stop = false; cli.run("stop ota", reply);
|
|
assert(strcmp(reply, "ERR: upload active") == 0 && !mesh.isMqttBridgeRunning());
|
|
mesh._cli.board.allow_stop = true; cli.run("stop ota", reply); mesh.serviceBridges();
|
|
assert(!mesh._cli.board.ota && mesh.isMqttBridgeRunning() && mesh.isEspNowBridgeRunning());
|
|
assert(mesh.isRs232BridgeRunning() && Serial2.begins == uart_before_ota);
|
|
// Wireless startup failure retries without stopping or reallocating UART.
|
|
cli.set("espnow.enabled off", reply); mesh.espnow_bridge.fail_starts = 2;
|
|
cli.set("espnow.enabled on", reply); mesh.serviceBridges();
|
|
const unsigned retry_starts = mesh.espnow_bridge.begins;
|
|
mesh.serviceBridges(); assert(mesh.espnow_bridge.begins == retry_starts);
|
|
fake_millis += 5001; mesh.serviceBridges(); assert(!mesh.isEspNowBridgeRunning());
|
|
fake_millis += 5001; mesh.serviceBridges(); assert(mesh.isEspNowBridgeRunning());
|
|
assert(mesh.isRs232BridgeRunning() && Serial2.begins == uart_before_ota);
|
|
// Every switch, parameter change and rollback leaves other transports intact.
|
|
for (unsigned cycle = 0; cycle < 100; ++cycle) {
|
|
cli.set("rs232.enabled off", reply);
|
|
assert(!mesh.bridge && mesh.isMqttBridgeRunning() && mesh.isEspNowBridgeRunning());
|
|
cli.set("rs232.enabled on", reply);
|
|
assert(mesh.isRs232BridgeRunning() && RS232Bridge::live == 1);
|
|
}
|
|
cli.set("rs232.enabled off", reply); cli.set("espnow.enabled off", reply); cli.set("mqtt.enabled off", reply);
|
|
assert(mesh.isMqttBridgeStopping() && mesh.bridgesPreventSleep());
|
|
mesh.serviceBridges();
|
|
assert(!mesh.bridgesPreventSleep() && RS232Bridge::live == 0);
|
|
cli.run("start ota", reply); cli.run("stop ota", reply); mesh.serviceBridges();
|
|
assert(!mesh.isRs232BridgeRunning() && !mesh.isMqttBridgeRunning() && !mesh.isEspNowBridgeRunning());
|
|
assert(mesh.sensors.yield_calls == 0);
|
|
puts("Three transport runtime checks passed");
|
|
}
|