What is CTRL+R?
CTRL+R is making robot operations easy. Experience one unified interface for full robotic control, designed to be intuitive for first-time operators, yet powerful and customizable for advanced robotics teams managing diverse fleets.
For robotics teams: CTRL+R is your one-stop-shop to operate, manage, and monitor your entire robot fleet. No more building in-house software or switching between tools — CTRL+R has everything baked in to help your fleet scale as your company grows.
For retail, construction, and more: CTRL+R gives teams real-time visibility into physical operations, helping them monitor site conditions, verify execution, and make faster decisions from anywhere.
What makes us different?
CTRL+R allows individuals and teams to operate and manage robotic fleets without building extensive software stacks. There is no vendor lock-in and our agent works on any robot running ROS or a manufacturer ROS SDK. CTRL+R is extremely easy to integrate with an existing software stack, taking just minutes to get up and running.
Ready to get started?
Use Cases
Coming soon.
Supported Platforms
Operating System
The CTRL+R agent requires Ubuntu 18.04 or newer.
ROS
The agent is compatible with both ROS1 and ROS2. For smoother operations we recommend using ROS2 (Foxy or newer).
Architectures
| Architecture | Example Hardware |
|---|---|
ARMv7 | Older embedded compute boards |
AArch64 | NVIDIA Jetson, newer Raspberry Pi models |
x86_64 | Standard desktop / server (Intel and AMD) |
Before You Begin
The following prerequisites apply to both individuals and organizations.
Docker
Your robot must have Docker installed. Install via apt — do not install via the Snap Store, as snap Docker has filesystem and permission restrictions that break bind mounts.
See the official Docker install guide.
ROS (recommended)
We recommend your robot is running ROS. The agent is compatible with both ROS1 and ROS2 — for smoother operations we recommend ROS2 (Foxy or newer). See for more information.
rosbridge_server
If you plan to use ROS to control your robot, you must have rosbridge_server installed on your robot.
Zenoh (optional)
Zenoh can be used as an alternative video source to ROS. See the Zenoh installation guide for setup instructions.
Getting Started
For Individuals
This section contains instructions for individuals getting set up operating robots. For organizations, please see .
Please read the section before completing the following steps.
- Go to the CTRL+R client from our website
- From the dashboard, click the Add Robot button
- Follow the webapp instructions and create your robot listing
- Once you reach the Robot Setup page, connect to your robot via SSH and follow the displayed instructions
Configuring Your Robot
Overview
Configuration can be updated at any time while the robot is connected, including ROS topic names and video parameters. There are two places in the webapp to do this:
- Robot Setup page — available during initial setup
- Edit Robot page — available at any time after setup
When the CTRL+R agent is installed, a configuration file is saved locally on the robot at:
/etc/ctrlr_agent/config.jsonThis file contains four main parameter sections:
- Core — These parameters should not be changed. They are essential for identifying the robot.
- Robot — These parameters relate to your robot's video feed.
- ROS Topics — These topics are used by the agent to communicate with your robot's ROS stack.
- Metrics — Controls whether the agent streams CPU and battery usage to the webapp.
Robot Parameters
| Parameter | Default |
|---|---|
| Image Format | Jpeg |
| Resolution | 640×480 |
| Video Source | Ros |
ROS Topics
These topics are used by the agent to communicate with your robot's ROS stack.
| Parameter | Default | Message Type | Description |
|---|---|---|---|
| Camera Raw | /camera/image_raw | sensor_msgs/Image | Raw camera frames |
| Camera JPEG | /camera/image_raw/compressed | sensor_msgs/CompressedImage | Compressed camera frames |
| Velocity | /cmd_vel | geometry_msgs/Twist | Movement commands published by the agent |
| Nav Goal | /ctrlr/nav/goal | geometry_msgs/PoseStamped | Autonomous navigation goals |
| Nav Cancel | /ctrlr/nav/cancel | std_msgs/Empty | Cancel active navigation |
| Nav Home | /ctrlr/nav/home | std_msgs/Empty | Send robot home |
| Nav Status | /ctrlr/nav/status | std_msgs/String | Navigation status published by the robot |
"started", "completed", or "failed". Anything else is ignored with a warning.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_commandsin/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.
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 Type | Payload Shape | Example 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/PoseStamped | header + pose (position + orientation) | Send a navigation goal to a saved pose |
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:
- Open the robot's Edit Robot page and connect to the live config session.
- Scroll to the Custom Commands panel and click Add Command.
- 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.).
- Click Save. The webapp sends an
agent.config.updatemessage with the newros.custom_commandslist; the agent validates against the allowlist and persists the definition to disk.
Binding to Inputs (Operator)
Bindings live per (user, robot) and persist across sessions:
- Start a teleop session with the robot.
- Open Input Bindings from the session toolbar. The modal lists every command currently defined on the robot.
- Switch between the Keyboard and Gamepad tabs and assign a key or button to each command you want to use.
- Click Save to persist the bindings to your account.
Firing Commands
During a teleop session:
- Press a bound key or button — the webapp sends an
agent.command.executeenvelope 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.responsewithsuccess/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.
Two-way Audio
Two-way audio lets you talk to people near the robot and hear them in real time during a teleop session. Audio is carried over the same WebRTC connection as video and is gated by a push-to-talk button on the operator side.
Overview
- Operator → robot: your browser captures your microphone and sends it to the robot's speakers.
- Robot → operator: the robot captures its microphone and sends it to your browser.
- Push-to-talk: the operator mic is muted by default. Hold
Space(or press the on-screen mic button) to transmit.
audio.enabled.Hardware Setup
To use two-way audio, the robot needs a microphone and speakers connected to its host computer:
- Microphone — USB mic, USB headset, or 3.5mm mic plugged into the robot's audio input.
- Speakers — USB speakers, the robot's built-in audio jack, or a Bluetooth/HDMI output (anything ALSA can route to).
Plug the devices in before starting the agent so ALSA enumerates them at startup. If you add a device after the agent is running, restart the agent.
Enabling Audio
Open the robot's Edit Robot page (or Robot Setup during initial setup), connect the configuration session, and look for the Audio group in the live config table.
- Set Two-way Voice to
Enabled. - Restart the agent for the change to take effect — the audio pipeline is wired up at startup, not on the fly. From the robot host:
docker restart ctrlr-agentIf you don't restart, the toggle will save but no audio will flow.
Selecting Devices
By default the agent uses GStreamer's auto-detect: autoaudiosrc for the mic and autoaudiosink for speakers. That works on many robots, but if you need a specific device (e.g. a USB mic when the robot also has an HDMI input it would otherwise prefer), set the Microphone Device and Speaker Device fields to ALSA device names.
To list available devices, run on the robot:
arecord -L # list inputs (microphones)
aplay -L # list outputs (speakers)Pick the name you want (typically a plughw:CARD=<name>,DEV=<n> entry — the plughw form lets ALSA convert sample rates if needed), paste it into the matching field in the webapp, and press Enter to save.
arecord -L shows a long list, the ones you usually want are the plughw:CARD=... entries for the physical device you plugged in, or the default alias if your system is already configured to route to it.Using It In a Session
- Start a teleop session for the robot.
- Click the microphone button in the session controls. The first time, the browser will prompt you for microphone permission — accept it.
- The mic stays muted until you transmit. To talk:
- Hold
Space— push-to-talk. Mic transmits while held, mutes on release. - Click the mic button — latches transmit on; click again to mute.
- Hold
- Robot audio plays automatically through your browser once the session connects.
audio.enabled is true and the agent was restarted afterward; verify the device names with arecord -L / aplay -L; and confirm browser microphone permission isn't blocked for the CTRL+R site.Locations
Coming soon.
Command HQ
Coming soon.
FoxGlove Data Integration
Coming soon.
Troubleshooting
Coming soon.
