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}