Skip to main content

GPS Forwarding

GPS Forwarding relays location data from the Peplink router to one or more external APIs. Each destination gets its own queue with independent retry logic and health tracking.

Configuration

Forwarding is configured in config/default.toml (or via environment variables). It is disabled when no destinations are configured.

Single Destination (backward compatible)

[forwarding]
interval_secs = 60
url = "https://api.example.com"
token = "your-api-token"
device_id = "550e8400-e29b-41d4-a716-446655440000" # optional
health_url = "https://api.example.com/health" # optional

Multi-Destination

[forwarding]
interval_secs = 60
max_queue_size = 100
max_retries = 3
retry_delay_ms = 5000
timeout_ms = 10000

[[forwarding.destinations]]
name = "primary"
url = "https://api1.example.com"
token = "token1"
device_id = "550e8400-e29b-41d4-a716-446655440000"

[[forwarding.destinations]]
name = "backup"
url = "https://api2.example.com"
token = "token2"
health_url = "https://api2.example.com/health"

When both [[forwarding.destinations]] and top-level url/token are set, the destinations array takes precedence.

Environment Variables

VariableDescription
APOLLON__FORWARDING__INTERVAL_SECSPolling interval in seconds (default: 60)
APOLLON__FORWARDING__URLSingle destination URL
APOLLON__FORWARDING__TOKENSingle destination API token (sent as Bearer)
APOLLON__FORWARDING__DEVICE_IDSingle destination device ID (sent as X-Device-ID header)
APOLLON__FORWARDING__HEALTH_URLSingle destination health check URL
APOLLON__FORWARDING__MAX_QUEUE_SIZEMax queued payloads per destination (default: 100)
APOLLON__FORWARDING__MAX_RETRIESRetry attempts per failed forward (default: 3)
APOLLON__FORWARDING__RETRY_DELAY_MSDelay between retries in ms (default: 5000)
APOLLON__FORWARDING__TIMEOUT_MSHTTP request timeout in ms (default: 10000)
note

Per-destination configuration via environment variables is limited. Use TOML config files for multi-destination setups.

Shared Settings

These settings apply to all destinations:

SettingDefaultDescription
interval_secs60GPS polling and forwarding interval
max_queue_size100Max queued payloads per destination (oldest dropped when full)
max_retries3Retry attempts per failed forward
retry_delay_ms5000Delay between retry attempts
timeout_ms10000HTTP request timeout

Payload Format

Each forward sends a POST request to {url}/v1/gps with:

  • Header: Authorization: Bearer {token}
  • Header: X-Device-ID: {device_id} (optional, only sent when configured)
  • Body: JSON with the following fields:
{
"latitude": 50.1234,
"longitude": 8.5678,
"altitude": 105.2,
"heading": 180.5,
"timestamp": 1718620800,
"speed_kmh": 12.5,
"speed_mps": 3.47,
"speed_mph": 7.77,
"speed_knots": 6.75,
"pdop": 1.8,
"hdop": 1.2,
"vdop": 1.4
}
FieldTypeDescription
latitudef64Latitude in decimal degrees
longitudef64Longitude in decimal degrees
altitudef64 | nullAltitude in meters
headingf64 | nullHeading in degrees (0-360)
timestampi64Unix timestamp (seconds)
speed_kmhf64Speed in km/h
speed_mpsf64Speed in m/s (raw from Peplink)
speed_mphf64Speed in mph
speed_knotsf64Speed in knots
pdopf64 | nullPosition dilution of precision
hdopf64 | nullHorizontal dilution of precision
vdopf64 | nullVertical dilution of precision

Retry Logic

Failed forwards are retried based on the HTTP status code:

  • Retryable (408, 429, 500, 502, 503, 504): Retried up to max_retries times with retry_delay_ms between attempts.
  • Non-retryable (4xx except 408/429): Dropped immediately.
  • Network errors: Retried like retryable status codes.

Each destination tracks failures independently. A destination is marked unhealthy after 5 consecutive failures — unless a health_url is configured, in which case the live health check result determines the status.

Health-Based Forwarding

When a destination has a health_url configured, the forwarding service performs periodic health checks and pauses forwarding to unhealthy destinations:

  • Healthy destinations: Health check every 60 seconds
  • Unhealthy destinations: Health check every 10 seconds (faster recovery detection)
  • While unhealthy: GPS data stays in the queue, nothing is sent
  • On recovery: The entire queued backlog is drained immediately — no GPS points are lost

Queue Drain Behavior

When a destination recovers, all queued items are sent sequentially in one cycle. Backlog items are logged with status catchup (instead of success) so they are distinguishable in the logs. If a send fails during drain, the remaining items stay in the queue for the next cycle.

Log Statuses

StatusDescription
successNormal GPS forward
catchupBacklog item sent after queue drain
health_upDestination recovered (state change)
health_downDestination unreachable, forwarding paused (state change)
retryingSend failed, will retry
failedSend failed permanently (max retries exceeded)

Health events (health_up / health_down) are only logged on state changes, not on every check.

Health Monitoring

When a destination has a health_url configured, the /v1/health endpoint performs a live GET request to that URL. A 2xx response means the destination is healthy. If no health_url is set, health is inferred from consecutive failure count (< 5 = healthy).

REST

GET /v1/health
{
"services": {
"forwarding": {
"enabled": true,
"healthy": true,
"destinations": [
{
"name": "primary",
"healthy": true,
"queueLength": 0,
"consecutiveFailures": 0,
"healthUrl": "https://api1.example.com/health",
"healthCheckOk": true,
"healthCheckResponseTimeMs": 8
}
]
}
}
}
FieldTypeDescription
healthUrlstring | absentOnly present when health_url is configured
healthCheckOkbool | nullLive health check result (true/false), null if no health_url
healthCheckResponseTimeMsnumber | absentResponse time of the health check in ms

GraphQL

query {
health {
forwardingEnabled
forwardingDestinations {
name
healthy
queueLength
consecutiveFailures
healthUrl
healthCheckOk
healthCheckResponseTimeMs
}
}
}

REST API

Status

GET /v1/forwarding/status

Returns per-destination health, queue depth, and failure count.

Logs

GET /v1/forwarding/logs?start=&end=&destination=&status=&limit=

Query forwarding log entries from TimescaleDB. Parameters:

  • start / end — Unix timestamps (default: last 24h)
  • destination — Filter by destination name
  • status — Filter by status (success, catchup, failed, retrying, health_up, health_down)
  • limit — Max entries (default: 100)

Stats

GET /v1/forwarding/stats?start=&end=&destination=

Returns hourly aggregated stats (total, success count, avg/max latency) per destination.

Queue

GET /v1/forwarding/queue

Returns current queue contents per destination (items with retries, age, coordinates).

DELETE /v1/forwarding/queue/{destination}

Clears the retry queue for a specific destination. Returns 204 on success, 404 if destination not found.

GraphQL API

Queries

query {
forwardingStatus { name healthy queueLength consecutiveFailures healthUrl healthCheckOk healthCheckResponseTimeMs }
forwardingLogs(start: Int, end: Int, destination: String, status: String, limit: Int) {
start end count logs { id timestamp destination status statusCode errorMessage postBody latencyMs retries }
}
forwardingStats(start: Int, end: Int, destination: String) {
start end buckets { bucket destination total successCount avgLatencyMs maxLatencyMs }
}
forwardingQueue { name count items { retries ageSecs latitude longitude } }
}

Mutations

mutation {
clearForwardingQueue(destination: "primary")
}

WebSocket Channel

Subscribe to the forwarding channel for live forwarding events:

{"action": "subscribe", "channels": ["forwarding"]}

Events:

{
"type": "forwarding",
"data": {
"destination": "primary",
"status": "success",
"statusCode": 200,
"latencyMs": 45,
"queueDepth": 0,
"consecutiveFailures": 0
}
}

Logging

When log_enabled = true (default), every forwarding attempt is logged to the forwarding_logs TimescaleDB hypertable. Logs are automatically cleaned up after log_retention_days (default: 30) by the cleanup scheduler.

ConfigDefaultDescription
log_enabledtrueLog forwarding attempts to TimescaleDB
log_retention_days30Auto-delete logs older than this

Graceful Shutdown

On shutdown, the forwarding service attempts to drain all remaining queue entries (best-effort). This ensures data collected before shutdown is forwarded if possible.

Troubleshooting

ProblemSolution
Forwarding disabledEnsure at least one destination has url and token set
5+ consecutive failuresCheck destination URL, token, and network connectivity
Queue filling upDestination may be unreachable or slow. Check max_queue_size and timeout_ms
Data not forwardedVerify interval_secs and check logs for GPS forwarding messages