import abc
from typing import Any, Dict, Final, List, Mapping, Optional
from viam.components import KinematicsReturn
from viam.components.component_base import ComponentBase
from viam.resource.types import API, RESOURCE_NAMESPACE_RDK, RESOURCE_TYPE_COMPONENT
from . import JointPositions, Mesh, MoveOptions, Pose
[docs]
class Arm(ComponentBase):
"""
Arm represents a physical robot arm that exists in three-dimensional space.
This acts as an abstract base class for any drivers representing specific
arm implementations. This cannot be used on its own. If the ``__init__()`` function is
overridden, it must call the ``super().__init__()`` function.
::
from viam.components.arm import Arm
# To use move_to_position:
from viam.components.arm import Pose
# To use move_to_joint_positions and move_through_joint_positions:
from viam.components.arm import JointPositions
# To use move_through_joint_positions:
from viam.components.arm import MoveOptions
# To use get_3d_models:
from viam.components.arm import Mesh
For more information, see `Arm component <https://docs.viam.com/dev/reference/apis/components/arm/>`_.
"""
API: Final = API(RESOURCE_NAMESPACE_RDK, RESOURCE_TYPE_COMPONENT, "arm") # pyright: ignore [reportIncompatibleVariableOverride]
[docs]
@abc.abstractmethod
async def get_end_position(
self,
*,
extra: Optional[Dict[str, Any]] = None,
timeout: Optional[float] = None,
**kwargs,
) -> Pose:
"""
Get the current position of the end of the arm expressed as a ``Pose``.
::
my_arm = Arm.from_robot(robot=machine, name="my_arm")
# Get the end position of the arm as a Pose.
pos = await my_arm.get_end_position()
Returns:
Pose: A representation of the arm's current position as a 6 DOF (six degrees of freedom) pose.
The ``Pose`` is composed of values for location and orientation with respect to the origin.
Location is expressed as distance, which is represented by x, y, and z coordinate values.
Orientation is expressed as an orientation vector, which is represented by o_x, o_y, o_z, and theta values.
For more information, see `Arm component <https://docs.viam.com/dev/reference/apis/components/arm/#getendposition>`_.
"""
...
[docs]
@abc.abstractmethod
async def move_to_position(
self,
pose: Pose,
*,
extra: Optional[Dict[str, Any]] = None,
timeout: Optional[float] = None,
**kwargs,
):
"""
Move the end of the arm to the Pose specified in ``pose``.
::
my_arm = Arm.from_robot(robot=machine, name="my_arm")
# Create a Pose for the arm.
examplePose = Pose(x=5, y=5, z=5, o_x=5, o_y=5, o_z=5, theta=20)
# Move your arm to the Pose.
await my_arm.move_to_position(pose=examplePose)
Args:
pose (Pose): The destination ``Pose`` for the arm. The ``Pose`` is composed of values for location and orientation
with respect to the origin.
Location is expressed as distance, which is represented by x, y, and z coordinate values.
Orientation is expressed as an orientation vector, which is represented by o_x, o_y, o_z, and theta values.
For more information, see `Arm component <https://docs.viam.com/dev/reference/apis/components/arm/#movetoposition>`_.
"""
...
[docs]
@abc.abstractmethod
async def move_to_joint_positions(
self,
positions: JointPositions,
*,
extra: Optional[Dict[str, Any]] = None,
timeout: Optional[float] = None,
**kwargs,
):
"""
Move each joint on the arm to the corresponding angle specified in ``positions``.
::
my_arm = Arm.from_robot(robot=machine, name="my_arm")
# Declare a list of values with your desired rotational value for each joint on
# the arm. This example is for a 5dof arm.
degrees = [0.0, 45.0, 0.0, 0.0, 0.0]
# Declare a new JointPositions with these values.
jointPos = JointPositions(values=degrees)
# Move each joint of the arm to the position these values specify.
await my_arm.move_to_joint_positions(positions=jointPos)
Args:
positions (JointPositions): The destination ``JointPositions`` for the arm.
For more information, see `Arm component <https://docs.viam.com/dev/reference/apis/components/arm/#movetojointpositions>`_.
"""
...
[docs]
@abc.abstractmethod
async def move_through_joint_positions(
self,
positions: List[JointPositions],
options: Optional[MoveOptions] = None,
*,
extra: Optional[Dict[str, Any]] = None,
timeout: Optional[float] = None,
**kwargs,
):
"""
Move the arm through the given joint positions in the order they are specified,
obeying the velocity and acceleration limits in ``options``.
::
my_arm = Arm.from_robot(robot=machine, name="my_arm")
# Move through two waypoints, capping joint speed and acceleration.
await my_arm.move_through_joint_positions(
positions=[
JointPositions(values=[0, 45, 0, 0, 0, 0]),
JointPositions(values=[0, 0, 0, 0, 0, 0]),
],
options=MoveOptions(max_vel_degs_per_sec=15.0, max_acc_degs_per_sec2=30.0),
)
Args:
positions (List[JointPositions]): The waypoints to move through, in order.
options (Optional[MoveOptions]): Optional kinematic ceilings obeyed at every
point along the trajectory. ``None`` means no limits are requested.
Note:
Unlike the Go SDK, this method does not validate the requested positions
against the arm's joint limits before sending them, because the Python SDK
cannot yet parse a kinematics model. Implementations are responsible for
their own limit checking.
Every scalar field on ``MoveOptions`` (``max_vel_degs_per_sec``,
``max_acc_degs_per_sec2``, ``max_tcp_speed``) also has explicit presence: an
unset field reads back as ``0.0``, indistinguishable from an explicitly-set
zero. Implementations must check ``options.HasField("max_vel_degs_per_sec")``
(and likewise for the other scalar fields) before applying it as a ceiling —
reading an unset field's ``0.0`` directly would misread "no limit requested"
as "do not move". Per the proto definition, ``max_vel_degs_per_sec`` is
ignored whenever ``max_vel_degs_per_sec_joints`` is set, and likewise
``max_acc_degs_per_sec2`` is ignored whenever ``max_acc_degs_per_sec2_joints``
is set; implementations should honor only the per-joint limit in that case,
not both.
An empty ``positions`` list is passed through to the implementation
unchanged; implementations must handle it, typically as a no-op.
For more information, see `Arm component <https://docs.viam.com/dev/reference/apis/components/arm/#movethroughjointpositions>`_.
"""
...
[docs]
@abc.abstractmethod
async def get_joint_positions(
self,
*,
extra: Optional[Dict[str, Any]] = None,
timeout: Optional[float] = None,
**kwargs,
) -> JointPositions:
"""
Get the JointPositions representing the current position of the arm.
::
my_arm = Arm.from_robot(robot=machine, name="my_arm")
# Get the current position of each joint on the arm as JointPositions.
pos = await my_arm.get_joint_positions()
Returns:
JointPositions: The current ``JointPositions`` for the arm.
``JointPositions`` can have one attribute, ``values``, a list of joint positions with rotational values (degrees)
and translational values (mm).
For more information, see `Arm component <https://docs.viam.com/dev/reference/apis/components/arm/#getjointpositions>`_.
"""
...
[docs]
@abc.abstractmethod
async def stop(
self,
*,
extra: Optional[Dict[str, Any]] = None,
timeout: Optional[float] = None,
**kwargs,
):
"""
Stop all motion of the arm. It is assumed that the arm stops immediately.
::
my_arm = Arm.from_robot(robot=machine, name="my_arm")
# Stop all motion of the arm. It is assumed that the arm stops immediately.
await my_arm.stop()
For more information, see `Arm component <https://docs.viam.com/dev/reference/apis/components/arm/#stop>`_.
"""
...
[docs]
@abc.abstractmethod
async def is_moving(self) -> bool:
"""
Get if the arm is currently moving.
::
my_arm = Arm.from_robot(robot=machine, name="my_arm")
# Stop all motion of the arm. It is assumed that the arm stops immediately.
await my_arm.stop()
# Print if the arm is currently moving.
print(await my_arm.is_moving())
Returns:
bool: Whether the arm is moving.
For more information, see `Arm component <https://docs.viam.com/dev/reference/apis/components/arm/#ismoving>`_.
"""
...
[docs]
@abc.abstractmethod
async def get_kinematics(
self, *, extra: Optional[Dict[str, Any]] = None, timeout: Optional[float] = None, **kwargs
) -> KinematicsReturn:
"""
Get the kinematics information associated with the arm.
::
my_arm = Arm.from_robot(robot=machine, name="my_arm")
# Get the kinematics information associated with the arm.
kinematics = await my_arm.get_kinematics()
# Get the format of the kinematics file.
k_file = kinematics[0]
# Get the byte contents of the file.
k_bytes = kinematics[1]
Returns:
Tuple[KinematicsFileFormat.ValueType, bytes]: A tuple containing two values; the first [0] value represents the format of the
file, either in URDF format (``KinematicsFileFormat.KINEMATICS_FILE_FORMAT_URDF``) or
Viam's kinematic parameter format (spatial vector algebra) (``KinematicsFileFormat.KINEMATICS_FILE_FORMAT_SVA``),
and the second [1] value represents the byte contents of the file.
If available, a third [2] value provides meshes keyed by URDF filepath.
See ``get_3d_models`` for meshes keyed by model name instead.
For more information, see `Arm component <https://docs.viam.com/dev/reference/apis/components/arm/#getkinematics>`_.
"""
...
[docs]
@abc.abstractmethod
async def get_3d_models(
self, *, extra: Optional[Dict[str, Any]] = None, timeout: Optional[float] = None, **kwargs
) -> Mapping[str, Mesh]:
"""
Get the 3D models associated with the arm, keyed by name.
::
my_arm = Arm.from_robot(robot=machine, name="my_arm")
# Get the arm's 3D models.
models = await my_arm.get_3d_models()
for name, mesh in models.items():
print(name, mesh.content_type, len(mesh.mesh))
Returns:
Mapping[str, Mesh]: The arm's 3D models keyed by name. Each ``Mesh`` carries a
``content_type`` (for example ``"ply"``) and the raw ``mesh`` bytes in that format.
This is distinct from ``get_kinematics``'s third return value, which keys meshes
by URDF filepath rather than by model name.
Note:
Implementations with no models must return an empty mapping, not ``None``.
For more information, see `Arm component <https://docs.viam.com/dev/reference/apis/components/arm/#get3dmodels>`_.
"""
...