hidpp/feature/
smartshift_enhanced.rs1use std::num::NonZeroU8;
4
5use openlogi_hidpp_derive::Feature;
6
7use crate::{
8 feature::{FeatureEndpoint, smartshift::WheelMode},
9 protocol::v20::Hidpp20Error,
10};
11
12bitflags::bitflags! {
13 #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
15 #[cfg_attr(feature = "serde", derive(serde::Serialize))]
16 pub struct SmartShiftEnhancedCapabilities: u8 {
17 const TUNABLE_TORQUE = 1 << 0;
19 }
20}
21
22#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
24#[cfg_attr(feature = "serde", derive(serde::Serialize))]
25#[non_exhaustive]
26pub struct SmartShiftEnhancedInfo {
27 pub capabilities: SmartShiftEnhancedCapabilities,
29 pub auto_disengage_default: u8,
31 pub default_tunable_torque: u8,
33 pub max_force: u8,
35}
36
37#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
39#[cfg_attr(feature = "serde", derive(serde::Serialize))]
40#[non_exhaustive]
41pub struct SmartShiftEnhancedStatus {
42 pub wheel_mode: WheelMode,
44 pub auto_disengage: u8,
46 pub current_tunable_torque: u8,
48}
49
50#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Default)]
52#[cfg_attr(feature = "serde", derive(serde::Serialize))]
53pub struct SmartShiftEnhancedStatusChange {
54 pub wheel_mode: Option<WheelMode>,
56 pub auto_disengage: Option<NonZeroU8>,
60 pub tunable_torque: Option<NonZeroU8>,
64}
65
66#[derive(Clone, Feature)]
68#[creatable(id = 0x2111, version = 0)]
69pub struct SmartShiftEnhancedFeature {
70 endpoint: FeatureEndpoint,
72}
73
74impl SmartShiftEnhancedFeature {
75 pub async fn get_capabilities(&self) -> Result<SmartShiftEnhancedInfo, Hidpp20Error> {
77 let payload = self.endpoint.call(0, [0; 3]).await?.extend_payload();
78 Ok(SmartShiftEnhancedInfo {
79 capabilities: SmartShiftEnhancedCapabilities::from_bits_retain(payload[0]),
80 auto_disengage_default: payload[1],
81 default_tunable_torque: payload[2],
82 max_force: payload[3],
83 })
84 }
85
86 pub async fn get_ratchet_control_mode(&self) -> Result<SmartShiftEnhancedStatus, Hidpp20Error> {
88 let payload = self.endpoint.call(1, [0; 3]).await?.extend_payload();
89 SmartShiftEnhancedStatus::from_payload(payload)
90 }
91
92 pub async fn set_ratchet_control_mode(
96 &self,
97 change: SmartShiftEnhancedStatusChange,
98 ) -> Result<SmartShiftEnhancedStatus, Hidpp20Error> {
99 let payload = self
100 .endpoint
101 .call(
102 2,
103 [
104 change.wheel_mode.map_or(0, u8::from),
105 change.auto_disengage.map_or(0, NonZeroU8::get),
106 change.tunable_torque.map_or(0, NonZeroU8::get),
107 ],
108 )
109 .await?
110 .extend_payload();
111 SmartShiftEnhancedStatus::from_payload(payload)
112 }
113}
114
115impl SmartShiftEnhancedStatus {
116 fn from_payload(payload: [u8; 16]) -> Result<Self, Hidpp20Error> {
117 Ok(Self {
118 wheel_mode: WheelMode::try_from(payload[0])
119 .map_err(|_| Hidpp20Error::UnsupportedResponse)?,
120 auto_disengage: payload[1],
121 current_tunable_torque: payload[2],
122 })
123 }
124}
125
126#[cfg(test)]
127#[allow(clippy::unwrap_used, reason = "expect/unwrap are idiomatic in tests")]
128mod tests {
129 use super::{Hidpp20Error, SmartShiftEnhancedStatus, WheelMode};
130
131 #[test]
132 fn parses_status() {
133 let mut payload = [0; 16];
134 payload[0] = 2;
135 payload[1] = 0xff;
136 payload[2] = 33;
137
138 let status = SmartShiftEnhancedStatus::from_payload(payload).unwrap();
139
140 assert_eq!(status.wheel_mode, WheelMode::Ratchet);
141 assert_eq!(status.auto_disengage, 0xff);
142 assert_eq!(status.current_tunable_torque, 33);
143 }
144
145 #[test]
146 fn unknown_wheel_mode_is_an_unsupported_response() {
147 let mut payload = [0; 16];
148 payload[0] = 9;
149
150 let err = SmartShiftEnhancedStatus::from_payload(payload).unwrap_err();
151
152 assert!(matches!(err, Hidpp20Error::UnsupportedResponse));
153 }
154}