diff --git a/src/helpers/RegulatorSettings.cpp b/src/helpers/RegulatorSettings.cpp index 7b8fb80ef..63aed1d34 100644 --- a/src/helpers/RegulatorSettings.cpp +++ b/src/helpers/RegulatorSettings.cpp @@ -11,6 +11,44 @@ #include #include +// Current STM32WL boards expose neither MCU nor radio regulator selection. +// Their radio and MCU share a supply, so adding that support requires a +// coordinated implementation rather than the independent controls used by +// nRF52 plus an external radio. Do not retain the unreachable persistence and +// recovery machinery on these flash-constrained targets. Match this exact +// driver: an STM32 target with a different radio keeps the ordinary path. +#define MESH_REGULATOR_FIXED_RADIO_CustomSTM32WLx 1 +#define MESH_REGULATOR_FIXED_RADIO_I(type) MESH_REGULATOR_FIXED_RADIO_##type +#define MESH_REGULATOR_FIXED_RADIO(type) MESH_REGULATOR_FIXED_RADIO_I(type) +#if defined(STM32_PLATFORM) && MESH_REGULATOR_FIXED_RADIO(RADIO_CLASS) +namespace mesh { namespace power { +bool beginRegulatorSettings(MainBoard&, Radio&, FILESYSTEM*) { return true; } +void serviceRegulatorSettings() {} +const char* regulatorModeName(RegulatorMode mode) { + return mode == RegulatorMode::Dcdc ? "dcdc" : mode == RegulatorMode::Ldo ? "ldo" : "auto"; +} +uint8_t configuredRegulatorModes(bool) { return 0; } +RegulatorMode requestedRegulatorMode(bool) { return RegulatorMode::Default; } +RegulatorMode activeRegulatorMode(bool) { return RegulatorMode::Default; } +const char* activeRegulatorModeName(bool) { return "unknown"; } +bool regulatorFallbackActive(bool) { return false; } +bool regulatorRecoveryAvailable() { return false; } +bool handleRegulatorCommand(const char* command, char* reply, size_t capacity) { + if (strncmp(command, "get ", 4) != 0 && strncmp(command, "set ", 4) != 0) return false; + const char* key = command + 4; + bool mcu; + size_t length; + if (strncmp(key, "mcu.regulator", 13) == 0) { mcu = true; length = 13; } + else if (strncmp(key, "radio.regulator", 15) == 0) { mcu = false; length = 15; } + else return false; + if (key[length] != 0 && key[length] != ' ') return false; + snprintf(reply, capacity, "Error: %s regulator selection unsupported", mcu ? "MCU" : "radio"); + return true; +} +void markRegulatorCleanReset() {} +void resumeRegulatorRecovery() {} +}} // namespace mesh::power +#else namespace mesh { namespace power { namespace { MainBoard* main_board = nullptr; @@ -289,6 +327,10 @@ void resumeRegulatorRecovery() { } }} // namespace mesh::power +#endif +#undef MESH_REGULATOR_FIXED_RADIO +#undef MESH_REGULATOR_FIXED_RADIO_I +#undef MESH_REGULATOR_FIXED_RADIO_CustomSTM32WLx extern "C" void meshRegulatorCleanReset() { // Intentional resets can happen before the settings/filesystem is ready. diff --git a/test/fixtures/regulator_settings_native.cpp b/test/fixtures/regulator_settings_native.cpp index 7ec639336..78aabc117 100644 --- a/test/fixtures/regulator_settings_native.cpp +++ b/test/fixtures/regulator_settings_native.cpp @@ -31,7 +31,7 @@ uint8_t configuredBatteryPercent(uint16_t mv) { } }} #endif -#if !defined(NRF52_PLATFORM) +#if !defined(NRF52_PLATFORM) && !defined(STM32_PLATFORM) #include #endif #if defined(NRF52_PLATFORM) diff --git a/test/fixtures/regulator_settings_stm32_fixed.cpp b/test/fixtures/regulator_settings_stm32_fixed.cpp new file mode 100644 index 000000000..2d9a982d1 --- /dev/null +++ b/test/fixtures/regulator_settings_stm32_fixed.cpp @@ -0,0 +1,55 @@ +#include +#include +#include +#include +#include +#include +uint32_t mock_now = 0; +#include + +int main() { + using namespace mesh::power; + mesh::MainBoard board; + mesh::Radio radio; + MockFS fs; + assert(beginRegulatorSettings(board, radio, &fs)); + for (bool mcu : {false, true}) { + assert(configuredRegulatorModes(mcu) == 0); + assert(requestedRegulatorMode(mcu) == RegulatorMode::Default); + assert(activeRegulatorMode(mcu) == RegulatorMode::Default); + assert(strcmp(activeRegulatorModeName(mcu), "unknown") == 0); + assert(!regulatorFallbackActive(mcu)); + } + assert(!regulatorRecoveryAvailable()); + assert(strcmp(regulatorModeName(RegulatorMode::Default), "auto") == 0); + assert(strcmp(regulatorModeName(RegulatorMode::Dcdc), "dcdc") == 0); + assert(strcmp(regulatorModeName(RegulatorMode::Ldo), "ldo") == 0); + for (const char* command : {"get mcu.regulator", "set mcu.regulator dcdc", + "set mcu.regulator ldo", "set mcu.regulator auto", "get radio.regulator", + "set radio.regulator dcdc", "set radio.regulator ldo", "set radio.regulator auto"}) { + char reply[80]; + assert(handleRegulatorCommand(command, reply, sizeof(reply))); + assert(std::string(reply) == std::string("Error: ") + + (strstr(command, "mcu.") ? "MCU" : "radio") + " regulator selection unsupported"); + for (size_t capacity = 0; capacity <= sizeof(reply); ++capacity) { + memset(reply, 0xa5, sizeof(reply)); + assert(handleRegulatorCommand(command, reply, capacity)); + if (capacity) assert(memchr(reply, 0, capacity)); + for (size_t i = capacity; i < sizeof(reply); ++i) assert(uint8_t(reply[i]) == 0xa5); + } + } + for (const char* command : {"get mcu.regulatorx", "set radio.regulators ldo", "get name", "help"}) { + char reply[80]; + assert(!handleRegulatorCommand(command, reply, sizeof(reply))); + } + for (unsigned i = 0; i < 10; ++i) { + mock_now += 30000; + serviceRegulatorSettings(); + meshRegulatorCleanReset(); + meshRegulatorResumeRecovery(); + } + assert(board.applies == 0 && board.sample_index == 0); + assert(radio.configures == 0 && radio.inits == 0); + assert(fs.files.empty() && fs.writes == 0 && fs.renames == 0); + assert(mock_now == 300000); // No ADC sampling delays or other hardware work. +} diff --git a/test/test_regulator_settings.py b/test/test_regulator_settings.py index f21393688..09c8f2aac 100644 --- a/test/test_regulator_settings.py +++ b/test/test_regulator_settings.py @@ -153,6 +153,8 @@ class RegulatorSettingsTests(unittest.TestCase): path.write_text(content) cls.binaries = {} for platform, flags in {'esp32': ['-DESP32=1'], + 'esp32_fixed_radio_name': ['-DESP32=1','-DRADIO_CLASS=CustomSTM32WLx'], + 'stm32_future_external_radio': ['-DSTM32_PLATFORM=1','-DRADIO_CLASS=CustomSX1262'], 'rp2040': ['-DRP2040_PLATFORM=1'], 'nrf52': ['-DNRF52_PLATFORM=1','-DNRF52_POWER_MANAGEMENT=1'], 'no_retention': ['-DESP32=1','-DSOC_RTC_SLOW_MEM_SUPPORTED=0']}.items(): @@ -164,6 +166,34 @@ class RegulatorSettingsTests(unittest.TestCase): if result.returncode: raise AssertionError(result.stdout+result.stderr) cls.binaries[platform] = binary + binary = folder / 'stm32_fixed' + result = subprocess.run([compiler, '-std=c++17','-Wall','-Wextra','-Werror', + *SANITIZER_FLAGS,'-DSTM32_PLATFORM=1','-DRADIO_CLASS=CustomSTM32WLx', + '-I',str(folder),'-I',str(ROOT/'src'), + str(ROOT/'test/fixtures/regulator_settings_stm32_fixed.cpp'),'-o',str(binary)], + text=True,capture_output=True,timeout=45) + if result.returncode: + raise AssertionError(result.stdout+result.stderr) + cls.stm32_fixed = binary + + def test_fixed_stm32wl_driver_rejects_selection_without_hardware_or_storage_work(self): + result = subprocess.run([str(self.stm32_fixed)], text=True,capture_output=True,timeout=10) + self.assertEqual(result.returncode,0,result.stdout+result.stderr) + + def test_fixed_stm32wl_guard_must_be_revisited_if_current_classes_gain_selection(self): + # These are the classes excluded by the exact platform/driver guard. + # A future implementation must remove or narrow that guard as it adds + # coordinated shared-supply support, rather than silently hiding it. + paths = [ROOT/'src/helpers/stm32/STM32Board.h', + ROOT/'src/helpers/radiolib/CustomSTM32WLx.h', + ROOT/'src/helpers/radiolib/CustomSTM32WLxWrapper.h'] + for variant in (ROOT/'variants').glob('*/platformio.ini'): + if 'extends = stm32_base' in variant.read_text(): + paths.extend(variant.parent.glob('*.h')) + paths.extend(variant.parent.glob('*.cpp')) + for path in paths: + with self.subTest(path=path.relative_to(ROOT)): + self.assertNotRegex(path.read_text(), r'\b(?:mcuRegulatorModes|regulatorModes)\s*\(') def scenario(self, scenario): for platform, binary in self.binaries.items():