can_viewer/
dto.rs

1//! Data Transfer Objects for frontend communication.
2//!
3//! These types are serializable versions of internal types, used for
4//! communication between the Rust backend and the JavaScript frontend.
5
6use serde::{Deserialize, Serialize};
7
8/// Kernel-level CAN filter (BPF) for socket filtering.
9///
10/// Filters are applied at the kernel level before frames reach userspace,
11/// providing efficient hardware-accelerated filtering.
12#[derive(Debug, Clone, Serialize, Deserialize)]
13pub struct CanBpfFilter {
14    /// CAN ID to match
15    pub can_id: u32,
16    /// Mask for matching (1 bits = must match, 0 bits = don't care)
17    pub mask: u32,
18    /// If true, filter matches extended (29-bit) IDs
19    pub is_extended: bool,
20    /// If true, invert the filter (reject matching frames)
21    pub inverted: bool,
22}
23
24/// Serializable CAN frame for frontend communication.
25#[derive(Debug, Clone, Serialize, Deserialize)]
26pub struct CanFrameDto {
27    pub timestamp: f64,
28    pub channel: String,
29    pub can_id: u32,
30    pub is_extended: bool,
31    pub is_fd: bool,
32    pub brs: bool,
33    pub esi: bool,
34    pub dlc: u8,
35    pub data: Vec<u8>,
36}
37
38impl CanFrameDto {
39    /// Helper to extract CAN ID as u32 from embedded_can::Id.
40    #[cfg(target_os = "linux")]
41    fn id_to_u32(id: embedded_can::Id) -> u32 {
42        match id {
43            embedded_can::Id::Standard(id) => id.as_raw() as u32,
44            embedded_can::Id::Extended(id) => id.as_raw(),
45        }
46    }
47
48    /// Create from any socketcan frame type (classic CAN or CAN FD).
49    /// Uses embedded_can::Frame trait for frame access.
50    /// Returns None for error and remote frames.
51    #[cfg(target_os = "linux")]
52    pub fn from_any_frame(
53        frame: &socketcan::CanAnyFrame,
54        timestamp: f64,
55        channel: &str,
56    ) -> Option<Self> {
57        // Use embedded_can::Frame trait for generic frame access
58        use embedded_can::Frame;
59        // Note: is_brs() and is_esi() are inherent methods on CanFdFrame
60
61        match frame {
62            socketcan::CanAnyFrame::Normal(data_frame) => Some(Self {
63                timestamp,
64                channel: channel.to_string(),
65                can_id: Self::id_to_u32(data_frame.id()),
66                is_extended: data_frame.is_extended(),
67                is_fd: false,
68                brs: false,
69                esi: false,
70                dlc: data_frame.dlc() as u8,
71                data: data_frame.data().to_vec(),
72            }),
73            socketcan::CanAnyFrame::Remote(_) => None, // Remote frames not supported (deprecated in CAN FD)
74            socketcan::CanAnyFrame::Fd(fd_frame) => Some(Self {
75                timestamp,
76                channel: channel.to_string(),
77                can_id: Self::id_to_u32(fd_frame.id()),
78                is_extended: fd_frame.is_extended(),
79                is_fd: true,
80                brs: fd_frame.is_brs(),
81                esi: fd_frame.is_esi(),
82                dlc: fd_frame.dlc() as u8,
83                data: fd_frame.data().to_vec(),
84            }),
85            socketcan::CanAnyFrame::Error(_) => None, // Skip error frames
86        }
87    }
88
89    /// Create from MDF4 channel data (classic CAN).
90    pub fn from_mdf4(timestamp: f64, channel: String, can_id: u32, dlc: u8, data: Vec<u8>) -> Self {
91        let is_fd = data.len() > 8 || dlc > 8;
92        Self {
93            timestamp,
94            channel,
95            can_id,
96            is_extended: can_id > 0x7FF,
97            is_fd,
98            brs: false, // Not available in basic MDF4 data
99            esi: false,
100            dlc,
101            data,
102        }
103    }
104
105    /// Create from MDF4 channel data with CAN FD flags.
106    #[allow(dead_code)]
107    pub fn from_mdf4_fd(
108        timestamp: f64,
109        channel: String,
110        can_id: u32,
111        dlc: u8,
112        data: Vec<u8>,
113        brs: bool,
114        esi: bool,
115    ) -> Self {
116        Self {
117            timestamp,
118            channel,
119            can_id,
120            is_extended: can_id > 0x7FF,
121            is_fd: true,
122            brs,
123            esi,
124            dlc,
125            data,
126        }
127    }
128}
129
130/// Serializable decoded signal for frontend communication.
131#[derive(Debug, Clone, Serialize, Deserialize)]
132pub struct DecodedSignalDto {
133    pub timestamp: f64,
134    pub message_name: String,
135    pub signal_name: String,
136    pub value: f64,
137    pub raw_value: i64,
138    pub unit: String,
139    #[serde(skip_serializing_if = "Option::is_none")]
140    pub description: Option<String>,
141}
142
143impl DecodedSignalDto {
144    /// Convert from dbc_rs::DecodedSignal, adding timestamp and message name.
145    pub fn from_dbc_signal(
146        sig: &dbc_rs::DecodedSignal<'_>,
147        timestamp: f64,
148        message_name: &str,
149    ) -> Self {
150        Self {
151            timestamp,
152            message_name: message_name.to_string(),
153            signal_name: sig.name.to_string(),
154            value: sig.value,
155            raw_value: sig.raw_value,
156            unit: sig.unit.unwrap_or("").to_string(),
157            description: sig.description.map(|s| s.to_string()),
158        }
159    }
160}
161
162/// Response from decode_frames command, including any errors.
163#[derive(Debug, Clone, Serialize, Deserialize)]
164pub struct DecodeResponse {
165    pub signals: Vec<DecodedSignalDto>,
166    pub errors: Vec<String>,
167}
168
169/// CAN bus error frame for frontend communication.
170#[derive(Debug, Clone, Serialize, Deserialize)]
171pub struct CanErrorDto {
172    pub timestamp: f64,
173    pub channel: String,
174    pub error_type: String,
175    pub details: String,
176}
177
178#[cfg(target_os = "linux")]
179impl CanErrorDto {
180    /// Create from socketcan error frame.
181    pub fn from_error_frame(
182        frame: socketcan::CanErrorFrame,
183        timestamp: f64,
184        channel: &str,
185    ) -> Self {
186        use socketcan::CanError;
187
188        // Convert frame to CanError using the From trait
189        let error: CanError = frame.into();
190
191        let (error_type, details) = match error {
192            CanError::TransmitTimeout => ("TX Timeout", "Transmit timeout".to_string()),
193            CanError::LostArbitration(bit) => ("Lost Arbitration", format!("at bit {}", bit)),
194            CanError::ControllerProblem(err) => ("Controller", format!("{:?}", err)),
195            CanError::ProtocolViolation { vtype, location } => (
196                "Protocol Violation",
197                format!("{:?} at {:?}", vtype, location),
198            ),
199            CanError::TransceiverError => ("Transceiver", "Transceiver error".to_string()),
200            CanError::NoAck => ("No ACK", "No acknowledgment received".to_string()),
201            CanError::BusOff => ("Bus Off", "Controller is bus-off".to_string()),
202            CanError::BusError => ("Bus Error", "Bus error occurred".to_string()),
203            CanError::Restarted => ("Restarted", "Controller restarted".to_string()),
204            CanError::DecodingFailure(msg) => ("Decode Error", msg.to_string()),
205            CanError::Unknown(code) => ("Unknown", format!("Error code: 0x{:08X}", code)),
206        };
207
208        Self {
209            timestamp,
210            channel: channel.to_string(),
211            error_type: error_type.to_string(),
212            details,
213        }
214    }
215}
216
217// ─────────────────────────────────────────────────────────────────────────────
218// Live Capture DTOs
219// ─────────────────────────────────────────────────────────────────────────────
220
221/// Capture statistics.
222#[derive(Debug, Clone, Serialize, Deserialize)]
223pub struct CaptureStatsDto {
224    pub frame_count: u64,
225    pub message_count: u32,
226    pub signal_count: u32,
227    pub frame_rate: f64,
228    pub elapsed_secs: f64,
229    pub capture_file: Option<String>,
230}
231
232/// Pre-rendered stats strings for frontend.
233#[derive(Debug, Clone, Serialize, Deserialize)]
234pub struct StatsHtml {
235    pub message_count: String,
236    pub frame_count: String,
237    pub frame_rate: String,
238    pub elapsed: String,
239}
240
241/// Periodic update sent to frontend during live capture.
242#[derive(Debug, Clone, Serialize, Deserialize)]
243pub struct LiveCaptureUpdate {
244    pub stats: CaptureStatsDto,
245    /// Pre-rendered HTML for message monitor table body.
246    pub messages_html: String,
247    /// Pre-rendered HTML for signal monitor container.
248    pub signals_html: String,
249    /// Pre-rendered HTML for frame stream table body.
250    pub frames_html: String,
251    /// Pre-rendered HTML for error monitor table body.
252    pub errors_html: String,
253    /// Pre-formatted stats strings.
254    pub stats_html: StatsHtml,
255    /// Badge counts for tabs.
256    pub message_count: u32,
257    pub signal_count: u32,
258    pub frame_count: usize,
259    pub error_count: u32,
260}