use std::fmt;
use std::str::FromStr;
use crate::engine::fastpacket;
use crate::engine::{FramePacketType, PgnDatabase, Units};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
#[non_exhaustive]
pub enum BusProtocol {
#[default]
Nmea2000,
J1939,
Quick,
}
impl BusProtocol {
pub const ALL: &'static [BusProtocol] = &[
BusProtocol::Nmea2000,
BusProtocol::J1939,
BusProtocol::Quick,
];
pub fn as_str(self) -> &'static str {
match self {
BusProtocol::Nmea2000 => "nmea2000",
BusProtocol::J1939 => "j1939",
BusProtocol::Quick => "quick",
}
}
pub fn standard_frames(self) -> bool {
self == BusProtocol::Quick
}
pub fn database(self, units: Units) -> &'static PgnDatabase {
match self {
BusProtocol::Nmea2000 => PgnDatabase::embedded(units),
BusProtocol::J1939 => PgnDatabase::embedded_j1939(units),
BusProtocol::Quick => PgnDatabase::embedded_quick(units),
}
}
pub fn packet_type(self, pgn: u32) -> FramePacketType {
match self {
BusProtocol::Nmea2000 => fastpacket::packet_type(pgn),
BusProtocol::J1939 | BusProtocol::Quick => FramePacketType::Single,
}
}
}
impl fmt::Display for BusProtocol {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct UnknownBusProtocol(pub String);
impl fmt::Display for UnknownBusProtocol {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"unknown bus protocol '{}' (expected nmea2000, j1939 or quick)",
self.0
)
}
}
impl std::error::Error for UnknownBusProtocol {}
impl FromStr for BusProtocol {
type Err = UnknownBusProtocol;
fn from_str(s: &str) -> Result<Self, Self::Err> {
BusProtocol::ALL
.iter()
.copied()
.find(|b| b.as_str().eq_ignore_ascii_case(s))
.ok_or_else(|| UnknownBusProtocol(s.to_string()))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parses_and_prints_the_cli_spelling() {
for &b in BusProtocol::ALL {
assert_eq!(b.as_str().parse::<BusProtocol>(), Ok(b));
assert_eq!(b.to_string(), b.as_str());
}
assert_eq!("J1939".parse::<BusProtocol>(), Ok(BusProtocol::J1939));
assert_eq!("quick".parse::<BusProtocol>(), Ok(BusProtocol::Quick));
assert!("canopen".parse::<BusProtocol>().is_err());
}
#[test]
fn j1939_never_uses_fast_packet_framing() {
assert_eq!(
BusProtocol::Nmea2000.packet_type(130816),
FramePacketType::Fast
);
assert_eq!(
BusProtocol::J1939.packet_type(130816),
FramePacketType::Single
);
}
#[test]
fn j1939_fields_carry_their_spn() {
let rpm = BusProtocol::J1939
.database(Units::Si)
.first_pgn(61444)
.unwrap()
.fields
.iter()
.find(|f| f.id == "engineRpm")
.unwrap();
assert_eq!(rpm.spn, Some(190));
let n2k = BusProtocol::Nmea2000.database(Units::Si);
let heading = n2k.first_pgn(127250).unwrap();
assert!(heading.fields.iter().all(|f| f.spn.is_none()));
}
#[test]
fn each_bus_has_its_own_table() {
let n2k = BusProtocol::Nmea2000.database(Units::Si);
let j1939 = BusProtocol::J1939.database(Units::Si);
assert!(!std::ptr::eq(n2k, j1939));
}
}