Match the three conventions in DMRGateway's config-blob sprintf: text
left-justified/space, numerics right-justified/zero, lat-long
right-justified/space. Was NUL-filling every field, which connects fine
but leaves NULs in values consumers read as text.
RPTO is now 8 + strlen(options) rather than a padded 308 bytes.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The most likely first-run symptom is silence with perfect logs at every
hop. Manual-dialing a talkgroup sets the TX contact but not the
channel's RX group list, so the echo returns on the right talkgroup and
the radio never unmutes. Points users at the radio rather than the
software, and gives them the two-call log signature that proves
everything upstream is working.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The 'Connecting it' instructions said to add a MODE: SERVER system for
each instance to log into. That was wrong: HBlink3 systems already exist
and the talkback connects to one as another client, alongside that
system's repeaters. You do not create systems for talkback -- except in
the deliberate case of a dedicated one that other systems bridge to.
Also documents why no bridge is needed for the direct case: a Server
with REPEAT: True forwards every stream to its connected clients except
the source (hblink.py:467), which both delivers traffic to the talkback
and keeps its replay from looping back in. And what the joined system's
ACLs must permit (REG_ACL, SUB_ACL for the replay source, and the TGID
ACL for the slot talkgroups).
README, sample config, and spec sections 1 and 12.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Test harnesses are for developers. End users installing a voice test
endpoint have no reason to build and run them, and listing them invites
'make test' as a step in setup. The suites and the make target are
unchanged; only the user-facing documentation of them is gone.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The constraint is a registration one, not a routing one. An HBP server
keys registered repeaters by radio ID and validates the source address
per packet (hblink3 hblink.py:478+), so two connections to the same
ip:port with one ID share an entry and fight over it. Routing never
enters into it -- group-only talkback has nothing routed to its ID.
It is per listening socket, not per host. Each HBlink3 system listens on
its own port with its own _repeaters dict, so N instances across N
systems of one HBlink3 server may all share a single radio ID -- which
is the expected arrangement.
The check was always correct (it compares ip AND port); the docs said
'same server', which described the exact N-instance case that is
supposed to work. README, sample config, error message, and the spec
now say what the code does.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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>
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>