1use crate::sim::types::{CAN_FLAG_EXTENDED, CAN_FLAG_FD, SimCanFrame};
2use can_dbc::{ByteOrder, Dbc, MessageId, MultiplexIndicator, ValueType};
3use std::collections::HashMap;
4use std::path::Path;
5
6#[derive(Debug, Clone)]
7pub struct DbcSignalDef {
8 pub name: String,
9 pub frame_key: u32,
10 pub arb_id: u32,
11 pub extended: bool,
12 pub message_size: u8,
13 pub start_bit: u64,
14 pub size: u64,
15 pub byte_order: ByteOrder,
16 pub value_type: ValueType,
17 pub factor: f64,
18 pub offset: f64,
19 pub min: f64,
20 pub max: f64,
21 pub unit: Option<String>,
22}
23
24#[derive(Debug, Clone)]
25pub struct DbcBusOverlay {
26 signals_by_name: HashMap<String, DbcSignalDef>,
27}
28
29impl DbcBusOverlay {
30 pub fn load(path: &Path) -> Result<Self, String> {
31 let content = std::fs::read_to_string(path)
32 .map_err(|e| format!("failed to read DBC '{}': {e}", path.display()))?;
33 let dbc = Dbc::try_from(content.as_str())
34 .map_err(|e| format!("failed to parse DBC '{}': {e}", path.display()))?;
35 let mut signals_by_name = HashMap::new();
36
37 for message in dbc.messages {
38 let (arb_id, extended) = match message.id {
39 MessageId::Standard(id) => (u32::from(id), false),
40 MessageId::Extended(id) => (id, true),
41 };
42 let message_size = u8::try_from(message.size).map_err(|_| {
43 format!(
44 "DBC message '{}' has invalid size {}; max supported is 255 bytes",
45 message.name, message.size
46 )
47 })?;
48 if message_size > 64 {
49 return Err(format!(
50 "DBC message '{}' size {} exceeds CAN FD max payload (64)",
51 message.name, message_size
52 ));
53 }
54
55 for signal in message.signals {
56 if signal.multiplexer_indicator != MultiplexIndicator::Plain {
57 return Err(format!(
58 "DBC signal '{}.{}' uses multiplexing, which is unsupported in this version",
59 message.name, signal.name
60 ));
61 }
62 let size = signal.size;
63 if size == 0 || size > 64 {
64 return Err(format!(
65 "DBC signal '{}.{}' has invalid size {}",
66 message.name, signal.name, size
67 ));
68 }
69 let frame_key = frame_key(arb_id, extended);
70 signals_by_name.insert(
71 signal.name.clone(),
72 DbcSignalDef {
73 name: signal.name,
74 frame_key,
75 arb_id,
76 extended,
77 message_size,
78 start_bit: signal.start_bit,
79 size,
80 byte_order: signal.byte_order,
81 value_type: signal.value_type,
82 factor: signal.factor,
83 offset: signal.offset,
84 min: signal.min,
85 max: signal.max,
86 unit: if signal.unit.is_empty() {
87 None
88 } else {
89 Some(signal.unit)
90 },
91 },
92 );
93 }
94 }
95
96 Ok(Self { signals_by_name })
97 }
98
99 pub fn signal(&self, name: &str) -> Option<&DbcSignalDef> {
100 self.signals_by_name.get(name)
101 }
102
103 pub fn signal_names(&self) -> impl Iterator<Item = &String> {
104 self.signals_by_name.keys()
105 }
106}
107
108pub fn decode_signal(frame: &SimCanFrame, signal: &DbcSignalDef) -> Result<f64, String> {
109 let raw = extract_raw(frame, signal)?;
110 let numeric = match signal.value_type {
111 ValueType::Signed => signed_from_raw(raw, signal.size) as f64,
112 ValueType::Unsigned => raw as f64,
113 };
114 Ok(numeric * signal.factor + signal.offset)
115}
116
117pub fn encode_signal(
118 frame: &mut SimCanFrame,
119 signal: &DbcSignalDef,
120 value: f64,
121) -> Result<(), String> {
122 if !value.is_finite() {
123 return Err(format!(
124 "invalid value for CAN signal '{}': {value}",
125 signal.name
126 ));
127 }
128 let has_explicit_range = signal.min != 0.0 || signal.max != 0.0;
129 if has_explicit_range && (value < signal.min || value > signal.max) {
130 return Err(format!(
131 "value {value} is out of DBC range [{}, {}] for CAN signal '{}'",
132 signal.min, signal.max, signal.name
133 ));
134 }
135 if signal.factor == 0.0 {
136 return Err(format!(
137 "DBC signal '{}' has zero factor, cannot encode",
138 signal.name
139 ));
140 }
141
142 let raw_float = (value - signal.offset) / signal.factor;
143 let raw_i64 = raw_float.round() as i64;
144 let raw_u64 = match signal.value_type {
145 ValueType::Signed => {
146 let min = -(1_i128 << (signal.size - 1));
147 let max = (1_i128 << (signal.size - 1)) - 1;
148 let val = i128::from(raw_i64);
149 if val < min || val > max {
150 return Err(format!(
151 "encoded raw value {raw_i64} exceeds signed {}-bit range for signal '{}'",
152 signal.size, signal.name
153 ));
154 }
155 let mask = if signal.size == 64 {
156 u64::MAX
157 } else {
158 (1_u64 << signal.size) - 1
159 };
160 (raw_i64 as i128 as u64) & mask
161 }
162 ValueType::Unsigned => {
163 if raw_i64 < 0 {
164 return Err(format!(
165 "encoded raw value {raw_i64} is negative for unsigned signal '{}'",
166 signal.name
167 ));
168 }
169 let max = if signal.size == 64 {
170 u64::MAX
171 } else {
172 (1_u64 << signal.size) - 1
173 };
174 let raw = raw_i64 as u64;
175 if raw > max {
176 return Err(format!(
177 "encoded raw value {raw} exceeds unsigned {}-bit range for signal '{}'",
178 signal.size, signal.name
179 ));
180 }
181 raw
182 }
183 };
184
185 insert_raw(frame, signal, raw_u64)?;
186 if signal.message_size > frame.len {
187 frame.len = signal.message_size;
188 }
189 if frame.len > 8 {
190 frame.flags |= CAN_FLAG_FD;
191 } else {
192 frame.flags &= !CAN_FLAG_FD;
193 }
194 if signal.extended {
195 frame.flags |= CAN_FLAG_EXTENDED;
196 } else {
197 frame.flags &= !CAN_FLAG_EXTENDED;
198 }
199 Ok(())
200}
201
202pub fn frame_key_from_frame(frame: &SimCanFrame) -> u32 {
203 frame_key(frame.arb_id, (frame.flags & CAN_FLAG_EXTENDED) != 0)
204}
205
206fn frame_key(arb_id: u32, extended: bool) -> u32 {
207 if extended { arb_id | (1 << 31) } else { arb_id }
208}
209
210fn extract_raw(frame: &SimCanFrame, signal: &DbcSignalDef) -> Result<u64, String> {
211 if signal.size > 64 {
212 return Err(format!(
213 "signal '{}' size {} exceeds 64 bits",
214 signal.name, signal.size
215 ));
216 }
217 match signal.byte_order {
218 ByteOrder::LittleEndian => {
219 let mut raw = 0_u64;
220 for idx in 0..signal.size {
221 let frame_bit = signal.start_bit + idx;
222 let bit = get_bit(&frame.data, frame_bit)?;
223 raw |= u64::from(bit) << idx;
224 }
225 Ok(raw)
226 }
227 ByteOrder::BigEndian => {
228 let mut raw = 0_u64;
229 let mut bit_pos = signal.start_bit as i64;
230 for _ in 0..signal.size {
231 let bit = get_bit(&frame.data, bit_pos as u64)?;
232 raw = (raw << 1) | u64::from(bit);
233 bit_pos = next_motorola_bit(bit_pos);
234 }
235 Ok(raw)
236 }
237 }
238}
239
240fn insert_raw(frame: &mut SimCanFrame, signal: &DbcSignalDef, raw: u64) -> Result<(), String> {
241 match signal.byte_order {
242 ByteOrder::LittleEndian => {
243 for idx in 0..signal.size {
244 let frame_bit = signal.start_bit + idx;
245 let bit = ((raw >> idx) & 1) as u8;
246 set_bit(&mut frame.data, frame_bit, bit)?;
247 }
248 }
249 ByteOrder::BigEndian => {
250 let mut bit_pos = signal.start_bit as i64;
251 for idx in 0..signal.size {
252 let shift = signal.size - 1 - idx;
253 let bit = ((raw >> shift) & 1) as u8;
254 set_bit(&mut frame.data, bit_pos as u64, bit)?;
255 bit_pos = next_motorola_bit(bit_pos);
256 }
257 }
258 }
259 Ok(())
260}
261
262fn get_bit(data: &[u8; 64], index: u64) -> Result<u8, String> {
263 let byte_index = usize::try_from(index / 8).map_err(|_| "bit index overflow".to_string())?;
264 if byte_index >= data.len() {
265 return Err(format!("bit index {index} is out of bounds"));
266 }
267 let bit_index = (index % 8) as u8;
268 Ok((data[byte_index] >> bit_index) & 1)
269}
270
271fn set_bit(data: &mut [u8; 64], index: u64, bit: u8) -> Result<(), String> {
272 let byte_index = usize::try_from(index / 8).map_err(|_| "bit index overflow".to_string())?;
273 if byte_index >= data.len() {
274 return Err(format!("bit index {index} is out of bounds"));
275 }
276 let mask = 1_u8 << (index % 8);
277 if bit == 0 {
278 data[byte_index] &= !mask;
279 } else {
280 data[byte_index] |= mask;
281 }
282 Ok(())
283}
284
285fn next_motorola_bit(current: i64) -> i64 {
286 if current % 8 == 0 {
287 current + 15
288 } else {
289 current - 1
290 }
291}
292
293fn signed_from_raw(raw: u64, bits: u64) -> i64 {
294 if bits == 0 {
295 return 0;
296 }
297 if bits >= 64 {
298 return raw as i64;
299 }
300 let sign_bit = 1_u64 << (bits - 1);
301 if (raw & sign_bit) == 0 {
302 raw as i64
303 } else {
304 (raw as i64) - ((1_u64 << bits) as i64)
305 }
306}
307
308#[cfg(test)]
309mod tests {
310 use super::*;
311
312 fn sample_signal(min: f64, max: f64) -> DbcSignalDef {
313 DbcSignalDef {
314 name: "EngineRPM".to_string(),
315 frame_key: 0x123,
316 arb_id: 0x123,
317 extended: false,
318 message_size: 8,
319 start_bit: 0,
320 size: 16,
321 byte_order: ByteOrder::LittleEndian,
322 value_type: ValueType::Unsigned,
323 factor: 0.25,
324 offset: 0.0,
325 min,
326 max,
327 unit: Some("RPM".to_string()),
328 }
329 }
330
331 fn empty_frame() -> SimCanFrame {
332 SimCanFrame {
333 arb_id: 0x123,
334 len: 0,
335 flags: 0,
336 data: [0; 64],
337 }
338 }
339
340 #[test]
341 fn encode_signal_allows_nonzero_with_unspecified_dbc_range() {
342 let signal = sample_signal(0.0, 0.0);
343 let mut frame = empty_frame();
344 encode_signal(&mut frame, &signal, 1500.0)
345 .expect("value should encode when DBC range is unspecified [0|0]");
346 let decoded = decode_signal(&frame, &signal).expect("encoded signal should decode");
347 assert!((decoded - 1500.0).abs() < f64::EPSILON);
348 }
349
350 #[test]
351 fn encode_signal_rejects_values_outside_explicit_dbc_range() {
352 let signal = sample_signal(0.0, 250.0);
353 let mut frame = empty_frame();
354 let err = encode_signal(&mut frame, &signal, 1500.0).expect_err("value should be rejected");
355 assert!(err.contains("out of DBC range [0, 250]"));
356 }
357}