diff --git a/.gitignore b/.gitignore index 80c1f2f..c20a3ad 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,5 @@ **/*.rs.bk Cargo.lock +*.orig local_build.sh diff --git a/Cargo.toml b/Cargo.toml index 0d99298..5df6388 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,3 +7,4 @@ members = [ [patch.crates-io] battery = { path = "./battery" } +battery-ffi = { path = "./battery-ffi" } diff --git a/battery-cli/src/ui/app.rs b/battery-cli/src/ui/app.rs index 7475915..f7b12c7 100644 --- a/battery-cli/src/ui/app.rs +++ b/battery-cli/src/ui/app.rs @@ -3,7 +3,7 @@ use super::util::tabs; use super::util::graph; -use battery::Battery; +use battery::{Battery, Result}; use battery::units::power::watt; use battery::units::electric_potential::volt; use battery::units::thermodynamic_temperature::degree_celsius; @@ -24,8 +24,9 @@ pub struct App<'a> { } impl<'a> App<'a> { - pub fn new(manager: battery::Manager) -> App<'a> { - let stats: Vec = manager.iter() + pub fn new(manager: battery::Manager) -> Result> { + let stats: Vec = manager.batteries()? + .flatten() .map(|b| { BatteryStats { battery: b, @@ -45,11 +46,11 @@ impl<'a> App<'a> { }) .collect(); - App { + Ok(App { manager, batteries: stats, tabs: tabs::TabsState::new(names), - } + }) } pub fn update(&mut self) { diff --git a/battery-cli/src/ui/mod.rs b/battery-cli/src/ui/mod.rs index f72437f..869dfd1 100644 --- a/battery-cli/src/ui/mod.rs +++ b/battery-cli/src/ui/mod.rs @@ -59,9 +59,9 @@ pub fn start() -> Result<(), Box> { terminal.hide_cursor()?; let events = Events::new(); - let manager = battery::Manager::new(); + let manager = battery::Manager::new()?; - let mut app = app::App::new(manager); + let mut app = app::App::new(manager)?; loop { terminal.draw(|mut f| { diff --git a/battery-ffi/Cargo.toml b/battery-ffi/Cargo.toml index 6ce9b55..645a42e 100644 --- a/battery-ffi/Cargo.toml +++ b/battery-ffi/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "battery-ffi" -version = "0.2.0" +version = "0.7.0" authors = ["svartalf "] edition = "2018" description = "FFI bindings for battery crate" diff --git a/battery-ffi/README.md b/battery-ffi/README.md index 0b144ea..c69d4be 100644 --- a/battery-ffi/README.md +++ b/battery-ffi/README.md @@ -3,7 +3,7 @@ [![Latest Version](https://img.shields.io/crates/v/battery-ffi.svg)](https://crates.io/crates/battery-ffi) [![Latest Version](https://docs.rs/battery-ffi/badge.svg)](https://docs.rs/battery-ffi) [![Build Status](https://travis-ci.org/svartalf/rust-battery.svg?branch=master)](https://travis-ci.org/svartalf/rust-battery) -[![dependency status](https://deps.rs/crate/battery-ffi/0.2.0/status.svg)](https://deps.rs/crate/battery-ffi/0.2.0) +[![dependency status](https://deps.rs/crate/battery-ffi/0.7.0/status.svg)](https://deps.rs/crate/battery-ffi/0.7.0) ![Apache 2.0 OR MIT licensed](https://img.shields.io/badge/license-Apache2.0%2FMIT-blue.svg) This is a FFI bindings for [battery](https://github.com/svartalf/rust-battery/tree/master/battery) diff --git a/battery-ffi/build.rs b/battery-ffi/build.rs index aea254d..bc16fa6 100644 --- a/battery-ffi/build.rs +++ b/battery-ffi/build.rs @@ -3,13 +3,10 @@ fn build_header() { use std::env; use std::path::PathBuf; - let crate_dir = env::var("CARGO_MANIFEST_DIR") - .expect("CARGO_MANIFEST_DIR env var is not defined"); - let out_dir = PathBuf::from(env::var("OUT_DIR") - .expect("OUT_DIR env var is not defined")); + let crate_dir = env::var("CARGO_MANIFEST_DIR").expect("CARGO_MANIFEST_DIR env var is not defined"); + let out_dir = PathBuf::from(env::var("OUT_DIR").expect("OUT_DIR env var is not defined")); - let config = cbindgen::Config::from_file("cbindgen.toml") - .expect("Unable to find cbindgen.toml configuration file"); + let config = cbindgen::Config::from_file("cbindgen.toml").expect("Unable to find cbindgen.toml configuration file"); cbindgen::generate_with_config(&crate_dir, config) .unwrap() diff --git a/battery-ffi/examples/ffi.c b/battery-ffi/examples/ffi.c index 8f624d6..5cd9d10 100644 --- a/battery-ffi/examples/ffi.c +++ b/battery-ffi/examples/ffi.c @@ -6,6 +6,7 @@ // // 3. Run `./a.out` +#include #include #include #include @@ -109,7 +110,7 @@ void pretty_print(Battery *battery, uint32_t *idx) { printf(" time-to-empty:\t\t%ld sec.\n", time_to_empty); } - printf(" state of charge:\t\t%.2f %%\n", battery_get_state_of_charge(battery)); + printf(" state of charge:\t%.2f %%\n", battery_get_state_of_charge(battery)); float temp = battery_get_temperature(battery); printf(" temperature:\t\t"); if (temp < FLT_MAX) { @@ -118,7 +119,7 @@ void pretty_print(Battery *battery, uint32_t *idx) { printf("N/A\n"); } - printf(" state of health:\t\t%.2f %%\n", battery_get_state_of_health(battery)); + printf(" state of health:\t%.2f %%\n", battery_get_state_of_health(battery)); uint32_t cycle_count = battery_get_cycle_count(battery); printf(" cycle-count:\t\t"); if (cycle_count < UINT_MAX) { @@ -128,13 +129,35 @@ void pretty_print(Battery *battery, uint32_t *idx) { } } +void print_error() { + int length = battery_last_error_length(); + char *message = malloc(length); + // Handle possible error return here + battery_last_error_message(message, strlen(message)); + printf("%s", message); + free(message); +} + void main() { Manager *manager = battery_manager_new(); + if (manager == NULL) { + print_error(); + return; + } + Batteries *iterator = battery_manager_iter(manager); + if (iterator == NULL) { + print_error(); + return; + } + uint32_t idx = 0; while (true) { Battery *battery = battery_iterator_next(iterator); if (battery == NULL) { + if (battery_have_last_error() == 1) { + print_error(); + } break; } diff --git a/battery-ffi/examples/ffi.py b/battery-ffi/examples/ffi.py index 149b12d..a1e7210 100755 --- a/battery-ffi/examples/ffi.py +++ b/battery-ffi/examples/ffi.py @@ -14,6 +14,7 @@ import sys import ctypes +import logging prefix = {'win32': ''}.get(sys.platform, 'lib') extension = {'darwin': 'dylib', 'win32': 'dll'}.get(sys.platform, 'so') @@ -56,19 +57,35 @@ class Battery(ctypes.Structure): pass +def check_result(result, _func, _args): + # Checking if passed value is not `NULL` pointer. + if lib.battery_have_last_error() == 0: + return result + + # If it is, constructing error message and raising it + length = lib.battery_last_error_length() + message = ctypes.create_string_buffer(length) + lib.battery_last_error_message(ctypes.byref(message), len(message)) + + raise ValueError(message.value) + + # # Bindings for exported functions # lib.battery_manager_new.argtypes = None lib.battery_manager_new.restype = ctypes.POINTER(Manager) +lib.battery_manager_new.errcheck = check_result lib.battery_manager_iter.argtypes = (ctypes.POINTER(Manager), ) lib.battery_manager_iter.restype = ctypes.POINTER(Batteries) +lib.battery_manager_iter.errcheck = check_result lib.battery_manager_free.argtypes = (ctypes.POINTER(Manager), ) lib.battery_manager_free.restype = None lib.battery_iterator_next.argtypes = (ctypes.POINTER(Batteries), ) lib.battery_iterator_next.restype = ctypes.POINTER(Battery) +lib.battery_iterator_next.errcheck = check_result lib.battery_free.argtypes = (ctypes.POINTER(Battery), ) lib.battery_free.restype = None @@ -107,6 +124,14 @@ class Battery(ctypes.Structure): lib.battery_get_cycle_count.argtypes = (ctypes.POINTER(Battery), ) lib.battery_get_cycle_count.restype = ctypes.c_uint32 +lib.battery_have_last_error.argtypes = None +lib.battery_have_last_error.restype = ctypes.c_int +lib.battery_last_error_length.argtypes = None +lib.battery_last_error_length.restype = ctypes.c_int +lib.battery_last_error_message.argtypes = (ctypes.c_char_p, ctypes.c_int) +lib.battery_last_error_message.restype = ctypes.c_int + + if __name__ == '__main__': manager = lib.battery_manager_new() iterator = lib.battery_manager_iter(manager) diff --git a/battery-ffi/src/errors.rs b/battery-ffi/src/errors.rs new file mode 100644 index 0000000..e2e929c --- /dev/null +++ b/battery-ffi/src/errors.rs @@ -0,0 +1,86 @@ +use std::cell::RefCell; +use std::error::Error; +use std::ptr; +use std::slice; + +thread_local! { + static LAST_ERROR: RefCell>> = RefCell::new(None); +} + +pub fn set_last_error(err: E) { + LAST_ERROR.with(|prev| { + *prev.borrow_mut() = Some(Box::new(err)); + }); +} + +pub fn take_last_error() -> Option> { + LAST_ERROR.with(|prev| prev.borrow_mut().take()) +} + +pub fn clear_last_error() { + let _ = take_last_error(); +} + +/// Checks if there was an error before. +/// +/// # Returns +/// +/// `0` if there was no error, `1` if error had occured. +#[no_mangle] +pub extern "C" fn battery_have_last_error() -> libc::c_int { + LAST_ERROR.with(|prev| match *prev.borrow() { + Some(_) => 1, + None => 0, + }) +} + +/// Gets error message length if any error had occurred. +/// +/// # Returns +/// +/// If there was no error before, returns `0`, +/// otherwise returns message length including trailing `\0`. +#[no_mangle] +pub extern "C" fn battery_last_error_length() -> libc::c_int { + // TODO: Support Windows UTF-16 strings + LAST_ERROR.with(|prev| match *prev.borrow() { + Some(ref err) => err.to_string().len() as libc::c_int + 1, + None => 0, + }) +} + +/// Fills passed buffer with an error message. +/// +/// Buffer length can be get with [battery_last_error_length](fn.battery_last_error_length.html) function. +/// +/// # Returns +/// +/// Returns `-1` is passed buffer is `NULL` or too small for error message. +/// Returns `0` if there was no error previously. +/// +/// In all other cases returns error message length. +#[no_mangle] +pub unsafe extern "C" fn battery_last_error_message(buffer: *mut libc::c_char, length: libc::c_int) -> libc::c_int { + if buffer.is_null() { + return -1; + } + + let last_error = match take_last_error() { + Some(err) => err, + None => return 0, + }; + + let error_message = last_error.to_string(); + + let buffer = slice::from_raw_parts_mut(buffer as *mut u8, length as usize); + + if error_message.len() >= buffer.len() { + return -1; + } + + ptr::copy_nonoverlapping(error_message.as_ptr(), buffer.as_mut_ptr(), error_message.len()); + + buffer[error_message.len()] = b'\0'; + + error_message.len() as libc::c_int +} diff --git a/battery-ffi/src/iterator.rs b/battery-ffi/src/iterator.rs index 8a6206b..0fef5eb 100644 --- a/battery-ffi/src/iterator.rs +++ b/battery-ffi/src/iterator.rs @@ -7,25 +7,35 @@ use crate::{Batteries, Battery}; /// Caller is required to call [battery_free](fn.battery_free.html) in order /// to properly free memory for the returned battery instance. /// -/// Caller is required to call [battery_iterator_free](fn.battery_iterator_free.html) -/// if order to properly free memory for the returned batteries iterator instance. -/// /// # Panics /// -/// This function will panic if any passed pointer is `NULL` +/// This function will panic if passed pointer is `NULL`. /// /// # Returns /// -/// If there is no batteries left to iterate, this function returns `NULL`, -/// otherwise it returns pointer to next battery. +/// Returns pointer to next battery. +/// +/// If there is no batteries left to iterate or some error happened, this function will return `NULL`. +/// +/// Caller is required to differentiate between these two cases and should check +/// if there was any error with [battery_have_last_error](fn.battery_have_last_error.html). +/// +/// If there is no batteries left, `battery_have_last_error` will return `0`. #[no_mangle] pub unsafe extern "C" fn battery_iterator_next(ptr: *mut Batteries) -> *mut Battery { assert!(!ptr.is_null()); let iterator = &mut *ptr; match iterator.next() { - None => ptr::null_mut(), - Some(battery) => Box::into_raw(Box::new(battery)), + None => { + crate::errors::clear_last_error(); + ptr::null_mut() + } + Some(Ok(battery)) => Box::into_raw(Box::new(battery)), + Some(Err(e)) => { + crate::errors::set_last_error(e); + ptr::null_mut() + } } } diff --git a/battery-ffi/src/lib.rs b/battery-ffi/src/lib.rs index 7c473fe..74774b3 100644 --- a/battery-ffi/src/lib.rs +++ b/battery-ffi/src/lib.rs @@ -2,12 +2,15 @@ //! //! # Bindings generation //! -//! Among library creation this crate generates `battery_ffi.h` file, +//! Among library creation this crate generates `battery_ffi.h` file, enabled by default by `cbindgen` feature, //! which might be useful for automatic bindings generation or just with plain `C`/`C++` development. //! //! After build it will be located somewhere at `target/*/build/battery-ffi-*/out/`, //! depending on build profile (`debug`/`release`) and build hash. //! +//! Disabling `cbindgen` feature might speed up compilation a little bit, +//! especially if you don't need the header file. +//! //! # Examples //! //! ```c @@ -15,9 +18,12 @@ //! //! void main() { //! Manager *manager = battery_manager_new(); +//! // .. handle `manager == NULL` here .. //! Batteries *iterator = battery_manager_iter(manager); +//! // .. handle `iterator == NULL` here .. //! while (true) { //! Battery *battery = battery_iterator_next(iterator); +//! // .. handle possible error here .. //! if (battery == NULL) { //! break; //! } @@ -31,12 +37,15 @@ //! battery_manager_free(manager); //! } //! ``` +//! +//! Also, check the `examples/` directory in the repository for examples with C and Python. // cbindgen==0.8.0 fails to export typedefs for opaque pointers // from the battery crate, if this line is missing extern crate battery as battery_lib; mod battery; +mod errors; mod iterator; mod manager; mod state; @@ -61,6 +70,7 @@ pub type Batteries = battery_lib::Batteries; pub type Battery = battery_lib::Battery; pub use self::battery::*; +pub use self::errors::{battery_last_error_length, battery_last_error_message}; pub use self::iterator::*; pub use self::manager::*; pub use self::state::*; diff --git a/battery-ffi/src/manager.rs b/battery-ffi/src/manager.rs index e507314..7362ee1 100644 --- a/battery-ffi/src/manager.rs +++ b/battery-ffi/src/manager.rs @@ -1,12 +1,27 @@ +use std::ptr; + use crate::{Batteries, Battery, Manager}; /// Creates new batteries manager instance. /// -/// Returns opaque pointer to it. Caller is required to call [battery_manager_free](fn.battery_manager_free.html) +/// # Returns +/// +/// Returns opaque pointer to manager instance. +/// Caller is required to call [battery_manager_free](fn.battery_manager_free.html) /// to properly free memory. +/// +/// `NULL` pointer might be returned if manager creation had failed. +/// Caller can check [battery_last_error_message](fn.battery_last_error_message.html) +/// for error details. #[no_mangle] pub extern "C" fn battery_manager_new() -> *mut Manager { - Box::into_raw(Box::new(Manager::new())) + match Manager::new() { + Ok(manager) => Box::into_raw(Box::new(manager)), + Err(e) => { + crate::errors::set_last_error(e); + ptr::null_mut() + } + } } /// Creates an iterator over batteries from manager instance. @@ -16,12 +31,24 @@ pub extern "C" fn battery_manager_new() -> *mut Manager { /// # Panics /// /// This function will panic if passed pointer is `NULL` +/// +/// # Returns +/// +/// `NULL` pointer will be returned if iterator creation had failed. +/// Caller can check [battery_last_error_message](fn.battery_last_error_message.html) +/// for error details. #[no_mangle] pub unsafe extern "C" fn battery_manager_iter(ptr: *mut Manager) -> *mut Batteries { assert!(!ptr.is_null()); let manager = &*ptr; - Box::into_raw(Box::new(manager.iter())) + match manager.batteries() { + Ok(iterator) => Box::into_raw(Box::new(iterator)), + Err(e) => { + crate::errors::set_last_error(e); + ptr::null_mut() + } + } } /// Refreshes battery information. @@ -32,21 +59,20 @@ pub unsafe extern "C" fn battery_manager_iter(ptr: *mut Manager) -> *mut Batteri /// /// # Returns /// -/// `0` if everything is okay, `1` if refresh failed and `battery_ptr` contains stale information. -pub unsafe extern "C" fn battery_manager_refresh( - manager_ptr: *mut Manager, - battery_ptr: *mut Battery, -) -> libc::c_int { +/// `0` if everything is okay, `-1` if refresh failed and `battery_ptr` contains stale information. +pub unsafe extern "C" fn battery_manager_refresh(manager_ptr: *mut Manager, battery_ptr: *mut Battery) -> libc::c_int { assert!(!manager_ptr.is_null()); let manager = &mut *manager_ptr; assert!(!battery_ptr.is_null()); let mut battery = &mut *battery_ptr; - // TODO: Should there be better error handling? match manager.refresh(&mut battery) { Ok(_) => 0, - Err(_) => 1, + Err(e) => { + crate::errors::set_last_error(e); + -1 + } } } diff --git a/battery-ffi/src/technology.rs b/battery-ffi/src/technology.rs index 999abca..f98352a 100644 --- a/battery-ffi/src/technology.rs +++ b/battery-ffi/src/technology.rs @@ -31,9 +31,7 @@ impl From for Technology { RawTech::NickelCadmium => Technology::TechnologyNickelCadmium, RawTech::NickelZinc => Technology::TechnologyNickelZinc, RawTech::LithiumIronPhosphate => Technology::TechnologyLithiumIronPhosphate, - RawTech::RechargeableAlkalineManganese => { - Technology::TechnologyRechargeableAlkalineManganese - } + RawTech::RechargeableAlkalineManganese => Technology::TechnologyRechargeableAlkalineManganese, _ => Technology::TechnologyUnknown, } } diff --git a/battery/Cargo.toml b/battery/Cargo.toml index 97e9042..ed51b50 100644 --- a/battery/Cargo.toml +++ b/battery/Cargo.toml @@ -20,7 +20,7 @@ num-traits = { version = "0.2", default_features = false } uom = { version = "0.21.1", features = ["autoconvert", "f32", "si"] } [target.'cfg(target_os = "linux")'.dependencies] -lazy-init = "0.3" +lazycell = "1.2.1" [target.'cfg(target_os = "macos")'.dependencies] libc = "0.2.48" @@ -32,3 +32,4 @@ winapi = { version ="0.3.6", features = ["impl-default", "devguid", "winbase", " [target.'cfg(any(target_os = "dragonfly", target_os = "freebsd"))'.dependencies] nix = "0.13.0" +libc = "0.2.48" diff --git a/battery/examples/simple.rs b/battery/examples/simple.rs index 679ebe7..8d561ee 100644 --- a/battery/examples/simple.rs +++ b/battery/examples/simple.rs @@ -4,20 +4,23 @@ use std::io; use std::thread; use std::time::Duration; -fn main() -> io::Result<()> { - let mut manager = battery::Manager::new(); - let mut battery = match manager.iter().next() { - Some(battery) => battery, +fn main() -> battery::Result<()> { + let manager = battery::Manager::new()?; + let mut battery = match manager.batteries()?.next() { + Some(Ok(battery)) => battery, + Some(Err(e)) => { + eprintln!("Unable to access battery information"); + return Err(e); + } None => { eprintln!("Unable to find any batteries"); - return Err(io::Error::from(io::ErrorKind::NotFound)); + return Err(io::Error::from(io::ErrorKind::NotFound).into()); } }; loop { - manager.refresh(&mut battery)?; println!("{:?}", battery); - thread::sleep(Duration::from_secs(1)); + manager.refresh(&mut battery)?; } } diff --git a/battery/src/errors.rs b/battery/src/errors.rs new file mode 100644 index 0000000..82b4fb0 --- /dev/null +++ b/battery/src/errors.rs @@ -0,0 +1,89 @@ +//! Errors handling +//! +use std::error::Error as StdError; +use std::fmt; +use std::io; +use std::result; + +pub type Result = result::Result; + +/// Battery routines error. +/// +/// Since all operations are basically I/O of some kind, +/// this is a thin wrapper around `::std::io::Error` with option +/// to store custom description for debugging purposes. +#[derive(Debug)] +pub struct Error { + source: io::Error, + description: Option<&'static str>, +} + +impl Error { + pub fn not_found(description: &'static str) -> Error { + Error { + source: io::Error::from(io::ErrorKind::NotFound), + description: Some(description), + } + } + + pub fn invalid_data(description: &'static str) -> Error { + Error { + source: io::Error::from(io::ErrorKind::InvalidData), + description: Some(description), + } + } +} + +impl StdError for Error { + fn source(&self) -> Option<&(dyn StdError + 'static)> { + Some(&self.source) + } +} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self.description { + Some(desc) => write!(f, "{}", desc), + None => self.source.fmt(f), + } + } +} + +impl From for Error { + fn from(e: io::Error) -> Self { + Error { + source: e, + description: None, + } + } +} + +#[cfg(any(target_os = "dragonfly", target_os = "freebsd"))] +mod nix_impl { + use std::io; + + use super::Error; + + impl From for Error { + fn from(e: nix::Error) -> Self { + match e { + nix::Error::Sys(errno) => Error { + source: io::Error::from_raw_os_error(errno as i32), + description: Some(errno.desc()), + }, + nix::Error::InvalidPath => Error { + source: io::Error::new(io::ErrorKind::InvalidInput, e), + description: Some("Invalid path"), + }, + nix::Error::InvalidUtf8 => Error { + source: io::Error::new(io::ErrorKind::InvalidData, e), + description: Some("Invalid UTF-8 string"), + }, + nix::Error::UnsupportedOperation => Error { + source: io::Error::new(io::ErrorKind::Other, e), + description: Some("Unsupported operation"), + }, + } + } + } +} diff --git a/battery/src/lib.rs b/battery/src/lib.rs index 4793e84..6cd9c15 100644 --- a/battery/src/lib.rs +++ b/battery/src/lib.rs @@ -19,6 +19,7 @@ #![deny(unused)] #![deny(unstable_features)] +#![deny(bare_trait_objects)] #[macro_use] extern crate cfg_if; @@ -34,6 +35,8 @@ extern crate nix; mod types; #[macro_use] pub mod units; +pub mod errors; mod platform; +pub use self::errors::{Error, Result}; pub use self::types::{Batteries, Battery, Manager, State, Technology}; diff --git a/battery/src/platform/freebsd/acpi.rs b/battery/src/platform/freebsd/acpi.rs index 898d39b..397da09 100644 --- a/battery/src/platform/freebsd/acpi.rs +++ b/battery/src/platform/freebsd/acpi.rs @@ -1,39 +1,35 @@ // https://github.com/freebsd/freebsd/blob/master/sys/dev/acpica/acpiio.h // https://github.com/freebsd/freebsd/blob/master/sys/dev/acpica/acpi_battery.c -use std::io; +use std::fs; use std::mem; use std::str::FromStr; use std::default::Default; -use std::os::unix::io::{RawFd, IntoRawFd}; +use std::os::unix::io::{IntoRawFd, AsRawFd, RawFd, FromRawFd}; use std::ffi::CStr; -use nix::Error; - -use crate::{State, Technology}; +use crate::{State, Technology, Result}; const ACPI_CMBAT_MAXSTRLEN: usize = 32; // This one const is not defined in FreeBSD sources, // but we are defining it for consistency. const ACPI_BATT_STAT_FULL: u32 = 0x0000; -// Declared at `sys/dev/acpica/acpiio.h` +// Following are declared at `sys/dev/acpica/acpiio.h` const ACPI_BATT_STAT_DISCHARG: u32 = 0x0001; const ACPI_BATT_STAT_CHARGING: u32 = 0x0002; const ACPI_BATT_STAT_CRITICAL: u32 = 0x0004; +const ACPI_BATT_STAT_INVALID: u32 = ACPI_BATT_STAT_DISCHARG | ACPI_BATT_STAT_CHARGING; +const ACPI_BATT_STAT_BST_MASK: u32 = ACPI_BATT_STAT_INVALID | ACPI_BATT_STAT_CRITICAL; +const ACPI_BATT_STAT_NOT_PRESENT: u32 = ACPI_BATT_STAT_BST_MASK; + +const ACPI_BATT_UNKNOWN: u32 = 0xffff_ffff; -/// FOr `AcpiBif` struct capacity is in mWh, rate in mW. +/// For `AcpiBif` struct capacity is in mWh, rate in mW. const ACPI_BIF_UNITS_MW: u32 = 0; /// For `AcpiBif` struct capacity is in mAh, rate in mA. const ACPI_BIF_UNITS_MA: u32 = 1; -fn map_nix_err(e: Error) -> io::Error { - match e { - Error::Sys(errno) => errno.into(), - other => io::Error::new(io::ErrorKind::Other, other), - } -} - #[derive(Debug, Eq, PartialEq)] pub enum Units { MilliWatts, @@ -43,7 +39,7 @@ pub enum Units { #[repr(C)] #[derive(Debug, Copy, Clone)] pub struct AcpiBif { - units: u32, // mW or mA + units: u32, // mW or mA, see `ACPI_BIF_UNITS_*` dcap: u32, // design capacity, lfcap: u32, // last full capacity, btech: u32, // battery technology, @@ -59,6 +55,11 @@ pub struct AcpiBif { } impl AcpiBif { + // int acpi_battery_bif_valid(struct acpi_bif *bif) + pub fn is_valid(&self) -> bool { + self.lfcap != 0 + } + pub fn units(&self) -> Units { match self.units { ACPI_BIF_UNITS_MW => Units::MilliWatts, @@ -134,6 +135,12 @@ pub struct AcpiBst { } impl AcpiBst { + // int acpi_battery_bst_valid(struct acpi_bst *bst) + pub fn is_valid(&self) -> bool { + self.state != ACPI_BATT_STAT_NOT_PRESENT && self.cap != ACPI_BATT_UNKNOWN + && self.volt != ACPI_BATT_UNKNOWN + } + // based on `ACPI_BATT_STAT_*` defines #[inline] pub fn state(&self) -> State { @@ -182,7 +189,7 @@ impl Default for AcpiBatteryIoctlArg { } } -//ioctl_readwrite!(acpiio_batt_get_battinfo, b'B', 0x03, AcpiBatteryIoctlArg); +ioctl_read!(acpiio_batt_get_units, b'B', 0x01, libc::c_int); ioctl_readwrite!(acpiio_batt_get_bif, b'B', 0x10, AcpiBatteryIoctlArg); ioctl_readwrite!(acpiio_batt_get_bst, b'B', 0x11, AcpiBatteryIoctlArg); @@ -190,31 +197,81 @@ ioctl_readwrite!(acpiio_batt_get_bst, b'B', 0x11, AcpiBatteryIoctlArg); pub struct AcpiDevice(RawFd); impl AcpiDevice { - pub fn new(file: T) -> AcpiDevice { - AcpiDevice(file.into_raw_fd()) + pub fn new() -> Result { + let file = fs::OpenOptions::new() + .read(true) + .open("/dev/acpi")?; + + Ok(AcpiDevice(file.into_raw_fd())) } - pub fn bif(&self) -> io::Result { + /// Count of the available batteries + pub fn count(&self) -> Result { + let mut arg = 0i32; + unsafe { + acpiio_batt_get_units(self.0, &mut arg as *mut _)? + }; + + Ok(arg) + } + + /// # Returns + /// + /// * `Ok(Some(bif))` - successfully fetched bif + /// * `Ok(None)` - bif was fetched but it is invalid; + /// it is not an error, because we want to skip it silently + /// * `Err(e)` - FFI call failed + pub fn bif(&self, unit: libc::c_int) -> Result> { let mut arg = AcpiBatteryIoctlArg::default(); unsafe { - acpiio_batt_get_bif(self.0, &mut arg as *mut _).map_err(map_nix_err)? + arg.unit = unit; + acpiio_batt_get_bif(self.0, &mut arg as *mut _)? }; let info = unsafe { arg.bif }; - Ok(info) + if info.is_valid() { + Ok(Some(info)) + } else { + Ok(None) + } } - pub fn bst(&self) -> io::Result { + /// # Returns + /// + /// * `Ok(Some(bst))` - successfully fetched bst + /// * `Ok(None)` - bst was fetched but it is invalid; + /// it is not an error, because we want to skip it silently + /// * `Err(e)` - FFI call failed + pub fn bst(&self, unit: i32) -> Result> { let mut arg = AcpiBatteryIoctlArg::default(); unsafe { - acpiio_batt_get_bst(self.0, &mut arg as *mut _).map_err(map_nix_err)? + arg.unit = unit; + acpiio_batt_get_bst(self.0, &mut arg as *mut _)? }; let info = unsafe { arg.bst }; - Ok(info) + if info.is_valid() { + Ok(Some(info)) + } else { + Ok(None) + } + } +} + +impl AsRawFd for AcpiDevice { + fn as_raw_fd(&self) -> RawFd { + self.0 + } +} + +impl Drop for AcpiDevice { + fn drop(&mut self) { + unsafe { + fs::File::from_raw_fd(self.0); + } } } diff --git a/battery/src/platform/freebsd/device.rs b/battery/src/platform/freebsd/device.rs index 13c4ee1..f2b2462 100644 --- a/battery/src/platform/freebsd/device.rs +++ b/battery/src/platform/freebsd/device.rs @@ -1,14 +1,14 @@ -use std::io; +use std::fmt; use std::convert::AsRef; -use std::fs::OpenOptions; -use crate::{State, Technology}; +use crate::{State, Technology, Result}; use crate::platform::traits::BatteryDevice; use crate::units::{Energy, Power, ElectricPotential, ThermodynamicTemperature}; use super::acpi; -#[derive(Debug)] +#[derive(Default)] pub struct IoCtlDevice { + unit: libc::c_int, state: State, technology: Technology, @@ -26,51 +26,53 @@ pub struct IoCtlDevice { } impl IoCtlDevice { - pub fn new() -> io::Result { - let file = OpenOptions::new() - .read(true) - .open("/dev/acpi")?; - let inner = acpi::AcpiDevice::new(file); - let bif = inner.bif()?; - let bst = inner.bst()?; + pub fn new(unit: libc::c_int, bif: acpi::AcpiBif, bst: acpi::AcpiBst) -> IoCtlDevice { + let mut device = IoCtlDevice { + unit, + ..Default::default() + }; + + device.manufacturer = bif.oem(); + device.model = bif.model(); + device.serial_number = bif.serial(); + device.technology = bif.technology(); + + device.refresh(bif, bst).expect("unreachable"); + device + } + + pub fn unit(&self) -> libc::c_int { + self.unit + } + + pub fn refresh(&mut self, bif: acpi::AcpiBif, bst: acpi::AcpiBst) -> Result<()> { let voltage = millivolt!(bst.voltage()); // FreeBSD returns battery info (bif) either in mA or mV, and we need the mW. // mA values are multiplied by `bif.dvol`, // as in `sys/dev/acpica/acpi_battery.c:acpi_battery_get_battinfo` function let design_voltage = millivolt!(bif.design_voltage()); - let energy_rate = match bif.units() { + self.energy_rate = match bif.units() { acpi::Units::MilliWatts => milliwatt!(bst.rate()), acpi::Units::MilliAmperes => milliampere!(bst.rate()) * design_voltage, }; - let current_capacity = match bif.units() { + self.current_capacity = match bif.units() { acpi::Units::MilliWatts => milliwatt_hour!(bst.capacity()), acpi::Units::MilliAmperes => milliampere_hour!(bst.capacity()) * design_voltage, }; - let design_capacity = match bif.units() { + self.design_capacity = match bif.units() { acpi::Units::MilliWatts => milliwatt_hour!(bif.design_capacity()), acpi::Units::MilliAmperes => milliampere_hour!(bif.design_capacity()) * design_voltage, }; - let max_capacity = match bif.units() { + self.max_capacity = match bif.units() { acpi::Units::MilliWatts => milliwatt_hour!(bif.last_full_capacity()), acpi::Units::MilliAmperes => milliampere_hour!(bif.last_full_capacity()) * design_voltage, }; + self.state = bst.state(); + self.voltage = voltage; - let device = IoCtlDevice { - manufacturer: bif.oem(), - model: bif.model(), - serial_number: bif.serial(), - state: bst.state(), - technology: bif.technology(), - energy_rate, - voltage, - design_capacity, - max_capacity, - current_capacity, - }; - - Ok(device) + Ok(()) } } @@ -122,5 +124,12 @@ impl BatteryDevice for IoCtlDevice { fn cycle_count(&self) -> Option { None } +} +impl fmt::Debug for IoCtlDevice { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.debug_struct("FreeBSDDevice") + .field("unit", &self.unit) + .finish() + } } diff --git a/battery/src/platform/freebsd/iterator.rs b/battery/src/platform/freebsd/iterator.rs index 6a569ba..2bad956 100644 --- a/battery/src/platform/freebsd/iterator.rs +++ b/battery/src/platform/freebsd/iterator.rs @@ -1,18 +1,66 @@ -use std::iter; +use std::fmt; +use std::rc::Rc; +use std::ops::Range; -use crate::Battery; -use super::device::IoCtlDevice; +use crate::Result; +use crate::platform::traits::{BatteryIterator}; +use super::{IoCtlDevice, IoCtlManager}; -#[derive(Debug)] -pub struct IoCtlIterator(pub Option); +pub struct IoCtlIterator { + manager: Rc, + range: Range, +} -impl iter::Iterator for IoCtlIterator { - type Item = Battery; +impl Iterator for IoCtlIterator { + type Item = Result; fn next(&mut self) -> Option { - match self.0.take() { - None => None, - Some(device) => Some(Battery::from(device)) + loop { + match self.range.next() { + None => return None, + Some(idx) => { + let bif = self.manager.bif(idx); + let bst = self.manager.bst(idx); + + match (bif, bst) { + (Ok(Some(bif)), Ok(Some(bst))) => { + return Some(Ok(IoCtlDevice::new(idx, bif, bst))); + }, + (Err(e), _) => return Some(Err(e)), + (_, Err(e)) => return Some(Err(e)), + // If bif or bst is invalid (`Ok(None)` here), + // silently skipping it, same as FreeBSD does + _ => continue, + } + } + } } } + + fn size_hint(&self) -> (usize, Option) { + (0, Some((self.range.end - self.range.start) as usize)) + } +} + +impl BatteryIterator for IoCtlIterator { + type Manager = IoCtlManager; + type Device = IoCtlDevice; + + fn new(manager: Rc) -> Result { + let batteries = manager.count()?; + + Ok(Self { + manager, + range: (0..batteries), + }) + } +} + +impl fmt::Debug for IoCtlIterator { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.debug_struct("FreeBSDIterator") + .field("start", &self.range.start) + .field("end", &self.range.end) + .finish() + } } diff --git a/battery/src/platform/freebsd/manager.rs b/battery/src/platform/freebsd/manager.rs index e357102..8d31af6 100644 --- a/battery/src/platform/freebsd/manager.rs +++ b/battery/src/platform/freebsd/manager.rs @@ -1,29 +1,44 @@ -use std::io; +use std::fmt; +use std::ops::Deref; +use std::os::unix::io::AsRawFd; -use crate::Battery; -use crate::platform::traits::BatteryManager; -use super::IoCtlDevice; -use super::IoCtlIterator; +use crate::{Result, Error}; +use crate::platform::traits::{BatteryManager, BatteryIterator}; +use super::{acpi, IoCtlIterator}; -#[derive(Debug, Default)] -pub struct IoCtlManager; +pub struct IoCtlManager(acpi::AcpiDevice); -impl IoCtlManager { - pub fn iter(&self) -> IoCtlIterator { - let inner = match IoCtlDevice::new() { - Ok(device) => Some(device), - Err(_) => None, - }; +impl BatteryManager for IoCtlManager { + type Iterator = IoCtlIterator; + + fn new() -> Result { + Ok(Self(acpi::AcpiDevice::new()?)) + } + + fn refresh(&self, device: &mut ::Device) -> Result<()> { + let bif = self.0.bif(device.unit())?; + let bst = self.0.bst(device.unit())?; - IoCtlIterator(inner) + match (bif, bst) { + (Some(bif), Some(bst)) => device.refresh(bif, bst), + (None, _) => Err(Error::invalid_data("Returned bif struct is invalid")), + (_, None) => Err(Error::invalid_data("Returned bst struct is invalid")), + } } } -impl BatteryManager for IoCtlManager { - fn refresh(&mut self, battery: &mut Battery) -> io::Result<()> { - *battery.get_mut_ref() = IoCtlDevice::new()?; +impl Deref for IoCtlManager { + type Target = acpi::AcpiDevice; - Ok(()) + fn deref(&self) -> &acpi::AcpiDevice { + &self.0 } } +impl fmt::Debug for IoCtlManager { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.debug_struct("FreeBSD") + .field("fd", &self.0.as_raw_fd()) + .finish() + } +} diff --git a/battery/src/platform/linux/device.rs b/battery/src/platform/linux/device.rs index 26e3e9c..41ee967 100644 --- a/battery/src/platform/linux/device.rs +++ b/battery/src/platform/linux/device.rs @@ -1,374 +1,124 @@ -use std::io; -use std::f32; -use std::convert::AsRef; -use std::str::FromStr; -use std::path::PathBuf; -use std::default::Default; +use std::fmt; +use std::path::{Path, PathBuf}; -use lazy_init::Lazy; -use num_traits::identities::Zero; +use crate::platform::traits::*; +use crate::units::{ElectricPotential, Energy, Power, Ratio, ThermodynamicTemperature}; +use crate::{Result, State, Technology}; -use crate::{State, Technology}; -use crate::units::{Energy, Power, ElectricPotential, ElectricCharge, Ratio, ThermodynamicTemperature}; -use crate::units::power::{watt, microwatt}; -use crate::platform::traits::{BatteryDevice, Bound}; -use super::sysfs; +use super::sysfs::{fs, DataBuilder, InstantData, Scope, Type}; -#[derive(Default)] -pub struct Inner { +pub struct SysFsDevice { root: PathBuf, - - design_voltage: Lazy, - energy: Lazy, - energy_full: Lazy, - energy_full_design: Lazy, - energy_rate: Lazy, - voltage: Lazy, - state_of_charge: Lazy, - - temperature: Lazy>, - cycle_count: Lazy>, - - state: Lazy, - technology: Lazy, - manufacturer: Lazy>, - model_name: Lazy>, - serial_number: Lazy>, -} - -impl Inner { - pub fn new(root: PathBuf) -> Inner { - let device = Inner { - root, - ..Default::default() - }; - - device.preload(); - - device - } -} - -impl Inner { - // With current design, `Inner` is not an instant representation of the battery stats - // because of `Lazy` fields. End user might fetch needed data with a significant time difference - // which will lead to an inconsistent results. - // All results should be loaded at the same time; as for now, making a quick hack - // and preloading all the stuff in once. - // It seems that even with ignored results (`let _ = self...()`), rust still calls all required methods, - // since we have side effects (file I/O) - fn preload(&self) { - let _ = self.design_voltage(); - let _ = self.energy(); - let _ = self.energy_full(); - let _ = self.energy_full_design(); - let _ = self.energy_rate(); - let _ = self.voltage(); - let _ = self.state_of_charge(); - let _ = self.temperature(); - let _ = self.state(); - let _ = self.technology(); - let _ = self.vendor(); - let _ = self.model(); - let _ = self.serial_number(); - let _ = self.cycle_count(); - } - - fn design_voltage(&self) -> ElectricPotential { - *self.design_voltage.get_or_create(|| { - ["voltage_max_design", "voltage_min_design", "voltage_present", "voltage_now"].iter() - .filter_map(|filename| sysfs::voltage(self.root.join(filename))) - .next() - // Same to `upower`, using 10V as an approximation - .unwrap_or_else(|| volt!(10.0)) - }) - } - - fn energy_now(&self) -> Option { - ["energy_now", "energy_avg"].iter() - .filter_map(|filename| sysfs::energy(self.root.join(filename))) - .next() - } - - fn charge_now(&self) -> Option { - ["charge_now", "charge_avg"].iter() - .filter_map(|filename| sysfs::charge(self.root.join(filename))) - .next() - } - - fn charge_full(&self) -> ElectricCharge { - ["charge_full", "charge_full_design"].iter() - .filter_map(|filename| sysfs::charge(self.root.join(filename))) - .next() - .unwrap_or_else(|| microampere_hour!(0.0)) - } - + source: InstantData, + + // These fields are "cached" outside from DataBuilder/InstantData, + // since they're can't change with refresh + vendor: Option, + model: Option, + serial_number: Option, + technology: Technology, } -impl BatteryDevice for Inner { - fn state_of_health(&self) -> Ratio { - let energy_full = self.energy_full(); - if !energy_full.is_zero() { - (energy_full / self.energy_full_design()).into_bounded() - } else { - percent!(100.0) +impl SysFsDevice { + pub fn is_system_battery>(path: T) -> Result { + let path = path.as_ref(); + if fs::type_(path.join("type"))? == Type::Battery && + fs::scope(path.join("scope"))? == Scope::System { + return Ok(true); } - } - fn energy(&self) -> Energy { - *self.energy.get_or_create(|| { - self.energy_now() - .or_else(|| match self.charge_now() { - Some(charge) => Some(charge * self.design_voltage()), - None => None, - }) - .unwrap_or_else(|| self.energy_full() * self.state_of_charge()) - }) + Ok(false) } - fn energy_full(&self) -> Energy { - *self.energy_full.get_or_create(|| { - sysfs::energy(self.root.join("energy_full")) - .or_else(|| { - match sysfs::charge(self.root.join("charge_full")) { - Some(charge) => Some(charge * self.design_voltage()), - None => None - } - }) - .unwrap_or_else(|| self.energy_full_design()) - }) - } + pub fn try_from(root: PathBuf) -> Result { + let builder = DataBuilder::new(&root); + let vendor = builder.manufacturer()?; + let model = builder.model()?; + let serial_number = builder.serial_number()?; + let technology = builder.technology()?; - fn energy_full_design(&self) -> Energy { - *self.energy_full_design.get_or_create(|| { - sysfs::energy(self.root.join("energy_full_design")) - .or_else(|| { - match sysfs::charge(self.root.join("charge_full_design")) { - Some(charge) => Some(charge * self.design_voltage()), - None => None - } - }) - // Seems to be an impossible case - .unwrap_or_else(|| microwatt_hour!(0.0)) - }) - } + let source = builder.collect()?; - fn energy_rate(&self) -> Power { - *self.energy_rate.get_or_create(|| { - sysfs::power(self.root.join("power_now")) - .or_else(|| { - match sysfs::get_f32(self.root.join("current_now")) { - Ok(current_now) => { - // If charge_full exists, then current_now is always reported in µA. - // In the legacy case, where energy only units exist, and power_now isn't present - // current_now is power in µW. - // Source: upower - if !self.charge_full().is_zero() { - // µA then - Some(microampere!(current_now) * self.design_voltage()) - } else { - // µW :| - Some(microwatt!(current_now)) - } - }, - _ => None, - } - }) - // Sanity check if power is greater than 100W (upower) - .map(|power| { - if power.get::() > 100.0 { - watt!(0.0) - } else { - power - } - }) - // Some batteries give out massive rate values when nearly empty (upower) - .map(|power| { - if power.get::() < 10.0 { - watt!(0.0) - } else { - power - } - }) - // ACPI gives out the special 'Ones' (Constant Ones Object) value for rate - // when it's unable to calculate the true rate. We should set the rate zero, - // and wait for the BIOS to stabilise. - // Source: upower - // - // It come as an `0xffff` originally, but we are operating with `Power` now, - // so this `Ones` value is recalculated a little. - .map(|power| { - // TODO: There might be a chance that we had lost a precision during the conversion - // from the microwatts into default watts, so this should be fixed - if (power.get::() - 65535.0).abs() < f32::EPSILON { - watt!(0.0) - } else { - power - } - }) - .unwrap_or_else(|| microwatt!(0.0)) - - // TODO: Calculate energy_rate manually, if hardware fails. - // if value < 0.01 { - // // Check upower `up_device_supply_calculate_rate` function - // } - }) - } - - fn state_of_charge(&self) -> Ratio { - *self.state_of_charge.get_or_create(|| { - match sysfs::get_f32(self.root.join("capacity")) { - Ok(capacity) => percent!(capacity).into_bounded(), - _ if self.energy_full().is_sign_positive() => self.energy() / self.energy_full(), - Err(_) => percent!(0.0), - } - }) - } - - fn state(&self) -> State { - *self.state.get_or_create(|| { - sysfs::get_string(self.root.join("status")) - .and_then(|x| State::from_str(&x)) - .unwrap_or(State::Unknown) - }) - } - - fn voltage(&self) -> ElectricPotential { - *self.voltage.get_or_create(|| { - ["voltage_now", "voltage_avg"].iter() // µV - .filter_map(|filename| sysfs::voltage(self.root.join(filename))) - .next() - .unwrap_or_else(|| microvolt!(0.0)) - }) - } - - fn temperature(&self) -> Option { - *self.temperature.get_or_create(|| { - let res = sysfs::get_f32(self.root.join("temp")) - .and_then(|temp| Ok(temp / 10.0)); - // TODO: Use .transmute() when it is stable - match res { - Ok(value) => Some(celsius!(value)), - Err(_) => None, - } - }) - } - - fn vendor(&self) -> Option<&str> { - self.manufacturer.get_or_create(|| { - match sysfs::get_string(self.root.join("manufacturer")) { - Ok(vendor) => Some(vendor), - Err(_) => None, - } - }).as_ref().map(AsRef::as_ref) - } - - fn model(&self) -> Option<&str> { - self.model_name.get_or_create(|| { - match sysfs::get_string(self.root.join("model_name")) { - Ok(model) => Some(model), - Err(_) => None, - } - }).as_ref().map(AsRef::as_ref) - } - - fn serial_number(&self) -> Option<&str> { - self.serial_number.get_or_create(|| { - match sysfs::get_string(self.root.join("serial_number")) { - Ok(serial) => Some(serial), - Err(_) => None, - } - }).as_ref().map(AsRef::as_ref) - } - - fn technology(&self) -> Technology { - *self.technology.get_or_create(|| { - match sysfs::get_string(self.root.join("technology")) { - Ok(ref tech) => Technology::from_str(tech).unwrap_or(Technology::Unknown), - Err(_) => Technology::Unknown, - } - }) - } - - fn cycle_count(&self) -> Option { - *self.cycle_count.get_or_create(|| { - match sysfs::get_u32(self.root.join("cycle_count")) { - Ok(value) => Some(value), - Err(_) => None, - } + Ok(SysFsDevice { + root, + source, + vendor, + model, + serial_number, + technology, }) } -} - -#[derive(Default)] -pub struct SysFsDevice(Inner); - -impl SysFsDevice { - pub fn new(root: PathBuf) -> SysFsDevice { - SysFsDevice(Inner::new(root)) - } - - pub fn refresh(&mut self) -> io::Result<()> { - self.0 = Inner::new(self.0.root.clone()); + pub fn refresh(&mut self) -> Result<()> { + let builder = DataBuilder::new(&self.root); + self.source = builder.collect()?; Ok(()) } } impl BatteryDevice for SysFsDevice { + fn state_of_health(&self) -> Ratio { - self.0.state_of_health() + self.source.state_of_health + } + + fn state_of_charge(&self) -> Ratio { + self.source.state_of_charge } fn energy(&self) -> Energy { - self.0.energy() + self.source.energy } fn energy_full(&self) -> Energy { - self.0.energy_full() + self.source.energy_full } fn energy_full_design(&self) -> Energy { - self.0.energy_full_design() + self.source.energy_full_design } fn energy_rate(&self) -> Power { - self.0.energy_rate() - } - - fn state_of_charge(&self) -> Ratio { - self.0.state_of_charge() + self.source.energy_rate } fn state(&self) -> State { - self.0.state() + self.source.state } fn voltage(&self) -> ElectricPotential { - self.0.voltage() + self.source.voltage } fn temperature(&self) -> Option { - self.0.temperature() + self.source.temperature } fn vendor(&self) -> Option<&str> { - self.0.vendor() + self.vendor.as_ref().map(AsRef::as_ref) } fn model(&self) -> Option<&str> { - self.0.model() + self.model.as_ref().map(AsRef::as_ref) } fn serial_number(&self) -> Option<&str> { - self.0.serial_number() + self.serial_number.as_ref().map(AsRef::as_ref) } fn technology(&self) -> Technology { - self.0.technology() + self.technology } fn cycle_count(&self) -> Option { - self.0.cycle_count() + self.source.cycle_count + } +} + +impl fmt::Debug for SysFsDevice { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.debug_struct("LinuxDevice") + .field("root", &self.root) + .finish() } } diff --git a/battery/src/platform/linux/iterator.rs b/battery/src/platform/linux/iterator.rs index 465cc80..126fe7b 100644 --- a/battery/src/platform/linux/iterator.rs +++ b/battery/src/platform/linux/iterator.rs @@ -1,56 +1,60 @@ -use std::io; -use std::fs; -use std::iter; -use std::path::Path; +use std::fmt; +use std::fs::{self, ReadDir}; +use std::rc::Rc; -use crate::Battery; -use crate::platform::traits::BatteryIterator; -use super::SysFsDevice; -use super::sysfs; +use super::{SysFsDevice, SysFsManager}; +use crate::platform::traits::*; +use crate::Result; -#[derive(Debug)] pub struct SysFsIterator { - // TODO: It is not cool to store all results at once, should keep iterator instead - entries: Vec>, + #[allow(dead_code)] + manager: Rc, + entries: ReadDir, } -impl SysFsIterator { - pub fn from_path(root: T) -> SysFsIterator where T: AsRef { - let entries = match fs::read_dir(root.as_ref()) { - Ok(entries) => entries.collect(), - Err(_) => vec![], - }; +impl BatteryIterator for SysFsIterator { + type Manager = SysFsManager; + type Device = SysFsDevice; - SysFsIterator { - entries, - } + fn new(manager: Rc) -> Result { + let entries = fs::read_dir(manager.path())?; + + Ok(SysFsIterator { manager, entries }) } } -impl iter::Iterator for SysFsIterator { - type Item = Battery; +impl Iterator for SysFsIterator { + type Item = Result<::Device>; fn next(&mut self) -> Option { loop { - match self.entries.pop() { - None => return None, // Nothing to iterate anymore - Some(Err(_)) => continue, // Unable to access the sysfs somehow // TODO: trace!() + return match self.entries.next() { + None => None, + // Unable to access sysfs for some reasons + Some(Err(e)) => Some(Err(e.into())), Some(Ok(entry)) => { let path = entry.path(); - let type_ = fs::read_to_string(path.join("type")); - let scope = sysfs::scope(path.join("scope")); - match type_ { - Ok(ref content) if content == "Battery\n" && scope == sysfs::Scope::System => { - let inner = SysFsDevice::new(path); - - return Some(Battery::from(inner)); - }, - _ => continue, // it is not a battery + match SysFsDevice::is_system_battery(&path) { + Ok(true) => Some(SysFsDevice::try_from(path)), + Ok(false) => continue, + Err(e) => Some(Err(e)), } } - } + }; } } + + fn size_hint(&self) -> (usize, Option) { + self.entries.size_hint() + } } -impl BatteryIterator for SysFsIterator {} +impl fmt::Debug for SysFsIterator { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let (start, end) = self.size_hint(); + f.debug_struct("LinuxIterator") + .field("start", &start) + .field("end", &end) + .finish() + } +} diff --git a/battery/src/platform/linux/manager.rs b/battery/src/platform/linux/manager.rs index cf792f6..35bc71a 100644 --- a/battery/src/platform/linux/manager.rs +++ b/battery/src/platform/linux/manager.rs @@ -1,30 +1,33 @@ -use std::io; -use std::default::Default; +use std::path::{Path, PathBuf}; -use crate::{Battery}; -use crate::platform::traits::{BatteryManager}; - -use super::SysFsIterator; +use super::device::SysFsDevice; +use super::iterator::SysFsIterator; +use crate::platform::traits::*; +use crate::Result; static SYSFS_ROOT: &'static str = "/sys/class/power_supply"; #[derive(Debug)] -pub struct SysFsManager; +pub struct SysFsManager { + root: PathBuf, +} impl SysFsManager { - pub fn iter(&self) -> SysFsIterator { - SysFsIterator::from_path(SYSFS_ROOT) + pub fn path(&self) -> &Path { + self.root.as_path() } } impl BatteryManager for SysFsManager { - fn refresh(&mut self, battery: &mut Battery) -> io::Result<()> { - battery.get_mut_ref().refresh() + type Iterator = SysFsIterator; + + fn new() -> Result { + Ok(Self { + root: PathBuf::from(SYSFS_ROOT), + }) } -} -impl Default for SysFsManager { - fn default() -> SysFsManager { - SysFsManager{} + fn refresh(&self, device: &mut SysFsDevice) -> Result<()> { + device.refresh() } } diff --git a/battery/src/platform/linux/mod.rs b/battery/src/platform/linux/mod.rs index 37c36f8..fe79b25 100644 --- a/battery/src/platform/linux/mod.rs +++ b/battery/src/platform/linux/mod.rs @@ -1,8 +1,8 @@ -mod manager; -mod iterator; mod device; +mod iterator; +mod manager; mod sysfs; -pub use self::manager::SysFsManager; -pub use self::iterator::SysFsIterator; pub use self::device::SysFsDevice; +pub use self::iterator::SysFsIterator; +pub use self::manager::SysFsManager; diff --git a/battery/src/platform/linux/sysfs.rs b/battery/src/platform/linux/sysfs.rs deleted file mode 100644 index ddae495..0000000 --- a/battery/src/platform/linux/sysfs.rs +++ /dev/null @@ -1,112 +0,0 @@ -use std::io; -use std::fs; -use std::str::FromStr; -use std::path::Path; - -use crate::units::{Energy, Power, ElectricPotential, ElectricCharge}; - -/// A power supply which doesn't have a "scope" attribute should be assumed to -/// have "System" scope. -#[derive(Debug, Eq, PartialEq)] -pub enum Scope { - /// Powers a specific device, or tree of devices - Device, - /// Powers the whole system - System, - /// Unknown power topology - Unknown, -} - -impl FromStr for Scope { - type Err = io::Error; - - fn from_str(s: &str) -> Result { - match s { - _ if s.eq_ignore_ascii_case("Device") => Ok(Scope::Device), - _ if s.eq_ignore_ascii_case("System") => Ok(Scope::System), - _ if s.eq_ignore_ascii_case("Unknown") => Ok(Scope::Unknown), - _ => Err(io::Error::from(io::ErrorKind::InvalidData)), - } - } -} - -/// Read µWh value from the `energy_` file and convert into `Energy` type. -pub fn energy>(path: T) -> Option { - let path = path.as_ref(); - debug_assert!(path.file_name().unwrap().to_string_lossy().starts_with("energy_")); - - match get_f32(path) { - Ok(value_uwh) => Some(microwatt_hour!(value_uwh)), - Err(_) => None, - } -} - -/// Read µAh value from the `charge_` file and convert into `ElectricCharge` type. -pub fn charge>(path: T) -> Option { - let path = path.as_ref(); - debug_assert!(path.file_name().unwrap().to_string_lossy().starts_with("charge_")); - - match get_f32(path) { - Ok(value_uah) => Some(microampere_hour!(value_uah)), - Err(_) => None, - } -} - -/// Read µV value from the `voltage_` file and convert into `ElectricPotential` type. -pub fn voltage>(path: T) -> Option { - let path = path.as_ref(); - debug_assert!(path.file_name().unwrap().to_string_lossy().starts_with("voltage_")); - - match get_f32(path) { - Ok(value_uv) if value_uv > 1.0 => Some(microvolt!(value_uv)), - _ => None, - } -} - -/// Read µW value from the `power_` file and convert into `Power` type. -pub fn power>(path: T) -> Option { - let path = path.as_ref(); - debug_assert!(path.file_name().unwrap().to_string_lossy().starts_with("power_")); - - match get_f32(path) { - Ok(value_uw) if value_uw > 10_000.0 => Some(microwatt!(value_uw)), - _ => None, - } -} - -pub fn scope>(path: T) -> Scope { - let path = path.as_ref(); - debug_assert!(path.file_name().unwrap().to_string_lossy() == "scope"); - - get_string(path) - .and_then(|str| Scope::from_str(&str)) - .unwrap_or(Scope::System) -} - -pub fn get_string>(path: T) -> io::Result { - match fs::read_to_string(path) { - Err(e) => Err(e), - Ok(ref content) => { - let trimmed = content.trim(); - if trimmed.starts_with('\0') { - Err(io::Error::from(io::ErrorKind::InvalidData)) - } else { - Ok(trimmed.to_string()) - } - } - } -} - -// TODO: Generic somehow? - -pub fn get_f32>(path: T) -> io::Result { - get_string(path).and_then(|value| { - value.parse::().map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e)) - }) -} - -pub fn get_u32>(path: T) -> io::Result { - get_string(path).and_then(|value| { - value.parse::().map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e)) - }) -} diff --git a/battery/src/platform/linux/sysfs/fs.rs b/battery/src/platform/linux/sysfs/fs.rs new file mode 100644 index 0000000..91bd988 --- /dev/null +++ b/battery/src/platform/linux/sysfs/fs.rs @@ -0,0 +1,121 @@ +use std::error; +use std::fs::read_to_string; +use std::io; +use std::path::Path; +use std::str::FromStr; + +use super::{Scope, Type}; +use crate::units::{ElectricCharge, ElectricPotential, Energy, Power}; +use crate::Result; + +/// Read µWh value from the `energy_` file and convert into `Energy` type. +pub fn energy>(path: T) -> Result> { + let path = path.as_ref(); + debug_assert!(path.file_name().unwrap().to_string_lossy().starts_with("energy_")); + + match get::(path) { + Ok(Some(value_uwh)) => Ok(Some(microwatt_hour!(value_uwh))), + Ok(None) => Ok(None), + Err(e) => Err(e), + } +} + +/// Read µAh value from the `charge_` file and convert into `ElectricCharge` type. +pub fn charge>(path: T) -> Result> { + let path = path.as_ref(); + debug_assert!(path.file_name().unwrap().to_string_lossy().starts_with("charge_")); + + match get::(path) { + Ok(Some(value_uah)) if value_uah > 1.0 => Ok(Some(microampere_hour!(value_uah))), + Ok(Some(_)) => Ok(None), + Ok(None) => Ok(None), + Err(e) => Err(e), + } +} + +/// Read µV value from the `voltage_` file and convert into `ElectricPotential` type. +pub fn voltage>(path: T) -> Result> { + let path = path.as_ref(); + debug_assert!(path.file_name().unwrap().to_string_lossy().starts_with("voltage_")); + + match get::(path) { + Ok(Some(value_uv)) if value_uv > 1.0 => Ok(Some(microvolt!(value_uv))), + Ok(Some(_)) => Ok(None), + Ok(None) => Ok(None), + Err(e) => Err(e), + } +} + +/// Read µW value from the `power_` file and convert into `Power` type. +pub fn power>(path: T) -> Result> { + let path = path.as_ref(); + debug_assert!(path.file_name().unwrap().to_string_lossy().starts_with("power_")); + + match get::(path) { + Ok(Some(value_uw)) if value_uw > 10_000.0 => Ok(Some(microwatt!(value_uw))), + Ok(Some(_)) => Ok(None), + Ok(None) => Ok(None), + Err(e) => Err(e), + } +} + +/// Read device `type` file and convert into `Type` enum. +pub fn type_>(path: T) -> Result { + let path = path.as_ref(); + debug_assert!(path.file_name().unwrap().to_string_lossy() == "type"); + + match get::(path) { + Ok(Some(value)) => Ok(value), + Ok(None) => Ok(Type::Unknown), + Err(e) => Err(e), + } +} + +/// Read device `scope` file and convert into `Scope` enum. +pub fn scope>(path: T) -> Result { + let path = path.as_ref(); + debug_assert!(path.file_name().unwrap().to_string_lossy() == "scope"); + + match get::(path) { + Ok(Some(value)) => Ok(value), + Ok(None) => Ok(Scope::System), + Err(e) => Err(e), + } +} + +/// ## Returns +/// +/// Ok(Some(value)) - file was read properly +/// Ok(None) - file is missing +/// Err(_) - unable to access file for some reasons (except `NotFound`) +pub fn get_string>(path: T) -> Result> { + match read_to_string(path) { + Ok(ref content) => { + // TODO: Get rid of allocation, read file only until `\n` comes + let trimmed = content.trim(); + if trimmed.starts_with('\0') { + Err(io::Error::from(io::ErrorKind::InvalidData).into()) + } else { + Ok(Some(trimmed.to_string())) + } + } + Err(ref e) if e.kind() == io::ErrorKind::NotFound => Ok(None), + Err(e) => Err(e.into()), + } +} + +pub fn get(path: T) -> Result> +where + T: AsRef, + V: FromStr, + ::Err: error::Error + Sync + Send, +{ + match get_string(path) { + Ok(Some(ref value)) => match V::from_str(value) { + Ok(result) => Ok(Some(result)), + Err(_) => Ok(None), + }, + Ok(None) => Ok(None), + Err(e) => Err(e), + } +} diff --git a/battery/src/platform/linux/sysfs/mod.rs b/battery/src/platform/linux/sysfs/mod.rs new file mode 100644 index 0000000..7c6781a --- /dev/null +++ b/battery/src/platform/linux/sysfs/mod.rs @@ -0,0 +1,61 @@ +use std::io; +use std::str::FromStr; + +pub mod fs; +mod source; + +pub use self::source::{DataBuilder, InstantData}; + +#[allow(dead_code)] +#[derive(Debug, Eq, PartialEq)] +pub enum Type { + Battery, + Mains, + Ups, + Usb, + Unknown, + __Nonexhaustive, +} + +impl FromStr for Type { + type Err = io::Error; + + fn from_str(s: &str) -> Result { + let value = match () { + _ if s.eq_ignore_ascii_case("Battery") => Type::Battery, + _ if s.eq_ignore_ascii_case("Mains") => Type::Mains, + _ if s.eq_ignore_ascii_case("Ups") => Type::Ups, + _ if s.eq_ignore_ascii_case("Usb") => Type::Usb, + _ => Type::Unknown, + }; + Ok(value) + } +} + +/// A power supply which doesn't have a "scope" attribute should be assumed to +/// have "System" scope. +#[derive(Debug, Eq, PartialEq)] +pub enum Scope { + /// Powers a specific device, or tree of devices + Device, + /// Powers the whole system + System, + /// Unknown power topology + Unknown, + __Nonexhaustive, +} + +impl FromStr for Scope { + type Err = io::Error; + + fn from_str(s: &str) -> Result { + let value = match s { + _ if s.eq_ignore_ascii_case("Device") => Scope::Device, + _ if s.eq_ignore_ascii_case("System") => Scope::System, + _ if s.eq_ignore_ascii_case("Unknown") => Scope::Unknown, + _ => Scope::Unknown, + }; + + Ok(value) + } +} diff --git a/battery/src/platform/linux/sysfs/source.rs b/battery/src/platform/linux/sysfs/source.rs new file mode 100644 index 0000000..d25a891 --- /dev/null +++ b/battery/src/platform/linux/sysfs/source.rs @@ -0,0 +1,329 @@ +use std::f32; +use std::io; +use std::path::Path; + +use lazycell::LazyCell; +use num_traits::identities::Zero; + +use super::fs; +use crate::units::power::{microwatt, watt}; +use crate::units::{ + Bound, ElectricCharge, ElectricPotential, Energy, Power, Ratio, ThermodynamicTemperature, +}; +use crate::{Result, Error, State, Technology}; + +#[derive(Debug)] +pub struct InstantData { + pub state_of_health: Ratio, + pub state_of_charge: Ratio, + + pub energy: Energy, + pub energy_full: Energy, + pub energy_full_design: Energy, + pub energy_rate: Power, + pub voltage: ElectricPotential, + pub state: State, + pub temperature: Option, + pub cycle_count: Option, +} + +pub struct DataBuilder<'p> { + root: &'p Path, + + design_voltage: LazyCell, + energy: LazyCell, + energy_full: LazyCell, + energy_full_design: LazyCell, + energy_rate: LazyCell, + + state_of_health: LazyCell, + state_of_charge: LazyCell, + + state: LazyCell, +} + +impl<'p> DataBuilder<'p> { + pub fn new(path: &'p Path) -> DataBuilder<'p> { + DataBuilder { + root: path, + + design_voltage: LazyCell::new(), + energy: LazyCell::new(), + energy_full: LazyCell::new(), + energy_full_design: LazyCell::new(), + energy_rate: LazyCell::new(), + state_of_health: LazyCell::new(), + state_of_charge: LazyCell::new(), + state: LazyCell::new(), + } + } + + pub fn collect(self) -> Result { + Ok(InstantData { + state_of_charge: *self.state_of_charge()?, + state_of_health: *self.state_of_health()?, + energy: *self.energy()?, + energy_full: *self.energy_full()?, + energy_full_design: *self.energy_full_design()?, + energy_rate: *self.energy_rate()?, + voltage: self.voltage()?, + state: *self.state()?, + temperature: self.temperature()?, + cycle_count: self.cycle_count()?, + }) + } + + fn design_voltage(&self) -> Result<&ElectricPotential> { + self.design_voltage.try_borrow_with(|| { + let value = [ + "voltage_max_design", + "voltage_min_design", + "voltage_present", + "voltage_now", + ] + .iter() + .filter_map(|filename| match fs::voltage(self.root.join(filename)) { + Ok(Some(value)) => Some(value), + _ => None, + }) + .next(); + match value { + Some(voltage) => Ok(voltage), + None => Err(io::Error::from(io::ErrorKind::NotFound).into()), + } + }) + } + + // Not cached because used only once + // IO errors are ignored, since later calculations will handle `None` result + fn energy_now(&self) -> Option { + ["energy_now", "energy_avg"] + .iter() + .filter_map(|filename| match fs::energy(self.root.join(filename)) { + Ok(Some(value)) => Some(value), + _ => None, + }) + .next() + } + + // Not cached because used only once. + // IO errors are ignored, since later calculations will handle `None` result + fn charge_now(&self) -> Option { + ["charge_now", "charge_avg"] + .iter() + .filter_map(|filename| match fs::charge(self.root.join(filename)) { + Ok(Some(value)) => Some(value), + _ => None, + }) + .next() + } + + // Not cached because used only once + fn charge_full(&self) -> ElectricCharge { + ["charge_full", "charge_full_design"] + .iter() + .filter_map(|filename| match fs::charge(self.root.join(filename)) { + Ok(Some(value)) => Some(value), + _ => None, + }) + .next() + .unwrap_or_else(|| microampere_hour!(0.0)) + } + + pub fn state_of_health(&self) -> Result<&Ratio> { + self.state_of_health.try_borrow_with(|| { + let energy_full = self.energy_full()?; + if !energy_full.is_zero() { + let energy_full_design = self.energy_full_design()?; + Ok((*energy_full / *energy_full_design).into_bounded()) + } else { + Ok(percent!(100.0)) + } + }) + } + + fn energy(&self) -> Result<&Energy> { + self.energy.try_borrow_with(|| { + match self.energy_now() { + Some(energy) => Ok(energy), + None => match self.charge_now() { + Some(charge) => Ok(charge * *self.design_voltage()?), + None => { + match fs::get::(self.root.join("capacity")) { + Ok(Some(capacity)) => Ok(*self.energy_full()? * percent!(capacity).into_bounded()), + _ => Err(Error::not_found("Unable to calculate device energy value")), + } + } + }, + } + }) + } + + fn energy_full(&self) -> Result<&Energy> { + self.energy_full + .try_borrow_with(|| match fs::energy(self.root.join("energy_full")) { + Ok(Some(value)) => Ok(value), + Ok(None) => match fs::charge(self.root.join("charge_full")) { + Ok(Some(value)) => Ok(value * *self.design_voltage()?), + Ok(None) => Ok(*self.energy_full_design()?), + Err(e) => Err(e), + }, + Err(e) => Err(e), + }) + } + + fn energy_full_design(&self) -> Result<&Energy> { + // Based on the `upower` source it seems to impossible not to have any of needed files, + // so fallback to `0 mWh` was removed, error will be propagated instead. + self.energy_full_design.try_borrow_with(|| { + match fs::energy(self.root.join("energy_full_design")) { + Ok(Some(value)) => Ok(value), + Ok(None) => match fs::charge(self.root.join("charge_full_design")) { + Ok(Some(value)) => Ok(value * *self.design_voltage()?), + Ok(None) => Ok(*self.energy_full_design()?), + Err(e) => Err(e), + }, + Err(e) => Err(e), + } + }) + } + + fn energy_rate(&self) -> Result<&Power> { + self.energy_rate.try_borrow_with(|| { + let value = match fs::power(self.root.join("power_now"))? { + Some(power) => Some(power), + None => { + match fs::get::(self.root.join("current_now"))? { + Some(current_now) => { + // If charge_full exists, then current_now is always reported in µA. + // In the legacy case, where energy only units exist, and power_now isn't present + // current_now is power in µW. + // Source: upower + if !self.charge_full().is_zero() { + // µA then + Some(microampere!(current_now) * *self.design_voltage()?) + } else { + // µW :| + Some(microwatt!(current_now)) + } + } + None => None, + } + } + }; + + let value = value + // Sanity check if power is greater than 100W (upower) + .map(|power| { + if power.get::() > 100.0 { + watt!(0.0) + } else { + power + } + }) + // Some batteries give out massive rate values when nearly empty (upower) + .map(|power| { + if power.get::() < 10.0 { + watt!(0.0) + } else { + power + } + }) + // ACPI gives out the special 'Ones' (Constant Ones Object) value for rate + // when it's unable to calculate the true rate. We should set the rate zero, + // and wait for the BIOS to stabilise. + // Source: upower + // + // It come as an `0xffff` originally, but we are operating with `Power` now, + // so this `Ones` value is recalculated a little. + .map(|power| { + // TODO: There might be a chance that we had lost a precision during the conversion + // from the microwatts into default watts, so this should be fixed + if (power.get::() - 65535.0).abs() < f32::EPSILON { + watt!(0.0) + } else { + power + } + }) + .unwrap_or_else(|| microwatt!(0.0)); + + // TODO: Calculate energy_rate manually, if hardware fails. + // if value < 0.01 { + // // Check upower `up_device_supply_calculate_rate` function + // } + + Ok(value) + }) + } + + fn state_of_charge(&self) -> Result<&Ratio> { + self.state_of_charge.try_borrow_with(|| { + match fs::get::(self.root.join("capacity")) { + Ok(Some(capacity)) => Ok(percent!(capacity).into_bounded()), + Ok(None) if self.energy_full()?.is_sign_positive() => { + Ok(*self.energy()? / *self.energy_full()?) + } + // Same as upower, falling back to 0.0% + Ok(None) => Ok(percent!(0.0)), + Err(e) => Err(e), + } + }) + } + + fn state(&self) -> Result<&State> { + self.state + .try_borrow_with(|| match fs::get::(self.root.join("status")) { + Ok(Some(state)) => Ok(state), + Ok(None) => Ok(State::Unknown), + Err(e) => Err(e), + }) + } + + fn voltage(&self) -> Result { + let mut value = ["voltage_now", "voltage_avg"] + .iter() + .filter_map(|filename| match fs::voltage(self.root.join(filename)) { + Ok(Some(value)) => Some(value), + _ => None, + }); + + match value.next() { + Some(value) => Ok(value), + None => Err(Error::not_found("Unable to calculate device voltage value")), + } + } + + fn temperature(&self) -> Result> { + match fs::get::(self.root.join("temp")) { + Ok(Some(value)) => Ok(Some(celsius!(value / 10.0))), + Ok(None) => Ok(None), + Err(e) => Err(e), + } + } + + fn cycle_count(&self) -> Result> { + fs::get::(self.root.join("cycle_count")) + } + + // Following methods are not cached in the struct + + pub fn manufacturer(&self) -> Result> { + fs::get_string(self.root.join("manufacturer")) + } + + pub fn model(&self) -> Result> { + fs::get_string(self.root.join("model_name")) + } + + pub fn serial_number(&self) -> Result> { + fs::get_string(self.root.join("serial_number")) + } + + pub fn technology(&self) -> Result { + match fs::get::(self.root.join("technology")) { + Ok(Some(tech)) => Ok(tech), + Ok(None) => Ok(Technology::Unknown), + Err(e) => Err(e), + } + } +} diff --git a/battery/src/platform/macos/device.rs b/battery/src/platform/macos/device.rs index d896139..db04436 100644 --- a/battery/src/platform/macos/device.rs +++ b/battery/src/platform/macos/device.rs @@ -5,30 +5,29 @@ // - ChargingCurrent // - NotChargingReason (?) +use std::fmt; use std::str; use std::boxed::Box; -use std::convert::AsRef; use num_traits::identities::Zero; - +use crate::Result; use crate::units::{ElectricPotential, ThermodynamicTemperature, Time, Power, Energy}; use crate::types::{State, Technology}; use crate::platform::traits::BatteryDevice; use super::traits::DataSource; -#[derive(Debug)] pub struct IoKitDevice { source: Box, - - manufacturer: Option, - model: Option, - serial_number: Option, } impl IoKitDevice { pub fn get_mut_ref(&mut self) -> &mut dyn DataSource { &mut self.source } + + pub fn refresh(&mut self) -> Result<()> { + self.source.refresh() + } } impl BatteryDevice for IoKitDevice { @@ -67,15 +66,15 @@ impl BatteryDevice for IoKitDevice { } fn vendor(&self) -> Option<&str> { - self.manufacturer.as_ref().map(AsRef::as_ref) + self.source.manufacturer() } fn model(&self) -> Option<&str> { - self.model.as_ref().map(AsRef::as_ref) + self.source.device_name() } fn serial_number(&self) -> Option<&str> { - self.serial_number.as_ref().map(AsRef::as_ref) + self.source.serial_number() } fn technology(&self) -> Technology { @@ -105,16 +104,16 @@ impl BatteryDevice for IoKitDevice { impl From for IoKitDevice where T: DataSource { fn from(ds: T) -> IoKitDevice { - let manufacturer = ds.manufacturer(); - let model = ds.device_name(); - let serial_number = ds.serial_number(); - IoKitDevice { source: Box::new(ds), - - manufacturer, - model, - serial_number, } } } + +impl fmt::Debug for IoKitDevice { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.debug_struct("MacOSDevice") + .field("source", &self.source) + .finish() + } +} diff --git a/battery/src/platform/macos/iokit/errors.rs b/battery/src/platform/macos/iokit/errors.rs index fb4e69d..778a2d9 100644 --- a/battery/src/platform/macos/iokit/errors.rs +++ b/battery/src/platform/macos/iokit/errors.rs @@ -1,44 +1,10 @@ -use std::io; -use std::fmt; -use std::result; -use std::error::Error; - -use mach::kern_return::{kern_return_t, KERN_SUCCESS}; - -pub type Result = result::Result; - -#[derive(Debug)] -pub struct KernError(kern_return_t); - -impl fmt::Display for KernError { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{}", self.0) - } -} - -impl Error for KernError {} - -impl From for KernError { - fn from(ret: kern_return_t) -> KernError { - debug_assert!(ret != KERN_SUCCESS); - - KernError(ret) - } -} - #[macro_export] macro_rules! r#kern_try { ($expr:expr) => (match $expr { mach::kern_return::KERN_SUCCESS => (), err_code => { - return Result::Err($crate::platform::macos::iokit::errors::KernError::from(err_code)); + return ::std::result::Result::Err(::std::io::Error::from_raw_os_error(err_code).into()) } }); - ($expr:expr,) => (r#try!($expr)); -} - -impl From for io::Error { - fn from(e: KernError) -> io::Error { - io::Error::new(io::ErrorKind::Other, e) - } + ($expr:expr,) => (r#kern_try!($expr)); } diff --git a/battery/src/platform/macos/iokit/mod.rs b/battery/src/platform/macos/iokit/mod.rs index c4f3e7c..dde38f4 100644 --- a/battery/src/platform/macos/iokit/mod.rs +++ b/battery/src/platform/macos/iokit/mod.rs @@ -4,5 +4,4 @@ mod wrappers; mod power_source; pub use self::power_source::PowerSource; -pub use self::errors::{Result, KernError}; pub use self::wrappers::*; diff --git a/battery/src/platform/macos/iokit/power_source.rs b/battery/src/platform/macos/iokit/power_source.rs index 9a2ae44..9b0a597 100644 --- a/battery/src/platform/macos/iokit/power_source.rs +++ b/battery/src/platform/macos/iokit/power_source.rs @@ -7,10 +7,13 @@ use core_foundation::string::{CFString, CFStringGetTypeID}; use core_foundation::boolean::{CFBoolean, CFBooleanGetTypeID}; use core_foundation::number::{CFNumber, CFNumberGetTypeID}; +use crate::{Result, Error}; use crate::units::{ElectricPotential, ElectricCurrent, ElectricCharge, ThermodynamicTemperature, Time}; -use super::{IoObject, Result}; +use super::{IoObject}; use super::super::traits::DataSource; +type Properties = CFDictionary; + static FULLY_CHARGED_KEY: &'static str = "FullyCharged"; static EXTERNAL_CONNECTED_KEY: &'static str = "ExternalConnected"; static IS_CHARGING_KEY: &'static str = "IsCharging"; @@ -26,16 +29,50 @@ static MANUFACTURER_KEY: &'static str = "Manufacturer"; static DEVICE_NAME_KEY: &'static str = "DeviceName"; static BATTERY_SERIAL_NUMBER_KEY: &'static str = "BatterySerialNumber"; -pub struct PowerSource { - object: IoObject, - props: CFDictionary, +#[derive(Debug)] +pub struct InstantData { + fully_charged: bool, + external_connected: bool, + is_charging: bool, + voltage: ElectricPotential, + amperage: ElectricCurrent, + design_capacity: ElectricCharge, + max_capacity: ElectricCharge, + current_capacity: ElectricCharge, + temperature: Option, + cycle_count: Option, + time_remaining: Option