Skip to content

feat: multi-talkgroup channels (denormalise one channel into repeater × talkgroup pairs on export) #36

Description

@pskillen

Problem

Many digital (DMR) radios only support one talk group per channel. This is by design — it suits the business/commercial customers these radios are built for, where a channel maps to a fixed talkgroup.

Amateur radio operators, however, usually think differently: they want to pick a repeater/hotspot (an RF channel: rx/tx frequency, colour code, timeslot context) and then independently select the DMR talk group to use on it. Expressing this in a vendor CPS means manually creating one channel per (repeater × talkgroup) combination — e.g. GB7XX TG235, GB7XX TG2351, GB7XX TG840, … — which is tedious and error-prone, and bloats the channel list.

We can't change the radio's behaviour. But our codeplug builder can let operators model the intent cleanly and do the expansion for them.

Intended outcome

Let a single logical channel carry multiple talk groups in our internal data models, and denormalise that into the required one-talkgroup-per-channel rows at the export boundary.

  • Per-channel opt-in: a checkbox/flag on a (digital) channel to enable "multi-talkgroup" mode. When off, the channel behaves exactly as today (single contact/TG). The feature must be entirely optional and invisible to users who don't enable it.
  • Assign multiple talk groups to an enabled channel within our models (an ordered list of TG references, building on the internal models from feat: genericise CPS import via internal data models #7 and the talkgroup/TG-list work in feat: CRUD DMR talk groups and TG lists (incl. TG ↔ TGL) #13).
  • Denormalise on export (feat: CPS export support (internal models → vendor format) #8): an enabled channel with n talk groups emits n concrete vendor channels — one per (channel × talkgroup) pair — each a normal single-TG channel the radio understands. A channel with the feature off (or with a single TG) emits exactly one row.
  • Naming scheme for generated channels: derive a clear, deterministic, collision-resistant name per generated pair (e.g. <base name> <TG> or a configurable template), respecting the target CPS's channel-name length limits and case-sensitive-foreign-key rules. Finalise the exact scheme during design.
  • Zone membership: decide how a denormalised channel participates in zones — a zone referencing a multi-TG channel likely needs to expand to its generated children (mind OpenGD77's ~80-channel zone cap, as with feat: nested zones (zones as members of zones, denormalised on export) #33). Finalise during design.

Import re-normalisation (best-effort, NOT a blocker)

On import we may be able to detect a family of generated channels and collapse them back into a single multi-TG logical channel, by grouping on channel name (base) + rx/tx frequencies + (varying) talk group IDs. This is explicitly best-effort:

  • It is not required to ship the export-side feature.
  • Vendor exports are flat and lossy, so perfect reconstruction isn't guaranteed; heuristics may mis-group or under-group.
  • If skipped, multi-TG channels imported from a flat CPS simply arrive as individual channels (no regression).

Affected

Notes / dependencies

Out of scope

  • Changing radio firmware behaviour (impossible — this is why we denormalise).
  • Guaranteed loss-free import re-normalisation (best-effort only, per above).

Workflow note (for whoever picks this up)

Likely multi-commit work: branch from origin/main, use atomic conventional commits per logical change (do not batch into one big end-of-plan commit), and open a PR linking Closes #. Pair with the docs/features / progress-tracking skills for the build log.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or requestquality-of-lifefeature to improve the QOL of the person making a codeplug

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions