Robot Systems and Components

A system is one robot arm plus everything attached to it. This guide covers how a System is assembled, the component catalogue, custom tools, and adding a system from a connected robot.

Anatomy of a system

world
 └─ system base                          header: pose_in_parent in its parent
     └─ external_robot_axis (ExternalAxis, optional)   e.g. a linear rail
         └─ manipulator_adapter (Adapter, optional)
             └─ manipulator (Manipulator)            the arm, e.g. "arm"
                 └─ wrist_sensor (WristSensor, optional)
                     └─ tool_adapter (ToolAdapter, optional)
                         └─ tool[] (Tool, optional)
 └─ external_workpiece[] (ExternalWorkpiece, optional)  e.g. a rotary table

The header places the system base at pose_in_parent in its parent. The pose is converted to the world once, when the system is saved, so the system does not follow its parent if the parent moves later. Get returns parent world and the pose in the world. The parent cannot be the system itself, one of its links, or a frame attached to them.

System fields

Field

Type

Description

header

ConfigHeader

Required. System name, parent and the pose of the system base in the parent.

control_box_serial_number

string

Required. For example "CX01-01-T1-000001".

manipulator

Manipulator

Required. name (unique in the system, e.g. "arm") and arm_serial_number, the internal (engineering) serial number, e.g. "A02L-00-M3-000001". Optional lower_limit/upper_limit/home_position in rad.

external_robot_axis

ExternalAxis

Axis the arm is mounted on (rail, pan-tilt platform, …). Requires name, manufacturer and model. Optional home_position (m or rad).

manipulator_adapter

Adapter

Adapter on the external axis flange.

wrist_sensor

WristSensor

Force/torque sensor. Requires name, manufacturer and model, e.g. manufacturer="flexiv", model="FT03-D1".

tool_adapter

ToolAdapter

Catalogue adapter. Requires name, manufacturer and model. Optional rotation (rad).

tool

repeated Tool

Catalogue or custom tools (name, manufacturer, model).

external_workpiece

repeated ExternalWorkpiece

Actuated devices that move workpieces independently of the arm.

An Adapter is a rigid offset: xyz (m) and rpy (rad) give the pose of the child base in the parent flange, with optional mass and inertia.

def create_system(name, arm_serial_number, control_box_serial_number, px, py, pz):
    system = System(
        header=make_header(name, px=px, py=py, pz=pz),
        control_box_serial_number=control_box_serial_number,
    )

    system.manipulator_adapter.name = "manipulator_adapter"
    system.manipulator_adapter.mass = 0.0
    system.manipulator_adapter.inertia.extend([0.0] * 9)
    system.manipulator_adapter.xyz.extend([0.0, 0.0, 0.0])
    system.manipulator_adapter.rpy.extend([0.0, 0.0, 0.0])

    system.manipulator.name = "arm"
    system.manipulator.arm_serial_number = arm_serial_number
    system.manipulator.lower_limit.extend([-2.76] * 7)
    system.manipulator.upper_limit.extend([2.76] * 7)
    system.manipulator.home_position.extend([0.0] * 7)

    return system

Component catalogue

ComponentsService (client.system_config) lists the components the server knows about. Each list call takes google.protobuf.Empty:

  • ListManipulators returns names;

  • ListToolAdapters, ListTools, ListCustomToolAdapters, ListCustomTools, ListExternalRobotAxes, ListExternalWorkpieceAxes return items of ManufacturerModel(manufacturer, model).

Use a listed manufacturer/model pair in Tool, ToolAdapter or ExternalAxis.

print("")
print("Listing tool adapters...")
try:
    tool_adapters_response = client.system_config.stub.ListToolAdapters(empty_pb2.Empty())
    if (tool_adapters_response.status.code == 0):
        for item in tool_adapters_response.items:
            print(f"Tool adapter manufacturer: {item.manufacturer}, model: {item.model}")   

except grpc.RpcError as e:
    print(f"ListToolAdapters failed: {e}")

Custom tools and adapters

Define your own end effector with CustomTool (tcp pose relative to the tool base, plus optional inertial, collision and visual) or CustomToolAdapter (one or more flange poses). Save it to the catalogue, then reference it from a Tool by manufacturer and model:

print("")
print("Creating custom tool...")
dummy_shape = Shape(box=Box(size=[0.05, 0.05, 0.05]))
dummy_origin = SE3Pose(
    position=Vector3(x=0.0, y=0.0, z=0.05),
    rotation=Quaternion(x=0.0, y=0.0, z=0.0, w=1.0)
)
custom_tool = CustomTool(
    manufacturer="example_manufacturer",
    model="example_model",
    tcp=SE3Pose(
        position=Vector3(x=0.0, y=0.0, z=0.1),
        rotation=Quaternion(x=0.0, y=0.0, z=0.0, w=1.0)
    ),
    collision=[Collision(origin=dummy_origin, shape=dummy_shape)],
    visual=[Visual(origin=dummy_origin, shape=dummy_shape)]
)
try:
    save_tool_response = client.system_config.stub.SaveCustomTool(
        SaveCustomToolRequest(custom_tool=custom_tool)
    )
    if save_tool_response.status.code == 0:
        print("Custom tool saved successfully.")
    else:
        print(f"SaveCustomTool returned status: {save_tool_response.status}")
except grpc.RpcError as e:
    print(f"SaveCustomTool failed: {e}")

# Helper to construct header
def make_header(name, parent="world", px=0.0, py=0.0, pz=0.0, rw=1.0, rx=0.0, ry=0.0, rz=0.0):
    return ConfigHeader(
        name=name,
        parent=parent,
        pose_in_parent=SE3Pose(
            position=Vector3(x=px, y=py, z=pz),
            rotation=Quaternion(x=rx, y=ry, z=rz, w=rw)
        )
    )

print("")
print("Saving robot system with custom tool to the scene...")
sys2 = System(
    header=make_header("system_2", px=1.0, py=0.0, pz=0.0),
    control_box_serial_number="CX01-01-T1-000002",
    manipulator=Manipulator(
        name="arm",
        arm_serial_number="A02L-00-M3-000002"
    ),
    tool=[Tool(
        name="example_tool",
        manufacturer="example_manufacturer",
        model="example_model"
    )]
)
try:
    client.config.stub.Save(SaveConfigRequest(system=sys2))
except grpc.RpcError:
    pass

RemoveCustomTool and RemoveCustomToolAdapter take a ManufacturerModel.

Adding a system from a real robot

SystemService.AddConnectedSystem connects to a physical robot (through RDK) by serial number, reads its configuration, and saves it as a system in the active workcell. Afterwards, read it back with ConfigService.Get.

print("Add a system by connecting to the RDK and saving its configuration to the PDK server")
system_name = "system_1"
serial_number = "Rizon4-062065"
try:
    # Load the system from RDK
    add_system_response = client.system.stub.AddConnectedSystem(AddConnectedSystemRequest(name=system_name, serial_number=serial_number))

    if add_system_response.status.code == 0:
        print(f"Successfully added system: {system_name} with serial number: {serial_number}")
    else:
        print(f"Failed to add system: {system_name} with serial number: {serial_number}")

except grpc.RpcError as e:
    print(f"System operation failed: {e}")

# 4. List existing systems in the scene
try:
    list_systems_response = client.config.stub.List(ListConfigRequest(type=ConfigType.CONFIG_TYPE_SYSTEM))
    print("Existing systems in the scene:")
    for sys in list_systems_response.names:
        print(f"- {sys}")
except grpc.RpcError as e:
    print(f"Failed to list systems: {e}")


Requirements: the robot is reachable and RDK remote mode is enabled. Change the serial number to your robot’s. AddConnectedSystem takes the robot’s public serial number (as used by RDK, e.g. "Rizon4-062065"), not the internal arm_serial_number stored in Manipulator.

To reposition a connected system, Get it, edit header.pose_in_parent, and Save it again:

print("Get system_1 and set its pose in the scene...")
try:
    system_response = client.config.stub.Get(
        TypeNameConfigRequest(type=ConfigType.CONFIG_TYPE_SYSTEM, name=system_name)
    )
    if system_response.status.code == 0:
        system = system_response.system
        system.header.pose_in_parent.position.x = 0.73
        system.header.pose_in_parent.position.y = -1.7655
        system.header.pose_in_parent.position.z = 0.5

        # Save the updated system configuration to the PDK server
        save_response = client.config.stub.Save(SaveConfigRequest(system=system))
        if save_response.status.code == 0:
            print(f"Successfully set pose for system: {system_name}")
        else:
            print(f"Failed to set pose for system: {system_name}")
    else:
        print(f"Failed to get system: {system_name}")
except grpc.RpcError as e:
    print(f"System operation failed: {e}")

Other system queries

SystemService (client.system):

GetUrdf(GetUrdfRequest(name))

URDF of one system.

GetArmSerialNumber(GetArmSerialNumberRequest(system_name))

arm_serial_number and arm_public_serial_number.

GetCalibrationStatus(GetCalibrationStatusRequest(system_name))

calibrated is true when a calibration exists and was applied.

Default versus current joint position

A system in the scene has two joint vectors. Neither is the real robot’s position; moving a real or simulated robot is done with the Flexiv RDK.

  • Default joint position: Manipulator.home_position (rad) and ExternalAxis.home_position (m or rad) in the saved System. Change it by editing the system and saving it with ConfigService.Save.

  • Current joint position: StateService.GetJointPosition / SetJointPosition (client.system_state). The joint positions the system has in the scene right now, used by planning, collision checks and the system’s frames. A system starts at its default position when it is added or its configuration changes. SetJointPosition takes effect immediately and is not saved.

GetJointPositionRequest and SetJointPositionRequest take system_name and an optional joint_group: JOINT_GROUP_SYSTEM (the default, all actuated joints), JOINT_GROUP_ARM, JOINT_GROUP_EXTERNAL_ROBOT_AXIS or JOINT_GROUP_EXTERNAL_WORKPIECE_AXIS. SetJointPositionRequest.joint_position has one value per joint in the group.

from flexiv.pdk.system.state_service_pb2 import SetJointPositionRequest
from flexiv.pdk.system.components.joint_group_pb2 import JointGroup

res = client.system_state.stub.SetJointPosition(SetJointPositionRequest(
    system_name="system_1",
    joint_group=JointGroup.JOINT_GROUP_ARM,
    joint_position=[0.0, -0.7, 0.0, 1.57, 0.0, 0.7, 0.0],
))
if res.status.code != 0:
    raise RuntimeError(res.status.message)