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