Skip to main content

WebSocket API

The Apollon API provides a WebSocket endpoint for real-time data broadcasts from the Peplink GPS router. Clients subscribe to named channels and receive structured JSON messages.

Endpoint

GET /ws?token=<token>

Upgrades to a WebSocket connection. Requires a valid API token as query parameter. Returns 503 Service Unavailable if the server is shutting down.

Channels

ChannelModeDescription
speedPolledSpeed in 4 units (km/h, m/s, mph, knots)
gpsPolledLocation, heading, and DOP (dilution of precision)
tripsEvent-drivenTrip lifecycle events (started, completed, cancelled)

Connection Flow

Client Server
| |
| --- WebSocket Upgrade (?token=xxx) --> |
| <-- 101 Switching Protocols ---------- |
| |
| --- Subscribe ["speed","gps"] -------> |
| <-- Ack { subscribed: [...] } -------- |
| |
| <-- Speed message ------------------- | (on change, at interval)
| <-- GPS message --------------------- | (on change, at interval)
| <-- Trip event ---------------------- | (when trip state changes)
| |
| <-- Ping ---------------------------- | (every 30 seconds)
| --- Pong ---------------------------> |
| |
| --- Unsubscribe ["speed"] ----------> |
| <-- Ack { unsubscribed: [...] } ----- |
| |
| --- Close --------------------------> |
| <-- Close --------------------------- |

Subscribe / Unsubscribe Protocol

Subscribe

Send a JSON message to subscribe to one or more channels:

{
"action": "subscribe",
"channels": ["speed", "gps"]
}

The server responds with an acknowledgement:

{
"type": "subscribed",
"channels": ["speed", "gps"]
}

Wildcard Subscribe

Subscribe to all available channels at once:

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

Unsubscribe

{
"action": "unsubscribe",
"channels": ["speed"]
}

The server responds:

{
"type": "unsubscribed",
"channels": ["speed"]
}

Error Handling

If a client sends an invalid message or references an unknown channel, the server responds with an error:

{
"type": "error",
"message": "Unknown channel: invalid_channel"
}

Errors do not close the connection. The client can continue sending valid messages.

Message Formats

Speed Channel

Broadcast when the speed value changes.

{
"type": "speed",
"data": {
"speed_kmh": 45.2,
"speed_mps": 12.56,
"speed_mph": 28.09,
"speed_knots": 24.41
}
}
FieldTypeDescription
speed_kmhf64Current speed in km/h
speed_mpsf64Current speed in meters per second
speed_mphf64Current speed in miles per hour
speed_knotsf64Current speed in knots

GPS Channel

Broadcast when the GPS position or heading changes.

{
"type": "gps",
"data": {
"latitude": 52.5200,
"longitude": 13.4050,
"altitude": 34.5,
"heading": 182.3,
"pdop": 1.2,
"hdop": 0.9,
"vdop": 0.8,
"timestamp": "2026-06-10T12:34:56Z"
}
}
FieldTypeDescription
latitudef64Latitude in decimal degrees
longitudef64Longitude in decimal degrees
altitudef64Altitude in meters above sea level
headingf64Heading in degrees (0-360)
pdopf64Position dilution of precision
hdopf64Horizontal dilution of precision
vdopf64Vertical dilution of precision
timestampstringISO 8601 timestamp of the GPS fix

Trips Channel

Event-driven messages when trip state changes.

{
"type": "trips",
"data": {
"event": "started",
"tripId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"trip": {
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"status": "ACTIVE",
"senderId": "...",
"receiverId": "..."
}
}
}
FieldTypeDescription
eventstringEvent type: started, completed, cancelled
tripIdstringUUID of the affected trip
tripobjectTrip snapshot at the time of the event

Configuration

WebSocket behavior is configured via the websocket section in the config file or APOLLON__WEBSOCKET__* environment variables:

Config KeyEnv VarDefaultDescription
websocket.pathAPOLLON__WEBSOCKET__PATH/wsWebSocket endpoint path
websocket.speed_interval_msAPOLLON__WEBSOCKET__SPEED_INTERVAL_MS1000Speed broadcast interval in milliseconds
websocket.gps_interval_msAPOLLON__WEBSOCKET__GPS_INTERVAL_MS1000GPS broadcast interval in milliseconds

JavaScript Client Example

const ws = new WebSocket('ws://localhost:3000/ws?token=YOUR_API_TOKEN');

ws.onopen = () => {
console.log('Connected to Apollon WebSocket');

// Subscribe to speed and GPS channels
ws.send(JSON.stringify({
action: 'subscribe',
channels: ['speed', 'gps', 'trips'],
}));
};

ws.onmessage = (event) => {
const message = JSON.parse(event.data);

switch (message.type) {
case 'subscribed':
console.log('Subscribed to:', message.channels);
break;
case 'speed':
console.log(`Speed: ${message.data.speed_kmh} km/h`);
break;
case 'gps':
console.log(`Position: ${message.data.latitude}, ${message.data.longitude}`);
break;
case 'trips':
console.log(`Trip ${message.data.event}: ${message.data.tripId}`);
break;
case 'error':
console.error('Server error:', message.message);
break;
}
};

ws.onclose = (event) => {
console.log(`Disconnected: code=${event.code}`);
};

ws.onerror = (error) => {
console.error('WebSocket error:', error);
};

Architecture

PeplinkRouter / MockProvider
|
v
GPS Provider (shared)
| |
v v
SpeedBroadcaster GpsBroadcaster (poll GPS at interval)
| |
v v
speed::broadcast gps::broadcast (tokio broadcast channels, capacity: 256)
| |
+------+------+ TripBroadcaster (event-driven)
| |
v v
WsServer ---- trips::broadcast (tokio broadcast channel)
| |
Client Client (each subscribes to selected channels)

SpeedBroadcaster

Runs as a tokio task that polls the GPS provider at the configured interval, compares the new speed to the last broadcast, and only sends if the value changed. Stores the last broadcast for greeting new subscribers.

GpsBroadcaster

Runs as a tokio task that polls the GPS provider at the configured interval, compares the new position/heading/DOP to the last broadcast, and only sends if any value changed. Stores the last broadcast for greeting new subscribers.

TripBroadcaster

Publishes trip lifecycle events (started, completed, cancelled) to the trips broadcast channel. Events are fired by the trip management endpoints when trip state changes.

WsServer

Manages client connections, channel subscriptions, and broadcast distribution. Rejects new connections during shutdown (503). Each client connection spawns a tokio task that listens for broadcasts on subscribed channels, pings, and client messages concurrently via tokio::select!.