Source code for robotblockset.franka.grippers_pandapy

"""Franka gripper interface via panda_py.

High-level interface for controlling gripper for Franka Emika Panda / FR3 robots via panda_py.

Copyright (c) 2024 Jozef Stefan Institute

Authors: Leon Zlajpah
"""

try:
    from panda_py import libfranka  # pyright: ignore[reportMissingImports]
except Exception as e:
    raise ModuleNotFoundError(f"{e}\nPython bindings for the Panda not installed. \nYou can install them through pip:\n  pip install panda-python") from None

from typing import Any, Optional
from time import sleep

from robotblockset.grippers import gripper
from robotblockset.robots import robot
from robotblockset.tools import isscalar


[docs] class panda_gripper(gripper): """PandaPy-backed interface for controlling the Franka Emika Panda gripper. Attributes ---------- Name : str Identifier string for the gripper instance. Robot : robot or None Robot instance this gripper is attached to, if available. hostname : str Hostname or IP address of the Panda/FR3 controller used by the gripper. """
[docs] def __init__(self, robot: Optional[robot], name: str = "panda_gripper", hostname: Optional[str] = None, **kwargs: Any) -> None: """ Initializes the gripper object with default attributes. Parameters ---------- robot : robot, optional robot, optional An instance of the robot class that the gripper is attached to. name : str, optional Name identifier for the gripper instance (default is 'panda_gripper'). hostname : str, optional IP address or hostname of the Panda / FR3 robot. Has to be defined if no robot is selected. **kwargs : Any Additional keyword arguments for future extensions or configuration. Returns ------- None This constructor initializes the Panda gripper interface in place. Raises ------ ValueError If neither a robot nor a non-empty hostname is provided. """ gripper.__init__(self, **kwargs) self.Name = name self.Robot = robot if robot is None: if not isinstance(hostname, str) or not hostname.strip(): raise ValueError("A non-empty hostname is required when robot is None") self.hostname = hostname else: self.hostname = robot.hostname self.gripper = libfranka.Gripper(self.hostname) self.gripper_state = self.gripper.read_once() self._width_grasp = 0 self._width = self.gripper_state.width self._width_max = self.gripper_state.max_width self._state = -1 self._speed = 0.0 self._speed_max = 0.5 self._force_max = 70.0 self._verbose = 1 self.DebugMessage("Created")
@property def width(self) -> float: """ Get the current width of the gripper. Returns ------- float Current gripper width. """ self.gripper_state = self.gripper.read_once() self._width = self.gripper_state.width return self.gripper_state.width
[docs] def is_grasped(self) -> bool: """ Check if the gripper is currently grasping an object. Returns ------- bool True if the gripper is grasping an object, False otherwise. """ self.gripper_state = self.gripper.read_once() return self.gripper_state.is_grasped
[docs] def GetState(self) -> str: """ Returns the current state of the gripper. Returns ------- str The state of the gripper, either "Opened", "Closed", or "Undefined". """ self.gripper_state = self.gripper.read_once() if self.gripper_state.is_grasped: self._state = 1 return "Closed" else: self._state = 0 return "Opened"
[docs] def Grasp(self, width: float, speed: float = 0.1, force: float = 5, eps: float = 0.005, **kwargs: Any) -> bool: """ Grasps an object with the gripper at a specified width. Parameters ---------- width : float The width to which the gripper should close to grasp the object. speed : float, optional The speed at which the gripper should move (default is 0.1). force : float, optional The force to apply during the grasp (default is 5). eps : float, optional The tolerance for the grasping width (default is 0.005). **kwargs : dict Additional arguments for the grasp operation, may include a "width" key. Returns ------- bool True if the gripper successfully grasps, False otherwise. Raises ------ ValueError If width, speed, force, or epsilon is outside the supported range. """ width, speed = self._validate_width_speed(width, speed) if not isscalar(force) or force <= 0 or force > self._force_max: raise ValueError(f"Force must be in the range (0, {self._force_max}]") if not isscalar(eps) or eps < 0 or eps > self._width_max: raise ValueError(f"Epsilon must be in the range [0, {self._width_max}]") force = float(force) eps = float(eps) _success = self.gripper.grasp(width, speed, force, epsilon_inner=eps, epsilon_outer=eps) if _success: self._state = 1 else: self._state = -1 return _success
[docs] def Move(self, width: float, speed: float = 0.1) -> bool: """ Moves the gripper to a specified width. Parameters ---------- width : float The width to which the gripper should move. speed : float, optional The speed at which the gripper should move (default is 0.1). Returns ------- bool True if the gripper successfully moves, False otherwise. Raises ------ ValueError If width or speed is outside the supported range. """ width, speed = self._validate_width_speed(width, speed) self._state = -1 return self.gripper.move(width, speed)
def _validate_width_speed(self, width: float, speed: float) -> tuple[float, float]: """Validate and normalize a gripper motion target.""" if not isscalar(width) or width < 0 or width > self._width_max: raise ValueError(f"Width must be in the range [0, {self._width_max}]") if not isscalar(speed) or speed <= 0 or speed > self._speed_max: raise ValueError(f"Speed must be in the range (0, {self._speed_max}]") return float(width), float(speed)
[docs] def Homing(self) -> bool: """ Resets the gripper to an open state. Returns ------- bool True if the gripper successfully homes, False otherwise. """ self._state = 0 sleep(1) # Wait for the gripper to settle before homing return self.gripper.homing()