mirror of
https://github.com/mikecarper/MeshCore.git
synced 2026-10-26 12:34:49 +00:00
Correct chip-specific radio profile validation and airtime
Retain supported LR2021 bandwidths and reject frequency gaps. Enable LR1121 high-band bandwidth selection and account for its forced LDRO in profile and beacon airtime. Cover all eight chip families with production-method regression tests.
This commit is contained in:
1 parent
25299a77b3
commit
ca67494f8c
5 files changed
+140
-11
No files matched your search
@@ -618,6 +618,7 @@ jobs:
|
||||
test_sx1262_modem_cache test_esp32_retune_priority test_tdeck_spi_clock_timing
|
||||
test_radio_chirp_math
|
||||
test_lr1110_profile_switch test_lr1110_packet_setter_cache
|
||||
test_radio_profile_validation_backport
|
||||
|
||||
- name: Verify radio lab collectors and compiled experiment contracts
|
||||
run: |
|
||||
|
||||
+3
-3
@@ -147,13 +147,13 @@ class RadioProfiles {
|
||||
return profile == 1 ? secondary.params : primary;
|
||||
}
|
||||
static bool valid(const RadioProfileParams& p) {
|
||||
if (!isfinite(p.freq) || !isfinite(p.bw) || p.freq < 150 || p.freq > 2500
|
||||
if (!isfinite(p.freq) || !isfinite(p.bw) || p.freq < 137 || p.freq > 2500
|
||||
|| p.sf < 5 || p.sf > 12 || p.cr < 5 || p.cr > 8
|
||||
|| (p.protocol != RadioProfileProtocol::MeshCore && p.protocol != RadioProfileProtocol::Meshtext
|
||||
&& p.protocol != RadioProfileProtocol::Ethernet)
|
||||
|| (p.preamble != 0 && (p.preamble < 8 || p.preamble > MaxPreamble))) return false;
|
||||
static constexpr float bandwidths[] = {7.8f, 10.4f, 15.6f, 20.8f, 31.25f, 41.7f,
|
||||
62.5f, 125, 250, 500, 1000, 812.5f, 1625};
|
||||
static constexpr float bandwidths[] = {7.8f, 10.4f, 15.6f, 20.8f, 31.25f, 41.67f, 41.7f,
|
||||
62.5f, 83.34f, 101.56f, 125, 203.125f, 250, 406.25f, 500, 812.5f, 1000, 1625};
|
||||
for (float bw : bandwidths) if (fabsf(p.bw - bw) < 0.01f) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -90,7 +90,7 @@ protected:
|
||||
bool applyParams(float freq, float bw, uint8_t sf, uint8_t cr) override {
|
||||
if (((CustomLR1121 *)_radio)->setFrequency(freq) != RADIOLIB_ERR_NONE
|
||||
|| ((CustomLR1121 *)_radio)->setSpreadingFactor(sf) != RADIOLIB_ERR_NONE
|
||||
|| ((CustomLR1121 *)_radio)->setBandwidth(bw) != RADIOLIB_ERR_NONE
|
||||
|| ((CustomLR1121 *)_radio)->setBandwidth(bw, bw == 203.125f || bw == 406.25f || bw == 812.5f) != RADIOLIB_ERR_NONE
|
||||
|| ((CustomLR1121 *)_radio)->setCodingRate(cr) != RADIOLIB_ERR_NONE
|
||||
|| !updatePreamble(sf, bw)) return false;
|
||||
PacketMillis pm = calcMaxPacketMillis(sf, bw, cr, preambleLengthForParams(sf, bw));
|
||||
|
||||
@@ -465,22 +465,40 @@ bool RadioLibWrapper::validateProfile(const mesh::RadioProfileParams& p) const {
|
||||
#else
|
||||
if (p.protocol == mesh::RadioProfileProtocol::Ethernet) return false;
|
||||
#endif
|
||||
#if MESH_RADIO_PROFILE_CHIP != 1110 && MESH_RADIO_PROFILE_CHIP != 1121 && MESH_RADIO_PROFILE_CHIP != 2021 && MESH_RADIO_PROFILE_CHIP != 68 && MESH_RADIO_PROFILE_CHIP != 1272
|
||||
static constexpr float bandwidths[] = {7.8f, 10.4f, 15.6f, 20.8f, 31.25f, 41.7f, 62.5f, 125, 250, 500};
|
||||
bool supported = false;
|
||||
for (float bw : bandwidths) if (fabsf(p.bw - bw) < 0.01f) supported = true;
|
||||
if (!supported) return false;
|
||||
#endif
|
||||
#if MESH_RADIO_PROFILE_CHIP == 1276
|
||||
return p.freq >= 137 && p.freq <= 1020 && p.sf >= 7 && p.bw <= 500;
|
||||
#elif MESH_RADIO_PROFILE_CHIP == 1272
|
||||
return p.freq >= 860 && p.freq <= 1020 && p.sf >= 7 && p.bw >= 125 && p.bw <= 500;
|
||||
return p.freq >= 860 && p.freq <= 1020 && p.sf >= 7
|
||||
&& (p.bw == 125 || p.bw == 250 || p.bw == 500);
|
||||
#elif MESH_RADIO_PROFILE_CHIP == 68
|
||||
return p.freq >= 150 && p.freq <= 960
|
||||
&& ((p.bw == 125 && p.sf <= 9) || (p.bw == 250 && p.sf <= 10)
|
||||
|| (p.bw == 500 && p.sf <= 11));
|
||||
#elif MESH_RADIO_PROFILE_CHIP == 2021
|
||||
return (p.freq <= 1090 || p.freq >= 1900) && p.bw >= 31.25 && p.bw <= 1000;
|
||||
static constexpr float bandwidths[] = {31.25f, 41.67f, 41.7f, 62.5f, 83.34f, 101.56f,
|
||||
125, 203.125f, 250, 406.25f, 500, 812.5f, 1000};
|
||||
bool supported = false;
|
||||
for (float bw : bandwidths) if (fabsf(p.bw - bw) < 0.01f) supported = true;
|
||||
return ((p.freq >= 150 && p.freq <= 1090) || (p.freq >= 1900 && p.freq <= 2200)
|
||||
|| (p.freq >= 2400 && p.freq <= 2500)) && supported;
|
||||
#elif MESH_RADIO_PROFILE_CHIP == 1110
|
||||
return p.freq <= 960 && p.bw >= 62.5 && p.bw <= 500;
|
||||
return p.freq >= 150 && p.freq <= 960
|
||||
&& (p.bw == 62.5 || p.bw == 125 || p.bw == 250 || p.bw == 500);
|
||||
#elif MESH_RADIO_PROFILE_CHIP == 1121
|
||||
return ((p.freq >= 150 && p.freq <= 960) || (p.freq >= 1900 && p.freq <= 2200)
|
||||
|| (p.freq >= 2400 && p.freq <= 2500))
|
||||
&& (p.bw == 62.5 || p.bw == 125 || p.bw == 250 || p.bw == 500
|
||||
|| (p.freq >= 1900 && (p.bw == 203.125f || p.bw == 406.25f || p.bw == 812.5f)));
|
||||
#elif MESH_RADIO_PROFILE_CHIP == 1268
|
||||
return p.freq >= 410 && p.freq <= 810 && p.bw <= 500;
|
||||
#else
|
||||
return p.freq <= 960 && p.bw <= 500;
|
||||
return p.freq >= 150 && p.freq <= 960 && p.bw <= 500;
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -613,7 +631,11 @@ uint32_t RadioLibWrapper::getBeaconAirtime(const mesh::RadioProfileParams& p, in
|
||||
? 16 : (p.preamble ? p.preamble : 32);
|
||||
if (!mesh::RadioProfiles::safePreamble(p, preamble)) return 0;
|
||||
const double symbol = mesh::RadioProfiles::symbolUs(p);
|
||||
const int de = symbol >= 16000 ? 1 : 0;
|
||||
const int de = symbol >= 16000
|
||||
#if MESH_RADIO_PROFILE_CHIP == 1121
|
||||
|| (p.freq > 1000 && p.sf > 10)
|
||||
#endif
|
||||
? 1 : 0;
|
||||
const double coded = ceil((8.0 * length - 4 * p.sf + (p.sf <= 6 ? 20 : 28) + 16)
|
||||
/ (4 * (p.sf - 2 * de)));
|
||||
const double payload = 8 + (coded > 0 ? coded * p.cr : 0);
|
||||
@@ -935,17 +957,31 @@ bool RadioLibWrapper::getProfileIdleDelay(uint32_t& delay_ms) const {
|
||||
}
|
||||
|
||||
uint32_t RadioLibWrapper::getProfileAirtime(uint8_t profile, int len, uint8_t cr) {
|
||||
if (profile > 1 || len < 0 || len > MAX_TRANS_UNIT) return 0;
|
||||
if (!_profiles.enabled() && profile == 0 && cr == 0) return getEstAirtimeFor(len);
|
||||
const auto& p = _profiles.params(profile);
|
||||
if (!validateProfile(p)) return 0;
|
||||
const double symbol = mesh::RadioProfiles::symbolUs(p);
|
||||
if (symbol <= 0 || len < 0) return 0;
|
||||
if (cr < 5 || cr > 8) cr = p.cr;
|
||||
const int de = symbol >= 16000 ? 1 : 0;
|
||||
const int de = symbol >= 16000
|
||||
#if MESH_RADIO_PROFILE_CHIP == 1121
|
||||
// RadioLib 7.8.1 forces SX128x-compatible LDRO for high-band SF11/12.
|
||||
|| (p.freq > 1000 && p.sf > 10)
|
||||
#endif
|
||||
? 1 : 0;
|
||||
const double coded = ceil((8.0 * len - 4 * p.sf + (p.sf <= 6 ? 20 : 28) + 16)
|
||||
/ (4 * (p.sf - 2 * de)));
|
||||
const double payload_symbols = 8 + (coded > 0 ? coded * cr : 0);
|
||||
const double suffix = p.sf <= 6 ? 6.25 : 4.25;
|
||||
return (uint32_t)ceil((profilePreamble(profile) + suffix + payload_symbols) * symbol / 1000.0);
|
||||
const double airtime = ceil((profilePreamble(profile) + suffix + payload_symbols) * symbol / 1000.0);
|
||||
const uint32_t rounded = airtime > UINT32_MAX ? UINT32_MAX : uint32_t(airtime);
|
||||
#if MESH_RADIO_PROFILE_CHIP == 1110 || MESH_RADIO_PROFILE_CHIP == 1121
|
||||
// Preserve these wrappers' operational floor even for an inactive tuple.
|
||||
return rounded < 200 ? 200 : rounded;
|
||||
#else
|
||||
return rounded;
|
||||
#endif
|
||||
}
|
||||
|
||||
bool RadioLibWrapper::setParams(float freq, float bw, uint8_t sf, uint8_t cr,
|
||||
|
||||
@@ -0,0 +1,92 @@
|
||||
"""Exercise production profile validation and per-tuple airtime on each chip."""
|
||||
from pathlib import Path
|
||||
import os
|
||||
import subprocess
|
||||
import tempfile
|
||||
import unittest
|
||||
from cpp_source import body
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
|
||||
|
||||
class ProfileValidationBackportTests(unittest.TestCase):
|
||||
def test_supported_tuples_protocol_guards_and_operational_airtime(self):
|
||||
source = (ROOT / "src/helpers/radiolib/RadioLibWrappers.cpp").read_text()
|
||||
methods = "\n".join(body(source, signature).replace("RadioLibWrapper::", "").replace("uint8_t cr)", "uint8_t cr = 0)")
|
||||
for signature in ("bool RadioLibWrapper::validateProfile(",
|
||||
"uint32_t RadioLibWrapper::getProfileAirtime(",
|
||||
"uint32_t RadioLibWrapper::getBeaconAirtime("))
|
||||
harness = r'''
|
||||
#include <cassert>
|
||||
#include <cmath>
|
||||
#include <RadioProfiles.h>
|
||||
#define MAX_TRANS_UNIT 255
|
||||
#define MESH_ENABLE_ETHERNET_WIRE 1
|
||||
struct RadioLibWrapper {
|
||||
mesh::RadioProfiles _profiles;
|
||||
bool foreign=false;
|
||||
bool supportsMeshtext()const{return foreign;}
|
||||
bool supportsEthernetTunnel()const{return foreign;}
|
||||
uint16_t profilePreamble(uint8_t)const{return 32;}
|
||||
uint32_t getEstAirtimeFor(int){return 777;}
|
||||
@METHODS@
|
||||
};
|
||||
int main(){
|
||||
RadioLibWrapper w;
|
||||
mesh::RadioProfileParams p;p.freq=MESH_RADIO_PROFILE_CHIP==1268?433:915;p.bw=125;p.sf=8;p.cr=5;
|
||||
assert(w.validateProfile(p));
|
||||
p.protocol=mesh::RadioProfileProtocol::Meshtext;assert(!w.validateProfile(p));
|
||||
w.foreign=true;assert(w.validateProfile(p));
|
||||
p.protocol=mesh::RadioProfileProtocol::Ethernet;assert(w.validateProfile(p));
|
||||
w.foreign=false;assert(!w.validateProfile(p));p.protocol=mesh::RadioProfileProtocol::MeshCore;
|
||||
p.freq=137;
|
||||
assert(w.validateProfile(p)==(MESH_RADIO_PROFILE_CHIP==1276));
|
||||
p.freq=149;assert(w.validateProfile(p)==(MESH_RADIO_PROFILE_CHIP==1276));
|
||||
p.freq=915;p.bw=406.25f;
|
||||
assert(w.validateProfile(p)==(MESH_RADIO_PROFILE_CHIP==2021));
|
||||
p.freq=2400;
|
||||
assert(w.validateProfile(p)==(MESH_RADIO_PROFILE_CHIP==1121||MESH_RADIO_PROFILE_CHIP==2021));
|
||||
p.freq=2300;assert(!w.validateProfile(p));
|
||||
p.freq=MESH_RADIO_PROFILE_CHIP==1268?433:915;p.bw=125;p.sf=8;
|
||||
w._profiles.primary=p;
|
||||
assert(w.getProfileAirtime(0,10)==777); // Preserve the existing live-tuple path.
|
||||
w._profiles.secondary.params=p;w._profiles.secondary.mode=mesh::RadioProfileMode::Rx;
|
||||
const auto short_air=w.getProfileAirtime(0,1);
|
||||
#if MESH_RADIO_PROFILE_CHIP==1110||MESH_RADIO_PROFILE_CHIP==1121
|
||||
assert(short_air==200);
|
||||
#else
|
||||
assert(short_air<200);
|
||||
#endif
|
||||
#if MESH_RADIO_PROFILE_CHIP==1121
|
||||
p.freq=2400;p.bw=812.5;p.sf=12;w._profiles.primary=p;
|
||||
// Pinned LR11xx forces high-band LDRO: 51 coded blocks * CR5, plus overhead.
|
||||
const auto expected=uint32_t(ceil((32+4.25+8+51*5)*4096.0/812.5));
|
||||
assert(w.getProfileAirtime(0,255)==expected);
|
||||
assert(w.getBeaconAirtime(p,255)==expected);
|
||||
#endif
|
||||
assert(w.getProfileAirtime(2,10)==0&&w.getProfileAirtime(0,-1)==0);
|
||||
assert(w.getProfileAirtime(0,256)==0);
|
||||
for(float bandwidth:{31.25f,41.67f,41.7f,62.5f,83.34f,101.56f,125.0f,203.125f,250.0f,406.25f,500.0f,812.5f,1000.0f}) {
|
||||
p.freq=915;p.bw=bandwidth;
|
||||
#if MESH_RADIO_PROFILE_CHIP==2021
|
||||
assert(w.validateProfile(p));
|
||||
#endif
|
||||
}
|
||||
}
|
||||
'''.replace("#include <cassert>", "#include <cassert>\n#include <initializer_list>").replace("@METHODS@", methods)
|
||||
with tempfile.TemporaryDirectory(prefix="profile-validation-") as folder:
|
||||
cpp = Path(folder) / "test.cpp"
|
||||
binary = Path(folder) / "test.exe"
|
||||
cpp.write_text(harness)
|
||||
for chip in (1262, 1268, 1276, 1272, 68, 1110, 1121, 2021):
|
||||
with self.subTest(chip=chip):
|
||||
flags = [] if os.name == "nt" else ["-fsanitize=address,undefined", "-fno-pie", "-no-pie"]
|
||||
built = subprocess.run([os.environ.get("CXX", "g++"), "-std=c++11", *flags,
|
||||
"-DMESH_RADIO_PROFILE_CHIP="+str(chip), "-I", str(ROOT / "src"),
|
||||
str(cpp), "-o", str(binary)], capture_output=True, text=True, timeout=60)
|
||||
self.assertEqual(built.returncode, 0, built.stderr)
|
||||
ran = subprocess.run([str(binary)], capture_output=True, text=True, timeout=10)
|
||||
self.assertEqual(ran.returncode, 0, ran.stderr)
|
||||
|
||||
|
||||
if __name__ == "__main__": unittest.main()
|
||||
Reference in new issue
Block a user