diff --git a/protobufs b/protobufs index 44298d374f..f57b423a39 160000 --- a/protobufs +++ b/protobufs @@ -1 +1 @@ -Subproject commit 44298d374fd83cfbc36fdb76c6f966e980cadd93 +Subproject commit f57b423a395c7a54d3acaa83ac9036e4d9281d9e diff --git a/src/DisplayFormatters.cpp b/src/DisplayFormatters.cpp index d88f9fc9fe..fe8f914cdb 100644 --- a/src/DisplayFormatters.cpp +++ b/src/DisplayFormatters.cpp @@ -37,6 +37,18 @@ const char *DisplayFormatters::getModemPresetDisplayName(meshtastic_Config_LoRaC case meshtastic_Config_LoRaConfig_ModemPreset_LONG_MODERATE: return useShortName ? "LongM" : "LongMod"; break; + case meshtastic_Config_LoRaConfig_ModemPreset_LITE_FAST: + return useShortName ? "LiteF" : "LiteFast"; + break; + case meshtastic_Config_LoRaConfig_ModemPreset_LITE_SLOW: + return useShortName ? "LiteS" : "LiteSlow"; + break; + case meshtastic_Config_LoRaConfig_ModemPreset_NARROW_FAST: + return useShortName ? "NarF" : "NarrowFast"; + break; + case meshtastic_Config_LoRaConfig_ModemPreset_NARROW_SLOW: + return useShortName ? "NarS" : "NarrowSlow"; + break; default: return useShortName ? "Custom" : "Invalid"; break; diff --git a/src/airtime.cpp b/src/airtime.cpp index a7736d6671..0e0d72e20e 100644 --- a/src/airtime.cpp +++ b/src/airtime.cpp @@ -133,11 +133,12 @@ bool AirTime::isTxAllowedChannelUtil(bool polite) bool AirTime::isTxAllowedAirUtil() { - if (!config.lora.override_duty_cycle && myRegion->dutyCycle < 100) { - if (utilizationTXPercent() < myRegion->dutyCycle * polite_duty_cycle_percent / 100) { + float effectiveDutyCycle = getEffectiveDutyCycle(); + if (!config.lora.override_duty_cycle && effectiveDutyCycle < 100) { + if (utilizationTXPercent() < effectiveDutyCycle * polite_duty_cycle_percent / 100) { return true; } else { - LOG_WARN("TX air util. >%f%%. Skip send", myRegion->dutyCycle * polite_duty_cycle_percent / 100); + LOG_WARN("TX air util. >%f%%. Skip send", effectiveDutyCycle * polite_duty_cycle_percent / 100); return false; } } diff --git a/src/graphics/draw/MenuHandler.cpp b/src/graphics/draw/MenuHandler.cpp index 195da09f9c..638dd80cc6 100644 --- a/src/graphics/draw/MenuHandler.cpp +++ b/src/graphics/draw/MenuHandler.cpp @@ -115,6 +115,8 @@ void menuHandler::LoraRegionPicker(uint32_t duration) {"US", OptionsAction::Select, meshtastic_Config_LoRaConfig_RegionCode_US}, {"EU_433", OptionsAction::Select, meshtastic_Config_LoRaConfig_RegionCode_EU_433}, {"EU_868", OptionsAction::Select, meshtastic_Config_LoRaConfig_RegionCode_EU_868}, + {"NARROW_868", OptionsAction::Select, meshtastic_Config_LoRaConfig_RegionCode_NARROW_868}, + {"EU_866", OptionsAction::Select, meshtastic_Config_LoRaConfig_RegionCode_EU_866}, {"CN", OptionsAction::Select, meshtastic_Config_LoRaConfig_RegionCode_CN}, {"JP", OptionsAction::Select, meshtastic_Config_LoRaConfig_RegionCode_JP}, {"ANZ", OptionsAction::Select, meshtastic_Config_LoRaConfig_RegionCode_ANZ}, @@ -137,8 +139,7 @@ void menuHandler::LoraRegionPicker(uint32_t duration) {"KZ_433", OptionsAction::Select, meshtastic_Config_LoRaConfig_RegionCode_KZ_433}, {"KZ_863", OptionsAction::Select, meshtastic_Config_LoRaConfig_RegionCode_KZ_863}, {"NP_865", OptionsAction::Select, meshtastic_Config_LoRaConfig_RegionCode_NP_865}, - {"BR_902", OptionsAction::Select, meshtastic_Config_LoRaConfig_RegionCode_BR_902}, - }; + {"BR_902", OptionsAction::Select, meshtastic_Config_LoRaConfig_RegionCode_BR_902}}; constexpr size_t regionCount = sizeof(regionOptions) / sizeof(regionOptions[0]); static std::array regionLabels{}; @@ -188,7 +189,7 @@ void menuHandler::LoraRegionPicker(uint32_t duration) #endif config.lora.tx_enabled = true; initRegion(); - if (myRegion->dutyCycle < 100) { + if (getEffectiveDutyCycle() < 100) { config.lora.ignore_mqtt = true; // Ignore MQTT by default if region has a duty cycle limit } @@ -325,7 +326,10 @@ void menuHandler::radioPresetPicker() {"ShortSlow", OptionsAction::Select, meshtastic_Config_LoRaConfig_ModemPreset_SHORT_SLOW}, {"ShortFast", OptionsAction::Select, meshtastic_Config_LoRaConfig_ModemPreset_SHORT_FAST}, {"ShortTurbo", OptionsAction::Select, meshtastic_Config_LoRaConfig_ModemPreset_SHORT_TURBO}, - }; + {"LiteFast", OptionsAction::Select, meshtastic_Config_LoRaConfig_ModemPreset_LITE_FAST}, + {"LiteSlow", OptionsAction::Select, meshtastic_Config_LoRaConfig_ModemPreset_LITE_SLOW}, + {"NarrowFast", OptionsAction::Select, meshtastic_Config_LoRaConfig_ModemPreset_NARROW_FAST}, + {"NarrowSlow", OptionsAction::Select, meshtastic_Config_LoRaConfig_ModemPreset_NARROW_SLOW}}; constexpr size_t presetCount = sizeof(presetOptions) / sizeof(presetOptions[0]); static std::array presetLabels{}; diff --git a/src/mesh/MeshRadio.h b/src/mesh/MeshRadio.h index bbb0ee00f2..29b8e4b4fa 100644 --- a/src/mesh/MeshRadio.h +++ b/src/mesh/MeshRadio.h @@ -10,12 +10,21 @@ struct RegionInfo { meshtastic_Config_LoRaConfig_RegionCode code; float freqStart; float freqEnd; - float dutyCycle; - float spacing; + float dutyCycle; // modified by getEffectiveDutyCycle + float spacing; // gaps between radio channels + float padding; // padding at each side of the "operating channel" uint8_t powerLimit; // Or zero for not set bool audioPermitted; bool freqSwitching; bool wideLora; + bool licensedOnly; // Only allow in HAM mode + int8_t textThrottle; // text broadcast throttle - signed to allow future changes + int8_t positionThrottle; // position broadcast throttle - signed to allow future changes + int8_t telemetryThrottle; // telemetry broadcast throttle - signed to allow future changes + uint8_t overrideSlot; // default frequency slot if not using channel hashing + meshtastic_Config_LoRaConfig_ModemPreset defaultPreset; + // static list of available presets + const meshtastic_Config_LoRaConfig_ModemPreset *availablePresets; const char *name; // EU433 etc }; @@ -24,6 +33,20 @@ extern const RegionInfo *myRegion; extern void initRegion(); +/** + * Get the effective duty cycle for the current region based on device role. + * For EU_866, returns 10% for fixed devices (ROUTER, ROUTER_LATE) and 2.5% for mobile devices. + * For other regions, returns the standard duty cycle. + */ +extern float getEffectiveDutyCycle(); + +extern meshtastic_Config_LoRaConfig_ModemPreset PRESETS_STD[]; +extern meshtastic_Config_LoRaConfig_ModemPreset PRESETS_EU_868[]; +extern meshtastic_Config_LoRaConfig_ModemPreset PRESETS_LITE[]; +extern meshtastic_Config_LoRaConfig_ModemPreset PRESETS_NARROW[]; +// extern meshtastic_Config_LoRaConfig_ModemPreset PRESETS_HAM[]; +extern meshtastic_Config_LoRaConfig_ModemPreset PRESETS_UNDEF[]; + static inline float bwCodeToKHz(uint16_t bwCode) { if (bwCode == 31) @@ -102,6 +125,26 @@ static inline void modemPresetToParams(meshtastic_Config_LoRaConfig_ModemPreset cr = 8; sf = 12; break; + case meshtastic_Config_LoRaConfig_ModemPreset_LITE_FAST: + bwKHz = 125; + cr = 5; + sf = 9; + break; + case meshtastic_Config_LoRaConfig_ModemPreset_LITE_SLOW: + bwKHz = 125; + cr = 5; + sf = 10; + break; + case meshtastic_Config_LoRaConfig_ModemPreset_NARROW_FAST: + bwKHz = 62.5f; + cr = 6; + sf = 7; + break; + case meshtastic_Config_LoRaConfig_ModemPreset_NARROW_SLOW: + bwKHz = 62.5f; + cr = 6; + sf = 8; + break; default: // LONG_FAST (or illegal) bwKHz = wideLora ? 812.5f : 250.0f; cr = 5; @@ -117,4 +160,4 @@ static inline float modemPresetToBwKHz(meshtastic_Config_LoRaConfig_ModemPreset uint8_t cr = 0; modemPresetToParams(preset, wideLora, bwKHz, sf, cr); return bwKHz; -} \ No newline at end of file +} diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp index 913d45b418..5b5196158e 100644 --- a/src/mesh/NodeDB.cpp +++ b/src/mesh/NodeDB.cpp @@ -1292,7 +1292,7 @@ void NodeDB::loadFromDisk() // Coerce LoRa config fields derived from presets while bootstrapping. // Some clients/UI components display bandwidth/spread_factor directly from config even in preset mode. if (config.has_lora && config.lora.use_preset) { - RadioInterface::bootstrapLoRaConfigFromPreset(config.lora); + RadioInterface::validateModemConfig(config.lora); } if (backupSecurity.private_key.size > 0) { diff --git a/src/mesh/RadioInterface.cpp b/src/mesh/RadioInterface.cpp index 9e1ea3f21c..39f37e2f5b 100644 --- a/src/mesh/RadioInterface.cpp +++ b/src/mesh/RadioInterface.cpp @@ -31,16 +31,44 @@ #include "STM32WLE5JCInterface.h" #endif +meshtastic_Config_LoRaConfig_ModemPreset PRESETS_STD[] = { + meshtastic_Config_LoRaConfig_ModemPreset_LONG_FAST, meshtastic_Config_LoRaConfig_ModemPreset_LONG_SLOW, + meshtastic_Config_LoRaConfig_ModemPreset_MEDIUM_SLOW, meshtastic_Config_LoRaConfig_ModemPreset_MEDIUM_FAST, + meshtastic_Config_LoRaConfig_ModemPreset_SHORT_SLOW, meshtastic_Config_LoRaConfig_ModemPreset_SHORT_FAST, + meshtastic_Config_LoRaConfig_ModemPreset_LONG_MODERATE, meshtastic_Config_LoRaConfig_ModemPreset_SHORT_TURBO, + meshtastic_Config_LoRaConfig_ModemPreset_LONG_TURBO}; + +meshtastic_Config_LoRaConfig_ModemPreset PRESETS_EU_868[] = { + meshtastic_Config_LoRaConfig_ModemPreset_LONG_FAST, meshtastic_Config_LoRaConfig_ModemPreset_LONG_SLOW, + meshtastic_Config_LoRaConfig_ModemPreset_MEDIUM_SLOW, meshtastic_Config_LoRaConfig_ModemPreset_MEDIUM_FAST, + meshtastic_Config_LoRaConfig_ModemPreset_SHORT_SLOW, meshtastic_Config_LoRaConfig_ModemPreset_SHORT_FAST, + meshtastic_Config_LoRaConfig_ModemPreset_LONG_MODERATE}; // no TURBO modes in EU868 + +meshtastic_Config_LoRaConfig_ModemPreset PRESETS_LITE[] = {meshtastic_Config_LoRaConfig_ModemPreset_LITE_FAST, + meshtastic_Config_LoRaConfig_ModemPreset_LITE_SLOW}; + +meshtastic_Config_LoRaConfig_ModemPreset PRESETS_NARROW[] = {meshtastic_Config_LoRaConfig_ModemPreset_NARROW_FAST, + meshtastic_Config_LoRaConfig_ModemPreset_NARROW_SLOW}; + +// // Same as Narrow presets, but separate so that extra ham settings can be added later. +// meshtastic_Config_LoRaConfig_ModemPreset PRESETS_HAM[] = {meshtastic_Config_LoRaConfig_ModemPreset_NARROW_FAST, +// meshtastic_Config_LoRaConfig_ModemPreset_NARROW_SLOW}; + +meshtastic_Config_LoRaConfig_ModemPreset PRESETS_UNDEF[] = {meshtastic_Config_LoRaConfig_ModemPreset_LONG_FAST}; + // Calculate 2^n without calling pow() uint32_t pow_of_2(uint32_t n) { return 1 << n; } -#define RDEF(name, freq_start, freq_end, duty_cycle, spacing, power_limit, audio_permitted, frequency_switching, wide_lora) \ +#define RDEF(name, freq_start, freq_end, duty_cycle, spacing, padding, power_limit, text_throttle, position_throttle, \ + telemetry_throttle, audio_permitted, frequency_switching, wide_lora, licensed_only, override_slot, default_preset, \ + available_presets) \ { \ - meshtastic_Config_LoRaConfig_RegionCode_##name, freq_start, freq_end, duty_cycle, spacing, power_limit, audio_permitted, \ - frequency_switching, wide_lora, #name \ + meshtastic_Config_LoRaConfig_RegionCode_##name, freq_start, freq_end, duty_cycle, spacing, padding, power_limit, \ + text_throttle, position_throttle, telemetry_throttle, audio_permitted, frequency_switching, wide_lora, \ + licensed_only, override_slot, meshtastic_Config_LoRaConfig_ModemPreset_##default_preset, available_presets, #name \ } const RegionInfo regions[] = { @@ -48,7 +76,7 @@ const RegionInfo regions[] = { https://link.springer.com/content/pdf/bbm%3A978-1-4842-4357-2%2F1.pdf https://www.thethingsnetwork.org/docs/lorawan/regional-parameters/ */ - RDEF(US, 902.0f, 928.0f, 100, 0, 30, true, false, false), + RDEF(US, 902.0f, 928.0f, 100, 0, 0, 30, true, false, false, false, 0, 0, 0, 0, LONG_FAST, PRESETS_STD), /* EN300220 ETSI V3.2.1 [Table B.1, Item H, p. 21] @@ -56,7 +84,7 @@ const RegionInfo regions[] = { https://www.etsi.org/deliver/etsi_en/300200_300299/30022002/03.02.01_60/en_30022002v030201p.pdf FIXME: https://github.com/meshtastic/firmware/issues/3371 */ - RDEF(EU_433, 433.0f, 434.0f, 10, 0, 10, true, false, false), + RDEF(EU_433, 433.0f, 434.0f, 10, 0, 0, 10, true, false, false, false, 0, 0, 0, 0, LONG_FAST, PRESETS_STD), /* https://www.thethingsnetwork.org/docs/lorawan/duty-cycle/ @@ -72,33 +100,33 @@ const RegionInfo regions[] = { AFA) to avoid a duty cycle. (Please refer to line P page 22 of this document.) https://www.etsi.org/deliver/etsi_en/300200_300299/30022002/03.01.01_60/en_30022002v030101p.pdf */ - RDEF(EU_868, 869.4f, 869.65f, 10, 0, 27, false, false, false), + RDEF(EU_868, 869.4f, 869.65f, 10, 0, 0, 27, false, false, false, false, 0, 0, 0, 0, LONG_FAST, PRESETS_EU_868), /* https://lora-alliance.org/wp-content/uploads/2020/11/lorawan_regional_parameters_v1.0.3reva_0.pdf */ - RDEF(CN, 470.0f, 510.0f, 100, 0, 19, true, false, false), + RDEF(CN, 470.0f, 510.0f, 100, 0, 0, 19, true, false, false, false, 0, 0, 0, 0, LONG_FAST, PRESETS_STD), /* https://lora-alliance.org/wp-content/uploads/2020/11/lorawan_regional_parameters_v1.0.3reva_0.pdf https://www.arib.or.jp/english/html/overview/doc/5-STD-T108v1_5-E1.pdf https://qiita.com/ammo0613/items/d952154f1195b64dc29f */ - RDEF(JP, 920.5f, 923.5f, 100, 0, 13, true, false, false), + RDEF(JP, 920.5f, 923.5f, 100, 0, 0, 13, true, false, false, false, 0, 0, 0, 0, LONG_FAST, PRESETS_STD), /* https://www.iot.org.au/wp/wp-content/uploads/2016/12/IoTSpectrumFactSheet.pdf https://iotalliance.org.nz/wp-content/uploads/sites/4/2019/05/IoT-Spectrum-in-NZ-Briefing-Paper.pdf Also used in Brazil. */ - RDEF(ANZ, 915.0f, 928.0f, 100, 0, 30, true, false, false), + RDEF(ANZ, 915.0f, 928.0f, 100, 0, 0, 30, true, false, false, false, 0, 0, 0, 0, LONG_FAST, PRESETS_STD), /* 433.05 - 434.79 MHz, 25mW EIRP max, No duty cycle restrictions AU Low Interference Potential https://www.acma.gov.au/licences/low-interference-potential-devices-lipd-class-licence NZ General User Radio Licence for Short Range Devices https://gazette.govt.nz/notice/id/2022-go3100 */ - RDEF(ANZ_433, 433.05f, 434.79f, 100, 0, 14, true, false, false), + RDEF(ANZ_433, 433.05f, 434.79f, 100, 0, 0, 14, true, false, false, false, 0, 0, 0, 0, LONG_FAST, PRESETS_STD), /* https://digital.gov.ru/uploaded/files/prilozhenie-12-k-reshenyu-gkrch-18-46-03-1.pdf @@ -106,13 +134,13 @@ const RegionInfo regions[] = { Note: - We do LBT, so 100% is allowed. */ - RDEF(RU, 868.7f, 869.2f, 100, 0, 20, true, false, false), + RDEF(RU, 868.7f, 869.2f, 100, 0, 0, 20, true, false, false, false, 0, 0, 0, 0, LONG_FAST, PRESETS_STD), /* https://www.law.go.kr/LSW/admRulLsInfoP.do?admRulId=53943&efYd=0 https://resources.lora-alliance.org/technical-specifications/rp002-1-0-4-regional-parameters */ - RDEF(KR, 920.0f, 923.0f, 100, 0, 23, true, false, false), + RDEF(KR, 920.0f, 923.0f, 100, 0, 0, 23, true, false, false, false, 0, 0, 0, 0, LONG_FAST, PRESETS_STD), /* Taiwan, 920-925Mhz, limited to 0.5W indoor or coastal, 1.0W outdoor. @@ -120,42 +148,41 @@ const RegionInfo regions[] = { https://www.ncc.gov.tw/english/files/23070/102_5190_230703_1_doc_C.PDF https://gazette.nat.gov.tw/egFront/e_detail.do?metaid=147283 */ - RDEF(TW, 920.0f, 925.0f, 100, 0, 27, true, false, false), + RDEF(TW, 920.0f, 925.0f, 100, 0, 0, 27, true, false, false, false, 0, 0, 0, 0, LONG_FAST, PRESETS_STD), /* https://lora-alliance.org/wp-content/uploads/2020/11/lorawan_regional_parameters_v1.0.3reva_0.pdf */ - RDEF(IN, 865.0f, 867.0f, 100, 0, 30, true, false, false), - + RDEF(IN, 865.0f, 867.0f, 100, 0, 0, 30, true, false, false, false, 0, 0, 0, 0, LONG_FAST, PRESETS_STD), /* https://rrf.rsm.govt.nz/smart-web/smart/page/-smart/domain/licence/LicenceSummary.wdk?id=219752 https://iotalliance.org.nz/wp-content/uploads/sites/4/2019/05/IoT-Spectrum-in-NZ-Briefing-Paper.pdf */ - RDEF(NZ_865, 864.0f, 868.0f, 100, 0, 36, true, false, false), + RDEF(NZ_865, 864.0f, 868.0f, 100, 0, 0, 36, true, false, false, false, 0, 0, 0, 0, LONG_FAST, PRESETS_STD), /* https://lora-alliance.org/wp-content/uploads/2020/11/lorawan_regional_parameters_v1.0.3reva_0.pdf */ - RDEF(TH, 920.0f, 925.0f, 100, 0, 16, true, false, false), + RDEF(TH, 920.0f, 925.0f, 100, 0, 0, 16, true, false, false, false, 0, 0, 0, 0, LONG_FAST, PRESETS_STD), /* 433,05-434,7 Mhz 10 mW https://nkrzi.gov.ua/images/upload/256/5810/PDF_UUZ_19_01_2016.pdf */ - RDEF(UA_433, 433.0f, 434.7f, 10, 0, 10, true, false, false), + RDEF(UA_433, 433.0f, 434.7f, 10, 0, 0, 10, true, false, false, false, 0, 0, 0, 0, LONG_FAST, PRESETS_STD), /* 868,0-868,6 Mhz 25 mW https://nkrzi.gov.ua/images/upload/256/5810/PDF_UUZ_19_01_2016.pdf */ - RDEF(UA_868, 868.0f, 868.6f, 1, 0, 14, true, false, false), + RDEF(UA_868, 868.0f, 868.6f, 1, 0, 0, 14, true, false, false, false, 0, 0, 0, 0, LONG_FAST, PRESETS_STD), /* Malaysia 433 - 435 MHz at 100mW, no restrictions. https://www.mcmc.gov.my/skmmgovmy/media/General/pdf/Short-Range-Devices-Specification.pdf */ - RDEF(MY_433, 433.0f, 435.0f, 100, 0, 20, true, false, false), + RDEF(MY_433, 433.0f, 435.0f, 100, 0, 0, 20, true, false, false, false, 0, 0, 0, 0, LONG_FAST, PRESETS_STD), /* Malaysia @@ -164,14 +191,14 @@ const RegionInfo regions[] = { Frequency hopping is used for 919 - 923 MHz. https://www.mcmc.gov.my/skmmgovmy/media/General/pdf/Short-Range-Devices-Specification.pdf */ - RDEF(MY_919, 919.0f, 924.0f, 100, 0, 27, true, true, false), + RDEF(MY_919, 919.0f, 924.0f, 100, 0, 0, 27, true, true, false, false, 0, 0, 0, 0, LONG_FAST, PRESETS_STD), /* Singapore SG_923 Band 30d: 917 - 925 MHz at 100mW, no restrictions. https://www.imda.gov.sg/-/media/imda/files/regulation-licensing-and-consultations/ict-standards/telecommunication-standards/radio-comms/imdatssrd.pdf */ - RDEF(SG_923, 917.0f, 925.0f, 100, 0, 20, true, false, false), + RDEF(SG_923, 917.0f, 925.0f, 100, 0, 0, 20, true, false, false, false, 0, 0, 0, 0, LONG_FAST, PRESETS_STD), /* Philippines @@ -181,8 +208,9 @@ const RegionInfo regions[] = { https://github.com/meshtastic/firmware/issues/4948#issuecomment-2394926135 */ - RDEF(PH_433, 433.0f, 434.7f, 100, 0, 10, true, false, false), RDEF(PH_868, 868.0f, 869.4f, 100, 0, 14, true, false, false), - RDEF(PH_915, 915.0f, 918.0f, 100, 0, 24, true, false, false), + RDEF(PH_433, 433.0f, 434.7f, 100, 0, 0, 10, true, false, false, false, 0, 0, 0, 0, LONG_FAST, PRESETS_STD), + RDEF(PH_868, 868.0f, 869.4f, 100, 0, 0, 14, true, false, false, false, 0, 0, 0, 0, LONG_FAST, PRESETS_STD), + RDEF(PH_915, 915.0f, 918.0f, 100, 0, 0, 24, true, false, false, false, 0, 0, 0, 0, LONG_FAST, PRESETS_STD), /* Kazakhstan @@ -190,37 +218,51 @@ const RegionInfo regions[] = { 863 - 868 MHz <25 mW EIRP, 500kHz channels allowed, must not be used at airfields https://github.com/meshtastic/firmware/issues/7204 */ - RDEF(KZ_433, 433.075f, 434.775f, 100, 0, 10, true, false, false), - RDEF(KZ_863, 863.0f, 868.0f, 100, 0, 30, true, false, false), + RDEF(KZ_433, 433.075f, 434.775f, 100, 0, 0, 10, true, false, false, false, 0, 0, 0, 0, LONG_FAST, PRESETS_STD), + RDEF(KZ_863, 863.0f, 868.0f, 100, 0, 0, 30, true, false, false, false, 0, 0, 0, 0, LONG_FAST, PRESETS_STD), /* Nepal 865 MHz to 868 MHz frequency band for IoT (Internet of Things), M2M (Machine-to-Machine), and smart metering use, specifically in non-cellular mode. https://www.nta.gov.np/uploads/contents/Radio-Frequency-Policy-2080-English.pdf */ - RDEF(NP_865, 865.0f, 868.0f, 100, 0, 30, true, false, false), + RDEF(NP_865, 865.0f, 868.0f, 100, 0, 0, 30, true, false, false, false, 0, 0, 0, 0, LONG_FAST, PRESETS_STD), /* Brazil 902 - 907.5 MHz , 1W power limit, no duty cycle restrictions https://github.com/meshtastic/firmware/issues/3741 */ - RDEF(BR_902, 902.0f, 907.5f, 100, 0, 30, true, false, false), + RDEF(BR_902, 902.0f, 907.5f, 100, 0, 0, 30, true, false, false, false, 0, 0, 0, 0, LONG_FAST, PRESETS_STD), + + /* + EU 866MHz band (Band no. 46b of 2006/771/EC and subsequent amendments) for Non-specific short-range devices (SRD) + Gives 4 channels at 865.7/866.3/866.9/867.5 MHz, 400 kHz gap plus 37.5 kHz padding between channels, 27 dBm, + duty cycle 2.5% (mobile) or 10% (fixed) https://eur-lex.europa.eu/legal-content/EN/TXT/?uri=CELEX:02006D0771(01)-20250123 + */ + RDEF(EU_866, 865.6f, 867.6f, 2.5, 0.4, 0.0375f, 27, false, false, false, false, 0, 99, 99, 0, LITE_FAST, PRESETS_LITE), + + /* + EU 868MHz band: 3 channels at 869.410/869.4625/869.577 MHz + Channel centres at 869.442/869.525/869.608 MHz, + 10.4 kHz padding on channels, 27 dBm, duty cycle 10% + */ + RDEF(NARROW_868, 869.4f, 869.65f, 10, 0, 0.0104f, 27, false, false, false, false, 0, 0, 0, 1, NARROW_FAST, PRESETS_NARROW), /* 2.4 GHZ WLAN Band equivalent. Only for SX128x chips. */ - RDEF(LORA_24, 2400.0f, 2483.5f, 100, 0, 10, true, false, true), + RDEF(LORA_24, 2400.0f, 2483.5f, 100, 0, 0, 10, true, false, true, false, 0, 0, 0, 0, LONG_FAST, PRESETS_STD), /* This needs to be last. Same as US. */ - RDEF(UNSET, 902.0f, 928.0f, 100, 0, 30, true, false, false) + RDEF(UNSET, 902.0f, 928.0f, 100, 0, 0, 30, true, false, false, false, 0, 0, 0, 0, LONG_FAST, PRESETS_UNDEF) }; const RegionInfo *myRegion; -bool RadioInterface::uses_default_frequency_slot = true; +bool RadioInterface::uses_default_frequency_slot = true; // this is modified in init region static uint8_t bytes[MAX_LORA_PAYLOAD_LEN + 1]; @@ -514,32 +556,29 @@ void initRegion() myRegion = r; } -void RadioInterface::bootstrapLoRaConfigFromPreset(meshtastic_Config_LoRaConfig &loraConfig) +const RegionInfo *getRegion(meshtastic_Config_LoRaConfig_RegionCode code) { - if (!loraConfig.use_preset) { - return; - } - - // Find region info to determine whether "wide" LoRa is permitted (2.4 GHz uses wider bandwidth codes). const RegionInfo *r = regions; - for (; r->code != meshtastic_Config_LoRaConfig_RegionCode_UNSET && r->code != loraConfig.region; r++) + for (; r->code != meshtastic_Config_LoRaConfig_RegionCode_UNSET && r->code != code; r++) ; + return r; +} - const bool regionWideLora = r->wideLora; - - float bwKHz = 0; - uint8_t sf = 0; - uint8_t cr = 0; - modemPresetToParams(loraConfig.modem_preset, regionWideLora, bwKHz, sf, cr); - - // If selected preset requests a bandwidth larger than the region span, fall back to LONG_FAST. - if (r->code != meshtastic_Config_LoRaConfig_RegionCode_UNSET && (r->freqEnd - r->freqStart) < (bwKHz / 1000.0f)) { - loraConfig.modem_preset = meshtastic_Config_LoRaConfig_ModemPreset_LONG_FAST; - modemPresetToParams(loraConfig.modem_preset, regionWideLora, bwKHz, sf, cr); +/** + * Get duty cycle for current region. EU_866: 10% for routers, 2.5% for mobile. + */ +float getEffectiveDutyCycle() +{ + if (myRegion->code == meshtastic_Config_LoRaConfig_RegionCode_EU_866) { + if (config.device.role == meshtastic_Config_DeviceConfig_Role_ROUTER || + config.device.role == meshtastic_Config_DeviceConfig_Role_ROUTER_LATE) { + return 10.0f; + } else { + return 2.5f; + } } - - loraConfig.bandwidth = bwKHzToCode(bwKHz); - loraConfig.spread_factor = sf; + // For all other regions, return the standard duty cycle + return myRegion->dutyCycle; } /** @@ -785,43 +824,63 @@ uint32_t RadioInterface::getChannelNum() return savedChannelNum; } -/** - * Pull our channel settings etc... from protobufs to the dumb interface settings - */ -void RadioInterface::applyModemConfig() +struct ModemConfig { + float bw; + uint8_t sf; + uint8_t cr; +}; + +bool RadioInterface::validateModemConfig(meshtastic_Config_LoRaConfig &loraConfig) { - // Set up default configuration - // No Sync Words in LORA mode - meshtastic_Config_LoRaConfig &loraConfig = config.lora; - bool validConfig = false; // We need to check for a valid configuration - while (!validConfig) { - if (loraConfig.use_preset) { - modemPresetToParams(loraConfig.modem_preset, myRegion->wideLora, bw, sf, cr); - if (loraConfig.coding_rate >= 5 && loraConfig.coding_rate <= 8 && loraConfig.coding_rate != cr) { - cr = loraConfig.coding_rate; - LOG_INFO("Using custom Coding Rate %u", cr); + bool validConfig = true; + char err_string[160]; + float bw; + uint8_t sf; + uint8_t cr; + + const RegionInfo *newRegion = getRegion(loraConfig.region); + if (!newRegion) { // copilot said I had to check for null pointer + LOG_ERROR("Invalid region code %d", loraConfig.region); + return false; + } + + if (newRegion->licensedOnly && !devicestate.owner.is_licensed) { + snprintf(err_string, sizeof(err_string), "Selected region %s is not permitted without licensed mode activated", + newRegion->name); + + LOG_ERROR("%s", err_string); + RECORD_CRITICALERROR(meshtastic_CriticalErrorCode_INVALID_RADIO_SETTING); + + meshtastic_ClientNotification *cn = clientNotificationPool.allocZeroed(); + cn->level = meshtastic_LogRecord_Level_ERROR; + snprintf(cn->message, sizeof(cn->message), "%s", err_string); + service->sendClientNotification(cn); + return false; + LOG_WARN("Region code %s not permitted without license, reverting", newRegion->name); + return false; + } + + modemPresetToParams(loraConfig.modem_preset, newRegion->wideLora, bw, sf, cr); + + // early check - if we use preset, make sure it's on available preset list + if (loraConfig.use_preset) { + bool preset_valid = false; + + for (size_t i = 0; i < sizeof(newRegion->availablePresets); + i++) { // copilot says int should be size_t or auto : preset ??? + if (loraConfig.modem_preset == newRegion->availablePresets[i]) { + preset_valid = true; + break; } - } else { - sf = loraConfig.spread_factor; - cr = loraConfig.coding_rate; - bw = bwCodeToKHz(loraConfig.bandwidth); } - if ((myRegion->freqEnd - myRegion->freqStart) < bw / 1000) { - const float regionSpanKHz = (myRegion->freqEnd - myRegion->freqStart) * 1000.0f; - const float requestedBwKHz = bw; - const bool isWideRequest = requestedBwKHz >= 499.5f; // treat as 500 kHz preset + if (!preset_valid) { const char *presetName = DisplayFormatters::getModemPresetDisplayName(loraConfig.modem_preset, false, loraConfig.use_preset); - char err_string[160]; - if (isWideRequest) { - snprintf(err_string, sizeof(err_string), "%s region too narrow for 500kHz preset (%s). Falling back to LongFast.", - myRegion->name, presetName); - } else { - snprintf(err_string, sizeof(err_string), "%s region span %.0fkHz < requested %.0fkHz. Falling back to LongFast.", - myRegion->name, regionSpanKHz, requestedBwKHz); - } + snprintf(err_string, sizeof(err_string), "Selected preset %s is not on a list of available presets for region %s", + presetName, newRegion->name); + LOG_ERROR("%s", err_string); RECORD_CRITICALERROR(meshtastic_CriticalErrorCode_INVALID_RADIO_SETTING); @@ -829,12 +888,76 @@ void RadioInterface::applyModemConfig() cn->level = meshtastic_LogRecord_Level_ERROR; snprintf(cn->message, sizeof(cn->message), "%s", err_string); service->sendClientNotification(cn); + return false; + } else { + bw = loraConfig.bandwidth; + } + } // end if use_preset + + // this is probably wrong (?) as you can still select last channel in a band, set + // wide bandwidth and transmit outside the band and the check will not catch it // phaseloop + // this only makes sense if you happen to be in the center of the region band + if ((newRegion->freqEnd - newRegion->freqStart) < bw / 1000) { + const float regionSpanKHz = (newRegion->freqEnd - newRegion->freqStart) * 1000.0f; + const float requestedBwKHz = bw; + const bool isWideRequest = requestedBwKHz >= 499.5f; // treat as 500 kHz preset + const char *presetName = + DisplayFormatters::getModemPresetDisplayName(loraConfig.modem_preset, false, loraConfig.use_preset); + const char *defaultPresetName = DisplayFormatters::getModemPresetDisplayName(newRegion->defaultPreset, false, true); + + // actual falling back is done in applyModemSettings() + if (isWideRequest) { + snprintf(err_string, sizeof(err_string), "%s region too narrow for 500kHz preset (%s). Falling back to %s.", + newRegion->name, presetName, defaultPresetName); + } else { + snprintf(err_string, sizeof(err_string), "%s region span %.0fkHz < requested %.0fkHz. Falling back to %s.", + newRegion->name, regionSpanKHz, requestedBwKHz, defaultPresetName); + } + LOG_ERROR("%s", err_string); + RECORD_CRITICALERROR(meshtastic_CriticalErrorCode_INVALID_RADIO_SETTING); + + meshtastic_ClientNotification *cn = clientNotificationPool.allocZeroed(); + cn->level = meshtastic_LogRecord_Level_ERROR; + snprintf(cn->message, sizeof(cn->message), "%s", err_string); + service->sendClientNotification(cn); + + // Set to default modem preset + loraConfig.use_preset = true; + loraConfig.modem_preset = newRegion->defaultPreset; + } - // Set to default modem preset - loraConfig.use_preset = true; - loraConfig.modem_preset = meshtastic_Config_LoRaConfig_ModemPreset_LONG_FAST; + return validConfig; +} + +/** + * Pull our channel settings etc... from protobufs to the dumb interface settings + */ +void RadioInterface::applyModemConfig() +{ + // Set up default configuration + // No Sync Words in LORA mode + meshtastic_Config_LoRaConfig &loraConfig = config.lora; + const RegionInfo *newRegion = getRegion(loraConfig.region); + + if (loraConfig.use_preset) { + if (!validateModemConfig(loraConfig)) { + loraConfig.modem_preset = newRegion->defaultPreset; + } + uint8_t newcr; + modemPresetToParams(loraConfig.modem_preset, newRegion->wideLora, 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) { + + LOG_INFO("Default Coding Rate is higher than custom setting, using %u", cr); + } + // If the custom CR is higher than the preset, use it + else if (loraConfig.coding_rate >= 5 && loraConfig.coding_rate <= 8 && loraConfig.coding_rate > newcr) { + cr = loraConfig.coding_rate; + LOG_INFO("Using custom Coding Rate %u", cr); } else { - validConfig = true; + sf = loraConfig.spread_factor; + cr = loraConfig.coding_rate; + bw = bwCodeToKHz(loraConfig.bandwidth); } } @@ -851,8 +974,11 @@ void RadioInterface::applyModemConfig() // Set final tx_power back onto config loraConfig.tx_power = (int8_t)power; // cppcheck-suppress assignmentAddressToInteger - // Calculate the number of channels - uint32_t numChannels = floor((myRegion->freqEnd - myRegion->freqStart) / (myRegion->spacing + (bw / 1000))); + // Calculate number of channels: + // spacing = gap between channels (0 for continuous spectrum) and at the beginning of the band + // padding = gap at the beginning and end of the channel (0 for no padding) + float channelSpacing = myRegion->spacing + (myRegion->padding * 2) + (bw / 1000); // in MHz + uint32_t numChannels = round((myRegion->freqEnd - myRegion->freqStart + myRegion->spacing) / channelSpacing); // If user has manually specified a channel num, then use that, otherwise generate one by hashing the name const char *channelName = channels.getName(channels.getPrimaryIndex()); @@ -860,15 +986,22 @@ void RadioInterface::applyModemConfig() uint32_t channel_num = (loraConfig.channel_num ? loraConfig.channel_num - 1 : hash(channelName)) % numChannels; // Check if we use the default frequency slot - RadioInterface::uses_default_frequency_slot = - channel_num == - hash(DisplayFormatters::getModemPresetDisplayName(config.lora.modem_preset, false, config.lora.use_preset)) % numChannels; - - // Old frequency selection formula - // float freq = myRegion->freqStart + ((((myRegion->freqEnd - myRegion->freqStart) / numChannels) / 2) * channel_num); + if (myRegion->overrideSlot == 0) { + RadioInterface::uses_default_frequency_slot = true; + channel_num = + hash(DisplayFormatters::getModemPresetDisplayName(config.lora.modem_preset, false, config.lora.use_preset)) % + numChannels; + } + // If we have an override slot, use it + // Note: overrideSlot is the same, regardless of which preset we use, so it should only be used where presets are limited. + else { + RadioInterface::uses_default_frequency_slot = false; + channel_num = myRegion->overrideSlot - 1; + } - // New frequency selection formula - float freq = myRegion->freqStart + (bw / 2000) + (channel_num * (bw / 1000)); + // Calculate frequency: freqStart is band edge, add half bandwidth (plus optional padding) to get middle of first channel + // subsequent channels are spaced by channelSpacing + float freq = myRegion->freqStart + (bw / 2000) + myRegion->padding + (channel_num * channelSpacing); // in MHz // override if we have a verbatim frequency if (loraConfig.override_frequency) { @@ -888,10 +1021,13 @@ void RadioInterface::applyModemConfig() LOG_INFO("myRegion->freqStart -> myRegion->freqEnd: %f -> %f (%f MHz)", myRegion->freqStart, myRegion->freqEnd, myRegion->freqEnd - myRegion->freqStart); LOG_INFO("numChannels: %d x %.3fkHz", numChannels, bw); + if (myRegion->overrideSlot != 0) { + LOG_INFO("Using region override slot: %d", myRegion->overrideSlot); + } LOG_INFO("channel_num: %d", channel_num + 1); LOG_INFO("frequency: %f", getFreq()); LOG_INFO("Slot time: %u msec, preamble time: %u msec", slotTimeMsec, preambleTimeMsec); -} +} // end of applyModemConfig /** Slottime is the time to detect a transmission has started, consisting of: - CAD duration; @@ -932,7 +1068,7 @@ void RadioInterface::limitPower(int8_t loraMaxPower) size_t num_pa_points = portduino_config.num_pa_points; const uint16_t *tx_gain = portduino_config.tx_gain_lora; #else - size_t num_pa_points = NUM_PA_POINTS; + int num_pa_points = NUM_PA_POINTS; const uint16_t tx_gain[NUM_PA_POINTS] = {TX_GAIN_LORA}; #endif @@ -999,4 +1135,4 @@ size_t RadioInterface::beginSending(meshtastic_MeshPacket *p) sendingPacket = p; return p->encrypted.size + sizeof(PacketHeader); -} +} \ No newline at end of file diff --git a/src/mesh/RadioInterface.h b/src/mesh/RadioInterface.h index cb092bc6de..1c45f499f9 100644 --- a/src/mesh/RadioInterface.h +++ b/src/mesh/RadioInterface.h @@ -229,6 +229,9 @@ class RadioInterface // Whether we use the default frequency slot given our LoRa config (region and modem preset) static bool uses_default_frequency_slot; + // Check if a candidate radio configuration is valid. + static bool validateModemConfig(meshtastic_Config_LoRaConfig &loraConfig); + protected: int8_t power = 17; // Set by applyModemConfig() diff --git a/src/mesh/Router.cpp b/src/mesh/Router.cpp index 32544a0515..506c3a9f29 100644 --- a/src/mesh/Router.cpp +++ b/src/mesh/Router.cpp @@ -299,10 +299,11 @@ ErrorCode Router::send(meshtastic_MeshPacket *p) } // should have already been handled by sendLocal // Abort sending if we are violating the duty cycle - if (!config.lora.override_duty_cycle && myRegion->dutyCycle < 100) { + float effectiveDutyCycle = getEffectiveDutyCycle(); + if (!config.lora.override_duty_cycle && effectiveDutyCycle < 100) { float hourlyTxPercent = airTime->utilizationTXPercent(); - if (hourlyTxPercent > myRegion->dutyCycle) { - uint8_t silentMinutes = airTime->getSilentMinutes(hourlyTxPercent, myRegion->dutyCycle); + if (hourlyTxPercent > effectiveDutyCycle) { + uint8_t silentMinutes = airTime->getSilentMinutes(hourlyTxPercent, effectiveDutyCycle); LOG_WARN("Duty cycle limit exceeded. Aborting send for now, you can send again in %d mins", silentMinutes); diff --git a/src/mesh/generated/meshtastic/config.pb.h b/src/mesh/generated/meshtastic/config.pb.h index c669de6a86..4252f175d4 100644 --- a/src/mesh/generated/meshtastic/config.pb.h +++ b/src/mesh/generated/meshtastic/config.pb.h @@ -1053,4 +1053,4 @@ extern const pb_msgdesc_t meshtastic_Config_SessionkeyConfig_msg; } /* extern "C" */ #endif -#endif +#endif \ No newline at end of file diff --git a/src/modules/AdminModule.cpp b/src/modules/AdminModule.cpp index 419d2b773f..0d424b17eb 100644 --- a/src/modules/AdminModule.cpp +++ b/src/modules/AdminModule.cpp @@ -772,6 +772,10 @@ void AdminModule::handleSetConfig(const meshtastic_Config &c) LOG_WARN("Invalid bandwidth %d, setting to default", originalBandwidth); } + if (!RadioInterface::validateModemConfig(validatedLora)) { + return; + } + // If no lora radio parameters change, don't need to reboot if (oldLoraConfig.use_preset == validatedLora.use_preset && oldLoraConfig.region == validatedLora.region && oldLoraConfig.modem_preset == validatedLora.modem_preset && oldLoraConfig.bandwidth == validatedLora.bandwidth && @@ -824,7 +828,7 @@ void AdminModule::handleSetConfig(const meshtastic_Config &c) #endif config.lora.tx_enabled = true; initRegion(); - if (myRegion->dutyCycle < 100) { + if (getEffectiveDutyCycle() < 100) { config.lora.ignore_mqtt = true; // Ignore MQTT by default if region has a duty cycle limit } // Compare the entire string, we are sure of the length as a topic has never been set diff --git a/src/modules/NeighborInfoModule.cpp b/src/modules/NeighborInfoModule.cpp index 2cd8ec5ed9..7401009d65 100644 --- a/src/modules/NeighborInfoModule.cpp +++ b/src/modules/NeighborInfoModule.cpp @@ -1,5 +1,6 @@ #include "NeighborInfoModule.h" #include "Default.h" +#include "MeshRadio.h" // needed for region specific broadcast throttling #include "MeshService.h" #include "NodeDB.h" #include "RTC.h" @@ -126,7 +127,7 @@ int32_t NeighborInfoModule::runOnce() { if (moduleConfig.neighbor_info.transmit_over_lora && (!channels.isDefaultChannel(channels.getPrimaryIndex()) || !RadioInterface::uses_default_frequency_slot) && - airTime->isTxAllowedChannelUtil(true) && airTime->isTxAllowedAirUtil()) { + airTime->isTxAllowedChannelUtil(true) && airTime->isTxAllowedAirUtil() && !myRegion->telemetryThrottle == 0) { sendNeighborInfo(NODENUM_BROADCAST, false); } else { sendNeighborInfo(NODENUM_BROADCAST_NO_LORA, false); diff --git a/src/modules/PositionModule.cpp b/src/modules/PositionModule.cpp index f7116e7012..1315ccb370 100644 --- a/src/modules/PositionModule.cpp +++ b/src/modules/PositionModule.cpp @@ -2,6 +2,7 @@ #include "PositionModule.h" #include "Default.h" #include "GPS.h" +#include "MeshRadio.h" // needed for region specific position throttling #include "MeshService.h" #include "NodeDB.h" #include "RTC.h" @@ -414,6 +415,12 @@ int32_t PositionModule::runOnce() if (node == nullptr) return RUNONCE_INTERVAL; + // Routine broadcast of position isn't permitted if the region is throttled. + if (!myRegion->positionThrottle == 0) { + LOG_DEBUG("Position broadcast throttled by region"); + return RUNONCE_INTERVAL; + } + // We limit our GPS broadcasts to a max rate uint32_t now = millis(); uint32_t intervalMs = Default::getConfiguredOrDefaultMsScaled(config.position.position_broadcast_secs, diff --git a/src/modules/Telemetry/AirQualityTelemetry.cpp b/src/modules/Telemetry/AirQualityTelemetry.cpp index d7127bb016..3f2e503d4c 100644 --- a/src/modules/Telemetry/AirQualityTelemetry.cpp +++ b/src/modules/Telemetry/AirQualityTelemetry.cpp @@ -6,6 +6,7 @@ #include "../mesh/generated/meshtastic/telemetry.pb.h" #include "AirQualityTelemetry.h" #include "Default.h" +#include "MeshRadio.h" // needed for region specific broadcast throttling #include "MeshService.h" #include "NodeDB.h" #include "PowerFSM.h" @@ -130,7 +131,7 @@ int32_t AirQualityTelemetryModule::runOnce() moduleConfig.telemetry.air_quality_interval, default_telemetry_broadcast_interval_secs, numOnlineNodes))) && airTime->isTxAllowedChannelUtil(config.device.role != meshtastic_Config_DeviceConfig_Role_SENSOR) && - airTime->isTxAllowedAirUtil()) { + airTime->isTxAllowedAirUtil() && !myRegion->telemetryThrottle == 0) { sendTelemetry(); lastSentToMesh = millis(); } else if (((lastSentToPhone == 0) || !Throttle::isWithinTimespanMs(lastSentToPhone, sendToPhoneIntervalMs)) && diff --git a/src/modules/Telemetry/EnvironmentTelemetry.cpp b/src/modules/Telemetry/EnvironmentTelemetry.cpp index 140c2c17e7..8af65cd1b9 100644 --- a/src/modules/Telemetry/EnvironmentTelemetry.cpp +++ b/src/modules/Telemetry/EnvironmentTelemetry.cpp @@ -5,6 +5,7 @@ #include "../mesh/generated/meshtastic/telemetry.pb.h" #include "Default.h" #include "EnvironmentTelemetry.h" +#include "MeshRadio.h" // needed for region specific broadcast throttling #include "MeshService.h" #include "NodeDB.h" #include "PowerFSM.h" @@ -322,7 +323,7 @@ int32_t EnvironmentTelemetryModule::runOnce() moduleConfig.telemetry.environment_update_interval, default_telemetry_broadcast_interval_secs, numOnlineNodes))) && airTime->isTxAllowedChannelUtil(config.device.role != meshtastic_Config_DeviceConfig_Role_SENSOR) && - airTime->isTxAllowedAirUtil()) { + airTime->isTxAllowedAirUtil() && !myRegion->telemetryThrottle == 0) { sendTelemetry(); lastSentToMesh = millis(); } else if (((lastSentToPhone == 0) || !Throttle::isWithinTimespanMs(lastSentToPhone, sendToPhoneIntervalMs)) && diff --git a/src/modules/Telemetry/HealthTelemetry.cpp b/src/modules/Telemetry/HealthTelemetry.cpp index bb35550624..9342137833 100644 --- a/src/modules/Telemetry/HealthTelemetry.cpp +++ b/src/modules/Telemetry/HealthTelemetry.cpp @@ -5,6 +5,7 @@ #include "../mesh/generated/meshtastic/telemetry.pb.h" #include "Default.h" #include "HealthTelemetry.h" +#include "MeshRadio.h" // needed for region specific broadcast throttling #include "MeshService.h" #include "NodeDB.h" #include "PowerFSM.h" @@ -74,7 +75,7 @@ int32_t HealthTelemetryModule::runOnce() moduleConfig.telemetry.health_update_interval, default_telemetry_broadcast_interval_secs, numOnlineNodes))) && airTime->isTxAllowedChannelUtil(config.device.role != meshtastic_Config_DeviceConfig_Role_SENSOR) && - airTime->isTxAllowedAirUtil()) { + airTime->isTxAllowedAirUtil() && !myRegion->telemetryThrottle == 0) { sendTelemetry(); lastSentToMesh = millis(); } else if (((lastSentToPhone == 0) || !Throttle::isWithinTimespanMs(lastSentToPhone, sendToPhoneIntervalMs)) && diff --git a/src/modules/Telemetry/PowerTelemetry.cpp b/src/modules/Telemetry/PowerTelemetry.cpp index 9047c7cd41..3d5f262863 100644 --- a/src/modules/Telemetry/PowerTelemetry.cpp +++ b/src/modules/Telemetry/PowerTelemetry.cpp @@ -4,6 +4,7 @@ #include "../mesh/generated/meshtastic/telemetry.pb.h" #include "Default.h" +#include "MeshRadio.h" // needed for region specific broadcast throttling #include "MeshService.h" #include "NodeDB.h" #include "PowerFSM.h" @@ -89,7 +90,7 @@ int32_t PowerTelemetryModule::runOnce() return disable(); if (((lastSentToMesh == 0) || !Throttle::isWithinTimespanMs(lastSentToMesh, sendToMeshIntervalMs)) && - airTime->isTxAllowedAirUtil()) { + airTime->isTxAllowedAirUtil() && !myRegion->telemetryThrottle == 0) { sendTelemetry(); lastSentToMesh = millis(); } else if (((lastSentToPhone == 0) || !Throttle::isWithinTimespanMs(lastSentToPhone, sendToPhoneIntervalMs)) &&