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}