use std::{num::NonZeroU8, sync::Arc};
use crate::{
channel::HidppChannel,
feature::{CreatableFeature, Feature, FeatureEndpoint, smartshift::WheelMode},
protocol::v20::Hidpp20Error,
};
bitflags::bitflags! {
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct SmartShiftEnhancedCapabilities: u8 {
const TUNABLE_TORQUE = 1 << 0;
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
#[non_exhaustive]
pub struct SmartShiftEnhancedInfo {
pub capabilities: SmartShiftEnhancedCapabilities,
pub auto_disengage_default: u8,
pub default_tunable_torque: u8,
pub max_force: u8,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
#[non_exhaustive]
pub struct SmartShiftEnhancedStatus {
pub wheel_mode: WheelMode,
pub auto_disengage: u8,
pub current_tunable_torque: u8,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct SmartShiftEnhancedStatusChange {
pub wheel_mode: Option<WheelMode>,
pub auto_disengage: Option<NonZeroU8>,
pub tunable_torque: Option<NonZeroU8>,
}
#[derive(Clone)]
pub struct SmartShiftEnhancedFeature {
endpoint: FeatureEndpoint,
}
impl CreatableFeature for SmartShiftEnhancedFeature {
const ID: u16 = 0x2111;
const STARTING_VERSION: u8 = 0;
fn new(chan: Arc<HidppChannel>, device_index: u8, feature_index: u8) -> Self {
Self {
endpoint: FeatureEndpoint::new(chan, device_index, feature_index),
}
}
}
impl Feature for SmartShiftEnhancedFeature {}
impl SmartShiftEnhancedFeature {
pub async fn get_capabilities(&self) -> Result<SmartShiftEnhancedInfo, Hidpp20Error> {
let payload = self.endpoint.call(0, [0; 3]).await?.extend_payload();
Ok(SmartShiftEnhancedInfo {
capabilities: SmartShiftEnhancedCapabilities::from_bits_retain(payload[0]),
auto_disengage_default: payload[1],
default_tunable_torque: payload[2],
max_force: payload[3],
})
}
pub async fn get_ratchet_control_mode(&self) -> Result<SmartShiftEnhancedStatus, Hidpp20Error> {
let payload = self.endpoint.call(1, [0; 3]).await?.extend_payload();
SmartShiftEnhancedStatus::from_payload(payload)
}
pub async fn set_ratchet_control_mode(
&self,
change: SmartShiftEnhancedStatusChange,
) -> Result<SmartShiftEnhancedStatus, Hidpp20Error> {
let payload = self
.endpoint
.call(
2,
[
change.wheel_mode.map_or(0, u8::from),
change.auto_disengage.map_or(0, NonZeroU8::get),
change.tunable_torque.map_or(0, NonZeroU8::get),
],
)
.await?
.extend_payload();
SmartShiftEnhancedStatus::from_payload(payload)
}
}
impl SmartShiftEnhancedStatus {
fn from_payload(payload: [u8; 16]) -> Result<Self, Hidpp20Error> {
Ok(Self {
wheel_mode: WheelMode::try_from(payload[0])
.map_err(|_| Hidpp20Error::UnsupportedResponse)?,
auto_disengage: payload[1],
current_tunable_torque: payload[2],
})
}
}
#[cfg(test)]
mod tests {
use super::{Hidpp20Error, SmartShiftEnhancedStatus, WheelMode};
#[test]
fn parses_status() {
let mut payload = [0; 16];
payload[0] = 2;
payload[1] = 0xff;
payload[2] = 33;
let status = SmartShiftEnhancedStatus::from_payload(payload).unwrap();
assert_eq!(status.wheel_mode, WheelMode::Ratchet);
assert_eq!(status.auto_disengage, 0xff);
assert_eq!(status.current_tunable_torque, 33);
}
#[test]
fn unknown_wheel_mode_is_an_unsupported_response() {
let mut payload = [0; 16];
payload[0] = 9;
let err = SmartShiftEnhancedStatus::from_payload(payload).unwrap_err();
assert!(matches!(err, Hidpp20Error::UnsupportedResponse));
}
}