Skip to content

Direct Connection (P2P)

By default, video travels browser → Adamo router → robot. A direct connection (peer-to-peer) sends it straight between the browser and the robot over WebTransport, cutting out the router hop for lower latency.

It’s additive and automatic: the browser uses the direct path when one is available and silently falls back to the relay when it isn’t — so nothing breaks, you just get lower latency where the network allows it.

A direct connection is used only when all of these hold — otherwise the session stays on the relay, which always works:

  • The robot serves P2P (see below) and its firewall allows the direct port (see Open the robot’s firewall).
  • A direct network path exists between your browser and the robot: the same LAN, the same Tailscale tailnet, or a robot with a publicly reachable IP. Robots behind symmetric CGNAT (for example Starlink) usually can’t be reached directly and stay on the relay.
  • You’re on Chrome / a Chromium browser. The direct path pins a self-signed certificate, which only Chromium supports; Safari and Firefox fall back to the relay.

The robot has to opt in. Add one call to the robot you already have (see Quickstart):

import adamo
robot = adamo.Robot(api_key="ak_...", name="my-arm", p2p=True)
robot.attach_video("main", device="/dev/video0")
robot.run()

The P2P listener is additive — the robot keeps its normal relay connection, so operators who can’t reach it directly still see video over the relay.

A direct connection is an inbound connection to the robot, so the robot’s own firewall has to let it in. The relay connection is outbound and unaffected, which is why a robot with a closed firewall still works fine over the relay — it just never goes direct.

Hosted robots listen for direct connections on UDP port 17447. The SDK prints the port it ended up on at startup:

adamo: direct connections listen on UDP port 17447 (and TCP 17447 for offline pairing); open the UDP port in the robot's firewall for operators on its network

If 17447 is already taken on the host (for example by another robot process), the SDK tries 17448, 17449, and so on, so check the printed port rather than assuming. To pin a different port, set ADAMO_DIRECT_PORT in the robot’s environment.

On Ubuntu / Debian with ufw, allow the port:

Terminal window
sudo ufw allow 17447/udp

To limit the rule to operators on the robot’s LAN, restrict it to your subnet instead (adjust 192.168.1.0/24 to match your network):

Terminal window
sudo ufw allow proto udp from 192.168.1.0/24 to any port 17447

Other firewalls follow the same idea — allow inbound UDP on the robot’s direct port:

Terminal window
# firewalld (Fedora / RHEL)
sudo firewall-cmd --permanent --add-port=17447/udp && sudo firewall-cmd --reload
# nftables / iptables
sudo iptables -I INPUT -p udp --dport 17447 -j ACCEPT

Offline pairing (robots running without an Adamo account, see the SDK’s Robot.local) also uses TCP on the same port; open 17447/tcp as well only if you use it.

A few things that look like a firewall problem but aren’t:

  • The browser’s “local network access” prompt (Chrome) only lets the page attempt a local connection. Allowing it does not open anything on the robot — the robot-side rule above is still required.
  • Wi-Fi client isolation. Many guest and office networks stop devices on the same Wi-Fi from talking to each other at all. If a wired laptop connects directly but a phone on the Wi-Fi stays on the relay with the firewall open, this is usually the cause. Connect both through the same switch or use Tailscale.
  • Different networks. The direct path needs a route from the browser to the robot; across networks that means Tailscale or a publicly reachable IP, not a firewall change.

To check which path you’re on, see Use it in the browser.

Nothing to configure. Open the robot on operate.adamohq.com in Chrome and it connects directly when it can.

  • Chrome may show a one-time “allow local network access” prompt the first time — click Allow (it persists for the site).
  • To confirm you’re direct, open the Control Panel latency breakdown: the path shows you ↔ robot (direct) instead of the router hops.

The most reliable way to get direct connections across different networks is to put both the robot and the machine running your browser on the same Tailscale tailnet. The robot then advertises its tailnet address as a candidate, and the connection works regardless of NAT — including symmetric CGNAT / Starlink, where a raw direct connection can’t be established. The robot must be reachable on its P2P port over the tailnet’s ACLs.

Direct connections fall back to the relay automatically, but you can also force relay-only for a browser session — run this in the browser console and reload:

localStorage.setItem('adamo_p2p', '0')
  • Direct connections are authenticated and org-scoped, and teleop minutes are metered, exactly like the relay.
  • If a direct connection drops mid-session, video continues over the relay.
  • P2P carries video (and teleop) directly; discovery, fleet presence, and the robot list still go through the Adamo network as usual.