logiops_core/features/
root.rs1use hidpp_transport::HidapiChannel;
8use tracing::{debug, trace};
9
10use crate::error::{HidppErrorCode, ProtocolError, Result};
11use crate::protocol::{
12 build_long_request, feature_id, get_error_code, is_error_response, ProtocolVersion,
13 DEVICE_INDEX_DIRECT,
14};
15
16pub struct RootFeature {
18 device_index: u8,
19}
20
21impl RootFeature {
22 #[must_use]
27 pub fn new(device_index: u8) -> Self {
28 Self { device_index }
29 }
30
31 #[must_use]
33 pub fn direct() -> Self {
34 Self::new(DEVICE_INDEX_DIRECT)
35 }
36
37 pub async fn ping(&self, channel: &HidapiChannel) -> Result<ProtocolVersion> {
49 let ping_data: u8 = 0x5A; let request = build_long_request(self.device_index, 0x00, 0x01, &[0x00, 0x00, ping_data]);
53
54 trace!("sending ping request (long report)");
55 let response = channel.request(&request, 5).await?;
56
57 if is_error_response(&response) {
58 let code = get_error_code(&response).unwrap_or(0);
59 return Err(ProtocolError::HidppError(HidppErrorCode::from_byte(code)));
60 }
61
62 if response.len() < 7 {
63 return Err(ProtocolError::InvalidResponse(
64 "ping response too short".to_string(),
65 ));
66 }
67
68 let major = response[4];
70 let minor = response[5];
71 let echo = response[6];
72
73 if echo != ping_data {
74 return Err(ProtocolError::InvalidResponse(format!(
75 "ping echo mismatch: expected 0x{ping_data:02X}, got 0x{echo:02X}"
76 )));
77 }
78
79 let version = ProtocolVersion::new(major, minor);
80 debug!(
81 version = %version,
82 major,
83 minor,
84 "device ping successful"
85 );
86
87 Ok(version)
88 }
89
90 pub async fn get_feature_index(
103 &self,
104 channel: &HidapiChannel,
105 feature_id: u16,
106 ) -> Result<Option<u8>> {
107 let feature_bytes = feature_id.to_be_bytes();
110 let request = build_long_request(self.device_index, 0x00, 0x00, &feature_bytes);
111
112 trace!(
113 feature_id = format!("0x{feature_id:04X}"),
114 "getting feature index"
115 );
116 let response = channel.request(&request, 5).await?;
117
118 if is_error_response(&response) {
119 let code = get_error_code(&response).unwrap_or(0);
120 if code == 0x01 {
121 return Ok(None);
123 }
124 return Err(ProtocolError::HidppError(HidppErrorCode::from_byte(code)));
125 }
126
127 if response.len() < 5 {
128 return Err(ProtocolError::InvalidResponse(
129 "feature index response too short".to_string(),
130 ));
131 }
132
133 let index = response[4];
134 if index == 0 && feature_id != feature_id::IROOT {
135 return Ok(None);
137 }
138
139 debug!(
140 feature_id = format!("0x{feature_id:04X}"),
141 index, "feature index found"
142 );
143 Ok(Some(index))
144 }
145}
146
147#[cfg(test)]
148mod tests {
149 use super::*;
150
151 #[test]
152 fn test_root_feature_creation() {
153 let root = RootFeature::direct();
154 assert_eq!(root.device_index, DEVICE_INDEX_DIRECT);
155
156 let root = RootFeature::new(0x01);
157 assert_eq!(root.device_index, 0x01);
158 }
159}