oze_canopen/message.rs
1use crate::proto::CobId;
2use socketcan::{CanDataFrame, EmbeddedFrame};
3use tokio::time::Instant;
4
5/// Represents a node identifier in the CAN network.
6pub type NodeId = u8;
7
8/// Enum for different string formats for RX messages.
9#[derive(Clone, Debug, Copy, PartialEq, Eq)]
10pub enum RxMessageToStringFormat {
11 /// Binary format.
12 Binary,
13 /// Hexadecimal format.
14 Hex,
15 /// ASCII format.
16 Ascii,
17 /// UTF-8 format.
18 Utf8,
19}
20
21/// Struct representing a received message.
22///
23/// # Examples
24///
25/// ```
26/// use oze_canopen::{RxMessage, CanDataFrame, RxMessageToStringFormat};
27///
28/// let frame = CanDataFrame::new(0x123, &[0xDE, 0xAD, 0xBE, 0xEF]).unwrap();
29/// if let Some(rx_msg) = RxMessage::from(frame) {
30/// println!("{}", rx_msg.data_to_string(RxMessageToStringFormat::Hex)); // Output: "DE AD BE EF"
31/// println!("{}", rx_msg.cob_id_to_string()); // Output: "123"
32/// }
33/// ```
34#[derive(Clone, Debug, Copy)]
35pub struct RxMessage {
36 /// Timestamp when the message was received.
37 pub timestamp: Instant,
38 /// COB ID of the message.
39 pub cob_id: CobId,
40 /// Data payload of the message.
41 pub data: [u8; 8],
42 /// Data length code (number of bytes in the data payload).
43 pub dlc: usize,
44}
45
46/// Converts a byte to a lossy ASCII string representation.
47///
48/// # Examples
49///
50/// ```
51/// use oze_canopen::u8_to_ascii_lossy;
52///
53/// let ascii_str = u8_to_ascii_lossy(65); // 'A'
54/// let non_ascii_str = u8_to_ascii_lossy(200); // '.'
55/// println!("{}", ascii_str); // Output: "A"
56/// println!("{}", non_ascii_str); // Output: "."
57/// ```
58pub(crate) fn u8_to_ascii_lossy(byte: u8) -> String {
59 if byte.is_ascii() && !byte.is_ascii_control() {
60 byte.escape_ascii().to_string()
61 } else {
62 ".".to_owned()
63 }
64}
65
66impl RxMessage {
67 /// Creates an `RxMessage` from a `CanDataFrame`.
68 ///
69 /// # Arguments
70 ///
71 /// * `frame` - The CAN data frame.
72 ///
73 /// # Returns
74 ///
75 /// An `Option` containing the `RxMessage` if the frame ID is standard, otherwise `None`.
76 ///
77 /// # Examples
78 ///
79 /// ```
80 /// use oze_canopen::{RxMessage, CanDataFrame};
81 ///
82 /// let frame = CanDataFrame::new(0x123, &[0xDE, 0xAD, 0xBE, 0xEF]).unwrap();
83 /// if let Some(rx_msg) = RxMessage::from(frame) {
84 /// println!("{:?}", rx_msg);
85 /// }
86 /// ```
87 pub fn from(frame: CanDataFrame) -> Option<Self> {
88 let socketcan::Id::Standard(id) = frame.id() else {
89 eprintln!("EEE");
90 return None;
91 };
92
93 let mut data = [0u8; 8];
94 let frame_data = frame.data();
95 let len = frame_data.len().min(8);
96 data[..len].copy_from_slice(&frame_data[..len]);
97 Some(RxMessage {
98 timestamp: Instant::now(),
99 cob_id: id.as_raw(),
100 data,
101 dlc: frame.dlc(),
102 })
103 }
104
105 /// Converts the data payload of the message to a string based on the specified format.
106 ///
107 /// # Arguments
108 ///
109 /// * `format` - The format in which to convert the data payload.
110 ///
111 /// # Returns
112 ///
113 /// A `String` representation of the data payload.
114 ///
115 /// # Examples
116 ///
117 /// ```
118 /// use oze_canopen::{RxMessage, CanDataFrame, RxMessageToStringFormat};
119 ///
120 /// let frame = CanDataFrame::new(0x123, &[0xDE, 0xAD, 0xBE, 0xEF]).unwrap();
121 /// if let Some(rx_msg) = RxMessage::from(frame) {
122 /// let hex_str = rx_msg.data_to_string(RxMessageToStringFormat::Hex);
123 /// println!("{}", hex_str); // Output: "DE AD BE EF"
124 /// }
125 /// ```
126 pub fn data_to_string(&self, format: RxMessageToStringFormat) -> String {
127 if format == RxMessageToStringFormat::Utf8 {
128 return String::from_utf8_lossy(&self.data).to_string();
129 }
130
131 let mut out = String::with_capacity(self.data.len() * 9);
132 for (i, d) in self.data.iter().enumerate() {
133 if i >= self.dlc {
134 break;
135 }
136
137 if i == 0 {
138 if format == RxMessageToStringFormat::Binary {
139 out.insert_str(0, &format!("{:08b}", d));
140 } else if format == RxMessageToStringFormat::Ascii {
141 out.insert_str(0, &u8_to_ascii_lossy(*d));
142 } else {
143 out.insert_str(0, &format!("{:02X}", d));
144 }
145 } else if format == RxMessageToStringFormat::Binary {
146 out.insert_str((i - 1) * 9 + 8, &format!(" {:08b}", d));
147 } else if format == RxMessageToStringFormat::Ascii {
148 out.insert_str(i, &u8_to_ascii_lossy(*d));
149 } else {
150 out.insert_str((i - 1) * 3 + 2, &format!(" {:02X}", d));
151 }
152 }
153 out
154 }
155
156 /// Converts the COB ID of the message to a string.
157 ///
158 /// # Returns
159 ///
160 /// A `String` representation of the COB ID.
161 ///
162 /// # Examples
163 ///
164 /// ```
165 /// use oze_canopen::{RxMessage, CanDataFrame};
166 ///
167 /// let frame = CanDataFrame::new(0x123, &[0xDE, 0xAD, 0xBE, 0xEF]).unwrap();
168 /// if let Some(rx_msg) = RxMessage::from(frame) {
169 /// let cob_id_str = rx_msg.cob_id_to_string();
170 /// println!("{}", cob_id_str); // Output: "123"
171 /// }
172 /// ```
173 pub fn cob_id_to_string(&self) -> String {
174 format!("{:03X}", self.cob_id)
175 }
176}