openbci
A pure-Rust driver for OpenBCI EEG/EMG boards. No C/C++ runtime, no BrainFlow dependency — communicates directly with the hardware over USB serial, Bluetooth LE, WiFi Shield, or UDP.
Supported boards
| Board | Channels | Interface | Struct |
|---|---|---|---|
| Cyton [1] | 8 EEG | USB serial (FTDI dongle) | board::cyton::CytonBoard |
| Cyton + Daisy [1] | 16 EEG | USB serial (FTDI dongle) | board::cyton_daisy::CytonDaisyBoard |
| Cyton WiFi [2] | 8 EEG | OpenBCI WiFi Shield → TCP | board::cyton_wifi::CytonWifiBoard |
| Cyton Daisy WiFi [2] | 16 EEG | OpenBCI WiFi Shield → TCP | board::cyton_daisy_wifi::CytonDaisyWifiBoard |
| Ganglion [3] | 4 EEG | Bluetooth LE (ble feature) |
board::ganglion::GanglionBoard |
| Ganglion WiFi [2][3] | 4 EEG | OpenBCI WiFi Shield → TCP | board::ganglion_wifi::GanglionWifiBoard |
| Galea [4] | 24 EEG+EMG | UDP | board::galea::GaleaBoard |
Quick start
Add to Cargo.toml:
[]
= "0.0.1"
Cyton — 8-channel EEG over USB
use CytonBoard;
use ;
use ;
use ;
// 1. Declare electrode placement (standard 10-20 site labels).
let layout = from_labels;
// 2. Create the board driver.
let mut board = new // Windows: "COM3"
.with_electrode_layout;
// 3. Connect: opens the serial port and waits for the "$$$" ready marker.
board.prepare.unwrap;
// 4. Optionally reconfigure the ADS1299 amplifier.
board.apply_all_channel_configs.unwrap;
// 5. Stream — the returned handle owns a background reader thread.
let stream = board.start_stream.unwrap;
for sample in stream.into_iter.take
// Dropping `stream` sends the stop signal to the reader thread.
// 6. Close the port.
board.release.unwrap;
Cyton + Daisy — 16-channel EEG
use CytonDaisyBoard;
use Board;
use ElectrodeLayout;
let mut board = new
.with_electrode_layout;
board.prepare.unwrap;
let stream = board.start_stream.unwrap;
for sample in stream.into_iter.take
Note: the Daisy firmware interleaves packets. The driver automatically pairs even (Daisy) and odd (Cyton) packets and emits 16-channel merged samples at ~125 Hz.
Ganglion — 4-channel EEG over Bluetooth LE
Requires the ble Cargo feature (enabled by default) and a Bluetooth adapter.
Cyton via WiFi Shield (auto-discovery)
use ;
use Board;
let cfg = CytonWifiConfig ;
let mut board = new;
board.prepare.unwrap; // discovers shield, opens TCP listener
let stream = board.start_stream.unwrap;
for sample in stream.into_iter.take
Channel configuration (ADS1299)
All Cyton-family boards implement ConfigurableBoard, which exposes per-channel
settings of the Texas Instruments ADS1299 [5] 24-bit biopotential
front-end:
use ;
use ConfigurableBoard;
// Standard EEG: 24× gain, normal input, in bias network, SRB2 reference.
let eeg = default
.gain
.input_type
.bias
.srb2;
// Noise floor measurement: shorted input.
let noise = default
.input_type
.bias
.srb2;
// Power a channel off entirely.
let off = default.power;
board.apply_channel_config.unwrap; // one channel
board.apply_all_channel_configs.unwrap; // all at once
board.reset_to_defaults.unwrap; // "d" → factory defaults
Gain options: 1×, 2×, 4×, 6×, 8×, 12×, 24×
Input types: Normal, Shorted, BiasMeas, Mvdd, Temp, TestSig, BiasDrp, BiasDrn
The ADS1299 µV scale factor for a given gain G is:
µV = raw_24bit × (4.5 V / 8,388,607 / G) × 1,000,000
Electrode placement and montage
ElectrodeLayout maps channel indices to electrode labels and 3-D head-surface
positions. The positions module exposes named constants for every site in the
three standard systems:
| System | Sites | Standard |
|---|---|---|
| 10-20 | 83 | Jasper (1958) [6] |
| 10-10 | 176 | American EEG Society (1994) [7] |
| 10-05 | 334 | Oostenveld & Praamstra (2001) [8] |
Positions are sourced from MNE-Python [9][10] in millimetres relative to the MNI head origin, converted to metres.
use ;
// Quick construction from label strings (aliases like "T3" resolved to "T7").
let layout = from_labels;
// Look up the 3-D head-surface position (metres, MNI coordinate frame).
let cz = position.unwrap;
println!;
// Montage sizes: 83 (10-20), 176 (10-10), 334 (10-05).
println!;
// Filter a layout to channels that have a known 10-20 position.
let subset = layout.subset_1020;
// Mix EEG and EMG channels in one layout.
let mixed = new
.with_electrode
.with_electrode
.with_electrode
.with_electrode;
Pre-built layouts
use ;
let layout_8ch = cyton_motor; // motor cortex + frontal (8 ch)
let layout_16ch = cyton_daisy_standard; // full 10-20 cap (16 ch)
let layout_4ch = ganglion_default; // frontal + occipital (4 ch)
The Sample type
Every board emits [Sample] values:
Streaming API
start_stream() returns a StreamHandle that can be used as a blocking
iterator, a non-blocking poller, or an explicit signaller:
// ── Blocking iterator ──────────────────────────────────────────────
for sample in board.start_stream.unwrap
// ── Non-blocking polling ───────────────────────────────────────────
let stream = board.start_stream.unwrap;
loop
// ── Explicit stop ──────────────────────────────────────────────────
let stream = board.start_stream.unwrap;
sleep;
stream.stop; // sends stop signal; drop() is a no-op afterwards
// ── Auto-stop on drop ──────────────────────────────────────────────
// reader thread stops here
board.release.unwrap;
Galea board
Galea [4] has 24 heterogeneous channels plus biometric sensors.
GaleaBoard decodes:
| Field | Meaning |
|---|---|
eeg[0..8] |
Upper-face EMG (µV, gain 4×) |
eeg[8..18] |
EEG 10-20 channels (µV, gain 12×) |
eeg[18..22] |
Auxiliary EMG (µV, gain 4×) |
GaleaSample::eda |
Skin conductance (volts) |
GaleaSample::ppg_red / ppg_ir |
PPG raw counts |
GaleaSample::temperature |
°C |
GaleaSample::battery |
% |
GaleaSample::accel / gyro / mag |
IMU (g / °/s / µT) when present |
Examples
Run from the repo root:
# Cyton over USB serial
# Cyton + Daisy
# Ganglion over BLE (optional MAC address filter)
# Cyton via WiFi Shield (optional IP)
# Galea
Cargo features
| Feature | Default | What it enables |
|---|---|---|
ble |
✅ | GanglionBoard via Bluetooth LE using btleplug + tokio |
Disable ble if you don't need Ganglion BLE support and want a smaller
dependency tree (no tokio, no btleplug):
[]
= { = "0.0.1", = false }
Platform notes
Linux
- BLE requires BlueZ (
bluetoothd) and thelibdbus-devsystem package. - Serial ports usually need the
dialoutgroup:sudo usermod -aG dialout $USER - Set FTDI latency timer to 1 ms for best performance:
echo 1 | sudo tee /sys/bus/usb-serial/devices/ttyUSB0/latency_timer
macOS
- BLE uses CoreBluetooth (no extra setup required).
- Serial dongle appears as
/dev/tty.usbserial-*or/dev/tty.usbmodem*. - Grant Bluetooth permission in System Preferences → Security & Privacy.
Windows
- BLE uses WinRT (Windows 10 v1703+ required).
- Serial dongle appears as
COM3(or whichever COM port Device Manager assigns). - In Device Manager → FTDI port → Properties → Latency Timer, set to 1 ms.
References
BibTeX citations for all references below are available in REFERENCES.bib.
[1] OpenBCI Inc. Cyton Biosensing Board (8-channel). OpenBCI Documentation, 2023. https://docs.openbci.com/Cyton/CytonLanding/
[2] OpenBCI Inc. WiFi Shield. OpenBCI Documentation, 2023. https://docs.openbci.com/Deprecated/WiFiShield/WiFiLanding/
[3] OpenBCI Inc. Ganglion Board. OpenBCI Documentation, 2023. https://docs.openbci.com/Ganglion/GanglionLanding/
[4] OpenBCI Inc. Galea: Biometric Interface for Extended Reality. https://galea.co/, 2020.
[5] Texas Instruments. ADS1299: Low-Noise, 8-Channel, 24-Bit Analog-to-Digital Converter for Biopotential Measurements. Data Sheet SBAS499C. Texas Instruments Incorporated, 2023. https://www.ti.com/product/ADS1299
[6] Jasper, H. H. (1958). The ten-twenty electrode system of the International Federation. Electroencephalography and Clinical Neurophysiology, 10, 371–375. Reprinted: American Journal of EEG Technology, 1(1), 13–19 (1961). https://doi.org/10.1080/00029238.1961.11080571
[7] American Electroencephalographic Society (1994). Guideline thirteen: Guidelines for standard electrode position nomenclature. Journal of Clinical Neurophysiology, 11(1), 111–113. https://doi.org/10.1097/00004691-199401000-00014
[8] Oostenveld, R., & Praamstra, P. (2001). The five percent electrode system for high-resolution EEG and ERP measurements. Clinical Neurophysiology, 112(4), 713–719. https://doi.org/10.1016/S1388-2457(00)00527-7
[9] Gramfort, A., Luessi, M., Larson, E., Engemann, D. A., Strohmeier, D., Brodbeck, C., Goj, R., Jas, M., Brooks, T., Parkkonen, L., & Hämäläinen, M. S. (2013). MEG and EEG data analysis with MNE-Python. Frontiers in Neuroscience, 7, Article 267, pp. 1–13. https://doi.org/10.3389/fnins.2013.00267
[10] Gramfort, A., Luessi, M., Larson, E., Engemann, D. A., Strohmeier, D., Brodbeck, C., Parkkonen, L., & Hämäläinen, M. S. (2014). MNE software for processing MEG and EEG data. NeuroImage, 86, 446–460. https://doi.org/10.1016/j.neuroimage.2013.10.027
[11] Komarov, A. BrainFlow: a library for obtaining, parsing and analyzing data from biosensors. GitHub, 2019–present. https://github.com/brainflow-dev/brainflow
Contributing
Issues and pull requests welcome at the repository. Please include a hardware type and firmware version when reporting bugs.
Citing
If you use this library in academic work, please cite it as:
Or in plain text:
Kosmyna, N. (2026). openbci: A Pure-Rust Driver for OpenBCI EEG/EMG Boards (v0.0.1). https://github.com/nataliyakosmyna/openbci
Licence
MIT — see LICENSE.