use num::FromPrimitive;
use num_derive::FromPrimitive;
use serde::{Deserialize, Serialize};
use tokio_modbus::prelude::*;
use tokio_serial::SerialPortBuilderExt;
static FLAG_ACT: u8 = 1 << 0;
static FLAG_GTO: u8 = 1 << 3;
static FLAG_ATR: u8 = 1 << 4;
static FLAG_ADR: u8 = 1 << 5;
#[repr(u8)]
#[derive(Debug, Clone, FromPrimitive, PartialEq, Serialize, Deserialize)]
pub enum ActivationStatus {
InReset,
InProgess,
NotUsed,
Completed,
}
#[repr(u8)]
#[derive(Debug, Clone, FromPrimitive, PartialEq, Serialize, Deserialize)]
pub enum ObjDetectStatus {
InMotion,
DetectedOpen,
DetectedClose,
NoObject,
}
impl ObjDetectStatus {
pub fn detected_obj(&self) -> bool {
match self {
ObjDetectStatus::DetectedClose | ObjDetectStatus::DetectedOpen => true,
_ => false,
}
}
}
#[repr(u8)]
#[derive(Debug, Clone, FromPrimitive, PartialEq, Error, Serialize, Deserialize)]
pub enum GripperFault {
NoFault = 0x00,
ActionDelay = 0x05,
NotActivated = 0x07,
OverHeated = 0x08,
NoComm = 0x09,
UnderVoltage = 0x0A,
Releasing = 0x0B,
InternalFault = 0x0C,
AcitivationFault = 0x0D,
OverCurrent = 0x0E,
AutomaticReleaseCompleted = 0x0F,
}
impl GripperFault {
pub fn reset_required(&self) -> bool {
self.clone() as u8 >= 0x0A
}
}
impl std::fmt::Display for GripperFault {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{:?}", self)
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GripperStatus {
pub act: bool,
pub gto: bool,
pub sta: ActivationStatus,
pub obj: ObjDetectStatus,
pub fault: GripperFault,
pub k_flt: u8,
pub pos_req: u8,
pub pos: u8,
pub current: u8,
}
impl GripperStatus {
pub fn parse(bytes: Vec<u8>) -> Self {
let act = bytes[0] & 1 != 0;
let gto = bytes[0] & 8 != 0;
let sta = ActivationStatus::from_u8((bytes[0] >> 4) & 0b11).unwrap();
let obj = ObjDetectStatus::from_u8((bytes[0] >> 6) & 0b11).unwrap();
let fault = GripperFault::from_u8(bytes[2]).unwrap();
let k_flt = bytes[2] & 0xF0;
let pos_req = bytes[3];
let pos = bytes[4];
let current = bytes[5];
GripperStatus {
act,
gto,
sta,
obj,
fault,
k_flt,
pos_req,
pos,
current,
}
}
}
impl From<Vec<u8>> for GripperStatus {
fn from(value: Vec<u8>) -> Self {
GripperStatus::parse(value)
}
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct GripperCommand {
pub act: bool,
pub gto: bool,
pub atr: bool,
pub ard: bool,
pub pos_req: u8,
pub speed: u8,
pub force: u8,
}
impl GripperCommand {
pub fn new() -> Self {
Self::default()
}
pub fn act(mut self, b: bool) -> Self {
self.act = b;
self
}
pub fn gto(mut self, b: bool) -> Self {
self.gto = b;
self
}
pub fn atr(mut self, b: bool) -> Self {
self.atr = b;
self
}
pub fn ard(mut self, b: bool) -> Self {
self.ard = b;
self
}
pub fn pos_req(mut self, b: u8) -> Self {
self.pos_req = b;
self
}
pub fn speed(mut self, b: u8) -> Self {
self.speed = b;
self
}
pub fn force(mut self, b: u8) -> Self {
self.force = b;
self
}
pub fn to_array(&self) -> [u16; 3] {
let mut req = 0;
if self.act {
req |= FLAG_ACT;
}
if self.gto {
req |= FLAG_GTO;
}
if self.atr {
req |= FLAG_ATR;
}
if self.ard {
req |= FLAG_ADR;
}
[
u16::from_be_bytes([req, 0]),
u16::from_be_bytes([0, self.pos_req]),
u16::from_be_bytes([self.speed, self.force]),
]
}
}
pub struct RobotiqGripper {
ctx: client::Context,
}
impl RobotiqGripper {
pub const DEFAULT_SLAVE_ID: u8 = 9;
pub fn new(ctx: client::Context) -> Self {
Self { ctx }
}
pub fn from_path_slave_id(
path: impl Into<String>,
slave_id: u8,
) -> Result<Self, std::io::Error> {
let port = tokio_serial::new(path.into(), 115_200)
.data_bits(tokio_serial::DataBits::Eight)
.stop_bits(tokio_serial::StopBits::One)
.parity(tokio_serial::Parity::None)
.timeout(std::time::Duration::from_millis(500))
.open_native_async()?;
let ctx = rtu::attach_slave(port, Slave(slave_id));
Ok(Self::new(ctx))
}
pub fn from_path(path: impl Into<String>) -> Result<Self, std::io::Error> {
Self::from_path_slave_id(path, Self::DEFAULT_SLAVE_ID)
}
pub async fn write_async(&mut self, cmd: GripperCommand) -> Result<(), RobotiqError> {
Ok(self
.ctx
.write_multiple_registers(1000, &cmd.to_array())
.await??)
}
pub async fn read_async(&mut self) -> Result<GripperStatus, RobotiqError> {
Ok(self
.ctx
.read_holding_registers(2000, 3)
.await??
.into_iter()
.map(|u| u.to_be_bytes())
.flatten()
.collect::<Vec<_>>()
.into())
}
pub async fn reset(&mut self) -> Result<&mut Self, RobotiqError> {
self.write_async(GripperCommand::default()).await?;
Ok(self)
}
pub async fn activate(&mut self) -> Result<&mut Self, RobotiqError> {
self.write_async(GripperCommand::new().act(true)).await?;
Ok(self)
}
pub async fn await_activate(&mut self) -> Result<&mut Self, RobotiqError> {
loop {
let status = self.read_async().await?;
if status.sta == ActivationStatus::Completed {
break;
}
if status.fault != GripperFault::NoFault {
return Err(status.fault.into());
}
std::thread::sleep(std::time::Duration::from_millis(100));
}
Ok(self)
}
pub async fn go_to(
&mut self,
pos_req: u8,
speed: u8,
force: u8,
) -> Result<&mut Self, RobotiqError> {
let cmd = GripperCommand::new()
.act(true)
.gto(true)
.pos_req(pos_req)
.speed(speed)
.force(force);
self.write_async(cmd).await?;
Ok(self)
}
pub async fn await_go_to(&mut self) -> Result<ObjDetectStatus, RobotiqError> {
loop {
let status = self.read_async().await?;
if status.obj != ObjDetectStatus::InMotion {
self.activate().await?;
return Ok(status.obj);
}
if status.fault != GripperFault::NoFault {
return Err(status.fault.into());
}
std::thread::sleep(std::time::Duration::from_millis(100));
}
}
pub async fn automatic_release(&mut self, open: bool) -> Result<&mut Self, RobotiqError> {
let cmd = GripperCommand::new().act(true).atr(true).ard(open);
self.write_async(cmd).await?;
Ok(self)
}
pub async fn await_automatic_release(&mut self) -> Result<&mut Self, RobotiqError> {
loop {
let status = self.read_async().await?;
match status.fault {
GripperFault::Releasing => {}
GripperFault::AutomaticReleaseCompleted => return Ok(self),
_ => return Err(status.fault.into()),
}
std::thread::sleep(std::time::Duration::from_millis(100));
}
}
}
impl Drop for RobotiqGripper {
fn drop(&mut self) {
tokio::task::block_in_place(move || {
tokio::runtime::Handle::current().block_on(async move {
let _ = self.ctx.disconnect().await;
});
});
}
}
use thiserror::Error;
#[derive(Debug, Error)]
pub enum RobotiqError {
#[error("std io error, serial comm error")]
IOError(#[from] std::io::Error),
#[error("Modbus protocol or transport errros.")]
ModbusError(#[from] tokio_modbus::Error),
#[error("A server (slave) exception.")]
ModbusException(#[from] tokio_modbus::Exception),
#[error("gripper fault")]
GripperFault(#[from] GripperFault),
}