Skip to main content

rm_frame/
remote.rs

1//! VT03/VT13 uplink control frame decoder.
2//!
3//! This module decodes the 21-byte uplink control payload transmitted by
4//! VT03/VT13 video transmitter links.
5//!
6//! The payload contains joystick channels, switch states, function keys,
7//! mouse deltas/buttons, and keyboard bitmask data.
8//!
9//! This format is independent from the standard `Messager` frame format
10//! (`SOF = 0xA5`) used by referee and custom command messages.
11//!
12//! # RC Frame Layout (21 bytes)
13//!
14//! All multi-byte values are little-endian.
15//!
16//! ```text
17//! +---------+----------------------+----------------------+-----------+--------+
18//! | Bytes   | Field                | Size                 | Note      | Source |
19//! +---------+----------------------+----------------------+-----------+--------+
20//! | 0..2    | SOF                  | 2 B                  | 0xA953    | raw    |
21//! | 2..10   | Data Group 1         | 8 B                  | bitfield  | raw    |
22//! | 10..17  | Data Group 2         | 7 B                  | bitfield  | raw    |
23//! | 17..19  | Keyboard bitmask     | 2 B                  | 16 keys   | raw    |
24//! | 19..21  | CRC16                | 2 B                  | over 0..19| raw    |
25//! +---------+----------------------+----------------------+-----------+--------+
26//! ```
27//!
28//! Internal storage expands Data Group 2 to `u64` by appending a zero byte as
29//! the highest byte, so bit extraction stays simple.
30//!
31//! # Data Group 1 Bit Mapping (`u64`)
32//!
33//! ```text
34//! bits  0..=10  : right_horizontal (11-bit channel, centered at 1024)
35//! bits 11..=21  : right_vertical   (11-bit channel, centered at 1024)
36//! bits 22..=32  : left_vertical    (11-bit channel, centered at 1024)
37//! bits 33..=43  : left_horizontal  (11-bit channel, centered at 1024)
38//! bits 44..=45  : switch           (0 = C, 1 = N, 2 = S)
39//! bit       46  : pause            (Boolean, true if pressed)
40//! bit       47  : left_fn          (Boolean, true if pressed)
41//! bit       48  : right_fn         (Boolean, true if pressed)
42//! bits 49..=59  : wheel            (11-bit channel, centered at 1024)
43//! bit       60  : trigger          (Boolean, true if pressed)
44//! ```
45//!
46//! # Data Group 2 Bit Mapping (`u64`)
47//!
48//! ```text
49//! bits  0..=15  : mouse_vx (i16)
50//! bits 16..=31  : mouse_vy (i16)
51//! bits 32..=47  : mouse_vz (i16)
52//! bits 48..=49  : left_button
53//! bits 50..=51  : right_button
54//! bits 52..=53  : mid_button
55//! ```
56//!
57//! Button extraction treats a non-zero 2-bit value as `pressed` for
58//! compatibility with existing upstream encoders.
59//!
60
61use crate::{DjiValidator, UnPackError, Validator};
62use atomic::{AtomicU16, AtomicU64, Ordering::Relaxed};
63use core::fmt::{Display, Formatter, Result as FmtResult};
64use core::marker::PhantomData;
65
66/// Start-of-frame marker for VT03/VT13 uplink stream
67/// (`0xA953`, little-endian bytes `[0xA9, 0x53]`).
68const SOF: u16 = 0x_53_A9;
69/// Electrical neutral position for 11-bit analog channels.
70const RC_CHANNEL_MID: i16 = 1024;
71/// Fixed size of one remote-control frame in bytes.
72const RC_FRAME_LENGTH: usize = 21;
73
74#[derive(Debug, Clone, Copy, PartialEq, Eq)]
75#[cfg_attr(feature = "defmt", derive(defmt::Format))]
76#[doc(alias("SwitchState", "3PosSwitch", "3WaySwitch"))]
77pub enum Switch {
78    /// Left position.
79    C = 0,
80    /// Middle position.
81    N = 1,
82    /// Right position.
83    S = 2,
84}
85
86/// Decoded view of the remote-control state.
87///
88/// The struct is designed for lock-free sharing across tasks/threads in
89/// embedded runtimes. `update` writes a new snapshot atomically per field, and
90/// all getters are wait-free atomic loads.
91///
92/// # Generic Parameter
93///
94/// `V` is the validator type used for CRC16 checksum verification. It must
95/// implement the [`Validator`] trait, which defines how to calculate the CRC16
96/// checksum for the frame. By default, `V` is set to [`DjiValidator`],
97/// which uses the standard DJI CRC16 algorithm.
98#[derive(Debug)]
99#[doc(alias("RemoteControlState", "RCState", "RemoteInput"))]
100pub struct RemoteControl<V: Validator = DjiValidator> {
101    /// Packed channels/switch/functions/wheel/trigger.
102    datagroup1: AtomicU64,
103    /// Packed mouse deltas and mouse buttons.
104    datagroup2: AtomicU64,
105    /// Packed keyboard bitmask (`W S A D Shift Ctrl Q E R F G Z X C V B`).
106    keyboard_v: AtomicU16,
107    /// Marker for the validator type.
108    _marker: PhantomData<V>,
109}
110
111impl<V: Validator> RemoteControl<V> {
112    /// Creates a zero-initialized remote-control state.
113    pub const fn new() -> RemoteControl<V> {
114        Self {
115            datagroup1: AtomicU64::new(0),
116            datagroup2: AtomicU64::new(0),
117            keyboard_v: AtomicU16::new(0),
118            _marker: PhantomData,
119        }
120    }
121
122    /// Parses one 21-byte VT03/VT13 uplink frame and updates internal state.
123    ///
124    /// Returns the number of consumed bytes (`21`) on success.
125    ///
126    /// # Errors
127    ///
128    /// - [`UnPackError::UnexpectedEnd`]: input shorter than 21 bytes.
129    /// - [`UnPackError::MissingHeader`]: SOF mismatch at frame start.
130    /// - [`UnPackError::InvalidChecksum`]: CRC16 mismatch (tail bytes 19..21).
131    pub fn update(&self, data: &[u8]) -> Result<usize, UnPackError> {
132        if data.len() < RC_FRAME_LENGTH {
133            return Err(UnPackError::UnexpectedEnd { read: data.len() });
134        }
135
136        let sof = u16::from_le_bytes([data[0], data[1]]);
137        if sof != SOF {
138            return Err(UnPackError::MissingHeader { skip: 1 });
139        }
140
141        let crc = u16::from_le_bytes([data[19], data[20]]);
142        let calc_crc = V::calculate_crc16(&data[..19]);
143        if crc != calc_crc {
144            return Err(UnPackError::InvalidChecksum { at: 19 });
145        }
146
147        let data = &data[2..19];
148        let datagroup1 = u64::from_le_bytes([
149            data[0], data[1], data[2], data[3], data[4], data[5], data[6], data[7],
150        ]);
151        let datagroup2 = u64::from_le_bytes([
152            data[8], data[9], data[10], data[11], data[12], data[13], data[14], 0,
153        ]);
154        let keyboard_v = u16::from_le_bytes([data[15], data[16]]);
155
156        self.datagroup1.store(datagroup1, Relaxed);
157        self.datagroup2.store(datagroup2, Relaxed);
158        self.keyboard_v.store(keyboard_v, Relaxed);
159
160        Ok(RC_FRAME_LENGTH)
161    }
162}
163
164impl<V: Validator> RemoteControl<V> {
165    fn get_datagroup1(&self) -> u64 {
166        self.datagroup1.load(Relaxed)
167    }
168
169    fn get_datagroup2(&self) -> u64 {
170        self.datagroup2.load(Relaxed)
171    }
172
173    fn get_keyboard_v(&self) -> u16 {
174        self.keyboard_v.load(Relaxed)
175    }
176}
177
178impl<V: Validator> RemoteControl<V> {
179    /// Right stick horizontal channel (`ch0`), (±660, centered at 0).
180    pub fn right_horizontal(&self) -> i16 {
181        let datagroup1 = self.get_datagroup1();
182        let right_horizontal = (datagroup1 & 0x7FF) as i16;
183        right_horizontal - RC_CHANNEL_MID
184    }
185
186    /// Right stick vertical channel (`ch1`), (±660, centered at 0).
187    pub fn right_vertical(&self) -> i16 {
188        let datagroup1 = self.get_datagroup1();
189        let right_vertical = ((datagroup1 >> 11) & 0x7FF) as i16;
190        right_vertical - RC_CHANNEL_MID
191    }
192
193    /// Left stick vertical channel (`ch2`), (±660, centered at 0).
194    pub fn left_vertical(&self) -> i16 {
195        let datagroup1 = self.get_datagroup1();
196        let left_vertical = ((datagroup1 >> 22) & 0x7FF) as i16;
197        left_vertical - RC_CHANNEL_MID
198    }
199
200    /// Left stick horizontal channel (`ch3`), (±660, centered at 0).
201    pub fn left_horizontal(&self) -> i16 {
202        let datagroup1 = self.get_datagroup1();
203        let left_horizontal = ((datagroup1 >> 33) & 0x7FF) as i16;
204        left_horizontal - RC_CHANNEL_MID
205    }
206
207    /// Three-position switch state (`C`/`N`/`S`).
208    pub fn switch(&self) -> Switch {
209        let datagroup1 = self.get_datagroup1();
210        let switch = ((datagroup1 >> 44) & 0x3) as u8;
211        match switch {
212            0 => Switch::C,
213            1 => Switch::N,
214            2 => Switch::S,
215            // Note: Only 2Bits are Used for Switch
216            _ => unreachable!(),
217        }
218    }
219
220    /// Pause key state, true if pressed.
221    pub fn pause(&self) -> bool {
222        let datagroup1 = self.get_datagroup1();
223        ((datagroup1 >> 46) & 0x1) != 0
224    }
225
226    /// Left function key state, true if pressed.
227    pub fn left_fn(&self) -> bool {
228        let datagroup1 = self.get_datagroup1();
229        ((datagroup1 >> 47) & 0x1) != 0
230    }
231
232    /// Right function key state, true if pressed.
233    pub fn right_fn(&self) -> bool {
234        let datagroup1 = self.get_datagroup1();
235        ((datagroup1 >> 48) & 0x1) != 0
236    }
237
238    /// Wheel channel, (±660, centered at 0).
239    pub fn wheel(&self) -> i16 {
240        let datagroup1 = self.get_datagroup1();
241        let wheel = ((datagroup1 >> 49) & 0x7FF) as i16;
242        wheel - RC_CHANNEL_MID
243    }
244
245    /// Trigger state, true if pressed.
246    pub fn trigger(&self) -> bool {
247        let datagroup1 = self.get_datagroup1();
248        ((datagroup1 >> 60) & 0x1) != 0
249    }
250}
251
252impl<V: Validator> RemoteControl<V> {
253    /// Mouse X delta: [`i16`].
254    pub fn mouse_vx(&self) -> i16 {
255        let datagroup2 = self.get_datagroup2();
256        (datagroup2 & 0xFFFF) as i16
257    }
258
259    /// Mouse Y delta: [`i16`].
260    pub fn mouse_vy(&self) -> i16 {
261        let datagroup2 = self.get_datagroup2();
262        ((datagroup2 >> 16) & 0xFFFF) as i16
263    }
264
265    /// Mouse wheel/scroll delta: [`i16`].
266    pub fn mouse_vz(&self) -> i16 {
267        let datagroup2 = self.get_datagroup2();
268        ((datagroup2 >> 32) & 0xFFFF) as i16
269    }
270
271    /// Left mouse button state, true if pressed.
272    pub fn left_button(&self) -> bool {
273        let datagroup2 = self.get_datagroup2();
274        ((datagroup2 >> 48) & 0x3) != 0
275    }
276
277    /// Right mouse button state, true if pressed.
278    pub fn right_button(&self) -> bool {
279        let datagroup2 = self.get_datagroup2();
280        ((datagroup2 >> 50) & 0x3) != 0
281    }
282
283    /// Middle mouse button state, true if pressed.
284    pub fn mid_button(&self) -> bool {
285        let datagroup2 = self.get_datagroup2();
286        ((datagroup2 >> 52) & 0x3) != 0
287    }
288}
289
290impl<V: Validator> RemoteControl<V> {
291    /// Keyboard `W` key state, true if pressed.
292    pub fn keyboard_w(&self) -> bool {
293        self.get_keyboard_v() & (1 << 0) != 0
294    }
295
296    /// Keyboard `S` key state, true if pressed.
297    pub fn keyboard_s(&self) -> bool {
298        (self.get_keyboard_v() & (1 << 1)) != 0
299    }
300
301    /// Keyboard `A` key state, true if pressed.
302    pub fn keyboard_a(&self) -> bool {
303        (self.get_keyboard_v() & (1 << 2)) != 0
304    }
305
306    /// Keyboard `D` key state, true if pressed.
307    pub fn keyboard_d(&self) -> bool {
308        (self.get_keyboard_v() & (1 << 3)) != 0
309    }
310
311    /// Keyboard `Shift` key state, true if pressed.
312    pub fn keyboard_shift(&self) -> bool {
313        (self.get_keyboard_v() & (1 << 4)) != 0
314    }
315
316    /// Keyboard `Ctrl` key state, true if pressed.
317    pub fn keyboard_ctrl(&self) -> bool {
318        (self.get_keyboard_v() & (1 << 5)) != 0
319    }
320
321    /// Keyboard `Q` key state, true if pressed.
322    pub fn keyboard_q(&self) -> bool {
323        (self.get_keyboard_v() & (1 << 6)) != 0
324    }
325
326    /// Keyboard `E` key state, true if pressed.
327    pub fn keyboard_e(&self) -> bool {
328        (self.get_keyboard_v() & (1 << 7)) != 0
329    }
330
331    /// Keyboard `R` key state, true if pressed.
332    pub fn keyboard_r(&self) -> bool {
333        (self.get_keyboard_v() & (1 << 8)) != 0
334    }
335
336    /// Keyboard `F` key state, true if pressed.
337    pub fn keyboard_f(&self) -> bool {
338        (self.get_keyboard_v() & (1 << 9)) != 0
339    }
340
341    /// Keyboard `G` key state, true if pressed.
342    pub fn keyboard_g(&self) -> bool {
343        (self.get_keyboard_v() & (1 << 10)) != 0
344    }
345
346    /// Keyboard `Z` key state, true if pressed.
347    pub fn keyboard_z(&self) -> bool {
348        (self.get_keyboard_v() & (1 << 11)) != 0
349    }
350
351    /// Keyboard `X` key state, true if pressed.
352    pub fn keyboard_x(&self) -> bool {
353        (self.get_keyboard_v() & (1 << 12)) != 0
354    }
355
356    /// Keyboard `C` key state, true if pressed.
357    pub fn keyboard_c(&self) -> bool {
358        (self.get_keyboard_v() & (1 << 13)) != 0
359    }
360
361    /// Keyboard `V` key state, true if pressed.
362    pub fn keyboard_v(&self) -> bool {
363        (self.get_keyboard_v() & (1 << 14)) != 0
364    }
365
366    /// Keyboard `B` key state, true if pressed.
367    pub fn keyboard_b(&self) -> bool {
368        (self.get_keyboard_v() & (1 << 15)) != 0
369    }
370}
371
372impl<V: Validator> Clone for RemoteControl<V> {
373    fn clone(&self) -> Self {
374        Self {
375            datagroup1: AtomicU64::new(self.get_datagroup1()),
376            datagroup2: AtomicU64::new(self.get_datagroup2()),
377            keyboard_v: AtomicU16::new(self.get_keyboard_v()),
378            _marker: PhantomData,
379        }
380    }
381}
382
383impl<V: Validator> Default for RemoteControl<V> {
384    fn default() -> Self {
385        Self::new()
386    }
387}
388
389impl<V: Validator> Display for RemoteControl<V> {
390    fn fmt(&self, f: &mut Formatter) -> FmtResult {
391        write!(
392            f,
393            "{{\n\tRH: {}, RV: {}, LV: {}, LH: {},\n\tSW: {:?}, P(HII): {},\n\tFn L: {}, R: {},\n\twheel: {}, trigger: {},\n\tMouse: X: {}, Y: {}, Z: {},\n\tMouse: L: {}, M: {}, R: {},\n\tKeyBoard: 0b{:016b}\n}}",
394            self.right_horizontal(),
395            self.right_vertical(),
396            self.left_vertical(),
397            self.left_horizontal(),
398            self.switch(),
399            self.pause(),
400            self.left_fn(),
401            self.right_fn(),
402            self.wheel(),
403            self.trigger(),
404            self.mouse_vx(),
405            self.mouse_vy(),
406            self.mouse_vz(),
407            self.left_button(),
408            self.mid_button(),
409            self.right_button(),
410            self.get_keyboard_v()
411        )
412    }
413}
414
415#[cfg(feature = "defmt")]
416impl<V: Validator> defmt::Format for RemoteControl<V> {
417    fn format(&self, fmt: defmt::Formatter) {
418        defmt::write!(
419            fmt,
420            "{{\n\tRH: {}, RV: {}, LV: {}, LH: {},\n\tSW: {:?}, P(HII): {},\n\tFn L: {}, R: {},\n\twheel: {}, trigger: {},\n\tMouse: X: {}, Y: {}, Z: {},\n\tMouse: L: {}, M: {}, R: {},\n\tKeyBoard: 0b{:016b}\n}}",
421            self.right_horizontal(),
422            self.right_vertical(),
423            self.left_vertical(),
424            self.left_horizontal(),
425            self.switch(),
426            self.pause(),
427            self.left_fn(),
428            self.right_fn(),
429            self.wheel(),
430            self.trigger(),
431            self.mouse_vx(),
432            self.mouse_vy(),
433            self.mouse_vz(),
434            self.left_button(),
435            self.mid_button(),
436            self.right_button(),
437            self.get_keyboard_v()
438        );
439    }
440}