mirror of
https://github.com/mikecarper/MeshCore.git
synced 2026-10-22 12:02:26 +00:00
Report the upload address and required network, honor AP switches on an existing uploader, and keep delayed WebConfig cleanup from disabling OTA WiFi. Preserve actionable startup errors and account for OTA in wireless state controls. Exercise the production OTA lifecycle and stock website console, including power-saving idle sessions, in host regression tests.
407 lines
22 KiB
Python
407 lines
22 KiB
Python
"""Compile and execute the production wireless command/state controller."""
|
|
from pathlib import Path
|
|
import os
|
|
import subprocess
|
|
import tempfile
|
|
import unittest
|
|
from test_radio_receive_contract import method
|
|
|
|
ROOT = Path(__file__).resolve().parents[1]
|
|
|
|
HARNESS = r'''
|
|
#include <helpers/WirelessControl.h>
|
|
#include <cassert>
|
|
#include <vector>
|
|
using namespace mesh::wireless;
|
|
struct Fake : Backend {
|
|
uint8_t supported=All, active=All, sessions=0, wait=0, fail=0;
|
|
bool busy=false;
|
|
std::vector<int> calls;
|
|
uint8_t available()const override{return supported;}
|
|
uint8_t enabled()const override{return active;}
|
|
uint8_t clients()const override{return sessions;}
|
|
bool replyBusy()const override{return busy;}
|
|
Result set(uint8_t bit,bool on)override {
|
|
calls.push_back(on?bit:-bit);
|
|
if(wait==bit)return Result::Pending;
|
|
if(fail==bit)return Result::Failed;
|
|
if(on)active|=bit;else active&=~bit;
|
|
return Result::Done;
|
|
}
|
|
};
|
|
struct Fixture {
|
|
Fake b; Control c; char reply[160]={}; uint32_t now=100;
|
|
Fixture(){c.begin(b);}
|
|
void run(const char* text,uint8_t requester=Independent){assert(c.handle(text,reply,sizeof(reply),now,requester));}
|
|
void tick(unsigned ms=250){now+=ms;c.service(now);}
|
|
};
|
|
int main(int argc,char** argv) {
|
|
const int test=argc>1?atoi(argv[1]):0;
|
|
if(test==0){
|
|
for(const char* text:{"set wifi","set espnow on force","set wifi off force extra",
|
|
"set 2.4ghz on all force","set 2.4ghz on forced","get wifi junk","set 2.4ghz off all"})
|
|
assert(parse(text).action==Action::Invalid);
|
|
for(const char* text:{"set wifi.ssid x","get wifi.status","set espnow.channel 3","set bluetooth off","get 2.4ghzx"})
|
|
assert(parse(text).action==Action::None);
|
|
assert(parse(nullptr).action==Action::None);
|
|
assert(parse(" set espnow\t off\tforce \t").action==Action::ForceOff);
|
|
assert(parse("set 2.4ghz on all").action==Action::OnAll);
|
|
assert(parse("set 2.4ghz on force").action==Action::OnAll);
|
|
} else if(test==1){
|
|
for(unsigned mask=0;mask<=All;++mask){
|
|
Fixture f;f.b.active=mask;
|
|
f.run("set 2.4ghz on");f.tick();assert(f.b.active==mask); // on before off is idempotent
|
|
f.run("set 2.4ghz off");f.tick();assert(f.b.active==0);
|
|
f.run("set 2.4ghz off");f.tick(); // never overwrite the saved state
|
|
f.run("set wifi on");assert(strstr(f.reply,"Error:"));
|
|
assert(!f.c.allowService(Bluetooth));
|
|
f.run("set 2.4ghz on");f.tick();assert(f.b.active==mask);
|
|
assert(f.c.allowService(Bluetooth));
|
|
f.run("set 2.4ghz on all");f.tick();assert(f.b.active==All);
|
|
f.run("set 2.4ghz off");f.tick();
|
|
f.run("set 2.4ghz on force");f.tick();assert(f.b.active==All);
|
|
}
|
|
} else if(test==2){
|
|
Fixture f;f.run("set wifi off",WiFi);assert(strstr(f.reply,"Error:"));
|
|
f.b.sessions=Bluetooth;f.run("set wifi off",WiFi);assert(f.c.pending());
|
|
f.tick(249);assert(f.b.active==All);f.b.sessions=0;f.tick(1);
|
|
assert(f.b.active==All&&!f.c.pending());f.run("get wifi");assert(strstr(f.reply,"cancelled"));
|
|
f.b.sessions=Bluetooth;f.run("set wifi off",WiFi);f.tick();assert(f.b.active==(Bluetooth|EspNow));
|
|
f.run("set wifi on");f.tick();assert(f.b.active==All);
|
|
f.b.sessions=Bluetooth|WiFi;f.run("set 2.4ghz off",Bluetooth);assert(strstr(f.reply,"Error:"));
|
|
f.run("set espnow off",EspNow);f.tick();assert(f.b.active==(WiFi|Bluetooth));
|
|
} else if(test==3){
|
|
Fixture f;f.b.busy=true;f.run("set 2.4ghz off force",Bluetooth);
|
|
f.tick();assert(f.b.active==All);f.tick(1749);assert(f.b.active==All);
|
|
f.tick(1);assert(f.b.active==0);assert(f.c.blocked(All));
|
|
assert((f.b.calls==std::vector<int>{-WiFi,-EspNow,-Bluetooth}));
|
|
f.b.calls.clear();f.b.busy=false;f.run("set 2.4ghz on");f.tick();
|
|
assert((f.b.calls==std::vector<int>{EspNow,WiFi,Bluetooth}));
|
|
} else if(test==4){
|
|
Fixture f;f.b.wait=WiFi;f.run("set 2.4ghz off");f.tick();
|
|
assert(f.c.pending()&&f.b.calls.size()==1&&f.b.active==All);
|
|
f.run("set espnow off");assert(strstr(f.reply,"pending"));
|
|
f.b.wait=0;f.tick(1);assert(f.b.active==0&&!f.c.pending());
|
|
f.b.fail=EspNow;f.run("set 2.4ghz on");f.tick();
|
|
assert(f.b.active==0&&!f.c.pending());f.run("get 2.4ghz");assert(strstr(f.reply,"failed"));
|
|
f.b.fail=0;f.run("set 2.4ghz on");f.tick();assert(f.b.active==All); // saved state survives failure
|
|
} else if(test==5){
|
|
Fixture f;f.run("set 2.4ghz off");f.tick(249);
|
|
f.run("set 2.4ghz on");f.tick();assert(f.b.active==All&&!f.c.masterOff());
|
|
f.b.sessions=Independent;f.run("set 2.4ghz off",Bluetooth);f.b.sessions=0;f.tick();
|
|
assert(f.b.active==All&&!f.c.masterOff());
|
|
f.now=UINT32_MAX-249;f.run("set 2.4ghz off");f.tick(249);assert(f.b.active==All);
|
|
f.tick(1);assert(f.b.active==0);
|
|
} else if(test==6){
|
|
Fixture f;f.b.supported=Bluetooth;f.b.active=Bluetooth;
|
|
f.run("set wifi on");assert(strstr(f.reply,"unavailable"));
|
|
f.run("get 2.4ghz");assert(strstr(f.reply,"wifi=unavailable")&&strstr(f.reply,"bluetooth=on"));
|
|
f.run("set 2.4ghz off");f.tick();assert(f.b.active==0);
|
|
f.run("set 2.4ghz on all");f.tick();assert(f.b.active==Bluetooth);
|
|
f.b.supported=0;f.run("set 2.4ghz on");assert(strstr(f.reply,"unavailable"));
|
|
} else if(test==7){
|
|
Fixture f;f.b.wait=WiFi;f.run("set 2.4ghz off");f.tick(30250);
|
|
assert(!f.c.pending()&&f.b.active==All&&f.b.calls.size()==1);
|
|
f.run("get 2.4ghz");assert(strstr(f.reply,"failed"));
|
|
f.b.wait=0;f.run("set 2.4ghz off");f.tick();assert(f.b.active==0);
|
|
} else if(test==8){
|
|
Fixture f;f.b.active=WiFi|Bluetooth;f.b.sessions=WiFi|Bluetooth;
|
|
f.run("set wifi off",Bluetooth);assert(f.c.pending());
|
|
f.b.active=WiFi;f.b.sessions=WiFi; // the retained requester disconnects
|
|
f.tick();assert(f.b.active==WiFi&&!f.c.pending());
|
|
f.run("get wifi");assert(strstr(f.reply,"cancelled"));
|
|
// Explicit force still permits shutting down the only live connection.
|
|
f.run("set wifi off force",WiFi);f.tick();assert(f.b.active==0);
|
|
} else if(test==9){
|
|
Fixture f;f.b.active=WiFi;
|
|
f.run("set 2.4ghz off");f.tick();
|
|
f.b.fail=WiFi;f.run("set 2.4ghz on");f.tick();assert(!f.c.pending());
|
|
f.b.fail=0;f.b.active=Bluetooth;f.b.sessions=Bluetooth;
|
|
// The failed restore retains a WiFi-only saved mask. Enabling BLE to
|
|
// recover access must not make the next ordinary restore cut that access.
|
|
f.run("set 2.4ghz on",Bluetooth);
|
|
assert(strstr(f.reply,"no remaining")&&!f.c.pending()&&f.b.active==Bluetooth);
|
|
f.run("set 2.4ghz on all",Bluetooth);f.tick();assert(f.b.active==All);
|
|
}
|
|
}
|
|
'''
|
|
|
|
class WirelessControlTests(unittest.TestCase):
|
|
def test_compiled_controller(self):
|
|
with tempfile.TemporaryDirectory() as tmp:
|
|
source = Path(tmp)/'wireless.cpp'
|
|
source.write_text('#include <cstdlib>\n'+HARNESS)
|
|
binary = Path(tmp)/'wireless'
|
|
subprocess.run([os.environ.get('CXX', 'g++'), '-std=c++11', '-Wall', '-Wextra', '-Werror',
|
|
'-I', str(ROOT/'src'), str(source), '-o', str(binary)], check=True)
|
|
for scenario in range(10):
|
|
with self.subTest(scenario=scenario):
|
|
subprocess.run([str(binary), str(scenario)], check=True)
|
|
|
|
def test_actual_companion_transport_adapter(self):
|
|
from test_companion_bluetooth_control import HARNESS as BT_HARNESS
|
|
main = (ROOT/'examples/companion_radio/main.cpp').read_text()
|
|
prefix = BT_HARNESS.split('@METHODS@')[0].replace(
|
|
' bool isAnyNetworkTerminalMode()',
|
|
' bool isWebConfigActiveOrStopping(){return false;}\n bool isAnyNetworkTerminalMode()')
|
|
adapter = method(main, 'class CompanionWirelessBackend')+';\n'
|
|
adapter += method(main, 'static void disableCompanionBluetoothForCli(')+'\n'
|
|
adapter += method(main, 'bool handleCompanionWirelessCommand(')
|
|
adapter += '\n'+method(main, 'static bool hasCompanionNonBluetoothClient(')
|
|
adapter += '\n'+method(main, 'static void serviceCompanionBluetoothControl(')
|
|
adapter += '\n'+method(main, 'bool handleCompanionBluetoothCommand(')
|
|
source_text = prefix+r'''
|
|
#include "examples/companion_radio/CompanionWireless.h"
|
|
bool companion_wifi_requested=true,companion_wifi_active=true;
|
|
bool companion_wifi_setup_requested=false,companion_wifi_disable_in_progress=false;
|
|
bool stop_pending=false;
|
|
struct {int mode_value=0;void mode(int value){mode_value=value;}void setAutoReconnect(bool){}} WiFi;
|
|
#define WIFI_OFF 0
|
|
bool finishStoppingCompanionWiFi(){
|
|
// Production tears down sockets before waiting for the web server, while
|
|
// companion_wifi_active remains true until the whole teardown completes.
|
|
wifi_interface.disable();
|
|
if(stop_pending)return false;
|
|
companion_wifi_active=false;return true;
|
|
}
|
|
void startCompanionWiFi(){
|
|
if(companion_wifi_active)return;
|
|
companion_wifi_active=true;wifi_interface.enable();
|
|
}
|
|
struct {
|
|
bool active=true;
|
|
bool isEnabled(){return active;}
|
|
void end(){active=false;}
|
|
void init(){active=true;}
|
|
} radio_driver;
|
|
static void disableCompanionBluetoothForCli();
|
|
'''+adapter+r'''
|
|
int main(){
|
|
CompanionWirelessBackend backend;mesh::wireless::control().begin(backend);
|
|
interface_manager.addInterface(InterfaceType::Bluetooth,&bluetooth_interface);
|
|
interface_manager.addInterface(InterfaceType::USB,&usb_serial_interface);
|
|
interface_manager.addInterface(InterfaceType::WiFi,&wifi_interface);
|
|
interface_manager.addInterface(InterfaceType::Ethernet,ðernet_interface);
|
|
interface_manager.enable();bluetooth_interface.connected=true;
|
|
assert(backend.available()==mesh::wireless::All);
|
|
assert(backend.enabled()==mesh::wireless::All);
|
|
// UART/HWCDC power cannot authorize cutting all wireless connections.
|
|
assert(!(backend.clients()&mesh::wireless::Independent));
|
|
char reply[160];
|
|
handleCompanionWirelessCommand("set 2.4ghz off",reply,sizeof(reply),CompanionWirelessSource::Network);
|
|
assert(strstr(reply,"Error:"));
|
|
// A stale framed BLE route must not disguise an actual USB ASCII request.
|
|
bluetooth_interface.frame=true;uint8_t frame[176];interface_manager.checkRecvFrame(frame);
|
|
handleCompanionWirelessCommand("set 2.4ghz off",reply,sizeof(reply),CompanionWirelessSource::Usb);
|
|
assert(strstr(reply,"requested"));stop_pending=true;now_ms+=250;
|
|
mesh::wireless::control().service(now_ms);
|
|
assert(radio_driver.active&&bluetooth_interface.enabled); // wait for WiFi tasks/sockets first
|
|
stop_pending=false;mesh::wireless::control().service(++now_ms);
|
|
assert(!radio_driver.active&&!bluetooth_interface.enabled&&!companion_wifi_active);
|
|
assert(!companion_wifi_requested);
|
|
handleCompanionWirelessCommand("set 2.4ghz on",reply,sizeof(reply),CompanionWirelessSource::Usb);
|
|
now_ms+=250;mesh::wireless::control().service(now_ms);
|
|
assert(radio_driver.active&&bluetooth_interface.enabled&&companion_wifi_active);
|
|
assert(companion_wifi_requested);
|
|
// Reversing an asynchronous shutdown must finish teardown and restart the
|
|
// sockets, rather than treating the still-active flag as a healthy WiFi stack.
|
|
for(const char* family : {"wifi", "2.4ghz"}) {
|
|
char command[40];snprintf(command,sizeof(command),"set %s off",family);
|
|
handleCompanionWirelessCommand(command,reply,sizeof(reply),CompanionWirelessSource::Usb);
|
|
stop_pending=true;now_ms+=250;mesh::wireless::control().service(now_ms);
|
|
assert(mesh::wireless::control().pending()&&companion_wifi_active&&!wifi_interface.enabled);
|
|
snprintf(command,sizeof(command),"set %s on",family);
|
|
handleCompanionWirelessCommand(command,reply,sizeof(reply),CompanionWirelessSource::Usb);
|
|
now_ms+=250;mesh::wireless::control().service(now_ms);
|
|
assert(mesh::wireless::control().pending()&&!wifi_interface.enabled);
|
|
stop_pending=false;mesh::wireless::control().service(++now_ms);
|
|
assert(!mesh::wireless::control().pending()&&companion_wifi_active&&wifi_interface.enabled);
|
|
assert(companion_wifi_requested&&!companion_wifi_disable_in_progress);
|
|
}
|
|
// WiFi's framed client must not be mistaken for independent USB.
|
|
wifi_interface.frame=true;wifi_interface.connected=true;bluetooth_interface.connected=false;
|
|
interface_manager.checkRecvFrame(frame);
|
|
handleCompanionWirelessCommand("set wifi off",reply,sizeof(reply),CompanionWirelessSource::Framed);
|
|
assert(strstr(reply,"Error:"));
|
|
// An actual framed USB request is proof even without DTR on a UART bridge.
|
|
usb_serial_interface.frame=true;usb_serial_interface.connected=true;
|
|
interface_manager.checkRecvFrame(frame);
|
|
handleCompanionWirelessCommand("set espnow off",reply,sizeof(reply),CompanionWirelessSource::Framed);
|
|
now_ms+=250;mesh::wireless::control().service(now_ms);
|
|
assert(!radio_driver.active&&companion_wifi_active&&bluetooth_interface.enabled);
|
|
// Each shutdown must not use the other pending shutdown's connection as
|
|
// its safety net. Bluetooth's force override remains available explicitly.
|
|
usb_serial_interface.connected=false;
|
|
bluetooth_interface.connected=true;bluetooth_interface.frame=true;
|
|
interface_manager.checkRecvFrame(frame);
|
|
handleCompanionWirelessCommand("set wifi off",reply,sizeof(reply),CompanionWirelessSource::Framed);
|
|
assert(mesh::wireless::control().pending());
|
|
handleCompanionBluetoothCommand("set bluetooth off",reply,sizeof(reply),CompanionBluetoothCommandSource::Terminal);
|
|
assert(strstr(reply,"pending")&&bluetooth_interface.enabled);
|
|
handleCompanionBluetoothCommand("set bluetooth off force",reply,sizeof(reply),CompanionBluetoothCommandSource::Terminal);
|
|
assert(!bluetooth_interface.enabled);
|
|
now_ms+=250;mesh::wireless::control().service(now_ms);
|
|
assert(companion_wifi_active&&!mesh::wireless::control().pending());
|
|
handleCompanionWirelessCommand("get wifi",reply,sizeof(reply),CompanionWirelessSource::Network);
|
|
assert(strstr(reply,"cancelled"));
|
|
// The opposite command order is protected too, including asynchronous
|
|
// WiFi teardown where its socket can stay visible during the first stop.
|
|
handleCompanionBluetoothCommand("set bluetooth on",reply,sizeof(reply),CompanionBluetoothCommandSource::Terminal);
|
|
bluetooth_interface.connected=true;bluetooth_interface.frame=true;
|
|
interface_manager.checkRecvFrame(frame);
|
|
handleCompanionBluetoothCommand("set bluetooth off",reply,sizeof(reply),CompanionBluetoothCommandSource::Framed);
|
|
assert(companion_bluetooth_off_at!=0);
|
|
handleCompanionWirelessCommand("set wifi off",reply,sizeof(reply),CompanionWirelessSource::Network);
|
|
assert(strstr(reply,"Bluetooth change pending")&&!mesh::wireless::control().pending());
|
|
now_ms+=250;serviceCompanionBluetoothControl();
|
|
assert(!bluetooth_interface.enabled&&companion_wifi_active);
|
|
// A restore can remove WiFi too. Keep a BLE-only saved snapshot after a
|
|
// failed restore, then simulate both management services recovering.
|
|
companion_wifi_requested=companion_wifi_active=false;wifi_interface.disable();
|
|
interface_manager.enableBluetooth();
|
|
handleCompanionWirelessCommand("set 2.4ghz off",reply,sizeof(reply),CompanionWirelessSource::Usb);
|
|
now_ms+=250;mesh::wireless::control().service(now_ms);
|
|
bluetooth_interface.enable_fails=true;
|
|
handleCompanionWirelessCommand("set 2.4ghz on",reply,sizeof(reply),CompanionWirelessSource::Usb);
|
|
now_ms+=250;mesh::wireless::control().service(now_ms);
|
|
assert(!mesh::wireless::control().pending()&&!bluetooth_interface.enabled);
|
|
bluetooth_interface.enable_fails=false;interface_manager.enableBluetooth();
|
|
companion_wifi_requested=companion_wifi_active=true;wifi_interface.enable();
|
|
wifi_interface.connected=true;bluetooth_interface.connected=true;
|
|
bluetooth_interface.frame=true;interface_manager.checkRecvFrame(frame);
|
|
handleCompanionBluetoothCommand("set bluetooth off",reply,sizeof(reply),CompanionBluetoothCommandSource::Framed);
|
|
assert(companion_bluetooth_off_at!=0);
|
|
handleCompanionWirelessCommand("set 2.4ghz on",reply,sizeof(reply),CompanionWirelessSource::Network);
|
|
assert(strstr(reply,"Bluetooth change pending")&&!mesh::wireless::control().pending());
|
|
// Explicit all cannot remove a service and may replace the pending stop.
|
|
handleCompanionWirelessCommand("set 2.4ghz on all",reply,sizeof(reply),CompanionWirelessSource::Network);
|
|
assert(mesh::wireless::control().pending());
|
|
now_ms+=250;mesh::wireless::control().service(now_ms);
|
|
assert(!companion_bluetooth_off_at&&bluetooth_interface.enabled&&companion_wifi_active);
|
|
}
|
|
'''
|
|
with tempfile.TemporaryDirectory() as tmp:
|
|
folder = Path(tmp)
|
|
(folder/'Arduino.h').write_text('#pragma once\n#include <cstdint>\n#include <cstddef>\n#include <initializer_list>\n')
|
|
(folder/'adapter.cpp').write_text(source_text)
|
|
binary=folder/'adapter'
|
|
subprocess.run([os.environ.get('CXX','g++'), '-std=c++17',
|
|
'-I',str(folder),'-I',str(ROOT),'-I',str(ROOT/'src'),
|
|
'-DESP32=1','-DBLE_PIN_CODE=1','-DWIFI_SSID=""',
|
|
'-DWITH_WEBCONFIG=1','-DMESH_PRIMARY_ESPNOW=1',
|
|
'-DENABLE_USB_INTERFACE=1','-DCOMPANION_FEATURE_TEXT_TERMINAL=1',
|
|
'-DCOMPANION_FEATURE_NETWORK_TERMINAL=1',
|
|
str(folder/'adapter.cpp'),'-o',str(binary)],check=True)
|
|
subprocess.run([str(binary)],check=True)
|
|
|
|
def test_role_and_restart_wiring(self):
|
|
for role, backend in [('companion_radio','companion_wireless'),
|
|
('simple_repeater','infrastructure_wireless'),
|
|
('simple_room_server','infrastructure_wireless')]:
|
|
source = (ROOT/'examples'/role/'main.cpp').read_text()
|
|
self.assertIn(f'control().begin({backend})', source)
|
|
self.assertIn('control().service(millis())', source[source.index('\nvoid loop()'):])
|
|
self.assertIn('&& !mesh::wireless::control().pending()', source[source.index('\nvoid loop()'):])
|
|
gates = {
|
|
'src/helpers/bridges/MQTTBridge.cpp': 'WiFi',
|
|
'src/helpers/bridges/ESPNowBridge.cpp': 'EspNow',
|
|
'src/helpers/esp32/ESPNOWRadio.cpp': 'EspNow',
|
|
'src/helpers/esp32/WebConfigServer.cpp': 'WiFi',
|
|
'src/helpers/WiFiSetupPortal.cpp': 'WiFi',
|
|
'src/helpers/MultiSerialInterface.h': 'Bluetooth',
|
|
}
|
|
for name, bit in gates.items():
|
|
with self.subTest(file=name):
|
|
self.assertIn(f'control().blocked(mesh::wireless::{bit})', (ROOT/name).read_text())
|
|
|
|
def test_actual_infrastructure_transport_adapter(self):
|
|
source = r'''
|
|
#include <helpers/WirelessControl.h>
|
|
#include <cassert>
|
|
enum {WIFI_STA=1,WIFI_OFF=0,WIFI_IF_STA=0};
|
|
struct {
|
|
bool usb=false, ota=false;
|
|
bool isUsbDataConnected(){return usb;}
|
|
bool isOTAUpdateRunning(){return ota;}
|
|
bool stopOTAUpdate(char*){ota=false;return true;}
|
|
} board;
|
|
struct {
|
|
int mode_value=WIFI_STA;bool autoreconnect=true;
|
|
void setAutoReconnect(bool value){autoreconnect=value;}
|
|
void disconnect(bool,bool){}
|
|
void mode(int value){mode_value=value;}
|
|
} WiFi;
|
|
namespace mesh {namespace wifi {void applyProtocolMask(int){}void restoreEspNowChannel(){}}}
|
|
struct {
|
|
bool active=true;bool isEnabled(){return active;}
|
|
void init(){active=true;}void end(){active=false;}
|
|
} radio_driver;
|
|
struct {
|
|
bool web=true,stopping=false,bridge=true,network=false;
|
|
bool isWebConfigActive(){return web||stopping;}
|
|
bool isWebConfigStopping(){return stopping;}
|
|
bool hasWirelessNetworkClient(){return network;}
|
|
bool isBridgeRunning(){return bridge;}
|
|
bool setBridgeState(bool on){bridge=on;return true;}
|
|
bool isMqttBridgeRunning(){return bridge;}
|
|
bool isEspNowBridgeRunning(){return bridge;}
|
|
bool setMqttBridgeState(bool on){bridge=on;return true;}
|
|
bool setEspNowBridgeState(bool on){bridge=on;return true;}
|
|
bool stopWebConfig(char*){web=false;stopping=true;return true;}
|
|
bool startWebConfig(bool,char*){web=true;return true;}
|
|
} the_mesh;
|
|
#include "examples/InfrastructureWireless.h"
|
|
int main(){
|
|
InfrastructureWirelessBackend b;mesh::wireless::Control c;c.begin(b);
|
|
char reply[160];using namespace mesh::wireless;
|
|
#ifdef WITH_MQTT_BRIDGE
|
|
assert(b.available()==(mesh::wireless::WiFi|EspNow));
|
|
assert(b.enabled()==(mesh::wireless::WiFi|EspNow));
|
|
assert(b.clients()==0);the_mesh.network=true;assert(b.clients()==mesh::wireless::WiFi);
|
|
c.handle("set 2.4ghz off",reply,sizeof(reply),0,mesh::wireless::WiFi);assert(strstr(reply,"Error:"));
|
|
c.handle("set 2.4ghz off",reply,sizeof(reply),0,Independent);c.service(250);
|
|
assert(c.pending()&&!the_mesh.bridge&&the_mesh.stopping&&radio_driver.active);
|
|
the_mesh.stopping=false;c.service(251);
|
|
assert(!c.pending()&&!radio_driver.active&&::WiFi.mode_value==WIFI_OFF);
|
|
c.handle("set 2.4ghz on",reply,sizeof(reply),300,Independent);c.service(550);
|
|
assert(the_mesh.bridge&&the_mesh.web&&radio_driver.active);
|
|
// OTA alone remains visible as an active WiFi owner after WebConfig drains.
|
|
the_mesh.web=the_mesh.bridge=false;board.ota=true;
|
|
assert(b.enabled()&mesh::wireless::WiFi);
|
|
InfrastructureWirelessBackend ota_backend;
|
|
assert(ota_backend.set(mesh::wireless::WiFi,false)==Result::Done);
|
|
assert(!board.ota);
|
|
the_mesh.web=the_mesh.bridge=true;
|
|
// Individual WiFi off retains the ESP-NOW driver and stops both its own services.
|
|
c.handle("set wifi off",reply,sizeof(reply),600,Independent);c.service(850);
|
|
the_mesh.stopping=false;c.service(851);
|
|
assert(!the_mesh.bridge&&!the_mesh.web&&radio_driver.active&&::WiFi.mode_value==WIFI_STA);
|
|
c.handle("set wifi on",reply,sizeof(reply),900,Independent);c.service(1150);
|
|
assert(the_mesh.bridge&&the_mesh.web&&radio_driver.active);
|
|
c.handle("set espnow off",reply,sizeof(reply),1200,Independent);c.service(1450);
|
|
assert(!radio_driver.active&&the_mesh.web&&the_mesh.bridge&&::WiFi.mode_value==WIFI_STA);
|
|
#else
|
|
assert(b.available()==EspNow&&b.enabled()==EspNow);
|
|
c.handle("set espnow off",reply,sizeof(reply),0,Independent);c.service(250);assert(!the_mesh.bridge);
|
|
c.handle("set espnow on",reply,sizeof(reply),300,Independent);c.service(550);assert(the_mesh.bridge);
|
|
#endif
|
|
}
|
|
'''
|
|
with tempfile.TemporaryDirectory() as tmp:
|
|
folder=Path(tmp)
|
|
(folder/'helpers/esp32').mkdir(parents=True)
|
|
(folder/'WiFi.h').write_text('#pragma once\n')
|
|
(folder/'helpers/esp32/WiFiRadioPolicy.h').write_text('#pragma once\n')
|
|
(folder/'infrastructure.cpp').write_text(source)
|
|
for defines in [('WITH_MQTT_BRIDGE','WITH_WEBCONFIG','MESH_PRIMARY_ESPNOW'),('WITH_ESPNOW_BRIDGE',)]:
|
|
with self.subTest(features=defines):
|
|
binary=folder/'infrastructure'
|
|
subprocess.run([os.environ.get('CXX','g++'),'-std=c++17','-DESP32=1',
|
|
'-I',str(folder),'-I',str(ROOT),'-I',str(ROOT/'src'),
|
|
*['-D'+d+'=1' for d in defines],str(folder/'infrastructure.cpp'),
|
|
'-o',str(binary)],check=True)
|
|
subprocess.run([str(binary)],check=True)
|
|
|
|
if __name__ == '__main__':
|
|
unittest.main()
|