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}