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 8000idirewolf.conf(shack-dator) - Python 3.10+ och
pip3 install requests(shack-dator)
DMZ – snabbstart
git clone <repo> 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
pip3 install requests
# Skapa konfigurationsfil
cat > heardlog/agw-forwarder/.env << 'EOF'
AGW_HOST=localhost
AGW_PORT=8000
COLLECTOR_URL=http://<dmz-ip>:8085
API_KEY=<samma nyckel som i DMZ .env>
STATION_CALL=SA6ANW-1
LOG_LEVEL=INFO
EOF
# Testa manuellt
cd heardlog/agw-forwarder
python3 agw_forwarder.py
Kör som systemd-tjänster
# 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 <UI pid=F0 Len=17 F=0 >[16:46:13]\r<info>
Via-pathen extraheras med regex mellan Via och <UI. heard_direct avgörs av om någon hop i pathen har * (har-repeaterats-flaggan).
Null-bytes
APRS-frames kan innehålla 0x00-bytes i info-fältet. Dessa stoppas ut innan DB-insert eftersom Postgres UTF8 inte accepterar null-bytes.
Nyttiga queries
-- Senaste RF-frames
SELECT ts, src_call, lat, lon, heard_direct, path
FROM rf_frames
ORDER BY ts DESC
LIMIT 20;
-- Direkt-hörda stationer senaste 7 dagarna
SELECT ts, src_call, lat, lon, path
FROM rf_frames
WHERE heard_direct = TRUE
AND ts > 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://<dmz-ip>:8085/docs