use std::ops::RangeInclusive;
use openlogi_hidpp_derive::Feature;
use crate::{
feature::FeatureEndpoint,
protocol::v20::{ErrorType, Hidpp20Error},
};
const REPORT_RATE_MS: RangeInclusive<u8> = 1..=8;
bitflags::bitflags! {
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct ReportRateList: u8 {
const MS_1 = 1 << 0;
const MS_2 = 1 << 1;
const MS_3 = 1 << 2;
const MS_4 = 1 << 3;
const MS_5 = 1 << 4;
const MS_6 = 1 << 5;
const MS_7 = 1 << 6;
const MS_8 = 1 << 7;
}
}
#[derive(Clone, Feature)]
#[creatable(id = 0x8060, version = 0)]
pub struct ReportRateFeature {
endpoint: FeatureEndpoint,
}
impl ReportRateFeature {
pub async fn get_report_rate_list(&self) -> Result<ReportRateList, Hidpp20Error> {
let payload = self.endpoint.call(0, [0; 3]).await?.extend_payload();
Ok(ReportRateList::from_bits_retain(payload[0]))
}
pub async fn get_report_rate(&self) -> Result<u8, Hidpp20Error> {
let report_rate_ms = self.endpoint.call(1, [0; 3]).await?.extend_payload()[0];
if REPORT_RATE_MS.contains(&report_rate_ms) {
Ok(report_rate_ms)
} else {
Err(Hidpp20Error::UnsupportedResponse)
}
}
pub async fn set_report_rate(&self, report_rate_ms: u8) -> Result<(), Hidpp20Error> {
if !REPORT_RATE_MS.contains(&report_rate_ms) {
return Err(Hidpp20Error::Feature(ErrorType::InvalidArgument));
}
self.endpoint.call(2, [report_rate_ms, 0, 0]).await?;
Ok(())
}
}
#[cfg(test)]
mod tests {
use std::{assert_matches, sync::Arc};
use super::ReportRateFeature;
use crate::{
channel::mock::{MockRawHidChannel, MockRawHidHandle, channel_with_reader},
feature::CreatableFeature,
nibble::U4,
protocol::v20::{ErrorType, Hidpp20Error, Message, MessageHeader},
};
const DEVICE: u8 = 3;
const FEATURE_INDEX: u8 = 8;
fn header(function: u8) -> MessageHeader {
MessageHeader {
device_index: DEVICE,
feature_index: FEATURE_INDEX,
function_id: U4::from_lo(function),
software_id: U4::from_lo(1),
}
}
async fn feature() -> (ReportRateFeature, MockRawHidHandle) {
let (raw, handle) = MockRawHidChannel::new();
let channel = Arc::new(channel_with_reader(raw).await);
(
ReportRateFeature::new(channel, DEVICE, FEATURE_INDEX),
handle,
)
}
fn read_rate(reply_ms: u8) -> Result<u8, Hidpp20Error> {
futures::executor::block_on(async {
let (feature, handle) = feature().await;
let mut pending = Box::pin(feature.get_report_rate());
assert!(futures::poll!(pending.as_mut()).is_pending());
handle
.send_incoming(Message::Short(header(1), [reply_ms, 0, 0]).into())
.await;
pending.await
})
}
#[test]
fn the_active_rate_is_one_to_eight_ms() {
assert_eq!(read_rate(1).ok(), Some(1));
assert_eq!(read_rate(8).ok(), Some(8));
for invalid in [0, 9, 255] {
assert_matches!(read_rate(invalid), Err(Hidpp20Error::UnsupportedResponse));
}
}
#[test]
fn an_out_of_range_rate_is_refused_before_it_is_sent() {
futures::executor::block_on(async {
let (feature, handle) = feature().await;
for invalid in [0, 9, 255] {
assert_matches!(
feature.set_report_rate(invalid).await,
Err(Hidpp20Error::Feature(ErrorType::InvalidArgument))
);
}
assert!(handle.written_reports().is_empty());
let mut pending = Box::pin(feature.set_report_rate(8));
assert!(futures::poll!(pending.as_mut()).is_pending());
handle
.send_incoming(Message::Short(header(2), [8, 0, 0]).into())
.await;
assert_matches!(pending.await, Ok(()));
let written = handle.written_reports();
assert_eq!(written.len(), 1);
assert_eq!(written[0][4], 8);
});
}
}