use bitmask_enum::bitmask;
use byteorder::{ReadBytesExt, WriteBytesExt};
use crate::{Error, IterableWireFormat, SingleValueWireFormat, WireFormat};
#[bitmask(u8)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
pub enum DTCStatusMask {
TestFailed,
TestFailedThisOperationCycle,
PendingDTC,
ConfirmedDTC,
TestNotCompletedSinceLastClear,
TestFailedSinceLastClear,
TestNotCompletedThisOperationCycle,
WarningIndicatorRequested,
}
impl WireFormat for DTCStatusMask {
fn decode<T: std::io::Read>(reader: &mut T) -> Result<Option<Self>, crate::Error> {
let status_byte = reader.read_u8()?;
Ok(Some(Self::from(status_byte)))
}
fn required_size(&self) -> usize {
1
}
fn encode<T: std::io::Write>(&self, writer: &mut T) -> Result<usize, crate::Error> {
writer.write_u8(self.bits())?;
Ok(1)
}
}
impl SingleValueWireFormat for DTCStatusMask {}
#[allow(non_camel_case_types)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
#[derive(Clone, Copy, Debug, PartialEq)]
#[non_exhaustive]
#[repr(u8)]
pub enum DTCFormatIdentifier {
SAE_J2012_DA_DTCFormat_00 = 0x00,
ISO_14229_1_DTCFormat = 0x01,
SAE_J1939_73_DTCFormat = 0x02,
ISO_11992_4_DTCFormat = 0x03,
SAE_J2012_DA_DTCFormat_04 = 0x04,
ISOSAEReserved(u8),
}
impl DTCFormatIdentifier {}
impl From<u8> for DTCFormatIdentifier {
fn from(value: u8) -> Self {
match value {
0x00 => DTCFormatIdentifier::SAE_J2012_DA_DTCFormat_00,
0x01 => DTCFormatIdentifier::ISO_14229_1_DTCFormat,
0x02 => DTCFormatIdentifier::SAE_J1939_73_DTCFormat,
0x03 => DTCFormatIdentifier::ISO_11992_4_DTCFormat,
0x04 => DTCFormatIdentifier::SAE_J2012_DA_DTCFormat_04,
val => DTCFormatIdentifier::ISOSAEReserved(val),
}
}
}
impl From<DTCFormatIdentifier> for u8 {
fn from(val: DTCFormatIdentifier) -> Self {
match val {
DTCFormatIdentifier::SAE_J2012_DA_DTCFormat_00 => 0x00,
DTCFormatIdentifier::ISO_14229_1_DTCFormat => 0x01,
DTCFormatIdentifier::SAE_J1939_73_DTCFormat => 0x02,
DTCFormatIdentifier::ISO_11992_4_DTCFormat => 0x03,
DTCFormatIdentifier::SAE_J2012_DA_DTCFormat_04 => 0x04,
DTCFormatIdentifier::ISOSAEReserved(value) => value, }
}
}
pub const CLEAR_ALL_DTCS: DTCRecord = DTCRecord {
high_byte: 0xFF,
middle_byte: 0xFF,
low_byte: 0xFF,
};
#[allow(clippy::struct_field_names)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct DTCRecord {
high_byte: u8,
middle_byte: u8,
low_byte: u8,
}
impl DTCRecord {
#[must_use]
pub fn new(high_byte: u8, middle_byte: u8, low_byte: u8) -> Self {
Self {
high_byte,
middle_byte,
low_byte,
}
}
}
impl From<u32> for DTCRecord {
fn from(value: u32) -> Self {
Self {
high_byte: ((value >> 16) & 0xFF) as u8,
middle_byte: ((value >> 8) & 0xFF) as u8,
low_byte: (value & 0xFF) as u8,
}
}
}
impl From<DTCRecord> for u32 {
fn from(value: DTCRecord) -> Self {
(u32::from(value.high_byte) << 16)
| (u32::from(value.middle_byte) << 8)
| u32::from(value.low_byte)
}
}
impl WireFormat for DTCRecord {
fn decode<T: std::io::Read>(reader: &mut T) -> Result<Option<Self>, crate::Error> {
let Ok(high_byte) = reader.read_u8() else {
return Ok(None);
};
let middle_byte = reader.read_u8()?;
let low_byte = reader.read_u8()?;
Ok(Some(Self {
high_byte,
middle_byte,
low_byte,
}))
}
fn required_size(&self) -> usize {
3
}
fn encode<T: std::io::Write>(&self, writer: &mut T) -> Result<usize, crate::Error> {
writer.write_all(&[self.high_byte, self.middle_byte, self.low_byte])?;
Ok(3)
}
}
impl SingleValueWireFormat for DTCRecord {}
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[non_exhaustive]
pub enum FunctionalGroupIdentifier {
ISOSAEReserved(u8),
EmissionsSystemGroup,
SafetySystemGroup,
LegislativeSystemGroup(u8),
VODBSystem,
}
impl FunctionalGroupIdentifier {
#[must_use]
pub fn value(&self) -> u8 {
match self {
FunctionalGroupIdentifier::EmissionsSystemGroup => 0x33,
FunctionalGroupIdentifier::SafetySystemGroup => 0xD0,
FunctionalGroupIdentifier::VODBSystem => 0xFE,
FunctionalGroupIdentifier::LegislativeSystemGroup(value) => {
todo!(
"FunctionalGroupIdentifiers::LegislativeSystemGroup is not a valid value {}",
value
)
}
FunctionalGroupIdentifier::ISOSAEReserved(value) => {
todo!(
"FunctionalGroupIdentifiers::ISOSAEReserved is not a valid value {}",
value
)
}
}
}
}
impl From<u8> for FunctionalGroupIdentifier {
fn from(value: u8) -> Self {
match value {
0x33 => FunctionalGroupIdentifier::EmissionsSystemGroup,
0xD0 => FunctionalGroupIdentifier::SafetySystemGroup,
0xFE => FunctionalGroupIdentifier::VODBSystem,
0xD1..=0xDF => FunctionalGroupIdentifier::LegislativeSystemGroup(value),
_ => FunctionalGroupIdentifier::ISOSAEReserved(value),
}
}
}
impl From<FunctionalGroupIdentifier> for u8 {
fn from(value: FunctionalGroupIdentifier) -> Self {
value.value()
}
}
#[allow(non_camel_case_types)]
#[bitmask(u8)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
pub enum DTCSeverityMask {
DTCClass_0,
DTCClass_1,
DTCClass_2,
DTCClass_3,
DTCClass_4,
MaintenanceOnly = 0b0010_0000,
CheckAtNextHalt = 0b0100_0000,
CheckImmediately = 0b1000_0000, }
impl DTCSeverityMask {
#[must_use]
pub fn is_valid(&self) -> bool {
self.intersects(
Self::DTCClass_0
| Self::DTCClass_1
| Self::DTCClass_2
| Self::DTCClass_3
| Self::DTCClass_4,
)
}
}
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct DTCStoredDataRecordNumber(u8);
impl DTCStoredDataRecordNumber {
pub fn new(record_number: u8) -> Result<Self, Error> {
if record_number == 0 || record_number == 0xF0 {
return Err(Error::ReservedForLegislativeUse(
"DTCStoredDataRecordNumber".to_string(),
record_number,
));
}
Ok(Self(record_number))
}
}
impl WireFormat for DTCStoredDataRecordNumber {
fn decode<T: std::io::Read>(reader: &mut T) -> Result<Option<Self>, Error> {
let value = reader.read_u8()?;
if value == 0x00 {
return Err(Error::ReservedForLegislativeUse(
"DTCStoredDataRecordNumber".to_string(),
value,
));
}
Ok(Some(Self(value)))
}
fn required_size(&self) -> usize {
1
}
fn encode<T: std::io::Write>(&self, writer: &mut T) -> Result<usize, Error> {
writer.write_u8(self.0)?;
Ok(1)
}
}
impl SingleValueWireFormat for DTCStoredDataRecordNumber {}
impl From<u8> for DTCStoredDataRecordNumber {
fn from(value: u8) -> Self {
Self(value)
}
}
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct DTCSeverityRecord {
pub severity: DTCSeverityMask,
pub functional_group_identifier: FunctionalGroupIdentifier,
pub dtc_record: DTCRecord,
pub dtc_status_mask: DTCStatusMask,
}
impl WireFormat for DTCSeverityRecord {
fn decode<T: std::io::Read>(reader: &mut T) -> Result<Option<Self>, Error> {
let Ok(sev) = reader.read_u8() else {
return Ok(None);
};
let severity = DTCSeverityMask::from(sev);
let functional_group_identifier = FunctionalGroupIdentifier::from(reader.read_u8()?);
let dtc_record = DTCRecord::decode(reader)?.unwrap();
let dtc_status_mask = DTCStatusMask::from(reader.read_u8()?);
Ok(Some(Self {
severity,
functional_group_identifier,
dtc_record,
dtc_status_mask,
}))
}
fn required_size(&self) -> usize {
6
}
fn encode<T: std::io::Write>(&self, writer: &mut T) -> Result<usize, Error> {
writer.write_u8(self.severity.bits())?;
writer.write_u8(self.functional_group_identifier.value())?;
self.dtc_record.encode(writer)?;
self.dtc_status_mask.encode(writer)?;
Ok(self.required_size())
}
}
impl IterableWireFormat for DTCSeverityRecord {}
#[cfg(test)]
mod dtc_status_tests {
use super::*;
#[test]
fn status_mask() {
let status_mask = DTCStatusMask::TestFailed | DTCStatusMask::PendingDTC;
assert_eq!(status_mask.bits(), 0b0000_0101);
let status_mask = DTCStatusMask::TestFailedThisOperationCycle
| DTCStatusMask::TestNotCompletedSinceLastClear;
assert_eq!(status_mask.bits(), 0b0001_0010);
}
#[test]
fn gtr_dtc_class_info() {
let dtc_class = DTCSeverityMask::DTCClass_1 | DTCSeverityMask::MaintenanceOnly;
assert_eq!(dtc_class.bits(), 0b0010_0010);
assert!(dtc_class.is_valid());
}
#[test]
fn dtc_severity_info() {
let dtc_severity = DTCSeverityMask::CheckImmediately;
assert_eq!(dtc_severity.bits(), 0b1000_0000);
}
#[test]
fn dtc_record() {
let record = DTCRecord::new(0x01, 0x02, 0x03);
let mut writer = Vec::new();
let written_number = record.encode(&mut writer).unwrap();
assert_eq!(record.required_size(), 3);
assert_eq!(written_number, 3);
}
}