Skip to main content

emotiv_cortex_v2/protocol/
training.rs

1//! Training and advanced BCI protocol types.
2
3use serde::{Deserialize, Serialize};
4
5/// Detection type for the `training` and `getDetectionInfo` methods.
6#[derive(Debug, Clone, Copy)]
7pub enum DetectionType {
8    /// Mental command detection.
9    MentalCommand,
10    /// Facial expression detection.
11    FacialExpression,
12}
13
14impl DetectionType {
15    /// Returns the Cortex API string for this detection type.
16    #[must_use]
17    pub fn as_str(&self) -> &'static str {
18        match self {
19            DetectionType::MentalCommand => "mentalCommand",
20            DetectionType::FacialExpression => "facialExpression",
21        }
22    }
23}
24
25/// Training status/command for the `training` method.
26#[derive(Debug, Clone, Copy)]
27pub enum TrainingStatus {
28    /// Start a new training for the specified action.
29    Start,
30    /// Accept a successful training and add it to the profile.
31    Accept,
32    /// Reject a completed training without saving.
33    Reject,
34    /// Cancel the current training session.
35    Reset,
36    /// Erase all training data for the specified action.
37    Erase,
38}
39
40impl TrainingStatus {
41    /// Returns the Cortex API string for this status.
42    #[must_use]
43    pub fn as_str(&self) -> &'static str {
44        match self {
45            TrainingStatus::Start => "start",
46            TrainingStatus::Accept => "accept",
47            TrainingStatus::Reject => "reject",
48            TrainingStatus::Reset => "reset",
49            TrainingStatus::Erase => "erase",
50        }
51    }
52}
53
54/// Detection info from `getDetectionInfo`.
55#[derive(Debug, Clone, Deserialize)]
56pub struct DetectionInfo {
57    /// Available actions for this detection type.
58    pub actions: Vec<String>,
59    /// Available training controls.
60    pub controls: Vec<String>,
61    /// Possible training events.
62    pub events: Vec<String>,
63}
64/// Trained signature actions from `getTrainedSignatureActions`.
65#[derive(Debug, Clone, Deserialize)]
66pub struct TrainedSignatureActions {
67    /// Total number of training sessions performed.
68    #[serde(rename = "totalTimesTraining")]
69    pub total_times_training: u32,
70
71    /// Per-action training counts.
72    #[serde(rename = "trainedActions")]
73    pub trained_actions: Vec<TrainedAction>,
74}
75
76/// A single trained action within a profile.
77#[derive(Debug, Clone, Deserialize)]
78pub struct TrainedAction {
79    /// Action name (e.g. "neutral", "push", "pull").
80    pub action: String,
81    /// Number of times this action has been trained.
82    pub times: u32,
83}
84
85/// Training time info from `getTrainingTime`.
86#[derive(Debug, Clone, Deserialize)]
87pub struct TrainingTime {
88    /// Training duration in seconds.
89    pub time: f64,
90}
91
92/// Request payload for `mentalCommandTrainingThreshold`.
93#[derive(Debug, Clone, Serialize, Deserialize)]
94pub struct MentalCommandTrainingThresholdRequest {
95    /// Session ID target. Mutually exclusive with `profile`.
96    pub session_id: Option<String>,
97    /// Profile name target. Mutually exclusive with `session_id`.
98    pub profile: Option<String>,
99    /// Explicit status (`"get"` / `"set"`). When omitted, inferred from `value`.
100    pub status: Option<String>,
101    /// Threshold value used with `status = "set"`.
102    pub value: Option<f64>,
103}
104
105/// Request payload for `facialExpressionSignatureType`.
106#[derive(Debug, Clone, Serialize, Deserialize)]
107pub struct FacialExpressionSignatureTypeRequest {
108    /// Operation status (`"get"` / `"set"`).
109    pub status: String,
110    /// Profile target (optional).
111    pub profile: Option<String>,
112    /// Session target (optional).
113    pub session: Option<String>,
114    /// Signature value for set operations.
115    pub signature: Option<String>,
116}
117
118/// Request payload for `facialExpressionThreshold`.
119#[derive(Debug, Clone, Serialize, Deserialize)]
120pub struct FacialExpressionThresholdRequest {
121    /// Operation status (`"get"` / `"set"`).
122    pub status: String,
123    /// Facial action name.
124    pub action: String,
125    /// Profile target (optional).
126    pub profile: Option<String>,
127    /// Session target (optional).
128    pub session: Option<String>,
129    /// Threshold value for set operations.
130    pub value: Option<u32>,
131}
132
133#[cfg(test)]
134mod tests {
135    use super::*;
136
137    #[test]
138    fn test_detection_type_strings() {
139        assert_eq!(DetectionType::MentalCommand.as_str(), "mentalCommand");
140        assert_eq!(DetectionType::FacialExpression.as_str(), "facialExpression");
141    }
142
143    #[test]
144    fn test_training_status_strings() {
145        assert_eq!(TrainingStatus::Start.as_str(), "start");
146        assert_eq!(TrainingStatus::Accept.as_str(), "accept");
147        assert_eq!(TrainingStatus::Reject.as_str(), "reject");
148        assert_eq!(TrainingStatus::Reset.as_str(), "reset");
149        assert_eq!(TrainingStatus::Erase.as_str(), "erase");
150    }
151    #[test]
152    fn test_deserialize_trained_signature_actions() {
153        let json = r#"{
154            "totalTimesTraining": 15,
155            "trainedActions": [
156                {"action": "neutral", "times": 8},
157                {"action": "push", "times": 4},
158                {"action": "pull", "times": 3}
159            ]
160        }"#;
161
162        let actions: TrainedSignatureActions = serde_json::from_str(json).unwrap();
163        assert_eq!(actions.total_times_training, 15);
164        assert_eq!(actions.trained_actions.len(), 3);
165        assert_eq!(actions.trained_actions[0].action, "neutral");
166        assert_eq!(actions.trained_actions[0].times, 8);
167        assert_eq!(actions.trained_actions[2].action, "pull");
168    }
169
170    #[test]
171    fn test_deserialize_training_time() {
172        let json = r#"{"time": 8.0}"#;
173
174        let tt: TrainingTime = serde_json::from_str(json).unwrap();
175        assert!((tt.time - 8.0).abs() < f64::EPSILON);
176    }
177}