use core::fmt; use std::collections::HashSet; use std::convert::TryFrom; use std::time::Duration; /// Well-known variable keys for NUT UPS devices. /// /// List retrieved from: pub mod key { /// Device model. pub const DEVICE_MODEL: &str = "device.model"; /// Device manufacturer. pub const DEVICE_MANUFACTURER: &str = "device.mfr"; /// Device serial number. pub const DEVICE_SERIAL: &str = "device.serial"; /// Device type. pub const DEVICE_TYPE: &str = "device.type"; /// Device description. pub const DEVICE_DESCRIPTION: &str = "device.description"; /// Device administrator name. pub const DEVICE_CONTACT: &str = "device.contact"; /// Device physical location. pub const DEVICE_LOCATION: &str = "device.location"; /// Device part number. pub const DEVICE_PART: &str = "device.part"; /// Device MAC address. pub const DEVICE_MAC_ADDRESS: &str = "device.macaddr"; /// Device uptime. pub const DEVICE_UPTIME: &str = "device.uptime"; } /// Well-known variables for NUT UPS devices. /// /// List retrieved from: #[derive(Debug, Clone, Eq, PartialEq)] pub enum Variable { /// Device model. DeviceModel(String), /// Device manufacturer. DeviceManufacturer(String), /// Device serial number. DeviceSerial(String), /// Device type. DeviceType(DeviceType), /// Device description. DeviceDescription(String), /// Device administrator name. DeviceContact(String), /// Device physical location. DeviceLocation(String), /// Device part number. DevicePart(String), /// Device MAC address. DeviceMacAddress(String), /// Device uptime. DeviceUptime(Duration), /// Any other variable. Value is a tuple of (key, value). Other((String, String)), } impl Variable { /// Parses a variable from its key and value. pub fn parse(name: &str, value: String) -> Variable { use self::key::*; match name { DEVICE_MODEL => Self::DeviceModel(value), DEVICE_MANUFACTURER => Self::DeviceManufacturer(value), DEVICE_SERIAL => Self::DeviceSerial(value), DEVICE_TYPE => Self::DeviceType(DeviceType::from(value)), DEVICE_DESCRIPTION => Self::DeviceDescription(value), DEVICE_CONTACT => Self::DeviceContact(value), DEVICE_LOCATION => Self::DeviceLocation(value), DEVICE_PART => Self::DevicePart(value), DEVICE_MAC_ADDRESS => Self::DeviceMacAddress(value), DEVICE_UPTIME => Self::DeviceUptime(Duration::from_secs( value.parse().expect("invalid uptime value"), )), _ => Self::Other((name.into(), value)), } } /// Returns the NUT name of the variable. pub fn name(&self) -> &str { use self::key::*; match self { Self::DeviceModel(_) => DEVICE_MODEL, Self::DeviceManufacturer(_) => DEVICE_MANUFACTURER, Self::DeviceSerial(_) => DEVICE_SERIAL, Self::DeviceType(_) => DEVICE_TYPE, Self::DeviceDescription(_) => DEVICE_DESCRIPTION, Self::DeviceContact(_) => DEVICE_CONTACT, Self::DeviceLocation(_) => DEVICE_LOCATION, Self::DevicePart(_) => DEVICE_PART, Self::DeviceMacAddress(_) => DEVICE_MAC_ADDRESS, Self::DeviceUptime(_) => DEVICE_UPTIME, Self::Other((name, _)) => name.as_str(), } } /// Returns the value of the NUT variable. pub fn value(&self) -> String { match self { Self::DeviceModel(value) => value.clone(), Self::DeviceManufacturer(value) => value.clone(), Self::DeviceSerial(value) => value.clone(), Self::DeviceType(value) => value.to_string(), Self::DeviceDescription(value) => value.clone(), Self::DeviceContact(value) => value.clone(), Self::DeviceLocation(value) => value.clone(), Self::DevicePart(value) => value.clone(), Self::DeviceMacAddress(value) => value.clone(), Self::DeviceUptime(value) => value.as_secs().to_string(), Self::Other((_, value)) => value.clone(), } } } impl fmt::Display for Variable { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}: {}", self.name(), self.value()) } } /// NUT device type. #[derive(Debug, Clone, Eq, PartialEq)] pub enum DeviceType { /// UPS (Uninterruptible Power Supply) Ups, /// PDU (Power Distribution Unit) Pdu, /// SCD (Solar Controller Device) Scd, /// PSU (Power Supply Unit) Psu, /// ATS (Automatic Transfer Switch) Ats, /// Other device type. Other(String), } impl DeviceType { /// Convert from string. pub fn from(v: String) -> DeviceType { match v.as_str() { "ups" => Self::Ups, "pdu" => Self::Pdu, "scd" => Self::Scd, "psu" => Self::Psu, "ats" => Self::Ats, _ => Self::Other(v), } } } impl fmt::Display for DeviceType { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Self::Ups => write!(f, "ups"), Self::Pdu => write!(f, "pdu"), Self::Scd => write!(f, "scd"), Self::Psu => write!(f, "psu"), Self::Ats => write!(f, "ats"), Self::Other(val) => write!(f, "other({})", val), } } } /// NUT Variable type #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] #[allow(dead_code)] pub(crate) enum VariableType { /// A mutable variable (`RW`). Rw, /// An enumerated type, which supports a few specific values (`ENUM`). Enum, /// A string with a maximum size (`STRING:n`). String(usize), /// A numeric type, either integer or float, comprised in the range defined by `LIST RANGE`. Range, /// A simple numeric value, either integer or float. Number, } impl TryFrom<&str> for VariableType { type Error = crate::ClientError; fn try_from(value: &str) -> Result { match value { "RW" => Ok(Self::Rw), "ENUM" => Ok(Self::Enum), "RANGE" => Ok(Self::Range), "NUMBER" => Ok(Self::Number), other => { if other.starts_with("STRING:") { let size = other .split_once(':') .and_then(|(_, s)| s.parse().ok()) .ok_or_else(|| crate::ClientError::generic("Invalid STRING definition"))?; Ok(Self::String(size)) } else { Err(crate::ClientError::generic(format!( "Unrecognized variable type: {}", value ))) } } } } } /// NUT Variable definition. #[derive(Debug, Clone, Eq, PartialEq)] pub struct VariableDefinition(String, HashSet); impl VariableDefinition { /// The name of this variable. pub fn name(&self) -> &str { self.0.as_str() } /// Whether this variable is mutable. pub fn is_mutable(&self) -> bool { self.1.contains(&VariableType::Rw) } /// Whether this variable is an enumerated type. pub fn is_enum(&self) -> bool { self.1.contains(&VariableType::Enum) } /// Whether this variable is a String type pub fn is_string(&self) -> bool { self.1.iter().any(|t| matches!(t, VariableType::String(_))) } /// Whether this variable is a numeric type, /// either integer or float, comprised in a range pub fn is_range(&self) -> bool { self.1.contains(&VariableType::Range) } /// Whether this variable is a numeric type, either integer or float. pub fn is_number(&self) -> bool { self.1.contains(&VariableType::Number) } /// Returns the max string length, if applicable. pub fn get_string_length(&self) -> Option { self.1.iter().find_map(|t| match t { VariableType::String(n) => Some(*n), _ => None, }) } } impl TryFrom<(A, Vec<&str>)> for VariableDefinition { type Error = crate::ClientError; fn try_from(value: (A, Vec<&str>)) -> Result { Ok(VariableDefinition( value.0.to_string(), value .1 .iter() .map(|s| VariableType::try_from(*s)) .collect::>>()?, )) } } /// A range of values for a variable. #[derive(Debug, Clone, Eq, PartialEq)] pub struct VariableRange(pub String, pub String); #[cfg(test)] mod tests { use std::iter::FromIterator; use super::*; #[test] fn test_parse_variable_definition() { assert_eq!( VariableDefinition::try_from(("var0", vec![])).unwrap(), VariableDefinition("var0".into(), HashSet::new()) ); assert_eq!( VariableDefinition::try_from(("var1", vec!["RW"])).unwrap(), VariableDefinition( "var1".into(), HashSet::from_iter(vec![VariableType::Rw].into_iter()) ) ); assert_eq!( VariableDefinition::try_from(("var1", vec!["RW", "STRING:123"])).unwrap(), VariableDefinition( "var1".into(), HashSet::from_iter(vec![VariableType::Rw, VariableType::String(123)].into_iter()) ) ); assert!( VariableDefinition::try_from(("var1", vec!["RW", "STRING:123"])) .unwrap() .is_mutable() ); assert!( VariableDefinition::try_from(("var1", vec!["RW", "STRING:123"])) .unwrap() .is_string() ); assert!( !VariableDefinition::try_from(("var1", vec!["RW", "STRING:123"])) .unwrap() .is_enum() ); assert!( !VariableDefinition::try_from(("var1", vec!["RW", "STRING:123"])) .unwrap() .is_number() ); assert!( !VariableDefinition::try_from(("var1", vec!["RW", "STRING:123"])) .unwrap() .is_range() ); assert_eq!( VariableDefinition::try_from(("var1", vec!["RW", "STRING:123"])) .unwrap() .get_string_length(), Some(123) ); } }