zencan_client/
lss_master.rs

1//! A
2use core::time::Duration;
3
4use tokio::time::timeout_at;
5use zencan_common::{
6    lss::{LssIdentity, LssRequest, LssResponse, LssState, LSS_FASTSCAN_CONFIRM},
7    traits::{AsyncCanReceiver, AsyncCanSender},
8    NodeId,
9};
10
11use snafu::Snafu;
12
13#[derive(Debug)]
14/// Struct to interact with nodes using the LSS protocol
15pub struct LssMaster<S, R> {
16    sender: S,
17    receiver: R,
18}
19
20/// Error returned by [`LssMaster`]
21#[derive(Debug, Snafu, Clone, Copy)]
22pub enum LssError {
23    /// Timed out while waiting for an expected LSS response
24    #[snafu(display("Timed out waiting for LSS response"))]
25    Timeout,
26    /// The LSS slave returned an error code in response to a ConfigBitTiming command
27    #[snafu(display(
28        "LSS slave returned an error in response to ConfigBitTiming command. error: {}, Spec error: {}",
29        error,
30        spec_error
31    ))]
32    BitTimingConfigError {
33        /// Error code
34        ///
35        /// 1 - Baudrate not supported
36        /// 255 - Special error code in spec_error
37        error: u8,
38        /// Manufacturer specific error code
39        ///
40        /// Only supposed to be valid when error is 255
41        spec_error: u8,
42    },
43    /// The LSS slave returned an error code in response to a ConfigNodeId command
44    #[snafu(display(
45        "LSS slave returned an error in response to ConfigNodeId command. error: {}, Spec error: {}",
46        error,
47        spec_error
48    ))]
49    NodeIdConfigError {
50        /// Error code
51        ///
52        /// 1 - Node address is invalid
53        /// 255 - Special error code in spec_error
54        error: u8,
55        /// Manufacturer specific error code
56        ///
57        /// Only supposed to be valid when error is 255
58        spec_error: u8,
59    },
60    /// The LSS slave returned an error code in response to a StoreConfiguration command
61    #[snafu(display(
62        "LSS slave returned an error in response to StoreConfiguration. error: {}, Spec error: {}",
63        error,
64        spec_error
65    ))]
66    NodeStoreConfigError {
67        /// Error code
68        ///
69        /// 1 - Node does not support storing configuration
70        /// 255 - Special error code in spec_error
71        error: u8,
72        /// Manufacturer specific error code
73        ///
74        /// Only supposed to be valid when error is 255
75        spec_error: u8,
76    },
77}
78
79impl<S: AsyncCanSender, R: AsyncCanReceiver> LssMaster<S, R> {
80    /// Create a new LssMaster
81    ///
82    /// # Arguments
83    /// - `sender`: An object which implements [`AsyncCanSender`] to be used for sending messages to
84    ///   the bus
85    /// - `receiver`: An object which implements [`AsyncCanReceiver`] to be used for receiving
86    ///   messages from the bus
87    ///
88    /// When using socketcan, these can be created with [`crate::open_socketcan`].
89    pub fn new(sender: S, receiver: R) -> Self {
90        Self { sender, receiver }
91    }
92
93    /// Configure an LSS slave with known identity
94    ///
95    /// If you know the 128-bit identity value for a node, you can configure it this way.
96    pub async fn configure_by_identity(
97        &mut self,
98        identity: LssIdentity,
99        node_id: NodeId,
100        baud_rate_table: u8,
101        baud_rate_index: u8,
102    ) -> Result<(), LssError> {
103        // Put the specified node into configuration mode
104        self.enter_config_by_identity(
105            identity.vendor_id,
106            identity.product_code,
107            identity.revision,
108            identity.serial,
109        )
110        .await?;
111        // set the node ID
112        self.set_node_id(node_id).await?;
113        // Set the bit rate
114        self.set_baud_rate(baud_rate_table, baud_rate_index).await?;
115
116        Ok(())
117    }
118
119    /// Send a sequence of messages to put a single node into configuration mode based on its identity
120    pub async fn enter_config_by_identity(
121        &mut self,
122        vendor_id: u32,
123        product_code: u32,
124        revision: u32,
125        serial: u32,
126    ) -> Result<(), LssError> {
127        const RESPONSE_TIMEOUT: Duration = Duration::from_millis(50);
128        // Send global mode to put all nodes into waiting state. No response expected.
129        self.send_and_receive(LssRequest::SwitchModeGlobal { mode: 0 }, Duration::ZERO)
130            .await;
131
132        // Now send the identity messages. If a LSS slave node recognizes its identity, it will respond
133        // to the serial setting message with a SwitchStateResponse message
134        self.send_and_receive(LssRequest::SwitchStateVendor { vendor_id }, Duration::ZERO)
135            .await;
136        self.send_and_receive(
137            LssRequest::SwitchStateProduct { product_code },
138            Duration::ZERO,
139        )
140        .await;
141        self.send_and_receive(LssRequest::SwitchStateRevision { revision }, Duration::ZERO)
142            .await;
143        match self
144            .send_and_receive(LssRequest::SwitchStateSerial { serial }, RESPONSE_TIMEOUT)
145            .await
146        {
147            Some(LssResponse::SwitchStateResponse) => Ok(()),
148            _ => Err(LssError::Timeout),
149        }
150    }
151
152    /// Send a command to set the baud rate on the LSS slave current in configuration mode
153    ///
154    /// The node must have been put into configuration mode already.
155    ///
156    /// Returns Err(LssError::Timeout) if the node does not respond to the command, or
157    /// Err(LssError::ConfigError) if the node responds with an error.
158    ///
159    /// # Arguments
160    /// * `table` - The index of the table of baud rate settings to use (0 for the default CANOpen
161    ///   table)
162    /// * `index` - The index into the table of the baud rate setting to use
163    pub async fn set_baud_rate(&mut self, table: u8, index: u8) -> Result<(), LssError> {
164        const RESPONSE_TIMEOUT: Duration = Duration::from_millis(50);
165        match self
166            .send_and_receive(
167                LssRequest::ConfigureBitTiming { table, index },
168                RESPONSE_TIMEOUT,
169            )
170            .await
171        {
172            Some(LssResponse::ConfigureBitTimingAck { error, spec_error }) => {
173                if error == 0 {
174                    Ok(())
175                } else {
176                    Err(LssError::BitTimingConfigError { error, spec_error })
177                }
178            }
179            _ => Err(LssError::Timeout),
180        }
181    }
182
183    /// Send a command to set the node ID on the LSS slave current in configuration mode
184    ///
185    /// The node must have been put into configuration mode already.
186    ///
187    /// Returns Err(LssError::Timeout) if the node does not respond to the command, or
188    /// Err(LssError::ConfigError) if the node responds with an error.
189    pub async fn set_node_id(&mut self, node_id: NodeId) -> Result<(), LssError> {
190        const RESPONSE_TIMEOUT: Duration = Duration::from_millis(50);
191        match self
192            .send_and_receive(
193                LssRequest::ConfigureNodeId {
194                    node_id: node_id.into(),
195                },
196                RESPONSE_TIMEOUT,
197            )
198            .await
199        {
200            Some(LssResponse::ConfigureNodeIdAck { error, spec_error }) => {
201                if error == 0 {
202                    Ok(())
203                } else {
204                    Err(LssError::NodeIdConfigError { error, spec_error })
205                }
206            }
207            _ => Err(LssError::Timeout),
208        }
209    }
210
211    /// Send command to store configuration
212    ///
213    /// The node must have been put into configuration mode already.
214    ///
215    /// Returns Err(LssError::Timeout) if the node does not respond to the command, or
216    /// Err(LssError::ConfigError) if the node responds with an error.
217    pub async fn store_config(&mut self) -> Result<(), LssError> {
218        const RESPONSE_TIMEOUT: Duration = Duration::from_millis(50);
219        match self
220            .send_and_receive(LssRequest::StoreConfiguration, RESPONSE_TIMEOUT)
221            .await
222        {
223            Some(LssResponse::StoreConfigurationAck { error, spec_error }) => {
224                if error == 0 {
225                    Ok(())
226                } else {
227                    Err(LssError::NodeStoreConfigError { error, spec_error })
228                }
229            }
230            _ => Err(LssError::Timeout),
231        }
232    }
233
234    /// Perform a fast scan of the network to find unconfigured nodes
235    ///
236    /// # Arguments
237    /// * `timeout` - The duration of time to wait for responses after each message.
238    ///   Duration::from_millis(20) is probably a pretty safe value, but this depends on the
239    ///   responsiveness of the slaves, and on the amount of bus traffic. If the timeout is set too
240    ///   short, the scan may fail to find existing nodes.
241    pub async fn fast_scan(&mut self, timeout: Duration) -> Option<LssIdentity> {
242        let mut id = [0, 0, 0, 0];
243        let mut sub = 0;
244        let mut next = 0;
245        let mut bit_check;
246
247        let mut send_fs = async |id: &[u32; 4], bit_check: u8, sub: u8, next: u8| -> bool {
248            // Unlike send_and_receive, this function always waits the full timeout, because we don't know
249            // how many nodes will respond to us, so we need to give them time.
250            self.sender
251                .send(
252                    LssRequest::FastScan {
253                        id: id[sub as usize],
254                        bit_check,
255                        sub,
256                        next,
257                    }
258                    .into(),
259                )
260                .await
261                .ok();
262
263            let wait_until = tokio::time::Instant::now() + timeout;
264            let mut resp_flag = false;
265            loop {
266                match timeout_at(wait_until, self.receiver.recv()).await {
267                    // timeout
268                    Err(_) => break,
269                    Ok(Ok(msg)) => {
270                        if let Ok(LssResponse::IdentifySlave) = LssResponse::try_from(msg) {
271                            resp_flag = true;
272                        }
273                    }
274                    _ => (),
275                }
276            }
277            resp_flag
278        };
279
280        // The first message resets the LSS state machines, and a response confirms that there is at
281        // least one unconfigured slave to discover
282        if !send_fs(&id, LSS_FASTSCAN_CONFIRM, sub, next).await {
283            return None;
284        }
285        while sub < 4 {
286            bit_check = 32;
287            while bit_check > 0 {
288                bit_check -= 1;
289                if !send_fs(&id, bit_check, sub, next).await {
290                    id[sub as usize] |= 1 << bit_check;
291                }
292            }
293            next = (sub + 1) % 4;
294            if !send_fs(&id, bit_check, sub, next).await {
295                return None;
296            }
297            sub += 1;
298        }
299
300        Some(LssIdentity {
301            vendor_id: id[0],
302            product_code: id[1],
303            revision: id[2],
304            serial: id[3],
305        })
306    }
307
308    /// Send command to the bus to set the LSS mode for all nodes
309    pub async fn set_global_mode(&mut self, mode: LssState) {
310        // Send global mode to put all nodes into waiting state. No response expected.
311        self.send_and_receive(
312            LssRequest::SwitchModeGlobal { mode: mode as u8 },
313            Duration::ZERO,
314        )
315        .await;
316    }
317
318    async fn send_and_receive(
319        &mut self,
320        msg: LssRequest,
321        timeout: Duration,
322    ) -> Option<LssResponse> {
323        self.sender.send(msg.into()).await.ok()?;
324
325        let wait_until = tokio::time::Instant::now() + timeout;
326        loop {
327            match timeout_at(wait_until, self.receiver.recv()).await {
328                // Got a message
329                Ok(Ok(msg)) => {
330                    match msg.try_into() {
331                        Ok(lss_resp) => return Some(lss_resp),
332                        // Failed to convert message into LSS response. Skip it.
333                        Err(_) => {}
334                    }
335                }
336                // `recv` returned without a message. Keep waiting.
337                Ok(Err(e)) => {
338                    log::error!("Error reading can socket: {e:?}");
339                    return None;
340                }
341                // Timeout waiting
342                Err(_) => return None,
343            }
344        }
345    }
346}