Skip to content

WebSocket API

aranet-service provides a WebSocket endpoint for receiving real-time sensor readings as they’re collected.

Connect to the WebSocket endpoint:

ws://localhost:8080/api/ws

Or with TLS (if configured):

wss://your-server:8080/api/ws

If API key authentication is enabled in your configuration, include the key in the connection URL or headers.

const ws = new WebSocket('ws://localhost:8080/api/ws?token=your-key');

Some WebSocket libraries support custom headers:

// Node.js with ws library
const WebSocket = require('ws');
const ws = new WebSocket('ws://localhost:8080/api/ws', {
headers: { 'X-API-Key': 'your-api-key' }
});
# Python with websockets
import websockets
async with websockets.connect(
"ws://localhost:8080/api/ws",
extra_headers={"X-API-Key": "your-api-key"}
) as ws:
...

Clients receive an initial snapshot of the latest reading for each device, followed by live updates. Both use the same payload shape:

{
"device_id": "Aranet4 17C3C",
"reading": {
"id": 42,
"device_id": "Aranet4 17C3C",
"co2": 750,
"temperature": 23.5,
"humidity": 48,
"pressure": 1013.25,
"battery": 85,
"status": "Green",
"captured_at": "2024-01-15T10:30:00Z",
"radon": null,
"radiation_rate": null,
"radiation_total": null
}
}
FieldTypeDescription
idnumberDatabase row ID for this stored reading
device_idstringDevice Bluetooth address or name
co2numberCO₂ concentration in ppm
temperaturenumberTemperature in °C
humiditynumberRelative humidity in %
pressurenumberAtmospheric pressure in hPa
batterynumberBattery level in % (0-100)
statusstringAir quality: “Green”, “Yellow”, “Red”, “Error”
captured_atstringISO 8601 timestamp (UTC)
radonnumber | nullRadon level in Bq/m³ (Aranet Rn+ only)
radiation_ratenumber | nullRadiation rate in µSv/h (Aranet Radiation only)
radiation_totalnumber | nullTotal radiation dose in mSv
const ws = new WebSocket('ws://localhost:8080/api/ws');
ws.onopen = () => {
console.log('Connected to Aranet WebSocket');
};
ws.onmessage = (event) => {
const data = JSON.parse(event.data);
console.log(`Device: ${data.device_id}`);
console.log(`CO2: ${data.reading.co2} ppm`);
console.log(`Temperature: ${data.reading.temperature}°C`);
};
ws.onerror = (error) => {
console.error('WebSocket error:', error);
};
ws.onclose = () => {
console.log('Disconnected from Aranet WebSocket');
// Implement reconnection logic here
};
import asyncio
import json
import websockets
async def listen_to_aranet():
uri = "ws://localhost:8080/api/ws"
async with websockets.connect(uri) as websocket:
print("Connected to Aranet WebSocket")
async for message in websocket:
data = json.loads(message)
reading = data["reading"]
print(f"Device: {data['device_id']}")
print(f" CO2: {reading['co2']} ppm")
print(f" Temperature: {reading['temperature']}°C")
print(f" Humidity: {reading['humidity']}%")
print()
if __name__ == "__main__":
asyncio.run(listen_to_aranet())
use futures_util::StreamExt;
use serde::Deserialize;
use tokio_tungstenite::connect_async;
#[derive(Debug, Deserialize)]
struct ReadingEvent {
device_id: String,
reading: Reading,
}
#[derive(Debug, Deserialize)]
struct Reading {
co2: u16,
temperature: f32,
humidity: u8,
pressure: f32,
battery: u8,
status: String,
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let url = "ws://localhost:8080/api/ws";
let (ws_stream, _) = connect_async(url).await?;
println!("Connected to Aranet WebSocket");
let (_, mut read) = ws_stream.split();
while let Some(msg) = read.next().await {
if let Ok(msg) = msg {
if let Ok(text) = msg.into_text() {
let event: ReadingEvent = serde_json::from_str(&text)?;
println!("Device: {}", event.device_id);
println!(" CO2: {} ppm", event.reading.co2);
println!(" Temp: {}°C", event.reading.temperature);
}
}
}
Ok(())
}
Terminal window
# Using websocat (install via cargo install websocat)
websocat ws://localhost:8080/api/ws
# With pretty printing
websocat ws://localhost:8080/api/ws | jq .

The WebSocket connection uses ping/pong frames for keep-alive. The server responds automatically to ping frames. Clients should:

  1. Send periodic ping frames (recommended: every 30 seconds)
  2. Implement reconnection logic for dropped connections

Implement exponential backoff for reconnection:

class AranetWebSocket {
constructor(url) {
this.url = url;
this.reconnectDelay = 1000;
this.maxDelay = 30000;
this.connect();
}
connect() {
this.ws = new WebSocket(this.url);
this.ws.onopen = () => {
console.log('Connected');
this.reconnectDelay = 1000; // Reset delay on success
};
this.ws.onmessage = (event) => {
const data = JSON.parse(event.data);
this.onReading(data);
};
this.ws.onclose = () => {
console.log(`Reconnecting in ${this.reconnectDelay}ms...`);
setTimeout(() => this.connect(), this.reconnectDelay);
this.reconnectDelay = Math.min(this.reconnectDelay * 2, this.maxDelay);
};
}
onReading(data) {
// Override this method to handle readings
console.log(data);
}
}
const client = new AranetWebSocket('ws://localhost:8080/api/ws');

Configure the broadcast buffer size in server.toml:

[server]
bind = "127.0.0.1:8080"
broadcast_buffer = 100 # Number of messages buffered

If clients are slow to process messages, they may miss readings when the buffer is full. Increase this value for slower clients.

<!DOCTYPE html>
<html>
<head>
<title>Aranet Monitor</title>
<style>
.reading { font-size: 2em; margin: 20px; }
.good { color: green; }
.moderate { color: orange; }
.poor { color: red; }
</style>
</head>
<body>
<div id="co2" class="reading">-- ppm</div>
<div id="temp" class="reading">--°C</div>
<script>
const ws = new WebSocket('ws://localhost:8080/api/ws');
ws.onmessage = (event) => {
const { reading } = JSON.parse(event.data);
const co2El = document.getElementById('co2');
co2El.textContent = `${reading.co2} ppm`;
co2El.className = 'reading ' +
(reading.co2 < 800 ? 'good' :
reading.co2 < 1000 ? 'moderate' : 'poor');
document.getElementById('temp').textContent =
`${reading.temperature.toFixed(1)}°C`;
};
</script>
</body>
</html>
  1. Add websocket in node
  2. Set URL to ws://localhost:8080/api/ws
  3. Connect to json node to parse
  4. Route to your automation flows