neurosky 0.0.1

Rust library and TUI for NeuroSky MindWave EEG headsets via the ThinkGear serial protocol
Documentation
//! High-level MindWave device API.
//!
//! ```rust,ignore
//! use neurosky::prelude::*;
//!
//! let ports = MindWaveDevice::find()?;
//! let mut device = MindWaveDevice::open(&ports[0])?;
//! device.auto_connect()?;
//!
//! println!("Streaming…");
//! loop {
//!     for packet in device.read()? {
//!         match packet {
//!             Packet::Attention(v)  => println!("Attention:  {v}/100"),
//!             Packet::Meditation(v) => println!("Meditation: {v}/100"),
//!             Packet::AsicEeg(eeg)  => println!("δ={} α={}", eeg.delta, eeg.low_alpha),
//!             _ => {}
//!         }
//!     }
//! }
//! ```

use std::io::Read;
use std::time::Duration;

use crate::error::NeuroSkyError;
use crate::parser::Parser;
use crate::types::*;

/// A connected NeuroSky MindWave headset.
pub struct MindWaveDevice {
    port:   Box<dyn serialport::SerialPort>,
    parser: Parser,
}

impl MindWaveDevice {
    // ── Static methods ───────────────────────────────────────────────────

    /// Find serial ports with MindWave dongles.
    ///
    /// Returns all ports whose USB VID matches the MindWave dongle, or all
    /// available ports if none match (useful when the device is paired over BT).
    pub fn find() -> Result<Vec<String>, NeuroSkyError> {
        let ports = serialport::available_ports()?;
        let mut result: Vec<String> = ports.iter()
            .filter(|p| match &p.port_type {
                serialport::SerialPortType::UsbPort(info) =>
                    info.vid == 0x04D8 ||
                    info.product.as_ref()
                        .map(|s| s.contains("MindWave") || s.contains("NeuroSky"))
                        .unwrap_or(false),
                _ => false,
            })
            .map(|p| p.port_name.clone())
            .collect();
        if result.is_empty() {
            result = ports.iter().map(|p| p.port_name.clone()).collect();
        }
        Ok(result)
    }

    // ── Instance methods ─────────────────────────────────────────────────

    /// Open a connection at 57 600 baud (USB dongle default).
    pub fn open(port_name: &str) -> Result<Self, NeuroSkyError> {
        let port = serialport::new(port_name, 57600)
            .timeout(Duration::from_millis(3000))
            .open()?;
        Ok(MindWaveDevice { port, parser: Parser::new() })
    }

    /// Open at 115 200 baud (MindWave Mobile / Mobile 2 Bluetooth SPP).
    pub fn open_bluetooth(port_name: &str) -> Result<Self, NeuroSkyError> {
        let port = serialport::new(port_name, 115200)
            .timeout(Duration::from_millis(3000))
            .open()?;
        Ok(MindWaveDevice { port, parser: Parser::new() })
    }

    /// Send the auto-connect command byte (`0xC2`) for the USB dongle.
    pub fn auto_connect(&mut self) -> Result<(), NeuroSkyError> {
        use std::io::Write;
        self.port.write_all(&[CMD_AUTOCONNECT])
            .map_err(|e| NeuroSkyError::SerialPort(e.to_string()))
    }

    /// Read and parse available bytes. Returns all decoded packets (may be empty).
    pub fn read(&mut self) -> Result<Vec<Packet>, NeuroSkyError> {
        let mut buf = [0u8; 512];
        match self.port.read(&mut buf) {
            Ok(n) => Ok(self.parser.parse(&buf[..n])),
            Err(ref e) if e.kind() == std::io::ErrorKind::TimedOut => Ok(Vec::new()),
            Err(e) => Err(NeuroSkyError::SerialPort(e.to_string())),
        }
    }

    /// Block until at least one packet is received.
    pub fn read_blocking(&mut self) -> Result<Vec<Packet>, NeuroSkyError> {
        loop {
            let packets = self.read()?;
            if !packets.is_empty() { return Ok(packets); }
        }
    }

    // ── Convenience methods ──────────────────────────────────────────────

    /// Close the connection (equivalent to dropping the device).
    pub fn close(self) { drop(self); }
}