use num_enum::{IntoPrimitive, TryFromPrimitive};
use thiserror::Error;
use crate::{
channel::{ChannelError, HidppChannel, HidppMessage, LONG_REPORT_LENGTH, SHORT_REPORT_LENGTH},
nibble::{self, U4},
};
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
pub struct MessageHeader {
pub device_index: u8,
pub feature_index: u8,
pub function_id: U4,
pub software_id: U4,
}
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
pub enum Message {
Short(MessageHeader, [u8; SHORT_REPORT_LENGTH - 4]),
Long(MessageHeader, [u8; LONG_REPORT_LENGTH - 4]),
}
impl Message {
pub fn header(&self) -> MessageHeader {
match *self {
Message::Short(header, _) => header,
Message::Long(header, _) => header,
}
}
pub fn extend_payload(&self) -> [u8; LONG_REPORT_LENGTH - 4] {
match *self {
Message::Short(_, payload) => {
let mut data = [0; LONG_REPORT_LENGTH - 4];
data[..SHORT_REPORT_LENGTH - 4].copy_from_slice(&payload);
data
},
Message::Long(_, payload) => payload,
}
}
}
impl From<HidppMessage> for Message {
fn from(msg: HidppMessage) -> Self {
match msg {
HidppMessage::Short(payload) => Message::Short(
MessageHeader {
device_index: payload[0],
feature_index: payload[1],
function_id: U4::from_hi(payload[2]),
software_id: U4::from_lo(payload[2]),
},
payload[3..].try_into().unwrap(),
),
HidppMessage::Long(payload) => Message::Long(
MessageHeader {
device_index: payload[0],
feature_index: payload[1],
function_id: U4::from_hi(payload[2]),
software_id: U4::from_lo(payload[2]),
},
payload[3..].try_into().unwrap(),
),
}
}
}
impl From<Message> for HidppMessage {
fn from(msg: Message) -> Self {
match msg {
Message::Short(header, payload) => {
let mut data = [0u8; SHORT_REPORT_LENGTH - 1];
data[0] = header.device_index;
data[1] = header.feature_index;
data[2] = nibble::combine(header.function_id, header.software_id);
data[3..].copy_from_slice(&payload);
HidppMessage::Short(data)
},
Message::Long(header, payload) => {
let mut data = [0u8; LONG_REPORT_LENGTH - 1];
data[0] = header.device_index;
data[1] = header.feature_index;
data[2] = nibble::combine(header.function_id, header.software_id);
data[3..].copy_from_slice(&payload);
HidppMessage::Long(data)
},
}
}
}
impl HidppChannel {
pub async fn send_v20(&self, msg: Message) -> Result<Message, Hidpp20Error> {
let header = msg.header();
let response = Message::from(
self.send(msg.into(), move |&response| {
let resp_msg = Message::from(response);
let resp_header = resp_msg.header();
let is_error = resp_header.device_index == header.device_index
&& resp_header.feature_index == 0xff
&& nibble::combine(resp_header.function_id, resp_header.software_id)
== header.feature_index
&& resp_msg.extend_payload()[0]
== nibble::combine(header.function_id, header.software_id);
is_error || resp_header == header
})
.await?,
);
if response.header().feature_index == 0xff {
let err = ErrorType::try_from(response.extend_payload()[1])
.map_err(|_| Hidpp20Error::UnsupportedResponse)?;
return Err(Hidpp20Error::Feature(err));
}
Ok(response)
}
}
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, IntoPrimitive, TryFromPrimitive)]
#[non_exhaustive]
#[repr(u8)]
pub enum ErrorType {
NoError = 0,
Unknown = 1,
InvalidArgument = 2,
OutOfRange = 3,
HwError = 4,
LogitechInternal = 5,
InvalidFeatureIndex = 6,
InvalidFunctionId = 7,
Busy = 8,
Unsupported = 9,
}
#[derive(Debug, Error)]
#[non_exhaustive]
pub enum Hidpp20Error {
#[error("the HID++ channel returned an error")]
Channel(#[from] ChannelError),
#[error("a HID++2.0 feature returned an error")]
Feature(ErrorType),
#[error("the received response from the device is (partly) unsupported")]
UnsupportedResponse,
}