use std::{collections::HashSet, hash::Hash, sync::Arc};
use num_enum::{IntoPrimitive, TryFromPrimitive};
use crate::{
channel::HidppChannel,
event::EventEmitter,
feature::{CreatableFeature, EmittingFeature, Feature, FeatureEndpoint, event_payload},
protocol::v20::Hidpp20Error,
};
pub struct UnifiedBatteryFeature {
endpoint: FeatureEndpoint,
emitter: Arc<EventEmitter<BatteryEvent>>,
msg_listener_hdl: u32,
}
impl CreatableFeature for UnifiedBatteryFeature {
const ID: u16 = 0x1004;
const STARTING_VERSION: u8 = 0;
fn new(chan: Arc<HidppChannel>, device_index: u8, feature_index: u8) -> Self {
let emitter = Arc::new(EventEmitter::new());
let hdl = chan.add_msg_listener({
let emitter = Arc::clone(&emitter);
move |raw, matched| {
let Some((func, payload)) =
event_payload(raw, matched, device_index, feature_index)
else {
return;
};
if func.to_lo() != 0 {
return;
}
let (Ok(level), Ok(status)) = (
BatteryLevel::try_from(payload[1]),
BatteryStatus::try_from(payload[2]),
) else {
return;
};
emitter.emit(BatteryEvent::InfoUpdate(BatteryInfo {
charging_percentage: payload[0],
level,
status,
}));
}
});
Self {
endpoint: FeatureEndpoint::new(chan, device_index, feature_index),
emitter,
msg_listener_hdl: hdl,
}
}
}
impl Feature for UnifiedBatteryFeature {}
impl EmittingFeature<BatteryEvent> for UnifiedBatteryFeature {
fn listen(&self) -> async_channel::Receiver<BatteryEvent> {
self.emitter.create_receiver()
}
}
impl Drop for UnifiedBatteryFeature {
fn drop(&mut self) {
self.endpoint
.chan()
.remove_msg_listener(self.msg_listener_hdl);
}
}
impl UnifiedBatteryFeature {
pub async fn get_battery_capabilities(&self) -> Result<BatteryCapabilities, Hidpp20Error> {
let payload: [u8; 2] = self.endpoint.call(0, [0; 3]).await?.extend_payload()[..2]
.try_into()
.unwrap();
Ok(BatteryCapabilities::from(payload))
}
pub async fn get_battery_info(&self) -> Result<BatteryInfo, Hidpp20Error> {
let payload = self.endpoint.call(1, [0; 3]).await?.extend_payload();
Ok(BatteryInfo {
charging_percentage: payload[0],
level: BatteryLevel::try_from(payload[1])
.map_err(|_| Hidpp20Error::UnsupportedResponse)?,
status: BatteryStatus::try_from(payload[2])
.map_err(|_| Hidpp20Error::UnsupportedResponse)?,
})
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
#[non_exhaustive]
pub struct BatteryCapabilities {
pub reported_levels: HashSet<BatteryLevel>,
pub rechargeable: bool,
pub percentage: bool,
}
impl From<[u8; 2]> for BatteryCapabilities {
fn from(value: [u8; 2]) -> Self {
let mut reported_levels = HashSet::new();
if value[0] & 1 != 0 {
reported_levels.insert(BatteryLevel::Critical);
}
if value[0] & (1 << 1) != 0 {
reported_levels.insert(BatteryLevel::Low);
}
if value[0] & (1 << 2) != 0 {
reported_levels.insert(BatteryLevel::Good);
}
if value[0] & (1 << 3) != 0 {
reported_levels.insert(BatteryLevel::Full);
}
Self {
reported_levels,
rechargeable: value[1] & 1 != 0,
percentage: value[1] & (1 << 1) != 0,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
#[non_exhaustive]
pub struct BatteryInfo {
pub charging_percentage: u8,
pub level: BatteryLevel,
pub status: BatteryStatus,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, IntoPrimitive, TryFromPrimitive)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
#[non_exhaustive]
#[repr(u8)]
pub enum BatteryLevel {
Critical = 1,
Low = 1 << 1,
Good = 1 << 2,
Full = 1 << 3,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, IntoPrimitive, TryFromPrimitive)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
#[non_exhaustive]
#[repr(u8)]
pub enum BatteryStatus {
Discharging = 0,
Charging = 1,
ChargingSlow = 2,
Full = 3,
Error = 4,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
#[non_exhaustive]
pub enum BatteryEvent {
InfoUpdate(BatteryInfo),
}