Files
HBlink4/.github/copilot-instructions.md
cortandClaude Opus 5 f1a2e4133c Make the module files directly runnable again; no install step
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>
2026-08-06 20:54:37 -05:00

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 handlers
  • hblink4/events.py: Event emission to dashboard (TCP/Unix socket transport abstraction)
  • hblink4/access_control.py: Pattern-based repeater matching and blacklisting
  • hblink4/models.py: Dataclasses for RepeaterState, StreamState, OutboundConnectionConfig
  • dashboard/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

  1. RepeaterMatcher.find_config() matches incoming repeater against patterns
  2. Checks blacklist patterns first (raises BlacklistError)
  3. Returns RepeaterConfig with per-repeater settings
  4. 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.py and dashboard/server.py are directly runnable (what systemd uses). run_hblink.py / run_dashboard.py in the root are logic-free convenience launchers; run_all.sh runs both. No install step required. pip install -e . additionally provides hblink4 / hblink4-dashboard commands.
  • 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.