Keep Companion get radio compatible with upstream CSV

This commit is contained in:
mikecarper
2026-09-30 23:48:47 -07:00
parent 31aa17b9e5
commit cab781f9a4
4 changed files with 29 additions and 3 deletions
+2 -1
View File
@@ -9544,7 +9544,8 @@ bool MyMesh::handleCommand(const char* command, uint32_t sender_timestamp,
}
}
}
if (!strcmp(command, "get radio")) _radio_profiles.appendSavedPreamble(reply, reply_capacity, _prefs.sf, _prefs.bw);
// Keep get radio as the upstream four-field tuple. Apps parse this CSV
// directly; preamble and timing diagnostics have separate commands.
if (!strncmp(command, "set radio ", 10) && !strncmp(reply, "OK", 2))
_radio_profiles.appendPrimaryChirpWarning(reply, reply_capacity, _prefs.sf, _prefs.bw,
_radio_profiles.primaryPreamble());
+6
View File
@@ -102,6 +102,12 @@ async def check_commands(client, args):
raise RuntimeError("Unexpected firmware: " + reply)
if "Unknown command" in reply or "Not Supported" in reply:
raise RuntimeError(reply)
if cmd == "get radio":
fields = reply.removeprefix("> ").split(",")
if len(fields) != 4:
raise RuntimeError("Radio reply must have exactly four upstream fields: " + reply)
# Reject annotations as well as extra CSV fields.
float(fields[0]); float(fields[1]); int(fields[2]); int(fields[3])
report("app_cli", command=cmd, trailing_nul=True, reply=reply)
+2 -1
View File
@@ -109,7 +109,8 @@ static void _ftoa(float f, char *p, int *status)
else if (exp2 >= 0)
{
int_part = mantissa>>(23 - exp2);
frac_part = (mantissa<<(exp2 + 1))&0xFFFFFF;
// Only the low 24 bits are needed; shift unsigned to avoid signed overflow.
frac_part = (uint32_t(mantissa)<<(exp2 + 1))&0xFFFFFF;
}
else
{
@@ -15,6 +15,7 @@ ROOT = Path(__file__).resolve().parents[1]
HARNESS = r'''
#include <helpers/RadioProfileCLI.h>
#include <helpers/TxtDataHelpers.h>
#include <helpers/radiolib/RXPowerSaving.h>
#include <limits>
#define constrain(value,low,high) ((value)<(low)?(low):((value)>(high)?(high):(value)))
@@ -88,16 +89,32 @@ struct MyMesh {
// CommonCLI's infrastructure roles share the same accepted duty-cycle range.
// Extract their actual setter and boot sanitization, with only storage mocked.
struct Infrastructure {
struct Prefs { float airtime_factor=1; } prefs;
struct Prefs { float airtime_factor=1,freq=909.5f,bw=62.5f;uint8_t sf=7,cr=5; } prefs;
Prefs* _prefs=&prefs;
uint8_t image[sizeof(float)]={};
void savePrefs() { memcpy(image,&prefs.airtime_factor,sizeof(float)); }
void command(const char* config,char* reply) { @INFRA_SETTER@ }
void radioCommand(char* reply) { @INFRA_RADIO@ }
void reboot() { memcpy(&prefs.airtime_factor,image,sizeof(float));@INFRA_SANITIZE@ }
};
static bool error(const char* reply) { return strstr(reply,"Error") || strstr(reply,"ERROR"); }
int main() {
unsigned checks=0;
// App radio settings require exactly the upstream four-field CSV, even
// with an explicit preamble or an active temporary/secondary profile.
for(uint32_t timestamp:{0U,1700000000U})for(uint16_t preamble:{0U,48U}) {
MyMesh m;assert(m._radio_profiles.savePrimaryPreamble(preamble));
m.radio.p.primary.freq=915;m.radio.p.primary.preamble=96;
m.radio.p.primary_temporary=true;
m.radio.p.secondary.params={916,125,64,8,6};
m.radio.p.secondary.mode=mesh::RadioProfileMode::RxTx;
const char* reply=m.command("get radio",timestamp);
assert(!strcmp(reply,"> 909.5,62.5,7,5"));
assert(m._radio_profiles.primaryPreamble()==preamble);
Infrastructure infra;char infrastructure_reply[160]={};
infra.radioCommand(infrastructure_reply);
assert(!strcmp(infrastructure_reply,reply));++checks;
}
// The outer profile validator accepts 125.005, but CommonRadioPrefs rejects
// it. Rejection must preserve both on-disk images, even with a dirty adapter.
for(uint32_t timestamp:{0U,1700000000U})for(bool dirty:{false,true}) {
@@ -199,6 +216,7 @@ class CompanionPrimaryRadioPersistenceTests(unittest.TestCase):
'@COMPANION_SANITIZE@': boot_checks(companion, '_prefs.airtime_factor'),
'@INFRA_SANITIZE@': boot_checks(infra, '_prefs->airtime_factor'),
'@INFRA_SETTER@': extract_braced(infra, 'if (memcmp(config, "dutycycle ", 10) == 0)'),
'@INFRA_RADIO@': extract_braced(infra, 'if (configKeyEquals(config, "radio"))').split('{', 1)[1].rsplit('}', 1)[0],
}
harness = support + HARNESS
for marker, value in replacements.items():