use std::collections::HashMap;
use std::time::{Duration, SystemTime, UNIX_EPOCH};
use crate::error::{AutomotiveError, Result};
use crate::types::Frame;
const PGN_DM1: u32 = 0x00FECA; const PGN_DM2: u32 = 0x00FECB; const PGN_DM3: u32 = 0x00FECC; const PGN_DM11: u32 = 0x00FED4; const PGN_DM13: u32 = 0x00FED6; const PGN_DM22: u32 = 0x00FEE3;
const DM1_BROADCAST_INTERVAL_MS: u64 = 1000;
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum LampStatus {
Off = 0, On = 1, SlowFlash = 2, FastFlash = 3, }
#[derive(Debug, Clone)]
pub struct DiagnosticTroubleCode {
spn: u32, fmi: u8, occurrence_count: u8, lamp_status: LampStatus, active: bool, }
impl DiagnosticTroubleCode {
pub fn new(spn: u32, fmi: u8) -> Self {
Self {
spn,
fmi,
occurrence_count: 1,
lamp_status: LampStatus::Off,
active: true,
}
}
fn to_bytes(&self) -> Vec<u8> {
let mut bytes = Vec::with_capacity(4);
let spn_bytes = ((self.spn & 0x7FFFF) << 5) as u32;
let fmi_byte = (self.fmi & 0x1F) as u32;
let count_byte = ((self.occurrence_count & 0x7F) as u32) << 24;
let combined = spn_bytes | fmi_byte | count_byte;
bytes.extend_from_slice(&combined.to_be_bytes());
bytes
}
fn from_bytes(data: &[u8]) -> Result<Self> {
if data.len() < 4 {
return Err(AutomotiveError::InvalidData);
}
let combined = u32::from_be_bytes([data[0], data[1], data[2], data[3]]);
let spn = (combined >> 5) & 0x7FFFF;
let fmi = (combined & 0x1F) as u8;
let occurrence_count = (combined >> 24) as u8;
Ok(Self {
spn,
fmi,
occurrence_count,
lamp_status: LampStatus::Off,
active: true,
})
}
}
pub struct ISOBUSDiagnosticProtocol {
active_dtcs: HashMap<(u32, u8), DiagnosticTroubleCode>, inactive_dtcs: HashMap<(u32, u8), DiagnosticTroubleCode>, last_dm1_broadcast: u64, broadcast_enabled: bool, }
impl ISOBUSDiagnosticProtocol {
pub fn new() -> Self {
Self {
active_dtcs: HashMap::new(),
inactive_dtcs: HashMap::new(),
last_dm1_broadcast: 0,
broadcast_enabled: true,
}
}
pub fn add_dtc(&mut self, dtc: DiagnosticTroubleCode) {
let key = (dtc.spn, dtc.fmi);
if dtc.active {
if let Some(existing) = self.active_dtcs.get_mut(&key) {
existing.occurrence_count = existing.occurrence_count.saturating_add(1);
} else {
self.active_dtcs.insert(key, dtc);
}
} else {
if let Some(existing) = self.inactive_dtcs.get_mut(&key) {
existing.occurrence_count = existing.occurrence_count.saturating_add(1);
} else {
self.inactive_dtcs.insert(key, dtc);
}
}
}
pub fn clear_active_dtcs(&mut self) {
self.active_dtcs.clear();
}
pub fn clear_inactive_dtcs(&mut self) {
self.inactive_dtcs.clear();
}
pub fn clear_dtc(&mut self, spn: u32, fmi: u8) {
let key = (spn, fmi);
self.active_dtcs.remove(&key);
self.inactive_dtcs.remove(&key);
}
pub fn set_broadcast_enabled(&mut self, enabled: bool) {
self.broadcast_enabled = enabled;
}
pub fn process_message(&mut self, frame: &Frame) -> Result<Option<Frame>> {
let pgn = (frame.id >> 8) as u32;
match pgn {
PGN_DM3 => {
self.clear_active_dtcs();
self.clear_inactive_dtcs();
Ok(None)
}
PGN_DM11 => {
self.clear_active_dtcs();
Ok(None)
}
PGN_DM13 => {
if frame.data.len() >= 1 {
self.broadcast_enabled = frame.data[0] != 0;
}
Ok(None)
}
PGN_DM22 => {
if frame.data.len() >= 4 {
if let Ok(dtc) = DiagnosticTroubleCode::from_bytes(&frame.data) {
self.clear_dtc(dtc.spn, dtc.fmi);
}
}
Ok(None)
}
_ => Ok(None),
}
}
pub fn update(&mut self) -> Result<Option<Frame>> {
if !self.broadcast_enabled {
return Ok(None);
}
let now = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_millis() as u64;
if now - self.last_dm1_broadcast >= DM1_BROADCAST_INTERVAL_MS {
self.last_dm1_broadcast = now;
if !self.active_dtcs.is_empty() {
let mut data = Vec::new();
data.extend_from_slice(&[0x00, 0x00]);
for dtc in self.active_dtcs.values() {
data.extend(dtc.to_bytes());
}
let frame = Frame {
id: (PGN_DM1 << 8) as u32,
data,
timestamp: now as u64,
is_extended: true,
is_fd: false,
};
return Ok(Some(frame));
}
}
Ok(None)
}
pub fn get_active_dtcs(&self) -> Vec<&DiagnosticTroubleCode> {
self.active_dtcs.values().collect()
}
pub fn get_inactive_dtcs(&self) -> Vec<&DiagnosticTroubleCode> {
self.inactive_dtcs.values().collect()
}
}