use std::{fmt, str::FromStr, time::Duration};
use crate::error::{Error, Result};
pub const MAX_STANDARD_ID: u16 = 0x07FF;
pub const MAX_EXTENDED_ID: u32 = 0x1FFF_FFFF;
pub const MAX_CAN_DATA_LEN: usize = 8;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum CanId {
Standard(u16),
Extended(u32),
}
impl CanId {
pub fn standard(id: u16) -> Result<Self> {
if id > MAX_STANDARD_ID {
return Err(Error::InvalidCanId(id.into()));
}
Ok(Self::Standard(id))
}
pub fn extended(id: u32) -> Result<Self> {
if id > MAX_EXTENDED_ID {
return Err(Error::InvalidCanId(id));
}
Ok(Self::Extended(id))
}
pub const fn raw(self) -> u32 {
match self {
Self::Standard(id) => id as u32,
Self::Extended(id) => id,
}
}
pub const fn is_extended(self) -> bool {
matches!(self, Self::Extended(_))
}
pub const fn kind_name(self) -> &'static str {
match self {
Self::Standard(_) => "standard",
Self::Extended(_) => "extended",
}
}
}
impl fmt::Display for CanId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{} 0x{:X}", self.kind_name(), self.raw())
}
}
impl FromStr for CanId {
type Err = String;
fn from_str(value: &str) -> std::result::Result<Self, Self::Err> {
let value = value.trim();
let (kind, raw) = if let Some((kind, raw)) = value.split_once(':') {
(kind.trim(), raw.trim())
} else {
let mut parts = value.split_whitespace();
match (parts.next(), parts.next(), parts.next()) {
(Some(kind), Some(raw), None) => (kind, raw),
_ => {
return Err(
"CAN ID must include a kind, like 'standard 0x123' or 'extended:0x501'"
.to_owned(),
);
}
}
};
let raw = parse_can_id_number(raw)?;
match kind.to_ascii_lowercase().as_str() {
"standard" | "std" | "s" => {
if raw > u32::from(MAX_STANDARD_ID) {
return Err(Error::InvalidCanId(raw).to_string());
}
CanId::standard(raw as u16).map_err(|err| err.to_string())
}
"extended" | "ext" | "e" => CanId::extended(raw).map_err(|err| err.to_string()),
other => Err(format!("unknown CAN ID kind {other:?}")),
}
}
}
fn parse_can_id_number(value: &str) -> std::result::Result<u32, String> {
if let Some(hex) = value
.strip_prefix("0x")
.or_else(|| value.strip_prefix("0X"))
{
u32::from_str_radix(hex, 16).map_err(|err| format!("invalid CAN ID {value:?}: {err}"))
} else {
value
.parse::<u32>()
.map_err(|err| format!("invalid CAN ID {value:?}: {err}"))
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CanFrame {
id: CanId,
data: [u8; MAX_CAN_DATA_LEN],
len: u8,
}
impl CanFrame {
pub fn new(id: CanId, data: &[u8]) -> Result<Self> {
if data.len() > MAX_CAN_DATA_LEN {
return Err(Error::PayloadTooLong { len: data.len() });
}
let mut padded = [0_u8; MAX_CAN_DATA_LEN];
padded[..data.len()].copy_from_slice(data);
Ok(Self {
id,
data: padded,
len: data.len() as u8,
})
}
pub fn new_padded(id: CanId, data: &[u8]) -> Result<Self> {
let mut frame = Self::new(id, data)?;
frame.len = MAX_CAN_DATA_LEN as u8;
Ok(frame)
}
pub const fn id(&self) -> CanId {
self.id
}
pub fn data(&self) -> &[u8] {
&self.data[..self.len as usize]
}
pub const fn padded_data(&self) -> [u8; MAX_CAN_DATA_LEN] {
self.data
}
pub const fn len(&self) -> usize {
self.len as usize
}
pub const fn is_empty(&self) -> bool {
self.len == 0
}
}
impl fmt::Display for CanFrame {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{} len={} data=", self.id, self.len())?;
for (index, byte) in self.data().iter().enumerate() {
if index > 0 {
f.write_str(" ")?;
}
write!(f, "{byte:02X}")?;
}
Ok(())
}
}
pub trait CanBus {
fn send(&mut self, frame: &CanFrame) -> Result<()>;
fn receive(&mut self, timeout: Duration) -> Result<Option<CanFrame>>;
}