mirror of
https://github.com/mikecarper/MeshCore.git
synced 2026-11-04 12:07:42 +00:00
feat(presets): MESH_ENABLE_RADIO_PRESETS build switch (off on STM32 and ESP-NOW-only)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
6806827c37
commit
1f76b79fa4
20 files changed
+244
-9
No files matched your search
@@ -105,7 +105,7 @@ over the normal binary USB, BLE, or TCP connection:
|
||||
| [`get bootloader.ver`](cli_commands.md#view-the-bootloader-version-nrf52-only) | nRF52 Companion terminal and command `0x42`; unidentifiable metadata returns `unknown` |
|
||||
| [`get/set sdcard`](test-results/companion_cli_parity_2026-10-23.md#shared-sd-commands) | nRF52 builds with SD OTA storage; maintenance retains OTA and Ethernet ownership guards |
|
||||
| `erase` | Local terminal and binary command `0x42`; reboot after erasing stored settings |
|
||||
| [`get preset [n]`; `add preset`; `load preset`; `del preset`](cli_commands.md#save-and-switch-named-radio-presets) | Local terminal and binary command `0x42` only; LoRa CLI cannot use presets on a Companion. `load preset` saves and applies at once with no revert window |
|
||||
| [`get preset [n]`; `add preset`; `load preset`; `del preset`](cli_commands.md#save-and-switch-named-radio-presets) | Local terminal and binary command `0x42` only; LoRa CLI cannot use presets on a Companion. `load preset` saves and applies at once with no revert window; omitted on STM32 and ESP-NOW-only builds (`MESH_ENABLE_RADIO_PRESETS`) |
|
||||
| `set freq <MHz>` | Local terminal, binary command `0x42`, and authorized LoRa CLI; applies automatically (infrastructure waits two seconds and for the reply to drain) |
|
||||
| [`get public.key`](cli_commands.md#view-this-nodes-public-key) | Companion text terminal and binary command `0x42`; read-only, no private-key export feature required |
|
||||
| `get prv.key` | Local terminal and binary command `0x42`; requires private key export enabled |
|
||||
@@ -173,7 +173,7 @@ should use command `0x42`. See [Companion radio binary protocol](companion_proto
|
||||
| Radio | [`get tx`; `set tx <dbm>`](cli_commands.md#view-or-change-this-nodes-transmit-power) | Board TX-power limits apply; busy changes apply automatically | Yes | Yes | Yes |
|
||||
| Radio | [`tempradio ...`; `normalradio`](cli_commands.md#change-the-radio-parameters-for-a-set-duration) | Full parser | Yes | Yes | Yes |
|
||||
| Radio | [`get/set/del radioat`; `get/set/del tempradioat`](cli_commands.md#schedule-radio-parameter-changes) | Full parser | Yes | Yes | Yes |
|
||||
| Radio | [`get preset [n]`; `add preset`; `load preset`; `del preset`](cli_commands.md#save-and-switch-named-radio-presets) | Admin only remotely; remote `load preset` reverts after 10 min unless an admin logs in again | Yes | Yes | Yes |
|
||||
| Radio | [`get preset [n]`; `add preset`; `load preset`; `del preset`](cli_commands.md#save-and-switch-named-radio-presets) | Admin only remotely; remote `load preset` reverts after 10 min unless an admin logs in again; omitted on STM32 and ESP-NOW-only builds (`MESH_ENABLE_RADIO_PRESETS`) | Yes | Yes | Yes |
|
||||
| Radio | [`get freq`; `set freq <mhz>`](cli_commands.md#view-or-change-this-nodes-frequency) | Local or authorized LoRa CLI | Yes | Yes | Yes |
|
||||
| Radio | [`get/set radio.rxgain`](cli_commands.md#view-or-change-this-nodes-rx-boosted-gain-mode-sx12xx-and-lr1110-v1141) | Supported radio | Feature | Feature | Feature |
|
||||
| Radio | [`get/set radio.fem.rxgain`](cli_commands.md#view-or-change-the-lora-fem-receive-path-gain-state-on-supported-boards) | Controllable LoRa FEM | Feature | Feature | Feature |
|
||||
@@ -354,7 +354,7 @@ should use command `0x42`. See [Companion radio binary protocol](companion_proto
|
||||
| Radio | [`get tx`; `set tx <dbm>`](cli_commands.md#view-or-change-this-nodes-transmit-power) | Board TX-power limits apply; busy changes apply automatically | Yes | Yes | Yes | Yes | Yes |
|
||||
| Radio | [`tempradio ...`; `normalradio`](cli_commands.md#change-the-radio-parameters-for-a-set-duration) | Full parser | Yes | Yes | Yes | Yes | Yes |
|
||||
| Radio | [`get/set/del radioat`; `get/set/del tempradioat`](cli_commands.md#schedule-radio-parameter-changes) | Full parser | Yes | Yes | Yes | Yes | Yes |
|
||||
| Radio | [`get preset [n]`; `add preset`; `load preset`; `del preset`](cli_commands.md#save-and-switch-named-radio-presets) | Admin only remotely; remote `load preset` reverts after 10 min unless an admin logs in again | Yes | Yes | Yes | Yes | Yes |
|
||||
| Radio | [`get preset [n]`; `add preset`; `load preset`; `del preset`](cli_commands.md#save-and-switch-named-radio-presets) | Admin only remotely; remote `load preset` reverts after 10 min unless an admin logs in again; omitted on STM32 and ESP-NOW-only builds (`MESH_ENABLE_RADIO_PRESETS`) | Yes | Yes | Yes | Yes | Yes |
|
||||
| Radio | [`get freq`; `set freq <mhz>`](cli_commands.md#view-or-change-this-nodes-frequency) | Local or authorized LoRa CLI | Yes | Yes | Yes | Yes | Yes |
|
||||
| Radio | [`get/set radio.rxgain`](cli_commands.md#view-or-change-this-nodes-rx-boosted-gain-mode-sx12xx-and-lr1110-v1141) | Supported radio | Feature | Feature | Feature | Feature | Feature |
|
||||
| Radio | [`get/set espnow.channel`](cli_commands.md#view-or-change-the-primary-esp-nowwifi-channel) | Primary ESP-NOW mesh radio | Feature | Feature | Feature | Feature | Feature |
|
||||
|
||||
@@ -2206,6 +2206,12 @@ remote load is refused. A remote load is also refused while a TempRadio session
|
||||
is active. If the revert itself fails, it retries every minute and at every
|
||||
boot.
|
||||
|
||||
**Build switch:** `MESH_ENABLE_RADIO_PRESETS`. Flash-constrained STM32WL builds
|
||||
(such as `wio-e5-mini_repeater`) and ESP-NOW-only builds (`MESH_PRIMARY_ESPNOW`
|
||||
without a LoRa primary radio) omit radio presets by default; there the preset
|
||||
commands return the ordinary unknown-command reply. Other builds include them;
|
||||
set `-D MESH_ENABLE_RADIO_PRESETS=0` or `=1` to override.
|
||||
|
||||
---
|
||||
|
||||
#### View or change this node's transmit power
|
||||
|
||||
@@ -31,7 +31,9 @@
|
||||
#include <helpers/ui/StartupScreen.h>
|
||||
#include "NotificationSettingsFile.h"
|
||||
#include "MyMesh.h"
|
||||
#if MESH_ENABLE_RADIO_PRESETS
|
||||
#include <helpers/RadioPresetFile.h>
|
||||
#endif
|
||||
#include <helpers/HostKiss.h>
|
||||
#if defined(RAK_4631) && defined(COMPANION_RADIO_FULL) \
|
||||
|| (defined(STM32_PLATFORM) && defined(STM32WLxx) && COMPANION_FEATURE_TEXT_TERMINAL)
|
||||
@@ -3461,6 +3463,7 @@ void MyMesh::begin(bool has_display, bool radio_available,
|
||||
_clock_sync.begin(nullptr);
|
||||
#endif
|
||||
|
||||
#if MESH_ENABLE_RADIO_PRESETS
|
||||
// After prefs are loaded, HIL-adjusted and sanitised. /presets is shared with
|
||||
// infrastructure roles, so a node reflashed to Companion can carry a pending
|
||||
// remote load; begin() reverts it through presetApply, which saves _prefs.
|
||||
@@ -3471,6 +3474,7 @@ void MyMesh::begin(bool has_display, bool radio_available,
|
||||
};
|
||||
_radio_presets.begin(preset_hooks);
|
||||
}
|
||||
#endif
|
||||
configureRadioFromPrefs();
|
||||
#if MESH_ENABLE_HOST_KISS
|
||||
mesh::hostkiss::begin(_store->getPrimaryFS(), *_radio, _radio_profiles, *this);
|
||||
@@ -3687,6 +3691,7 @@ MyMesh::RadioSettingResult MyMesh::applyAndSaveRadioParams(
|
||||
return preamble_restored ? RadioSettingResult::SaveFailed : RadioSettingResult::SaveUnconfirmed;
|
||||
}
|
||||
|
||||
#if MESH_ENABLE_RADIO_PRESETS
|
||||
bool MyMesh::presetLoad(void* ctx, uint8_t* buf, size_t cap, size_t& len) {
|
||||
MyMesh* self = static_cast<MyMesh*>(ctx);
|
||||
return mesh::loadRadioPresetFile(self->_store->getPrimaryFS(), buf, cap, len);
|
||||
@@ -3734,6 +3739,7 @@ bool MyMesh::presetTempRadioActive(void* ctx) {
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
|
||||
bool MyMesh::applyRxPowerSavingRfRxDisabled(const char* value, char* reply, size_t capacity) {
|
||||
if (strcmp(value, "on") && strcmp(value, "off")) {
|
||||
@@ -11380,10 +11386,12 @@ bool MyMesh::handleCommand(const char* command, uint32_t sender_timestamp,
|
||||
if (mesh::handleMorseReplyCommand(_morse_reply, _radio, command, reply,
|
||||
reply_capacity, millis())) return true;
|
||||
if (mesh::handleMorseCommand(_radio, command, reply, reply_capacity)) return true;
|
||||
#if MESH_ENABLE_RADIO_PRESETS
|
||||
// Presets are local-only on companions: there is no revert path, so an
|
||||
// authorized LoRa CLI command (nonzero timestamp) is not consumed here.
|
||||
if (sender_timestamp == 0 && _radio_presets.handle(command, reply, reply_capacity, false))
|
||||
return true;
|
||||
#endif
|
||||
if (_radio_profiles.handle(command, reply, reply_capacity, sender_timestamp != 0)) return true;
|
||||
if (!strncmp(command, "set tempradio ", 14)) command += 4;
|
||||
if (!strcmp(command, "get tempradio")) command += 4;
|
||||
@@ -11998,7 +12006,9 @@ void MyMesh::loop() {
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if MESH_ENABLE_RADIO_PRESETS
|
||||
_radio_presets.loop(millis());
|
||||
#endif
|
||||
if (!mesh::morseTimingActive(_radio)) _radio_profiles.loop();
|
||||
#if MESH_ENABLE_HOST_KISS
|
||||
if (!mesh::morseTimingActive(_radio)) mesh::hostkiss::loop();
|
||||
|
||||
@@ -29,7 +29,10 @@
|
||||
|
||||
#include "DataStore.h"
|
||||
#include <helpers/RadioProfileCLI.h>
|
||||
#include <helpers/RadioPresetConfig.h>
|
||||
#if MESH_ENABLE_RADIO_PRESETS
|
||||
#include <helpers/RadioPresetCLI.h>
|
||||
#endif
|
||||
#include <helpers/MorseReplyHandoff.h>
|
||||
#include <helpers/TempRadioReplyBarrier.h>
|
||||
#include <helpers/MeshtextConfig.h>
|
||||
@@ -224,12 +227,14 @@ public:
|
||||
return handleCommand(command, 0, reply);
|
||||
}
|
||||
|
||||
#if MESH_ENABLE_RADIO_PRESETS
|
||||
// Named primary-radio presets (local operator only; Cardputer UI uses these).
|
||||
bool loadRadioPreset(uint8_t slot, char* reply, size_t cap) {
|
||||
return _radio_presets.loadSlot(slot, reply, cap);
|
||||
}
|
||||
uint8_t activeRadioPreset() const { return _radio_presets.activeSlot(); }
|
||||
const mesh::RadioPresetTable& radioPresetTable() const { return _radio_presets.table(); }
|
||||
#endif
|
||||
|
||||
void begin(bool has_display, bool radio_available = true,
|
||||
mesh::ui::StartupScreen* startup_screen = nullptr);
|
||||
@@ -869,6 +874,7 @@ private:
|
||||
|
||||
DataStore* _store;
|
||||
mesh::RadioProfileCLI _radio_profiles;
|
||||
#if MESH_ENABLE_RADIO_PRESETS
|
||||
// Hooks are captureless trampolines with this MyMesh as ctx; /presets lives
|
||||
// on the primary filesystem.
|
||||
mesh::RadioPresetCLI _radio_presets;
|
||||
@@ -879,6 +885,7 @@ private:
|
||||
uint16_t preamble);
|
||||
static bool presetApply(void* ctx, const mesh::RadioPreset& p, char* reply, size_t cap);
|
||||
static bool presetTempRadioActive(void* ctx);
|
||||
#endif
|
||||
#if defined(NRF52_PLATFORM) && defined(OTA_SD_STORE)
|
||||
mesh::cli::SdCardCLIState _sdcard_cli;
|
||||
#endif
|
||||
|
||||
@@ -2845,10 +2845,12 @@ void MyMesh::onAnonDataRecv(mesh::Packet *packet, const uint8_t *secret, const m
|
||||
reply_path_len = 0xFF;
|
||||
if (data[4] == 0 || data[4] >= ' ') { // is password, ie. a login request
|
||||
reply_len = handleLoginReq(sender, secret, timestamp, &data[4], packet->isRouteFlood());
|
||||
#if MESH_ENABLE_RADIO_PRESETS
|
||||
// A fresh full-admin login (LoRa or ESP-NOW bridge) proves the operator
|
||||
// can still reach this node: keep a remotely loaded preset.
|
||||
if (reply_len > 4 && reply_data[4] == RESP_SERVER_LOGIN_OK && login_full_admin)
|
||||
_cli.radioPresets().onAdminLogin();
|
||||
#endif
|
||||
} else if (data[4] == ANON_REQ_TYPE_REGIONS && packet->isRouteDirect()) {
|
||||
reply_len = handleAnonRegionsReq(sender, timestamp, &data[5], len - 5);
|
||||
} else if (data[4] == ANON_REQ_TYPE_OWNER && packet->isRouteDirect()) {
|
||||
|
||||
@@ -1961,8 +1961,10 @@ void MyMesh::onAnonDataRecv(mesh::Packet *packet, const uint8_t *secret, const m
|
||||
reply_data[7] = client->permissions; // NEW
|
||||
getRNG()->random(&reply_data[8], 4); // random blob to help packet-hash uniqueness
|
||||
reply_data[12] = FIRMWARE_VER_LEVEL; // New field
|
||||
#if MESH_ENABLE_RADIO_PRESETS
|
||||
// A fresh strict-admin login keeps a remotely loaded radio preset.
|
||||
if (client->isAdmin()) _cli.radioPresets().onAdminLogin();
|
||||
#endif
|
||||
|
||||
next_push = futureMillis(PUSH_NOTIFY_DELAY_MILLIS); // delay next push, give RESPONSE packet time to arrive first
|
||||
|
||||
|
||||
@@ -650,8 +650,10 @@ uint8_t SensorMesh::handleLoginReq(const mesh::Identity& sender, const uint8_t*
|
||||
getRNG()->random(&reply_data[8], 4); // random blob to help packet-hash uniqueness
|
||||
reply_data[12] = FIRMWARE_VER_LEVEL;
|
||||
|
||||
#if MESH_ENABLE_RADIO_PRESETS
|
||||
// A fresh strict-admin login keeps a remotely loaded radio preset.
|
||||
if (client->isAdmin()) _cli.radioPresets().onAdminLogin();
|
||||
#endif
|
||||
return 13; // reply length
|
||||
}
|
||||
|
||||
|
||||
@@ -74,7 +74,9 @@
|
||||
#elif defined(ESP32_PLATFORM) || defined(RP2040_PLATFORM)
|
||||
#include "ContactFileTransaction.h"
|
||||
#endif
|
||||
#if MESH_ENABLE_RADIO_PRESETS
|
||||
#include "RadioPresetFile.h"
|
||||
#endif
|
||||
#include <RTClib.h>
|
||||
#include <ctype.h>
|
||||
#include <Utils.h>
|
||||
@@ -250,7 +252,9 @@ void CommonCLI::loop() {
|
||||
servicePendingTxPower();
|
||||
servicePendingRxSettings();
|
||||
_radio_profiles.loop();
|
||||
#if MESH_ENABLE_RADIO_PRESETS
|
||||
_radio_presets.loop(millis());
|
||||
#endif
|
||||
#if MESH_ENABLE_HOST_KISS
|
||||
mesh::hostkiss::loop();
|
||||
#endif
|
||||
@@ -290,6 +294,7 @@ bool CommonCLI::applyAndSavePrimaryRadioParams(float freq, float bw, uint8_t sf,
|
||||
return true;
|
||||
}
|
||||
|
||||
#if MESH_ENABLE_RADIO_PRESETS
|
||||
bool CommonCLI::presetLoad(void* ctx, uint8_t* buf, size_t cap, size_t& len) {
|
||||
return mesh::loadRadioPresetFile(static_cast<CommonCLI*>(ctx)->_presets_fs, buf, cap, len);
|
||||
}
|
||||
@@ -327,6 +332,7 @@ bool CommonCLI::presetApply(void* ctx, const mesh::RadioPreset& p, char* reply,
|
||||
bool CommonCLI::presetTempRadioActive(void* ctx) {
|
||||
return static_cast<CommonCLI*>(ctx)->_callbacks->isTempRadioActive();
|
||||
}
|
||||
#endif
|
||||
|
||||
bool CommonCLI::primaryRadioApplyReady(bool reply_idle) {
|
||||
if (_primary_radio_apply_at
|
||||
@@ -1366,6 +1372,7 @@ void CommonCLI::loadPrefs(FILESYSTEM* fs) {
|
||||
#endif
|
||||
_radio_profiles.adoptPrimaryPreamble(_prefs->primary_radio_preamble);
|
||||
_radio_profiles.stagePrimary(_prefs->primary_radio_preamble, false);
|
||||
#if MESH_ENABLE_RADIO_PRESETS
|
||||
// After prefs are final. A preset left pending by a reboot is reverted here
|
||||
// through presetApply: that saves the previous tuple into _prefs, so the
|
||||
// role's boot-time radio apply (which runs after loadPrefs) starts on it.
|
||||
@@ -1375,6 +1382,7 @@ void CommonCLI::loadPrefs(FILESYSTEM* fs) {
|
||||
presetTempRadioActive
|
||||
};
|
||||
_radio_presets.begin(preset_hooks);
|
||||
#endif
|
||||
mesh::hilStartupTrace("prefs_apply_ready");
|
||||
}
|
||||
|
||||
@@ -3731,8 +3739,10 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, char* command, char* re
|
||||
command, reply, 160 - 3, millis())) return;
|
||||
if (mesh::handleMorseCommand(_callbacks->getProfileRadio(), command, reply, 160 - 3)) return;
|
||||
#endif
|
||||
#if MESH_ENABLE_RADIO_PRESETS
|
||||
// Remote (sender_timestamp != 0) loads are provisional until a fresh admin login.
|
||||
if (_radio_presets.handle(command, reply, 160, sender_timestamp != 0)) return;
|
||||
#endif
|
||||
if (_radio_profiles.handle(command, reply, 160, sender_timestamp != 0)) return;
|
||||
#if defined(ENABLE_OTA)
|
||||
const auto* ota_context = mesh::ota::ota_context_if_active();
|
||||
|
||||
@@ -15,7 +15,10 @@
|
||||
#include <helpers/RepeaterRadioTiming.h>
|
||||
#include <helpers/DiscoveryResponsePrefs.h>
|
||||
#include <helpers/RadioProfileCLI.h>
|
||||
#include <helpers/RadioPresetConfig.h>
|
||||
#if MESH_ENABLE_RADIO_PRESETS
|
||||
#include <helpers/RadioPresetCLI.h>
|
||||
#endif
|
||||
#include <helpers/MorseReplyHandoff.h>
|
||||
#include <helpers/WirelessControl.h>
|
||||
#include <helpers/FleetCommand.h>
|
||||
@@ -1025,6 +1028,7 @@ class CommonCLI {
|
||||
uint32_t _rx_settings_retry_at = 0;
|
||||
uint32_t _prefs_save_failures = 0;
|
||||
mesh::RadioProfileCLI _radio_profiles;
|
||||
#if MESH_ENABLE_RADIO_PRESETS
|
||||
// Named primary-radio presets. Hooks are captureless trampolines with this
|
||||
// CommonCLI as ctx; storage uses the filesystem handed to loadPrefs().
|
||||
mesh::RadioPresetCLI _radio_presets;
|
||||
@@ -1036,6 +1040,7 @@ class CommonCLI {
|
||||
uint16_t preamble);
|
||||
static bool presetApply(void* ctx, const mesh::RadioPreset& p, char* reply, size_t cap);
|
||||
static bool presetTempRadioActive(void* ctx);
|
||||
#endif
|
||||
#if MESH_ENABLE_TX_BEACONS
|
||||
mesh::BeaconManager* _beacons = nullptr;
|
||||
void beginBeacons();
|
||||
@@ -1120,8 +1125,10 @@ public:
|
||||
uint16_t preamble);
|
||||
mesh::RadioProfileCLI& radioProfiles() { return _radio_profiles; }
|
||||
const mesh::RadioProfileCLI& radioProfiles() const { return _radio_profiles; }
|
||||
#if MESH_ENABLE_RADIO_PRESETS
|
||||
mesh::RadioPresetCLI& radioPresets() { return _radio_presets; }
|
||||
const mesh::RadioPresetCLI& radioPresets() const { return _radio_presets; }
|
||||
#endif
|
||||
// Bind only after the role has loaded its saved regions, so boot-time
|
||||
// radio2 scope reconciliation does not get replaced by the region image.
|
||||
void onRegionsLoaded(FILESYSTEM* fs, bool loaded);
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
#include <helpers/RadioPresetConfig.h>
|
||||
|
||||
#if MESH_ENABLE_RADIO_PRESETS
|
||||
|
||||
#include "RadioPresetCLI.h"
|
||||
|
||||
#include <math.h>
|
||||
@@ -339,3 +343,5 @@ void RadioPresetCLI::loop(uint32_t now_ms) {
|
||||
}
|
||||
|
||||
} // namespace mesh
|
||||
|
||||
#endif // MESH_ENABLE_RADIO_PRESETS
|
||||
@@ -0,0 +1,15 @@
|
||||
#pragma once
|
||||
|
||||
// Build switch for named primary-radio presets ("get/set/load/save preset").
|
||||
// Define MESH_ENABLE_RADIO_PRESETS=0/1 in build_flags to override the default.
|
||||
#ifndef MESH_ENABLE_RADIO_PRESETS
|
||||
#if defined(STM32_PLATFORM)
|
||||
// STM32WL images have too little application flash (wio-e5-mini repeater overflows).
|
||||
#define MESH_ENABLE_RADIO_PRESETS 0
|
||||
#elif defined(MESH_PRIMARY_ESPNOW) && MESH_PRIMARY_ESPNOW
|
||||
// ESP-NOW-only builds have no LoRa primary radio for a preset to switch.
|
||||
#define MESH_ENABLE_RADIO_PRESETS 0
|
||||
#else
|
||||
#define MESH_ENABLE_RADIO_PRESETS 1
|
||||
#endif
|
||||
#endif
|
||||
Vendored
+3
-3
@@ -1406,15 +1406,15 @@
|
||||
}
|
||||
],
|
||||
"noncompact_tokens_sha256": {
|
||||
"src/helpers/CommonCLI.cpp": "2f6da7a2ebd16fc45df8dff27bcc6ef2eef53fd554bc54f9d6cbb8c62c5cf619",
|
||||
"examples/simple_repeater/MyMesh.cpp": "16f50f6a222042004731b4776e321c30d10bd7715bba56707beded1095a3ed2b",
|
||||
"src/helpers/CommonCLI.cpp": "c335e314f95fc9f7406a96c397ada99e2edf75f361ad55fd6076886901c87688",
|
||||
"examples/simple_repeater/MyMesh.cpp": "1d0341dd5206ff824502cb8916a0d3961569505349c70a3aacfe4f67c1f7a4b1",
|
||||
"src/helpers/RadioProfileCLI.cpp": "d641d16e872eba3e73b9b5a50f9718c10b0b7da72782f793706ac36b3724a624",
|
||||
"src/helpers/WirelessControl.h": "f19bb602999118cc423ec74c7de7100ed91cb2c553295038077e52e321bb4e73",
|
||||
"src/helpers/PowerSavingDiagnostics.h": "18b78f5780046ea8c772c4ce6c337741ae0c027039d9399c94c5f4205d114000",
|
||||
"src/helpers/FloodAdvertCLI.h": "0f378e4d0f6c3b9b79991068a4b4208139484d2144ee1d7c5094d39043463be4",
|
||||
"src/helpers/LedSettings.cpp": "df0681234ca1337148ad59caa935857bc692f98bf18603ca6d099c0382028400",
|
||||
"src/helpers/StorageLayout.cpp": "5dd1c8bcbe2902e1f22d14f9ab425465bdc38926fae23989987d850b484cbb18",
|
||||
"examples/companion_radio/MyMesh.cpp": "44f6bc257edbd90c0534389d4c01c933d7b7b0cf502c9d51cdbf2aace8b397e9",
|
||||
"examples/companion_radio/MyMesh.cpp": "0604e9a24c24b973987b5719cf21a2f5ac9795db20bc53fbd4355ceaa1a22ded",
|
||||
"src/helpers/CommonRadioPrefs.cpp": "16b1fb499426abde83566b6481df89b80302c72e00f0f4aa3843b6c9b9de4218",
|
||||
"examples/companion_radio/CompanionRetry.h": "e0ae10c7b2f778346f4645c4e147d87e1fbc7f8b99484752a8eed914bb21380f",
|
||||
"examples/companion_radio/Notifications.cpp": "173fb0d452fdd383585dd88ab35fbb079a90e068adca2db9bc44d604557d3873"
|
||||
|
||||
Vendored
+1
@@ -1,4 +1,5 @@
|
||||
#include <helpers/MorseLoopPolicy.h>
|
||||
#include <helpers/RadioPresetConfig.h>
|
||||
static uint32_t now_ms = 100;
|
||||
uint32_t millis() { return now_ms; }
|
||||
#define PUB_KEY_SIZE 32
|
||||
|
||||
@@ -14,6 +14,7 @@ SANITIZER_FLAGS = [] if os.name == "nt" else ["-fsanitize=address,undefined", "-
|
||||
PREAMBLE = r'''
|
||||
#include <cassert>
|
||||
#include <helpers/TempRadioReplyBarrier.h>
|
||||
#include <helpers/RadioPresetConfig.h>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <cstdint>
|
||||
|
||||
@@ -152,6 +152,7 @@ bool checkRadioReference() { @EXPRESSION@ return morse_timing; }
|
||||
code = FIXTURE.replace("@REDRAW@", "").replace("@CUTOFF@", "") + r'''
|
||||
#define MESH_ENABLE_MORSE_RX 0
|
||||
#define USER_GPIO_CONTROL 1
|
||||
#include <helpers/RadioPresetConfig.h>
|
||||
struct UserGpio {
|
||||
struct Completion { uint8_t pin=4,state=0; uint32_t request_id=99; };
|
||||
unsigned loops=0; bool pending=true;
|
||||
|
||||
+148
-3
@@ -588,7 +588,8 @@ class RadioPresetWiringContractTest(unittest.TestCase):
|
||||
self.assertSourceMatches(login, r'login_full_admin\s*=\s*client->isAdmin\(\)\s*;\s*\n\s*return 13;')
|
||||
self.assertSourceMatches(
|
||||
cpp,
|
||||
r'reply_len\s*=\s*handleLoginReq\([^;]*;\s*\n(?:\s*//[^\n]*\n)*\s*if\s*\(reply_len\s*>\s*4\s*'
|
||||
r'reply_len\s*=\s*handleLoginReq\([^;]*;\s*\n(?:#if MESH_ENABLE_RADIO_PRESETS\n)?'
|
||||
r'(?:\s*//[^\n]*\n)*\s*if\s*\(reply_len\s*>\s*4\s*'
|
||||
r'&&\s*reply_data\[4\]\s*==\s*RESP_SERVER_LOGIN_OK\s*&&\s*login_full_admin\)\s*\n?\s*'
|
||||
r'_cli\.radioPresets\(\)\.onAdminLogin\(\);')
|
||||
self.assertEqual(cpp.count('onAdminLogin()'), 1)
|
||||
@@ -609,7 +610,8 @@ class RadioPresetWiringContractTest(unittest.TestCase):
|
||||
login = _function_body(sensor, 'uint8_t SensorMesh::handleLoginReq(')
|
||||
self.assertIsNotNone(login)
|
||||
self.assertSourceMatches(login, r'RESP_SERVER_LOGIN_OK[\s\S]*if \(client->isAdmin\(\)\) '
|
||||
r'_cli\.radioPresets\(\)\.onAdminLogin\(\);\s*\n\s*return 13;')
|
||||
r'_cli\.radioPresets\(\)\.onAdminLogin\(\);\s*\n'
|
||||
r'#endif\s*\n\s*return 13;')
|
||||
self.assertEqual(sensor.count('onAdminLogin()'), 1)
|
||||
docs = _read('docs/cli_commands.md')
|
||||
self.assertNotIn('remote load always reverts', docs)
|
||||
@@ -734,7 +736,9 @@ class CompanionPresetContractTest(unittest.TestCase):
|
||||
depth_guards.pop()
|
||||
elif t == '#include <helpers/RadioPresetFile.h>':
|
||||
found = True
|
||||
self.assertEqual(depth_guards, [], 'RadioPresetFile.h include is guarded')
|
||||
# Only the feature switch may guard it, never a platform.
|
||||
self.assertEqual(depth_guards, ['#if MESH_ENABLE_RADIO_PRESETS'],
|
||||
'RadioPresetFile.h include is platform-guarded')
|
||||
self.assertTrue(found, 'companion MyMesh.cpp must include RadioPresetFile.h')
|
||||
|
||||
def test_docs_list_companion_presets(self):
|
||||
@@ -745,5 +749,146 @@ class CompanionPresetContractTest(unittest.TestCase):
|
||||
self.assertIn('load preset', section)
|
||||
|
||||
|
||||
CONFIG_PROBE = r'''
|
||||
#include <helpers/RadioPresetConfig.h>
|
||||
#include <cstdio>
|
||||
int main() { std::printf("presets=%d\n", (int)MESH_ENABLE_RADIO_PRESETS); return 0; }
|
||||
'''
|
||||
|
||||
# Tokens that only exist for the presets feature. With the switch off none may
|
||||
# survive preprocessing in these files (RadioPresetConfig.h itself is allowed).
|
||||
_PRESET_TOKENS = re.compile(
|
||||
r'_radio_presets|radioPresets\(\)|RadioPreset(?!Config)|preset_hooks'
|
||||
r'|preset(?:Load|Save|Current|Accepts|Apply|TempRadioActive)\b'
|
||||
r'|loadRadioPreset|activeRadioPreset|radioPresetTable|_presets_fs|onAdminLogin')
|
||||
|
||||
_GATED_FILES = (
|
||||
'src/helpers/CommonCLI.h',
|
||||
'src/helpers/CommonCLI.cpp',
|
||||
'src/helpers/RadioPresetCLI.cpp',
|
||||
'examples/companion_radio/MyMesh.h',
|
||||
'examples/companion_radio/MyMesh.cpp',
|
||||
'examples/simple_repeater/MyMesh.cpp',
|
||||
'examples/simple_room_server/MyMesh.cpp',
|
||||
'examples/simple_sensor/SensorMesh.cpp',
|
||||
'variants/m5stack_cardputer_adv/companion_radio/ui-cardputer/CardputerUITask.cpp',
|
||||
'variants/m5stack_cardputer_adv/companion_radio/ui-cardputer/screen/MainScreen.cpp',
|
||||
)
|
||||
|
||||
|
||||
def _lines_live_when_disabled(text):
|
||||
"""Yield (lineno, line) for lines kept with MESH_ENABLE_RADIO_PRESETS=0.
|
||||
|
||||
Only the switch's own #if/#if ! blocks are evaluated; every other
|
||||
conditional is treated as possibly live, which is the conservative choice.
|
||||
"""
|
||||
stack = [] # per #if: None (other condition) or bool (switch block live?)
|
||||
for n, line in enumerate(text.splitlines(), 1):
|
||||
t = line.strip()
|
||||
if t.startswith('#if'):
|
||||
if re.match(r'#if\s+MESH_ENABLE_RADIO_PRESETS\b', t):
|
||||
stack.append(False)
|
||||
elif re.match(r'#if\s+!\s*MESH_ENABLE_RADIO_PRESETS\b', t):
|
||||
stack.append(True)
|
||||
else:
|
||||
stack.append(None)
|
||||
continue
|
||||
if t.startswith('#else') and stack:
|
||||
if stack[-1] is not None:
|
||||
stack[-1] = not stack[-1]
|
||||
continue
|
||||
if t.startswith('#elif') and stack:
|
||||
continue
|
||||
if t.startswith('#endif') and stack:
|
||||
stack.pop()
|
||||
continue
|
||||
if any(s is False for s in stack):
|
||||
continue
|
||||
yield n, line
|
||||
|
||||
|
||||
class RadioPresetBuildSwitchTest(unittest.TestCase):
|
||||
"""MESH_ENABLE_RADIO_PRESETS: off on STM32 and ESP-NOW-only, on elsewhere."""
|
||||
|
||||
def probe(self, *defines):
|
||||
compiler = shutil.which('g++') or shutil.which('clang++')
|
||||
if compiler is None:
|
||||
self.skipTest('a host C++17 compiler is required')
|
||||
with tempfile.TemporaryDirectory(prefix='radio-preset-switch-') as directory:
|
||||
work = Path(directory)
|
||||
cpp = work / 'probe.cpp'
|
||||
cpp.write_text(CONFIG_PROBE, encoding='ascii')
|
||||
binary = work / ('probe' + ('.exe' if os.name == 'nt' else ''))
|
||||
built = subprocess.run([
|
||||
compiler, '-std=c++17', '-Wall', '-Wextra', '-Werror', *defines,
|
||||
'-I', str(ROOT / 'src'), str(cpp), '-o', str(binary),
|
||||
], capture_output=True, text=True, timeout=120)
|
||||
self.assertEqual(built.returncode, 0, built.stdout + built.stderr)
|
||||
ran = subprocess.run([str(binary)], capture_output=True, text=True, timeout=20)
|
||||
self.assertEqual(ran.returncode, 0, ran.stdout + ran.stderr)
|
||||
return int(ran.stdout.strip().split('=')[1])
|
||||
|
||||
def test_default_resolution(self):
|
||||
self.assertEqual(self.probe(), 1)
|
||||
self.assertEqual(self.probe('-DESP32=1'), 1)
|
||||
self.assertEqual(self.probe('-DNRF52_PLATFORM=1'), 1)
|
||||
self.assertEqual(self.probe('-DSTM32_PLATFORM'), 0)
|
||||
self.assertEqual(self.probe('-DSTM32_PLATFORM=1'), 0)
|
||||
self.assertEqual(self.probe('-DMESH_PRIMARY_ESPNOW=1'), 0)
|
||||
self.assertEqual(self.probe('-DESP32=1', '-DMESH_PRIMARY_ESPNOW=1'), 0)
|
||||
self.assertEqual(self.probe('-DMESH_PRIMARY_ESPNOW=0'), 1)
|
||||
|
||||
def test_explicit_override_wins(self):
|
||||
self.assertEqual(self.probe('-DSTM32_PLATFORM', '-DMESH_ENABLE_RADIO_PRESETS=1'), 1)
|
||||
self.assertEqual(self.probe('-DMESH_PRIMARY_ESPNOW=1', '-DMESH_ENABLE_RADIO_PRESETS=1'), 1)
|
||||
self.assertEqual(self.probe('-DMESH_ENABLE_RADIO_PRESETS=0'), 0)
|
||||
|
||||
def test_preset_cli_translation_unit_compiles_empty_when_disabled(self):
|
||||
# The src glob still builds RadioPresetCLI.cpp; disabled it must be an
|
||||
# empty, warning-free object so nothing preset-related links.
|
||||
compiler = shutil.which('g++') or shutil.which('clang++')
|
||||
if compiler is None:
|
||||
self.skipTest('a host C++17 compiler is required')
|
||||
with tempfile.TemporaryDirectory(prefix='radio-preset-off-') as directory:
|
||||
obj = Path(directory) / 'off.o'
|
||||
built = subprocess.run([
|
||||
compiler, '-std=c++17', '-Wall', '-Wextra', '-Werror', '-c',
|
||||
'-DMESH_ENABLE_RADIO_PRESETS=0', '-DNAME_MAX=255',
|
||||
'-I', str(ROOT / 'src'), '-I', str(ROOT / 'test'),
|
||||
'-isystem', str(ROOT / 'test' / 'mocks'),
|
||||
str(ROOT / 'src/helpers/RadioPresetCLI.cpp'), '-o', str(obj),
|
||||
], capture_output=True, text=True, timeout=120, cwd=str(ROOT / 'test'))
|
||||
self.assertEqual(built.returncode, 0, built.stdout + built.stderr)
|
||||
pre = subprocess.run([
|
||||
compiler, '-std=c++17', '-E', '-P', '-DMESH_ENABLE_RADIO_PRESETS=0',
|
||||
'-I', str(ROOT / 'src'), str(ROOT / 'src/helpers/RadioPresetCLI.cpp'),
|
||||
], capture_output=True, text=True, timeout=120)
|
||||
self.assertEqual(pre.returncode, 0, pre.stderr)
|
||||
self.assertNotIn('RadioPresetCLI', pre.stdout)
|
||||
|
||||
def test_every_preset_use_is_behind_the_switch(self):
|
||||
for rel in _GATED_FILES:
|
||||
text = _read(rel)
|
||||
self.assertIn('MESH_ENABLE_RADIO_PRESETS', text, rel)
|
||||
leaks = ['%s:%d: %s' % (rel, n, line.strip())
|
||||
for n, line in _lines_live_when_disabled(text)
|
||||
if _PRESET_TOKENS.search(line)]
|
||||
self.assertEqual(leaks, [], 'preset code outside MESH_ENABLE_RADIO_PRESETS')
|
||||
|
||||
def test_headers_pull_in_the_switch(self):
|
||||
for rel in ('src/helpers/CommonCLI.h', 'examples/companion_radio/MyMesh.h'):
|
||||
text = _read(rel)
|
||||
inc = text.find('#include <helpers/RadioPresetConfig.h>')
|
||||
self.assertGreaterEqual(inc, 0, rel)
|
||||
self.assertLess(inc, text.find('#if MESH_ENABLE_RADIO_PRESETS'), rel)
|
||||
|
||||
def test_docs_mention_the_switch(self):
|
||||
docs = _read('docs/cli_commands.md')
|
||||
self.assertIn('MESH_ENABLE_RADIO_PRESETS', docs)
|
||||
self.assertIn('ESP-NOW-only', docs)
|
||||
avail = _read('docs/cli_command_availability.md')
|
||||
self.assertGreaterEqual(avail.count('MESH_ENABLE_RADIO_PRESETS'), 3)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
@@ -31,6 +31,7 @@ HARNESS = r'''
|
||||
#include <helpers/FloodCommandAliases.h>
|
||||
#include <helpers/LoginHistory.h>
|
||||
#include <helpers/DefaultPasswordWarning.h>
|
||||
#include <helpers/RadioPresetConfig.h>
|
||||
#define PUB_KEY_SIZE 32
|
||||
#define MAX_PATH_SIZE 64
|
||||
#define MAX_PACKET_PAYLOAD 184
|
||||
|
||||
@@ -23,6 +23,7 @@ HARNESS = r'''
|
||||
#include <algorithm>
|
||||
#include "filesystem.h"
|
||||
#include <helpers/RoomAccessPolicy.h>
|
||||
#include <helpers/RadioPresetConfig.h>
|
||||
#include <helpers/RemoteCliReplyCache.h>
|
||||
#include <helpers/RemoteCliRequest.h>
|
||||
#include <helpers/ClientACLResponse.h>
|
||||
|
||||
@@ -11,7 +11,10 @@
|
||||
#include <helpers/CompanionNotificationPolicy.h>
|
||||
#include <helpers/ui/BluetoothPairingUiPolicy.h>
|
||||
|
||||
#include <helpers/RadioPresetConfig.h>
|
||||
#if MESH_ENABLE_RADIO_PRESETS
|
||||
#include <helpers/RadioPresets.h>
|
||||
#endif
|
||||
|
||||
void CardputerUITask::begin(DisplayDriver *display, SensorManager *sensors, CompanionNodePrefs *node_prefs,
|
||||
CustomNodePrefs *custom_prefs) {
|
||||
@@ -488,6 +491,16 @@ static void formatTuple(char *out, size_t cap, const char *name, float freq, flo
|
||||
// slot 1..5 loads and shows that preset; slot 0 shows the active preset, or "custom" and the live radio.
|
||||
void CardputerUITask::showPresetAlert(uint8_t slot) {
|
||||
char buf[80];
|
||||
#if !MESH_ENABLE_RADIO_PRESETS
|
||||
// Built without presets (MESH_ENABLE_RADIO_PRESETS=0): Fn+0 still shows the live radio.
|
||||
if (slot != 0) {
|
||||
showAlert("Presets not in this build", 2000);
|
||||
return;
|
||||
}
|
||||
formatTuple(buf, sizeof(buf), "custom", _node_prefs->freq, _node_prefs->bw, _node_prefs->sf,
|
||||
_node_prefs->cr, 0);
|
||||
showAlert(buf, 3000);
|
||||
#else
|
||||
const mesh::RadioPreset *p = nullptr;
|
||||
if (slot == 0) {
|
||||
uint8_t active = the_mesh_cp.activeRadioPreset();
|
||||
@@ -515,6 +528,7 @@ void CardputerUITask::showPresetAlert(uint8_t slot) {
|
||||
}
|
||||
formatTuple(buf, sizeof(buf), p->name, p->freq, p->bw, p->sf, p->cr, p->preamble);
|
||||
showAlert(buf, 3000);
|
||||
#endif
|
||||
}
|
||||
|
||||
void CardputerUITask::pingLastRepeater() {
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
#include "MainScreen.h"
|
||||
|
||||
#include <helpers/RadioPresetConfig.h>
|
||||
|
||||
#include "helpers.h"
|
||||
#include "main/ChannelsPage.h"
|
||||
#include "main/ChatPage.h"
|
||||
@@ -56,6 +58,7 @@ void MainScreen::renderStatusIcons(CardputerDisplay &lcd) {
|
||||
lcd.setColor(CardputerDisplay::P_BLACK);
|
||||
lcd.drawTextCentered(iconX + 2 + iconWidth / 2 - 1, 0, tmp);
|
||||
|
||||
#if MESH_ENABLE_RADIO_PRESETS
|
||||
// active radio preset name (or "custom") just left of the battery icon
|
||||
{
|
||||
uint8_t active = the_mesh_cp.activeRadioPreset();
|
||||
@@ -63,6 +66,7 @@ void MainScreen::renderStatusIcons(CardputerDisplay &lcd) {
|
||||
lcd.setColor(CardputerDisplay::P_GREEN);
|
||||
lcd.drawTextRightAlign(iconX - 4, 0, preset ? preset->name : "custom");
|
||||
}
|
||||
#endif
|
||||
|
||||
iconX = 3;
|
||||
iconY = 5;
|
||||
|
||||
Reference in new issue
Block a user