Adding a Controller¶
AirStack splits control into two roles (Controls overview), and the first decision is which one you are replacing:
- Trajectory controller — a pure-pursuit trajectory manager, not itself a feedback controller. It owns the trajectory group (spec §5): it consumes
trajectory_controller/trajectory_segment_to_addandtrajectory_override(airstack_msgs/msg/TrajectoryXYZVYaw), serves theset_trajectory_modeservice, and emitstracking_pointandlook_ahead(airstack_msgs/msg/Odometry— notnav_msgs). Reference: Trajectory Controller. - Feedback controller — closes the loop between the tracking point and the vehicle's actual state and emits the
control_setpoint(spec §6) command into the interface. Reference:pid_controller(robot/ros_ws/src/local/controls/pid_controller— no README; the cascaded position→velocity PID is described in the Trajectory Controller README's Control Architecture section).
The verified chain in every reference stack (full_default, full_droan_cpu, full_macvo, full_mighty, lite_default, lite_offload_global onboard) is:
trajectory_controller/tracking_point (airstack_msgs/Odometry, §5)
│ + odometry_conversion/odometry (§2)
▼
control/pid_controller ──► interface/cmd_roll_pitch_yawrate_thrust
(mav_msgs/RollPitchYawrateThrust, §6) ──► robot_interface → MAVROS/PX4
Both roles are onboard-only. Spec §5 and §6 names may never appear in a split stack's bridge.yaml — airstack doctor hard-errors on it. The rationale is the spec's safety floor: command authority flows through the trajectory controller (arming, safety monitoring, takeover come for free to anything publishing trajectory_override), and that floor collapses if control crosses a link that can drop. A controller can never run offboard.
This guide assumes you know the layered architecture and have flown a stack in sim. Link the Interface Conventions Specification from your README instead of restating its tables.
Package or module?¶
Decide early where the controller lives:
- In-tree package — a package under
robot/ros_ws/src/local/controls/, or a scaffolded module boundary in your fork viaairstack module create --in-tree <name>. - Module repo — shareable, version-pinned, own CI and Docker dependency layer, added with
airstack module add <url> --version <pin>. See AirStack Modules and the create-module skill.
The wiring steps below are identical either way.
Path A: replace the feedback controller (the common case)¶
Swap pid_controller for your own attitude/velocity controller. The §5 surface stays owned by the stock trajectory controller — you only consume its setpoint.
Conventions worth copying from the reference (all in pid_controller.cpp / its launch file):
- Runs as node
pid_controllerunder thecontrolnamespace (node path/robot_1/control/pid_controller); gains load from a params YAML passed withallow_substs="true". - Gains are dynamic parameters (
airstack::dynamic_paramfromairstack_common) — per-axisp/i/d/ff/min/max/constantplus a_d_alphaderivative filter — tunable at runtime withros2 param set, no rebuild between tuning iterations. - It exposes a
reset_integratorssubscription (std_msgs/msg/Empty, relative name in its namespace) so flight phases can clear integral windup; keep an equivalent if your controller integrates. - It is control-rate agnostic: it computes on every
tracking_pointmessage (the trajectory controller ticks at 20 Hz) rather than running its own timer.
1. Create the package¶
Follow the add-ros2-package skill and the Module Integration Checklist, under robot/ros_ws/src/local/controls/. Declare every topic endpoint as a launch argument defaulting to its canonical spec name — copy the pattern from pid_controller/launch/pid_controller.launch.xml (pid_controller_odometry_topic, pid_controller_tracking_point_topic, pid_controller_command_topic).
Verify: docker exec airstack-robot-desktop-1 bash -c "bws --packages-select <your_package>" exits cleanly.
2. Conform to the interchange¶
Inputs: trajectory_controller/tracking_point (§5, airstack_msgs/msg/Odometry — pose, velocity, acceleration, jerk along the trajectory) and odometry_conversion/odometry (§2, nav_msgs/msg/Odometry). Output: one §6 command dialect — interface/cmd_roll_pitch_yawrate_thrust (mav_msgs/msg/RollPitchYawrateThrust, the blessed publisher slot the PID fills today) or the alternates interface/cmd_pose / interface/cmd_velocity. Never publish tracking_point or look_ahead yourself — that is impersonating the trajectory controller, and doctor --live flags it.
Verify: with the node running under a full stack, ros2 topic info /robot_1/interface/cmd_roll_pitch_yawrate_thrust lists your node as the only publisher, and ros2 topic info /robot_1/trajectory_controller/tracking_point lists it as a subscriber.
3. Wire it into a custom stack¶
Controller variants are named stacks (see Creating a Custom Stack Topology and the single-locus rule): airstack stack new full_default full_my_controller, then in stacks/full_my_controller/launch/stack.launch.xml replace the pid_controller.launch.xml include with your controller's include. Canonical arg defaults mean a conforming controller needs no include args.
Verify: airstack up --stack full_my_controller --sim isaac --robots 1 && airstack ready succeeds and ros2 node list shows your controller in place of control/pid_controller.
4. Verify with wiring and a flight¶
airstack test -m wiring --stack full_my_controllerregenerateswiring.md;airstack stack diff full_default full_my_controllershould show exactly the controller swap.airstack doctor --live --stack full_my_controllermust be clean.- Fly it:
airstack test -m takeoff_hover_land --sim isaacsim --num-robots 1 -v— every command the vehicle receives in all four phases flows through your feedback controller, so this is the cheapest full-chain exercise (waypoint_flightadds planner behavior, not controller coverage). Thenairstack test -m autonomy --trajectory-types Circle,Figure8for tracking quality: it flies fixed trajectories straight through the controllers and records cross-track error and path RMSE — the numbers your gains actually move.
Path B: replace the trajectory controller itself¶
This is a much bigger lift: you take over the entire §5 surface, and every task server, the local planner, the feedback controller, and the safety monitor are your clients. Study the Trajectory Controller README end-to-end before writing code. Your replacement must:
- Consume
trajectory_controller/trajectory_segment_to_add(appended segments from the local planner, stitched into the live trajectory near the current tracking position) andtrajectory_override(complete replacement trajectories from takeoff/land and fixed-trajectory task servers). - Serve
trajectory_controller/set_trajectory_mode(airstack_msgs/srv/TrajectoryMode) with all five modes —ROBOT_POSE,TRACK,ADD_SEGMENT,PAUSE,REWIND— including the transition semantics in the README. Clients includetakeoff_landing_planner, the fixed-trajectory task, the local planner, anddrone_safety_monitor. - Publish
tracking_pointandlook_ahead(airstack_msgs/msg/Odometry), keepinglook_aheadfar enough ahead for the planner's cycle, plustrajectory_completion_percentage(std_msgs/msg/Float32), which task servers use to judge goal completion. - Broadcast the four TF frames (
tracking_point,look_ahead_point, and their_stabilizedvariants) the README documents. - Honor the safety integration: the safety monitor commands
PAUSE/REWINDthrough your mode service on state-estimate timeout — this path is why §5 is a spec, and it must work before anything else does.
Wire it the same way as Path A step 3 (replace the trajectory_controller.launch.xml include; the fixed_trajectory_task.launch.xml include comes from the same package — replace or keep it deliberately), then verify as in Path A step 4 — but fly with airstack test -m waypoint_flight --sim isaacsim --num-robots 1 -v: its chain (takeoff → NavigateTask route → land) exercises the whole surface — TRACK/override for takeoff and landing, ADD_SEGMENT stitching under a continuously replanning local planner, and the mode transitions between them — where takeoff_hover_land never enters ADD_SEGMENT.
See also¶
- Interface Conventions Specification — §2 odometry, §5 trajectory group, §6 control_setpoint
- Controls overview · Trajectory Controller README
- Creating a Custom Stack Topology · AirStack Stacks
- Module Integration Checklist
- Skills: add-ros2-package · create-module