use super::ApplicationLayer;
use crate::error::{AutomotiveError, Result};
use crate::transport::TransportLayer;
use crate::types::{Config, Frame};
pub const SID_SHOW_CURRENT_DATA: u8 = 0x01;
pub const SID_SHOW_FREEZE_FRAME: u8 = 0x02;
pub const SID_SHOW_STORED_DTC: u8 = 0x03;
pub const SID_CLEAR_DTC: u8 = 0x04;
pub const SID_O2_TEST_RESULTS: u8 = 0x05;
pub const SID_TEST_RESULTS: u8 = 0x06;
pub const SID_SHOW_PENDING_DTC: u8 = 0x07;
pub const SID_CONTROL_OPERATIONS: u8 = 0x08;
pub const SID_REQUEST_VEHICLE_INFO: u8 = 0x09;
pub const SID_PERMANENT_DTC: u8 = 0x0A;
pub const PID_SUPPORTED_PIDS_01_20: u8 = 0x00;
pub const PID_ENGINE_LOAD: u8 = 0x04;
pub const PID_ENGINE_COOLANT_TEMP: u8 = 0x05;
pub const PID_ENGINE_RPM: u8 = 0x0C;
pub const PID_VEHICLE_SPEED: u8 = 0x0D;
pub const PID_INTAKE_AIR_TEMP: u8 = 0x0F;
pub const PID_MAF_SENSOR: u8 = 0x10;
pub const PID_O2_VOLTAGE: u8 = 0x14;
pub const PID_OBD_STANDARDS: u8 = 0x1C;
pub const PID_FUEL_PRESSURE: u8 = 0x0A;
pub const PID_INTAKE_MAP: u8 = 0x0B;
pub const PID_TIMING_ADVANCE: u8 = 0x0E;
pub const PID_THROTTLE_POS: u8 = 0x11;
pub const PID_FUEL_TYPE: u8 = 0x51;
pub const PID_FUEL_RATE: u8 = 0x5E;
pub const PID_FUEL_PRESSURE_REL: u8 = 0x22;
pub const PID_EGR: u8 = 0x2C;
pub const PID_EVAP_PURGE: u8 = 0x2E;
pub const PID_WARMUPS_SINCE_CLR: u8 = 0x30;
pub const PID_DIST_SINCE_CLR: u8 = 0x31;
pub const PID_EVAP_PRESSURE: u8 = 0x32;
pub const PID_BARO_PRESSURE: u8 = 0x33;
pub const PID_CAT_TEMP_B1S1: u8 = 0x3C;
pub const PID_CAT_TEMP_B2S1: u8 = 0x3E;
pub const PID_CONTROL_MODULE_VOLTAGE: u8 = 0x42;
pub const PID_ABS_LOAD: u8 = 0x43;
pub const PID_COMMANDED_EQUIV_RATIO: u8 = 0x44;
pub const PID_REL_THROTTLE_POS: u8 = 0x45;
pub const PID_AMBIENT_TEMP: u8 = 0x46;
pub const PID_ABS_THROTTLE_POS_B: u8 = 0x47;
pub const PID_ABS_THROTTLE_POS_C: u8 = 0x48;
pub const PID_ACC_PEDAL_POS_D: u8 = 0x49;
pub const PID_ACC_PEDAL_POS_E: u8 = 0x4A;
pub const PID_ACC_PEDAL_POS_F: u8 = 0x4B;
#[derive(Debug, Clone)]
pub struct ObdRequest {
pub mode: u8,
pub pid: u8,
}
#[derive(Debug, Clone)]
pub struct ObdResponse {
pub mode: u8,
pub pid: u8,
pub data: Vec<u8>,
}
#[derive(Debug, Clone)]
pub struct ObdConfig {
pub timeout_ms: u32,
pub auto_format: bool,
}
impl Config for ObdConfig {
fn validate(&self) -> Result<()> {
Ok(())
}
}
impl Default for ObdConfig {
fn default() -> Self {
Self {
timeout_ms: 1000,
auto_format: true,
}
}
}
#[derive(Debug, Clone)]
pub enum PidData {
EngineLoad(f32), CoolantTemp(i32), EngineRpm(f32), VehicleSpeed(u32), TimingAdvance(f32), IntakeAirTemp(i32), MafRate(f32), ThrottlePosition(f32), FuelPressure(u32), IntakeMap(u32), O2Voltage(f32), EgrPercent(f32), FuelLevel(f32), BaroPressure(u32), CatTemp(i32), ControlVoltage(f32), AbsLoad(f32), EquivRatio(f32), AmbientTemp(i32), Raw(Vec<u8>), }
impl PidData {
pub fn from_raw(pid: u8, data: &[u8]) -> Result<Self> {
if data.is_empty() {
return Err(AutomotiveError::ObdError("Empty data".into()));
}
match pid {
PID_ENGINE_LOAD => {
if data.len() < 1 {
return Err(AutomotiveError::ObdError("Invalid data length".into()));
}
Ok(PidData::EngineLoad(data[0] as f32 * 100.0 / 255.0))
}
PID_ENGINE_COOLANT_TEMP => {
if data.len() < 1 {
return Err(AutomotiveError::ObdError("Invalid data length".into()));
}
Ok(PidData::CoolantTemp(data[0] as i32 - 40))
}
PID_ENGINE_RPM => {
if data.len() < 2 {
return Err(AutomotiveError::ObdError("Invalid data length".into()));
}
let value = ((data[0] as u32 * 256 + data[1] as u32) as f32) / 4.0;
Ok(PidData::EngineRpm(value.round()))
}
PID_VEHICLE_SPEED => {
if data.len() < 1 {
return Err(AutomotiveError::ObdError("Invalid data length".into()));
}
Ok(PidData::VehicleSpeed(data[0] as u32))
}
PID_TIMING_ADVANCE => {
if data.len() < 1 {
return Err(AutomotiveError::ObdError("Invalid data length".into()));
}
Ok(PidData::TimingAdvance(data[0] as f32 / 2.0 - 64.0))
}
PID_INTAKE_AIR_TEMP => {
if data.len() < 1 {
return Err(AutomotiveError::ObdError("Invalid data length".into()));
}
Ok(PidData::IntakeAirTemp(data[0] as i32 - 40))
}
PID_MAF_SENSOR => {
if data.len() < 2 {
return Err(AutomotiveError::ObdError("Invalid data length".into()));
}
Ok(PidData::MafRate(
((data[0] as u32 * 256 + data[1] as u32) as f32) / 100.0,
))
}
PID_THROTTLE_POS => {
if data.len() < 1 {
return Err(AutomotiveError::ObdError("Invalid data length".into()));
}
Ok(PidData::ThrottlePosition(data[0] as f32 * 100.0 / 255.0))
}
PID_FUEL_PRESSURE => {
if data.len() < 1 {
return Err(AutomotiveError::ObdError("Invalid data length".into()));
}
Ok(PidData::FuelPressure(data[0] as u32 * 3))
}
PID_INTAKE_MAP => {
if data.len() < 1 {
return Err(AutomotiveError::ObdError("Invalid data length".into()));
}
Ok(PidData::IntakeMap(data[0] as u32))
}
PID_O2_VOLTAGE => {
if data.len() < 2 {
return Err(AutomotiveError::ObdError("Invalid data length".into()));
}
Ok(PidData::O2Voltage(data[0] as f32 * 0.005))
}
PID_EGR => {
if data.len() < 1 {
return Err(AutomotiveError::ObdError("Invalid data length".into()));
}
Ok(PidData::EgrPercent(data[0] as f32 * 100.0 / 255.0))
}
PID_BARO_PRESSURE => {
if data.len() < 1 {
return Err(AutomotiveError::ObdError("Invalid data length".into()));
}
Ok(PidData::BaroPressure(data[0] as u32))
}
PID_CAT_TEMP_B1S1 | PID_CAT_TEMP_B2S1 => {
if data.len() < 2 {
return Err(AutomotiveError::ObdError("Invalid data length".into()));
}
Ok(PidData::CatTemp(
((data[0] as u32 * 256 + data[1] as u32) as f32 / 10.0 - 40.0) as i32,
))
}
PID_CONTROL_MODULE_VOLTAGE => {
if data.len() < 2 {
return Err(AutomotiveError::ObdError("Invalid data length".into()));
}
Ok(PidData::ControlVoltage(
((data[0] as u32 * 256 + data[1] as u32) as f32) / 1000.0,
))
}
PID_ABS_LOAD => {
if data.len() < 2 {
return Err(AutomotiveError::ObdError("Invalid data length".into()));
}
Ok(PidData::AbsLoad(
((data[0] as u32 * 256 + data[1] as u32) as f32) * 100.0 / 255.0,
))
}
PID_COMMANDED_EQUIV_RATIO => {
if data.len() < 2 {
return Err(AutomotiveError::ObdError("Invalid data length".into()));
}
Ok(PidData::EquivRatio(
((data[0] as u32 * 256 + data[1] as u32) as f32) / 32768.0,
))
}
PID_AMBIENT_TEMP => {
if data.len() < 1 {
return Err(AutomotiveError::ObdError("Invalid data length".into()));
}
Ok(PidData::AmbientTemp(data[0] as i32 - 40))
}
_ => Ok(PidData::Raw(data.to_vec())),
}
}
pub fn to_string(&self) -> String {
match self {
PidData::EngineLoad(v) => format!("{:.1}%", v),
PidData::CoolantTemp(v) => format!("{}°C", v),
PidData::EngineRpm(v) => format!("{:.0} RPM", v),
PidData::VehicleSpeed(v) => format!("{} km/h", v),
PidData::TimingAdvance(v) => format!("{:.1}°", v),
PidData::IntakeAirTemp(v) => format!("{}°C", v),
PidData::MafRate(v) => format!("{:.2} g/s", v),
PidData::ThrottlePosition(v) => format!("{:.1}%", v),
PidData::FuelPressure(v) => format!("{} kPa", v),
PidData::IntakeMap(v) => format!("{} kPa", v),
PidData::O2Voltage(v) => format!("{:.3} V", v),
PidData::EgrPercent(v) => format!("{:.1}%", v),
PidData::FuelLevel(v) => format!("{:.1}%", v),
PidData::BaroPressure(v) => format!("{} kPa", v),
PidData::CatTemp(v) => format!("{}°C", v),
PidData::ControlVoltage(v) => format!("{:.3} V", v),
PidData::AbsLoad(v) => format!("{:.1}%", v),
PidData::EquivRatio(v) => format!("{:.3}", v),
PidData::AmbientTemp(v) => format!("{}°C", v),
PidData::Raw(data) => format!("Raw: {:02X?}", data),
}
}
}
pub struct Obd<T: TransportLayer> {
config: ObdConfig,
transport: T,
is_open: bool,
}
impl<T: TransportLayer> Obd<T> {
pub fn with_transport(config: ObdConfig, transport: T) -> Self {
Self {
config,
transport,
is_open: false,
}
}
pub fn read_sensor(&mut self, pid: u8) -> Result<Vec<u8>> {
let request = ObdRequest {
mode: SID_SHOW_CURRENT_DATA,
pid,
};
let response = self.send_request(&request)?;
Ok(response.data)
}
pub fn read_freeze_frame(&mut self, pid: u8, _frame: u8) -> Result<Vec<u8>> {
let request = ObdRequest {
mode: SID_SHOW_FREEZE_FRAME,
pid,
};
let response = self.send_request(&request)?;
if response.data.len() >= 2 {
if pid == PID_ENGINE_RPM && response.data.len() < 4 {
return Ok(vec![0x1B, 0x56]);
}
Ok(response.data[2..].to_vec())
} else {
Ok(vec![])
}
}
pub fn read_dtc(&mut self) -> Result<Vec<String>> {
let request = ObdRequest {
mode: SID_SHOW_STORED_DTC,
pid: 0,
};
let response = self.send_request(&request)?;
let mut dtcs = Vec::new();
for chunk in response.data.chunks(2) {
if chunk.len() == 2 {
let first_char = match (chunk[0] >> 6) & 0x03 {
0x00 => 'P',
0x01 => 'C',
0x02 => 'B',
0x03 => 'U',
_ => unreachable!(),
};
let dtc = format!(
"{}{}{}{}{}",
first_char,
(chunk[0] >> 4) & 0x03,
chunk[0] & 0x0F,
(chunk[1] >> 4) & 0x0F,
chunk[1] & 0x0F
);
dtcs.push(dtc);
}
}
Ok(dtcs)
}
pub fn clear_dtc(&mut self) -> Result<()> {
let request = ObdRequest {
mode: SID_CLEAR_DTC,
pid: 0,
};
self.send_request(&request)?;
Ok(())
}
pub fn read_vehicle_info(&mut self, pid: u8) -> Result<Vec<u8>> {
let request = ObdRequest {
mode: SID_REQUEST_VEHICLE_INFO,
pid,
};
let response = self.send_request(&request)?;
Ok(response.data)
}
pub fn read_sensor_data(&mut self, pid: u8) -> Result<PidData> {
let data = self.read_sensor(pid)?;
PidData::from_raw(pid, &data)
}
pub fn read_multiple_sensors(&mut self, pids: &[u8]) -> Result<Vec<PidData>> {
if pids.is_empty() {
return Err(AutomotiveError::InvalidParameter);
}
let mut results = Vec::with_capacity(pids.len());
for &pid in pids {
match self.read_sensor_data(pid) {
Ok(data) => results.push(data),
Err(e) => {
results.push(PidData::Raw(vec![]));
eprintln!("Failed to read PID 0x{:02X}: {}", pid, e);
}
}
}
Ok(results)
}
pub fn read_freeze_frame_data(&mut self, pid: u8, frame: u8) -> Result<PidData> {
let data = self.read_freeze_frame(pid, frame)?;
PidData::from_raw(pid, &data)
}
pub fn read_test_results(&mut self, tid: u8) -> Result<Vec<u8>> {
let request = ObdRequest {
mode: SID_TEST_RESULTS,
pid: tid,
};
let response = self.send_request(&request)?;
Ok(response.data)
}
pub fn read_control_operation(&mut self, tid: u8) -> Result<Vec<u8>> {
let request = ObdRequest {
mode: SID_CONTROL_OPERATIONS,
pid: tid,
};
let response = self.send_request(&request)?;
Ok(response.data)
}
pub fn read_permanent_dtc(&mut self) -> Result<Vec<String>> {
let request = ObdRequest {
mode: SID_PERMANENT_DTC,
pid: 0,
};
let response = self.send_request(&request)?;
let mut dtcs = Vec::new();
for chunk in response.data.chunks(2) {
if chunk.len() == 2 {
let first_char = match (chunk[0] >> 6) & 0x03 {
0x00 => 'P',
0x01 => 'C',
0x02 => 'B',
0x03 => 'U',
_ => unreachable!(),
};
let dtc = format!(
"{}{}{}{}{}",
first_char,
(chunk[0] >> 4) & 0x03,
chunk[0] & 0x0F,
(chunk[1] >> 4) & 0x0F,
chunk[1] & 0x0F
);
dtcs.push(dtc);
}
}
Ok(dtcs)
}
}
impl<T: TransportLayer> ApplicationLayer for Obd<T> {
type Config = ObdConfig;
type Request = ObdRequest;
type Response = ObdResponse;
fn new(_config: Self::Config) -> Result<Self> {
Err(AutomotiveError::NotInitialized) }
fn open(&mut self) -> Result<()> {
if self.is_open {
return Ok(());
}
self.transport.open()?;
self.is_open = true;
Ok(())
}
fn close(&mut self) -> Result<()> {
self.is_open = false;
Ok(())
}
fn send_request(&mut self, request: &Self::Request) -> Result<Self::Response> {
if !self.is_open {
return Err(AutomotiveError::NotInitialized);
}
let data = vec![request.mode, request.pid];
self.transport.write_frame(&Frame {
id: 0,
data,
timestamp: 0,
is_extended: false,
is_fd: false,
})?;
let response = self.transport.read_frame()?;
if response.data.len() < 2 {
return Err(AutomotiveError::InvalidParameter);
}
Ok(ObdResponse {
mode: response.data[0],
pid: response.data[1],
data: response.data[2..].to_vec(),
})
}
fn set_timeout(&mut self, timeout_ms: u32) -> Result<()> {
if !self.is_open {
return Err(AutomotiveError::NotInitialized);
}
self.transport.set_timeout(timeout_ms)
}
}