Add saved controls for radio switching acceleration
This commit is contained in:
1 parent
5a4e81c4d5
commit
61d024bc72
78 files changed
+1696
-160
No files matched your search
@@ -233,6 +233,9 @@ jobs:
|
||||
python3 -B test/test_management_feature_gate.py -v
|
||||
python3 -B test/test_carrier_wave.py -v
|
||||
python3 -B test/test_radio_profile_cli.py -v
|
||||
python3 -B test/test_radio_switch_tuning_cli.py -v
|
||||
python3 -B test/test_radio_switch_tuning_transaction.py -v
|
||||
python3 -B test/test_radio_switch_driver_controls.py -v
|
||||
python3 -B test/test_ed25519_compact_scalar.py -v
|
||||
python3 -B test/test_ed25519_compact_field.py -v
|
||||
python3 -B test/test_ed25519_compact.py -v
|
||||
|
||||
@@ -1653,6 +1653,31 @@ capacity. Remote use follows the existing administrator-command permissions.
|
||||
|
||||
### Radio
|
||||
|
||||
#### Tune and save radio switching acceleration
|
||||
|
||||
Use local USB or BLE to change these settings on supported SX1262 and LR1110
|
||||
radios. Supported acceleration features default to on, including on boards
|
||||
awaiting hardware qualification. `get radio.fast.caps` lists the options the
|
||||
current radio and SPI bus actually support.
|
||||
|
||||
| Command | Purpose |
|
||||
| --- | --- |
|
||||
| `get radio.fast` | Read feature selections, actual SPI clock and any pending saved policy |
|
||||
| `get radio.fast.caps` | List supported feature names and SPI MHz values |
|
||||
| `get radio.fast.<feature>` | Read an individual feature |
|
||||
| `set radio.fast.<feature> on\|off` | Save one feature selection: `warm`, `fs`, `packet`, `iq`, `modem`, `batch`, or `buffer` |
|
||||
| `get radio.fast.spi` | Read the selected MHz and actual clock in Hz |
|
||||
| `set radio.fast.spi 0\|2\|4\|8\|16` | Save a supported SPI MHz choice; `0` restores the board default |
|
||||
| `set radio.fast off` | Disable supported acceleration features and use 2 MHz where available |
|
||||
| `set radio.fast on` | Enable supported features and restore the board default SPI clock |
|
||||
|
||||
The change applies safely without reboot and survives firmware updates. A busy
|
||||
radio or failed apply/save leaves the previous policy in use. Remote LoRa
|
||||
mutations and unsupported choices are rejected. Both radio profiles share the
|
||||
settings. Changing them restarts the switching timing test; inspect
|
||||
`get radio2.timing` after it completes. See [saved switching controls](radio_profiles.md#saved-switching-controls)
|
||||
for what each feature does, hardware limits, and default/fallback behavior.
|
||||
|
||||
#### Select MCU and LoRa regulator modes
|
||||
|
||||
```text
|
||||
|
||||
+66
-3
@@ -1196,6 +1196,68 @@ This ends temporary second-profile activity, cancels its temporary schedules and
|
||||
restores saved `radio2`. If the saved second profile is off, this returns to one
|
||||
profile. Packets/retries bound to an expired or changed session are discarded.
|
||||
|
||||
## Saved switching controls
|
||||
|
||||
Supported SX1262 and LR1110 radios expose separate saved controls for their
|
||||
switching optimizations. Available acceleration features start enabled,
|
||||
including on supported boards that have not had a hardware timing test. The
|
||||
board's SPI default remains its selected default clock. Availability depends on
|
||||
the radio, board bus, and compiled feature support; inspect the device first:
|
||||
|
||||
```text
|
||||
get radio.fast
|
||||
get radio.fast.caps
|
||||
```
|
||||
|
||||
`get radio.fast` reports the selected SPI MHz, its actual clock in Hz, and each
|
||||
feature as `on`, `off`, or `na` when unsupported. `get radio.fast.caps` lists
|
||||
supported feature names and accepted SPI values (`spi=na` without clock
|
||||
control). Settings affect the one
|
||||
physical radio and both profiles. They are shared by the same CLI roles as
|
||||
`radio2`, including Full Companion, and survive reboot and firmware updates.
|
||||
|
||||
| Control | Values | Effect when enabled |
|
||||
| --- | --- | --- |
|
||||
| `radio.fast.warm` | `on`, `off` | Retain owned continuous-RX state between profile changes |
|
||||
| `radio.fast.fs` | `on`, `off` | Use the synthesizer/FS shortcut for an eligible retune |
|
||||
| `radio.fast.packet` | `on`, `off` | Reuse acknowledged unchanged receive-packet parameters |
|
||||
| `radio.fast.iq` | `on`, `off` | Reuse the unchanged SX1262 IQ workaround state |
|
||||
| `radio.fast.modem` | `on`, `off` | Reuse confirmed unchanged modem state/queries |
|
||||
| `radio.fast.batch` | `on`, `off` | Apply SF, bandwidth and coding rate together |
|
||||
| `radio.fast.buffer` | `on`, `off` | Transfer SPI command bytes in a buffer where supported |
|
||||
| `radio.fast.spi` | `0`, `2`, `4`, `8`, `16` where supported | Select the radio SPI clock in MHz; `0` restores the board default |
|
||||
|
||||
For example, compare buffered transfers or a lower clock independently:
|
||||
|
||||
```text
|
||||
set radio.fast.buffer off
|
||||
set radio.fast.spi 4
|
||||
get radio2.timing
|
||||
set radio.fast.spi 0
|
||||
set radio.fast.buffer on
|
||||
```
|
||||
|
||||
`0` preserves board-specific defaults, including clocks that are not whole MHz.
|
||||
An unsupported feature or clock is rejected. A feature saved as `on` only runs
|
||||
when its normal ownership and safety checks allow it; disabling `warm` also
|
||||
prevents its dependent RX shortcuts from being used. Buffered transfer support
|
||||
is separate from clock support. Some nRF52 buses support an 8 MHz byte path
|
||||
while reporting `buffer=na`.
|
||||
|
||||
For a broad comparison, `set radio.fast off` disables all supported acceleration
|
||||
features and selects 2 MHz where available. `set radio.fast on` enables all
|
||||
supported features and restores the board's default clock. These commands do
|
||||
not change the chosen SPI controller or the pins and do not adjust CPU clock,
|
||||
RX power saving, channel settings, or whether `radio2` is enabled.
|
||||
|
||||
Changes require local USB or BLE access. A busy radio rejects a change without
|
||||
saving it. An accepted change restarts receive safely and verifies it before
|
||||
saving; a receive or storage failure restores the prior policy. Switching
|
||||
measurements restart after a policy change, so check `get radio2.timing` again
|
||||
after its timing test completes. Packet and error history remains available.
|
||||
If a saved policy cannot be applied during boot, its application retries at
|
||||
most once per second; the aggregate getter reports `pending saved policy`.
|
||||
|
||||
## Power saving and diagnostics
|
||||
|
||||
Dual mode suspends RX power saving and MCU idle sleep so receive visits can run
|
||||
@@ -1237,8 +1299,8 @@ calculation remain synchronized; frequency, preamble, packet guards, and RX
|
||||
restart are unchanged. A later [HIL-only screening](test-results/radio_profile_switch_validation.md#expanded-screening-redundant-rx-setup-and-spi-v5)
|
||||
tested redundant standby/IRQ/buffer/packet setup and SPI transfer changes.
|
||||
Its fastest candidate measured 0.535 ms mean on XIAO but 8.203 ms on the
|
||||
Indicator, whose radio GPIOs go through an I2C expander. The validated production
|
||||
implementation now opts XIAO S3 WIO and Indicator LoRa into buffered 8 MHz SPI
|
||||
Indicator, whose radio GPIOs go through an I2C expander. That production stage
|
||||
opted XIAO S3 WIO and Indicator LoRa into buffered 8 MHz SPI
|
||||
and a guarded fast-RX state machine. The `heltec_v4_kiss_modem` and
|
||||
`Station_G3_ESP32_kiss_modem` targets use the same guarded path only for KISS
|
||||
dual-profile scanning; their normal board roles keep their established
|
||||
@@ -1251,7 +1313,8 @@ retune. Preamble/packet parameters (including the IQ workaround), stale-IRQ
|
||||
clearing, RX commands and BUSY waits remain. TX, CAD, sleep, reset, RXPS and
|
||||
failed commands revoke reuse; failed RX startup rolls back the profile.
|
||||
Sleep entry uses standby and wake restores the existing TCXO voltage/delay.
|
||||
Other variants do not opt in to faster SPI or the fast-RX state machine.
|
||||
At that stage other variants retained their earlier SPI and RX policies. The
|
||||
current saved controls and supported defaults are described above.
|
||||
Within a valid owned fast-RX retune, identical acknowledged SF/BW/CR/LDRO
|
||||
settings now reuse the previous modulation command: frequency-only hops omit
|
||||
`SetModulationParams` (0x8B). A changed effective LDRO value also forces a write,
|
||||
|
||||
@@ -224,7 +224,11 @@ MESH_COMPANION_HELP_BEGIN
|
||||
" set espnow.channel <1-13>\r\n"
|
||||
MESH_COMPANION_HELP_END
|
||||
#endif
|
||||
// Keep native USB producer writes within 256 bytes without changing help text.
|
||||
// Keep native USB producer writes within 256 bytes.
|
||||
MESH_COMPANION_HELP_BEGIN
|
||||
" get radio.fast / get radio.fast.caps (supported saved controls)\r\n"
|
||||
" set radio.fast <on|off> / set radio.fast.spi <0|2|4|8|16>\r\n"
|
||||
MESH_COMPANION_HELP_END
|
||||
MESH_COMPANION_HELP_BEGIN
|
||||
" get radio.rxps.config\r\n"
|
||||
" get/set radio.rxps <off|on|level 1-10 [preamble 16|32]|rx_us sleep_us>\r\n"
|
||||
|
||||
@@ -3654,6 +3654,7 @@ void setup() {
|
||||
if (!mesh::power::beginRegulatorSettings(board, radio_driver, regulator_prefs_fs)) {
|
||||
MESH_DEBUG_PRINTLN("Regulator selection could not be applied");
|
||||
}
|
||||
mesh::RadioProfileCLI::configureBootTuning(regulator_prefs_fs, radio_driver);
|
||||
|
||||
|
||||
#ifdef DISPLAY_CLASS
|
||||
|
||||
@@ -221,6 +221,7 @@ void setup() {
|
||||
if (!mesh::power::beginRegulatorSettings(board, radio_driver, regulator_prefs_fs)) {
|
||||
MESH_DEBUG_PRINTLN("Regulator selection could not be applied");
|
||||
}
|
||||
mesh::RadioProfileCLI::configureBootTuning(regulator_prefs_fs, radio_driver);
|
||||
mesh::hilStartupTrace("fs_ready");
|
||||
#ifdef DISPLAY_CLASS
|
||||
mesh::hilStartupTrace("display_begin");
|
||||
|
||||
@@ -155,6 +155,7 @@ void setup() {
|
||||
if (!mesh::power::beginRegulatorSettings(board, radio_driver, regulator_prefs_fs)) {
|
||||
MESH_DEBUG_PRINTLN("Regulator selection could not be applied");
|
||||
}
|
||||
mesh::RadioProfileCLI::configureBootTuning(regulator_prefs_fs, radio_driver);
|
||||
#ifdef DISPLAY_CLASS
|
||||
mesh::ui::loadDisplayPowerSettings(fs, false);
|
||||
#if defined(OLED_AUTO_DETECT) && OLED_AUTO_DETECT
|
||||
|
||||
@@ -765,22 +765,10 @@ void setup() {
|
||||
external_watchdog.begin();
|
||||
#endif
|
||||
|
||||
int radioinit_attempts = 0;
|
||||
while (!radio_init()) {
|
||||
++radioinit_attempts;
|
||||
MESH_DEBUG_PRINTLN("Radio init failed! (attempt %d)", radioinit_attempts);
|
||||
if (radioinit_attempts >= 3) {
|
||||
MESH_DEBUG_PRINTLN("Radio init failed 3x - rebooting");
|
||||
board.reboot();
|
||||
}
|
||||
delay(500);
|
||||
}
|
||||
|
||||
fast_rng.begin(radio_driver.getRngSeed());
|
||||
|
||||
#if defined(NRF52_PLATFORM)
|
||||
FILESYSTEM* primary_fs = &InternalFS;
|
||||
FILESYSTEM* primary_fs;
|
||||
bool volatile_primary_fs = false;
|
||||
#if defined(NRF52_PLATFORM)
|
||||
primary_fs = &InternalFS;
|
||||
mesh::storage::RamFallbackFileSystem* ram_primary_fs = nullptr;
|
||||
const auto primary_fs_boot =
|
||||
mesh::storage::beginInternalPrimaryFilesystemSafely(InternalFS);
|
||||
@@ -794,16 +782,31 @@ void setup() {
|
||||
primary_fs = &ram_primary_fs->filesystem();
|
||||
volatile_primary_fs = true;
|
||||
}
|
||||
the_mesh.begin(*primary_fs, volatile_primary_fs);
|
||||
#elif defined(RP2040_PLATFORM)
|
||||
LittleFS.begin();
|
||||
the_mesh.begin(LittleFS);
|
||||
primary_fs = &LittleFS;
|
||||
#elif defined(ESP32)
|
||||
SPIFFS.begin(true);
|
||||
the_mesh.begin(SPIFFS);
|
||||
primary_fs = &SPIFFS;
|
||||
#else
|
||||
#error "need to define filesystem"
|
||||
#endif
|
||||
mesh::RadioProfileCLI::configureBootTuning(
|
||||
volatile_primary_fs ? nullptr : primary_fs, radio_driver);
|
||||
|
||||
int radioinit_attempts = 0;
|
||||
while (!radio_init()) {
|
||||
++radioinit_attempts;
|
||||
MESH_DEBUG_PRINTLN("Radio init failed! (attempt %d)", radioinit_attempts);
|
||||
if (radioinit_attempts >= 3) {
|
||||
MESH_DEBUG_PRINTLN("Radio init failed 3x - rebooting");
|
||||
board.reboot();
|
||||
}
|
||||
delay(500);
|
||||
}
|
||||
|
||||
fast_rng.begin(radio_driver.getRngSeed());
|
||||
the_mesh.begin(*primary_fs, volatile_primary_fs);
|
||||
|
||||
radio_driver.setParams(the_mesh.getFreqPref(), LORA_BW, LORA_SF, LORA_CR);
|
||||
radio_driver.setTxPower(the_mesh.getTxPowerPref());
|
||||
|
||||
@@ -176,6 +176,7 @@ void setup() {
|
||||
if (!mesh::power::beginRegulatorSettings(board, radio_driver, regulator_prefs_fs)) {
|
||||
MESH_DEBUG_PRINTLN("Regulator selection could not be applied");
|
||||
}
|
||||
mesh::RadioProfileCLI::configureBootTuning(regulator_prefs_fs, radio_driver);
|
||||
#ifdef DISPLAY_CLASS
|
||||
mesh::ui::loadDisplayPowerSettings(fs, false);
|
||||
#if defined(OLED_AUTO_DETECT) && OLED_AUTO_DETECT
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include <Packet.h>
|
||||
#include <Utils.h>
|
||||
#include <RadioProfiles.h>
|
||||
#include <helpers/RadioSwitchTuning.h>
|
||||
#include <helpers/MeshtextConfig.h>
|
||||
#include <helpers/MorseTx.h>
|
||||
#include <helpers/MorseRx.h>
|
||||
@@ -98,6 +99,16 @@ public:
|
||||
virtual void begin() { }
|
||||
virtual RadioProfiles* profiles() { return nullptr; }
|
||||
virtual const RadioProfiles* profiles() const { return nullptr; }
|
||||
virtual RadioSwitchCapabilities switchTuningCapabilities() const { return {}; }
|
||||
virtual RadioSwitchTuning switchTuning() const { return {}; }
|
||||
virtual uint32_t switchSpiHz() const { return 0; }
|
||||
virtual bool configureBootSwitchTuning(const RadioSwitchTuning&) { return false; }
|
||||
// Commit only after admission and verified RX restoration. The callback
|
||||
// must not access the radio; a failed save restores the previous policy.
|
||||
virtual RadioParamApplyResult trySetSwitchTuning(const RadioSwitchTuning&,
|
||||
bool (*)(void*, const RadioSwitchTuning&) = nullptr, void* = nullptr) {
|
||||
return RadioParamApplyResult::FAILED;
|
||||
}
|
||||
virtual bool validateProfile(const RadioProfileParams&) const { return false; }
|
||||
virtual bool supportsMeshtext() const { return false; }
|
||||
virtual bool supportsEthernetTunnel() const { return false; }
|
||||
|
||||
@@ -36,6 +36,9 @@ 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";
|
||||
const char* const FastPath = "/radio_fast";
|
||||
const char* const FastBackup = "/radio_fast.bak";
|
||||
const char* const FastTemp = "/radio_fast.tmp";
|
||||
uint32_t checksum(const uint8_t* p, size_t n) {
|
||||
return storage::updateCRC32(0xffffffffU, p, n);
|
||||
}
|
||||
@@ -198,21 +201,176 @@ bool RadioProfileCLI::prepareSavedImage(const char* path, const RadioProfileConf
|
||||
}
|
||||
|
||||
bool RadioProfileCLI::commitSavedImage(const char* path) {
|
||||
if (!fs_ || hold_) return false;
|
||||
if (fs_->exists(ImagePath)) {
|
||||
if (fs_->exists(BackupPath) && !fs_->remove(BackupPath)) return false;
|
||||
if (!fs_->rename(ImagePath, BackupPath)) return false;
|
||||
return commitImage(path, ImagePath, BackupPath, hold_);
|
||||
}
|
||||
|
||||
bool RadioProfileCLI::commitImage(const char* path, const char* active,
|
||||
const char* backup, bool& hold, bool preserve_backup) {
|
||||
if (!fs_ || hold) return false;
|
||||
if (fs_->exists(active)) {
|
||||
if (preserve_backup) {
|
||||
if (!fs_->remove(active)) return false;
|
||||
} else {
|
||||
if (fs_->exists(backup) && !fs_->remove(backup)) return false;
|
||||
if (!fs_->rename(active, backup)) return false;
|
||||
}
|
||||
}
|
||||
if (!fs_->rename(path, ImagePath)) {
|
||||
if (fs_->exists(BackupPath) && !fs_->rename(BackupPath, ImagePath)) hold_ = true;
|
||||
if (!fs_->rename(path, active)) {
|
||||
if (fs_->exists(backup) && !fs_->rename(backup, active)) hold = true;
|
||||
return false;
|
||||
}
|
||||
if (fs_->exists(BackupPath)) fs_->remove(BackupPath);
|
||||
if (fs_->exists(backup)) fs_->remove(backup);
|
||||
return true;
|
||||
}
|
||||
|
||||
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;
|
||||
value.flags = bytes[3]; value.spi_mhz = bytes[4];
|
||||
return radio_ && validRadioSwitchTuning(value, radio_->switchTuningCapabilities());
|
||||
}
|
||||
|
||||
void RadioProfileCLI::configureBootTuning(FILESYSTEM* fs, Radio& radio) {
|
||||
RadioProfileCLI loader;
|
||||
loader.fs_ = fs; loader.radio_ = &radio;
|
||||
loader.loadFastSettings(true);
|
||||
}
|
||||
|
||||
__attribute__((noinline)) void RadioProfileCLI::loadFastSettings(bool boot) {
|
||||
fast_pending_ = fast_hold_ = false;
|
||||
if (!fs_ || !radio_) return;
|
||||
uint8_t bytes[ImageSize];
|
||||
const auto primary = readImage(FastPath, bytes, sizeof(bytes));
|
||||
bool loaded = primary == ImageReadResult::Valid && validFastImage(bytes, fast_saved_);
|
||||
if (!loaded) {
|
||||
const auto backup = readImage(FastBackup, bytes, sizeof(bytes));
|
||||
loaded = backup == ImageReadResult::Valid && validFastImage(bytes, fast_saved_);
|
||||
fast_hold_ = primary == ImageReadResult::Unreadable || backup == ImageReadResult::Unreadable;
|
||||
if (loaded && !fast_hold_) {
|
||||
// Publish the known-good backup before accepting further writes. Never
|
||||
// move a corrupt/unsupported primary over this only valid record.
|
||||
fast_hold_ = (fs_->exists(FastPath) && !fs_->remove(FastPath))
|
||||
|| !fs_->rename(FastBackup, FastPath);
|
||||
}
|
||||
}
|
||||
if (loaded) {
|
||||
fast_pending_ = boot ? !radio_->configureBootSwitchTuning(fast_saved_)
|
||||
: radio_->trySetSwitchTuning(fast_saved_) != RadioParamApplyResult::APPLIED;
|
||||
fast_retry_ms_ = millis() + 1000;
|
||||
}
|
||||
}
|
||||
|
||||
bool RadioProfileCLI::saveFastSettings(const RadioSwitchTuning& value) {
|
||||
if (!fs_ || fast_hold_) return false;
|
||||
// A read error must not be interpreted as a missing/corrupt saved policy.
|
||||
uint8_t previous[ImageSize];
|
||||
RadioSwitchTuning decoded;
|
||||
const auto read = readImage(FastPath, previous, sizeof(previous));
|
||||
if (read == ImageReadResult::Unreadable) return false;
|
||||
const bool preserve = read != ImageReadResult::Valid || !validFastImage(previous, decoded);
|
||||
uint8_t image[ImageSize] = {'R', '2', 1, value.flags, value.spi_mhz};
|
||||
const uint32_t crc = checksum(image, ImageSize - 4);
|
||||
memcpy(image + ImageSize - 4, &crc, 4);
|
||||
if (!writeImage(FastTemp, image, sizeof(image))
|
||||
|| !commitImage(FastTemp, FastPath, FastBackup, fast_hold_, preserve)) return false;
|
||||
fast_saved_ = value;
|
||||
fast_pending_ = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool RadioProfileCLI::commitFastSettings(void* context, const RadioSwitchTuning& value) {
|
||||
return static_cast<RadioProfileCLI*>(context)->saveFastSettings(value);
|
||||
}
|
||||
|
||||
__attribute__((noinline)) bool RadioProfileCLI::handleFastSettings(const char* key, const char* args, int verb,
|
||||
bool remote, char* reply, size_t capacity) {
|
||||
if (strcmp(key, "radio.fast") && strncmp(key, "radio.fast.", 11)) return false;
|
||||
if (!radio_) { cli::formatError(reply, capacity, "radio tuning unsupported"); return true; }
|
||||
const auto caps = radio_->switchTuningCapabilities();
|
||||
const auto current = radio_->switchTuning();
|
||||
const char* suffix = key + (key[10] ? 11 : 10);
|
||||
static const char names[] = "warm\0fs\0packet\0iq\0modem\0batch\0buffer";
|
||||
const char* name = names;
|
||||
unsigned feature = 0;
|
||||
while (feature < 7 && strcmp(suffix, name)) { name += strlen(name) + 1; ++feature; }
|
||||
const bool master = !*suffix, spi = !strcmp(suffix, "spi");
|
||||
const bool capabilities = !strcmp(suffix, "caps");
|
||||
if (!master && !spi && !capabilities && feature == 7) {
|
||||
cli::formatError(reply, capacity, "unknown radio.fast control"); return true;
|
||||
}
|
||||
const uint8_t bit = feature < 7 ? 1U << feature : 0;
|
||||
if ((spi && !caps.spi_clocks) || (bit && !(caps.flags & bit))
|
||||
|| (master && !caps.flags && !caps.spi_clocks)) {
|
||||
cli::formatError(reply, capacity, "radio tuning unsupported"); return true;
|
||||
}
|
||||
if (verb == 0 && !*args) {
|
||||
if (capabilities) {
|
||||
snprintf(reply, capacity, "> features:");
|
||||
name = names;
|
||||
for (unsigned n = 0; n < 7; ++n) {
|
||||
if (caps.flags & (1U << n)) appendReply(reply, capacity, " %s", name);
|
||||
name += strlen(name) + 1;
|
||||
}
|
||||
appendReply(reply, capacity, "; spi=%s", caps.spi_clocks ? "0" : "na");
|
||||
for (unsigned n = 1; n <= 4; ++n)
|
||||
if (caps.spi_clocks & (1U << n)) appendReply(reply, capacity, ",%u", 1U << n);
|
||||
} else if (master) {
|
||||
if (caps.spi_clocks) {
|
||||
snprintf(reply, capacity, "> spi=%u (%luHz)", current.spi_mhz,
|
||||
(unsigned long)radio_->switchSpiHz());
|
||||
} else snprintf(reply, capacity, "> spi=na");
|
||||
if (fast_pending_) appendReply(reply, capacity, " pending saved policy");
|
||||
name = names;
|
||||
for (unsigned n = 0; n < 7; ++n) {
|
||||
appendReply(reply, capacity, " %s=%s", name,
|
||||
!(caps.flags & (1U << n)) ? "na" : current.flags & (1U << n) ? "on" : "off");
|
||||
name += strlen(name) + 1;
|
||||
}
|
||||
} else if (spi) snprintf(reply, capacity, "> %u (%luHz; 0=board default)",
|
||||
current.spi_mhz, (unsigned long)radio_->switchSpiHz());
|
||||
else snprintf(reply, capacity, "> %s", current.flags & bit ? "on" : "off");
|
||||
return true;
|
||||
}
|
||||
if (capabilities) {
|
||||
cli::formatError(reply, capacity, "radio.fast.caps is read-only"); return true;
|
||||
}
|
||||
if (verb != 1 || !*args) {
|
||||
cli::formatError(reply, capacity, "use get/set radio.fast[.control] value"); return true;
|
||||
}
|
||||
if (remote || remote_command_) {
|
||||
cli::formatError(reply, capacity, "radio.fast changes require local USB or BLE"); return true;
|
||||
}
|
||||
if (hasReplyMutation() || fast_hold_) {
|
||||
cli::formatError(reply, capacity, "radio settings held; retry after recovery"); return true;
|
||||
}
|
||||
RadioSwitchTuning next = current;
|
||||
if (spi) {
|
||||
// Keep parsing strict without five separate whole-string comparisons.
|
||||
if (!args[1] && strchr("0248", args[0])) next.spi_mhz = args[0] - '0';
|
||||
else if (!strcmp(args, "16")) next.spi_mhz = 16;
|
||||
else { cli::formatError(reply, capacity, "SPI MHz: 0,2,4,8,16 where supported"); return true; }
|
||||
} else {
|
||||
const bool on = !strcmp(args, "on");
|
||||
if (!on && strcmp(args, "off")) { cli::formatError(reply, capacity, "use on or off"); return true; }
|
||||
if (master) {
|
||||
next.flags = on ? caps.flags : 0;
|
||||
next.spi_mhz = !on && (caps.spi_clocks & 2) ? 2 : 0;
|
||||
} else if (on) next.flags |= bit;
|
||||
else next.flags &= ~bit;
|
||||
}
|
||||
if (!validRadioSwitchTuning(next, caps)) {
|
||||
cli::formatError(reply, capacity, "radio tuning unsupported"); return true;
|
||||
}
|
||||
const auto result = radio_->trySetSwitchTuning(next, commitFastSettings, this);
|
||||
if (result == RadioParamApplyResult::APPLIED) snprintf(reply, capacity, "OK - %s=%s (saved)", key, args);
|
||||
else cli::formatError(reply, capacity, result == RadioParamApplyResult::BUSY
|
||||
? "radio busy; retry" : "radio tuning could not be applied/saved");
|
||||
return true;
|
||||
}
|
||||
|
||||
void RadioProfileCLI::begin(FILESYSTEM* fs, Radio* radio, RTCClock* rtc, bool infrastructure_replies) {
|
||||
fs_ = fs; radio_ = radio; rtc_ = rtc; last_ms_ = millis();
|
||||
loadFastSettings();
|
||||
hold_ = false;
|
||||
cross_ = RadioCrossMode::Off;
|
||||
recoverable_corrupt_store_ = false;
|
||||
@@ -575,8 +733,15 @@ bool RadioProfileCLI::applyReplyMutation() {
|
||||
}
|
||||
|
||||
void RadioProfileCLI::loop() {
|
||||
const uint32_t now = millis();
|
||||
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;
|
||||
}
|
||||
if (!radio_ || !radio_->profiles()) return;
|
||||
const uint32_t now = millis(), elapsed = now - last_ms_;
|
||||
const uint32_t elapsed = now - last_ms_;
|
||||
last_ms_ = now;
|
||||
remote_commit_retry_ms_ = elapsed >= remote_commit_retry_ms_ ? 0 : remote_commit_retry_ms_ - elapsed;
|
||||
if (remote_delivered_ && !remote_commit_retry_ms_ && !applyReplyMutation()) {
|
||||
@@ -784,6 +949,7 @@ 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 (handleFastSettings(key, args, verb, remote_origin, reply, capacity)) return true;
|
||||
enum { Base, Temporary, Scheduled, ScheduledTemporary, Crossing, Replies,
|
||||
Status, Scan, Timing, TimingAlias, KeyCount };
|
||||
static const char keys[] = "radio2\0tempradio2\0radioat2\0tempradioat2\0radio2.cross\0"
|
||||
|
||||
@@ -56,6 +56,9 @@ class RadioProfileCLI {
|
||||
// readable backup. Read/I/O failures and an intact backup remain read-only.
|
||||
bool recoverable_corrupt_store_ = false;
|
||||
bool publish_pending_ = false;
|
||||
RadioSwitchTuning fast_saved_;
|
||||
uint32_t fast_retry_ms_ = 0;
|
||||
bool fast_pending_ = false, fast_hold_ = false;
|
||||
enum class ImageReadResult : uint8_t { Missing, Valid, Invalid, Unreadable };
|
||||
enum class RemoteMutation : uint8_t {
|
||||
None, Saved, Temporary, Off, TempOff, DeleteTemp
|
||||
@@ -83,6 +86,14 @@ class RadioProfileCLI {
|
||||
bool prepareSavedImage(const char* path, const RadioProfileConfig& config,
|
||||
uint16_t preamble, RadioCrossMode cross, int8_t manual_secondary = -1);
|
||||
bool commitSavedImage(const char* path);
|
||||
bool commitImage(const char* staged, const char* active, const char* backup,
|
||||
bool& hold, bool preserve_backup = false);
|
||||
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);
|
||||
bool discardCorruptSavedImagesForWrite();
|
||||
bool applyReplyMutation();
|
||||
ImageReadResult readImage(const char* path, uint8_t* bytes, size_t size);
|
||||
@@ -96,6 +107,8 @@ class RadioProfileCLI {
|
||||
const Schedule* schedule = nullptr) const;
|
||||
void appendSecondaryWarnings(char* reply, size_t capacity, const RadioProfiles& preview) const;
|
||||
public:
|
||||
// Call after the settings filesystem mounts and before radio_init().
|
||||
static void configureBootTuning(FILESYSTEM* fs, Radio& radio);
|
||||
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);
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
#pragma once
|
||||
#include <stdint.h>
|
||||
|
||||
namespace mesh {
|
||||
|
||||
// Saved policy for one physical radio, shared by both RF profiles. SPI MHz 0
|
||||
// retains the board's default clock (which need not be an integer MHz).
|
||||
struct RadioSwitchTuning {
|
||||
enum Feature : uint8_t {
|
||||
Warm = 1, Fs = 2, Packet = 4, Iq = 8, Modem = 16,
|
||||
Batch = 32, Buffer = 64
|
||||
};
|
||||
uint8_t flags = 0;
|
||||
uint8_t spi_mhz = 0;
|
||||
bool operator==(const RadioSwitchTuning& other) const {
|
||||
return flags == other.flags && spi_mhz == other.spi_mhz;
|
||||
}
|
||||
bool operator!=(const RadioSwitchTuning& other) const { return !(*this == other); }
|
||||
};
|
||||
|
||||
struct RadioSwitchCapabilities {
|
||||
uint8_t flags = 0;
|
||||
// Bit N permits 2^N MHz; bit 0 (1 MHz) is deliberately unused.
|
||||
uint8_t spi_clocks = 0;
|
||||
};
|
||||
|
||||
inline bool validRadioSwitchTuning(const RadioSwitchTuning& value,
|
||||
const RadioSwitchCapabilities& caps) {
|
||||
if (value.flags & ~caps.flags) return false;
|
||||
if (!value.spi_mhz) return true;
|
||||
for (uint8_t n = 1; n <= 4; ++n)
|
||||
if (value.spi_mhz == (1U << n)) return caps.spi_clocks & (1U << n);
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace mesh
|
||||
@@ -6,6 +6,7 @@
|
||||
#include "RXPowerSaving.h"
|
||||
#include "LR1110RxRecovery.h"
|
||||
#include "LR1110ProfileSwitchState.h"
|
||||
#include <helpers/RadioSwitchTuning.h>
|
||||
#include "LR11x0MorseRfSwitch.h"
|
||||
#include "../MorseRx.h"
|
||||
|
||||
@@ -29,6 +30,7 @@ class CustomLR1110 : public LR1110 {
|
||||
bool _headerSeen = false;
|
||||
bool _rx_boosted = false;
|
||||
LR1110ProfileSwitchState _profileSwitch{MC_LR1110_FAST_PROFILE_SWITCH != 0};
|
||||
uint8_t _profileSwitchFeatures = profileSwitchCapabilities();
|
||||
bool _profileStandbyWarm = false;
|
||||
bool _profilePacketDirty = false;
|
||||
uint32_t _profileFastResumes = 0;
|
||||
@@ -74,11 +76,23 @@ class CustomLR1110 : public LR1110 {
|
||||
return _regulator.finishInitialization(status);
|
||||
}
|
||||
|
||||
void setProfileSwitchOptimization(bool enabled) {
|
||||
_profileSwitch.enabled = enabled;
|
||||
uint8_t profileSwitchCapabilities() const {
|
||||
return MC_LR1110_FAST_PROFILE_SWITCH
|
||||
? mesh::RadioSwitchTuning::Warm | mesh::RadioSwitchTuning::Fs
|
||||
| mesh::RadioSwitchTuning::Packet | mesh::RadioSwitchTuning::Modem
|
||||
| mesh::RadioSwitchTuning::Batch
|
||||
: 0;
|
||||
}
|
||||
uint8_t getProfileSwitchFeatures() const { return _profileSwitchFeatures; }
|
||||
void setProfileSwitchFeatures(uint8_t features) {
|
||||
_profileSwitchFeatures = features & profileSwitchCapabilities();
|
||||
_profileSwitch.enabled = (_profileSwitchFeatures & mesh::RadioSwitchTuning::Warm) != 0;
|
||||
_profileSwitch.invalidate();
|
||||
_profilePacketDirty = false;
|
||||
}
|
||||
void setProfileSwitchOptimization(bool enabled) {
|
||||
setProfileSwitchFeatures(enabled ? profileSwitchCapabilities() : 0);
|
||||
}
|
||||
void setProfileStandbyWarm(bool enabled) {
|
||||
_profileStandbyWarm = enabled;
|
||||
if (!enabled) _profileSwitch.invalidate();
|
||||
@@ -119,8 +133,8 @@ class CustomLR1110 : public LR1110 {
|
||||
const bool warm = _profileSwitch.active && _profileSwitch.rxValid;
|
||||
if (!warm) _profileSwitch.invalidate();
|
||||
// RadioLib 7.7.1 defines STANDBY_XOSC as 0, the same as STANDBY_RC.
|
||||
// The LR1110 command's XOSC selector is 1 (Semtech UM §2.1.1).
|
||||
bool useFs = warm;
|
||||
// The LR1110 command's XOSC selector is 1 (Semtech UM section 2.1.1).
|
||||
bool useFs = warm && (_profileSwitchFeatures & mesh::RadioSwitchTuning::Fs);
|
||||
#ifdef MC_LR1110_HIL
|
||||
useFs = useFs && hilUseFsOnRetune;
|
||||
#endif
|
||||
@@ -227,7 +241,8 @@ class CustomLR1110 : public LR1110 {
|
||||
else if (fabsf(bw - 250.0f) < 0.01f) bwCode = RADIOLIB_LR11X0_LORA_BW_250_0;
|
||||
else if (fabsf(bw - 500.0f) < 0.01f) bwCode = RADIOLIB_LR11X0_LORA_BW_500_0;
|
||||
else return RADIOLIB_ERR_INVALID_BANDWIDTH;
|
||||
if (!_profileSwitch.canResumeFast()) {
|
||||
if (!(_profileSwitchFeatures & mesh::RadioSwitchTuning::Modem)
|
||||
|| !_profileSwitch.canResumeFast()) {
|
||||
uint8_t type = RADIOLIB_LR11X0_PACKET_TYPE_NONE;
|
||||
const int16_t rc = getPacketType(&type);
|
||||
if (rc != RADIOLIB_ERR_NONE) return rc;
|
||||
@@ -237,7 +252,8 @@ class CustomLR1110 : public LR1110 {
|
||||
? (float(uint32_t(1) << sf) / bw >= 16.0f
|
||||
? RADIOLIB_LR11X0_LORA_LDRO_ENABLED : RADIOLIB_LR11X0_LORA_LDRO_DISABLED)
|
||||
: ldrOptimize;
|
||||
const bool unchanged = _profileSwitch.matchesModulation(sf, bwCode, cr - 4, effectiveLdro);
|
||||
const bool unchanged = (_profileSwitchFeatures & mesh::RadioSwitchTuning::Batch)
|
||||
&& _profileSwitch.matchesModulation(sf, bwCode, cr - 4, effectiveLdro);
|
||||
const auto oldSf = spreadingFactor, oldBw = bandwidth, oldCr = codingRate, oldLdro = ldrOptimize;
|
||||
const auto oldBwKhz = bandwidthKhz;
|
||||
spreadingFactor = sf;
|
||||
@@ -467,7 +483,8 @@ class CustomLR1110 : public LR1110 {
|
||||
fastClearCycles += DWT->CYCCNT - segment;
|
||||
segment = DWT->CYCCNT;
|
||||
#endif
|
||||
if (rc == RADIOLIB_ERR_NONE && _profilePacketDirty) {
|
||||
if (rc == RADIOLIB_ERR_NONE && (_profilePacketDirty
|
||||
|| !(_profileSwitchFeatures & mesh::RadioSwitchTuning::Packet))) {
|
||||
rc = setPacketParamsLoRa(preambleLengthLoRa, headerType,
|
||||
RADIOLIB_LR11X0_MAX_PACKET_LENGTH, crcTypeLoRa, invertIQEnabled);
|
||||
}
|
||||
|
||||
@@ -35,6 +35,15 @@ public:
|
||||
void setDeepInitCallback(DeepInitCallback callback) { _deep_init = callback; }
|
||||
|
||||
protected:
|
||||
uint8_t getDriverSwitchFeatures() const override {
|
||||
return static_cast<const CustomLR1110*>(_radio)->getProfileSwitchFeatures();
|
||||
}
|
||||
uint8_t driverSwitchCapabilities() const override {
|
||||
return static_cast<const CustomLR1110*>(_radio)->profileSwitchCapabilities();
|
||||
}
|
||||
void setDriverSwitchFeatures(uint8_t features) override {
|
||||
static_cast<CustomLR1110*>(_radio)->setProfileSwitchFeatures(features);
|
||||
}
|
||||
#if MESH_ENABLE_MESHTEXT_WIRE
|
||||
bool applyProtocol(mesh::RadioProfileProtocol protocol) override {
|
||||
if (protocol != mesh::RadioProfileProtocol::MeshCore
|
||||
@@ -97,19 +106,20 @@ public:
|
||||
}
|
||||
|
||||
protected:
|
||||
bool applyProfileModulation(float bw, uint8_t sf, uint8_t cr) {
|
||||
auto* radio = static_cast<CustomLR1110*>(_radio);
|
||||
if (getDriverSwitchFeatures() & mesh::RadioSwitchTuning::Batch)
|
||||
return radio->setLoRaModulationParams(bw, sf, cr) == RADIOLIB_ERR_NONE;
|
||||
return radio->setSpreadingFactor(sf) == RADIOLIB_ERR_NONE
|
||||
&& radio->setBandwidth(bw) == RADIOLIB_ERR_NONE
|
||||
&& radio->setCodingRate(cr) == RADIOLIB_ERR_NONE;
|
||||
}
|
||||
|
||||
bool applyParams(float freq, float bw, uint8_t sf, uint8_t cr) override {
|
||||
if (static_cast<CustomLR1110*>(_radio)->profileSwitchFailed()) return false;
|
||||
#if MC_LR1110_FAST_PROFILE_SWITCH
|
||||
bool success = ((CustomLR1110 *)_radio)->setFrequency(freq) == RADIOLIB_ERR_NONE
|
||||
&& ((CustomLR1110 *)_radio)->setLoRaModulationParams(bw, sf, cr) == RADIOLIB_ERR_NONE
|
||||
&& applyProfileModulation(bw, sf, cr)
|
||||
&& updatePreamble(sf, bw);
|
||||
#else
|
||||
bool success = ((CustomLR1110 *)_radio)->setFrequency(freq) == RADIOLIB_ERR_NONE
|
||||
&& ((CustomLR1110 *)_radio)->setSpreadingFactor(sf) == RADIOLIB_ERR_NONE
|
||||
&& ((CustomLR1110 *)_radio)->setBandwidth(bw) == RADIOLIB_ERR_NONE
|
||||
&& ((CustomLR1110 *)_radio)->setCodingRate(cr) == RADIOLIB_ERR_NONE
|
||||
&& updatePreamble(sf, bw);
|
||||
#endif
|
||||
if (!success) return false;
|
||||
|
||||
PacketMillis pm = calcMaxPacketMillis(sf, bw, cr, preambleLengthForParams(sf, bw));
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#include "MeshCore.h"
|
||||
#include "RXPowerSaving.h"
|
||||
#include "SX1262ProfileSwitchState.h"
|
||||
#include <helpers/RadioSwitchTuning.h>
|
||||
#include "SX126xMorseRssi.h"
|
||||
#include "ESP32RadioTrace.h"
|
||||
#if defined(STATION_G3_ESP32)
|
||||
@@ -67,6 +68,7 @@ class CustomSX1262 : public SX1262 {
|
||||
bool _rx_ps_rf_rx_disabled = false;
|
||||
uint32_t _rxDutyCycleTransitionUs = 1000;
|
||||
SX1262ProfileSwitchState _profileSwitch{MC_SX1262_FAST_PROFILE_SWITCH != 0};
|
||||
uint8_t _profileSwitchFeatures = defaultProfileSwitchFeatures();
|
||||
uint32_t _profileFastResumes = 0;
|
||||
bool _coldStandby = true;
|
||||
bool _tcxoWakePending = false;
|
||||
@@ -113,10 +115,53 @@ class CustomSX1262 : public SX1262 {
|
||||
return status == RADIOLIB_ERR_NONE ? raw * -0.5f : NAN;
|
||||
}
|
||||
|
||||
void setProfileSwitchOptimization(bool enabled) {
|
||||
_profileSwitch.enabled = enabled;
|
||||
uint8_t defaultProfileSwitchFeatures() const {
|
||||
return mesh::RadioSwitchTuning::Batch
|
||||
| (MC_SX1262_FAST_PROFILE_SWITCH ? mesh::RadioSwitchTuning::Warm : 0)
|
||||
| (MC_SX1262_FAST_FS ? mesh::RadioSwitchTuning::Fs : 0)
|
||||
| (MC_SX1262_FAST_PACKET_CACHE ? mesh::RadioSwitchTuning::Packet : 0)
|
||||
| (MC_SX1262_FAST_IQ_CACHE ? mesh::RadioSwitchTuning::Iq : 0)
|
||||
| (MC_SX1262_FAST_MODEM_CACHE ? mesh::RadioSwitchTuning::Modem : 0);
|
||||
}
|
||||
uint8_t profileSwitchCapabilities() const {
|
||||
uint8_t features = defaultProfileSwitchFeatures();
|
||||
#ifdef MC_SX1262_HIL
|
||||
// Lab images retain their explicit per-shortcut experiments even when a
|
||||
// production macro defaults off. Ordinary builds never gain these caps.
|
||||
if (MC_SX1262_FAST_PROFILE_SWITCH)
|
||||
features |= mesh::RadioSwitchTuning::Fs | mesh::RadioSwitchTuning::Packet
|
||||
| mesh::RadioSwitchTuning::Iq | mesh::RadioSwitchTuning::Modem;
|
||||
#endif
|
||||
return features;
|
||||
}
|
||||
uint8_t getProfileSwitchFeatures() const {
|
||||
uint8_t features = _profileSwitchFeatures;
|
||||
#ifdef MC_SX1262_HIL
|
||||
// Legacy lab commands directly select these booleans. Reflect them in
|
||||
// the same getter used by the production feature gates and new CLI.
|
||||
features &= ~(mesh::RadioSwitchTuning::Fs | mesh::RadioSwitchTuning::Packet
|
||||
| mesh::RadioSwitchTuning::Iq | mesh::RadioSwitchTuning::Modem);
|
||||
if (hilUseFsOnRetune) features |= mesh::RadioSwitchTuning::Fs;
|
||||
if (hilSkipUnchangedPacket) features |= mesh::RadioSwitchTuning::Packet;
|
||||
if (hilSkipUnchangedIq) features |= mesh::RadioSwitchTuning::Iq;
|
||||
if (hilSkipConfirmedModemQuery) features |= mesh::RadioSwitchTuning::Modem;
|
||||
#endif
|
||||
return features & profileSwitchCapabilities();
|
||||
}
|
||||
void setProfileSwitchFeatures(uint8_t features) {
|
||||
_profileSwitchFeatures = features & profileSwitchCapabilities();
|
||||
#ifdef MC_SX1262_HIL
|
||||
hilUseFsOnRetune = (_profileSwitchFeatures & mesh::RadioSwitchTuning::Fs) != 0;
|
||||
hilSkipUnchangedPacket = (_profileSwitchFeatures & mesh::RadioSwitchTuning::Packet) != 0;
|
||||
hilSkipUnchangedIq = (_profileSwitchFeatures & mesh::RadioSwitchTuning::Iq) != 0;
|
||||
hilSkipConfirmedModemQuery = (_profileSwitchFeatures & mesh::RadioSwitchTuning::Modem) != 0;
|
||||
#endif
|
||||
_profileSwitch.enabled = (_profileSwitchFeatures & mesh::RadioSwitchTuning::Warm) != 0;
|
||||
_profileSwitch.invalidate();
|
||||
}
|
||||
void setProfileSwitchOptimization(bool enabled) {
|
||||
setProfileSwitchFeatures(enabled ? profileSwitchCapabilities() : 0);
|
||||
}
|
||||
void beginProfileSwitch(bool continuousRx) { _profileSwitch.begin(continuousRx, standbyXOSC); }
|
||||
void endProfileSwitch(bool success) { _profileSwitch.end(success); }
|
||||
bool profileSwitchFailed() const { return _profileSwitch.failed(); }
|
||||
@@ -160,9 +205,9 @@ class CustomSX1262 : public SX1262 {
|
||||
const bool alreadyAwake = !_coldStandby && _profileSwitch.active && _profileSwitch.rxValid;
|
||||
if (!alreadyAwake) _profileSwitch.invalidate();
|
||||
int16_t rc;
|
||||
bool useFs = MC_SX1262_FAST_FS != 0;
|
||||
bool useFs = (getProfileSwitchFeatures() & mesh::RadioSwitchTuning::Fs) != 0;
|
||||
#ifdef MC_SX1262_HIL
|
||||
useFs = hilUseFsOnRetune;
|
||||
useFs = useFs && hilUseFsOnRetune;
|
||||
#endif
|
||||
if (alreadyAwake && useFs) {
|
||||
getMod()->setRfSwitchState(Module::MODE_IDLE);
|
||||
@@ -327,9 +372,9 @@ class CustomSX1262 : public SX1262 {
|
||||
}
|
||||
bool queryModem = true;
|
||||
#if MC_SX1262_FAST_MODEM_CACHE || defined(MC_SX1262_HIL)
|
||||
bool reuseModem = MC_SX1262_FAST_MODEM_CACHE != 0;
|
||||
bool reuseModem = (getProfileSwitchFeatures() & mesh::RadioSwitchTuning::Modem) != 0;
|
||||
#ifdef MC_SX1262_HIL
|
||||
reuseModem = hilSkipConfirmedModemQuery;
|
||||
reuseModem = reuseModem && hilSkipConfirmedModemQuery;
|
||||
#endif
|
||||
// rxValid is established only after full RX setup confirms LoRa, or by
|
||||
// resuming that same owned context. A successful owned standby, warm
|
||||
@@ -353,7 +398,8 @@ class CustomSX1262 : public SX1262 {
|
||||
const uint8_t effectiveLdro = ldroAuto
|
||||
? ((float(uint32_t(1) << sf) / bw) >= 16.0f ? 1 : 0)
|
||||
: ldrOptimize;
|
||||
const bool unchanged = _profileSwitch.matchesModulation(
|
||||
const bool unchanged = (_profileSwitchFeatures & mesh::RadioSwitchTuning::Batch)
|
||||
&& _profileSwitch.matchesModulation(
|
||||
sf, codes[index], cr - 4, effectiveLdro);
|
||||
const auto oldSf = spreadingFactor;
|
||||
const auto oldBw = bandwidth;
|
||||
@@ -744,17 +790,17 @@ class CustomSX1262 : public SX1262 {
|
||||
// Existing continuous RX already established buffer bases and the
|
||||
// MeshCore IRQ map (including preamble detection). No TX/CAD/sleep/
|
||||
// reset/staging may intervene without revoking that context.
|
||||
bool skipPacket = MC_SX1262_FAST_PACKET_CACHE != 0;
|
||||
bool skipPacket = (getProfileSwitchFeatures() & mesh::RadioSwitchTuning::Packet) != 0;
|
||||
#ifdef MC_SX1262_HIL
|
||||
skipPacket = hilSkipUnchangedPacket;
|
||||
skipPacket = skipPacket && hilSkipUnchangedPacket;
|
||||
#endif
|
||||
skipPacket = skipPacket && _profileSwitch.matchesPacket(
|
||||
preambleLengthLoRa, crcTypeLoRa, implicitLen,
|
||||
headerType, invertIQEnabled);
|
||||
#if MC_SX1262_FAST_IQ_CACHE || defined(MC_SX1262_HIL)
|
||||
bool reuseIq = MC_SX1262_FAST_IQ_CACHE != 0;
|
||||
bool reuseIq = (getProfileSwitchFeatures() & mesh::RadioSwitchTuning::Iq) != 0;
|
||||
#ifdef MC_SX1262_HIL
|
||||
reuseIq = hilSkipUnchangedIq;
|
||||
reuseIq = reuseIq && hilSkipUnchangedIq;
|
||||
#endif
|
||||
// Capture this capability BEFORE revoking rxValid for the command
|
||||
// sequence. Packet/IQ setters, protocol changes, sleep, reset, TX/CAD,
|
||||
|
||||
@@ -17,6 +17,15 @@ class CustomSX1262Wrapper : public RadioLibWrapper {
|
||||
bool _saved_standby_xosc = false;
|
||||
|
||||
protected:
|
||||
uint8_t getDriverSwitchFeatures() const override {
|
||||
return static_cast<const CustomSX1262*>(_radio)->getProfileSwitchFeatures();
|
||||
}
|
||||
uint8_t driverSwitchCapabilities() const override {
|
||||
return static_cast<const CustomSX1262*>(_radio)->profileSwitchCapabilities();
|
||||
}
|
||||
void setDriverSwitchFeatures(uint8_t features) override {
|
||||
static_cast<CustomSX1262*>(_radio)->setProfileSwitchFeatures(features);
|
||||
}
|
||||
int8_t readReceiveMode() override { return sx126xReceiveMode((SX126x*)_radio); }
|
||||
void beginProfileRetune(bool continuousRx) override {
|
||||
static_cast<CustomSX1262*>(_radio)->beginProfileSwitch(continuousRx);
|
||||
@@ -125,13 +134,22 @@ protected:
|
||||
#endif
|
||||
}
|
||||
|
||||
bool applyProfileModulation(float bw, uint8_t sf, uint8_t cr) {
|
||||
auto* radio = static_cast<CustomSX1262*>(_radio);
|
||||
if (getDriverSwitchFeatures() & mesh::RadioSwitchTuning::Batch)
|
||||
return radio->setLoRaModulationParams(bw, sf, cr) == RADIOLIB_ERR_NONE;
|
||||
return radio->setSpreadingFactor(sf) == RADIOLIB_ERR_NONE
|
||||
&& radio->setBandwidth(bw) == RADIOLIB_ERR_NONE
|
||||
&& radio->setCodingRate(cr) == RADIOLIB_ERR_NONE;
|
||||
}
|
||||
|
||||
bool applyParams(float freq, float bw, uint8_t sf, uint8_t cr) override {
|
||||
if (static_cast<CustomSX1262*>(_radio)->profileSwitchFailed()) return false;
|
||||
#if MESH_HIL_PROFILE_TIMING
|
||||
bool success = ((CustomSX1262 *)_radio)->setFrequency(freq) == RADIOLIB_ERR_NONE;
|
||||
mesh::profiletiming::mark(mesh::profiletiming::Frequency, mesh::profiletiming::readClock());
|
||||
if (success) {
|
||||
success = ((CustomSX1262 *)_radio)->setLoRaModulationParams(bw, sf, cr) == RADIOLIB_ERR_NONE;
|
||||
success = applyProfileModulation(bw, sf, cr);
|
||||
mesh::profiletiming::mark(mesh::profiletiming::Modulation, mesh::profiletiming::readClock());
|
||||
}
|
||||
if (success) {
|
||||
@@ -140,7 +158,7 @@ protected:
|
||||
}
|
||||
#else
|
||||
bool success = ((CustomSX1262 *)_radio)->setFrequency(freq) == RADIOLIB_ERR_NONE
|
||||
&& ((CustomSX1262 *)_radio)->setLoRaModulationParams(bw, sf, cr) == RADIOLIB_ERR_NONE
|
||||
&& applyProfileModulation(bw, sf, cr)
|
||||
&& updatePreamble(sf, bw);
|
||||
#endif
|
||||
if (!success) return false;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#pragma once
|
||||
#include <hal/Arduino/ArduinoHal.h>
|
||||
#include "TunableRadioHal.h"
|
||||
|
||||
#ifndef MC_SX1262_SPI_HZ
|
||||
#define MC_SX1262_SPI_HZ 8000000
|
||||
@@ -8,11 +8,13 @@
|
||||
// Opted-in ESP32 boards only. Keep RadioLib's transaction/CS, BUSY polling and
|
||||
// status parsing unchanged; send each full-duplex buffer in one SPI operation.
|
||||
// SX1262 boards retain their configured default; LR1110 boards pass 16 MHz.
|
||||
class ESP32BufferedRadioHal : public ArduinoHal {
|
||||
class ESP32BufferedRadioHal : public TunableRadioHal {
|
||||
public:
|
||||
explicit ESP32BufferedRadioHal(SPIClass& bus, uint32_t hz = MC_SX1262_SPI_HZ)
|
||||
: ArduinoHal(bus, SPISettings(hz, MSBFIRST, SPI_MODE0)) {}
|
||||
: TunableRadioHal(bus, hz, false, true,
|
||||
hz >= 16000000 ? 16000000 : 8000000) {}
|
||||
void spiTransfer(uint8_t* out, size_t len, uint8_t* in) override {
|
||||
if (len) spi->transferBytes(out, in, len);
|
||||
if (!bufferedTransfersEnabled()) ArduinoHal::spiTransfer(out, len, in);
|
||||
else if (len) spi->transferBytes(out, in, len);
|
||||
}
|
||||
};
|
||||
@@ -1,6 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include <hal/Arduino/ArduinoHal.h>
|
||||
#include "TunableRadioHal.h"
|
||||
#include "NrfRadioProfileExperimentConfig.h"
|
||||
|
||||
#ifndef MC_NRF_RADIO_PROFILE_PRIORITY
|
||||
@@ -26,13 +26,14 @@
|
||||
// nRF52840 SPIClass::transfer(uint8_t) starts one SPIM transaction per byte.
|
||||
// RadioLib sends whole command buffers; keep its CS, BUSY, and status handling
|
||||
// but transfer each buffer in one SPIM transaction. LR1110 supports 16 MHz SPI.
|
||||
class Nrf52BufferedRadioHal : public ArduinoHal {
|
||||
class Nrf52BufferedRadioHal : public TunableRadioHal {
|
||||
#if MC_NRF_RADIO_BUSY_SPIN_POLLS > 0
|
||||
uint8_t _busy_spin_polls_remaining = 0;
|
||||
#endif
|
||||
public:
|
||||
explicit Nrf52BufferedRadioHal(SPIClass& bus, uint32_t hz = 16000000)
|
||||
: ArduinoHal(bus, SPISettings(hz, MSBFIRST, SPI_MODE0)) {}
|
||||
: TunableRadioHal(bus, hz, false, true,
|
||||
hz >= 16000000 ? 16000000 : 8000000) {}
|
||||
|
||||
#if MC_NRF_RADIO_PROFILE_PRIORITY || MC_NRF_RADIO_PROFILE_USB_IRQ_GUARD
|
||||
void spiBeginTransaction() override {
|
||||
@@ -50,7 +51,8 @@ public:
|
||||
|
||||
void spiTransfer(uint8_t* out, size_t len, uint8_t* in) override {
|
||||
if (!len) return;
|
||||
spi->transfer(out, in, len);
|
||||
if (!bufferedTransfersEnabled()) ArduinoHal::spiTransfer(out, len, in);
|
||||
else spi->transfer(out, in, len);
|
||||
#if MC_NRF_RADIO_BUSY_SPIN_POLLS > 0
|
||||
_busy_spin_polls_remaining = MC_NRF_RADIO_BUSY_SPIN_POLLS;
|
||||
#endif
|
||||
|
||||
@@ -6,6 +6,10 @@
|
||||
#include "ProfileSwitchTiming.h"
|
||||
#include "NrfRadioPriorityLease.h"
|
||||
#include "NrfRadioUsbIrqGuard.h"
|
||||
#if defined(ESP32_PLATFORM) || defined(NRF52_PLATFORM) || defined(RP2040_PLATFORM)
|
||||
#include "TunableRadioHal.h"
|
||||
#define MESH_TUNABLE_RADIO_HAL 1
|
||||
#endif
|
||||
#if MESH_HIL_PROFILE_TIMING
|
||||
namespace mesh { namespace profiletiming {
|
||||
extern "C" {
|
||||
@@ -443,6 +447,69 @@ void RadioLibWrapper::endReconfigure(bool resume_rx) {
|
||||
if (resume_rx) startRecv();
|
||||
}
|
||||
|
||||
mesh::RadioSwitchCapabilities RadioLibWrapper::switchTuningCapabilities() const {
|
||||
mesh::RadioSwitchCapabilities caps;
|
||||
#if MESH_TUNABLE_RADIO_HAL
|
||||
auto* hal = TunableRadioHal::find(_radio->getMod()->hal);
|
||||
if (hal) caps = hal->capabilities();
|
||||
#endif
|
||||
caps.flags |= driverSwitchCapabilities();
|
||||
return caps;
|
||||
}
|
||||
|
||||
mesh::RadioSwitchTuning RadioLibWrapper::switchTuning() const {
|
||||
mesh::RadioSwitchTuning value;
|
||||
#if MESH_TUNABLE_RADIO_HAL
|
||||
auto* hal = TunableRadioHal::find(_radio->getMod()->hal);
|
||||
if (hal) value = hal->getTuning();
|
||||
#endif
|
||||
value.flags |= getDriverSwitchFeatures();
|
||||
return value;
|
||||
}
|
||||
|
||||
uint32_t RadioLibWrapper::switchSpiHz() const {
|
||||
#if MESH_TUNABLE_RADIO_HAL
|
||||
auto* hal = TunableRadioHal::find(_radio->getMod()->hal);
|
||||
if (hal) return hal->effectiveHz();
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
|
||||
mesh::RadioParamApplyResult RadioLibWrapper::trySetSwitchTuning(
|
||||
const mesh::RadioSwitchTuning& value,
|
||||
bool (*persist)(void*, const mesh::RadioSwitchTuning&), void* context) {
|
||||
using Result = mesh::RadioParamApplyResult;
|
||||
if (!mesh::validRadioSwitchTuning(value, switchTuningCapabilities())) return Result::FAILED;
|
||||
const auto previous = switchTuning();
|
||||
if (value == previous) return !persist || persist(context, value) ? Result::APPLIED : Result::FAILED;
|
||||
const uint8_t resume_rx = beginReconfigure();
|
||||
if (resume_rx > 1) return resume_rx == 2 ? Result::BUSY : Result::FAILED;
|
||||
|
||||
// These setters change policy/SPISettings only, never an SPI peripheral or
|
||||
// pin mapping. Clearing owned caches forces the next retune to prove RX.
|
||||
configureBootSwitchTuning(value);
|
||||
_profiles.resetSwitchTest();
|
||||
endReconfigure(resume_rx);
|
||||
const bool resumed = !resume_rx || isInRecvMode();
|
||||
if (resumed && (!persist || persist(context, value))) return Result::APPLIED;
|
||||
|
||||
// There is no SPI in these setters, including after an RX completion was
|
||||
// queued during a filesystem save. Never rearm or clear that unread FIFO.
|
||||
configureBootSwitchTuning(previous);
|
||||
if (!resumed) endReconfigure(resume_rx);
|
||||
return Result::FAILED;
|
||||
}
|
||||
|
||||
bool RadioLibWrapper::configureBootSwitchTuning(const mesh::RadioSwitchTuning& value) {
|
||||
if (!mesh::validRadioSwitchTuning(value, switchTuningCapabilities())) return false;
|
||||
#if MESH_TUNABLE_RADIO_HAL
|
||||
auto* hal = TunableRadioHal::find(_radio->getMod()->hal);
|
||||
if (hal && !hal->setTuning(value)) return false;
|
||||
#endif
|
||||
setDriverSwitchFeatures(value.flags);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool RadioLibWrapper::restoreAfterDeepInit() {
|
||||
#if MESH_ENABLE_TX_BEACONS
|
||||
if (_beacon_owned) cacheBeaconRestoreParams();
|
||||
|
||||
@@ -328,6 +328,9 @@ 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;
|
||||
virtual uint8_t getDriverSwitchFeatures() const { return 0; }
|
||||
virtual uint8_t driverSwitchCapabilities() const { return 0; }
|
||||
virtual void setDriverSwitchFeatures(uint8_t) {}
|
||||
virtual int16_t applyCachedTxPower(int8_t dbm) {
|
||||
return _radio->setOutputPower(dbm);
|
||||
}
|
||||
@@ -483,6 +486,13 @@ public:
|
||||
#endif
|
||||
mesh::RadioProfiles* profiles() override { return &_profiles; }
|
||||
const mesh::RadioProfiles* profiles() const override { return &_profiles; }
|
||||
mesh::RadioSwitchCapabilities switchTuningCapabilities() const override;
|
||||
mesh::RadioSwitchTuning switchTuning() const override;
|
||||
uint32_t switchSpiHz() const override;
|
||||
bool configureBootSwitchTuning(const mesh::RadioSwitchTuning& tuning) override;
|
||||
mesh::RadioParamApplyResult trySetSwitchTuning(const mesh::RadioSwitchTuning& tuning,
|
||||
bool (*persist)(void*, const mesh::RadioSwitchTuning&) = nullptr,
|
||||
void* context = nullptr) override;
|
||||
bool validateProfile(const mesh::RadioProfileParams& params) const override;
|
||||
uint8_t receiveProfile() const override { return _active_profile; }
|
||||
uint32_t receiveProfileGeneration() const override { return _profile_generation; }
|
||||
|
||||
@@ -11,15 +11,16 @@
|
||||
#include "ESP32BufferedRadioHal.h"
|
||||
using SX126xBufferedRadioHalBase = ESP32BufferedRadioHal;
|
||||
#elif defined(RP2040_PLATFORM)
|
||||
#include <hal/Arduino/ArduinoHal.h>
|
||||
#include "TunableRadioHal.h"
|
||||
// Earle Philhower's HardwareSPI bulk API retains full-duplex blocking
|
||||
// completion while filling/draining the controller FIFO in one call.
|
||||
class SX126xBufferedRadioHalBase : public ArduinoHal {
|
||||
class SX126xBufferedRadioHalBase : public TunableRadioHal {
|
||||
public:
|
||||
explicit SX126xBufferedRadioHalBase(SPIClass& bus, uint32_t hz)
|
||||
: ArduinoHal(bus, SPISettings(hz, MSBFIRST, SPI_MODE0)) {}
|
||||
: TunableRadioHal(bus, hz, false, true) {}
|
||||
void spiTransfer(uint8_t* out, size_t len, uint8_t* in) override {
|
||||
if (len) spi->transfer(out, in, len);
|
||||
if (!bufferedTransfersEnabled()) ArduinoHal::spiTransfer(out, len, in);
|
||||
else if (len) spi->transfer(out, in, len);
|
||||
}
|
||||
};
|
||||
#else
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
#pragma once
|
||||
|
||||
#include <hal/Arduino/ArduinoHal.h>
|
||||
#include "../RadioSwitchTuning.h"
|
||||
|
||||
// A radio keeps the board-selected SPI controller and its original lifetime
|
||||
// owner. Runtime tuning only changes transaction settings and transfer style;
|
||||
// it must never remap an nRF controller or restart a shared storage/display bus.
|
||||
class TunableRadioHal : public ArduinoHal {
|
||||
TunableRadioHal* _next;
|
||||
const uint32_t _defaultHz;
|
||||
const uint8_t _spiClocks;
|
||||
const bool _canBuffer;
|
||||
uint8_t _spiMHz = 0;
|
||||
bool _bufferEnabled;
|
||||
|
||||
static TunableRadioHal*& registry() {
|
||||
static TunableRadioHal* head = nullptr;
|
||||
return head;
|
||||
}
|
||||
|
||||
protected:
|
||||
bool bufferedTransfersEnabled() const { return _bufferEnabled; }
|
||||
|
||||
public:
|
||||
explicit TunableRadioHal(SPIClass& bus, uint32_t hz = 8000000,
|
||||
bool initializeBus = false, bool canBuffer = false,
|
||||
uint32_t maxHz = 8000000)
|
||||
: ArduinoHal(bus, SPISettings(hz, MSBFIRST, SPI_MODE0)),
|
||||
_next(registry()), _defaultHz(hz),
|
||||
_spiClocks(2 | 4 | 8 | (maxHz >= 16000000 ? 16 : 0)),
|
||||
_canBuffer(canBuffer), _bufferEnabled(canBuffer) {
|
||||
initInterface = initializeBus;
|
||||
registry() = this;
|
||||
}
|
||||
|
||||
~TunableRadioHal() override {
|
||||
TunableRadioHal** entry = ®istry();
|
||||
while (*entry && *entry != this) entry = &(*entry)->_next;
|
||||
if (*entry) *entry = _next;
|
||||
}
|
||||
|
||||
TunableRadioHal(const TunableRadioHal&) = delete;
|
||||
TunableRadioHal& operator=(const TunableRadioHal&) = delete;
|
||||
|
||||
// Pointer identity avoids RTTI and safely rejects unrelated board HALs.
|
||||
static TunableRadioHal* find(RadioLibHal* hal) {
|
||||
for (TunableRadioHal* entry = registry(); entry; entry = entry->_next)
|
||||
if (static_cast<RadioLibHal*>(entry) == hal) return entry;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
mesh::RadioSwitchTuning getTuning() const {
|
||||
mesh::RadioSwitchTuning result;
|
||||
result.flags = _bufferEnabled ? mesh::RadioSwitchTuning::Buffer : 0;
|
||||
result.spi_mhz = _spiMHz;
|
||||
return result;
|
||||
}
|
||||
|
||||
mesh::RadioSwitchCapabilities capabilities() const {
|
||||
mesh::RadioSwitchCapabilities result;
|
||||
result.flags = _canBuffer ? mesh::RadioSwitchTuning::Buffer : 0;
|
||||
result.spi_clocks = _spiClocks;
|
||||
return result;
|
||||
}
|
||||
|
||||
bool setTuning(const mesh::RadioSwitchTuning& tuning) {
|
||||
mesh::RadioSwitchTuning value = tuning;
|
||||
value.flags &= mesh::RadioSwitchTuning::Buffer;
|
||||
if (!mesh::validRadioSwitchTuning(value, capabilities())) return false;
|
||||
_spiMHz = value.spi_mhz;
|
||||
_bufferEnabled = value.flags & mesh::RadioSwitchTuning::Buffer;
|
||||
spiSettings = SPISettings(effectiveHz(), MSBFIRST, SPI_MODE0);
|
||||
return true;
|
||||
}
|
||||
|
||||
uint32_t defaultHz() const { return _defaultHz; }
|
||||
uint32_t effectiveHz() const {
|
||||
return _spiMHz ? static_cast<uint32_t>(_spiMHz) * 1000000UL : _defaultHz;
|
||||
}
|
||||
};
|
||||
+400
@@ -0,0 +1,400 @@
|
||||
// Exercise the production shared parser and persistence with deterministic
|
||||
// radio admission and storage failures. The physical driver has its own tests.
|
||||
#define main existing_cli_main
|
||||
#include "cli_test.cpp"
|
||||
#undef main
|
||||
|
||||
struct TuningRadio : Radio {
|
||||
mesh::RadioSwitchCapabilities caps;
|
||||
mesh::RadioSwitchTuning tuning;
|
||||
mesh::RadioParamApplyResult admission = mesh::RadioParamApplyResult::APPLIED;
|
||||
uint32_t default_spi_hz = 8888888;
|
||||
unsigned boot_configurations = 0, attempts = 0, changes = 0, persists = 0;
|
||||
TuningRadio() {
|
||||
caps.flags = 127;
|
||||
caps.spi_clocks = 0x1e;
|
||||
tuning.flags = caps.flags;
|
||||
}
|
||||
mesh::RadioSwitchCapabilities switchTuningCapabilities() const override { return caps; }
|
||||
mesh::RadioSwitchTuning switchTuning() const override { return tuning; }
|
||||
uint32_t switchSpiHz() const override {
|
||||
return tuning.spi_mhz ? uint32_t(tuning.spi_mhz) * 1000000UL : default_spi_hz;
|
||||
}
|
||||
bool configureBootSwitchTuning(const mesh::RadioSwitchTuning& value) override {
|
||||
++boot_configurations;
|
||||
if (!mesh::validRadioSwitchTuning(value, caps)) return false;
|
||||
tuning = value;
|
||||
return true;
|
||||
}
|
||||
mesh::RadioParamApplyResult trySetSwitchTuning(const mesh::RadioSwitchTuning& value,
|
||||
bool (*persist)(void*, const mesh::RadioSwitchTuning&) = nullptr,
|
||||
void* context = nullptr) override {
|
||||
++attempts;
|
||||
if (!mesh::validRadioSwitchTuning(value, caps)) return mesh::RadioParamApplyResult::FAILED;
|
||||
if (admission != mesh::RadioParamApplyResult::APPLIED) return admission;
|
||||
const auto previous = tuning;
|
||||
tuning = value;
|
||||
if (persist) {
|
||||
++persists;
|
||||
if (!persist(context, value)) {
|
||||
tuning = previous;
|
||||
return mesh::RadioParamApplyResult::FAILED;
|
||||
}
|
||||
}
|
||||
if (value != previous) {
|
||||
++changes;
|
||||
p.resetSwitchTest();
|
||||
}
|
||||
return mesh::RadioParamApplyResult::APPLIED;
|
||||
}
|
||||
};
|
||||
|
||||
struct TuningFixture {
|
||||
MemoryFS fs;
|
||||
Clock clock;
|
||||
TuningRadio radio;
|
||||
mesh::RadioProfileCLI cli;
|
||||
char reply[160];
|
||||
TuningFixture() { g_mock_millis = 0; cli.begin(&fs, &radio, &clock); }
|
||||
const char* cmd(const char* text, bool success = true, bool remote = false) {
|
||||
memset(reply, 0x55, sizeof(reply));
|
||||
assert(cli.handle(text, reply, sizeof(reply), remote));
|
||||
assert(memchr(reply, 0, sizeof(reply)));
|
||||
if (!strncmp(text, "set ", 4)) {
|
||||
if ((!strncmp(reply, "OK", 2)) != success) {
|
||||
fprintf(stderr, "%s -> %s\n", text, reply);
|
||||
assert(false);
|
||||
}
|
||||
}
|
||||
return reply;
|
||||
}
|
||||
void advance(uint32_t milliseconds) { g_mock_millis += milliseconds; cli.loop(); }
|
||||
};
|
||||
|
||||
static void crc(std::vector<uint8_t>& image) {
|
||||
assert(image.size() == 24);
|
||||
uint32_t value = 0xffffffffU;
|
||||
for (size_t i = 0; i < image.size() - 4; ++i) {
|
||||
value ^= image[i];
|
||||
for (unsigned bit = 0; bit < 8; ++bit)
|
||||
value = (value >> 1) ^ ((value & 1) ? 0xedb88320U : 0);
|
||||
}
|
||||
memcpy(image.data() + image.size() - 4, &value, 4);
|
||||
}
|
||||
|
||||
static void controlsAndPersistence() {
|
||||
TuningFixture f;
|
||||
assert(f.radio.tuning.flags == 127 && !f.radio.tuning.spi_mhz);
|
||||
assert(f.fs.files.empty());
|
||||
const char* features[] = {"warm", "fs", "packet", "iq", "modem", "batch", "buffer"};
|
||||
for (unsigned i = 0; i < 7; ++i) {
|
||||
char command[48];
|
||||
snprintf(command, sizeof(command), "get radio.fast.%s", features[i]);
|
||||
assert(!strcmp(f.cmd(command), "> on"));
|
||||
snprintf(command, sizeof(command), "set radio.fast.%s off", features[i]);
|
||||
f.cmd(command);
|
||||
assert(!(f.radio.tuning.flags & (1U << i)));
|
||||
const auto expected = f.radio.tuning;
|
||||
TuningRadio reboot;
|
||||
mesh::RadioProfileCLI restored;
|
||||
restored.begin(&f.fs, &reboot, &f.clock);
|
||||
assert(reboot.tuning == expected);
|
||||
// An absent radio2 record must not prevent independent fast-policy restore.
|
||||
assert(!f.fs.files.count("/radio_profiles"));
|
||||
snprintf(command, sizeof(command), "set radio.fast.%s on", features[i]);
|
||||
f.cmd(command);
|
||||
assert(f.radio.tuning.flags & (1U << i));
|
||||
}
|
||||
for (unsigned mhz : {2, 4, 8, 16, 0}) {
|
||||
char command[48];
|
||||
snprintf(command, sizeof(command), "set radio.fast.spi %u", mhz);
|
||||
f.cmd(command);
|
||||
assert(f.radio.tuning.spi_mhz == mhz);
|
||||
TuningRadio reboot;
|
||||
mesh::RadioProfileCLI restored;
|
||||
restored.begin(&f.fs, &reboot, &f.clock);
|
||||
assert(reboot.tuning == f.radio.tuning);
|
||||
assert(reboot.switchSpiHz() == (mhz ? mhz * 1000000UL : 8888888));
|
||||
}
|
||||
assert(strstr(f.cmd("get radio.fast"), "8888888"));
|
||||
assert(!strcmp(f.cmd("get radio.fast.caps"),
|
||||
"> features: warm fs packet iq modem batch buffer; spi=0,2,4,8,16"));
|
||||
f.cmd("set radio.fast off");
|
||||
assert(!f.radio.tuning.flags && f.radio.tuning.spi_mhz == 2);
|
||||
f.cmd("set radio.fast on");
|
||||
assert(f.radio.tuning.flags == f.radio.caps.flags && !f.radio.tuning.spi_mhz);
|
||||
for (size_t size = 1; size <= sizeof(f.reply); ++size) {
|
||||
char buffer[161];
|
||||
memset(buffer, 0x55, sizeof(buffer));
|
||||
for (const char* command : {"get radio.fast", "get radio.fast.caps"}) {
|
||||
memset(buffer, 0x55, sizeof(buffer));
|
||||
assert(f.cli.handle(command, buffer, size));
|
||||
assert(memchr(buffer, 0, size) && buffer[size] == 0x55);
|
||||
}
|
||||
}
|
||||
puts("Fast tuning: independent features, board clocks, saved restore and bounded output passed");
|
||||
}
|
||||
|
||||
static void strictInputsAndUnsupportedFeatures() {
|
||||
TuningFixture f;
|
||||
const auto original = f.radio.tuning;
|
||||
for (const char* command : {
|
||||
"set radio.fast", "set radio.fast maybe", "set radio.fast on extra",
|
||||
"set radio.fast.fs", "set radio.fast.fs ON", "set radio.fast.fs on extra",
|
||||
"set radio.fast.spi", "set radio.fast.spi -2", "set radio.fast.spi +8",
|
||||
"set radio.fast.spi 8mhz", "set radio.fast.spi 3", "set radio.fast.spi 32",
|
||||
"set radio.fast.spi 256", "set radio.fast.spi 8 16", "set radio.fast.caps on"}) {
|
||||
f.cmd(command, false);
|
||||
assert(f.radio.tuning == original && f.fs.files.empty());
|
||||
}
|
||||
for (const char* command : {"get radio.fast on", "get radio.fast.fs extra",
|
||||
"get radio.fast.spi extra", "get radio.fast.caps extra"}) {
|
||||
assert(strstr(f.cmd(command), "Error"));
|
||||
assert(f.radio.tuning == original && f.fs.files.empty());
|
||||
}
|
||||
f.radio.caps.flags &= ~mesh::RadioSwitchTuning::Buffer;
|
||||
f.radio.tuning.flags &= f.radio.caps.flags;
|
||||
f.radio.caps.spi_clocks &= ~(1U << 4);
|
||||
const auto limited = f.radio.tuning;
|
||||
f.cmd("set radio.fast.buffer on", false);
|
||||
f.cmd("set radio.fast.spi 16", false);
|
||||
assert(f.radio.tuning == limited && f.fs.files.empty());
|
||||
assert(strstr(f.cmd("get radio.fast.buffer"), "unsupported"));
|
||||
assert(!strstr(f.cmd("get radio.fast.caps"), "buffer"));
|
||||
assert(strstr(f.reply, "; spi=0,2,4,8") && !strstr(f.reply, ",16"));
|
||||
f.cmd("set radio.fast off");
|
||||
f.cmd("set radio.fast on");
|
||||
assert(f.radio.tuning.flags == f.radio.caps.flags);
|
||||
f.radio.caps.spi_clocks = 0;
|
||||
assert(strstr(f.cmd("get radio.fast.caps"), "; spi=na"));
|
||||
assert(strstr(f.cmd("get radio.fast"), "> spi=na"));
|
||||
f.cmd("set radio.fast.spi 0", false);
|
||||
const auto no_clock_control = f.radio.tuning;
|
||||
f.cmd("set radio.fast off");
|
||||
assert(!f.radio.tuning.flags && !f.radio.tuning.spi_mhz);
|
||||
f.cmd("set radio.fast on");
|
||||
assert(f.radio.tuning == no_clock_control);
|
||||
puts("Fast tuning: strict parsing and unsupported controls passed");
|
||||
}
|
||||
|
||||
static void localOnlyAndBusyTransactions() {
|
||||
TuningFixture f;
|
||||
f.cmd("set radio.fast.fs off");
|
||||
const auto original = f.radio.tuning;
|
||||
const auto saved = f.fs.files["/radio_fast"];
|
||||
const unsigned persists = f.radio.persists;
|
||||
for (const auto result : {mesh::RadioParamApplyResult::BUSY,
|
||||
mesh::RadioParamApplyResult::FAILED}) {
|
||||
f.radio.admission = result;
|
||||
f.cmd("set radio.fast.fs on", false);
|
||||
assert(f.radio.tuning == original && f.radio.persists == persists);
|
||||
assert(f.fs.files["/radio_fast"] == saved);
|
||||
}
|
||||
f.radio.admission = mesh::RadioParamApplyResult::APPLIED;
|
||||
assert(strstr(f.cmd("set radio.fast.fs on", false, true), "local"));
|
||||
f.cli.beginReplyCommand();
|
||||
assert(strstr(f.cmd("set radio.fast.fs on", false), "local"));
|
||||
assert(strstr(f.cmd("set radio.fast.fs on", false, true), "local"));
|
||||
f.cli.endReplyCommand();
|
||||
assert(!f.cli.hasReplyMutation());
|
||||
assert(f.radio.tuning == original && f.fs.files["/radio_fast"] == saved);
|
||||
assert(!strcmp(f.cmd("get radio.fast.fs", true, true), "> off"));
|
||||
puts("Fast tuning: busy admission and local-only changes passed");
|
||||
}
|
||||
|
||||
static void storageFailureKeepsCommittedPolicy() {
|
||||
for (unsigned failure = 0; failure < 6; ++failure) {
|
||||
TuningFixture f;
|
||||
f.cmd("set radio.fast.fs off");
|
||||
const auto original = f.radio.tuning;
|
||||
const auto saved = f.fs.files["/radio_fast"];
|
||||
if (failure == 0) f.fs.fail_write = true;
|
||||
else if (failure == 1) f.fs.fail_write_after = 7;
|
||||
else if (failure == 2) f.fs.fail_read_after = 7;
|
||||
else if (failure == 3) f.fs.fail_rename = 1;
|
||||
else if (failure == 4) f.fs.fail_rename = 2;
|
||||
else {
|
||||
f.fs.files["/radio_fast.tmp"] = {0};
|
||||
f.fs.fail_remove = true;
|
||||
}
|
||||
f.cmd("set radio.fast.fs on", false);
|
||||
assert(f.radio.tuning == original);
|
||||
f.fs.fail_write = f.fs.fail_remove = false;
|
||||
f.fs.fail_write_after = f.fs.fail_read_after = -1;
|
||||
f.fs.fail_rename = 0;
|
||||
TuningRadio reboot;
|
||||
mesh::RadioProfileCLI restored;
|
||||
restored.begin(&f.fs, &reboot, &f.clock);
|
||||
assert(reboot.tuning == original);
|
||||
assert(f.fs.files["/radio_fast"] == saved);
|
||||
}
|
||||
puts("Fast tuning: short writes, readback and atomic rename failures passed");
|
||||
}
|
||||
|
||||
static void bootRecoveryAndDeferredAdmission() {
|
||||
for (unsigned damaged_primary = 0; damaged_primary < 4; ++damaged_primary) {
|
||||
TuningFixture f;
|
||||
f.cmd("set radio.fast.modem off");
|
||||
const auto expected = f.radio.tuning;
|
||||
const auto saved = f.fs.files["/radio_fast"];
|
||||
assert(f.fs.rename("/radio_fast", "/radio_fast.bak"));
|
||||
if (damaged_primary == 1) f.fs.files["/radio_fast"] = {0};
|
||||
else if (damaged_primary >= 2) {
|
||||
auto& invalid = f.fs.files["/radio_fast"] = saved;
|
||||
invalid[damaged_primary == 2 ? 3 : 4] = damaged_primary == 2 ? 128 : 3;
|
||||
crc(invalid);
|
||||
}
|
||||
TuningRadio reboot;
|
||||
mesh::RadioProfileCLI restored;
|
||||
restored.begin(&f.fs, &reboot, &f.clock);
|
||||
assert(reboot.tuning == expected);
|
||||
assert(f.fs.files["/radio_fast"] == saved);
|
||||
}
|
||||
{
|
||||
TuningFixture f;
|
||||
f.cmd("set radio.fast.packet off");
|
||||
const auto expected = f.radio.tuning;
|
||||
const auto saved = f.fs.files;
|
||||
TuningRadio reboot;
|
||||
reboot.admission = mesh::RadioParamApplyResult::BUSY;
|
||||
mesh::RadioProfileCLI restored;
|
||||
restored.begin(&f.fs, &reboot, &f.clock);
|
||||
assert(reboot.tuning != expected && f.fs.files == saved);
|
||||
restored.loop();
|
||||
assert(reboot.tuning != expected);
|
||||
reboot.admission = mesh::RadioParamApplyResult::APPLIED;
|
||||
g_mock_millis += 1000;
|
||||
restored.loop();
|
||||
assert(reboot.tuning == expected && f.fs.files == saved);
|
||||
}
|
||||
{
|
||||
TuningFixture f;
|
||||
f.cmd("set radio.fast.iq off");
|
||||
const auto expected = f.radio.tuning;
|
||||
f.fs.files["/radio_profiles"] = {0};
|
||||
TuningRadio reboot;
|
||||
mesh::RadioProfileCLI restored;
|
||||
restored.begin(&f.fs, &reboot, &f.clock);
|
||||
assert(reboot.tuning == expected); // A corrupt radio2 image is isolated.
|
||||
}
|
||||
puts("Fast tuning: interrupted commit, deferred boot admission and store isolation passed");
|
||||
}
|
||||
|
||||
static void malformedAndUnreadableImagesStayProtected() {
|
||||
for (unsigned corruption = 0; corruption < 5; ++corruption) {
|
||||
TuningFixture f;
|
||||
f.cmd("set radio.fast.fs off");
|
||||
auto& image = f.fs.files["/radio_fast"];
|
||||
if (corruption == 0) image[0] ^= 1;
|
||||
else if (corruption == 1) { image[3] = 128; crc(image); }
|
||||
else if (corruption == 2) { image[4] = 3; crc(image); }
|
||||
else if (corruption == 3) { image[5] = 1; crc(image); }
|
||||
else { image[2] = 2; crc(image); }
|
||||
const auto damaged = image;
|
||||
TuningRadio reboot;
|
||||
mesh::RadioProfileCLI restored;
|
||||
restored.begin(&f.fs, &reboot, &f.clock);
|
||||
assert(reboot.tuning.flags == 127 && !reboot.tuning.spi_mhz);
|
||||
assert(f.fs.files["/radio_fast"] == damaged);
|
||||
}
|
||||
{
|
||||
TuningFixture f;
|
||||
f.cmd("set radio.fast.fs off");
|
||||
const auto saved = f.fs.files["/radio_fast"];
|
||||
f.fs.fail_read_open = true;
|
||||
TuningRadio reboot;
|
||||
mesh::RadioProfileCLI restored;
|
||||
restored.begin(&f.fs, &reboot, &f.clock);
|
||||
f.fs.fail_read_open = false;
|
||||
assert(restored.handle("set radio.fast.fs off", f.reply, sizeof(f.reply)));
|
||||
assert(strstr(f.reply, "Error"));
|
||||
assert(f.fs.files["/radio_fast"] == saved);
|
||||
}
|
||||
puts("Fast tuning: corrupt, unsupported, reserved and unreadable images protected");
|
||||
}
|
||||
|
||||
static void bootRetriesAreBoundedAcrossRollover() {
|
||||
for (bool rollover : {false, true}) {
|
||||
for (const auto admission : {mesh::RadioParamApplyResult::BUSY,
|
||||
mesh::RadioParamApplyResult::FAILED}) {
|
||||
TuningFixture f;
|
||||
f.cmd("set radio.fast.fs off");
|
||||
const auto expected = f.radio.tuning;
|
||||
const auto saved = f.fs.files;
|
||||
TuningRadio reboot;
|
||||
reboot.admission = admission;
|
||||
mesh::RadioProfileCLI restored;
|
||||
g_mock_millis = rollover ? UINT32_MAX - 500 : 100;
|
||||
restored.begin(&f.fs, &reboot, &f.clock);
|
||||
assert(reboot.attempts == 1);
|
||||
// Hundreds of main-loop visits must not keep trying a rejected clock or
|
||||
// RX policy. Millisecond rollover does not make its deadline due early.
|
||||
for (unsigned i = 0; i < 999; ++i) {
|
||||
++g_mock_millis;
|
||||
restored.loop();
|
||||
assert(reboot.attempts == 1);
|
||||
}
|
||||
++g_mock_millis;
|
||||
restored.loop();
|
||||
assert(reboot.attempts == 2 && reboot.tuning != expected);
|
||||
restored.loop();
|
||||
assert(reboot.attempts == 2);
|
||||
reboot.admission = mesh::RadioParamApplyResult::APPLIED;
|
||||
g_mock_millis += 1000;
|
||||
restored.loop();
|
||||
assert(reboot.attempts == 3 && reboot.tuning == expected);
|
||||
assert(f.fs.files == saved && !reboot.persists);
|
||||
}
|
||||
}
|
||||
puts("Fast tuning: failed/busy boot retries bounded across timer rollover");
|
||||
}
|
||||
|
||||
static void savedSlowClockPrecedesRadioInitialization() {
|
||||
for (bool backup : {false, true}) {
|
||||
TuningFixture f;
|
||||
f.cmd("set radio.fast off");
|
||||
const auto expected = f.radio.tuning;
|
||||
assert(!expected.flags && expected.spi_mhz == 2);
|
||||
if (backup) {
|
||||
assert(f.fs.rename("/radio_fast", "/radio_fast.bak"));
|
||||
f.fs.files["/radio_fast"] = {0};
|
||||
}
|
||||
TuningRadio reboot;
|
||||
reboot.admission = mesh::RadioParamApplyResult::FAILED;
|
||||
assert(reboot.switchSpiHz() == 8888888);
|
||||
mesh::RadioProfileCLI::configureBootTuning(&f.fs, reboot);
|
||||
// This is the policy used by the chip's first initialization transaction.
|
||||
// No runtime admission, RX restart, or persistence callback is permitted
|
||||
// before that initialization; a runtime failure cannot veto boot policy.
|
||||
assert(reboot.tuning == expected && reboot.switchSpiHz() == 2000000);
|
||||
assert(reboot.boot_configurations == 1 && !reboot.attempts);
|
||||
assert(!reboot.changes && !reboot.persists);
|
||||
reboot.admission = mesh::RadioParamApplyResult::APPLIED;
|
||||
mesh::RadioProfileCLI restored;
|
||||
restored.begin(&f.fs, &reboot, &f.clock);
|
||||
assert(reboot.tuning == expected);
|
||||
}
|
||||
for (unsigned unavailable = 0; unavailable < 4; ++unavailable) {
|
||||
TuningFixture f;
|
||||
if (unavailable != 0) f.cmd("set radio.fast off");
|
||||
if (unavailable == 1) f.fs.files["/radio_fast"][0] ^= 1;
|
||||
if (unavailable == 2) f.fs.fail_read_open = true;
|
||||
TuningRadio reboot;
|
||||
const auto defaults = reboot.tuning;
|
||||
mesh::RadioProfileCLI::configureBootTuning(unavailable == 3 ? nullptr : &f.fs, reboot);
|
||||
assert(reboot.tuning == defaults && reboot.switchSpiHz() == 8888888);
|
||||
assert(!reboot.boot_configurations && !reboot.attempts && !reboot.persists);
|
||||
}
|
||||
puts("Fast tuning: saved fallback clock configured before first radio initialization");
|
||||
}
|
||||
|
||||
int main() {
|
||||
controlsAndPersistence();
|
||||
strictInputsAndUnsupportedFeatures();
|
||||
localOnlyAndBusyTransactions();
|
||||
storageFailureKeepsCommittedPolicy();
|
||||
bootRecoveryAndDeferredAdmission();
|
||||
malformedAndUnreadableImagesStayProtected();
|
||||
bootRetriesAreBoundedAcrossRollover();
|
||||
savedSlowClockPrecedesRadioInitialization();
|
||||
}
|
||||
@@ -35,6 +35,9 @@ PROFILES = {
|
||||
# suffix. Non-RAK call hashes bind print/println order as well as all text bytes.
|
||||
GOLDEN = {'rak_full': {'text': '9a68ce5a10905a201c3bddeb19f4819ce03c6dcaff248cff9c140803b70b14a7', 'calls': '56ed09dee7d53881b2c9fcf3f4ec9b6223e1562ce63e110a065e46706702b53b'}, 'nrf_full': {'text': 'b114ac12ae2ea01b7c47609b72f7eafb616c1dbeb7fb796a95b1377465fd9105', 'calls': '4973a7f21887d07b129cce4ae4cc8145f3f35e664bae09be658957210440d52e'}, 'nrf_minimal': {'text': '15fa8f290cd8c75cfde2fd2ac1cb5b5151a6755c68448fb2793dde3f27669033', 'calls': '6063fd1a699f9c0b588252bd8ce5922e0ff31cec6bbd24522789284ab2a37644'}, 'esp_full': {'text': '1e3b6781c81b5603f7092108e5e309543c60cef8ff4d7cb41a891f67396f716f', 'calls': 'b6335df2bd05b9cab44a7933469b76c5b2611c4b81057436a959feaf3bfbaf60'}}
|
||||
|
||||
FAST_HELP = (b' get radio.fast / get radio.fast.caps (supported saved controls)\r\n'
|
||||
b' set radio.fast <on|off> / set radio.fast.spi <0|2|4|8|16>\r\n')
|
||||
|
||||
HARNESS = r'''
|
||||
#include "CompanionHelpWriter.h"
|
||||
#include <algorithm>
|
||||
@@ -198,9 +201,14 @@ class CompanionHelpWriterTests(unittest.TestCase):
|
||||
self.assertEqual(result.returncode,0,result.stdout+result.stderr)
|
||||
result = subprocess.run([str(work/'test')],capture_output=True)
|
||||
self.assertEqual(result.returncode,0,result.stderr.decode(errors='replace'))
|
||||
self.assertEqual(hashlib.sha256(result.stdout).hexdigest(),GOLDEN[name]['text'])
|
||||
self.assertEqual(result.stdout.count(FAST_HELP), 1)
|
||||
previous_help = result.stdout.replace(FAST_HELP, b'')
|
||||
self.assertEqual(hashlib.sha256(previous_help).hexdigest(),GOLDEN[name]['text'])
|
||||
if name != 'rak_full':
|
||||
self.assertEqual(hashlib.sha256(result.stderr).hexdigest(),GOLDEN[name]['calls'])
|
||||
fast_call = b'P' + len(FAST_HELP).to_bytes(4, 'little') + FAST_HELP
|
||||
self.assertEqual(result.stderr.count(fast_call), 1)
|
||||
previous_calls = result.stderr.replace(fast_call, b'')
|
||||
self.assertEqual(hashlib.sha256(previous_calls).hexdigest(),GOLDEN[name]['calls'])
|
||||
else:
|
||||
image = (work / 'test').read_bytes()
|
||||
self.assertNotIn(b'Commands:\r\n stats-core / stats-radio / stats-radio-diag',image)
|
||||
@@ -228,7 +236,8 @@ class CompanionHelpWriterTests(unittest.TestCase):
|
||||
self.assertEqual(result.returncode,0,result.stdout+result.stderr)
|
||||
result=subprocess.run([str(work/'test')],capture_output=True)
|
||||
self.assertEqual(result.returncode,0,result.stderr.decode(errors='replace'))
|
||||
self.assertEqual(hashlib.sha256(result.stdout).hexdigest(),GOLDEN['rak_full']['text'])
|
||||
self.assertEqual(result.stdout.count(FAST_HELP), 1)
|
||||
self.assertEqual(hashlib.sha256(result.stdout.replace(FAST_HELP, b'')).hexdigest(),GOLDEN['rak_full']['text'])
|
||||
self.assertNotIn(b'Commands:\r\n stats-core / stats-radio / stats-radio-diag',(work/'test').read_bytes())
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
@@ -88,7 +88,7 @@ struct Radio {
|
||||
clock = 8888888 if target == "lilygo_tdeck" else 8000000
|
||||
buffered = local or target not in LEGACY_FALLBACKS
|
||||
expected = (4000000 if local else 2000000) if target == "heltec_rc32" else (
|
||||
clock if buffered else 2000000)
|
||||
clock)
|
||||
selected = "spi" if local else "::SPI"
|
||||
name = f"target_{index}_{int(local)}"
|
||||
options = f"#define P_LORA_SCLK 5\n" if local else ""
|
||||
|
||||
@@ -37,6 +37,7 @@ class LR1110PacketSetterCacheTests(unittest.TestCase):
|
||||
state = (ROOT / "src/helpers/radiolib/LR1110ProfileSwitchState.h").read_text()
|
||||
harness = r"""
|
||||
#include <cassert>
|
||||
#include <helpers/RadioSwitchTuning.h>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
@@ -106,6 +107,7 @@ struct LR1110 {
|
||||
struct Radio : LR1110 {
|
||||
uint32_t _activityAt=0;bool _headerSeen=false;
|
||||
LR1110ProfileSwitchState _profileSwitch{true};
|
||||
uint8_t _profileSwitchFeatures=55;
|
||||
bool _profilePacketDirty = true;
|
||||
uint32_t _profileFastResumes = 0;
|
||||
Radio() { ownership = &_profileSwitch; }
|
||||
@@ -182,7 +184,7 @@ int main(int argc, char** argv) {
|
||||
flags = [] if os.name == "nt" else ["-fsanitize=address,undefined", "-fno-sanitize-recover=all",
|
||||
"-fno-pie", "-no-pie"]
|
||||
built = subprocess.run([compiler, "-std=gnu++11", "-Wall", "-Wextra", *flags,
|
||||
str(cpp), "-o", str(cls.binary)],
|
||||
"-I", str(ROOT / "src"), str(cpp), "-o", str(cls.binary)],
|
||||
capture_output=True, text=True, timeout=60)
|
||||
if built.returncode:
|
||||
raise AssertionError(built.stdout + built.stderr)
|
||||
|
||||
@@ -20,7 +20,7 @@ class LR1110ProfileSwitchTests(unittest.TestCase):
|
||||
"thinknode_m3": "Nrf52BufferedRadioHal",
|
||||
# Fast RX ownership is independent of the SPI transport. Keep M4
|
||||
# on its prior bus until its SPIM3/BLE transfer path is qualified.
|
||||
"thinknode_m4": None,
|
||||
"thinknode_m4": "TunableRadioHal",
|
||||
"minewsemi_me25ls01": "Nrf52BufferedRadioHal",
|
||||
"thinknode_m7": "ESP32BufferedRadioHal",
|
||||
"thinknode_m9": "ESP32BufferedRadioHal",
|
||||
@@ -33,8 +33,9 @@ class LR1110ProfileSwitchTests(unittest.TestCase):
|
||||
self.assertIn("-D RADIO_CLASS=CustomLR1110", config)
|
||||
self.assertIn("-D WRAPPER_CLASS=CustomLR1110Wrapper", config)
|
||||
if hal is not None:
|
||||
self.assertIn(f"{hal} radioHal(", target_source)
|
||||
self.assertIn("new Module(&radioHal,", target_source)
|
||||
name = "radio_hal" if target == "thinknode_m4" else "radioHal"
|
||||
self.assertIn(f"{hal} {name}(", target_source)
|
||||
self.assertIn(f"new Module(&{name},", target_source)
|
||||
else:
|
||||
self.assertNotIn("BufferedRadioHal", target_source)
|
||||
self.assertIn(
|
||||
@@ -43,9 +44,11 @@ class LR1110ProfileSwitchTests(unittest.TestCase):
|
||||
|
||||
esp32_hal = (ROOT / "src/helpers/radiolib/ESP32BufferedRadioHal.h").read_text()
|
||||
nrf52_hal = (ROOT / "src/helpers/radiolib/Nrf52BufferedRadioHal.h").read_text()
|
||||
self.assertIn("SPISettings(hz, MSBFIRST, SPI_MODE0)", esp32_hal)
|
||||
tunable_hal = (ROOT / "src/helpers/radiolib/TunableRadioHal.h").read_text()
|
||||
self.assertIn("SPISettings(hz, MSBFIRST, SPI_MODE0)", tunable_hal)
|
||||
self.assertIn("TunableRadioHal(bus, hz, false, true,", esp32_hal)
|
||||
self.assertIn("spi->transferBytes(out, in, len)", esp32_hal)
|
||||
self.assertIn("SPISettings(hz, MSBFIRST, SPI_MODE0)", nrf52_hal)
|
||||
self.assertIn("TunableRadioHal(bus, hz, false, true,", nrf52_hal)
|
||||
self.assertIn("spi->transfer(out, in, len)", nrf52_hal)
|
||||
for target in ("thinknode_m7", "thinknode_m9"):
|
||||
source = (ROOT / "variants" / target / "target.cpp").read_text()
|
||||
@@ -62,9 +65,11 @@ class LR1110ProfileSwitchTests(unittest.TestCase):
|
||||
implementation = method(source, "int16_t snapshotIrqAfterRxExit(")
|
||||
harness = r'''
|
||||
#include <cassert>
|
||||
#include <helpers/RadioSwitchTuning.h>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#define MC_LR1110_FAST_PROFILE_SWITCH 1
|
||||
#define RADIOLIB_ERR_NONE 0
|
||||
#define RADIOLIB_MODULE_SPI_WIDTH_STATUS 2
|
||||
struct Module {
|
||||
@@ -96,7 +101,8 @@ int main() {
|
||||
def test_fast_rx_skips_only_unchanged_packet_tuple_and_fails_closed(self):
|
||||
source = (ROOT / "src/helpers/radiolib/CustomLR1110.h").read_text()
|
||||
signatures = (
|
||||
"void setProfileSwitchOptimization(", "void setProfileStandbyWarm(",
|
||||
"uint8_t profileSwitchCapabilities(", "uint8_t getProfileSwitchFeatures(",
|
||||
"void setProfileSwitchFeatures(", "void setProfileSwitchOptimization(", "void setProfileStandbyWarm(",
|
||||
"void beginProfileSwitch(", "void endProfileSwitch(",
|
||||
"bool profileSwitchFailed(", "int16_t standby()", "int16_t standby(uint8_t",
|
||||
"int16_t sleep()", "int16_t reset()", "int16_t stageMode(",
|
||||
@@ -109,10 +115,12 @@ int main() {
|
||||
)
|
||||
harness = r'''
|
||||
#include <cassert>
|
||||
#include <helpers/RadioSwitchTuning.h>
|
||||
#include <cstdint>
|
||||
#include <cstddef>
|
||||
#include <string>
|
||||
@STATE@
|
||||
#define MC_LR1110_FAST_PROFILE_SWITCH 1
|
||||
#define RADIOLIB_ERR_NONE 0
|
||||
#define RADIOLIB_LR11X0_STANDBY_XOSC 0
|
||||
#define RADIOLIB_LR11X0_RX_TIMEOUT_INF 0xffffff
|
||||
@@ -161,6 +169,7 @@ struct LR1110 {
|
||||
struct Radio:LR1110 {
|
||||
uint32_t _activityAt=0;bool _headerSeen=false;
|
||||
LR1110ProfileSwitchState _profileSwitch{true};
|
||||
uint8_t _profileSwitchFeatures=55;
|
||||
bool _profileStandbyWarm=false,_profilePacketDirty=false;
|
||||
uint32_t _profileFastResumes=0;
|
||||
uint32_t pendingIrq=0;
|
||||
@@ -216,7 +225,29 @@ int main() {
|
||||
prime(r);r.stageMode(2,nullptr);assert(!r._profileSwitch.rxValid);
|
||||
prime(r);r.sleep();assert(!r._profileSwitch.rxValid);
|
||||
prime(r);r.reset();assert(!r._profileSwitch.rxValid);
|
||||
// FS and packet reuse remain independently controllable without disabling
|
||||
// warm receive ownership; unsupported SX1262 IQ/transport bits stay absent.
|
||||
for(unsigned requested=0;requested<256;++requested) {
|
||||
Radio x;x.setProfileSwitchFeatures(uint8_t(requested));
|
||||
const uint8_t features=requested & x.profileSwitchCapabilities();
|
||||
assert(x.getProfileSwitchFeatures()==features && x.trace.empty());
|
||||
assert(!(features & (mesh::RadioSwitchTuning::Iq | mesh::RadioSwitchTuning::Buffer)));
|
||||
assert(x._profileSwitch.enabled==bool(features & mesh::RadioSwitchTuning::Warm));
|
||||
assert(!x._profileSwitch.rxValid && !x._profileSwitch.modulationValid);
|
||||
assert(x.startReceive()==0);x.setProfileStandbyWarm(true);x.trace.clear();
|
||||
x.beginProfileSwitch(true);assert(x.standby()==0);
|
||||
const bool warm=features & mesh::RadioSwitchTuning::Warm;
|
||||
const bool fs=features & mesh::RadioSwitchTuning::Fs;
|
||||
assert(x.trace==(warm ? (fs ? "IF":"s") : "S"));x.trace.clear();
|
||||
assert(x.startReceive()==0);
|
||||
const bool packet=features & mesh::RadioSwitchTuning::Packet;
|
||||
assert(x.trace==(warm ? (packet ? "QWR":"QPWR") : "N"));
|
||||
x.endProfileSwitch(true);x.trace.clear();x._profilePacketDirty=true;
|
||||
x.setProfileSwitchFeatures(features);
|
||||
assert(x.trace.empty() && !x._profileSwitch.rxValid && !x._profilePacketDirty);
|
||||
}
|
||||
}
|
||||
|
||||
'''
|
||||
self.compile_run(harness.replace("@STATE@", state).replace("@METHODS@", methods))
|
||||
|
||||
@@ -226,6 +257,7 @@ int main() {
|
||||
)
|
||||
harness = r'''
|
||||
#include <cassert>
|
||||
#include <helpers/RadioSwitchTuning.h>
|
||||
#include <cstdint>
|
||||
@STATE@
|
||||
int main() {
|
||||
@@ -279,10 +311,12 @@ int main() {
|
||||
)
|
||||
harness = r'''
|
||||
#include <cassert>
|
||||
#include <helpers/RadioSwitchTuning.h>
|
||||
#include <cmath>
|
||||
#include <cstdint>
|
||||
#include <initializer_list>
|
||||
@STATE@
|
||||
#define MC_LR1110_FAST_PROFILE_SWITCH 1
|
||||
#define RADIOLIB_ERR_NONE 0
|
||||
#define RADIOLIB_ERR_INVALID_SPREADING_FACTOR -1
|
||||
#define RADIOLIB_ERR_INVALID_CODING_RATE -2
|
||||
@@ -298,6 +332,7 @@ int main() {
|
||||
#define RADIOLIB_LR11X0_LORA_LDRO_DISABLED 0
|
||||
struct Radio {
|
||||
LR1110ProfileSwitchState _profileSwitch{true};
|
||||
uint8_t _profileSwitchFeatures=55;
|
||||
uint8_t spreadingFactor=7, bandwidth=4, codingRate=1, ldrOptimize=0;
|
||||
float bandwidthKhz=125;
|
||||
bool ldroAuto=true;
|
||||
@@ -350,6 +385,15 @@ int main() {
|
||||
assert(r.spreadingFactor==oldSf && r.bandwidth==oldBw && r.codingRate==oldCr);
|
||||
assert(r.bandwidthKhz==oldKhz && r.ldrOptimize==oldLdro);
|
||||
assert(r._profileSwitch.failed() && !r._profileSwitch.rxValid);
|
||||
for(unsigned features=0;features<64;++features) {
|
||||
Radio x;x._profileSwitchFeatures=features;
|
||||
assert(x.setLoRaModulationParams(125,7,5)==0 && x.writes==1 && x.queries==1);
|
||||
x._profileSwitch.rxResult(0,true);x._profileSwitch.begin(true,true);
|
||||
x._profileSwitch.standbyResult(0);
|
||||
assert(x.setLoRaModulationParams(125,7,5)==0);
|
||||
assert(x.queries==((features & mesh::RadioSwitchTuning::Modem) ? 1U:2U));
|
||||
assert(x.writes==((features & mesh::RadioSwitchTuning::Batch) ? 1U:2U));
|
||||
}
|
||||
}
|
||||
'''
|
||||
self.compile_run(harness.replace("@STATE@", state).replace("@PRODUCTION@", production))
|
||||
|
||||
@@ -185,7 +185,7 @@ int main() {
|
||||
assert(SPI1._spim.drv_inst_idx==unsigned(MC_NRF52_HIL_SPIM1_RADIO));
|
||||
assert(c_indices()==(MC_NRF52_HIL_SPIM1_RADIO?0x3210u:0x2100u));
|
||||
assert(radio.module->hal->spi==&SPI && !radio.module->hal->initInterface);
|
||||
assert(radio.module->hal->spiSettings.hz==(MC_NRF52_HIL_SPIM1_RADIO?8000000:2000000));
|
||||
assert(radio.module->hal->spiSettings.hz==8000000);
|
||||
assert(radio_init() && SPI.begins==1 && SPI1.begins==0);
|
||||
uint8_t tx[]={0x86,1,2,3},rx[4]={};radio.module->hal->spiTransfer(tx,4,rx);
|
||||
assert(SPI.bursts==unsigned(MC_NRF52_HIL_SPIM1_RADIO));
|
||||
|
||||
@@ -23,7 +23,7 @@ int main() {
|
||||
ArduinoHal& radio=concrete;
|
||||
constexpr unsigned budget=MC_NRF_RADIO_BUSY_SPIN_POLLS;
|
||||
#if MC_NRF_RADIO_BUSY_SPIN_POLLS == 0
|
||||
static_assert(sizeof(Nrf52BufferedRadioHal)==sizeof(ArduinoHal),"default RAM growth");
|
||||
static_assert(sizeof(Nrf52BufferedRadioHal)==sizeof(TunableRadioHal),"default RAM growth");
|
||||
#endif
|
||||
// The first pre-transfer wait delegates immediately, even in opt-in builds.
|
||||
for(unsigned i=0;i<1000;++i) radio.yield();
|
||||
|
||||
@@ -52,7 +52,7 @@ class ProfileIqCacheTest(unittest.TestCase):
|
||||
sanitizers = [] if os.name == "nt" else [
|
||||
"-fsanitize=address,undefined", "-fno-omit-frame-pointer", "-no-pie"]
|
||||
built = subprocess.run([os.environ.get("CXX", "g++"), "-std=c++17",
|
||||
"-Wall", "-Wextra", *sanitizers, str(cpp), "-o", str(binary)],
|
||||
"-Wall", "-Wextra", *sanitizers, "-I", str(ROOT / "src"), str(cpp), "-o", str(binary)],
|
||||
capture_output=True, text=True)
|
||||
self.assertEqual(built.returncode, 0, built.stderr)
|
||||
ran = subprocess.run([str(binary)], capture_output=True, text=True)
|
||||
@@ -61,7 +61,8 @@ class ProfileIqCacheTest(unittest.TestCase):
|
||||
def test_owned_iq_ack_wire_payload_setters_lifecycle_and_errors(self):
|
||||
source = (ROOT / "src/helpers/radiolib/CustomSX1262.h").read_text()
|
||||
state = (ROOT / "src/helpers/radiolib/SX1262ProfileSwitchState.h").read_text().replace("#pragma once", "")
|
||||
signatures = ("void setProfileSwitchOptimization(", "void beginProfileSwitch(",
|
||||
signatures = ("uint8_t defaultProfileSwitchFeatures(", "uint8_t profileSwitchCapabilities(", "uint8_t getProfileSwitchFeatures(",
|
||||
"void setProfileSwitchFeatures(", "void setProfileSwitchOptimization(", "void beginProfileSwitch(",
|
||||
"void endProfileSwitch(", "int16_t reset(bool", "int16_t sleep(bool",
|
||||
"int16_t stageMode(", "int16_t setPreambleLength(", "int16_t setCRC(",
|
||||
"int16_t implicitHeader(", "int16_t explicitHeader()", "int16_t invertIQ(",
|
||||
@@ -71,11 +72,15 @@ class ProfileIqCacheTest(unittest.TestCase):
|
||||
methods = "\n".join(method(source, signature).replace(" override", "") for signature in signatures)
|
||||
fixture = r'''
|
||||
#include <cassert>
|
||||
#include <helpers/RadioSwitchTuning.h>
|
||||
#include <cstdint>
|
||||
#include <cstddef>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
@DEFINES@
|
||||
#define MC_SX1262_FAST_PROFILE_SWITCH 1
|
||||
#define MC_SX1262_FAST_FS 1
|
||||
#define MC_SX1262_FAST_MODEM_CACHE 0
|
||||
#define MC_SX1262_FAST_PACKET_CACHE 0
|
||||
#define RADIOLIB_ERR_NONE 0
|
||||
#define RADIOLIB_SX126X_STANDBY_RC 0
|
||||
@@ -153,10 +158,12 @@ struct SX1262 {
|
||||
struct Radio : SX1262 {
|
||||
uint32_t _activityAt=0;bool _headerSeen=false;
|
||||
SX1262ProfileSwitchState _profileSwitch{true};
|
||||
uint8_t _profileSwitchFeatures=defaultProfileSwitchFeatures();
|
||||
bool _coldStandby=true,_tcxoWakePending=false,_rx_ps_rf_rx_disabled=false;
|
||||
uint32_t _profileFastResumes=0;
|
||||
#ifdef MC_SX1262_HIL
|
||||
bool hilSkipUnchangedPacket=false,hilSkipUnchangedIq=true;
|
||||
bool hilUseFsOnRetune=false,hilSkipConfirmedModemQuery=false;
|
||||
uint32_t hilPacketSkips=0,hilIqWorkaroundSkips=0;
|
||||
#endif
|
||||
@METHODS@
|
||||
@@ -197,6 +204,11 @@ int main() {
|
||||
#endif
|
||||
assert(r.startReceive()==0 && r.trace=="CIiPWR");
|
||||
}
|
||||
// A runtime IQ opt-out keeps owned RX but forces its checked workaround.
|
||||
{ Radio r;prime(r);r.setProfileSwitchFeatures(
|
||||
r.getProfileSwitchFeatures() & ~mesh::RadioSwitchTuning::Iq);
|
||||
r.trace.clear();prime(r);arm(r);
|
||||
assert(r.startReceive()==0 && r.trace=="CIiPWR"); }
|
||||
// Owned preamble staging is the intended fast case, unlike an ordinary
|
||||
// preamble command. Every ordinary packet/direct-register path revokes IQ.
|
||||
{ Radio r;prime(r);arm(r);assert(r.setPreambleLength(64)==0 && r.trace.empty());
|
||||
|
||||
@@ -24,6 +24,7 @@ def build_source(family='CustomSX1262',remove_reset=False,include_duty=False):
|
||||
source=source.replace('#include <cassert>', '''#include <cassert>
|
||||
#include <cstdio>
|
||||
#include <RadioProfiles.h>
|
||||
#include <helpers/RadioSwitchTuning.h>
|
||||
#include <helpers/radiolib/NoiseFloorEstimator.h>
|
||||
#include <helpers/radiolib/SX1262ProfileSwitchState.h>
|
||||
#include <helpers/radiolib/LR1110ProfileSwitchState.h>
|
||||
@@ -59,11 +60,14 @@ namespace mesh { enum class RadioParamApplyResult { APPLIED, BUSY, FAILED }; }
|
||||
int64_t esp_timer_get_time(){return int64_t(now_ms)*1000;}
|
||||
uint32_t profileTimestamp(bool){return now_ms*1000;}
|
||||
uint32_t profileElapsedUs(uint32_t a,uint32_t b,bool){return b-a;}''')
|
||||
source=source.replace('struct Board {', 'struct Board { bool prepareRadioProfileCpuClock(bool, bool& changed) {changed=false;return true;}')
|
||||
source=source.replace('struct Module {', '''struct Module {
|
||||
enum {MODE_RX=1};void setRfSwitchState(int){}''')
|
||||
source=source.replace('struct Chip : LR2021 {', '''using SX1262=LR2021;using SX1268=LR2021;using LLCC68=LR2021;using STM32WLx=LR2021;using LR1110=LR2021;using LR1121=LR2021;
|
||||
struct Chip : LR2021 {
|
||||
SX1262ProfileSwitchState _profileSwitch{true};bool _coldStandby=true;
|
||||
uint8_t _profileSwitchFeatures=63;
|
||||
uint8_t getProfileSwitchFeatures() const { return _profileSwitchFeatures; }
|
||||
uint32_t _profileFastResumes=0;
|
||||
bool _profilePacketDirty=false;Module* mod=&module;
|
||||
int16_t snapshotIrqAfterRxExit(uint32_t* pending){*pending=irq;return snapshot_failure;}
|
||||
|
||||
@@ -0,0 +1,143 @@
|
||||
"""Execute production switch-feature masks and conservative modulation paths."""
|
||||
from pathlib import Path
|
||||
import unittest
|
||||
|
||||
from test_radio_receive_contract import method
|
||||
import test_sx1262_batched_modulation as compiler
|
||||
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
|
||||
|
||||
class RadioSwitchDriverControlsTests(unittest.TestCase):
|
||||
compile_run = compiler.BatchedModulationTests.compile_run
|
||||
|
||||
def test_sx1262_compile_opt_outs_cannot_be_enabled_at_runtime(self):
|
||||
source = (ROOT / "src/helpers/radiolib/CustomSX1262.h").read_text()
|
||||
definitions = source[source.index("#ifndef MC_SX1262_FAST_PROFILE_SWITCH"):
|
||||
source.index("#ifndef SX126X_TX_BUSY_TIMEOUT_MS")]
|
||||
methods = "\n".join(method(source, signature) for signature in (
|
||||
"uint8_t defaultProfileSwitchFeatures(", "uint8_t profileSwitchCapabilities(",
|
||||
"uint8_t getProfileSwitchFeatures(", "void setProfileSwitchFeatures("))
|
||||
state = (ROOT / "src/helpers/radiolib/SX1262ProfileSwitchState.h").read_text()
|
||||
harness = r'''
|
||||
#include <cassert>
|
||||
#include <helpers/RadioSwitchTuning.h>
|
||||
@OVERRIDES@
|
||||
@DEFINITIONS@
|
||||
@STATE@
|
||||
struct Radio {
|
||||
SX1262ProfileSwitchState _profileSwitch{true};
|
||||
uint8_t _profileSwitchFeatures=defaultProfileSwitchFeatures();
|
||||
@METHODS@
|
||||
};
|
||||
int main() {
|
||||
Radio r;
|
||||
assert(r.profileSwitchCapabilities()==@CAPS@);
|
||||
assert(r.getProfileSwitchFeatures()==@CAPS@);
|
||||
for(unsigned bits=0;bits<256;++bits) {
|
||||
r._profileSwitch.rxResult(0,true);
|
||||
r._profileSwitch.begin(true,true);r._profileSwitch.standbyResult(0);
|
||||
r._profileSwitch.packetResult(0,32,1,255,0,0);
|
||||
r._profileSwitch.modulationResult(0,7,3,1,0);
|
||||
r.setProfileSwitchFeatures(uint8_t(bits));
|
||||
assert(r.getProfileSwitchFeatures()==(bits & @CAPS@));
|
||||
assert(r._profileSwitch.enabled==bool(bits & @CAPS@ & mesh::RadioSwitchTuning::Warm));
|
||||
assert(!r._profileSwitch.rxValid && !r._profileSwitch.active
|
||||
&& !r._profileSwitch.modulationValid && !r._profileSwitch.packetValid);
|
||||
}
|
||||
}
|
||||
'''
|
||||
policies = (
|
||||
("", 63),
|
||||
("#define MC_SX1262_FAST_PROFILE_SWITCH 0", 32),
|
||||
("#define MC_SX1262_FAST_FS 0", 37),
|
||||
("#define MC_SX1262_FAST_PACKET_CACHE 0", 59),
|
||||
# The I/O-expander board retains its proven IQ workaround cache.
|
||||
("#define MC_SX1262_FAST_FS 0\n#define MC_SX1262_FAST_PACKET_CACHE 0\n"
|
||||
"#define MC_SX1262_FAST_IQ_CACHE 1", 41),
|
||||
)
|
||||
for overrides, caps in policies:
|
||||
with self.subTest(overrides=overrides):
|
||||
self.compile_run(harness.replace("@OVERRIDES@", overrides)
|
||||
.replace("@DEFINITIONS@", definitions)
|
||||
.replace("@STATE@", state)
|
||||
.replace("@METHODS@", methods)
|
||||
.replace("@CAPS@", str(caps)))
|
||||
|
||||
def test_lr1110_compile_opt_out_keeps_all_shortcuts_unavailable(self):
|
||||
source = (ROOT / "src/helpers/radiolib/CustomLR1110.h").read_text()
|
||||
methods = "\n".join(method(source, signature) for signature in (
|
||||
"uint8_t profileSwitchCapabilities(", "uint8_t getProfileSwitchFeatures(",
|
||||
"void setProfileSwitchFeatures("))
|
||||
state = (ROOT / "src/helpers/radiolib/LR1110ProfileSwitchState.h").read_text()
|
||||
harness = r'''
|
||||
#include <cassert>
|
||||
#include <helpers/RadioSwitchTuning.h>
|
||||
#define MC_LR1110_FAST_PROFILE_SWITCH @ENABLED@
|
||||
@STATE@
|
||||
struct Radio {
|
||||
LR1110ProfileSwitchState _profileSwitch{true};
|
||||
uint8_t _profileSwitchFeatures=profileSwitchCapabilities();
|
||||
bool _profilePacketDirty=true;
|
||||
@METHODS@
|
||||
};
|
||||
int main() {
|
||||
Radio r;
|
||||
assert(r.profileSwitchCapabilities()==@CAPS@);
|
||||
for(unsigned bits=0;bits<256;++bits) {
|
||||
r._profilePacketDirty=true;r.setProfileSwitchFeatures(uint8_t(bits));
|
||||
assert(r.getProfileSwitchFeatures()==(bits & @CAPS@));
|
||||
assert(r._profileSwitch.enabled==bool(bits & @CAPS@ & mesh::RadioSwitchTuning::Warm));
|
||||
assert(!r._profileSwitch.rxValid && !r._profileSwitch.modulationValid
|
||||
&& !r._profilePacketDirty);
|
||||
}
|
||||
}
|
||||
'''
|
||||
for enabled in (0, 1):
|
||||
with self.subTest(enabled=enabled):
|
||||
self.compile_run(harness.replace("@STATE@", state)
|
||||
.replace("@METHODS@", methods)
|
||||
.replace("@ENABLED@", str(enabled))
|
||||
.replace("@CAPS@", "55" if enabled else "0"))
|
||||
|
||||
def test_both_wrappers_execute_normal_setters_when_batch_is_disabled(self):
|
||||
harness = r'''
|
||||
#include <cassert>
|
||||
#include <string>
|
||||
#include <helpers/RadioSwitchTuning.h>
|
||||
#define RADIOLIB_ERR_NONE 0
|
||||
struct @CHIP@ {
|
||||
std::string trace;char failure=0;
|
||||
int call(char op) {trace+=op;return failure==op?-7:0;}
|
||||
int setLoRaModulationParams(float bw,uint8_t sf,uint8_t cr) {
|
||||
assert(bw==62.5f && sf==7 && cr==5);return call('M');
|
||||
}
|
||||
int setSpreadingFactor(uint8_t sf) {assert(sf==7);return call('S');}
|
||||
int setBandwidth(float bw) {assert(bw==62.5f);return call('B');}
|
||||
int setCodingRate(uint8_t cr) {assert(cr==5);return call('C');}
|
||||
};
|
||||
struct Wrapper {
|
||||
@CHIP@ radio;@CHIP@* _radio=&radio;uint8_t features=0;
|
||||
uint8_t getDriverSwitchFeatures() const {return features;}
|
||||
@METHOD@
|
||||
};
|
||||
int main() {
|
||||
for(unsigned flags=0;flags<128;++flags) for(char failure:std::string("0MSBC")) {
|
||||
Wrapper w;w.features=flags;w.radio.failure=failure;
|
||||
const bool batch=flags & mesh::RadioSwitchTuning::Batch;
|
||||
assert(w.applyProfileModulation(62.5f,7,5)==(batch ? failure!='M'
|
||||
: failure!='S' && failure!='B' && failure!='C'));
|
||||
assert(w.radio.trace==(batch ? "M":failure=='S'?"S":failure=='B'?"SB":"SBC"));
|
||||
}
|
||||
}
|
||||
'''
|
||||
for family in ("SX1262", "LR1110"):
|
||||
with self.subTest(family=family):
|
||||
source = (ROOT / f"src/helpers/radiolib/Custom{family}Wrapper.h").read_text()
|
||||
self.compile_run(harness.replace("@CHIP@", f"Custom{family}")
|
||||
.replace("@METHOD@", method(source, "bool applyProfileModulation(")))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -0,0 +1,59 @@
|
||||
"""Exercise saved fast-switch controls through the production shared CLI."""
|
||||
from pathlib import Path
|
||||
import re
|
||||
import shutil
|
||||
import subprocess
|
||||
import tempfile
|
||||
import unittest
|
||||
from cpp_source import body
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
|
||||
|
||||
class RadioSwitchTuningCLITest(unittest.TestCase):
|
||||
def test_five_cli_roles_restore_tuning_before_first_radio_initialization(self):
|
||||
for role in ('companion_radio', 'simple_repeater', 'simple_room_server',
|
||||
'simple_sensor', 'simple_secure_chat'):
|
||||
with self.subTest(role=role):
|
||||
source = (ROOT / f'examples/{role}/main.cpp').read_text()
|
||||
setup = body(source, '\nvoid setup(')
|
||||
setup = re.sub(r'//[^\n]*|/\*.*?\*/', '', setup, flags=re.S)
|
||||
hook = 'mesh::RadioProfileCLI::configureBootTuning('
|
||||
self.assertEqual(setup.count(hook), 1)
|
||||
if role != 'simple_secure_chat':
|
||||
self.assertLess(setup.index('beginRegulatorSettings('), setup.index(hook))
|
||||
self.assertRegex(setup, r'configureBootTuning\(regulator_prefs_fs,\s*radio_driver\)')
|
||||
else:
|
||||
self.assertRegex(setup, r'configureBootTuning\(\s*volatile_primary_fs\s*\?\s*nullptr\s*:\s*primary_fs,\s*radio_driver\)')
|
||||
for mount in ('beginInternalPrimaryFilesystemSafely(',
|
||||
'LittleFS.begin()', 'SPIFFS.begin(true)'):
|
||||
self.assertEqual(setup.count(mount), 1)
|
||||
self.assertLess(setup.index(mount), setup.index(hook))
|
||||
self.assertEqual(setup.count('the_mesh.begin('), 1)
|
||||
self.assertLess(setup.index('fast_rng.begin('), setup.index('the_mesh.begin('))
|
||||
radio_init = re.search(r'\bradio_init\s*\(', setup)
|
||||
self.assertIsNotNone(radio_init)
|
||||
self.assertLess(setup.index(hook), radio_init.start())
|
||||
if role == 'simple_secure_chat':
|
||||
self.assertLess(radio_init.start(), setup.index('fast_rng.begin('))
|
||||
|
||||
def test_saved_controls_and_failure_transactions(self):
|
||||
compiler = shutil.which('g++') or shutil.which('clang++')
|
||||
self.assertIsNotNone(compiler)
|
||||
with tempfile.TemporaryDirectory() as work:
|
||||
for fleet_enabled in (False, True):
|
||||
with self.subTest(fleet=fleet_enabled):
|
||||
executable = Path(work) / f'switch-{int(fleet_enabled)}'
|
||||
subprocess.run([
|
||||
compiler, '-std=c++17', '-Wall', '-Wextra',
|
||||
f'-DMESH_ENABLE_FLEET_CONTROL={int(fleet_enabled)}',
|
||||
'-I', str(ROOT / 'test/fixtures/radio_profiles/mocks'),
|
||||
'-I', str(ROOT / 'test/mocks'), '-I', str(ROOT / 'src'),
|
||||
str(ROOT / 'src/helpers/RadioProfileCLI.cpp'),
|
||||
str(ROOT / 'test/fixtures/radio_profiles/switch_tuning_test.cpp'),
|
||||
'-o', str(executable)], check=True, capture_output=True, text=True)
|
||||
subprocess.run([str(executable)], check=True)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
@@ -0,0 +1,91 @@
|
||||
"""Run the actual tuning transaction with radio ownership and RX/save faults."""
|
||||
from pathlib import Path
|
||||
import unittest
|
||||
from test_radio_receive_contract import method
|
||||
from test_radio_interrupt_recovery import compile_run
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
|
||||
|
||||
class RadioSwitchTransactionTest(unittest.TestCase):
|
||||
def test_admission_restore_and_commit_order(self):
|
||||
source = (ROOT / 'src/helpers/radiolib/RadioLibWrappers.cpp').read_text()
|
||||
transaction = method(source, 'mesh::RadioParamApplyResult RadioLibWrapper::trySetSwitchTuning(')
|
||||
compile_run(r'''
|
||||
#include <cassert>
|
||||
#include <initializer_list>
|
||||
#include <helpers/RadioSwitchTuning.h>
|
||||
namespace mesh { enum class RadioParamApplyResult { APPLIED, BUSY, FAILED }; }
|
||||
using Result=mesh::RadioParamApplyResult;
|
||||
struct RadioLibWrapper {
|
||||
mesh::RadioSwitchTuning policy;
|
||||
struct { unsigned resets=0; void resetSwitchTest(){++resets;} } _profiles;
|
||||
uint8_t admission=1;
|
||||
bool receiving=true, fail_rx=false, inject_packet=false, pending_packet=false;
|
||||
unsigned admissions=0, resumes=0, mutations=0;
|
||||
mesh::RadioSwitchCapabilities switchTuningCapabilities()const {
|
||||
mesh::RadioSwitchCapabilities caps; caps.flags=63; return caps;
|
||||
}
|
||||
mesh::RadioSwitchTuning switchTuning()const{return policy;}
|
||||
uint8_t beginReconfigure(){++admissions;return pending_packet?2:admission;}
|
||||
void setDriverSwitchFeatures(uint8_t value){++mutations;policy.flags=value;}
|
||||
bool configureBootSwitchTuning(const mesh::RadioSwitchTuning& value){
|
||||
setDriverSwitchFeatures(value.flags);return true;
|
||||
}
|
||||
void endReconfigure(bool resume) {
|
||||
if(resume){++resumes;receiving=!fail_rx;if(inject_packet)pending_packet=true;}
|
||||
}
|
||||
bool isInRecvMode()const{return receiving;}
|
||||
Result trySetSwitchTuning(const mesh::RadioSwitchTuning&,
|
||||
bool(*)(void*,const mesh::RadioSwitchTuning&)=nullptr,void*=nullptr);
|
||||
};
|
||||
struct Commit {
|
||||
RadioLibWrapper* radio;
|
||||
bool fail=false;
|
||||
unsigned calls=0;
|
||||
static bool save(void* context,const mesh::RadioSwitchTuning& value){
|
||||
auto& self=*static_cast<Commit*>(context);
|
||||
++self.calls;
|
||||
assert(self.radio->receiving && self.radio->policy==value);
|
||||
return !self.fail;
|
||||
}
|
||||
};
|
||||
'''.replace('void*=nullptr', 'void* = nullptr') + transaction + r'''
|
||||
int main(){
|
||||
mesh::RadioSwitchTuning requested;requested.flags=63;
|
||||
for(uint8_t rejected:{uint8_t(2),uint8_t(3)}){
|
||||
RadioLibWrapper radio;radio.admission=rejected;Commit store{&radio};
|
||||
auto result=radio.trySetSwitchTuning(requested,Commit::save,&store);
|
||||
assert(result==(rejected==2?Result::BUSY:Result::FAILED));
|
||||
assert(!radio.policy.flags && !radio.mutations && !store.calls && !radio._profiles.resets);
|
||||
}
|
||||
{
|
||||
RadioLibWrapper radio;radio.pending_packet=true;Commit store{&radio};
|
||||
assert(radio.trySetSwitchTuning(requested,Commit::save,&store)==Result::BUSY);
|
||||
assert(radio.pending_packet && !radio.mutations && !radio.resumes && !store.calls);
|
||||
}
|
||||
{
|
||||
RadioLibWrapper radio;Commit store{&radio};
|
||||
auto invalid=requested;invalid.flags|=64;
|
||||
assert(radio.trySetSwitchTuning(invalid,Commit::save,&store)==Result::FAILED);
|
||||
assert(!radio.admissions && !radio.mutations && !store.calls);
|
||||
assert(radio.trySetSwitchTuning(requested,Commit::save,&store)==Result::APPLIED);
|
||||
assert(radio.policy==requested && radio._profiles.resets==1 && store.calls==1);
|
||||
}
|
||||
{
|
||||
RadioLibWrapper radio;radio.fail_rx=true;Commit store{&radio};
|
||||
assert(radio.trySetSwitchTuning(requested,Commit::save,&store)==Result::FAILED);
|
||||
assert(!radio.policy.flags && !store.calls && radio.mutations==2);
|
||||
}
|
||||
{
|
||||
RadioLibWrapper radio;radio.inject_packet=true;Commit store{&radio};store.fail=true;
|
||||
assert(radio.trySetSwitchTuning(requested,Commit::save,&store)==Result::FAILED);
|
||||
assert(!radio.policy.flags && store.calls==1 && radio.pending_packet);
|
||||
assert(radio.receiving && radio.resumes==1 && radio.mutations==2);
|
||||
}
|
||||
}
|
||||
''')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
@@ -42,7 +42,9 @@ struct SPIClass {
|
||||
}
|
||||
uint8_t transfer(uint8_t value) { ++bytes; return value ^ 0xa5; }
|
||||
};
|
||||
struct ArduinoHal {
|
||||
struct RadioLibHal { virtual ~RadioLibHal()=default; };
|
||||
struct ArduinoHal : RadioLibHal {
|
||||
bool initInterface = false;
|
||||
SPIClass* spi;
|
||||
SPISettings spiSettings;
|
||||
explicit ArduinoHal(SPIClass& bus, SPISettings settings)
|
||||
|
||||
@@ -16,7 +16,7 @@ class BatchedModulationTests(unittest.TestCase):
|
||||
cpp, exe = Path(folder) / "test.cpp", Path(folder) / "test.exe"
|
||||
cpp.write_text(source)
|
||||
built = subprocess.run([os.environ.get("CXX", "g++"), "-std=c++17",
|
||||
"-Wall", "-Wextra", str(cpp), "-o", str(exe)],
|
||||
"-Wall", "-Wextra", "-I", str(ROOT / "src"), str(cpp), "-o", str(exe)],
|
||||
capture_output=True, text=True)
|
||||
self.assertEqual(built.returncode, 0, built.stderr)
|
||||
ran = subprocess.run([str(exe)], capture_output=True, text=True)
|
||||
@@ -27,6 +27,7 @@ class BatchedModulationTests(unittest.TestCase):
|
||||
"int16_t setLoRaModulationParams(")
|
||||
harness = r'''
|
||||
#include <cassert>
|
||||
#include <helpers/RadioSwitchTuning.h>
|
||||
#include <cmath>
|
||||
#include <cstdint>
|
||||
#include <cstddef>
|
||||
@@ -51,6 +52,7 @@ using std::isfinite;
|
||||
#define RADIOLIB_SX126X_LORA_BW_500_0 0x06
|
||||
struct Radio {
|
||||
SX1262ProfileSwitchState _profileSwitch{true};
|
||||
uint8_t _profileSwitchFeatures=63;
|
||||
uint8_t spreadingFactor=9, bandwidth=4, codingRate=3, ldrOptimize=0;
|
||||
float bandwidthKhz=125;
|
||||
bool ldroAuto=true;
|
||||
@@ -181,6 +183,7 @@ int main() {
|
||||
state = (ROOT / "src/helpers/radiolib/SX1262ProfileSwitchState.h").read_text().replace("#pragma once", "")
|
||||
harness = r'''
|
||||
#include <cassert>
|
||||
#include <helpers/RadioSwitchTuning.h>
|
||||
#include <cstdint>
|
||||
@STATE@
|
||||
struct SX1262 {
|
||||
@@ -193,6 +196,7 @@ struct SX1262 {
|
||||
};
|
||||
struct Radio: SX1262 {
|
||||
SX1262ProfileSwitchState _profileSwitch{true};
|
||||
uint8_t _profileSwitchFeatures=63;
|
||||
@METHODS@
|
||||
};
|
||||
int main() {
|
||||
@@ -218,13 +222,18 @@ int main() {
|
||||
|
||||
def test_wrapper_batches_once_and_preserves_failure_short_circuit(self):
|
||||
source = (ROOT / "src/helpers/radiolib/CustomSX1262Wrapper.h").read_text()
|
||||
production = method(source, "bool applyParams(").replace(" override", "")
|
||||
production = "\n".join(method(source, signature).replace(" override", "")
|
||||
for signature in ("bool applyProfileModulation(", "bool applyParams("))
|
||||
harness = r'''
|
||||
#include <cassert>
|
||||
#include <helpers/RadioSwitchTuning.h>
|
||||
#include <cstdint>
|
||||
#define RADIOLIB_ERR_NONE 0
|
||||
struct CustomSX1262 {
|
||||
unsigned freq=0,mod=0,limits=0;int failure=0;
|
||||
unsigned freq=0,mod=0,limits=0,ordinary=0;int failure=0;
|
||||
int setSpreadingFactor(uint8_t sf) { assert(sf==8);++ordinary;return failure==2?-1:0; }
|
||||
int setBandwidth(float bw) { assert(bw==500);++ordinary;return 0; }
|
||||
int setCodingRate(uint8_t cr) { assert(cr==5);++ordinary;return 0; }
|
||||
bool profileSwitchFailed() const { return failure==4; }
|
||||
int setFrequency(float f) { assert(f==910.5f);++freq;return failure==1?-1:0; }
|
||||
int setLoRaModulationParams(float bw,uint8_t sf,uint8_t cr) {
|
||||
@@ -236,6 +245,8 @@ struct CustomSX1262 {
|
||||
struct PacketMillis { uint32_t preambleMillis,payloadMillis; };
|
||||
struct Wrapper {
|
||||
CustomSX1262 chip;CustomSX1262* _radio=&chip;unsigned preamble=0,calcs=0;
|
||||
uint8_t features=mesh::RadioSwitchTuning::Batch;
|
||||
uint8_t getDriverSwitchFeatures() const { return features; }
|
||||
bool updatePreamble(uint8_t sf,float bw) {
|
||||
assert(sf==8 && bw==500);++preamble;return chip.failure!=3;
|
||||
}
|
||||
@@ -256,6 +267,10 @@ int main() {
|
||||
assert(w.chip.mod==(failure==1?0u:1u));
|
||||
assert(w.preamble==((failure==1 || failure==2)?0u:1u));
|
||||
assert(w.calcs==(failure?0u:1u) && w.chip.limits==(failure?0u:2u));
|
||||
Wrapper slow;slow.features=0;slow.chip.failure=failure;
|
||||
assert(slow.applyParams(910.5f,500,8,5)==(failure==0));
|
||||
assert(slow.chip.mod==0 && slow.chip.ordinary==(failure==1?0u:failure==2?1u:3u));
|
||||
assert(slow.preamble==((failure==1 || failure==2)?0u:1u));
|
||||
}
|
||||
}
|
||||
'''
|
||||
|
||||
@@ -15,17 +15,23 @@ class ModemCacheTests(unittest.TestCase):
|
||||
def test_confirmed_modem_requires_owned_warm_rx_and_survives_no_failure(self):
|
||||
source = (ROOT / "src/helpers/radiolib/CustomSX1262.h").read_text()
|
||||
state = (ROOT / "src/helpers/radiolib/SX1262ProfileSwitchState.h").read_text().replace("#pragma once", "")
|
||||
signatures = ("void invalidateMorseModemCache(", "void setProfileSwitchOptimization(",
|
||||
signatures = ("uint8_t defaultProfileSwitchFeatures(", "uint8_t profileSwitchCapabilities(", "uint8_t getProfileSwitchFeatures(",
|
||||
"void setProfileSwitchFeatures(", "void invalidateMorseModemCache(", "void setProfileSwitchOptimization(",
|
||||
"int16_t begin(const ConfigLoRa_t&", "int16_t beginFSK(const ConfigFSK_t&",
|
||||
"int16_t beginLRFHSS(const ConfigLRFHSS_t&", "int16_t setModem(",
|
||||
"int16_t setLoRaModulationParams(")
|
||||
production = "\n".join(method(source, signature).replace(" override", "") for signature in signatures)
|
||||
fixture = r'''
|
||||
#include <cassert>
|
||||
#include <helpers/RadioSwitchTuning.h>
|
||||
#include <cmath>
|
||||
#include <cstdint>
|
||||
#include <cstddef>
|
||||
@DEFINES@
|
||||
#define MC_SX1262_FAST_PROFILE_SWITCH 1
|
||||
#define MC_SX1262_FAST_FS 1
|
||||
#define MC_SX1262_FAST_PACKET_CACHE 1
|
||||
#define MC_SX1262_FAST_IQ_CACHE 1
|
||||
@STATE@
|
||||
using std::isfinite;
|
||||
#define RADIOLIB_ERR_NONE 0
|
||||
@@ -67,6 +73,7 @@ struct SX1262 {
|
||||
};
|
||||
struct Radio : SX1262 {
|
||||
SX1262ProfileSwitchState _profileSwitch{true};
|
||||
uint8_t _profileSwitchFeatures=defaultProfileSwitchFeatures();
|
||||
bool _coldStandby = false, _tcxoWakePending = true;
|
||||
uint8_t spreadingFactor = 9, bandwidth = 4, codingRate = 3, ldrOptimize = 0;
|
||||
float bandwidthKhz = 125;
|
||||
@@ -76,6 +83,7 @@ struct Radio : SX1262 {
|
||||
int16_t writeResult = 0;
|
||||
#ifdef MC_SX1262_HIL
|
||||
bool hilSkipConfirmedModemQuery = false;
|
||||
bool hilUseFsOnRetune=false,hilSkipUnchangedPacket=false,hilSkipUnchangedIq=false;
|
||||
uint32_t hilModemQuerySkips = 0;
|
||||
#endif
|
||||
using SX1262::beginFSK;
|
||||
@@ -91,19 +99,11 @@ struct Radio : SX1262 {
|
||||
@PRODUCTION@
|
||||
};
|
||||
bool skipsModem(const Radio& r) {
|
||||
#ifdef MC_SX1262_HIL
|
||||
return r.hilSkipConfirmedModemQuery;
|
||||
#else
|
||||
(void)r;
|
||||
return MC_SX1262_FAST_MODEM_CACHE != 0;
|
||||
#endif
|
||||
return (r.getProfileSwitchFeatures() & mesh::RadioSwitchTuning::Modem) != 0;
|
||||
}
|
||||
void selectCache(Radio& r, bool enabled) {
|
||||
#ifdef MC_SX1262_HIL
|
||||
r.hilSkipConfirmedModemQuery = enabled;
|
||||
#else
|
||||
(void)r; (void)enabled;
|
||||
#endif
|
||||
const auto flags=r.getProfileSwitchFeatures() & ~mesh::RadioSwitchTuning::Modem;
|
||||
r.setProfileSwitchFeatures(flags | (enabled ? mesh::RadioSwitchTuning::Modem : 0));
|
||||
}
|
||||
void prime(Radio& r) {
|
||||
// Models the full RX path's successful live LoRa confirmation. The ordinary
|
||||
@@ -126,7 +126,7 @@ int main() {
|
||||
assert(r.setLoRaModulationParams(500, 8, 5) == 0 && r.writes == 2);
|
||||
assert(r.queries == (skipsModem(r) ? 1U : 3U));
|
||||
#ifdef MC_SX1262_HIL
|
||||
assert(r.hilModemQuerySkips == (requested ? 2U : 0U));
|
||||
assert(r.hilModemQuerySkips == (skipsModem(r) ? 2U : 0U));
|
||||
#endif
|
||||
// Every ownership gate independently restores the live query.
|
||||
for (int gate = 0; gate < 9; ++gate) {
|
||||
|
||||
@@ -37,6 +37,7 @@ class FastProfileSwitchTests(unittest.TestCase):
|
||||
state = (ROOT / "src/helpers/radiolib/SX1262ProfileSwitchState.h").read_text()
|
||||
harness = r'''
|
||||
#include <cassert>
|
||||
#include <helpers/RadioSwitchTuning.h>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
@STATE@
|
||||
@@ -66,7 +67,8 @@ int main() {
|
||||
def test_owned_rx_window_commands_errors_and_lifecycle_invalidation(self):
|
||||
src = (ROOT / "src/helpers/radiolib/CustomSX1262.h").read_text()
|
||||
state = (ROOT / "src/helpers/radiolib/SX1262ProfileSwitchState.h").read_text().replace("#pragma once", "")
|
||||
signatures = ["void setProfileSwitchOptimization(", "void beginProfileSwitch(",
|
||||
signatures = ["uint8_t defaultProfileSwitchFeatures(", "uint8_t profileSwitchCapabilities(", "uint8_t getProfileSwitchFeatures(",
|
||||
"void setProfileSwitchFeatures(", "void setProfileSwitchOptimization(", "void beginProfileSwitch(",
|
||||
"void endProfileSwitch(", "bool profileSwitchFailed(", "int16_t restoreTcxoAfterSleep(",
|
||||
"int16_t standby()", "int16_t standby(uint8_t", "int16_t sleep()",
|
||||
"int16_t sleep(bool", "int16_t reset(bool", "int16_t stageMode(",
|
||||
@@ -77,10 +79,14 @@ int main() {
|
||||
methods = "\n".join(method(src, signature).replace(" override", "") for signature in signatures)
|
||||
harness = r'''
|
||||
#include <cassert>
|
||||
#include <helpers/RadioSwitchTuning.h>
|
||||
#include <cstdint>
|
||||
#include <cstddef>
|
||||
#include <string>
|
||||
@STATE@
|
||||
#define MC_SX1262_FAST_PROFILE_SWITCH 1
|
||||
#define MC_SX1262_FAST_IQ_CACHE 0
|
||||
#define MC_SX1262_FAST_MODEM_CACHE 0
|
||||
#define MC_SX1262_FAST_FS @FS@
|
||||
#define MC_SX1262_FAST_PACKET_CACHE @PACKET@
|
||||
#define RADIOLIB_ERR_NONE 0
|
||||
@@ -142,7 +148,8 @@ struct SX1262 {
|
||||
};
|
||||
struct Radio: SX1262 {
|
||||
uint32_t _activityAt=0;bool _headerSeen=false;
|
||||
SX1262ProfileSwitchState _profileSwitch{true};bool _rx_ps_rf_rx_disabled=false;uint32_t _profileFastResumes=0;
|
||||
SX1262ProfileSwitchState _profileSwitch{true};
|
||||
uint8_t _profileSwitchFeatures=defaultProfileSwitchFeatures();bool _rx_ps_rf_rx_disabled=false;uint32_t _profileFastResumes=0;
|
||||
bool _coldStandby=true;
|
||||
bool _tcxoWakePending=false;
|
||||
@METHODS@
|
||||
@@ -232,6 +239,27 @@ int main() {
|
||||
fault.errors=32;fault.trace.clear();assert(fault.standby()==0 && fault.trace=="UEV");
|
||||
assert(!fault._tcxoWakePending);
|
||||
Radio wrong;wrong.modem=0;assert(wrong.startReceive()==0 && !wrong._profileSwitch.rxValid);
|
||||
// Every supported feature is independently selectable. Unsupported request
|
||||
// bits are masked before ownership changes; setters issue no radio command.
|
||||
for(unsigned requested=0;requested<256;++requested) {
|
||||
Radio x;x.setProfileSwitchFeatures(uint8_t(requested));
|
||||
const uint8_t features=requested & x.profileSwitchCapabilities();
|
||||
assert(x.getProfileSwitchFeatures()==features && x.trace.empty());
|
||||
assert(x._profileSwitch.enabled==bool(features & mesh::RadioSwitchTuning::Warm));
|
||||
assert(!x._profileSwitch.rxValid && !x._profileSwitch.packetValid
|
||||
&& !x._profileSwitch.modulationValid);
|
||||
assert(x.startReceive()==0);x.trace.clear();x.beginProfileSwitch(true);
|
||||
assert(x.standby()==0);
|
||||
const bool warm=features & mesh::RadioSwitchTuning::Warm;
|
||||
const bool fs=features & mesh::RadioSwitchTuning::Fs;
|
||||
assert(x.trace==(warm ? (fs ? "If":"s") : "S"));x.trace.clear();
|
||||
assert(x.startReceive()==0);
|
||||
const bool packet=features & mesh::RadioSwitchTuning::Packet;
|
||||
assert(x.trace==(warm ? (packet ? "CWR":"CPWR") : "GSF"));
|
||||
x.endProfileSwitch(true);x.trace.clear();
|
||||
x.setProfileSwitchFeatures(features);
|
||||
assert(x.trace.empty() && !x._profileSwitch.rxValid && !x._profileSwitch.packetValid);
|
||||
}
|
||||
Radio off;off.setProfileSwitchOptimization(false);assert(off.startReceive()==0);
|
||||
assert(off.trace=="GUF" && !off._profileSwitch.rxValid);
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ class ProtocolBatchingTests(unittest.TestCase):
|
||||
"int16_t setProfileProtocol(", "int16_t startReceive()"))
|
||||
harness = r'''
|
||||
#include <cassert>
|
||||
#include <helpers/RadioSwitchTuning.h>
|
||||
#include <cstdint>
|
||||
#include <cstddef>
|
||||
#include <string>
|
||||
@@ -86,6 +87,8 @@ struct SX1262 {
|
||||
struct Radio: SX1262 {
|
||||
uint32_t _activityAt=0;bool _headerSeen=false;
|
||||
SX1262ProfileSwitchState _profileSwitch{true};
|
||||
uint8_t _profileSwitchFeatures=63;
|
||||
uint8_t getProfileSwitchFeatures() const { return _profileSwitchFeatures; }
|
||||
bool _coldStandby=false;uint32_t _profileFastResumes=0;
|
||||
@METHODS@
|
||||
};
|
||||
|
||||
@@ -41,7 +41,8 @@ struct SPIClass {
|
||||
#endif
|
||||
uint8_t transfer(uint8_t value) { ++bytes; return value^0xa5; }
|
||||
};
|
||||
struct ArduinoHal {
|
||||
struct RadioLibHal { virtual ~RadioLibHal()=default; };
|
||||
struct ArduinoHal : RadioLibHal {
|
||||
SPIClass* spi; SPISettings spiSettings; bool initInterface=false;
|
||||
explicit ArduinoHal(SPIClass& bus,SPISettings settings)
|
||||
: spi(&bus),spiSettings(settings) {}
|
||||
@@ -92,6 +93,63 @@ int main() {
|
||||
hal.spiTransfer(nullptr,0,nullptr);
|
||||
assert(explicitBus.bursts==before && explicitBus.bytes==0);
|
||||
assert(unused.bursts==0 && fallbackBus.bursts==0);
|
||||
|
||||
using Tuning=mesh::RadioSwitchTuning;
|
||||
assert(TunableRadioHal::find(&hal)==&external);
|
||||
assert(external.defaultHz()==MC_SX1262_SPI_HZ);
|
||||
assert(external.effectiveHz()==MC_SX1262_SPI_HZ);
|
||||
assert(external.getTuning().spi_mhz==0);
|
||||
assert(external.getTuning().flags==Tuning::Buffer);
|
||||
assert(external.capabilities().flags==Tuning::Buffer);
|
||||
assert(external.capabilities().spi_clocks==(2|4|8));
|
||||
// Disable bulk transfers without changing the controller, bus lifetime or
|
||||
// packet response. Driver-owned feature bits are handled by the wrapper.
|
||||
Tuning tuning;
|
||||
tuning.flags=Tuning::Warm;
|
||||
tuning.spi_mhz=2;
|
||||
assert(external.setTuning(tuning));
|
||||
assert(external.getTuning().flags==0 && external.effectiveHz()==2000000);
|
||||
assert(external.spiSettings.hz==2000000 && hal.spi==&explicitBus);
|
||||
const unsigned bytes=explicitBus.bytes;
|
||||
rx.fill(0x77);
|
||||
hal.spiTransfer(tx.data(),7,rx.data());
|
||||
assert(explicitBus.bursts==before && explicitBus.bytes==bytes+7);
|
||||
for (unsigned i=0;i<7;++i) assert(rx[i]==uint8_t(tx[i]^0xa5));
|
||||
for (unsigned i=7;i<rx.size();++i) assert(rx[i]==0x77);
|
||||
for (uint8_t clock : {uint8_t(4),uint8_t(8)}) {
|
||||
tuning.spi_mhz=clock;
|
||||
assert(external.setTuning(tuning));
|
||||
assert(external.effectiveHz()==unsigned(clock)*1000000);
|
||||
assert(external.spiSettings.hz==external.effectiveHz());
|
||||
}
|
||||
const Tuning previous=external.getTuning();
|
||||
for (uint8_t clock : {uint8_t(1),uint8_t(3),uint8_t(16),uint8_t(255)}) {
|
||||
tuning.spi_mhz=clock;
|
||||
assert(!external.setTuning(tuning) && external.getTuning()==previous);
|
||||
}
|
||||
tuning.spi_mhz=0;
|
||||
tuning.flags=Tuning::Buffer;
|
||||
assert(external.setTuning(tuning));
|
||||
assert(external.effectiveHz()==MC_SX1262_SPI_HZ);
|
||||
assert(external.spiSettings.hz==MC_SX1262_SPI_HZ);
|
||||
hal.spiTransfer(tx.data(),7,rx.data());
|
||||
assert(explicitBus.bursts==before+1 && explicitBus.bytes==bytes+7);
|
||||
assert(explicitBus.begins==1 && explicitBus.ends==1);
|
||||
|
||||
// The plain nRF SPIM3 byte route exposes clock selection but never admits
|
||||
// the buffered path that needs a separately qualified controller mapping.
|
||||
{
|
||||
TunableRadioHal byteHal(unused);
|
||||
assert(TunableRadioHal::find(&byteHal)==&byteHal);
|
||||
assert(byteHal.capabilities().flags==0 && byteHal.defaultHz()==8000000);
|
||||
assert(!byteHal.setTuning(tuning));
|
||||
tuning.flags=0;tuning.spi_mhz=2;
|
||||
assert(byteHal.setTuning(tuning) && byteHal.spiSettings.hz==2000000);
|
||||
}
|
||||
ArduinoHal foreign(unused,SPISettings(2000000,MSBFIRST,SPI_MODE0));
|
||||
assert(TunableRadioHal::find(&foreign)==nullptr);
|
||||
assert(TunableRadioHal::find(nullptr)==nullptr);
|
||||
assert(TunableRadioHal::find(&external)==&external);
|
||||
}
|
||||
'''
|
||||
|
||||
@@ -101,12 +159,15 @@ class SX126xBufferedRadioHalTests(unittest.TestCase):
|
||||
compiler=shutil.which(os.environ.get("CXX","g++"))
|
||||
self.assertIsNotNone(compiler)
|
||||
headers={name:(RADIO/name).read_text(encoding="ascii") for name in (
|
||||
"SX126xBufferedRadioHal.h","Nrf52BufferedRadioHal.h","ESP32BufferedRadioHal.h")}
|
||||
"SX126xBufferedRadioHal.h","Nrf52BufferedRadioHal.h","ESP32BufferedRadioHal.h",
|
||||
"TunableRadioHal.h")}
|
||||
with tempfile.TemporaryDirectory(prefix="sx126x-buffered-radio-") as folder:
|
||||
base=Path(folder)
|
||||
(base/"hal/Arduino").mkdir(parents=True)
|
||||
(base/"helpers/radiolib").mkdir(parents=True)
|
||||
(base/"hal/Arduino/ArduinoHal.h").write_text(MOCK,encoding="ascii")
|
||||
(base/"helpers/RadioSwitchTuning.h").write_text(
|
||||
(ROOT/"src/helpers/RadioSwitchTuning.h").read_text(),encoding="ascii")
|
||||
cpp,executable=base/"test.cpp",base/"test"
|
||||
cpp.write_text(FIXTURE,encoding="ascii")
|
||||
for arch,header in (("NRF52_PLATFORM","Nrf52BufferedRadioHal.h"),
|
||||
@@ -125,7 +186,7 @@ class SX126xBufferedRadioHalTests(unittest.TestCase):
|
||||
implementation if file==header else content,encoding="ascii")
|
||||
built=subprocess.run([compiler,"-std=c++17","-Wall","-Wextra",
|
||||
"-Werror",f"-D{arch}",f"-DMC_SX1262_SPI_HZ={clock}",
|
||||
"-I",str(base),str(cpp),"-o",str(executable)],
|
||||
"-I",str(base),"-I",str(ROOT/"src"),"-I",str(RADIO),str(cpp),"-o",str(executable)],
|
||||
capture_output=True,text=True,timeout=60)
|
||||
self.assertEqual(built.returncode,0,built.stdout+built.stderr)
|
||||
ran=subprocess.run([str(executable)],capture_output=True,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
"""Compile actual conservative nRF52 and buffered RP2040 radio constructors."""
|
||||
"""Compile actual tunable nRF52 and buffered RP2040 radio constructors."""
|
||||
from pathlib import Path
|
||||
import os
|
||||
import re
|
||||
@@ -118,7 +118,7 @@ void check() {{
|
||||
fixture+="\nint main() {"+"".join(f"{name}::check();" for name,_ in cases)+"}\n"
|
||||
self.compile_run(arch,fixture)
|
||||
|
||||
def test_all_new_nrf52_spim3_defaults_keep_prior_2mhz_byte_path(self):
|
||||
def test_all_nrf52_spim3_defaults_use_tunable_8mhz_byte_path(self):
|
||||
cases=[]
|
||||
sources=target_sources({"nrf52_base","rak4631_hw"})
|
||||
self.assertEqual(len(sources),32)
|
||||
@@ -134,12 +134,15 @@ void check() {{
|
||||
self.assertIn("#if MC_NRF52_HIL_SPIM1_RADIO",text)
|
||||
else:
|
||||
self.assertNotIn("BufferedRadioHal",text,str(source))
|
||||
constructions=re.findall(r"^RADIO_CLASS radio = new Module\(P_LORA_NSS[^;]+;",text,re.MULTILINE)
|
||||
constructions=re.findall(r"^static TunableRadioHal radio_hal\(([^;]+)\);\n"
|
||||
r"(RADIO_CLASS radio = new Module\(&radio_hal,[^;]+;)",
|
||||
text,re.MULTILINE)
|
||||
fallback=source.parent.name in {"heltec_t096","heltec_t1","heltec_t114"}
|
||||
self.assertEqual(len(constructions),2 if fallback else 1,str(source))
|
||||
for ordinal,construction in enumerate(constructions):
|
||||
for ordinal,(arguments,module) in enumerate(constructions):
|
||||
owns=fallback and ordinal==1
|
||||
self.assertEqual(construction.endswith(", SPI);"),not owns,str(source))
|
||||
self.assertEqual(arguments, "SPI, 8000000, true" if owns else "SPI",str(source))
|
||||
construction=f"static TunableRadioHal radio_hal({arguments});\n{module}"
|
||||
name=f"case_{len(cases)}"
|
||||
cases.append((name,f'''
|
||||
namespace {name} {{
|
||||
@@ -147,8 +150,9 @@ SPIClass SPI;
|
||||
struct BindDefaultBus {{ BindDefaultBus() {{testDefaultBus=&SPI;}} }} bindDefaultBus;
|
||||
{construction}
|
||||
void check() {{
|
||||
assert(radio.mod->owned && radio.mod->hal->spi==&SPI);
|
||||
assert(radio.mod->hal->spiSettings.hz==2000000);
|
||||
assert(!radio.mod->owned && radio.mod->hal==&radio_hal && radio.mod->hal->spi==&SPI);
|
||||
assert(radio.mod->hal->spiSettings.hz==8000000);
|
||||
assert(radio_hal.capabilities().flags==0 && radio_hal.capabilities().spi_clocks==(2|4|8));
|
||||
assert(radio.mod->hal->initInterface=={str(owns).lower()});
|
||||
radio.mod->hal->init();radio.mod->hal->term();
|
||||
assert(SPI.begins=={int(owns)} && SPI.ends=={int(owns)});
|
||||
@@ -163,15 +167,17 @@ void check() {{
|
||||
fixture+="\nint main() {"+"".join(f"{name}::check();" for name,_ in cases)+"}\n"
|
||||
self.compile_run("NRF52_PLATFORM",fixture)
|
||||
|
||||
def test_muzi_uno_and_lr1121_duo_keep_prior_constructor(self):
|
||||
def test_muzi_uno_and_lr1121_duo_keep_bus_and_gain_tunable_byte_clock(self):
|
||||
text=(ROOT/"variants/muzi_base/target.cpp").read_text()
|
||||
constructor=re.search(r"^RADIO_CLASS radio = new Module\(P_LORA_NSS[^;]+;",text,re.MULTILINE)
|
||||
constructor=re.search(r"^static TunableRadioHal radio_hal\([^;]+;\n"
|
||||
r"RADIO_CLASS radio = new Module\(&radio_hal,[^;]+;",text,re.MULTILINE)
|
||||
self.assertIsNotNone(constructor)
|
||||
self.assertNotIn("BufferedRadioHal",text)
|
||||
fixture=PREFIX+"\nSPIClass SPI;\n"+constructor.group(0)+r'''
|
||||
int main() {
|
||||
assert(radio.mod->hal->spi==&SPI);
|
||||
assert(radio.mod->owned && radio.mod->hal->spiSettings.hz==2000000);
|
||||
assert(!radio.mod->owned && radio.mod->hal->spiSettings.hz==8000000);
|
||||
assert(TunableRadioHal::find(radio.mod->hal)==&radio_hal);
|
||||
}
|
||||
'''
|
||||
for definitions in ((),("USE_SX1262",)):
|
||||
@@ -180,7 +186,8 @@ int main() {
|
||||
|
||||
def test_lr1110_m4_keeps_manual_spi_start_and_prior_2mhz_byte_path(self):
|
||||
text=(ROOT/"variants/thinknode_m4/target.cpp").read_text()
|
||||
construction=re.search(r"^RADIO_CLASS radio = new Module\(P_LORA_NSS[^;]+;",text,re.MULTILINE)
|
||||
construction=re.search(r"^static TunableRadioHal radio_hal\([^;]+;\n"
|
||||
r"RADIO_CLASS radio = new Module\(&radio_hal,[^;]+;",text,re.MULTILINE)
|
||||
self.assertIsNotNone(construction)
|
||||
self.assertNotIn("BufferedRadioHal",text)
|
||||
self.assertIn("SPI.setPins(P_LORA_MISO, P_LORA_SCLK, P_LORA_MOSI);",text)
|
||||
@@ -188,7 +195,7 @@ int main() {
|
||||
self.assertIn("int status = radio.begin(",text)
|
||||
fixture=PREFIX+"\nSPIClass SPI;\n"+construction.group(0)+r'''
|
||||
int main() {
|
||||
assert(radio.mod->owned && radio.mod->hal->spi==&SPI);
|
||||
assert(!radio.mod->owned && radio.mod->hal==&radio_hal && radio.mod->hal->spi==&SPI);
|
||||
assert(radio.mod->hal->spiSettings.hz==2000000 && !radio.mod->hal->initInterface);
|
||||
radio.mod->hal->init();radio.mod->hal->term();
|
||||
assert(SPI.begins==0 && SPI.ends==0);
|
||||
|
||||
@@ -73,6 +73,7 @@ class XiaoNrf52BufferedSpiTests(unittest.TestCase):
|
||||
#include <cstdint>
|
||||
#include <hal/Arduino/ArduinoHal.h>
|
||||
#include "XiaoRadioSpiConfig.h"
|
||||
#include <helpers/radiolib/TunableRadioHal.h>
|
||||
#define SPI_INTERFACES_COUNT 2
|
||||
#define PIN_SPI_MISO 9
|
||||
#define PIN_SPI_MOSI 10
|
||||
@@ -118,7 +119,7 @@ int main() {
|
||||
assert(radio.module->hal->spi==&SPI);
|
||||
assert(SPI.device->number==(MC_XIAO_NRF52_HIL_FAST_SPI?2:3));
|
||||
assert(SPI1.device->number==(MC_XIAO_NRF52_HIL_FAST_SPI?3:2));
|
||||
assert(radio.module->hal->spiSettings.hz==(MC_XIAO_NRF52_HIL_FAST_SPI?8000000:2000000));
|
||||
assert(radio.module->hal->spiSettings.hz==8000000);
|
||||
assert(!radio.module->hal->initInterface && SPI.begins==0 && SPI1.begins==0);
|
||||
radio.module->hal->init();radio.module->hal->term();
|
||||
assert(SPI.begins==0 && SPI.ends==0); // The board still owns bus lifetime.
|
||||
@@ -187,7 +188,7 @@ int main() {
|
||||
self.assertIn(diagnostic, result.stderr)
|
||||
|
||||
def test_default_candidate_has_no_code_or_sdk_dependency_and_recipes_do_not_enable_it(self):
|
||||
result = subprocess.run([os.environ.get("CXX", "g++"), "-E", "-P", "-x", "c++", str(CONFIG)],
|
||||
result = subprocess.run([os.environ.get("CXX", "g++"), "-E", "-P", "-x", "c++", "-I", str(ROOT / "src"), str(CONFIG)],
|
||||
capture_output=True, text=True)
|
||||
self.assertEqual(result.returncode, 0, result.stderr)
|
||||
self.assertFalse(result.stdout.strip())
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#include <Arduino.h>
|
||||
#include "target.h"
|
||||
#include <helpers/radiolib/TunableRadioHal.h>
|
||||
#include <helpers/ArduinoHelpers.h>
|
||||
|
||||
GAT56230SMeshKitBoard board;
|
||||
@@ -18,7 +19,8 @@ GAT56230SMeshKitBoard board;
|
||||
#endif
|
||||
|
||||
|
||||
RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY, SPI);
|
||||
static TunableRadioHal radio_hal(SPI);
|
||||
RADIO_CLASS radio = new Module(&radio_hal, P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY);
|
||||
|
||||
WRAPPER_CLASS radio_driver(radio, board);
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#include <Arduino.h>
|
||||
#include "target.h"
|
||||
#include <helpers/radiolib/TunableRadioHal.h>
|
||||
#include <helpers/ArduinoHelpers.h>
|
||||
|
||||
GAT562EVBProBoard board;
|
||||
@@ -17,7 +18,8 @@ GAT562EVBProBoard board;
|
||||
#endif
|
||||
|
||||
|
||||
RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY, SPI);
|
||||
static TunableRadioHal radio_hal(SPI);
|
||||
RADIO_CLASS radio = new Module(&radio_hal, P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY);
|
||||
|
||||
WRAPPER_CLASS radio_driver(radio, board);
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#include <Arduino.h>
|
||||
#include "target.h"
|
||||
#include <helpers/radiolib/TunableRadioHal.h>
|
||||
#include <helpers/ArduinoHelpers.h>
|
||||
|
||||
GAT562MeshTrackerProBoard board;
|
||||
@@ -18,7 +19,8 @@ GAT562MeshTrackerProBoard board;
|
||||
#endif
|
||||
|
||||
|
||||
RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY, SPI);
|
||||
static TunableRadioHal radio_hal(SPI);
|
||||
RADIO_CLASS radio = new Module(&radio_hal, P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY);
|
||||
|
||||
WRAPPER_CLASS radio_driver(radio, board);
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#include <Arduino.h>
|
||||
#include "target.h"
|
||||
#include <helpers/radiolib/TunableRadioHal.h>
|
||||
#include <helpers/ArduinoHelpers.h>
|
||||
|
||||
GAT56MeshWatch13Board board;
|
||||
@@ -15,7 +16,8 @@ GAT56MeshWatch13Board board;
|
||||
#endif
|
||||
|
||||
|
||||
RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY, SPI);
|
||||
static TunableRadioHal radio_hal(SPI);
|
||||
RADIO_CLASS radio = new Module(&radio_hal, P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY);
|
||||
|
||||
WRAPPER_CLASS radio_driver(radio, board);
|
||||
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
#include <Arduino.h>
|
||||
#include "target.h"
|
||||
#include <helpers/radiolib/TunableRadioHal.h>
|
||||
#include <helpers/ArduinoHelpers.h>
|
||||
#include <helpers/sensors/MicroNMEALocationProvider.h>
|
||||
|
||||
MeshSolarBoard board;
|
||||
|
||||
RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY, SPI);
|
||||
static TunableRadioHal radio_hal(SPI);
|
||||
RADIO_CLASS radio = new Module(&radio_hal, P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY);
|
||||
|
||||
WRAPPER_CLASS radio_driver(radio, board);
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#include "target.h"
|
||||
#include <helpers/radiolib/TunableRadioHal.h>
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <helpers/ArduinoHelpers.h>
|
||||
@@ -10,9 +11,11 @@
|
||||
T096Board board;
|
||||
|
||||
#if defined(P_LORA_SCLK)
|
||||
RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY, SPI);
|
||||
static TunableRadioHal radio_hal(SPI);
|
||||
RADIO_CLASS radio = new Module(&radio_hal, P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY);
|
||||
#else
|
||||
RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY);
|
||||
static TunableRadioHal radio_hal(SPI, 8000000, true);
|
||||
RADIO_CLASS radio = new Module(&radio_hal, P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY);
|
||||
#endif
|
||||
|
||||
WRAPPER_CLASS radio_driver(radio, board);
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#include "target.h"
|
||||
#include <helpers/radiolib/TunableRadioHal.h>
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <helpers/ArduinoHelpers.h>
|
||||
@@ -10,9 +11,11 @@
|
||||
T1Board board;
|
||||
|
||||
#if defined(P_LORA_SCLK)
|
||||
RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY, SPI);
|
||||
static TunableRadioHal radio_hal(SPI);
|
||||
RADIO_CLASS radio = new Module(&radio_hal, P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY);
|
||||
#else
|
||||
RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY);
|
||||
static TunableRadioHal radio_hal(SPI, 8000000, true);
|
||||
RADIO_CLASS radio = new Module(&radio_hal, P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY);
|
||||
#endif
|
||||
|
||||
WRAPPER_CLASS radio_driver(radio, board);
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#include "target.h"
|
||||
#include <helpers/radiolib/TunableRadioHal.h>
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <helpers/ArduinoHelpers.h>
|
||||
@@ -10,9 +11,11 @@
|
||||
T114Board board;
|
||||
|
||||
#if defined(P_LORA_SCLK)
|
||||
RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY, SPI);
|
||||
static TunableRadioHal radio_hal(SPI);
|
||||
RADIO_CLASS radio = new Module(&radio_hal, P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY);
|
||||
#else
|
||||
RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY);
|
||||
static TunableRadioHal radio_hal(SPI, 8000000, true);
|
||||
RADIO_CLASS radio = new Module(&radio_hal, P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY);
|
||||
#endif
|
||||
|
||||
WRAPPER_CLASS radio_driver(radio, board);
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#include "target.h"
|
||||
#include <helpers/radiolib/TunableRadioHal.h>
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <helpers/ArduinoHelpers.h>
|
||||
@@ -14,7 +15,8 @@ HeltecTowerV2Board board;
|
||||
static Nrf52BufferedRadioHal radioHal(SPI, 8000000);
|
||||
RADIO_CLASS radio = new Module(&radioHal, P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY);
|
||||
#else
|
||||
RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY, SPI);
|
||||
static TunableRadioHal radio_hal(SPI);
|
||||
RADIO_CLASS radio = new Module(&radio_hal, P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY);
|
||||
#endif
|
||||
|
||||
WRAPPER_CLASS radio_driver(radio, board);
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
#include <Arduino.h>
|
||||
#include "target.h"
|
||||
#include <helpers/radiolib/TunableRadioHal.h>
|
||||
#include <helpers/ArduinoHelpers.h>
|
||||
|
||||
IkokaNrf52Board board;
|
||||
|
||||
RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY, SPI);
|
||||
static TunableRadioHal radio_hal(SPI);
|
||||
RADIO_CLASS radio = new Module(&radio_hal, P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY);
|
||||
|
||||
WRAPPER_CLASS radio_driver(radio, board);
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#include <Arduino.h>
|
||||
#include "target.h"
|
||||
#include <helpers/radiolib/TunableRadioHal.h>
|
||||
#include <helpers/ArduinoHelpers.h>
|
||||
|
||||
IkokaNanoNRFBoard board;
|
||||
@@ -9,7 +10,8 @@ IkokaNanoNRFBoard board;
|
||||
// MomentaryButton user_btn(PIN_USER_BTN, 1000, true);
|
||||
#endif
|
||||
|
||||
RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY, SPI);
|
||||
static TunableRadioHal radio_hal(SPI);
|
||||
RADIO_CLASS radio = new Module(&radio_hal, P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY);
|
||||
|
||||
WRAPPER_CLASS radio_driver(radio, board);
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#include <Arduino.h>
|
||||
#include "target.h"
|
||||
#include <helpers/radiolib/TunableRadioHal.h>
|
||||
#include <helpers/ArduinoHelpers.h>
|
||||
|
||||
IkokaStickNRFBoard board;
|
||||
@@ -9,7 +10,8 @@ IkokaStickNRFBoard board;
|
||||
MomentaryButton user_btn(PIN_USER_BTN, 1000, true);
|
||||
#endif
|
||||
|
||||
RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY, SPI);
|
||||
static TunableRadioHal radio_hal(SPI);
|
||||
RADIO_CLASS radio = new Module(&radio_hal, P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY);
|
||||
|
||||
WRAPPER_CLASS radio_driver(radio, board);
|
||||
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
#include <Arduino.h>
|
||||
#include "target.h"
|
||||
#include <helpers/radiolib/TunableRadioHal.h>
|
||||
#include <helpers/ArduinoHelpers.h>
|
||||
|
||||
KeepteenLT1Board board;
|
||||
|
||||
RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY, SPI);
|
||||
static TunableRadioHal radio_hal(SPI);
|
||||
RADIO_CLASS radio = new Module(&radio_hal, P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY);
|
||||
|
||||
WRAPPER_CLASS radio_driver(radio, board);
|
||||
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
#include <Arduino.h>
|
||||
#include "target.h"
|
||||
#include <helpers/radiolib/TunableRadioHal.h>
|
||||
#include <helpers/ArduinoHelpers.h>
|
||||
#include <helpers/sensors/MicroNMEALocationProvider.h>
|
||||
|
||||
TImpulsePlusBoard board;
|
||||
|
||||
RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY, SPI);
|
||||
static TunableRadioHal radio_hal(SPI);
|
||||
RADIO_CLASS radio = new Module(&radio_hal, P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY);
|
||||
|
||||
WRAPPER_CLASS radio_driver(radio, board);
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#include <Arduino.h>
|
||||
#include "target.h"
|
||||
#include <helpers/radiolib/TunableRadioHal.h>
|
||||
#include <helpers/radiolib/ESP32BufferedRadioHal.h>
|
||||
|
||||
TDeckBoard board;
|
||||
@@ -9,7 +10,8 @@ TDeckBoard board;
|
||||
static ESP32BufferedRadioHal radio_hal(spi);
|
||||
RADIO_CLASS radio = new Module(&radio_hal, P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY);
|
||||
#else
|
||||
RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY);
|
||||
static TunableRadioHal radio_hal(SPI, MC_SX1262_SPI_HZ, true);
|
||||
RADIO_CLASS radio = new Module(&radio_hal, P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY);
|
||||
#endif
|
||||
|
||||
WRAPPER_CLASS radio_driver(radio, board);
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
#include <Arduino.h>
|
||||
#include "target.h"
|
||||
#include <helpers/radiolib/TunableRadioHal.h>
|
||||
#include <helpers/ArduinoHelpers.h>
|
||||
#include <helpers/sensors/MicroNMEALocationProvider.h>
|
||||
|
||||
TechoBoard board;
|
||||
|
||||
RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY, SPI);
|
||||
static TunableRadioHal radio_hal(SPI);
|
||||
RADIO_CLASS radio = new Module(&radio_hal, P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY);
|
||||
|
||||
WRAPPER_CLASS radio_driver(radio, board);
|
||||
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
#include <Arduino.h>
|
||||
#include "target.h"
|
||||
#include <helpers/radiolib/TunableRadioHal.h>
|
||||
#include <helpers/ArduinoHelpers.h>
|
||||
#include <helpers/sensors/MicroNMEALocationProvider.h>
|
||||
|
||||
TechoCardBoard board;
|
||||
|
||||
RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY, SPI);
|
||||
static TunableRadioHal radio_hal(SPI);
|
||||
RADIO_CLASS radio = new Module(&radio_hal, P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY);
|
||||
|
||||
WRAPPER_CLASS radio_driver(radio, board);
|
||||
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
#include <Arduino.h>
|
||||
#include "target.h"
|
||||
#include <helpers/radiolib/TunableRadioHal.h>
|
||||
#include <helpers/ArduinoHelpers.h>
|
||||
#include <helpers/sensors/MicroNMEALocationProvider.h>
|
||||
|
||||
TechoBoard board;
|
||||
|
||||
RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY, SPI);
|
||||
static TunableRadioHal radio_hal(SPI);
|
||||
RADIO_CLASS radio = new Module(&radio_hal, P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY);
|
||||
|
||||
WRAPPER_CLASS radio_driver(radio, board);
|
||||
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
#include <Arduino.h>
|
||||
#include "target.h"
|
||||
#include <helpers/radiolib/TunableRadioHal.h>
|
||||
#include <helpers/ArduinoHelpers.h>
|
||||
#include <helpers/sensors/MicroNMEALocationProvider.h>
|
||||
|
||||
HeltecMeshPocket board;
|
||||
|
||||
RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY, SPI);
|
||||
static TunableRadioHal radio_hal(SPI);
|
||||
RADIO_CLASS radio = new Module(&radio_hal, P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY);
|
||||
|
||||
WRAPPER_CLASS radio_driver(radio, board);
|
||||
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
#include "target.h"
|
||||
#include <helpers/radiolib/TunableRadioHal.h>
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <helpers/ArduinoHelpers.h>
|
||||
|
||||
MeshtinyBoard board;
|
||||
|
||||
RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY, SPI);
|
||||
static TunableRadioHal radio_hal(SPI);
|
||||
RADIO_CLASS radio = new Module(&radio_hal, P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY);
|
||||
|
||||
WRAPPER_CLASS radio_driver(radio, board);
|
||||
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
#include <Arduino.h>
|
||||
#include <nrf_gpio.h>
|
||||
#include "target.h"
|
||||
#include <helpers/radiolib/TunableRadioHal.h>
|
||||
#include "variant.h"
|
||||
|
||||
MuziBaseBoard board;
|
||||
|
||||
RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY, SPI);
|
||||
static TunableRadioHal radio_hal(SPI);
|
||||
RADIO_CLASS radio = new Module(&radio_hal, P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY);
|
||||
|
||||
WRAPPER_CLASS radio_driver(radio, board);
|
||||
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
#include <Arduino.h>
|
||||
#include "target.h"
|
||||
#include <helpers/radiolib/TunableRadioHal.h>
|
||||
#include <helpers/ArduinoHelpers.h>
|
||||
|
||||
R1NeoBoard board;
|
||||
|
||||
RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY, SPI);
|
||||
static TunableRadioHal radio_hal(SPI);
|
||||
RADIO_CLASS radio = new Module(&radio_hal, P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY);
|
||||
WRAPPER_CLASS radio_driver(radio, board);
|
||||
|
||||
#ifdef DISPLAY_CLASS
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#include "target.h"
|
||||
#include <helpers/radiolib/TunableRadioHal.h>
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <helpers/ArduinoHelpers.h>
|
||||
@@ -6,7 +7,8 @@
|
||||
|
||||
NanoG2Ultra board;
|
||||
|
||||
RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY, SPI);
|
||||
static TunableRadioHal radio_hal(SPI);
|
||||
RADIO_CLASS radio = new Module(&radio_hal, P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY);
|
||||
|
||||
WRAPPER_CLASS radio_driver(radio, board);
|
||||
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
#include <Arduino.h>
|
||||
#include "target.h"
|
||||
#include <helpers/radiolib/TunableRadioHal.h>
|
||||
#include <helpers/ArduinoHelpers.h>
|
||||
|
||||
PromicroBoard board;
|
||||
|
||||
RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY, SPI);
|
||||
static TunableRadioHal radio_hal(SPI);
|
||||
RADIO_CLASS radio = new Module(&radio_hal, P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY);
|
||||
|
||||
WRAPPER_CLASS radio_driver(radio, board);
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#include <Arduino.h>
|
||||
#include "target.h"
|
||||
#include <helpers/radiolib/TunableRadioHal.h>
|
||||
#include <helpers/ArduinoHelpers.h>
|
||||
|
||||
RAK4631Board board;
|
||||
@@ -17,7 +18,8 @@ RAK4631Board board;
|
||||
#endif
|
||||
#endif
|
||||
|
||||
RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY, SPI);
|
||||
static TunableRadioHal radio_hal(SPI);
|
||||
RADIO_CLASS radio = new Module(&radio_hal, P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY);
|
||||
|
||||
WRAPPER_CLASS radio_driver(radio, board);
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#include <Arduino.h>
|
||||
#include "target.h"
|
||||
#include <helpers/radiolib/TunableRadioHal.h>
|
||||
#include <helpers/ArduinoHelpers.h>
|
||||
#include <helpers/nrf52/Nrf52HilSpim1Owner.h>
|
||||
#if MC_NRF52_HIL_SPIM1_RADIO
|
||||
@@ -21,7 +22,8 @@ RAKWismeshTagBoard board;
|
||||
static Nrf52BufferedRadioHal radioHal(SPI, 8000000);
|
||||
RADIO_CLASS radio = new Module(&radioHal, P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY);
|
||||
#else
|
||||
RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY, SPI);
|
||||
static TunableRadioHal radio_hal(SPI);
|
||||
RADIO_CLASS radio = new Module(&radio_hal, P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY);
|
||||
#endif
|
||||
|
||||
WRAPPER_CLASS radio_driver(radio, board);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#include <Arduino.h>
|
||||
#include "target.h"
|
||||
#include <helpers/radiolib/TunableRadioHal.h>
|
||||
#include <helpers/ArduinoHelpers.h>
|
||||
|
||||
#if ENV_INCLUDE_GPS
|
||||
@@ -8,7 +9,8 @@
|
||||
|
||||
SenseCapSolarBoard board;
|
||||
|
||||
RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY, SPI);
|
||||
static TunableRadioHal radio_hal(SPI);
|
||||
RADIO_CLASS radio = new Module(&radio_hal, P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY);
|
||||
|
||||
WRAPPER_CLASS radio_driver(radio, board);
|
||||
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
#include <Arduino.h>
|
||||
#include "target.h"
|
||||
#include <helpers/radiolib/TunableRadioHal.h>
|
||||
#include <helpers/ArduinoHelpers.h>
|
||||
#include <helpers/sensors/MicroNMEALocationProvider.h>
|
||||
|
||||
ThinkNodeM1Board board;
|
||||
|
||||
RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY, SPI);
|
||||
static TunableRadioHal radio_hal(SPI);
|
||||
RADIO_CLASS radio = new Module(&radio_hal, P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY);
|
||||
|
||||
WRAPPER_CLASS radio_driver(radio, board);
|
||||
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
#include <Arduino.h>
|
||||
#include "target.h"
|
||||
#include <helpers/radiolib/TunableRadioHal.h>
|
||||
#include <helpers/sensors/MicroNMEALocationProvider.h>
|
||||
|
||||
ThinkNodeM4Board board;
|
||||
|
||||
RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY, SPI);
|
||||
static TunableRadioHal radio_hal(SPI, 2000000);
|
||||
RADIO_CLASS radio = new Module(&radio_hal, P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY);
|
||||
|
||||
WRAPPER_CLASS radio_driver(radio, board);
|
||||
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
#include <Arduino.h>
|
||||
#include "target.h"
|
||||
#include <helpers/radiolib/TunableRadioHal.h>
|
||||
#include <helpers/ArduinoHelpers.h>
|
||||
#include <helpers/sensors/MicroNMEALocationProvider.h>
|
||||
|
||||
ThinkNodeM6Board board;
|
||||
|
||||
RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY, SPI);
|
||||
static TunableRadioHal radio_hal(SPI);
|
||||
RADIO_CLASS radio = new Module(&radio_hal, P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY);
|
||||
|
||||
WRAPPER_CLASS radio_driver(radio, board);
|
||||
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
#include <Arduino.h>
|
||||
#include "target.h"
|
||||
#include <helpers/radiolib/TunableRadioHal.h>
|
||||
#include <helpers/ArduinoHelpers.h>
|
||||
#include <helpers/sensors/MicroNMEALocationProvider.h>
|
||||
#include <Wire.h>
|
||||
|
||||
ThinkNodeM8Board board;
|
||||
|
||||
RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY, SPI);
|
||||
static TunableRadioHal radio_hal(SPI);
|
||||
RADIO_CLASS radio = new Module(&radio_hal, P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY);
|
||||
|
||||
WRAPPER_CLASS radio_driver(radio, board);
|
||||
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
#include <Arduino.h>
|
||||
#include "target.h"
|
||||
#include <helpers/radiolib/TunableRadioHal.h>
|
||||
#include <helpers/ArduinoHelpers.h>
|
||||
#include <helpers/sensors/MicroNMEALocationProvider.h>
|
||||
|
||||
WioTrackerL1Board board;
|
||||
|
||||
RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY, SPI);
|
||||
static TunableRadioHal radio_hal(SPI);
|
||||
RADIO_CLASS radio = new Module(&radio_hal, P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY);
|
||||
|
||||
WRAPPER_CLASS radio_driver(radio, board);
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#include <Arduino.h>
|
||||
#include "target.h"
|
||||
#include <helpers/radiolib/TunableRadioHal.h>
|
||||
#include <helpers/ArduinoHelpers.h>
|
||||
|
||||
#ifdef ENV_INCLUDE_GPS
|
||||
@@ -8,7 +9,8 @@
|
||||
|
||||
WioTrackerL1Board board;
|
||||
|
||||
RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY, SPI);
|
||||
static TunableRadioHal radio_hal(SPI);
|
||||
RADIO_CLASS radio = new Module(&radio_hal, P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY);
|
||||
|
||||
WRAPPER_CLASS radio_driver(radio, board);
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#include <Arduino.h>
|
||||
#include "target.h"
|
||||
#include <helpers/radiolib/TunableRadioHal.h>
|
||||
#include "XiaoRadioSpiConfig.h"
|
||||
#include <helpers/ArduinoHelpers.h>
|
||||
#ifdef USE_CC310_HW_CRYPTO
|
||||
@@ -21,7 +22,8 @@ XiaoNrf52Board board;
|
||||
static Nrf52BufferedRadioHal radioHal(SPI, 8000000);
|
||||
RADIO_CLASS radio = new Module(&radioHal, P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY);
|
||||
#else
|
||||
RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY, SPI);
|
||||
static TunableRadioHal radio_hal(SPI);
|
||||
RADIO_CLASS radio = new Module(&radio_hal, P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY);
|
||||
#endif
|
||||
|
||||
WRAPPER_CLASS radio_driver(radio, board);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#include <Arduino.h>
|
||||
#include "target.h"
|
||||
#include <helpers/radiolib/TunableRadioHal.h>
|
||||
#include <helpers/radiolib/ESP32BufferedRadioHal.h>
|
||||
|
||||
XiaoS3WIOBoard board;
|
||||
@@ -9,7 +10,8 @@ XiaoS3WIOBoard board;
|
||||
static ESP32BufferedRadioHal radio_hal(spi);
|
||||
RADIO_CLASS radio = new Module(&radio_hal, P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY);
|
||||
#else
|
||||
RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY);
|
||||
static TunableRadioHal radio_hal(SPI, MC_SX1262_SPI_HZ, true);
|
||||
RADIO_CLASS radio = new Module(&radio_hal, P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY);
|
||||
#endif
|
||||
|
||||
WRAPPER_CLASS radio_driver(radio, board);
|
||||
|
||||
Reference in new issue
Block a user