Docs›Custom Commands

Custom Commands

Custom commands let you publish to any ROS topic on your robot from the CTRL+R webapp, beyond the built-in movement and navigation controls. Each command is a named, one-shot publish bound to a keyboard key or gamepad button.

Overview

There are two roles in the custom-commands workflow:

  • Robot owner—defines the available commands on the robot (topic, message type, default payload). Definitions are stored on the agent under ros.custom_commands in /etc/ctrlr_agent/config.json.
  • Operator—binds the available commands to keyboard keys or gamepad buttons during a teleop session. Bindings are saved per user, per robot.
Capability gate

Custom commands require an agent on protocol 0.6 or newer. Older agents will not advertise the capability and the UI will be hidden for that robot.

Supported Message Types

Buttons are one-shot—they fire a single publish per press. To keep that safe, only message types whose payload has a well-defined "single value" are allowed. Continuous actuator types like geometry_msgs/Twist are intentionally excluded—a one-shot button bound to Twist would leave the actuator running indefinitely. Use the built-in joystick / WASD cmd_vel path for continuous motion.

Message TypePayload ShapeExample Use
std_msgs/Bool{ data: true | false }Toggle lights, enable/disable a subsystem
std_msgs/Int32{ data: <i32> }Select a mode, set a discrete level
std_msgs/Float32{ data: <number> }Set a target speed, gain, or threshold
std_msgs/String{ data: "<text>" }Trigger a named behavior, play a sound
std_msgs/Empty{}Fire-and-forget trigger (e-stop, take photo)
geometry_msgs/PoseStampedheader + pose (position + orientation)Send a navigation goal to a saved pose
PoseStamped timestamps

The agent overwrites header.stamp with the current time at publish, per ROS convention. You don't need to set it when defining the command.

Defining Commands (Robot Owner)

Commands are defined per-robot from the Edit Robot page:

Organizations have a second option. Under Command HQ → Customizations → ROS Commands, you can save commands at the organization level instead of setting them up on each robot separately. They take the same three things as a per-robot command: the topic, the message type, and the payload. When adding one, you can load the available commands from a connected robot rather than typing them in. Useful when a fleet shares the same hardware and you want one place to manage the actions your operators get.

  1. Open the robot's Edit Robot page and connect to the live config session.
  2. Scroll to the Custom Commands panel and click Add Command.
  3. Fill in:
    • Name—display label shown to operators.
    • Topic—ROS topic to publish on, e.g. /lights/toggle.
    • Message type—one of the supported types above.
    • Default payload—the value to publish when the command fires. The editor renders a typed form per message type (checkbox for Bool, number field for Int32/Float32, etc.).
  4. Click Save. The webapp sends an agent.config.update message with the new ros.custom_commands list; the agent validates against the allowlist and persists the definition to disk.
Lazy advertise

The agent doesn't advertise the ROS publisher until the command is fired for the first time in a session, then caches the publisher handle. No robot restart is required after adding, editing, or removing a command.

Binding to Inputs (Operator)

Bindings live per (user, robot) and persist across sessions:

  1. Start a teleop session with the robot.
  2. Open Input Bindings from the session toolbar. The modal lists every command currently defined on the robot.
  3. Switch between the Keyboard and Gamepad tabs and assign a key or button to each command you want to use.
  4. Click Save to persist the bindings to your account.
Triggers count as buttons

Gamepad triggers (LT/RT) are treated as one-shot buttons—they fire when the analog value crosses the 0.5 threshold, not continuously while held.

Firing Commands

During a teleop session:

  • Press a bound key or button—the webapp sends an agent.command.execute envelope over the WebRTC data channel with the command ID and payload.
  • The agent looks up the definition, validates the payload against the message-type schema, publishes on the configured ROS topic, and returns an agent.command.response with success / error.
  • A toast surfaces the outcome (success or the agent's error message).

Notes & Limitations

  • Publish-only. Custom commands publish to ROS; they do not subscribe. Sensor readback from custom topics is planned for a future release.
  • One-shot only. Each press fires one publish. Continuous bindings from sticks/triggers (e.g. right stick → head pan/tilt) are not yet supported.
  • Defense in depth. The agent re-validates the payload at execute time against the registered message type, even though the webapp also validates when editing—a stale binding or corrupted payload will be rejected cleanly.
  • Per-robot bindings. If you operate multiple robots, you bind keys separately for each. The set of available commands also comes from each robot's own configuration.