Quickstart
Get a robot streaming video to operate.adamohq.com in under five minutes. Pick the language you want to write your robot in — Python, Rust, or C — install the SDK, write a short program that opens a camera, run it.
You don’t need to write an operator — operate.adamohq.com provides a gamepad, VR teleop, recording, and replay against any robot you’ve started. The Building Your Own Operator page covers the case when you want a custom one.
Install the SDK
Section titled “Install the SDK”pip install adamoRequires Python 3.10+. On the robot host you’ll also want the video extras:
pip install 'adamo[video]'cargo add adamoFor video capture (requires iceoryx2 on the host):
cargo add adamo --features videoPrebuilt for aarch64-unknown-linux-gnu (Jetson / Ubuntu 22.04 arm64), x86_64-unknown-linux-gnu, and aarch64-apple-darwin — cargo add adamo just works on those hosts. Other targets can point at a local libadamo via ADAMO_LIB_DIR.
Download the prebuilt tarball for your host and extract it into a CMake-discoverable prefix:
# Linux arm64 (Jetson / Ubuntu 22.04 aarch64), with video:curl -fsSL -o adamo-sdk.tar.gz \ https://install.adamohq.com/sdk/adamo-sdk-linux-arm64-video-latest.tar.gzsudo tar xzf adamo-sdk.tar.gz -C /optBuilds are also available for Linux x86_64 and macOS arm64, with and without video — see the C SDK install section for the full list and the GStreamer runtime the -video builds need.
Then in your project’s CMakeLists.txt:
list(APPEND CMAKE_PREFIX_PATH "/opt/adamo-sdk-linux-arm64-video-latest")find_package(Adamo REQUIRED)target_link_libraries(my_robot PRIVATE Adamo::adamo)# C++17 RAII wrapper is also available:# target_link_libraries(my_robot PRIVATE Adamo::adamo_cpp)Get an API Key
Section titled “Get an API Key”Sign up at operate.adamohq.com and go to Settings → API Keys. Create a key (it starts with ak_) and copy it. The SDK uses this key to find the right router for your organisation.
You can pass the key inline or read it from the ADAMO_API_KEY environment variable.
Write Your Robot Program
Section titled “Write Your Robot Program”A robot program does two things: declares the cameras it streams, then blocks driving the pipeline. That’s it.
import adamo
robot = adamo.Robot(api_key="ak_...", name="my-arm")robot.attach_video("main", device="/dev/video0")robot.run()use adamo::Robot;
fn main() -> adamo::Result<()> { let mut robot = Robot::new_default("ak_...", Some("my-arm"))?; robot.attach_v4l2("main", "/dev/video0", 1280, 720, 30, 4000, false)?; robot.run()}#include "adamo/adamo.h"#include <stdio.h>
int main(void) { adamo_robot_t *robot = adamo_robot_new_default("ak_...", "my-arm"); if (!robot) { fprintf(stderr, "adamo_robot_new: %s\n", adamo_last_error()); return 1; } if (adamo_robot_attach_video_v4l2( robot, "main", "/dev/video0", 1280, 720, 30, 4000, false) != 0) { fprintf(stderr, "attach: %s\n", adamo_last_error()); return 1; } return adamo_robot_run(robot);}Common Issues
Section titled “Common Issues”First step: run the doctor
Section titled “First step: run the doctor”python -m adamo doctor --api-key ak_... # or set ADAMO_API_KEYIt checks API key validity, QUIC reachability (including whether this network blocks UDP), encoder availability, and the video extras — and prints output you can paste straight into a support request.
The robot starts but never connects
Section titled “The robot starts but never connects”The default transport is QUIC, which runs over UDP (port 443). Some corporate networks, lab firewalls, VPNs, and hotel Wi-Fi block outbound UDP even when normal HTTPS works.
The fix is to open the firewall, not to switch transports: ask your network administrator to allow outbound UDP 443 to the Adamo relays (*.adamohq.com).
To confirm UDP blocking is the problem — or to get telemetry flowing while the firewall change lands — you can temporarily connect over TCP:
robot = adamo.Robot(api_key="ak_...", name="my-arm", protocol="tcp")use adamo::{Protocol, Robot};
let mut robot = Robot::new("ak_...", Some("my-arm"), Protocol::Tcp)?;adamo_robot_t *robot = adamo_robot_new( "ak_...", "my-arm", adamo_protocol_t_ADAMO_PROTOCOL_TCP);Treat TCP as a diagnostic or telemetry-only fallback. Do not teleoperate over TCP: when packets drop, TCP retransmits and queues everything behind the loss, so control commands arrive late and bunched together — on a moving robot that is dangerous.
Run It
Section titled “Run It”Run your program. On startup the SDK fetches your router endpoint and opens a session.
[adamo] connecting to router as my-arm…[adamo] connected (org: my-org)[adamo] streaming main: 1280x720 @ 30 fps, 4000 kbps (nvh264enc)See Your Robot
Section titled “See Your Robot”Open operate.adamohq.com and log in. Your robot appears in the grid with its live camera feed already playing. Click it to open the teleoperation view.
From there you have:
- Gamepad control — plug in a USB or Bluetooth controller; the web app publishes Joy messages that your robot can subscribe to. See Receiving Control.
- VR teleop — for stereo camera tracks, “View Stereo” launches a WebXR view with head + controller pose forwarded to the robot.
- Recording & replay — every session can be recorded and replayed later.
- Layouts — multi-camera grids you can configure once and reuse.
See The operate.adamohq.com Platform for the full tour.
To make the robot respond to gamepad or VR input, see Receiving Control.
Inspect Live Topics
Section titled “Inspect Live Topics”When you are not sure what is flowing through the network, subscribe to a wildcard and print the keys that arrive. This is the live equivalent of “list topics”: it observes traffic, so quiet topics appear only after they publish a sample.
import adamo
session = adamo.connect(api_key="ak_...")seen = set()
with session.subscribe("my-arm/**") as sub: for sample in sub: if sample.key not in seen: seen.add(sample.key) print(sample.key)Use my-arm/** for one robot, **/control/** for control traffic, or ** when you really want to see every sample in your org. See Robot and Topic Discovery for Python, Rust, C, and TypeScript versions.
Add More Cameras
Section titled “Add More Cameras”Most robots have more than one camera. Just call the attach method again — once per track.
import adamo
robot = adamo.Robot(api_key="ak_...", name="my-arm")robot.attach_video("wrist_left", device="/dev/video0")robot.attach_video("wrist_right", device="/dev/video1")robot.attach_video("head", shm="head_cam") # iceoryx2 SHMrobot.run()use adamo::Robot;
fn main() -> adamo::Result<()> { let mut robot = Robot::new_default("ak_...", Some("my-arm"))?; robot.attach_v4l2("wrist_left", "/dev/video0", 1280, 720, 30, 4000, false)?; robot.attach_v4l2("wrist_right", "/dev/video1", 1280, 720, 30, 4000, false)?; robot.attach_shm("head", "head_cam", 1280, 720, "BGRA", 30, 4000, false)?; robot.run()}adamo_robot_t *robot = adamo_robot_new_default("ak_...", "my-arm");adamo_robot_attach_video_v4l2(robot, "wrist_left", "/dev/video0", 1280, 720, 30, 4000, false);adamo_robot_attach_video_v4l2(robot, "wrist_right", "/dev/video1", 1280, 720, 30, 4000, false);adamo_robot_attach_video_shm(robot, "head", "head_cam", 1280, 720, "BGRA", 30, 4000, false);adamo_robot_run(robot);For the full set of input methods (V4L2 and shared memory), see Adding Cameras.