diff --git a/.editorconfig b/.editorconfig index dc0fc7f..66f50df 100644 --- a/.editorconfig +++ b/.editorconfig @@ -5,3 +5,4 @@ insert_final_newline = true charset = utf-8 indent_style = space indent_size = 4 +trim_trailing_whitespace = true diff --git a/.travis.yml b/.travis.yml index df6d6c8..c5a1ec9 100644 --- a/.travis.yml +++ b/.travis.yml @@ -46,7 +46,7 @@ matrix: cache: - cargo before_cache: - - chmod -R a+r ${HOME}/.cargo + - chmod -R a+r ${HOME}/.cargo before_install: - cargo install --force cross diff --git a/Cargo.toml b/Cargo.toml index 6457ab8..0d99298 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,3 +4,6 @@ members = [ "battery-ffi", "battery-cli", ] + +[patch.crates-io] +battery = { path = "./battery" } diff --git a/README.md b/README.md index e6d9710..6dbb20f 100644 --- a/README.md +++ b/README.md @@ -3,13 +3,13 @@ [![Latest Version](https://img.shields.io/crates/v/battery.svg)](https://crates.io/crates/battery) [![Latest Version](https://docs.rs/battery/badge.svg)](https://docs.rs/battery) [![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/0.6.2/status.svg)](https://deps.rs/crate/battery/0.6.2) +[![dependency status](https://deps.rs/crate/battery/0.7.0/status.svg)](https://deps.rs/crate/battery/0.7.0) ![Apache 2.0 OR MIT licensed](https://img.shields.io/badge/license-Apache2.0%2FMIT-blue.svg) Rust crate providing cross-platform information about batteries. Gives access to a system independent battery state, capacity, charge and voltage values -recalculated as necessary to be returned in `mW`, `mWh` or `mV` units. +recalculated as necessary to be returned [SI measurement units](https://www.bipm.org/en/measurement-units/). ## Supported platforms diff --git a/battery-cli/Cargo.toml b/battery-cli/Cargo.toml index f746402..dca9561 100644 --- a/battery-cli/Cargo.toml +++ b/battery-cli/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "battery-cli" -version = "0.1.3" +version = "0.1.4" authors = ["svartalf "] edition = "2018" description = "CLI tool for batteries reports" @@ -25,7 +25,7 @@ test = false cfg-if = "0.1" [target.'cfg(not(windows))'.dependencies] -battery = "0.6.2" +battery = "0.7.0" humantime = "1.2.0" tui = "0.4.0" termion = "1.5.1" diff --git a/battery-cli/README.md b/battery-cli/README.md index 228a8cd..4cbdcf7 100644 --- a/battery-cli/README.md +++ b/battery-cli/README.md @@ -3,7 +3,7 @@ [![Latest Version](https://img.shields.io/crates/v/battery-cli.svg)](https://crates.io/crates/battery-cli) [![Latest Version](https://docs.rs/battery-cli/badge.svg)](https://docs.rs/battery-cli) [![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-cli/0.1.3/status.svg)](https://deps.rs/crate/battery-cli/0.1.3) +[![dependency status](https://deps.rs/crate/battery-cli/0.1.4/status.svg)](https://deps.rs/crate/battery-cli/0.1.4) ![Apache 2.0 OR MIT licensed](https://img.shields.io/badge/license-Apache2.0%2FMIT-blue.svg) `battery-cli` is a Proof-Of-Concept binary crate, that provides terminal user interface diff --git a/battery-cli/src/main.rs b/battery-cli/src/main.rs index 33cd443..7e9085a 100644 --- a/battery-cli/src/main.rs +++ b/battery-cli/src/main.rs @@ -1,4 +1,5 @@ -#[macro_use] extern crate cfg_if; +#[macro_use] +extern crate cfg_if; cfg_if! { if #[cfg(windows)] { diff --git a/battery-cli/src/ui/app.rs b/battery-cli/src/ui/app.rs index 3698f2e..7475915 100644 --- a/battery-cli/src/ui/app.rs +++ b/battery-cli/src/ui/app.rs @@ -2,7 +2,11 @@ use super::util::tabs; use super::util::graph; + use battery::Battery; +use battery::units::power::watt; +use battery::units::electric_potential::volt; +use battery::units::thermodynamic_temperature::degree_celsius; #[derive(Debug)] pub struct BatteryStats<'b> { @@ -12,12 +16,6 @@ pub struct BatteryStats<'b> { pub temperature_graph: graph::GraphData<'b>, } -impl<'b> BatteryStats<'b> { - pub fn info_rows(&self) -> Vec<(String, String)> { - vec![] - } -} - #[derive(Debug)] pub struct App<'a> { pub manager: battery::Manager, @@ -57,10 +55,10 @@ impl<'a> App<'a> { pub fn update(&mut self) { for stat in self.batteries.iter_mut() { let _ = self.manager.refresh(&mut stat.battery); - stat.voltage_graph.push(f64::from(stat.battery.voltage()) / 1_000.0); - stat.energy_rate_graph.push(f64::from(stat.battery.energy_rate()) / 1_000.0); + stat.voltage_graph.push(f64::from(stat.battery.voltage().get::())); + stat.energy_rate_graph.push(f64::from(stat.battery.energy_rate().get::())); if let Some(temp) = stat.battery.temperature() { - stat.temperature_graph.push(f64::from(temp)); + stat.temperature_graph.push(f64::from(temp.get::())); } } } diff --git a/battery-cli/src/ui/mod.rs b/battery-cli/src/ui/mod.rs index 60a1f57..f72437f 100644 --- a/battery-cli/src/ui/mod.rs +++ b/battery-cli/src/ui/mod.rs @@ -20,6 +20,7 @@ use std::io; use std::error::Error; +use std::time::Duration; use termion::input::MouseTerminal; use termion::screen::AlternateScreen; @@ -32,6 +33,13 @@ use tui::style::*; use tui::widgets::*; use battery::{Battery, State}; +use battery::units::Unit; +use battery::units::power::watt; +use battery::units::time::second; +use battery::units::ratio::percent; +use battery::units::energy::watt_hour; +use battery::units::electric_potential::volt; +use battery::units::thermodynamic_temperature::degree_celsius; use crate::ui::app::BatteryStats; use crate::ui::util::event::{Event, Events}; @@ -131,7 +139,7 @@ fn draw_tabs(f: &mut Frame, area: Rect, tabs: &TabsState) where B: Backend } fn draw_percentage_bar(f: &mut Frame, area: Rect, stat: &BatteryStats) where B: Backend { - let value = f64::from(stat.battery.percentage()); + let value = f64::from(stat.battery.state_of_charge().get::()); let block = Block::default() .title("Percentage") .borders(Borders::ALL); @@ -216,12 +224,12 @@ fn draw_energy_information(f: &mut Frame, area: Rect, battery: &Battery) w let block = Block::default() .borders(Borders::LEFT | Borders::RIGHT); - let consumption = &format!("{:.2} W", battery.energy_rate() as f32 / 1000.0); - let voltage = &format!("{:.2} V", battery.voltage() as f32 / 1000.0); - let capacity = &format!("{:.2}%", battery.capacity()); - let current = &format!("{:.2} Wh", battery.energy() as f32 / 1000.0); - let last_full = &format!("{:.2} Wh", battery.energy_full() as f32 / 1000.0); - let full_design = &format!("{:.2} Wh", battery.energy_full_design() as f32 / 1000.0); + let consumption = &format!("{:.2} {}", battery.energy_rate().get::(), watt::abbreviation()); + let voltage = &format!("{:.2} {}", battery.voltage().get::(), volt::abbreviation()); + let capacity = &format!("{:.2} {}", battery.state_of_health().get::(), percent::abbreviation()); + let current = &format!("{:.2} {}", battery.energy().get::(), watt_hour::abbreviation()); + let last_full = &format!("{:.2} {}", battery.energy_full().get::(), watt_hour::abbreviation()); + let full_design = &format!("{:.2} {}", battery.energy_full_design().get::(), watt_hour::abbreviation()); let consumption_label = match battery.state() { State::Charging => "Charging with", State::Discharging => "Discharging with", @@ -253,12 +261,12 @@ fn draw_time_information(f: &mut Frame, area: Rect, battery: &Battery) whe .borders(Borders::LEFT | Borders::RIGHT); let time_to_full = match battery.time_to_full() { - Some(time) => humantime::format_duration(time).to_string(), + Some(time) => humantime::format_duration(Duration::from_secs(time.get::() as u64)).to_string(), None => "N/A".to_string(), }; let time_to_empty = match battery.time_to_empty() { - Some(time) => humantime::format_duration(time).to_string(), + Some(time) => humantime::format_duration(Duration::from_secs(time.get::() as u64)).to_string(), None => "N/A".to_string(), }; let items = vec![ @@ -283,7 +291,7 @@ fn draw_env_information(f: &mut Frame, area: Rect, battery: &Battery) wher .borders(Borders::LEFT | Borders::RIGHT | Borders::BOTTOM); let temperature = match battery.temperature() { - Some(temp) => format!("{:.2} °C", temp), + Some(temp) => format!("{:.2} {}", temp.get::(), degree_celsius::abbreviation()), None => "N/A".to_string(), }; let items = vec![ diff --git a/battery-cli/src/ui/util/graph.rs b/battery-cli/src/ui/util/graph.rs index 197e23a..5cad007 100644 --- a/battery-cli/src/ui/util/graph.rs +++ b/battery-cli/src/ui/util/graph.rs @@ -87,6 +87,7 @@ impl<'g> GraphData<'g> { [self.y_lower(), self.y_upper()] } + #[allow(clippy::cast_lossless)] pub fn push(&mut self, value: f64) { if self.points.len() == RESOLUTION { self.points.remove(0); diff --git a/battery-ffi/Cargo.toml b/battery-ffi/Cargo.toml index fa88273..6ce9b55 100644 --- a/battery-ffi/Cargo.toml +++ b/battery-ffi/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "battery-ffi" -version = "0.1.5" +version = "0.2.0" authors = ["svartalf "] edition = "2018" description = "FFI bindings for battery crate" @@ -22,7 +22,7 @@ crate-type = ["cdylib"] default = ["cbindgen"] [dependencies] -battery = "0.6.2" +battery = "0.7.0" libc = "0.2.48" [build-dependencies] diff --git a/battery-ffi/README.md b/battery-ffi/README.md index b7bff34..0b144ea 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.1.5/status.svg)](https://deps.rs/crate/battery-ffi/0.1.5) +[![dependency status](https://deps.rs/crate/battery-ffi/0.2.0/status.svg)](https://deps.rs/crate/battery-ffi/0.2.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/examples/ffi.c b/battery-ffi/examples/ffi.c index c947853..8f624d6 100644 --- a/battery-ffi/examples/ffi.c +++ b/battery-ffi/examples/ffi.c @@ -12,10 +12,6 @@ #include "battery_ffi.h" -float from_millis(uint32_t value) { - return (float) value / 1000; -} - void pretty_print(Battery *battery, uint32_t *idx) { printf("Device:\t\t\t%d\n", *idx); @@ -66,11 +62,11 @@ void pretty_print(Battery *battery, uint32_t *idx) { printf("full\n"); break; } - printf(" energy:\t\t%.2f Wh\n", from_millis(battery_get_energy(battery))); - printf(" energy-full:\t\t%.2f Wh\n", from_millis(battery_get_energy_full(battery))); - printf(" energy-full-design:\t%.2f Wh\n", from_millis(battery_get_energy_full_design(battery))); - printf(" energy-rate:\t\t%.2f W\n", from_millis(battery_get_energy_rate(battery))); - printf(" voltage:\t\t%.2f V\n", from_millis(battery_get_voltage(battery))); + printf(" energy:\t\t%.2f joule\n", battery_get_energy(battery)); + printf(" energy-full:\t\t%.2f joule\n", battery_get_energy_full(battery)); + printf(" energy-full-design:\t%.2f joule\n", battery_get_energy_full_design(battery)); + printf(" energy-rate:\t\t%.2f W\n", battery_get_energy_rate(battery)); + printf(" voltage:\t\t%.2f V\n", battery_get_voltage(battery)); printf(" technology:\t\t"); switch (battery_get_technology(battery)) { @@ -105,24 +101,24 @@ void pretty_print(Battery *battery, uint32_t *idx) { uint64_t time_to_full = battery_get_time_to_full(battery); if ((state == StateCharging) && (time_to_full > 0)) { - printf(" time-to-full:\t\t%d sec.\n", time_to_full); + printf(" time-to-full:\t\t%ld sec.\n", time_to_full); } uint64_t time_to_empty = battery_get_time_to_empty(battery); if ((state == StateDischarging) && (time_to_empty > 0)) { - printf(" time-to-empty:\t\t%d sec.\n", time_to_empty); + printf(" time-to-empty:\t\t%ld sec.\n", time_to_empty); } - printf(" percentage:\t\t%.2f %%\n", battery_get_percentage(battery)); + printf(" state of charge:\t\t%.2f %%\n", battery_get_state_of_charge(battery)); float temp = battery_get_temperature(battery); printf(" temperature:\t\t"); if (temp < FLT_MAX) { - printf("%.2f C\n", temp); + printf("%.2f K\n", temp); } else { printf("N/A\n"); } - printf(" capacity:\t\t%.2f %%\n", battery_get_capacity(battery)); + printf(" state of health:\t\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) { diff --git a/battery-ffi/examples/ffi.py b/battery-ffi/examples/ffi.py index d5ccd79..149b12d 100755 --- a/battery-ffi/examples/ffi.py +++ b/battery-ffi/examples/ffi.py @@ -83,27 +83,27 @@ class Battery(ctypes.Structure): lib.battery_get_serial_number.restype = ctypes.c_char_p lib.battery_get_state.restype = ctypes.c_uint8 lib.battery_get_energy.argtypes = (ctypes.POINTER(Battery), ) -lib.battery_get_energy.restype = ctypes.c_uint32 +lib.battery_get_energy.restype = ctypes.c_float lib.battery_get_energy_full.argtypes = (ctypes.POINTER(Battery), ) -lib.battery_get_energy_full.restype = ctypes.c_uint32 +lib.battery_get_energy_full.restype = ctypes.c_float lib.battery_get_energy_full_design.argtypes = (ctypes.POINTER(Battery), ) -lib.battery_get_energy_full_design.restype = ctypes.c_uint32 +lib.battery_get_energy_full_design.restype = ctypes.c_float lib.battery_get_energy_rate.argtypes = (ctypes.POINTER(Battery), ) -lib.battery_get_energy_rate.restype = ctypes.c_uint32 +lib.battery_get_energy_rate.restype = ctypes.c_float lib.battery_get_voltage.argtypes = (ctypes.POINTER(Battery), ) -lib.battery_get_voltage.restype = ctypes.c_uint32 +lib.battery_get_voltage.restype = ctypes.c_float lib.battery_get_technology.argtypes = (ctypes.POINTER(Battery), ) lib.battery_get_technology.restype = ctypes.c_uint8 lib.battery_get_time_to_full.argtypes = (ctypes.POINTER(Battery), ) -lib.battery_get_time_to_full.restype = ctypes.c_uint64 +lib.battery_get_time_to_full.restype = ctypes.c_float lib.battery_get_time_to_empty.argtypes = (ctypes.POINTER(Battery), ) -lib.battery_get_time_to_empty.restype = ctypes.c_uint64 -lib.battery_get_percentage.argtypes = (ctypes.POINTER(Battery), ) -lib.battery_get_percentage.restype = ctypes.c_float +lib.battery_get_time_to_empty.restype = ctypes.c_float +lib.battery_get_state_of_charge.argtypes = (ctypes.POINTER(Battery), ) +lib.battery_get_state_of_charge.restype = ctypes.c_float lib.battery_get_temperature.argtypes = (ctypes.POINTER(Battery), ) lib.battery_get_temperature.restype = ctypes.c_float -lib.battery_get_capacity.argtypes = (ctypes.POINTER(Battery), ) -lib.battery_get_capacity.restype = ctypes.c_float +lib.battery_get_state_of_health.argtypes = (ctypes.POINTER(Battery), ) +lib.battery_get_state_of_health.restype = ctypes.c_float lib.battery_get_cycle_count.argtypes = (ctypes.POINTER(Battery), ) lib.battery_get_cycle_count.restype = ctypes.c_uint32 @@ -120,16 +120,16 @@ class Battery(ctypes.Structure): print('S/N', lib.battery_get_serial_number(battery)) print('State', STATE.get(lib.battery_get_state(battery))) print('Technology', TECHNOLOGY.get(lib.battery_get_technology(battery))) - print('Energy (Wh)', lib.battery_get_energy(battery) / 1000) - print('Energy full (Wh)', lib.battery_get_energy_full_design(battery) / 1000) - print('Energy full design (Wh)', lib.battery_get_energy_full_design(battery) / 1000) - print('Energy rate (W)', lib.battery_get_energy_rate(battery) / 1000) - print('Voltage (V)', lib.battery_get_voltage(battery) / 1000) + print('Energy (joule)', lib.battery_get_energy(battery)) + print('Energy full (joule)', lib.battery_get_energy_full_design(battery)) + print('Energy full design (joule)', lib.battery_get_energy_full_design(battery)) + print('Energy rate (W)', lib.battery_get_energy_rate(battery)) + print('Voltage (V)', lib.battery_get_voltage(battery)) print('Time to full (sec)', lib.battery_get_time_to_full(battery)) print('Time to empty (sec)', lib.battery_get_time_to_empty(battery)) - print('Percentage (%)', lib.battery_get_percentage(battery)) - print('Temperature (C)', lib.battery_get_temperature(battery)) - print('Capacity (%)', lib.battery_get_capacity(battery)) + print('State of charge (%)', lib.battery_get_state_of_charge(battery)) + print('Temperature (K)', lib.battery_get_temperature(battery)) + print('State of health (%)', lib.battery_get_state_of_health(battery)) print('Cycle count', lib.battery_get_cycle_count(battery)) lib.battery_free(battery) diff --git a/battery-ffi/src/battery.rs b/battery-ffi/src/battery.rs index da9299e..f3a790f 100644 --- a/battery-ffi/src/battery.rs +++ b/battery-ffi/src/battery.rs @@ -1,101 +1,108 @@ -use std::ptr; use std::f32; -use std::u32; use std::ffi::CString; +use std::ptr; +use std::u32; -use crate::Battery; -use crate::technology::Technology; use crate::state::State; +use crate::technology::Technology; +use crate::Battery; + +use battery::units::electric_potential::volt; +use battery::units::energy::joule; +use battery::units::power::watt; +use battery::units::ratio::percent; +use battery::units::thermodynamic_temperature::kelvin; +use battery::units::time::second; -/// Returns battery percentage. +/// Returns battery state of charge as a percentage value from `0.0` to `100.0`. /// /// # Panics /// /// This function will panic if passed pointer is `NULL` #[no_mangle] -pub unsafe extern fn battery_get_percentage(ptr: *const Battery) -> libc::c_float { +pub unsafe extern "C" fn battery_get_state_of_charge(ptr: *const Battery) -> libc::c_float { assert!(!ptr.is_null()); let battery = &*ptr; - battery.percentage() + battery.state_of_charge().get::() } -/// Returns battery energy (in `mWh`). +/// Returns battery energy (in `joule`). /// /// # Panics /// /// This function will panic if passed pointer is `NULL` #[no_mangle] -pub unsafe extern fn battery_get_energy(ptr: *const Battery) -> libc::uint32_t { +pub unsafe extern "C" fn battery_get_energy(ptr: *const Battery) -> libc::c_float { assert!(!ptr.is_null()); let battery = &*ptr; - battery.energy() + battery.energy().get::() } -/// Returns battery energy (in `mWh`) when it is considered full. +/// Returns battery energy (in `joule`) when it is considered full. /// /// # Panics /// /// This function will panic if passed pointer is `NULL` #[no_mangle] -pub unsafe extern fn battery_get_energy_full(ptr: *const Battery) -> libc::uint32_t { +pub unsafe extern "C" fn battery_get_energy_full(ptr: *const Battery) -> libc::c_float { assert!(!ptr.is_null()); let battery = &*ptr; - battery.energy_full() + battery.energy_full().get::() } -/// Returns battery energy (in `mWh`) designed to hold when it is considered full. +/// Returns battery energy (in `joule`) designed to hold when it is considered full. /// /// # Panics /// /// This function will panic if passed pointer is `NULL` #[no_mangle] -pub unsafe extern fn battery_get_energy_full_design(ptr: *const Battery) -> libc::uint32_t { +pub unsafe extern "C" fn battery_get_energy_full_design(ptr: *const Battery) -> libc::c_float { assert!(!ptr.is_null()); let battery = &*ptr; - battery.energy_full_design() + battery.energy_full_design().get::() } -/// Returns battery energy rate (in `mW`). +/// Returns battery energy rate (in `W`). /// /// # Panics /// /// This function will panic if passed pointer is `NULL` #[no_mangle] -pub unsafe extern fn battery_get_energy_rate(ptr: *const Battery) -> libc::uint32_t { +pub unsafe extern "C" fn battery_get_energy_rate(ptr: *const Battery) -> libc::c_float { assert!(!ptr.is_null()); let battery = &*ptr; - battery.energy_rate() + battery.energy_rate().get::() } -/// Returns battery voltage (in `mV`) +/// Returns battery voltage (in `V`) /// /// # Panics /// /// This function will panic if passed pointer is `NULL` #[no_mangle] -pub unsafe extern fn battery_get_voltage(ptr: *const Battery) -> libc::uint32_t { +pub unsafe extern "C" fn battery_get_voltage(ptr: *const Battery) -> libc::c_float { assert!(!ptr.is_null()); let battery = &*ptr; - battery.voltage() + battery.voltage().get::() } -/// Returns battery capacity in `0.0`..`100.0` percents range. +/// Returns battery state of health as a percentage value from `0.0` to `100.0`. /// /// # Panics /// /// This function will panic if passed pointer is `NULL` #[no_mangle] -pub unsafe extern fn battery_get_capacity(ptr: *const Battery) -> libc::c_float { +pub unsafe extern "C" fn battery_get_state_of_health(ptr: *const Battery) -> libc::c_float { assert!(!ptr.is_null()); let battery = &*ptr; - battery.capacity() + battery.state_of_health().get::() } /// Returns battery state. @@ -104,7 +111,7 @@ pub unsafe extern fn battery_get_capacity(ptr: *const Battery) -> libc::c_float /// /// This function will panic if passed pointer is `NULL` #[no_mangle] -pub unsafe extern fn battery_get_state(ptr: *const Battery) -> State { +pub unsafe extern "C" fn battery_get_state(ptr: *const Battery) -> State { assert!(!ptr.is_null()); let battery = &*ptr; @@ -117,30 +124,30 @@ pub unsafe extern fn battery_get_state(ptr: *const Battery) -> State { /// /// This function will panic if passed pointer is `NULL` #[no_mangle] -pub unsafe extern fn battery_get_technology(ptr: *const Battery) -> Technology { +pub unsafe extern "C" fn battery_get_technology(ptr: *const Battery) -> Technology { assert!(!ptr.is_null()); let battery = &*ptr; battery.technology().into() } -/// Returns battery temperature. +/// Returns battery temperature in Kelvin. /// /// # Returns /// -/// If value is not available, function returns max possible value for `float` type (`1E+37`). +/// If value is not available, function returns `NaN`. /// /// # Panics /// /// This function will panic if passed pointer is `NULL` #[no_mangle] -pub unsafe extern fn battery_get_temperature(ptr: *const Battery) -> libc::c_float { +pub unsafe extern "C" fn battery_get_temperature(ptr: *const Battery) -> libc::c_float { assert!(!ptr.is_null()); let battery = &*ptr; match battery.temperature() { - None => f32::MAX, - Some(temp) => temp, + None => f32::NAN, + Some(temp) => temp.get::(), } } @@ -154,7 +161,7 @@ pub unsafe extern fn battery_get_temperature(ptr: *const Battery) -> libc::c_flo /// /// This function will panic if passed pointer is `NULL` #[no_mangle] -pub unsafe extern fn battery_get_cycle_count(ptr: *const Battery) -> libc::uint32_t { +pub unsafe extern "C" fn battery_get_cycle_count(ptr: *const Battery) -> libc::uint32_t { assert!(!ptr.is_null()); let battery = &*ptr; @@ -179,7 +186,7 @@ pub unsafe extern fn battery_get_cycle_count(ptr: *const Battery) -> libc::uint3 /// /// This function will panic if passed pointer is `NULL` #[no_mangle] -pub unsafe extern fn battery_get_vendor(ptr: *const Battery) -> *mut libc::c_char { +pub unsafe extern "C" fn battery_get_vendor(ptr: *const Battery) -> *mut libc::c_char { assert!(!ptr.is_null()); let battery = &*ptr; @@ -187,7 +194,7 @@ pub unsafe extern fn battery_get_vendor(ptr: *const Battery) -> *mut libc::c_cha Some(vendor) => { let c_str = CString::new(vendor).unwrap(); c_str.into_raw() - }, + } None => ptr::null_mut(), } } @@ -207,7 +214,7 @@ pub unsafe extern fn battery_get_vendor(ptr: *const Battery) -> *mut libc::c_cha /// /// This function will panic if passed pointer is `NULL` #[no_mangle] -pub unsafe extern fn battery_get_model(ptr: *const Battery) -> *mut libc::c_char { +pub unsafe extern "C" fn battery_get_model(ptr: *const Battery) -> *mut libc::c_char { assert!(!ptr.is_null()); let battery = &*ptr; @@ -215,7 +222,7 @@ pub unsafe extern fn battery_get_model(ptr: *const Battery) -> *mut libc::c_char Some(model) => { let c_str = CString::new(model).unwrap(); c_str.into_raw() - }, + } None => ptr::null_mut(), } } @@ -235,7 +242,7 @@ pub unsafe extern fn battery_get_model(ptr: *const Battery) -> *mut libc::c_char /// /// This function will panic if passed pointer is `NULL` #[no_mangle] -pub unsafe extern fn battery_get_serial_number(ptr: *const Battery) -> *mut libc::c_char { +pub unsafe extern "C" fn battery_get_serial_number(ptr: *const Battery) -> *mut libc::c_char { assert!(!ptr.is_null()); let battery = &*ptr; @@ -243,7 +250,7 @@ pub unsafe extern fn battery_get_serial_number(ptr: *const Battery) -> *mut libc Some(sn) => { let c_str = CString::new(sn).unwrap(); c_str.into_raw() - }, + } None => ptr::null_mut(), } } @@ -252,20 +259,20 @@ pub unsafe extern fn battery_get_serial_number(ptr: *const Battery) -> *mut libc /// /// # Returns /// -/// If battery is not charging at the moment, this function will return `0`, +/// If battery is not charging at the moment, this function will return `NaN`, /// otherwise it will return seconds amount. /// /// # Panics /// /// This function will panic if passed pointer is `NULL` #[no_mangle] -pub unsafe extern fn battery_get_time_to_full(ptr: *const Battery) -> libc::uint64_t { +pub unsafe extern "C" fn battery_get_time_to_full(ptr: *const Battery) -> libc::c_float { assert!(!ptr.is_null()); let battery = &*ptr; match battery.time_to_full() { - None => 0, - Some(duration) => duration.as_secs(), + None => f32::NAN, + Some(duration) => duration.get::(), } } @@ -273,20 +280,20 @@ pub unsafe extern fn battery_get_time_to_full(ptr: *const Battery) -> libc::uint /// /// # Returns /// -/// If battery is not discharging at the moment, this function will return `0`, +/// If battery is not discharging at the moment, this function will return `NaN`, /// otherwise it will return seconds amount. /// /// # Panics /// /// This function will panic if passed pointer is `NULL` #[no_mangle] -pub unsafe extern fn battery_get_time_to_empty(ptr: *const Battery) -> libc::uint64_t { +pub unsafe extern "C" fn battery_get_time_to_empty(ptr: *const Battery) -> libc::c_float { assert!(!ptr.is_null()); let battery = &*ptr; match battery.time_to_empty() { - None => 0, - Some(duration) => duration.as_secs(), + None => f32::NAN, + Some(duration) => duration.get::(), } } @@ -295,7 +302,7 @@ pub unsafe extern fn battery_get_time_to_empty(ptr: *const Battery) -> libc::uin /// Caller is required to call this function when battery pointer is not needed anymore /// in order to properly free memory. #[no_mangle] -pub unsafe extern fn battery_free(ptr: *mut Battery) { +pub unsafe extern "C" fn battery_free(ptr: *mut Battery) { if ptr.is_null() { return; } @@ -310,7 +317,7 @@ pub unsafe extern fn battery_free(ptr: *mut Battery) { /// * [battery_model](fn.battery_model.html) /// * [battery_serial_number](fn.battery_serial_number.html) #[no_mangle] -pub unsafe extern fn battery_str_free(ptr: *mut libc::c_char) { +pub unsafe extern "C" fn battery_str_free(ptr: *mut libc::c_char) { if ptr.is_null() { return; } diff --git a/battery-ffi/src/iterator.rs b/battery-ffi/src/iterator.rs index 7c76a10..8a6206b 100644 --- a/battery-ffi/src/iterator.rs +++ b/battery-ffi/src/iterator.rs @@ -19,7 +19,7 @@ use crate::{Batteries, Battery}; /// If there is no batteries left to iterate, this function returns `NULL`, /// otherwise it returns pointer to next battery. #[no_mangle] -pub unsafe extern fn battery_iterator_next(ptr: *mut Batteries) -> *mut Battery { +pub unsafe extern "C" fn battery_iterator_next(ptr: *mut Batteries) -> *mut Battery { assert!(!ptr.is_null()); let iterator = &mut *ptr; @@ -31,7 +31,7 @@ pub unsafe extern fn battery_iterator_next(ptr: *mut Batteries) -> *mut Battery /// Frees previously created batteries iterator. #[no_mangle] -pub unsafe extern fn battery_iterator_free(ptr: *mut Batteries) { +pub unsafe extern "C" fn battery_iterator_free(ptr: *mut Batteries) { if ptr.is_null() { return; } diff --git a/battery-ffi/src/lib.rs b/battery-ffi/src/lib.rs index bd189e2..7c473fe 100644 --- a/battery-ffi/src/lib.rs +++ b/battery-ffi/src/lib.rs @@ -36,9 +36,9 @@ // from the battery crate, if this line is missing extern crate battery as battery_lib; -mod manager; -mod iterator; mod battery; +mod iterator; +mod manager; mod state; mod technology; @@ -60,8 +60,8 @@ pub type Batteries = battery_lib::Batteries; /// and work with it only via library methods. pub type Battery = battery_lib::Battery; -pub use self::manager::*; -pub use self::iterator::*; pub use self::battery::*; +pub use self::iterator::*; +pub use self::manager::*; pub use self::state::*; pub use self::technology::*; diff --git a/battery-ffi/src/manager.rs b/battery-ffi/src/manager.rs index 017ab1c..e507314 100644 --- a/battery-ffi/src/manager.rs +++ b/battery-ffi/src/manager.rs @@ -1,11 +1,11 @@ -use crate::{Manager, Batteries, Battery}; +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) /// to properly free memory. #[no_mangle] -pub extern fn battery_manager_new() -> *mut Manager { +pub extern "C" fn battery_manager_new() -> *mut Manager { Box::into_raw(Box::new(Manager::new())) } @@ -17,7 +17,7 @@ pub extern fn battery_manager_new() -> *mut Manager { /// /// This function will panic if passed pointer is `NULL` #[no_mangle] -pub unsafe extern fn battery_manager_iter(ptr: *mut Manager) -> *mut Batteries { +pub unsafe extern "C" fn battery_manager_iter(ptr: *mut Manager) -> *mut Batteries { assert!(!ptr.is_null()); let manager = &*ptr; @@ -33,7 +33,10 @@ pub unsafe extern fn battery_manager_iter(ptr: *mut Manager) -> *mut Batteries { /// # Returns /// /// `0` if everything is okay, `1` if refresh failed and `battery_ptr` contains stale information. -pub unsafe extern fn battery_manager_refresh(manager_ptr: *mut Manager, battery_ptr: *mut Battery) -> libc::c_int { +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; @@ -49,7 +52,7 @@ pub unsafe extern fn battery_manager_refresh(manager_ptr: *mut Manager, battery_ /// Frees manager instance. #[no_mangle] -pub unsafe extern fn battery_manager_free(ptr: *mut Manager) { +pub unsafe extern "C" fn battery_manager_free(ptr: *mut Manager) { if ptr.is_null() { return; } diff --git a/battery-ffi/src/state.rs b/battery-ffi/src/state.rs index 2f57a4c..1b3236b 100644 --- a/battery-ffi/src/state.rs +++ b/battery-ffi/src/state.rs @@ -5,6 +5,7 @@ use battery::State as RawState; /// Enum members are prefixed here in order to not have "redeclaration of enumerator" error in C. #[repr(u8)] pub enum State { + // DO NOT RE-ORDER VALUES IN THIS ENUM, IT WILL AFFECT FFI USERS! StateUnknown = 0, StateCharging = 1, StateDischarging = 2, @@ -20,6 +21,7 @@ impl From for State { RawState::Discharging => State::StateDischarging, RawState::Empty => State::StateEmpty, RawState::Full => State::StateFull, + _ => State::StateUnknown, } } } diff --git a/battery-ffi/src/technology.rs b/battery-ffi/src/technology.rs index 602d558..999abca 100644 --- a/battery-ffi/src/technology.rs +++ b/battery-ffi/src/technology.rs @@ -8,6 +8,7 @@ use battery::Technology as RawTech; /// Enum members are prefixed here in order to not have "redeclaration of enumerator" error in C. #[repr(u8)] pub enum Technology { + // DO NOT RE-ORDER VALUES IN THIS ENUM, IT WILL AFFECT FFI USERS TechnologyUnknown = 0, TechnologyLithiumIon = 1, TechnologyLeadAcid = 2, @@ -30,7 +31,10 @@ 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/CHANGELOG.md b/battery/CHANGELOG.md index 95173b2..bffb96f 100644 --- a/battery/CHANGELOG.md +++ b/battery/CHANGELOG.md @@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased ### Changed +- Return SI measurement units from `uom` crate for almost all public `Battery` methods +- Re-export used `uom` quantities and measurement units in public `battery::units` module +- Rename `Battery::percentage` method into `Battery::state_of_charge` +- Rename `Battery::capacity` method into `Battery::state_of_health` +- Mark `battery::State` and `battery::Technology` enums as a non-exhaustive +- Ignore devices with `scope` attributes different from `System` for Linux [#18](https://github.com/svartalf/rust-battery/issues/18) - Update outdated `mach` dependency for Mac OS ## [0.6.2] - 2019-02-28 diff --git a/battery/Cargo.toml b/battery/Cargo.toml index 5bc0622..97e9042 100644 --- a/battery/Cargo.toml +++ b/battery/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "battery" -version = "0.6.2" +version = "0.7.0" authors = ["svartalf "] edition = "2018" description = "Cross-platform information about batteries" @@ -16,6 +16,8 @@ maintenance = { status = "actively-developed" } [dependencies] cfg-if = "0.1" +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" diff --git a/battery/examples/simple.rs b/battery/examples/simple.rs index 9fe3038..679ebe7 100644 --- a/battery/examples/simple.rs +++ b/battery/examples/simple.rs @@ -10,7 +10,7 @@ fn main() -> io::Result<()> { Some(battery) => battery, None => { eprintln!("Unable to find any batteries"); - return Err(io::Error::from(io::ErrorKind::NotFound)) + return Err(io::Error::from(io::ErrorKind::NotFound)); } }; diff --git a/battery/src/lib.rs b/battery/src/lib.rs index b145642..4793e84 100644 --- a/battery/src/lib.rs +++ b/battery/src/lib.rs @@ -1,12 +1,12 @@ //! This crate provides cross-platform information about batteries. //! //! Gives access to a system independent battery state, capacity, charge and voltage values -//! recalculated as necessary to be returned in `mW`, `mWh` or `mV` units. +//! recalculated as necessary to be returned in [SI measurement units](https://www.bipm.org/en/measurement-units/). //! //! ## Supported platforms //! //! * Linux 2.6.39+ -//! * MacOS (10.10+ probably, needs to be confirmed) +//! * MacOS 10.10+ //! * Windows 7+ //! * FreeBSD //! * DragonFlyBSD @@ -20,15 +20,20 @@ #![deny(unused)] #![deny(unstable_features)] -#[macro_use] extern crate cfg_if; +#[macro_use] +extern crate cfg_if; #[cfg(target_os = "windows")] -#[macro_use] extern crate winapi; +#[macro_use] +extern crate winapi; #[cfg(any(target_os = "dragonfly", target_os = "freebsd"))] -#[macro_use] extern crate nix; +#[macro_use] +extern crate nix; mod types; +#[macro_use] +pub mod units; mod platform; -pub use self::types::{Manager, Batteries, Battery, State, Technology}; +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 e9355bb..898d39b 100644 --- a/battery/src/platform/freebsd/acpi.rs +++ b/battery/src/platform/freebsd/acpi.rs @@ -13,7 +13,18 @@ use nix::Error; use crate::{State, Technology}; 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` +const ACPI_BATT_STAT_DISCHARG: u32 = 0x0001; +const ACPI_BATT_STAT_CHARGING: u32 = 0x0002; +const ACPI_BATT_STAT_CRITICAL: u32 = 0x0004; + +/// 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 { @@ -23,11 +34,10 @@ fn map_nix_err(e: Error) -> io::Error { } } -#[allow(non_camel_case_types)] // it would be stupid to call it `Mw`, right? Looks like megawatt #[derive(Debug, Eq, PartialEq)] pub enum Units { - mW, - mA, + MilliWatts, + MilliAmperes, } #[repr(C)] @@ -51,9 +61,9 @@ pub struct AcpiBif { impl AcpiBif { pub fn units(&self) -> Units { match self.units { - ACPI_BIF_UNITS_MW => Units::mW, - ACPI_BIF_UNITS_MA => Units::mA, - _ => panic!("Unknown units from acpi_bif"), + ACPI_BIF_UNITS_MW => Units::MilliWatts, + ACPI_BIF_UNITS_MA => Units::MilliAmperes, + _ => unreachable!("Unknown units from acpi_bif"), } } @@ -79,16 +89,24 @@ impl AcpiBif { } } + /// mV always + #[inline] + pub fn design_voltage(&self) -> u32 { + self.dvol + } + pub fn oem(&self) -> Option { self.get_string(&self.oeminfo) } - // energy_design + /// Either mWh or mAh, depends on `self.units` + #[inline] pub fn design_capacity(&self) -> u32 { self.dcap } - // energy_full_design + /// Either mWh or mAh, depends on `self.units` + #[inline] pub fn last_full_capacity(&self) -> u32 { self.lfcap } @@ -117,24 +135,33 @@ pub struct AcpiBst { impl AcpiBst { // based on `ACPI_BATT_STAT_*` defines + #[inline] pub fn state(&self) -> State { match self.state { - 0x0000 => State::Full, - 0x0001 => State::Discharging, - 0x0002 => State::Charging, - 0x0004 => State::Charging, + ACPI_BATT_STAT_FULL => State::Full, + value if value & ACPI_BATT_STAT_DISCHARG != 0 => State::Discharging, + value if value & ACPI_BATT_STAT_CHARGING != 0 => State::Charging, + // This is probably a wrong state, because battery might be in critical state, + // but charging at the moment. Might worth to investigate if it is possible + // to implement `State::Critical` for all supported platforms. + // In fact, right now this match arm is unreachable in most cases, + // because previous arms will match first, but it would be harder to forget about it + value if value & ACPI_BATT_STAT_CRITICAL != 0 => State::Discharging, _ => State::Unknown } } + #[inline] pub fn rate(&self) -> u32 { self.rate } + #[inline] pub fn capacity(&self) -> u32 { self.cap } + #[inline] pub fn voltage(&self) -> u32 { self.volt } diff --git a/battery/src/platform/freebsd/device.rs b/battery/src/platform/freebsd/device.rs index 637adfd..13c4ee1 100644 --- a/battery/src/platform/freebsd/device.rs +++ b/battery/src/platform/freebsd/device.rs @@ -4,6 +4,7 @@ use std::fs::OpenOptions; use crate::{State, Technology}; use crate::platform::traits::BatteryDevice; +use crate::units::{Energy, Power, ElectricPotential, ThermodynamicTemperature}; use super::acpi; #[derive(Debug)] @@ -11,12 +12,12 @@ pub struct IoCtlDevice { state: State, technology: Technology, - energy_rate: u32, - voltage: u32, + energy_rate: Power, + voltage: ElectricPotential, - design_capacity: u32, - max_capacity: u32, - current_capacity: u32, + design_capacity: Energy, + max_capacity: Energy, + current_capacity: Energy, manufacturer: Option, model: Option, @@ -33,61 +34,72 @@ impl IoCtlDevice { let bif = inner.bif()?; let bst = inner.bst()?; - let voltage = bst.voltage(); - let mut device = IoCtlDevice { - state: bst.state(), - technology: bif.technology(), - energy_rate: bst.rate(), - voltage: voltage, - design_capacity: bif.design_capacity(), - max_capacity: bif.last_full_capacity(), - current_capacity: bst.capacity(), + 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() { + acpi::Units::MilliWatts => milliwatt!(bst.rate()), + acpi::Units::MilliAmperes => milliampere!(bst.rate()) * design_voltage, + }; + let 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() { + acpi::Units::MilliWatts => milliwatt_hour!(bif.design_capacity()), + acpi::Units::MilliAmperes => milliampere_hour!(bif.design_capacity()) * design_voltage, + }; + let 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, + }; + + 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, }; - if bif.units() == acpi::Units::mA { - device.energy_rate *= voltage; - device.current_capacity *= voltage; - device.design_capacity *= voltage; - device.max_capacity *= voltage; - } - Ok(device) } } impl BatteryDevice for IoCtlDevice { - fn energy(&self) -> u32 { + fn energy(&self) -> Energy { self.current_capacity } - fn energy_full(&self) -> u32 { + fn energy_full(&self) -> Energy { self.max_capacity } - fn energy_full_design(&self) -> u32 { + fn energy_full_design(&self) -> Energy { self.design_capacity } - fn energy_rate(&self) -> u32 { + fn energy_rate(&self) -> Power { self.energy_rate } - fn percentage(&self) -> f32 { - (100 * self.energy() / self.energy_full()) as f32 - } - fn state(&self) -> State { self.state } - fn voltage(&self) -> u32 { + fn voltage(&self) -> ElectricPotential { self.voltage } - fn temperature(&self) -> Option { + fn temperature(&self) -> Option { None } diff --git a/battery/src/platform/linux/device.rs b/battery/src/platform/linux/device.rs index 8a37797..26e3e9c 100644 --- a/battery/src/platform/linux/device.rs +++ b/battery/src/platform/linux/device.rs @@ -1,35 +1,32 @@ use std::io; +use std::f32; use std::convert::AsRef; use std::str::FromStr; use std::path::PathBuf; use std::default::Default; use lazy_init::Lazy; +use num_traits::identities::Zero; use crate::{State, Technology}; -use crate::platform::traits::BatteryDevice; +use crate::units::{Energy, Power, ElectricPotential, ElectricCharge, Ratio, ThermodynamicTemperature}; +use crate::units::power::{watt, microwatt}; +use crate::platform::traits::{BatteryDevice, Bound}; use super::sysfs; -const DESIGN_VOLTAGE_PROBES: [&str; 4] = [ - "voltage_max_design", - "voltage_min_design", - "voltage_present", - "voltage_now", -]; - #[derive(Default)] pub struct Inner { root: PathBuf, - design_voltage: Lazy, - energy: Lazy, - energy_full: Lazy, - energy_full_design: Lazy, - energy_rate: Lazy, - voltage: Lazy, // mV - percentage: Lazy, // 0.0 .. 100.0 + design_voltage: Lazy, + energy: Lazy, + energy_full: Lazy, + energy_full_design: Lazy, + energy_rate: Lazy, + voltage: Lazy, + state_of_charge: Lazy, - temperature: Lazy>, + temperature: Lazy>, cycle_count: Lazy>, state: Lazy, @@ -67,7 +64,7 @@ impl Inner { let _ = self.energy_full_design(); let _ = self.energy_rate(); let _ = self.voltage(); - let _ = self.percentage(); + let _ = self.state_of_charge(); let _ = self.temperature(); let _ = self.state(); let _ = self.technology(); @@ -77,168 +74,154 @@ impl Inner { let _ = self.cycle_count(); } - fn design_voltage(&self) -> u32 { + fn design_voltage(&self) -> ElectricPotential { *self.design_voltage.get_or_create(|| { - DESIGN_VOLTAGE_PROBES.iter() - .filter_map(|filename| { - match sysfs::get_u32(self.root.join(filename)) { - Ok(value) if value > 1 => Some(value / 1_000_000), - _ => None, - } - }) - .next() - // Same to `upower`, using 10V as an approximation - .unwrap_or(10) + ["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 charge_full(&self) -> u32 { - ["charge_full", "charge_full_design"].iter() // µAh - .filter_map(|filename| { - match sysfs::get_u32(self.root.join(filename)) { - Ok(value) => Some(value / 1_000), - _ => None, - } - }) + 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() - .unwrap_or(0) + } + + 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)) } } impl BatteryDevice for Inner { - fn capacity(&self) -> f32 { + fn state_of_health(&self) -> Ratio { let energy_full = self.energy_full(); - if energy_full > 0 { - let capacity = (energy_full as f32 / self.energy_full_design() as f32) * 100.0; - set_bounds(capacity) + if !energy_full.is_zero() { + (energy_full / self.energy_full_design()).into_bounded() } else { - 100.0 + percent!(100.0) } } - fn energy(&self) -> u32 { + fn energy(&self) -> Energy { *self.energy.get_or_create(|| { - let mut value = ["energy_now", "energy_avg"].iter() - .filter_map(|filename| { - match sysfs::get_u32(self.root.join(filename)) { - Ok(energy) => Some(energy / 1_000), - Err(_) => None, - } + self.energy_now() + .or_else(|| match self.charge_now() { + Some(charge) => Some(charge * self.design_voltage()), + None => None, }) - .next(); - - if value.is_none() { - value = ["charge_now", "charge_avg"].iter() - .filter_map(|filename| { - match sysfs::get_u32(self.root.join(filename)) { - Ok(charge) => Some(charge / 1_000 * self.design_voltage()), - Err(_) => None, - } - }) - .next(); - } - - match value { - None => self.energy_full() * self.percentage() as u32 / 100, - Some(energy) => energy, - } + .unwrap_or_else(|| self.energy_full() * self.state_of_charge()) }) } - fn energy_full(&self) -> u32 { + fn energy_full(&self) -> Energy { *self.energy_full.get_or_create(|| { - let res = match sysfs::get_u32(self.root.join("energy_full")) { - Ok(energy) => energy / 1_000, - Err(_) => match sysfs::get_u32(self.root.join("charge_full")) { - Ok(charge) => charge / 1_000 * self.design_voltage(), - Err(_) => 0, - } - }; - - if res == 0 { - self.energy_full_design() - } else { - res - } + 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()) }) - } - fn energy_full_design(&self) -> u32 { + fn energy_full_design(&self) -> Energy { *self.energy_full_design.get_or_create(|| { - match sysfs::get_u32(self.root.join("energy_full_design")) { - Ok(energy) => energy / 1_000, - Err(_) => match sysfs::get_u32(self.root.join("charge_full_design")) { - Ok(charge) => charge / 1_000 * self.design_voltage(), - Err(_) => 0, - } - } + 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)) }) } - fn energy_rate(&self) -> u32 { + fn energy_rate(&self) -> Power { *self.energy_rate.get_or_create(|| { - let mut value = match sysfs::get_u32(self.root.join("power_now")) { - Ok(power) if power > 10_000 => power / 1_000, - _ => { - match sysfs::get_u32(self.root.join("current_now")) { + 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 uA. + // 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 uW. + // current_now is power in µW. // Source: upower - let mut current = current_now / 1_000; - if self.charge_full() != 0 { - current *= self.design_voltage(); + if !self.charge_full().is_zero() { + // µA then + Some(microampere!(current_now) * self.design_voltage()) + } else { + // µW :| + Some(microwatt!(current_now)) } - current - }, - Err(_) => { - 0u32 }, + _ => None, } - } - }; - - // ACPI gives out the special 'Ones' 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 - // TODO: Uncomment and fix - // if value == 0xffff { - // value = 0.0; - // } - - // Sanity check, same as upower does, if power is greater than 100W - if value > 100_000 { - value = 0; - } - - // TODO: Calculate energy_rate manually, if hardware fails. - // if value < 0.01 { - // // Check upower `up_device_supply_calculate_rate` function - // } - - // Some batteries give out massive rate values when nearly empty - if value < 10 { - value = 0; - } + }) + // 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)) - value + // TODO: Calculate energy_rate manually, if hardware fails. + // if value < 0.01 { + // // Check upower `up_device_supply_calculate_rate` function + // } }) } - // 0.0..100.0 - fn percentage(&self) -> f32 { - *self.percentage.get_or_create(|| { - let capacity= match sysfs::get_u32(self.root.join("capacity")) { - Ok(capacity) => capacity, - _ if self.energy_full() > 0 => 100 * self.energy() / self.energy_full(), - Err(_) => 0, - }; - - set_bounds(capacity as f32) + 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), + } }) } @@ -250,27 +233,22 @@ impl BatteryDevice for Inner { }) } - // mV - fn voltage(&self) -> u32 { + fn voltage(&self) -> ElectricPotential { *self.voltage.get_or_create(|| { - ["voltage_now", "voltage_avg"].iter() - .filter_map(|filename| { - match sysfs::get_u32(self.root.join(filename)) { - Ok(voltage) if voltage > 1 => Some(voltage / 1_000), - _ => None, - } - }) + ["voltage_now", "voltage_avg"].iter() // µV + .filter_map(|filename| sysfs::voltage(self.root.join(filename))) .next() - .unwrap_or(0) // TODO: Check if it is really unreachable + .unwrap_or_else(|| microvolt!(0.0)) }) } - fn temperature(&self) -> Option { + 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(value), + Ok(value) => Some(celsius!(value)), Err(_) => None, } }) @@ -338,39 +316,39 @@ impl SysFsDevice { } impl BatteryDevice for SysFsDevice { - fn capacity(&self) -> f32 { - self.0.capacity() + fn state_of_health(&self) -> Ratio { + self.0.state_of_health() } - fn energy(&self) -> u32 { + fn energy(&self) -> Energy { self.0.energy() } - fn energy_full(&self) -> u32 { + fn energy_full(&self) -> Energy { self.0.energy_full() } - fn energy_full_design(&self) -> u32 { + fn energy_full_design(&self) -> Energy { self.0.energy_full_design() } - fn energy_rate(&self) -> u32 { + fn energy_rate(&self) -> Power { self.0.energy_rate() } - fn percentage(&self) -> f32 { - self.0.percentage() + fn state_of_charge(&self) -> Ratio { + self.0.state_of_charge() } fn state(&self) -> State { self.0.state() } - fn voltage(&self) -> u32 { + fn voltage(&self) -> ElectricPotential { self.0.voltage() } - fn temperature(&self) -> Option { + fn temperature(&self) -> Option { self.0.temperature() } @@ -394,15 +372,3 @@ impl BatteryDevice for SysFsDevice { self.0.cycle_count() } } - -#[inline] -fn set_bounds(value: f32) -> f32 { - if value < 0.0 { - return 0.0; - } - if value > 100.0 { - return 100.0; - } - - value -} diff --git a/battery/src/platform/linux/iterator.rs b/battery/src/platform/linux/iterator.rs index b973cb1..465cc80 100644 --- a/battery/src/platform/linux/iterator.rs +++ b/battery/src/platform/linux/iterator.rs @@ -6,6 +6,7 @@ use std::path::Path; use crate::Battery; use crate::platform::traits::BatteryIterator; use super::SysFsDevice; +use super::sysfs; #[derive(Debug)] pub struct SysFsIterator { @@ -36,8 +37,10 @@ impl iter::Iterator for SysFsIterator { Some(Err(_)) => continue, // Unable to access the sysfs somehow // TODO: trace!() Some(Ok(entry)) => { let path = entry.path(); - match fs::read_to_string(path.join("type")) { - Ok(ref content) if content == "Battery\n" => { + 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)); diff --git a/battery/src/platform/linux/sysfs.rs b/battery/src/platform/linux/sysfs.rs index 5b3f13d..ddae495 100644 --- a/battery/src/platform/linux/sysfs.rs +++ b/battery/src/platform/linux/sysfs.rs @@ -1,7 +1,87 @@ 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) { diff --git a/battery/src/platform/macos/device.rs b/battery/src/platform/macos/device.rs index 4b7f4d4..d896139 100644 --- a/battery/src/platform/macos/device.rs +++ b/battery/src/platform/macos/device.rs @@ -8,8 +8,10 @@ use std::str; use std::boxed::Box; use std::convert::AsRef; -use std::time::Duration; +use num_traits::identities::Zero; + +use crate::units::{ElectricPotential, ThermodynamicTemperature, Time, Power, Energy}; use crate::types::{State, Technology}; use crate::platform::traits::BatteryDevice; use super::traits::DataSource; @@ -29,56 +31,38 @@ impl IoKitDevice { } } -// Note about `mWh` values calculation, used in `energy`, `energy_full` and `energy_full_design` -// method, which caused https://github.com/svartalf/rust-battery/issues/8 bug -// -// Formula: mWh = mAh * V -// -// But `self.source.voltage()` returns `mV`, not the `V` units. impl BatteryDevice for IoKitDevice { - fn energy(&self) -> u32 { - let voltage = self.source.voltage() as f32 / 1_000.0; // V units - - (self.source.current_capacity() as f32 * voltage) as u32 + fn energy(&self) -> Energy { + self.source.current_capacity() * self.source.voltage() } - fn energy_full(&self) -> u32 { - let voltage = self.source.voltage() as f32 / 1_000.0; // V units - - (self.source.max_capacity() as f32 * voltage) as u32 + fn energy_full(&self) -> Energy { + self.source.max_capacity() * self.source.voltage() } - fn energy_full_design(&self) -> u32 { - let voltage = self.source.voltage() as f32 / 1_000.0; // V units - - (self.source.design_capacity() as f32 * voltage) as u32 - } - - fn energy_rate(&self) -> u32 { - let voltage = self.source.voltage() as f32 / 1_000.0; // V units - - (self.source.amperage().abs() as f32 * voltage) as u32 + fn energy_full_design(&self) -> Energy { + self.source.design_capacity() * self.source.voltage() } - fn percentage(&self) -> f32 { - 100.0 * ((self.energy() as f32) / (self.energy_full() as f32)) + fn energy_rate(&self) -> Power { + self.source.amperage() * self.source.voltage() } fn state(&self) -> State { match () { _ if !self.source.external_connected() => State::Discharging, _ if self.source.is_charging() => State::Charging, - _ if self.source.current_capacity() == 0 => State::Empty, + _ if self.source.current_capacity().is_zero() => State::Empty, _ if self.source.fully_charged() => State::Full, _ => State::Unknown, } } - fn voltage(&self) -> u32 { + fn voltage(&self) -> ElectricPotential { self.source.voltage() } - fn temperature(&self) -> Option { + fn temperature(&self) -> Option { self.source.temperature() } @@ -102,7 +86,7 @@ impl BatteryDevice for IoKitDevice { self.source.cycle_count() } - fn time_to_full(&self) -> Option { + fn time_to_full(&self) -> Option