mechutil/object_dictionary.rs
1//
2// Copyright (C) 2024 Automated Design Corp.. All Rights Reserved.
3// Created Date: 2024-10-23 06:12:47
4// -----
5// Last Modified: 2024-11-04 07:41:41
6// -----
7//
8//
9
10//! This module provides an implementation based upon the CANopen object dictionary
11//! or CAN over EtherCAT (CoE) systems. The object
12//! dictionary is the core mechanism for configuration and communication with
13//! CANopen devices. Each entry in the dictionary is represented by an index, which
14//! may contain sub-indices for more complex data structures such as arrays and
15//! records.
16//!
17//! The `MechObjectDictionary` struct serves as a collection of these entries, each
18//! described by its index, object type, access rights, data type, and value. The
19//! module supports serialization and deserialization to/from JSON to facilitate
20//! easy configuration management and device integration.
21//!
22//! Key Components:
23//! - `MechObjectDictionary`: The main container for the object dictionary entries.
24//! - `MechObjectDictionaryEntry`: Represents a single entry in the object dictionary.
25//! - `RegisterValue`: Enum representing either a single value or a set of sub-indices.
26//! - `AccessRights`: Enum describing the access permissions for an entry (ReadWrite, ReadOnly, WriteOnly).
27//! - `ObjectType`: Enum for the type of object (Array, Record, Variable).
28//!
29//! Features:
30//! - Create and manage object dictionary entries.
31//! - Support for both single values and arrays of sub-indices.
32//! - Serialization and deserialization to/from JSON for configuration persistence.
33//!
34//! Example Usage:
35//! ```no_run
36//! use mechutil::object_dictionary::{MechObjectDictionary, ObjectType, AccessRights, MechObjectDictionaryValue};
37//! use mechutil::register_value::MechCommandRegisterValue;
38//!
39//! let mut dictionary = MechObjectDictionary::new(
40//! 0x000401, // Device Type (generic I/O device)
41//! 1000, // Heartbeat interval of 1000 ms
42//! 12345, // Vendor ID
43//! 67890, // Product Code
44//! );
45//! let initial_value = MechCommandRegisterValue::new(); // Assume this creates a default value.
46//! dictionary.add_entry(0x1000, ObjectType::Array, "Example Object".to_string(), "uint32".to_string(), AccessRights::ReadWrite, true, 5, initial_value);
47//! ```
48//!
49//! In general, for custom functionality, the objects should be in the range 0x2000–0x5FFF, **Manufacturer-Specific Objects**.
50//! See the common_canopen_objects.md doc for CANOpen standard dictionary objects and their addresses.
51//!
52//!
53
54//
55// TODO:
56// - [] PDO-like features.
57// - - [] Register PDOs
58// - - [] Map registers to a PDO
59// - [] API for making required objects:
60// - - 0x1000 Device Type (Read Only)
61// - - 0x1001 Error Register (Read Only)
62// - - 0x1017 Producer Heartbeat Time (Const)
63// - - 0x1018 Identity Object (Read Only)
64//
65// - [] Look into this:
66// - - "In addition you need Communication Parameters and Mapping parameters for every PDO, but these are allowed to be constants.""
67
68use crate::register_value::MechCommandRegisterValue;
69use anyhow::anyhow;
70use serde::{Deserialize, Serialize};
71use std::collections::HashMap;
72use std::fs;
73
74/// Enum for access rights (read/write, read-only, write-only) for the object dictionary entry.
75#[derive(Clone, Debug, Serialize, Deserialize)]
76pub enum AccessRights {
77 ReadWrite,
78 Const,
79 ReadOnly,
80 WriteOnly,
81}
82
83/// Enum for the object type in the object dictionary entry (e.g., array, record, variable).
84#[derive(Clone, Debug, Serialize, Deserialize)]
85pub enum ObjectType {
86 /// A collection of homogeneous sub-indices, meaning all sub-indices within an Array must have the same data type.
87 Array,
88 /// A collection of heterogeneous sub-indices, meaning each sub-index can represent a different type of data
89 Record,
90 /// A single value. This object does not contain sub-indices.
91 Variable,
92}
93
94/// Represents an entry in the CANopen Object Dictionary.
95///
96/// This struct models the data, metadata, and access properties for a specific
97/// entry within a CANopen-compliant Object Dictionary, where each entry is
98/// identified by a unique 16-bit `index`.
99///
100/// ### Fields
101/// - `index`: The 16-bit address of the object in the Object Dictionary.
102/// - `object_type`: Specifies the type of object. This can be an `Array`, `Record`,
103/// or `Variable`, which determines if the object contains a list of values,
104/// a structured set of different data types, or a single value.
105/// - `name`: A descriptive name for the object, helping to identify its purpose
106/// or function within the device configuration.
107/// - `datatype`: The data type of the object as a `String`. This string represents
108/// the underlying data format (e.g., `"uint32"`, `"int16"`, `"float"`). The `datatype`
109/// string is informational, ensuring compatibility across systems without imposing
110/// a strict type. To enforce or interpret data types programmatically, use the
111/// appropriate `from_*` and `to_*` methods in `MechCommandRegisterValue`.
112/// - `access_rights`: Defines the access permissions for the entry, including `ReadOnly`,
113/// `WriteOnly`, `ReadWrite`, or `Const` (constant, unmodifiable data).
114/// - `mandatory`: Indicates whether the object is required for the device to be CANopen-compliant
115/// (`true` if mandatory) or is optional (`false`).
116/// - `value`: Stores the entry’s value(s). This field can either hold a single value or
117/// a collection of sub-indices, depending on the object type.
118///
119/// ### Example Usage
120/// ```no_run
121/// use mechutil::object_dictionary::{MechObjectDictionaryEntry, ObjectType, AccessRights, MechObjectDictionaryValue};
122/// use mechutil::register_value::MechCommandRegisterValue;
123///
124/// let entry = MechObjectDictionaryEntry {
125/// index: 0x1018,
126/// object_type: ObjectType::Record,
127/// name: "Identity Object".to_string(),
128/// datatype: "record".to_string(),
129/// access_rights: AccessRights::ReadOnly,
130/// mandatory: true,
131/// value: MechObjectDictionaryValue::SubIndices(vec![
132/// MechCommandRegisterValue::from_uint32(12345), // Vendor ID
133/// MechCommandRegisterValue::from_uint32(67890), // Product Code
134/// MechCommandRegisterValue::from_uint32(1), // Revision Number
135/// MechCommandRegisterValue::from_uint32(12345678), // Serial Number
136/// ]),
137/// };
138/// ```
139#[derive(Clone, Debug, Serialize, Deserialize)]
140pub struct MechObjectDictionaryEntry {
141 pub index: u16, // 16-bit address of the object in the dictionary
142 pub object_type: ObjectType, // Type of object (array, record, variable)
143 pub name: String, // Descriptive name of the object
144 pub datatype: String, // Data type of the variable
145 pub access_rights: AccessRights, // Access rights (read/write, read-only, write-only)
146 pub mandatory: bool, // Indicates if the object is mandatory (true) or optional (false)
147 pub value: MechObjectDictionaryValue, // The value or sub-indices for the object
148}
149
150/// Defines the value storage for an entry in the CANopen Object Dictionary.
151///
152/// `MechObjectDictionaryValue` can store a single `MechCommandRegisterValue`
153/// or a vector of `MechCommandRegisterValue`s, representing an array or record
154/// structure with multiple sub-indices. This enum allows flexibility in how
155/// values are stored within the Object Dictionary:
156/// - `Single`: Used for entries that have a single value (e.g., device-specific parameters).
157/// - `SubIndices`: Used for entries that contain multiple values, typically
158/// when `object_type` is `Record` or `Array`.
159///
160/// ### Example Usage
161/// ```no_run
162/// use mechutil::object_dictionary::{MechObjectDictionaryValue};
163/// use mechutil::register_value::MechCommandRegisterValue;
164///
165/// // Single value example
166/// let single_value = MechObjectDictionaryValue::Single(MechCommandRegisterValue::from_int32(42));
167///
168/// // Array of values example
169/// let array_value = MechObjectDictionaryValue::SubIndices(vec![
170/// MechCommandRegisterValue::from_uint32(0),
171/// MechCommandRegisterValue::from_uint32(1),
172/// MechCommandRegisterValue::from_uint32(2),
173/// ]);
174/// ```
175#[derive(Clone, Debug, Serialize, Deserialize)]
176pub enum MechObjectDictionaryValue {
177 Single(MechCommandRegisterValue),
178 SubIndices(Vec<MechCommandRegisterValue>),
179}
180
181/// `MechObjectDictionary` is a structured collection of entries for a CANopen device's Object Dictionary.
182///
183/// This dictionary serves as the central data structure for defining and organizing device parameters,
184/// configuration settings, and runtime information according to CANopen standards. Each entry in the
185/// dictionary is mapped by its index and may represent a single value, a structured record, or an array of
186/// homogeneous elements.
187///
188/// ### Key Features
189/// - **Standard Entries**: The `new` method initializes the dictionary with commonly used CANopen objects
190/// (e.g., Device Type, Error Register, Producer Heartbeat Time, Identity Object).
191/// - **Customizable**: New entries can be added dynamically to the dictionary with specific properties
192/// like access rights and data types.
193/// - **Supports Single and Array Entries**: Entries can either hold a single value or a collection of sub-indices
194/// (e.g., for records or arrays).
195///
196/// ### Example Usage
197/// ```no_run
198/// use mechutil::object_dictionary::{MechObjectDictionary, ObjectType, AccessRights};
199/// use mechutil::register_value::MechCommandRegisterValue;
200///
201/// // Initialize a new object dictionary with standard entries and default values
202/// let mut dictionary = MechObjectDictionary::new(
203/// 0x000401, // Device Type (generic I/O device)
204/// 1000, // Heartbeat interval of 1000 ms
205/// 12345, // Vendor ID
206/// 67890, // Product Code
207/// );
208///
209/// // Adding a custom entry for a device-specific parameter (single value)
210/// let custom_value = MechCommandRegisterValue::from_int32(42);
211/// dictionary.add_value_entry(
212/// 0x2000,
213/// ObjectType::Variable,
214/// "Custom Parameter".to_string(),
215/// "int32".to_string(),
216/// AccessRights::ReadWrite,
217/// false,
218/// custom_value,
219/// );
220///
221/// // Adding an array entry for logging error history (multiple sub-indices)
222/// let error_history_value = MechCommandRegisterValue::from_uint32(0);
223/// dictionary.add_array_entry(
224/// 0x1003,
225/// ObjectType::Array,
226/// "Error History".to_string(),
227/// "uint32".to_string(),
228/// AccessRights::ReadWrite,
229/// false,
230/// 5, // Number of sub-indices for error codes
231/// error_history_value,
232/// );
233///
234/// // Reading the value of a single-value entry (e.g., Device Type)
235/// if let Ok(value) = dictionary.get_index_value(0x1000) {
236/// println!("Device Type: {:?}", value);
237/// }
238///
239/// // Reading a specific sub-index value from an array entry (e.g., Error History at index 1)
240/// if let Ok(error_code) = dictionary.get_sub_index_value(0x1003, 1) {
241/// println!("Error Code at sub-index 1: {:?}", error_code);
242/// }
243/// ```
244#[derive(Clone, Debug, Serialize, Deserialize)]
245pub struct MechObjectDictionary {
246 registers: HashMap<u16, MechObjectDictionaryEntry>, // Register index to entry mapping
247}
248
249impl MechObjectDictionary {
250 /// Creates a new `MechObjectDictionary` with standard default entries.
251 ///
252 /// This function initializes the dictionary with commonly used CANopen objects, including:
253 /// - `0x1000` Device Type (Read Only): Specifies the type of the device.
254 /// - `0x1001` Error Register (Read Only): Holds error flags for device status monitoring.
255 /// - `0x1017` Producer Heartbeat Time (Const): Sets the interval for heartbeat messages.
256 /// - `0x1018` Identity Object (Read Only): Contains identification information about the device.
257 ///
258 /// # Parameters
259 /// - `device_type`: The initial value for the `0x1000` Device Type entry.
260 /// - `heartbeat_time_interval_ms`: The interval in milliseconds for the `0x1017` Producer Heartbeat Time entry.
261 /// - `vendor_id`: The Vendor ID for the `0x1018:01` sub-index of the Identity Object.
262 /// - `product_code`: The Product Code for the `0x1018:02` sub-index of the Identity Object.
263 ///
264 /// # Example
265 /// ```no_run
266 /// use mechutil::object_dictionary::MechObjectDictionary;
267 /// let dictionary = MechObjectDictionary::new(
268 /// 0x2001, // Device Type for a software device
269 /// 1000, // Heartbeat interval of 1000 ms (1 second)
270 /// 12345, // Vendor ID
271 /// 67890, // Product Code
272 /// );
273 /// ```
274 pub fn new(
275 device_type: u32,
276 heartbeat_time_interval_ms: u16,
277 vendor_id: u32,
278 product_code: u32,
279 ) -> Self {
280 let mut dictionary = MechObjectDictionary {
281 registers: HashMap::new(),
282 };
283
284 // Default initial value with zero values for each register
285 let default_value = MechCommandRegisterValue::new();
286
287 // 0x1000 Device Type
288 dictionary.add_value_entry(
289 0x1000,
290 ObjectType::Variable,
291 "Device Type".to_string(),
292 "uint32".to_string(),
293 AccessRights::ReadOnly,
294 true,
295 MechCommandRegisterValue::from_uint32(device_type),
296 );
297
298 // 0x1001 Error Register
299 dictionary.add_value_entry(
300 0x1001,
301 ObjectType::Variable,
302 "Error Register".to_string(),
303 "uint8".to_string(),
304 AccessRights::ReadOnly,
305 true,
306 default_value.clone(),
307 );
308
309 // 0x1017 Producer Heartbeat Time
310 dictionary.add_value_entry(
311 0x1017,
312 ObjectType::Variable,
313 "Producer Heartbeat Time".to_string(),
314 "uint16".to_string(),
315 AccessRights::Const,
316 true,
317 MechCommandRegisterValue::from_uint16(heartbeat_time_interval_ms),
318 );
319
320 // 0x1018 Identity Object (Typically a Record with multiple sub-indices)
321 dictionary.add_array_entry(
322 0x1018,
323 ObjectType::Record,
324 "Identity Object".to_string(),
325 "record".to_string(),
326 AccessRights::ReadOnly,
327 true,
328 4, // Example: Identity Object might have 4 sub-indices for Vendor ID, Product Code, Revision Number, Serial Number
329 default_value,
330 );
331
332 let _ = dictionary.set_value(
333 0x1018,
334 Some(1),
335 MechCommandRegisterValue::from_uint32(vendor_id),
336 );
337 let _ = dictionary.set_value(
338 0x1018,
339 Some(2),
340 MechCommandRegisterValue::from_uint32(product_code),
341 );
342 // revision number
343 let _ = dictionary.set_value(0x1018, Some(3), MechCommandRegisterValue::from_uint32(0));
344 // serial number
345 let _ = dictionary.set_value(0x1018, Some(4), MechCommandRegisterValue::from_uint32(0));
346
347 return dictionary;
348 }
349
350 /// Adds a new entry to the object dictionary with an initial value.
351 /// If `num_sub_indices` is greater than 1, it creates sub-indices with the provided initial value;
352 /// otherwise, it creates a single value entry.
353 ///
354 /// # Parameters
355 /// - `index`: The register index.
356 /// - `object_type`: The type of the object (array, record, variable).
357 /// - `name`: The name of the object.
358 /// - `datatype`: The data type of the object.
359 /// - `access_rights`: The access rights for this object (read/write, read-only, write-only).
360 /// - `mandatory`: Whether the object is mandatory or optional.
361 /// - `num_indices`: The number of indices/sub-indices in this value. If 0 or 1, it creates a single value.
362 /// - `initial_value`: The initial value to set for the entry (either single or in each sub-index).
363 ///
364 /// # Example
365 /// ```
366 /// use mechutil::object_dictionary::MechObjectDictionary;
367 /// use mechutil::object_dictionary::AccessRights;
368 /// use mechutil::object_dictionary::ObjectType;
369 /// use mechutil::register_value::MechCommandRegisterValue;
370 /// let mut dictionary = MechObjectDictionary::new(
371 /// 0x000401, // Device Type (generic I/O device)
372 /// 1000, // Heartbeat interval of 1000 ms
373 /// 12345, // Vendor ID
374 /// 67890, // Product Code
375 /// );
376 /// let initial_value = MechCommandRegisterValue::new(); // Assume this creates a default value.
377 /// dictionary.add_entry(0x1000, ObjectType::Array, "Example Object".to_string(), "uint32".to_string(), AccessRights::ReadWrite, true, 5, initial_value);
378 /// ```
379 pub fn add_entry(
380 &mut self,
381 index: u16,
382 object_type: ObjectType,
383 name: String,
384 datatype: String,
385 access_rights: AccessRights,
386 mandatory: bool,
387 num_indices: usize,
388 initial_value: MechCommandRegisterValue,
389 ) {
390 let value = if num_indices <= 1 {
391 // Create a single entry with the provided initial value
392 MechObjectDictionaryValue::Single(initial_value)
393 } else {
394 // Create an array of sub-indices, each initialized with the provided initial value
395 // The first index of the array is used to store the size/number of values. So, we need to
396 // add an additional index to the num_indices.
397 MechObjectDictionaryValue::SubIndices(vec![initial_value; num_indices + 1])
398 };
399
400 let entry = MechObjectDictionaryEntry {
401 index,
402 object_type,
403 name,
404 datatype,
405 access_rights,
406 mandatory,
407 value,
408 };
409
410 self.registers.insert(index, entry);
411 }
412
413 /// Adds a new entry with a single value to the object dictionary.
414 /// This function is used for entries that do not have sub-indices and only require a single `MechCommandRegisterValue`.
415 ///
416 /// # Parameters
417 /// - `index`: The register index of the object.
418 /// - `object_type`: The type of the object (e.g., array, record, variable).
419 /// - `name`: A string representing the name of the object.
420 /// - `datatype`: The data type of the object.
421 /// - `access_rights`: Access permissions for this entry (read/write, read-only, write-only).
422 /// - `mandatory`: A boolean indicating whether the object is mandatory or optional.
423 /// - `initial_value`: The initial value to set for the entry.
424 ///
425 /// # Example
426 /// ```
427 /// use mechutil::object_dictionary::MechObjectDictionary;
428 /// use mechutil::object_dictionary::ObjectType;
429 /// use mechutil::object_dictionary::AccessRights;
430 /// use mechutil::register_value::MechCommandRegisterValue;
431 /// let mut dictionary = MechObjectDictionary::new(
432 /// 0x000401, // Device Type (generic I/O device)
433 /// 1000, // Heartbeat interval of 1000 ms
434 /// 12345, // Vendor ID
435 /// 67890, // Product Code
436 /// );
437 /// let initial_value = MechCommandRegisterValue::from_int32(12345);
438 /// dictionary.add_value_entry(
439 /// 0x1000,
440 /// ObjectType::Variable,
441 /// "Example Value".to_string(),
442 /// "int32".to_string(),
443 /// AccessRights::ReadWrite,
444 /// true,
445 /// initial_value,
446 /// );
447 /// ```
448 pub fn add_value_entry(
449 &mut self,
450 index: u16,
451 object_type: ObjectType,
452 name: String,
453 datatype: String,
454 access_rights: AccessRights,
455 mandatory: bool,
456 initial_value: MechCommandRegisterValue,
457 ) {
458 let value = MechObjectDictionaryValue::Single(initial_value);
459
460 let entry = MechObjectDictionaryEntry {
461 index,
462 object_type,
463 name,
464 datatype,
465 access_rights,
466 mandatory,
467 value,
468 };
469
470 self.registers.insert(index, entry);
471 }
472
473 /// Adds a new entry with multiple sub-indices to the object dictionary.
474 /// This function is used for entries that require an array of sub-indices, each initialized to `initial_value`.
475 ///
476 /// # Parameters
477 /// - `index`: The register index of the object.
478 /// - `object_type`: The type of the object (e.g., array, record, variable).
479 /// - `name`: A string representing the name of the object.
480 /// - `datatype`: The data type of the object.
481 /// - `access_rights`: Access permissions for this entry (read/write, read-only, write-only).
482 /// - `mandatory`: A boolean indicating whether the object is mandatory or optional.
483 /// - `num_sub_indices`: The number of sub-indices within the entry (each initialized with `initial_value`).
484 /// - `initial_value`: The initial value to set for each sub-index in the array.
485 ///
486 /// # Example
487 /// ```
488 /// use mechutil::object_dictionary::AccessRights;
489 /// use mechutil::object_dictionary::ObjectType;
490 /// use mechutil::register_value::MechCommandRegisterValue;
491 /// use mechutil::object_dictionary::MechObjectDictionary;
492 /// let mut dictionary = MechObjectDictionary::new(
493 /// 0x000401, // Device Type (generic I/O device)
494 /// 1000, // Heartbeat interval of 1000 ms
495 /// 12345, // Vendor ID
496 /// 67890, // Product Code
497 /// );
498 /// let initial_value = MechCommandRegisterValue::from_int32(0);
499 /// dictionary.add_array_entry(
500 /// 0x2000,
501 /// ObjectType::Array,
502 /// "Example Array".to_string(),
503 /// "int32".to_string(),
504 /// AccessRights::ReadWrite,
505 /// true,
506 /// 5,
507 /// initial_value,
508 /// );
509 /// ```
510 pub fn add_array_entry(
511 &mut self,
512 index: u16,
513 object_type: ObjectType,
514 name: String,
515 datatype: String,
516 access_rights: AccessRights,
517 mandatory: bool,
518 num_sub_indices: usize,
519 initial_value: MechCommandRegisterValue,
520 ) {
521 // Create an array of sub-indices, each initialized with the provided initial value
522 // The first index of the array is used to store the size/number of values. So, we need to
523 // add an additional index to the num_indices.
524 let value = MechObjectDictionaryValue::SubIndices(vec![initial_value; num_sub_indices + 1]);
525
526 let entry = MechObjectDictionaryEntry {
527 index,
528 object_type,
529 name,
530 datatype,
531 access_rights,
532 mandatory,
533 value,
534 };
535
536 self.registers.insert(index, entry);
537
538 // Set the 0 index to the number of indices.
539 let _ = self.set_value(
540 index,
541 Some(0),
542 MechCommandRegisterValue::from_uint16(num_sub_indices as u16),
543 );
544 }
545
546 /// Checks if an entry exists in the object dictionary.
547 ///
548 /// # Parameters
549 /// - `index`: The register index.
550 ///
551 /// # Returns
552 /// - `true` if the index exists, otherwise `false`.
553 ///
554 /// # Example
555 /// ```no_run
556 /// use mechutil::{object_dictionary::{MechObjectDictionary, ObjectType,AccessRights}};
557 /// use mechutil::register_value::MechCommandRegisterValue;
558 /// let mut dictionary = MechObjectDictionary::new(
559 /// 0x000401, // Device Type (generic I/O device)
560 /// 1000, // Heartbeat interval of 1000 ms
561 /// 12345, // Vendor ID
562 /// 67890, // Product Code
563 /// );
564 /// let initial_value = MechCommandRegisterValue::new(); // Assume this creates a default value.
565 /// dictionary.add_entry(0x1000, ObjectType::Array, "Example Object".to_string(), "uint32".to_string(), AccessRights::ReadWrite, true, 5, initial_value);
566 /// assert!(dictionary.entry_exists(0x1000)); // Returns true
567 /// ```
568 pub fn entry_exists(&self, index: u16) -> bool {
569 self.registers.contains_key(&index)
570 }
571
572 /// Reads the value of an object dictionary entry without sub-indices.
573 ///
574 /// # Parameters
575 /// - `index`: The register index.
576 ///
577 /// # Returns
578 /// - `Result<&MechCommandRegisterValue, anyhow::Error>`: Returns the value if found and valid,
579 /// or an error message if the entry does not exist or if it contains sub-indices.
580 pub fn get_index_value(&self, index: u16) -> Result<&MechCommandRegisterValue, anyhow::Error> {
581 let entry;
582 match self.registers.get(&index) {
583 Some(res) => entry = res,
584 None => return Err(anyhow!("Entry {} not found", index)),
585 }
586
587 // Check that this entry is a single value, not an array
588 match &entry.value {
589 MechObjectDictionaryValue::Single(value) => Ok(value),
590 MechObjectDictionaryValue::SubIndices(values) => {
591 // Return the value of the first index, which should be the length of this index.
592 match values.first() {
593 Some(ret) => Ok(ret),
594 None => {
595 return Err(anyhow!("Index {} is empty.", index));
596 }
597 }
598 }
599 }
600 }
601
602 /// Reads the value of a specific sub-index for an entry that has sub-indices.
603 ///
604 /// # Parameters
605 /// - `index`: The register index.
606 /// - `sub_index`: The sub-index to retrieve within the entry.
607 ///
608 /// # Returns
609 /// - `Result<&MechCommandRegisterValue, anyhow::Error>`: Returns the value at the specified sub-index if found and valid,
610 /// or an error message if the entry does not exist, if it does not have sub-indices, or if the sub-index is out of bounds.
611 pub fn get_sub_index_value(
612 &self,
613 index: u16,
614 sub_index: u16,
615 ) -> Result<&MechCommandRegisterValue, anyhow::Error> {
616 let entry;
617 match self.registers.get(&index) {
618 Some(res) => entry = res,
619 None => return Err(anyhow!("Entry {} not found", index)),
620 }
621
622 // Check that this entry has sub-indices
623 match &entry.value {
624 MechObjectDictionaryValue::Single(_) => Err(anyhow!(
625 "Entry does not contain sub-indices; use `get_index_value` instead".to_string()
626 )),
627 MechObjectDictionaryValue::SubIndices(values) => match values.get(sub_index as usize) {
628 Some(ret) => return Ok(ret),
629 None => {
630 return Err(anyhow!("Sub-index {} out of bounds", sub_index));
631 }
632 },
633 }
634 }
635
636 /// Retrieves all sub-index values of a specified entry in the Object Dictionary.
637 /// For convenience.
638 ///
639 /// This function is useful for obtaining the full list of values when an entry
640 /// contains multiple sub-indices (e.g., in array or record types).
641 ///
642 /// # Parameters
643 /// - `index`: The 16-bit register index of the object in the Object Dictionary.
644 ///
645 /// # Returns
646 /// - `Ok(Vec<MechCommandRegisterValue>)`: A vector containing all sub-index values
647 /// if the specified entry exists and contains sub-indices.
648 /// - `Err(anyhow::Error)`: An error if the entry does not exist or does not contain sub-indices.
649 ///
650 /// # Errors
651 /// Returns an error if the entry:
652 /// - Does not exist in the Object Dictionary.
653 /// - Exists but does not have sub-indices.
654 ///
655 /// # Example
656 /// ```no_run
657 /// use mechutil::object_dictionary::MechObjectDictionary;
658 /// use mechutil::register_value::MechCommandRegisterValue;
659 ///
660 /// let dictionary = MechObjectDictionary::new(0x000401, 1000, 12345, 67890);
661 ///
662 /// // Assuming an array entry at index 0x2000 has been added to the dictionary.
663 /// let result = dictionary.get_all_sub_index_values(0x2000);
664 /// match result {
665 /// Ok(values) => {
666 /// for (i, value) in values.iter().enumerate() {
667 /// println!("Sub-index {}: {:?}", i, value);
668 /// }
669 /// },
670 /// Err(e) => println!("Error retrieving sub-index values: {}", e),
671 /// }
672 /// ```
673 pub fn get_all_sub_index_values(
674 &self,
675 index: u16,
676 ) -> Result<Vec<MechCommandRegisterValue>, anyhow::Error> {
677 let entry;
678 match self.registers.get(&index) {
679 Some(res) => entry = res,
680 None => return Err(anyhow!("Entry {} not found", index)),
681 }
682
683 // Check that this entry has sub-indices
684 match &entry.value {
685 MechObjectDictionaryValue::Single(_) => {
686 Err(anyhow!("Entry does not contain sub-indices!"))
687 }
688 MechObjectDictionaryValue::SubIndices(values) => {
689 return Ok(values.clone());
690 }
691 }
692 }
693
694 /// Retrieves a specified range of sub-index values for an entry in the Object Dictionary.
695 /// For convenience.
696 ///
697 /// This function provides a subset of sub-indices between `start` and `end` for entries
698 /// with multiple sub-indices (such as arrays). If `end` is negative, it includes all indices
699 /// from `start` to the last index.
700 ///
701 /// # Parameters
702 /// - `index`: The 16-bit register index of the object in the Object Dictionary.
703 /// - `start`: The starting sub-index (inclusive).
704 /// - `end`: The ending sub-index (inclusive). If `end` is negative, it defaults to the last sub-index.
705 ///
706 /// # Returns
707 /// - `Ok(Vec<MechCommandRegisterValue>)`: A vector containing values in the specified range,
708 /// if the entry exists and contains sub-indices within the range.
709 /// - `Err(anyhow::Error)`: An error if the entry does not exist, does not contain sub-indices, or if
710 /// the specified range is out of bounds.
711 ///
712 /// # Errors
713 /// Returns an error if:
714 /// - The entry does not exist or does not contain sub-indices.
715 /// - The specified `start` index is out of bounds.
716 /// - The specified `end` index is out of bounds.
717 ///
718 /// # Example
719 /// ```no_run
720 /// use mechutil::object_dictionary::MechObjectDictionary;
721 /// use mechutil::register_value::MechCommandRegisterValue;
722 ///
723 /// let dictionary = MechObjectDictionary::new(0x000401, 1000, 12345, 67890);
724 ///
725 /// // Assuming an array entry at index 0x2000 with multiple sub-indices has been added.
726 /// let result = dictionary.get_sub_index_range_values(0x2000, 1, 3);
727 /// match result {
728 /// Ok(values) => {
729 /// for (i, value) in values.iter().enumerate() {
730 /// println!("Sub-index {}: {:?}", i + 1, value);
731 /// }
732 /// },
733 /// Err(e) => println!("Error retrieving range of sub-index values: {}", e),
734 /// }
735 /// ```
736 pub fn get_sub_index_range_values(
737 &self,
738 index: u16,
739 start: u16,
740 end: i16,
741 ) -> Result<Vec<MechCommandRegisterValue>, anyhow::Error> {
742 let entry;
743 match self.registers.get(&index) {
744 Some(res) => entry = res,
745 None => return Err(anyhow!("Entry {} not found", index)),
746 }
747
748 // Check that this entry has sub-indices
749 match &entry.value {
750 MechObjectDictionaryValue::Single(_) => {
751 Err(anyhow!("Entry does not contain sub-indices!"))
752 }
753 MechObjectDictionaryValue::SubIndices(values) => {
754 if (start as usize) >= values.len() {
755 return Err(anyhow!(
756 "start {} out of range of values len {}",
757 start,
758 values.len()
759 ));
760 }
761
762 let end_index;
763 if end < 0 {
764 end_index = values.len() - 1;
765 } else if (end as usize) < values.len() - 1 {
766 end_index = end as usize;
767 } else {
768 return Err(anyhow!(
769 "end {} out of range of values len {}",
770 end,
771 values.len()
772 ));
773 }
774
775 return Ok(values[(start as usize)..=end_index].to_vec());
776 }
777 }
778 }
779
780 /// Sets the value of an object dictionary entry at a specific index. For use internally. Does not
781 /// take access rights into account.
782 ///
783 /// # Parameters
784 /// - `index`: The register index.
785 /// - `sub_index`: Optional sub-index for entries with multiple values (arrays).
786 /// - `new_value`: The new value to be set.
787 ///
788 /// # Returns
789 /// - `Result<(), anyhow::Error>`: Returns Ok if the value is set successfully, or an error message if setting is denied or if the entry does not exist.
790 pub fn set_value(
791 &mut self,
792 index: u16,
793 sub_index: Option<u16>,
794 new_value: MechCommandRegisterValue,
795 ) -> Result<(), anyhow::Error> {
796 // Get the entry for the specified index.
797
798 let entry;
799 match self.registers.get_mut(&index) {
800 Some(res) => {
801 entry = res;
802 }
803 None => {
804 log::debug!("Entry {} not found. Available entries:", index);
805 for key in self.registers.keys() {
806 log::debug!("\t{}", key);
807 }
808
809 return Err(anyhow!("Entry {} not found.", index));
810 }
811 }
812
813 // Set the value based on whether the entry has sub-indices or is a single value.
814 match &mut entry.value {
815 MechObjectDictionaryValue::Single(value) => {
816 if sub_index.is_some() {
817 return Err(anyhow!(
818 "Sub-index provided, but entry {} is not an array",
819 index
820 ));
821 }
822 *value = new_value; // Directly set the single value
823 }
824 MechObjectDictionaryValue::SubIndices(values) => {
825 if let Some(sub_idx) = sub_index {
826 // Ensure sub-index is within bounds
827 if (sub_idx as usize) < values.len() {
828 values[sub_idx as usize] = new_value;
829 } else {
830 return Err(anyhow!("Sub-index {} out of bounds", sub_idx));
831 }
832 } else {
833 return Err(anyhow!("Sub-index is required for array values"));
834 }
835 }
836 }
837
838 Ok(())
839 }
840
841 /// Sets the value of an object dictionary entry at a specific index. For use internally. Does not
842 /// take access rights into account. Only works for single-value entries.
843 ///
844 /// # Parameters
845 /// - `index`: The register index.
846 /// - `new_value`: The new value to be set.
847 ///
848 /// # Returns
849 /// - `Result<(), anyhow::Error>`: Returns Ok if the value is set successfully, or an error message if setting is denied or if the entry does not exist.
850 pub fn set_index_value(
851 &mut self,
852 index: u16,
853 new_value: MechCommandRegisterValue,
854 ) -> Result<(), anyhow::Error> {
855 // Get the entry for the specified index.
856 let entry;
857 match self.registers.get_mut(&index) {
858 Some(res) => entry = res,
859 None => return Err(anyhow!("Entry {} not found", index)),
860 }
861
862 // Set the value based on whether the entry has sub-indices or is a single value.
863 match &mut entry.value {
864 MechObjectDictionaryValue::Single(value) => {
865 *value = new_value; // Directly set the single value
866 }
867 MechObjectDictionaryValue::SubIndices(_) => {
868 return Err(anyhow!("Sub-index is required for array values"));
869 }
870 }
871
872 Ok(())
873 }
874
875 /// Sets the value of the sub-index an object dictionary entry at a specific index. For use internally. Does not
876 /// take access rights into account. Only works for Array or Record entries, not Single value entries.
877 ///
878 /// # Parameters
879 /// - `index`: The register index.
880 /// - `sub_index`: sub-index for entries with multiple values (arrays & records).
881 /// - `new_value`: The new value to be set.
882 ///
883 /// # Returns
884 /// - `Result<(), anyhow::Error>`: Returns Ok if the value is set successfully, or an error message if setting is denied or if the entry does not exist.
885 pub fn set_sub_index_value(
886 &mut self,
887 index: u16,
888 sub_index: u16,
889 new_value: MechCommandRegisterValue,
890 ) -> Result<(), anyhow::Error> {
891 // Get the entry for the specified index.
892 let entry;
893 match self.registers.get_mut(&index) {
894 Some(res) => entry = res,
895 None => return Err(anyhow!("Entry {} not found", index)),
896 }
897
898 // Set the value based on whether the entry has sub-indices or is a single value.
899 match &mut entry.value {
900 MechObjectDictionaryValue::Single(value) => {
901 return Err(anyhow!(
902 "Sub-index provided, but entry {} is not an array",
903 index
904 ));
905 }
906 MechObjectDictionaryValue::SubIndices(values) => {
907 if (sub_index as usize) < values.len() {
908 values[sub_index as usize] = new_value;
909 } else {
910 return Err(anyhow!("Sub-index {} out of bounds", sub_index));
911 }
912 }
913 }
914
915 Ok(())
916 }
917
918 /// Client/External-facing function to set the value of an object dictionary entry at a specific index, if allowed by access rights.
919 /// When taking requests from an external source, use this function to set the value of an Object Dictionary Entry.
920 ///
921 /// # Parameters
922 /// - `index`: The register index.
923 /// - `sub_index`: Optional sub-index for entries with multiple values (arrays).
924 /// - `new_value`: The new value to be set.
925 ///
926 /// # Returns
927 /// - `Result<(), anyhow::Error>`: Returns Ok if the value is set successfully, or an error message if setting is denied or if the entry does not exist.
928 pub fn set_value_external(
929 &mut self,
930 index: u16,
931 sub_index: Option<u16>,
932 new_value: MechCommandRegisterValue,
933 ) -> Result<(), anyhow::Error> {
934 // Get the entry for the specified index.
935 let entry;
936 match self.registers.get_mut(&index) {
937 Some(res) => entry = res,
938 None => return Err(anyhow!("Entry {} not found", index)),
939 }
940
941 // Check if the entry is writable based on its access rights.
942 match entry.access_rights {
943 AccessRights::ReadOnly | AccessRights::Const => {
944 return Err(anyhow!("Cannot set value: Entry is read-only"));
945 }
946 AccessRights::WriteOnly | AccessRights::ReadWrite => {}
947 }
948
949 return self.set_value(index, sub_index, new_value);
950 }
951
952 /// Loads the object dictionary from a JSON file.
953 ///
954 /// # Parameters
955 /// - `filename`: The path to the JSON file.
956 ///
957 /// # Returns
958 /// - `Result<(), Box<dyn std::error::Error>>`: Ok if successful, or an error otherwise.
959 ///
960 /// # Example
961 /// ```no_run
962 /// use mechutil::{object_dictionary::MechObjectDictionary};
963 /// let mut dictionary = MechObjectDictionary::new(
964 /// 0x000401, // Device Type (generic I/O device)
965 /// 1000, // Heartbeat interval of 1000 ms
966 /// 12345, // Vendor ID
967 /// 67890, // Product Code
968 /// );
969 /// dictionary.load_from_json("input.json").unwrap();
970 /// ```
971 pub fn load_from_json(&mut self, filename: &str) -> Result<(), Box<dyn std::error::Error>> {
972 let data = fs::read_to_string(filename)?;
973 let json_data: HashMap<u16, MechObjectDictionaryEntry> = serde_json::from_str(&data)?;
974
975 for (index, entry) in json_data {
976 self.registers.insert(index, entry);
977 }
978
979 Ok(())
980 }
981
982 /// Serializes the object dictionary to a JSON file.
983 ///
984 /// # Parameters
985 /// - `filename`: The path to the JSON file.
986 ///
987 /// # Returns
988 /// - `Result<(), Box<dyn std::error::Error>>`: Ok if successful, or an error otherwise.
989 ///
990 /// # Example
991 /// ```no_run
992 /// use mechutil::{object_dictionary::MechObjectDictionary};
993 /// let mut dictionary = MechObjectDictionary::new(
994 /// 0x000401, // Device Type (generic I/O device)
995 /// 1000, // Heartbeat interval of 1000 ms
996 /// 12345, // Vendor ID
997 /// 67890, // Product Code
998 /// );
999 /// dictionary.save_to_json("output.json").unwrap();
1000 /// ```
1001 pub fn save_to_json(&self, filename: &str) -> Result<(), Box<dyn std::error::Error>> {
1002 let json_data = serde_json::to_string_pretty(&self.registers)?;
1003 fs::write(filename, json_data)?;
1004 Ok(())
1005 }
1006}
1007
1008#[cfg(test)]
1009mod tests {
1010 use super::*;
1011
1012 #[test]
1013 fn test_create_and_check_entry() {
1014 let mut dictionary = MechObjectDictionary::new(0x2001, 1000, 0, 0);
1015
1016 // Create a new entry with 5 sub-indices
1017 dictionary.add_entry(
1018 0x1000,
1019 ObjectType::Array,
1020 "Example Object".to_string(),
1021 "uint32".to_string(),
1022 AccessRights::ReadWrite,
1023 true,
1024 5,
1025 MechCommandRegisterValue::from_uint32(5),
1026 );
1027
1028 // Check if the entry exists
1029 assert!(dictionary.entry_exists(0x1000));
1030
1031 // Check if an entry that hasn't been added doesn't exist
1032 assert!(!dictionary.entry_exists(0x2000));
1033 }
1034
1035 #[test]
1036 fn test_single_value_entry() {
1037 let mut dictionary = MechObjectDictionary::new(0x2001, 1000, 0, 0);
1038
1039 // Create a new entry with a single value (no sub-indices)
1040 dictionary.add_entry(
1041 0x2000,
1042 ObjectType::Variable,
1043 "Single Value Object".to_string(),
1044 "int16".to_string(),
1045 AccessRights::ReadOnly,
1046 false,
1047 1,
1048 MechCommandRegisterValue::from_int16(1),
1049 );
1050
1051 // Check if the entry exists
1052 assert!(dictionary.entry_exists(0x2000));
1053
1054 assert!(
1055 dictionary
1056 .set_index_value(0x2000, MechCommandRegisterValue::from_int16(1234))
1057 .is_ok()
1058 );
1059
1060 let val_check = dictionary.get_index_value(0x2000).unwrap();
1061 assert_eq!(val_check.to_int16().unwrap(), 1234);
1062 }
1063
1064 #[test]
1065 fn test_serialize_to_json() {
1066 let mut dictionary = MechObjectDictionary::new(0x2001, 1000, 0, 0);
1067
1068 // Add an entry
1069 dictionary.add_entry(
1070 0x1000,
1071 ObjectType::Array,
1072 "Test Object".to_string(),
1073 "uint32".to_string(),
1074 AccessRights::ReadWrite,
1075 true,
1076 3,
1077 MechCommandRegisterValue::from_int16(3),
1078 );
1079
1080 // Serialize to JSON
1081 let json_data = serde_json::to_string_pretty(&dictionary).unwrap();
1082
1083 // Check if JSON contains the index and the correct values
1084 assert!(json_data.contains("\"index\": 4096")); // 0x1000 in decimal is 4096
1085 assert!(json_data.contains("\"name\": \"Test Object\""));
1086 assert!(json_data.contains("\"access_rights\": \"ReadWrite\""));
1087 }
1088
1089 #[test]
1090 fn test_deserialize_from_json() {
1091 let json_data = r#"
1092 {
1093 "4096": {
1094 "index": 4096,
1095 "object_type": "Array",
1096 "name": "Deserialized Object",
1097 "datatype": "uint32",
1098 "access_rights": "ReadWrite",
1099 "mandatory": true,
1100 "value": {
1101 "SubIndices": [
1102 {
1103 "type_id": 7,
1104 "value_len": 4,
1105 "num_columns": 1,
1106 "num_rows": 1,
1107 "data": [1, 2, 3, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
1108 }
1109 ]
1110 }
1111 }
1112 }
1113 "#;
1114
1115 let mut dictionary = MechObjectDictionary::new(0x2001, 1000, 0, 0);
1116
1117 // Deserialize from JSON
1118 let json_data: HashMap<u16, MechObjectDictionaryEntry> =
1119 serde_json::from_str(&json_data).unwrap();
1120
1121 // Check if the entry was deserialized correctly
1122 dictionary.registers = json_data;
1123 assert!(dictionary.entry_exists(0x1000));
1124 let entry = dictionary.registers.get(&0x1000).unwrap();
1125 assert_eq!(entry.name, "Deserialized Object");
1126 assert_eq!(entry.datatype, "uint32");
1127 assert!(matches!(
1128 entry.value,
1129 MechObjectDictionaryValue::SubIndices(_)
1130 ));
1131 }
1132}