#[derive(Debug, Clone)]
pub struct EegPacket {
pub timestamp: f64,
pub event_id: u8,
pub counter: u8,
pub ref_value: f32,
pub drl: f32,
pub channels: Vec<f32>,
pub feature_status: u8,
pub checksum_valid: bool,
}
#[derive(Debug, Clone)]
pub struct BatteryInfo {
pub level: u8,
}
#[derive(Debug, Clone, Default)]
pub struct ChecksumStats {
pub valid_packets: u64,
pub invalid_packets: u64,
pub total_packets: u64,
}
impl ChecksumStats {
pub fn error_rate(&self) -> f64 {
if self.total_packets == 0 {
0.0
} else {
(self.invalid_packets as f64 / self.total_packets as f64) * 100.0
}
}
}
#[derive(Debug, Clone)]
pub struct ActivationStatus {
pub eeg_enabled: bool,
pub raw_mode_enabled: bool,
}
#[derive(Debug, Clone)]
pub enum Mw75Event {
Eeg(EegPacket),
Battery(BatteryInfo),
Activated(ActivationStatus),
Connected(String),
Disconnected,
RawData(Vec<u8>),
OtherEvent {
event_id: u8,
counter: u8,
raw: Vec<u8>,
},
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn eeg_packet_construction() {
let pkt = EegPacket {
timestamp: 1700000000.0,
event_id: 239,
counter: 100,
ref_value: 1.5,
drl: -2.3,
channels: vec![10.0; 12],
feature_status: 0,
checksum_valid: true,
};
assert_eq!(pkt.channels.len(), 12);
assert_eq!(pkt.event_id, 239);
assert!(pkt.checksum_valid);
}
#[test]
fn battery_info() {
let b = BatteryInfo { level: 100 };
assert_eq!(b.level, 100);
}
#[test]
fn checksum_stats_default() {
let s = ChecksumStats::default();
assert_eq!(s.valid_packets, 0);
assert_eq!(s.invalid_packets, 0);
assert_eq!(s.total_packets, 0);
assert_eq!(s.error_rate(), 0.0);
}
#[test]
fn checksum_stats_error_rate() {
let s = ChecksumStats {
valid_packets: 80,
invalid_packets: 20,
total_packets: 100,
};
assert!((s.error_rate() - 20.0).abs() < 0.01);
}
#[test]
fn checksum_stats_all_valid() {
let s = ChecksumStats {
valid_packets: 1000,
invalid_packets: 0,
total_packets: 1000,
};
assert_eq!(s.error_rate(), 0.0);
}
#[test]
fn activation_status() {
let a = ActivationStatus {
eeg_enabled: true,
raw_mode_enabled: false,
};
assert!(a.eeg_enabled);
assert!(!a.raw_mode_enabled);
}
#[test]
fn event_matching() {
let events: Vec<Mw75Event> = vec![
Mw75Event::Connected("MW75".into()),
Mw75Event::Battery(BatteryInfo { level: 50 }),
Mw75Event::Activated(ActivationStatus {
eeg_enabled: true,
raw_mode_enabled: true,
}),
Mw75Event::Eeg(EegPacket {
timestamp: 0.0,
event_id: 239,
counter: 0,
ref_value: 0.0,
drl: 0.0,
channels: vec![0.0; 12],
feature_status: 0,
checksum_valid: true,
}),
Mw75Event::Disconnected,
Mw75Event::RawData(vec![0xAA]),
Mw75Event::OtherEvent {
event_id: 100,
counter: 5,
raw: vec![0; 63],
},
];
assert!(matches!(events[0], Mw75Event::Connected(_)));
assert!(matches!(events[1], Mw75Event::Battery(_)));
assert!(matches!(events[2], Mw75Event::Activated(_)));
assert!(matches!(events[3], Mw75Event::Eeg(_)));
assert!(matches!(events[4], Mw75Event::Disconnected));
assert!(matches!(events[5], Mw75Event::RawData(_)));
assert!(matches!(events[6], Mw75Event::OtherEvent { .. }));
}
#[test]
fn event_clone() {
let event = Mw75Event::Eeg(EegPacket {
timestamp: 1.0,
event_id: 239,
counter: 0,
ref_value: 0.0,
drl: 0.0,
channels: vec![1.0, 2.0, 3.0],
feature_status: 0,
checksum_valid: true,
});
let cloned = event.clone();
if let (Mw75Event::Eeg(a), Mw75Event::Eeg(b)) = (&event, &cloned) {
assert_eq!(a.channels, b.channels);
} else {
panic!("Clone should preserve variant");
}
}
#[test]
fn event_debug() {
let event = Mw75Event::Connected("Test".into());
let debug = format!("{:?}", event);
assert!(debug.contains("Connected"));
assert!(debug.contains("Test"));
}
}