-
-
Notifications
You must be signed in to change notification settings - Fork 2.6k
fix(radio): validate 2.4 GHz bandwidths #11317
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop
Are you sure you want to change the base?
Changes from 4 commits
e57ebb0
e884072
74c86dc
3d169c9
8d478f8
b6d2ed2
88e643e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -200,6 +200,40 @@ static inline uint16_t clampBandwidthCode(uint16_t bwCode) | |
| return bwCode; | ||
| } | ||
|
|
||
| static inline bool supportsSx128xLoRaBandwidth(float bandwidthKHz, bool wideBand) | ||
| { | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This whole section looks like an anti-pattern to the rest of the file, or at least a horrible typo trap, even for an llm |
||
| return wideBand && (bandwidthKHz == 203.125f || bandwidthKHz == 406.25f || bandwidthKHz == 812.5f || bandwidthKHz == 1625.0f); | ||
| } | ||
|
|
||
| static inline bool supportsLr11x0LoRaBandwidth(float bandwidthKHz, bool wideBand) | ||
| { | ||
| return !wideBand || bandwidthKHz == 203.125f || bandwidthKHz == 406.25f || bandwidthKHz == 812.5f; | ||
| } | ||
|
|
||
| static inline bool supportsLr20x0LoRaBandwidth(float bandwidthKHz, bool wideBand) | ||
| { | ||
| if (!wideBand) | ||
| return true; | ||
|
|
||
| switch (static_cast<int>(bandwidthKHz / 2 + 0.01f)) { | ||
| case 15: | ||
| case 20: | ||
| case 31: | ||
| case 41: | ||
| case 50: | ||
| case 62: | ||
| case 101: | ||
| case 125: | ||
| case 203: | ||
| case 250: | ||
| case 406: | ||
| case 500: | ||
| return true; | ||
| default: | ||
| return false; | ||
| } | ||
| } | ||
|
|
||
| static inline void modemPresetToParams(meshtastic_Config_LoRaConfig_ModemPreset preset, bool wideLora, float &bwKHz, uint8_t &sf, | ||
| uint8_t &cr) | ||
| { | ||
|
|
@@ -294,4 +328,4 @@ static inline float modemPresetToBwKHz(meshtastic_Config_LoRaConfig_ModemPreset | |
| uint8_t cr = 0; | ||
| modemPresetToParams(preset, wideLora, bwKHz, sf, cr); | ||
| return bwKHz; | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -373,6 +373,7 @@ extern SPIClass SPI1; | |
| std::unique_ptr<RadioInterface> initLoRa() | ||
| { | ||
| std::unique_ptr<RadioInterface> rIf = nullptr; | ||
| const auto loraConfigBeforeProbe = config.lora; | ||
|
|
||
| #if ARCH_PORTDUINO | ||
| SPISettings loraSpiSettings(portduino_config.spiSpeed, MSBFIRST, SPI_MODE0); | ||
|
|
@@ -581,10 +582,16 @@ std::unique_ptr<RadioInterface> initLoRa() | |
| if (!rIf) { | ||
| rIf = std::unique_ptr<LR1120Interface>( | ||
| new LR1120Interface(loraHal, LR1120_SPI_NSS_PIN, LR1120_IRQ_PIN, LR1120_NRESET_PIN, LR1120_BUSY_PIN)); | ||
| rIf->setConfigErrorReporting(false); | ||
| if (!rIf->init()) { | ||
| LOG_WARN("No LR1120 radio"); | ||
| config.lora = loraConfigBeforeProbe; | ||
| rIf = nullptr; | ||
| } else { | ||
| config.lora = loraConfigBeforeProbe; | ||
| rIf->setConfigErrorReporting(true); | ||
| if (!rIf->reconfigure()) | ||
| LOG_WARN("LR1120 reconfigure failed after probe"); | ||
|
coderabbitai[bot] marked this conversation as resolved.
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why are you using a string for a known variable? |
||
| LOG_INFO("LR1120 init success"); | ||
| radioType = LR1120_RADIO; | ||
| } | ||
|
|
@@ -595,10 +602,16 @@ std::unique_ptr<RadioInterface> initLoRa() | |
| if (!rIf) { | ||
| rIf = std::unique_ptr<LR1121Interface>( | ||
| new LR1121Interface(loraHal, LR1121_SPI_NSS_PIN, LR1121_IRQ_PIN, LR1121_NRESET_PIN, LR1121_BUSY_PIN)); | ||
| rIf->setConfigErrorReporting(false); | ||
| if (!rIf->init()) { | ||
| LOG_WARN("No LR1121 radio"); | ||
| config.lora = loraConfigBeforeProbe; | ||
| rIf = nullptr; | ||
| } else { | ||
| config.lora = loraConfigBeforeProbe; | ||
| rIf->setConfigErrorReporting(true); | ||
| if (!rIf->reconfigure()) | ||
| LOG_WARN("LR1121 reconfigure failed after probe"); | ||
| LOG_INFO("LR1121 init success"); | ||
| radioType = LR1121_RADIO; | ||
| } | ||
|
|
@@ -609,10 +622,16 @@ std::unique_ptr<RadioInterface> initLoRa() | |
| if (!rIf) { | ||
| rIf = std::unique_ptr<LR2021Interface>( | ||
| new LR2021Interface(loraHal, LR2021_SPI_NSS_PIN, LR2021_IRQ_PIN, LR2021_NRESET_PIN, LR2021_BUSY_PIN)); | ||
| rIf->setConfigErrorReporting(false); | ||
| if (!rIf->init()) { | ||
| LOG_WARN("No LR2021 radio"); | ||
| config.lora = loraConfigBeforeProbe; | ||
| rIf = nullptr; | ||
| } else { | ||
| config.lora = loraConfigBeforeProbe; | ||
| rIf->setConfigErrorReporting(true); | ||
| if (!rIf->reconfigure()) | ||
| LOG_WARN("LR2021 reconfigure failed after probe"); | ||
| LOG_INFO("LR2021 init success"); | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Strings for known variables. Tell sol to think harder. |
||
| radioType = LR2021_RADIO; | ||
| } | ||
|
|
@@ -1085,7 +1104,8 @@ bool RadioInterface::validateConfigRegion(const meshtastic_Config_LoRaConfig &lo | |
| * When clamp==false, returns false on first error (pure validation). | ||
| * When clamp==true, fixes invalid settings in-place and returns true. | ||
| */ | ||
| bool RadioInterface::checkOrClampConfigLora(meshtastic_Config_LoRaConfig &loraConfig, bool clamp) | ||
| bool RadioInterface::checkOrClampConfigLora(meshtastic_Config_LoRaConfig &loraConfig, bool clamp, RadioInterface *radio, | ||
| bool reportErrors) | ||
| { | ||
| char err_string[160]; | ||
| float check_bw; | ||
|
|
@@ -1108,14 +1128,17 @@ bool RadioInterface::checkOrClampConfigLora(meshtastic_Config_LoRaConfig &loraCo | |
| // Validation must still fail so callers route into the clamp, but quietly: | ||
| // the clamp will accept this config by swapping regions, so don't record a | ||
| // critical error or alarm the user over a change that is about to succeed. | ||
| LOG_INFO("Preset %s implies region swap %s to %s, deferring to clamp", presetName, newRegion->name, | ||
| swapRegion->name); | ||
| if (reportErrors) | ||
| LOG_INFO("Preset %s implies region swap %s to %s, deferring to clamp", presetName, newRegion->name, | ||
| swapRegion->name); | ||
| return false; | ||
| } | ||
| snprintf(err_string, sizeof(err_string), "Preset %s swaps region %s to %s", presetName, newRegion->name, | ||
| swapRegion->name); | ||
| LOG_INFO("%s", err_string); | ||
| sendErrorNotification(err_string, meshtastic_LogRecord_Level_INFO); | ||
| if (reportErrors) { | ||
| LOG_INFO("%s", err_string); | ||
| sendErrorNotification(err_string, meshtastic_LogRecord_Level_INFO); | ||
| } | ||
|
|
||
| loraConfig.region = swapRegion->code; | ||
| newRegion = swapRegion; | ||
|
|
@@ -1131,9 +1154,11 @@ bool RadioInterface::checkOrClampConfigLora(meshtastic_Config_LoRaConfig &loraCo | |
| } else { | ||
| snprintf(err_string, sizeof(err_string), "Preset %s invalid for %s", presetName, newRegion->name); | ||
| } | ||
| LOG_ERROR("%s", err_string); | ||
| RECORD_CRITICALERROR(meshtastic_CriticalErrorCode_INVALID_RADIO_SETTING); | ||
| sendErrorNotification(err_string); | ||
| if (reportErrors) { | ||
| LOG_ERROR("%s", err_string); | ||
| RECORD_CRITICALERROR(meshtastic_CriticalErrorCode_INVALID_RADIO_SETTING); | ||
| sendErrorNotification(err_string); | ||
| } | ||
|
|
||
| if (clamp) { | ||
| loraConfig.modem_preset = newRegion->getDefaultPreset(); | ||
|
|
@@ -1147,6 +1172,26 @@ bool RadioInterface::checkOrClampConfigLora(meshtastic_Config_LoRaConfig &loraCo | |
| check_bw = clampBandwidthKHz(bwCodeToKHz(loraConfig.bandwidth)); | ||
| } | ||
|
|
||
| RadioInterface *candidateRadio = radio ? radio : RadioLibInterface::instance; | ||
| if (candidateRadio && !candidateRadio->supportsLoRaBandwidth(check_bw, newRegion->wideLora)) { | ||
| const float defaultBandwidth = modemPresetToBwKHz(newRegion->getDefaultPreset(), newRegion->wideLora); | ||
| snprintf(err_string, sizeof(err_string), "Bandwidth %.3fkHz invalid for this radio in %s", check_bw, newRegion->name); | ||
| if (reportErrors) { | ||
| LOG_ERROR("%s", err_string); | ||
| RECORD_CRITICALERROR(meshtastic_CriticalErrorCode_INVALID_RADIO_SETTING); | ||
| sendErrorNotification(err_string); | ||
| } | ||
|
|
||
| if (!clamp) | ||
| return false; | ||
|
|
||
| if (loraConfig.use_preset) | ||
| loraConfig.modem_preset = newRegion->getDefaultPreset(); | ||
| else | ||
| loraConfig.bandwidth = bwKHzToCode(defaultBandwidth); | ||
| check_bw = defaultBandwidth; | ||
| } | ||
|
|
||
| // Calculate width of slots (aka channels) based on bandwidth and any spacing or padding required by the region: | ||
| // spacing = gap between slots (0 for continuous spectrum) and at the beginning of the band | ||
| // padding = gap at the beginning and end of the slots (0 for no padding) | ||
|
|
@@ -1157,9 +1202,11 @@ bool RadioInterface::checkOrClampConfigLora(meshtastic_Config_LoRaConfig &loraCo | |
| if ((newRegion->freqEnd - newRegion->freqStart) < freqSlotWidth) { | ||
| const float regionSpanKHz = (newRegion->freqEnd - newRegion->freqStart) * 1000.0f; | ||
| snprintf(err_string, sizeof(err_string), "%s span %.0fkHz < requested %.0fkHz", newRegion->name, regionSpanKHz, check_bw); | ||
| LOG_ERROR("%s", err_string); | ||
| RECORD_CRITICALERROR(meshtastic_CriticalErrorCode_INVALID_RADIO_SETTING); | ||
| sendErrorNotification(err_string); | ||
| if (reportErrors) { | ||
| LOG_ERROR("%s", err_string); | ||
| RECORD_CRITICALERROR(meshtastic_CriticalErrorCode_INVALID_RADIO_SETTING); | ||
| sendErrorNotification(err_string); | ||
| } | ||
|
|
||
| if (clamp) { | ||
| loraConfig.bandwidth = bwKHzToCode(modemPresetToBwKHz(newRegion->getDefaultPreset(), newRegion->wideLora)); | ||
|
|
@@ -1199,9 +1246,11 @@ bool RadioInterface::checkOrClampConfigLora(meshtastic_Config_LoRaConfig &loraCo | |
| if (loraConfig.channel_num > numFreqSlots) { | ||
| snprintf(err_string, sizeof(err_string), "Channel number %u invalid for %s, max is %u", loraConfig.channel_num, | ||
| newRegion->name, numFreqSlots); | ||
| LOG_ERROR("%s", err_string); | ||
| RECORD_CRITICALERROR(meshtastic_CriticalErrorCode_INVALID_RADIO_SETTING); | ||
| sendErrorNotification(err_string); | ||
| if (reportErrors) { | ||
| LOG_ERROR("%s", err_string); | ||
| RECORD_CRITICALERROR(meshtastic_CriticalErrorCode_INVALID_RADIO_SETTING); | ||
| sendErrorNotification(err_string); | ||
| } | ||
|
|
||
| if (clamp) { | ||
| if (uses_custom_channel_name) { // clamp to channel name hash | ||
|
|
@@ -1231,15 +1280,15 @@ bool RadioInterface::checkOrClampConfigLora(meshtastic_Config_LoRaConfig &loraCo | |
| return true; | ||
| } | ||
|
|
||
| bool RadioInterface::validateConfigLora(const meshtastic_Config_LoRaConfig &loraConfig) | ||
| bool RadioInterface::validateConfigLora(const meshtastic_Config_LoRaConfig &loraConfig, RadioInterface *radio, bool reportErrors) | ||
| { | ||
| auto copy = loraConfig; | ||
| return checkOrClampConfigLora(copy, false); | ||
| return checkOrClampConfigLora(copy, false, radio, reportErrors); | ||
| } | ||
|
|
||
| void RadioInterface::clampConfigLora(meshtastic_Config_LoRaConfig &loraConfig) | ||
| void RadioInterface::clampConfigLora(meshtastic_Config_LoRaConfig &loraConfig, RadioInterface *radio, bool reportErrors) | ||
| { | ||
| checkOrClampConfigLora(loraConfig, true); | ||
| checkOrClampConfigLora(loraConfig, true, radio, reportErrors); | ||
| } | ||
|
|
||
| /** | ||
|
|
@@ -1252,14 +1301,15 @@ void RadioInterface::applyModemConfig() | |
| // No Sync Words in LORA mode | ||
| meshtastic_Config_LoRaConfig &loraConfig = config.lora; | ||
| const RegionInfo *newRegion = getRegion(loraConfig.region); | ||
| const bool useWideModemParameters = newRegion->wideLora || !supportsSubGhz(); | ||
| myRegion = newRegion; | ||
|
|
||
| if (loraConfig.use_preset) { | ||
| if (!validateConfigLora(loraConfig)) { | ||
| if (!validateConfigLora(loraConfig, this, reportConfigErrors)) { | ||
| loraConfig.modem_preset = newRegion->getDefaultPreset(); | ||
| } | ||
| uint8_t newcr; | ||
| modemPresetToParams(loraConfig.modem_preset, newRegion->wideLora, bw, sf, newcr); | ||
| modemPresetToParams(loraConfig.modem_preset, useWideModemParameters, bw, sf, newcr); | ||
| // If custom CR is being used already, check if the new preset is higher | ||
| if (loraConfig.coding_rate >= 5 && loraConfig.coding_rate <= 8 && loraConfig.coding_rate < newcr) { | ||
| cr = newcr; | ||
|
|
@@ -1274,14 +1324,19 @@ void RadioInterface::applyModemConfig() | |
| } | ||
|
|
||
| } else { // if not using preset, then just use the custom settings | ||
| if (validateConfigLora(loraConfig)) { | ||
| if (validateConfigLora(loraConfig, this, reportConfigErrors)) { | ||
| } else { | ||
| LOG_WARN("Invalid LoRa config settings, cannot apply requested modem config - falling back to %s defaults", | ||
| newRegion->name); | ||
| clampConfigLora(loraConfig); | ||
| clampConfigLora(loraConfig, this, reportConfigErrors); | ||
| } | ||
| // Clamp at the source so numFreqSlots below can never be 0 (a bandwidth-0 config may already be persisted) | ||
| bw = clampBandwidthKHz(bwCodeToKHz(loraConfig.bandwidth)); | ||
| if (!supportsLoRaBandwidth(bw, useWideModemParameters)) { | ||
| // Keep 2.4 GHz-only radios operational while an incompatible or UNSET region is being corrected. | ||
| bw = modemPresetToBwKHz(newRegion->getDefaultPreset(), useWideModemParameters); | ||
| loraConfig.bandwidth = bwKHzToCode(bw); | ||
| } | ||
| sf = loraConfig.spread_factor; | ||
| cr = loraConfig.coding_rate; | ||
| } | ||
|
|
@@ -1353,7 +1408,7 @@ void RadioInterface::applyModemConfig() | |
| saveChannelNum(channel_num); | ||
| saveFreq(freq + loraConfig.frequency_offset); | ||
|
|
||
| if (newRegion->wideLora) { // clamp if wide freq range | ||
| if (useWideModemParameters) { // clamp if wide freq range | ||
| preambleLength = wideLoraPreambleLengthDefault; // 12 is the default for operation above 2GHz | ||
| } else { | ||
| preambleLength = | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use the
IS_ONE_OFmacro to clean up these chained comparisons in these 3 methods