1pub mod athena;
7pub mod classic;
8pub mod utils;
9
10#[derive(Debug, Clone, Copy, PartialEq, Eq)]
12pub enum MuseVariant {
13 Classic,
14 Athena,
15}
16
17pub const SERVICE_UUID: &str = "0000fe8d-0000-1000-8000-00805f9b34fb";
19pub const SERVICE_UUID_U128: u128 = 0x0000fe8d_0000_1000_8000_00805f9b34fb;
20
21pub mod characteristic {
23 pub const COMMAND: &str = "273e0001-4c4d-454d-96be-f03bac821358";
25 pub const COMMAND_U128: u128 = 0x273e0001_4c4d_454d_96be_f03bac821358;
26
27 pub const TP9: &str = "273e0003-4c4d-454d-96be-f03bac821358";
29 pub const TP9_U128: u128 = 0x273e0003_4c4d_454d_96be_f03bac821358;
30
31 pub const AF7: &str = "273e0004-4c4d-454d-96be-f03bac821358";
33 pub const AF7_U128: u128 = 0x273e0004_4c4d_454d_96be_f03bac821358;
34
35 pub const AF8: &str = "273e0005-4c4d-454d-96be-f03bac821358";
37 pub const AF8_U128: u128 = 0x273e0005_4c4d_454d_96be_f03bac821358;
38
39 pub const TP10: &str = "273e0006-4c4d-454d-96be-f03bac821358";
41 pub const TP10_U128: u128 = 0x273e0006_4c4d_454d_96be_f03bac821358;
42
43 pub const RIGHT_AUX: &str = "273e0007-4c4d-454d-96be-f03bac821358";
45 pub const RIGHT_AUX_U128: u128 = 0x273e0007_4c4d_454d_96be_f03bac821358;
46
47 pub const GYRO: &str = "273e0009-4c4d-454d-96be-f03bac821358";
49 pub const GYRO_U128: u128 = 0x273e0009_4c4d_454d_96be_f03bac821358;
50
51 pub const ACCEL: &str = "273e000a-4c4d-454d-96be-f03bac821358";
53 pub const ACCEL_U128: u128 = 0x273e000a_4c4d_454d_96be_f03bac821358;
54
55 pub const TELEMETRY: &str = "273e000b-4c4d-454d-96be-f03bac821358";
57 pub const TELEMETRY_U128: u128 = 0x273e000b_4c4d_454d_96be_f03bac821358;
58
59 pub const PPG1: &str = "273e000f-4c4d-454d-96be-f03bac821358";
61 pub const PPG1_U128: u128 = 0x273e000f_4c4d_454d_96be_f03bac821358;
62
63 pub const PPG2: &str = "273e0010-4c4d-454d-96be-f03bac821358";
65 pub const PPG2_U128: u128 = 0x273e0010_4c4d_454d_96be_f03bac821358;
66
67 pub const PPG3: &str = "273e0011-4c4d-454d-96be-f03bac821358";
69 pub const PPG3_U128: u128 = 0x273e0011_4c4d_454d_96be_f03bac821358;
70
71 pub const EEG_CHANNELS: [u128; 4] = [TP9_U128, AF7_U128, AF8_U128, TP10_U128];
73 pub const EEG_CHANNEL_NAMES: [&str; 4] = ["TP9", "AF7", "AF8", "TP10"];
74
75 pub const PPG_CHANNELS: [u128; 3] = [PPG1_U128, PPG2_U128, PPG3_U128];
77 pub const PPG_CHANNEL_NAMES: [&str; 3] = ["PPG1", "PPG2", "PPG3"];
78}
79
80pub mod spec {
82 pub const SAMPLE_RATE: u16 = 256;
84 pub const CHANNEL_COUNT: usize = 4;
86 pub const SAMPLES_PER_PACKET: usize = 12;
88 pub const PACKET_SIZE: usize = 20;
90
91 pub const PPG_SAMPLE_RATE: u16 = 64;
93 pub const PPG_CHANNEL_COUNT: usize = 3;
95 pub const PPG_SAMPLES_PER_PACKET: usize = 6;
97 pub const PPG_PACKET_SIZE: usize = 20;
99}
100
101pub mod command {
103 pub const SET_PRESET: &str = "v1";
105 pub const ENABLE_AUX: &str = "p21";
107 pub const START_STREAM: &str = "d";
109 pub const STOP_STREAM: &str = "h";
111 pub const DEVICE_INFO: &str = "?";
113
114 pub fn encode(cmd: &str) -> Vec<u8> {
117 let mut bytes = Vec::with_capacity(cmd.len() + 2);
118 bytes.push((cmd.len() + 1) as u8); bytes.extend_from_slice(cmd.as_bytes());
120 bytes.push(0x0A); bytes
122 }
123
124 pub fn decode(data: &[u8]) -> Option<&str> {
126 if data.is_empty() {
127 return None;
128 }
129 let len = data[0] as usize;
130 if data.len() < len + 1 {
131 return None;
132 }
133 let cmd_end = if data.len() > 1 && data[len] == 0x0A {
135 len
136 } else {
137 len.min(data.len() - 1)
138 };
139 std::str::from_utf8(&data[1..cmd_end]).ok()
140 }
141}
142
143pub fn encode_eeg_packet(sequence: u16, samples: &[f32]) -> [u8; 20] {
152 let mut packet = [0u8; 20];
153
154 packet[0] = (sequence >> 8) as u8;
156 packet[1] = (sequence & 0xFF) as u8;
157
158 let mut byte_idx = 2;
161 let total_samples = spec::SAMPLES_PER_PACKET;
162 for i in (0..total_samples).step_by(2) {
163 let s1 = samples.get(i).copied().unwrap_or(0.0);
164 let s2 = samples.get(i + 1).copied().unwrap_or(0.0);
165
166 let v1 = uv_to_adc(s1);
168 let v2 = uv_to_adc(s2);
169
170 packet[byte_idx] = (v1 >> 4) as u8;
172 packet[byte_idx + 1] = ((v1 & 0x0F) << 4 | (v2 >> 8)) as u8;
173 packet[byte_idx + 2] = (v2 & 0xFF) as u8;
174
175 byte_idx += 3;
176 }
177
178 packet
179}
180
181pub fn decode_eeg_packet(packet: &[u8]) -> (u16, Vec<f32>) {
183 if packet.len() < 20 {
184 return (0, Vec::new());
185 }
186
187 let sequence = ((packet[0] as u16) << 8) | (packet[1] as u16);
189
190 let mut samples = Vec::with_capacity(12);
191
192 for i in (2..20).step_by(3) {
194 if i + 2 >= packet.len() {
195 break;
196 }
197
198 let b0 = packet[i] as u16;
199 let b1 = packet[i + 1] as u16;
200 let b2 = packet[i + 2] as u16;
201
202 let v1_raw = (b0 << 4) | (b1 >> 4);
204 let v2_raw = ((b1 & 0x0F) << 8) | b2;
206
207 samples.push(adc_to_uv(v1_raw));
208 samples.push(adc_to_uv(v2_raw));
209 }
210
211 (sequence, samples)
212}
213
214#[derive(Debug, Clone, PartialEq, Eq)]
223pub struct PpgFrame {
224 pub sequence: u16,
225 pub samples: [u32; 6],
226}
227
228pub fn encode_ppg_packet(sequence: u16, samples: &[u32; 6]) -> [u8; 20] {
234 let mut packet = [0u8; 20];
235 packet[0] = (sequence >> 8) as u8;
236 packet[1] = (sequence & 0xFF) as u8;
237
238 for (i, &sample) in samples.iter().enumerate() {
239 let sample = sample & 0x00FF_FFFF; let idx = 2 + i * 3;
241 packet[idx] = (sample >> 16) as u8;
242 packet[idx + 1] = (sample >> 8) as u8;
243 packet[idx + 2] = sample as u8;
244 }
245
246 packet
247}
248
249pub fn decode_ppg_packet(packet: &[u8]) -> Option<PpgFrame> {
253 if packet.len() < 20 {
254 return None;
255 }
256
257 let sequence = ((packet[0] as u16) << 8) | (packet[1] as u16);
258 let data = &packet[2..20];
259
260 let mut samples = [0u32; 6];
261 for (i, sample) in samples.iter_mut().enumerate() {
262 let idx = i * 3;
263 if idx + 2 >= data.len() {
264 return None;
265 }
266 *sample =
267 ((data[idx] as u32) << 16) | ((data[idx + 1] as u32) << 8) | (data[idx + 2] as u32);
268 }
269
270 Some(PpgFrame { sequence, samples })
271}
272
273fn uv_to_adc(uv: f32) -> u16 {
275 let adc = (uv * 256.0 / 125.0) + 2048.0;
277 (adc.clamp(0.0, 4095.0) as u16) & 0x0FFF
278}
279
280fn adc_to_uv(adc: u16) -> f32 {
282 ((adc as i16 - 0x800) as f32) * 125.0 / 256.0
284}
285
286#[cfg(test)]
287mod tests {
288 use super::*;
289
290 #[test]
291 fn test_adc_conversion_roundtrip() {
292 let test_values = [0.0, 10.0, -10.0, 100.0, -100.0, 200.0, -200.0];
293 for &uv in &test_values {
294 let adc = uv_to_adc(uv);
295 let back = adc_to_uv(adc);
296 assert!((uv - back).abs() < 0.5, "uv={uv}, adc={adc}, back={back}");
298 }
299 }
300
301 #[test]
302 fn test_packet_roundtrip() {
303 let samples: Vec<f32> = (0..12).map(|i| (i as f32 - 6.0) * 10.0).collect();
304 let packet = encode_eeg_packet(42, &samples);
305 let (seq, decoded) = decode_eeg_packet(&packet);
306
307 assert_eq!(seq, 42);
308 assert_eq!(decoded.len(), 12);
309
310 for (i, (&orig, &dec)) in samples.iter().zip(decoded.iter()).enumerate() {
311 assert!(
312 (orig - dec).abs() < 0.5,
313 "sample {i}: orig={orig}, decoded={dec}"
314 );
315 }
316 }
317
318 #[test]
319 fn test_command_encode_decode() {
320 let cmd = "d";
321 let encoded = command::encode(cmd);
322 assert_eq!(encoded, vec![2, b'd', 0x0A]);
323
324 let decoded = command::decode(&encoded);
325 assert_eq!(decoded, Some("d"));
326 }
327
328 #[test]
329 fn test_command_decode_rejects_short_payload() {
330 let data = vec![3, b'd', 0x0A];
331 let decoded = command::decode(&data);
332 assert_eq!(decoded, None);
333 }
334
335 #[test]
336 fn test_decode_short_packet_returns_empty() {
337 let (seq, samples) = decode_eeg_packet(&[0u8; 10]);
338 assert_eq!(seq, 0);
339 assert!(samples.is_empty());
340 }
341
342 #[test]
343 fn test_packet_with_short_sample_input_zero_fills() {
344 let samples = vec![50.0, -25.0];
345 let packet = encode_eeg_packet(1, &samples);
346 let (_seq, decoded) = decode_eeg_packet(&packet);
347
348 assert_eq!(decoded.len(), 12);
349 assert!((decoded[0] - 50.0).abs() < 0.5);
350 assert!((decoded[1] + 25.0).abs() < 0.5);
351 for value in decoded.iter().skip(2) {
352 assert!(value.abs() < 0.5);
353 }
354 }
355
356 #[test]
357 fn test_packet_clamps_out_of_range_samples() {
358 let samples = [1_000_000.0, -1_000_000.0].repeat(6);
359 let packet = encode_eeg_packet(7, &samples);
360 let (_seq, decoded) = decode_eeg_packet(&packet);
361
362 let max_uv = ((0x0FFFu16 as i16 - 0x800) as f32) * 125.0 / 256.0;
363 let min_uv = ((0u16 as i16 - 0x800) as f32) * 125.0 / 256.0;
364
365 assert!((decoded[0] - max_uv).abs() < 0.5);
366 assert!((decoded[1] - min_uv).abs() < 0.5);
367 }
368
369 #[test]
370 fn test_ppg_packet_roundtrip() {
371 let samples = [0x000000, 0x000001, 0xABCDEF, 0xFFFFFF, 0x123456, 0x654321];
373 let packet = encode_ppg_packet(9, &samples);
374 let decoded = decode_ppg_packet(&packet).expect("ppg decode");
375
376 assert_eq!(decoded.sequence, 9);
377 assert_eq!(decoded.samples, samples);
378 }
379}