robojackets_robocup_rtp/
control_message.rs

1//!
2//! The Control Message is sent to robots over radio to inform them of what actions to take.
3//!
4
5#![allow(dead_code)]
6
7use nalgebra::base::*;
8use ncomm_utils::packing::{Packable, PackingError};
9
10use crate::Team;
11
12/// The body{X, Y, W} are multiplied (upon sending) by the VELOCITY_SCALE_FACTOR and divided
13/// (upon receiving) to preserve at least 3 decimals of floating point precision.
14pub const VELOCITY_SCALE_FACTOR: f32 = 1000.0;
15
16/// The size of a ControlMessage in Bytes as a constant.
17/// Note: This is tested in the tests so it can be trusted
18pub const CONTROL_MESSAGE_SIZE: usize = 10;
19
20/// The Trigger Mode Kicking
21#[derive(Clone, Copy, Debug, PartialEq, Eq)]
22pub enum TriggerMode {
23    /// Slowly expel the charge in the kicker
24    StandDown = 0,
25    /// Immediately activate the kicker
26    Immediate = 1,
27    /// Activate the kicker on the next break beam trip
28    OnBreakBeam = 2,
29}
30
31impl Into<u8> for TriggerMode {
32    fn into(self) -> u8 {
33        self as u8
34    }
35}
36
37#[derive(Clone, Copy, Debug, PartialEq, Eq)]
38/// How the robot should kick the ball
39pub enum ShootMode {
40    /// The robot should kick the ball
41    Kick = 0,
42    /// The robot should chip the ball
43    Chip = 1,
44}
45
46impl Into<bool> for ShootMode {
47    fn into(self) -> bool {
48        match self {
49            ShootMode::Kick => false,
50            ShootMode::Chip => true,
51        }
52    }
53}
54
55#[derive(Clone, Copy, Debug, PartialEq, Eq)]
56/// The `mode` the robot should be in
57/// 
58/// In general, this field should pretty much always be set to Default.  However, I created
59/// this field so we can tell the robots to conduct specific testing procedures (or perhaps
60/// special moves) based on the flags sent from the base station
61pub enum Mode {
62    /// Default execution mode.  In default execution mode, the robot continually
63    /// runs the normal motion control update loop and should behave as one would expect
64    /// our robots to perform
65    Default = 0,
66    /// Test the IMU on the robot
67    ImuTest = 1,
68    /// Benchmark the radio's receive functionality
69    ReceiveBenchmark = 2,
70    /// Benchmark the radio's send functionality
71    SendBenchmark = 3,
72    /// Program the kicker with kick-on-breakbeam
73    ProgramKickOnBreakbeam = 4,
74    /// Program the kicker with normal-operation
75    ProgramKicker = 5,
76    /// Test the kicker
77    KickerTest = 6,
78    /// Test the FPGA Movement
79    FpgaTest = 7,
80}
81
82impl Default for Mode {
83    fn default() -> Self {
84        Self::Default
85    }
86}
87
88impl From<u8> for Mode {
89    fn from(value: u8) -> Self {
90        match value {
91            0 => Self::Default,
92            1 => Self::ImuTest,
93            2 => Self::ReceiveBenchmark,
94            3 => Self::SendBenchmark,
95            4 => Self::ProgramKickOnBreakbeam,
96            5 => Self::ProgramKicker,
97            6 => Self::KickerTest,
98            7 => Self::FpgaTest,
99            _ => Self::Default,
100        }
101    }
102}
103
104/// The Control Message is Sent from the Base Station to the Robots.
105///
106/// The Packed Format of this message is as follows:
107/// +---------+---------+---------+---------+---------+---------+---------+---------+
108/// |    7    |    6    |    5    |    4    |    3    |    2    |    1    |    0    |
109/// +---------+---------+---------+---------+---------+---------+---------+---------+
110/// | team    | robot id                              | shoot_m | trigger_mode      |
111/// +---------+---------+---------+---------+---------+---------+---------+---------+
112/// | body_x (lsb)                                                                  |
113/// +---------+---------+---------+---------+---------+---------+---------+---------+
114/// | body_x (msb)                                                                  |
115/// +---------+---------+---------+---------+---------+---------+---------+---------+
116/// | body_y (lsb)                                                                  |
117/// +---------+---------+---------+---------+---------+---------+---------+---------+
118/// | body_y (msb)                                                                  |
119/// +---------+---------+---------+---------+---------+---------+---------+---------+
120/// | body_w (lsb)                                                                  |
121/// +---------+---------+---------+---------+---------+---------+---------+---------+
122/// | body_w (msb)                                                                  |
123/// +---------+---------+---------+---------+---------+---------+---------+---------+
124/// | dribbler_speed                                                                |
125/// +---------+---------+---------+---------+---------+---------+---------+---------+
126/// | kick_strength                                                                 |
127/// +---------+---------+---------+---------+---------+---------+---------+---------+
128/// | role              | mode                                                      |
129/// +---------+---------+---------+---------+---------+---------+---------+---------+
130///
131/// Size = 80 Bits = 10 Bytes
132#[derive(Clone, Copy, Debug, PartialEq, Eq)]
133pub struct ControlMessage {
134    /// Team of the Robot (0: Blue) (1: Yellow)
135    pub team: Team,
136    /// Id of the Robot
137    pub robot_id: u8,
138    /// Mode of kicking for the robot
139    pub shoot_mode: ShootMode,
140    /// Trigger Mode for the Robot (TODO: Finish Docs)
141    pub trigger_mode: TriggerMode,
142    /// X Coordinate of the Robot's Body Frame (multiplied by VELOCITY_SCALE_FACTOR
143    /// and truncated)
144    pub body_x: i16,
145    /// Y Coordinate of the Robot's Body Frame (multiplied by VELOCITY_SCALE_FACTOR
146    /// and truncated)
147    pub body_y: i16,
148    /// W Coordinate of the Robot's Body Frame (multiplied by VELOCITY_SCALE_FACTOR
149    /// and truncated))
150    pub body_w: i16,
151    /// Speed of the dribbler (TODO: Determine Units)
152    pub dribbler_speed: i8,
153    /// Strength of the kicker on kick (TODO: Determine Units)
154    pub kick_strength: u8,
155    /// Role of This Robot (TODO: Finish Docs)
156    pub role: u8,
157    /// The mode of the robot
158    pub mode: Mode,
159}
160
161impl ControlMessage {
162    /// Get the velocity (x, y, w) from the control message in a vector
163    pub fn get_velocity(&self) -> Vector3<f32> {
164        Vector3::new(
165            (self.body_x as f32) / VELOCITY_SCALE_FACTOR,
166            (self.body_y as f32) / VELOCITY_SCALE_FACTOR,
167            (self.body_w as f32) / VELOCITY_SCALE_FACTOR,
168        )
169    }
170}
171
172impl Packable for ControlMessage {
173    fn len() -> usize {
174        CONTROL_MESSAGE_SIZE
175    }
176
177    fn pack(self, buffer: &mut [u8]) -> Result<(), PackingError> {
178        if buffer.len() < CONTROL_MESSAGE_SIZE {
179            return Err(PackingError::InvalidBufferSize);
180        }
181
182        buffer[0] = ((self.team as u8) & 0b1) << 7
183            | (self.robot_id & 0b1111) << 3
184            | ((self.shoot_mode as u8) & 0b1) << 2
185            | (self.trigger_mode as u8) & 0b11;
186        let bytes = self.body_x.to_le_bytes();
187        buffer[1] = bytes[0];
188        buffer[2] = bytes[1];
189        let bytes = self.body_y.to_le_bytes();
190        buffer[3] = bytes[0];
191        buffer[4] = bytes[1];
192        let bytes = self.body_w.to_le_bytes();
193        buffer[5] = bytes[0];
194        buffer[6] = bytes[1];
195        buffer[7] = self.dribbler_speed.to_le_bytes()[0];
196        buffer[8] = self.kick_strength;
197        buffer[9] = (self.role & 0b11) << 6 | (self.mode as u8);
198        Ok(())
199    }
200
201    fn unpack(data: &[u8]) -> Result<Self, PackingError> {
202        if data.len() < CONTROL_MESSAGE_SIZE {
203            return Err(PackingError::InvalidBufferSize);
204        }
205        
206        Ok(Self {
207            team: if data[0] & (0b1 << 7) == 0 {
208                Team::Blue
209            } else {
210                Team::Yellow
211            },
212            robot_id: (data[0] & (0b1111 << 3)) >> 3,
213            shoot_mode: if data[0] & (0b1 << 2) != 0 {
214                ShootMode::Chip
215            } else {
216                ShootMode::Kick
217            },
218            trigger_mode: match data[0] & 0b11 {
219                1 => TriggerMode::Immediate,
220                2 => TriggerMode::OnBreakBeam,
221                _ => TriggerMode::StandDown,
222            },
223            body_x: i16::from_le_bytes(data[1..=2].try_into().unwrap()),
224            body_y: i16::from_le_bytes(data[3..=4].try_into().unwrap()),
225            body_w: i16::from_le_bytes(data[5..=6].try_into().unwrap()),
226            dribbler_speed: i8::from_le_bytes([data[7]]),
227            kick_strength: data[8],
228            role: (data[9] & (0b11 << 6)) >> 6,
229            mode: (data[9] & 0b0011_1111).into(),
230        })
231    }
232}
233
234/// Builder for a Control Message
235pub struct ControlMessageBuilder {
236    /// The message's team
237    pub team: Option<Team>,
238    /// The message's robot_id
239    pub robot_id: Option<u8>,
240    /// The message's shoot mode
241    pub shoot_mode: Option<ShootMode>,
242    /// The message's trigger mode
243    pub trigger_mode: Option<TriggerMode>,
244    /// The message's body velocity in the x direction
245    pub body_x: Option<i16>,
246    /// The message's body velocity in the y direction
247    pub body_y: Option<i16>,
248    /// The message's body velocity in the w direction
249    pub body_w: Option<i16>,
250    /// The speed of the dribbler
251    pub dribbler_speed: Option<i8>,
252    /// The strength of the kicker (used to charge the kicker)
253    pub kick_strength: Option<u8>,
254    /// The role the robot is playing
255    pub role: Option<u8>,
256    /// The mode the robot is in
257    pub mode: Option<Mode>,
258}
259
260impl ControlMessageBuilder {
261    /// Start building a new control message
262    pub fn new() -> Self {
263        Self {
264            team: None,
265            robot_id: None,
266            shoot_mode: None,
267            trigger_mode: None,
268            body_x: None,
269            body_y: None,
270            body_w: None,
271            dribbler_speed: None,
272            kick_strength: None,
273            role: None,
274            mode: None,
275        }
276    }
277
278    /// Assign the team for the control message
279    pub fn team(mut self, team: Team) -> Self {
280        self.team = Some(team);
281        self
282    }
283    
284    /// Assign the robot_id for the control message
285    pub fn robot_id(mut self, robot_id: u8) -> Self {
286        self.robot_id = Some(robot_id);
287        self
288    }
289    
290    /// Assign the shoot mode for the control message
291    pub fn shoot_mode(mut self, shoot_mode: ShootMode) -> Self {
292        self.shoot_mode = Some(shoot_mode);
293        self
294    }
295
296    /// Assign the trigger mode for the control message
297    pub fn trigger_mode(mut self, trigger_mode: TriggerMode) -> Self {
298        self.trigger_mode = Some(trigger_mode);
299        self
300    }
301
302    /// Assign the x-direction body velocity for the control message
303    pub fn body_x(mut self, body_x: f32) -> Self {
304        self.body_x = Some((body_x * VELOCITY_SCALE_FACTOR) as i16);
305        self
306    }
307
308    /// Assign the y-direction body velocity for the control message
309    pub fn body_y(mut self, body_y: f32) -> Self {
310        self.body_y = Some((body_y * VELOCITY_SCALE_FACTOR) as i16);
311        self
312    }
313    
314    /// Assign the w-direction body velocity for the control message
315    pub fn body_w(mut self, body_w: f32) -> Self {
316        self.body_w = Some((body_w * VELOCITY_SCALE_FACTOR) as i16);
317        self
318    }
319
320    /// Assign the dribbler velocity for the control message
321    pub fn dribbler_speed(mut self, dribbler_speed: i8) -> Self {
322        self.dribbler_speed = Some(dribbler_speed);
323        self
324    }
325
326    /// Assign the kick strength for the control message
327    pub fn kick_strength(mut self, kick_strength: u8) -> Self {
328        self.kick_strength = Some(kick_strength);
329        self
330    }
331
332    /// Assign the role for the control message
333    pub fn role(mut self, role: u8) -> Self {
334        self.role = Some(role);
335        self
336    }
337
338    /// Assign a specific mode for the robot in the control message
339    pub fn mode(mut self, mode: Mode) -> Self {
340        self.mode = Some(mode);
341        self
342    }
343
344    /// Build the control message from the assigned fields.
345    pub fn build(self) -> ControlMessage {
346        let team = match self.team {
347            Some(team) => team,
348            None => Team::Blue,
349        };
350
351        let robot_id = match self.robot_id {
352            Some(robot_id) => robot_id,
353            None => 0,
354        };
355
356        let shoot_mode = match self.shoot_mode {
357            Some(shoot_mode) => shoot_mode.into(),
358            None => ShootMode::Kick,
359        };
360
361        let trigger_mode = match self.trigger_mode {
362            Some(trigger_mode) => trigger_mode.into(),
363            None => TriggerMode::StandDown,
364        };
365
366        let body_x = match self.body_x {
367            Some(body_x) => body_x,
368            None => 0,
369        };
370
371        let body_y = match self.body_y {
372            Some(body_y) => body_y,
373            None => 0,
374        };
375
376        let body_w = match self.body_w {
377            Some(body_w) => body_w,
378            None => 0,
379        };
380
381        let dribbler_speed = match self.dribbler_speed {
382            Some(dribbler_speed) => dribbler_speed,
383            None => 0,
384        };
385
386        let kick_strength = match self.kick_strength {
387            Some(kick_strength) => kick_strength,
388            None => 0,
389        };
390
391        let role = match self.role {
392            Some(role) => role,
393            None => 0,
394        };
395
396        let mode = self.mode.unwrap_or_default();
397
398        ControlMessage {
399            team,
400            robot_id,
401            shoot_mode,
402            trigger_mode,
403            body_x,
404            body_y,
405            body_w,
406            dribbler_speed,
407            kick_strength,
408            role,
409            mode,
410        }
411    }
412}
413
414#[cfg(test)]
415mod tests {
416    use super::*;
417
418    /// Test that ControlMessageBuilder uses the correct default fields when
419    /// they are not provided.
420    #[test]
421    fn test_empty_control_message_builder() {
422        let control_message = ControlMessageBuilder::new().build();
423
424        let expected = ControlMessage {
425            team: Team::Blue,
426            robot_id: 0,
427            shoot_mode: ShootMode::Kick,
428            trigger_mode: TriggerMode::StandDown,
429            body_x: 0,
430            body_y: 0,
431            body_w: 0,
432            dribbler_speed: 0,
433            kick_strength: 0,
434            role: 0,
435            mode: Mode::default(),
436        };
437
438        assert_eq!(expected, control_message);
439    }
440
441    /// Test that the ControlMessageBuilder uses the correct fields when they
442    /// are provided
443    #[test]
444    fn test_complete_control_message_builder() {
445        let control_message = ControlMessageBuilder::new()
446            .team(Team::Yellow)
447            .robot_id(3)
448            .shoot_mode(ShootMode::Chip)
449            .trigger_mode(TriggerMode::OnBreakBeam)
450            .body_x(20.0)
451            .body_y(10.0)
452            .body_w(45.0)
453            .dribbler_speed(-5)
454            .kick_strength(3)
455            .role(1)
456            .build();
457
458        let expected = ControlMessage {
459            team: Team::Yellow,
460            robot_id: 3,
461            shoot_mode: ShootMode::Chip,
462            trigger_mode: TriggerMode::OnBreakBeam,
463            body_x: 20_000,
464            body_y: 10_000,
465            body_w: 32_767,
466            dribbler_speed: -5,
467            kick_strength: 3,
468            role: 1,
469            mode: Mode::default(),
470        };
471
472        assert_eq!(expected, control_message);
473    }
474
475    /// The Control Message for
476    /// ControlMessage {
477    ///     team: Yellow (false),
478    ///     robot_id: 3,
479    ///     shoot_mode: Chip (1),
480    ///     trigger_mode: OnBreakBeam (2),
481    ///     body_x: 20.0 (20_000),
482    ///     body_y: 10.0 (10_000),
483    ///     body_w: 45.0 (32_767),
484    ///     dribbler_speed: -5,
485    ///     role: 1,
486    /// }
487    ///
488    /// is as follows:
489    ///     1_0011_1_10 | 00100000 | 01001110 | 00010000 | 00100111 | ...
490    ///     ^   ^  ^  ^       ^          ^         ^          ^
491    ///     |   |  |  |       |          |         |          |
492    /// team-   |  |  |       |          |         |          |
493    /// robot_id-  |  |       |          |         |          |
494    /// shoot_mode--  |       |          |         |          |
495    /// trigger_mode---       |          |         |          |
496    /// body_x (lsb)-----------          |         |          |
497    /// body_x (msb)----------------------         |          |
498    /// body_y (lsb)--------------------------------          |
499    /// body_y (msb)-------------------------------------------
500    ///
501    ///     11111111 | 01111111 | 11111011 | 00000011 | 01_000111
502    ///         ^          ^          ^          ^       ^    ^
503    ///         |          |          |          |       |    |
504    /// body_w (lsb)       |          |          |       |    |
505    /// body_w (msb)--------          |          |       |    |
506    /// dribbler_speed (2s Comp)-------          |       |    |
507    /// kick_strength-----------------------------       |    |
508    /// role----------------------------------------------    |
509    /// mode---------------------------------------------------
510    #[test]
511    fn test_pack() {
512        let control_message = ControlMessageBuilder::new()
513            .team(Team::Yellow)
514            .robot_id(3)
515            .shoot_mode(ShootMode::Chip)
516            .trigger_mode(TriggerMode::OnBreakBeam)
517            .body_x(20.0)
518            .body_y(10.0)
519            .body_w(45.0)
520            .dribbler_speed(-5)
521            .kick_strength(3)
522            .role(1)
523            .mode(Mode::FpgaTest)
524            .build();
525
526        let mut packed_data = [0u8; CONTROL_MESSAGE_SIZE];
527        control_message.pack(&mut packed_data).unwrap();
528
529        assert_eq!(packed_data.len(), CONTROL_MESSAGE_SIZE);
530        assert_eq!(packed_data[0], 0b1_0011_1_10);
531        assert_eq!(packed_data[1], 0b00100000);
532        assert_eq!(packed_data[2], 0b01001110);
533        assert_eq!(packed_data[3], 0b00010000);
534        assert_eq!(packed_data[4], 0b00100111);
535        assert_eq!(packed_data[5], 0b11111111);
536        assert_eq!(packed_data[6], 0b01111111);
537        assert_eq!(packed_data[7], 0b11111011);
538        assert_eq!(packed_data[8], 0b00000011);
539        assert_eq!(packed_data[9], 0b01_000111);
540    }
541
542    /// The Control Message from:
543    ///     1_0011_1_10 | 00100000 | 01001110 | 00010000 | 00100111 | ...
544    ///     ^   ^  ^  ^       ^          ^         ^          ^
545    ///     |   |  |  |       |          |         |          |
546    /// team-   |  |  |       |          |         |          |
547    /// robot_id-  |  |       |          |         |          |
548    /// shoot_mode--  |       |          |         |          |
549    /// trigger_mode---       |          |         |          |
550    /// body_x (lsb)-----------          |         |          |
551    /// body_x (msb)----------------------         |          |
552    /// body_y (lsb)--------------------------------          |
553    /// body_y (msb)-------------------------------------------
554    ///
555    ///     11111111 | 01111111 | 11111011 | 00000011 | 01_000010
556    ///         ^          ^          ^          ^       ^    ^
557    ///         |          |          |          |       |    |
558    /// body_w (lsb)       |          |          |       |    |
559    /// body_w (msb)--------          |          |       |    |
560    /// dribbler_speed (2s Comp)-------          |       |    |
561    /// kick_strength-----------------------------       |    |
562    /// role----------------------------------------------    |
563    /// unused-------------------------------------------------
564    ///
565    /// is as follows:
566    /// ControlMessage {
567    ///     team: Yellow (false),
568    ///     robot_id: 3,
569    ///     shoot_mode: Chip (1),
570    ///     trigger_mode: OnBreakBeam (2),
571    ///     body_x: 20.0 (20_000),
572    ///     body_y: 10.0 (10_000),
573    ///     body_w: 45.0 (32_767),
574    ///     dribbler_speed: -5,
575    ///     role: 1,
576    ///     mode: Mode::ReceiveBenchmark,
577    /// }
578    #[test]
579    fn test_unpack() {
580        let data = [
581            0b1_0011_1_10,
582            0b00100000,
583            0b01001110,
584            0b00010000,
585            0b00100111,
586            0b11111111,
587            0b01111111,
588            0b11111011,
589            0b00000011,
590            0b01_000010,
591        ];
592
593        let control_message = ControlMessage::unpack(&data).unwrap();
594
595        let expected = ControlMessage {
596            team: Team::Yellow,
597            robot_id: 3,
598            shoot_mode: ShootMode::Chip,
599            trigger_mode: TriggerMode::OnBreakBeam,
600            body_x: 20_000,
601            body_y: 10_000,
602            body_w: 32_767,
603            dribbler_speed: -5,
604            kick_strength: 3,
605            role: 1,
606            mode: Mode::ReceiveBenchmark,
607        };
608
609        assert_eq!(expected, control_message);
610    }
611}