Omit unsupported tuning controls on integrated STM32WL radios
This commit is contained in:
1 parent
901a8c4cd5
commit
c9de5c8350
7 files changed
+132
-1
No files matched your search
@@ -36,9 +36,11 @@ const char* const TempPath = "/radio_profiles.tmp";
|
||||
// Never considered a boot-time committed image. Losing power before the
|
||||
// acknowledgement leaves the previous saved radio untouched.
|
||||
const char* const ReplyPath = "/radio_profiles.reply";
|
||||
#if MESH_RADIO_SWITCH_TUNING
|
||||
const char* const FastPath = "/radio_fast";
|
||||
const char* const FastBackup = "/radio_fast.bak";
|
||||
const char* const FastTemp = "/radio_fast.tmp";
|
||||
#endif
|
||||
uint32_t checksum(const uint8_t* p, size_t n) {
|
||||
return storage::updateCRC32(0xffffffffU, p, n);
|
||||
}
|
||||
@@ -223,6 +225,7 @@ bool RadioProfileCLI::commitImage(const char* path, const char* active,
|
||||
return true;
|
||||
}
|
||||
|
||||
#if MESH_RADIO_SWITCH_TUNING
|
||||
bool RadioProfileCLI::validFastImage(const uint8_t* bytes, RadioSwitchTuning& value) const {
|
||||
if (bytes[2] != 1) return false;
|
||||
for (size_t n = 5; n < ImageSize - 4; ++n) if (bytes[n]) return false;
|
||||
@@ -367,10 +370,13 @@ __attribute__((noinline)) bool RadioProfileCLI::handleFastSettings(const char* k
|
||||
? "radio busy; retry" : "radio tuning could not be applied/saved");
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
||||
void RadioProfileCLI::begin(FILESYSTEM* fs, Radio* radio, RTCClock* rtc, bool infrastructure_replies) {
|
||||
fs_ = fs; radio_ = radio; rtc_ = rtc; last_ms_ = millis();
|
||||
#if MESH_RADIO_SWITCH_TUNING
|
||||
loadFastSettings();
|
||||
#endif
|
||||
hold_ = false;
|
||||
cross_ = RadioCrossMode::Off;
|
||||
recoverable_corrupt_store_ = false;
|
||||
@@ -734,12 +740,14 @@ bool RadioProfileCLI::applyReplyMutation() {
|
||||
|
||||
void RadioProfileCLI::loop() {
|
||||
const uint32_t now = millis();
|
||||
#if MESH_RADIO_SWITCH_TUNING
|
||||
if (fast_pending_ && radio_ && (int32_t)(now - fast_retry_ms_) >= 0) {
|
||||
// A saved setting may fail RX verification as well as be temporarily busy.
|
||||
// Avoid repeatedly applying and rolling it back at the main-loop rate.
|
||||
fast_pending_ = radio_->trySetSwitchTuning(fast_saved_) != RadioParamApplyResult::APPLIED;
|
||||
fast_retry_ms_ = now + 1000;
|
||||
}
|
||||
#endif
|
||||
if (!radio_ || !radio_->profiles()) return;
|
||||
const uint32_t elapsed = now - last_ms_;
|
||||
last_ms_ = now;
|
||||
@@ -949,7 +957,9 @@ bool RadioProfileCLI::handle(const char* command, char* reply, size_t capacity,
|
||||
memcpy(key, text, n); key[n] = 0;
|
||||
const char* args = text + n;
|
||||
while (*args == ' ' || *args == '\t') ++args;
|
||||
#if MESH_RADIO_SWITCH_TUNING
|
||||
if (handleFastSettings(key, args, verb, remote_origin, reply, capacity)) return true;
|
||||
#endif
|
||||
enum { Base, Temporary, Scheduled, ScheduledTemporary, Crossing, Replies,
|
||||
Status, Scan, Timing, TimingAlias, KeyCount };
|
||||
static const char keys[] = "radio2\0tempradio2\0radioat2\0tempradioat2\0radio2.cross\0"
|
||||
|
||||
@@ -56,9 +56,11 @@ class RadioProfileCLI {
|
||||
// readable backup. Read/I/O failures and an intact backup remain read-only.
|
||||
bool recoverable_corrupt_store_ = false;
|
||||
bool publish_pending_ = false;
|
||||
#if MESH_RADIO_SWITCH_TUNING
|
||||
RadioSwitchTuning fast_saved_;
|
||||
uint32_t fast_retry_ms_ = 0;
|
||||
bool fast_pending_ = false, fast_hold_ = false;
|
||||
#endif
|
||||
enum class ImageReadResult : uint8_t { Missing, Valid, Invalid, Unreadable };
|
||||
enum class RemoteMutation : uint8_t {
|
||||
None, Saved, Temporary, Off, TempOff, DeleteTemp
|
||||
@@ -88,12 +90,14 @@ class RadioProfileCLI {
|
||||
bool commitSavedImage(const char* path);
|
||||
bool commitImage(const char* staged, const char* active, const char* backup,
|
||||
bool& hold, bool preserve_backup = false);
|
||||
#if MESH_RADIO_SWITCH_TUNING
|
||||
bool validFastImage(const uint8_t* bytes, RadioSwitchTuning& value) const;
|
||||
void loadFastSettings(bool boot = false);
|
||||
bool saveFastSettings(const RadioSwitchTuning& value);
|
||||
static bool commitFastSettings(void* context, const RadioSwitchTuning& value);
|
||||
bool handleFastSettings(const char* key, const char* args, int verb,
|
||||
bool remote, char* reply, size_t capacity);
|
||||
#endif
|
||||
bool discardCorruptSavedImagesForWrite();
|
||||
bool applyReplyMutation();
|
||||
ImageReadResult readImage(const char* path, uint8_t* bytes, size_t size);
|
||||
@@ -108,7 +112,11 @@ class RadioProfileCLI {
|
||||
void appendSecondaryWarnings(char* reply, size_t capacity, const RadioProfiles& preview) const;
|
||||
public:
|
||||
// Call after the settings filesystem mounts and before radio_init().
|
||||
#if MESH_RADIO_SWITCH_TUNING
|
||||
static void configureBootTuning(FILESYSTEM* fs, Radio& radio);
|
||||
#else
|
||||
static void configureBootTuning(FILESYSTEM*, Radio&) {}
|
||||
#endif
|
||||
void begin(FILESYSTEM* fs, Radio* radio, RTCClock* rtc, bool infrastructure_replies = false);
|
||||
void loop();
|
||||
bool handle(const char* command, char* reply, size_t capacity = 160, bool remote_origin = false);
|
||||
|
||||
@@ -1,6 +1,17 @@
|
||||
#pragma once
|
||||
#include <stdint.h>
|
||||
|
||||
// The integrated STM32WL driver has neither the cached retune paths nor a
|
||||
// tunable SPI HAL. Do not retain saved controls it cannot apply on small WL
|
||||
// images. Other radios retain every supported acceleration by default.
|
||||
#ifndef MESH_RADIO_SWITCH_TUNING
|
||||
#if defined(STM32_PLATFORM) && defined(STM32WLxx)
|
||||
#define MESH_RADIO_SWITCH_TUNING 0
|
||||
#else
|
||||
#define MESH_RADIO_SWITCH_TUNING 1
|
||||
#endif
|
||||
#endif
|
||||
|
||||
namespace mesh {
|
||||
|
||||
// Saved policy for one physical radio, shared by both RF profiles. SPI MHz 0
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
#include "ProfileSwitchTiming.h"
|
||||
#include "NrfRadioPriorityLease.h"
|
||||
#include "NrfRadioUsbIrqGuard.h"
|
||||
#if defined(ESP32_PLATFORM) || defined(NRF52_PLATFORM) || defined(RP2040_PLATFORM)
|
||||
#if MESH_RADIO_SWITCH_TUNING && (defined(ESP32_PLATFORM) || defined(NRF52_PLATFORM) || defined(RP2040_PLATFORM))
|
||||
#include "TunableRadioHal.h"
|
||||
#define MESH_TUNABLE_RADIO_HAL 1
|
||||
#endif
|
||||
@@ -447,6 +447,7 @@ void RadioLibWrapper::endReconfigure(bool resume_rx) {
|
||||
if (resume_rx) startRecv();
|
||||
}
|
||||
|
||||
#if MESH_RADIO_SWITCH_TUNING
|
||||
mesh::RadioSwitchCapabilities RadioLibWrapper::switchTuningCapabilities() const {
|
||||
mesh::RadioSwitchCapabilities caps;
|
||||
#if MESH_TUNABLE_RADIO_HAL
|
||||
@@ -509,6 +510,7 @@ bool RadioLibWrapper::configureBootSwitchTuning(const mesh::RadioSwitchTuning& v
|
||||
setDriverSwitchFeatures(value.flags);
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
||||
bool RadioLibWrapper::restoreAfterDeepInit() {
|
||||
#if MESH_ENABLE_TX_BEACONS
|
||||
|
||||
@@ -328,9 +328,11 @@ protected:
|
||||
virtual bool radioDeepInit() { return false; }
|
||||
virtual bool supportsRadioDeepInit() const { return false; }
|
||||
virtual bool applyParams(float freq, float bw, uint8_t sf, uint8_t cr) = 0;
|
||||
#if MESH_RADIO_SWITCH_TUNING
|
||||
virtual uint8_t getDriverSwitchFeatures() const { return 0; }
|
||||
virtual uint8_t driverSwitchCapabilities() const { return 0; }
|
||||
virtual void setDriverSwitchFeatures(uint8_t) {}
|
||||
#endif
|
||||
virtual int16_t applyCachedTxPower(int8_t dbm) {
|
||||
return _radio->setOutputPower(dbm);
|
||||
}
|
||||
@@ -486,6 +488,7 @@ public:
|
||||
#endif
|
||||
mesh::RadioProfiles* profiles() override { return &_profiles; }
|
||||
const mesh::RadioProfiles* profiles() const override { return &_profiles; }
|
||||
#if MESH_RADIO_SWITCH_TUNING
|
||||
mesh::RadioSwitchCapabilities switchTuningCapabilities() const override;
|
||||
mesh::RadioSwitchTuning switchTuning() const override;
|
||||
uint32_t switchSpiHz() const override;
|
||||
@@ -493,6 +496,7 @@ public:
|
||||
mesh::RadioParamApplyResult trySetSwitchTuning(const mesh::RadioSwitchTuning& tuning,
|
||||
bool (*persist)(void*, const mesh::RadioSwitchTuning&) = nullptr,
|
||||
void* context = nullptr) override;
|
||||
#endif
|
||||
bool validateProfile(const mesh::RadioProfileParams& params) const override;
|
||||
uint8_t receiveProfile() const override { return _active_profile; }
|
||||
uint32_t receiveProfileGeneration() const override { return _profile_generation; }
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
// Integrated STM32WL has no tunable transport or acknowledged fast retune
|
||||
// cache. Its shared boot hook must not inspect an irrelevant saved record.
|
||||
#define main existing_cli_main
|
||||
#include "cli_test.cpp"
|
||||
#undef main
|
||||
|
||||
int main() {
|
||||
static_assert(MESH_RADIO_SWITCH_TUNING == 0, "Integrated STM32WL tuning must be absent");
|
||||
MemoryFS fs;
|
||||
Clock clock;
|
||||
Radio radio;
|
||||
const std::vector<uint8_t> record(24, 0x55);
|
||||
fs.files["/radio_fast"] = record;
|
||||
mesh::RadioProfileCLI::configureBootTuning(&fs, radio);
|
||||
assert(fs.opened_paths.empty());
|
||||
|
||||
mesh::RadioProfileCLI cli;
|
||||
cli.begin(&fs, &radio, &clock);
|
||||
for (const auto& path : fs.opened_paths) assert(path.find("/radio_fast") != 0);
|
||||
fs.opened_paths.clear();
|
||||
cli.loop();
|
||||
char reply[160];
|
||||
for (const char* command : {"get radio.fast", "get radio.fast.spi", "get radio.fast.caps",
|
||||
"set radio.fast on", "set radio.fast.spi 8"}) {
|
||||
assert(!cli.handle(command, reply, sizeof(reply)));
|
||||
}
|
||||
assert(fs.opened_paths.empty());
|
||||
assert(fs.files["/radio_fast"] == record);
|
||||
assert(fs.files.size() == 1);
|
||||
const auto capabilities = radio.switchTuningCapabilities();
|
||||
assert(capabilities.flags == 0 && capabilities.spi_clocks == 0);
|
||||
assert(!radio.configureBootSwitchTuning({}));
|
||||
assert(radio.trySetSwitchTuning({}) == mesh::RadioParamApplyResult::FAILED);
|
||||
// Existing radio2 configuration commands remain available.
|
||||
assert(cli.handle("get radio2", reply, sizeof(reply)));
|
||||
assert(!strncmp(reply, "> off", 5));
|
||||
}
|
||||
@@ -37,6 +37,65 @@ class RadioSwitchTuningCLITest(unittest.TestCase):
|
||||
if role == 'simple_secure_chat':
|
||||
self.assertLess(radio_init.start(), setup.index('fast_rng.begin('))
|
||||
|
||||
def test_capability_gate_only_defaults_off_for_integrated_stm32wl(self):
|
||||
compiler = shutil.which('g++') or shutil.which('clang++')
|
||||
self.assertIsNotNone(compiler)
|
||||
for defines, expected in (((), 1), (('NRF52_PLATFORM',), 1), (('ESP32_PLATFORM',), 1),
|
||||
(('RP2040_PLATFORM',), 1), (('STM32_PLATFORM',), 1),
|
||||
(('STM32WLxx',), 1), (('STM32_PLATFORM', 'STM32WLxx'), 0),
|
||||
(('STM32_PLATFORM', 'STM32WLxx', 'MESH_RADIO_SWITCH_TUNING=1'), 1)):
|
||||
with self.subTest(defines=defines):
|
||||
source = '#include <helpers/RadioSwitchTuning.h>\n'
|
||||
source += f'static_assert(MESH_RADIO_SWITCH_TUNING == {expected}, "capability gate");\n'
|
||||
built = subprocess.run([compiler, '-std=c++11', '-fsyntax-only', '-x', 'c++',
|
||||
'-I', str(ROOT / 'src'), *['-D' + value for value in defines], '-'],
|
||||
input=source, capture_output=True, text=True)
|
||||
self.assertEqual(built.returncode, 0, built.stdout + built.stderr)
|
||||
|
||||
def test_integrated_stm32wl_omits_unsupported_controls_and_boot_storage_reads(self):
|
||||
compiler = shutil.which('g++') or shutil.which('clang++')
|
||||
self.assertIsNotNone(compiler)
|
||||
with tempfile.TemporaryDirectory() as directory:
|
||||
work = Path(directory)
|
||||
helpers = work / 'helpers'
|
||||
helpers.mkdir()
|
||||
# Instrument the existing filesystem mock, rather than accepting
|
||||
# an untouched file as proof that no startup read occurred.
|
||||
mock = (ROOT / 'test/fixtures/radio_profiles/mocks/helpers/IdentityStore.h').read_text()
|
||||
mock = mock.replace(' std::map<std::string, std::vector<uint8_t>> files;',
|
||||
' std::map<std::string, std::vector<uint8_t>> files;\n'
|
||||
' std::vector<std::string> opened_paths;')
|
||||
mock = mock.replace(' File open(const char* path, const char* mode = "r", bool = false) {',
|
||||
' File open(const char* path, const char* mode = "r", bool = false) {\n'
|
||||
' opened_paths.push_back(path);')
|
||||
mock = mock.replace(' File open(const char* path, const char* mode = "r", bool = false) {',
|
||||
' File open(const char* path, int mode) { return open(path, mode ? "w" : "r"); }\n'
|
||||
' File open(const char* path, const char* mode = "r", bool = false) {')
|
||||
(helpers / 'IdentityStore.h').write_text(mock, encoding='ascii')
|
||||
flags = [compiler, '-std=c++17', '-O2', '-Wall', '-Wextra', '-DSTM32_PLATFORM',
|
||||
'-DSTM32WLxx', '-DLFS_ERR_NOENT=-2', '-I', str(work),
|
||||
'-I', str(ROOT / 'test/fixtures/radio_profiles/mocks'),
|
||||
'-I', str(ROOT / 'test/mocks'), '-I', str(ROOT / 'src')]
|
||||
source = ROOT / 'src/helpers/RadioProfileCLI.cpp'
|
||||
executable = work / 'unsupported'
|
||||
built = subprocess.run(flags + [str(source), str(ROOT / 'test/fixtures/radio_profiles/'
|
||||
'switch_tuning_unsupported_test.cpp'), '-o', str(executable)],
|
||||
capture_output=True, text=True)
|
||||
self.assertEqual(built.returncode, 0, built.stdout + built.stderr)
|
||||
subprocess.run([str(executable)], check=True)
|
||||
# Inspect real production objects. The inactive branch must omit
|
||||
# both saved-file names and all parser/persistence entry points.
|
||||
for enabled in (False, True):
|
||||
obj = work / f'cli-{int(enabled)}.o'
|
||||
override = ['-DMESH_RADIO_SWITCH_TUNING=1'] if enabled else []
|
||||
subprocess.run(flags + override + ['-c', str(source), '-o', str(obj)],
|
||||
check=True, capture_output=True, text=True)
|
||||
raw = obj.read_bytes()
|
||||
for marker in (b'/radio_fast', b'handleFastSettings', b'loadFastSettings',
|
||||
b'saveFastSettings', b'validFastImage'):
|
||||
self.assertEqual(marker in raw, enabled, marker)
|
||||
self.assertIn(b'/radio_profiles', raw)
|
||||
|
||||
def test_saved_controls_and_failure_transactions(self):
|
||||
compiler = shutil.which('g++') or shutil.which('clang++')
|
||||
self.assertIsNotNone(compiler)
|
||||
|
||||
Reference in new issue
Block a user