use crate::can::PiperFrame;
use crate::{ProtocolError, i16_to_bytes_be, i32_to_bytes_be, ids::*};
use bilge::prelude::*;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum ControlModeCommand {
#[default]
Standby = 0x00,
CanControl = 0x01,
Teach = 0x02,
Ethernet = 0x03,
Wifi = 0x04,
OfflineTrajectory = 0x07,
}
impl TryFrom<u8> for ControlModeCommand {
type Error = ProtocolError;
fn try_from(value: u8) -> Result<Self, Self::Error> {
match value {
0x00 => Ok(ControlModeCommand::Standby),
0x01 => Ok(ControlModeCommand::CanControl),
0x02 => Ok(ControlModeCommand::Teach),
0x03 => Ok(ControlModeCommand::Ethernet),
0x04 => Ok(ControlModeCommand::Wifi),
0x07 => Ok(ControlModeCommand::OfflineTrajectory),
_ => Err(ProtocolError::InvalidValue {
field: "ControlModeCommand".to_string(),
value,
}),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum MitMode {
#[default]
PositionVelocity = 0x00,
Mit = 0xAD,
}
impl TryFrom<u8> for MitMode {
type Error = ProtocolError;
fn try_from(value: u8) -> Result<Self, Self::Error> {
match value {
0x00 => Ok(MitMode::PositionVelocity),
0xAD => Ok(MitMode::Mit),
_ => Err(ProtocolError::InvalidValue {
field: "MitMode".to_string(),
value,
}),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum InstallPosition {
#[default]
Invalid = 0x00,
Horizontal = 0x01,
SideLeft = 0x02,
SideRight = 0x03,
}
impl TryFrom<u8> for InstallPosition {
type Error = ProtocolError;
fn try_from(value: u8) -> Result<Self, Self::Error> {
match value {
0x00 => Ok(InstallPosition::Invalid),
0x01 => Ok(InstallPosition::Horizontal),
0x02 => Ok(InstallPosition::SideLeft),
0x03 => Ok(InstallPosition::SideRight),
_ => Err(ProtocolError::InvalidValue {
field: "InstallPosition".to_string(),
value,
}),
}
}
}
use crate::feedback::MoveMode;
#[derive(Debug, Clone, Copy, Default)]
pub struct ControlModeCommandFrame {
pub control_mode: ControlModeCommand, pub move_mode: MoveMode, pub speed_percent: u8, pub mit_mode: MitMode, pub trajectory_stay_time: u8, pub install_position: InstallPosition, }
impl ControlModeCommandFrame {
pub fn mode_switch(control_mode: ControlModeCommand) -> Self {
Self {
control_mode,
move_mode: MoveMode::MoveP, speed_percent: 0,
mit_mode: MitMode::PositionVelocity,
trajectory_stay_time: 0,
install_position: InstallPosition::Invalid,
}
}
pub fn new(
control_mode: ControlModeCommand,
move_mode: MoveMode,
speed_percent: u8,
mit_mode: MitMode,
trajectory_stay_time: u8,
install_position: InstallPosition,
) -> Self {
Self {
control_mode,
move_mode,
speed_percent,
mit_mode,
trajectory_stay_time,
install_position,
}
}
pub fn to_frame(self) -> PiperFrame {
let mut data = [0u8; 8];
data[0] = self.control_mode as u8;
data[1] = self.move_mode as u8;
data[2] = self.speed_percent;
data[3] = self.mit_mode as u8;
data[4] = self.trajectory_stay_time;
data[5] = self.install_position as u8;
PiperFrame::new_standard(ID_CONTROL_MODE as u16, &data)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_control_mode_command_from_u8() {
assert_eq!(
ControlModeCommand::try_from(0x00).unwrap(),
ControlModeCommand::Standby
);
assert_eq!(
ControlModeCommand::try_from(0x01).unwrap(),
ControlModeCommand::CanControl
);
assert_eq!(
ControlModeCommand::try_from(0x02).unwrap(),
ControlModeCommand::Teach
);
assert_eq!(
ControlModeCommand::try_from(0x03).unwrap(),
ControlModeCommand::Ethernet
);
assert_eq!(
ControlModeCommand::try_from(0x04).unwrap(),
ControlModeCommand::Wifi
);
assert_eq!(
ControlModeCommand::try_from(0x07).unwrap(),
ControlModeCommand::OfflineTrajectory
);
}
#[test]
fn test_control_mode_command_invalid_values() {
assert!(ControlModeCommand::try_from(0x05).is_err());
assert!(ControlModeCommand::try_from(0x06).is_err());
assert!(ControlModeCommand::try_from(0xFF).is_err());
}
#[test]
fn test_mit_mode_from_u8() {
assert_eq!(MitMode::try_from(0x00).unwrap(), MitMode::PositionVelocity);
assert_eq!(MitMode::try_from(0xAD).unwrap(), MitMode::Mit);
}
#[test]
fn test_mit_mode_invalid_values() {
assert!(MitMode::try_from(0x01).is_err());
assert!(MitMode::try_from(0xFF).is_err());
}
#[test]
fn test_install_position_from_u8() {
assert_eq!(
InstallPosition::try_from(0x00).unwrap(),
InstallPosition::Invalid
);
assert_eq!(
InstallPosition::try_from(0x01).unwrap(),
InstallPosition::Horizontal
);
assert_eq!(
InstallPosition::try_from(0x02).unwrap(),
InstallPosition::SideLeft
);
assert_eq!(
InstallPosition::try_from(0x03).unwrap(),
InstallPosition::SideRight
);
}
#[test]
fn test_install_position_invalid_values() {
assert!(InstallPosition::try_from(0x04).is_err());
assert!(InstallPosition::try_from(0xFF).is_err());
}
#[test]
fn test_control_mode_command_frame_mode_switch() {
let cmd = ControlModeCommandFrame::mode_switch(ControlModeCommand::CanControl);
let frame = cmd.to_frame();
assert_eq!(frame.id, ID_CONTROL_MODE);
assert_eq!(frame.data[0], 0x01); assert_eq!(frame.data[1], 0x00); assert_eq!(frame.data[2], 0x00); assert_eq!(frame.data[3], 0x00); assert_eq!(frame.data[4], 0x00); assert_eq!(frame.data[5], 0x00); assert_eq!(frame.data[6], 0x00); assert_eq!(frame.data[7], 0x00); }
#[test]
fn test_control_mode_command_frame_new() {
let cmd = ControlModeCommandFrame::new(
ControlModeCommand::CanControl,
MoveMode::MoveJ,
50, MitMode::Mit,
10, InstallPosition::Horizontal,
);
let frame = cmd.to_frame();
assert_eq!(frame.id, ID_CONTROL_MODE);
assert_eq!(frame.data[0], 0x01); assert_eq!(frame.data[1], 0x01); assert_eq!(frame.data[2], 50); assert_eq!(frame.data[3], 0xAD); assert_eq!(frame.data[4], 10); assert_eq!(frame.data[5], 0x01); }
#[test]
fn test_control_mode_command_frame_trajectory_terminate() {
let cmd = ControlModeCommandFrame::new(
ControlModeCommand::OfflineTrajectory,
MoveMode::MoveP,
0,
MitMode::PositionVelocity,
255, InstallPosition::Invalid,
);
let frame = cmd.to_frame();
assert_eq!(frame.data[4], 255); }
}
#[derive(Debug, Clone, Copy, Default)]
pub struct JointControl12 {
pub j1_deg: i32, pub j2_deg: i32, }
impl JointControl12 {
pub fn new(j1: f64, j2: f64) -> Self {
Self {
j1_deg: (j1 * 1000.0).round() as i32,
j2_deg: (j2 * 1000.0).round() as i32,
}
}
pub fn to_frame(self) -> PiperFrame {
let mut data = [0u8; 8];
let j1_bytes = i32_to_bytes_be(self.j1_deg);
let j2_bytes = i32_to_bytes_be(self.j2_deg);
data[0..4].copy_from_slice(&j1_bytes);
data[4..8].copy_from_slice(&j2_bytes);
PiperFrame::new_standard(ID_JOINT_CONTROL_12 as u16, &data)
}
}
#[derive(Debug, Clone, Copy, Default)]
pub struct JointControl34 {
pub j3_deg: i32, pub j4_deg: i32, }
impl JointControl34 {
pub fn new(j3: f64, j4: f64) -> Self {
Self {
j3_deg: (j3 * 1000.0).round() as i32,
j4_deg: (j4 * 1000.0).round() as i32,
}
}
pub fn to_frame(self) -> PiperFrame {
let mut data = [0u8; 8];
let j3_bytes = i32_to_bytes_be(self.j3_deg);
let j4_bytes = i32_to_bytes_be(self.j4_deg);
data[0..4].copy_from_slice(&j3_bytes);
data[4..8].copy_from_slice(&j4_bytes);
PiperFrame::new_standard(ID_JOINT_CONTROL_34 as u16, &data)
}
}
#[derive(Debug, Clone, Copy, Default)]
pub struct JointControl56 {
pub j5_deg: i32, pub j6_deg: i32, }
impl JointControl56 {
pub fn new(j5: f64, j6: f64) -> Self {
Self {
j5_deg: (j5 * 1000.0).round() as i32,
j6_deg: (j6 * 1000.0).round() as i32,
}
}
pub fn to_frame(self) -> PiperFrame {
let mut data = [0u8; 8];
let j5_bytes = i32_to_bytes_be(self.j5_deg);
let j6_bytes = i32_to_bytes_be(self.j6_deg);
data[0..4].copy_from_slice(&j5_bytes);
data[4..8].copy_from_slice(&j6_bytes);
PiperFrame::new_standard(ID_JOINT_CONTROL_56 as u16, &data)
}
}
#[cfg(test)]
mod joint_control_tests {
use super::*;
#[test]
fn test_joint_control12_new() {
let cmd = JointControl12::new(90.0, -45.0);
assert_eq!(cmd.j1_deg, 90000);
assert_eq!(cmd.j2_deg, -45000);
}
#[test]
fn test_joint_control12_to_frame() {
let cmd = JointControl12::new(90.0, -45.0);
let frame = cmd.to_frame();
assert_eq!(frame.id, ID_JOINT_CONTROL_12);
let j1_decoded =
i32::from_be_bytes([frame.data[0], frame.data[1], frame.data[2], frame.data[3]]);
let j2_decoded =
i32::from_be_bytes([frame.data[4], frame.data[5], frame.data[6], frame.data[7]]);
assert_eq!(j1_decoded, 90000);
assert_eq!(j2_decoded, -45000);
}
#[test]
fn test_joint_control12_roundtrip() {
let cmd = JointControl12::new(90.0, -45.0);
let frame = cmd.to_frame();
assert_eq!(frame.id, ID_JOINT_CONTROL_12);
let j1_decoded =
i32::from_be_bytes([frame.data[0], frame.data[1], frame.data[2], frame.data[3]]);
let j2_decoded =
i32::from_be_bytes([frame.data[4], frame.data[5], frame.data[6], frame.data[7]]);
assert_eq!(j1_decoded, 90000);
assert_eq!(j2_decoded, -45000);
assert_eq!(cmd.j1_deg, 90000);
assert_eq!(cmd.j2_deg, -45000);
}
#[test]
fn test_joint_control34_new() {
let cmd = JointControl34::new(30.0, -60.0);
assert_eq!(cmd.j3_deg, 30000);
assert_eq!(cmd.j4_deg, -60000);
}
#[test]
fn test_joint_control34_to_frame() {
let cmd = JointControl34::new(30.0, -60.0);
let frame = cmd.to_frame();
assert_eq!(frame.id, ID_JOINT_CONTROL_34);
let j3_decoded =
i32::from_be_bytes([frame.data[0], frame.data[1], frame.data[2], frame.data[3]]);
let j4_decoded =
i32::from_be_bytes([frame.data[4], frame.data[5], frame.data[6], frame.data[7]]);
assert_eq!(j3_decoded, 30000);
assert_eq!(j4_decoded, -60000);
}
#[test]
fn test_joint_control56_new() {
let cmd = JointControl56::new(180.0, -90.0);
assert_eq!(cmd.j5_deg, 180000);
assert_eq!(cmd.j6_deg, -90000);
}
#[test]
fn test_joint_control56_to_frame() {
let cmd = JointControl56::new(180.0, -90.0);
let frame = cmd.to_frame();
assert_eq!(frame.id, ID_JOINT_CONTROL_56);
let j5_decoded =
i32::from_be_bytes([frame.data[0], frame.data[1], frame.data[2], frame.data[3]]);
let j6_decoded =
i32::from_be_bytes([frame.data[4], frame.data[5], frame.data[6], frame.data[7]]);
assert_eq!(j5_decoded, 180000);
assert_eq!(j6_decoded, -90000);
}
#[test]
fn test_joint_control_precision() {
let cmd = JointControl12::new(0.5, -0.5);
assert_eq!(cmd.j1_deg, 500);
assert_eq!(cmd.j2_deg, -500);
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum EmergencyStopAction {
#[default]
Invalid = 0x00,
EmergencyStop = 0x01,
Resume = 0x02,
}
impl TryFrom<u8> for EmergencyStopAction {
type Error = ProtocolError;
fn try_from(value: u8) -> Result<Self, Self::Error> {
match value {
0x00 => Ok(EmergencyStopAction::Invalid),
0x01 => Ok(EmergencyStopAction::EmergencyStop),
0x02 => Ok(EmergencyStopAction::Resume),
_ => Err(ProtocolError::InvalidValue {
field: "EmergencyStopAction".to_string(),
value,
}),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum TrajectoryCommand {
#[default]
Closed = 0x00,
PausePlanning = 0x01,
StartContinue = 0x02,
ClearCurrent = 0x03,
ClearAll = 0x04,
GetCurrentPlanning = 0x05,
Terminate = 0x06,
Transmit = 0x07,
TransmitEnd = 0x08,
}
impl TryFrom<u8> for TrajectoryCommand {
type Error = ProtocolError;
fn try_from(value: u8) -> Result<Self, Self::Error> {
match value {
0x00 => Ok(TrajectoryCommand::Closed),
0x01 => Ok(TrajectoryCommand::PausePlanning),
0x02 => Ok(TrajectoryCommand::StartContinue),
0x03 => Ok(TrajectoryCommand::ClearCurrent),
0x04 => Ok(TrajectoryCommand::ClearAll),
0x05 => Ok(TrajectoryCommand::GetCurrentPlanning),
0x06 => Ok(TrajectoryCommand::Terminate),
0x07 => Ok(TrajectoryCommand::Transmit),
0x08 => Ok(TrajectoryCommand::TransmitEnd),
_ => Err(ProtocolError::InvalidValue {
field: "TrajectoryCommand".to_string(),
value,
}),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum TeachCommand {
#[default]
Closed = 0x00,
StartRecord = 0x01,
EndRecord = 0x02,
Execute = 0x03,
Pause = 0x04,
Continue = 0x05,
Terminate = 0x06,
MoveToStart = 0x07,
}
impl TryFrom<u8> for TeachCommand {
type Error = ProtocolError;
fn try_from(value: u8) -> Result<Self, Self::Error> {
match value {
0x00 => Ok(TeachCommand::Closed),
0x01 => Ok(TeachCommand::StartRecord),
0x02 => Ok(TeachCommand::EndRecord),
0x03 => Ok(TeachCommand::Execute),
0x04 => Ok(TeachCommand::Pause),
0x05 => Ok(TeachCommand::Continue),
0x06 => Ok(TeachCommand::Terminate),
0x07 => Ok(TeachCommand::MoveToStart),
_ => Err(ProtocolError::InvalidValue {
field: "TeachCommand".to_string(),
value,
}),
}
}
}
#[derive(Debug, Clone, Copy, Default)]
pub struct EmergencyStopCommand {
pub emergency_stop: EmergencyStopAction, pub trajectory_command: TrajectoryCommand, pub teach_command: TeachCommand, pub trajectory_index: u8, pub name_index: u16, pub crc16: u16, }
impl EmergencyStopCommand {
pub fn emergency_stop() -> Self {
Self {
emergency_stop: EmergencyStopAction::EmergencyStop,
trajectory_command: TrajectoryCommand::Closed,
teach_command: TeachCommand::Closed,
trajectory_index: 0,
name_index: 0,
crc16: 0,
}
}
pub fn resume() -> Self {
Self {
emergency_stop: EmergencyStopAction::Resume,
trajectory_command: TrajectoryCommand::Closed,
teach_command: TeachCommand::Closed,
trajectory_index: 0,
name_index: 0,
crc16: 0,
}
}
pub fn trajectory_transmit(trajectory_index: u8, name_index: u16, crc16: u16) -> Self {
Self {
emergency_stop: EmergencyStopAction::Invalid,
trajectory_command: TrajectoryCommand::Transmit,
teach_command: TeachCommand::Closed,
trajectory_index,
name_index,
crc16,
}
}
pub fn to_frame(self) -> PiperFrame {
let mut data = [0u8; 8];
data[0] = self.emergency_stop as u8;
data[1] = self.trajectory_command as u8;
data[2] = self.teach_command as u8;
data[3] = self.trajectory_index;
let name_index_bytes = self.name_index.to_be_bytes();
data[4] = name_index_bytes[0];
data[5] = name_index_bytes[1];
let crc_bytes = self.crc16.to_be_bytes();
data[6] = crc_bytes[0];
data[7] = crc_bytes[1];
PiperFrame::new_standard(ID_EMERGENCY_STOP as u16, &data)
}
}
#[cfg(test)]
mod emergency_stop_tests {
use super::*;
#[test]
fn test_emergency_stop_action_from_u8() {
assert_eq!(
EmergencyStopAction::try_from(0x00).unwrap(),
EmergencyStopAction::Invalid
);
assert_eq!(
EmergencyStopAction::try_from(0x01).unwrap(),
EmergencyStopAction::EmergencyStop
);
assert_eq!(
EmergencyStopAction::try_from(0x02).unwrap(),
EmergencyStopAction::Resume
);
}
#[test]
fn test_trajectory_command_from_u8() {
assert_eq!(
TrajectoryCommand::try_from(0x00).unwrap(),
TrajectoryCommand::Closed
);
assert_eq!(
TrajectoryCommand::try_from(0x07).unwrap(),
TrajectoryCommand::Transmit
);
assert_eq!(
TrajectoryCommand::try_from(0x08).unwrap(),
TrajectoryCommand::TransmitEnd
);
}
#[test]
fn test_teach_command_from_u8() {
assert_eq!(TeachCommand::try_from(0x00).unwrap(), TeachCommand::Closed);
assert_eq!(
TeachCommand::try_from(0x01).unwrap(),
TeachCommand::StartRecord
);
assert_eq!(
TeachCommand::try_from(0x07).unwrap(),
TeachCommand::MoveToStart
);
}
#[test]
fn test_emergency_stop_command_emergency_stop() {
let cmd = EmergencyStopCommand::emergency_stop();
let frame = cmd.to_frame();
assert_eq!(frame.id, ID_EMERGENCY_STOP);
assert_eq!(frame.data[0], 0x01); assert_eq!(frame.data[1], 0x00); assert_eq!(frame.data[2], 0x00); assert_eq!(frame.data[3], 0x00); }
#[test]
fn test_emergency_stop_command_resume() {
let cmd = EmergencyStopCommand::resume();
let frame = cmd.to_frame();
assert_eq!(frame.id, ID_EMERGENCY_STOP);
assert_eq!(frame.data[0], 0x02); }
#[test]
fn test_emergency_stop_command_trajectory_transmit() {
let cmd = EmergencyStopCommand::trajectory_transmit(5, 0x1234, 0x5678);
let frame = cmd.to_frame();
assert_eq!(frame.id, ID_EMERGENCY_STOP);
assert_eq!(frame.data[0], 0x00); assert_eq!(frame.data[1], 0x07); assert_eq!(frame.data[3], 5);
let name_index = u16::from_be_bytes([frame.data[4], frame.data[5]]);
let crc16 = u16::from_be_bytes([frame.data[6], frame.data[7]]);
assert_eq!(name_index, 0x1234);
assert_eq!(crc16, 0x5678);
}
}
#[derive(Debug, Clone, Copy)]
pub struct MotorEnableCommand {
pub joint_index: u8, pub enable: bool, }
impl MotorEnableCommand {
pub fn enable(joint_index: u8) -> Self {
Self {
joint_index,
enable: true,
}
}
pub fn disable(joint_index: u8) -> Self {
Self {
joint_index,
enable: false,
}
}
pub fn enable_all() -> Self {
Self {
joint_index: 7,
enable: true,
}
}
pub fn disable_all() -> Self {
Self {
joint_index: 7,
enable: false,
}
}
pub fn to_frame(self) -> PiperFrame {
let mut data = [0u8; 8];
data[0] = self.joint_index;
data[1] = if self.enable { 0x02 } else { 0x01 };
PiperFrame::new_standard(ID_MOTOR_ENABLE as u16, &data)
}
}
#[cfg(test)]
mod motor_enable_tests {
use super::*;
#[test]
fn test_motor_enable_command_enable() {
let cmd = MotorEnableCommand::enable(1);
let frame = cmd.to_frame();
assert_eq!(frame.id, ID_MOTOR_ENABLE);
assert_eq!(frame.data[0], 1);
assert_eq!(frame.data[1], 0x02); }
#[test]
fn test_motor_enable_command_disable() {
let cmd = MotorEnableCommand::disable(2);
let frame = cmd.to_frame();
assert_eq!(frame.id, ID_MOTOR_ENABLE);
assert_eq!(frame.data[0], 2);
assert_eq!(frame.data[1], 0x01); }
#[test]
fn test_motor_enable_command_enable_all() {
let cmd = MotorEnableCommand::enable_all();
let frame = cmd.to_frame();
assert_eq!(frame.id, ID_MOTOR_ENABLE);
assert_eq!(frame.data[0], 7); assert_eq!(frame.data[1], 0x02); }
#[test]
fn test_motor_enable_command_all_joints() {
for i in 1..=6 {
let cmd = MotorEnableCommand::enable(i);
let frame = cmd.to_frame();
assert_eq!(frame.data[0], i);
assert_eq!(frame.data[1], 0x02);
}
}
}
#[bitsize(8)]
#[derive(FromBits, DebugBits, Clone, Copy, Default)]
pub struct GripperControlFlags {
pub enable: bool, pub clear_error: bool, pub reserved: u6, }
#[derive(Debug, Clone, Copy)]
pub struct GripperControlCommand {
pub travel_mm: i32, pub torque_nm: i16, pub control_flags: GripperControlFlags, pub zero_setting: u8, }
impl GripperControlCommand {
pub fn new(travel_mm: f64, torque_nm: f64, enable: bool) -> Self {
Self {
travel_mm: (travel_mm * 1000.0) as i32,
torque_nm: (torque_nm * 1000.0) as i16,
control_flags: {
let mut flags = GripperControlFlags::from(u8::new(0));
flags.set_enable(enable);
flags
},
zero_setting: 0x00,
}
}
pub fn set_zero_point(mut self) -> Self {
self.zero_setting = 0xAE;
let mut flags = GripperControlFlags::from(u8::new(0));
flags.set_enable(false);
self.control_flags = flags;
self
}
pub fn clear_error(mut self) -> Self {
let mut flags = self.control_flags;
flags.set_clear_error(true);
self.control_flags = flags;
self
}
pub fn to_frame(self) -> PiperFrame {
let mut data = [0u8; 8];
let travel_bytes = i32_to_bytes_be(self.travel_mm);
data[0..4].copy_from_slice(&travel_bytes);
let torque_bytes = i16_to_bytes_be(self.torque_nm);
data[4..6].copy_from_slice(&torque_bytes);
data[6] = u8::from(self.control_flags).value();
data[7] = self.zero_setting;
PiperFrame::new_standard(ID_GRIPPER_CONTROL as u16, &data)
}
}
#[cfg(test)]
mod gripper_control_tests {
use super::*;
#[test]
fn test_gripper_control_flags_parse() {
let byte = 0b0000_0011;
let flags = GripperControlFlags::from(u8::new(byte));
assert!(flags.enable());
assert!(flags.clear_error());
}
#[test]
fn test_gripper_control_flags_encode() {
let mut flags = GripperControlFlags::from(u8::new(0));
flags.set_enable(true);
flags.set_clear_error(true);
let encoded = u8::from(flags).value();
assert_eq!(encoded, 0b0000_0011);
}
#[test]
fn test_gripper_control_command_new() {
let cmd = GripperControlCommand::new(50.0, 2.5, true);
assert_eq!(cmd.travel_mm, 50000);
assert_eq!(cmd.torque_nm, 2500);
assert!(cmd.control_flags.enable());
assert_eq!(cmd.zero_setting, 0x00);
}
#[test]
fn test_gripper_control_command_to_frame() {
let cmd = GripperControlCommand::new(50.0, 2.5, true);
let frame = cmd.to_frame();
assert_eq!(frame.id, ID_GRIPPER_CONTROL);
let travel_decoded =
i32::from_be_bytes([frame.data[0], frame.data[1], frame.data[2], frame.data[3]]);
let torque_decoded = i16::from_be_bytes([frame.data[4], frame.data[5]]);
assert_eq!(travel_decoded, 50000);
assert_eq!(torque_decoded, 2500);
assert_eq!(frame.data[6] & 0x01, 0x01); assert_eq!(frame.data[7], 0x00); }
#[test]
fn test_gripper_control_command_set_zero_point() {
let cmd = GripperControlCommand::new(0.0, 0.0, false).set_zero_point();
let frame = cmd.to_frame();
assert_eq!(frame.data[6], 0x00); assert_eq!(frame.data[7], 0xAE); }
#[test]
fn test_gripper_control_command_clear_error() {
let cmd = GripperControlCommand::new(50.0, 2.5, true).clear_error();
let frame = cmd.to_frame();
assert_eq!(frame.data[6] & 0x03, 0x03); }
#[test]
fn test_gripper_control_command_fully_closed() {
let cmd = GripperControlCommand::new(0.0, 1.0, true);
assert_eq!(cmd.travel_mm, 0);
}
}
#[derive(Debug, Clone, Copy, Default)]
pub struct EndPoseControl1 {
pub x_mm: i32, pub y_mm: i32, }
impl EndPoseControl1 {
pub fn new(x: f64, y: f64) -> Self {
Self {
x_mm: (x * 1000.0) as i32,
y_mm: (y * 1000.0) as i32,
}
}
pub fn to_frame(self) -> PiperFrame {
let mut data = [0u8; 8];
let x_bytes = i32_to_bytes_be(self.x_mm);
let y_bytes = i32_to_bytes_be(self.y_mm);
data[0..4].copy_from_slice(&x_bytes);
data[4..8].copy_from_slice(&y_bytes);
PiperFrame::new_standard(ID_END_POSE_CONTROL_1 as u16, &data)
}
}
#[derive(Debug, Clone, Copy, Default)]
pub struct EndPoseControl2 {
pub z_mm: i32, pub rx_deg: i32, }
impl EndPoseControl2 {
pub fn new(z: f64, rx: f64) -> Self {
Self {
z_mm: (z * 1000.0) as i32,
rx_deg: (rx * 1000.0) as i32,
}
}
pub fn to_frame(self) -> PiperFrame {
let mut data = [0u8; 8];
let z_bytes = i32_to_bytes_be(self.z_mm);
let rx_bytes = i32_to_bytes_be(self.rx_deg);
data[0..4].copy_from_slice(&z_bytes);
data[4..8].copy_from_slice(&rx_bytes);
PiperFrame::new_standard(ID_END_POSE_CONTROL_2 as u16, &data)
}
}
#[derive(Debug, Clone, Copy, Default)]
pub struct EndPoseControl3 {
pub ry_deg: i32, pub rz_deg: i32, }
impl EndPoseControl3 {
pub fn new(ry: f64, rz: f64) -> Self {
Self {
ry_deg: (ry * 1000.0) as i32,
rz_deg: (rz * 1000.0) as i32,
}
}
pub fn to_frame(self) -> PiperFrame {
let mut data = [0u8; 8];
let ry_bytes = i32_to_bytes_be(self.ry_deg);
let rz_bytes = i32_to_bytes_be(self.rz_deg);
data[0..4].copy_from_slice(&ry_bytes);
data[4..8].copy_from_slice(&rz_bytes);
PiperFrame::new_standard(ID_END_POSE_CONTROL_3 as u16, &data)
}
}
#[cfg(test)]
mod end_pose_control_tests {
use super::*;
#[test]
fn test_end_pose_control1_new() {
let cmd = EndPoseControl1::new(100.0, -50.0);
assert_eq!(cmd.x_mm, 100000);
assert_eq!(cmd.y_mm, -50000);
}
#[test]
fn test_end_pose_control1_to_frame() {
let cmd = EndPoseControl1::new(100.0, -50.0);
let frame = cmd.to_frame();
assert_eq!(frame.id, ID_END_POSE_CONTROL_1);
let x_decoded =
i32::from_be_bytes([frame.data[0], frame.data[1], frame.data[2], frame.data[3]]);
let y_decoded =
i32::from_be_bytes([frame.data[4], frame.data[5], frame.data[6], frame.data[7]]);
assert_eq!(x_decoded, 100000);
assert_eq!(y_decoded, -50000);
}
#[test]
fn test_end_pose_control2_new() {
let cmd = EndPoseControl2::new(200.0, 90.0);
assert_eq!(cmd.z_mm, 200000);
assert_eq!(cmd.rx_deg, 90000);
}
#[test]
fn test_end_pose_control2_to_frame() {
let cmd = EndPoseControl2::new(200.0, 90.0);
let frame = cmd.to_frame();
assert_eq!(frame.id, ID_END_POSE_CONTROL_2);
let z_decoded =
i32::from_be_bytes([frame.data[0], frame.data[1], frame.data[2], frame.data[3]]);
let rx_decoded =
i32::from_be_bytes([frame.data[4], frame.data[5], frame.data[6], frame.data[7]]);
assert_eq!(z_decoded, 200000);
assert_eq!(rx_decoded, 90000);
}
#[test]
fn test_end_pose_control3_new() {
let cmd = EndPoseControl3::new(-45.0, 180.0);
assert_eq!(cmd.ry_deg, -45000);
assert_eq!(cmd.rz_deg, 180000);
}
#[test]
fn test_end_pose_control3_to_frame() {
let cmd = EndPoseControl3::new(-45.0, 180.0);
let frame = cmd.to_frame();
assert_eq!(frame.id, ID_END_POSE_CONTROL_3);
let ry_decoded =
i32::from_be_bytes([frame.data[0], frame.data[1], frame.data[2], frame.data[3]]);
let rz_decoded =
i32::from_be_bytes([frame.data[4], frame.data[5], frame.data[6], frame.data[7]]);
assert_eq!(ry_decoded, -45000);
assert_eq!(rz_decoded, 180000);
}
#[test]
fn test_end_pose_control_precision() {
let cmd = EndPoseControl1::new(0.5, -0.5);
assert_eq!(cmd.x_mm, 500);
assert_eq!(cmd.y_mm, -500);
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ArcPointIndex {
Invalid = 0x00,
Start = 0x01,
Middle = 0x02,
End = 0x03,
}
impl TryFrom<u8> for ArcPointIndex {
type Error = ProtocolError;
fn try_from(value: u8) -> Result<Self, Self::Error> {
match value {
0x00 => Ok(ArcPointIndex::Invalid),
0x01 => Ok(ArcPointIndex::Start),
0x02 => Ok(ArcPointIndex::Middle),
0x03 => Ok(ArcPointIndex::End),
_ => Err(ProtocolError::InvalidValue {
field: "ArcPointIndex".to_string(),
value,
}),
}
}
}
#[derive(Debug, Clone, Copy)]
pub struct ArcPointCommand {
pub point_index: ArcPointIndex,
}
impl ArcPointCommand {
pub fn start() -> Self {
Self {
point_index: ArcPointIndex::Start,
}
}
pub fn middle() -> Self {
Self {
point_index: ArcPointIndex::Middle,
}
}
pub fn end() -> Self {
Self {
point_index: ArcPointIndex::End,
}
}
pub fn new(point_index: ArcPointIndex) -> Self {
Self { point_index }
}
pub fn to_frame(self) -> PiperFrame {
let mut data = [0u8; 8];
data[0] = self.point_index as u8;
PiperFrame::new_standard(ID_ARC_POINT as u16, &data)
}
}
#[cfg(test)]
mod arc_point_tests {
use super::*;
#[test]
fn test_arc_point_index_from_u8() {
assert_eq!(
ArcPointIndex::try_from(0x00).unwrap(),
ArcPointIndex::Invalid
);
assert_eq!(ArcPointIndex::try_from(0x01).unwrap(), ArcPointIndex::Start);
assert_eq!(
ArcPointIndex::try_from(0x02).unwrap(),
ArcPointIndex::Middle
);
assert_eq!(ArcPointIndex::try_from(0x03).unwrap(), ArcPointIndex::End);
}
#[test]
fn test_arc_point_index_invalid() {
assert!(ArcPointIndex::try_from(0x04).is_err());
assert!(ArcPointIndex::try_from(0xFF).is_err());
}
#[test]
fn test_arc_point_command_start() {
let cmd = ArcPointCommand::start();
let frame = cmd.to_frame();
assert_eq!(frame.id, ID_ARC_POINT);
assert_eq!(frame.data[0], 0x01);
assert_eq!(frame.data[1], 0x00); }
#[test]
fn test_arc_point_command_middle() {
let cmd = ArcPointCommand::middle();
let frame = cmd.to_frame();
assert_eq!(frame.id, ID_ARC_POINT);
assert_eq!(frame.data[0], 0x02);
}
#[test]
fn test_arc_point_command_end() {
let cmd = ArcPointCommand::end();
let frame = cmd.to_frame();
assert_eq!(frame.id, ID_ARC_POINT);
assert_eq!(frame.data[0], 0x03);
}
}
#[derive(Debug, Clone, Copy)]
pub struct MitControlCommand {
pub joint_index: u8, pub pos_ref: f32, pub vel_ref: f32, pub kp: f32, pub kd: f32, pub t_ref: f32, }
impl MitControlCommand {
const P_MIN: f32 = -12.5;
const P_MAX: f32 = 12.5;
const V_MIN: f32 = -45.0;
const V_MAX: f32 = 45.0;
const KP_MIN: f32 = 0.0;
const KP_MAX: f32 = 500.0;
const KD_MIN: f32 = -5.0;
const KD_MAX: f32 = 5.0;
const T_MIN: f32 = -18.0;
const T_MAX: f32 = 18.0;
fn float_to_uint(x: f32, x_min: f32, x_max: f32, bits: u32) -> u32 {
let span = x_max - x_min;
let offset = x_min;
if span <= 0.0 {
return 0;
}
let result = ((x - offset) * ((1u32 << bits) - 1) as f32 / span) as u32;
result.min((1u32 << bits) - 1)
}
#[allow(dead_code)]
pub fn uint_to_float(x_int: u32, x_min: f32, x_max: f32, bits: u32) -> f32 {
let span = x_max - x_min;
let offset = x_min;
(x_int as f32) * span / ((1u32 << bits) - 1) as f32 + offset
}
pub fn new(joint_index: u8, pos_ref: f32, vel_ref: f32, kp: f32, kd: f32, t_ref: f32) -> Self {
Self {
joint_index,
pos_ref,
vel_ref,
kp,
kd,
t_ref,
}
}
fn encode_to_bytes(&self) -> [u8; 8] {
let mut data = [0u8; 8];
let pos_ref_uint = Self::float_to_uint(self.pos_ref, Self::P_MIN, Self::P_MAX, 16);
data[0] = ((pos_ref_uint >> 8) & 0xFF) as u8;
data[1] = (pos_ref_uint & 0xFF) as u8;
let vel_ref_uint = Self::float_to_uint(self.vel_ref, Self::V_MIN, Self::V_MAX, 12);
data[2] = ((vel_ref_uint >> 4) & 0xFF) as u8;
let kp_uint = Self::float_to_uint(self.kp, Self::KP_MIN, Self::KP_MAX, 12);
let vel_ref_low = (vel_ref_uint & 0x0F) as u8;
let kp_high = ((kp_uint >> 8) & 0x0F) as u8;
data[3] = (vel_ref_low << 4) | kp_high;
data[4] = (kp_uint & 0xFF) as u8;
let kd_uint = Self::float_to_uint(self.kd, Self::KD_MIN, Self::KD_MAX, 12);
data[5] = ((kd_uint >> 4) & 0xFF) as u8;
let t_ref_uint = Self::float_to_uint(self.t_ref, Self::T_MIN, Self::T_MAX, 8);
let kd_low = (kd_uint & 0x0F) as u8;
let t_ref_high = ((t_ref_uint >> 4) & 0x0F) as u8;
data[6] = (kd_low << 4) | t_ref_high;
let t_ref_low = (t_ref_uint & 0x0F) as u8;
data[7] = t_ref_low << 4;
data
}
fn calculate_crc(data: &[u8; 7], _joint_index: u8) -> u8 {
let crc = data[0] ^ data[1] ^ data[2] ^ data[3] ^ data[4] ^ data[5] ^ data[6];
crc & 0x0F
}
pub fn to_frame(self) -> PiperFrame {
let mut data = self.encode_to_bytes();
let crc = Self::calculate_crc(data[0..7].try_into().unwrap(), self.joint_index);
data[7] |= crc & 0x0F;
let can_id = ID_MIT_CONTROL_BASE + (self.joint_index - 1) as u32;
PiperFrame::new_standard(can_id as u16, &data)
}
#[cfg(test)]
pub fn to_frame_with_custom_crc(self, custom_crc: u8) -> PiperFrame {
let mut data = self.encode_to_bytes();
data[7] = (data[7] & 0xF0) | (custom_crc & 0x0F);
let can_id = ID_MIT_CONTROL_BASE + (self.joint_index - 1) as u32;
PiperFrame::new_standard(can_id as u16, &data)
}
}
#[cfg(test)]
mod mit_control_tests {
use super::*;
#[test]
fn test_float_to_uint() {
let result = MitControlCommand::float_to_uint(5.0, 0.0, 10.0, 12);
assert_eq!(result, 2047);
}
#[test]
fn test_float_to_uint_boundary() {
let min = MitControlCommand::float_to_uint(0.0, 0.0, 10.0, 12);
let max = MitControlCommand::float_to_uint(10.0, 0.0, 10.0, 12);
assert_eq!(min, 0);
assert_eq!(max, 4095);
}
#[test]
fn test_uint_to_float() {
let result = MitControlCommand::uint_to_float(2047, 0.0, 10.0, 12);
assert!((result - 5.0).abs() < 0.01);
}
#[test]
fn test_uint_to_float_boundary() {
let min = MitControlCommand::uint_to_float(0, 0.0, 10.0, 12);
let max = MitControlCommand::uint_to_float(4095, 0.0, 10.0, 12);
assert!((min - 0.0).abs() < 0.001);
assert!((max - 10.0).abs() < 0.001);
}
#[test]
fn test_mit_control_command_new() {
let cmd = MitControlCommand::new(1, 1.0, 2.0, 10.0, 0.8, 5.0);
assert_eq!(cmd.joint_index, 1);
assert_eq!(cmd.pos_ref, 1.0);
assert_eq!(cmd.vel_ref, 2.0);
assert_eq!(cmd.kp, 10.0);
assert_eq!(cmd.kd, 0.8);
assert_eq!(cmd.t_ref, 5.0);
}
#[test]
fn test_mit_control_command_calculate_crc() {
let cmd = MitControlCommand::new(1, 0.0, 0.0, 0.0, 0.0, 0.0);
let frame = cmd.to_frame();
let crc = frame.data[7] & 0x0F;
assert!(crc <= 0x0F, "CRC 应该在 0-15 范围内");
let cmd2 = MitControlCommand::new(1, 0.0, 0.0, 10.0, 0.8, 0.0);
let frame2 = cmd2.to_frame();
let crc2 = frame2.data[7] & 0x0F;
assert!(crc2 <= 0x0F, "CRC 应该在 0-15 范围内");
}
#[test]
fn test_mit_control_command_roundtrip() {
let original = 5.0f32;
let x_min = 0.0f32;
let x_max = 10.0f32;
let bits = 12u32;
let uint_val = MitControlCommand::float_to_uint(original, x_min, x_max, bits);
let float_val = MitControlCommand::uint_to_float(uint_val, x_min, x_max, bits);
assert!((float_val - original).abs() < 0.01);
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, num_enum::FromPrimitive)]
#[repr(u8)]
pub enum LightControlEnable {
#[default]
Disabled = 0x00,
Enabled = 0x01,
}
#[derive(Debug, Clone, Copy)]
pub struct LightControlCommand {
pub enable: LightControlEnable, pub joint_index: u8, pub led_index: u8, pub r: u8, pub g: u8, pub b: u8, pub counter: u8, }
impl LightControlCommand {
pub fn new(
enable: LightControlEnable,
joint_index: u8,
led_index: u8,
r: u8,
g: u8,
b: u8,
counter: u8,
) -> Self {
Self {
enable,
joint_index,
led_index,
r,
g,
b,
counter,
}
}
pub fn to_frame(self) -> PiperFrame {
let mut data = [0u8; 8];
data[0] = self.enable as u8;
data[1] = self.joint_index;
data[2] = self.led_index;
data[3] = self.r;
data[4] = self.g;
data[5] = self.b;
data[7] = self.counter;
PiperFrame::new_standard(ID_LIGHT_CONTROL as u16, &data)
}
}
#[cfg(test)]
mod light_control_tests {
use super::*;
#[test]
fn test_light_control_enable_from() {
assert_eq!(LightControlEnable::from(0x00), LightControlEnable::Disabled);
assert_eq!(LightControlEnable::from(0x01), LightControlEnable::Enabled);
assert_eq!(LightControlEnable::from(0xFF), LightControlEnable::Disabled);
}
#[test]
fn test_light_control_command_new() {
let cmd = LightControlCommand::new(LightControlEnable::Enabled, 1, 0xFF, 255, 128, 0, 10);
assert_eq!(cmd.enable, LightControlEnable::Enabled);
assert_eq!(cmd.joint_index, 1);
assert_eq!(cmd.led_index, 0xFF);
assert_eq!(cmd.r, 255);
assert_eq!(cmd.g, 128);
assert_eq!(cmd.b, 0);
assert_eq!(cmd.counter, 10);
}
#[test]
fn test_light_control_command_to_frame() {
let cmd = LightControlCommand::new(LightControlEnable::Enabled, 2, 5, 100, 200, 50, 42);
let frame = cmd.to_frame();
assert_eq!(frame.id, ID_LIGHT_CONTROL);
assert_eq!(frame.data[0], 0x01); assert_eq!(frame.data[1], 2); assert_eq!(frame.data[2], 5); assert_eq!(frame.data[3], 100); assert_eq!(frame.data[4], 200); assert_eq!(frame.data[5], 50); assert_eq!(frame.data[6], 0x00); assert_eq!(frame.data[7], 42); }
#[test]
fn test_light_control_command_all_leds() {
let cmd = LightControlCommand::new(LightControlEnable::Enabled, 3, 0xFF, 255, 255, 255, 0);
let frame = cmd.to_frame();
assert_eq!(frame.data[2], 0xFF);
}
#[test]
fn test_light_control_command_disabled() {
let cmd = LightControlCommand::new(LightControlEnable::Disabled, 1, 0, 0, 0, 0, 0);
let frame = cmd.to_frame();
assert_eq!(frame.data[0], 0x00);
}
}