Omit unused forwarding region automation from Full Companions

This commit is contained in:
mikecarper committed 2026-10-26 22:21:35 -07:00
1 parent cf0628d3db
commit 7321f858d8
3 files changed
+73 -5

No files matched your search

+9 -5
View File
@@ -92,14 +92,17 @@ bool secondaryOverlaps(uint32_t primary_hz, float secondary_mhz, uint32_t cleara
return (primary_hz > secondary_hz ? primary_hz - secondary_hz : secondary_hz - primary_hz)
<= clearance_hz;
}
#if !defined(COMPANION_RADIO_FULL)
bool isMediaMeshProfile(const RadioProfileConfig& config) {
return config.mode != RadioProfileMode::Off
&& config.params.freq >= 911.1495f && config.params.freq <= 911.1505f
&& (config.params.bw == 250.0f || config.params.bw == 125.0f)
&& config.params.sf == 7 && config.params.cr == 5;
}
#endif
}
#if !defined(COMPANION_RADIO_FULL)
void RadioProfileCLI::setMediaMeshScopeHandler(MediaMeshScopeHandler handler, void* context) {
media_scope_handler_ = handler;
media_scope_context_ = context;
@@ -129,6 +132,7 @@ void RadioProfileCLI::retryMediaMeshScope() {
media_scope_pending_ = !media_scope_handler_(media_scope_context_, media_scope_enabled_, true);
media_scope_retry_ms_ = media_scope_pending_ ? millis() + 1000 : 0;
}
#endif
RadioProfileCLI::ImageReadResult RadioProfileCLI::readImage(
const char* path, uint8_t* bytes, size_t size) {
@@ -934,7 +938,7 @@ void RadioProfileCLI::appendPrimaryChirpWarning(char* reply, size_t capacity, ui
void RadioProfileCLI::appendSecondaryWarnings(char* reply, size_t capacity,
const RadioProfiles& preview) const {
if (!capacity) return;
if (media_scope_pending_) appendReply(reply, capacity, "; MediaMesh scope save pending");
if (mediaMeshScopePending()) appendReply(reply, capacity, "; MediaMesh scope save pending");
if (preview.enabled() && RadioProfiles::valid(preview.primary)
&& RadioProfiles::valid(preview.secondary.params)) {
const uint32_t primary_hz = frequencyHz(preview.primary.freq);
@@ -1188,7 +1192,7 @@ bool RadioProfileCLI::handle(const char* command, char* reply, size_t capacity,
p.recommendationReady() ? " ready" : " pending", (unsigned long)p.switchBudgetUs());
appendChirpWarning(reply, capacity, p);
}
if (media_scope_pending_) appendReply(reply, capacity, "; MediaMesh scope save pending");
if (mediaMeshScopePending()) appendReply(reply, capacity, "; MediaMesh scope save pending");
return true;
}
if ((base || temporary) && verb == Get && !*args) {
@@ -1199,7 +1203,7 @@ bool RadioProfileCLI::handle(const char* command, char* reply, size_t capacity,
}
if (base && saved_.mode == RadioProfileMode::Off) {
snprintf(reply, capacity, "> off (%s)", manual_secondary_ ? "manual" : "automatic allowed");
if (media_scope_pending_) appendReply(reply, capacity, "; MediaMesh scope save pending");
if (mediaMeshScopePending()) appendReply(reply, capacity, "; MediaMesh scope save pending");
return true;
}
#endif
@@ -1227,7 +1231,7 @@ bool RadioProfileCLI::handle(const char* command, char* reply, size_t capacity,
publish_pending_ = true; publish_after_ms_ = millis() + 2000;
publish();
snprintf(reply, capacity, "OK - automatic radio2 permitted in 2s");
if (media_scope_pending_) appendReply(reply, capacity, "; MediaMesh scope save pending");
if (mediaMeshScopePending()) appendReply(reply, capacity, "; MediaMesh scope save pending");
return true;
}
#endif
@@ -1251,7 +1255,7 @@ bool RadioProfileCLI::handle(const char* command, char* reply, size_t capacity,
for (auto& s : schedules_) if (base || s.temporary) s = {};
publish_pending_ = true; publish_after_ms_ = millis() + 2000;
publish(); snprintf(reply, capacity, "OK - %s in 2s", saved_.mode != RadioProfileMode::Off ? "saved radio2 restored" : "single radio");
if (media_scope_pending_) appendReply(reply, capacity, "; MediaMesh scope save pending");
if (mediaMeshScopePending()) appendReply(reply, capacity, "; MediaMesh scope save pending");
return true;
}
if (scheduled && (verb == Get || verb == Delete)) {
+14
View File
@@ -61,6 +61,7 @@ class RadioProfileCLI {
// readable backup. Read/I/O failures and an intact backup remain read-only.
bool recoverable_corrupt_store_ = false;
bool publish_pending_ = false;
#if !defined(COMPANION_RADIO_FULL)
MediaMeshScopeHandler media_scope_handler_ = nullptr;
void* media_scope_context_ = nullptr;
uint32_t media_scope_retry_ms_ = 0;
@@ -68,6 +69,15 @@ class RadioProfileCLI {
bool mediaMeshScopeReady(const RadioProfileConfig& config) const;
void updateMediaMeshScope();
void retryMediaMeshScope();
bool mediaMeshScopePending() const { return media_scope_pending_; }
#else
// Full Companions have no forwarding RegionMap. Do not retain unreachable
// infrastructure callbacks, state or failure text in their tight images.
bool mediaMeshScopeReady(const RadioProfileConfig&) const { return true; }
void updateMediaMeshScope() {}
void retryMediaMeshScope() {}
bool mediaMeshScopePending() const { return false; }
#endif
#if MESH_RADIO_SWITCH_TUNING
RadioSwitchTuning fast_saved_;
uint32_t fast_retry_ms_ = 0;
@@ -131,7 +141,11 @@ class RadioProfileCLI {
#endif
void begin(FILESYSTEM* fs, Radio* radio, RTCClock* rtc, bool infrastructure_replies = false);
// Bind only after RegionMap::load(), so boot reconciliation is not discarded.
#if !defined(COMPANION_RADIO_FULL)
void setMediaMeshScopeHandler(MediaMeshScopeHandler handler, void* context);
#else
void setMediaMeshScopeHandler(MediaMeshScopeHandler, void*) {}
#endif
void servicePendingTuning();
bool hasPendingTuning() const {
#if MESH_RADIO_SWITCH_TUNING
+50
View File
@@ -9,6 +9,56 @@ import unittest
ROOT = Path(__file__).resolve().parents[1]
class RadioProfileCLITest(unittest.TestCase):
def test_full_companion_keeps_rf_presets_without_unused_region_code(self):
compiler = shutil.which('g++') or shutil.which('clang++')
self.assertIsNotNone(compiler)
code = r'''
#define main existing_cli_main
#include "test/fixtures/radio_profiles/cli_test.cpp"
#undef main
int main() {
Fixture f;
MediaScope scope;
f.cli.setMediaMeshScopeHandler(MediaScope::update, &scope);
f.cmd("set radio2 mediamesh");
f.advance(2000);
assert(f.radio.p.enabled() && fabsf(f.radio.p.secondary.params.freq - 911.150f) < 0.0001f);
#if defined(COMPANION_RADIO_FULL)
assert(!scope.commits && !scope.checks);
#else
assert(scope.enabled && scope.durable && scope.commits);
#endif
f.cmd("set radio2 off");
assert(!scope.enabled);
printf("%zu", sizeof(mesh::RadioProfileCLI));
}
'''
sizes = {}
with tempfile.TemporaryDirectory(prefix='profile-region-role-') as work:
source = Path(work)/'role.cpp'
source.write_text(code, encoding='ascii')
for full in (False, True):
binary = Path(work)/('companion' if full else 'infrastructure')
flags = ['-DCOMPANION_RADIO_FULL=1'] if full else []
compiled = subprocess.run([
compiler, '-std=c++17', '-Os', '-ffunction-sections',
'-fdata-sections', '-Wl,--gc-sections',
'-DMESH_ENABLE_FLEET_CONTROL=0', '-DMESH_ENABLE_CARRIER_TOOLS=0',
'-DMESH_ENABLE_MESHTEXT_CHAT=0', '-DMESH_ENABLE_HOST_KISS=0',
'-DMESH_ENABLE_ETHERNET_TUNNEL=0', *flags,
'-I', str(ROOT), '-I', str(ROOT/'test/fixtures/radio_profiles/mocks'),
'-I', str(ROOT/'test/mocks'), '-I', str(ROOT/'src'),
str(ROOT/'src/helpers/RadioProfileCLI.cpp'), str(source),
'-o', str(binary)], capture_output=True, text=True, timeout=60)
self.assertEqual(compiled.returncode, 0, compiled.stderr)
run = subprocess.run([str(binary)], capture_output=True, text=True, timeout=10)
self.assertEqual(run.returncode, 0, run.stdout+run.stderr)
sizes[full] = int(run.stdout)
image = binary.read_bytes()
for marker in (b'MediaMesh scope save pending', b'MediaMesh region unavailable or full'):
self.assertEqual(marker in image, not full)
self.assertLess(sizes[True], sizes[False])
def test_commands_persistence_and_timers(self):
compiler = shutil.which('g++') or shutil.which('clang++')
self.assertIsNotNone(compiler)