#![forbid(unsafe_code)]
use std::collections::VecDeque;
pub const KCAN_DATA_BYTES: usize = 8;
pub const KCAN_BASE_CMD_ID: u32 = 0x200;
pub const KCAN_STANDARD_ID_MAX: u32 = 0x7ff;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct KCanFrame {
id: u32,
len: u8,
data: [u8; KCAN_DATA_BYTES],
}
impl KCanFrame {
pub fn new(id: u32, payload: &[u8]) -> Self {
let mut data = [0u8; KCAN_DATA_BYTES];
let len = payload.len().min(KCAN_DATA_BYTES);
data[..len].copy_from_slice(&payload[..len]);
Self {
id,
len: len as u8,
data,
}
}
pub const fn id(&self) -> u32 {
self.id
}
pub const fn dlc(&self) -> u8 {
self.len
}
pub fn payload(&self) -> &[u8] {
&self.data[..self.len as usize]
}
pub const fn is_standard_id(&self) -> bool {
self.id <= KCAN_STANDARD_ID_MAX
}
pub const fn is_extended_id(&self) -> bool {
!self.is_standard_id()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u8)]
pub enum KCanActuatorMode {
Position = 0x01,
Velocity = 0x02,
Torque = 0x03,
Enable = 0x04,
Disable = 0x05,
QueryStatus = 0x06,
}
impl KCanActuatorMode {
pub const fn frame_id(self, node_id: u8) -> u32 {
KCAN_BASE_CMD_ID + node_id as u32 + (self as u32)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum KCanDecodeError {
TooShort,
UnknownMode,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct KCanActuatorCommand {
pub node_id: u8,
pub mode: KCanActuatorMode,
pub value: i16,
}
impl KCanActuatorCommand {
pub const fn position(node_id: u8, position: i16) -> Self {
Self {
node_id,
mode: KCanActuatorMode::Position,
value: position,
}
}
pub const fn velocity(node_id: u8, velocity: i16) -> Self {
Self {
node_id,
mode: KCanActuatorMode::Velocity,
value: velocity,
}
}
pub const fn torque(node_id: u8, torque: i16) -> Self {
Self {
node_id,
mode: KCanActuatorMode::Torque,
value: torque,
}
}
pub const fn enable(node_id: u8) -> Self {
Self {
node_id,
mode: KCanActuatorMode::Enable,
value: 1,
}
}
pub const fn disable(node_id: u8) -> Self {
Self {
node_id,
mode: KCanActuatorMode::Disable,
value: 0,
}
}
pub fn encode(&self) -> KCanFrame {
let value = self.value.to_be_bytes();
let payload = [
self.mode as u8,
self.node_id,
value[0],
value[1],
0,
0,
0,
0,
];
KCanFrame::new(self.mode.frame_id(self.node_id), &payload)
}
pub fn decode(frame: &KCanFrame) -> Result<Self, KCanDecodeError> {
let bytes = frame.payload();
if bytes.len() < 4 {
return Err(KCanDecodeError::TooShort);
}
let mode = match bytes[0] {
0x01 => KCanActuatorMode::Position,
0x02 => KCanActuatorMode::Velocity,
0x03 => KCanActuatorMode::Torque,
0x04 => KCanActuatorMode::Enable,
0x05 => KCanActuatorMode::Disable,
0x06 => KCanActuatorMode::QueryStatus,
_ => return Err(KCanDecodeError::UnknownMode),
};
let value = i16::from_be_bytes([bytes[2], bytes[3]]);
Ok(Self {
node_id: bytes[1],
mode,
value,
})
}
}
#[derive(Debug, Default)]
pub struct KCanMailbox {
outbound: VecDeque<KCanFrame>,
inbound: VecDeque<KCanFrame>,
}
impl KCanMailbox {
pub fn new() -> Self {
Self {
outbound: VecDeque::new(),
inbound: VecDeque::new(),
}
}
pub fn send(&mut self, frame: KCanFrame) {
self.outbound.push_back(frame);
}
pub fn receive(&mut self, frame: KCanFrame) {
self.inbound.push_back(frame);
}
pub fn send_command(&mut self, command: KCanActuatorCommand) {
self.send(command.encode());
}
pub fn pop_outbound(&mut self) -> Option<KCanFrame> {
self.outbound.pop_front()
}
pub fn pop_inbound(&mut self) -> Option<KCanFrame> {
self.inbound.pop_front()
}
pub fn pop_inbound_command(&mut self) -> Option<KCanActuatorCommand> {
self.pop_inbound().and_then(|frame| KCanActuatorCommand::decode(&frame).ok())
}
}
#[cfg(test)]
mod tests {
use super::{
KCanActuatorCommand, KCanActuatorMode, KCanDecodeError, KCanFrame, KCanMailbox,
KCAN_DATA_BYTES, KCAN_STANDARD_ID_MAX,
};
#[test]
fn frame_truncates_long_payload_and_reads() {
let payload = [1u8, 2, 3, 4, 5, 6, 7, 8, 9, 10];
let frame = KCanFrame::new(0x42, &payload);
assert_eq!(frame.id(), 0x42);
assert!(frame.is_standard_id());
assert!(!frame.is_extended_id());
assert_eq!(frame.dlc(), 8);
assert_eq!(frame.payload().len(), KCAN_DATA_BYTES);
assert_eq!(frame.payload()[0], 1);
assert_eq!(frame.payload()[7], 8);
}
#[test]
fn frame_id_range_flags() {
let ext = KCanFrame::new(KCAN_STANDARD_ID_MAX + 1, &[0]);
assert!(!ext.is_standard_id());
assert!(ext.is_extended_id());
}
#[test]
fn actuator_command_roundtrip() {
let command = KCanActuatorCommand::position(0x12, 512);
let frame = command.encode();
assert_eq!(frame.id(), KCanActuatorMode::Position.frame_id(0x12));
assert_eq!(frame.payload()[0], KCanActuatorMode::Position as u8);
let decoded = KCanActuatorCommand::decode(&frame).expect("decode");
assert_eq!(decoded, command);
assert_eq!(decoded.value, 512);
}
#[test]
fn decode_rejects_short_frame() {
let frame = KCanFrame::new(0x321, &[0x01, 0x22]);
let err = KCanActuatorCommand::decode(&frame).unwrap_err();
assert!(matches!(err, KCanDecodeError::TooShort));
}
#[test]
fn mailbox_push_and_pop() {
let mut mailbox = KCanMailbox::new();
mailbox.send_command(KCanActuatorCommand::enable(0x05));
mailbox.receive(KCanActuatorCommand::torque(0x05, -128).encode());
let sent = mailbox.pop_outbound().expect("outbound");
assert_eq!(sent.id(), KCanActuatorMode::Enable.frame_id(0x05));
let received = mailbox.pop_inbound_command().expect("received cmd");
assert_eq!(received, KCanActuatorCommand::torque(0x05, -128));
}
}