patlite-beacon-serv 0.1.0

RESTful API server for controlling PATLITE USB beacons with comprehensive light patterns, sequences, and buzzer control
use serde::{Deserialize, Serialize};

#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Hash)]
#[serde(rename_all = "lowercase")]
pub enum LightColor {
    Red,
    Yellow,
    Green,
    Blue,
    White,
}

#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "lowercase")]
pub enum LightMode {
    Off,
    On,
    #[serde(alias = "solid")]
    Solid,
    Pattern1,
    Pattern2,
    Pattern3,
    Pattern4,
    Pattern5,
    Pattern6,
    // Legacy aliases for backward compatibility
    #[serde(alias = "blink")]
    Blink,
    #[serde(alias = "flash")]
    Flash,
}

#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "snake_case")]
pub enum BuzzerPattern {
    Off,
    On,
    #[serde(alias = "continuous")]
    Continuous,
    Sweep,
    Intermittent,
    #[serde(alias = "weak_attention", alias = "weak", alias = "chirp")]
    WeakAttention,
    #[serde(alias = "strong_attention", alias = "strong", alias = "alarm")]
    StrongAttention,
    #[serde(alias = "shining_star")]
    ShiningStar,
    #[serde(alias = "london_bridge")]
    LondonBridge,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LightSettings {
    pub color: String,
    pub mode: LightMode,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub duration_ms: Option<u64>,
}

#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
pub struct LightCommand {
    pub color: LightColor,
    pub mode: LightMode,
    pub duration_ms: u64,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LightSequence {
    pub commands: Vec<LightCommand>,
    #[serde(default = "default_loop")]
    pub loop_sequence: bool,
}

fn default_loop() -> bool {
    true
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CurrentLightState {
    pub color: LightColor,
    pub mode: LightMode,
    pub active: bool,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub remaining_ms: Option<u64>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BuzzerSettings {
    pub pattern: BuzzerPattern,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub volume: Option<u8>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub duration_ms: Option<u64>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BuzzerPatternSettings {
    pub pattern: Vec<BuzzerNote>,
    pub repetitions: u8,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub volume: Option<u8>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub duration_ms: Option<u64>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BuzzerNote {
    pub frequency_hz: u16,
    pub duration_ms: u64,
    pub pause_ms: u64,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LightState {
    pub mode: LightMode,
    pub active: bool,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub remaining_ms: Option<u64>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BuzzerState {
    pub active: bool,
    pub pattern: Option<BuzzerPattern>,
    pub volume: u8,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub remaining_ms: Option<u64>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BeaconStatus {
    pub light: Option<CurrentLightState>,
    pub sequence: Option<SequenceStatus>,
    pub buzzer: BuzzerState,
    pub connected: bool,
    pub last_updated: String,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SequenceStatus {
    pub commands: Vec<LightCommand>,
    pub current_index: usize,
    pub loop_sequence: bool,
    pub active: bool,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TestResult {
    pub success: bool,
    pub results: Vec<String>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ApiKeyQuery {
    #[serde(rename = "apiKey")]
    pub api_key: Option<String>,
}

impl Default for LightState {
    fn default() -> Self {
        Self {
            mode: LightMode::Off,
            active: false,
            remaining_ms: None,
        }
    }
}

impl Default for BuzzerState {
    fn default() -> Self {
        Self {
            active: false,
            pattern: None,
            volume: 50,
            remaining_ms: None,
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_light_color_serialization() {
        let color = LightColor::Red;
        let json = serde_json::to_string(&color).unwrap();
        assert_eq!(json, r#""red""#);
        
        let deserialized: LightColor = serde_json::from_str(&json).unwrap();
        assert_eq!(deserialized, color);
    }

    #[test]
    fn test_light_mode_serialization() {
        let mode = LightMode::Flash;
        let json = serde_json::to_string(&mode).unwrap();
        assert_eq!(json, r#""flash""#);
        
        let deserialized: LightMode = serde_json::from_str(&json).unwrap();
        assert_eq!(deserialized, mode);
    }

    #[test]
    fn test_light_settings_serialization() {
        let settings = LightSettings {
            color: "red".to_string(),
            mode: LightMode::On,
            duration_ms: Some(1000),
        };
        
        let json = serde_json::to_string(&settings).unwrap();
        assert!(json.contains(r#""color":"red""#));
        assert!(json.contains(r#""mode":"on""#));
        assert!(json.contains(r#""duration_ms":1000"#));
        
        let deserialized: LightSettings = serde_json::from_str(&json).unwrap();
        assert_eq!(deserialized.color, settings.color);
        assert_eq!(deserialized.mode, settings.mode);
        assert_eq!(deserialized.duration_ms, settings.duration_ms);
    }

    #[test]
    fn test_beacon_status_default() {
        let status = BeaconStatus {
            light: None,
            sequence: None,
            buzzer: BuzzerState::default(),
            connected: true,
            last_updated: "2025-01-01T00:00:00Z".to_string(),
        };
        
        assert!(!status.buzzer.active);
        assert_eq!(status.buzzer.volume, 50);
    }

    #[test]
    fn test_buzzer_pattern_serialization() {
        let pattern = BuzzerPattern::StrongAttention;
        let json = serde_json::to_string(&pattern).unwrap();
        assert_eq!(json, r#""strong_attention""#);
        
        let deserialized: BuzzerPattern = serde_json::from_str(&json).unwrap();
        assert_eq!(deserialized, pattern);
        
        // Test aliases
        let json_alias = r#""alarm""#;
        let deserialized: BuzzerPattern = serde_json::from_str(json_alias).unwrap();
        assert_eq!(deserialized, BuzzerPattern::StrongAttention);
    }

    #[test]
    fn test_light_sequence() {
        let sequence = LightSequence {
            commands: vec![
                LightCommand {
                    color: LightColor::Red,
                    mode: LightMode::On,
                    duration_ms: 1000,
                },
                LightCommand {
                    color: LightColor::Green,
                    mode: LightMode::Flash,
                    duration_ms: 2000,
                },
            ],
            loop_sequence: true,
        };
        
        let json = serde_json::to_string(&sequence).unwrap();
        let deserialized: LightSequence = serde_json::from_str(&json).unwrap();
        
        assert_eq!(deserialized.commands.len(), 2);
        assert_eq!(deserialized.commands[0].color, LightColor::Red);
        assert_eq!(deserialized.commands[1].mode, LightMode::Flash);
        assert!(deserialized.loop_sequence);
    }
}