Skip to main content

canopen_rs/
object_dictionary.rs

1//! The CANopen object dictionary (OD) model.
2//!
3//! Every CANopen device exposes its data as a dictionary of objects addressed
4//! by a 16-bit `index` and an 8-bit `subindex` (CiA 301 §7.4). Communication
5//! objects live in `0x1000..=0x1FFF`, manufacturer-specific objects in
6//! `0x2000..=0x5FFF`, and standardised device-profile objects in
7//! `0x6000..=0x9FFF`.
8//!
9//! [`ObjectDictionary`] is backed by a [`heapless::LinearMap`] with a
10//! compile-time capacity, so the same type serves both `no_std` embedded
11//! nodes and host tooling with no allocator.
12
13use heapless::LinearMap;
14
15use crate::datatypes::Value;
16use crate::{Error, Result};
17
18/// The address of an object dictionary entry: a 16-bit index and 8-bit
19/// subindex.
20///
21/// Subindex `0` conventionally holds the "number of entries" for `RECORD` and
22/// `ARRAY` objects; simple `VAR` objects use subindex `0` for the value.
23#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
24pub struct Address {
25    /// 16-bit object index.
26    pub index: u16,
27    /// 8-bit subindex.
28    pub subindex: u8,
29}
30
31impl Address {
32    /// Construct an address from an index and subindex.
33    pub const fn new(index: u16, subindex: u8) -> Self {
34        Self { index, subindex }
35    }
36}
37
38/// Access rights for an object dictionary entry.
39#[derive(Debug, Clone, Copy, PartialEq, Eq)]
40pub enum AccessType {
41    /// Read-only.
42    Ro,
43    /// Write-only.
44    Wo,
45    /// Read/write.
46    Rw,
47    /// Read-only constant (value fixed at design time).
48    Const,
49}
50
51impl AccessType {
52    /// Whether an SDO/PDO read is permitted.
53    pub const fn is_readable(self) -> bool {
54        matches!(self, AccessType::Ro | AccessType::Rw | AccessType::Const)
55    }
56
57    /// Whether an SDO/PDO write is permitted.
58    pub const fn is_writable(self) -> bool {
59        matches!(self, AccessType::Wo | AccessType::Rw)
60    }
61}
62
63/// A single object dictionary entry: a typed value and its access rights.
64#[derive(Debug, Clone, Copy, PartialEq)]
65pub struct Entry {
66    /// The current value (its variant fixes the object's data type).
67    pub value: Value,
68    /// The entry's access rights.
69    pub access: AccessType,
70}
71
72impl Entry {
73    /// A read/write entry.
74    pub const fn rw(value: Value) -> Self {
75        Self {
76            value,
77            access: AccessType::Rw,
78        }
79    }
80
81    /// A read-only entry.
82    pub const fn ro(value: Value) -> Self {
83        Self {
84            value,
85            access: AccessType::Ro,
86        }
87    }
88
89    /// A read-only constant entry.
90    pub const fn constant(value: Value) -> Self {
91        Self {
92            value,
93            access: AccessType::Const,
94        }
95    }
96}
97
98/// An object dictionary holding up to `N` entries, addressed by
99/// `(index, subindex)`.
100#[derive(Debug)]
101pub struct ObjectDictionary<const N: usize> {
102    entries: LinearMap<Address, Entry, N>,
103}
104
105impl<const N: usize> Default for ObjectDictionary<N> {
106    fn default() -> Self {
107        Self::new()
108    }
109}
110
111impl<const N: usize> ObjectDictionary<N> {
112    /// Create an empty object dictionary.
113    pub const fn new() -> Self {
114        Self {
115            entries: LinearMap::new(),
116        }
117    }
118
119    /// Insert or replace the entry at `addr`.
120    ///
121    /// Returns [`Error::DictionaryFull`] when capacity `N` is exhausted.
122    pub fn insert(&mut self, addr: Address, entry: Entry) -> Result<()> {
123        self.entries
124            .insert(addr, entry)
125            .map(|_| ())
126            .map_err(|_| Error::DictionaryFull)
127    }
128
129    /// Borrow the raw entry at `addr`, ignoring access rights.
130    pub fn entry(&self, addr: Address) -> Option<&Entry> {
131        self.entries.get(&addr)
132    }
133
134    /// Read the value at `addr`, honouring access rights.
135    ///
136    /// Returns [`Error::ObjectNotFound`] if absent, or [`Error::WriteOnly`] if
137    /// the object is not readable.
138    pub fn read(&self, addr: Address) -> Result<Value> {
139        let entry = self.entries.get(&addr).ok_or(Error::ObjectNotFound)?;
140        if !entry.access.is_readable() {
141            return Err(Error::WriteOnly);
142        }
143        Ok(entry.value)
144    }
145
146    /// Write `value` at `addr`, honouring access rights and the object's data
147    /// type.
148    ///
149    /// Returns [`Error::ObjectNotFound`] if absent, [`Error::ReadOnly`] if not
150    /// writable, or [`Error::TypeMismatch`] if `value`'s type differs from the
151    /// stored object's type.
152    pub fn write(&mut self, addr: Address, value: Value) -> Result<()> {
153        let entry = self.entries.get_mut(&addr).ok_or(Error::ObjectNotFound)?;
154        if !entry.access.is_writable() {
155            return Err(Error::ReadOnly);
156        }
157        if entry.value.data_type() != value.data_type() {
158            return Err(Error::TypeMismatch);
159        }
160        entry.value = value;
161        Ok(())
162    }
163}
164
165#[cfg(test)]
166mod tests {
167    use super::*;
168    use crate::datatypes::Value;
169
170    fn od() -> ObjectDictionary<8> {
171        let mut od = ObjectDictionary::new();
172        // 0x1000 Device type — read-only constant.
173        od.insert(
174            Address::new(0x1000, 0),
175            Entry::constant(Value::Unsigned32(0x0000_0192)),
176        )
177        .unwrap();
178        // 0x1017 Producer heartbeat time — read/write.
179        od.insert(Address::new(0x1017, 0), Entry::rw(Value::Unsigned16(1000)))
180            .unwrap();
181        od
182    }
183
184    #[test]
185    fn read_returns_stored_value() {
186        assert_eq!(
187            od().read(Address::new(0x1000, 0)).unwrap(),
188            Value::Unsigned32(0x192)
189        );
190    }
191
192    #[test]
193    fn read_missing_object_errors() {
194        assert_eq!(
195            od().read(Address::new(0x9999, 0)),
196            Err(Error::ObjectNotFound)
197        );
198    }
199
200    #[test]
201    fn write_updates_value() {
202        let mut od = od();
203        od.write(Address::new(0x1017, 0), Value::Unsigned16(500))
204            .unwrap();
205        assert_eq!(
206            od.read(Address::new(0x1017, 0)).unwrap(),
207            Value::Unsigned16(500)
208        );
209    }
210
211    #[test]
212    fn write_read_only_object_errors() {
213        let mut od = od();
214        assert_eq!(
215            od.write(Address::new(0x1000, 0), Value::Unsigned32(1)),
216            Err(Error::ReadOnly)
217        );
218    }
219
220    #[test]
221    fn write_wrong_type_errors() {
222        let mut od = od();
223        assert_eq!(
224            od.write(Address::new(0x1017, 0), Value::Unsigned32(1)),
225            Err(Error::TypeMismatch)
226        );
227    }
228
229    #[test]
230    fn dictionary_full_errors() {
231        let mut od: ObjectDictionary<1> = ObjectDictionary::new();
232        od.insert(Address::new(0x2000, 0), Entry::rw(Value::Unsigned8(1)))
233            .unwrap();
234        assert_eq!(
235            od.insert(Address::new(0x2001, 0), Entry::rw(Value::Unsigned8(2))),
236            Err(Error::DictionaryFull)
237        );
238    }
239}