Build a new application

This guide takes you from an empty repository to an application that brings up a workcell, plans a motion, moves the arm to a destination and drives an end effector.

There is no framework class to inherit. An application is a set of packages that mirror core_pick_and_place one-for-one. sorting_app is the reference implementation of that mapping, and this guide follows the same shape.

Before you start, run the example workcell (Getting started) so you have something working to compare against.

The mapping

Framework (core_pick_and_place)

Reference (sorting_app)

Yours

pick_and_place_controller

sorting_controller

<app>_controller

core_decision_taking

sorting_decision_taking

<app>_decision_taking

pick_and_place_interfaces

sorting_interfaces

<app>_interfaces

pick_and_place_cli

sorting_cli

<app>_cli

(bringup, plugins, models)

sorting_bringup, sorting_plugins, sorting_models

<app>_bringup

You do not need all of them on day one. The minimum that runs is bringup plus cli; add decision taking when the application needs to choose its own work.

Step 1 — Decide what your application does

Write this down before creating packages, because it determines which of the above you need:

  • What triggers work? A CLI command, an operator UI, a sensor, a PLC.

  • What is a cycle? For sorting_app: an item arrives, is picked, and is placed at a destination chosen from a shelf map.

  • What varies per site? Destinations, poses, speeds — these belong in configuration, not code.

Step 2 — Create the bringup package

Bringup owns launch files and configuration. It is the only package you truly need to see a robot move.

<app>_bringup/
  launch/
    app_launch/<app>.launch.py       full application
    node_launch/*.launch.py          one file per node, composed above
  config/
    gazebo/sdf/world.sdf             simulated workcell + plugins
    config_parser/ee_tip_specific/
      parameterization_config.json   speed profiles
      app_poses.json                 base and pivot poses

Copy sorting_bringup and strip it back. The pieces that must be right:

  • the world SDF loads libArmPlugin.so with the ports the driver expects (50240/50241) and the joint list of your arm

  • ``parameterization_config.json`` defines a low- and high-speed profile

  • ``app_poses.json`` defines base_pose and pivot_pose

Note

Start from the low-speed profile. High-speed profiles demand accelerations the arm may not be able to track, and an arm that cannot track its trajectory leaves the planned path — see Debug a collision.

Step 3 — Add the CLI

The CLI is how you drive the application before there is a UI. sorting_cli is a thin argparse front end that issues HTTP requests to the controller:

send_commands_parser = subparsers.add_parser('send-commands')
send_commands_parser.add_argument('cmd', choices=['grasp', 'release', 'secure'])

Keep it thin. Logic belongs in the controller, so the UI and the CLI share it.

Step 4 — Add the controller

The controller exposes the application’s operations and owns its state machine. It is a lifecycle node managed by core_supervisor: the supervisor configures and activates it, and the controller refuses work when it is not active.

Follow pick_and_place_controller: an application per capability (move_arm, send_commands, …), each with a router and a set of sub-controllers, one per operation.

Step 5 — Move the arm

Use the framework rather than calling planning services directly. move_arm_app already handles the sequence — plan to base, replay a pre-planned trajectory, re-parameterise, send, wait:

from pick_and_place_interfaces.action import MoveArm

goal = MoveArm.Goal()
goal.destination = '5'
goal.ee_tip = 'sorting'
goal.parameterization = MoveArm.Goal.LOW_SPEED_PARAMETERIZATION

Reach for C++ and Python API only when the framework genuinely cannot express what you need.

Step 6 — Drive the end effector

End effectors are Gazebo plugins in simulation and real hardware otherwise, behind the same services. conveyor_gripper_interfaces defines the contract; the commands are grasp, release and secure.

Step 7 — Add decision taking

Once the application chooses its own work rather than being told, add a decision taking package modelled on core_decision_taking: given the current state, return the next action. Keeping this separate is what lets you test the policy without a simulator.

Checklist

[ ] bringup launches a workcell and the arm holds position
[ ] CLI reaches the controller (HTTP 200) and the supervisor activates it
[ ] a low-speed motion to one destination completes
[ ] the collision monitor reports 0% for that motion
[ ] the end effector grasps and releases
[ ] the same cycle runs at the high-speed profile
[ ] configuration, not code, carries anything site-specific

Common mistakes

Calling planning services directly. The framework exists because the sequence around a plan (pre-plan, merge, re-parameterise, wait for completion) is easy to get subtly wrong.

Tuning speed by editing code. Speed lives in parameterization_config.json. If the arm collides at high speed but not low, that is a tracking problem, not a planning one.

Leaving a previous launch running. Duplicate nodes make an application accept commands and return success while nothing moves. Always confirm the previous launch is gone before starting another.