Subscriber-to-subscriber call routing with user cache + broadcast
fallback, subscriber-pair hang time, cross-slot forwarding, and
optional propagation over outbound server links that forms an
implicit reverse-path tree between HBlink4 peers.
Source-side gating:
- Per-repeater unit_calls_enabled seeded from pattern default_unit_calls
- Trusted repeaters can override via UNIT=true|false in RPTO
Target calculation (_calculate_unit_call_targets):
- Cache hit -> one-to-one to local repeater or outbound link
- Cache miss -> broadcast to all unit-enabled peers on source's slot
- Anti-loop: outbound-sourced calls never forward to any outbound
Slot semantics are ships-in-the-night. The originating slot is always
preserved on forward; the target's cached slot is informational only
because DMR radios can monitor both slots simultaneously.
Hang time protects the (rf_src, dst_id) subscriber pair. Either
direction of the same pair passes; same source to a new target
passes; anything else is a hijack.
Log lines are normalized so group and unit share identical shape,
differing only in TS/TGID vs TS/RID and optional annotations
([broadcast], [one-to-one via N], etc).
Dashboard Last Heard "Talkgroup" column renamed to "Destination"
with group/unit emoji indicators.
Known limitation: no cross-outbound forwarding for either call type;
TODO includes a stream-id-based loop-detection design sketch.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Documentation cleanup pass across the tree, driven by the upcoming merge
of the dmrd-translation branch to main.
Deletions (obsolete / superseded):
- docs/user_csv_automation_proposal.md — implemented as dashboard/user_db.py
- docs/QUICK_TEST_REFERENCE.md — stale test counts, referenced a
TEST_COVERAGE.md that no longer exists
- user.old.csv — development backup artifact (also added user.old* and
user.bak* to .gitignore)
Release notes renumbered to the 4.x series (file kept 1.x-labelled from
a prior numbering scheme that predated the 4.1.0 release):
- docs/RELEASE_NOTES_v1.5.md -> docs/RELEASE_NOTES_v4.5.md
- docs/RELEASE_NOTES_v1.6.1.md -> docs/RELEASE_NOTES_v4.6.1.md
- Also fixed a stray "v1.5+" -> "v4.5+" in stream_tracking_diagrams.md
Content fixes where docs contradicted code or were stale:
- docs/dmrd_translation.md: replaced the "Payload blanking" section with
"Link Control rewriting" describing VHEAD/VTERM full-LC and voice-burst
EMB_LC re-encoding via dmr_utils3.bptc
- docs/connecting_to_hblink4.md: cross-reference updated to match
- docs/protocol.md: corrected call-type bit direction (0=Group,
1=Private/Unit — previously reversed), replaced "Bits 0-3: Reserved"
with actual dtype_vseq semantics, updated translation-doc link
- readme.md: "Built on Twisted" -> asyncio (Twisted was removed in the
outbound-connection release); expanded docs index to include routing,
DMRD translation, diagrams, TODO, and OpenBridge analysis; added DMRD
translation to the features list
- docs/stream_tracking.md: StreamState snippet now shows lc_base and
lc_cache fields added for LC rewriting
- scripts/README.md: noted that dashboard/user_db.py is the primary
refresh path; script remains as manual fallback
- dashboard/user_db.py: dropped the doctring reference to the deleted
proposal file
Note: dmrd-translation branch remains in active development but is
fully functional as of this commit. Tests: 75/75 pass (pre-existing
test_access_control.py failures are unrelated and predate this branch).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Major changes:
- Comprehensive documentation audit completed
- Config restructure: dashboard moved from global to top-level
- Dual-stack IPv6: bind_ipv4/bind_ipv6 with port_ipv4/port_ipv6
- Dashboard transport: host_ipv4/host_ipv6 with disable_ipv6 flag
- Fixed corrupted dashboard/server.py imports
- Updated all documentation to match actual implementation
- Removed duplication, established single source of truth
Documentation updates:
- docs/configuration.md: Complete overhaul with accurate field definitions
- docs/dashboard_transport.md: Updated transport configuration guide
- docs/integration.md: Dual-stack integration examples
- dashboard/README.md: Clarified dashboard configuration requirements
- DASHBOARD_INTEGRATION.md: Streamlined integration summary
- All config examples now use correct field names
Configuration changes:
- dashboard section moved to top-level (not nested under global)
- IPv6 settings: disable_ipv6 flag replaces ipv6 boolean
- Separate bind addresses and ports for IPv4 and IPv6
- Dashboard uses host_ipv4/host_ipv6 instead of single host field
Bug fixes:
- Fixed syntax error in dashboard/server.py (corrupted imports)
- All JSON configs validate successfully
- All 39 tests passing
See DOCUMENTATION_AUDIT.md for complete audit report.
Problem:
- When we TX to a repeater (assumed stream) and it starts RX'ing
- The repeater ignores our TX packets (hardware busy receiving)
- We waste bandwidth sending packets to a busy repeater
Solution:
- Detect when repeater has assumed stream and receives real stream
- Remove that repeater from all active route-caches
- Stop sending to it immediately (bandwidth saved)
- Real RX streams always win over assumed TX streams
Implementation:
- Added check in _handle_stream_start() for is_assumed flag
- Iterate all streams and remove repeater from target_repeaters
- O(R×S) efficiency (~10-20 operations typical)
- Falls through to create real stream normally
Testing:
- Added test_assumed_stream_route_cache_removal()
- 39 tests passing (38 existing + 1 new)
- Validates route-cache removal logic
Documentation:
- Added section in stream_tracking.md
- Added detailed diagrams in stream_tracking_diagrams.md
- Explains real vs assumed stream priority
- Includes performance analysis and examples