Create a Custom Arm Model

The RDK provides a number of built-in arm models that implement the API protocol of the arm subtype of component, such as the ur5e, xArm6, and xArm7.

If you have a robot arm that is not already supported by an existing arm model, create a module that provides a customized model for your arm to program. Then you can control it with the arm API, or use it with services like Motion, just as you would with a built-in model.

See Modular Resources for more information.

Get your arm’s kinematics file

The way arms move through space is more complicated than Viam’s other components. Because of this, an arm, unlike other components, requires a kinematic configuration file describing its geometry. This provides the necessary information for the frame system service and built-in motion service to work with the arm.

Find a pre-built kinematics file:

  • viam-server will work with URDF (United Robot Description Format) kinematics files, which are currently the standard for ROS drivers. You can find URDF “robot descriptions” for many industrial robot arm models on GitHub that are compatible with the Viam platform.

Create your own kinematics file:

Create a new directory. Give it the name your want you call your custom model of arm. Save the JSON as your-model-name.json inside of this directory. While completing the following step, make sure to save any new files that you generate inside of this same directory.

Create a custom arm model as a modular resource

To create a custom arm model, code a module in Python with the module support libraries provided by Viam’s SDKs:

  1. Code a new resource model implementing all methods the Viam RDK requires in the API definition of its built-in subtype, rdk:component:arm, which is available for reference on GitHub. Import your custom model and API into the main program and register the new resource model with the Python SDK.

  2. Code a main program that starts the module after adding your desired resources from the registry. This main program is the “entry point” to your module.

  3. Compile or package the module into a single executable that can receive a socket argument from viam-server, open the socket, and start the module at the entry point.

Code a new resource model

Save the following two files, my_modular_arm.py and __init__.py, on your computer and edit the code as applicable.

This module template registers a modular resource implementing Viam’s built-in Arm API (rdk:service:arm) as a new model, "myarm":

  • my_modular_arm.py implements a custom model of the arm component built-in resource, "myarm".

    Click to view sample code from my_modular_arm.py
    import asyncio
    import os
    from typing import Any, ClassVar, Dict, Mapping, Optional, Tuple
    from typing_extensions import Self
    
    from viam.components.arm import Arm, JointPositions, KinematicsFileFormat, Pose
    from viam.operations import run_with_operation
    from viam.proto.app.robot import ComponentConfig
    from viam.proto.common import ResourceName
    from viam.resource.base import ResourceBase
    from viam.resource.types import Model, ModelFamily
    
    
    class MyModularArm(Arm):
        # Subclass the Viam Arm component and implement the required functions
        MODEL: ClassVar[Model] = Model(ModelFamily("acme", "demo"), "myarm")
    
        def __init__(self, name: str):
            # Starting joint positions
            self.joint_positions = JointPositions(values=[0, 0, 0, 0, 0, 0])
            super().__init__(name)
    
        @classmethod
        def new(cls, config: ComponentConfig, dependencies: Mapping[ResourceName, ResourceBase]) -> Self:
            arm = cls(config.name)
            return arm
    
        async def get_end_position(self, extra: Optional[Dict[str, Any]] = None, **kwargs) -> Pose:
            raise NotImplementedError()
    
        async def move_to_position(self, pose: Pose, extra: Optional[Dict[str, Any]] = None, **kwargs):
            raise NotImplementedError()
    
        async def get_joint_positions(self, extra: Optional[Dict[str, Any]] = None, **kwargs) -> JointPositions:
            return self.joint_positions
    
        @run_with_operation
        async def move_to_joint_positions(self, positions: JointPositions, extra: Optional[Dict[str, Any]] = None, **kwargs):
            operation = self.get_operation(kwargs)
    
            self.is_stopped = False
    
            # Simulate the length of time it takes for the arm to move to its new joint position
            for x in range(10):
                await asyncio.sleep(1)
    
                # Check if the operation is cancelled and, if it is, stop the arm's motion
                if await operation.is_cancelled():
                    await self.stop()
                    break
    
            self.joint_positions = positions
            self.is_stopped = True
    
        async def stop(self, extra: Optional[Dict[str, Any]] = None, **kwargs):
            self.is_stopped = True
    
        async def is_moving(self) -> bool:
            return not self.is_stopped
    
        async def get_kinematics(self, **kwargs) -> Tuple[KinematicsFileFormat.ValueType, bytes]:
            dirname = os.path.dirname(__file__)
            filepath = os.path.join(dirname, "./xarm6_kinematics.json")
            with open(filepath, mode="rb") as f:
                file_data = f.read()
            return (KinematicsFileFormat.KINEMATICS_FILE_FORMAT_SVA, file_data)
    

  • __init__.py registers the my_modular_arm custom model and API helper functions with the Python SDK.

    Click to view sample code from __init__.py
    from viam.components.arm import Arm
    from viam.resource.registry import Registry, ResourceCreatorRegistration
    from .my_modular_arm import MyModularArm
    
    
    Registry.register_resource_creator(Arm.SUBTYPE, MyModularArm.MODEL, ResourceCreatorRegistration(MyModularArm.new))
    

Code a main entry point program

main.py is the Python module’s entry point file. When executed, it initializes the myarm custom model and API helper functions from the registry.

Click to view sample code from main.py
import asyncio

from viam.module.module import Module
from viam.components.arm import Arm

from .my_modular_arm import MyModularArm


async def main():
    """This function creates and starts a new module, after adding all desired
    resources. Resources must be pre-registered. For an example, see the
    `__init__.py` file.
    """

    module = Module.from_args()
    module.add_model_from_registry(Arm.SUBTYPE, MyModularArm.MODEL)
    await module.start()


if __name__ == "__main__":
    asyncio.run(main())

Prepare the module for execution

To add a module to the configuration of your machine, you need to have an executable that:

  • runs your module when executed,
  • takes a local socket as a command line argument, and
  • exits cleanly when sent a termination signal.

Your options for completing this step are flexible, as this file does not need to be in a raw binary format.

One option is to create and save a new shell script (.sh) that runs your module at your entry point (main program) file.

For example:

#!/bin/sh
cd `dirname $0`

exec python3 -m <your-module-directory-name>.<main-program-filename-without-extension> $@

This script uses exec to be able to ensure that termination signals reach the python process. If you omit this, be sure to handle the forwarding of termination signals accordingly.

To make this shell script executable, run the following command in your terminal:

sudo chmod +x <FILEPATH>/<FILENAME>

Ensure any dependencies for your module (including the Python SDK) are installed on your machine’s computer. Your executable will be run by viam-server as root, so dependencies need to be available to the root user.

Configure the module and modular resource on your machine

Follow these configuration instructions to add your custom resource to your machine.

Have questions, or want to meet other people working on robots? Join our Community Discord.

If you notice any issues with the documentation, feel free to file an issue or edit this file.