Skip to content

Extend radio capabilities model with bands, modes, and bandwidths - #6

Merged
emuehlstein merged 1 commit into
mainfrom
capabilities-model
Aug 7, 2026
Merged

Extend radio capabilities model with bands, modes, and bandwidths#6
emuehlstein merged 1 commit into
mainfrom
capabilities-model

Conversation

@emuehlstein

Copy link
Copy Markdown
Contributor

The capabilities model is currently three integers:

{ "id": "...", "name": "...",
  "limits": { "max_channels": N, "max_zones": N, "max_channels_per_zone": N } }

That answers will this profile fit but not will these channels work on this radio. A profile can select out-of-band or unsupported-mode channels and validate clean.

Motivating case

The Retevis MateTalk P4 is UHF-only (400–470 MHz), but nothing stopped a profile from putting MURS at 151/154 MHz on it. That had to be caught by hand while writing the shared ChiOff profiles — the validator would otherwise have emitted a codeplug with ten unusable channels.

A related gap: capabilities.json had no schema, so a typo in a limits key surfaced as a raw KeyError rather than a useful message.

Changes

Three optional capability fields:

  • bands[] — tunable ranges in MHz, with optional rx_only for receive-only spans. Checked against each selected channel RX and TX frequency. rx_only bands satisfy receive checks but never transmit checks.
  • modes[]FM / DMR. Checked against each chain mode type.
  • bandwidths_khz[] — supported analog bandwidths, checked when a chain declares one.

Plus schemas/capabilities-0.2.schema.json, validated at load.

Every check is skipped when its field is absent, so an incomplete capabilities file degrades to current behavior instead of producing false failures. Existing capabilities files remain valid — the new fields are additive and optional.

Both radios are populated. DM-32 band edges and scan-list count are cross-checked against qdmr, not guessed.

Prior art

  • qdmr models per-radio limits as a RadioLimits tree. RadioLimitFrequencies holds explicit MHz ranges per channel type, declared per radio — lib/dm32uv_limits.cc states 136–174 and 400–480 MHz for RX and TX separately, with RadioLimitTransmitFrequencies as a distinct subclass. Bandwidth is a RadioLimitEnum over narrow/wide. That RX/TX split is the direct ancestor of rx_only here.
  • NeonPlug (src/types/radioCapabilities.ts) carries bandLimits (vhfMin/vhfMax, optional uhfMin/uhfMax — absent on VHF-only radios), an analogOnly flag, and per-object maxima. Its DEFAULT_BAND_LIMITS fallback is a useful counterexample: defaulting to a plausible band silently accepts out-of-band channels on unknown radios, so this PR skips the check instead of assuming.
  • dmrconfig does band checks inline per radio model rather than as data, so there was no reusable shape to borrow.

Both mature tools treat band coverage as a first-class radio fact. This takes the same shape at the scope Codeplugger needs, leaving scan lists, contacts, and DMR identities out of the resolved model as the README already specifies.

Verification

  • Both ChiOff shared profiles validate unchanged (chioff_p4_shared 1 zone / 6 assignments, chioff_dm32_shared 2 zones / 16 assignments).
  • MURS on the P4 now fails: channel 'ChiO MURS 1' RX 151.82 MHz is outside the bands supported by Retevis MateTalk P4 (UHF (400.0-470.0 MHz))
  • DMR on an analog-only radio fails: channel 'HS W9CRS' uses mode DMR, which Fake Analog-Only does not support (FM)
  • Unknown capabilities key fails cleanly: limits: Additional properties are not allowed ('bogus_key' was unexpected)

Notes for review

  • Band bounds are inclusive on both ends.
  • Bandwidth comparison uses a small epsilon rather than float equality.
  • max_zones allows 0 to describe radios with no zone concept (NeonPlug models this with supportsZones: false); nothing enforces that yet, it is just expressible.
  • The P4 bandwidths_khz of [12.5, 25.0] reflects the manual Features page stating "Wideband/Narrowband Selectable". The specifications table in that PDF is scanned images with no extractable text, so the exact kHz figures are not machine-readable from the source — worth a second pair of eyes.

The capabilities model was three integers (max_channels, max_zones,
max_channels_per_zone). That answers 'will this profile fit' but not
'will these channels work on this radio', so a profile could select
out-of-band or unsupported-mode channels and validate clean.

Concretely: the MateTalk P4 is UHF-only (400-470 MHz), but nothing
stopped a profile putting MURS at 151/154 MHz on it. That had to be
caught by hand when writing the shared ChiOff profiles; the validator
would have emitted a codeplug with unusable channels.

Adds three optional capability fields:

- bands[]: tunable ranges in MHz, with optional rx_only for receive-only
  spans. Checked against each selected channel's RX and TX frequency.
- modes[]: FM / DMR support. Checked against each chain's mode type.
- bandwidths_khz[]: supported analog bandwidths, checked when a chain
  declares one.

Every check is skipped when its field is absent, so an incomplete
capabilities file degrades to current behavior rather than emitting
false failures. Existing files stay valid.

Also adds schemas/capabilities-0.2.schema.json and validates
capabilities.json against it at load. Previously a typo in a limits key
surfaced as a raw KeyError; it now reports the offending property.

Populates both radios. DM-32 band edges and scan-list count are
cross-checked against qdmr's DM32UVLimits (lib/dm32uv_limits.cc), which
declares 136-174 / 400-480 MHz and a narrow/wide bandwidth enum.

Prior art: qdmr models per-radio limits as a RadioLimits tree with
RadioLimitFrequencies holding explicit MHz ranges per channel type, and
NeonPlug's RadioCapabilities carries bandLimits plus analogOnly and
per-object maxima. Both treat band coverage as a first-class radio fact.
This change takes the same shape at the scope Codeplugger needs, leaving
scan lists, contacts, and DMR identities out of the resolved model as
the README already specifies.

Verified: both ChiOff shared profiles still validate unchanged; MURS on
the P4 now fails with an explicit band error; a DMR channel on an
analog-only radio fails with a mode error; an unknown capabilities key
fails with a schema error instead of KeyError.
@emuehlstein
emuehlstein merged commit 2ad4d0e into main Aug 7, 2026
2 checks passed
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