An OGN (Open Glider Network) server for XCSoar that connects to the glidernet.org APRS network to receive and process glider beacon data.
- Connects to the Open Glider Network (OGN) APRS server
- Filters beacons by geographic bounds via REST API
- Dynamic APRS-IS Filtering: Automatically requests only aircraft within vicinity of client location (reduces bandwidth 90%+)
- Writes IGC flight recording files
- Telegram bot for managing glider names
- Web API for XCSoar to retrieve live beacon data
- DDB Integration: Automatic download of FLARM Device Database on startup, using aircraft registration as primary display name
-
Create configuration files:
cp names.csv.example names.csv cp serverdata.txt.example serverdata.txt cp private.key.example private.key cp location.txt.example location.txt
-
Edit
serverdata.txt:<access_token> <host> <target_latitude> <target_longitude> -
Add your Telegram bot token to
private.key -
Start the server:
docker compose up --build
The API will be available at http://localhost:8000
<access_token>
<host>
<target_latitude>
<target_longitude>
Example:
mysecrettoken
0.0.0.0
47.5
13.0
Your Telegram bot token (single line).
Your Telegram chat ID (binary file).
CSV file with flarm ID to pilot name mappings:
fid,name
FLR123456,John Doe
FLR789012,Jane Smith
The server automatically downloads the FLARM Device Database from glidernet.org on startup. This provides automatic registration lookup for known gliders.
Name resolution priority:
- names.csv nickname (e.g., "John Doe") - user-defined via Telegram bot - HIGHEST PRIORITY
- DDB registration (e.g., "D-1234") - downloaded automatically
- FLARM ID suffix (last 4 chars) - fallback if neither available
Cache behavior:
- Downloaded DDB is cached in
ddb.json - Cache TTL: 60 minutes (re-downloaded after expiry)
- If DDB download fails, server starts with names.csv only and retries DDB in background
Rate limiting: The DDB API enforces rate limits. If 429 Too Many Requests is received, the server waits and retries up to 3 times before falling back to names.csv.
The server automatically applies location-based filtering to reduce bandwidth by requesting only aircraft within a configurable radius of the last client request.
How it works:
- XCSoar requests beacons with
boundsparameter - Server calculates center point and checks if moved more than threshold (default: 50km)
- On next reconnection, applies APRS-IS filter
r/LAT/LON/RADIUSto receive only local traffic - Automatically switches to port 14580 (supports filtering) when active
Configuration via environment variables:
# Filter radius in kilometers (default: 200)
OGN_APRS_FILTER_RADIUS_KM=200
# Minimum distance to trigger filter update (default: 50)
OGN_FILTER_MIN_CHANGE_KM=50
# Enable/disable filtering (default: true)
OGN_APRS_FILTER_ENABLED=trueExample Docker Compose configuration:
environment:
- OGN_APRS_FILTER_RADIUS_KM=150
- OGN_FILTER_MIN_CHANGE_KM=30
- OGN_APRS_FILTER_ENABLED=trueBenefits:
- Reduces bandwidth by ~90% (only receives local aircraft)
- Automatic updates as client moves
- No connection churn (updates only on reconnection)
- Configurable radius and update threshold
The OGN client implements robust error handling for DNS resolution failures and connection errors, ensuring the server continues running even when the OGN server is unreachable.
Graceful Degradation:
- Client never crashes after connection failures - continues retrying indefinitely
- Server remains operational (API can serve cached beacon data)
- All errors logged in JSON format for monitoring
DNS Resolution Failures:
- Specific handling for
socket.gaierror(cannot resolve hostname) - Automatically attempts fallback hostname if primary DNS fails
- Uses longer retry interval (60s) since DNS issues typically persist longer
- Clear error messages:
"DNS resolution failed: {host} - {error}"
Connection Errors:
- Exponential backoff for transient connection failures (10s → 20s → 40s...)
- Separate from DNS retry strategy
- Logs all connection attempts for debugging
Fallback Hostname:
- Primary:
glidern3.glidernet.org(default) - Fallback:
aprs.glidernet.org(automatic on DNS failure) - Switches immediately without waiting on first DNS failure
Configuration via environment variables:
# Maximum retry attempts before graceful degradation (default: 5)
OGN_CONNECT_MAX_RETRIES=5
# Base retry delay for connection errors in seconds (default: 10)
OGN_CONNECT_RETRY_DELAY=10
# Retry delay for DNS failures in seconds (default: 60)
OGN_DNS_RETRY_DELAY=60
# Fallback hostname if primary DNS fails (default: aprs.glidernet.org)
OGN_SERVER_HOST_FALLBACK=aprs.glidernet.orgExample Docker Compose configuration:
environment:
- OGN_CONNECT_MAX_RETRIES=10
- OGN_CONNECT_RETRY_DELAY=15
- OGN_DNS_RETRY_DELAY=120
- OGN_SERVER_HOST_FALLBACK=aprs.glidernet.orgMonitoring:
- Check logs for
"DNS resolution failed"or"Connection failed"messages - CRITICAL log level indicates max retries reached
- Server continues running even after repeated failures
GET /?access_token=<token>&bounds=<min_lat>,<max_lat>,<min_lon>,<max_lon>
Returns CSV-formatted beacon data:
<count>,<count>
<name>,<lat>,<lon>,<track>,<alt>,<speed>,<climb>,<timestamp>,<type>
Example:
GET /?access_token=mysecrettoken&bounds=47.0,48.0,12.0,14.0
Response:
2,2
John Doe,47.51234,13.01234,180,1500,100,2.5,1705312245,^
Jane,47.52345,13.02345,90,1600,120,1.5,1705312246,>
-
/start- Show available commands and usage examples -
/a <fid>,<name>- Add a new glider name (e.g.,/a FLR123456,John Doe) -
/d <fid>- Delete a glider name (e.g.,/d FLR123456) -
/refreshddb- Refresh FLARM Device Database from glidernet.org -
/igc- Request IGC flight files (interactive conversation)- Shows list of available aircraft with IGC files
- Select aircraft → Select date → Receive IGC file(s)
- Supports cancel (
Cancelbutton or/cancel) and back navigation at any step - 5-minute conversation timeout for inactive sessions
Changes to names.csv are persisted to the host file via Docker volume mount.
.
├── main.py # Entry point
├── src/ogn_server/ # Main package
│ ├── __init__.py
│ ├── beacon.py # Beacon dataclass
│ ├── client.py # OGN client
│ ├── config.py # Configuration
│ ├── api.py # Flask API
│ └── telegram_bot.py # Telegram bot
├── tests/ # Test suite
├── requirements.txt # Python dependencies
├── pyproject.toml # Package configuration
└── docker-compose.yml # Docker Compose configuration
Run tests:
pytest tests/ -vWith coverage:
pytest tests/ --cov=src --cov-report=htmlBuild and start:
docker compose up --buildRun in background:
docker compose up --build -dView logs:
docker compose logs -fStop:
docker compose downIf you want to run without Docker:
-
Create and activate a virtual environment:
python3 -m venv .venv source .venv/bin/activate -
Install dependencies:
pip install -r requirements.txt
-
Start the server:
python main.py
The server supports graceful shutdown via SIGTERM. For automatic daily restarts, configure an external cronjob instead of using internal scheduling.
Host-level cronjob (runs on your server):
# Edit system crontab: sudo crontab -e
# Add this line to restart container daily at 3 AM UTC:
0 3 * * * docker restart xcsoar-ogn-server >> /var/log/ogn-restart.log 2>&1Systemd timer (modern alternative to cron):
Create /etc/systemd/system/ogn-server-restart.service:
[Unit]
Description=Daily restart of XCSoar OGN Server
After=network.target
[Service]
Type=oneshot
ExecStart=/usr/bin/docker restart xcsoar-ogn-server
User=youruserCreate /etc/systemd/system/ogn-server-restart.timer:
[Unit]
Description=Restart XCSoar OGN Server daily at 3 AM
[Timer]
OnCalendar=*-*-* 03:00:00
Persistent=true
[Install]
WantedBy=timers.targetEnable with:
sudo systemctl daemon-reload
sudo systemctl enable --now ogn-server-restart.timerSystemd timer:
Create /etc/systemd/system/ogn-server-restart.service:
[Unit]
Description=Daily restart of XCSoar OGN Server
After=network.target
[Service]
Type=oneshot
ExecStart=/bin/systemctl restart ogn-server
User=youruserCreate /etc/systemd/system/ogn-server-restart.timer:
[Unit]
Description=Restart XCSoar OGN Server daily at 3 AM
[Timer]
OnCalendar=*-* escapes-*-* 03:00:00
Persistent=true
[Install]
WantedBy=timers.targetCron equivalent:
# Edit user crontab: crontab -e
0 3 * * * /bin/systemctl restart ogn-server >> /var/log/ogn-restart.log 2>&1- Docker:
docker logs xcsoar-ogn-serverorjournalctl -u xcsoar-ogn-server - Systemd:
journalctl -u ogn-serverfor server logs,journalctl -u ogn-server-restart.servicefor restart logs - Cron: Logs redirected to
/var/log/ogn-restart.log
The server handles SIGTERM gracefully:
- OGN client disconnects cleanly
- Telegram bot stops polling and closes connections
- Flask API shuts down properly
This ensures no data loss during restart.
MIT
- Add health check endpoint (
/health) for monitoring and container orchestration - Implement API rate limiting to prevent abuse
- Add Prometheus metrics endpoint for observability
- Add unit tests for telegram_bot module
- Add integration tests for the full system
- Improve IGC file handling with automatic file rotation and cleanup
- Add structured logging (JSON format) for better log analysis
- Implement input validation for configuration files on startup
- Add caching layer for frequently requested beacon data
- Improve error handling and recovery in OGN client with retry logic
- Add dynamic APRS-IS location filtering for bandwidth reduction
-
Features
- Added robust DNS resolution failure handling (
socket.gaierror) - Implemented graceful degradation - client never crashes after max retries
- Automatic fallback hostname switching on DNS failure
- Configurable retry parameters via environment variables
- Separate retry strategies for DNS (60s) vs connection errors (exponential backoff)
- Added robust DNS resolution failure handling (
-
Configuration
OGN_CONNECT_MAX_RETRIES- Maximum retry attempts (default: 5)OGN_CONNECT_RETRY_DELAY- Connection retry delay (default: 10s)OGN_DNS_RETRY_DELAY- DNS retry delay (default: 60s)OGN_SERVER_HOST_FALLBACK- Fallback hostname (default: aprs.glidernet.org)
-
Testing
- Added 6 unit tests for error handling scenarios
- Tests cover DNS failures, connection errors, fallback hostname, graceful degradation
- All new tests passing
-
Features
- Added dynamic APRS-IS location filtering for bandwidth reduction (90%+)
- Auto-switch to port 14580 when filter is active
- Configurable filter radius and update threshold via environment variables
- Deferred filter updates to avoid connection churn
-
Bug Fixes
- Fixed
UnboundLocalErrorin_load_names_df()when names.csv doesn't exist
- Fixed
-
Testing
- Added 16 unit tests for APRS filter functionality
- All existing tests passing (129/130, 1 pre-existing failure unrelated)
-
Features
- Added
/healthendpoint for container orchestration monitoring - Added
/metricsendpoint for Prometheus observability - Added API rate limiting (60 requests/minute per client)
- Added caching layer for beacon data (5 second TTL)
- Added IGC file retention (30 days) with automatic cleanup
- Added configuration validation on startup
- Added
-
Testing
- Added unit tests for telegram_bot module
- Added integration tests for full system
-
Logging
- Added structured JSON logging for better log analysis