neurosky 0.0.1

Rust library and TUI for NeuroSky MindWave EEG headsets via the ThinkGear serial protocol
Documentation
//! ThinkGear protocol types and constants.
//!
//! Based on the [ThinkGear Serial Stream Guide](http://developer.neurosky.com/docs/doku.php?id=thinkgear_communications_protocol).

/// Sampling rate for raw EEG (Hz).
pub const RAW_SAMPLING_RATE: u32 = 512;

/// ASIC EEG band power update rate (Hz).
pub const ASIC_SAMPLING_RATE: u32 = 1;

/// Sync byte (two consecutive 0xAA = packet start).
pub const SYNC_BYTE: u8 = 0xAA;

/// Maximum packet payload length.
pub const MAX_PAYLOAD_LENGTH: u8 = 169;

/// Auto-connect command byte.
pub const CMD_AUTOCONNECT: u8 = 0xC2;

// ── Packet codes ─────────────────────────────────────────────────────────────

/// Single-byte codes (CODE < 0x80).
pub const CODE_POOR_SIGNAL: u8 = 0x02;
pub const CODE_ATTENTION: u8 = 0x04;
pub const CODE_MEDITATION: u8 = 0x05;
pub const CODE_BLINK: u8 = 0x16;

/// Multi-byte codes (CODE >= 0x80).
pub const CODE_RAW_VALUE: u8 = 0x80;
pub const CODE_ASIC_EEG: u8 = 0x83;

/// Connection status codes.
pub const CODE_HEADSET_CONNECTED: u8 = 0xD0;
pub const CODE_HEADSET_NOT_FOUND: u8 = 0xD1;
pub const CODE_HEADSET_DISCONNECTED: u8 = 0xD2;
pub const CODE_REQUEST_DENIED: u8 = 0xD3;
pub const CODE_STANDBY: u8 = 0xD4;

// ── Data types ───────────────────────────────────────────────────────────────

/// EEG frequency band powers from the ASIC processor.
#[derive(Debug, Clone, Copy, Default, PartialEq)]
pub struct AsicEeg {
    pub delta: u32,
    pub theta: u32,
    pub low_alpha: u32,
    pub high_alpha: u32,
    pub low_beta: u32,
    pub high_beta: u32,
    pub low_gamma: u32,
    pub mid_gamma: u32,
}

/// Band names in order.
pub const BAND_NAMES: [&str; 8] = [
    "Delta", "Theta", "Low Alpha", "High Alpha",
    "Low Beta", "High Beta", "Low Gamma", "Mid Gamma",
];

impl AsicEeg {
    /// Get band values as an array.
    pub fn as_array(&self) -> [u32; 8] {
        [self.delta, self.theta, self.low_alpha, self.high_alpha,
         self.low_beta, self.high_beta, self.low_gamma, self.mid_gamma]
    }
}

/// A parsed ThinkGear packet.
#[derive(Debug, Clone)]
pub enum Packet {
    /// Poor signal quality (0 = good, 200 = off head).
    PoorSignal(u8),
    /// Attention eSense (0-100).
    Attention(u8),
    /// Meditation eSense (0-100).
    Meditation(u8),
    /// Blink strength (1-255).
    Blink(u8),
    /// Raw 16-bit signed EEG value (512 Hz).
    RawValue(i16),
    /// ASIC EEG frequency band powers (1 Hz).
    AsicEeg(AsicEeg),
    /// Headset connected (headset ID).
    HeadsetConnected(u16),
    /// Headset not found.
    HeadsetNotFound,
    /// Headset disconnected.
    HeadsetDisconnected,
    /// Standby / scanning.
    Standby,
    /// Request denied.
    RequestDenied,
}

/// Decode ASIC EEG from 24 bytes (8 × 3-byte big-endian unsigned).
pub fn decode_asic_eeg(data: &[u8]) -> Option<AsicEeg> {
    if data.len() < 24 { return None; }
    let u24 = |i: usize| -> u32 {
        (data[i] as u32) << 16 | (data[i+1] as u32) << 8 | (data[i+2] as u32)
    };
    Some(AsicEeg {
        delta:      u24(0),
        theta:      u24(3),
        low_alpha:  u24(6),
        high_alpha: u24(9),
        low_beta:   u24(12),
        high_beta:  u24(15),
        low_gamma:  u24(18),
        mid_gamma:  u24(21),
    })
}

/// Decode raw 16-bit signed EEG value from 2 bytes (big-endian).
pub fn decode_raw_value(data: &[u8]) -> Option<i16> {
    if data.len() < 2 { return None; }
    Some(((data[0] as i16) << 8) | data[1] as i16)
}