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 #[must_use]
45 pub fn header(&self) -> MessageHeader {
46 match *self {
47 Message::Short(header, _) | Message::Long(header, _) => header,
48 }
49 }
50
51 #[must_use]
55 pub fn extend_payload(&self) -> [u8; LONG_REPORT_LENGTH - 4] {
56 match *self {
57 Message::Short(_, payload) => {
58 let mut data = [0; LONG_REPORT_LENGTH - 4];
59 data[..SHORT_REPORT_LENGTH - 4].copy_from_slice(&payload);
60 data
61 }
62 Message::Long(_, payload) => payload,
63 }
64 }
65}
66
67impl From<HidppMessage> for Message {
68 fn from(msg: HidppMessage) -> Self {
69 match msg {
70 HidppMessage::Short(payload) => {
71 let [_, _, _, rest @ ..] = payload;
72 Message::Short(
73 MessageHeader {
74 device_index: payload[0],
75 feature_index: payload[1],
76 function_id: U4::from_hi(payload[2]),
77 software_id: U4::from_lo(payload[2]),
78 },
79 rest,
80 )
81 }
82 HidppMessage::Long(payload) => {
83 let [_, _, _, rest @ ..] = payload;
84 Message::Long(
85 MessageHeader {
86 device_index: payload[0],
87 feature_index: payload[1],
88 function_id: U4::from_hi(payload[2]),
89 software_id: U4::from_lo(payload[2]),
90 },
91 rest,
92 )
93 }
94 }
95 }
96}
97
98impl From<Message> for HidppMessage {
99 fn from(msg: Message) -> Self {
100 match msg {
101 Message::Short(header, payload) => {
102 let mut data = [0u8; SHORT_REPORT_LENGTH - 1];
103 data[0] = header.device_index;
104 data[1] = header.feature_index;
105 data[2] = nibble::combine(header.function_id, header.software_id);
106 data[3..].copy_from_slice(&payload);
107
108 HidppMessage::Short(data)
109 }
110 Message::Long(header, payload) => {
111 let mut data = [0u8; LONG_REPORT_LENGTH - 1];
112 data[0] = header.device_index;
113 data[1] = header.feature_index;
114 data[2] = nibble::combine(header.function_id, header.software_id);
115 data[3..].copy_from_slice(&payload);
116
117 HidppMessage::Long(data)
118 }
119 }
120 }
121}
122
123impl HidppChannel {
124 pub async fn send_v20(&self, msg: Message) -> Result<Message, Hidpp20Error> {
130 let header = msg.header();
131
132 let response = Message::from(
133 self.send(msg.into(), move |&response| {
134 let resp_msg = Message::from(response);
135 let resp_header = resp_msg.header();
136
137 let is_error = resp_header.device_index == header.device_index
140 && resp_header.feature_index == 0xff
141 && nibble::combine(resp_header.function_id, resp_header.software_id)
142 == header.feature_index
143 && resp_msg.extend_payload()[0]
144 == nibble::combine(header.function_id, header.software_id);
145
146 is_error || resp_header == header
147 })
148 .await?,
149 );
150
151 if response.header().feature_index == 0xff {
152 let err = ErrorType::try_from(response.extend_payload()[1])
153 .map_err(|_| Hidpp20Error::UnsupportedResponse)?;
154
155 return Err(Hidpp20Error::Feature(err));
156 }
157
158 Ok(response)
159 }
160}
161
162#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, IntoPrimitive, TryFromPrimitive)]
165#[cfg_attr(feature = "serde", derive(serde::Serialize))]
166#[non_exhaustive]
167#[repr(u8)]
168pub enum ErrorType {
169 NoError = 0,
171 Unknown = 1,
173 InvalidArgument = 2,
175 OutOfRange = 3,
177 HwError = 4,
179 LogitechInternal = 5,
181 InvalidFeatureIndex = 6,
183 InvalidFunctionId = 7,
185 Busy = 8,
187 Unsupported = 9,
189}
190
191#[derive(Debug, Error)]
193#[non_exhaustive]
194pub enum Hidpp20Error {
195 #[error("the HID++ channel returned an error")]
198 Channel(#[from] ChannelError),
199
200 #[error("a HID++2.0 feature returned an error")]
203 Feature(ErrorType),
204
205 #[error("the received response from the device is (partly) unsupported")]
207 UnsupportedResponse,
208}