pub const PAYLOAD_LENGTH: usize = 45;
pub const CMD_START_ACQUISITION: [u8; 3] = [0x61, 0x7C, 0x87];
pub const CMD_STOP_ACQUISITION: [u8; 3] = [0x63, 0x5C, 0xC5];
pub const ACK_RESPONSE: [u8; 3] = [0x00, 0x00, 0x00];
pub const PAYLOAD_HEADER: [u8; 2] = [0xC0, 0x00];
pub const PAYLOAD_FOOTER: [u8; 2] = [0x0D, 0x0A];
pub const EEG_SCALE_FACTOR: f32 = 4_500_000.0 / 50_331_642.0;
pub const ACCEL_SCALE_FACTOR: f32 = 1.0 / 4096.0;
pub const GYRO_SCALE_FACTOR: f32 = 1.0 / 32.8;
#[derive(Debug, Clone)]
pub struct UnicornPayload {
pub battery_percent: f32,
pub eeg: [f32; 8],
pub accelerometer: [f32; 3],
pub gyroscope: [f32; 3],
pub counter: u32,
}
pub fn decode_payload(raw: &[u8; PAYLOAD_LENGTH]) -> Option<UnicornPayload> {
if raw[0] != PAYLOAD_HEADER[0] || raw[1] != PAYLOAD_HEADER[1] {
return None;
}
if raw[43] != PAYLOAD_FOOTER[0] || raw[44] != PAYLOAD_FOOTER[1] {
return None;
}
let battery_raw = (raw[2] & 0x0F) as f32;
let battery_percent = (100.0 / 1.3) * (battery_raw * 1.3 / 15.0);
let mut eeg = [0.0f32; 8];
for i in 0..8 {
let offset = 3 + i * 3;
let mut value = (raw[offset] as i32) << 16
| (raw[offset + 1] as i32) << 8
| (raw[offset + 2] as i32);
if value & 0x0080_0000 != 0 {
value |= 0xFF00_0000u32 as i32;
}
eeg[i] = value as f32 * EEG_SCALE_FACTOR;
}
let mut accelerometer = [0.0f32; 3];
for i in 0..3 {
let offset = 27 + i * 2;
let value = (raw[offset] as u16 | (raw[offset + 1] as u16) << 8) as i16;
accelerometer[i] = value as f32 * ACCEL_SCALE_FACTOR;
}
let mut gyroscope = [0.0f32; 3];
for i in 0..3 {
let offset = 33 + i * 2;
let value = (raw[offset] as u16 | (raw[offset + 1] as u16) << 8) as i16;
gyroscope[i] = value as f32 * GYRO_SCALE_FACTOR;
}
let counter = raw[39] as u32
| (raw[40] as u32) << 8
| (raw[41] as u32) << 16
| (raw[42] as u32) << 24;
Some(UnicornPayload {
battery_percent,
eeg,
accelerometer,
gyroscope,
counter,
})
}
#[cfg(test)]
mod tests {
use super::*;
const EXAMPLE_PAYLOAD: [u8; 45] = [
0xC0, 0x00, 0x0F, 0x00, 0x9F, 0xAF, 0x00, 0x9F,
0xD4, 0x00, 0xA0, 0x40, 0x00, 0x9F, 0x43, 0x00,
0x9F, 0x9A, 0x00, 0x9F, 0xE3, 0x00, 0x9F, 0x85,
0x00, 0x9F, 0xBB, 0x2E, 0xF6, 0xE9, 0x02, 0x8D,
0xF2, 0xF3, 0xFF, 0xEF, 0xFF, 0x23, 0x00, 0xB0,
0x00, 0x00, 0x00, 0x0D, 0x0A,
];
#[test]
fn test_decode_example_payload() {
let p = decode_payload(&EXAMPLE_PAYLOAD).expect("should decode");
assert!((p.battery_percent - 100.0).abs() < 0.1);
assert!((p.eeg[0] - 3654.87).abs() < 0.1, "CH1: {}", p.eeg[0]);
assert!((p.eeg[1] - 3658.18).abs() < 0.1, "CH2: {}", p.eeg[1]);
assert!((p.accelerometer[0] - (-0.614)).abs() < 0.01, "AccX: {}", p.accelerometer[0]);
assert!((p.gyroscope[0] - (-0.397)).abs() < 0.01, "GyrX: {}", p.gyroscope[0]);
assert_eq!(p.counter, 176);
}
#[test]
fn test_invalid_header() {
let mut bad = EXAMPLE_PAYLOAD;
bad[0] = 0xFF;
assert!(decode_payload(&bad).is_none());
}
#[test]
fn test_invalid_footer() {
let mut bad = EXAMPLE_PAYLOAD;
bad[43] = 0xFF;
assert!(decode_payload(&bad).is_none());
}
#[test]
fn test_eeg_negative_value() {
let mut payload = [0u8; 45];
payload[0] = 0xC0; payload[1] = 0x00; payload[43] = 0x0D; payload[44] = 0x0A;
payload[3] = 0xFF; payload[4] = 0x00; payload[5] = 0x00;
let p = decode_payload(&payload).expect("decode");
assert!(p.eeg[0] < 0.0, "Should be negative: {}", p.eeg[0]);
}
#[test]
fn test_constants() {
assert_eq!(CMD_START_ACQUISITION, [0x61, 0x7C, 0x87]);
assert_eq!(CMD_STOP_ACQUISITION, [0x63, 0x5C, 0xC5]);
assert_eq!(ACK_RESPONSE, [0x00, 0x00, 0x00]);
assert_eq!(PAYLOAD_LENGTH, 45);
}
}