Add a new frequency range for EU SRD area with adaptive DC

This commit is contained in:
Thomas Göttgens
2025-11-19 12:38:34 +01:00
parent f9433a31d1
commit cd1cc032ed
7 changed files with 68 additions and 16 deletions

View File

@@ -34,6 +34,9 @@ const char *DisplayFormatters::getModemPresetDisplayName(meshtastic_Config_LoRaC
case meshtastic_Config_LoRaConfig_ModemPreset_LONG_MODERATE: case meshtastic_Config_LoRaConfig_ModemPreset_LONG_MODERATE:
return useShortName ? "LongM" : "LongMod"; return useShortName ? "LongM" : "LongMod";
break; break;
case meshtastic_Config_LoRaConfig_ModemPreset_LITE_FAST:
return useShortName ? "LiteF" : "LiteFast";
break;
default: default:
return useShortName ? "Custom" : "Invalid"; return useShortName ? "Custom" : "Invalid";
break; break;

View File

@@ -133,11 +133,12 @@ bool AirTime::isTxAllowedChannelUtil(bool polite)
bool AirTime::isTxAllowedAirUtil() bool AirTime::isTxAllowedAirUtil()
{ {
if (!config.lora.override_duty_cycle && myRegion->dutyCycle < 100) { float effectiveDutyCycle = getEffectiveDutyCycle();
if (utilizationTXPercent() < myRegion->dutyCycle * polite_duty_cycle_percent / 100) { if (!config.lora.override_duty_cycle && effectiveDutyCycle < 100) {
if (utilizationTXPercent() < effectiveDutyCycle * polite_duty_cycle_percent / 100) {
return true; return true;
} else { } 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; return false;
} }
} }

View File

@@ -141,7 +141,7 @@ void menuHandler::LoraRegionPicker(uint32_t duration)
} }
config.lora.tx_enabled = true; config.lora.tx_enabled = true;
initRegion(); initRegion();
if (myRegion->dutyCycle < 100) { if (getEffectiveDutyCycle() < 100) {
config.lora.ignore_mqtt = true; // Ignore MQTT by default if region has a duty cycle limit config.lora.ignore_mqtt = true; // Ignore MQTT by default if region has a duty cycle limit
} }

View File

@@ -22,4 +22,11 @@ struct RegionInfo {
extern const RegionInfo regions[]; extern const RegionInfo regions[];
extern const RegionInfo *myRegion; extern const RegionInfo *myRegion;
extern void initRegion(); 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();

View File

@@ -187,6 +187,13 @@ const RegionInfo regions[] = {
*/ */
RDEF(BR_902, 902.0f, 907.5f, 100, 0, 30, true, false, false), RDEF(BR_902, 902.0f, 907.5f, 100, 0, 30, true, false, false),
/*
EU 866MHz RFID band (ETSI EN 302 208): 4 channels at 865.7/866.3/866.9/867.5 MHz
475 kHz gap between channels, 27 dBm, duty cycle 2.5% (mobile) or 10% (fixed)
https://www.etsi.org/deliver/etsi_en/302200_302299/302208/03.04.01_60/en_302208v030401p.pdf
*/
RDEF(EU_866, 865.6375f, 867.5625f, 2.5, 0.475, 27, true, false, false),
/* /*
2.4 GHZ WLAN Band equivalent. Only for SX128x chips. 2.4 GHZ WLAN Band equivalent. Only for SX128x chips.
*/ */
@@ -219,6 +226,23 @@ void initRegion()
myRegion = r; myRegion = r;
} }
/**
* 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;
}
}
// For all other regions, return the standard duty cycle
return myRegion->dutyCycle;
}
/** /**
* ## LoRaWAN for North America * ## LoRaWAN for North America
@@ -518,6 +542,11 @@ void RadioInterface::applyModemConfig()
cr = 8; cr = 8;
sf = 12; sf = 12;
break; break;
case meshtastic_Config_LoRaConfig_ModemPreset_LITE_FAST:
bw = 125;
cr = 5;
sf = 9;
break;
} }
} else { } else {
sf = loraConfig.spread_factor; sf = loraConfig.spread_factor;
@@ -551,6 +580,19 @@ void RadioInterface::applyModemConfig()
// Set to default modem preset // Set to default modem preset
loraConfig.use_preset = true; loraConfig.use_preset = true;
loraConfig.modem_preset = meshtastic_Config_LoRaConfig_ModemPreset_LONG_FAST; loraConfig.modem_preset = meshtastic_Config_LoRaConfig_ModemPreset_LONG_FAST;
} else if (myRegion->code == meshtastic_Config_LoRaConfig_RegionCode_EU_866 && bw != 125) {
static const char *err_string = "EU_866 requires 125kHz bandwidth. Fall back to LiteFast preset";
LOG_ERROR(err_string);
RECORD_CRITICALERROR(meshtastic_CriticalErrorCode_INVALID_RADIO_SETTING);
meshtastic_ClientNotification *cn = clientNotificationPool.allocZeroed();
cn->level = meshtastic_LogRecord_Level_ERROR;
sprintf(cn->message, err_string);
service->sendClientNotification(cn);
// Set to LiteFast preset which is compliant
loraConfig.use_preset = true;
loraConfig.modem_preset = meshtastic_Config_LoRaConfig_ModemPreset_LITE_FAST;
} else { } else {
validConfig = true; validConfig = true;
} }
@@ -569,8 +611,9 @@ void RadioInterface::applyModemConfig()
// Set final tx_power back onto config // Set final tx_power back onto config
loraConfig.tx_power = (int8_t)power; // cppcheck-suppress assignmentAddressToInteger loraConfig.tx_power = (int8_t)power; // cppcheck-suppress assignmentAddressToInteger
// Calculate the number of channels // Calculate number of channels: spacing = gap between channels (0 for continuous spectrum)
uint32_t numChannels = floor((myRegion->freqEnd - myRegion->freqStart) / (myRegion->spacing + (bw / 1000))); float channelSpacing = myRegion->spacing + (bw / 1000);
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 // 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()); const char *channelName = channels.getName(channels.getPrimaryIndex());
@@ -582,11 +625,8 @@ void RadioInterface::applyModemConfig()
channel_num == channel_num ==
hash(DisplayFormatters::getModemPresetDisplayName(config.lora.modem_preset, false, config.lora.use_preset)) % numChannels; hash(DisplayFormatters::getModemPresetDisplayName(config.lora.modem_preset, false, config.lora.use_preset)) % numChannels;
// Old frequency selection formula // Calculate frequency: freqStart is band edge, add half bandwidth to get first channel center
// float freq = myRegion->freqStart + ((((myRegion->freqEnd - myRegion->freqStart) / numChannels) / 2) * channel_num); float freq = myRegion->freqStart + (bw / 2000) + (channel_num * channelSpacing);
// New frequency selection formula
float freq = myRegion->freqStart + (bw / 2000) + (channel_num * (bw / 1000));
// override if we have a verbatim frequency // override if we have a verbatim frequency
if (loraConfig.override_frequency) { if (loraConfig.override_frequency) {

View File

@@ -294,10 +294,11 @@ ErrorCode Router::send(meshtastic_MeshPacket *p)
} // should have already been handled by sendLocal } // should have already been handled by sendLocal
// Abort sending if we are violating the duty cycle // 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(); float hourlyTxPercent = airTime->utilizationTXPercent();
if (hourlyTxPercent > myRegion->dutyCycle) { if (hourlyTxPercent > effectiveDutyCycle) {
uint8_t silentMinutes = airTime->getSilentMinutes(hourlyTxPercent, myRegion->dutyCycle); 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); LOG_WARN("Duty cycle limit exceeded. Aborting send for now, you can send again in %d mins", silentMinutes);

View File

@@ -793,7 +793,7 @@ void AdminModule::handleSetConfig(const meshtastic_Config &c)
} }
config.lora.tx_enabled = true; config.lora.tx_enabled = true;
initRegion(); initRegion();
if (myRegion->dutyCycle < 100) { if (getEffectiveDutyCycle() < 100) {
config.lora.ignore_mqtt = true; // Ignore MQTT by default if region has a duty cycle limit 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 // Compare the entire string, we are sure of the length as a topic has never been set