zencan_client/
nmt_master.rs

1//! Simple interface for sending NMT commands to a bus
2use std::time::Instant;
3
4use zencan_common::{
5    messages::{CanMessage, NmtCommand, NmtCommandSpecifier, ZencanMessage},
6    nmt::NmtState,
7    traits::{AsyncCanReceiver, AsyncCanSender},
8};
9
10type Result<T> = std::result::Result<T, ()>;
11
12/// Represents the information about a single node detected on the bus by the [NmtMaster]
13#[derive(Copy, Clone, Debug)]
14pub struct Node {
15    /// The ID of the node
16    pub id: u8,
17    /// The last NMT state reported by the node
18    pub state: NmtState,
19    /// The time when the last heartbeat message from received from the node
20    pub last_status: Instant,
21    last_toggle: bool,
22}
23
24impl Default for Node {
25    fn default() -> Self {
26        Self {
27            id: 0,
28            state: NmtState::Bootup,
29            last_status: Instant::now(),
30            last_toggle: true,
31        }
32    }
33}
34
35const MAX_NODES: usize = 127;
36
37#[derive(Debug)]
38/// An NMT master which allows monitoring the bus for heartbeats and commanding state changes
39pub struct NmtMaster<S, R> {
40    sender: S,
41    receiver: R,
42    nodes: [Node; MAX_NODES],
43}
44
45impl<S: AsyncCanSender, R: AsyncCanReceiver> NmtMaster<S, R> {
46    /// Create a new NmtMaster
47    ///
48    /// # Arguments
49    /// - `sender`: An object which implements [`AsyncCanSender`] to be used for sending messages to
50    ///   the bus
51    /// - `receiver`: An object which implements [`AsyncCanReceiver`] to be used for receiving
52    ///   messages from the bus
53    ///
54    /// When using socketcan, these can be created with [`crate::open_socketcan`].
55    pub fn new(sender: S, receiver: R) -> Self {
56        let nodes = [Node::default(); MAX_NODES];
57        Self {
58            sender,
59            receiver,
60            nodes,
61        }
62    }
63
64    /// Receive and process all messages available from the message receiver
65    pub fn process_rx(&mut self) {
66        while let Some(msg) = self.receiver.try_recv() {
67            self.handle_message(msg);
68        }
69    }
70
71    fn handle_message(&mut self, msg: CanMessage) {
72        // Attempt to convert the raw message into a zencanMessage. This may fail, e.g. if
73        // non zencan messages are received, and that's OK; those are ignored.
74        let open_msg: ZencanMessage = match msg.try_into() {
75            Ok(m) => m,
76            Err(_) => return,
77        };
78
79        if let ZencanMessage::Heartbeat(heartbeat) = open_msg {
80            self.handle_heartbeat(heartbeat.node, heartbeat.state, heartbeat.toggle)
81        }
82    }
83
84    /// Get a list of all nodes detected on the bus via heartbeat/reset messages
85    pub fn get_nodes(&mut self) -> &[Node] {
86        self.process_rx();
87
88        // Find the first empty slot; this indicates the end of the list
89        let n = self
90            .nodes
91            .iter()
92            .position(|n| n.id == 0)
93            .unwrap_or(MAX_NODES);
94        &self.nodes[0..n]
95    }
96
97    fn handle_heartbeat(&mut self, node: u8, state: NmtState, toggle: bool) {
98        // Find the node in the ordered list, inserting if needed.
99        for i in 0..self.nodes.len() {
100            let list_node = &mut self.nodes[i];
101            if list_node.id == node {
102                // Node already in list. Update it
103                list_node.last_status = Instant::now();
104                list_node.last_toggle = toggle;
105                list_node.state = state;
106                break;
107            } else if list_node.id == 0 || list_node.id > node {
108                // Found end of list or higher node - insert here
109                // Shift all higher nodes
110                for j in self.nodes.len() - 1..i {
111                    self.nodes[j] = self.nodes[j - 1];
112                }
113                self.nodes[i] = Node {
114                    id: node,
115                    state,
116                    last_status: Instant::now(),
117                    last_toggle: toggle,
118                };
119                break;
120            }
121        }
122    }
123
124    /// Send application reset command
125    ///
126    /// # Arguments
127    ///
128    /// - `node`: The node ID to command, or 0 to broadcast to all nodes
129    pub async fn nmt_reset_app(&mut self, node: u8) -> Result<()> {
130        self.send_nmt_cmd(NmtCommandSpecifier::ResetApp, node).await
131    }
132
133    /// Send communications reset command
134    ///
135    /// # Arguments
136    ///
137    /// - `node`: The node ID to command, or 0 to broadcast to all nodes
138    pub async fn nmt_reset_comms(&mut self, node: u8) -> Result<()> {
139        self.send_nmt_cmd(NmtCommandSpecifier::ResetComm, node)
140            .await
141    }
142
143    /// Send start operation command
144    ///
145    /// # Arguments
146    ///
147    /// - `node`: The node ID to command, or 0 to broadcast to all nodes
148    pub async fn nmt_start(&mut self, node: u8) -> Result<()> {
149        self.send_nmt_cmd(NmtCommandSpecifier::Start, node).await
150    }
151
152    /// Send start operation command
153    ///
154    /// # Arguments
155    ///
156    /// - `node`: The node ID to command, or 0 to broadcast to all nodes
157    pub async fn nmt_stop(&mut self, node: u8) -> Result<()> {
158        self.send_nmt_cmd(NmtCommandSpecifier::Stop, node).await
159    }
160
161    async fn send_nmt_cmd(&mut self, cmd: NmtCommandSpecifier, node: u8) -> Result<()> {
162        let message = NmtCommand { cs: cmd, node };
163        self.sender.send(message.into()).await.map_err(|_| ())?;
164        Ok(())
165    }
166}