use crate::can::PiperFrame;
use crate::control::{ControlModeCommand, InstallPosition, MitMode};
use crate::{
ProtocolError, bytes_to_i16_be, bytes_to_i32_be,
ids::{
ID_CONTROL_MODE, ID_END_POSE_1, ID_END_POSE_2, ID_END_POSE_3, ID_FIRMWARE_READ,
ID_GRIPPER_CONTROL, ID_GRIPPER_FEEDBACK, ID_JOINT_CONTROL_12, ID_JOINT_CONTROL_34,
ID_JOINT_CONTROL_56, ID_JOINT_DRIVER_HIGH_SPEED_BASE, ID_JOINT_DRIVER_LOW_SPEED_BASE,
ID_JOINT_END_VELOCITY_ACCEL_BASE, ID_JOINT_FEEDBACK_12, ID_JOINT_FEEDBACK_34,
ID_JOINT_FEEDBACK_56, ID_ROBOT_STATUS,
},
};
use bilge::prelude::*;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, num_enum::FromPrimitive)]
#[repr(u8)]
pub enum ControlMode {
#[default]
Standby = 0x00,
CanControl = 0x01,
Teach = 0x02,
Ethernet = 0x03,
Wifi = 0x04,
Remote = 0x05,
LinkTeach = 0x06,
OfflineTrajectory = 0x07,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, num_enum::FromPrimitive)]
#[repr(u8)]
pub enum RobotStatus {
#[default]
Normal = 0x00,
EmergencyStop = 0x01,
NoSolution = 0x02,
Singularity = 0x03,
AngleLimitExceeded = 0x04,
JointCommError = 0x05,
JointBrakeNotOpen = 0x06,
Collision = 0x07,
TeachOverspeed = 0x08,
JointStatusError = 0x09,
OtherError = 0x0A,
TeachRecord = 0x0B,
TeachExecute = 0x0C,
TeachPause = 0x0D,
MainControlOverTemp = 0x0E,
ResistorOverTemp = 0x0F,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, num_enum::FromPrimitive)]
#[repr(u8)]
pub enum MoveMode {
#[default]
MoveP = 0x00,
MoveJ = 0x01,
MoveL = 0x02,
MoveC = 0x03,
MoveM = 0x04,
MoveCpv = 0x05,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, num_enum::FromPrimitive)]
#[repr(u8)]
pub enum TeachStatus {
#[default]
Closed = 0x00,
StartRecord = 0x01,
EndRecord = 0x02,
Execute = 0x03,
Pause = 0x04,
Continue = 0x05,
Terminate = 0x06,
MoveToStart = 0x07,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, num_enum::FromPrimitive)]
#[repr(u8)]
pub enum MotionStatus {
#[default]
Arrived = 0x00,
NotArrived = 0x01,
}
#[bitsize(8)]
#[derive(FromBits, DebugBits, Clone, Copy, Default)]
pub struct FaultCodeAngleLimit {
pub joint1_limit: bool, pub joint2_limit: bool, pub joint3_limit: bool, pub joint4_limit: bool, pub joint5_limit: bool, pub joint6_limit: bool, pub reserved: u2, }
#[bitsize(8)]
#[derive(FromBits, DebugBits, Clone, Copy, Default)]
pub struct FaultCodeCommError {
pub joint1_comm_error: bool, pub joint2_comm_error: bool, pub joint3_comm_error: bool, pub joint4_comm_error: bool, pub joint5_comm_error: bool, pub joint6_comm_error: bool, pub reserved: u2, }
#[derive(Debug, Clone, Copy, Default)]
pub struct RobotStatusFeedback {
pub control_mode: ControlMode, pub robot_status: RobotStatus, pub move_mode: MoveMode, pub teach_status: TeachStatus, pub motion_status: MotionStatus, pub trajectory_point_index: u8, pub fault_code_angle_limit: FaultCodeAngleLimit, pub fault_code_comm_error: FaultCodeCommError, }
impl TryFrom<PiperFrame> for RobotStatusFeedback {
type Error = ProtocolError;
fn try_from(frame: PiperFrame) -> Result<Self, Self::Error> {
if frame.id != ID_ROBOT_STATUS {
return Err(ProtocolError::InvalidCanId { id: frame.id });
}
if frame.len < 8 {
return Err(ProtocolError::InvalidLength {
expected: 8,
actual: frame.len as usize,
});
}
Ok(Self {
control_mode: ControlMode::from(frame.data[0]),
robot_status: RobotStatus::from(frame.data[1]),
move_mode: MoveMode::from(frame.data[2]),
teach_status: TeachStatus::from(frame.data[3]),
motion_status: MotionStatus::from(frame.data[4]),
trajectory_point_index: frame.data[5],
fault_code_angle_limit: FaultCodeAngleLimit::from(u8::new(frame.data[6])),
fault_code_comm_error: FaultCodeCommError::from(u8::new(frame.data[7])),
})
}
}
#[derive(Debug, Clone, Copy, Default)]
pub struct JointFeedback12 {
pub j1_deg: i32, pub j2_deg: i32, }
impl JointFeedback12 {
pub fn j1_raw(&self) -> i32 {
self.j1_deg
}
pub fn j2_raw(&self) -> i32 {
self.j2_deg
}
pub fn j1(&self) -> f64 {
self.j1_deg as f64 / 1000.0
}
pub fn j2(&self) -> f64 {
self.j2_deg as f64 / 1000.0
}
pub fn j1_rad(&self) -> f64 {
self.j1() * std::f64::consts::PI / 180.0
}
pub fn j2_rad(&self) -> f64 {
self.j2() * std::f64::consts::PI / 180.0
}
}
impl TryFrom<PiperFrame> for JointFeedback12 {
type Error = ProtocolError;
fn try_from(frame: PiperFrame) -> Result<Self, Self::Error> {
if frame.id != ID_JOINT_FEEDBACK_12 {
return Err(ProtocolError::InvalidCanId { id: frame.id });
}
if frame.len < 8 {
return Err(ProtocolError::InvalidLength {
expected: 8,
actual: frame.len as usize,
});
}
let j1_bytes = [frame.data[0], frame.data[1], frame.data[2], frame.data[3]];
let j1_deg = bytes_to_i32_be(j1_bytes);
let j2_bytes = [frame.data[4], frame.data[5], frame.data[6], frame.data[7]];
let j2_deg = bytes_to_i32_be(j2_bytes);
Ok(Self { j1_deg, j2_deg })
}
}
#[derive(Debug, Clone, Copy, Default)]
pub struct JointFeedback34 {
pub j3_deg: i32, pub j4_deg: i32, }
impl JointFeedback34 {
pub fn j3_raw(&self) -> i32 {
self.j3_deg
}
pub fn j4_raw(&self) -> i32 {
self.j4_deg
}
pub fn j3(&self) -> f64 {
self.j3_deg as f64 / 1000.0
}
pub fn j4(&self) -> f64 {
self.j4_deg as f64 / 1000.0
}
pub fn j3_rad(&self) -> f64 {
self.j3() * std::f64::consts::PI / 180.0
}
pub fn j4_rad(&self) -> f64 {
self.j4() * std::f64::consts::PI / 180.0
}
}
impl TryFrom<PiperFrame> for JointFeedback34 {
type Error = ProtocolError;
fn try_from(frame: PiperFrame) -> Result<Self, Self::Error> {
if frame.id != ID_JOINT_FEEDBACK_34 {
return Err(ProtocolError::InvalidCanId { id: frame.id });
}
if frame.len < 8 {
return Err(ProtocolError::InvalidLength {
expected: 8,
actual: frame.len as usize,
});
}
let j3_bytes = [frame.data[0], frame.data[1], frame.data[2], frame.data[3]];
let j3_deg = bytes_to_i32_be(j3_bytes);
let j4_bytes = [frame.data[4], frame.data[5], frame.data[6], frame.data[7]];
let j4_deg = bytes_to_i32_be(j4_bytes);
Ok(Self { j3_deg, j4_deg })
}
}
#[derive(Debug, Clone, Copy, Default)]
pub struct JointFeedback56 {
pub j5_deg: i32, pub j6_deg: i32, }
impl JointFeedback56 {
pub fn j5_raw(&self) -> i32 {
self.j5_deg
}
pub fn j6_raw(&self) -> i32 {
self.j6_deg
}
pub fn j5(&self) -> f64 {
self.j5_deg as f64 / 1000.0
}
pub fn j6(&self) -> f64 {
self.j6_deg as f64 / 1000.0
}
pub fn j5_rad(&self) -> f64 {
self.j5() * std::f64::consts::PI / 180.0
}
pub fn j6_rad(&self) -> f64 {
self.j6() * std::f64::consts::PI / 180.0
}
}
impl TryFrom<PiperFrame> for JointFeedback56 {
type Error = ProtocolError;
fn try_from(frame: PiperFrame) -> Result<Self, Self::Error> {
if frame.id != ID_JOINT_FEEDBACK_56 {
return Err(ProtocolError::InvalidCanId { id: frame.id });
}
if frame.len < 8 {
return Err(ProtocolError::InvalidLength {
expected: 8,
actual: frame.len as usize,
});
}
let j5_bytes = [frame.data[0], frame.data[1], frame.data[2], frame.data[3]];
let j5_deg = bytes_to_i32_be(j5_bytes);
let j6_bytes = [frame.data[4], frame.data[5], frame.data[6], frame.data[7]];
let j6_deg = bytes_to_i32_be(j6_bytes);
Ok(Self { j5_deg, j6_deg })
}
}
#[derive(Debug, Clone, Copy, Default)]
pub struct EndPoseFeedback1 {
pub x_mm: i32, pub y_mm: i32, }
impl EndPoseFeedback1 {
pub fn x_raw(&self) -> i32 {
self.x_mm
}
pub fn y_raw(&self) -> i32 {
self.y_mm
}
pub fn x(&self) -> f64 {
self.x_mm as f64 / 1000.0
}
pub fn y(&self) -> f64 {
self.y_mm as f64 / 1000.0
}
}
impl TryFrom<PiperFrame> for EndPoseFeedback1 {
type Error = ProtocolError;
fn try_from(frame: PiperFrame) -> Result<Self, Self::Error> {
if frame.id != ID_END_POSE_1 {
return Err(ProtocolError::InvalidCanId { id: frame.id });
}
if frame.len < 8 {
return Err(ProtocolError::InvalidLength {
expected: 8,
actual: frame.len as usize,
});
}
let x_bytes = [frame.data[0], frame.data[1], frame.data[2], frame.data[3]];
let x_mm = bytes_to_i32_be(x_bytes);
let y_bytes = [frame.data[4], frame.data[5], frame.data[6], frame.data[7]];
let y_mm = bytes_to_i32_be(y_bytes);
Ok(Self { x_mm, y_mm })
}
}
#[derive(Debug, Clone, Copy, Default)]
pub struct EndPoseFeedback2 {
pub z_mm: i32, pub rx_deg: i32, }
impl EndPoseFeedback2 {
pub fn z_raw(&self) -> i32 {
self.z_mm
}
pub fn rx_raw(&self) -> i32 {
self.rx_deg
}
pub fn z(&self) -> f64 {
self.z_mm as f64 / 1000.0
}
pub fn rx(&self) -> f64 {
self.rx_deg as f64 / 1000.0
}
pub fn rx_rad(&self) -> f64 {
self.rx() * std::f64::consts::PI / 180.0
}
}
impl TryFrom<PiperFrame> for EndPoseFeedback2 {
type Error = ProtocolError;
fn try_from(frame: PiperFrame) -> Result<Self, Self::Error> {
if frame.id != ID_END_POSE_2 {
return Err(ProtocolError::InvalidCanId { id: frame.id });
}
if frame.len < 8 {
return Err(ProtocolError::InvalidLength {
expected: 8,
actual: frame.len as usize,
});
}
let z_bytes = [frame.data[0], frame.data[1], frame.data[2], frame.data[3]];
let z_mm = bytes_to_i32_be(z_bytes);
let rx_bytes = [frame.data[4], frame.data[5], frame.data[6], frame.data[7]];
let rx_deg = bytes_to_i32_be(rx_bytes);
Ok(Self { z_mm, rx_deg })
}
}
#[derive(Debug, Clone, Copy, Default)]
pub struct EndPoseFeedback3 {
pub ry_deg: i32, pub rz_deg: i32, }
impl EndPoseFeedback3 {
pub fn ry_raw(&self) -> i32 {
self.ry_deg
}
pub fn rz_raw(&self) -> i32 {
self.rz_deg
}
pub fn ry(&self) -> f64 {
self.ry_deg as f64 / 1000.0
}
pub fn rz(&self) -> f64 {
self.rz_deg as f64 / 1000.0
}
pub fn ry_rad(&self) -> f64 {
self.ry() * std::f64::consts::PI / 180.0
}
pub fn rz_rad(&self) -> f64 {
self.rz() * std::f64::consts::PI / 180.0
}
}
impl TryFrom<PiperFrame> for EndPoseFeedback3 {
type Error = ProtocolError;
fn try_from(frame: PiperFrame) -> Result<Self, Self::Error> {
if frame.id != ID_END_POSE_3 {
return Err(ProtocolError::InvalidCanId { id: frame.id });
}
if frame.len < 8 {
return Err(ProtocolError::InvalidLength {
expected: 8,
actual: frame.len as usize,
});
}
let ry_bytes = [frame.data[0], frame.data[1], frame.data[2], frame.data[3]];
let ry_deg = bytes_to_i32_be(ry_bytes);
let rz_bytes = [frame.data[4], frame.data[5], frame.data[6], frame.data[7]];
let rz_deg = bytes_to_i32_be(rz_bytes);
Ok(Self { ry_deg, rz_deg })
}
}
#[derive(Debug, Clone, Copy, Default)]
pub struct JointDriverHighSpeedFeedback {
pub joint_index: u8, pub speed_rad_s: i16, pub current_a: i16, pub position_rad: i32, }
impl JointDriverHighSpeedFeedback {
pub const COEFFICIENT_1_3: f64 = 1.18125;
pub const COEFFICIENT_4_6: f64 = 0.95844;
pub fn speed_raw(&self) -> i16 {
self.speed_rad_s
}
pub fn current_raw(&self) -> i16 {
self.current_a
}
pub fn position_raw(&self) -> i32 {
self.position_rad
}
pub fn speed(&self) -> f64 {
self.speed_rad_s as f64 / 1000.0
}
pub fn current(&self) -> f64 {
self.current_a as f64 / 1000.0
}
#[deprecated(
since = "0.1.0",
note = "Field unit unverified (rad vs mrad). Prefer `Observer::get_joint_position()` for verified position data, or use `position_raw()` for raw access."
)]
pub fn position(&self) -> f64 {
self.position_rad as f64
}
pub fn torque(&self, current_opt: Option<f64>) -> f64 {
let current = current_opt.unwrap_or_else(|| self.current());
let coefficient = if self.joint_index <= 3 {
Self::COEFFICIENT_1_3
} else {
Self::COEFFICIENT_4_6
};
current * coefficient
}
pub fn torque_raw(&self) -> i32 {
(self.torque(None) * 1000.0).round() as i32
}
}
impl TryFrom<PiperFrame> for JointDriverHighSpeedFeedback {
type Error = ProtocolError;
fn try_from(frame: PiperFrame) -> Result<Self, Self::Error> {
if frame.id < ID_JOINT_DRIVER_HIGH_SPEED_BASE
|| frame.id > ID_JOINT_DRIVER_HIGH_SPEED_BASE + 5
{
return Err(ProtocolError::InvalidCanId { id: frame.id });
}
let joint_index = (frame.id - ID_JOINT_DRIVER_HIGH_SPEED_BASE + 1) as u8;
if frame.len < 8 {
return Err(ProtocolError::InvalidLength {
expected: 8,
actual: frame.len as usize,
});
}
let speed_bytes = [frame.data[0], frame.data[1]];
let speed_rad_s = bytes_to_i16_be(speed_bytes);
let current_bytes = [frame.data[2], frame.data[3]];
let current_a = bytes_to_i16_be(current_bytes);
let position_bytes = [frame.data[4], frame.data[5], frame.data[6], frame.data[7]];
let position_rad = bytes_to_i32_be(position_bytes);
Ok(Self {
joint_index,
speed_rad_s,
current_a,
position_rad,
})
}
}
#[bitsize(8)]
#[derive(FromBits, DebugBits, Clone, Copy, Default)]
pub struct DriverStatus {
pub voltage_low: bool, pub motor_over_temp: bool, pub driver_over_current: bool, pub driver_over_temp: bool, pub collision_protection: bool, pub driver_error: bool, pub enabled: bool, pub stall_protection: bool, }
#[derive(Debug, Clone, Copy, Default)]
pub struct JointDriverLowSpeedFeedback {
pub joint_index: u8, pub voltage: u16, pub driver_temp: i16, pub motor_temp: i8, pub status: DriverStatus, pub bus_current: u16, }
impl JointDriverLowSpeedFeedback {
pub fn voltage_raw(&self) -> u16 {
self.voltage
}
pub fn driver_temp_raw(&self) -> i16 {
self.driver_temp
}
pub fn motor_temp_raw(&self) -> i8 {
self.motor_temp
}
pub fn bus_current_raw(&self) -> u16 {
self.bus_current
}
pub fn voltage(&self) -> f64 {
self.voltage as f64 / 10.0
}
pub fn driver_temp(&self) -> f64 {
self.driver_temp as f64
}
pub fn motor_temp(&self) -> f64 {
self.motor_temp as f64
}
pub fn bus_current(&self) -> f64 {
self.bus_current as f64 / 1000.0
}
}
impl TryFrom<PiperFrame> for JointDriverLowSpeedFeedback {
type Error = ProtocolError;
fn try_from(frame: PiperFrame) -> Result<Self, Self::Error> {
let joint_index = (frame.id - ID_JOINT_DRIVER_LOW_SPEED_BASE + 1) as u8;
if !(1..=6).contains(&joint_index) {
return Err(ProtocolError::InvalidCanId { id: frame.id });
}
if frame.len < 8 {
return Err(ProtocolError::InvalidLength {
expected: 8,
actual: frame.len as usize,
});
}
let voltage_bytes = [frame.data[0], frame.data[1]];
let voltage = u16::from_be_bytes(voltage_bytes);
let driver_temp_bytes = [frame.data[2], frame.data[3]];
let driver_temp = bytes_to_i16_be(driver_temp_bytes);
let motor_temp = frame.data[4] as i8;
let status = DriverStatus::from(u8::new(frame.data[5]));
let bus_current_bytes = [frame.data[6], frame.data[7]];
let bus_current = u16::from_be_bytes(bus_current_bytes);
Ok(Self {
joint_index,
voltage,
driver_temp,
motor_temp,
status,
bus_current,
})
}
}
#[derive(Debug, Clone, Copy, Default)]
pub struct JointEndVelocityAccelFeedback {
pub joint_index: u8, pub linear_velocity_m_s_raw: u16, pub angular_velocity_rad_s_raw: u16, pub linear_accel_m_s2_raw: u16, pub angular_accel_rad_s2_raw: u16, }
impl JointEndVelocityAccelFeedback {
pub fn linear_velocity_raw(&self) -> u16 {
self.linear_velocity_m_s_raw
}
pub fn angular_velocity_raw(&self) -> u16 {
self.angular_velocity_rad_s_raw
}
pub fn linear_accel_raw(&self) -> u16 {
self.linear_accel_m_s2_raw
}
pub fn angular_accel_raw(&self) -> u16 {
self.angular_accel_rad_s2_raw
}
pub fn linear_velocity(&self) -> f64 {
self.linear_velocity_m_s_raw as f64 / 1000.0
}
pub fn angular_velocity(&self) -> f64 {
self.angular_velocity_rad_s_raw as f64 / 1000.0
}
pub fn linear_accel(&self) -> f64 {
self.linear_accel_m_s2_raw as f64 / 1000.0
}
pub fn angular_accel(&self) -> f64 {
self.angular_accel_rad_s2_raw as f64 / 1000.0
}
}
impl TryFrom<PiperFrame> for JointEndVelocityAccelFeedback {
type Error = ProtocolError;
fn try_from(frame: PiperFrame) -> Result<Self, Self::Error> {
if frame.id < ID_JOINT_END_VELOCITY_ACCEL_BASE
|| frame.id > ID_JOINT_END_VELOCITY_ACCEL_BASE + 5
{
return Err(ProtocolError::InvalidCanId { id: frame.id });
}
let joint_index = (frame.id - ID_JOINT_END_VELOCITY_ACCEL_BASE + 1) as u8;
if frame.len < 8 {
return Err(ProtocolError::InvalidLength {
expected: 8,
actual: frame.len as usize,
});
}
let linear_velocity_bytes = [frame.data[0], frame.data[1]];
let linear_velocity_m_s_raw = u16::from_be_bytes(linear_velocity_bytes);
let angular_velocity_bytes = [frame.data[2], frame.data[3]];
let angular_velocity_rad_s_raw = u16::from_be_bytes(angular_velocity_bytes);
let linear_accel_bytes = [frame.data[4], frame.data[5]];
let linear_accel_m_s2_raw = u16::from_be_bytes(linear_accel_bytes);
let angular_accel_bytes = [frame.data[6], frame.data[7]];
let angular_accel_rad_s2_raw = u16::from_be_bytes(angular_accel_bytes);
Ok(Self {
joint_index,
linear_velocity_m_s_raw,
angular_velocity_rad_s_raw,
linear_accel_m_s2_raw,
angular_accel_rad_s2_raw,
})
}
}
#[bitsize(8)]
#[derive(FromBits, DebugBits, Clone, Copy, Default)]
pub struct GripperStatus {
pub voltage_low: bool, pub motor_over_temp: bool, pub driver_over_current: bool, pub driver_over_temp: bool, pub sensor_error: bool, pub driver_error: bool, pub enabled: bool, pub homed: bool, }
#[derive(Debug, Clone, Copy, Default)]
pub struct GripperFeedback {
pub travel_mm: i32, pub torque_nm: i16, pub status: GripperStatus, }
impl GripperFeedback {
pub fn travel_raw(&self) -> i32 {
self.travel_mm
}
pub fn torque_raw(&self) -> i16 {
self.torque_nm
}
pub fn travel(&self) -> f64 {
self.travel_mm as f64 / 1000.0
}
pub fn torque(&self) -> f64 {
self.torque_nm as f64 / 1000.0
}
}
impl TryFrom<PiperFrame> for GripperFeedback {
type Error = ProtocolError;
fn try_from(frame: PiperFrame) -> Result<Self, Self::Error> {
if frame.id != ID_GRIPPER_FEEDBACK {
return Err(ProtocolError::InvalidCanId { id: frame.id });
}
if frame.len < 7 {
return Err(ProtocolError::InvalidLength {
expected: 7,
actual: frame.len as usize,
});
}
let travel_bytes = [frame.data[0], frame.data[1], frame.data[2], frame.data[3]];
let travel_mm = bytes_to_i32_be(travel_bytes);
let torque_bytes = [frame.data[4], frame.data[5]];
let torque_nm = bytes_to_i16_be(torque_bytes);
let status = GripperStatus::from(u8::new(frame.data[6]));
Ok(Self {
travel_mm,
torque_nm,
status,
})
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_control_mode_from_u8() {
assert_eq!(ControlMode::from(0x00), ControlMode::Standby);
assert_eq!(ControlMode::from(0x01), ControlMode::CanControl);
assert_eq!(ControlMode::from(0x02), ControlMode::Teach);
assert_eq!(ControlMode::from(0x03), ControlMode::Ethernet);
assert_eq!(ControlMode::from(0x04), ControlMode::Wifi);
assert_eq!(ControlMode::from(0x05), ControlMode::Remote);
assert_eq!(ControlMode::from(0x06), ControlMode::LinkTeach);
assert_eq!(ControlMode::from(0x07), ControlMode::OfflineTrajectory);
assert_eq!(ControlMode::from(0xFF), ControlMode::Standby);
}
#[test]
fn test_robot_status_from_u8() {
assert_eq!(RobotStatus::from(0x00), RobotStatus::Normal);
assert_eq!(RobotStatus::from(0x01), RobotStatus::EmergencyStop);
assert_eq!(RobotStatus::from(0x0F), RobotStatus::ResistorOverTemp);
for i in 0x00..=0x0F {
let status = RobotStatus::from(i);
assert_eq!(status as u8, i);
}
}
#[test]
fn test_move_mode_from_u8() {
assert_eq!(MoveMode::from(0x00), MoveMode::MoveP);
assert_eq!(MoveMode::from(0x01), MoveMode::MoveJ);
assert_eq!(MoveMode::from(0x02), MoveMode::MoveL);
assert_eq!(MoveMode::from(0x03), MoveMode::MoveC);
assert_eq!(MoveMode::from(0x04), MoveMode::MoveM);
assert_eq!(MoveMode::from(0x05), MoveMode::MoveCpv);
assert_eq!(MoveMode::from(0xFF), MoveMode::MoveP);
}
#[test]
fn test_move_mode_cpv() {
assert_eq!(MoveMode::from(0x05), MoveMode::MoveCpv);
assert_eq!(MoveMode::MoveCpv as u8, 0x05);
}
#[test]
fn test_move_mode_all_values() {
for (value, expected) in [
(0x00, MoveMode::MoveP),
(0x01, MoveMode::MoveJ),
(0x02, MoveMode::MoveL),
(0x03, MoveMode::MoveC),
(0x04, MoveMode::MoveM),
(0x05, MoveMode::MoveCpv),
] {
assert_eq!(MoveMode::from(value), expected);
assert_eq!(expected as u8, value);
}
}
#[test]
fn test_teach_status_from_u8() {
assert_eq!(TeachStatus::from(0x00), TeachStatus::Closed);
assert_eq!(TeachStatus::from(0x01), TeachStatus::StartRecord);
assert_eq!(TeachStatus::from(0x07), TeachStatus::MoveToStart);
for i in 0x00..=0x07 {
let status = TeachStatus::from(i);
assert_eq!(status as u8, i);
}
}
#[test]
fn test_motion_status_from_u8() {
assert_eq!(MotionStatus::from(0x00), MotionStatus::Arrived);
assert_eq!(MotionStatus::from(0x01), MotionStatus::NotArrived);
assert_eq!(MotionStatus::from(0xFF), MotionStatus::Arrived);
}
#[test]
fn test_enum_values_match_protocol() {
assert_eq!(ControlMode::Standby as u8, 0x00);
assert_eq!(ControlMode::CanControl as u8, 0x01);
assert_eq!(ControlMode::Remote as u8, 0x05);
assert_eq!(ControlMode::LinkTeach as u8, 0x06);
assert_eq!(RobotStatus::Normal as u8, 0x00);
assert_eq!(RobotStatus::EmergencyStop as u8, 0x01);
assert_eq!(RobotStatus::ResistorOverTemp as u8, 0x0F);
assert_eq!(MoveMode::MoveP as u8, 0x00);
assert_eq!(MoveMode::MoveM as u8, 0x04);
assert_eq!(MoveMode::MoveCpv as u8, 0x05);
}
#[test]
fn test_fault_code_angle_limit_bit_order() {
let byte = 0x01;
let fault = FaultCodeAngleLimit::from(u8::new(byte));
assert!(fault.joint1_limit(), "1号关节应该超限位");
assert!(!fault.joint2_limit(), "2号关节应该正常");
assert!(!fault.joint3_limit(), "3号关节应该正常");
assert!(!fault.joint4_limit(), "4号关节应该正常");
assert!(!fault.joint5_limit(), "5号关节应该正常");
assert!(!fault.joint6_limit(), "6号关节应该正常");
let byte = 0x02;
let fault = FaultCodeAngleLimit::from(u8::new(byte));
assert!(!fault.joint1_limit(), "1号关节应该正常");
assert!(fault.joint2_limit(), "2号关节应该超限位");
assert!(!fault.joint3_limit(), "3号关节应该正常");
let byte = 0x03;
let fault = FaultCodeAngleLimit::from(u8::new(byte));
assert!(fault.joint1_limit(), "1号关节应该超限位");
assert!(fault.joint2_limit(), "2号关节应该超限位");
assert!(!fault.joint3_limit(), "3号关节应该正常");
let byte = 0x3F;
let fault = FaultCodeAngleLimit::from(u8::new(byte));
assert!(fault.joint1_limit(), "1号关节应该超限位");
assert!(fault.joint2_limit(), "2号关节应该超限位");
assert!(fault.joint3_limit(), "3号关节应该超限位");
assert!(fault.joint4_limit(), "4号关节应该超限位");
assert!(fault.joint5_limit(), "5号关节应该超限位");
assert!(fault.joint6_limit(), "6号关节应该超限位");
}
#[test]
fn test_fault_code_angle_limit_encode() {
let mut fault = FaultCodeAngleLimit::from(u8::new(0));
fault.set_joint1_limit(true);
fault.set_joint3_limit(true);
fault.set_joint5_limit(true);
let encoded = u8::from(fault).value();
assert_eq!(encoded, 0x15, "编码值应该是 0x15 (Bit 0, 2, 4 = 1)");
assert!(fault.joint1_limit());
assert!(!fault.joint2_limit());
assert!(fault.joint3_limit());
assert!(!fault.joint4_limit());
assert!(fault.joint5_limit());
assert!(!fault.joint6_limit());
}
#[test]
fn test_fault_code_angle_limit_roundtrip() {
let original_byte = 0x3C;
let fault = FaultCodeAngleLimit::from(u8::new(original_byte));
let encoded = u8::from(fault).value();
assert!(!fault.joint1_limit());
assert!(!fault.joint2_limit());
assert!(fault.joint3_limit());
assert!(fault.joint4_limit());
assert!(fault.joint5_limit());
assert!(fault.joint6_limit());
assert_eq!(encoded & 0b0011_1111, original_byte & 0b0011_1111);
}
#[test]
fn test_fault_code_comm_error_bit_order() {
let byte = 0x01;
let fault = FaultCodeCommError::from(u8::new(byte));
assert!(fault.joint1_comm_error(), "1号关节应该通信异常");
assert!(!fault.joint2_comm_error(), "2号关节应该正常");
let byte = 0x02;
let fault = FaultCodeCommError::from(u8::new(byte));
assert!(!fault.joint1_comm_error(), "1号关节应该正常");
assert!(fault.joint2_comm_error(), "2号关节应该通信异常");
}
#[test]
fn test_fault_code_comm_error_encode() {
let mut fault = FaultCodeCommError::from(u8::new(0));
fault.set_joint2_comm_error(true);
fault.set_joint6_comm_error(true);
let encoded = u8::from(fault).value();
assert_eq!(encoded, 0x22, "编码值应该是 0x22 (Bit 1, 5 = 1)");
}
#[test]
fn test_fault_code_comm_error_all_joints() {
let mut fault = FaultCodeCommError::from(u8::new(0));
fault.set_joint1_comm_error(true);
fault.set_joint2_comm_error(true);
fault.set_joint3_comm_error(true);
fault.set_joint4_comm_error(true);
fault.set_joint5_comm_error(true);
fault.set_joint6_comm_error(true);
let encoded = u8::from(fault).value();
assert_eq!(encoded & 0b0011_1111, 0x3F, "前6位应该都是1");
}
#[test]
fn test_robot_status_feedback_parse() {
let frame = PiperFrame::new_standard(
ID_ROBOT_STATUS as u16,
&[
0x01, 0x00, 0x01, 0x00, 0x00, 0x05, 0b0011_1111, 0b0000_0000, ],
);
let status = RobotStatusFeedback::try_from(frame).unwrap();
assert_eq!(status.control_mode, ControlMode::CanControl);
assert_eq!(status.robot_status, RobotStatus::Normal);
assert_eq!(status.move_mode, MoveMode::MoveJ);
assert_eq!(status.teach_status, TeachStatus::Closed);
assert_eq!(status.motion_status, MotionStatus::Arrived);
assert_eq!(status.trajectory_point_index, 5);
assert!(status.fault_code_angle_limit.joint1_limit());
assert!(status.fault_code_angle_limit.joint6_limit());
assert!(!status.fault_code_comm_error.joint1_comm_error());
}
#[test]
fn test_robot_status_feedback_invalid_id() {
let frame = PiperFrame::new_standard(0x999, &[0; 8]);
let result = RobotStatusFeedback::try_from(frame);
assert!(result.is_err());
match result.unwrap_err() {
ProtocolError::InvalidCanId { id } => assert_eq!(id, 0x999),
_ => panic!("Expected InvalidCanId error"),
}
}
#[test]
fn test_robot_status_feedback_invalid_length() {
let frame = PiperFrame::new_standard(ID_ROBOT_STATUS as u16, &[0; 4]);
let result = RobotStatusFeedback::try_from(frame);
assert!(result.is_err());
match result.unwrap_err() {
ProtocolError::InvalidLength { expected, actual } => {
assert_eq!(expected, 8);
assert_eq!(actual, 4);
},
_ => panic!("Expected InvalidLength error"),
}
}
#[test]
fn test_robot_status_feedback_all_fields() {
let frame = PiperFrame::new_standard(
ID_ROBOT_STATUS as u16,
&[
0x07, 0x0F, 0x04, 0x07, 0x01, 0xFF, 0b0011_1111, 0b0011_1111, ],
);
let status = RobotStatusFeedback::try_from(frame).unwrap();
assert_eq!(status.control_mode, ControlMode::OfflineTrajectory);
assert_eq!(status.robot_status, RobotStatus::ResistorOverTemp);
assert_eq!(status.move_mode, MoveMode::MoveM);
assert_eq!(status.teach_status, TeachStatus::MoveToStart);
assert_eq!(status.motion_status, MotionStatus::NotArrived);
assert_eq!(status.trajectory_point_index, 0xFF);
assert!(status.fault_code_angle_limit.joint1_limit());
assert!(status.fault_code_angle_limit.joint2_limit());
assert!(status.fault_code_angle_limit.joint3_limit());
assert!(status.fault_code_angle_limit.joint4_limit());
assert!(status.fault_code_angle_limit.joint5_limit());
assert!(status.fault_code_angle_limit.joint6_limit());
assert!(status.fault_code_comm_error.joint1_comm_error());
assert!(status.fault_code_comm_error.joint2_comm_error());
assert!(status.fault_code_comm_error.joint3_comm_error());
assert!(status.fault_code_comm_error.joint4_comm_error());
assert!(status.fault_code_comm_error.joint5_comm_error());
assert!(status.fault_code_comm_error.joint6_comm_error());
}
#[test]
fn test_joint_feedback12_parse() {
let j1_val = 90000i32;
let j2_val = -45000i32;
let mut data = [0u8; 8];
data[0..4].copy_from_slice(&j1_val.to_be_bytes());
data[4..8].copy_from_slice(&j2_val.to_be_bytes());
let frame = PiperFrame::new_standard(ID_JOINT_FEEDBACK_12 as u16, &data);
let feedback = JointFeedback12::try_from(frame).unwrap();
assert_eq!(feedback.j1_raw(), 90000);
assert_eq!(feedback.j2_raw(), -45000);
assert!((feedback.j1() - 90.0).abs() < 0.0001);
assert!((feedback.j2() - (-45.0)).abs() < 0.0001);
}
#[test]
fn test_joint_feedback12_physical_conversion() {
let frame = PiperFrame::new_standard(
ID_JOINT_FEEDBACK_12 as u16,
&[
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xF4, ],
);
let feedback = JointFeedback12::try_from(frame).unwrap();
assert_eq!(feedback.j1(), 0.0);
assert!((feedback.j2() - 0.5).abs() < 0.0001);
assert!((feedback.j1_rad() - 0.0).abs() < 0.0001);
assert!((feedback.j2_rad() - (0.5 * std::f64::consts::PI / 180.0)).abs() < 0.0001);
}
#[test]
fn test_joint_feedback12_boundary_values() {
let max_positive = i32::MAX;
let mut data = [0u8; 8];
data[0..4].copy_from_slice(&max_positive.to_be_bytes());
data[4..8].copy_from_slice(&max_positive.to_be_bytes());
let frame = PiperFrame::new_standard(ID_JOINT_FEEDBACK_12 as u16, &data);
let feedback = JointFeedback12::try_from(frame).unwrap();
assert_eq!(feedback.j1_raw(), max_positive);
assert_eq!(feedback.j2_raw(), max_positive);
let min_negative = i32::MIN;
let mut data = [0u8; 8];
data[0..4].copy_from_slice(&min_negative.to_be_bytes());
data[4..8].copy_from_slice(&min_negative.to_be_bytes());
let frame = PiperFrame::new_standard(ID_JOINT_FEEDBACK_12 as u16, &data);
let feedback = JointFeedback12::try_from(frame).unwrap();
assert_eq!(feedback.j1_raw(), min_negative);
assert_eq!(feedback.j2_raw(), min_negative);
}
#[test]
fn test_joint_feedback12_invalid_id() {
let frame = PiperFrame::new_standard(0x999, &[0; 8]);
let result = JointFeedback12::try_from(frame);
assert!(result.is_err());
match result.unwrap_err() {
ProtocolError::InvalidCanId { id } => assert_eq!(id, 0x999),
_ => panic!("Expected InvalidCanId error"),
}
}
#[test]
fn test_joint_feedback12_invalid_length() {
let frame = PiperFrame::new_standard(ID_JOINT_FEEDBACK_12 as u16, &[0; 4]);
let result = JointFeedback12::try_from(frame);
assert!(result.is_err());
match result.unwrap_err() {
ProtocolError::InvalidLength { expected, actual } => {
assert_eq!(expected, 8);
assert_eq!(actual, 4);
},
_ => panic!("Expected InvalidLength error"),
}
}
#[test]
fn test_joint_feedback34_parse() {
let j3_val = 30000i32;
let j4_val = -60000i32;
let mut data = [0u8; 8];
data[0..4].copy_from_slice(&j3_val.to_be_bytes());
data[4..8].copy_from_slice(&j4_val.to_be_bytes());
let frame = PiperFrame::new_standard(ID_JOINT_FEEDBACK_34 as u16, &data);
let feedback = JointFeedback34::try_from(frame).unwrap();
assert_eq!(feedback.j3_raw(), 30000);
assert_eq!(feedback.j4_raw(), -60000);
assert!((feedback.j3() - 30.0).abs() < 0.0001);
assert!((feedback.j4() - (-60.0)).abs() < 0.0001);
}
#[test]
fn test_joint_feedback56_parse() {
let j5_val = 180000i32;
let j6_val = -90000i32;
let mut data = [0u8; 8];
data[0..4].copy_from_slice(&j5_val.to_be_bytes());
data[4..8].copy_from_slice(&j6_val.to_be_bytes());
let frame = PiperFrame::new_standard(ID_JOINT_FEEDBACK_56 as u16, &data);
let feedback = JointFeedback56::try_from(frame).unwrap();
assert_eq!(feedback.j5_raw(), 180000);
assert_eq!(feedback.j6_raw(), -90000);
assert!((feedback.j5() - 180.0).abs() < 0.0001);
assert!((feedback.j6() - (-90.0)).abs() < 0.0001);
}
#[test]
fn test_joint_feedback_roundtrip() {
let test_cases = vec![
(0i32, 0i32),
(90000i32, -45000i32),
(i32::MAX, i32::MIN),
(180000i32, -90000i32),
];
for (j1_val, j2_val) in test_cases {
let frame = PiperFrame::new_standard(
ID_JOINT_FEEDBACK_12 as u16,
&[
j1_val.to_be_bytes()[0],
j1_val.to_be_bytes()[1],
j1_val.to_be_bytes()[2],
j1_val.to_be_bytes()[3],
j2_val.to_be_bytes()[0],
j2_val.to_be_bytes()[1],
j2_val.to_be_bytes()[2],
j2_val.to_be_bytes()[3],
],
);
let feedback = JointFeedback12::try_from(frame).unwrap();
assert_eq!(feedback.j1_raw(), j1_val);
assert_eq!(feedback.j2_raw(), j2_val);
}
}
#[test]
fn test_end_pose_feedback1_parse() {
let x_val = 100000i32;
let y_val = -50000i32;
let mut data = [0u8; 8];
data[0..4].copy_from_slice(&x_val.to_be_bytes());
data[4..8].copy_from_slice(&y_val.to_be_bytes());
let frame = PiperFrame::new_standard(ID_END_POSE_1 as u16, &data);
let feedback = EndPoseFeedback1::try_from(frame).unwrap();
assert_eq!(feedback.x_raw(), 100000);
assert_eq!(feedback.y_raw(), -50000);
assert!((feedback.x() - 100.0).abs() < 0.0001);
assert!((feedback.y() - (-50.0)).abs() < 0.0001);
}
#[test]
fn test_end_pose_feedback1_unit_conversion() {
let x_val = 1234i32; let y_val = -5678i32; let mut data = [0u8; 8];
data[0..4].copy_from_slice(&x_val.to_be_bytes());
data[4..8].copy_from_slice(&y_val.to_be_bytes());
let frame = PiperFrame::new_standard(ID_END_POSE_1 as u16, &data);
let feedback = EndPoseFeedback1::try_from(frame).unwrap();
assert!((feedback.x() - 1.234).abs() < 0.0001);
assert!((feedback.y() - (-5.678)).abs() < 0.0001);
}
#[test]
fn test_end_pose_feedback1_invalid_id() {
let frame = PiperFrame::new_standard(0x999, &[0; 8]);
let result = EndPoseFeedback1::try_from(frame);
assert!(result.is_err());
}
#[test]
fn test_end_pose_feedback2_parse() {
let z_val = 200000i32;
let rx_val = 45000i32;
let mut data = [0u8; 8];
data[0..4].copy_from_slice(&z_val.to_be_bytes());
data[4..8].copy_from_slice(&rx_val.to_be_bytes());
let frame = PiperFrame::new_standard(ID_END_POSE_2 as u16, &data);
let feedback = EndPoseFeedback2::try_from(frame).unwrap();
assert_eq!(feedback.z_raw(), 200000);
assert_eq!(feedback.rx_raw(), 45000);
assert!((feedback.z() - 200.0).abs() < 0.0001);
assert!((feedback.rx() - 45.0).abs() < 0.0001);
assert!((feedback.rx_rad() - (45.0 * std::f64::consts::PI / 180.0)).abs() < 0.0001);
}
#[test]
fn test_end_pose_feedback3_parse() {
let ry_val = 90000i32;
let rz_val = -30000i32;
let mut data = [0u8; 8];
data[0..4].copy_from_slice(&ry_val.to_be_bytes());
data[4..8].copy_from_slice(&rz_val.to_be_bytes());
let frame = PiperFrame::new_standard(ID_END_POSE_3 as u16, &data);
let feedback = EndPoseFeedback3::try_from(frame).unwrap();
assert_eq!(feedback.ry_raw(), 90000);
assert_eq!(feedback.rz_raw(), -30000);
assert!((feedback.ry() - 90.0).abs() < 0.0001);
assert!((feedback.rz() - (-30.0)).abs() < 0.0001);
}
#[test]
fn test_joint_driver_high_speed_feedback_parse() {
let speed_val = 1500i16;
let current_val = 2500u16;
let position_val = 1000000i32;
let mut data = [0u8; 8];
data[0..2].copy_from_slice(&speed_val.to_be_bytes());
data[2..4].copy_from_slice(¤t_val.to_be_bytes());
data[4..8].copy_from_slice(&position_val.to_be_bytes());
let frame = PiperFrame::new_standard(ID_JOINT_DRIVER_HIGH_SPEED_BASE as u16, &data);
let feedback = JointDriverHighSpeedFeedback::try_from(frame).unwrap();
assert_eq!(feedback.joint_index, 1);
assert_eq!(feedback.speed_raw(), 1500);
assert_eq!(feedback.current_raw(), 2500i16); assert_eq!(feedback.position_raw(), 1000000);
assert!((feedback.speed() - 1.5).abs() < 0.0001);
assert!((feedback.current() - 2.5).abs() < 0.0001);
}
#[test]
fn test_joint_driver_high_speed_feedback_all_joints() {
for joint_id in 1..=6 {
let can_id = ID_JOINT_DRIVER_HIGH_SPEED_BASE + (joint_id - 1);
let frame = PiperFrame::new_standard(can_id as u16, &[0; 8]);
let feedback = JointDriverHighSpeedFeedback::try_from(frame).unwrap();
assert_eq!(feedback.joint_index, joint_id as u8);
}
}
#[test]
fn test_joint_driver_high_speed_feedback_physical_conversion() {
let speed_val = 3141i16; let current_val = 5000u16; let position_val = 3141592i32;
let mut data = [0u8; 8];
data[0..2].copy_from_slice(&speed_val.to_be_bytes());
data[2..4].copy_from_slice(¤t_val.to_be_bytes());
data[4..8].copy_from_slice(&position_val.to_be_bytes());
let frame = PiperFrame::new_standard(ID_JOINT_DRIVER_HIGH_SPEED_BASE as u16, &data);
let feedback = JointDriverHighSpeedFeedback::try_from(frame).unwrap();
assert!((feedback.speed() - std::f64::consts::PI).abs() < 0.001);
assert!((feedback.current() - 5.0).abs() < 0.001);
assert_eq!(feedback.position_raw(), position_val);
}
#[test]
fn test_joint_driver_high_speed_feedback_boundary_values() {
let speed_val = i16::MAX;
let current_val = i16::MAX; let position_val = i32::MAX;
let mut data = [0u8; 8];
data[0..2].copy_from_slice(&speed_val.to_be_bytes());
data[2..4].copy_from_slice(¤t_val.to_be_bytes());
data[4..8].copy_from_slice(&position_val.to_be_bytes());
let frame = PiperFrame::new_standard(ID_JOINT_DRIVER_HIGH_SPEED_BASE as u16, &data);
let feedback = JointDriverHighSpeedFeedback::try_from(frame).unwrap();
assert_eq!(feedback.speed_raw(), i16::MAX);
assert_eq!(feedback.current_raw(), i16::MAX);
assert_eq!(feedback.position_raw(), i32::MAX);
}
#[test]
fn test_joint_driver_high_speed_feedback_invalid_id() {
let frame = PiperFrame::new_standard(0x250, &[0; 8]); let result = JointDriverHighSpeedFeedback::try_from(frame);
assert!(result.is_err());
let frame = PiperFrame::new_standard(0x257, &[0; 8]); let result = JointDriverHighSpeedFeedback::try_from(frame);
assert!(result.is_err());
}
#[test]
fn test_joint_driver_high_speed_feedback_invalid_length() {
let frame = PiperFrame::new_standard(ID_JOINT_DRIVER_HIGH_SPEED_BASE as u16, &[0; 4]);
let result = JointDriverHighSpeedFeedback::try_from(frame);
assert!(result.is_err());
match result.unwrap_err() {
ProtocolError::InvalidLength { expected, actual } => {
assert_eq!(expected, 8);
assert_eq!(actual, 4);
},
_ => panic!("Expected InvalidLength error"),
}
}
#[test]
fn test_joint_driver_high_speed_feedback_negative_speed() {
let speed_val = -1000i16; let current_val = 1000u16; let position_val = 0i32;
let mut data = [0u8; 8];
data[0..2].copy_from_slice(&speed_val.to_be_bytes());
data[2..4].copy_from_slice(¤t_val.to_be_bytes());
data[4..8].copy_from_slice(&position_val.to_be_bytes());
let frame = PiperFrame::new_standard(ID_JOINT_DRIVER_HIGH_SPEED_BASE as u16, &data);
let feedback = JointDriverHighSpeedFeedback::try_from(frame).unwrap();
assert_eq!(feedback.speed_raw(), -1000);
assert!((feedback.speed() - (-1.0)).abs() < 0.0001);
}
#[test]
fn test_joint_driver_high_speed_feedback_torque_joints_1_3() {
let current_val = 1000u16; let mut data = [0u8; 8];
data[2..4].copy_from_slice(¤t_val.to_be_bytes());
let frame = PiperFrame::new_standard(ID_JOINT_DRIVER_HIGH_SPEED_BASE as u16, &data);
let feedback = JointDriverHighSpeedFeedback::try_from(frame).unwrap();
assert_eq!(feedback.joint_index, 1);
let expected_torque = 1.0 * JointDriverHighSpeedFeedback::COEFFICIENT_1_3; assert!((feedback.torque(None) - expected_torque).abs() < 0.0001);
assert_eq!(
feedback.torque_raw(),
(expected_torque * 1000.0).round() as i32
);
let frame = PiperFrame::new_standard(ID_JOINT_DRIVER_HIGH_SPEED_BASE as u16 + 1, &data);
let feedback = JointDriverHighSpeedFeedback::try_from(frame).unwrap();
assert_eq!(feedback.joint_index, 2);
assert!((feedback.torque(None) - expected_torque).abs() < 0.0001);
let frame = PiperFrame::new_standard(ID_JOINT_DRIVER_HIGH_SPEED_BASE as u16 + 2, &data);
let feedback = JointDriverHighSpeedFeedback::try_from(frame).unwrap();
assert_eq!(feedback.joint_index, 3);
assert!((feedback.torque(None) - expected_torque).abs() < 0.0001);
}
#[test]
fn test_joint_driver_high_speed_feedback_torque_joints_4_6() {
let current_val = 1000u16; let mut data = [0u8; 8];
data[2..4].copy_from_slice(¤t_val.to_be_bytes());
let frame = PiperFrame::new_standard(ID_JOINT_DRIVER_HIGH_SPEED_BASE as u16 + 3, &data);
let feedback = JointDriverHighSpeedFeedback::try_from(frame).unwrap();
assert_eq!(feedback.joint_index, 4);
let expected_torque = 1.0 * JointDriverHighSpeedFeedback::COEFFICIENT_4_6; assert!((feedback.torque(None) - expected_torque).abs() < 0.0001);
assert_eq!(
feedback.torque_raw(),
(expected_torque * 1000.0).round() as i32
);
let frame = PiperFrame::new_standard(ID_JOINT_DRIVER_HIGH_SPEED_BASE as u16 + 4, &data);
let feedback = JointDriverHighSpeedFeedback::try_from(frame).unwrap();
assert_eq!(feedback.joint_index, 5);
assert!((feedback.torque(None) - expected_torque).abs() < 0.0001);
let frame = PiperFrame::new_standard(ID_JOINT_DRIVER_HIGH_SPEED_BASE as u16 + 5, &data);
let feedback = JointDriverHighSpeedFeedback::try_from(frame).unwrap();
assert_eq!(feedback.joint_index, 6);
assert!((feedback.torque(None) - expected_torque).abs() < 0.0001);
}
#[test]
fn test_joint_driver_high_speed_feedback_torque_with_custom_current() {
let current_val = 2000u16; let custom_current = 2.5;
let mut data = [0u8; 8];
data[2..4].copy_from_slice(¤t_val.to_be_bytes());
let frame = PiperFrame::new_standard(ID_JOINT_DRIVER_HIGH_SPEED_BASE as u16, &data);
let feedback = JointDriverHighSpeedFeedback::try_from(frame).unwrap();
let torque_from_feedback = feedback.torque(None);
let expected_from_feedback = 2.0 * JointDriverHighSpeedFeedback::COEFFICIENT_1_3;
assert!((torque_from_feedback - expected_from_feedback).abs() < 0.0001);
let torque_from_custom = feedback.torque(Some(custom_current));
let expected_from_custom = custom_current * JointDriverHighSpeedFeedback::COEFFICIENT_1_3;
assert!((torque_from_custom - expected_from_custom).abs() < 0.0001);
let frame = PiperFrame::new_standard(ID_JOINT_DRIVER_HIGH_SPEED_BASE as u16 + 3, &data);
let feedback = JointDriverHighSpeedFeedback::try_from(frame).unwrap();
let torque_from_custom = feedback.torque(Some(custom_current));
let expected_from_custom = custom_current * JointDriverHighSpeedFeedback::COEFFICIENT_4_6;
assert!((torque_from_custom - expected_from_custom).abs() < 0.0001);
}
#[test]
fn test_joint_driver_high_speed_feedback_torque_coefficients() {
assert_eq!(JointDriverHighSpeedFeedback::COEFFICIENT_1_3, 1.18125);
assert_eq!(JointDriverHighSpeedFeedback::COEFFICIENT_4_6, 0.95844);
let current_val = 1000u16; let mut data = [0u8; 8];
data[2..4].copy_from_slice(¤t_val.to_be_bytes());
let frame = PiperFrame::new_standard(ID_JOINT_DRIVER_HIGH_SPEED_BASE as u16, &data);
let feedback = JointDriverHighSpeedFeedback::try_from(frame).unwrap();
let torque = feedback.torque(None);
assert!((torque - 1.18125).abs() < 0.0001);
assert_eq!(feedback.torque_raw(), 1181); }
#[test]
fn test_gripper_status_bit_order() {
let byte = 0x41;
let status = GripperStatus::from(u8::new(byte));
assert!(status.voltage_low(), "电压应该过低");
assert!(!status.motor_over_temp(), "电机应该正常");
assert!(status.enabled(), "应该使能(Bit 6 = 1)");
assert!(!status.homed(), "应该没有回零");
}
#[test]
fn test_gripper_status_encode() {
let mut status = GripperStatus::from(u8::new(0));
status.set_voltage_low(true);
status.set_enabled(true);
status.set_homed(true);
let encoded = u8::from(status).value();
assert_eq!(encoded, 0xC1);
}
#[test]
fn test_gripper_feedback_parse() {
let travel_val = 50000i32;
let torque_val = 2500i16;
let status_byte = 0x41u8;
let mut data = [0u8; 8];
data[0..4].copy_from_slice(&travel_val.to_be_bytes());
data[4..6].copy_from_slice(&torque_val.to_be_bytes());
data[6] = status_byte;
let frame = PiperFrame::new_standard(ID_GRIPPER_FEEDBACK as u16, &data);
let feedback = GripperFeedback::try_from(frame).unwrap();
assert_eq!(feedback.travel_raw(), 50000);
assert_eq!(feedback.torque_raw(), 2500);
assert!((feedback.travel() - 50.0).abs() < 0.0001);
assert!((feedback.torque() - 2.5).abs() < 0.0001);
assert!(feedback.status.voltage_low());
assert!(feedback.status.enabled());
}
#[test]
fn test_gripper_feedback_invalid_id() {
let frame = PiperFrame::new_standard(0x999, &[0; 8]);
let result = GripperFeedback::try_from(frame);
assert!(result.is_err());
}
#[test]
fn test_gripper_feedback_invalid_length() {
let frame = PiperFrame::new_standard(ID_GRIPPER_FEEDBACK as u16, &[0; 4]);
let result = GripperFeedback::try_from(frame);
assert!(result.is_err());
}
#[test]
fn test_gripper_status_all_flags() {
let mut status = GripperStatus::from(u8::new(0));
status.set_voltage_low(true);
status.set_motor_over_temp(true);
status.set_driver_over_current(true);
status.set_driver_over_temp(true);
status.set_sensor_error(true);
status.set_driver_error(true);
status.set_enabled(true);
status.set_homed(true);
let encoded = u8::from(status).value();
assert_eq!(encoded, 0xFF); }
#[test]
fn test_driver_status_bit_order() {
let byte = 0x55;
let status = DriverStatus::from(u8::new(byte));
assert!(status.voltage_low(), "电源电压应该过低");
assert!(!status.motor_over_temp(), "电机应该正常温度");
assert!(status.driver_over_current(), "驱动器应该过流");
assert!(status.collision_protection(), "碰撞保护应该触发");
assert!(status.enabled(), "驱动器应该使能");
}
#[test]
fn test_driver_status_encode() {
let mut status = DriverStatus::from(u8::new(0));
status.set_voltage_low(true);
status.set_driver_over_current(true);
status.set_collision_protection(true);
status.set_enabled(true);
let encoded = u8::from(status).value();
assert_eq!(encoded, 0x55);
}
#[test]
fn test_joint_driver_low_speed_feedback_parse() {
let voltage_val = 240u16;
let driver_temp_val = 45i16;
let motor_temp_val = 50i8;
let status_byte = 0x44u8; let bus_current_val = 5000u16;
let mut data = [0u8; 8];
data[0..2].copy_from_slice(&voltage_val.to_be_bytes());
data[2..4].copy_from_slice(&driver_temp_val.to_be_bytes());
data[4] = motor_temp_val as u8;
data[5] = status_byte;
data[6..8].copy_from_slice(&bus_current_val.to_be_bytes());
let frame = PiperFrame::new_standard(ID_JOINT_DRIVER_LOW_SPEED_BASE as u16, &data);
let feedback = JointDriverLowSpeedFeedback::try_from(frame).unwrap();
assert_eq!(feedback.joint_index, 1);
assert_eq!(feedback.voltage, 240);
assert_eq!(feedback.driver_temp, 45);
assert_eq!(feedback.motor_temp, 50);
assert_eq!(feedback.bus_current, 5000);
assert!(feedback.status.driver_over_current());
assert!(feedback.status.enabled());
}
#[test]
fn test_joint_driver_low_speed_feedback_all_joints() {
for i in 1..=6 {
let id = ID_JOINT_DRIVER_LOW_SPEED_BASE + (i - 1) as u32;
let mut data = [0u8; 8];
data[0..2].copy_from_slice(&240u16.to_be_bytes()); data[2..4].copy_from_slice(&45i16.to_be_bytes()); data[4] = 50; data[5] = 0x40;
let frame = PiperFrame::new_standard(id as u16, &data);
let feedback = JointDriverLowSpeedFeedback::try_from(frame).unwrap();
assert_eq!(feedback.joint_index, i);
}
}
#[test]
fn test_joint_driver_low_speed_feedback_conversions() {
let mut data = [0u8; 8];
data[0..2].copy_from_slice(&240u16.to_be_bytes()); data[2..4].copy_from_slice(&45i16.to_be_bytes()); data[4] = 50; data[5] = 0x40; data[6..8].copy_from_slice(&5000u16.to_be_bytes());
let frame = PiperFrame::new_standard(ID_JOINT_DRIVER_LOW_SPEED_BASE as u16, &data);
let feedback = JointDriverLowSpeedFeedback::try_from(frame).unwrap();
assert!((feedback.voltage() - 24.0).abs() < 0.01);
assert!((feedback.driver_temp() - 45.0).abs() < 0.01);
assert!((feedback.motor_temp() - 50.0).abs() < 0.01);
assert!((feedback.bus_current() - 5.0).abs() < 0.001);
}
#[test]
fn test_joint_driver_low_speed_feedback_invalid_id() {
let frame = PiperFrame::new_standard(0x999, &[0; 8]);
let result = JointDriverLowSpeedFeedback::try_from(frame);
assert!(result.is_err());
}
#[test]
fn test_joint_driver_low_speed_feedback_invalid_length() {
let frame = PiperFrame::new_standard(ID_JOINT_DRIVER_LOW_SPEED_BASE as u16, &[0; 4]);
let result = JointDriverLowSpeedFeedback::try_from(frame);
assert!(result.is_err());
}
#[test]
fn test_joint_end_velocity_accel_feedback_parse() {
let linear_vel = 1000u16; let angular_vel = 5000u16; let linear_accel = 2000u16; let angular_accel = 3000u16;
let mut data = [0u8; 8];
data[0..2].copy_from_slice(&linear_vel.to_be_bytes());
data[2..4].copy_from_slice(&angular_vel.to_be_bytes());
data[4..6].copy_from_slice(&linear_accel.to_be_bytes());
data[6..8].copy_from_slice(&angular_accel.to_be_bytes());
let frame = PiperFrame::new_standard(ID_JOINT_END_VELOCITY_ACCEL_BASE as u16, &data);
let feedback = JointEndVelocityAccelFeedback::try_from(frame).unwrap();
assert_eq!(feedback.joint_index, 1);
assert_eq!(feedback.linear_velocity_m_s_raw, 1000);
assert_eq!(feedback.angular_velocity_rad_s_raw, 5000);
assert_eq!(feedback.linear_accel_m_s2_raw, 2000);
assert_eq!(feedback.angular_accel_rad_s2_raw, 3000);
}
#[test]
fn test_joint_end_velocity_accel_feedback_all_joints() {
for i in 1..=6 {
let id = ID_JOINT_END_VELOCITY_ACCEL_BASE + (i - 1) as u32;
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(&3000u16.to_be_bytes());
data[6..8].copy_from_slice(&4000u16.to_be_bytes());
let frame = PiperFrame::new_standard(id as u16, &data);
let feedback = JointEndVelocityAccelFeedback::try_from(frame).unwrap();
assert_eq!(feedback.joint_index, i);
}
}
#[test]
fn test_joint_end_velocity_accel_feedback_conversions() {
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(&3000u16.to_be_bytes()); data[6..8].copy_from_slice(&4000u16.to_be_bytes());
let frame = PiperFrame::new_standard(ID_JOINT_END_VELOCITY_ACCEL_BASE as u16, &data);
let feedback = JointEndVelocityAccelFeedback::try_from(frame).unwrap();
assert!((feedback.linear_velocity() - 1.0).abs() < 0.0001);
assert!((feedback.angular_velocity() - 2.0).abs() < 0.0001);
assert!((feedback.linear_accel() - 3.0).abs() < 0.0001);
assert!((feedback.angular_accel() - 4.0).abs() < 0.0001);
}
#[test]
fn test_joint_end_velocity_accel_feedback_zero() {
let data = [0u8; 8];
let frame = PiperFrame::new_standard(ID_JOINT_END_VELOCITY_ACCEL_BASE as u16, &data);
let feedback = JointEndVelocityAccelFeedback::try_from(frame).unwrap();
assert_eq!(feedback.linear_velocity_m_s_raw, 0);
assert_eq!(feedback.angular_velocity_rad_s_raw, 0);
assert_eq!(feedback.linear_accel_m_s2_raw, 0);
assert_eq!(feedback.angular_accel_rad_s2_raw, 0);
assert!((feedback.linear_velocity() - 0.0).abs() < 0.0001);
assert!((feedback.angular_velocity() - 0.0).abs() < 0.0001);
}
#[test]
fn test_joint_end_velocity_accel_feedback_invalid_id() {
let frame = PiperFrame::new_standard(0x999, &[0; 8]);
let result = JointEndVelocityAccelFeedback::try_from(frame);
assert!(result.is_err());
}
#[test]
fn test_joint_end_velocity_accel_feedback_invalid_length() {
let frame = PiperFrame::new_standard(ID_JOINT_END_VELOCITY_ACCEL_BASE as u16, &[0; 7]);
let result = JointEndVelocityAccelFeedback::try_from(frame);
assert!(result.is_err());
}
}
#[derive(Debug, Clone, Default)]
pub struct FirmwareReadFeedback {
pub firmware_data: [u8; 8],
}
impl FirmwareReadFeedback {
pub fn firmware_data(&self) -> &[u8; 8] {
&self.firmware_data
}
pub fn parse_version_string(accumulated_data: &[u8]) -> Option<String> {
if let Some(version_start) = accumulated_data.windows(3).position(|w| w == b"S-V") {
let version_length = 8;
let version_end = (version_start + version_length).min(accumulated_data.len());
let version_bytes = &accumulated_data[version_start..version_end];
Some(String::from_utf8_lossy(version_bytes).trim().to_string())
} else {
None
}
}
}
impl TryFrom<PiperFrame> for FirmwareReadFeedback {
type Error = ProtocolError;
fn try_from(frame: PiperFrame) -> Result<Self, Self::Error> {
if frame.id != ID_FIRMWARE_READ {
return Err(ProtocolError::InvalidCanId { id: frame.id });
}
if frame.len == 0 {
return Err(ProtocolError::InvalidLength {
expected: 1,
actual: 0,
});
}
let mut firmware_data = [0u8; 8];
let copy_len = (frame.len as usize).min(8);
firmware_data[..copy_len].copy_from_slice(&frame.data[..copy_len]);
Ok(Self { firmware_data })
}
}
#[cfg(test)]
mod firmware_read_tests {
use super::*;
#[test]
fn test_firmware_read_feedback_parse() {
let data = b"S-V1.6-3";
let frame = PiperFrame::new_standard(ID_FIRMWARE_READ as u16, data);
let feedback = FirmwareReadFeedback::try_from(frame).unwrap();
assert_eq!(&feedback.firmware_data[..8], data);
}
#[test]
fn test_firmware_read_feedback_parse_version_string() {
let accumulated_data = b"Some prefix S-V1.6-3\nOther data";
let version = FirmwareReadFeedback::parse_version_string(accumulated_data);
assert_eq!(version, Some("S-V1.6-3".to_string()));
}
#[test]
fn test_firmware_read_feedback_parse_version_string_fixed_length() {
let accumulated_data = b"Some prefix S-V1.6-3\nOther data";
let version = FirmwareReadFeedback::parse_version_string(accumulated_data);
assert_eq!(version, Some("S-V1.6-3".to_string()));
}
#[test]
fn test_firmware_read_feedback_parse_version_string_short() {
let accumulated_data = b"S-V1.6";
let version = FirmwareReadFeedback::parse_version_string(accumulated_data);
assert_eq!(version, Some("S-V1.6".to_string()));
}
#[test]
fn test_firmware_read_feedback_parse_version_string_invalid_utf8() {
let data = vec![b'S', b'-', b'V', 0xFF, 0xFE, b'1', b'.', b'6'];
let version = FirmwareReadFeedback::parse_version_string(&data);
assert!(version.is_some());
let version_str = version.unwrap();
assert!(version_str.contains("S-V"));
}
#[test]
fn test_firmware_read_feedback_parse_version_string_not_found() {
let accumulated_data = b"Some data without version";
let version = FirmwareReadFeedback::parse_version_string(accumulated_data);
assert_eq!(version, None);
}
#[test]
fn test_firmware_read_feedback_invalid_id() {
let frame = PiperFrame::new_standard(0x999, &[0; 8]);
let result = FirmwareReadFeedback::try_from(frame);
assert!(result.is_err());
}
#[test]
fn test_firmware_read_feedback_empty_data() {
let frame = PiperFrame::new_standard(ID_FIRMWARE_READ as u16, &[]);
let result = FirmwareReadFeedback::try_from(frame);
assert!(result.is_err());
}
}
#[derive(Debug, Clone, Copy, Default)]
pub struct ControlModeCommandFeedback {
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 TryFrom<PiperFrame> for ControlModeCommandFeedback {
type Error = ProtocolError;
fn try_from(frame: PiperFrame) -> Result<Self, Self::Error> {
if frame.id != ID_CONTROL_MODE {
return Err(ProtocolError::InvalidCanId { id: frame.id });
}
if frame.len < 6 {
return Err(ProtocolError::InvalidLength {
expected: 6,
actual: frame.len as usize,
});
}
Ok(Self {
control_mode: ControlModeCommand::try_from(frame.data[0])?,
move_mode: MoveMode::from(frame.data[1]),
speed_percent: frame.data[2],
mit_mode: MitMode::try_from(frame.data[3])?,
trajectory_stay_time: frame.data[4],
install_position: InstallPosition::try_from(frame.data[5])?,
})
}
}
#[derive(Debug, Clone, Copy, Default)]
pub struct JointControlFeedback {
pub j1_deg: i32,
pub j2_deg: i32,
pub j3_deg: i32,
pub j4_deg: i32,
pub j5_deg: i32,
pub j6_deg: i32,
}
impl JointControlFeedback {
pub fn update_from_12(&mut self, feedback: JointControl12Feedback) {
self.j1_deg = feedback.j1_deg;
self.j2_deg = feedback.j2_deg;
}
pub fn update_from_34(&mut self, feedback: JointControl34Feedback) {
self.j3_deg = feedback.j3_deg;
self.j4_deg = feedback.j4_deg;
}
pub fn update_from_56(&mut self, feedback: JointControl56Feedback) {
self.j5_deg = feedback.j5_deg;
self.j6_deg = feedback.j6_deg;
}
}
#[derive(Debug, Clone, Copy, Default)]
pub struct JointControl12Feedback {
pub j1_deg: i32,
pub j2_deg: i32,
}
impl TryFrom<PiperFrame> for JointControl12Feedback {
type Error = ProtocolError;
fn try_from(frame: PiperFrame) -> Result<Self, Self::Error> {
if frame.id != ID_JOINT_CONTROL_12 {
return Err(ProtocolError::InvalidCanId { id: frame.id });
}
if frame.len < 8 {
return Err(ProtocolError::InvalidLength {
expected: 8,
actual: frame.len as usize,
});
}
let j1_bytes = [frame.data[0], frame.data[1], frame.data[2], frame.data[3]];
let j1_deg = bytes_to_i32_be(j1_bytes);
let j2_bytes = [frame.data[4], frame.data[5], frame.data[6], frame.data[7]];
let j2_deg = bytes_to_i32_be(j2_bytes);
Ok(Self { j1_deg, j2_deg })
}
}
#[derive(Debug, Clone, Copy, Default)]
pub struct JointControl34Feedback {
pub j3_deg: i32,
pub j4_deg: i32,
}
impl TryFrom<PiperFrame> for JointControl34Feedback {
type Error = ProtocolError;
fn try_from(frame: PiperFrame) -> Result<Self, Self::Error> {
if frame.id != ID_JOINT_CONTROL_34 {
return Err(ProtocolError::InvalidCanId { id: frame.id });
}
if frame.len < 8 {
return Err(ProtocolError::InvalidLength {
expected: 8,
actual: frame.len as usize,
});
}
let j3_bytes = [frame.data[0], frame.data[1], frame.data[2], frame.data[3]];
let j3_deg = bytes_to_i32_be(j3_bytes);
let j4_bytes = [frame.data[4], frame.data[5], frame.data[6], frame.data[7]];
let j4_deg = bytes_to_i32_be(j4_bytes);
Ok(Self { j3_deg, j4_deg })
}
}
#[derive(Debug, Clone, Copy, Default)]
pub struct JointControl56Feedback {
pub j5_deg: i32,
pub j6_deg: i32,
}
impl TryFrom<PiperFrame> for JointControl56Feedback {
type Error = ProtocolError;
fn try_from(frame: PiperFrame) -> Result<Self, Self::Error> {
if frame.id != ID_JOINT_CONTROL_56 {
return Err(ProtocolError::InvalidCanId { id: frame.id });
}
if frame.len < 8 {
return Err(ProtocolError::InvalidLength {
expected: 8,
actual: frame.len as usize,
});
}
let j5_bytes = [frame.data[0], frame.data[1], frame.data[2], frame.data[3]];
let j5_deg = bytes_to_i32_be(j5_bytes);
let j6_bytes = [frame.data[4], frame.data[5], frame.data[6], frame.data[7]];
let j6_deg = bytes_to_i32_be(j6_bytes);
Ok(Self { j5_deg, j6_deg })
}
}
#[derive(Debug, Clone, Copy, Default)]
pub struct GripperControlFeedback {
pub travel_mm: i32,
pub torque_nm: i16,
pub status_code: u8,
pub set_zero: u8,
}
impl TryFrom<PiperFrame> for GripperControlFeedback {
type Error = ProtocolError;
fn try_from(frame: PiperFrame) -> Result<Self, Self::Error> {
if frame.id != ID_GRIPPER_CONTROL {
return Err(ProtocolError::InvalidCanId { id: frame.id });
}
if frame.len < 8 {
return Err(ProtocolError::InvalidLength {
expected: 8,
actual: frame.len as usize,
});
}
let travel_bytes = [frame.data[0], frame.data[1], frame.data[2], frame.data[3]];
let travel_mm = bytes_to_i32_be(travel_bytes);
let torque_bytes = [frame.data[4], frame.data[5]];
let torque_nm = bytes_to_i16_be(torque_bytes);
Ok(Self {
travel_mm,
torque_nm,
status_code: frame.data[6],
set_zero: frame.data[7],
})
}
}