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}