use std::{collections::HashSet, hash::Hash, sync::Arc};
use num_enum::{IntoPrimitive, TryFromPrimitive};
use crate::{
channel::HidppChannel,
event::EventEmitter,
feature::{CreatableFeature, EmittingFeature, Feature},
nibble::{self, U4},
protocol::v20::{self, Hidpp20Error},
};
pub struct UnifiedBatteryFeatureV0 {
chan: Arc<HidppChannel>,
device_index: u8,
feature_index: u8,
emitter: Arc<EventEmitter<BatteryEvent>>,
msg_listener_hdl: u32,
}
impl CreatableFeature for UnifiedBatteryFeatureV0 {
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| {
if matched {
return;
}
let msg = v20::Message::from(raw);
let header = msg.header();
if header.device_index != device_index
|| header.feature_index != feature_index
|| nibble::combine(header.software_id, header.function_id) != 0
{
return;
}
let payload = msg.extend_payload();
let Ok(level) = BatteryLevel::try_from(payload[1]) else {
return;
};
let Ok(status) = BatteryStatus::try_from(payload[2]) else {
return;
};
emitter.emit(BatteryEvent::InfoUpdate(BatteryInfo {
charging_percentage: payload[0],
level,
status,
}));
}
});
Self {
chan,
device_index,
feature_index,
emitter,
msg_listener_hdl: hdl,
}
}
}
impl Feature for UnifiedBatteryFeatureV0 {
}
impl EmittingFeature<BatteryEvent> for UnifiedBatteryFeatureV0 {
fn listen(&self) -> async_channel::Receiver<BatteryEvent> {
self.emitter.create_receiver()
}
}
impl Drop for UnifiedBatteryFeatureV0 {
fn drop(&mut self) {
self.chan.remove_msg_listener(self.msg_listener_hdl);
}
}
impl UnifiedBatteryFeatureV0 {
pub async fn get_battery_capabilities(&self) -> Result<BatteryCapabilities, Hidpp20Error> {
let response = self
.chan
.send_v20(v20::Message::Short(
v20::MessageHeader {
device_index: self.device_index,
feature_index: self.feature_index,
function_id: U4::from_lo(0),
software_id: self.chan.get_sw_id(),
},
[0x00, 0x00, 0x00],
))
.await?;
let payload: [u8; 2] = response.extend_payload()[..2].try_into().unwrap();
Ok(BatteryCapabilities::from(payload))
}
pub async fn get_battery_info(&self) -> Result<BatteryInfo, Hidpp20Error> {
let response = self
.chan
.send_v20(v20::Message::Short(
v20::MessageHeader {
device_index: self.device_index,
feature_index: self.feature_index,
function_id: U4::from_lo(1),
software_id: self.chan.get_sw_id(),
},
[0x00, 0x00, 0x00],
))
.await?;
let payload = response.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)]
#[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)]
#[non_exhaustive]
pub struct BatteryInfo {
pub charging_percentage: u8,
pub level: BatteryLevel,
pub status: BatteryStatus,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, IntoPrimitive, TryFromPrimitive)]
#[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)]
#[non_exhaustive]
#[repr(u8)]
pub enum BatteryStatus {
Discharging = 0,
Charging = 1,
ChargingSlow = 2,
Full = 3,
Error = 4,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum BatteryEvent {
InfoUpdate(BatteryInfo),
}