Skip to main content

Crate Architecture

The firmware is a Rust workspace split into five crates with strict layering — no circular dependencies.
CrateOwnsDepends On
eeg_typesPacket, ChannelId, AdcConfig, error enums
sensorsRegister maps, low-level SPI/I2C for a single chipeeg_types
boardsGlue that unites one or more chips into a PCB driver (impl EegDriver)sensors, eeg_types
pipelineDSP graph, processing stages, sinkseeg_types
daemonCLI, config, picks a board driver and pumps packets into the pipelineboards, pipeline, eeg_types
The daemon is the only binary crate. Everything else is a library.

Plugin System

Plugins are event-driven and run in parallel across CPU cores. Each plugin is a separate Rust crate in the plugins/ directory.

Writing a Plugin

Every plugin implements the EegPlugin trait:
#[async_trait]
impl EegPlugin for MyPlugin {
    fn name(&self) -> &'static str { "my_plugin" }
    fn clone_box(&self) -> Box<dyn EegPlugin> { Box::new(self.clone()) }

    async fn run(
        &mut self,
        bus: Arc<dyn EventBus>,
        mut receiver: broadcast::Receiver<SensorEvent>,
        shutdown_token: CancellationToken,
    ) -> Result<()> {
        loop {
            tokio::select! {
                biased;
                _ = shutdown_token.cancelled() => break,
                Ok(event) = receiver.recv() => {
                    // process event, then broadcast results back to bus
                }
            }
        }
        Ok(())
    }
}
Plugins can optionally declare an event_filter to subscribe only to relevant event types, avoiding unnecessary wake-ups.

Included Plugins

PluginPurpose
basic_voltage_filterVoltage scaling and filtering
brain_waves_fftFFT spectral decomposition with browser UI
csv_recorderRecord sessions to CSV files

WebSocket API

The daemon exposes a centralized WebSocket endpoint for real-time data streaming with a topic-based pub/sub model. Endpoint: ws://localhost:9000/ws/data

Subscribe to a Topic

{ "type": "subscribe", "topic": "eeg_voltage", "epoch": 1 }

Unsubscribe

{ "type": "unsubscribe", "topic": "eeg_voltage" }
Data frames are binary (RtPacket format) for high-performance streaming. A single client can hold multiple topic subscriptions simultaneously.

Control Plane (REST + SSE)

In addition to the WebSocket data plane, the daemon provides HTTP endpoints on the same port:
EndpointMethodPurpose
/api/pipelinesGETList available pipeline definitions
/api/pipelines/{id}/startPOSTStart a pipeline
/api/pipeline/stopPOSTStop the running pipeline
/api/stateGETCurrent runtime configuration snapshot
/api/eventsGETSSE stream of state updates
/api/pipelines/{id}/controlPOSTSend runtime commands (e.g. SetParameter)

Board Configurations

V1 — Single ADS1299

One ADS1299 EVM connected directly to the Pi 5 via SPI. Provides 8 channels with buffered reference (SRB1) and fixed internal bias drive.
SPI SignalADS1299 Pin (J3)Pi 5 Pin
CSPin 1Pin 24 (CE0)
SCLKPin 3Pin 23
MOSI (DIN)Pin 11Pin 19
MISO (DOUT)Pin 13Pin 21
DRDYPin 15Pin 22 (GPIO25)

V2 — Four Synchronized ADS1299 Boards

Star topology with one master board and three secondaries sharing clock, MOSI, SCLK, and MISO. Each board has a dedicated chip-select and DRDY line. Only the master board (Board 0) drives the bias electrode.
SignalPi 5 PinNotes
CS_A (Board 0)Pin 24 (CE0)Hardware CS
CS_B (Board 1)Pin 26 (CE1)Hardware CS
CS_C (Board 2)Pin 29 (GPIO5)Software CS
CS_D (Board 3)Pin 31 (GPIO6)Software CS
START (all boards)Pin 15 (GPIO22)Sync pulse
DRDY (Board 0)Pin 22 (GPIO25)Falling-edge interrupt

Next

EEG Overview

Hardware BOM and quickstart

SDK — eeg-web-ble

Connect to EEG headbands over Web Bluetooth