1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
//! # mendi
//!
//! Async Rust library for streaming fNIRS neurofeedback data from
//! [Mendi](https://www.mendi.io/) 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. It communicates
//! over BLE using protobuf-encoded messages on six GATT characteristics:
//!
//! | UUID | Characteristic | Data |
//! |--------|---------------|------|
//! | 0xABB1 | Frame | IMU + temperature + 3 optical channels (IR/red/ambient) |
//! | 0xABB2 | Sensor | Optical sensor register read/write |
//! | 0xABB3 | IMU | IMU register read/write |
//! | 0xABB4 | ADC | Battery voltage, charging, USB status |
//! | 0xABB5 | Diagnostics | Self-test results at power-on |
//! | 0xABB6 | Calibration | LED current offsets, auto-cal, low-power mode |
//!
//! ## Quick start
//!
//! ```no_run
//! use mendi::prelude::*;
//!
//! #[tokio::main]
//! async fn main() -> anyhow::Result<()> {
//! let client = MendiClient::new(MendiClientConfig::default());
//! let (mut rx, handle) = client.connect().await?;
//!
//! while let Some(event) = rx.recv().await {
//! match event {
//! MendiEvent::Frame(f) => {
//! println!("temp={:.1}°C IR: L={} R={} P={}",
//! f.temperature, f.ir_left, f.ir_right, f.ir_pulse);
//! }
//! MendiEvent::Battery(b) => {
//! println!("Battery: {:.2}V charging={}", b.voltage(), b.charging);
//! }
//! MendiEvent::Disconnected => break,
//! _ => {}
//! }
//! }
//! Ok(())
//! }
//! ```
//!
//! ## Module overview
//!
//! | Module | Purpose |
//! |--------|---------|
//! | [`mendi_client`] | BLE scanning, connecting, and the [`mendi_client::MendiHandle`] API |
//! | [`types`] | All event and data types produced by the client |
//! | [`protocol`] | GATT UUIDs and BLE constants |
//! | [`parse`] | Protobuf decoders for BLE notification payloads |
//! | [`wire`] | Generated protobuf types from `device_v4.proto` |
//!
//! ## Protobuf code generation
//!
//! The [`wire`] module contains Rust types generated from `proto/device_v4.proto`
//! using [`prost`]. By default, **pre-generated code** is bundled in
//! `src/wire_generated.rs` so you do **not** need `protoc` installed.
//!
//! To regenerate from the `.proto` file (e.g. after modifying it), enable the
//! `regenerate-proto` feature:
//!
//! ```bash
//! cargo build --features regenerate-proto
//! ```
//!
//! This requires the `protoc` compiler to be available on your `PATH`.
//! After regenerating, copy the output to `src/wire_generated.rs` to update
//! the bundled version:
//!
//! ```bash
//! cp target/debug/build/mendi-*/out/mendi.rs src/wire_generated.rs
//! ```
//!
//! ## Feature flags
//!
//! | Feature | Description |
//! |---------|-------------|
//! | `simulate` | Simulated + mock devices for testing without hardware |
//! | `tui` | Terminal UI with real-time charts (implies `simulate`) |
//! | `regenerate-proto` | Rebuild protobuf types from `.proto` (requires `protoc`) |
/// Simulated and mock Mendi devices for testing without hardware.
///
/// Requires `--features simulate`.
///
/// - [`simulate::SimulatedDevice`] — generates realistic fNIRS data with
/// sinusoidal optical signals, IMU noise, and periodic battery/calibration.
/// - [`simulate::MockDevice`] — deterministic scripted mock for unit tests.
/// Generated protobuf types from the Mendi V4 wire protocol.
///
/// By default the pre-generated code in `src/wire_generated.rs` is used.
/// Enable the `regenerate-proto` feature (requires `protoc`) to rebuild from
/// `proto/device_v4.proto`.
/// Convenience re-exports.
///
/// ```no_run
/// use mendi::prelude::*;
/// ```