• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2020 The Chromium OS Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 use crate::{BatteryData, BatteryStatus, PowerData, PowerMonitor};
6 
7 mod proto;
8 use proto::system_api::power_supply_properties::{
9     PowerSupplyProperties, PowerSupplyProperties_BatteryState, PowerSupplyProperties_ExternalPower,
10 };
11 
12 use dbus::ffidisp::{BusType, Connection, ConnectionItem, WatchEvent};
13 
14 use protobuf::error::ProtobufError;
15 use protobuf::Message;
16 use remain::sorted;
17 use thiserror::Error;
18 
19 use std::error::Error;
20 use std::os::unix::io::RawFd;
21 
22 // Interface name from power_manager/dbus_bindings/org.chromium.PowerManager.xml.
23 const POWER_INTERFACE_NAME: &str = "org.chromium.PowerManager";
24 
25 // Signal name from power_manager/dbus_constants.h.
26 const POLL_SIGNAL_NAME: &str = "PowerSupplyPoll";
27 
28 impl From<PowerSupplyProperties> for PowerData {
from(props: PowerSupplyProperties) -> Self29     fn from(props: PowerSupplyProperties) -> Self {
30         let ac_online = if props.has_external_power() {
31             props.get_external_power() != PowerSupplyProperties_ExternalPower::DISCONNECTED
32         } else {
33             false
34         };
35 
36         let battery = if props.has_battery_state()
37             && props.get_battery_state() != PowerSupplyProperties_BatteryState::NOT_PRESENT
38         {
39             let status = match props.get_battery_state() {
40                 PowerSupplyProperties_BatteryState::FULL => BatteryStatus::NotCharging,
41                 PowerSupplyProperties_BatteryState::CHARGING => BatteryStatus::Charging,
42                 PowerSupplyProperties_BatteryState::DISCHARGING => BatteryStatus::Discharging,
43                 _ => BatteryStatus::Unknown,
44             };
45 
46             let percent = std::cmp::min(100, props.get_battery_percent().round() as u32);
47             // Convert from volts to microvolts.
48             let voltage = (props.get_battery_voltage() * 1_000_000f64).round() as u32;
49             // Convert from amps to microamps.
50             let current = (props.get_battery_current() * 1_000_000f64).round() as u32;
51             // Convert from ampere-hours to micro ampere-hours.
52             let charge_counter = (props.get_battery_charge() * 1_000_000f64).round() as u32;
53             let charge_full = (props.get_battery_charge_full() * 1_000_000f64).round() as u32;
54 
55             Some(BatteryData {
56                 status,
57                 percent,
58                 voltage,
59                 current,
60                 charge_counter,
61                 charge_full,
62             })
63         } else {
64             None
65         };
66 
67         Self { ac_online, battery }
68     }
69 }
70 
71 #[sorted]
72 #[derive(Error, Debug)]
73 pub enum DBusMonitorError {
74     #[error("failed to convert protobuf message: {0}")]
75     ConvertProtobuf(ProtobufError),
76     #[error("failed to add D-Bus match rule: {0}")]
77     DBusAddMatch(dbus::Error),
78     #[error("failed to connect to D-Bus: {0}")]
79     DBusConnect(dbus::Error),
80     #[error("failed to read D-Bus message: {0}")]
81     DBusRead(dbus::arg::TypeMismatchError),
82     #[error("multiple D-Bus fds")]
83     MultipleDBusFd,
84     #[error("no D-Bus fd")]
85     NoDBusFd,
86 }
87 
88 pub struct DBusMonitor {
89     connection: Connection,
90     connection_fd: RawFd,
91 }
92 
93 impl DBusMonitor {
94     /// Connects and configures a new D-Bus connection to listen for powerd's PowerSupplyPoll
95     /// signal.
connect() -> std::result::Result<Box<dyn PowerMonitor>, Box<dyn Error>>96     pub fn connect() -> std::result::Result<Box<dyn PowerMonitor>, Box<dyn Error>> {
97         let connection =
98             Connection::get_private(BusType::System).map_err(DBusMonitorError::DBusConnect)?;
99         connection
100             .add_match(&format!(
101                 "interface='{}',member='{}'",
102                 POWER_INTERFACE_NAME, POLL_SIGNAL_NAME
103             ))
104             .map_err(DBusMonitorError::DBusAddMatch)?;
105         // Get the D-Bus connection's fd for async I/O. This should always return a single fd.
106         let fds: Vec<RawFd> = connection
107             .watch_fds()
108             .into_iter()
109             .filter(|w| w.readable())
110             .map(|w| w.fd())
111             .collect();
112         if fds.is_empty() {
113             return Err(DBusMonitorError::NoDBusFd.into());
114         }
115         if fds.len() > 1 {
116             return Err(DBusMonitorError::MultipleDBusFd.into());
117         }
118         Ok(Box::new(Self {
119             connection,
120             connection_fd: fds[0],
121         }))
122     }
123 }
124 
125 impl PowerMonitor for DBusMonitor {
126     /// Returns the newest pending `PowerData` message, if any.
127     /// Callers should poll `PowerMonitor` to determine when messages are available.
read_message(&mut self) -> std::result::Result<Option<PowerData>, Box<dyn Error>>128     fn read_message(&mut self) -> std::result::Result<Option<PowerData>, Box<dyn Error>> {
129         // Select the newest available power message before converting to protobuf.
130         let newest_message: Option<dbus::Message> = self
131             .connection
132             .watch_handle(
133                 self.connection_fd,
134                 WatchEvent::Readable as std::os::raw::c_uint,
135             )
136             .fold(None, |last, item| match item {
137                 ConnectionItem::Signal(message) => {
138                     // Ignore non-matching signals: although match rules are configured, some system
139                     // signals can still get through, eg. NameAcquired.
140                     let interface = match message.interface() {
141                         Some(i) => i,
142                         None => {
143                             return last;
144                         }
145                     };
146 
147                     if &*interface != POWER_INTERFACE_NAME {
148                         return last;
149                     }
150 
151                     let member = match message.member() {
152                         Some(m) => m,
153                         None => {
154                             return last;
155                         }
156                     };
157 
158                     if &*member != POLL_SIGNAL_NAME {
159                         return last;
160                     }
161 
162                     Some(message)
163                 }
164                 _ => last,
165             });
166 
167         match newest_message {
168             Some(message) => {
169                 let data_bytes: Vec<u8> = message.read1().map_err(DBusMonitorError::DBusRead)?;
170                 let mut props = PowerSupplyProperties::new();
171                 props
172                     .merge_from_bytes(&data_bytes)
173                     .map_err(DBusMonitorError::ConvertProtobuf)?;
174                 Ok(Some(props.into()))
175             }
176             None => Ok(None),
177         }
178     }
179 
poll_fd(&self) -> RawFd180     fn poll_fd(&self) -> RawFd {
181         self.connection_fd
182     }
183 }
184