sysfuss 0.3.0

sysfs wrapper for convenience
Documentation
use std::path::{Path, PathBuf};
use std::convert::AsRef;
use std::io::Result as IoResult;

use crate::SysEntityAttributesExt;

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(Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "derive", derive(Debug))]
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 instantaneous charge value of the power supply (uA).
    ChargeNow,
    /// Reports the maximum charge value of the power supply (uA).
    ChargeFull,
    /// Reports the maximum rated charge value of the power supply (uA).
    ChargeFullDesign,
    /// 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
    /// Custom entity attribute
    Custom(&'static str)
}

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::ChargeNow => "charge_now",
            Self::ChargeFull => "charge_full",
            Self::ChargeFullDesign => "charge_full_design",
            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",
            Self::Custom(s) => s,
        };
        s.into()
    }
}

/// power_supply/<entity>/type attribute value
#[derive(Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "derive", derive(Debug))]
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<Self, Self::Err> {
        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/<entity>/ directory
#[cfg_attr(feature = "derive", derive(Debug, Clone))]
pub struct PowerSupplyPath {
    inner: crate::BasicEntityPath,
}

impl PowerSupplyPath {
    pub(crate) fn all(root: impl AsRef<Path>) -> IoResult<impl Iterator<Item=IoResult<Self>>> {
        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<String> {
        self.attribute(PowerSupplyAttribute::Type).map_err(|e| e.map_infallible_second())
    }
    
    /// Get the power supply type
    pub fn type_enum(&self) -> Result<PowerSupplyType, crate::EitherErr2<std::io::Error, <PowerSupplyType as std::str::FromStr>::Err>> {
        self.attribute(PowerSupplyAttribute::Type)
    }
}


impl AsRef<Path> 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<String> {
        self.inner.name()
    }
}

impl crate::SysEntityAttributes<PowerSupplyAttribute> for PowerSupplyPath {
    fn capabilities(&self) -> Vec<PowerSupplyAttribute> {
        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::ChargeNow,
            PowerSupplyAttribute::ChargeFull,
            PowerSupplyAttribute::ChargeFullDesign,
            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;
    use crate::SysEntityAttributesExt;
    use crate::SysAttribute;
    
    #[test]
    fn power_supply_all() -> std::io::Result<()> {
        if std::fs::read_dir("/sys/class/power_supply")?.count() == 0 {
            eprintln!("power_supply test skipped since none exist (maybe running on a desktop PC?)");
            return Ok(())
        }
        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(())
    }

    #[test]
    fn power_supply_capabilities() -> std::io::Result<()> {
        let sys = crate::SysPath::default();
        if !PowerSupplyAttribute::Type.exists(&sys.power_supply_by_name("BAT0")) {
            // skip if system has no battery
            eprintln!("power_supply test skipped since BAT0 does not exist (maybe running on a desktop PC?)");
            return Ok(())
        }
        let psu = sys.power_supply(crate::capability::attributes([
            PowerSupplyAttribute::Type,
            PowerSupplyAttribute::Capacity,
        ].into_iter()))?.next().expect("Missing capable battery");
        assert!(psu.attribute::<PowerSupplyType, _>(PowerSupplyAttribute::Type).expect("type capable but also incapable") == PowerSupplyType::Battery);
        Ok(())
    }
}