Joakim Svensson b86aba7bcc first commit
2026-04-26 17:23:01 +02:00
2026-04-26 17:20:58 +02:00
2026-04-26 17:20:58 +02:00
2026-04-26 17:20:58 +02:00
2026-04-26 17:20:58 +02:00
2026-04-26 17:20:58 +02:00
2026-04-26 17:22:40 +02:00

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

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

Description
No description provided
Readme 84 KiB
Languages
Python 54.9%
HTML 44.8%
Dockerfile 0.3%