Skip to main content

muse_rs/
types.rs

1/// An EEG reading — one BLE notification from a single electrode.
2///
3/// # Classic firmware (Muse 1 / Muse 2 / Muse S ≤ fw 3.x)
4/// One notification per channel at 256 Hz, carrying **12 samples** each
5/// (≈ 46.9 ms of signal per packet).  Samples are decoded from 12-bit
6/// big-endian packed values and scaled by 0.48828125 µV/LSB.
7///
8/// # Athena firmware (Muse S fw ≥ 4.x)
9/// All channels arrive on a single universal characteristic.  Each notification
10/// is split into per-channel `EegReading`s carrying **2 samples** decoded from
11/// 14-bit little-endian packed values and scaled by 0.0885 µV/LSB.
12/// Channels 4–7 (FPz, AUX\_R, AUX\_L, AUX) are only produced by Athena hardware.
13#[derive(Debug, Clone)]
14pub struct EegReading {
15    /// Sequential packet index emitted by the headset (wraps at 0xFFFF).
16    ///
17    /// Used to reconstruct timestamps for Classic firmware.
18    /// Always `0` for Athena (Athena notifications do not carry a per-channel index).
19    pub index: u16,
20    /// Electrode channel index.
21    ///
22    /// **Classic (channels 0–4):**
23    /// * 0 = TP9 (left rear)
24    /// * 1 = AF7 (left front)
25    /// * 2 = AF8 (right front)
26    /// * 3 = TP10 (right rear)
27    /// * 4 = AUX (optional, `enable_aux: true` required)
28    ///
29    /// **Athena (channels 0–7):** same 0–3 as above, plus:
30    /// * 4 = FPz (frontal midline)
31    /// * 5 = AUX\_R
32    /// * 6 = AUX\_L
33    /// * 7 = AUX
34    pub electrode: usize,
35    /// Wall-clock timestamp in milliseconds since Unix epoch for the *first*
36    /// sample in this packet.
37    ///
38    /// **Classic:** extrapolated from `index` and the known sample rate;
39    /// accurate even under BLE jitter.
40    ///
41    /// **Athena:** always `0.0` — Athena packets do not carry a per-channel
42    /// index, so timestamp reconstruction is not possible with current data.
43    pub timestamp: f64,
44    /// Voltage samples in µV.
45    ///
46    /// **Classic:** 12 samples per packet at 256 Hz.  
47    /// **Athena:** 2 samples per packet at 256 Hz.
48    pub samples: Vec<f64>,
49}
50
51/// A PPG (photoplethysmography) reading from the optical heart-rate sensor.
52///
53/// Available on Muse 2 and Muse S only. Each notification carries 6 raw
54/// 24-bit samples at 64 Hz.
55#[derive(Debug, Clone)]
56pub struct PpgReading {
57    /// Sequential packet index (wraps at 0xFFFF), same purpose as [`EegReading::index`].
58    pub index: u16,
59    /// Optical channel:
60    /// * 0 = ambient (background light subtraction)
61    /// * 1 = infrared
62    /// * 2 = red
63    pub ppg_channel: usize,
64    /// Wall-clock timestamp in milliseconds since Unix epoch for the first sample.
65    pub timestamp: f64,
66    /// Raw 24-bit ADC values (not scaled to physical units).
67    /// 6 samples per notification at 64 Hz.
68    pub samples: Vec<u32>,
69}
70
71/// Battery and housekeeping telemetry packet.
72///
73/// Sent by the headset roughly once per second on both Classic and Athena
74/// firmware, though the wire format differs:
75///
76/// | Field | Classic | Athena |
77/// |---|---|---|
78/// | `sequence_id` | from packet | always `0` |
79/// | `battery_level` | u16 BE ÷ 512 | u16 LE ÷ 512 |
80/// | `fuel_gauge_voltage` | u16 BE × 2.2 mV | always `0.0` |
81/// | `temperature` | u16 BE raw ADC | always `0` |
82#[derive(Debug, Clone)]
83pub struct TelemetryData {
84    /// Monotonically increasing packet counter (wraps at 0xFFFF).
85    /// Always `0` for Athena notifications.
86    pub sequence_id: u16,
87    /// Battery state-of-charge in percent (0–100).
88    /// Derived from the raw fuel-gauge reading divided by 512.
89    pub battery_level: f32,
90    /// Fuel-gauge terminal voltage in millivolts (Classic only).
91    /// Raw reading multiplied by 2.2.  Always `0.0` for Athena.
92    pub fuel_gauge_voltage: f32,
93    /// Raw ADC temperature value (not converted to °C).
94    /// Always `0` for Athena (field not present in Athena battery packets).
95    pub temperature: u16,
96}
97
98/// A single 3-axis inertial measurement.
99#[derive(Debug, Clone, Copy)]
100pub struct XyzSample {
101    /// X-axis value in sensor-specific units (g for accelerometer, °/s for gyroscope).
102    pub x: f32,
103    /// Y-axis value.
104    pub y: f32,
105    /// Z-axis value.
106    pub z: f32,
107}
108
109/// A batch of inertial measurements from one BLE notification.
110///
111/// Both the accelerometer and gyroscope fire at ≈ 52 Hz and carry 3 XYZ
112/// samples per notification on Classic firmware.  Athena packs 3 samples
113/// as well but only the first is currently forwarded (indices 1 and 2 are
114/// copies of index 0 in the Athena decoder).
115///
116/// # Wire format differences
117///
118/// | Property | Classic | Athena |
119/// |---|---|---|
120/// | Integer type | `i16` big-endian | `i16` little-endian |
121/// | Accel scale | +0.0000610352 g/LSB | +0.0000610352 g/LSB |
122/// | Gyro scale | +0.0074768 °/s/LSB | −0.0074768 °/s/LSB (negated) |
123/// | `sequence_id` | from packet | always `0` |
124#[derive(Debug, Clone)]
125pub struct ImuData {
126    /// Monotonically increasing packet counter (wraps at 0xFFFF).
127    /// Always `0` for Athena notifications.
128    pub sequence_id: u16,
129    /// Three consecutive XYZ samples; index 0 is the oldest.
130    /// For Athena, indices 1 and 2 are currently copies of index 0.
131    pub samples: [XyzSample; 3],
132}
133
134/// A time-aligned multi-channel EEG snapshot.
135///
136/// Produced by code that zips per-electrode [`EegReading`]s together so every
137/// channel has a value for the same timestamp.
138#[derive(Debug, Clone)]
139#[allow(dead_code)]
140pub struct EegSample {
141    /// Packet index from the electrode that completed this sample set.
142    pub index: u16,
143    /// Wall-clock timestamp in milliseconds since Unix epoch.
144    pub timestamp: f64,
145    /// Voltage in µV for each channel in order `[TP9, AF7, AF8, TP10, AUX]`.
146    /// Channels not yet received are `f64::NAN`.
147    pub data: Vec<f64>,
148}
149
150/// A parsed Muse control/status response decoded from the control characteristic.
151///
152/// The headset replies to `v1` (device info), `s` (status), and similar
153/// commands with a JSON object split across several BLE packets.
154/// [`crate::parse::ControlAccumulator`] reassembles the fragments; this struct
155/// carries the final result.
156#[derive(Debug, Clone)]
157pub struct ControlResponse {
158    /// The raw, un-parsed JSON string.
159    pub raw: String,
160    /// Key-value pairs from the parsed JSON object.
161    pub fields: serde_json::Map<String, serde_json::Value>,
162}
163
164/// All data events emitted by [`crate::muse_client::MuseClient`].
165///
166/// Consumers receive these values through the `mpsc::Receiver` returned by
167/// [`crate::muse_client::MuseClient::connect`] or
168/// [`crate::muse_client::MuseClient::connect_to`].
169///
170/// # Firmware availability
171///
172/// | Variant | Classic | Athena |
173/// |---|---|---|
174/// | `Eeg` (ch 0–3) | ✓ | ✓ |
175/// | `Eeg` (ch 4–7) | ✗ | ✓ |
176/// | `Ppg` | ✓ (opt-in) | ✗ |
177/// | `Accelerometer` | ✓ | ✓ |
178/// | `Gyroscope` | ✓ | ✓ |
179/// | `Telemetry` | ✓ | ✓ |
180/// | `Control` | ✓ | ✓ |
181#[derive(Debug, Clone)]
182pub enum MuseEvent {
183    /// An EEG packet from one electrode channel.
184    ///
185    /// Produced for channels 0–3 on both Classic and Athena.
186    /// Channels 4–7 (FPz, AUX\_R, AUX\_L, AUX) are Athena-only.
187    /// See [`EegReading`] for per-firmware format differences.
188    Eeg(EegReading),
189    /// A PPG (photoplethysmography) optical packet.
190    ///
191    /// Classic requires `enable_ppg: true` in [`crate::muse_client::MuseClientConfig`].
192    /// Athena optical data is always included with preset `p1045`.
193    Ppg(PpgReading),
194    /// Battery and housekeeping telemetry (~1 Hz).
195    ///
196    /// Produced by both Classic and Athena; some fields are `0` on Athena.
197    /// See [`TelemetryData`] for per-firmware field availability.
198    Telemetry(TelemetryData),
199    /// Accelerometer batch (~52 Hz).
200    ///
201    /// Produced by both Classic and Athena.  The gyroscope sign convention and
202    /// byte order differ between firmwares; see [`ImuData`] for details.
203    Accelerometer(ImuData),
204    /// Gyroscope batch (~52 Hz).
205    ///
206    /// Produced by both Classic and Athena.  Athena gyro values are negated
207    /// relative to Classic (−0.0074768 vs +0.0074768 °/s/LSB).
208    Gyroscope(ImuData),
209    /// A complete JSON control/status response from the headset.
210    ///
211    /// Produced by both Classic and Athena in response to commands such as
212    /// `v1` (device info) or `s` (status).
213    Control(ControlResponse),
214    /// The BLE link has been established and GATT services discovered.
215    /// The inner `String` is the advertised device name (e.g. `"Muse-AB12"`).
216    Connected(String),
217    /// The BLE link was lost (headset turned off, out of range, etc.).
218    ///
219    /// After receiving this event the channel will be closed; no further
220    /// events will arrive.  The TUI and CLI both restart scanning automatically
221    /// when this event is seen.
222    Disconnected,
223}