zencan_node/
storage.rs

1//! Handling for persistent storage control objects
2//!
3//!
4
5use core::convert::Infallible;
6
7use zencan_common::{
8    constants::values::SAVE_CMD,
9    objects::{ObjectCode, SubInfo},
10    sdo::AbortCode,
11    AtomicCell,
12};
13
14use crate::object_dict::{ODEntry, ObjectAccess};
15
16/// A callback function type for handling a store objects event
17pub type StoreObjectsCallback =
18    dyn Fn(&mut dyn embedded_io::Read<Error = Infallible>, usize) + Sync;
19
20#[derive(Default)]
21#[allow(missing_debug_implementations)]
22/// Shared state for supporting object storage
23pub struct StorageContext {
24    pub(crate) store_callback: AtomicCell<Option<&'static StoreObjectsCallback>>,
25}
26
27impl StorageContext {
28    /// Create a new StorageContext
29    pub const fn new() -> Self {
30        Self {
31            store_callback: AtomicCell::new(None),
32        }
33    }
34}
35
36/// Implements the storage command object (0x1010)
37#[allow(missing_debug_implementations)]
38pub struct StorageCommandObject {
39    od: &'static [ODEntry<'static>],
40    storage_context: &'static StorageContext,
41}
42
43impl StorageCommandObject {
44    /// Create a new storage context object
45    pub const fn new(
46        od: &'static [ODEntry<'static>],
47        storage_context: &'static StorageContext,
48    ) -> Self {
49        Self {
50            od,
51            storage_context,
52        }
53    }
54}
55
56impl ObjectAccess for StorageCommandObject {
57    fn read(&self, sub: u8, offset: usize, buf: &mut [u8]) -> Result<usize, AbortCode> {
58        match sub {
59            0 => {
60                if offset != 0 || buf.len() != 1 {
61                    Err(AbortCode::DataTypeMismatch)
62                } else {
63                    buf[0] = 1;
64                    Ok(1)
65                }
66            }
67            1 => {
68                // Bit 0 indicates the node is capable of saving objects. Set it if a callback has
69                // been registered.
70                let mut value = 0u32;
71                if self.storage_context.store_callback.load().is_some() {
72                    value |= 1;
73                }
74                let value_bytes = value.to_le_bytes();
75                if offset < value_bytes.len() {
76                    let read_len = buf.len().min(value_bytes.len() - offset);
77                    buf[..read_len].copy_from_slice(&value_bytes[offset..offset + read_len]);
78                    Ok(read_len)
79                } else {
80                    Ok(0)
81                }
82            }
83            _ => Err(AbortCode::NoSuchSubIndex),
84        }
85    }
86
87    fn read_size(&self, sub: u8) -> Result<usize, AbortCode> {
88        match sub {
89            0 => Ok(1),
90            1 => Ok(4),
91            _ => Err(AbortCode::NoSuchSubIndex),
92        }
93    }
94
95    fn write(&self, sub: u8, data: &[u8]) -> Result<(), AbortCode> {
96        match sub {
97            0 => Err(AbortCode::ReadOnly),
98            1 => {
99                if data.len() != 4 {
100                    Err(AbortCode::DataTypeMismatch)
101                } else {
102                    let value = u32::from_le_bytes(data[0..4].try_into().unwrap());
103                    // Magic value ('save') triggering a save
104                    if value == SAVE_CMD {
105                        if let Some(cb) = self.storage_context.store_callback.load() {
106                            crate::persist::serialize(self.od, cb);
107                            Ok(())
108                        } else {
109                            Err(AbortCode::ResourceNotAvailable)
110                        }
111                    } else {
112                        Err(AbortCode::IncompatibleParameter)
113                    }
114                }
115            }
116            _ => Err(AbortCode::NoSuchSubIndex),
117        }
118    }
119
120    fn object_code(&self) -> ObjectCode {
121        ObjectCode::Record
122    }
123
124    fn sub_info(&self, sub: u8) -> Result<SubInfo, AbortCode> {
125        match sub {
126            0 => Ok(SubInfo::MAX_SUB_NUMBER),
127            1 => Ok(SubInfo::new_u32().rw_access()),
128            _ => Err(AbortCode::NoSuchSubIndex),
129        }
130    }
131}