# heardlog Samlar APRS-data från två källor och lagrar i TimescaleDB + PostGIS som grund för en täckningskarta. ``` [Shack-LAN] [DMZ] Direwolf AGW:8000 │ agw_forwarder.py ──POST/Bearer──► FastAPI :8085 ──► TimescaleDB + PostGIS │ rotate.aprs2.net┘ (APRS-IS, outbound från DMZ) ``` | Källa | Tabell | Nyckeldata | |---|---|---| | Direwolf AGW (RF) | `rf_frames` | `heard_direct` – hördes utan mellanliggande digi | | APRS-IS regionalt | `is_frames` | Stationer inom 200 km som digipeatas till internet | `heard_direct = TRUE` är grundstenen för täckningskartan – ett bevis på att en station nådde vår mottagare direkt. --- ## Krav - Docker + Docker Compose (DMZ-server) - Direwolf med `AGWPORT 8000` i `direwolf.conf` (shack-dator) - Python 3.10+ och `pip3 install requests` (shack-dator) --- ## DMZ – snabbstart ```bash git clone heardlog cd heardlog cp .env.example .env # Generera API-nyckel python3 -c "import secrets; print(secrets.token_hex(32))" # Klistra in som API_KEY i .env docker compose up -d docker compose logs -f collector ``` Förväntad output: ``` INFO db Database schema ready INFO main Startup complete – listening for RF frames INFO aprs_is APRS-IS login: # logresp SA6ANW-1 unverified, server T2... ``` `unverified` på APRS-IS är förväntat – passcode `-1` ger receive-only vilket är tillräckligt. --- ## Shack – agw-forwarder ```bash pip3 install requests # Skapa konfigurationsfil cat > heardlog/agw-forwarder/.env << 'EOF' AGW_HOST=localhost AGW_PORT=8000 COLLECTOR_URL=http://:8085 API_KEY= STATION_CALL=SA6ANW-1 LOG_LEVEL=INFO EOF # Testa manuellt cd heardlog/agw-forwarder python3 agw_forwarder.py ``` ### Kör som systemd-tjänster ```bash # Kopiera service-filer sudo cp heardlog/agw-forwarder/direwolf.service /etc/systemd/system/ sudo cp heardlog/agw-forwarder/agw-forwarder.service /etc/systemd/system/ sudo systemctl daemon-reload sudo systemctl enable --now direwolf sudo systemctl enable --now agw-forwarder # Verifiera journalctl -u direwolf -u agw-forwarder -f ``` `agw-forwarder` har `Wants=direwolf.service` – systemd startar Direwolf automatiskt om den inte kör. --- ## Konfiguration ### DMZ (`.env`) | Variabel | Standard | Beskrivning | |---|---|---| | `STATION_CALL` | `SA6ANW-1` | Din anropssignal | | `DB_PASSWORD` | – | Postgres-lösenord | | `API_KEY` | – | Delad hemlighet med forwardern | | `APRS_IS_PASSCODE` | `-1` | `-1` för receive-only | | `APRS_IS_FILTER` | `r/58.35/14.05/200` | lat/lon/km runt din QTH | | `LOG_LEVEL` | `INFO` | `DEBUG` för varje frame | ### Shack (`agw-forwarder/.env`) | Variabel | Standard | Beskrivning | |---|---|---| | `AGW_HOST` | `localhost` | Direwolf-host | | `AGW_PORT` | `8000` | Direwolf AGW-port | | `COLLECTOR_URL` | – | URL till DMZ-API:t | | `API_KEY` | – | Samma som i DMZ `.env` | | `STATION_CALL` | `SA6ANW-1` | Din anropssignal | --- ## Implementationsnoteringar ### AGW monitoring-format Direwolf skickar `U`-frames i monitoring-mode med ett ledande mellanslag och full monitoring-header före `\r`: ``` 1:Fm SM6MJW-9 To UXRT8L Via WIDE1-1,WIDE2-1 [16:46:13]\r ``` Via-pathen extraheras med regex mellan `Via ` och ` NOW() - INTERVAL '7 days' ORDER BY ts DESC; -- Digis/stationer hörda senaste 30 min SELECT src_call, MAX(ts) AS last_seen, COUNT(*) AS frames FROM rf_frames WHERE ts > NOW() - INTERVAL '30 minutes' GROUP BY src_call ORDER BY last_seen DESC; -- Täckningspunkter per rutnätscell (0.01°) SELECT ROUND(lat::numeric, 2) AS grid_lat, ROUND(lon::numeric, 2) AS grid_lon, COUNT(*) AS hits FROM rf_frames WHERE heard_direct = TRUE AND lat IS NOT NULL GROUP BY grid_lat, grid_lon; -- APRS-IS senaste timmen SELECT ts, src_call, lat, lon FROM is_frames WHERE ts > NOW() - INTERVAL '1 hour' ORDER BY ts DESC; ``` --- ## API | Endpoint | Metod | Auth | Beskrivning | |---|---|---|---| | `/ingest/rf` | POST | Bearer | RF-frame från forwarder | | `/health` | GET | – | Liveness check | Swagger UI: `http://:8085/docs`