use std::error::Error;
use std::os::unix::io::RawFd;
use base::AsRawDescriptor;
use base::RawDescriptor;
use base::ReadNotifier;
use dbus::ffidisp::BusType;
use dbus::ffidisp::Connection;
use dbus::ffidisp::ConnectionItem;
use dbus::ffidisp::WatchEvent;
use protobuf::Message;
use remain::sorted;
use thiserror::Error;
use crate::powerd::POWER_INTERFACE_NAME;
use crate::protos::power_supply_properties::PowerSupplyProperties;
use crate::BatteryData;
use crate::PowerData;
use crate::PowerMonitor;
const POLL_SIGNAL_NAME: &str = "PowerSupplyPoll";
#[sorted]
#[derive(Error, Debug)]
pub enum DBusMonitorError {
#[error("failed to convert protobuf message: {0}")]
ConvertProtobuf(protobuf::Error),
#[error("failed to add D-Bus match rule: {0}")]
DBusAddMatch(dbus::Error),
#[error("failed to connect to D-Bus: {0}")]
DBusConnect(dbus::Error),
#[error("failed to read D-Bus message: {0}")]
DBusRead(dbus::arg::TypeMismatchError),
#[error("multiple D-Bus fds")]
MultipleDBusFd,
#[error("no D-Bus fd")]
NoDBusFd,
}
pub struct DBusMonitor {
connection: Connection,
connection_fd: RawFd,
previous_data: Option<BatteryData>,
}
impl DBusMonitor {
pub fn connect() -> std::result::Result<Box<dyn PowerMonitor>, Box<dyn Error>> {
let connection =
Connection::get_private(BusType::System).map_err(DBusMonitorError::DBusConnect)?;
connection
.add_match(&format!(
"interface='{}',member='{}'",
POWER_INTERFACE_NAME, POLL_SIGNAL_NAME
))
.map_err(DBusMonitorError::DBusAddMatch)?;
let fds: Vec<RawFd> = connection
.watch_fds()
.into_iter()
.filter(|w| w.readable())
.map(|w| w.fd())
.collect();
if fds.is_empty() {
return Err(DBusMonitorError::NoDBusFd.into());
}
if fds.len() > 1 {
return Err(DBusMonitorError::MultipleDBusFd.into());
}
Ok(Box::new(Self {
connection,
connection_fd: fds[0],
previous_data: None,
}))
}
}
fn denoise_value(new_val: u32, prev_val: u32, margin: f64) -> u32 {
if new_val.abs_diff(prev_val) as f64 / prev_val.min(new_val).max(1) as f64 >= margin {
new_val
} else {
prev_val
}
}
impl PowerMonitor for DBusMonitor {
fn read_message(&mut self) -> std::result::Result<Option<PowerData>, Box<dyn Error>> {
let newest_message: Option<dbus::Message> = self
.connection
.watch_handle(
self.connection_fd,
WatchEvent::Readable as std::os::raw::c_uint,
)
.fold(None, |last, item| match item {
ConnectionItem::Signal(message) => {
let interface = match message.interface() {
Some(i) => i,
None => {
return last;
}
};
if &*interface != POWER_INTERFACE_NAME {
return last;
}
let member = match message.member() {
Some(m) => m,
None => {
return last;
}
};
if &*member != POLL_SIGNAL_NAME {
return last;
}
Some(message)
}
_ => last,
});
let previous_data = self.previous_data.take();
match newest_message {
Some(message) => {
let data_bytes: Vec<u8> = message.read1().map_err(DBusMonitorError::DBusRead)?;
let mut props = PowerSupplyProperties::new();
props
.merge_from_bytes(&data_bytes)
.map_err(DBusMonitorError::ConvertProtobuf)?;
let mut data: PowerData = props.into();
if let (Some(new_data), Some(previous)) = (data.battery.as_mut(), previous_data) {
new_data.voltage = denoise_value(new_data.voltage, previous.voltage, 0.1);
new_data.current = denoise_value(new_data.current, previous.current, 0.1);
new_data.charge_counter =
denoise_value(new_data.charge_counter, previous.charge_counter, 0.01);
}
self.previous_data = data.battery;
Ok(Some(data))
}
None => Ok(None),
}
}
}
impl AsRawDescriptor for DBusMonitor {
fn as_raw_descriptor(&self) -> RawDescriptor {
self.connection_fd
}
}
impl ReadNotifier for DBusMonitor {
fn get_read_notifier(&self) -> &dyn AsRawDescriptor {
self
}
}