1use crate::error::{Error, Result};
6
7#[derive(Debug, Clone, Copy, PartialEq, Eq)]
9pub enum CommunicationObject {
10 Nmt,
12 Sync,
14 Emergency,
16 Time,
18 Tpdo1,
19 Rpdo1,
20 Tpdo2,
21 Rpdo2,
22 Tpdo3,
23 Rpdo3,
24 Tpdo4,
25 Rpdo4,
26 Tsdo,
28 Rsdo,
30 Heartbeat,
32}
33
34impl CommunicationObject {
35 pub fn function_code(self) -> u16 {
37 match self {
38 CommunicationObject::Nmt => 0x000,
39 CommunicationObject::Sync => 0x080,
40 CommunicationObject::Emergency => 0x080,
41 CommunicationObject::Time => 0x100,
42 CommunicationObject::Tpdo1 => 0x180,
43 CommunicationObject::Rpdo1 => 0x200,
44 CommunicationObject::Tpdo2 => 0x280,
45 CommunicationObject::Rpdo2 => 0x300,
46 CommunicationObject::Tpdo3 => 0x380,
47 CommunicationObject::Rpdo3 => 0x400,
48 CommunicationObject::Tpdo4 => 0x480,
49 CommunicationObject::Rpdo4 => 0x500,
50 CommunicationObject::Tsdo => 0x580,
51 CommunicationObject::Rsdo => 0x600,
52 CommunicationObject::Heartbeat => 0x700,
53 }
54 }
55
56 pub fn cob_id(self, node_id: u8) -> Result<u16> {
60 let base = self.function_code();
61 match self {
62 CommunicationObject::Nmt
63 | CommunicationObject::Sync
64 | CommunicationObject::Time => Ok(base),
65 _ => {
66 if node_id == 0 || node_id > 0x7F {
67 return Err(Error::InvalidNodeId(node_id));
68 }
69 Ok(base | node_id as u16)
70 }
71 }
72 }
73}
74
75pub fn parse_cob_id(cob_id: u16) -> Option<(CommunicationObject, u8)> {
78 if cob_id == 0x000 {
79 return Some((CommunicationObject::Nmt, 0));
80 }
81 if cob_id == 0x080 {
82 return Some((CommunicationObject::Sync, 0));
83 }
84 if cob_id == 0x100 {
85 return Some((CommunicationObject::Time, 0));
86 }
87
88 let function = cob_id & 0x780;
89 let nid = (cob_id & 0x7F) as u8;
90 if nid == 0 || nid > 0x7F {
91 return None;
92 }
93
94 let obj = match function {
95 0x080 => CommunicationObject::Emergency,
96 0x180 => CommunicationObject::Tpdo1,
97 0x200 => CommunicationObject::Rpdo1,
98 0x280 => CommunicationObject::Tpdo2,
99 0x300 => CommunicationObject::Rpdo2,
100 0x380 => CommunicationObject::Tpdo3,
101 0x400 => CommunicationObject::Rpdo3,
102 0x480 => CommunicationObject::Tpdo4,
103 0x500 => CommunicationObject::Rpdo4,
104 0x580 => CommunicationObject::Tsdo,
105 0x600 => CommunicationObject::Rsdo,
106 0x700 => CommunicationObject::Heartbeat,
107 _ => return None,
108 };
109 Some((obj, nid))
110}
111
112pub fn is_reserved_standard_cob_id(cob_id: u16) -> bool {
115 matches!(
116 cob_id,
117 0x000 | 0x080 | 0x100 ) || (0x081..=0x0FF).contains(&cob_id) || (0x181..=0x1FF).contains(&cob_id) || (0x201..=0x27F).contains(&cob_id) || (0x281..=0x2FF).contains(&cob_id) || (0x301..=0x37F).contains(&cob_id) || (0x381..=0x3FF).contains(&cob_id) || (0x401..=0x47F).contains(&cob_id) || (0x481..=0x4FF).contains(&cob_id) || (0x501..=0x57F).contains(&cob_id) || (0x581..=0x5FF).contains(&cob_id) || (0x601..=0x67F).contains(&cob_id) || (0x701..=0x77F).contains(&cob_id) || (0x680..=0x6FF).contains(&cob_id) || (0x780..=0x7FF).contains(&cob_id) }
136
137pub fn is_reserved_extended_cob_id(cob_id: u32) -> bool {
139 matches!(cob_id, 0x09 | 0xA9 | 0xAA) || (0xFE00..=0xFEFF).contains(&cob_id)
140}
141
142#[cfg(test)]
143mod tests {
144 use super::*;
145
146 #[test]
147 fn cob_id_tpdo1_node10() {
148 assert_eq!(CommunicationObject::Tpdo1.cob_id(0x10).unwrap(), 0x190);
149 }
150
151 #[test]
152 fn cob_id_heartbeat_node1() {
153 assert_eq!(CommunicationObject::Heartbeat.cob_id(0x01).unwrap(), 0x701);
154 }
155
156 #[test]
157 fn cob_id_rsdo_node127() {
158 assert_eq!(CommunicationObject::Rsdo.cob_id(0x7F).unwrap(), 0x67F);
159 }
160
161 #[test]
162 fn cob_id_nmt_ignores_node() {
163 assert_eq!(CommunicationObject::Nmt.cob_id(0).unwrap(), 0x000);
164 }
165
166 #[test]
167 fn cob_id_rejects_zero_node() {
168 assert!(matches!(
169 CommunicationObject::Tpdo1.cob_id(0),
170 Err(Error::InvalidNodeId(0))
171 ));
172 }
173
174 #[test]
175 fn cob_id_rejects_node_over_127() {
176 assert!(matches!(
177 CommunicationObject::Tpdo1.cob_id(128),
178 Err(Error::InvalidNodeId(128))
179 ));
180 }
181
182 #[test]
183 fn parse_heartbeat() {
184 assert_eq!(parse_cob_id(0x710), Some((CommunicationObject::Heartbeat, 0x10)));
185 }
186
187 #[test]
188 fn parse_tpdo1() {
189 assert_eq!(parse_cob_id(0x190), Some((CommunicationObject::Tpdo1, 0x10)));
190 }
191
192 #[test]
193 fn parse_sync() {
194 assert_eq!(parse_cob_id(0x080), Some((CommunicationObject::Sync, 0)));
195 }
196
197 #[test]
198 fn parse_emergency() {
199 assert_eq!(parse_cob_id(0x081), Some((CommunicationObject::Emergency, 1)));
201 }
202
203 #[test]
204 fn parse_invalid() {
205 assert_eq!(parse_cob_id(0x150), None); }
207
208 #[test]
209 fn reserved_standard_covers_all_canopen_objects() {
210 assert!(is_reserved_standard_cob_id(0x701));
212 assert!(is_reserved_standard_cob_id(0x77F));
213 assert!(is_reserved_standard_cob_id(0x181));
215 assert!(is_reserved_standard_cob_id(0x1FF));
216 assert!(is_reserved_standard_cob_id(0x581));
218 assert!(is_reserved_standard_cob_id(0x67F));
219 assert!(is_reserved_standard_cob_id(0x000));
221 assert!(is_reserved_standard_cob_id(0x080));
222 assert!(is_reserved_standard_cob_id(0x100));
223 assert!(is_reserved_standard_cob_id(0x7E5));
225 }
226
227 #[test]
228 fn reserved_standard_allows_safe_ids() {
229 assert!(!is_reserved_standard_cob_id(0x101));
232 assert!(!is_reserved_standard_cob_id(0x17F));
233 assert!(!is_reserved_standard_cob_id(0x180));
234 assert!(!is_reserved_standard_cob_id(0x200));
235 assert!(!is_reserved_standard_cob_id(0x280));
236 assert!(!is_reserved_standard_cob_id(0x300));
237 assert!(!is_reserved_standard_cob_id(0x380));
238 assert!(!is_reserved_standard_cob_id(0x400));
239 assert!(!is_reserved_standard_cob_id(0x480));
240 assert!(!is_reserved_standard_cob_id(0x500));
241 assert!(!is_reserved_standard_cob_id(0x580));
242 assert!(!is_reserved_standard_cob_id(0x600));
243 assert!(!is_reserved_standard_cob_id(0x700));
244 }
245
246 #[test]
247 fn reserved_standard_includes_lss_range() {
248 assert!(is_reserved_standard_cob_id(0x680));
250 assert!(is_reserved_standard_cob_id(0x6FF));
251 assert!(is_reserved_standard_cob_id(0x780));
252 assert!(is_reserved_standard_cob_id(0x7FF));
253 }
254
255 #[test]
256 fn reserved_extended_known() {
257 assert!(is_reserved_extended_cob_id(0x09));
258 assert!(is_reserved_extended_cob_id(0xA9));
259 assert!(is_reserved_extended_cob_id(0xAA));
260 assert!(is_reserved_extended_cob_id(0xFE00));
261 assert!(is_reserved_extended_cob_id(0xFEFF));
262 assert!(!is_reserved_extended_cob_id(0xAB));
263 assert!(!is_reserved_extended_cob_id(0xFDFF));
264 }
265}