Derive the duty cycle limit from the configured frequency - #3361
Open
efiten wants to merge 2 commits into
Open
Conversation
The shipped default of airtime_factor 1.0 reports as 50%, while ETSI EN 300 220-2 caps the 869.4 to 869.65 sub-band at 10%, and that sub-band is where the default LORA_FREQ of 869.618 lives. A build constant cannot express the limit, because it differs per sub-band and does not exist at all outside 863 to 870 MHz. New DutyCycleLimits looks the limit up from a frequency and converts it to the airtime budget factor Dispatcher works with. New dutycycle_auto pref, default on, makes the five getAirtimeBudgetFactor() overrides return that derived factor. Dispatcher::updateTxBudget() re-reads it on every pass, so the limit follows set freq and set radio without a reboot. set dutycycle <n>, set af and CMD_SET_TUNING_PARAMS clear the flag, so an explicit value stays in force until set dutycycle auto restores it. The matching getters report the factor that is actually applied. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EbLWXAAQGKA8AGgg5WnCcj
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EbLWXAAQGKA8AGgg5WnCcj
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Replaces #3351, which set a single 10% default for every build. As noted there, many regions have no duty cycle limit at all, so one fixed default is wrong outside the EU.
Problem
The shipped default is
airtime_factor = 1.0, whichget dutycyclereports as 50%. The default frequency is 869.618 MHz (platformio.ini:29), and ETSI EN 300 220-2 caps the 869.4 to 869.65 sub-band at 10%. The default is five times higher than what is allowed on its own default frequency.A build constant cannot express that limit. It differs per sub-band: 868.5 allows 1%, 869.0 allows 0.1%, and on 910 or 915 MHz there is no ETSI limit. The repo also builds one binary per board, with no
LORA_FREQoverride in anyvariants/*/platformio.ini, so there is no region-specific build to attach a default to. The region is picked at runtime through the client preset.Change
New
src/helpers/DutyCycleLimits.hand.cpp.getMaxDutyCyclePercent(freq)looks up the sub-band,dutyCycleToAirtimeFactor(percent)is the conversionset dutycyclealready used, andgetEffectiveAirtimeFactor()combines both with the auto flag.The table covers 863 to 870 MHz only. Every other frequency returns
DUTY_CYCLE_UNLIMITED, so the US and ANZ channel plans keep the behaviour they have now. A frequency exactly on a boundary takes the lower of the two sub-bands, a gap inside the band takes the tightest limit of the band.This covers the country presets within the EU as well. A preset that differs only in SF, such as NL on SF7, sits on the same sub-band as the general EU preset and gets the same limit. Only the frequency touches the regulation.
New pref
dutycycle_auto, default 1, serialised asdc_auto, added to bothNodePrefsclasses and exposed through two newCommonRadioPrefsvirtuals. While the flag is set, the fivegetAirtimeBudgetFactor()overrides return the derived factor instead of the stored one.Dispatcher::updateTxBudget()calls that on every pass (src/Dispatcher.cpp:42), so the limit followsset freqandset radiowithout a reboot and without any change toDispatcher.set dutycycle <n>,set afand the companionCMD_SET_TUNING_PARAMSclear the flag, so an explicit value stays in force untilset dutycycle autorestores it.get dutycycle,get afandCMD_GET_TUNING_PARAMSreport the factor that is actually in force.Behaviour change
Existing nodes migrate to auto. Neither the legacy
/com_prefslayout (src/helpers/CommonCLI.cpp:49) nor an existingprefs.jsoncarriesdc_auto, so the struct default applies. A node on 869.618 that never touched its duty cycle drops from 50% to 10%.On the 868.7 to 869.2 sub-band auto resolves to 0.1%, stricter than the 1% floor
set dutycycleaccepts. That is the ETSI limit for that sub-band, but it makes those nodes nearly silent.set dutycycle <n>overrides it.simple_secure_chatwrites its prefs struct as raw bytes with no version field.dutycycle_autois appended after the existingunused[3]instead of taking one of those bytes, so the struct grows from 64 to 72 bytes and a shorter legacy file leaves the new field at its constructor value of 1. Those nodes migrate to auto like the rest.Out of scope
applyTempRadioParams(src/helpers/CommonCLI.cpp:262) sets a temporary frequency without touching_prefs.freq, so duringtempradiothe limit stays that of the stored frequency. That is a separate change.The existing
constrain(airtime_factor, 0, 9.0f)inloadPrefsInt()and the upper bound of 100 inset dutycycleare left alone.Testing
pio test -e native, all 51 cases including the 6 new ones and no regression in the existing suites:Compile coverage for all five node types on both platforms:
The
simple_secure_chatstruct growth was checked separately on aarch64:sizeofgoes from 64 to 72 with the new field at offset 64, and copying a 64 byte legacy image over an initialised struct leavesdutycycle_autoat 1. That check was not run on the 32 bit ARM and Xtensa targets themselves.Not tested on hardware.
🤖 Generated with Claude Code
https://claude.ai/code/session_01EbLWXAAQGKA8AGgg5WnCcj