Changes to hblink4/hblink.py:
- Renamed our_id → radio_id (more consistent with DMR terminology)
- Added colorcode, software_id, package_id fields to OutboundConnectionConfig
- Updated _send_outbound_config() to use configurable values instead of hardcoded
- All event emissions updated to use 'radio_id' field name
Changes to config/config_sample.json:
- Fixed field names: host→address, passphrase→password, our_id→radio_id
- Added all optional HBP metadata fields with proper defaults
- Shows comprehensive example with full metadata and minimal example
Changes to docs/configuration.md:
- Added complete Outbound Connections section
- Documents all required and optional fields
- Explains options format (TS1=x,y;TS2=a,b)
- Covers bidirectional traffic, multiple connections, DNS resolution
- Examples showing different configuration scenarios
CRITICAL documentation fixes:
- Hang time reserves slot for same TALKGROUP (or original user)
- Fixed typical transmission duration: 30-90 seconds (not 0.6-3.6s)
- Removed confusing echo/loop prevention discussion (half-duplex is obvious)
- Updated all examples to reflect actual implementation
Implementation reference: hblink.py lines 2173-2183 (same_tg or same_user)
- Complete design specification for server-to-server linking
- Config schema based on MMDVM.ini format
- State management with separate dictionaries for O(1) routing
- ID conflict protection to prevent DoS
- Connection lifecycle and error handling
- Dashboard integration requirements
- Implementation checklist with 11 phases
The goal was always to move to asyncio from twisted, however, it was necessary
to develop the asyncio solution over time. Now that it is stable, the
twisted version is being moved to a legacy branch, and the asyncio will
become the main branch for continued development
- Add early detection of unit/private calls (call_type_bit == 1)
- Log unit calls at INFO level with clear identification
- Reject unit calls until full implementation ready (return False)
- Update TODO with comprehensive 8-phase unit call implementation plan
- Move code refactoring to 'Ongoing Priority' section
- Document user cache enhancement needed (slot tracking)
- Document routing strategies: one-to-one and broadcast with pruning
- Document slot compatibility requirements (no slot translation)
- Document hang time and contention handling for unit calls
- Include performance monitoring and testing strategies
- Fix whitespace alignment in stream_tracking_diagrams.md
Features:
- Add trust flag to RepeaterConfig (default: false)
- Trusted repeaters can use any TG dynamically via OPTIONS
- Config TGs become defaults instead of restrictions
- Use for core network, testing, or administrative repeaters
- Make default repeater config optional
- Omitting default rejects non-matching repeaters
- Provides better security by requiring explicit config
Code improvements:
- Remove deprecated _is_talkgroup_allowed() method (never called)
- Remove defensive exception masking from _load_repeater_tg_config()
- Implement fail-fast design - bugs crash immediately with clear traceback
- Authentication at line 1120 is single point of truth for config validation
Documentation:
- Update configuration.md with trust flag examples and security notes
- Explain optional default behavior
- Update config_sample.json with trusted repeater example
- Allow patterns to specify multiple match types (ids, id_ranges, callsigns)
- Patterns evaluated in config order (first match wins) with OR logic
- Callsign '*' matches all repeaters (useful for catch-all patterns)
- Emit repeater_connected event when any packet clears missed_pings
- Update documentation to reflect pattern matching behavior
- Remove outdated config fields from docs (enabled, timeout, talkgroups)
- Update tests to match new pattern evaluation order
Added configuration examples and guidance for using TCP transport with
dashboard on a remote machine. Same dual-stack considerations apply as
the UDP server when using TCP for remote dashboard connections.
Options documented:
- Localhost: No dual-stack issues (127.0.0.1 / ::1)
- Remote dual-stack: host_ipv4="", host_ipv6="::", single port
- Remote separate ports: If dual-stack bind conflicts
- Remote IPv4-only: Simplest for cross-machine deployments
HBlink4's event emitter already tries IPv6 first with automatic IPv4
fallback, so dual-stack works seamlessly.
When IPv6 bind fails with 'address already in use' on the same port as IPv4,
provide helpful warnings explaining this is normal dual-stack behavior and
offer three clear solutions.
Code changes:
- Detect 'address already in use' errors specifically
- Provide context-aware warnings with actionable solutions
- Changed log from 'IPv6/dual-stack' to just 'IPv6' (more accurate)
Documentation changes:
- Added 'Common Issue' section explaining dual-stack port conflicts
- Recommend using separate ports as primary solution
- Document IPv4-only and IPv6-only alternatives
- Updated examples to show port_ipv4: 62031, port_ipv6: 62032
Production-tested solution for the common dual-stack bind error.
Added examples showing multiple ID ranges in a single pattern for both
blacklist and repeater configuration sections. This feature already works
in the code but wasn't clearly documented.
Examples added:
- Blacklist: Multiple blocked ranges in one pattern
- Repeater configs: Regional network covering multiple DMR-MARC regions
- Both config_sample.json and configuration.md updated
Example usage:
"id_ranges": [[310000, 310999], [311000, 311999], [312000, 312999]]
This is much cleaner than creating separate patterns for each range!
- Note that most firewalls handle both protocols with single rule
- Explain when separate IPv4/IPv6 rules might be needed
- Make port purposes clearer in comments
Clarify the HomeBrew DMR protocol implementation details and correct errors in the keepalive mechanism. Update the table formatting for configuration data fields and DMRD packet structure.
- Document RPTO message format and purpose
- Explain talkgroup negotiation and intersection logic
- Note that server config is master allow list
- Include examples of RPTO usage and filtering
- Completes protocol documentation for all implemented commands
Implement transport abstraction for dashboard communication, supporting TCP and Unix socket transports while removing UDP. Update configuration files and documentation accordingly.
Updated implementation summary with changes to stream tracking, hang time, and comprehensive documentation. Removed sections on modified files and configuration examples for clarity.
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
- Implement TCP and Unix socket transports (removed UDP per requirements)
- Add connection state awareness and automatic reconnection (10s interval)
- Fix TCP race condition: Initial state sync on (re)connection
- Add reconnect callback to send current repeater state to dashboard
- Update all configuration files with transport settings
- Add comprehensive documentation (dashboard_transport.md, TRANSPORT_IMPLEMENTATION_SUMMARY.md)
- Clean up debug logging
This change addresses all user requirements:
1. Dashboard can run on separate server (TCP transport)
2. Dashboard never shows stale data (reliable delivery + state sync)
3. Dashboard knows when HBlink is offline (connection state tracking)
4. Unix socket for local (fastest), TCP for remote (flexible)
5. Robust against connection timing issues (initial state sync)
Tested: Both TCP and Unix socket transports working correctly
Building on the success of the _end_stream() helper consolidation,
next session will focus on identifying and eliminating repetitive
code patterns across all files using helper functions.
Code cleanup:
- Remove stale LC extraction comment from imports
- Remove Phase 1/2/3 references from comments (work complete)
- Simplify verbose architecture notes to concise descriptions
- Remove TODO for RSSI parsing (not implementing)
- Remove 'Critical:' and 'Must' tags from comments
Dashboard cleanup:
- Remove talker_alias display (replaced with packet count in slot view)
- Update Last Heard to show callsign from user_cache (not talker_alias)
- Remove talker_alias from stream_start event formatting
Documentation:
- Update IMPLEMENTATION_SUMMARY git history (remove LC reference)
All 29 tests passing. No functional changes.
Performance Monitoring:
- Focus on latency and jitter as most critical metrics
- Remove packets/sec and active streams (not issues)
- Latency/jitter through hblink4 is key quality indicator
Removed items not aligned with design:
- Multi-Server Clustering (design is endpoint, no scaling needed)
- Mobile App Integration (web-only, mobile optimization in dashboard)
Items now numbered 1-5 (was 1-7)
- Removed LC Extraction from Sync Frames from Medium Priority
- Removed 'LC Decoding Clarification' section
- Removed 'DMR Link Control Extraction' from completed features
- LC functionality is abandoned going forward
- Renumbered remaining items 1-9 (was 1-10)
Dead code removed:
- bhex() function (only used once, now inlined)
- get_user_cache_data() method (never called, duplicated functionality)
- Unused dmr_utils3 imports (decode, bptc - were for LC extraction)
Documentation cleanup:
- Remove all LC (Link Control) references from docs
- Remove all talker alias references from docs (changed to 'alias' in dashboard docs)
- Update IMPLEMENTATION_SUMMARY.md to remove LC/talker alias sections
- Update readme.md to remove LC/talker alias feature mentions
- Update DASHBOARD_INTEGRATION.md to remove talker_alias from event parameters
- Update docs/TODO.md to remove completed talker alias items
All changes validated:
- No linting errors in hblink.py
- 23 tests passing (6 pre-existing failures unrelated to these changes)
- Code is cleaner and more maintainable
Major changes:
- Replaced ambiguous 'talkgroups' list with slot1_talkgroups/slot2_talkgroups
- Simplified routing: same lists control inbound and outbound (no separate rules)
- No wildcards or rewriting (simplicity over flexibility)
Configuration updates:
- Updated config.json with real KS-DMR settings (TS1: TG2,8,9 / TS2: TG3120)
- Updated config_sample.json with slot-specific examples
- Created docs/routing.md with complete documentation
Code changes:
- Added _check_inbound_routing(): Verify repeater can send on TS/TGID
- Added _check_outbound_routing(): Verify repeater should receive on TS/TGID
- Added _is_slot_busy(): Contention detection with hang time support
- Added _update_assumed_stream(): Track streams forwarded to targets
- Updated _forward_stream(): Full Phase 3 implementation with routing + contention
- Updated StreamState: Added is_assumed flag and end_time field
Routing logic:
- Inbound: Only accept traffic matching repeater's slot_N_talkgroups
- Outbound: Only forward to repeaters with matching slot_N_talkgroups
- Contention: Block busy slots, respect hang time after stream ends
- Assumed streams: Track forwarded streams on target repeaters
Ready for real-world testing with multiple repeaters!
- Comment out embedded LC extraction functions and StreamInfo fields
- Add common search phrase: 'LC recovery - disabled until fixed'
- Skip all embedded LC tests with clear reason
- Document both debug comment phrases in docs/logging.md:
* 'Per-packet logging - only enable for heavy troubleshooting'
* 'LC recovery - disabled until fixed'
- LC still obtained from DMRD packet header, this was backup mechanism only