use crate::device_address::DeviceAddress;
use crate::transport::transaction::FrameFilter;
use moteus_protocol::{calculate_arbitration_id, CanFdFrame, SERVER_TO_CLIENT};
#[non_exhaustive]
#[derive(Clone)]
pub struct Command {
pub destination: i8,
pub source: i8,
pub can_prefix: u16,
pub reply_required: bool,
pub expected_reply_size: u8,
pub raw: bool,
pub arbitration_id: u32,
pub reply_filter: Option<FrameFilter>,
pub channel: Option<usize>,
pub address: Option<DeviceAddress>,
frame: CanFdFrame,
}
impl Command {
pub fn new(destination: i8, source: i8, can_prefix: u16) -> Self {
Command {
destination,
source,
can_prefix,
reply_required: false,
expected_reply_size: 0,
raw: false,
arbitration_id: 0,
reply_filter: None,
channel: None,
address: None,
frame: CanFdFrame::new(),
}
}
#[must_use]
pub fn reply_required(mut self, required: bool) -> Self {
self.reply_required = required;
self
}
pub fn frame_mut(&mut self) -> &mut CanFdFrame {
&mut self.frame
}
pub fn data(&self) -> &[u8] {
self.frame.payload()
}
pub fn data_size(&self) -> u8 {
self.frame.size
}
pub fn query_reply_filter() -> FrameFilter {
FrameFilter::custom(|f| {
if f.size < 1 {
return false;
}
f.data[0] & 0xf0 == 0x20 || f.data[0] == 0x31
})
}
pub fn diagnostic_reply_filter() -> FrameFilter {
FrameFilter::custom(|f| {
if f.size < 3 {
return false;
}
f.data[0] == SERVER_TO_CLIENT
})
}
pub fn into_frame(self) -> CanFdFrame {
let mut frame = self.frame;
if self.raw {
frame.arbitration_id = self.arbitration_id;
} else {
frame.arbitration_id = calculate_arbitration_id(
self.source,
self.destination,
self.can_prefix,
self.reply_required,
);
}
frame.channel = self.channel;
frame
}
}
impl std::fmt::Debug for Command {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Command")
.field("destination", &self.destination)
.field("source", &self.source)
.field("can_prefix", &self.can_prefix)
.field("reply_required", &self.reply_required)
.field("data_size", &self.frame.size)
.finish()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_command_new() {
let cmd = Command::new(1, 0, 0);
assert_eq!(cmd.destination, 1);
assert_eq!(cmd.source, 0);
assert_eq!(cmd.can_prefix, 0);
assert!(!cmd.reply_required);
assert_eq!(cmd.expected_reply_size, 0);
assert!(!cmd.raw);
assert!(cmd.channel.is_none());
assert_eq!(cmd.data_size(), 0);
}
#[test]
fn test_command_reply_required_builder() {
let cmd = Command::new(1, 0, 0).reply_required(true);
assert!(cmd.reply_required);
}
#[test]
fn test_command_into_frame() {
let cmd = Command::new(1, 0, 0).reply_required(true);
let frame = cmd.into_frame();
assert_eq!(frame.arbitration_id, 0x8001);
}
#[test]
fn test_command_into_frame_no_reply() {
let cmd = Command::new(1, 0, 0).reply_required(false);
let frame = cmd.into_frame();
assert_eq!(frame.arbitration_id, 0x0001);
}
#[test]
fn test_command_into_frame_raw() {
let mut cmd = Command::new(1, 0, 0);
cmd.raw = true;
cmd.arbitration_id = 0xDEAD;
let frame = cmd.into_frame();
assert_eq!(frame.arbitration_id, 0xDEAD);
}
#[test]
fn test_command_into_frame_with_prefix() {
let cmd = Command::new(3, 5, 0x10).reply_required(true);
let frame = cmd.into_frame();
assert_eq!(frame.arbitration_id, 0x00_10_85_03);
}
#[test]
fn test_command_into_frame_channel_propagation() {
let mut cmd = Command::new(1, 0, 0);
cmd.channel = Some(2);
let frame = cmd.into_frame();
assert_eq!(frame.channel, Some(2));
}
#[test]
fn test_command_into_frame_channel_none() {
let cmd = Command::new(1, 0, 0);
let frame = cmd.into_frame();
assert!(frame.channel.is_none());
}
#[test]
fn test_command_frame_mut_serialization() {
let mut cmd = Command::new(1, 0, 0).reply_required(true);
let frame = cmd.frame_mut();
frame.data[0] = 0x01;
frame.data[1] = 0x00;
frame.data[2] = 0x0A;
frame.size = 3;
assert_eq!(cmd.data(), &[0x01, 0x00, 0x0A]);
let wire_frame = cmd.into_frame();
assert_eq!(wire_frame.payload(), &[0x01, 0x00, 0x0A]);
assert_eq!(wire_frame.arbitration_id, 0x8001);
}
}