Self-Hosted Biosignal Streaming Server
One process acquires 8 to 32 channels over SPI, Bluetooth LE, or USB serial and emits one JSON object per sample on WebSocket :1616. A React dashboard is served on :1617. Optional LSL, OSC, and HTTP webhooks attach to the same loop. MIT licensed.
{ "t": 1711234567.123, "n": 18420, "channels": [12.40, -5.11, 8.02, 15.60, …] }[01 · SERVER ARCHITECTURE]
Trace the sample from ADC to JSON, LSL, and OSC
Pick a subsystem to inspect how acquisition, the WebSocket contract, the dashboard, and optional outlets share one loop. Hover any specification row to highlight its nodes.
Live output preview
Values in the live panel are simulated to illustrate the runtime contract of each server surface.
[02 · INSTALL, DEVICES & WEBSOCKET]
One package. A device flag. JSON on :1616.
Python 3.10+ on Raspberry Pi, Jetson, Linux, macOS, or Windows. The dashboard is already inside the wheel. Node is not required unless you are editing dashboard/src.
pip install pieeg-server pieeg-server --mock
PiEEG-16
16 ch · SPIDefault device. Raspberry Pi shield over SPI. First boot: enable SPI, then reboot.
$ pieeg-server
WebSocket contract
import asyncio, json, websockets
async def main():
async with websockets.connect("ws://raspberrypi.local:1616") as ws:
async for msg in ws:
frame = json.loads(msg)
# frame = {"t": 1711234567.123, "n": 42, "channels": [12.34, -5.67, ...]}
print(f"#{frame['n']}: {len(frame['channels'])} ch @ {frame['t']}")
asyncio.run(main())const ws = new WebSocket("ws://raspberrypi.local:1616");
ws.onmessage = (e) => {
const frame = JSON.parse(e.data);
// frame.channels = µV per channel
// frame.t = Unix time (seconds)
// frame.n = monotonic sample index
console.log(`#${frame.n}: ${frame.channels.length} channels`);
};tUnix timestamp (seconds, float)
nMonotonic sample index
channelsArray of microvolts, one per ADC channel
Filter
Butterworth bandpass, live toggle
{ "cmd": "set_filter", "enabled": true, "lowcut": 1.0, "highcut": 40.0 }Record
CSV start / stop
{ "cmd": "start_record" }
{ "cmd": "stop_record" }ADS1299
Registers and noise test
{ "cmd": "reg_read" }
{ "cmd": "reg_write", "regs": { "0x05": "0x00" } }
{ "cmd": "noise_test", "duration": 3 }LSL
Outlet and channel groups
{ "cmd": "lsl_start" }
{ "cmd": "lsl_groups_set", "groups": [
{ "name": "EEG", "channels": [0, 1, 2, 3] }
] }OSC
VRChat chatbox / parameters
{ "cmd": "osc_configure", "host": "127.0.0.1", "port": 9000, "mode": "both" }
{ "cmd": "osc_start" }Webhooks
HTTP callbacks on EEG events
{ "cmd": "webhook_create", "rule": { "trigger_type": "amplitude_above", "params": { "channel": 0, "threshold": 150 } } }[03 · MIT LICENSE & ECOSYSTEM]
Software is MIT.
pieeg-server, the dashboard source, and pieeg.js are published under the MIT license. You may use, modify, and redistribute that code, including in commercial products, subject to the license text in the repository.
What MIT covers
The Python package, WebSocket protocol, committed dashboard build, pieeg.js, and examples in pieeg-club/PiEEG-server. LICENSE file in that repo is the source of truth.
pieeg.js
Zero-dependency browser library in the same repo. connectBLE() / connectSerial() must run from a user gesture. CDN: jsDelivr PiEEG-server@main/pieeg.js.
Local by default
Acquisition, filtering, recording, and the dashboard run on the machine that started the process. Cloud relay, session upload, and Buddy keys are separate, opt-in surfaces. --auth is a local 6-digit gate, not a cloud account.
Lab Streaming Layer
Raw samples on the LAN. OpenViBE, MNE-LSL, LabRecorder, BCI2000, NeuroPype.
--lslOSC
Band powers to UDP :9000. Chatbox and /avatar/parameters, including per-region groups.
--oscWebhooks
HTTP POST when a band, ratio, or amplitude rule matches. Dashboard wizard for IFTTT and Zapier.
webhooks.jsonJupyter
CSV on disk, or stream live. notebooks/ in the repo. Agent can generate notebooks over LSL.
notebooks/| Layer | pieeg-server | PiEEG Cloud |
|---|---|---|
| Installation | pip install pieeg-server (Python 3.10+) | Open cloud.pieeg.com in Chrome, Edge, Brave, or Opera |
| Hardware input | SPI shields (PiEEG-8/16, JNEEG) plus BLE and USB serial boards | Web Bluetooth (IronBCI 8/16, XR, Octopus-16); Web Serial (IronBCI-32, Kraken-48) |
| Where DSP runs | Optional server-side IIR; dashboard still does client-side FFT | Client-side in the tab (Hampel, Butterworth IIR, Worker FFT) |
| Local app output | Native LSL (--lsl), OSC (--osc), HTTP webhooks | WebRTC P2P to Local Bridge → UDP OSC |
| AI copilot | PiEEG Agent over LSL + MCP (pieeg-server --lsl, then pieeg-agent mcp) | Buddy at buddy.pieeg.com (in-tab, read-only tools) |
| License | MIT (pieeg-server, pieeg.js, dashboard source) | Dashboard UI MIT; hosted relay is opt-in |
Start the process, then open :1617
No board: pieeg-server --mock. Hardware: pass --device. The dashboard and the WebSocket share that process.