vex_cdc/cdc2/
system.rs

1//! VEXos system packets.
2
3use core::u8;
4
5use alloc::{
6    string::{String, ToString},
7    vec::Vec,
8};
9
10use crate::{
11    Decode, DecodeError, DecodeWithLength, Encode, FixedString, Version,
12    cdc::cmds::USER_CDC,
13    cdc2::{
14        Cdc2CommandPacket, Cdc2ReplyPacket,
15        ecmds::{
16            DEV_STATUS, FDT_STATUS, LOG_READ, LOG_STATUS, RADIO_STATUS, SYS_C_INFO_14,
17            SYS_C_INFO_58, SYS_DASH_SEL, SYS_DASH_TOUCH, SYS_FLAGS, SYS_KV_LOAD, SYS_KV_SAVE,
18            SYS_SCREEN_CAP, SYS_STATUS, SYS_USER_PROG,
19        },
20    },
21    decode::DecodeErrorKind,
22};
23
24pub struct SystemFlags {
25    /// Bit mask.
26    /// From left to right:
27    /// no.1 to no.8 bit - Page index
28    /// no.12 bit = Radio Data mode on
29    /// no.14 bit = Brain button double clicked
30    /// no.15 bit = Battery is charging
31    /// no.17 bit = Brain button clicked
32    /// no.18 bit = Is VexNet mode
33    /// no.19 bit = Has partner controller
34    /// no.22 bit = Radio connected
35    /// no.23 bit = Radio available
36    /// no.24 bit = Controller tethered
37    /// no.30 bit = Page changed
38    /// no.32 bit = Device added/removed
39    /// (RESEARCH NEEDED)
40    pub flags: u32,
41
42    /// Battery percent = First four bits * 8
43    /// Controller battery percent = Last four bits * 8
44    pub byte_1: u8,
45
46    /// Radio quality = First four bits * 8
47    /// Partner controller battery percent = Last four bits * 8
48    pub byte_2: u8,
49
50    /// The current program slot number, 0 means not in a program.
51    /// 129 = ClawBot program
52    /// 145 = Driver program
53    pub current_program: u8,
54}
55impl Decode for SystemFlags {
56    fn decode(data: &mut &[u8]) -> Result<Self, DecodeError> {
57        let flags = u32::decode(data)?;
58        let byte_1 = u8::decode(data)?;
59        let byte_2 = u8::decode(data)?;
60        let current_program = u8::decode(data)?;
61
62        Ok(Self {
63            flags,
64            byte_1,
65            byte_2,
66            current_program,
67        })
68    }
69}
70
71#[derive(Debug, Clone, Copy, Eq, PartialEq)]
72pub struct SystemStatus {
73    /// Always zero as of VEXos 1.1.5
74    pub reserved: u8,
75    /// returns None when connected via controller
76    pub system_version: Option<Version>,
77    pub cpu0_version: Version,
78    pub cpu1_version: Version,
79    pub touch_version: Version,
80    pub details: Option<SystemDetails>,
81}
82impl Decode for SystemStatus {
83    fn decode(data: &mut &[u8]) -> Result<Self, DecodeError> {
84        let reserved = u8::decode(data)?;
85        let system_version = match Version::decode(data)? {
86            Version {
87                major: 0,
88                minor: 0,
89                build: 0,
90                beta: 0,
91            } => None,
92            version => Some(version),
93        };
94
95        let cpu0_version = Version::decode(data)?;
96        let cpu1_version = Version::decode(data)?;
97
98        // This version is little endian for some reason
99        let touch_version = Version {
100            beta: u8::decode(data)?,
101            build: u8::decode(data)?,
102            minor: u8::decode(data)?,
103            major: u8::decode(data)?,
104        };
105
106        let details = match SystemDetails::decode(data) {
107            Ok(details) => Some(details),
108            Err(e) if e.kind() == DecodeErrorKind::UnexpectedEnd => None,
109            Err(e) => return Err(e),
110        };
111
112        Ok(Self {
113            reserved,
114            system_version,
115            cpu0_version,
116            cpu1_version,
117            touch_version,
118            details,
119        })
120    }
121}
122
123#[derive(Debug, Clone, Copy, Eq, PartialEq)]
124pub struct SystemDetails {
125    /// Unique ID for the Brain.
126    pub ssn: u32,
127    pub boot_flags: u32,
128    pub system_flags: u32,
129    pub golden_version: Version,
130    pub nxp_version: Option<Version>,
131}
132impl Decode for SystemDetails {
133    fn decode(data: &mut &[u8]) -> Result<Self, DecodeError> {
134        let ssn = u32::decode(data)?;
135        let boot_flags = u32::decode(data)?;
136        let system_flags = u32::decode(data)?;
137        let golden_version = Version::decode(data)?;
138        let nxp_version = match Version::decode(data) {
139            Ok(version) => Some(version),
140            Err(e) if e.kind() == DecodeErrorKind::UnexpectedEnd => None,
141            Err(e) => return Err(e),
142        };
143
144        Ok(Self {
145            ssn,
146            boot_flags,
147            system_flags,
148            golden_version,
149            nxp_version,
150        })
151    }
152}
153
154pub type SystemFlagsPacket = Cdc2CommandPacket<USER_CDC, SYS_FLAGS, ()>;
155pub type SystemFlagsReplyPacket = Cdc2ReplyPacket<USER_CDC, SYS_FLAGS, SystemFlags>;
156
157pub type SystemStatusPacket = Cdc2CommandPacket<USER_CDC, SYS_STATUS, ()>;
158pub type SystemStatusReplyPacket = Cdc2ReplyPacket<USER_CDC, SYS_STATUS, SystemStatus>;
159
160#[derive(Debug, Clone, Copy, Eq, PartialEq)]
161pub struct LogEntry {
162    /// (RESEARCH NEEDED)
163    pub code: u8,
164
165    /// The subtype under the description (RESEARCH NEEDED)
166    pub log_type: u8,
167
168    /// The type of the log message (RESEARCH NEEDED)
169    pub description: u8,
170
171    /// (RESEARCH NEEDED)
172    pub spare: u8,
173
174    /// How long (in milliseconds) after the brain powered on
175    pub time: u32,
176}
177impl Decode for LogEntry {
178    fn decode(data: &mut &[u8]) -> Result<Self, DecodeError> {
179        let code = u8::decode(data)?;
180        let log_type = u8::decode(data)?;
181        let description = u8::decode(data)?;
182        let spare = u8::decode(data)?;
183        let time = u32::decode(data)?;
184
185        Ok(Self {
186            code,
187            log_type,
188            description,
189            spare,
190            time,
191        })
192    }
193}
194
195pub type LogStatusPacket = Cdc2CommandPacket<USER_CDC, LOG_STATUS, ()>;
196pub type LogStatusReplyPacket = Cdc2ReplyPacket<USER_CDC, LOG_STATUS, LogStatusReplyPayload>;
197
198#[derive(Debug, Clone, Copy, Eq, PartialEq)]
199pub struct LogStatusReplyPayload {
200    /// Always zero as of VEXos 1.1.5
201    pub reserved_1: u8,
202
203    /// Total number of recorded event logs.
204    pub count: u32,
205
206    /// Always zero as of VEXos 1.1.5
207    pub reserved_2: u32,
208
209    /// Always zero as of VEXos 1.1.5
210    pub reserved_3: u32,
211
212    /// Always zero as of VEXos 1.1.5
213    pub reserved_4: u32,
214}
215impl Decode for LogStatusReplyPayload {
216    fn decode(data: &mut &[u8]) -> Result<Self, DecodeError> {
217        let reserved = u8::decode(data)?;
218        let count = u32::decode(data)?;
219        let reserved_2 = u32::decode(data)?;
220        let reserved_3 = u32::decode(data)?;
221        let reserved_4 = u32::decode(data)?;
222
223        Ok(Self {
224            reserved_1: reserved,
225            count,
226            reserved_2,
227            reserved_3,
228            reserved_4,
229        })
230    }
231}
232
233/// For example: If the brain has 26 logs, from A to Z. With offset 5 and count 5, it returns [V, W, X, Y, Z]. With offset 10 and count 5, it returns [Q, R, S, T, U].
234pub type LogReadPacket = Cdc2CommandPacket<USER_CDC, LOG_READ, LogReadPayload>;
235pub type LogReadReplyPacket = Cdc2ReplyPacket<USER_CDC, LOG_READ, LogReadReplyPayload>;
236
237#[derive(Debug, Clone, Copy, Eq, PartialEq)]
238pub struct LogReadPayload {
239    pub offset: u32,
240    pub count: u32,
241}
242impl Encode for LogReadPayload {
243    fn size(&self) -> usize {
244        8
245    }
246
247    fn encode(&self, data: &mut [u8]) {
248        self.offset.encode(data);
249        self.count.encode(&mut data[4..]);
250    }
251}
252
253#[derive(Debug, Clone, Eq, PartialEq)]
254pub struct LogReadReplyPayload {
255    /// Size of each log item in bytes.
256    pub log_size: u8,
257    /// The offset number used in this packet.
258    pub offset: u32,
259    /// Number of elements in the following array.
260    pub count: u16,
261    pub entries: Vec<LogEntry>,
262}
263impl Decode for LogReadReplyPayload {
264    fn decode(data: &mut &[u8]) -> Result<Self, DecodeError> {
265        let log_size = u8::decode(data)?;
266        let offset = u32::decode(data)?;
267        let count = u16::decode(data)?;
268        let entries = Vec::decode_with_len(data, count as _)?;
269
270        Ok(Self {
271            log_size,
272            offset,
273            count,
274            entries,
275        })
276    }
277}
278
279pub type KeyValueLoadPacket = Cdc2CommandPacket<USER_CDC, SYS_KV_LOAD, FixedString<31>>;
280pub type KeyValueLoadReplyPacket = Cdc2ReplyPacket<USER_CDC, SYS_KV_LOAD, FixedString<255>>;
281
282pub type KeyValueSavePacket = Cdc2CommandPacket<USER_CDC, SYS_KV_SAVE, KeyValueSavePayload>;
283pub type KeyValueSaveReplyPacket = Cdc2ReplyPacket<USER_CDC, SYS_KV_SAVE, ()>;
284
285#[derive(Debug, Clone, Eq, PartialEq)]
286pub struct KeyValueSavePayload {
287    pub key: FixedString<31>,
288    pub value: FixedString<255>,
289}
290impl Encode for KeyValueSavePayload {
291    fn size(&self) -> usize {
292        self.key.size() + self.value.size()
293    }
294
295    fn encode(&self, data: &mut [u8]) {
296        self.key.to_string().encode(data);
297        self.value.to_string().encode(&mut data[self.key.size()..]);
298    }
299}
300
301#[derive(Debug, Clone, Eq, PartialEq)]
302pub struct Slot {
303    /// The number in the file icon: 'USER???x.bmp'.
304    pub icon_number: u16,
305    pub name_length: u8,
306    pub name: String,
307}
308impl Decode for Slot {
309    fn decode(data: &mut &[u8]) -> Result<Self, DecodeError> {
310        let icon_number = u16::decode(data)?;
311        let name_length = u8::decode(data)?;
312        let name = String::decode_with_len(data, (name_length - 1) as _)?;
313
314        Ok(Self {
315            icon_number,
316            name_length,
317            name,
318        })
319    }
320}
321
322pub type CatalogSlot1To4InfoPacket = Cdc2CommandPacket<USER_CDC, SYS_C_INFO_14, ()>;
323pub type CatalogSlot1To4InfoReplyPacket =
324    Cdc2CommandPacket<USER_CDC, SYS_C_INFO_14, SlotInfoPayload>;
325pub type CatalogSlot5To8InfoPacket = Cdc2CommandPacket<USER_CDC, SYS_C_INFO_58, ()>;
326pub type CatalogSlot5To8InfoReplyPacket =
327    Cdc2CommandPacket<USER_CDC, SYS_C_INFO_58, SlotInfoPayload>;
328
329#[derive(Debug, Clone, Eq, PartialEq)]
330pub struct SlotInfoPayload {
331    /// Bit Mask.
332    ///
333    /// `flags & 2^(x - 1)` = Is slot x used
334    pub flags: u8,
335
336    /// Individual Slot Data
337    pub slots: [Slot; 4],
338}
339
340impl Decode for SlotInfoPayload {
341    fn decode(data: &mut &[u8]) -> Result<Self, DecodeError> {
342        let flags = u8::decode(data)?;
343        let slots = <[Slot; 4]>::decode(data)?;
344
345        Ok(Self { flags, slots })
346    }
347}
348
349pub type ProgramControlPacket = Cdc2CommandPacket<USER_CDC, SYS_USER_PROG, ()>;
350pub type ProgramControlReplyPacket = Cdc2CommandPacket<USER_CDC, SYS_USER_PROG, ()>;
351
352#[derive(Debug, Clone, Copy, Eq, PartialEq)]
353#[repr(u8)]
354pub enum DashScreen {
355    /// Home screen
356    Home = 0,
357
358    /// Devices -> Battery
359    Battery = 1,
360
361    /// Devices -> Motor (program running)
362    // Motor = 2,
363
364    /// Unused Test Device
365    Led = 3,
366
367    /// Program -> Match
368    Match = 4,
369
370    /// Program -> Timed Run
371    TimedRun = 5,
372
373    /// Program -> Wiring
374    Wiring = 6,
375
376    /// Devices -> Partner
377    // Controller2 = 7,
378
379    /// Devices -> Radio
380    Radio = 8,
381
382    /// Devices -> Controller 1
383    Controller1 = 9,
384
385    /// Devices -> Brain
386    Brain = 10,
387
388    /// Devices -> Camera
389    // Camera = 11,
390
391    /// Devices -> Three-wire Ports
392    // ThreeWire = 12,
393
394    /// Program -> Run
395    Running = 13,
396
397    /// Program -> Controls
398    ControlsA = 14,
399
400    /// Default Drive Program
401    Drive = 15,
402
403    /// Devices Menu
404    Devices = 16,
405
406    /// Home -> User Folder
407    UserFolder = 17,
408
409    /// Home -> VEX Folder
410    VexFolder = 18,
411
412    /// Home -> Settings
413    Settings = 19,
414
415    /// Development Menu (be careful!!!)
416    Config = 20,
417
418    /// Settings -> Language (also shown on first boot)
419    Language = 21,
420
421    /// Drive -> Reverse
422    MotorReverse = 22,
423
424    /// Confirmation Screen (used to confirm a bunch of different settings changes)
425    Confirm = 23,
426
427    /// Program Menu
428    UserProgram = 24,
429
430    /// Shutdown Screen
431    Off = 25,
432
433    /// User controls for Controller 2 (Partner)
434    Controller2Mapping = 26,
435
436    /// Development Menu (be careful!!!)
437    Config2 = 27,
438
439    /// Error/Alert Screens
440    Alert = 28,
441
442    /// User controls for Controller 2 (Master)
443    Controller1Mapping = 29,
444
445    /// Drive -> Controls
446    ControlsB = 30,
447
448    /// Drive -> Controls
449    ControlsC = 31,
450
451    /// Drive -> Controls
452    ControlsD = 32,
453
454    /// (UNUSED) Multiplayer Match Screen
455    Multiplayer = 33,
456
457    /// Devices -> Brain -> Event Log
458    EventLog = 34,
459
460    /// Devices -> Motor (no program running)
461    Motor2 = 35,
462
463    // Test = 36,
464    /// Program -> Wiring
465    UserWiring = 40,
466
467    /// Clawbot Program Run Screen
468    Clawbot = 41,
469
470    /// Settings -> Regulatory
471    About = 42,
472
473    /// Settings -> Language -> (more)
474    Language2 = 43,
475
476    /// Devices -> Camera -> Change Color
477    Colors = 45,
478
479    /// Devices -> Vision -> Select Signature
480    SelectSignature = 46,
481
482    /// (unknown)
483    LogInfo = 47,
484    // Abs = 48,
485    // Imu = 49,
486    // Color = 50,
487    // Magnet = 51,
488    // Distance = 52,
489    // DistanceDev = 53,
490    // Gps = 54,
491    // AiCamera = 55,
492    // LightTower = 56,
493    // Arm = 57,
494    // AiVision = 58,
495    // Pneumatic = 59,
496}
497
498pub type DashTouchPacket = Cdc2CommandPacket<USER_CDC, SYS_DASH_TOUCH, DashTouchPayload>;
499pub type DashTouchReplyPacket = Cdc2ReplyPacket<USER_CDC, SYS_DASH_TOUCH, ()>;
500
501#[derive(Debug, Clone, Copy, Eq, PartialEq)]
502pub struct DashTouchPayload {
503    pub x: u16,
504    pub y: u16,
505    /// 1 for pressing, 0 for released
506    pub pressing: u16,
507}
508impl Encode for DashTouchPayload {
509    fn size(&self) -> usize {
510        6
511    }
512
513    fn encode(&self, data: &mut [u8]) {
514        self.x.encode(data);
515        self.y.encode(&mut data[2..]);
516        self.pressing.encode(&mut data[4..]);
517    }
518}
519
520pub type DashSelectPacket = Cdc2CommandPacket<USER_CDC, SYS_DASH_SEL, DashSelectPayload>;
521pub type DashSelectReplyPacket = Cdc2ReplyPacket<USER_CDC, SYS_DASH_SEL, ()>;
522
523#[derive(Debug, Clone, Copy, Eq, PartialEq)]
524pub struct DashSelectPayload {
525    pub screen: DashScreen,
526
527    /// This serves as a generic argument to the dash
528    /// screen to select its "variant". It's named this
529    /// because it's used to select a specific port number
530    /// on a device screen.
531    pub port: u8,
532}
533impl Encode for DashSelectPayload {
534    fn size(&self) -> usize {
535        2
536    }
537
538    fn encode(&self, data: &mut [u8]) {
539        data[0] = self.screen as _;
540        data[1] = self.port;
541    }
542}
543
544pub type ScreenCapturePacket = Cdc2CommandPacket<USER_CDC, SYS_SCREEN_CAP, ScreenCapturePayload>;
545pub type ScreenCaptureReplyPacket = Cdc2ReplyPacket<USER_CDC, SYS_SCREEN_CAP, ()>;
546
547#[derive(Debug, Clone, Copy, Eq, PartialEq)]
548pub struct ScreenCapturePayload {
549    /// Optionally, a specific LogiCVC layer to capture.
550    pub layer: Option<u8>,
551}
552impl Encode for ScreenCapturePayload {
553    fn size(&self) -> usize {
554        if self.layer.is_some() { 1 } else { 0 }
555    }
556
557    fn encode(&self, data: &mut [u8]) {
558        if let Some(layer) = self.layer {
559            data[0] = layer;
560        }
561    }
562}
563
564// This is copied from vex-sdk
565#[derive(Debug, Copy, Clone, Eq, PartialEq)]
566#[repr(u8)]
567pub enum DeviceType {
568    NoSensor = 0,
569    Motor = 2,
570    Led = 3,
571    AbsEncoder = 4,
572    CrMotor = 5,
573    Imu = 6,
574    DistanceSensor = 7,
575    Radio = 8,
576    TetheredController = 9,
577    Brain = 10,
578    VisionSensor = 11,
579    AdiExpander = 12,
580    Res1Sensor = 13,
581    Battery = 14,
582    Res3Sensor = 15,
583    OpticalSensor = 16,
584    Magnet = 17,
585    GpsSensor = 20,
586    AicameraSensor = 26,
587    LightTower = 27,
588    ArmDevice = 28,
589    AiVisionSensor = 29,
590    Pneumatic = 30,
591    BumperSensor = 0x40,
592    GyroSensor = 0x46,
593    SonarSensor = 0x47,
594    GenericSensor = 128,
595    GenericSerial = 129,
596    UndefinedSensor = 255,
597}
598impl Decode for DeviceType {
599    fn decode(data: &mut &[u8]) -> Result<Self, DecodeError> {
600        let value = u8::decode(data)?;
601        Ok(match value {
602            0 => DeviceType::NoSensor,
603            2 => DeviceType::Motor,
604            3 => DeviceType::Led,
605            4 => DeviceType::AbsEncoder,
606            5 => DeviceType::CrMotor,
607            6 => DeviceType::Imu,
608            7 => DeviceType::DistanceSensor,
609            8 => DeviceType::Radio,
610            9 => DeviceType::TetheredController,
611            10 => DeviceType::Brain,
612            11 => DeviceType::VisionSensor,
613            12 => DeviceType::AdiExpander,
614            13 => DeviceType::Res1Sensor,
615            14 => DeviceType::Battery,
616            15 => DeviceType::Res3Sensor,
617            16 => DeviceType::OpticalSensor,
618            17 => DeviceType::Magnet,
619            20 => DeviceType::GpsSensor,
620            26 => DeviceType::AicameraSensor,
621            27 => DeviceType::LightTower,
622            28 => DeviceType::ArmDevice,
623            29 => DeviceType::AiVisionSensor,
624            30 => DeviceType::Pneumatic,
625            0x40 => DeviceType::BumperSensor,
626            0x46 => DeviceType::GyroSensor,
627            0x47 => DeviceType::SonarSensor,
628            128 => DeviceType::GenericSensor,
629            129 => DeviceType::GenericSerial,
630            255 => DeviceType::UndefinedSensor,
631            _ => {
632                return Err(DecodeError::new::<Self>(DecodeErrorKind::UnexpectedByte {
633                    name: "DeviceType",
634                    value,
635                    expected: &[
636                        0, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 20, 26, 27, 28,
637                        29, 30, 64, 70, 71, 128, 129, 255,
638                    ],
639                }));
640            }
641        })
642    }
643}
644
645#[derive(Debug, Clone, Copy, Eq, PartialEq)]
646pub struct DeviceStatus {
647    /// 1-indexed smart port number. Port 22 is the internal ADI expander and Port 23 is the battery.
648    pub port: u8,
649
650    /// Following V5_DeviceType
651    pub device_type: DeviceType,
652
653    /// 1 = smart port device, 0 = otherwise. (UNCONFIRMED)
654    pub status: u8,
655    pub beta_version: u8,
656    pub version: u16,
657    pub boot_version: u16,
658}
659impl Decode for DeviceStatus {
660    fn decode(data: &mut &[u8]) -> Result<Self, DecodeError> {
661        let port = u8::decode(data)?;
662        let device_type = DeviceType::decode(data)?;
663        let status = u8::decode(data)?;
664        let beta_version = u8::decode(data)?;
665        let version = u16::decode(data)?;
666        let boot_version = u16::decode(data)?;
667
668        Ok(Self {
669            port,
670            device_type,
671            status,
672            beta_version,
673            version,
674            boot_version,
675        })
676    }
677}
678
679pub type DeviceStatusPacket = Cdc2CommandPacket<USER_CDC, DEV_STATUS, ()>;
680pub type DeviceStatusReplyPacket = Cdc2ReplyPacket<USER_CDC, DEV_STATUS, DeviceStatusReplyPayload>;
681
682#[derive(Debug, Clone, Eq, PartialEq)]
683pub struct DeviceStatusReplyPayload {
684    /// Number of elements in the following array.
685    pub count: u8,
686    pub devices: Vec<DeviceStatus>,
687}
688impl Decode for DeviceStatusReplyPayload {
689    fn decode(data: &mut &[u8]) -> Result<Self, DecodeError> {
690        let count = u8::decode(data)?;
691        let devices = Vec::decode_with_len(data, count as _)?;
692        Ok(Self { count, devices })
693    }
694}
695
696#[derive(Debug, Clone, Eq, PartialEq)]
697pub struct FdtStatus {
698    pub count: u8,
699    pub files: Vec<Fdt>,
700}
701impl Decode for FdtStatus {
702    fn decode(data: &mut &[u8]) -> Result<Self, DecodeError> {
703        let count = u8::decode(data)?;
704        let entries = Vec::decode_with_len(data, count as _)?;
705
706        Ok(Self {
707            count,
708            files: entries,
709        })
710    }
711}
712
713#[derive(Debug, Clone, Copy, Eq, PartialEq)]
714pub struct Fdt {
715    pub index: u8,
716    pub fdt_type: u8,
717    pub status: u8,
718    pub beta_version: u8,
719    pub version: u16,
720    pub boot_version: u16,
721}
722impl Decode for Fdt {
723    fn decode(data: &mut &[u8]) -> Result<Self, DecodeError> {
724        let index = u8::decode(data)?;
725        let fdt_type = u8::decode(data)?;
726        let status = u8::decode(data)?;
727        let beta_version = u8::decode(data)?;
728        let version = u16::decode(data)?;
729        let boot_version = u16::decode(data)?;
730
731        Ok(Self {
732            index,
733            fdt_type,
734            status,
735            beta_version,
736            version,
737            boot_version,
738        })
739    }
740}
741
742pub type FdtStatusPacket = Cdc2CommandPacket<USER_CDC, FDT_STATUS, ()>;
743pub type FdtStatusReplyPacket = Cdc2ReplyPacket<USER_CDC, FDT_STATUS, FdtStatus>;
744
745#[derive(Debug, Clone, Copy, Eq, PartialEq)]
746pub struct RadioStatus {
747    /// 0 = No controller, 4 = Controller connected (UNCONFIRMED)
748    pub device: u8,
749    /// From 0 to 100
750    pub quality: u16,
751    /// Probably RSSI (UNCONFIRMED)
752    pub strength: i16,
753    /// 5 = download, 31 = pit, 245 = bluetooth
754    pub channel: u8,
755    /// Latency between controller and brain (UNCONFIRMED)
756    pub timeslot: u8,
757}
758impl Decode for RadioStatus {
759    fn decode(data: &mut &[u8]) -> Result<Self, DecodeError> {
760        let device = u8::decode(data)?;
761        let quality = u16::decode(data)?;
762        let strength = i16::decode(data)?;
763        let channel = u8::decode(data)?;
764        let timeslot = u8::decode(data)?;
765
766        Ok(Self {
767            device,
768            quality,
769            strength,
770            channel,
771            timeslot,
772        })
773    }
774}
775
776pub type RadioStatusPacket = Cdc2CommandPacket<USER_CDC, RADIO_STATUS, ()>;
777pub type RadioStatusReplyPacket = Cdc2ReplyPacket<USER_CDC, RADIO_STATUS, RadioStatus>;