1use num_enum::{IntoPrimitive, TryFromPrimitive};
4use thiserror::Error;
5
6use crate::{
7 channel::{ChannelError, HidppChannel, HidppMessage, LONG_REPORT_LENGTH, SHORT_REPORT_LENGTH},
8 nibble::{self, U4},
9};
10
11#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
13#[cfg_attr(feature = "serde", derive(serde::Serialize))]
14pub struct MessageHeader {
15 pub device_index: u8,
17
18 pub feature_index: u8,
23
24 pub function_id: U4,
26
27 pub software_id: U4,
29}
30
31#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
33#[cfg_attr(feature = "serde", derive(serde::Serialize))]
34pub enum Message {
35 Short(MessageHeader, [u8; SHORT_REPORT_LENGTH - 4]),
37
38 Long(MessageHeader, [u8; LONG_REPORT_LENGTH - 4]),
40}
41
42impl Message {
43 pub fn header(&self) -> MessageHeader {
45 match *self {
46 Message::Short(header, _) => header,
47 Message::Long(header, _) => header,
48 }
49 }
50
51 pub fn extend_payload(&self) -> [u8; LONG_REPORT_LENGTH - 4] {
55 match *self {
56 Message::Short(_, payload) => {
57 let mut data = [0; LONG_REPORT_LENGTH - 4];
58 data[..SHORT_REPORT_LENGTH - 4].copy_from_slice(&payload);
59 data
60 }
61 Message::Long(_, payload) => payload,
62 }
63 }
64}
65
66impl From<HidppMessage> for Message {
67 fn from(msg: HidppMessage) -> Self {
68 match msg {
69 HidppMessage::Short(payload) => Message::Short(
70 MessageHeader {
71 device_index: payload[0],
72 feature_index: payload[1],
73 function_id: U4::from_hi(payload[2]),
74 software_id: U4::from_lo(payload[2]),
75 },
76 payload[3..].try_into().unwrap(),
77 ),
78 HidppMessage::Long(payload) => Message::Long(
79 MessageHeader {
80 device_index: payload[0],
81 feature_index: payload[1],
82 function_id: U4::from_hi(payload[2]),
83 software_id: U4::from_lo(payload[2]),
84 },
85 payload[3..].try_into().unwrap(),
86 ),
87 }
88 }
89}
90
91impl From<Message> for HidppMessage {
92 fn from(msg: Message) -> Self {
93 match msg {
94 Message::Short(header, payload) => {
95 let mut data = [0u8; SHORT_REPORT_LENGTH - 1];
96 data[0] = header.device_index;
97 data[1] = header.feature_index;
98 data[2] = nibble::combine(header.function_id, header.software_id);
99 data[3..].copy_from_slice(&payload);
100
101 HidppMessage::Short(data)
102 }
103 Message::Long(header, payload) => {
104 let mut data = [0u8; LONG_REPORT_LENGTH - 1];
105 data[0] = header.device_index;
106 data[1] = header.feature_index;
107 data[2] = nibble::combine(header.function_id, header.software_id);
108 data[3..].copy_from_slice(&payload);
109
110 HidppMessage::Long(data)
111 }
112 }
113 }
114}
115
116impl HidppChannel {
117 pub async fn send_v20(&self, msg: Message) -> Result<Message, Hidpp20Error> {
123 let header = msg.header();
124
125 let response = Message::from(
126 self.send(msg.into(), move |&response| {
127 let resp_msg = Message::from(response);
128 let resp_header = resp_msg.header();
129
130 let is_error = resp_header.device_index == header.device_index
133 && resp_header.feature_index == 0xff
134 && nibble::combine(resp_header.function_id, resp_header.software_id)
135 == header.feature_index
136 && resp_msg.extend_payload()[0]
137 == nibble::combine(header.function_id, header.software_id);
138
139 is_error || resp_header == header
140 })
141 .await?,
142 );
143
144 if response.header().feature_index == 0xff {
145 let err = ErrorType::try_from(response.extend_payload()[1])
146 .map_err(|_| Hidpp20Error::UnsupportedResponse)?;
147
148 return Err(Hidpp20Error::Feature(err));
149 }
150
151 Ok(response)
152 }
153}
154
155#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, IntoPrimitive, TryFromPrimitive)]
158#[cfg_attr(feature = "serde", derive(serde::Serialize))]
159#[non_exhaustive]
160#[repr(u8)]
161pub enum ErrorType {
162 NoError = 0,
163 Unknown = 1,
164 InvalidArgument = 2,
165 OutOfRange = 3,
166 HwError = 4,
167 LogitechInternal = 5,
168 InvalidFeatureIndex = 6,
169 InvalidFunctionId = 7,
170 Busy = 8,
171 Unsupported = 9,
172}
173
174#[derive(Debug, Error)]
176#[non_exhaustive]
177pub enum Hidpp20Error {
178 #[error("the HID++ channel returned an error")]
181 Channel(#[from] ChannelError),
182
183 #[error("a HID++2.0 feature returned an error")]
186 Feature(ErrorType),
187
188 #[error("the received response from the device is (partly) unsupported")]
190 UnsupportedResponse,
191}