| // Copyright 2019 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| use anyhow::Error; |
| use fidl::endpoints::Proxy; |
| use futures::lock::Mutex; |
| use futures::TryStreamExt; |
| use std::sync::{Arc, RwLock}; |
| use tracing::{debug, error}; |
| use { |
| fidl_fuchsia_hardware_powersource as hpower, fidl_fuchsia_power_battery as fpower, |
| fuchsia_async as fasync, fuchsia_zircon as zx, |
| }; |
| |
| #[derive(Debug, PartialEq)] |
| enum StatusUpdateResult { |
| Notify, |
| DoNotNotify, |
| } |
| |
| pub(crate) trait BatterySimulationStateObserver { |
| fn update_simulation(&self, new_state: bool); |
| fn update_simulated_battery_info(&self, battery_info: fpower::BatteryInfo); |
| } |
| |
| impl BatterySimulationStateObserver for BatteryManager { |
| fn update_simulation(&self, is_simulating: bool) { |
| let mut sim_state = self.simulation_state.write().unwrap(); |
| *sim_state = is_simulating; |
| drop(sim_state); |
| if !is_simulating { |
| self.update_watchers(); |
| } |
| } |
| fn update_simulated_battery_info(&self, battery_info: fpower::BatteryInfo) { |
| let mut simulated_battery_info = self.simulated_battery_info.write().unwrap(); |
| *simulated_battery_info = battery_info.clone(); |
| drop(simulated_battery_info); |
| self.update_watchers(); |
| } |
| } |
| |
| /// Core component for the battery manager system. |
| /// |
| /// BatteryManager maintains the current state info for the battery system |
| /// as well as the watchers that share this information with subscribed clients. |
| /// |
| /// simulation_state: true when the simulator is running |
| pub struct BatteryManager { |
| battery_info: RwLock<fpower::BatteryInfo>, |
| watchers: Arc<Mutex<Vec<fpower::BatteryInfoWatcherProxy>>>, |
| simulation_state: RwLock<bool>, |
| simulated_battery_info: RwLock<fpower::BatteryInfo>, |
| } |
| |
| #[inline] |
| fn get_current_time() -> i64 { |
| let t = fuchsia_runtime::utc_time(); |
| (t.into_nanos() / 1000) as i64 |
| } |
| |
| impl BatteryManager { |
| pub fn new() -> BatteryManager { |
| BatteryManager { |
| battery_info: RwLock::new(fpower::BatteryInfo { |
| status: Some(fpower::BatteryStatus::NotAvailable), |
| charge_status: Some(fpower::ChargeStatus::Unknown), |
| charge_source: Some(fpower::ChargeSource::Unknown), |
| level_percent: None, |
| level_status: Some(fpower::LevelStatus::Unknown), |
| health: Some(fpower::HealthStatus::Unknown), |
| time_remaining: Some(fpower::TimeRemaining::Indeterminate(0)), |
| timestamp: Some(get_current_time()), |
| ..Default::default() |
| }), |
| watchers: Arc::new(Mutex::new(Vec::new())), |
| simulation_state: RwLock::new(false), |
| simulated_battery_info: RwLock::new(fpower::BatteryInfo { |
| status: Some(fpower::BatteryStatus::NotAvailable), |
| charge_status: Some(fpower::ChargeStatus::Unknown), |
| charge_source: Some(fpower::ChargeSource::Unknown), |
| level_percent: None, |
| level_status: Some(fpower::LevelStatus::Unknown), |
| health: Some(fpower::HealthStatus::Unknown), |
| time_remaining: Some(fpower::TimeRemaining::Indeterminate(0)), |
| timestamp: Some(get_current_time()), |
| ..Default::default() |
| }), |
| } |
| } |
| |
| // Adds watcher |
| pub async fn add_watcher(&self, watcher: fpower::BatteryInfoWatcherProxy) { |
| let mut watchers = self.watchers.lock().await; |
| debug!("::manager:: adding watcher: {:?} [{:?}]", watcher, watchers.len()); |
| watchers.push(watcher) |
| } |
| |
| // Updates the status |
| pub fn update_status( |
| &self, |
| power_info: hpower::SourceInfo, |
| battery_info: Option<hpower::BatteryInfo>, |
| ) -> Result<(), anyhow::Error> { |
| debug!(?power_info, ?battery_info, "update_status",); |
| |
| match self.update_battery_info(power_info, battery_info) { |
| Ok(StatusUpdateResult::Notify) => { |
| debug!("::manager:: update status changed - NOTIFY"); |
| let info = self.get_battery_info_copy(); |
| let watchers = self.watchers.clone(); |
| debug!("::manager:: run watchers {:?} with info {:?}", &watchers, &info); |
| BatteryManager::run_watchers(watchers.clone(), info.clone()); |
| } |
| Ok(StatusUpdateResult::DoNotNotify) => { |
| debug!("::manager:: update status unchanged - skipping NOTIFY"); |
| } |
| Err(e) => return Err(e), |
| } |
| |
| Ok(()) |
| } |
| |
| pub fn run_watchers( |
| watchers: Arc<Mutex<Vec<fpower::BatteryInfoWatcherProxy>>>, |
| info: fpower::BatteryInfo, |
| ) { |
| debug!("::manager:: run watchers..."); |
| fasync::Task::spawn(async move { |
| let watchers = { |
| let mut watchers = watchers.lock().await; |
| watchers.retain(|w| !w.is_closed()); |
| watchers.clone() |
| }; |
| debug!("::manager:: run watchers [{:?}]", &watchers.len()); |
| for w in &watchers { |
| if let Err(e) = w.on_change_battery_info(&info.clone().into()).await { |
| error!("failed to send battery info to watcher {:?}", e); |
| } |
| } |
| }) |
| .detach() |
| } |
| |
| fn update_battery_info( |
| &self, |
| power_info: hpower::SourceInfo, |
| battery_info: Option<hpower::BatteryInfo>, |
| ) -> Result<StatusUpdateResult, anyhow::Error> { |
| let now = get_current_time(); |
| let old_battery_info = self.get_battery_info_copy(); |
| debug!("::battery_manager:: old battery info: {:?}", &old_battery_info); |
| |
| let mut new_battery_info = self.battery_info.write().unwrap(); |
| |
| // info from AC power source |
| if power_info.type_ == hpower::PowerType::Ac { |
| // charge status/source |
| if power_info.state & hpower::POWER_STATE_CHARGING != 0 { |
| new_battery_info.charge_status = Some(fpower::ChargeStatus::Charging); |
| if power_info.type_ == hpower::PowerType::Ac { |
| new_battery_info.charge_source = Some(fpower::ChargeSource::AcAdapter); |
| } else { |
| //TODO: how to detect USB/Wireless |
| new_battery_info.charge_source = Some(fpower::ChargeSource::Unknown); |
| } |
| } |
| } |
| |
| // info from battery power source will include battery info |
| if let Some(bi) = battery_info { |
| assert!( |
| power_info.type_ == hpower::PowerType::Battery, |
| "updating battery info with non-battery power source" |
| ); |
| |
| debug!(?power_info, ?bi, "::battery_manager:: update with hpower info"); |
| |
| // check battery online and update accordingly |
| if power_info.state & hpower::POWER_STATE_ONLINE != 0 { |
| new_battery_info.status = Some(fpower::BatteryStatus::Ok); |
| |
| // charge status/source |
| if power_info.state & hpower::POWER_STATE_CHARGING != 0 { |
| new_battery_info.charge_status = Some(fpower::ChargeStatus::Charging); |
| } |
| |
| if bi.remaining_capacity == bi.last_full_capacity { |
| new_battery_info.charge_status = Some(fpower::ChargeStatus::Full); |
| } |
| |
| // level percent |
| new_battery_info.level_percent = Some( |
| (bi.remaining_capacity.saturating_mul(100)) as f32 |
| / bi.last_full_capacity as f32, |
| ); |
| |
| new_battery_info.present_voltage_mv = Some(bi.present_voltage); |
| |
| let battery_spec = fpower::BatterySpec { |
| max_charging_current_ua: bi.battery_spec.max_charging_current_ua, |
| max_charnging_voltage_uv: bi.battery_spec.max_charnging_voltage_uv, |
| design_capacity_uah: bi.battery_spec.design_capacity_uah, |
| ..Default::default() |
| }; |
| new_battery_info.battery_spec = Some(battery_spec); |
| |
| match bi.unit { |
| hpower::BatteryUnit::Ma => { |
| new_battery_info.remaining_capacity_uah = |
| Some(bi.remaining_capacity * 1000); |
| } |
| hpower::BatteryUnit::Mw => { |
| let uah = |
| bi.remaining_capacity as f64 * 1000000.0 / bi.present_voltage as f64; |
| new_battery_info.remaining_capacity_uah = Some(uah as u32); |
| } |
| } |
| |
| // level_status |
| if power_info.state & hpower::POWER_STATE_CRITICAL != 0 { |
| new_battery_info.level_status = Some(fpower::LevelStatus::Critical); |
| } else if bi.remaining_capacity <= bi.capacity_low { |
| new_battery_info.level_status = Some(fpower::LevelStatus::Low); |
| } else if bi.remaining_capacity <= bi.capacity_warning { |
| new_battery_info.level_status = Some(fpower::LevelStatus::Warning); |
| } else { |
| new_battery_info.level_status = Some(fpower::LevelStatus::Ok); |
| } |
| |
| // time remaining, provided by hardware as hours |
| let nanos_in_one_hour = zx::Duration::from_hours(1); |
| |
| if bi.present_rate < 0 { |
| // discharging |
| let remaining_hours = |
| bi.remaining_capacity as f32 / (bi.present_rate.saturating_mul(-1)) as f32; |
| new_battery_info.time_remaining = Some(fpower::TimeRemaining::BatteryLife( |
| (remaining_hours as i64).saturating_mul(nanos_in_one_hour.into_nanos()), |
| )); |
| } else { |
| // charging |
| let remaining_hours = (bi.last_full_capacity as f32 |
| - bi.remaining_capacity as f32) |
| / (bi.present_rate) as f32; |
| new_battery_info.time_remaining = Some(fpower::TimeRemaining::FullCharge( |
| (remaining_hours as i64).saturating_mul(nanos_in_one_hour.into_nanos()), |
| )); |
| } |
| |
| // TODO: determine actual battery health |
| new_battery_info.health = Some(fpower::HealthStatus::Unknown); |
| } else { |
| // battery offline/not present |
| new_battery_info.status = Some(fpower::BatteryStatus::NotPresent); |
| } |
| } |
| |
| if power_info.state & hpower::POWER_STATE_DISCHARGING != 0 { |
| new_battery_info.charge_status = Some(fpower::ChargeStatus::Discharging); |
| } |
| |
| if (power_info.state & hpower::POWER_STATE_DISCHARGING == 0) |
| && (power_info.state & hpower::POWER_STATE_CHARGING == 0) |
| { |
| new_battery_info.charge_status = Some(fpower::ChargeStatus::NotCharging); |
| } |
| |
| if new_battery_info.charge_status != Some(fpower::ChargeStatus::Charging) |
| && new_battery_info.charge_status != Some(fpower::ChargeStatus::Full) |
| { |
| new_battery_info.charge_source = Some(fpower::ChargeSource::None); |
| } |
| |
| if *self.simulation_state.read().unwrap() { |
| return Ok(StatusUpdateResult::DoNotNotify); |
| } |
| |
| match old_battery_info == (*new_battery_info) { |
| true => Ok(StatusUpdateResult::DoNotNotify), |
| false => { |
| new_battery_info.timestamp = Some(now); |
| Ok(StatusUpdateResult::Notify) |
| } |
| } |
| } |
| |
| pub fn get_battery_info_copy(&self) -> fpower::BatteryInfo { |
| if *self.simulation_state.read().unwrap() { |
| let info_lock = self.simulated_battery_info.read().unwrap(); |
| (*info_lock).clone() |
| } else { |
| let info_lock = self.battery_info.read().unwrap(); |
| (*info_lock).clone() |
| } |
| } |
| |
| fn update_watchers(&self) { |
| let info = self.get_battery_info_copy(); |
| let watchers = self.watchers.clone(); |
| BatteryManager::run_watchers(watchers.clone(), info.clone()); |
| } |
| |
| pub fn is_simulating(&self) -> bool { |
| *self.simulation_state.read().unwrap() |
| } |
| |
| pub(crate) async fn serve( |
| &self, |
| stream: fpower::BatteryManagerRequestStream, |
| ) -> Result<(), Error> { |
| stream |
| .try_for_each_concurrent(None, move |request| { |
| async move { |
| match request { |
| fpower::BatteryManagerRequest::GetBatteryInfo { responder, .. } => { |
| let info = self.get_battery_info_copy(); |
| debug!( |
| ?info, |
| "::battery_manager_request:: handle GetBatteryInfo request" |
| ); |
| responder.send(&info)?; |
| } |
| fpower::BatteryManagerRequest::Watch { watcher, .. } => { |
| let watcher = watcher.into_proxy()?; |
| debug!("::battery_manager_request:: handle Watch request"); |
| self.add_watcher(watcher.clone()).await; |
| |
| // make sure watcher has current battery info |
| let info = self.get_battery_info_copy(); |
| |
| debug!(?info, "::battery_manager_request:: callback on new watcher"); |
| watcher.on_change_battery_info(&info).await?; |
| } |
| } |
| Ok(()) |
| } |
| }) |
| .await?; |
| |
| Ok(()) |
| } |
| } |
| |
| #[cfg(test)] |
| mod tests { |
| use super::*; |
| use fidl::endpoints::create_request_stream; |
| use futures::future::*; |
| |
| macro_rules! cmp_fields { |
| ($got:ident, $want:ident, [$($field:ident,)*], $test_no:expr) => { $( |
| assert_eq!($got.$field, $want.$field, "test no: {}", $test_no); |
| )* } |
| } |
| |
| fn check_status( |
| got: &fpower::BatteryInfo, |
| want: &fpower::BatteryInfo, |
| updated: bool, |
| test_no: u32, |
| ) { |
| if updated { |
| cmp_fields!( |
| want, |
| got, |
| [ |
| status, |
| charge_status, |
| charge_source, |
| level_percent, |
| present_voltage_mv, |
| remaining_capacity_uah, |
| level_status, |
| health, |
| time_remaining, |
| ], |
| test_no |
| ); |
| } else { |
| assert_eq!(want, got, "test: {}", test_no); |
| } |
| } |
| |
| fn get_default_battery_info() -> hpower::BatteryInfo { |
| let battery_info = hpower::BatteryInfo { |
| unit: hpower::BatteryUnit::Ma, |
| design_capacity: 5000, |
| last_full_capacity: 5000, |
| design_voltage: 7000, |
| capacity_warning: 700, |
| capacity_low: 500, |
| capacity_granularity_low_warning: 1, |
| capacity_granularity_warning_full: 1, |
| present_rate: -500, |
| remaining_capacity: 3000, |
| present_voltage: 7000, |
| battery_spec: hpower::BatterySpec { |
| max_charging_current_ua: Some(500000), |
| max_charnging_voltage_uv: Some(5000000), |
| design_capacity_uah: Some(380000), |
| ..Default::default() |
| }, |
| }; |
| return battery_info; |
| } |
| |
| #[fuchsia_async::run_until_stalled(test)] |
| async fn test_run_watcher() { |
| let battery_manager = BatteryManager::new(); |
| let mut battery_info: fpower::BatteryInfo = battery_manager.get_battery_info_copy(); |
| battery_info.level_percent = Some(50.0); |
| |
| let (watcher_client_end, mut stream) = |
| create_request_stream::<fpower::BatteryInfoWatcherMarker>().unwrap(); |
| let watcher = watcher_client_end.into_proxy().unwrap(); |
| |
| let watchers = Arc::new(Mutex::new(vec![watcher])); |
| |
| let serve_fut = async move { |
| let request = stream.try_next().await.unwrap(); |
| if let Some(fpower::BatteryInfoWatcherRequest::OnChangeBatteryInfo { |
| info, |
| responder, |
| }) = request |
| { |
| let level = info.level_percent.unwrap().round() as u8; |
| assert_eq!(level, 50); |
| responder.send().unwrap(); |
| } else { |
| panic!("Unexpected message received"); |
| }; |
| }; |
| let request_fut = async move { |
| BatteryManager::run_watchers(watchers, battery_info); |
| }; |
| |
| join(serve_fut, request_fut).await; |
| } |
| |
| #[fuchsia::test] |
| async fn test_run_watchers_channel_closed() { |
| let battery_manager = BatteryManager::new(); |
| let mut battery_info: fpower::BatteryInfo = battery_manager.get_battery_info_copy(); |
| battery_info.level_percent = Some(50.0); |
| |
| let (watcher1_client_end, mut stream1) = |
| create_request_stream::<fpower::BatteryInfoWatcherMarker>().unwrap(); |
| let watcher1 = watcher1_client_end.into_proxy().unwrap(); |
| |
| let (watcher2_client_end, mut stream2) = |
| create_request_stream::<fpower::BatteryInfoWatcherMarker>().unwrap(); |
| let watcher2 = watcher2_client_end.into_proxy().unwrap(); |
| |
| let watchers = Arc::new(Mutex::new(vec![watcher1, watcher2])); |
| |
| let serve1_fut = async move { |
| // first request should match first change notification sent |
| // at 50% |
| let request = stream1.try_next().await.unwrap(); |
| if let Some(fpower::BatteryInfoWatcherRequest::OnChangeBatteryInfo { |
| info, |
| responder, |
| }) = request |
| { |
| let level = info.level_percent.unwrap().round() as u8; |
| assert_eq!(level, 50); |
| responder.send().unwrap(); |
| } else { |
| panic!("Unexpected message received"); |
| }; |
| // second should match subsequent notification at 60% |
| let request = stream1.try_next().await.unwrap(); |
| if let Some(fpower::BatteryInfoWatcherRequest::OnChangeBatteryInfo { |
| info, |
| responder, |
| }) = request |
| { |
| let level = info.level_percent.unwrap().round() as u8; |
| assert_eq!(level, 60); |
| responder.send().unwrap(); |
| } else { |
| panic!("Unexpected message received"); |
| }; |
| }; |
| |
| let serve2_fut = async move { |
| // first request should match first change notification sent |
| // at 50% |
| let request = stream2.try_next().await.unwrap(); |
| if let Some(fpower::BatteryInfoWatcherRequest::OnChangeBatteryInfo { |
| info, |
| responder, |
| }) = request |
| { |
| let level = info.level_percent.unwrap().round() as u8; |
| assert_eq!(level, 50); |
| // but then we drop the channel... |
| std::mem::drop(responder); |
| } else { |
| panic!("Unexpected message received"); |
| }; |
| // should not get the second... |
| if let Some(_) = stream2.try_next().await.unwrap() { |
| panic!("Unexpected message, channel should be closed"); |
| } |
| }; |
| |
| let request_fut = async move { |
| BatteryManager::run_watchers(watchers.clone(), battery_info.clone()); |
| battery_info.level_percent = Some(60.0); |
| BatteryManager::run_watchers(watchers, battery_info); |
| }; |
| |
| join3(serve1_fut, serve2_fut, request_fut).await; |
| } |
| |
| #[fuchsia::test] |
| async fn update_battery_info() { |
| let nanos_in_one_hour = zx::Duration::from_hours(1); |
| |
| let battery_manager = BatteryManager::new(); |
| let mut power_info = hpower::SourceInfo { type_: hpower::PowerType::Ac, state: 1 }; |
| |
| // state: ac powered, with no battery info to update |
| let mut want = battery_manager.get_battery_info_copy(); |
| want.status = Some(fpower::BatteryStatus::NotAvailable); |
| want.charge_source = Some(fpower::ChargeSource::None); |
| want.charge_status = Some(fpower::ChargeStatus::NotCharging); |
| want.level_percent = None; |
| let _ = battery_manager.update_battery_info(power_info.clone(), None); |
| check_status(&battery_manager.get_battery_info_copy(), &want, true, 1); |
| |
| // state: unchanged |
| let want = battery_manager.get_battery_info_copy(); |
| let result = battery_manager.update_battery_info(power_info.clone(), None); |
| assert_eq!(result.unwrap(), StatusUpdateResult::DoNotNotify); |
| check_status(&battery_manager.get_battery_info_copy(), &want, false, 2); |
| |
| // state: battery powered, discharging |
| power_info.type_ = hpower::PowerType::Battery; |
| power_info.state = 0x3; // ONLINE | DISCHARGING |
| let mut want = battery_manager.get_battery_info_copy(); |
| want.status = Some(fpower::BatteryStatus::Ok); |
| want.charge_status = Some(fpower::ChargeStatus::Discharging); |
| want.charge_source = Some(fpower::ChargeSource::None); |
| want.level_status = Some(fpower::LevelStatus::Ok); |
| want.level_percent = Some((3000.0 * 100.0) / 5000.0); |
| want.present_voltage_mv = Some(7000); |
| want.remaining_capacity_uah = Some(3000 * 1000); |
| want.time_remaining = |
| Some(fpower::TimeRemaining::BatteryLife(6 * nanos_in_one_hour.into_nanos())); |
| let mut battery_info = get_default_battery_info(); |
| let result = |
| battery_manager.update_battery_info(power_info.clone(), Some(battery_info.clone())); |
| assert_eq!(result.unwrap(), StatusUpdateResult::Notify); |
| check_status(&battery_manager.get_battery_info_copy(), &want, true, 3); |
| |
| // state: battery powered, discharging/warning |
| power_info.state = 0x3; // ONLINE | DISCHARGING |
| battery_info.remaining_capacity = 700; |
| let mut want = battery_manager.get_battery_info_copy(); |
| want.status = Some(fpower::BatteryStatus::Ok); |
| want.charge_status = Some(fpower::ChargeStatus::Discharging); |
| want.charge_source = Some(fpower::ChargeSource::None); |
| want.level_status = Some(fpower::LevelStatus::Warning); |
| want.level_percent = Some((700.0 * 100.0) / 5000.0); |
| want.remaining_capacity_uah = Some(700 * 1000); |
| want.time_remaining = |
| Some(fpower::TimeRemaining::BatteryLife(nanos_in_one_hour.into_nanos())); |
| let _ = battery_manager.update_battery_info(power_info.clone(), Some(battery_info.clone())); |
| check_status(&battery_manager.get_battery_info_copy(), &want, true, 4); |
| |
| // state: battery powered, discharging/low |
| power_info.state = 0x3; // ONLINE | DISCHARGING |
| battery_info.remaining_capacity = 500; |
| let mut want = battery_manager.get_battery_info_copy(); |
| want.status = Some(fpower::BatteryStatus::Ok); |
| want.charge_status = Some(fpower::ChargeStatus::Discharging); |
| want.charge_source = Some(fpower::ChargeSource::None); |
| want.level_status = Some(fpower::LevelStatus::Low); |
| want.level_percent = Some((500.0 * 100.0) / 5000.0); |
| want.remaining_capacity_uah = Some(500 * 1000); |
| want.time_remaining = |
| Some(fpower::TimeRemaining::BatteryLife(nanos_in_one_hour.into_nanos())); |
| let _ = battery_manager.update_battery_info(power_info.clone(), Some(battery_info.clone())); |
| check_status(&battery_manager.get_battery_info_copy(), &want, true, 5); |
| |
| // state: battery powered, discharging/critical |
| power_info.state = 0xB; // ONLINE | DISCHARGING | CRITICAL |
| let mut want = battery_manager.get_battery_info_copy(); |
| want.status = Some(fpower::BatteryStatus::Ok); |
| want.charge_status = Some(fpower::ChargeStatus::Discharging); |
| want.charge_source = Some(fpower::ChargeSource::None); |
| want.level_status = Some(fpower::LevelStatus::Critical); |
| let _ = battery_manager.update_battery_info(power_info.clone(), Some(battery_info.clone())); |
| check_status(&battery_manager.get_battery_info_copy(), &want, true, 6); |
| |
| // state: battery charging via AC |
| power_info.type_ = hpower::PowerType::Ac; |
| power_info.state = 0x5; // ONLINE | CHARGING |
| let _ = battery_manager.update_battery_info(power_info.clone(), None); |
| power_info.type_ = hpower::PowerType::Battery; |
| power_info.state = 0x5; // ONLINE | CHARGING |
| battery_info.present_rate = 1000; |
| battery_info.remaining_capacity = 3000; |
| let mut want = battery_manager.get_battery_info_copy(); |
| want.status = Some(fpower::BatteryStatus::Ok); |
| want.charge_status = Some(fpower::ChargeStatus::Charging); |
| want.charge_source = Some(fpower::ChargeSource::AcAdapter); |
| want.level_status = Some(fpower::LevelStatus::Ok); |
| want.level_percent = Some((3000.0 * 100.0) / 5000.0); |
| want.remaining_capacity_uah = Some(3000 * 1000); |
| want.time_remaining = |
| Some(fpower::TimeRemaining::FullCharge(2 * nanos_in_one_hour.into_nanos())); |
| let _ = battery_manager.update_battery_info(power_info.clone(), Some(battery_info.clone())); |
| check_status(&battery_manager.get_battery_info_copy(), &want, true, 7); |
| |
| // state: battery charging via AC/level critical |
| power_info.type_ = hpower::PowerType::Ac; |
| power_info.state = 0xD; // ONLINE | CHARGING | CRITICAL |
| let _ = battery_manager.update_battery_info(power_info.clone(), None); |
| power_info.type_ = hpower::PowerType::Battery; |
| power_info.state = 0xD; // ONLINE | CHARGING | CRITICAL |
| let mut want = battery_manager.get_battery_info_copy(); |
| want.status = Some(fpower::BatteryStatus::Ok); |
| want.charge_status = Some(fpower::ChargeStatus::Charging); |
| want.charge_source = Some(fpower::ChargeSource::AcAdapter); |
| want.level_status = Some(fpower::LevelStatus::Critical); |
| let _ = battery_manager.update_battery_info(power_info.clone(), Some(battery_info.clone())); |
| check_status(&battery_manager.get_battery_info_copy(), &want, true, 8); |
| |
| // state: battery charging via AC/level full |
| power_info.state = 0x5; // ONLINE | CHARGING |
| battery_info.remaining_capacity = 5000; |
| let mut want = battery_manager.get_battery_info_copy(); |
| want.status = Some(fpower::BatteryStatus::Ok); |
| want.charge_status = Some(fpower::ChargeStatus::Full); |
| want.charge_source = Some(fpower::ChargeSource::AcAdapter); |
| want.level_status = Some(fpower::LevelStatus::Ok); |
| want.level_percent = Some(100.0); |
| want.remaining_capacity_uah = Some(5000 * 1000); |
| want.time_remaining = Some(fpower::TimeRemaining::FullCharge(0)); |
| let _ = battery_manager.update_battery_info(power_info.clone(), Some(battery_info.clone())); |
| check_status(&battery_manager.get_battery_info_copy(), &want, true, 9); |
| |
| // state: battery charging via AC/level full, unit set to mW |
| power_info.state = 0x5; // ONLINE | CHARGING |
| battery_info.remaining_capacity = 5000; |
| battery_info.unit = hpower::BatteryUnit::Mw; |
| let mut want = battery_manager.get_battery_info_copy(); |
| want.status = Some(fpower::BatteryStatus::Ok); |
| want.charge_status = Some(fpower::ChargeStatus::Full); |
| want.charge_source = Some(fpower::ChargeSource::AcAdapter); |
| want.level_status = Some(fpower::LevelStatus::Ok); |
| want.level_percent = Some(100.0); |
| want.remaining_capacity_uah = Some(5000 * 1000 / 7); |
| want.time_remaining = Some(fpower::TimeRemaining::FullCharge(0)); |
| let _ = battery_manager.update_battery_info(power_info.clone(), Some(battery_info.clone())); |
| check_status(&battery_manager.get_battery_info_copy(), &want, true, 10); |
| |
| // state: battery charging via AC/extreme values (check overflow) |
| power_info.state = 0x5; // ONLINE | CHARGING |
| battery_info.last_full_capacity = u32::max_value(); |
| battery_info.remaining_capacity = u32::min_value(); |
| battery_info.present_rate = 1; |
| let mut want = battery_manager.get_battery_info_copy(); |
| want.status = Some(fpower::BatteryStatus::Ok); |
| want.charge_status = Some(fpower::ChargeStatus::Charging); |
| want.charge_source = Some(fpower::ChargeSource::AcAdapter); |
| want.level_status = Some(fpower::LevelStatus::Low); |
| want.level_percent = Some(0.0); |
| want.remaining_capacity_uah = Some(0); |
| want.time_remaining = Some(fpower::TimeRemaining::FullCharge(i64::max_value())); |
| let _ = battery_manager.update_battery_info(power_info.clone(), Some(battery_info.clone())); |
| check_status(&battery_manager.get_battery_info_copy(), &want, true, 11); |
| } |
| } |