Architecture

The stack is 79 ROS 2 packages across 20 repositories. They form layers: each one depends on those below it and knows nothing about those above.

application      sorting_app                  <- what you write
   |
framework        core_pick_and_place          <- move_arm, decision taking, CLI
   |             core_supervisor              <- lifecycle orchestration
   |
services         core_motion_planning         <- plan, IK/FK, collision checking
   |             core_motion_control          <- execute on a real or simulated arm
   |             core_modeling                <- workcell description, joint states
   |
contract         core_interfaces              <- 110 msg / srv / action definitions
   |
foundation       core_types  core_utils  core_logger  core_tf
   |
simulation       core_plugins  mesh_models    <- Gazebo plugins, arm and EE models

The layer that matters most when you start is contract. Packages do not link against each other’s internals; they exchange the messages and services in Interfaces. If you want to know what a subsystem can do, read its interfaces first.

Which layer am I writing in?

You are…

Work in

building a new robot application

application — see Build a new application

adding a behaviour every application should share

framework (core_pick_and_place)

changing how motions are planned or executed

services

adding a new arm or gripper

simulation (mesh_models) plus configuration

Most work belongs in the application layer. Reaching into services usually means either the framework is missing something, or an interface needs extending.

The request path

A pick-and-place cycle moves through the layers like this:

CLI / UI
  -> supervisor            activates the application's lifecycle nodes
  -> application           decides what to do next (which destination)
  -> move_arm (framework)  plans + sends the trajectory
     -> motion_planning    /compute_motion_plan, /check_robot_collision
     -> motion_control     queues the trajectory, drives the arm
        -> ArmPlugin       (simulation only) applies joint commands in Gazebo
  -> end effector          grasp / release

Two properties of this path are worth internalising early.

The same driver runs against simulation and hardware. motion_control_ros speaks the Motoman simple_message protocol over TCP. In simulation core_pluginsArmPlugin implements the other end of that protocol on ports 50240/50241, so nothing above the driver knows whether it is talking to a real robot. Code that works in simulation is the code that runs on hardware.

Planning and execution are separate. motion_planning produces a time-parameterised trajectory; motion_control executes it. A trajectory that plans cleanly can still be executed badly — see Debug a collision.

Applications are a template, not a framework call

There is no Application base class to inherit. An application is a set of packages that mirror core_pick_and_place one-for-one:

Framework package

Your application provides

pick_and_place_controller

a controller (HTTP/UI entry point)

core_decision_taking

decision taking (what to do next)

pick_and_place_interfaces

your own msg/srv/action

pick_and_place_cli

a CLI

move_arm_app, pre_planner_app

bringup and any custom plugins

sorting_app is the reference implementation of that mapping. Build a new application walks through it.

Configuration is part of the API

Behaviour that looks like code is often configuration, and getting it wrong produces symptoms that look like bugs:

  • parameterization_config.json — velocity and acceleration limits per speed profile. Too aggressive and the arm cannot track the trajectory it was given.

  • app_poses.json — base and pivot poses the application plans between.

  • the ArmPlugin block in the world SDF — how joint commands reach the simulated arm, including drive_mode.

See How-to guides for what each key does and what breaks when it is wrong.