Add basic and power supply sysfs classes
This commit is contained in:
parent
e4579b6998
commit
346341b804
9 changed files with 715 additions and 8 deletions
|
@ -1,5 +1,5 @@
|
||||||
[package]
|
[package]
|
||||||
name = "sysfs-nav"
|
name = "sysfuss"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
|
|
59
src/attribute.rs
Normal file
59
src/attribute.rs
Normal file
|
@ -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<Vec<u8>> {
|
||||||
|
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<String> {
|
||||||
|
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<T: FromStr<Err=E>, E>(&self, entity: &dyn crate::SysEntity) -> Result<T, EitherErr2<std::io::Error, E>> {
|
||||||
|
let s = self.read_str(entity).map_err(EitherErr2::First)?;
|
||||||
|
T::from_str(&s).map_err(EitherErr2::Second)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl <X: SysAttribute> SysAttributeExt for X {}
|
91
src/basic.rs
Normal file
91
src/basic.rs
Normal file
|
@ -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<Path>) -> Self {
|
||||||
|
Self {
|
||||||
|
path: path.as_ref().to_path_buf(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn all(root: impl AsRef<Path>, class: impl AsRef<Path>) -> IoResult<impl Iterator<Item=IoResult<Self>>> {
|
||||||
|
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<A: crate::SysAttribute>(&self, attributes: impl Iterator<Item=A>) -> impl Iterator<Item=A> {
|
||||||
|
let found_attrs: Vec<PathBuf> = 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<Path> 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<String> {
|
||||||
|
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<String, String, <String as std::str::FromStr>::Err> for BasicEntityPath {
|
||||||
|
fn capabilities(&self) -> Vec<String> {
|
||||||
|
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(())
|
||||||
|
}
|
||||||
|
}
|
107
src/entity.rs
Normal file
107
src/entity.rs
Normal file
|
@ -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<Path> {
|
||||||
|
/// Convert specialized entity into general entity path container
|
||||||
|
fn to_entity_path(self) -> EntityPath;
|
||||||
|
|
||||||
|
/// Get the entity's name
|
||||||
|
fn name(&self) -> IoResult<String>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// sysfs class entity functionality extension
|
||||||
|
pub trait SysEntityRawExt: SysEntity {
|
||||||
|
/// Get an attribute on the entity
|
||||||
|
fn attribute<A: crate::SysAttribute, T: std::str::FromStr<Err=E>, E>(&self, attr: A) -> Result<T, EitherErr2<std::io::Error, E>>;
|
||||||
|
/// Get an attribute by filename in the entity's directory
|
||||||
|
fn attribute_str<A: AsRef<Path>>(&self, attr: A) -> IoResult<String>;
|
||||||
|
|
||||||
|
/// Returns true when the entity has the attribute
|
||||||
|
fn exists<A: crate::SysAttribute>(&self, attr: A) -> bool;
|
||||||
|
|
||||||
|
/// Returns true when the entity has the attribute
|
||||||
|
fn exists_str<A: AsRef<Path>>(&self, attr: A) -> bool;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl <X: SysEntity> SysEntityRawExt for X {
|
||||||
|
fn attribute<A: crate::SysAttribute, T: std::str::FromStr<Err=E>, E>(&self, attr: A) -> Result<T, EitherErr2<std::io::Error, E>> {
|
||||||
|
attr.parse(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn attribute_str<A: AsRef<Path>>(&self, attr: A) -> IoResult<String> {
|
||||||
|
std::fs::read_to_string(self.as_ref().join(attr))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn exists_str<A: AsRef<Path>>(&self, attr: A) -> bool {
|
||||||
|
self.as_ref().join(attr).exists()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn exists<A: crate::SysAttribute>(&self, attr: A) -> bool {
|
||||||
|
attr.exists(self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// sysfs class entity attribute type indicator
|
||||||
|
pub trait SysEntityAttributes<A: crate::SysAttribute, T: std::str::FromStr<Err=E>, E>: SysEntity + Sized {
|
||||||
|
/// Get an attribute on the entity
|
||||||
|
fn attribute(&self, attr: A) -> Result<T, EitherErr2<std::io::Error, E>> {
|
||||||
|
attr.parse(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get attributes available on this entity;
|
||||||
|
fn capabilities(&self) -> Vec<A>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*pub trait SysEntityAttributesExt<A: crate::SysAttribute, T: std::str::FromStr<Err=E>, E>: SysEntityAttributes<A, T, E> {
|
||||||
|
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<dyn SysEntity>),
|
||||||
|
}
|
||||||
|
|
||||||
|
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<Path> 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<String> {
|
||||||
|
self.inner().name()
|
||||||
|
}
|
||||||
|
}
|
47
src/errors.rs
Normal file
47
src/errors.rs
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
/// Multi-error convenience enum
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum EitherErr2<T1, T2> {
|
||||||
|
/// Type 1
|
||||||
|
First(T1),
|
||||||
|
/// Type 2
|
||||||
|
Second(T2),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl <T1, T2> EitherErr2<T1, T2> {
|
||||||
|
/// 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 <T1: std::fmt::Display, T2: std::fmt::Display> std::fmt::Display for EitherErr2<T1, T2> {
|
||||||
|
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 <T1: std::error::Error, T2: std::error::Error> std::error::Error for EitherErr2<T1, T2> {}
|
||||||
|
|
||||||
|
/// 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> {}
|
95
src/hwmon.rs
95
src/hwmon.rs
|
@ -6,12 +6,12 @@ const HWMON_DIR_PATH: &'static str = "sys/class/hwmon/";
|
||||||
|
|
||||||
/// Attribute representation.
|
/// Attribute representation.
|
||||||
/// Attribute files are usually in the format `<type><number>_<item>`.
|
/// Attribute files are usually in the format `<type><number>_<item>`.
|
||||||
#[derive(Clone, Copy)]
|
#[derive(Clone, Copy, PartialEq, Eq)]
|
||||||
pub struct HwMonAttribute {
|
pub struct HwMonAttribute {
|
||||||
inner: HwMonAttributeInner,
|
inner: HwMonAttributeInner,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy)]
|
#[derive(Clone, Copy, PartialEq, Eq)]
|
||||||
enum HwMonAttributeInner {
|
enum HwMonAttributeInner {
|
||||||
Name,
|
Name,
|
||||||
Standard {
|
Standard {
|
||||||
|
@ -22,8 +22,30 @@ enum HwMonAttributeInner {
|
||||||
Uevent,
|
Uevent,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl std::str::FromStr for HwMonAttributeInner {
|
||||||
|
type Err = String;
|
||||||
|
|
||||||
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
|
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 `<type><number>_<item>`
|
/// Attribute type in the format `<type><number>_<item>`
|
||||||
#[derive(Clone, Copy)]
|
#[derive(Clone, Copy, PartialEq, Eq)]
|
||||||
pub enum HwMonAttributeType {
|
pub enum HwMonAttributeType {
|
||||||
/// Voltage
|
/// Voltage
|
||||||
In,
|
In,
|
||||||
|
@ -36,7 +58,7 @@ pub enum HwMonAttributeType {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl HwMonAttributeType {
|
impl HwMonAttributeType {
|
||||||
fn to_attr_str(&self) -> &str {
|
const fn to_attr_str(&self) -> &str {
|
||||||
match self {
|
match self {
|
||||||
Self::In => "in",
|
Self::In => "in",
|
||||||
Self::Temp => "out",
|
Self::Temp => "out",
|
||||||
|
@ -44,10 +66,24 @@ impl HwMonAttributeType {
|
||||||
Self::Curr => "curr",
|
Self::Curr => "curr",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn from_attr_start(s: &str) -> Result<Self, String> {
|
||||||
|
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 `<type><number>_<item>`
|
/// Attribute item in the format `<type><number>_<item>`
|
||||||
#[derive(Clone, Copy)]
|
#[derive(Clone, Copy, PartialEq, Eq)]
|
||||||
pub enum HwMonAttributeItem {
|
pub enum HwMonAttributeItem {
|
||||||
/// Input
|
/// Input
|
||||||
Input,
|
Input,
|
||||||
|
@ -58,13 +94,25 @@ pub enum HwMonAttributeItem {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl HwMonAttributeItem {
|
impl HwMonAttributeItem {
|
||||||
fn to_attr_str(&self) -> &str {
|
const fn to_attr_str(&self) -> &str {
|
||||||
match self {
|
match self {
|
||||||
Self::Input => "input",
|
Self::Input => "input",
|
||||||
Self::Min => "min",
|
Self::Min => "min",
|
||||||
Self::Max => "max",
|
Self::Max => "max",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn from_attr_end(s: &str) -> Result<Self, String> {
|
||||||
|
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 {
|
impl HwMonAttribute {
|
||||||
|
@ -97,6 +145,13 @@ impl HwMonAttribute {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl crate::SysAttribute for HwMonAttribute {
|
||||||
|
fn filename(&self) -> PathBuf {
|
||||||
|
PathBuf::from(self.to_attr_str())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// hwmon<number>/ directory
|
/// hwmon<number>/ directory
|
||||||
pub struct HwMonPath {
|
pub struct HwMonPath {
|
||||||
path: PathBuf
|
path: PathBuf
|
||||||
|
@ -161,10 +216,33 @@ impl AsRef<Path> for HwMonPath {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl crate::SysEntity for HwMonPath {
|
||||||
|
fn to_entity_path(self) -> crate::EntityPath {
|
||||||
|
crate::EntityPath::HwMon(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn name(&self) -> IoResult<String> {
|
||||||
|
self.attribute(HwMonAttribute::name())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl crate::SysEntityAttributes<HwMonAttribute, String, <String as std::str::FromStr>::Err> for HwMonPath {
|
||||||
|
fn capabilities(&self) -> Vec<HwMonAttribute> {
|
||||||
|
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::<HwMonAttributeInner>().ok())
|
||||||
|
).map(|inner| HwMonAttribute {inner}).collect()
|
||||||
|
} else {
|
||||||
|
Vec::with_capacity(0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
use crate::SysEntityAttributes;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn hwmon_all() -> std::io::Result<()> {
|
fn hwmon_all() -> std::io::Result<()> {
|
||||||
|
@ -172,7 +250,10 @@ mod tests {
|
||||||
let all_hwmon: Vec<_> = HwMonPath::all(sys)?.collect();
|
let all_hwmon: Vec<_> = HwMonPath::all(sys)?.collect();
|
||||||
assert!(!all_hwmon.is_empty());
|
assert!(!all_hwmon.is_empty());
|
||||||
for hwmon in all_hwmon.into_iter() {
|
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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
15
src/lib.rs
15
src/lib.rs
|
@ -4,9 +4,24 @@
|
||||||
|
|
||||||
pub(crate) const DEFAULT_ROOT: &'static str = "/";
|
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;
|
mod hwmon;
|
||||||
pub use hwmon::{HwMonPath, HwMonAttribute, HwMonAttributeType, HwMonAttributeItem};
|
pub use hwmon::{HwMonPath, HwMonAttribute, HwMonAttributeType, HwMonAttributeItem};
|
||||||
|
|
||||||
|
mod power_supply;
|
||||||
|
pub use power_supply::{PowerSupplyPath, PowerSupplyType, PowerSupplyAttribute};
|
||||||
|
|
||||||
mod syspath;
|
mod syspath;
|
||||||
pub use syspath::SysPath;
|
pub use syspath::SysPath;
|
||||||
|
|
||||||
|
|
292
src/power_supply.rs
Normal file
292
src/power_supply.rs
Normal file
|
@ -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/<entity>/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<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
|
||||||
|
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_str("type")
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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, String, <String as std::str::FromStr>::Err> 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::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(())
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,6 +1,8 @@
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use std::convert::AsRef;
|
use std::convert::AsRef;
|
||||||
|
|
||||||
|
use crate::SysEntity;
|
||||||
|
|
||||||
/// sysfs root
|
/// sysfs root
|
||||||
pub struct SysPath {
|
pub struct SysPath {
|
||||||
root: PathBuf,
|
root: PathBuf,
|
||||||
|
@ -15,6 +17,14 @@ impl SysPath {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get all entities in the system
|
||||||
|
pub fn entities(&self) -> std::io::Result<impl Iterator<Item=std::io::Result<crate::EntityPath>> + '_> {
|
||||||
|
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
|
/// Find a hardware monitor entry by name
|
||||||
pub fn hwmon_by_name(&self, name: &str) -> std::io::Result<crate::HwMonPath> {
|
pub fn hwmon_by_name(&self, name: &str) -> std::io::Result<crate::HwMonPath> {
|
||||||
match crate::HwMonPath::name(self, name) {
|
match crate::HwMonPath::name(self, name) {
|
||||||
|
@ -30,6 +40,11 @@ impl SysPath {
|
||||||
entry.attribute(crate::HwMonAttribute::name())?;
|
entry.attribute(crate::HwMonAttribute::name())?;
|
||||||
Ok(entry)
|
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 {
|
impl std::default::Default for SysPath {
|
||||||
|
|
Loading…
Reference in a new issue