matroskin 0.1.0

whatsminer api client lib
Documentation
//! Implement `set.system.led` command
//!
//! Controls the device's LED indicators.
//!
//! This command supports two modes:
//! - **auto**: The system controls the LEDs automatically based on the device status.
//! - **manual**: Define custom flashing patterns for the red and green LEDs.
//!
//! - Command: [SetSystemLed]
//! - ApiDoc: <https://apidoc.whatsminer.com/#api-System-system_set_led>
use core::str;

use serde::Serialize;

use crate::{
    command::{Command, CommandLevel},
    error::Result,
};

/// Flash pattern for a single LED color in manual mode.
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
pub struct LedFlashPattern {
    /// LED color: `red` or `green`.
    pub color: String,
    /// Duration of one full flash cycle, in milliseconds.
    pub period: u32,
    /// Duration the LED stays lit during a cycle, in milliseconds.
    pub duration: u32,
    /// Start offset for the flashing cycle, in milliseconds.
    pub start: u32,
}

/// LED operating mode.
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum LedMode {
    /// Automatic LED control based on device status.
    Auto,
    /// Manual flashing patterns for the LEDs.
    Manual(Vec<LedFlashPattern>),
}

impl serde::Serialize for LedMode {
    fn serialize<S: serde::Serializer>(
        &self,
        serializer: S,
    ) -> std::result::Result<S::Ok, S::Error> {
        match self {
            Self::Auto => "auto".serialize(serializer),
            Self::Manual(patterns) => patterns.serialize(serializer),
        }
    }
}

/// This command represents the `set.system.led` operation.
///
/// Controls the device's LED indicators.
///
/// - ApiDoc: <https://apidoc.whatsminer.com/#api-System-system_set_led>
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SetSystemLed {
    /// LED operating mode
    pub mode: LedMode,
}

impl SetSystemLed {
    /// Create a new `set.system.led` command with specified mode.
    pub fn new(mode: LedMode) -> Self {
        Self { mode }
    }

    /// Automatic LED control based on device status.
    pub fn auto() -> Self {
        Self {
            mode: LedMode::Auto,
        }
    }

    /// Manual flashing patterns for the LEDs.
    pub fn manual(patterns: impl Into<Vec<LedFlashPattern>>) -> Self {
        Self {
            mode: LedMode::Manual(patterns.into()),
        }
    }
}

impl Command for SetSystemLed {
    type Response = String;
    const CMD_NAME: &'static str = "set.system.led";
    const LEVEL: CommandLevel = CommandLevel::Secured;

    fn params(&self) -> Result<Option<impl serde::Serialize>> {
        Ok(Some(&self.mode))
    }
}

#[cfg(test)]
mod tests {
    use crate::account::Account;
    use crate::auth_data::AuthData;

    use super::*;

    const RESPONSE_JSON: &str =
        r#"{"code":0,"when":1692685512,"msg":"ok","desc":"set.system.led"}"#;

    #[test]
    fn request_auto_mode() {
        let cmd = SetSystemLed::auto();
        assert!(cmd.is_secured());
        assert!(!cmd.is_encrypted());

        let auth = AuthData::new::<SetSystemLed>(Account::Super, "pass", "salt").unwrap();
        let encoded = cmd.encode(Some(auth)).unwrap();
        let json: serde_json::Value = serde_json::from_slice(&encoded.0).unwrap();

        assert_eq!(json["cmd"], "set.system.led");
        assert_eq!(json["param"], "auto");
        assert_eq!(json["account"], "super");
        assert!(json["ts"].as_u64().unwrap() > 0);
        assert_eq!(json["token"].as_str().unwrap().len(), 8);
    }

    #[test]
    fn request_manual_mode() {
        let cmd = SetSystemLed::manual(vec![
            LedFlashPattern {
                color: "red".to_string(),
                period: 200,
                duration: 100,
                start: 0,
            },
            LedFlashPattern {
                color: "green".to_string(),
                period: 200,
                duration: 150,
                start: 0,
            },
        ]);

        let auth = AuthData::new::<SetSystemLed>(Account::Super, "pass", "salt").unwrap();
        let encoded = cmd.encode(Some(auth)).unwrap();
        let json: serde_json::Value = serde_json::from_slice(&encoded.0).unwrap();

        assert_eq!(json["cmd"], "set.system.led");
        assert!(json["param"].is_array());
        assert_eq!(json["param"][0]["color"], "red");
    }

    #[test]
    fn request_without_auth_omits_token() {
        let cmd = SetSystemLed::auto();
        let encoded = cmd.encode(None).unwrap();
        let json: serde_json::Value = serde_json::from_slice(&encoded.0).unwrap();
        assert!(json.get("token").is_none());
        assert_eq!(json["param"], "auto");
    }

    #[test]
    fn deserialize_response_fixture() {
        let response = SetSystemLed::parse_response(RESPONSE_JSON.as_bytes()).unwrap();
        assert!(response.code.is_success());
        assert_eq!(response.desc, "set.system.led");
        assert_eq!(response.when, 1692685512);
        assert_eq!(response.msg, "ok");
    }
}