# Connection Modes

> UDP, Hybrid and LiveKit: what each carries, what it needs, and how the SDK picks one.

- Page: https://docs.menlo.ai/sdk/connection-modes
- Section: Python SDK
- Previous: [Quickstart](https://docs.menlo.ai/sdk/quickstart.md)
- Next: [Move the Robot](https://docs.menlo.ai/sdk/move.md)

The SDK reaches the robot in one of three connection modes. The `Robot` object and every
verb are the same in all three; the mode decides what the connection carries and where it
works from.

| Mode | Control and state | Camera and audio | Works from |
| - | - | - | - |
| `udp` | UDP on the robot's network | none | the robot's network |
| `hybrid` | UDP on the robot's network | a LiveKit room | the robot's network |
| `livekit` | a LiveKit room | a LiveKit room | wherever Asimov Manager is reachable |

A saved robot carries a mode, so `Robot().connect()` needs no argument. Pass one to override
it for a session: `connect("udp")`.

## In Code

`connect.py` builds the connection in code. Set `MODE` at the top of the file to `"udp"`,
`"hybrid"` or `"livekit"`; the three `ConnectionConfig` shapes sit side by side:

```python
# udp: control and state over UDP on the robot's network. No camera or audio.
udp = ConnectionConfig(udp=UdpConfig(host=ROBOT_ADDRESS))

# hybrid: control and state over UDP, camera and audio over LiveKit.
hybrid = ConnectionConfig(
    udp=UdpConfig(host=ROBOT_ADDRESS),
    livekit=ManagerConfig(url=MANAGER_URL, credential=CREDENTIAL),
)

# livekit: everything through the robot's LiveKit room, wherever Asimov Manager is reachable.
livekit = ConnectionConfig(livekit=ManagerConfig(url=MANAGER_URL, credential=CREDENTIAL))

config = {"udp": udp, "hybrid": hybrid, "livekit": livekit}[MODE]

try:
    with Robot(config).connect(MODE) as robot:
        state = robot.get_state()
        print(f"connected over {MODE} to {robot.info.endpoint}")
        print(f"robot mode {state.mode.name}, armed {robot.armed}")
        if state.battery is not None:
            print(f"battery {state.battery.soc_percent:.0f} %")
        else:
            print("battery not reported")
        capabilities = ("drive", "state", "battery", "camera", "microphone", "speaker")
        print("capabilities:", ", ".join(c for c in capabilities if robot.has(c)))
except CompatibilityError as exc:
    # The robot answered, but its model or Robot OS line is not supported.
    # Updating the SDK or robot is appropriate; blindly retrying is not.
    print(f"incompatible robot: {exc}")
except ConnectError as exc:
    # The connection itself failed: robot, credentials, room or transport.
    # Check the reported cause; a retry may make sense for a transient failure.
    print(f"could not connect: {exc}")

```

From `examples/connect.py` in the menlo-sdk repository (the excerpt the page shows).

The other example scripts call `Robot()` with no config and read the environment or a saved
robot instead; see [How the SDK Picks a Connection](https://docs.menlo.ai/sdk/connection-modes.md#how-the-sdk-picks-a-connection).

## UDP

Commands go to the robot on UDP port 8850 and state comes back on port 8851. There is no
media and no sign-in: anyone on the robot's network who can reach port 8850 can command it,
so trust the network before you turn it on.

The robot sends UDP state to one host. In Asimov Manager, set the Asimov Edge parameters
`udp-control` to on and `udp-state-host` to your computer's address
([Asimov Manager](https://docs.menlo.ai/asimov/1/operate/asimov-manager.md#overview)).

One client per state port: a second script on the same computer binds
`UdpConfig(state_bind=("0.0.0.0", <port>))` and the robot's `udp-state-port` matches it.

## Hybrid

Control and state travel as in udp, so a velocity command does not cross the internet. The
camera, microphone and speaker travel over the robot's LiveKit room, which the SDK joins
with an SDK credential from Asimov Manager. Use hybrid on the robot's network when a script
needs the camera or sound.

It needs the robot's address, its Asimov Manager URL and an SDK credential. Issue the
credential on the **Developer** page of Asimov Manager
([Get an SDK Credential](https://docs.menlo.ai/sdk/quickstart.md#get-an-sdk-credential)). The
credential's role applies to the LiveKit room: a **Control** credential can talk through the
speaker, an **Observe** credential watches and listens only. Commands in hybrid travel over
UDP, which has no sign-in, so the role does not limit driving.

## LiveKit

Everything, commands included, goes through the robot's LiveKit room. Commands travel on the
data topic `commands` and state on the data track `state`, at about 10 Hz. When the robot's
Asimov Edge reads it, a non-zero velocity and a trajectory go on the SDK's own data track
`commands` instead: a lost frame is replaced by the next one, where a lost packet would hold
the rest of the stream back until it is resent. Mode commands and a zero velocity always go
as packets. Use livekit away
from the robot's network, or when an agent framework joins the same room and the SDK is one
more participant in it.

It needs the Asimov Manager URL and an SDK credential
([Get an SDK Credential](https://docs.menlo.ai/sdk/quickstart.md#get-an-sdk-credential)). The SDK asks Asimov
Manager for a room token, joins, and reports `robot.info.endpoint` as `room@url as <identity>`. The
credential's role applies:
a **Control** credential drives, an **Observe** credential watches and listens, and the robot
drops its commands.

The Asimov Manager URL is the one you open in the browser: `http://192.168.22.32`,
`http://asimov.local:8080` or a bare host name. The LiveKit URL Asimov Manager hands out is
the one the robot itself uses; when it says `localhost`, the SDK substitutes the manager's
host. A manager that redirects is reported as a `ConnectError`, not followed.

Every SDK session joins the room as its own participant, `sdk-<credential id>-<host>-<6 hex>`.
`ManagerConfig(label="...")` fixes the suffix so a restarted script replaces its previous session
instead of joining beside it. To join with a token you minted yourself, pass
`LiveKitConfig(url, room, token)` instead of `ManagerConfig`; one token is one participant.
To use the room without the SDK at all, see [Without the SDK](https://docs.menlo.ai/sdk/without-the-sdk.md).

## How the SDK Picks a Connection

`Robot()` with no argument builds its connection from the environment or a saved robot, in
this order:

1. Environment variables: `MENLO_UDP_HOST`, or `MENLO_MANAGER_URL` and `MENLO_CREDENTIAL`, or
   both pairs. They are not merged with a saved robot.
2. A saved robot from `~/.menlo/robots.toml`: the one named by `MENLO_ROBOT`, else the file's
   default, else the only one.
3. Otherwise `ConnectError`, naming `menlo setup` and the variables.

`MENLO_MODE` sets the mode `connect()` uses with no argument and `MENLO_LIMITS` the velocity
limits, whichever source supplied the connection. `MENLO_HOME` moves the store directory.

A saved robot holds everything one mode needs, and can hold more than one mode's fields:

```toml title="~/.menlo/robots.toml"
default = "lab"

[robots.lab]
mode = "hybrid"
udp_host = "192.168.22.32"
manager_url = "http://192.168.22.32"
credential = "..."
room = "robot-menlo-0001"

[robots.lab.limits]
vx = 0.3
```

`udp` needs `udp_host`; `hybrid` needs `udp_host`, `manager_url` and `credential`; `livekit`
needs `manager_url` and `credential`. A mode whose field is missing raises `ConnectError`
naming the field and the `menlo robots add` command that fills it. Save and change robots
with the [command line](https://docs.menlo.ai/sdk/cli.md); the file is created with mode 0600
because it holds SDK credentials.

## What Connect Waits For

`connect()` returns once the robot has reported state, so `robot.get_state()` is valid on the
next line. It raises `ConnectError` after `timeout` (5 s by default) without state. On hybrid and
livekit the media tracks are awaited for `media_timeout` (3 s); the capabilities the SDK claims
are the tracks that arrived.

`connect(require_state=False)` opens the room without waiting for the firmware: camera,
microphone and speaker work at once. State unlocks when the first state frame arrives; a
motion verb sent before then waits for it up to its `timeout`.

A protocol version other than the one the SDK speaks raises `ProtocolMismatchError`.
`allow_version_skew=True` connects anyway, and the robot may misread the commands the SDK
sends. Update the SDK or the robot instead.

> [!WARNING]
>
> **A lost link ends a held velocity**
>
> After 2 s without state the SDK raises `LinkLostError`, sends zero velocity if it holds one,
> and every verb raises until you `close()` and `connect()` again. On `udp` and `hybrid`, the
> robot also zeroes a velocity 2 s after the last command it received. On `livekit`, Asimov
> Edge stops a held velocity when the SDK sends zero or leaves the room; see
> [Watchdogs](https://docs.menlo.ai/sdk/safety.md#watchdogs).

## Related

- [Quickstart](https://docs.menlo.ai/sdk/quickstart.md): point the SDK at the robot and run the first scripts
- [Command Line](https://docs.menlo.ai/sdk/cli.md): `menlo setup` and `menlo robots`
- [Drive from Python](https://docs.menlo.ai/asimov/1/operate/drive/python-sdk.md): the SDK credential and the UDP settings, step by step
- [Asimov Manager](https://docs.menlo.ai/asimov/1/operate/asimov-manager.md): the robot's settings
- [SDK Reference](https://docs.menlo.ai/sdk/reference.md#connection): `ConnectionConfig`, `UdpConfig`, `ManagerConfig`, `LiveKitConfig`
