WebSocket API
Plain WebSocket, UTF-8 JSON frames on ws://<host>:1616. No SDK needed.
Connection
| Scenario | URL |
|---|---|
| No auth | ws://raspberrypi.local:1616 |
| With auth | ws://raspberrypi.local:1616?token=<ws-token> |
Authenticated Connection
With --auth enabled: POST /auth with the 6-digit code → GET /auth/ws-token → connect with ?token=....
const dashboardBase = "http://raspberrypi.local:1617";
const wsBase = "ws://raspberrypi.local:1616";
async function connectAuthenticated(code: string): Promise<WebSocket> {
await fetch(`${dashboardBase}/auth`, {
method: "POST",
headers: { "Content-Type": "application/json" },
credentials: "include",
body: JSON.stringify({ code }),
});
const { token } = await fetch(`${dashboardBase}/auth/ws-token`, {
credentials: "include",
}).then((r) => r.json());
return new WebSocket(`${wsBase}?token=${encodeURIComponent(token)}`);
}Commands (Client → Server)
Filter
{"cmd": "set_filter", "enabled": true, "lowcut": 1.0, "highcut": 40.0}
{"cmd": "set_filter", "enabled": false}Recording
{"cmd": "start_record"}
{"cmd": "stop_record"}Webhooks
{"cmd": "webhook_list"}
{"cmd": "webhook_create", "rule": {
"name": "Alpha alert",
"trigger_type": "band_power_above",
"params": {"band": "alpha", "threshold": 20},
"url": "https://example.com/hook",
"method": "POST",
"headers": {"Authorization": "Bearer ..."},
"cooldown": 30
}}
{"cmd": "webhook_update", "rule": {"id": "abc123", "enabled": false}}
{"cmd": "webhook_delete", "id": "abc123"}
{"cmd": "webhook_test", "id": "abc123"}VRChat OSC
{"cmd": "osc_start"}
{"cmd": "osc_stop"}
{"cmd": "osc_configure", "host": "127.0.0.1", "port": 9000, "mode": "both", "interval": 0.25}
{"cmd": "osc_status"}Lab Streaming Layer
{"cmd": "lsl_start"}
{"cmd": "lsl_stop"}
{"cmd": "lsl_status"}Spike Filter
Configure the hardware delta-threshold spike rejection (SPI boards only):
{"cmd": "spike_config"}
{"cmd": "spike_config", "config": {"threshold": 200, "reset_after": 50}}Hampel Filter
Device-agnostic per-channel Hampel spike filter (works on all devices):
{"cmd": "hampel_config"}
{"cmd": "hampel_config", "config": {"enabled": true, "window_size": 5, "n_sigma": 3.0}}| Field | Type | Default | Description |
|---|---|---|---|
enabled | bool | true | Toggle the filter on/off |
window_size | int | 5 | Sliding window length (odd, ≥ 3) |
n_sigma | float | 3.0 | Outlier threshold in MAD units (≥ 1.0) |
Server responds with the current config including replaced_count.
ADS1299 Registers
Real-time register configuration for PiEEG boards. See Register Configuration for full details.
{"cmd": "reg_read"}
{"cmd": "reg_write", "regs": {"0x05": "0x00", "0x06": "0x01"}}
{"cmd": "reg_preset", "preset": "internal_short"}
{"cmd": "noise_test", "duration": 3}Presets: normal, internal_short, test_signal, temp_sensor.
Messages (Server → Client)
EEG Frame
Sent at 250 Hz:
{"t": 1711234567.123, "n": 42, "channels": [12.34, -5.67, 8.90, ...]}Welcome Message
Sent on connect:
{
"status": "connected",
"sample_rate": 250,
"channels": 16,
"filter": false,
"recording": false,
"engine": { "native": true, "engine": "pieeg-core", "version": "0.1.1" },
"spike_config": { "enabled": true, "threshold": 200 },
"hampel_config": { "enabled": true, "window_size": 5, "n_sigma": 3.0, "replaced_count": 0 }
}The channels field reflects the selected device: 16 for pieeg16 (default), 8 for pieeg8 or ironbci8.
The engine field reports which DSP implementation is active. When the
optional pieeg-core (opens in a new tab) wheel is
installed (pip install 'pieeg-server[fast]'), native is true and
version is the pieeg-core version. Otherwise engine is "python",
native is false, and version is null — the pure-Python fallback
is in use.
Record Status
{
"record_status": {
"recording": true,
"stopped": {
"filename": "pieeg_20260401_143136.csv",
"frames": 1000,
"duration": 4.0,
"path": "/path/to/file"
}
}
}Webhook Event
{
"webhook_event": {
"rule_id": "abc",
"value": 12.5,
"ts": 1711234567.123456
}
}