core_pb/
names.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
//! See [`RobotName`], a unique identifier for each known robot

/// The number of unique [`RobotName`]s
pub const NUM_ROBOT_NAMES: usize = 6;

/// Represents a unique robot, either a physical device or a simulation
///
/// Robot names are six letters, where the first letter indicates its type:
/// - names beginning with 'S' are simulated robots, not real
/// - names beginning with 'P' are raspberry pi pico boards
///
/// See [`NUM_ROBOT_NAMES`] for the number of names
///
/// usize values should be consecutive such that an array like `[(); NUM_ROBOT_NAMES]`
/// can be indexed like `arr[robot_name as usize]`
///
/// However, while they are set at compile time, these values are not stable over the
/// development of the codebase; code should not, for example, specifically rely on
/// [`Stella`] as index 0
#[derive(Copy, Clone, Debug, PartialOrd, PartialEq, Ord, Eq, Serialize, Deserialize)]
#[repr(u8)]
pub enum RobotName {
    // [P]ico boards
    Pierre = 0,
    Prince = 1,
    Patric = 2,
    // Pancho,
    // [S]imulated robots
    Stella = 3,
    Stevie = 4,
    Speers = 5,
}

impl From<usize> for RobotName {
    fn from(value: usize) -> Self {
        match value {
            0 => Pierre,
            1 => Prince,
            2 => Patric,
            3 => Stella,
            4 => Stevie,
            5 => Speers,
            _ => panic!("Invalid robot name index: {}", value),
        }
    }
}

impl Display for RobotName {
    fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
        write!(f, "{}", self.get_str())
    }
}

#[cfg(feature = "defmt")]
impl defmt::Format for RobotName {
    fn format(&self, fmt: defmt::Formatter) {
        defmt::write!(fmt, "{}", self.get_str())
    }
}

use crate::robot_definition::RobotDefinition;
use core::fmt::{Display, Formatter};
use serde::{Deserialize, Serialize};
use RobotName::*;

impl RobotName {
    /// All robot names in order
    pub const fn get_all() -> [RobotName; NUM_ROBOT_NAMES] {
        [Pierre, Prince, Patric, Stella, Stevie, Speers]
    }

    pub fn get_str(&self) -> &'static str {
        match self {
            Stella => "Stella",
            Stevie => "Stevie",
            Speers => "Speers",
            Pierre => "Pierre",
            Prince => "Prince",
            Patric => "Patric",
        }
    }

    /// Whether this robot is managed by the simulator
    pub fn is_simulated(&self) -> bool {
        self.mac_address()[0] == 0x02
    }

    /// Whether this robot is a raspberry pi pico
    pub fn is_pico(&self) -> bool {
        matches!(self, Pierre | Prince | Patric)
    }

    /// The mac address of this robot, must be unique
    ///
    /// Simulated robots look like 02:00:00:00::00:xx
    pub fn mac_address(&self) -> [u8; 6] {
        match self {
            Stella => [0x02, 0, 0, 0, 0, 0x01],
            Stevie => [0x02, 0, 0, 0, 0, 0x02],
            Speers => [0x02, 0, 0, 0, 0, 0x03],

            Pierre => [0x28, 0xcd, 0xc1, 0x0d, 0x29, 0x35],
            Prince => [0x28, 0xcd, 0xc1, 0x0c, 0x81, 0xca],
            Patric => [0x28, 0xcd, 0xc1, 0x0d, 0x29, 0x4f],
        }
    }

    /// Uniquely determine the robot name from the mac address, if recognized
    ///
    /// Simulated robots look like 02:00:00:00::00:xx
    pub fn from_mac_address(address: &[u8; 6]) -> Option<Self> {
        match address {
            [0x02, 0x00, 0x00, 0x00, 0x00, x] => match x {
                0x01 => Some(Stella),
                0x02 => Some(Stevie),
                0x03 => Some(Speers),
                _ => None,
            },

            [0x28, 0xcd, 0xc1, 0x0d, 0x29, 0x35] => Some(Pierre),
            [0x28, 0xcd, 0xc1, 0x0c, 0x81, 0xca] => Some(Prince),
            [0x28, 0xcd, 0xc1, 0x0d, 0x29, 0x4f] => Some(Patric),

            _ => None,
        }
    }

    /// The default pre-filled ip - robots need not necessarily use this ip
    pub fn default_ip(&self) -> [u8; 4] {
        match self {
            Pierre => [192, 168, 8, 114],
            Prince => [192, 168, 8, 113],
            Patric => [192, 168, 8, 115],
            // simulated robots are local
            _ => [127, 0, 0, 1],
        }
    }

    /// The port this robot will listen on for TCP connections
    ///
    /// Physical robots may listen on the same port
    pub fn port(&self) -> u16 {
        match self {
            // simulated robots each require their own port
            // spaced out in case an additional port is desired for each in the future
            Stella => 20022,
            Stevie => 20024,
            Speers => 20026,

            // picos may share ports
            _ => 20020,
        }
    }

    /// The characteristics of this robot
    pub fn robot(&self) -> RobotDefinition<3> {
        RobotDefinition::new(*self)
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    pub fn test_robot_names() {
        for i in 0..NUM_ROBOT_NAMES {
            let name: RobotName = i.into();
            assert_eq!(
                name,
                RobotName::get_all()[i],
                "usize value should match index of get_all"
            );
            assert!(
                !(name.is_pico() && name.is_simulated()),
                "a robot cannot be both a pico and simulated"
            );
            assert_eq!(
                Some(name),
                RobotName::from_mac_address(&name.mac_address()),
                "mac_address() and from_mac_address() match"
            );
            if name.is_simulated() {
                assert_eq!(name.mac_address()[0], 0x02);
                assert_eq!(name.default_ip(), [127, 0, 0, 1]);
            } else {
                assert_ne!(name.mac_address()[0], 0x02);
            }
            for other in RobotName::get_all() {
                if name != other {
                    assert_ne!(
                        name.mac_address(),
                        other.mac_address(),
                        "robots cannot share the same mac address"
                    );
                    if name.is_simulated() && other.is_simulated() {
                        assert_ne!(
                            name.port(),
                            other.port(),
                            "simulated robots cannot share the same port"
                        )
                    }
                }
            }
        }
    }
}