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 (:doc:`../getting_started/index`) so you have something working to compare against. The mapping ----------- .. list-table:: :header-rows: 1 :widths: 34 30 36 * - Framework (``core_pick_and_place``) - Reference (``sorting_app``) - Yours * - ``pick_and_place_controller`` - ``sorting_controller`` - ``_controller`` * - ``core_decision_taking`` - ``sorting_decision_taking`` - ``_decision_taking`` * - ``pick_and_place_interfaces`` - ``sorting_interfaces`` - ``_interfaces`` * - ``pick_and_place_cli`` - ``sorting_cli`` - ``_cli`` * - (bringup, plugins, models) - ``sorting_bringup``, ``sorting_plugins``, ``sorting_models`` - ``_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. .. code-block:: text _bringup/ launch/ app_launch/.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 :doc:`../howto/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: .. code-block:: python 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: .. code-block:: python 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 :doc:`../reference/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 --------- .. code-block:: text [ ] 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.