nut_client/
var.rs

1use core::fmt;
2use std::collections::HashSet;
3use std::convert::TryFrom;
4use std::time::Duration;
5
6/// Well-known variable keys for NUT UPS devices.
7///
8/// List retrieved from: https://networkupstools.org/docs/user-manual.chunked/apcs01.html
9pub mod key {
10    /// Device model.
11    pub const DEVICE_MODEL: &str = "device.model";
12    /// Device manufacturer.
13    pub const DEVICE_MANUFACTURER: &str = "device.mfr";
14    /// Device serial number.
15    pub const DEVICE_SERIAL: &str = "device.serial";
16    /// Device type.
17    pub const DEVICE_TYPE: &str = "device.type";
18    /// Device description.
19    pub const DEVICE_DESCRIPTION: &str = "device.description";
20    /// Device administrator name.
21    pub const DEVICE_CONTACT: &str = "device.contact";
22    /// Device physical location.
23    pub const DEVICE_LOCATION: &str = "device.location";
24    /// Device part number.
25    pub const DEVICE_PART: &str = "device.part";
26    /// Device MAC address.
27    pub const DEVICE_MAC_ADDRESS: &str = "device.macaddr";
28    /// Device uptime.
29    pub const DEVICE_UPTIME: &str = "device.uptime";
30}
31
32/// Well-known variables for NUT UPS devices.
33///
34/// List retrieved from: https://networkupstools.org/docs/user-manual.chunked/apcs01.html
35#[derive(Debug, Clone, Eq, PartialEq)]
36pub enum Variable {
37    /// Device model.
38    DeviceModel(String),
39    /// Device manufacturer.
40    DeviceManufacturer(String),
41    /// Device serial number.
42    DeviceSerial(String),
43    /// Device type.
44    DeviceType(DeviceType),
45    /// Device description.
46    DeviceDescription(String),
47    /// Device administrator name.
48    DeviceContact(String),
49    /// Device physical location.
50    DeviceLocation(String),
51    /// Device part number.
52    DevicePart(String),
53    /// Device MAC address.
54    DeviceMacAddress(String),
55    /// Device uptime.
56    DeviceUptime(Duration),
57
58    /// Any other variable. Value is a tuple of (key, value).
59    Other((String, String)),
60}
61
62impl Variable {
63    /// Parses a variable from its key and value.
64    pub fn parse(name: &str, value: String) -> Variable {
65        use self::key::*;
66
67        match name {
68            DEVICE_MODEL => Self::DeviceModel(value),
69            DEVICE_MANUFACTURER => Self::DeviceManufacturer(value),
70            DEVICE_SERIAL => Self::DeviceSerial(value),
71            DEVICE_TYPE => Self::DeviceType(DeviceType::from(value)),
72            DEVICE_DESCRIPTION => Self::DeviceDescription(value),
73            DEVICE_CONTACT => Self::DeviceContact(value),
74            DEVICE_LOCATION => Self::DeviceLocation(value),
75            DEVICE_PART => Self::DevicePart(value),
76            DEVICE_MAC_ADDRESS => Self::DeviceMacAddress(value),
77            DEVICE_UPTIME => Self::DeviceUptime(Duration::from_secs(
78                value.parse().expect("invalid uptime value"),
79            )),
80
81            _ => Self::Other((name.into(), value)),
82        }
83    }
84
85    /// Returns the NUT name of the variable.
86    pub fn name(&self) -> &str {
87        use self::key::*;
88        match self {
89            Self::DeviceModel(_) => DEVICE_MODEL,
90            Self::DeviceManufacturer(_) => DEVICE_MANUFACTURER,
91            Self::DeviceSerial(_) => DEVICE_SERIAL,
92            Self::DeviceType(_) => DEVICE_TYPE,
93            Self::DeviceDescription(_) => DEVICE_DESCRIPTION,
94            Self::DeviceContact(_) => DEVICE_CONTACT,
95            Self::DeviceLocation(_) => DEVICE_LOCATION,
96            Self::DevicePart(_) => DEVICE_PART,
97            Self::DeviceMacAddress(_) => DEVICE_MAC_ADDRESS,
98            Self::DeviceUptime(_) => DEVICE_UPTIME,
99            Self::Other((name, _)) => name.as_str(),
100        }
101    }
102
103    /// Returns the value of the NUT variable.
104    pub fn value(&self) -> String {
105        match self {
106            Self::DeviceModel(value) => value.clone(),
107            Self::DeviceManufacturer(value) => value.clone(),
108            Self::DeviceSerial(value) => value.clone(),
109            Self::DeviceType(value) => value.to_string(),
110            Self::DeviceDescription(value) => value.clone(),
111            Self::DeviceContact(value) => value.clone(),
112            Self::DeviceLocation(value) => value.clone(),
113            Self::DevicePart(value) => value.clone(),
114            Self::DeviceMacAddress(value) => value.clone(),
115            Self::DeviceUptime(value) => value.as_secs().to_string(),
116            Self::Other((_, value)) => value.clone(),
117        }
118    }
119}
120
121impl fmt::Display for Variable {
122    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
123        write!(f, "{}: {}", self.name(), self.value())
124    }
125}
126
127/// NUT device type.
128#[derive(Debug, Clone, Eq, PartialEq)]
129pub enum DeviceType {
130    /// UPS (Uninterruptible Power Supply)
131    Ups,
132    /// PDU (Power Distribution Unit)
133    Pdu,
134    /// SCD (Solar Controller Device)
135    Scd,
136    /// PSU (Power Supply Unit)
137    Psu,
138    /// ATS (Automatic Transfer Switch)
139    Ats,
140    /// Other device type.
141    Other(String),
142}
143
144impl DeviceType {
145    /// Convert from string.
146    pub fn from(v: String) -> DeviceType {
147        match v.as_str() {
148            "ups" => Self::Ups,
149            "pdu" => Self::Pdu,
150            "scd" => Self::Scd,
151            "psu" => Self::Psu,
152            "ats" => Self::Ats,
153            _ => Self::Other(v),
154        }
155    }
156}
157
158impl fmt::Display for DeviceType {
159    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
160        match self {
161            Self::Ups => write!(f, "ups"),
162            Self::Pdu => write!(f, "pdu"),
163            Self::Scd => write!(f, "scd"),
164            Self::Psu => write!(f, "psu"),
165            Self::Ats => write!(f, "ats"),
166            Self::Other(val) => write!(f, "other({})", val),
167        }
168    }
169}
170
171/// NUT Variable type
172#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
173#[allow(dead_code)]
174pub(crate) enum VariableType {
175    /// A mutable variable (`RW`).
176    Rw,
177    /// An enumerated type, which supports a few specific values (`ENUM`).
178    Enum,
179    /// A string with a maximum size (`STRING:n`).
180    String(usize),
181    /// A numeric type, either integer or float, comprised in the range defined by `LIST RANGE`.
182    Range,
183    /// A simple numeric value, either integer or float.
184    Number,
185}
186
187impl TryFrom<&str> for VariableType {
188    type Error = crate::ClientError;
189
190    fn try_from(value: &str) -> Result<Self, Self::Error> {
191        match value {
192            "RW" => Ok(Self::Rw),
193            "ENUM" => Ok(Self::Enum),
194            "RANGE" => Ok(Self::Range),
195            "NUMBER" => Ok(Self::Number),
196            other => {
197                if other.starts_with("STRING:") {
198                    let size = other
199                        .splitn(2, ':')
200                        .nth(1)
201                        .map(|s| s.parse().ok())
202                        .flatten()
203                        .ok_or_else(|| {
204                            crate::ClientError::Nut(crate::NutError::Generic(
205                                "Invalid STRING definition".into(),
206                            ))
207                        })?;
208                    Ok(Self::String(size))
209                } else {
210                    Err(crate::ClientError::Nut(crate::NutError::Generic(format!(
211                        "Unrecognized variable type: {}",
212                        value
213                    ))))
214                }
215            }
216        }
217    }
218}
219
220/// NUT Variable definition.
221#[derive(Debug, Clone, Eq, PartialEq)]
222pub struct VariableDefinition(String, HashSet<VariableType>);
223
224impl VariableDefinition {
225    /// The name of this variable.
226    pub fn name(&self) -> &str {
227        self.0.as_str()
228    }
229
230    /// Whether this variable is mutable.
231    pub fn is_mutable(&self) -> bool {
232        self.1.contains(&VariableType::Rw)
233    }
234
235    /// Whether this variable is an enumerated type.
236    pub fn is_enum(&self) -> bool {
237        self.1.contains(&VariableType::Enum)
238    }
239
240    /// Whether this variable is a String type
241    pub fn is_string(&self) -> bool {
242        self.1.iter().any(|t| matches!(t, VariableType::String(_)))
243    }
244
245    /// Whether this variable is a numeric type,
246    /// either integer or float, comprised in a range
247    pub fn is_range(&self) -> bool {
248        self.1.contains(&VariableType::Range)
249    }
250
251    /// Whether this variable is a numeric type, either integer or float.
252    pub fn is_number(&self) -> bool {
253        self.1.contains(&VariableType::Number)
254    }
255
256    /// Returns the max string length, if applicable.
257    pub fn get_string_length(&self) -> Option<usize> {
258        self.1.iter().find_map(|t| match t {
259            VariableType::String(n) => Some(*n),
260            _ => None,
261        })
262    }
263}
264
265impl<A: ToString> TryFrom<(A, Vec<&str>)> for VariableDefinition {
266    type Error = crate::ClientError;
267
268    fn try_from(value: (A, Vec<&str>)) -> Result<Self, Self::Error> {
269        Ok(VariableDefinition(
270            value.0.to_string(),
271            value
272                .1
273                .iter()
274                .map(|s| VariableType::try_from(*s))
275                .collect::<crate::Result<HashSet<VariableType>>>()?,
276        ))
277    }
278}
279
280/// A range of values for a variable.
281#[derive(Debug, Clone, Eq, PartialEq)]
282pub struct VariableRange(pub String, pub String);
283
284#[cfg(test)]
285mod tests {
286    use std::iter::FromIterator;
287
288    use super::*;
289
290    #[test]
291    fn test_parse_variable_definition() {
292        assert_eq!(
293            VariableDefinition::try_from(("var0", vec![])).unwrap(),
294            VariableDefinition("var0".into(), HashSet::new())
295        );
296
297        assert_eq!(
298            VariableDefinition::try_from(("var1", vec!["RW"])).unwrap(),
299            VariableDefinition(
300                "var1".into(),
301                HashSet::from_iter(vec![VariableType::Rw].into_iter())
302            )
303        );
304
305        assert_eq!(
306            VariableDefinition::try_from(("var1", vec!["RW", "STRING:123"])).unwrap(),
307            VariableDefinition(
308                "var1".into(),
309                HashSet::from_iter(vec![VariableType::Rw, VariableType::String(123)].into_iter())
310            )
311        );
312
313        assert!(
314            VariableDefinition::try_from(("var1", vec!["RW", "STRING:123"]))
315                .unwrap()
316                .is_mutable()
317        );
318        assert!(
319            VariableDefinition::try_from(("var1", vec!["RW", "STRING:123"]))
320                .unwrap()
321                .is_string()
322        );
323        assert!(
324            !VariableDefinition::try_from(("var1", vec!["RW", "STRING:123"]))
325                .unwrap()
326                .is_enum()
327        );
328        assert!(
329            !VariableDefinition::try_from(("var1", vec!["RW", "STRING:123"]))
330                .unwrap()
331                .is_number()
332        );
333        assert!(
334            !VariableDefinition::try_from(("var1", vec!["RW", "STRING:123"]))
335                .unwrap()
336                .is_range()
337        );
338        assert_eq!(
339            VariableDefinition::try_from(("var1", vec!["RW", "STRING:123"]))
340                .unwrap()
341                .get_string_length(),
342            Some(123)
343        );
344    }
345}