diff --git a/examples/simple_repeater/MyMesh.h b/examples/simple_repeater/MyMesh.h index f169e38ee..8ef873ab3 100644 --- a/examples/simple_repeater/MyMesh.h +++ b/examples/simple_repeater/MyMesh.h @@ -672,9 +672,6 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks #endif #if defined(WITH_ESPNOW_BRIDGE) -#if !defined(WITH_MQTT_BRIDGE) && !defined(WITH_RS232_BRIDGE) - bool isEspNowBridgeRunning() override { return bridge.isRunning(); } -#endif ESPNowBridge& diagnosticEspNowBridge() { #if defined(WITH_MQTT_BRIDGE) || defined(WITH_RS232_BRIDGE) return espnow_bridge; @@ -1542,12 +1539,21 @@ public: #endif } +#if defined(WITH_ESPNOW_BRIDGE) + // The wireless backend queries this public transport API in standalone + // Full repeaters as well as MQTT/UART combinations. UDP can be running + // independently, so the generic isBridgeRunning() result is insufficient. + bool isEspNowBridgeRunning() override { +#if defined(WITH_MQTT_BRIDGE) || defined(WITH_RS232_BRIDGE) + return espnow_bridge.isRunning(); +#else + return bridge.isRunning(); +#endif + } +#endif + #if defined(WITH_ESPNOW_BRIDGE) \ && (defined(WITH_MQTT_BRIDGE) || defined(WITH_RS232_BRIDGE)) - bool isEspNowBridgeRunning() override { - return espnow_bridge.isRunning(); - } - #ifdef WITH_MQTT_BRIDGE bool setMqttBridgeState(bool enable) override { if (!enable) { diff --git a/test/test_udp_bridge_integration.py b/test/test_udp_bridge_integration.py index c5083eef5..e4cbc0062 100644 --- a/test/test_udp_bridge_integration.py +++ b/test/test_udp_bridge_integration.py @@ -2,6 +2,7 @@ """Execute production repeater UDP lifecycle with shared station owners.""" from pathlib import Path import os +import re import subprocess import tempfile import unittest @@ -173,6 +174,65 @@ int main(){ ''' class UDPBridgeIntegrationTest(unittest.TestCase): + def test_wireless_backend_can_query_public_espnow_state_in_every_udp_owner_mix(self): + header = (ROOT / 'examples/simple_repeater/MyMesh.h').read_text(encoding='ascii') + signature = 'bool isEspNowBridgeRunning() override' + offset = header.index(signature) + # Preserve the actual member access from the production class. A + # private override compiles inside a role fixture but fails when the + # external wireless backend calls it, as the Indicator build exposed. + labels = re.findall(r'^\s*(public|protected|private):', header[:offset], re.M) + access = labels[-1] if labels else 'private' + method = body(header, signature) + backend = body((ROOT / 'examples/InfrastructureWireless.h').read_text(encoding='ascii'), + 'uint8_t enabled() const override') + fixture = r''' +#include +#include +#include +struct Base { virtual bool isEspNowBridgeRunning() { return false; } }; +struct Bridge { bool running; bool isRunning() const { return running; } }; +class MyMesh : public Base { + Bridge bridge{false}, espnow_bridge{true}; +public: + bool isUDPBridgeNetworkEnabled() const { return true; } + bool isMqttBridgeRunning() const { return false; } +@ACCESS@: +@METHOD@ +public: + void flip() { bridge.running = true; espnow_bridge.running = false; } +} the_mesh; +struct BackendBase { virtual uint8_t enabled() const = 0; }; +struct Backend : BackendBase { +@BACKEND@ +}; +int main() { + Backend backend; + assert(backend.enabled() & mesh::wireless::WiFi); +#if defined(WITH_MQTT_BRIDGE) || defined(WITH_RS232_BRIDGE) + assert(backend.enabled() & mesh::wireless::EspNow); + the_mesh.flip(); + assert(!(backend.enabled() & mesh::wireless::EspNow)); +#else + assert(!(backend.enabled() & mesh::wireless::EspNow)); + the_mesh.flip(); + assert(backend.enabled() & mesh::wireless::EspNow); +#endif +} +'''.replace('@ACCESS@', access).replace('@METHOD@', method).replace('@BACKEND@', backend) + for flags in ((), ('WITH_MQTT_BRIDGE',), ('WITH_RS232_BRIDGE',), + ('WITH_MQTT_BRIDGE', 'WITH_RS232_BRIDGE')): + with self.subTest(flags=flags), tempfile.TemporaryDirectory(prefix='udp-public-api-') as tmp: + path = Path(tmp) + (path / 'test.cpp').write_text(fixture, encoding='ascii') + binary = path / 'test' + subprocess.run([os.environ.get('CXX', 'g++'), '-std=c++11', '-Wall', '-Wextra', '-Werror', + '-DWITH_UDP_BRIDGE=1', '-DWITH_ESPNOW_BRIDGE=1', + '-fsanitize=address,undefined', '-fno-omit-frame-pointer', '-fno-pie', '-no-pie', + '-I', str(ROOT / 'src'), *['-D' + flag + '=1' for flag in flags], + str(path / 'test.cpp'), '-o', str(binary)], check=True) + subprocess.run([str(binary)], check=True) + def test_wireless_master_restores_udp_station_without_starting_setup_ap(self): backend=body((ROOT/'examples/InfrastructureWireless.h').read_text(), 'class InfrastructureWirelessBackend')+';'