oze_canopen/
nmt.rs

1use crate::{
2    error::CoError,
3    interface::{CanOpenInterface, SEND_TIMOUT},
4    proto::nmt::{NmtCommand, NmtCommandSpecifier},
5    transmitter::TxPacket,
6};
7use binrw::BinWrite;
8use std::time::Duration;
9use tokio::{sync::watch, time::sleep};
10
11/// A control struct that holds the period duration and an optional NMT command.
12#[derive(Clone)]
13struct Control {
14    period: Duration,
15    command: Option<NmtCommand>,
16}
17
18/// A server struct responsible for managing and sending NMT commands.
19pub struct Server {
20    control: watch::Sender<Control>,
21}
22
23impl Server {
24    /// Sets the period and command for sending NMT commands.
25    ///
26    /// # Arguments
27    ///
28    /// * `period` - The period duration for sending the command.
29    /// * `command` - The optional NMT command to be sent.
30    ///
31    /// # Examples
32    ///
33    /// ```
34    /// use oze_canopen::Server;
35    /// use std::time::Duration;
36    /// use oze_canopen::proto::nmt::{NmtCommand, NmtCommandSpecifier};
37    ///
38    /// let server = Server::start(interface);
39    /// server.set_command_period(Duration::from_secs(1), Some(NmtCommand::new(NmtCommandSpecifier::StartRemoteNode, 0)));
40    /// ```
41    pub fn set_command_period(&self, period: Duration, command: Option<NmtCommand>) {
42        let _ = self.control.send(Control { period, command });
43    }
44
45    /// Starts the server with the given CANopen interface.
46    ///
47    /// # Arguments
48    ///
49    /// * `interface` - The CANopen interface to be used by the server.
50    ///
51    /// # Returns
52    ///
53    /// A new `Server` instance.
54    ///
55    /// # Examples
56    ///
57    /// ```
58    /// use oze_canopen::Server;
59    ///
60    /// let interface = oze_canopen::start_interface();
61    /// let server = Server::start(interface);
62    /// ```
63    pub fn start(interface: CanOpenInterface) -> Server {
64        let (snd, rcv) = watch::channel::<Control>(Control {
65            period: Duration::MAX,
66            command: None,
67        });
68        tokio::spawn(async move {
69            Self::task(&interface, rcv).await;
70        });
71
72        Server { control: snd }
73    }
74
75    /// The main task loop for the server, responsible for sending NMT commands based on the control settings.
76    ///
77    /// # Arguments
78    ///
79    /// * `interface` - The CANopen interface to be used.
80    /// * `rcv` - The watch receiver for control settings.
81    async fn task(interface: &CanOpenInterface, mut rcv: watch::Receiver<Control>) {
82        let mut control;
83        loop {
84            control = rcv.borrow().clone();
85            if let Some(cmd) = control.command {
86                tokio::select! {
87                    _ = sleep(control.period) => {},
88                    _ = rcv.changed() => {},
89                }
90
91                _ = interface.send_nmt(cmd).await;
92            } else {
93                let _ = rcv.changed().await;
94            }
95        }
96    }
97}
98
99impl CanOpenInterface {
100    /// Sends an NMT command through the CANopen interface.
101    ///
102    /// # Arguments
103    ///
104    /// * `nmt` - The NMT command to be sent.
105    ///
106    /// # Returns
107    ///
108    /// A `Result` indicating success or failure.
109    ///
110    /// # Examples
111    ///
112    /// ```
113    /// use oze_canopen::CanOpenInterface;
114    /// use oze_canopen::proto::nmt::{NmtCommand, NmtCommandSpecifier};
115    ///
116    /// let interface = oze_canopen::start_interface();
117    /// let result = interface.send_nmt(NmtCommand::new(NmtCommandSpecifier::StartRemoteNode, 0)).await;
118    /// ```
119    pub async fn send_nmt(&self, nmt: NmtCommand) -> Result<(), CoError> {
120        let mut writer = binrw::io::Cursor::new(Vec::new());
121        nmt.write(&mut writer)?;
122        self.tx
123            .send_timeout(
124                TxPacket {
125                    cob_id: 0x000,
126                    data: writer.into_inner(),
127                },
128                Duration::from_millis(SEND_TIMOUT),
129            )
130            .await?;
131        Ok(())
132    }
133
134    /// Sends an NMT command through the CANopen interface using a command specifier.
135    ///
136    /// # Arguments
137    ///
138    /// * `nmt_command` - The NMT command specifier to be used.
139    ///
140    /// # Returns
141    ///
142    /// A `Result` indicating success or failure.
143    ///
144    /// # Examples
145    ///
146    /// ```
147    /// use oze_canopen::CanOpenInterface;
148    /// use oze_canopen::proto::nmt::NmtCommandSpecifier;
149    ///
150    /// let interface = oze_canopen::start_interface();
151    /// let result = interface.send_nmt_command(NmtCommandSpecifier::StartRemoteNode).await;
152    /// ```
153    pub async fn send_nmt_command(&self, nmt_command: NmtCommandSpecifier) -> Result<(), CoError> {
154        self.send_nmt(NmtCommand::new(nmt_command, 0)).await
155    }
156}