2 Commits
Author SHA1 Message Date
Cort BuffingtonandClaude Opus 5 046c47b7d0 Group calls only, per-slot lanes, and N instances per process
Three changes, all driven by the same design conversation.

GROUP CALLS ONLY. Private calls to the talkback's radio ID are now
ignored. Unit routing draws on a globally administered namespace and is
unbounded -- a private call to an unknown ID floods until located -- so
reachability-by-ID would require a unique registered radio ID for every
instance anyone ever deploys, for something that is neither a radio nor
a repeater. BrandMeister can do it because it is the sole authority over
its own namespace; these tools assume an internet of independent
routers. Talkgroups are operator-scoped and bounded by the operator's
own rules, so they cost nobody else anything.

Consequence: the radio ID never needs to be reachable, so one ID an
operator already owns serves every instance they run.

PER-SLOT LANES. The previous version had a single global capture buffer,
so two simultaneous callers -- one per timeslot, which the wire delivers
routinely -- reset it on every interleaved packet and captured nothing.
That is the same defect the 2019 Python playback.py had. Capture and
replay are now per timeslot and fully independent. Within a lane, a
second stream no longer supersedes unconditionally: first-come-wins
unless the current capture has gone quiet, so interleaving can never
thrash the buffer.

One talkgroup per slot, deliberately: a slot carries one call at a time,
so a list would advertise capacity that does not exist.

N INSTANCES. One process now runs several independent connections, each
with its own radio ID, server, and talkgroups. The purpose is one
instance per HBlink3 *system*, so each system gets a private talkback
without building a bridge to multiplex everyone into one engine.
Bridging into a single instance remains supported. Two instances sharing
a radio ID on the same server is rejected at startup.

Adds toml_sections() to the vendored reader to discover [instance.<name>]
sections, since it has no array-of-tables.

Tests: rewrite conformance 566 checks; new test_lanes covers lane
isolation, same-lane non-thrash, the ingress gate, and the capture
ceiling.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-15 15:55:00 -05:00
Cort BuffingtonandClaude Opus 5 d2d99cca3e Initial commit — DMR voice test (talkback) endpoint
Connects to an HBP master as an ordinary repeater, records one call, and
replays it sourced from its own radio ID.

- One configured radio ID performs every radio-ID function: HBP login,
  DMRD repeater field, source of the replay, and the unit-call target.
- Group, unit, or both; the reply mirrors the inbound call type. Group
  goes back onto the same talkgroup, unit is a private call to the caller.
- Full rewrite of the DMRD header AND the payload Link Control (full LC in
  VHEAD/VTERM, embedded LC fragments in bursts B-E, FLCO per call type).
  AMBE is never touched, so replayed audio is bit-identical. Earlier
  implementations rewrote only the header, leaving radios displaying the
  original caller.
- Talkgroup config is emitted as an RPTO subscription at login, which is
  what makes it work on HBlink4 without server-side configuration.
- Clocked egress at 60 ms off the event loop; fixed capture buffer sized
  at startup; nothing allocates on the data path.

HBP client and the DMR DSP module are lifted from ipsc2hbpc.

Conformance test is the loopback identity: 378 checks over a synthetic
capture covering every frame kind.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-15 14:54:42 -05:00