Skip to content

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

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

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
Elektr0Vodka added a commit that referenced this pull request Sep 11, 2026
- companion RadioPrefs: implement getDutyCycleAuto/setDutyCycleAuto (new
  pure-virtuals added to CommonRadioPrefs so 'set af' clears auto mode);
  without these its RadioPrefs stayed abstract and test_companion_node_prefs
  failed to compile.
- test_duty_cycle_limits: add the gtest main() every native test in this
  repo provides itself (PlatformIO does not auto-inject one here), fixing
  the WinMain link error.

Native: 112/112 pass.
Elektr0Vodka added a commit that referenced this pull request Sep 11, 2026
simple_secure_chat persisted NodePrefs as a raw positional byte struct with no
version or magic, so a new field could only inherit the old file's padding byte
(PR #5's dutycycle_auto read back as 0 = auto off on pre-existing files).

Migrate it to ConfigSerializer like the other node types: NodePrefs is now a
ConfigSerializer with def()'d fields written as self-describing text to
/node_prefs.json, where a missing field falls back to its struct default. A
legacy /node_prefs binary is read once via a plain LegacyNodePrefs struct and
re-saved in the new format; dutycycle_auto defaults to 1 (auto) on migration
rather than inheriting padding. memset on the now-polymorphic _prefs removed.

Build: Heltec_v3_terminal_chat SUCCESS.
@Elektr0Vodka

Copy link
Copy Markdown

Merged into dmc-dev via the v1.17.1.01 devsync (dmc-dev @ 1b1a25f). Because upstream/dev moved the radio-pref CLI handlers into CommonRadioPrefs, the dutycycle auto / set af logic was re-homed there (accessor-based) rather than in CommonCLI.cpp; DutyCycleLimits.{h,cpp} and test_duty_cycle_limits are in as-is. Fixes #4. Closing as merged-via-consolidation.

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.

2 participants