Skip to main content

dshot_codec/
dshot_commands.rs

1use super::DshotError;
2
3#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
4pub enum DshotCommand {
5    #[default]
6    MotorStop = 0,
7    /// Wait at least 260ms before next command.
8    Beep1,
9    /// Wait at least 260ms before next command.
10    Beep2,
11    /// Wait at least 260ms before next command.
12    Beep3,
13    /// Wait at least 260ms before next command.
14    Beep4,
15    /// Wait at least 260ms before next command.
16    Beep5,
17    /// Wait at least 12ms before next command.
18    EscInfo,
19    /// Needs 6 transmissions.
20    SpinDirection1,
21    /// Needs 6 transmissions.
22    SpinDirection2,
23    /// Needs 6 transmissions.
24    ThreeDModeOn,
25    /// Needs 6 transmissions.
26    ThreeDModeOff,
27    SettingsRequest,
28    /// Needs 6 transmissions. Wait at least 35ms before next command.
29    SettingsSave,
30    /// Needs 6 transmissions.
31    ExtendedTelemetryEnable,
32    /// Needs 6 transmissions.
33    ExtendedTelemetryDisable,
34
35    // 15-19 are unassigned.
36    /// Needs 6 transmissions.
37    SpinDirectionNormal = 20,
38    /// Needs 6 transmissions.
39    SpinDirectionReversed,
40    Led0On,
41    Led1On,
42    Led2On,
43    Led3On,
44    Led0Off,
45    Led1Off,
46    Led2Off,
47    Led3Off,
48    AudioStreamModeToggle,
49    SilentModeToggle,
50    /// Needs 6 transmissions. Enables individual signal line commands.
51    SignalLineTelemetryEnable,
52    /// Needs 6 transmissions. Disables individual signal line commands.
53    SignalLineTelemetryDisable,
54    /// Needs 6 transmissions. Enables individual signal line commands.
55    SignalLineContinuousErpmTelemetry,
56    /// Needs 6 transmissions. Enables individual signal line commands.
57    SignalLineContinuousErpmPeriodTelemetry,
58
59    // 36-41 are unassigned.
60    /// Temperature: 1ÂșC per LSB.
61    SignalLineTemperatureTelemetry = 42,
62    /// Voltage: 10mV per LSB, 40.95V max.
63    SignalLineVoltageTelemetry,
64    /// Current: 100mA per LSB, 409.5A max.
65    SignalLineCurrentTelemetry,
66    /// Power: 10mAh per LSB, 40.95Ah max.
67    SignalLineConsumptionTelemetry,
68    /// erpm: 100erpm per LSB, 409500erpm max.
69    SignalLineErpmTelemetry,
70    /// 16us per LSB, 65520us max.
71    SignalLineErpmPeriodTelemetry,
72}
73
74impl TryFrom<u8> for DshotCommand {
75    type Error = DshotError;
76
77    /// Validating conversion from `u8` to `Command`. Invalid values return error.
78    fn try_from(value: u8) -> Result<Self, DshotError> {
79        let default = Self::default();
80        if value == default as u8 {
81            Ok(default)
82        } else {
83            let ret = Self::from_u8(value);
84            if ret == default {
85                Err(DshotError::InvalidDshotCommand)
86            } else {
87                Ok(ret)
88            }
89        }
90    }
91}
92
93impl DshotCommand {
94    /// Forgiving conversion from `u8` to `Command`, converts invalid values to default.
95    #[must_use]
96    pub fn from_u8(value: u8) -> Self {
97        match value {
98            0 => Self::MotorStop,
99            1 => Self::Beep1,
100            2 => Self::Beep2,
101            3 => Self::Beep3,
102            4 => Self::Beep4,
103            5 => Self::Beep5,
104            6 => Self::EscInfo,
105            7 => Self::SpinDirection1,
106            8 => Self::SpinDirection2,
107            9 => Self::ThreeDModeOn,
108            10 => Self::ThreeDModeOff,
109            11 => Self::SettingsRequest,
110            12 => Self::SettingsSave,
111            13 => Self::ExtendedTelemetryEnable,
112            14 => Self::ExtendedTelemetryDisable,
113            20 => Self::SpinDirectionNormal,
114            21 => Self::SpinDirectionReversed,
115            22 => Self::Led0On,
116            23 => Self::Led1On,
117            24 => Self::Led2On,
118            25 => Self::Led3On,
119            26 => Self::Led0Off,
120            27 => Self::Led1Off,
121            28 => Self::Led2Off,
122            29 => Self::Led3Off,
123            30 => Self::AudioStreamModeToggle,
124            31 => Self::SilentModeToggle,
125            32 => Self::SignalLineTelemetryEnable,
126            33 => Self::SignalLineTelemetryDisable,
127            34 => Self::SignalLineContinuousErpmTelemetry,
128            35 => Self::SignalLineContinuousErpmPeriodTelemetry,
129            // 36-41 are unassigned.
130            42 => Self::SignalLineTemperatureTelemetry,
131            43 => Self::SignalLineVoltageTelemetry,
132            44 => Self::SignalLineCurrentTelemetry,
133            45 => Self::SignalLineConsumptionTelemetry,
134            46 => Self::SignalLineErpmTelemetry,
135            47 => Self::SignalLineErpmPeriodTelemetry,
136            _ => Self::default(),
137        }
138    }
139}
140
141impl DshotCommand {
142    #[must_use]
143    pub const fn repetitions_required(self) -> u8 {
144        match self {
145            // Protocol documentation often states 6 repeats for these commands.
146            // We use 10, like Betaflight, as a conservative measure.
147            Self::SpinDirection1
148            | Self::SpinDirection2
149            | Self::ThreeDModeOn
150            | Self::ThreeDModeOff
151            | Self::SettingsSave
152            | Self::ExtendedTelemetryEnable
153            | Self::ExtendedTelemetryDisable
154            | Self::SpinDirectionNormal
155            | Self::SpinDirectionReversed
156            | Self::SignalLineTelemetryEnable
157            | Self::SignalLineTelemetryDisable
158            | Self::SignalLineContinuousErpmTelemetry
159            | Self::SignalLineContinuousErpmPeriodTelemetry => 10,
160            _ => 1,
161        }
162    }
163
164    #[allow(unused)]
165    #[must_use]
166    pub const fn delay_required_us(self) -> u32 {
167        match self {
168            Self::Beep1 | Self::Beep2 | Self::Beep3 | Self::Beep4 | Self::Beep5 => 260_000,
169            Self::EscInfo => 12_000,
170            Self::SettingsSave => 35_000,
171            _ => 0,
172        }
173    }
174}
175
176#[cfg(test)]
177mod test_traits {
178    use super::*;
179
180    fn is_full_eq<T: Sized + Send + Sync + Unpin + Copy + Clone + Default + Eq + PartialEq>() {}
181
182    #[test]
183    fn normal_types() {
184        is_full_eq::<DshotCommand>();
185    }
186}
187
188#[cfg(test)]
189mod tests {
190    use super::*;
191
192    #[test]
193    fn u8_to_command() {
194        // Test forgiving conversions.
195        assert_eq!(DshotCommand::from_u8(0), DshotCommand::MotorStop);
196        assert_eq!(DshotCommand::from_u8(1), DshotCommand::Beep1);
197        assert_eq!(DshotCommand::from_u8(35), DshotCommand::SignalLineContinuousErpmPeriodTelemetry);
198        assert_eq!(DshotCommand::from_u8(36), DshotCommand::MotorStop);
199        assert_eq!(DshotCommand::from_u8(37), DshotCommand::MotorStop);
200        assert_eq!(DshotCommand::from_u8(38), DshotCommand::MotorStop);
201        assert_eq!(DshotCommand::from_u8(39), DshotCommand::MotorStop);
202        assert_eq!(DshotCommand::from_u8(40), DshotCommand::MotorStop);
203        assert_eq!(DshotCommand::from_u8(41), DshotCommand::MotorStop);
204        assert_eq!(DshotCommand::from_u8(42), DshotCommand::SignalLineTemperatureTelemetry);
205        assert_eq!(DshotCommand::from_u8(47), DshotCommand::SignalLineErpmPeriodTelemetry);
206        assert_eq!(DshotCommand::from_u8(48), DshotCommand::MotorStop);
207        assert_eq!(DshotCommand::from_u8(49), DshotCommand::MotorStop);
208        assert_eq!(DshotCommand::from_u8(50), DshotCommand::MotorStop);
209        assert_eq!(DshotCommand::from_u8(255), DshotCommand::MotorStop);
210
211        // Test unforgiving conversions.
212        assert_eq!(DshotCommand::try_from(0), Ok(DshotCommand::MotorStop));
213        assert_eq!(DshotCommand::try_from(1), Ok(DshotCommand::Beep1));
214        assert_eq!(DshotCommand::try_from(35), Ok(DshotCommand::SignalLineContinuousErpmPeriodTelemetry));
215        assert_eq!(DshotCommand::try_from(36), Err(DshotError::InvalidDshotCommand));
216        assert_eq!(DshotCommand::try_from(37), Err(DshotError::InvalidDshotCommand));
217        assert_eq!(DshotCommand::try_from(38), Err(DshotError::InvalidDshotCommand));
218        assert_eq!(DshotCommand::try_from(39), Err(DshotError::InvalidDshotCommand));
219        assert_eq!(DshotCommand::try_from(40), Err(DshotError::InvalidDshotCommand));
220        assert_eq!(DshotCommand::try_from(41), Err(DshotError::InvalidDshotCommand));
221        assert_eq!(DshotCommand::try_from(42), Ok(DshotCommand::SignalLineTemperatureTelemetry));
222        assert_eq!(DshotCommand::try_from(47), Ok(DshotCommand::SignalLineErpmPeriodTelemetry));
223        assert_eq!(DshotCommand::try_from(48), Err(DshotError::InvalidDshotCommand));
224        assert_eq!(DshotCommand::try_from(49), Err(DshotError::InvalidDshotCommand));
225        assert_eq!(DshotCommand::try_from(50), Err(DshotError::InvalidDshotCommand));
226        assert_eq!(DshotCommand::try_from(255), Err(DshotError::InvalidDshotCommand));
227    }
228}