The previous commit required "pip install -e ." before anything would run, and made "python hblink4/hblink.py" fail outright. That trades a real support burden -- an install step people forget after git pull, and an error message that looks like a broken checkout -- for an internal correctness property. Not a good trade for this project's users, who have run hblink by interpreter-plus-path since HBlink3 and DMRlink. Establish the package context explicitly instead. When hblink4/hblink.py or dashboard/server.py is run by path, __package__ is empty, so each now puts the project root on sys.path and sets __package__ before its relative imports. That is enough for "from .constants import ..." to resolve to hblink4.constants -- one copy, not the two the old except-ImportError fallback produced. Importing the parent package is cheap because __init__.py is metadata only. Every invocation style now works and none of them double-load: python hblink4/hblink.py config/config.json <- systemd uses this python dashboard/server.py <- systemd uses this python run_hblink.py config/config.json <- convenience python run_dashboard.py <- convenience python -m hblink4.hblink hblink4 <- if pip installed The systemd units invoke the module files directly rather than going through a launcher, so there is one less moving part between systemd and the program. run_hblink.py and run_dashboard.py stay in the project root as launchers for interactive use, but they are now genuinely logic-free: the uvicorn call that used to live in run_dashboard.py is dashboard.server.run(). requirements.txt and requirements-dashboard.txt come back as the documented install path. pyproject.toml stays for those who want "pip install -e .", but is now optional and reads its dependencies from those files so the two cannot drift. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
4.8 KiB
HBlink4 Copilot Instructions
Project Overview
HBlink4 is a DMR Server implementing the HomeBrew protocol for amateur radio repeater networks. It's a repeater-centric architecture focusing on per-repeater control rather than server-level groupings. The system consists of two main components:
- Core server (
hblink4/): Asyncio-based UDP server handling DMR protocol, stream tracking, and access control - Web dashboard (
dashboard/): FastAPI-based real-time monitoring with WebSocket updates
This project should always focus on efficiency and low-latency handling of DMR streams, what we call the "hot path". Additionally, ensuring that the dashboard does not consume resources in a way that negatively impacts the hot path or greatly increases system resource consumption. The low-end target system should be a modern Raspberry Pi or equivalent.
Architecture Patterns
Component Structure
hblink4/hblink.py: Main server with asyncio UDP protocol handlershblink4/events.py: Event emission to dashboard (TCP/Unix socket transport abstraction)hblink4/access_control.py: Pattern-based repeater matching and blacklistinghblink4/models.py: Dataclasses forRepeaterState,StreamState,OutboundConnectionConfigdashboard/server.py: FastAPI app with WebSocket real-time updates
Configuration Pattern
JSON-based configs in config/ with pattern matching:
{
"repeater_configurations": {
"patterns": [
{
"match": {"ids": [123], "callsigns": ["N0MJS*"], "id_ranges": [[315000, 315999]]},
"config": {"enabled": true, "timeout": 30, "talkgroups": [3120]}
}
]
}
}
Import Strategy
Modules use dual import paths (package-relative first, fallback to direct):
try:
from .constants import RPTA, RPTL
from .utils import safe_decode_bytes
except ImportError:
from constants import RPTA, RPTL
from utils import safe_decode_bytes
Development Workflows
Running & Testing
# Development setup
python3 -m venv venv && source venv/bin/activate
pip install -r requirements.txt
pip install -r requirements-dashboard.txt
# Run server only
python run_hblink.py [config/config.json] # or: python hblink4/hblink.py ...
# Run dashboard only (bind/port from the 'web' section of dashboard/config.json)
python run_dashboard.py [bind] [port] # or: python dashboard/server.py ...
# Run both (production-like)
./run_all.sh
# Tests
python -m pytest tests/ -v
python -m pytest tests/test_access_control.py::TestRepeaterMatcher::test_specific_id_match -v
Transport Configuration
Dashboard communication uses transport abstraction:
- Unix socket (default):
/tmp/hblink4.sock- fastest for localhost - TCP: IPv4/IPv6 dual-stack - for remote dashboard
Both config/config.json and dashboard/config.json must use matching transport settings.
Stream Tracking Pattern
Real-time DMR stream monitoring with dual termination detection:
- Immediate: DMR terminator packet detection (~60ms)
- Fallback: Timeout-based cleanup (configurable
stream_timeout)
Event emission happens every 10 superframes (1 second) for dashboard updates.
Key Integration Points
Access Control Flow
RepeaterMatcher.find_config()matches incoming repeater against patterns- Checks blacklist patterns first (raises
BlacklistError) - Returns
RepeaterConfigwith per-repeater settings - Pattern types:
specific_id,id_range,callsign(with wildcards)
Event System
EventEmitter sends JSON events to dashboard:
- Stream events:
stream_start,stream_end,stream_update - Repeater events:
repeater_connected,repeater_disconnected - Uses non-blocking sockets with connection state tracking
User Cache
UserCache manages private call routing with TTL expiration:
user_cache.update_user(radio_id, repeater_id, slot)
target_repeater = user_cache.lookup_user(target_id)
File Organization
- Core logic:
hblink4/package with protocol handling - Configuration:
config/config.json(server),dashboard/config.json(web) - Entry points:
hblink4/hblink.pyanddashboard/server.pyare directly runnable (what systemd uses).run_hblink.py/run_dashboard.pyin the root are logic-free convenience launchers;run_all.shruns both. No install step required.pip install -e .additionally provideshblink4/hblink4-dashboardcommands. - Deployment: Systemd services expect user ownership, virtual environment
- Data:
logs/,dashboard/data/for persistence - Tests:
tests/with pytest, focus on access control and stream tracking
When editing protocol handling, ensure proper terminator detection in protocol.py. For dashboard changes, maintain WebSocket message format compatibility. Access control changes should validate against test patterns in tests/test_access_control.py.