Skip to main content

zencan_client/bus_manager/
bus_manager.rs

1use std::ops::{Deref, DerefMut};
2use std::sync::Mutex;
3use std::time::Duration;
4use std::{collections::HashMap, sync::Arc, time::Instant};
5
6use futures::future::join_all;
7use tokio::task::JoinHandle;
8use zencan_common::constants::object_ids::{
9    RPDO_COMM_BASE, RPDO_MAP_BASE, TPDO_COMM_BASE, TPDO_MAP_BASE,
10};
11use zencan_common::lss::{LssIdentity, LssState};
12use zencan_common::messages::{NmtCommand, NmtCommandSpecifier, SyncObject, ZencanMessage};
13use zencan_common::nmt::NmtState;
14use zencan_common::node_id::ConfiguredNodeId;
15use zencan_common::pdo::PdoCommParameter;
16use zencan_common::sdo::AbortCode;
17use zencan_common::{
18    node_configuration::PdoConfig,
19    pdo::PdoMapping,
20    traits::{AsyncCanReceiver, AsyncCanSender},
21    CanId, NodeId,
22};
23
24use super::shared_sender::SharedSender;
25use crate::sdo_client::{SdoClient, SdoClientError};
26use crate::{LssError, LssMaster, RawAbortCode};
27
28use super::shared_receiver::{SharedReceiver, SharedReceiverChannel};
29
30#[derive(Debug, Clone)]
31pub struct NodeInfo {
32    pub node_id: u8,
33    pub identity: Option<LssIdentity>,
34    pub device_name: Option<String>,
35    pub software_version: Option<String>,
36    pub hardware_version: Option<String>,
37    pub last_seen: Instant,
38    pub nmt_state: Option<NmtState>,
39}
40
41impl core::fmt::Display for NodeInfo {
42    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
43        writeln!(
44            f,
45            "Node {}: {}",
46            self.node_id,
47            self.nmt_state
48                .map(|s| s.to_string())
49                .unwrap_or("Unknown State".into())
50        )?;
51        match self.identity {
52            Some(id) => writeln!(
53                f,
54                "    Identity vendor: {:X}, product: {:X}, revision: {:X}, serial: {:X}",
55                id.vendor_id, id.product_code, id.revision, id.serial
56            )?,
57            None => writeln!(f, "    Identity: Unknown")?,
58        }
59        writeln!(
60            f,
61            "    Device Name: '{}'",
62            self.device_name.as_deref().unwrap_or("Unknown")
63        )?;
64        writeln!(
65            f,
66            "    Versions: '{}' SW, '{}' HW",
67            self.software_version.as_deref().unwrap_or("Unknown"),
68            self.hardware_version.as_deref().unwrap_or("Unknown")
69        )?;
70        let age = Instant::now().duration_since(self.last_seen);
71        writeln!(f, "    Last Seen: {}s ago", age.as_secs())?;
72
73        Ok(())
74    }
75}
76
77impl NodeInfo {
78    pub fn new(node_id: u8) -> Self {
79        Self {
80            node_id,
81            last_seen: Instant::now(),
82            device_name: None,
83            identity: None,
84            software_version: None,
85            hardware_version: None,
86            nmt_state: None,
87        }
88    }
89
90    /// Update / merge new information about the node
91    pub fn update(&mut self, info: &NodeInfo) {
92        if info.device_name.is_some() {
93            self.device_name = info.device_name.clone();
94        }
95        if info.identity.is_some() {
96            self.identity = info.identity;
97        }
98        if info.software_version.is_some() {
99            self.software_version = info.software_version.clone();
100        }
101        if info.hardware_version.is_some() {
102            self.hardware_version = info.hardware_version.clone();
103        }
104        if info.nmt_state.is_some() {
105            self.nmt_state = info.nmt_state;
106        }
107        self.last_seen = Instant::now();
108    }
109}
110
111async fn scan_node<S: AsyncCanSender + Sync + Send>(
112    node_id: u8,
113    clients: &SdoClientMutex<S>,
114) -> Result<Option<NodeInfo>, SdoClientError> {
115    let mut sdo_client = clients.lock(node_id);
116    log::info!("Scanning Node {node_id}");
117
118    let identity = match sdo_client.read_identity().await {
119        Ok(id) => Some(id),
120        // A no response here is not really an error, it just indicates the node is not present
121        Err(SdoClientError::NoResponse) => {
122            log::info!("No response from node {node_id}");
123            return Ok(None);
124        }
125        Err(e) => return Err(e),
126    };
127    let device_name = match sdo_client.read_device_name().await {
128        Ok(s) => Some(s),
129        Err(e) => {
130            log::error!("SDO Abort Response scanning node {node_id} device name: {e:?}");
131            None
132        }
133    };
134    let software_version = match sdo_client.read_software_version().await {
135        Ok(s) => Some(s),
136        Err(e) => {
137            log::error!("SDO Abort Response scanning node {node_id} SW version: {e:?}");
138            None
139        }
140    };
141    let hardware_version = match sdo_client.read_hardware_version().await {
142        Ok(s) => Some(s),
143        Err(e) => {
144            log::error!("SDO Abort Response scanning node {node_id} HW version: {e:?}");
145            None
146        }
147    };
148    Ok(Some(NodeInfo {
149        node_id,
150        identity,
151        device_name,
152        software_version,
153        hardware_version,
154        nmt_state: None,
155        last_seen: Instant::now(),
156    }))
157}
158
159/// Result struct for reading PDO configuration from a single node
160#[derive(Clone, Debug)]
161pub struct PdoScanResult {
162    /// List of TPDO configurations
163    pub tpdos: Vec<PdoConfig>,
164    /// List of RPDO configurations
165    pub rpdos: Vec<PdoConfig>,
166}
167
168async fn read_pdos<S: AsyncCanSender + Sync + Send, R: AsyncCanReceiver>(
169    mut comm_base: u16,
170    mut mapping_base: u16,
171    client: &mut SdoClient<S, R>,
172) -> Result<Vec<PdoConfig>, SdoClientError> {
173    let mut result = Vec::new();
174
175    loop {
176        let _comm_max_sub = match client.read_u8(comm_base, 0).await {
177            Ok(val) => val,
178            // This error is expected; this means there are no more PDOs to read
179            Err(SdoClientError::ServerAbort {
180                index: _,
181                sub: _,
182                abort_code: RawAbortCode::Valid(AbortCode::NoSuchObject),
183            }) => break,
184            // Any other error is unexpected
185            Err(e) => {
186                return Err(e);
187            }
188        };
189
190        let cob_value = client.read_u32(comm_base, 1).await?;
191        let transmission_type = client.read_u8(comm_base, 2).await?;
192
193        let frame = (cob_value & (1 << 29)) != 0;
194        let rtr_disabled = (cob_value & (1 << 30)) != 0;
195        let valid = (cob_value & (1 << 31)) == 0;
196
197        let cob_id = cob_value & 0x1FFFFFFF;
198        let cob_id = if frame {
199            CanId::extended(cob_id)
200        } else {
201            CanId::std((cob_id & 0x7ff) as u16)
202        };
203        let num_mappings = client.read_u8(mapping_base, 0).await?;
204        let mut mappings = Vec::new();
205        for i in 0..num_mappings {
206            let map_param = client.read_u32(mapping_base, i + 1).await?;
207            mappings.push(PdoMapping::from_object_value(map_param));
208        }
209
210        result.push(PdoConfig {
211            comm: PdoCommParameter {
212                cob_id,
213                valid,
214                rtr_disabled,
215                transmission_type,
216            },
217            mappings,
218        });
219        comm_base += 1;
220        mapping_base += 1;
221    }
222    Ok(result)
223}
224
225async fn read_rpdo_config<S: AsyncCanSender + Sync + Send, R: AsyncCanReceiver>(
226    client: &mut SdoClient<S, R>,
227) -> Result<Vec<PdoConfig>, SdoClientError> {
228    read_pdos(RPDO_COMM_BASE, RPDO_MAP_BASE, client).await
229}
230
231async fn read_tpdo_config<S: AsyncCanSender + Sync + Send, R: AsyncCanReceiver>(
232    client: &mut SdoClient<S, R>,
233) -> Result<Vec<PdoConfig>, SdoClientError> {
234    read_pdos(TPDO_COMM_BASE, TPDO_MAP_BASE, client).await
235}
236
237#[derive(Debug)]
238pub struct SdoClientGuard<'a, S, R>
239where
240    S: AsyncCanSender,
241    R: AsyncCanReceiver,
242{
243    _guard: std::sync::MutexGuard<'a, ()>,
244    client: SdoClient<S, R>,
245}
246
247impl<S, R> Deref for SdoClientGuard<'_, S, R>
248where
249    S: AsyncCanSender,
250    R: AsyncCanReceiver,
251{
252    type Target = SdoClient<S, R>;
253
254    fn deref(&self) -> &Self::Target {
255        &self.client
256    }
257}
258
259impl<S, R> DerefMut for SdoClientGuard<'_, S, R>
260where
261    S: AsyncCanSender,
262    R: AsyncCanReceiver,
263{
264    fn deref_mut(&mut self) -> &mut Self::Target {
265        &mut self.client
266    }
267}
268
269#[derive(Debug)]
270struct SdoClientMutex<S>
271where
272    S: AsyncCanSender + Sync,
273{
274    sender: SharedSender<S>,
275    receiver: SharedReceiver,
276    clients: HashMap<u8, Mutex<()>>,
277}
278
279impl<S> SdoClientMutex<S>
280where
281    S: AsyncCanSender + Sync,
282{
283    pub fn new(sender: SharedSender<S>, receiver: SharedReceiver) -> Self {
284        let mut clients = HashMap::new();
285        for i in 0u8..128 {
286            clients.insert(i, Mutex::new(()));
287        }
288
289        Self {
290            sender,
291            receiver,
292            clients,
293        }
294    }
295
296    pub fn lock(&self, id: u8) -> SdoClientGuard<'_, SharedSender<S>, SharedReceiverChannel> {
297        if !(1..=127).contains(&id) {
298            panic!("ID {} out of range", id);
299        }
300        let guard = self.clients.get(&id).unwrap().lock().unwrap();
301        let client = SdoClient::new_std(id, self.sender.clone(), self.receiver.create_rx());
302        SdoClientGuard {
303            _guard: guard,
304            client,
305        }
306    }
307}
308
309/// Manage a zencan bus
310#[derive(Debug)]
311pub struct BusManager<S: AsyncCanSender + Sync + Send> {
312    sender: SharedSender<S>,
313    receiver: SharedReceiver,
314    nodes: Arc<tokio::sync::Mutex<HashMap<u8, NodeInfo>>>,
315    sdo_clients: SdoClientMutex<S>,
316    _monitor_task: JoinHandle<()>,
317}
318
319impl<S: AsyncCanSender + Sync + Send> BusManager<S> {
320    /// Create a new bus manager
321    ///
322    /// # Arguments
323    /// - `sender`: An object which implements [`AsyncCanSender`] to be used for sending messages to
324    ///   the bus
325    /// - `receiver`: An object which implements [`AsyncCanReceiver`] to be used for receiving
326    ///   messages from the bus
327    ///
328    /// When using socketcan, these can be created with [`crate::open_socketcan`]
329    pub fn new(sender: S, receiver: impl AsyncCanReceiver + Sync + 'static) -> Self {
330        let receiver = SharedReceiver::new(receiver);
331        let sender = SharedSender::new(Arc::new(tokio::sync::Mutex::new(sender)));
332        let sdo_clients = SdoClientMutex::new(sender.clone(), receiver.clone());
333
334        let mut state_rx = receiver.create_rx();
335        let nodes = Arc::new(tokio::sync::Mutex::new(HashMap::new()));
336
337        let monitor_task = {
338            let nodes = nodes.clone();
339            tokio::spawn(async move {
340                loop {
341                    if let Ok(msg) = state_rx.recv().await {
342                        if let Ok(ZencanMessage::Heartbeat(heartbeat)) =
343                            ZencanMessage::try_from(msg)
344                        {
345                            let id_num = heartbeat.node;
346                            if let Ok(node_id) = NodeId::try_from(id_num) {
347                                let mut nodes = nodes.lock().await;
348                                if let std::collections::hash_map::Entry::Vacant(e) =
349                                    nodes.entry(id_num)
350                                {
351                                    e.insert(NodeInfo::new(node_id.raw()));
352                                } else {
353                                    let node = nodes.get_mut(&id_num).unwrap();
354                                    node.nmt_state = Some(heartbeat.state);
355                                    node.last_seen = Instant::now();
356                                }
357                            } else {
358                                log::warn!("Invalid heartbeat node ID {id_num} received");
359                            }
360                        }
361                    }
362                }
363            })
364        };
365
366        Self {
367            sender,
368            receiver,
369            sdo_clients,
370            nodes,
371            _monitor_task: monitor_task,
372        }
373    }
374
375    /// Get an SDO client for a particular node
376    ///
377    /// This function may block if another task is using the required SDO client, as it ensures
378    /// exclusive access to each node's SDO server.
379    pub fn sdo_client(
380        &self,
381        node_id: u8,
382    ) -> SdoClientGuard<'_, SharedSender<S>, SharedReceiverChannel> {
383        self.sdo_clients.lock(node_id)
384    }
385
386    /// Get a list of known nodes
387    pub async fn node_list(&self) -> Vec<NodeInfo> {
388        let node_map = self.nodes.lock().await;
389        let mut nodes = Vec::with_capacity(node_map.len());
390        for n in node_map.values() {
391            nodes.push(n.clone());
392        }
393
394        nodes.sort_by_key(|n| n.node_id);
395        nodes
396    }
397
398    /// Perform a scan of all possible node IDs
399    ///
400    /// Will find all configured devices, and read metadata from required objects, including:
401    /// - Identity
402    /// - Device Name
403    /// - Software Version
404    /// - Hardware Version
405    pub async fn scan_nodes(&mut self) -> Result<Vec<NodeInfo>, SdoClientError> {
406        const N_PARALLEL: usize = 10;
407
408        let ids = Vec::from_iter(1..128u8);
409        let mut nodes: Vec<NodeInfo> = Vec::new();
410
411        let mut chunks = Vec::new();
412        for chunk in ids.chunks(128 / N_PARALLEL) {
413            chunks.push(Vec::from_iter(chunk.iter().cloned()));
414        }
415
416        let mut futures = Vec::new();
417
418        for block in chunks {
419            futures.push(async {
420                let mut block_nodes = Vec::new();
421                for id in block {
422                    block_nodes.push(scan_node(id, &self.sdo_clients).await);
423                }
424                block_nodes
425            });
426        }
427
428        // Collect the results from batches. If any errors occurred, fail.
429        let results = join_all(futures).await;
430        for r in results {
431            let r: Result<Vec<Option<NodeInfo>>, SdoClientError> = r.into_iter().collect();
432            nodes.extend(r?.into_iter().flatten());
433        }
434
435        let mut node_map = self.nodes.lock().await;
436        // Update our nodes
437        for n in &nodes {
438            if let std::collections::hash_map::Entry::Vacant(e) = node_map.entry(n.node_id) {
439                e.insert(n.clone());
440            } else {
441                node_map.get_mut(&n.node_id).unwrap().update(n);
442            }
443        }
444
445        // Pull the just scanned nodes from the collection so that
446        // 1) We only included nodes which responded just now to the scan, but
447        // 2) we also display the latest NMT state for that node, which comes from the heartbeat
448        //    rather than the scan
449        Ok(nodes
450            .iter()
451            .map(|n| node_map.get(&n.node_id).unwrap().clone())
452            .collect())
453    }
454
455    /// Find all unconfigured devices on the bus
456    ///
457    /// The LSS fastscan protocol is used to identify devices which do not have an assigned node ID.
458    ///
459    /// Devices that do have a node ID can be found using [`scan_nodes`](Self::scan_nodes), or by
460    /// their heartbeat messages.
461    ///
462    /// After devices are found, they are all put back into waiting state
463    pub async fn lss_fastscan(&mut self, timeout: Duration) -> Vec<LssIdentity> {
464        let mut devices = Vec::new();
465        let mut lss = LssMaster::new(self.sender.clone(), self.receiver.create_rx());
466
467        // Put all nodes into Waiting state
468        lss.set_global_mode(LssState::Waiting).await;
469
470        // Each time a device is completely identified, it goes into Configuring mode and will not
471        // respond to further scans. Once all devices are identified, the scan will return None.
472        while let Some(id) = lss.fast_scan(timeout).await {
473            devices.push(id);
474        }
475
476        lss.set_global_mode(LssState::Waiting).await;
477
478        devices
479    }
480
481    /// Activate a single LSS slave by its identity
482    ///
483    /// All nodes are put into Waiting mode via the global command, then the specified node is
484    /// activates. Will return `Ok(())` if the activated node acknowledges, or an Err otherwise.
485    ///
486    /// The identity consists of the four u32 values from the 0x1018 object, which should uniquely
487    /// identify a device on the bus. If they are not known, they can be found using
488    /// [`lss_fastscan()`](Self::lss_fastscan).
489    pub async fn lss_activate(&mut self, ident: LssIdentity) -> Result<(), LssError> {
490        let mut lss = LssMaster::new(self.sender.clone(), self.receiver.create_rx());
491        lss.set_global_mode(LssState::Waiting).await;
492        lss.enter_config_by_identity(
493            ident.vendor_id,
494            ident.product_code,
495            ident.revision,
496            ident.serial,
497        )
498        .await
499    }
500
501    /// Set the node ID of LSS slave in Configuration mode
502    ///
503    /// It is required that one node has been put into Configuration mode already when this is
504    /// called, e.g. using [`lss_activate`](Self::lss_activate)
505    pub async fn lss_set_node_id(&mut self, node_id: NodeId) -> Result<(), LssError> {
506        let mut lss = LssMaster::new(self.sender.clone(), self.receiver.create_rx());
507        lss.set_node_id(node_id).await?;
508        Ok(())
509    }
510
511    /// Command the node in Configuration mode to store its configuration
512    ///
513    /// It is required that one node has been put into Configuration mode already when this is
514    /// called, e.g. using [`lss_activate`](Self::lss_activate)
515    pub async fn lss_store_config(&mut self) -> Result<(), LssError> {
516        let mut lss = LssMaster::new(self.sender.clone(), self.receiver.create_rx());
517        lss.store_config().await
518    }
519
520    /// Send a command to put all devices into the specified LSS state
521    pub async fn lss_set_global_mode(&mut self, mode: LssState) {
522        let mut lss = LssMaster::new(self.sender.clone(), self.receiver.create_rx());
523        lss.set_global_mode(mode).await;
524    }
525
526    /// Send application reset command
527    ///
528    /// node - The node ID to command, or 0 to broadcast to all nodes
529    pub async fn nmt_reset_app(&mut self, node: u8) {
530        self.send_nmt_cmd(NmtCommandSpecifier::ResetApp, node).await
531    }
532
533    /// Send communications reset command
534    ///
535    /// node - The node ID to command, or 0 to broadcast to all nodes
536    pub async fn nmt_reset_comms(&mut self, node: u8) {
537        self.send_nmt_cmd(NmtCommandSpecifier::ResetComm, node)
538            .await
539    }
540
541    /// Send start operation command
542    ///
543    /// node - The node ID to command, or 0 to broadcast to all nodes
544    pub async fn nmt_start(&mut self, node: u8) {
545        self.send_nmt_cmd(NmtCommandSpecifier::Start, node).await
546    }
547
548    /// Send start operation command
549    ///
550    /// node - The node ID to command, or 0 to broadcast to all nodes
551    pub async fn nmt_stop(&mut self, node: u8) {
552        self.send_nmt_cmd(NmtCommandSpecifier::Stop, node).await
553    }
554
555    /// Send a SYNC packet on the bus
556    pub async fn sync(&mut self, count: Option<u8>) {
557        let sync_obj = SyncObject::new(count);
558        self.sender.send(sync_obj.into()).await.ok();
559    }
560
561    async fn send_nmt_cmd(&mut self, cmd: NmtCommandSpecifier, node: u8) {
562        let message = NmtCommand { cs: cmd, node };
563        self.sender.send(message.into()).await.ok();
564    }
565
566    /// Read the RPDO and TPDO configuration for the specified node
567    ///
568    /// node - The node ID to read from
569    pub async fn read_pdo_config(
570        &mut self,
571        node: ConfiguredNodeId,
572    ) -> Result<PdoScanResult, SdoClientError> {
573        let mut client = self.sdo_client(node.raw());
574
575        let tpdos = read_tpdo_config(&mut client).await?;
576        let rpdos = read_rpdo_config(&mut client).await?;
577
578        Ok(PdoScanResult { tpdos, rpdos })
579    }
580}