144 lines
6.5 KiB
Python
144 lines
6.5 KiB
Python
#!/usr/bin/env python3
|
|
"""Compile the real manual-sleep driver guard for WiFi/BT SDK configurations."""
|
|
|
|
import os
|
|
from pathlib import Path
|
|
import subprocess
|
|
import tempfile
|
|
import unittest
|
|
|
|
ROOT = Path(__file__).resolve().parents[1]
|
|
|
|
WIFI = r'''
|
|
#pragma once
|
|
using esp_err_t = int;
|
|
constexpr int ESP_OK = 0, ESP_ERR_WIFI_NOT_INIT = 1, ESP_ERR_WIFI_NOT_STARTED = 2;
|
|
enum wifi_mode_t { WIFI_MODE_NULL, WIFI_MODE_STA, WIFI_MODE_AP, WIFI_MODE_APSTA };
|
|
static wifi_mode_t mode = WIFI_MODE_NULL;
|
|
static int wifi_error = ESP_OK;
|
|
inline int esp_wifi_get_mode(wifi_mode_t* out) { *out = mode; return wifi_error; }
|
|
'''
|
|
CONTROLLER = r'''
|
|
#pragma once
|
|
enum esp_bt_controller_status_t { ESP_BT_CONTROLLER_STATUS_IDLE,
|
|
ESP_BT_CONTROLLER_STATUS_INITED, ESP_BT_CONTROLLER_STATUS_ENABLED };
|
|
static auto controller = ESP_BT_CONTROLLER_STATUS_IDLE;
|
|
inline esp_bt_controller_status_t esp_bt_controller_get_status() { return controller; }
|
|
'''
|
|
HOST = r'''
|
|
#pragma once
|
|
enum esp_bluedroid_status_t { ESP_BLUEDROID_STATUS_UNINITIALIZED,
|
|
ESP_BLUEDROID_STATUS_INITIALIZED, ESP_BLUEDROID_STATUS_ENABLED };
|
|
static auto host = ESP_BLUEDROID_STATUS_UNINITIALIZED;
|
|
inline esp_bluedroid_status_t esp_bluedroid_get_status() { return host; }
|
|
'''
|
|
HARNESS = r'''
|
|
#include <helpers/esp32/LightSleepPolicy.h>
|
|
#include <cassert>
|
|
using mesh::powerdiag::Reason;
|
|
using mesh::esp32::manualLightSleepBlocker;
|
|
using mesh::esp32::manualLightSleepReady;
|
|
int main() {
|
|
assert(manualLightSleepReady());
|
|
for (auto value : {WIFI_MODE_STA, WIFI_MODE_AP, WIFI_MODE_APSTA}) {
|
|
mode = value;
|
|
assert(manualLightSleepBlocker() == Reason::Network);
|
|
}
|
|
mode = static_cast<wifi_mode_t>(99);
|
|
assert(manualLightSleepBlocker() == Reason::Network);
|
|
mode = WIFI_MODE_NULL;
|
|
for (int error : {ESP_ERR_WIFI_NOT_INIT, ESP_ERR_WIFI_NOT_STARTED}) {
|
|
wifi_error = error;
|
|
assert(manualLightSleepReady());
|
|
}
|
|
wifi_error = -1;
|
|
assert(manualLightSleepBlocker() == Reason::Network);
|
|
wifi_error = ESP_OK;
|
|
#if defined(CONFIG_BT_ENABLED) && CONFIG_BT_ENABLED
|
|
controller = ESP_BT_CONTROLLER_STATUS_INITED;
|
|
assert(manualLightSleepReady());
|
|
controller = ESP_BT_CONTROLLER_STATUS_ENABLED;
|
|
assert(manualLightSleepBlocker() == Reason::Wireless);
|
|
controller = static_cast<esp_bt_controller_status_t>(99);
|
|
assert(manualLightSleepBlocker() == Reason::Wireless);
|
|
controller = ESP_BT_CONTROLLER_STATUS_IDLE;
|
|
#if defined(CONFIG_BLUEDROID_ENABLED) && CONFIG_BLUEDROID_ENABLED \
|
|
&& !defined(MESH_USE_NIMBLE_ARDUINO)
|
|
host = ESP_BLUEDROID_STATUS_INITIALIZED;
|
|
assert(manualLightSleepReady());
|
|
host = ESP_BLUEDROID_STATUS_ENABLED;
|
|
assert(manualLightSleepBlocker() == Reason::Wireless);
|
|
host = static_cast<esp_bluedroid_status_t>(99);
|
|
assert(manualLightSleepBlocker() == Reason::Wireless);
|
|
host = ESP_BLUEDROID_STATUS_UNINITIALIZED;
|
|
#endif
|
|
#endif
|
|
assert(manualLightSleepReady());
|
|
}
|
|
'''
|
|
|
|
|
|
SDK_MODES = (
|
|
(), ("CONFIG_BT_ENABLED=0", "CONFIG_IDF_TARGET_ESP32S2=1"),
|
|
("CONFIG_BT_ENABLED=1", "CONFIG_BLUEDROID_ENABLED=1", "CONFIG_IDF_TARGET_ESP32=1"),
|
|
("CONFIG_BT_ENABLED=1", "CONFIG_BLUEDROID_ENABLED=1", "CONFIG_IDF_TARGET_ESP32S3=1"),
|
|
("CONFIG_BT_ENABLED=1", "CONFIG_BLUEDROID_ENABLED=0", "CONFIG_IDF_TARGET_ESP32C3=1"),
|
|
("CONFIG_BT_ENABLED=1", "CONFIG_BLUEDROID_ENABLED=1", "MESH_USE_NIMBLE_ARDUINO=1", "CONFIG_IDF_TARGET_ESP32C6=1"),
|
|
)
|
|
|
|
|
|
class LightSleepPolicyTest(unittest.TestCase):
|
|
def test_real_guard_across_sdk_and_host_modes(self):
|
|
for flags in SDK_MODES:
|
|
with self.subTest(flags=flags):
|
|
self.run_guard((*flags, 'WIFI_SSID=""'))
|
|
|
|
def test_every_wifi_owner_keeps_physical_driver_fail_closed(self):
|
|
for owner in ("WIFI_SSID", "ENABLE_WIFI_INTERFACE", "WITH_MQTT_BRIDGE",
|
|
"WITH_ESPNOW_BRIDGE", "MESH_PRIMARY_ESPNOW", "WIFI_OTA_SEEDER",
|
|
"ADMIN_PASSWORD", "COMPANION_RADIO_FULL"):
|
|
with self.subTest(owner=owner):
|
|
self.run_guard((owner, "CONFIG_BT_ENABLED=1",
|
|
"CONFIG_BLUEDROID_ENABLED=1"))
|
|
|
|
def test_ble_only_links_without_wifi_sdk_or_headers(self):
|
|
# No WiFi SDK header or function definition exists. An unconditional
|
|
# check fails compilation/linking instead of silently passing a fake.
|
|
# ENABLE_OTA is deliberately present: this role still has LoRa OTA.
|
|
source = "#include <initializer_list>\n" + HARNESS
|
|
start = source.index(" for (auto value : {WIFI_MODE_STA")
|
|
end = source.index("#if defined(CONFIG_BT_ENABLED)", start)
|
|
source = source[:start] + source[end:]
|
|
for flags in SDK_MODES:
|
|
with self.subTest(flags=flags):
|
|
self.run_guard((*flags, "BLE_PIN_CODE=123456", "ENABLE_OTA=1",
|
|
"MESH_PRIMARY_ESPNOW=0"), source, wifi=False)
|
|
|
|
def run_guard(self, flags, source=None, wifi=True):
|
|
with tempfile.TemporaryDirectory(prefix="mesh-esp32-sleep-driver-") as directory:
|
|
root = Path(directory)
|
|
(root / "sdkconfig.h").write_text("#pragma once\n")
|
|
(root / "esp_wifi.h").write_text(WIFI if wifi else '#error "WiFi SDK is absent"\n')
|
|
bt = "CONFIG_BT_ENABLED=1" in flags
|
|
bluedroid = bt and "CONFIG_BLUEDROID_ENABLED=1" in flags and "MESH_USE_NIMBLE_ARDUINO=1" not in flags
|
|
(root / "esp_bt.h").write_text(CONTROLLER if bt else '#error "Bluetooth absent on this target"\n')
|
|
(root / "esp_bt_main.h").write_text(HOST if bluedroid else '#error "Bluedroid absent from this host build"\n')
|
|
cpp, exe = root / "guard.cpp", root / "guard"
|
|
cpp.write_text(source or "#include <initializer_list>\n" + HARNESS)
|
|
result = subprocess.run([
|
|
os.environ.get("CXX", "c++"), "-std=c++17", "-Wall", "-Wextra", "-Werror",
|
|
*("-D" + flag for flag in flags), "-I", str(root), "-I", str(ROOT / "src"),
|
|
str(cpp), "-o", str(exe)], text=True, capture_output=True, timeout=30)
|
|
self.assertEqual(result.returncode, 0, result.stderr)
|
|
result = subprocess.run([str(exe)], text=True, capture_output=True, timeout=10)
|
|
self.assertEqual(result.returncode, 0, result.stderr)
|
|
if not wifi:
|
|
symbols = subprocess.run(["nm", "-C", str(exe)], text=True,
|
|
capture_output=True, timeout=10)
|
|
self.assertEqual(symbols.returncode, 0, symbols.stderr)
|
|
self.assertNotIn("esp_wifi_", symbols.stdout)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|