1use crate::datatypes::{ByteString, Value, MAX_STRING_LEN};
33use crate::lss::LssAddress;
34use crate::object_dictionary::{Address, Entry, ObjectDictionary};
35use crate::Result;
36
37#[derive(Debug, Clone, Copy)]
44pub struct StandardObjects {
45 pub device_type: u32,
47 pub identity: LssAddress,
50 pub producer_heartbeat_ms: u16,
52 pub error_history_len: u8,
55 pub device_name: Option<ByteString>,
58}
59
60impl StandardObjects {
61 pub const fn new(device_type: u32, identity: LssAddress) -> Self {
65 Self {
66 device_type,
67 identity,
68 producer_heartbeat_ms: 0,
69 error_history_len: 0,
70 device_name: None,
71 }
72 }
73
74 pub fn with_heartbeat(mut self, ms: u16) -> Self {
76 self.producer_heartbeat_ms = ms;
77 self
78 }
79
80 pub fn with_error_history(mut self, entries: u8) -> Self {
84 self.error_history_len = entries;
85 self
86 }
87
88 pub fn with_device_name(mut self, name: &str) -> Self {
91 let end = name.len().min(MAX_STRING_LEN);
92 self.device_name = ByteString::from_bytes(&name.as_bytes()[..end]).ok();
94 self
95 }
96
97 pub fn insert_into<const N: usize>(&self, od: &mut ObjectDictionary<N>) -> Result<()> {
104 od.insert(
106 Address::new(0x1000, 0),
107 Entry::constant(Value::Unsigned32(self.device_type)),
108 )?;
109 od.insert(Address::new(0x1001, 0), Entry::ro(Value::Unsigned8(0)))?;
110 od.insert(
111 Address::new(0x1017, 0),
112 Entry::rw(Value::Unsigned16(self.producer_heartbeat_ms)),
113 )?;
114
115 od.insert(
118 Address::new(0x1018, 0),
119 Entry::constant(Value::Unsigned8(4)),
120 )?;
121 od.insert(
122 Address::new(0x1018, 1),
123 Entry::constant(Value::Unsigned32(self.identity.vendor_id)),
124 )?;
125 od.insert(
126 Address::new(0x1018, 2),
127 Entry::constant(Value::Unsigned32(self.identity.product_code)),
128 )?;
129 od.insert(
130 Address::new(0x1018, 3),
131 Entry::constant(Value::Unsigned32(self.identity.revision_number)),
132 )?;
133 od.insert(
134 Address::new(0x1018, 4),
135 Entry::constant(Value::Unsigned32(self.identity.serial_number)),
136 )?;
137
138 if let Some(name) = self.device_name {
140 od.insert(
141 Address::new(0x1008, 0),
142 Entry::constant(Value::VisibleString(name)),
143 )?;
144 }
145
146 if self.error_history_len > 0 {
149 od.insert(Address::new(0x1003, 0), Entry::rw(Value::Unsigned8(0)))?;
150 for sub in 1..=self.error_history_len {
151 od.insert(Address::new(0x1003, sub), Entry::ro(Value::Unsigned32(0)))?;
152 }
153 }
154 Ok(())
155 }
156}
157
158#[cfg(test)]
159mod tests {
160 use super::*;
161 use crate::Error;
162
163 fn identity() -> LssAddress {
164 LssAddress {
165 vendor_id: 0x0000_012E,
166 product_code: 0x4001_0002,
167 revision_number: 0x0000_0003,
168 serial_number: 0x00C0_FFEE,
169 }
170 }
171
172 #[test]
173 fn inserts_the_mandatory_baseline() {
174 let mut od = ObjectDictionary::<16>::new();
175 StandardObjects::new(0x0004_0192, identity())
176 .with_heartbeat(1000)
177 .insert_into(&mut od)
178 .unwrap();
179
180 assert_eq!(
181 od.read(Address::new(0x1000, 0)).unwrap(),
182 Value::Unsigned32(0x0004_0192)
183 );
184 assert_eq!(
185 od.read(Address::new(0x1001, 0)).unwrap(),
186 Value::Unsigned8(0)
187 );
188 assert_eq!(
189 od.read(Address::new(0x1017, 0)).unwrap(),
190 Value::Unsigned16(1000)
191 );
192 }
193
194 #[test]
195 fn identity_record_matches_the_lss_address() {
196 let mut od = ObjectDictionary::<16>::new();
197 let id = identity();
198 StandardObjects::new(0, id).insert_into(&mut od).unwrap();
199
200 assert_eq!(
201 od.read(Address::new(0x1018, 0)).unwrap(),
202 Value::Unsigned8(4) );
204 assert_eq!(
205 od.read(Address::new(0x1018, 1)).unwrap(),
206 Value::Unsigned32(id.vendor_id)
207 );
208 assert_eq!(
209 od.read(Address::new(0x1018, 2)).unwrap(),
210 Value::Unsigned32(id.product_code)
211 );
212 assert_eq!(
213 od.read(Address::new(0x1018, 3)).unwrap(),
214 Value::Unsigned32(id.revision_number)
215 );
216 assert_eq!(
217 od.read(Address::new(0x1018, 4)).unwrap(),
218 Value::Unsigned32(id.serial_number)
219 );
220 }
221
222 #[test]
223 fn error_history_is_optional_and_sized() {
224 let mut od = ObjectDictionary::<16>::new();
226 StandardObjects::new(0, identity())
227 .insert_into(&mut od)
228 .unwrap();
229 assert!(od.entry(Address::new(0x1003, 0)).is_none());
230
231 let mut od = ObjectDictionary::<16>::new();
233 StandardObjects::new(0, identity())
234 .with_error_history(3)
235 .insert_into(&mut od)
236 .unwrap();
237 assert_eq!(
238 od.read(Address::new(0x1003, 0)).unwrap(),
239 Value::Unsigned8(0)
240 );
241 assert!(od.entry(Address::new(0x1003, 3)).is_some());
242 assert!(od.entry(Address::new(0x1003, 4)).is_none());
243 }
244
245 #[test]
246 fn node_built_from_standard_objects_mirrors_emergency() {
247 use crate::emcy::{error_code, ErrorRegister};
248 use crate::node::Node;
249 use crate::types::NodeId;
250
251 let mut od = ObjectDictionary::<16>::new();
252 StandardObjects::new(0x0004_0192, identity())
253 .with_heartbeat(1000)
254 .with_error_history(4)
255 .insert_into(&mut od)
256 .unwrap();
257 let mut node = Node::new(NodeId::new(0x10).unwrap(), od);
258
259 node.raise_emergency(
260 error_code::VOLTAGE,
261 ErrorRegister(ErrorRegister::VOLTAGE),
262 [0; 5],
263 );
264 assert_eq!(
267 node.od().read(Address::new(0x1001, 0)).unwrap(),
268 Value::Unsigned8(ErrorRegister::GENERIC | ErrorRegister::VOLTAGE)
269 );
270 assert_eq!(
271 node.od().read(Address::new(0x1003, 0)).unwrap(),
272 Value::Unsigned8(1)
273 );
274 assert_eq!(
275 node.od().read(Address::new(0x1003, 1)).unwrap(),
276 Value::Unsigned32(u32::from(error_code::VOLTAGE))
277 );
278 }
279
280 #[test]
281 fn device_name_is_optional_visible_string() {
282 let mut od = ObjectDictionary::<16>::new();
284 StandardObjects::new(0, identity())
285 .insert_into(&mut od)
286 .unwrap();
287 assert!(od.entry(Address::new(0x1008, 0)).is_none());
288
289 let mut od = ObjectDictionary::<16>::new();
291 StandardObjects::new(0, identity())
292 .with_device_name("canopen-rs node")
293 .insert_into(&mut od)
294 .unwrap();
295 match od.read(Address::new(0x1008, 0)).unwrap() {
296 Value::VisibleString(bs) => assert_eq!(bs.as_str(), Some("canopen-rs node")),
297 other => panic!("expected a VISIBLE_STRING, got {other:?}"),
298 }
299 }
300
301 #[test]
302 fn reports_dictionary_full() {
303 let mut od = ObjectDictionary::<4>::new();
305 assert_eq!(
306 StandardObjects::new(0, identity()).insert_into(&mut od),
307 Err(Error::DictionaryFull)
308 );
309 }
310}