From 346341b804d5e6c795c8bb9e27b3062c584539a5 Mon Sep 17 00:00:00 2001 From: NGnius Date: Tue, 1 Aug 2023 11:32:04 -0400 Subject: [PATCH] Add basic and power supply sysfs classes --- Cargo.toml | 2 +- src/attribute.rs | 59 +++++++++ src/basic.rs | 91 ++++++++++++++ src/entity.rs | 107 ++++++++++++++++ src/errors.rs | 47 +++++++ src/hwmon.rs | 95 ++++++++++++-- src/lib.rs | 15 +++ src/power_supply.rs | 292 ++++++++++++++++++++++++++++++++++++++++++++ src/syspath.rs | 15 +++ 9 files changed, 715 insertions(+), 8 deletions(-) create mode 100644 src/attribute.rs create mode 100644 src/basic.rs create mode 100644 src/entity.rs create mode 100644 src/errors.rs create mode 100644 src/power_supply.rs diff --git a/Cargo.toml b/Cargo.toml index 5d7509b..35bf9b9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "sysfs-nav" +name = "sysfuss" version = "0.1.0" edition = "2021" diff --git a/src/attribute.rs b/src/attribute.rs new file mode 100644 index 0000000..405049e --- /dev/null +++ b/src/attribute.rs @@ -0,0 +1,59 @@ +use std::path::PathBuf; +use std::io::Result as IoResult; +use std::str::FromStr; + +use crate::EitherErr2; + +/// sysfs entity attribute file functionality +pub trait SysAttribute: Eq { + /// attribute file name + fn filename(&self) -> PathBuf; + + /// full path to attribute file + fn path(&self, entity: &dyn crate::SysEntity) -> PathBuf { + entity.as_ref().join(self.filename()) + } + + /// returns true if the path to the attribute file exists + fn exists(&self, entity: &dyn crate::SysEntity) -> bool { + let path = self.path(entity); + path.exists() && path.is_file() + } + + /// read attribute value from sysfs + fn read_value(&self, entity: &dyn crate::SysEntity) -> IoResult> { + std::fs::read(self.path(entity)) + } + + /// write attribute value to sysfs + fn write_value(&self, entity: &dyn crate::SysEntity, value: &[u8]) -> IoResult<()> { + std::fs::write(self.path(entity), value) + } +} + +impl SysAttribute for String { + fn filename(&self) -> PathBuf { + PathBuf::from(self) + } +} + +/// sysfs entity attribute functionality extension +pub trait SysAttributeExt: SysAttribute { + /// read attribute as string + fn read_str(&self, entity: &dyn crate::SysEntity) -> IoResult { + std::fs::read_to_string(self.path(entity)) + } + + /// write attribute string value to sysfsfilter_map + fn write_str(&self, entity: &dyn crate::SysEntity, s: &str) -> IoResult<()> { + std::fs::write(self.path(entity), s.as_bytes()) + } + + /// read and parse the attribute value + fn parse, E>(&self, entity: &dyn crate::SysEntity) -> Result> { + let s = self.read_str(entity).map_err(EitherErr2::First)?; + T::from_str(&s).map_err(EitherErr2::Second) + } +} + +impl SysAttributeExt for X {} diff --git a/src/basic.rs b/src/basic.rs new file mode 100644 index 0000000..375c095 --- /dev/null +++ b/src/basic.rs @@ -0,0 +1,91 @@ +use std::path::{Path, PathBuf}; +use std::convert::AsRef; +use std::io::Result as IoResult; + +const SYS_CLASS_PATH: &str = "sys/class"; + +/// Generic entity path with basic functionality +pub struct BasicEntityPath { + path: PathBuf +} + +impl BasicEntityPath { + /// Create a new BasicEntityPath for manipulating path + pub fn new(path: impl AsRef) -> Self { + Self { + path: path.as_ref().to_path_buf(), + } + } + + pub(crate) fn all(root: impl AsRef, class: impl AsRef) -> IoResult>> { + let dir_path = root.as_ref().join(SYS_CLASS_PATH).join(class); + dir_path.read_dir() + .map( + |iter| iter.filter_map( + |entry| entry.map( + |entry| if entry.path().is_file() { None } else { Some(Self::new(entry.path())) } + ).transpose())) + } + + /// Filter out attributes that do not exist on this entity + pub fn filter_capabilities(&self, attributes: impl Iterator) -> impl Iterator { + let found_attrs: Vec = if let Ok(dir_iter) = self.path.read_dir() { + dir_iter.filter_map( + |entry| entry.ok().filter(|entry| entry.path().is_file()).map(|entry| PathBuf::from(entry.file_name())) + ).collect() + } else { + Vec::with_capacity(0) + }; + + attributes.filter(move |attr| found_attrs.contains(&attr.filename())) + } +} + +impl AsRef for BasicEntityPath { + fn as_ref(&self) -> &Path { + self.path.as_path() + } +} + +impl crate::SysEntity for BasicEntityPath { + fn to_entity_path(self) -> crate::EntityPath { + crate::EntityPath::Generic(self) + } + + fn name(&self) -> IoResult { + if let Some(s) = self.path.file_name().map(|name| name.to_str().map(|s| s.to_owned())).flatten() { + Ok(s) + } else { + Err(std::io::Error::from_raw_os_error(std::io::ErrorKind::InvalidInput as _)) + } + } +} + +impl crate::SysEntityAttributes::Err> for BasicEntityPath { + fn capabilities(&self) -> Vec { + if let Ok(dir_iter) = self.path.read_dir() { + dir_iter.filter_map( + |entry| entry.ok().filter(|entry| entry.path().is_file()).and_then(|entry| entry.file_name().to_str().map(|s| s.to_owned())) + ).collect() + } else { + Vec::with_capacity(0) + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::SysEntityRawExt; + + #[test] + fn basic_entity_all() -> std::io::Result<()> { + let sys = crate::SysPath::default(); + let all_basic: Vec<_> = BasicEntityPath::all(sys, "drm")?.collect(); + assert!(!all_basic.is_empty()); + for ent in all_basic.into_iter() { + assert!(ent?.attribute_str("uevent")? != ""); + } + Ok(()) + } +} diff --git a/src/entity.rs b/src/entity.rs new file mode 100644 index 0000000..ee21e9c --- /dev/null +++ b/src/entity.rs @@ -0,0 +1,107 @@ +use std::path::Path; +use std::convert::AsRef; +use std::io::Result as IoResult; + +use crate::EitherErr2; +use crate::SysAttributeExt; + +/// sysfs class entity functionality +pub trait SysEntity: AsRef { + /// Convert specialized entity into general entity path container + fn to_entity_path(self) -> EntityPath; + + /// Get the entity's name + fn name(&self) -> IoResult; +} + +/// sysfs class entity functionality extension +pub trait SysEntityRawExt: SysEntity { + /// Get an attribute on the entity + fn attribute, E>(&self, attr: A) -> Result>; + /// Get an attribute by filename in the entity's directory + fn attribute_str>(&self, attr: A) -> IoResult; + + /// Returns true when the entity has the attribute + fn exists(&self, attr: A) -> bool; + + /// Returns true when the entity has the attribute + fn exists_str>(&self, attr: A) -> bool; +} + +impl SysEntityRawExt for X { + fn attribute, E>(&self, attr: A) -> Result> { + attr.parse(self) + } + + fn attribute_str>(&self, attr: A) -> IoResult { + std::fs::read_to_string(self.as_ref().join(attr)) + } + + fn exists_str>(&self, attr: A) -> bool { + self.as_ref().join(attr).exists() + } + + fn exists(&self, attr: A) -> bool { + attr.exists(self) + } +} + + +/// sysfs class entity attribute type indicator +pub trait SysEntityAttributes, E>: SysEntity + Sized { + /// Get an attribute on the entity + fn attribute(&self, attr: A) -> Result> { + attr.parse(self) + } + + /// Get attributes available on this entity; + fn capabilities(&self) -> Vec; +} + +/*pub trait SysEntityAttributesExt, E>: SysEntityAttributes { + fn attribute() +}*/ + +/// sysfs class entity implementors +pub enum EntityPath { + /// hwmon + HwMon(crate::HwMonPath), + /// power_supply + PowerSupply(crate::PowerSupplyPath), + /// Generic + Generic(crate::BasicEntityPath), + /// Miscellaneous + Custom(Box), +} + +impl EntityPath { + fn inner(&self) -> &'_ dyn SysEntity { + match self { + Self::HwMon(inner) => inner, + Self::PowerSupply(inner) => inner, + Self::Generic(inner) => inner, + Self::Custom(inner) => inner.as_ref(), + } + } +} + +impl AsRef for EntityPath { + fn as_ref(&self) -> &Path { + match self { + Self::HwMon(inner) => inner.as_ref(), + Self::PowerSupply(inner) => inner.as_ref(), + Self::Generic(inner) => inner.as_ref(), + Self::Custom(inner) => inner.as_ref().as_ref(), + } + } +} + +impl SysEntity for EntityPath { + fn to_entity_path(self) -> Self { + self + } + + fn name(&self) -> IoResult { + self.inner().name() + } +} diff --git a/src/errors.rs b/src/errors.rs new file mode 100644 index 0000000..cb2265b --- /dev/null +++ b/src/errors.rs @@ -0,0 +1,47 @@ +/// Multi-error convenience enum +#[derive(Debug)] +pub enum EitherErr2 { + /// Type 1 + First(T1), + /// Type 2 + Second(T2), +} + +impl EitherErr2 { + /// Is this the first error type? + pub fn is_type1(&self) -> bool { + matches!(self, Self::First(_)) + } + + /// Is this the second error type? + pub fn is_type2(&self) -> bool { + matches!(self, Self::Second(_)) + } +} + +impl std::fmt::Display for EitherErr2 { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::First(x) => write!(f, "{}", x), + Self::Second(x) => write!(f, "{}", x), + } + } +} + +impl std::error::Error for EitherErr2 {} + +/// Value parse error for an enumeration. +/// This usually indicates an unexpected value (one without an enum variant) was received while parsing. +#[derive(Debug)] +pub struct ValueEnumError<'a> { + pub(crate) got: String, + pub(crate) allowed: &'a [&'a str], +} + +impl <'a> std::fmt::Display for ValueEnumError<'a> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "Got {}, expected one of {:?}", self.got, self.allowed) + } +} + +impl <'a> std::error::Error for ValueEnumError<'a> {} diff --git a/src/hwmon.rs b/src/hwmon.rs index f7ff4c7..a0911c8 100644 --- a/src/hwmon.rs +++ b/src/hwmon.rs @@ -6,12 +6,12 @@ const HWMON_DIR_PATH: &'static str = "sys/class/hwmon/"; /// Attribute representation. /// Attribute files are usually in the format `_`. -#[derive(Clone, Copy)] +#[derive(Clone, Copy, PartialEq, Eq)] pub struct HwMonAttribute { inner: HwMonAttributeInner, } -#[derive(Clone, Copy)] +#[derive(Clone, Copy, PartialEq, Eq)] enum HwMonAttributeInner { Name, Standard { @@ -22,8 +22,30 @@ enum HwMonAttributeInner { Uevent, } +impl std::str::FromStr for HwMonAttributeInner { + type Err = String; + + fn from_str(s: &str) -> Result { + match s { + "name" => Ok(Self::Name), + "uevent" => Ok(Self::Uevent), + s => { + let start = HwMonAttributeType::from_attr_start(s)?; + let end = HwMonAttributeItem::from_attr_end(s)?; + let trimmed = s.trim_start_matches(start.to_attr_str()).trim_end_matches(end.to_attr_str()); + if trimmed.ends_with("_") { + let index = trimmed.trim_end_matches('_').parse().map_err(|e| format!("Invalid number in attribute name {}: {}", s, e))?; + Ok(Self::Standard { ty: start, number: index, item: end }) + } else { + Err(format!("Missing underscore in attribute name {}", s)) + } + } + } + } +} + /// Attribute type in the format `_` -#[derive(Clone, Copy)] +#[derive(Clone, Copy, PartialEq, Eq)] pub enum HwMonAttributeType { /// Voltage In, @@ -36,7 +58,7 @@ pub enum HwMonAttributeType { } impl HwMonAttributeType { - fn to_attr_str(&self) -> &str { + const fn to_attr_str(&self) -> &str { match self { Self::In => "in", Self::Temp => "out", @@ -44,10 +66,24 @@ impl HwMonAttributeType { Self::Curr => "curr", } } + + fn from_attr_start(s: &str) -> Result { + if s.starts_with(Self::In.to_attr_str()) { + Ok(Self::In) + } else if s.starts_with(Self::Temp.to_attr_str()) { + Ok(Self::Temp) + } else if s.starts_with(Self::Fan.to_attr_str()) { + Ok(Self::Fan) + } else if s.starts_with(Self::Curr.to_attr_str()) { + Ok(Self::Curr) + } else { + Err(format!("Unrecognised hwmon attribute type in name {}", s)) + } + } } /// Attribute item in the format `_` -#[derive(Clone, Copy)] +#[derive(Clone, Copy, PartialEq, Eq)] pub enum HwMonAttributeItem { /// Input Input, @@ -58,13 +94,25 @@ pub enum HwMonAttributeItem { } impl HwMonAttributeItem { - fn to_attr_str(&self) -> &str { + const fn to_attr_str(&self) -> &str { match self { Self::Input => "input", Self::Min => "min", Self::Max => "max", } } + + fn from_attr_end(s: &str) -> Result { + if s.ends_with(Self::Input.to_attr_str()) { + Ok(Self::Input) + } else if s.ends_with(Self::Min.to_attr_str()) { + Ok(Self::Min) + } else if s.ends_with(Self::Max.to_attr_str()) { + Ok(Self::Max) + } else { + Err(format!("Unrecognised hwmon attribute type in name {}", s)) + } + } } impl HwMonAttribute { @@ -97,6 +145,13 @@ impl HwMonAttribute { } } } + +impl crate::SysAttribute for HwMonAttribute { + fn filename(&self) -> PathBuf { + PathBuf::from(self.to_attr_str()) + } +} + /// hwmon/ directory pub struct HwMonPath { path: PathBuf @@ -161,10 +216,33 @@ impl AsRef for HwMonPath { } } +impl crate::SysEntity for HwMonPath { + fn to_entity_path(self) -> crate::EntityPath { + crate::EntityPath::HwMon(self) + } + + fn name(&self) -> IoResult { + self.attribute(HwMonAttribute::name()) + } +} + +impl crate::SysEntityAttributes::Err> for HwMonPath { + fn capabilities(&self) -> Vec { + if let Ok(dir_iter) = self.path.read_dir() { + dir_iter.filter_map( + |entry| entry.ok().filter(|entry| entry.path().is_file()).and_then(|entry| entry.file_name().into_string().ok()).and_then(|s| s.parse::().ok()) + ).map(|inner| HwMonAttribute {inner}).collect() + } else { + Vec::with_capacity(0) + } + } +} + #[cfg(test)] mod tests { use super::*; + use crate::SysEntityAttributes; #[test] fn hwmon_all() -> std::io::Result<()> { @@ -172,7 +250,10 @@ mod tests { let all_hwmon: Vec<_> = HwMonPath::all(sys)?.collect(); assert!(!all_hwmon.is_empty()); for hwmon in all_hwmon.into_iter() { - assert!(hwmon?.attribute(HwMonAttribute::name())? != ""); + let hwmon = hwmon?; + assert!(hwmon.attribute(HwMonAttribute::name())? != ""); + assert!(!hwmon.capabilities().is_empty()); + assert!(hwmon.capabilities().contains(&HwMonAttribute::name())) } Ok(()) } diff --git a/src/lib.rs b/src/lib.rs index 4ee7097..1acc06a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,9 +4,24 @@ pub(crate) const DEFAULT_ROOT: &'static str = "/"; +mod attribute; +pub use attribute::{SysAttribute, SysAttributeExt}; + +mod basic; +pub use basic::BasicEntityPath; + +mod entity; +pub use entity::{EntityPath, SysEntity, SysEntityRawExt, SysEntityAttributes}; + +mod errors; +pub use errors::{EitherErr2, ValueEnumError}; + mod hwmon; pub use hwmon::{HwMonPath, HwMonAttribute, HwMonAttributeType, HwMonAttributeItem}; +mod power_supply; +pub use power_supply::{PowerSupplyPath, PowerSupplyType, PowerSupplyAttribute}; + mod syspath; pub use syspath::SysPath; diff --git a/src/power_supply.rs b/src/power_supply.rs new file mode 100644 index 0000000..86a3ff2 --- /dev/null +++ b/src/power_supply.rs @@ -0,0 +1,292 @@ +use std::path::{Path, PathBuf}; +use std::convert::AsRef; +use std::io::Result as IoResult; + +use crate::SysEntityRawExt; +//use crate::SysEntityAttributes; + +const PSU_DIR_PATH: &'static str = "sys/class/power_supply/"; + +const CLASS_PATH: &'static str = "power_supply"; + +/// Attribute files in a power supply directory +/// These attributes are taken from https://github.com/torvalds/linux/blob/master/Documentation/ABI/testing/sysfs-class-power +#[derive(Debug, PartialEq, Eq)] +pub enum PowerSupplyAttribute { + /// Reports the name of the device manufacturer. + Manufacturer, + /// Reports the name of the device model. + ModelName, + /// Reports the serial number of the device. + SerialNumber, + /// Describes the main type of the supply. + Type, + /// Reports an average current reading over a fixed period (uA). + CurrentAverage, + /// Reports the maximum current the power supply supports (uA). + CurrentMax, + /// Reports the instantaneous current of the power supply (uA). + CurrentNow, + /// Reports the temperature reading (dC -- 1/10 degree Celcius). + Temperature, + /// Maximum temperature where the supply will notify user-space of the event (dC). + TemperatureAlertMax, + /// Minimum temperature where the supply will notify user-space of the event (dC). + TemperatureAlertMin, + /// Reports the maximum allowed temperature for operation (dC). + TemperatureMax, + /// Reports the minimum allowed temperature for operation (dC). + TemperatureMin, + /// Reports the maximum safe voltage (uV). + VoltageMax, + /// Reports the minimum safe voltage (uV). + VoltageMin, + /// Reports the instantaneous voltage of the power supply (uV). + VoltageNow, + /// Fine grain representation of the battery capacity (%). + Capacity, + /// Maximum battery capacity trip-wire value where the supply will notify user-space of the event (%). + CapacityAlertMax, + /// Minimum battery capacity trip-wire value where the supply will notify user-space of the event (%). + CapacityAlertMin, + /// This values provides the maximum error margin expected to exist by the fuel gauge in percent (%). + CapacityErrorMargin, + /// Coarse representation of battery capacity ("Unknown", "Critical", "Low", "Normal", "High", or "Full"). + CapacityLevel, + /// Maximum allowable charging current (uA). + ChargeControlLimit, + /// Maximum legal value for the charge_control_limit property (uA). + ChargeControlLimitMax, + /// Represents a battery percentage level, below which charging will begin (%). + ChargeControlStartThreshold, + /// Represents a battery percentage level, above which charging will stop (%). + ChargeControlEndThreshold, + /// Represents the type of charging currently being applied to the battery ("Unknown", "N/A", "Trickle", "Fast", "Standard", "Adaptive", "Custom", "Long Life", or "Bypass"). + ChargeType, + /// Reports the charging current value which is used to determine when the battery is considered full and charging should end (uA). + ChargeTermCurrent, + /// Reports the health of the battery or battery side of charger functionality ( "Unknown", "Good", "Overheat", "Dead", "Over voltage", "Unspecified failure", "Cold", "Watchdog timer expire", "Safety timer expire", "Over current", "Calibration required", "Warm", "Cool", "Hot", or "No battery"). + Health, + /// Reports the charging current applied during pre-charging phase for a battery charge cycle (uA). + PrechargeCurrent, + /// Reports whether a battery is present or not in the system (0 or 1). If the property does not exist, the battery is considered to be present. + Present, + /// Represents the charging status of the battery ( "Unknown", "Charging", "Discharging", "Not charging", or "Full"). + Status, + /// Represents the charging behaviour ("auto", "inhibit-charge", or "force-discharge"). + ChargeBehaviour, + /// Describes the battery technology supported by the supply. + Technology, + /// Reports an average voltage over a fixed period (uV). + VoltageAverage, + /// Reports the number of full charge + discharge cycles the battery has undergone. + CycleCount, + // TODO USB properties +} + +impl crate::SysAttribute for PowerSupplyAttribute { + fn filename(&self) -> PathBuf { + let s = match self { + Self::Manufacturer => "manufacturer", + Self::ModelName => "model_name", + Self::SerialNumber => "serial_number", + Self::Type => "type", + Self::CurrentAverage => "current_avg", + Self::CurrentMax => "current_max", + Self::CurrentNow => "current_now", + Self::Temperature => "temp", + Self::TemperatureAlertMax => "temp_alert_max", + Self::TemperatureAlertMin => "temp_alert_min", + Self::TemperatureMax => "temp_max", + Self::TemperatureMin => "temp_min", + Self::VoltageMax => "voltage_max", + Self::VoltageMin => "voltage_min", + Self::VoltageNow => "voltage_now", + Self::Capacity => "capacity", + Self::CapacityAlertMax => "capacity_alert_max", + Self::CapacityAlertMin => "capacity_alert_min", + Self::CapacityErrorMargin => "capacity_error_margin", + Self::CapacityLevel => "capacity_level", + Self::ChargeControlLimit => "charge_control_limit", + Self::ChargeControlLimitMax => "charge_control_limit_max", + Self::ChargeControlStartThreshold => "charge_control_start_threshold", + Self::ChargeControlEndThreshold => "charge_control_end_threshold", + Self::ChargeType => "charge_type", + Self::ChargeTermCurrent => "charge_term_current", + Self::Health => "health", + Self::PrechargeCurrent => "precharge_current", + Self::Present => "present", + Self::Status => "status", + Self::ChargeBehaviour => "charge_behaviour", + Self::Technology => "technology", + Self::VoltageAverage => "voltage_avg", + Self::CycleCount => "cycle_count", + }; + s.into() + } +} + +/// power_supply//type attribute value +#[derive(Debug, PartialEq, Eq)] +pub enum PowerSupplyType { + /// Battery + Battery, + /// UPS + UPS, + /// Mains + Mains, + /// USB + USB, + /// Wireless + Wireless, +} + +impl PowerSupplyType { + fn allowed_values() -> &'static [&'static str] { + &[ + "Battery", + "UPS", + "Mains", + "USB", + "Wireless", + ] + } +} + +impl std::fmt::Display for PowerSupplyType { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Battery => write!(f, "Battery"), + Self::UPS => write!(f, "UPS"), + Self::Mains => write!(f, "Mains"), + Self::USB => write!(f, "USB"), + Self::Wireless => write!(f, "Wireless") + } + } +} + +impl std::str::FromStr for PowerSupplyType { + type Err = crate::ValueEnumError<'static>; + + fn from_str(s: &str) -> Result { + match s { + "Battery" => Ok(Self::Battery), + "UPS" => Ok(Self::UPS), + "Mains" => Ok(Self::Mains), + "USB" => Ok(Self::USB), + "Wireless" => Ok(Self::Wireless), + other => Err(crate::ValueEnumError { + got: other.to_owned(), + allowed: Self::allowed_values(), + }) + } + } +} + +/// power_supply// directory +pub struct PowerSupplyPath { + inner: crate::BasicEntityPath, +} + +impl PowerSupplyPath { + pub(crate) fn all(root: impl AsRef) -> IoResult>> { + Ok(crate::BasicEntityPath::all(root, CLASS_PATH)? + .map(|result| result.map(|inner| Self { inner }))) + } + + /// Get a power_supply entry by directory name + pub(crate) fn name(root: &crate::SysPath, name: &str) -> Self { + Self { + inner: crate::BasicEntityPath::new(root.as_ref().join(PSU_DIR_PATH).join(name)) + } + } + + /// Get the power supply type + pub fn type_str(&self) -> IoResult { + self.attribute_str("type") + } + + /// Get the power supply type + pub fn type_enum(&self) -> Result::Err>> { + self.attribute(PowerSupplyAttribute::Type) + } +} + + +impl AsRef for PowerSupplyPath { + fn as_ref(&self) -> &Path { + self.inner.as_ref() + } +} + +impl crate::SysEntity for PowerSupplyPath { + fn to_entity_path(self) -> crate::EntityPath { + crate::EntityPath::PowerSupply(self) + } + + fn name(&self) -> IoResult { + self.inner.name() + } +} + +impl crate::SysEntityAttributes::Err> for PowerSupplyPath { + fn capabilities(&self) -> Vec { + self.inner.filter_capabilities(vec![ + PowerSupplyAttribute::Manufacturer, + PowerSupplyAttribute::ModelName, + PowerSupplyAttribute::SerialNumber, + PowerSupplyAttribute::Type, + PowerSupplyAttribute::CurrentAverage, + PowerSupplyAttribute::CurrentMax, + PowerSupplyAttribute::CurrentNow, + PowerSupplyAttribute::Temperature, + PowerSupplyAttribute::TemperatureAlertMax, + PowerSupplyAttribute::TemperatureAlertMin, + PowerSupplyAttribute::TemperatureMax, + PowerSupplyAttribute::TemperatureMin, + PowerSupplyAttribute::VoltageMax, + PowerSupplyAttribute::VoltageMin, + PowerSupplyAttribute::VoltageNow, + PowerSupplyAttribute::Capacity, + PowerSupplyAttribute::CapacityAlertMax, + PowerSupplyAttribute::CapacityAlertMin, + PowerSupplyAttribute::CapacityErrorMargin, + PowerSupplyAttribute::CapacityLevel, + PowerSupplyAttribute::ChargeControlLimit, + PowerSupplyAttribute::ChargeControlLimitMax, + PowerSupplyAttribute::ChargeControlStartThreshold, + PowerSupplyAttribute::ChargeControlEndThreshold, + PowerSupplyAttribute::ChargeType, + PowerSupplyAttribute::ChargeTermCurrent, + PowerSupplyAttribute::Health, + PowerSupplyAttribute::PrechargeCurrent, + PowerSupplyAttribute::Present, + PowerSupplyAttribute::Status, + PowerSupplyAttribute::ChargeBehaviour, + PowerSupplyAttribute::Technology, + PowerSupplyAttribute::VoltageAverage, + PowerSupplyAttribute::CycleCount, + ].into_iter()).collect() + } +} + + +#[cfg(test)] +mod tests { + use super::*; + use crate::SysEntityAttributes; + + #[test] + fn power_supply_all() -> std::io::Result<()> { + let sys = crate::SysPath::default(); + let all_psu: Vec<_> = PowerSupplyPath::all(sys)?.collect(); + assert!(!all_psu.is_empty()); + for p in all_psu.into_iter() { + let p = p?; + assert!(p.type_str()? != ""); + assert!(!p.capabilities().is_empty()); + assert!(p.capabilities().contains(&PowerSupplyAttribute::Type)) + } + Ok(()) + } +} diff --git a/src/syspath.rs b/src/syspath.rs index 7812f67..c0dd0e9 100644 --- a/src/syspath.rs +++ b/src/syspath.rs @@ -1,6 +1,8 @@ use std::path::{Path, PathBuf}; use std::convert::AsRef; +use crate::SysEntity; + /// sysfs root pub struct SysPath { root: PathBuf, @@ -15,6 +17,14 @@ impl SysPath { } } + /// Get all entities in the system + pub fn entities(&self) -> std::io::Result> + '_> { + Ok(crate::HwMonPath::all(self)? + .map(|entity| entity.map(|entity| entity.to_entity_path())) + .chain(crate::PowerSupplyPath::all(self)? + .map(|entity| entity.map(|entity| entity.to_entity_path())))) + } + /// Find a hardware monitor entry by name pub fn hwmon_by_name(&self, name: &str) -> std::io::Result { match crate::HwMonPath::name(self, name) { @@ -30,6 +40,11 @@ impl SysPath { entry.attribute(crate::HwMonAttribute::name())?; Ok(entry) } + + /// Find a power supply entry by name + pub fn power_supply(&self, name: &str) -> crate::PowerSupplyPath { + crate::PowerSupplyPath::name(self, name) + } } impl std::default::Default for SysPath {