litterrobot3 0.1.2

A client wrapper over the litter robot API. Control your robot from rust :)
Documentation
use serde::Deserialize;
use serde::de;
use serde_json::Value;

#[derive(Clone, Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Robot {
    pub litter_robot_id: String,
    pub litter_robot_serial: String,
    pub litter_robot_nickname: String,
    pub device_type: String,
    #[serde(deserialize_with = "deserialize_u64")]
    pub cycle_count: u64,
    #[serde(deserialize_with = "deserialize_u64")]
    pub total_cycle_count: u64,
    #[serde(deserialize_with = "deserialize_u64")]
    pub cycle_capacity: u64,
    #[serde(deserialize_with = "deserialize_u64")]
    pub new_cycle_capacity: u64,
    #[serde(deserialize_with = "deserialize_bool")]
    #[serde(rename = "isDFITriggered")]
    pub is_dfi_triggered: bool,
    #[serde(deserialize_with = "deserialize_bool")]
    pub is_df1_triggered: bool,
    #[serde(deserialize_with = "deserialize_bool")]
    pub is_df2_triggered: bool,
    #[serde(deserialize_with = "deserialize_bool")]
    pub is_dfs_triggered: bool,
    #[serde(deserialize_with = "deserialize_bool")]
    pub is_manual_reset: bool,
    #[serde(deserialize_with = "deserialize_bool")]
    pub saved_is_manual_reset: bool,
    #[serde(deserialize_with = "deserialize_bool")]
    #[serde(rename = "previousDFITriggered")]
    pub previous_dfi_triggered: bool,
    #[serde(deserialize_with = "deserialize_u64")]
    #[serde(rename = "DFICycleCount")]
    pub dfi_cycle_count: u64,
    #[serde(deserialize_with = "deserialize_u64")]
    pub saved_cycle_count: u64,
    pub clean_cycle_wait_time_minutes: String,
    pub cycles_after_drawer_full: u64,
    #[serde(deserialize_with = "deserialize_bool")]
    pub night_light_active: bool,
    #[serde(deserialize_with = "deserialize_bool")]
    pub panel_lock_active: bool,
    #[serde(deserialize_with = "deserialize_bool")]
    pub sleep_mode_active: bool,
    pub sleep_mode_time: u64,
    pub power_status: String,
    pub unit_status: UnitStatus,
    #[serde(deserialize_with = "deserialize_u64")]
    pub sleep_mode_end_time: u64,
    #[serde(deserialize_with = "deserialize_u64")]
    pub sleep_mode_start_time: u64,
    pub last_seen: String,
    pub setup_date: String,
    pub is_onboarded: bool,
    pub did_notify_offline: bool,
    pub auto_offline_disabled: bool,
    pub scoops_saved_count: u64,
}

impl std::fmt::Display for Robot {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}: {}", self.litter_robot_nickname, self.unit_status)
    }
}

#[derive(Clone, Copy, Debug, Deserialize, PartialEq)]
pub enum UnitStatus {
    RDY,
    CCP,
    CCC,
    CSF,
    DF1,
    DF2,
    CST,
    CSI,
    BR,
    P,
    OFF,
    SDF,
    DFS,
}

impl std::fmt::Display for UnitStatus {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        let s = match *self {
            Self::RDY => "Unit is ready",
            Self::CCP => "Clean cycle in progress",
            Self::CCC => "Clean cycle complete",
            Self::CSF => "Cat sensor fault",
            Self::DF1 => "Drawer full - 2 cycles remaining",
            Self::DF2 => "Drawer full - 1 cycle remaining",
            Self::CST => "Cat sensor timing",
            Self::CSI => "Cat sensor interrupted",
            Self::BR => "Bonnet removed",
            Self::P => "Unit is paused",
            Self::OFF => "Unit is powered off",
            Self::SDF => "Drawer full on startup and will not cycle",
            Self::DFS => "Drawer full and will not cycle",
        };

        write!(f, "{}", s)
    }
}

fn deserialize_bool<'de, D>(deserializer: D) -> Result<bool, D::Error>
where
    D: de::Deserializer<'de>,
{
    Ok(match de::Deserialize::deserialize(deserializer)? {
        Value::Bool(b) => b,
        Value::String(s) => match s.as_str() {
            "0" | "false" => false,
            "1" | "true" => true,
            _ => return Err(de::Error::custom("Failed to parse bool")),
        }
        _ => return Err(de::Error::custom("Failed to parse bool")),
    })
}


fn deserialize_u64<'de, D>(deserializer: D) -> Result<u64, D::Error>
where
    D: de::Deserializer<'de>,
{
    Ok(match de::Deserialize::deserialize(deserializer)? {
        Value::Number(n) => n.as_u64().unwrap(),
        Value::String(s) => s.parse::<u64>().unwrap(),
        _ => return Err(de::Error::custom("Failed to parse u64")),
    })
}