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
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
//! GATT UUIDs, sampling constants, and BLE wire-format helpers for Muse headsets.
//!
//! All UUIDs belong to the Muse vendor namespace
//! `273eXXXX-4c4d-454d-96be-f03bac821358`.
use Uuid;
// ── Service ──────────────────────────────────────────────────────────────────
/// Primary GATT service UUID advertised by all Muse devices.
///
/// Used as a scan filter to identify Muse headsets among nearby BLE peripherals.
pub const MUSE_SERVICE_UUID: Uuid = from_u128;
// ── Characteristics ───────────────────────────────────────────────────────────
/// Bidirectional control channel.
///
/// The host writes length-prefixed ASCII commands (e.g. `"d"`, `"h"`, `"p21"`)
/// and receives JSON status fragments in response notifications.
/// See [`encode_command`] and [`decode_response`] for the wire format.
pub const CONTROL_CHARACTERISTIC: Uuid =
from_u128;
/// Telemetry characteristic (battery level, temperature, fuel-gauge voltage).
///
/// Notified approximately once per second on classic Muse devices.
pub const TELEMETRY_CHARACTERISTIC: Uuid =
from_u128;
/// Gyroscope characteristic — 3 × XYZ samples per notification at ~52 Hz.
pub const GYROSCOPE_CHARACTERISTIC: Uuid =
from_u128;
/// Accelerometer characteristic — 3 × XYZ samples per notification at ~52 Hz.
pub const ACCELEROMETER_CHARACTERISTIC: Uuid =
from_u128;
/// EEG per-channel characteristics, indexed by electrode:
///
/// | Index | UUID suffix | Electrode |
/// |-------|-------------|-----------|
/// | 0 | 0003 | TP9 |
/// | 1 | 0004 | AF7 |
/// | 2 | 0005 | AF8 |
/// | 3 | 0006 | TP10 |
/// | 4 | 0007 | AUX |
///
/// Subscribe to the first 4 for standard 4-channel EEG; add index 4 for the
/// auxiliary input (enabled via [`crate::muse_client::MuseClientConfig::enable_aux`]).
pub const EEG_CHARACTERISTICS: = ;
/// Athena (new Muse S firmware) universal sensor characteristic.
///
/// All sensor data (EEG, IMU, optical, battery) is multiplexed on this single
/// characteristic using tag-based binary packets. Classic Muse devices do not
/// expose this characteristic; its presence is used as the Athena detector in
/// [`crate::muse_client::MuseClient`].
pub const ATHENA_SENSOR_CHARACTERISTIC: Uuid =
from_u128;
/// PPG (optical heart-rate) characteristics, indexed by channel:
///
/// | Index | UUID suffix | Channel |
/// |-------|-------------|------------|
/// | 0 | 000f | ambient |
/// | 1 | 0010 | infrared |
/// | 2 | 0011 | red |
///
/// Available on Muse 2 and Muse S only. Enable via
/// [`crate::muse_client::MuseClientConfig::enable_ppg`].
pub const PPG_CHARACTERISTICS: = ;
// ── Sampling constants ────────────────────────────────────────────────────────
/// EEG sample rate in Hz (256 samples per second per channel).
pub const EEG_FREQUENCY: f64 = 256.0;
/// Number of EEG voltage samples packed into one classic Muse BLE notification.
///
/// At 256 Hz, 12 samples represent ≈ 46.9 ms of signal per packet.
pub const EEG_SAMPLES_PER_READING: usize = 12;
/// PPG sample rate in Hz (64 samples per second per optical channel).
pub const PPG_FREQUENCY: f64 = 64.0;
/// Number of raw 24-bit PPG values packed into one BLE notification.
///
/// At 64 Hz, 6 samples represent ≈ 93.75 ms of signal per packet.
pub const PPG_SAMPLES_PER_READING: usize = 6;
// ── Human-readable labels ─────────────────────────────────────────────────────
/// Electrode names in [`EEG_CHARACTERISTICS`] index order.
///
/// Index 4 (`"AUX"`) is only populated when
/// [`crate::muse_client::MuseClientConfig::enable_aux`] is `true`.
pub const EEG_CHANNEL_NAMES: = ;
/// Optical channel names in [`PPG_CHARACTERISTICS`] index order.
pub const PPG_CHANNEL_NAMES: = ;
// ── Athena-specific constants ─────────────────────────────────────────────────
/// Number of EEG channels carried in a single Athena EEG notification.
///
/// Channel order: TP9, AF7, AF8, TP10, FPz, AUX\_R, AUX\_L, AUX.
/// Only the first 4 map to the standard Muse electrode positions shared with
/// Classic firmware. Channels 4–7 are Athena-only extended inputs.
pub const ATHENA_EEG_CHANNELS: usize = 8;
/// EEG samples per channel per Athena notification (14-bit LE, channel-major).
///
/// Each notification contains `ATHENA_EEG_CHANNELS × ATHENA_EEG_SAMPLES_PER_PKT`
/// = 16 values packed into 28 bytes (16 × 14 bits = 224 bits = 28 bytes exactly).
///
/// Compare to Classic firmware: 12 samples per notification per channel using
/// 12-bit big-endian packing (18 bytes of payload per characteristic).
pub const ATHENA_EEG_SAMPLES_PER_PKT: usize = 2;
/// Number of optical channels in an Athena PPG notification.
///
/// Channel order: ambient (0), infrared (1), red (2). A fourth channel is
/// present in the raw 30-byte payload but currently unused.
pub const ATHENA_PPG_CHANNELS: usize = 3;
/// Samples per channel per Athena optical (PPG) notification.
///
/// Each tag-0x_4/0x_5 entry carries `ATHENA_PPG_CHANNELS × ATHENA_PPG_SAMPLES_PER_PKT`
/// = 12 values packed as 20-bit LE unsigned integers into 30 bytes.
pub const ATHENA_PPG_SAMPLES_PER_PKT: usize = 3;
/// Athena PPG sample rate in Hz (same as Classic PPG).
pub const ATHENA_PPG_FREQUENCY: f64 = 64.0;
/// EEG voltage scale factor for Athena firmware in µV per raw LSB.
///
/// `µV = (raw₁₄ − 8192) × ATHENA_EEG_SCALE`
///
/// Contrast with Classic firmware which uses a 12-bit ADC centred at 2048
/// with a scale of 0.48828125 µV/LSB. The two scales differ by ~5.5×,
/// reflecting the different ADC resolution and gain settings.
pub const ATHENA_EEG_SCALE: f64 = 0.0885;
// ── Control commands ──────────────────────────────────────────────────────────
/// Encode a text command for the Muse control characteristic.
///
/// The Muse expects a length-prefixed frame:
/// ```text
/// byte 0 : payload length (= command.len() + 1 for the trailing '\n')
/// bytes 1..N : ASCII command string
/// byte N+1 : '\n' terminator
/// ```
///
/// This is equivalent to the TypeScript `encodeCommand` in `muse-utils.ts`.
///
/// # Example
///
/// ```
/// # use muse_rs::protocol::encode_command;
/// assert_eq!(encode_command("d"), &[0x02, b'd', b'\n']);
/// ```
/// Decode a raw BLE notification from the control characteristic into a string.
///
/// The headset uses the same length-prefix framing for responses:
/// ```text
/// byte 0 : payload length
/// bytes 1..N : UTF-8 (or lossy) response fragment
/// ```
///
/// Partial or multi-packet JSON responses should be assembled with
/// [`crate::parse::ControlAccumulator`].
///
/// Returns an empty string if `bytes` is empty.