Skip to content

Commit 28bf7ae

Browse files
committed
gripper control is functional
1 parent 2357d42 commit 28bf7ae

File tree

2 files changed

+169
-48
lines changed

2 files changed

+169
-48
lines changed

teleop_demo_python/hardware/robotiq_gripper.py

Lines changed: 124 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -6,35 +6,39 @@
66
from enum import Enum
77
from typing import Union, Tuple, OrderedDict
88

9+
910
class RobotiqGripper:
1011
"""
1112
Communicates with the gripper directly, via socket with string commands, leveraging string names for variables.
1213
"""
14+
1315
# WRITE VARIABLES (CAN ALSO READ)
14-
ACT = 'ACT' # act : activate (1 while activated, can be reset to clear fault status)
15-
GTO = 'GTO' # gto : go to (will perform go to with the actions set in pos, for, spe)
16-
ATR = 'ATR' # atr : auto-release (emergency slow move)
17-
ADR = 'ADR' # adr : auto-release direction (open(1) or close(0) during auto-release)
18-
FOR = 'FOR' # for : force (0-255)
19-
SPE = 'SPE' # spe : speed (0-255)
20-
POS = 'POS' # pos : position (0-255), 0 = open
16+
ACT = "ACT" # act : activate (1 while activated, can be reset to clear fault status)
17+
GTO = "GTO" # gto : go to (will perform go to with the actions set in pos, for, spe)
18+
ATR = "ATR" # atr : auto-release (emergency slow move)
19+
ADR = "ADR" # adr : auto-release direction (open(1) or close(0) during auto-release)
20+
FOR = "FOR" # for : force (0-255)
21+
SPE = "SPE" # spe : speed (0-255)
22+
POS = "POS" # pos : position (0-255), 0 = open
2123
# READ VARIABLES
22-
STA = 'STA' # status (0 = is reset, 1 = activating, 3 = active)
23-
PRE = 'PRE' # position request (echo of last commanded position)
24-
OBJ = 'OBJ' # object detection (0 = moving, 1 = outer grip, 2 = inner grip, 3 = no object at rest)
25-
FLT = 'FLT' # fault (0=ok, see manual for errors if not zero)
24+
STA = "STA" # status (0 = is reset, 1 = activating, 3 = active)
25+
PRE = "PRE" # position request (echo of last commanded position)
26+
OBJ = "OBJ" # object detection (0 = moving, 1 = outer grip, 2 = inner grip, 3 = no object at rest)
27+
FLT = "FLT" # fault (0=ok, see manual for errors if not zero)
2628

27-
ENCODING = 'UTF-8' # ASCII and UTF-8 both seem to work
29+
ENCODING = "UTF-8" # ASCII and UTF-8 both seem to work
2830

2931
class GripperStatus(Enum):
3032
"""Gripper status reported by the gripper. The integer values have to match what the gripper sends."""
33+
3134
RESET = 0
3235
ACTIVATING = 1
3336
# UNUSED = 2 # This value is currently not used by the gripper firmware
3437
ACTIVE = 3
3538

3639
class ObjectStatus(Enum):
3740
"""Object status reported by the gripper. The integer values have to match what the gripper sends."""
41+
3842
MOVING = 0
3943
STOPPED_OUTER_OBJECT = 1
4044
STOPPED_INNER_OBJECT = 2
@@ -51,7 +55,9 @@ def __init__(self):
5155
self._min_force = 0
5256
self._max_force = 255
5357

54-
def connect(self, hostname: str, port: int, socket_timeout: float = 2.0) -> None:
58+
def connect(
59+
self, hostname: str, port: int, socket_timeout: float = 2.0
60+
) -> None:
5561
"""Connects to a gripper at the given address.
5662
:param hostname: Hostname or ip.
5763
:param port: Port.
@@ -75,7 +81,7 @@ def _set_vars(self, var_dict: OrderedDict[str, Union[int, float]]):
7581
cmd = "SET"
7682
for variable, value in var_dict.items():
7783
cmd += f" {variable} {str(value)}"
78-
cmd += '\n' # new line is required for the command to finish
84+
cmd += "\n" # new line is required for the command to finish
7985
# atomic commands send/rcv
8086
with self.command_lock:
8187
self.socket.sendall(cmd.encode(self.ENCODING))
@@ -107,13 +113,15 @@ def _get_var(self, variable: str):
107113
# note some special variables (like FLT) may send 2 bytes, instead of an integer. We assume integer here
108114
var_name, value_str = data.decode(self.ENCODING).split()
109115
if var_name != variable:
110-
raise ValueError(f"Unexpected response {data} ({data.decode(self.ENCODING)}): does not match '{variable}'")
116+
raise ValueError(
117+
f"Unexpected response {data} ({data.decode(self.ENCODING)}): does not match '{variable}'"
118+
)
111119
value = int(value_str)
112120
return value
113121

114122
@staticmethod
115123
def _is_ack(data: str):
116-
return data == b'ack'
124+
return data == b"ack"
117125

118126
def _reset(self):
119127
"""
@@ -134,12 +142,13 @@ def rq_reset(gripper_socket="1"):
134142
"""
135143
self._set_var(self.ACT, 0)
136144
self._set_var(self.ATR, 0)
137-
while (not self._get_var(self.ACT) == 0 or not self._get_var(self.STA) == 0):
145+
while (
146+
not self._get_var(self.ACT) == 0 or not self._get_var(self.STA) == 0
147+
):
138148
self._set_var(self.ACT, 0)
139149
self._set_var(self.ATR, 0)
140150
time.sleep(0.5)
141151

142-
143152
def activate(self, auto_calibrate: bool = True):
144153
"""Resets the activation flag in the gripper, and sets it back to one, clearing previous fault flags.
145154
:param auto_calibrate: Whether to calibrate the minimum and maximum positions based on actual motion.
@@ -171,12 +180,18 @@ def rq_activate_and_wait(gripper_socket="1"):
171180
"""
172181
if not self.is_active():
173182
self._reset()
174-
while (not self._get_var(self.ACT) == 0 or not self._get_var(self.STA) == 0):
183+
while (
184+
not self._get_var(self.ACT) == 0
185+
or not self._get_var(self.STA) == 0
186+
):
175187
time.sleep(0.01)
176188

177189
self._set_var(self.ACT, 1)
178190
time.sleep(1.0)
179-
while (not self._get_var(self.ACT) == 1 or not self._get_var(self.STA) == 3):
191+
while (
192+
not self._get_var(self.ACT) == 1
193+
or not self._get_var(self.STA) == 3
194+
):
180195
time.sleep(0.01)
181196

182197
# auto-calibrate position range if desired
@@ -186,7 +201,10 @@ def rq_activate_and_wait(gripper_socket="1"):
186201
def is_active(self):
187202
"""Returns whether the gripper is active."""
188203
status = self._get_var(self.STA)
189-
return RobotiqGripper.GripperStatus(status) == RobotiqGripper.GripperStatus.ACTIVE
204+
return (
205+
RobotiqGripper.GripperStatus(status)
206+
== RobotiqGripper.GripperStatus.ACTIVE
207+
)
190208

191209
def get_min_position(self) -> int:
192210
"""Returns the minimum position the gripper can reach (open position)."""
@@ -221,26 +239,49 @@ def auto_calibrate(self, log: bool = True) -> None:
221239
:param log: Whether to print the results to log.
222240
"""
223241
# first try to open in case we are holding an object
224-
(position, status) = self.move_and_wait_for_pos(self.get_open_position(), 64, 1)
225-
if RobotiqGripper.ObjectStatus(status) != RobotiqGripper.ObjectStatus.AT_DEST:
226-
raise RuntimeError(f"Calibration failed opening to start: {str(status)}")
242+
(position, status) = self.move_and_wait_for_pos(
243+
self.get_open_position(), 64, 1
244+
)
245+
if (
246+
RobotiqGripper.ObjectStatus(status)
247+
!= RobotiqGripper.ObjectStatus.AT_DEST
248+
):
249+
raise RuntimeError(
250+
f"Calibration failed opening to start: {str(status)}"
251+
)
227252

228253
# try to close as far as possible, and record the number
229-
(position, status) = self.move_and_wait_for_pos(self.get_closed_position(), 64, 1)
230-
if RobotiqGripper.ObjectStatus(status) != RobotiqGripper.ObjectStatus.AT_DEST:
231-
raise RuntimeError(f"Calibration failed because of an object: {str(status)}")
254+
(position, status) = self.move_and_wait_for_pos(
255+
self.get_closed_position(), 64, 1
256+
)
257+
if (
258+
RobotiqGripper.ObjectStatus(status)
259+
!= RobotiqGripper.ObjectStatus.AT_DEST
260+
):
261+
raise RuntimeError(
262+
f"Calibration failed because of an object: {str(status)}"
263+
)
232264
assert position <= self._max_position
233265
self._max_position = position
234266

235267
# try to open as far as possible, and record the number
236-
(position, status) = self.move_and_wait_for_pos(self.get_open_position(), 64, 1)
237-
if RobotiqGripper.ObjectStatus(status) != RobotiqGripper.ObjectStatus.AT_DEST:
238-
raise RuntimeError(f"Calibration failed because of an object: {str(status)}")
268+
(position, status) = self.move_and_wait_for_pos(
269+
self.get_open_position(), 64, 1
270+
)
271+
if (
272+
RobotiqGripper.ObjectStatus(status)
273+
!= RobotiqGripper.ObjectStatus.AT_DEST
274+
):
275+
raise RuntimeError(
276+
f"Calibration failed because of an object: {str(status)}"
277+
)
239278
assert position >= self._min_position
240279
self._min_position = position
241280

242281
if log:
243-
print(f"Gripper auto-calibrated to [{self.get_min_position()}, {self.get_max_position()}]")
282+
print(
283+
f"Gripper auto-calibrated to [{self.get_min_position()}, {self.get_max_position()}]"
284+
)
244285

245286
def move(self, position: int, speed: int, force: int) -> Tuple[bool, int]:
246287
"""Sends commands to start moving towards the given position, with the specified speed and force.
@@ -259,10 +300,19 @@ def clip_val(min_val, val, max_val):
259300
clip_for = clip_val(self._min_force, force, self._max_force)
260301

261302
# moves to the given position with the given speed and force
262-
var_dict = OrderedDict([(self.POS, clip_pos), (self.SPE, clip_spe), (self.FOR, clip_for), (self.GTO, 1)])
303+
var_dict = OrderedDict(
304+
[
305+
(self.POS, clip_pos),
306+
(self.SPE, clip_spe),
307+
(self.FOR, clip_for),
308+
(self.GTO, 1),
309+
]
310+
)
263311
return self._set_vars(var_dict), clip_pos
264312

265-
def move_and_wait_for_pos(self, position: int, speed: int, force: int) -> Tuple[int, ObjectStatus]: # noqa
313+
def move_and_wait_for_pos(
314+
self, position: int, speed: int, force: int
315+
) -> Tuple[int, ObjectStatus]: # noqa
266316
"""Sends commands to start moving towards the given position, with the specified speed and force, and
267317
then waits for the move to complete.
268318
:param position: Position to move to [min_position, max_position]
@@ -282,10 +332,49 @@ def move_and_wait_for_pos(self, position: int, speed: int, force: int) -> Tuple[
282332

283333
# wait until not moving
284334
cur_obj = self._get_var(self.OBJ)
285-
while RobotiqGripper.ObjectStatus(cur_obj) == RobotiqGripper.ObjectStatus.MOVING:
335+
while (
336+
RobotiqGripper.ObjectStatus(cur_obj)
337+
== RobotiqGripper.ObjectStatus.MOVING
338+
):
286339
cur_obj = self._get_var(self.OBJ)
287340

288341
# report the actual position and the object status
289342
final_pos = self._get_var(self.POS)
290343
final_obj = cur_obj
291-
return final_pos, RobotiqGripper.ObjectStatus(final_obj)
344+
return final_pos, RobotiqGripper.ObjectStatus(final_obj)
345+
346+
347+
def calc_gripper_position(min_pos: int, max_pos: int, percentage: float) -> int:
348+
"""Calculates the gripper position based on a percentage of the range between min and max positions.
349+
:param min_pos: Minimum position of the gripper.
350+
:param max_pos: Maximum position of the gripper.
351+
:param percentage: Percentage of the range to calculate the position for (0.0 to 1.0).
352+
:return: Calculated position as an integer.
353+
"""
354+
if not (0.0 <= percentage <= 1.0):
355+
raise ValueError("Percentage must be between 0.0 and 1.0.")
356+
return int(min_pos + (max_pos - min_pos) * percentage)
357+
358+
359+
if __name__ == "__main__":
360+
ip = "192.168.50.195"
361+
362+
def log_info(gripper):
363+
print(
364+
f"Pos: {str(gripper.get_current_position()): >3} "
365+
f"Open: {gripper.is_open(): <2} "
366+
f"Closed: {gripper.is_closed(): <2} "
367+
)
368+
369+
print("Creating gripper...")
370+
gripper = RobotiqGripper()
371+
print("Connecting to gripper...")
372+
gripper.connect(ip, 63352)
373+
print("Activating gripper...")
374+
gripper.activate()
375+
376+
print("Testing gripper...")
377+
gripper.move_and_wait_for_pos(255, 255, 255)
378+
log_info(gripper)
379+
gripper.move_and_wait_for_pos(0, 255, 255)
380+
log_info(gripper)

0 commit comments

Comments
 (0)