use can_transport::{CanFrame, CanId, CanIoError, FrameKind};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[repr(u8)]
pub enum NmtState {
BootUp = 0x00,
Stopped = 0x04,
Operational = 0x05,
PreOperational = 0x7F,
}
impl NmtState {
pub fn try_from_byte(b: u8) -> Option<Self> {
Some(match b {
0x00 => Self::BootUp,
0x04 => Self::Stopped,
0x05 => Self::Operational,
0x7F => Self::PreOperational,
_ => return None,
})
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u8)]
pub enum NmtCommand {
StartRemoteNode = 0x01,
StopRemoteNode = 0x02,
EnterPreOperational = 0x80,
ResetNode = 0x81,
ResetCommunication = 0x82,
}
pub fn build_nmt_command(cmd: NmtCommand, target_node_id: u8) -> Result<CanFrame, CanIoError> {
let data = [cmd as u8, target_node_id];
CanFrame::new_data(CanId::Standard(0x000), &data)
}
pub fn parse_heartbeat(frame: &CanFrame) -> Option<(u8, NmtState)> {
if !matches!(frame.kind(), FrameKind::Data) {
return None;
}
let CanId::Standard(cob_id) = frame.id() else {
return None;
};
if cob_id & 0x780 != 0x700 {
return None;
}
let nid = (cob_id & 0x7F) as u8;
if nid == 0 {
return None;
}
let data = frame.data();
if data.len() != 1 {
return None;
}
NmtState::try_from_byte(data[0]).map(|s| (nid, s))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn nmt_cmd_start_node() {
let f = build_nmt_command(NmtCommand::StartRemoteNode, 0x10).unwrap();
assert_eq!(f.id(), CanId::Standard(0x000));
assert_eq!(f.data(), &[0x01, 0x10]);
}
#[test]
fn nmt_cmd_broadcast_preop() {
let f = build_nmt_command(NmtCommand::EnterPreOperational, 0).unwrap();
assert_eq!(f.data(), &[0x80, 0x00]);
}
#[test]
fn parse_hb_operational() {
let f = CanFrame::new_data(CanId::Standard(0x710), &[0x05]).unwrap();
assert_eq!(parse_heartbeat(&f), Some((0x10, NmtState::Operational)));
}
#[test]
fn parse_hb_bootup() {
let f = CanFrame::new_data(CanId::Standard(0x711), &[0x00]).unwrap();
assert_eq!(parse_heartbeat(&f), Some((0x11, NmtState::BootUp)));
}
#[test]
fn parse_hb_preop() {
let f = CanFrame::new_data(CanId::Standard(0x77F), &[0x7F]).unwrap();
assert_eq!(parse_heartbeat(&f), Some((0x7F, NmtState::PreOperational)));
}
#[test]
fn parse_hb_wrong_size() {
let f = CanFrame::new_data(CanId::Standard(0x710), &[0x05, 0x00]).unwrap();
assert_eq!(parse_heartbeat(&f), None);
}
#[test]
fn parse_hb_wrong_cob() {
let f = CanFrame::new_data(CanId::Standard(0x180), &[0x05]).unwrap();
assert_eq!(parse_heartbeat(&f), None);
}
#[test]
fn parse_hb_unknown_state_byte() {
let f = CanFrame::new_data(CanId::Standard(0x710), &[0x42]).unwrap();
assert_eq!(parse_heartbeat(&f), None);
}
#[test]
fn parse_hb_node_zero() {
let f = CanFrame::new_data(CanId::Standard(0x700), &[0x05]).unwrap();
assert_eq!(parse_heartbeat(&f), None);
}
}