use snafu::Snafu;
use crate::{
lss::{LssRequest, LssResponse},
nmt::{InvalidNmtStateError, NmtState},
sdo::{SdoRequest, SdoResponse},
};
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum CanId {
Extended(u32),
Std(u16),
}
impl core::fmt::Display for CanId {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
CanId::Extended(id) => write!(f, "Extended(0x{id:x})"),
CanId::Std(id) => write!(f, "Std(0x{id:x})"),
}
}
}
impl CanId {
pub const fn extended(id: u32) -> CanId {
CanId::Extended(id)
}
pub const fn std(id: u16) -> CanId {
CanId::Std(id)
}
pub const fn raw(&self) -> u32 {
match self {
CanId::Extended(id) => *id,
CanId::Std(id) => *id as u32,
}
}
pub const fn is_extended(&self) -> bool {
match self {
CanId::Extended(_) => true,
CanId::Std(_) => false,
}
}
}
const MAX_DATA_LENGTH: usize = 8;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct CanMessage {
pub data: [u8; MAX_DATA_LENGTH],
pub dlc: u8,
pub rtr: bool,
pub id: CanId,
}
impl Default for CanMessage {
fn default() -> Self {
Self {
data: [0; MAX_DATA_LENGTH],
dlc: 0,
id: CanId::Std(0),
rtr: false,
}
}
}
impl CanMessage {
pub fn new(id: CanId, data: &[u8]) -> Self {
let dlc = data.len() as u8;
if dlc > MAX_DATA_LENGTH as u8 {
panic!(
"Data length exceeds maximum size of {} bytes",
MAX_DATA_LENGTH
);
}
let mut buf = [0u8; MAX_DATA_LENGTH];
buf[0..dlc as usize].copy_from_slice(data);
let rtr = false;
Self {
id,
dlc,
data: buf,
rtr,
}
}
pub fn new_rtr(id: CanId) -> Self {
Self {
id,
rtr: true,
..Default::default()
}
}
pub fn id(&self) -> CanId {
self.id
}
pub fn data(&self) -> &[u8] {
&self.data[0..self.dlc as usize]
}
pub fn is_rtr(&self) -> bool {
self.rtr
}
}
#[derive(Clone, Copy, Debug, Snafu)]
#[repr(u8)]
pub enum CanError {
Bit = 1,
Stuff = 2,
Form = 3,
Ack = 4,
Crc = 5,
Other,
}
impl CanError {
pub fn from_raw(raw: u8) -> Self {
match raw {
1 => Self::Bit,
2 => Self::Stuff,
3 => Self::Form,
4 => Self::Ack,
5 => Self::Crc,
_ => Self::Other,
}
}
}
#[derive(Copy, Clone, Debug, PartialEq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[repr(u8)]
pub enum NmtCommandSpecifier {
Start = 1,
Stop = 2,
EnterPreOp = 128,
ResetApp = 129,
ResetComm = 130,
}
impl NmtCommandSpecifier {
pub fn from_byte(b: u8) -> Result<Self, MessageError> {
match b {
1 => Ok(Self::Start),
2 => Ok(Self::Stop),
128 => Ok(Self::EnterPreOp),
129 => Ok(Self::ResetApp),
130 => Ok(Self::ResetComm),
_ => Err(MessageError::InvalidField),
}
}
}
pub const NMT_CMD_ID: CanId = CanId::Std(0);
pub const SYNC_ID: CanId = CanId::Std(0x80);
pub const LSS_RESP_ID: CanId = CanId::Std(0x7E4);
pub const LSS_REQ_ID: CanId = CanId::Std(0x7E5);
pub const HEARTBEAT_ID: u16 = 0x700;
pub const SDO_REQ_BASE: u16 = 0x600;
pub const SDO_RESP_BASE: u16 = 0x580;
#[derive(Clone, Copy, Debug)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct NmtCommand {
pub cs: NmtCommandSpecifier,
pub node: u8,
}
impl TryFrom<CanMessage> for NmtCommand {
type Error = MessageError;
fn try_from(msg: CanMessage) -> Result<Self, Self::Error> {
let payload = msg.data();
if msg.id() != NMT_CMD_ID {
Err(MessageError::UnexpectedId {
cob_id: msg.id(),
expected: NMT_CMD_ID,
})
} else if payload.len() >= 2 {
let cmd = NmtCommandSpecifier::from_byte(payload[0])?;
let node = payload[1];
Ok(NmtCommand { cs: cmd, node })
} else {
Err(MessageError::MessageTooShort)
}
}
}
impl From<NmtCommand> for CanMessage {
fn from(cmd: NmtCommand) -> Self {
let mut msg = CanMessage {
id: NMT_CMD_ID,
dlc: 2,
..Default::default()
};
msg.data[0] = cmd.cs as u8;
msg.data[1] = cmd.node;
msg
}
}
#[derive(Clone, Copy, Debug)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct Heartbeat {
pub node: u8,
pub toggle: bool,
pub state: NmtState,
}
impl From<Heartbeat> for CanMessage {
fn from(value: Heartbeat) -> Self {
let mut msg = CanMessage {
id: CanId::Std(HEARTBEAT_ID | value.node as u16),
dlc: 1,
..Default::default()
};
msg.data[0] = value.state as u8;
if value.toggle {
msg.data[0] |= 1 << 7;
}
msg
}
}
#[derive(Clone, Copy, Debug, Default)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct SyncObject {
pub count: Option<u8>,
}
impl SyncObject {
pub fn new(count: Option<u8>) -> Self {
Self { count }
}
}
impl From<SyncObject> for CanMessage {
fn from(value: SyncObject) -> Self {
if let Some(count) = value.count {
CanMessage::new(SYNC_ID, &[count])
} else {
CanMessage::new(SYNC_ID, &[])
}
}
}
impl From<CanMessage> for SyncObject {
fn from(msg: CanMessage) -> Self {
if msg.id() == SYNC_ID {
let count = if msg.data().is_empty() {
None
} else {
Some(msg.data()[0])
};
Self { count }
} else {
panic!("Invalid message ID for SyncObject");
}
}
}
impl TryFrom<CanMessage> for ZencanMessage {
type Error = MessageError;
fn try_from(msg: CanMessage) -> Result<Self, Self::Error> {
let cob_id = msg.id();
if cob_id == NMT_CMD_ID {
Ok(ZencanMessage::NmtCommand(msg.try_into()?))
} else if cob_id.raw() & !0x7f == HEARTBEAT_ID as u32 {
let node = (cob_id.raw() & 0x7f) as u8;
let toggle = (msg.data[0] & (1 << 7)) != 0;
let state: NmtState = (msg.data[0] & 0x7f)
.try_into()
.map_err(|e: InvalidNmtStateError| MessageError::InvalidNmtState { value: e.0 })?;
Ok(ZencanMessage::Heartbeat(Heartbeat {
node,
toggle,
state,
}))
} else if cob_id.raw() & 0xff80 == 0x580 {
let resp: SdoResponse = msg
.try_into()
.map_err(|_| MessageError::MalformedMsg { cob_id })?;
Ok(ZencanMessage::SdoResponse(resp))
} else if cob_id.raw() >= 0x580 && cob_id.raw() <= 0x580 + 256 {
let req: SdoRequest = msg
.data()
.try_into()
.map_err(|_| MessageError::MalformedMsg { cob_id })?;
Ok(ZencanMessage::SdoRequest(req))
} else if cob_id == SYNC_ID {
Ok(ZencanMessage::Sync(msg.into()))
} else if cob_id == LSS_REQ_ID {
let req: LssRequest = msg
.data()
.try_into()
.map_err(|_| MessageError::MalformedMsg { cob_id })?;
Ok(ZencanMessage::LssRequest(req))
} else if cob_id == LSS_RESP_ID {
let resp: LssResponse = msg
.data()
.try_into()
.map_err(|_| MessageError::MalformedMsg { cob_id })?;
Ok(ZencanMessage::LssResponse(resp))
} else {
Err(MessageError::UnrecognizedId { cob_id })
}
}
}
#[derive(Clone, Copy, Debug)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[allow(missing_docs)]
pub enum ZencanMessage {
NmtCommand(NmtCommand),
Sync(SyncObject),
Heartbeat(Heartbeat),
SdoRequest(SdoRequest),
SdoResponse(SdoResponse),
LssRequest(LssRequest),
LssResponse(LssResponse),
}
#[derive(Debug, Clone, Copy, PartialEq, Snafu)]
pub enum MessageError {
MessageTooShort,
MalformedMsg {
cob_id: CanId,
},
#[snafu(display("Unexpected message ID found: {cob_id:?}, expected: {expected:?}"))]
UnexpectedId {
cob_id: CanId,
expected: CanId,
},
InvalidField,
UnrecognizedId {
cob_id: CanId,
},
InvalidNmtState {
value: u8,
},
#[snafu(display("Unexpected LSS command: {value}"))]
UnexpectedLssCommand {
value: u8,
},
}