use crate::can::PiperFrame;
use crate::{ProtocolError, bytes_to_i16_be, i16_to_bytes_be, ids::*};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum LinkSetting {
Invalid = 0x00,
TeachInputArm = 0xFA,
MotionOutputArm = 0xFC,
}
impl TryFrom<u8> for LinkSetting {
type Error = ProtocolError;
fn try_from(value: u8) -> Result<Self, Self::Error> {
match value {
0x00 => Ok(LinkSetting::Invalid),
0xFA => Ok(LinkSetting::TeachInputArm),
0xFC => Ok(LinkSetting::MotionOutputArm),
_ => Err(ProtocolError::InvalidValue {
field: "LinkSetting".to_string(),
value,
}),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum FeedbackIdOffset {
None = 0x00,
Offset2Bx = 0x10,
Offset2Cx = 0x20,
}
impl TryFrom<u8> for FeedbackIdOffset {
type Error = ProtocolError;
fn try_from(value: u8) -> Result<Self, Self::Error> {
match value {
0x00 => Ok(FeedbackIdOffset::None),
0x10 => Ok(FeedbackIdOffset::Offset2Bx),
0x20 => Ok(FeedbackIdOffset::Offset2Cx),
_ => Err(ProtocolError::InvalidValue {
field: "FeedbackIdOffset".to_string(),
value,
}),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ControlIdOffset {
None = 0x00,
Offset16x = 0x10,
Offset17x = 0x20,
}
impl TryFrom<u8> for ControlIdOffset {
type Error = ProtocolError;
fn try_from(value: u8) -> Result<Self, Self::Error> {
match value {
0x00 => Ok(ControlIdOffset::None),
0x10 => Ok(ControlIdOffset::Offset16x),
0x20 => Ok(ControlIdOffset::Offset17x),
_ => Err(ProtocolError::InvalidValue {
field: "ControlIdOffset".to_string(),
value,
}),
}
}
}
#[derive(Debug, Clone, Copy)]
pub struct MasterSlaveModeCommand {
pub link_setting: LinkSetting, pub feedback_id_offset: FeedbackIdOffset, pub control_id_offset: ControlIdOffset, pub target_id_offset: ControlIdOffset, }
impl MasterSlaveModeCommand {
pub fn set_teach_input_arm(
feedback_id_offset: FeedbackIdOffset,
control_id_offset: ControlIdOffset,
target_id_offset: ControlIdOffset,
) -> Self {
Self {
link_setting: LinkSetting::TeachInputArm,
feedback_id_offset,
control_id_offset,
target_id_offset,
}
}
pub fn set_motion_output_arm() -> Self {
Self {
link_setting: LinkSetting::MotionOutputArm,
feedback_id_offset: FeedbackIdOffset::None,
control_id_offset: ControlIdOffset::None,
target_id_offset: ControlIdOffset::None,
}
}
pub fn to_frame(self) -> PiperFrame {
let mut data = [0u8; 8];
data[0] = self.link_setting as u8;
data[1] = self.feedback_id_offset as u8;
data[2] = self.control_id_offset as u8;
data[3] = self.target_id_offset as u8;
PiperFrame::new_standard(ID_MASTER_SLAVE_MODE as u16, &data)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_link_setting_from_u8() {
assert_eq!(LinkSetting::try_from(0x00).unwrap(), LinkSetting::Invalid);
assert_eq!(
LinkSetting::try_from(0xFA).unwrap(),
LinkSetting::TeachInputArm
);
assert_eq!(
LinkSetting::try_from(0xFC).unwrap(),
LinkSetting::MotionOutputArm
);
}
#[test]
fn test_feedback_id_offset_from_u8() {
assert_eq!(
FeedbackIdOffset::try_from(0x00).unwrap(),
FeedbackIdOffset::None
);
assert_eq!(
FeedbackIdOffset::try_from(0x10).unwrap(),
FeedbackIdOffset::Offset2Bx
);
assert_eq!(
FeedbackIdOffset::try_from(0x20).unwrap(),
FeedbackIdOffset::Offset2Cx
);
}
#[test]
fn test_control_id_offset_from_u8() {
assert_eq!(
ControlIdOffset::try_from(0x00).unwrap(),
ControlIdOffset::None
);
assert_eq!(
ControlIdOffset::try_from(0x10).unwrap(),
ControlIdOffset::Offset16x
);
assert_eq!(
ControlIdOffset::try_from(0x20).unwrap(),
ControlIdOffset::Offset17x
);
}
#[test]
fn test_master_slave_mode_command_set_teach_input_arm() {
let cmd = MasterSlaveModeCommand::set_teach_input_arm(
FeedbackIdOffset::Offset2Bx,
ControlIdOffset::Offset16x,
ControlIdOffset::Offset16x,
);
let frame = cmd.to_frame();
assert_eq!(frame.id, ID_MASTER_SLAVE_MODE);
assert_eq!(frame.data[0], 0xFA); assert_eq!(frame.data[1], 0x10); assert_eq!(frame.data[2], 0x10); assert_eq!(frame.data[3], 0x10); assert_eq!(frame.data[4], 0x00); }
#[test]
fn test_master_slave_mode_command_set_motion_output_arm() {
let cmd = MasterSlaveModeCommand::set_motion_output_arm();
let frame = cmd.to_frame();
assert_eq!(frame.id, ID_MASTER_SLAVE_MODE);
assert_eq!(frame.data[0], 0xFC); assert_eq!(frame.data[1], 0x00); assert_eq!(frame.data[2], 0x00); assert_eq!(frame.data[3], 0x00); }
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum QueryType {
AngleAndMaxVelocity = 0x01,
MaxAcceleration = 0x02,
}
impl TryFrom<u8> for QueryType {
type Error = ProtocolError;
fn try_from(value: u8) -> Result<Self, Self::Error> {
match value {
0x01 => Ok(QueryType::AngleAndMaxVelocity),
0x02 => Ok(QueryType::MaxAcceleration),
_ => Err(ProtocolError::InvalidValue {
field: "QueryType".to_string(),
value,
}),
}
}
}
#[derive(Debug, Clone, Copy)]
pub struct QueryMotorLimitCommand {
pub joint_index: u8, pub query_type: QueryType, }
impl QueryMotorLimitCommand {
pub fn query_angle_and_max_velocity(joint_index: u8) -> Self {
Self {
joint_index,
query_type: QueryType::AngleAndMaxVelocity,
}
}
pub fn query_max_acceleration(joint_index: u8) -> Self {
Self {
joint_index,
query_type: QueryType::MaxAcceleration,
}
}
pub fn to_frame(self) -> PiperFrame {
let mut data = [0u8; 8];
data[0] = self.joint_index;
data[1] = self.query_type as u8;
PiperFrame::new_standard(ID_QUERY_MOTOR_LIMIT as u16, &data)
}
}
#[derive(Debug, Clone, Copy, Default)]
pub struct MotorLimitFeedback {
pub joint_index: u8, pub max_angle_deg: i16, pub min_angle_deg: i16, pub max_velocity_rad_s: u16, }
impl MotorLimitFeedback {
pub fn max_angle(&self) -> f64 {
self.max_angle_deg as f64 / 10.0
}
pub fn min_angle(&self) -> f64 {
self.min_angle_deg as f64 / 10.0
}
pub fn max_velocity(&self) -> f64 {
self.max_velocity_rad_s as f64 / 100.0
}
}
impl TryFrom<PiperFrame> for MotorLimitFeedback {
type Error = ProtocolError;
fn try_from(frame: PiperFrame) -> Result<Self, Self::Error> {
if frame.id != ID_MOTOR_LIMIT_FEEDBACK {
return Err(ProtocolError::InvalidCanId { id: frame.id });
}
if frame.len < 7 {
return Err(ProtocolError::InvalidLength {
expected: 7,
actual: frame.len as usize,
});
}
let joint_index = frame.data[0];
let max_angle_bytes = [frame.data[1], frame.data[2]];
let max_angle_deg = bytes_to_i16_be(max_angle_bytes);
let min_angle_bytes = [frame.data[3], frame.data[4]];
let min_angle_deg = bytes_to_i16_be(min_angle_bytes);
let max_velocity_bytes = [frame.data[5], frame.data[6]];
let max_velocity_rad_s = u16::from_be_bytes(max_velocity_bytes);
Ok(Self {
joint_index,
max_angle_deg,
min_angle_deg,
max_velocity_rad_s,
})
}
}
#[cfg(test)]
mod motor_limit_tests {
use super::*;
#[test]
fn test_query_type_from_u8() {
assert_eq!(
QueryType::try_from(0x01).unwrap(),
QueryType::AngleAndMaxVelocity
);
assert_eq!(
QueryType::try_from(0x02).unwrap(),
QueryType::MaxAcceleration
);
}
#[test]
fn test_query_motor_limit_command_query_angle_and_max_velocity() {
let cmd = QueryMotorLimitCommand::query_angle_and_max_velocity(1);
let frame = cmd.to_frame();
assert_eq!(frame.id, ID_QUERY_MOTOR_LIMIT);
assert_eq!(frame.data[0], 1);
assert_eq!(frame.data[1], 0x01); }
#[test]
fn test_query_motor_limit_command_query_max_acceleration() {
let cmd = QueryMotorLimitCommand::query_max_acceleration(2);
let frame = cmd.to_frame();
assert_eq!(frame.id, ID_QUERY_MOTOR_LIMIT);
assert_eq!(frame.data[0], 2);
assert_eq!(frame.data[1], 0x02); }
#[test]
fn test_motor_limit_feedback_parse() {
let max_angle_val = 1800i16;
let min_angle_val = -1800i16;
let max_velocity_val = 500u16;
let mut data = [0u8; 8];
data[0] = 1; data[1..3].copy_from_slice(&max_angle_val.to_be_bytes());
data[3..5].copy_from_slice(&min_angle_val.to_be_bytes());
data[5..7].copy_from_slice(&max_velocity_val.to_be_bytes());
let frame = PiperFrame::new_standard(ID_MOTOR_LIMIT_FEEDBACK as u16, &data);
let feedback = MotorLimitFeedback::try_from(frame).unwrap();
assert_eq!(feedback.joint_index, 1);
assert_eq!(feedback.max_angle_deg, 1800);
assert_eq!(feedback.min_angle_deg, -1800);
assert_eq!(feedback.max_velocity_rad_s, 500);
assert!((feedback.max_angle() - 180.0).abs() < 0.0001);
assert!((feedback.min_angle() - (-180.0)).abs() < 0.0001);
assert!((feedback.max_velocity() - 5.0).abs() < 0.0001);
}
#[test]
fn test_motor_limit_feedback_invalid_id() {
let frame = PiperFrame::new_standard(0x999, &[0; 8]);
let result = MotorLimitFeedback::try_from(frame);
assert!(result.is_err());
}
#[test]
fn test_motor_limit_feedback_invalid_length() {
let frame = PiperFrame::new_standard(ID_MOTOR_LIMIT_FEEDBACK as u16, &[0; 4]);
let result = MotorLimitFeedback::try_from(frame);
assert!(result.is_err());
}
}
#[derive(Debug, Clone, Copy)]
pub struct SetMotorLimitCommand {
pub joint_index: u8, pub max_angle_deg: Option<i16>, pub min_angle_deg: Option<i16>, pub max_velocity_rad_s: Option<u16>, }
impl SetMotorLimitCommand {
pub fn new(
joint_index: u8,
max_angle: Option<f64>,
min_angle: Option<f64>,
max_velocity: Option<f64>,
) -> Self {
Self {
joint_index,
max_angle_deg: max_angle.map(|a| (a * 10.0) as i16),
min_angle_deg: min_angle.map(|a| (a * 10.0) as i16),
max_velocity_rad_s: max_velocity.map(|v| (v * 100.0) as u16),
}
}
pub fn to_frame(self) -> PiperFrame {
let mut data = [0u8; 8];
data[0] = self.joint_index;
let max_angle_bytes = if let Some(angle) = self.max_angle_deg {
i16_to_bytes_be(angle)
} else {
[0x7F, 0xFF] };
data[1..3].copy_from_slice(&max_angle_bytes);
let min_angle_bytes = if let Some(angle) = self.min_angle_deg {
i16_to_bytes_be(angle)
} else {
[0x7F, 0xFF] };
data[3..5].copy_from_slice(&min_angle_bytes);
let max_velocity_bytes = if let Some(velocity) = self.max_velocity_rad_s {
velocity.to_be_bytes()
} else {
[0x7F, 0xFF] };
data[5..7].copy_from_slice(&max_velocity_bytes);
PiperFrame::new_standard(ID_SET_MOTOR_LIMIT as u16, &data)
}
}
#[cfg(test)]
mod set_motor_limit_tests {
use super::*;
#[test]
fn test_set_motor_limit_command_new() {
let cmd = SetMotorLimitCommand::new(
1,
Some(180.0), Some(-180.0), Some(5.0), );
assert_eq!(cmd.joint_index, 1);
assert_eq!(cmd.max_angle_deg, Some(1800));
assert_eq!(cmd.min_angle_deg, Some(-1800));
assert_eq!(cmd.max_velocity_rad_s, Some(500));
}
#[test]
fn test_set_motor_limit_command_to_frame() {
let cmd = SetMotorLimitCommand::new(1, Some(180.0), Some(-180.0), Some(5.0));
let frame = cmd.to_frame();
assert_eq!(frame.id, ID_SET_MOTOR_LIMIT);
assert_eq!(frame.data[0], 1);
let max_angle = i16::from_be_bytes([frame.data[1], frame.data[2]]);
let min_angle = i16::from_be_bytes([frame.data[3], frame.data[4]]);
let max_velocity = u16::from_be_bytes([frame.data[5], frame.data[6]]);
assert_eq!(max_angle, 1800);
assert_eq!(min_angle, -1800);
assert_eq!(max_velocity, 500);
}
#[test]
fn test_set_motor_limit_command_invalid_values() {
let cmd = SetMotorLimitCommand::new(1, None, None, None);
let frame = cmd.to_frame();
assert_eq!(frame.data[1], 0x7F);
assert_eq!(frame.data[2], 0xFF);
assert_eq!(frame.data[3], 0x7F);
assert_eq!(frame.data[4], 0xFF);
assert_eq!(frame.data[5], 0x7F);
assert_eq!(frame.data[6], 0xFF);
}
#[test]
fn test_set_motor_limit_command_partial_values() {
let cmd = SetMotorLimitCommand::new(
2,
Some(90.0), None, Some(3.0), );
let frame = cmd.to_frame();
let max_angle = i16::from_be_bytes([frame.data[1], frame.data[2]]);
let min_angle = i16::from_be_bytes([frame.data[3], frame.data[4]]);
let max_velocity = u16::from_be_bytes([frame.data[5], frame.data[6]]);
assert_eq!(max_angle, 900);
assert_eq!(min_angle, 0x7FFF); assert_eq!(max_velocity, 300);
}
}
#[derive(Debug, Clone, Copy)]
pub struct JointSettingCommand {
pub joint_index: u8, pub set_zero_point: bool, pub accel_param_enable: bool, pub max_accel_rad_s2: Option<u16>, pub clear_error: bool, }
impl JointSettingCommand {
pub fn set_zero_point(joint_index: u8) -> Self {
Self {
joint_index,
set_zero_point: true,
accel_param_enable: false,
max_accel_rad_s2: None,
clear_error: false,
}
}
pub fn set_acceleration(joint_index: u8, max_accel: f64) -> Self {
Self {
joint_index,
set_zero_point: false,
accel_param_enable: true,
max_accel_rad_s2: Some((max_accel * 100.0) as u16),
clear_error: false,
}
}
pub fn clear_error(joint_index: u8) -> Self {
Self {
joint_index,
set_zero_point: false,
accel_param_enable: false,
max_accel_rad_s2: None,
clear_error: true,
}
}
pub fn to_frame(self) -> PiperFrame {
let mut data = [0u8; 8];
data[0] = self.joint_index;
data[1] = if self.set_zero_point { 0xAE } else { 0x00 };
data[2] = if self.accel_param_enable { 0xAE } else { 0x00 };
let accel_bytes = if let Some(accel) = self.max_accel_rad_s2 {
accel.to_be_bytes()
} else {
[0x7F, 0xFF] };
data[3..5].copy_from_slice(&accel_bytes);
data[5] = if self.clear_error { 0xAE } else { 0x00 };
PiperFrame::new_standard(ID_JOINT_SETTING as u16, &data)
}
}
#[cfg(test)]
mod joint_setting_tests {
use super::*;
#[test]
fn test_joint_setting_command_set_zero_point() {
let cmd = JointSettingCommand::set_zero_point(1);
let frame = cmd.to_frame();
assert_eq!(frame.id, ID_JOINT_SETTING);
assert_eq!(frame.data[0], 1);
assert_eq!(frame.data[1], 0xAE); assert_eq!(frame.data[2], 0x00);
assert_eq!(frame.data[5], 0x00);
}
#[test]
fn test_joint_setting_command_set_acceleration() {
let cmd = JointSettingCommand::set_acceleration(2, 10.0); let frame = cmd.to_frame();
assert_eq!(frame.id, ID_JOINT_SETTING);
assert_eq!(frame.data[0], 2);
assert_eq!(frame.data[1], 0x00);
assert_eq!(frame.data[2], 0xAE);
let max_accel = u16::from_be_bytes([frame.data[3], frame.data[4]]);
assert_eq!(max_accel, 1000); }
#[test]
fn test_joint_setting_command_clear_error() {
let cmd = JointSettingCommand::clear_error(3);
let frame = cmd.to_frame();
assert_eq!(frame.id, ID_JOINT_SETTING);
assert_eq!(frame.data[0], 3);
assert_eq!(frame.data[1], 0x00);
assert_eq!(frame.data[2], 0x00);
assert_eq!(frame.data[5], 0xAE); }
#[test]
fn test_joint_setting_command_all_joints() {
let cmd = JointSettingCommand::set_zero_point(7);
let frame = cmd.to_frame();
assert_eq!(frame.data[0], 7);
}
#[test]
fn test_joint_setting_command_invalid_accel() {
let mut cmd = JointSettingCommand::set_acceleration(1, 10.0);
cmd.max_accel_rad_s2 = None;
let frame = cmd.to_frame();
assert_eq!(frame.data[3], 0x7F);
assert_eq!(frame.data[4], 0xFF);
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TrajectoryPackCompleteStatus {
Success,
ChecksumFailed,
Other(u8),
}
impl From<u8> for TrajectoryPackCompleteStatus {
fn from(value: u8) -> Self {
match value {
0xAE => TrajectoryPackCompleteStatus::Success,
0xEE => TrajectoryPackCompleteStatus::ChecksumFailed,
_ => TrajectoryPackCompleteStatus::Other(value),
}
}
}
impl From<TrajectoryPackCompleteStatus> for u8 {
fn from(status: TrajectoryPackCompleteStatus) -> Self {
match status {
TrajectoryPackCompleteStatus::Success => 0xAE,
TrajectoryPackCompleteStatus::ChecksumFailed => 0xEE,
TrajectoryPackCompleteStatus::Other(val) => val,
}
}
}
#[derive(Debug, Clone, Copy)]
pub struct SettingResponse {
pub response_index: u8, pub zero_point_success: bool, pub trajectory_index: u8, pub pack_complete_status: Option<TrajectoryPackCompleteStatus>, pub name_index: u16, pub crc16: u16, }
impl SettingResponse {
pub fn is_trajectory_response(&self) -> bool {
self.response_index == 0x50
}
pub fn is_setting_response(&self) -> bool {
!self.is_trajectory_response()
}
pub fn trajectory_pack_complete_status(&self) -> Option<TrajectoryPackCompleteStatus> {
self.pack_complete_status
}
pub fn trajectory_point_index(&self) -> Option<u8> {
if self.is_trajectory_response() {
Some(self.trajectory_index)
} else {
None
}
}
pub fn trajectory_name_index(&self) -> Option<u16> {
if self.is_trajectory_response() {
Some(self.name_index)
} else {
None
}
}
pub fn trajectory_crc16(&self) -> Option<u16> {
if self.is_trajectory_response() {
Some(self.crc16)
} else {
None
}
}
}
impl TryFrom<PiperFrame> for SettingResponse {
type Error = ProtocolError;
fn try_from(frame: PiperFrame) -> Result<Self, Self::Error> {
if frame.id != ID_SETTING_RESPONSE {
return Err(ProtocolError::InvalidCanId { id: frame.id });
}
if frame.len < 8 {
return Err(ProtocolError::InvalidLength {
expected: 8,
actual: frame.len as usize,
});
}
let response_index = frame.data[0];
let zero_point_success = frame.data[1] == 0x01;
let trajectory_index = frame.data[2];
let pack_complete_status = if response_index == 0x50 {
Some(TrajectoryPackCompleteStatus::from(frame.data[3]))
} else {
None
};
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]]);
Ok(Self {
response_index,
zero_point_success,
trajectory_index,
pack_complete_status,
name_index,
crc16,
})
}
}
#[cfg(test)]
mod setting_response_tests {
use super::*;
#[test]
fn test_setting_response_setting_command() {
let mut data = [0u8; 8];
data[0] = 0x71; data[1] = 0x00;
let frame = PiperFrame::new_standard(ID_SETTING_RESPONSE as u16, &data);
let response = SettingResponse::try_from(frame).unwrap();
assert_eq!(response.response_index, 0x71);
assert!(!response.zero_point_success);
assert!(response.is_setting_response());
assert!(!response.is_trajectory_response());
}
#[test]
fn test_setting_response_zero_point_success() {
let mut data = [0u8; 8];
data[0] = 0x75; data[1] = 0x01;
let frame = PiperFrame::new_standard(ID_SETTING_RESPONSE as u16, &data);
let response = SettingResponse::try_from(frame).unwrap();
assert_eq!(response.response_index, 0x75);
assert!(response.zero_point_success);
}
#[test]
fn test_setting_response_trajectory_transmit() {
let mut data = [0u8; 8];
data[0] = 0x50; data[1] = 0x00; data[2] = 5; data[3] = 0xAE; data[4] = 0x12; data[5] = 0x34; data[6] = 0x56; data[7] = 0x78;
let frame = PiperFrame::new_standard(ID_SETTING_RESPONSE as u16, &data);
let response = SettingResponse::try_from(frame).unwrap();
assert_eq!(response.response_index, 0x50);
assert_eq!(response.trajectory_index, 5);
assert!(response.is_trajectory_response());
assert!(!response.is_setting_response());
assert_eq!(
response.trajectory_pack_complete_status(),
Some(TrajectoryPackCompleteStatus::Success)
);
assert_eq!(response.trajectory_name_index(), Some(0x1234));
assert_eq!(response.trajectory_crc16(), Some(0x5678));
}
#[test]
fn test_setting_response_trajectory_checksum_failed() {
let mut data = [0u8; 8];
data[0] = 0x50; data[1] = 0x00;
data[2] = 10; data[3] = 0xEE;
let frame = PiperFrame::new_standard(ID_SETTING_RESPONSE as u16, &data);
let response = SettingResponse::try_from(frame).unwrap();
assert_eq!(
response.trajectory_pack_complete_status(),
Some(TrajectoryPackCompleteStatus::ChecksumFailed)
);
}
#[test]
fn test_setting_response_invalid_id() {
let frame = PiperFrame::new_standard(0x999, &[0; 8]);
let result = SettingResponse::try_from(frame);
assert!(result.is_err());
}
#[test]
fn test_setting_response_invalid_length() {
let frame = PiperFrame::new_standard(ID_SETTING_RESPONSE as u16, &[0; 2]);
let result = SettingResponse::try_from(frame);
assert!(result.is_err());
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ParameterQueryType {
EndVelocityAccel = 0x01,
CollisionProtectionLevel = 0x02,
CurrentTrajectoryIndex = 0x03,
GripperTeachParamsIndex = 0x04,
}
impl TryFrom<u8> for ParameterQueryType {
type Error = ProtocolError;
fn try_from(value: u8) -> Result<Self, Self::Error> {
match value {
0x01 => Ok(ParameterQueryType::EndVelocityAccel),
0x02 => Ok(ParameterQueryType::CollisionProtectionLevel),
0x03 => Ok(ParameterQueryType::CurrentTrajectoryIndex),
0x04 => Ok(ParameterQueryType::GripperTeachParamsIndex),
_ => Err(ProtocolError::InvalidValue {
field: "ParameterQueryType".to_string(),
value,
}),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ParameterSetType {
EndVelocityAccelToDefault = 0x01,
AllJointLimitsToDefault = 0x02,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum Feedback48XSetting {
#[default]
Invalid = 0x00,
Enable = 0x01,
Disable = 0x02,
}
impl TryFrom<u8> for Feedback48XSetting {
type Error = ProtocolError;
fn try_from(value: u8) -> Result<Self, Self::Error> {
match value {
0x00 => Ok(Feedback48XSetting::Invalid),
0x01 => Ok(Feedback48XSetting::Enable),
0x02 => Ok(Feedback48XSetting::Disable),
_ => Err(ProtocolError::InvalidValue {
field: "Feedback48XSetting".to_string(),
value,
}),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
#[allow(clippy::enum_variant_names)]
pub enum EndLoadSetting {
#[default]
NoLoad = 0x00,
HalfLoad = 0x01,
FullLoad = 0x02,
}
impl TryFrom<u8> for EndLoadSetting {
type Error = ProtocolError;
fn try_from(value: u8) -> Result<Self, Self::Error> {
match value {
0x00 => Ok(EndLoadSetting::NoLoad),
0x01 => Ok(EndLoadSetting::HalfLoad),
0x02 => Ok(EndLoadSetting::FullLoad),
_ => Err(ProtocolError::InvalidValue {
field: "EndLoadSetting".to_string(),
value,
}),
}
}
}
impl TryFrom<u8> for ParameterSetType {
type Error = ProtocolError;
fn try_from(value: u8) -> Result<Self, Self::Error> {
match value {
0x01 => Ok(ParameterSetType::EndVelocityAccelToDefault),
0x02 => Ok(ParameterSetType::AllJointLimitsToDefault),
_ => Err(ProtocolError::InvalidValue {
field: "ParameterSetType".to_string(),
value,
}),
}
}
}
#[derive(Debug, Clone, Copy)]
pub struct ParameterQuerySetCommand {
pub query_type: Option<ParameterQueryType>, pub set_type: Option<ParameterSetType>, pub feedback_48x_setting: Feedback48XSetting, pub load_param_enable: bool, pub end_load: EndLoadSetting, }
impl ParameterQuerySetCommand {
pub fn query(query_type: ParameterQueryType) -> Self {
Self {
query_type: Some(query_type),
set_type: None,
feedback_48x_setting: Feedback48XSetting::Invalid,
load_param_enable: false,
end_load: EndLoadSetting::NoLoad,
}
}
pub fn set(set_type: ParameterSetType) -> Self {
Self {
query_type: None,
set_type: Some(set_type),
feedback_48x_setting: Feedback48XSetting::Invalid,
load_param_enable: false,
end_load: EndLoadSetting::NoLoad,
}
}
pub fn with_feedback_48x(mut self, setting: Feedback48XSetting) -> Self {
self.feedback_48x_setting = setting;
self
}
pub fn with_end_load(mut self, load: EndLoadSetting) -> Self {
self.load_param_enable = true; self.end_load = load;
self
}
pub fn validate(&self) -> Result<(), ProtocolError> {
if self.query_type.is_some() && self.set_type.is_some() {
return Err(ProtocolError::ParseError(
"查询和设置不能同时进行".to_string(),
));
}
if self.query_type.is_none() && self.set_type.is_none() {
return Err(ProtocolError::ParseError(
"必须指定查询或设置之一".to_string(),
));
}
Ok(())
}
pub fn to_frame(self) -> Result<PiperFrame, ProtocolError> {
self.validate()?;
let mut data = [0u8; 8];
data[0] = self.query_type.map(|q| q as u8).unwrap_or(0x00);
data[1] = self.set_type.map(|s| s as u8).unwrap_or(0x00);
data[2] = self.feedback_48x_setting as u8;
data[3] = if self.load_param_enable { 0xAE } else { 0x00 };
data[4] = self.end_load as u8;
Ok(PiperFrame::new_standard(
ID_PARAMETER_QUERY_SET as u16,
&data,
))
}
}
#[cfg(test)]
mod parameter_query_set_tests {
use super::*;
#[test]
fn test_parameter_query_type_from_u8() {
assert_eq!(
ParameterQueryType::try_from(0x01).unwrap(),
ParameterQueryType::EndVelocityAccel
);
assert_eq!(
ParameterQueryType::try_from(0x02).unwrap(),
ParameterQueryType::CollisionProtectionLevel
);
assert_eq!(
ParameterQueryType::try_from(0x03).unwrap(),
ParameterQueryType::CurrentTrajectoryIndex
);
assert_eq!(
ParameterQueryType::try_from(0x04).unwrap(),
ParameterQueryType::GripperTeachParamsIndex
);
}
#[test]
fn test_parameter_set_type_from_u8() {
assert_eq!(
ParameterSetType::try_from(0x01).unwrap(),
ParameterSetType::EndVelocityAccelToDefault
);
assert_eq!(
ParameterSetType::try_from(0x02).unwrap(),
ParameterSetType::AllJointLimitsToDefault
);
}
#[test]
fn test_parameter_query_set_command_query() {
let cmd = ParameterQuerySetCommand::query(ParameterQueryType::EndVelocityAccel);
let frame = cmd.to_frame().unwrap();
assert_eq!(frame.id, ID_PARAMETER_QUERY_SET);
assert_eq!(frame.data[0], 0x01); assert_eq!(frame.data[1], 0x00); }
#[test]
fn test_parameter_query_set_command_set() {
let cmd = ParameterQuerySetCommand::set(ParameterSetType::AllJointLimitsToDefault);
let frame = cmd.to_frame().unwrap();
assert_eq!(frame.id, ID_PARAMETER_QUERY_SET);
assert_eq!(frame.data[0], 0x00); assert_eq!(frame.data[1], 0x02); }
#[test]
fn test_parameter_query_set_command_validate_mutually_exclusive() {
let mut cmd = ParameterQuerySetCommand::query(ParameterQueryType::EndVelocityAccel);
cmd.set_type = Some(ParameterSetType::AllJointLimitsToDefault);
assert!(cmd.validate().is_err());
assert!(cmd.to_frame().is_err());
}
#[test]
fn test_parameter_query_set_command_validate_neither() {
let cmd = ParameterQuerySetCommand {
query_type: None,
set_type: None,
feedback_48x_setting: Feedback48XSetting::Invalid,
load_param_enable: false,
end_load: EndLoadSetting::NoLoad,
};
assert!(cmd.validate().is_err());
assert!(cmd.to_frame().is_err());
}
#[test]
fn test_feedback_48x_setting_from_u8() {
assert_eq!(
Feedback48XSetting::try_from(0x00).unwrap(),
Feedback48XSetting::Invalid
);
assert_eq!(
Feedback48XSetting::try_from(0x01).unwrap(),
Feedback48XSetting::Enable
);
assert_eq!(
Feedback48XSetting::try_from(0x02).unwrap(),
Feedback48XSetting::Disable
);
}
#[test]
fn test_end_load_setting_from_u8() {
assert_eq!(
EndLoadSetting::try_from(0x00).unwrap(),
EndLoadSetting::NoLoad
);
assert_eq!(
EndLoadSetting::try_from(0x01).unwrap(),
EndLoadSetting::HalfLoad
);
assert_eq!(
EndLoadSetting::try_from(0x02).unwrap(),
EndLoadSetting::FullLoad
);
}
#[test]
fn test_parameter_query_set_command_with_feedback_48x() {
let cmd = ParameterQuerySetCommand::query(ParameterQueryType::EndVelocityAccel)
.with_feedback_48x(Feedback48XSetting::Enable);
let frame = cmd.to_frame().unwrap();
assert_eq!(frame.id, ID_PARAMETER_QUERY_SET);
assert_eq!(frame.data[0], 0x01); assert_eq!(frame.data[1], 0x00); assert_eq!(frame.data[2], 0x01); }
#[test]
fn test_parameter_query_set_command_with_end_load() {
let cmd = ParameterQuerySetCommand::set(ParameterSetType::EndVelocityAccelToDefault)
.with_end_load(EndLoadSetting::FullLoad);
let frame = cmd.to_frame().unwrap();
assert_eq!(frame.id, ID_PARAMETER_QUERY_SET);
assert_eq!(frame.data[0], 0x00); assert_eq!(frame.data[1], 0x01); assert_eq!(frame.data[3], 0xAE); assert_eq!(frame.data[4], 0x02); }
}
#[derive(Debug, Clone, Copy, Default)]
pub struct EndVelocityAccelFeedback {
pub max_linear_velocity: u16, pub max_angular_velocity: u16, pub max_linear_accel: u16, pub max_angular_accel: u16, }
impl EndVelocityAccelFeedback {
pub fn max_linear_velocity(&self) -> f64 {
self.max_linear_velocity as f64 / 1000.0
}
pub fn max_angular_velocity(&self) -> f64 {
self.max_angular_velocity as f64 / 1000.0
}
pub fn max_linear_accel(&self) -> f64 {
self.max_linear_accel as f64 / 1000.0
}
pub fn max_angular_accel(&self) -> f64 {
self.max_angular_accel as f64 / 1000.0
}
}
impl TryFrom<PiperFrame> for EndVelocityAccelFeedback {
type Error = ProtocolError;
fn try_from(frame: PiperFrame) -> Result<Self, Self::Error> {
if frame.id != ID_END_VELOCITY_ACCEL_FEEDBACK {
return Err(ProtocolError::InvalidCanId { id: frame.id });
}
if frame.len < 8 {
return Err(ProtocolError::InvalidLength {
expected: 8,
actual: frame.len as usize,
});
}
let max_linear_velocity = u16::from_be_bytes([frame.data[0], frame.data[1]]);
let max_angular_velocity = u16::from_be_bytes([frame.data[2], frame.data[3]]);
let max_linear_accel = u16::from_be_bytes([frame.data[4], frame.data[5]]);
let max_angular_accel = u16::from_be_bytes([frame.data[6], frame.data[7]]);
Ok(Self {
max_linear_velocity,
max_angular_velocity,
max_linear_accel,
max_angular_accel,
})
}
}
#[cfg(test)]
mod end_velocity_accel_feedback_tests {
use super::*;
#[test]
fn test_end_velocity_accel_feedback_parse() {
let mut data = [0u8; 8];
data[0..2].copy_from_slice(&1000u16.to_be_bytes());
data[2..4].copy_from_slice(&2000u16.to_be_bytes());
data[4..6].copy_from_slice(&500u16.to_be_bytes());
data[6..8].copy_from_slice(&1500u16.to_be_bytes());
let frame = PiperFrame::new_standard(ID_END_VELOCITY_ACCEL_FEEDBACK as u16, &data);
let feedback = EndVelocityAccelFeedback::try_from(frame).unwrap();
assert_eq!(feedback.max_linear_velocity, 1000);
assert_eq!(feedback.max_angular_velocity, 2000);
assert_eq!(feedback.max_linear_accel, 500);
assert_eq!(feedback.max_angular_accel, 1500);
assert!((feedback.max_linear_velocity() - 1.0).abs() < 0.0001);
assert!((feedback.max_angular_velocity() - 2.0).abs() < 0.0001);
assert!((feedback.max_linear_accel() - 0.5).abs() < 0.0001);
assert!((feedback.max_angular_accel() - 1.5).abs() < 0.0001);
}
#[test]
fn test_end_velocity_accel_feedback_invalid_id() {
let frame = PiperFrame::new_standard(0x999, &[0; 8]);
let result = EndVelocityAccelFeedback::try_from(frame);
assert!(result.is_err());
}
#[test]
fn test_end_velocity_accel_feedback_invalid_length() {
let frame = PiperFrame::new_standard(ID_END_VELOCITY_ACCEL_FEEDBACK as u16, &[0; 4]);
let result = EndVelocityAccelFeedback::try_from(frame);
assert!(result.is_err());
}
}
#[derive(Debug, Clone, Copy)]
pub struct SetEndVelocityAccelCommand {
pub max_linear_velocity: Option<u16>, pub max_angular_velocity: Option<u16>, pub max_linear_accel: Option<u16>, pub max_angular_accel: Option<u16>, }
impl SetEndVelocityAccelCommand {
pub fn new(
max_linear_velocity: Option<f64>,
max_angular_velocity: Option<f64>,
max_linear_accel: Option<f64>,
max_angular_accel: Option<f64>,
) -> Self {
Self {
max_linear_velocity: max_linear_velocity.map(|v| (v * 1000.0) as u16),
max_angular_velocity: max_angular_velocity.map(|v| (v * 1000.0) as u16),
max_linear_accel: max_linear_accel.map(|a| (a * 1000.0) as u16),
max_angular_accel: max_angular_accel.map(|a| (a * 1000.0) as u16),
}
}
pub fn to_frame(self) -> PiperFrame {
let mut data = [0u8; 8];
let linear_vel_bytes = if let Some(vel) = self.max_linear_velocity {
vel.to_be_bytes()
} else {
[0x7F, 0xFF] };
data[0..2].copy_from_slice(&linear_vel_bytes);
let angular_vel_bytes = if let Some(vel) = self.max_angular_velocity {
vel.to_be_bytes()
} else {
[0x7F, 0xFF] };
data[2..4].copy_from_slice(&angular_vel_bytes);
let linear_accel_bytes = if let Some(accel) = self.max_linear_accel {
accel.to_be_bytes()
} else {
[0x7F, 0xFF] };
data[4..6].copy_from_slice(&linear_accel_bytes);
let angular_accel_bytes = if let Some(accel) = self.max_angular_accel {
accel.to_be_bytes()
} else {
[0x7F, 0xFF] };
data[6..8].copy_from_slice(&angular_accel_bytes);
PiperFrame::new_standard(ID_SET_END_VELOCITY_ACCEL as u16, &data)
}
}
#[cfg(test)]
mod set_end_velocity_accel_tests {
use super::*;
#[test]
fn test_set_end_velocity_accel_command_new() {
let cmd = SetEndVelocityAccelCommand::new(
Some(1.0), Some(2.0), Some(0.5), Some(1.5), );
assert_eq!(cmd.max_linear_velocity, Some(1000));
assert_eq!(cmd.max_angular_velocity, Some(2000));
assert_eq!(cmd.max_linear_accel, Some(500));
assert_eq!(cmd.max_angular_accel, Some(1500));
}
#[test]
fn test_set_end_velocity_accel_command_to_frame() {
let cmd = SetEndVelocityAccelCommand::new(Some(1.0), Some(2.0), Some(0.5), Some(1.5));
let frame = cmd.to_frame();
assert_eq!(frame.id, ID_SET_END_VELOCITY_ACCEL);
let linear_vel = u16::from_be_bytes([frame.data[0], frame.data[1]]);
let angular_vel = u16::from_be_bytes([frame.data[2], frame.data[3]]);
let linear_accel = u16::from_be_bytes([frame.data[4], frame.data[5]]);
let angular_accel = u16::from_be_bytes([frame.data[6], frame.data[7]]);
assert_eq!(linear_vel, 1000);
assert_eq!(angular_vel, 2000);
assert_eq!(linear_accel, 500);
assert_eq!(angular_accel, 1500);
}
#[test]
fn test_set_end_velocity_accel_command_invalid_values() {
let cmd = SetEndVelocityAccelCommand::new(None, None, None, None);
let frame = cmd.to_frame();
assert_eq!(frame.data[0], 0x7F);
assert_eq!(frame.data[1], 0xFF);
assert_eq!(frame.data[2], 0x7F);
assert_eq!(frame.data[3], 0xFF);
assert_eq!(frame.data[4], 0x7F);
assert_eq!(frame.data[5], 0xFF);
assert_eq!(frame.data[6], 0x7F);
assert_eq!(frame.data[7], 0xFF);
}
#[test]
fn test_set_end_velocity_accel_command_partial_values() {
let cmd = SetEndVelocityAccelCommand::new(
Some(1.0), None, Some(0.5), None, );
let frame = cmd.to_frame();
let linear_vel = u16::from_be_bytes([frame.data[0], frame.data[1]]);
let angular_vel = u16::from_be_bytes([frame.data[2], frame.data[3]]);
let linear_accel = u16::from_be_bytes([frame.data[4], frame.data[5]]);
let angular_accel = u16::from_be_bytes([frame.data[6], frame.data[7]]);
assert_eq!(linear_vel, 1000);
assert_eq!(angular_vel, 0x7FFF); assert_eq!(linear_accel, 500);
assert_eq!(angular_accel, 0x7FFF); }
}
#[derive(Debug, Clone, Copy)]
pub struct CollisionProtectionLevelCommand {
pub levels: [u8; 6], }
impl CollisionProtectionLevelCommand {
pub fn new(levels: [u8; 6]) -> Self {
for &level in &levels {
if level > 8 {
panic!("碰撞防护等级必须在0~8之间");
}
}
Self { levels }
}
pub fn all_joints(level: u8) -> Self {
if level > 8 {
panic!("碰撞防护等级必须在0~8之间");
}
Self { levels: [level; 6] }
}
pub fn to_frame(self) -> PiperFrame {
let mut data = [0u8; 8];
data[0..6].copy_from_slice(&self.levels);
PiperFrame::new_standard(ID_COLLISION_PROTECTION_LEVEL as u16, &data)
}
}
#[derive(Debug, Clone, Copy, Default)]
pub struct CollisionProtectionLevelFeedback {
pub levels: [u8; 6], }
impl TryFrom<PiperFrame> for CollisionProtectionLevelFeedback {
type Error = ProtocolError;
fn try_from(frame: PiperFrame) -> Result<Self, Self::Error> {
if frame.id != ID_COLLISION_PROTECTION_LEVEL_FEEDBACK {
return Err(ProtocolError::InvalidCanId { id: frame.id });
}
if frame.len < 6 {
return Err(ProtocolError::InvalidLength {
expected: 6,
actual: frame.len as usize,
});
}
let mut levels = [0u8; 6];
levels.copy_from_slice(&frame.data[0..6]);
Ok(Self { levels })
}
}
#[cfg(test)]
mod collision_protection_tests {
use super::*;
#[test]
fn test_collision_protection_level_command_new() {
let cmd = CollisionProtectionLevelCommand::new([1, 2, 3, 4, 5, 6]);
assert_eq!(cmd.levels, [1, 2, 3, 4, 5, 6]);
}
#[test]
fn test_collision_protection_level_command_all_joints() {
let cmd = CollisionProtectionLevelCommand::all_joints(5);
assert_eq!(cmd.levels, [5; 6]);
}
#[test]
fn test_collision_protection_level_command_to_frame() {
let cmd = CollisionProtectionLevelCommand::new([1, 2, 3, 4, 5, 6]);
let frame = cmd.to_frame();
assert_eq!(frame.id, ID_COLLISION_PROTECTION_LEVEL);
assert_eq!(frame.data[0..6], [1, 2, 3, 4, 5, 6]);
assert_eq!(frame.data[6], 0x00); assert_eq!(frame.data[7], 0x00); }
#[test]
fn test_collision_protection_level_feedback_parse() {
let mut data = [0u8; 8];
data[0..6].copy_from_slice(&[1, 2, 3, 4, 5, 6]);
let frame = PiperFrame::new_standard(ID_COLLISION_PROTECTION_LEVEL_FEEDBACK as u16, &data);
let feedback = CollisionProtectionLevelFeedback::try_from(frame).unwrap();
assert_eq!(feedback.levels, [1, 2, 3, 4, 5, 6]);
}
#[test]
fn test_collision_protection_level_feedback_invalid_id() {
let frame = PiperFrame::new_standard(0x999, &[0; 8]);
let result = CollisionProtectionLevelFeedback::try_from(frame);
assert!(result.is_err());
}
#[test]
fn test_collision_protection_level_feedback_invalid_length() {
let frame =
PiperFrame::new_standard(ID_COLLISION_PROTECTION_LEVEL_FEEDBACK as u16, &[0; 4]);
let result = CollisionProtectionLevelFeedback::try_from(frame);
assert!(result.is_err());
}
#[test]
fn test_collision_protection_level_zero() {
let cmd = CollisionProtectionLevelCommand::all_joints(0);
let frame = cmd.to_frame();
assert_eq!(frame.data[0..6], [0; 6]);
}
#[test]
fn test_collision_protection_level_max() {
let cmd = CollisionProtectionLevelCommand::all_joints(8);
let frame = cmd.to_frame();
assert_eq!(frame.data[0..6], [8; 6]);
}
}
#[derive(Debug, Clone, Copy, Default)]
pub struct MotorMaxAccelFeedback {
pub joint_index: u8, pub max_accel_rad_s2: u16, }
impl MotorMaxAccelFeedback {
pub fn max_accel(&self) -> f64 {
self.max_accel_rad_s2 as f64 / 1000.0
}
}
impl TryFrom<PiperFrame> for MotorMaxAccelFeedback {
type Error = ProtocolError;
fn try_from(frame: PiperFrame) -> Result<Self, Self::Error> {
if frame.id != ID_MOTOR_MAX_ACCEL_FEEDBACK {
return Err(ProtocolError::InvalidCanId { id: frame.id });
}
if frame.len < 3 {
return Err(ProtocolError::InvalidLength {
expected: 3,
actual: frame.len as usize,
});
}
let joint_index = frame.data[0];
let max_accel_bytes = [frame.data[1], frame.data[2]];
let max_accel_rad_s2 = u16::from_be_bytes(max_accel_bytes);
Ok(Self {
joint_index,
max_accel_rad_s2,
})
}
}
#[cfg(test)]
mod motor_max_accel_feedback_tests {
use super::*;
#[test]
fn test_motor_max_accel_feedback_parse() {
let mut data = [0u8; 8];
data[0] = 1;
data[1..3].copy_from_slice(&10000u16.to_be_bytes());
let frame = PiperFrame::new_standard(ID_MOTOR_MAX_ACCEL_FEEDBACK as u16, &data);
let feedback = MotorMaxAccelFeedback::try_from(frame).unwrap();
assert_eq!(feedback.joint_index, 1);
assert_eq!(feedback.max_accel_rad_s2, 10000);
assert!((feedback.max_accel() - 10.0).abs() < 0.0001);
}
#[test]
fn test_motor_max_accel_feedback_all_joints() {
for i in 1..=6 {
let mut data = [0u8; 8];
data[0] = i;
data[1..3].copy_from_slice(&5000u16.to_be_bytes());
let frame = PiperFrame::new_standard(ID_MOTOR_MAX_ACCEL_FEEDBACK as u16, &data);
let feedback = MotorMaxAccelFeedback::try_from(frame).unwrap();
assert_eq!(feedback.joint_index, i);
}
}
#[test]
fn test_motor_max_accel_feedback_invalid_id() {
let frame = PiperFrame::new_standard(0x999, &[0; 8]);
let result = MotorMaxAccelFeedback::try_from(frame);
assert!(result.is_err());
}
#[test]
fn test_motor_max_accel_feedback_invalid_length() {
let frame = PiperFrame::new_standard(ID_MOTOR_MAX_ACCEL_FEEDBACK as u16, &[0; 2]);
let result = MotorMaxAccelFeedback::try_from(frame);
assert!(result.is_err());
}
}
#[derive(Debug, Clone, Copy)]
pub struct GripperTeachParamsCommand {
pub teach_travel_coeff: u8, pub max_travel_limit: u8, pub friction_coeff: u8, }
impl GripperTeachParamsCommand {
pub fn new(teach_travel_coeff: u8, max_travel_limit: u8, friction_coeff: u8) -> Self {
Self {
teach_travel_coeff,
max_travel_limit,
friction_coeff,
}
}
pub fn to_frame(self) -> PiperFrame {
let mut data = [0u8; 8];
data[0] = self.teach_travel_coeff;
data[1] = self.max_travel_limit;
data[2] = self.friction_coeff;
PiperFrame::new_standard(ID_GRIPPER_TEACH_PARAMS as u16, &data)
}
}
#[derive(Debug, Clone, Copy, Default)]
pub struct GripperTeachParamsFeedback {
pub teach_travel_coeff: u8, pub max_travel_limit: u8, pub friction_coeff: u8, }
impl TryFrom<PiperFrame> for GripperTeachParamsFeedback {
type Error = ProtocolError;
fn try_from(frame: PiperFrame) -> Result<Self, Self::Error> {
if frame.id != ID_GRIPPER_TEACH_PARAMS_FEEDBACK {
return Err(ProtocolError::InvalidCanId { id: frame.id });
}
if frame.len < 3 {
return Err(ProtocolError::InvalidLength {
expected: 3,
actual: frame.len as usize,
});
}
Ok(Self {
teach_travel_coeff: frame.data[0],
max_travel_limit: frame.data[1],
friction_coeff: frame.data[2],
})
}
}
#[cfg(test)]
mod gripper_teach_params_tests {
use super::*;
#[test]
fn test_gripper_teach_params_command_new() {
let cmd = GripperTeachParamsCommand::new(150, 70, 5);
assert_eq!(cmd.teach_travel_coeff, 150);
assert_eq!(cmd.max_travel_limit, 70);
assert_eq!(cmd.friction_coeff, 5);
}
#[test]
fn test_gripper_teach_params_command_to_frame() {
let cmd = GripperTeachParamsCommand::new(150, 70, 5);
let frame = cmd.to_frame();
assert_eq!(frame.id, ID_GRIPPER_TEACH_PARAMS);
assert_eq!(frame.data[0], 150);
assert_eq!(frame.data[1], 70);
assert_eq!(frame.data[2], 5);
assert_eq!(frame.data[3], 0x00); }
#[test]
fn test_gripper_teach_params_feedback_parse() {
let mut data = [0u8; 8];
data[0] = 150; data[1] = 70; data[2] = 5;
let frame = PiperFrame::new_standard(ID_GRIPPER_TEACH_PARAMS_FEEDBACK as u16, &data);
let feedback = GripperTeachParamsFeedback::try_from(frame).unwrap();
assert_eq!(feedback.teach_travel_coeff, 150);
assert_eq!(feedback.max_travel_limit, 70);
assert_eq!(feedback.friction_coeff, 5);
}
#[test]
fn test_gripper_teach_params_feedback_invalid_id() {
let frame = PiperFrame::new_standard(0x999, &[0; 8]);
let result = GripperTeachParamsFeedback::try_from(frame);
assert!(result.is_err());
}
#[test]
fn test_gripper_teach_params_feedback_invalid_length() {
let frame = PiperFrame::new_standard(ID_GRIPPER_TEACH_PARAMS_FEEDBACK as u16, &[0; 2]);
let result = GripperTeachParamsFeedback::try_from(frame);
assert!(result.is_err());
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, num_enum::FromPrimitive)]
#[repr(u8)]
pub enum FirmwareUpgradeMode {
#[default]
Exit = 0x00,
CanUpgradeSilent = 0x01,
CombinedUpgrade = 0x02,
}
#[derive(Debug, Clone, Copy)]
pub struct FirmwareUpgradeCommand {
pub mode: FirmwareUpgradeMode,
}
impl FirmwareUpgradeCommand {
pub fn new(mode: FirmwareUpgradeMode) -> Self {
Self { mode }
}
pub fn exit() -> Self {
Self {
mode: FirmwareUpgradeMode::Exit,
}
}
pub fn can_upgrade_silent() -> Self {
Self {
mode: FirmwareUpgradeMode::CanUpgradeSilent,
}
}
pub fn combined_upgrade() -> Self {
Self {
mode: FirmwareUpgradeMode::CombinedUpgrade,
}
}
pub fn to_frame(self) -> PiperFrame {
let data = [self.mode as u8];
PiperFrame::new_standard(ID_FIRMWARE_UPGRADE as u16, &data)
}
}
#[cfg(test)]
mod firmware_upgrade_tests {
use super::*;
#[test]
fn test_firmware_upgrade_mode_from() {
assert_eq!(FirmwareUpgradeMode::from(0x00), FirmwareUpgradeMode::Exit);
assert_eq!(
FirmwareUpgradeMode::from(0x01),
FirmwareUpgradeMode::CanUpgradeSilent
);
assert_eq!(
FirmwareUpgradeMode::from(0x02),
FirmwareUpgradeMode::CombinedUpgrade
);
assert_eq!(FirmwareUpgradeMode::from(0xFF), FirmwareUpgradeMode::Exit); }
#[test]
fn test_firmware_upgrade_command_new() {
let cmd = FirmwareUpgradeCommand::new(FirmwareUpgradeMode::CanUpgradeSilent);
assert_eq!(cmd.mode, FirmwareUpgradeMode::CanUpgradeSilent);
}
#[test]
fn test_firmware_upgrade_command_exit() {
let cmd = FirmwareUpgradeCommand::exit();
assert_eq!(cmd.mode, FirmwareUpgradeMode::Exit);
}
#[test]
fn test_firmware_upgrade_command_can_upgrade_silent() {
let cmd = FirmwareUpgradeCommand::can_upgrade_silent();
assert_eq!(cmd.mode, FirmwareUpgradeMode::CanUpgradeSilent);
}
#[test]
fn test_firmware_upgrade_command_combined_upgrade() {
let cmd = FirmwareUpgradeCommand::combined_upgrade();
assert_eq!(cmd.mode, FirmwareUpgradeMode::CombinedUpgrade);
}
#[test]
fn test_firmware_upgrade_command_to_frame() {
let cmd = FirmwareUpgradeCommand::new(FirmwareUpgradeMode::CanUpgradeSilent);
let frame = cmd.to_frame();
assert_eq!(frame.id, ID_FIRMWARE_UPGRADE);
assert_eq!(frame.len, 1);
assert_eq!(frame.data[0], 0x01);
}
#[test]
fn test_firmware_upgrade_command_all_modes() {
let modes = [
FirmwareUpgradeMode::Exit,
FirmwareUpgradeMode::CanUpgradeSilent,
FirmwareUpgradeMode::CombinedUpgrade,
];
for mode in modes.iter() {
let cmd = FirmwareUpgradeCommand::new(*mode);
let frame = cmd.to_frame();
assert_eq!(frame.id, ID_FIRMWARE_UPGRADE);
assert_eq!(frame.data[0], *mode as u8);
}
}
}
#[derive(Debug, Clone, Copy)]
pub struct FirmwareVersionQueryCommand;
impl FirmwareVersionQueryCommand {
pub fn new() -> Self {
Self
}
pub fn to_frame(self) -> PiperFrame {
let data = [0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00];
PiperFrame::new_standard(ID_FIRMWARE_READ as u16, &data)
}
}
impl Default for FirmwareVersionQueryCommand {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod firmware_version_query_tests {
use super::*;
#[test]
fn test_firmware_version_query_command_new() {
let cmd = FirmwareVersionQueryCommand::new();
let frame = cmd.to_frame();
assert_eq!(frame.id, ID_FIRMWARE_READ);
assert_eq!(frame.len, 8);
assert_eq!(frame.data[0], 0x01);
assert_eq!(frame.data[1..8], [0x00; 7]);
}
#[test]
fn test_firmware_version_query_command_default() {
let cmd = FirmwareVersionQueryCommand;
let frame = cmd.to_frame();
assert_eq!(frame.id, ID_FIRMWARE_READ);
assert_eq!(frame.data, [0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]);
}
#[test]
fn test_firmware_version_query_command_data_format() {
let cmd = FirmwareVersionQueryCommand::new();
let frame = cmd.to_frame();
let expected_data = [0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00];
assert_eq!(frame.data, expected_data);
}
}