• 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::Message;
20 use remain::sorted;
21 use thiserror::Error;
22 
23 use crate::protos::power_supply_properties::power_supply_properties;
24 use crate::protos::power_supply_properties::PowerSupplyProperties;
25 use crate::BatteryData;
26 use crate::BatteryStatus;
27 use crate::PowerData;
28 use crate::PowerMonitor;
29 
30 // Interface name from power_manager/dbus_bindings/org.chromium.PowerManager.xml.
31 const POWER_INTERFACE_NAME: &str = "org.chromium.PowerManager";
32 
33 // Signal name from power_manager/dbus_constants.h.
34 const POLL_SIGNAL_NAME: &str = "PowerSupplyPoll";
35 
36 impl From<PowerSupplyProperties> for PowerData {
from(props: PowerSupplyProperties) -> Self37     fn from(props: PowerSupplyProperties) -> Self {
38         let ac_online = if props.has_external_power() {
39             props.external_power() != power_supply_properties::ExternalPower::DISCONNECTED
40         } else {
41             false
42         };
43 
44         let battery = if props.has_battery_state()
45             && props.battery_state() != power_supply_properties::BatteryState::NOT_PRESENT
46         {
47             let status = match props.battery_state() {
48                 power_supply_properties::BatteryState::FULL => BatteryStatus::NotCharging,
49                 power_supply_properties::BatteryState::CHARGING => BatteryStatus::Charging,
50                 power_supply_properties::BatteryState::DISCHARGING => BatteryStatus::Discharging,
51                 _ => BatteryStatus::Unknown,
52             };
53 
54             let percent = std::cmp::min(100, props.battery_percent().round() as u32);
55             // Convert from volts to microvolts.
56             let voltage = (props.battery_voltage() * 1_000_000f64).round() as u32;
57             // Convert from amps to microamps.
58             let current = (props.battery_current() * 1_000_000f64).round() as u32;
59             // Convert from ampere-hours to micro ampere-hours.
60             let charge_counter = (props.battery_charge() * 1_000_000f64).round() as u32;
61             let charge_full = (props.battery_charge_full() * 1_000_000f64).round() as u32;
62 
63             Some(BatteryData {
64                 status,
65                 percent,
66                 voltage,
67                 current,
68                 charge_counter,
69                 charge_full,
70             })
71         } else {
72             None
73         };
74 
75         Self { ac_online, battery }
76     }
77 }
78 
79 #[sorted]
80 #[derive(Error, Debug)]
81 pub enum DBusMonitorError {
82     #[error("failed to convert protobuf message: {0}")]
83     ConvertProtobuf(protobuf::Error),
84     #[error("failed to add D-Bus match rule: {0}")]
85     DBusAddMatch(dbus::Error),
86     #[error("failed to connect to D-Bus: {0}")]
87     DBusConnect(dbus::Error),
88     #[error("failed to read D-Bus message: {0}")]
89     DBusRead(dbus::arg::TypeMismatchError),
90     #[error("multiple D-Bus fds")]
91     MultipleDBusFd,
92     #[error("no D-Bus fd")]
93     NoDBusFd,
94 }
95 
96 pub struct DBusMonitor {
97     connection: Connection,
98     connection_fd: RawFd,
99     previous_data: Option<BatteryData>,
100 }
101 
102 impl DBusMonitor {
103     /// Connects and configures a new D-Bus connection to listen for powerd's PowerSupplyPoll
104     /// signal.
connect() -> std::result::Result<Box<dyn PowerMonitor>, Box<dyn Error>>105     pub fn connect() -> std::result::Result<Box<dyn PowerMonitor>, Box<dyn Error>> {
106         let connection =
107             Connection::get_private(BusType::System).map_err(DBusMonitorError::DBusConnect)?;
108         connection
109             .add_match(&format!(
110                 "interface='{}',member='{}'",
111                 POWER_INTERFACE_NAME, POLL_SIGNAL_NAME
112             ))
113             .map_err(DBusMonitorError::DBusAddMatch)?;
114         // Get the D-Bus connection's fd for async I/O. This should always return a single fd.
115         let fds: Vec<RawFd> = connection
116             .watch_fds()
117             .into_iter()
118             .filter(|w| w.readable())
119             .map(|w| w.fd())
120             .collect();
121         if fds.is_empty() {
122             return Err(DBusMonitorError::NoDBusFd.into());
123         }
124         if fds.len() > 1 {
125             return Err(DBusMonitorError::MultipleDBusFd.into());
126         }
127         Ok(Box::new(Self {
128             connection,
129             connection_fd: fds[0],
130             previous_data: None,
131         }))
132     }
133 }
134 
denoise_value(new_val: u32, prev_val: u32, margin: f64) -> u32135 fn denoise_value(new_val: u32, prev_val: u32, margin: f64) -> u32 {
136     if new_val.abs_diff(prev_val) as f64 / prev_val.min(new_val).max(1) as f64 >= margin {
137         new_val
138     } else {
139         prev_val
140     }
141 }
142 
143 impl PowerMonitor for DBusMonitor {
144     /// Returns the newest pending `PowerData` message, if any.
145     /// Callers should poll `PowerMonitor` to determine when messages are available.
read_message(&mut self) -> std::result::Result<Option<PowerData>, Box<dyn Error>>146     fn read_message(&mut self) -> std::result::Result<Option<PowerData>, Box<dyn Error>> {
147         // Select the newest available power message before converting to protobuf.
148         let newest_message: Option<dbus::Message> = self
149             .connection
150             .watch_handle(
151                 self.connection_fd,
152                 WatchEvent::Readable as std::os::raw::c_uint,
153             )
154             .fold(None, |last, item| match item {
155                 ConnectionItem::Signal(message) => {
156                     // Ignore non-matching signals: although match rules are configured, some system
157                     // signals can still get through, eg. NameAcquired.
158                     let interface = match message.interface() {
159                         Some(i) => i,
160                         None => {
161                             return last;
162                         }
163                     };
164 
165                     if &*interface != POWER_INTERFACE_NAME {
166                         return last;
167                     }
168 
169                     let member = match message.member() {
170                         Some(m) => m,
171                         None => {
172                             return last;
173                         }
174                     };
175 
176                     if &*member != POLL_SIGNAL_NAME {
177                         return last;
178                     }
179 
180                     Some(message)
181                 }
182                 _ => last,
183             });
184 
185         let previous_data = self.previous_data.take();
186         match newest_message {
187             Some(message) => {
188                 let data_bytes: Vec<u8> = message.read1().map_err(DBusMonitorError::DBusRead)?;
189                 let mut props = PowerSupplyProperties::new();
190                 props
191                     .merge_from_bytes(&data_bytes)
192                     .map_err(DBusMonitorError::ConvertProtobuf)?;
193                 let mut data: PowerData = props.into();
194                 if let (Some(new_data), Some(previous)) = (data.battery.as_mut(), previous_data) {
195                     // The raw information from powerd signals isn't really that useful to
196                     // the guest. Voltage/current are volatile values, so the .0333 hZ
197                     // snapshot provided by powerd isn't particularly meaningful. We do
198                     // need to provide *something*, but we might as well make it less noisy
199                     // to avoid having the guest try to process mostly useless information.
200                     // charge_counter is potentially useful to the guest, but it doesn't
201                     // need to be higher precision than battery.percent.
202                     new_data.voltage = denoise_value(new_data.voltage, previous.voltage, 0.1);
203                     new_data.current = denoise_value(new_data.current, previous.current, 0.1);
204                     new_data.charge_counter =
205                         denoise_value(new_data.charge_counter, previous.charge_counter, 0.01);
206                 }
207                 self.previous_data = data.battery;
208                 Ok(Some(data))
209             }
210             None => Ok(None),
211         }
212     }
213 }
214 
215 impl AsRawDescriptor for DBusMonitor {
as_raw_descriptor(&self) -> RawDescriptor216     fn as_raw_descriptor(&self) -> RawDescriptor {
217         self.connection_fd
218     }
219 }
220 
221 impl ReadNotifier for DBusMonitor {
get_read_notifier(&self) -> &dyn AsRawDescriptor222     fn get_read_notifier(&self) -> &dyn AsRawDescriptor {
223         self
224     }
225 }
226