hidpp/feature/smartshift_enhanced/
mod.rs1use std::{num::NonZeroU8, sync::Arc};
4
5use crate::{
6 channel::HidppChannel,
7 feature::{CreatableFeature, Feature, FeatureEndpoint, smartshift::WheelMode},
8 protocol::v20::Hidpp20Error,
9};
10
11bitflags::bitflags! {
12 #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
14 #[cfg_attr(feature = "serde", derive(serde::Serialize))]
15 pub struct SmartShiftEnhancedCapabilities: u8 {
16 const TUNABLE_TORQUE = 1 << 0;
18 }
19}
20
21#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
23#[cfg_attr(feature = "serde", derive(serde::Serialize))]
24#[non_exhaustive]
25pub struct SmartShiftEnhancedInfo {
26 pub capabilities: SmartShiftEnhancedCapabilities,
28 pub auto_disengage_default: u8,
30 pub default_tunable_torque: u8,
32 pub max_force: u8,
34}
35
36#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
38#[cfg_attr(feature = "serde", derive(serde::Serialize))]
39#[non_exhaustive]
40pub struct SmartShiftEnhancedStatus {
41 pub wheel_mode: WheelMode,
43 pub auto_disengage: u8,
45 pub current_tunable_torque: u8,
47}
48
49#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Default)]
51#[cfg_attr(feature = "serde", derive(serde::Serialize))]
52pub struct SmartShiftEnhancedStatusChange {
53 pub wheel_mode: Option<WheelMode>,
55 pub auto_disengage: Option<NonZeroU8>,
59 pub tunable_torque: Option<NonZeroU8>,
63}
64
65#[derive(Clone)]
67pub struct SmartShiftEnhancedFeature {
68 endpoint: FeatureEndpoint,
70}
71
72impl CreatableFeature for SmartShiftEnhancedFeature {
73 const ID: u16 = 0x2111;
74 const STARTING_VERSION: u8 = 0;
75
76 fn new(chan: Arc<HidppChannel>, device_index: u8, feature_index: u8) -> Self {
77 Self {
78 endpoint: FeatureEndpoint::new(chan, device_index, feature_index),
79 }
80 }
81}
82
83impl Feature for SmartShiftEnhancedFeature {}
84
85impl SmartShiftEnhancedFeature {
86 pub async fn get_capabilities(&self) -> Result<SmartShiftEnhancedInfo, Hidpp20Error> {
88 let payload = self.endpoint.call(0, [0; 3]).await?.extend_payload();
89 Ok(SmartShiftEnhancedInfo {
90 capabilities: SmartShiftEnhancedCapabilities::from_bits_retain(payload[0]),
91 auto_disengage_default: payload[1],
92 default_tunable_torque: payload[2],
93 max_force: payload[3],
94 })
95 }
96
97 pub async fn get_ratchet_control_mode(&self) -> Result<SmartShiftEnhancedStatus, Hidpp20Error> {
99 let payload = self.endpoint.call(1, [0; 3]).await?.extend_payload();
100 SmartShiftEnhancedStatus::from_payload(payload)
101 }
102
103 pub async fn set_ratchet_control_mode(
107 &self,
108 change: SmartShiftEnhancedStatusChange,
109 ) -> Result<SmartShiftEnhancedStatus, Hidpp20Error> {
110 let payload = self
111 .endpoint
112 .call(
113 2,
114 [
115 change.wheel_mode.map_or(0, u8::from),
116 change.auto_disengage.map_or(0, NonZeroU8::get),
117 change.tunable_torque.map_or(0, NonZeroU8::get),
118 ],
119 )
120 .await?
121 .extend_payload();
122 SmartShiftEnhancedStatus::from_payload(payload)
123 }
124}
125
126impl SmartShiftEnhancedStatus {
127 fn from_payload(payload: [u8; 16]) -> Result<Self, Hidpp20Error> {
128 Ok(Self {
129 wheel_mode: WheelMode::try_from(payload[0])
130 .map_err(|_| Hidpp20Error::UnsupportedResponse)?,
131 auto_disengage: payload[1],
132 current_tunable_torque: payload[2],
133 })
134 }
135}
136
137#[cfg(test)]
138mod tests {
139 use super::{Hidpp20Error, SmartShiftEnhancedStatus, WheelMode};
140
141 #[test]
142 fn parses_status() {
143 let mut payload = [0; 16];
144 payload[0] = 2;
145 payload[1] = 0xff;
146 payload[2] = 33;
147
148 let status = SmartShiftEnhancedStatus::from_payload(payload).unwrap();
149
150 assert_eq!(status.wheel_mode, WheelMode::Ratchet);
151 assert_eq!(status.auto_disengage, 0xff);
152 assert_eq!(status.current_tunable_torque, 33);
153 }
154
155 #[test]
156 fn unknown_wheel_mode_is_an_unsupported_response() {
157 let mut payload = [0; 16];
158 payload[0] = 9;
159
160 let err = SmartShiftEnhancedStatus::from_payload(payload).unwrap_err();
161
162 assert!(matches!(err, Hidpp20Error::UnsupportedResponse));
163 }
164}