Skip to content

Duty cycle afleiden uit de ETSI sub-band van de ingestelde frequentie 馃馃 - #5

Open
efiten wants to merge 2 commits into
Dutch-MeshCore:dmc-devfrom
efiten:feat/dutycycle-from-subband
Open

Duty cycle afleiden uit de ETSI sub-band van de ingestelde frequentie 馃馃#5
efiten wants to merge 2 commits into
Dutch-MeshCore:dmc-devfrom
efiten:feat/dutycycle-from-subband

Conversation

@efiten

@efiten efiten commented Sep 4, 2026

Copy link
Copy Markdown

Fixes #4.

Probleem

De uitgeleverde default is airtime_factor = 1.0, wat get dutycycle als 50% rapporteert. De default frequentie is 869.618 MHz (platformio.ini:29), en de sub-band 869.4 tot 869.65 is door ETSI EN 300 220-2 begrensd op 10%. De default staat dus vijf keer hoger dan wat op zijn eigen default frequentie is toegestaan.

Een enkele build-constante kan die limiet niet uitdrukken, want hij verschilt per sub-band: 868.5 mag 1%, 869.0 mag 0.1%, en op 910 of 915 MHz geldt er geen ETSI-limiet.

Wijziging

Nieuw src/helpers/DutyCycleLimits.h / .cpp. getMaxDutyCyclePercent(freq) zoekt de sub-band op, dutyCycleToAirtimeFactor(percent) doet de conversie die set dutycycle al gebruikte, en getEffectiveAirtimeFactor() combineert beide met de auto-vlag.

sub-band (MHz) limiet
863.0 tot 865.0 0.1%
865.0 tot 868.0 1%
868.0 tot 868.6 1%
868.7 tot 869.2 0.1%
869.4 tot 869.65 10%
869.7 tot 870.0 1%
de gaten ertussen 0.1%

De tabel dekt alleen 863 tot 870 MHz. Elke andere frequentie geeft DUTY_CYCLE_UNLIMITED, dus US- en ANZ-builds houden het gedrag dat ze hadden. Een frequentie precies op een grens neemt de lagere van de twee sub-banden, een gat binnen de band neemt de strengste limiet van de band.

Dit dekt ook de land-presets binnen de EU. Een preset die alleen in SF verschilt, zoals NL op SF7, zit op dezelfde sub-band als de algemene EU-preset en krijgt dezelfde limiet. Alleen de frequentie raakt de regelgeving.

Nieuwe pref dutycycle_auto, default 1, geserialiseerd als dc_auto. Zolang die aan staat geven de vijf getAirtimeBudgetFactor() overrides de afgeleide factor terug in plaats van de opgeslagen. Dispatcher::updateTxBudget() roept die functie bij elke pas opnieuw aan (src/Dispatcher.cpp:42), dus de limiet volgt set freq en set radio zonder herstart en zonder wijziging in Dispatcher.

set dutycycle <n>, set af en de companion CMD_SET_TUNING_PARAMS zetten de vlag uit, zodat een expliciete waarde blijft staan tot set dutycycle auto hem terugzet. get dutycycle, get af en CMD_GET_TUNING_PARAMS rapporteren de factor die daadwerkelijk geldt.

Gedragsverandering

Bestaande nodes migreren naar auto. Noch de legacy /com_prefs layout, noch een bestaande prefs.json bevat dc_auto, dus de struct-default geldt. Een node op 869.618 die zijn duty cycle nooit heeft aangepast, zakt van 50% naar 10%.

simple_secure_chat is de uitzondering. Dat voorbeeld schrijft zijn prefs-struct als ruwe bytes en dutycycle_auto hergebruikt een van de bestaande padding-bytes, die uit een oudere file als 0 terugkomt. Die nodes houden hun opgeslagen airtime factor tot set af of een factory reset.

Buiten scope

applyTempRadioParams (src/helpers/CommonCLI.cpp:251) zet een tijdelijke frequentie zonder _prefs.freq aan te raken, dus tijdens tempradio blijft de limiet die van de opgeslagen frequentie. Dat is een aparte wijziging.

De bestaande clamp constrain(airtime_factor, 0, 9.0f) in loadPrefsInt() en de bovengrens van 100 in set dutycycle zijn ongemoeid gelaten. Daardoor kan auto wel op 0.1% uitkomen terwijl set dutycycle niet lager dan 1 accepteert.

Testen

test/test_duty_cycle_limits/ bevat 8 gtest cases over de tabel, de grenzen, de gaten, de conversie en de auto-vlag, en [env:native] compileert nu DutyCycleLimits.cpp mee.

pio test -e native kon ik hier niet draaien, op deze machine staat geen g++. In plaats daarvan zijn dezelfde 29 assertions als los C++ programma gecompileerd en uitgevoerd met g++ 14.2 op een Debian 12 host: alle 29 slagen. De gtest-suite zelf is dus nog niet gedraaid, alleen de logica die hij controleert.

Compile-coverage voor alle vijf de node-types, plus beide takken van de gewijzigde include in de repeater-header:

Heltec_v3_terminal_chat                 SUCCESS
Heltec_v3_sensor                        SUCCESS
RAK_4631_repeater_observer_serial       SUCCESS
RAK_4631_repeater_bridge_rs232_serial1  SUCCESS
RAK_4631_room_server                    SUCCESS
RAK_4631_companion_radio_ble            SUCCESS

Niet getest op hardware.

馃 Generated with Claude Code

efiten and others added 2 commits September 4, 2026 14:14
The shipped default was airtime_factor 1.0, which get dutycycle reports as
50%. The default frequency is 869.618 MHz (platformio.ini:29), and the
869.4 to 869.65 sub-band is capped at 10% by ETSI EN 300 220-2. The default
therefore allowed five times what its own default frequency permits.

A single constant cannot express the limit, because it differs per sub-band:
868.5 is 1%, 869.0 is 0.1%, and 910 or 915 MHz carry no ETSI limit at all.

New src/helpers/DutyCycleLimits.{h,cpp} maps a frequency to its sub-band
limit, covering 863 to 870 MHz only. Every other frequency reports
DUTY_CYCLE_UNLIMITED, so US and ANZ builds keep the behaviour they had.
A frequency on a boundary takes the lower of the two sub-bands, and a gap
inside the band takes the tightest limit of the band.

New pref dutycycle_auto, default 1, serialized as dc_auto. While it is set,
the five getAirtimeBudgetFactor() overrides return the derived factor rather
than the stored one. Dispatcher::updateTxBudget() re-evaluates that call on
every pass (src/Dispatcher.cpp:42), so the limit follows set freq and
set radio without a restart and without touching Dispatcher.

set dutycycle <n>, set af and the companion CMD_SET_TUNING_PARAMS clear the
flag, so an explicit value stays in force until set dutycycle auto restores
it. get dutycycle, get af and CMD_GET_TUNING_PARAMS report the factor that
is actually in force.

Existing nodes migrate to auto: neither the legacy /com_prefs layout nor an
existing prefs.json carries dc_auto, so the struct default applies. A node
on 869.618 that never had its duty cycle changed drops from 50% to 10%.
simple_secure_chat is the exception: it writes its prefs struct as raw bytes
and dutycycle_auto reuses one of its unused padding bytes, which reads back
as 0 from a file written before this change, so those nodes keep their
stored airtime factor until set af or a factory reset.

Not addressed here: applyTempRadioParams (src/helpers/CommonCLI.cpp:251)
sets a temporary frequency without touching _prefs.freq, so during tempradio
the limit stays that of the stored frequency.

Refs Dutch-MeshCore#4

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YJ6qztG9Mcx2cQ2rdJ8LCq
Records set dutycycle auto, the sub-band table it uses, the boundary and gap
rules, and that get af now reports the factor in force rather than the
stored value.

Refs Dutch-MeshCore#4

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YJ6qztG9Mcx2cQ2rdJ8LCq
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant