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