| // Copyright 2018 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. |
| |
| #![feature(async_await, await_macro, futures_api)] |
| #![deny(warnings)] |
| |
| use failure::{Error, ResultExt}; |
| use fidl::endpoints::{RequestStream, ServiceMarker}; |
| use fidl_fuchsia_power::{BatteryStatus, PowerManagerMarker, PowerManagerRequest, |
| PowerManagerRequestStream, PowerManagerWatcherProxy, |
| Status as power_status}; |
| use fuchsia_app::server::ServicesServer; |
| use fuchsia_async as fasync; |
| use fuchsia_syslog::{self as syslog, fx_log_err, fx_log_info, fx_vlog}; |
| use fuchsia_vfs_watcher as vfs_watcher; |
| use fuchsia_zircon as zx; |
| use futures::prelude::*; |
| use parking_lot::Mutex; |
| use std::fs::File; |
| use std::path::PathBuf; |
| use std::sync::Arc; |
| |
| mod power; |
| |
| static POWER_DEVICE: &str = "/dev/class/power"; |
| |
| // Time to sleep between status update in seconds. |
| static SLEEP_TIME: i64 = 180; |
| |
| struct BatteryStatusHelper { |
| battery_status: BatteryStatus, |
| watchers: Vec<PowerManagerWatcherProxy>, |
| } |
| |
| #[inline] |
| fn get_current_time() -> i64 { |
| let t = zx::Time::get(zx::ClockId::UTC); |
| (t.nanos() / 1000) as i64 |
| } |
| |
| #[derive(Debug)] |
| enum WatchSuccess { |
| Completed, |
| BatteryAlreadyFound, |
| AdapterAlreadyFound, |
| } |
| |
| impl BatteryStatusHelper { |
| pub fn new() -> BatteryStatusHelper { |
| BatteryStatusHelper { |
| battery_status: BatteryStatus { |
| status: power_status::NotAvailable, |
| battery_present: false, |
| charging: false, |
| discharging: false, |
| critical: false, |
| power_adapter_online: false, |
| timestamp: get_current_time(), |
| level: 0.0, |
| remaining_battery_life: 0.0, |
| }, |
| watchers: Vec::new(), |
| } |
| } |
| |
| // Adds and calls watcher |
| fn add_watcher(&mut self, watcher: PowerManagerWatcherProxy) { |
| match watcher.on_change_battery_status(&mut self.battery_status) { |
| Ok(_) => self.watchers.push(watcher), |
| Err(e) => fx_log_err!("did not add watcher: {:?}", e), |
| } |
| } |
| |
| fn run_watchers(&mut self) { |
| let mut bs = &mut self.battery_status; |
| self.watchers.retain(|w| { |
| if let Err(e) = w.on_change_battery_status(&mut bs) { |
| match e { |
| fidl::Error::ClientRead(zx::Status::PEER_CLOSED) |
| | fidl::Error::ClientWrite(zx::Status::PEER_CLOSED) => return false, |
| e => { |
| fx_log_err!("calling watcher: {:?}", e); |
| return true; |
| } |
| } |
| } |
| return true; |
| }); |
| } |
| |
| fn update_battery_status( |
| &mut self, power_info: power::ioctl_power_get_info_t, |
| battery_info: Option<power::ioctl_power_get_battery_info_t>, |
| ) { |
| let now = get_current_time(); |
| let old_battery_status = self.get_battery_status_copy(); |
| if let Some(bi) = battery_info { |
| self.battery_status.battery_present = power_info.state & power::POWER_STATE_ONLINE != 0; |
| self.battery_status.charging = power_info.state & power::POWER_STATE_CHARGING != 0; |
| self.battery_status.discharging = |
| power_info.state & power::POWER_STATE_DISCHARGING != 0; |
| self.battery_status.critical = power_info.state & power::POWER_STATE_CRITICAL != 0; |
| if self.battery_status.battery_present { |
| self.battery_status.level = |
| (bi.remaining_capacity * 100) as f32 / bi.last_full_capacity as f32; |
| if bi.present_rate < 0 { |
| self.battery_status.remaining_battery_life = |
| bi.remaining_capacity as f32 / (bi.present_rate * -1) as f32; |
| } else { |
| self.battery_status.remaining_battery_life = -1.0; |
| } |
| } |
| } else { |
| self.battery_status.power_adapter_online = power_info.state == 0; |
| } |
| |
| self.battery_status.status = power_status::Ok; |
| fx_vlog!(1, "{:?}", self.battery_status); |
| if old_battery_status != self.battery_status { |
| self.battery_status.timestamp = now; |
| self.run_watchers(); |
| } |
| } |
| |
| // Updates the status |
| fn update_status(&mut self, file: &File) -> Result<(), failure::Error> { |
| let power_info = power::get_power_info(file).context("getting power_info")?; |
| let mut battery_info: Option<power::ioctl_power_get_battery_info_t> = None; |
| if power_info.power_type == power::POWER_TYPE_BATTERY { |
| battery_info = Some(power::get_battery_info(file).context("getting battery_info")?); |
| } |
| self.update_battery_status(power_info, battery_info); |
| Ok(()) |
| } |
| |
| fn get_battery_status_copy(&self) -> BatteryStatus { |
| return BatteryStatus { |
| ..self.battery_status |
| }; |
| } |
| } |
| |
| struct PowerManagerServer { |
| battery_status_helper: Arc<Mutex<BatteryStatusHelper>>, |
| } |
| |
| fn process_watch_event( |
| filepath: &PathBuf, bsh: Arc<Mutex<BatteryStatusHelper>>, battery_device_found: &mut bool, |
| adapter_device_found: &mut bool, |
| ) -> Result<WatchSuccess, failure::Error> { |
| let file = File::open(&filepath)?; |
| let powerbuffer = power::get_power_info(&file).context("getting power_info")?; |
| |
| if powerbuffer.power_type == power::POWER_TYPE_BATTERY && *battery_device_found { |
| return Ok(WatchSuccess::BatteryAlreadyFound); |
| } else if powerbuffer.power_type == power::POWER_TYPE_AC && *adapter_device_found { |
| return Ok(WatchSuccess::AdapterAlreadyFound); |
| } |
| let bsh2 = bsh.clone(); |
| power::add_listener(&file, move |file: &File| { |
| let mut bsh2 = bsh2.lock(); |
| if let Err(e) = bsh2.update_status(&file) { |
| fx_log_err!("{}", e); |
| } |
| }).context("adding listener")?; |
| { |
| let mut bsh = bsh.lock(); |
| bsh.update_status(&file).context("adding watch events")?; |
| } |
| |
| if powerbuffer.power_type == power::POWER_TYPE_BATTERY { |
| *battery_device_found = true; |
| let bsh = bsh.clone(); |
| let mut timer = fasync::Interval::new(zx::Duration::from_seconds(SLEEP_TIME)); |
| fasync::spawn(async move { |
| while let Some(()) = await!(timer.next()) { |
| let mut bsh = bsh.lock(); |
| if let Err(e) = bsh.update_status(&file) { |
| fx_log_err!("{}", e); |
| } |
| } |
| }); |
| } else { |
| *adapter_device_found = true; |
| } |
| Ok(WatchSuccess::Completed) |
| } |
| |
| async fn watch_power_device(bsh: Arc<Mutex<BatteryStatusHelper>>) -> Result<(), Error> { |
| let file = File::open(POWER_DEVICE).context("cannot find power device")?; |
| let mut watcher = vfs_watcher::Watcher::new(&file).context("error watching power device")?; |
| let mut adapter_device_found = false; |
| let mut battery_device_found = false; |
| while let Some(msg) = await!(watcher.try_next())? { |
| if battery_device_found && adapter_device_found { |
| continue; |
| } |
| let mut filepath = PathBuf::from(POWER_DEVICE); |
| filepath.push(msg.filename); |
| match process_watch_event( |
| &filepath, |
| bsh.clone(), |
| &mut battery_device_found, |
| &mut adapter_device_found, |
| ) { |
| Ok(WatchSuccess::Completed) => {} |
| Ok(early_return) => { |
| let device_type = match early_return { |
| WatchSuccess::Completed => unreachable!(), |
| WatchSuccess::BatteryAlreadyFound => "battery", |
| WatchSuccess::AdapterAlreadyFound => "adapter", |
| }; |
| fx_log_info!( |
| "Skip '{:?}' as {} device already found", |
| filepath, |
| device_type |
| ); |
| } |
| Err(err) => { |
| fx_log_err!( |
| "error for file while adding watch event '{:?}': {}", |
| filepath, |
| err |
| ); |
| } |
| } |
| } |
| Ok(()) |
| } |
| |
| fn spawn_power_manager(pm: PowerManagerServer, chan: fasync::Channel) { |
| fasync::spawn(async move { |
| let mut stream = PowerManagerRequestStream::from_channel(chan); |
| while let Some(req) = await!(stream.try_next())? { |
| match req { |
| PowerManagerRequest::GetBatteryStatus { responder, .. } => { |
| fx_log_info!("get_battery_status called"); |
| let mut bsh = pm.battery_status_helper.lock(); |
| if let Err(e) = responder.send(&mut bsh.battery_status) { |
| fx_log_err!("sending battery status: {:?}", e); |
| } |
| } |
| PowerManagerRequest::Watch { watcher, .. } => { |
| fx_log_info!("watch called"); |
| match watcher.into_proxy() { |
| Err(e) => { |
| fx_log_err!("getting watcher proxy: {:?}", e); |
| } |
| Ok(w) => { |
| let mut bsh = pm.battery_status_helper.lock(); |
| bsh.add_watcher(w); |
| } |
| } |
| } |
| } |
| } |
| Ok(()) |
| }.unwrap_or_else(|e: failure::Error| fx_log_err!("{:?}", e))); |
| } |
| |
| fn main() { |
| syslog::init_with_tags(&["power_manager"]).expect("Can't init logger"); |
| fx_log_info!("start"); |
| if let Err(e) = main_pm() { |
| fx_log_err!("{:?}", e); |
| } |
| fx_log_info!("stop"); |
| } |
| |
| fn main_pm() -> Result<(), Error> { |
| let mut executor = fasync::Executor::new().context("unable to create executor")?; |
| let bsh = Arc::new(Mutex::new(BatteryStatusHelper::new())); |
| let bsh2 = bsh.clone(); |
| let f = watch_power_device(bsh2); |
| |
| fasync::spawn(f.unwrap_or_else(|e| { |
| fx_log_err!("watch_power_device failed {:?}", e); |
| })); |
| |
| let server_fut = ServicesServer::new() |
| .add_service((PowerManagerMarker::NAME, move |chan| { |
| let pm = PowerManagerServer { |
| battery_status_helper: bsh.clone(), |
| }; |
| spawn_power_manager(pm, chan); |
| })).start() |
| .map_err(|e| e.context("starting service server"))?; |
| Ok(executor.run(server_fut, 2).context("running server")?) // 2 threads |
| } |
| |
| #[cfg(test)] |
| mod tests { |
| use super::*; |
| |
| 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: &BatteryStatus, want: &BatteryStatus, updated: bool, test_no: u32) { |
| if updated { |
| cmp_fields!( |
| want, |
| got, |
| [ |
| status, |
| battery_present, |
| charging, |
| critical, |
| power_adapter_online, |
| remaining_battery_life, |
| ], |
| test_no |
| ); |
| } else { |
| assert_eq!(want, got, "test: {}", test_no); |
| } |
| } |
| |
| fn get_default_battery_info() -> power::ioctl_power_get_battery_info_t { |
| let mut battery_info = power::ioctl_power_get_battery_info_t::new(); |
| battery_info.last_full_capacity = 3000; |
| battery_info.present_rate = -1000; |
| battery_info.remaining_capacity = 2000; |
| return battery_info; |
| } |
| |
| #[test] |
| fn update_battery_status() { |
| let mut bsh = BatteryStatusHelper::new(); |
| let mut power_info = power::ioctl_power_get_info_t { |
| power_type: power::POWER_TYPE_AC, |
| state: 0, |
| }; |
| let mut want = bsh.get_battery_status_copy(); |
| want.status = power_status::Ok; |
| want.power_adapter_online = true; |
| bsh.update_battery_status(power_info.clone(), None); |
| check_status(&bsh.battery_status, &want, true, 1); |
| |
| let want = bsh.get_battery_status_copy(); |
| bsh.update_battery_status(power_info.clone(), None); // should not be updated |
| check_status(&bsh.battery_status, &want, false, 2); |
| |
| let mut want = bsh.get_battery_status_copy(); |
| want.battery_present = true; |
| want.level = 200.0 / 3.0; |
| want.remaining_battery_life = 2.0; |
| power_info.power_type = power::POWER_TYPE_BATTERY; |
| power_info.state = 1; |
| let battery_info = get_default_battery_info(); |
| bsh.update_battery_status(power_info.clone(), Some(battery_info.clone())); |
| check_status(&bsh.battery_status, &want, true, 3); |
| |
| let mut want = bsh.get_battery_status_copy(); |
| power_info.state = 3; |
| want.discharging = true; |
| bsh.update_battery_status(power_info.clone(), Some(battery_info.clone())); |
| check_status(&bsh.battery_status, &want, true, 4); |
| |
| let mut want = bsh.get_battery_status_copy(); |
| power_info.state = 5; |
| want.discharging = false; |
| want.charging = true; |
| bsh.update_battery_status(power_info.clone(), Some(battery_info.clone())); |
| check_status(&bsh.battery_status, &want, true, 5); |
| |
| let mut want = bsh.get_battery_status_copy(); |
| power_info.state = 13; |
| want.discharging = false; |
| want.charging = true; |
| want.critical = true; |
| bsh.update_battery_status(power_info.clone(), Some(battery_info.clone())); |
| check_status(&bsh.battery_status, &want, true, 6); |
| |
| let mut want = bsh.get_battery_status_copy(); |
| power_info.state = 11; |
| want.discharging = true; |
| want.charging = false; |
| want.critical = true; |
| bsh.update_battery_status(power_info.clone(), Some(battery_info.clone())); |
| check_status(&bsh.battery_status, &want, true, 7); |
| } |
| } |