mendi
Async Rust library and CLI for streaming fNIRS neurofeedback data from Mendi headbands over Bluetooth Low Energy.
Hardware
The Mendi headband is a consumer fNIRS (functional near-infrared spectroscopy) device that measures blood oxygenation in the prefrontal cortex using infrared and red LEDs. It communicates over BLE using protobuf-encoded messages on six GATT characteristics:
| UUID | Name | Data |
|---|---|---|
| 0xABB1 | Frame | IMU (accel + gyro) + temperature + 3 optical channels |
| 0xABB2 | Sensor | Optical sensor register read/write |
| 0xABB3 | IMU | IMU register read/write |
| 0xABB4 | ADC | Battery voltage, charging status, USB status |
| 0xABB5 | Diagnostics | Self-test results (IMU ok, sensor ok) |
| 0xABB6 | Calibration | LED current offsets, auto-calibration, low-power mode |
Each optical channel (left, right, pulse) provides three readings:
- IR (infrared) — penetrates tissue to measure oxygenated hemoglobin
- Red — measures deoxygenated hemoglobin
- Ambient — background light for subtraction
Quick start
use *;
async
CLI usage
# Run the CLI (scans, connects, and prints all data)
# With debug logging
RUST_LOG=mendi=debug
Interactive commands once connected:
q– quitc– write default calibration (auto-cal on)e– enable optical sensord– disable optical sensor
TUI (real-time charts)
# Real device
# Simulated (no hardware needed)
Keys: 1 IR view, 2 full view, +/- scale, a auto-scale,
v smooth toggle, p pause, r resume, c clear, q quit
Using as a library
[]
= "0.1.0"
# With simulation/mock support for testing:
= { = "0.1.0", = ["simulate"] }
Features
| Feature | Default | Description |
|---|---|---|
simulate |
No | Simulated and mock devices for testing without hardware |
tui |
No | Terminal UI with real-time charts (implies simulate) |
regenerate-proto |
No | Rebuild protobuf Rust types from proto/device_v4.proto (requires protoc) |
By default, no extra features are enabled and no system dependencies are required beyond a Rust toolchain.
Protocol details
The wire protocol is defined in proto/device_v4.proto (protobuf v3). All
characteristics use protobuf encoding. The Frame characteristic (0xABB1) is
the primary data stream, delivering real-time sensor readings at the headband's
native sample rate.
Protobuf code generation
The Rust types for the wire protocol live in the wire module. By default,
pre-generated code is used (src/wire_generated.rs), so you do not
need protoc or the Protocol Buffers compiler installed to build this crate.
If you modify proto/device_v4.proto and want to regenerate the Rust types,
enable the regenerate-proto feature:
This requires protoc to be installed on your system (install instructions).
After regenerating, copy the output from target/debug/build/mendi-*/out/mendi.rs
to src/wire_generated.rs to update the bundled version:
Frame fields
| Field | Type | Description |
|---|---|---|
| acc_x/y/z | i32 | Accelerometer (±2G default, raw int16) |
| ang_x/y/z | i32 | Gyroscope (±125°/s default, raw int16) |
| temp | f32 | Temperature in °C |
| ir_l/r/p | i32 | Infrared readings (left, right, pulse) |
| red_l/r/p | i32 | Red LED readings (left, right, pulse) |
| amb_l/r/p | i32 | Ambient light readings (left, right, pulse) |
Convenience methods on FrameReading convert raw IMU values to physical units:
accel_x_g(),accel_y_g(),accel_z_g()— acceleration in ggyro_x_dps(),gyro_y_dps(),gyro_z_dps()— angular velocity in °/s
UUID namespace
Mendi uses a vendor-specific UUID base: fc3eXXXX-c6c4-49e6-922a-6e551c455af5.
Use mendi_uuid(short) to construct UUIDs from 16-bit short codes.
Simulation & testing (feature simulate)
The simulate feature adds two test utilities that don't require hardware:
SimulatedDevice
Generates realistic fNIRS data with sinusoidal optical signals, IMU noise, and periodic battery/calibration events:
use ;
use MendiEvent;
let config = SimConfig ;
let = start;
while let Some = rx.recv.await
Run the simulator binary:
MockDevice
Deterministic scripted mock for unit tests — no timing, no randomness:
use MockDevice;
use MendiEvent;
let = with_frames;
// Receives: Connected, 10 Frames, Disconnected
MendiHandle API
Once connected, the MendiHandle provides:
| Method | Description |
|---|---|
write_calibration(...) |
Set LED current offsets and auto-calibration mode |
enable_sensor() |
Start optical sensor data (some FW versions require this) |
disable_sensor() |
Stop optical sensor data |
write_sensor_register(addr, data) |
Low-level optical sensor register write |
read_sensor_register(addr) |
Low-level optical sensor register read |
write_imu_register(addr, data) |
Low-level IMU register write |
read_imu_register(addr, n) |
Low-level IMU register read |
is_connected() |
Check BLE connection status |
disconnect() |
Gracefully disconnect |
License
MIT