# Robotics — SDK Methods

> The typed Robotics SDK: publishTelemetry/Command, subscribeTelemetry/Command, QoS profiles, the type-name-prefixed CDR wire frame, and events.

The robotics surface lives in its own subpath import. You construct one
`Robotics` client per `(tenant, robot)`, then open scoped publications and
subscriptions. The client holds a single MoQT session, connects lazily on the
first call, and auto-reconnects — replaying every publication and subscription
on reconnect — so your code does nothing on a link flap.

## Import

**TypeScript:**
```ts
import { Robotics } from "@clutchcall/sdk/robotics";
```

**Python:**
```python
from clutchcall.robotics import Robotics, QoSProfile
```

**Go:**
```go
import "github.com/clutchcall/clutchcall-sdk/go/pkg/robotics"
```

## Construct a client

**TypeScript:**
```ts
const r = new Robotics({
  relayHost: "relay.clutchcall.dev",
  robotId:   "spot-12",
  token:     process.env.CLUTCHCALL_CREDENTIALS!,
});
```

**Python:**
```python
r = Robotics(
    relay_host="relay.clutchcall.dev",
    robot_id="spot-12",
    token=os.environ["CLUTCHCALL_CREDENTIALS"],
)
```

**Go:**
```go
r, err := robotics.New(robotics.Options{
    RelayHost: "relay.clutchcall.dev",
    RobotID:   "spot-12",
    Token:     os.Getenv("CLUTCHCALL_CREDENTIALS"),
})
```

### `RoboticsOptions`

<ParamField path="relayHost" type="string" required>
  Relay host or full URL — `"relay.clutchcall.dev"` or
  `"https://relay.clutchcall.dev"`. Every namespace derives from it.
</ParamField>
<ParamField path="robotId" type="string" required>
  The robot this client speaks for. Used to build `robot/<id>` (telemetry) and
  `robot/<id>/ctl` (commands).
</ParamField>
<ParamField path="token" type="string">
  Bearer token for the relay session, scoped to `(tenant, robotId)`.
</ParamField>
<ParamField path="serverCertificateHash" type="string">
  Base64 SHA-256 of the relay's cert, for pinned WebTransport in the browser.
</ParamField>
<ParamField path="webTransport" type="WebTransportFactory">
  Inject a custom WebTransport factory (Node polyfill / tests).
</ParamField>
<ParamField path="onError" type="(e: Error) => void">
  Called when the underlying relay session closes with an error.
</ParamField>

## Methods

### `publishTelemetry(spec)`

Open a robot → cloud track under `robot/<id>`. Returns a `RoboticsPublication`.

<ParamField path="spec.topic" type="string" required>
  Track name under the telemetry namespace, e.g. `"odom"`, `"battery_state"`.
</ParamField>
<ParamField path="spec.typeName" type="string" required>
  Full ROS 2 message type, e.g. `"nav_msgs/msg/Odometry"`. Prefixed on the wire.
</ParamField>
<ParamField path="spec.qos" type="QoS">
  QoS profile (below). Omit for the best-effort default.
</ParamField>

**TypeScript:**
```ts
const odom = await r.publishTelemetry({
  topic:    "odom",
  typeName: "nav_msgs/msg/Odometry",
  qos:      { reliability: "best_effort", depth: 1 },
});
odom.write(cdrBytes);
```

**Python:**
```python
odom = r.publish_telemetry(
    topic="odom",
    type_name="nav_msgs/msg/Odometry",
    qos=QoSProfile(reliability="best_effort", depth=1),
)
odom.write(cdr_bytes)
```

**Go:**
```go
odom, _ := r.PublishTelemetry("odom", "nav_msgs/msg/Odometry",
    &robotics.QoS{Reliability: "best_effort", Depth: 1})
odom.Write(cdrBytes)
```

### `publishCommand(spec)`

Open a cloud → robot track under `robot/<id>/ctl`. Same `spec` shape as
`publishTelemetry`; returns a `RoboticsPublication`.

**TypeScript:**
```ts
const cmd = await r.publishCommand({
  topic:    "cmd_vel",
  typeName: "geometry_msgs/msg/Twist",
  qos:      { reliability: "reliable" },
});
cmd.write(twistCdr);
```

**Python:**
```python
cmd = r.publish_command(topic="cmd_vel", type_name="geometry_msgs/msg/Twist")
cmd.write(twist_cdr)
```

**Go:**
```go
cmd, _ := r.PublishCommand("cmd_vel", "geometry_msgs/msg/Twist")
cmd.Write(twistCdr)
```

### `subscribeTelemetry(spec, onMessage)`

Receive robot → cloud frames from `robot/<id>`. The callback gets the raw CDR
payload and the wire type name — use the type name to guard / route.

<ParamField path="spec.topic" type="string" required>
  Track name to subscribe under the telemetry namespace.
</ParamField>
<ParamField path="onMessage" type="(cdr: Uint8Array, typeName: string) => void" required>
  Invoked per delivered message. `cdr` is the opaque payload; `typeName` is the
  prefix decoded off the wire.
</ParamField>

**TypeScript:**
```ts
await r.subscribeTelemetry({ topic: "odom" }, (cdr, typeName) => {
  if (typeName !== "nav_msgs/msg/Odometry") return; // schema guard
  handleOdom(cdr);
});
```

**Python:**
```python
def on_odom(cdr: bytes, type_name: str) -> None:
    if type_name != "nav_msgs/msg/Odometry":
        return
    handle_odom(cdr)

r.subscribe_telemetry(topic="odom", on_message=on_odom)
```

**Go:**
```go
err := r.SubscribeTelemetry("odom", func(cdr []byte, typeName string) {
    if typeName != "nav_msgs/msg/Odometry" {
        return
    }
    handleOdom(cdr)
})
```

### `subscribeCommand(spec, onMessage)`

Receive cloud → robot frames from `robot/<id>/ctl`. Same shape as
`subscribeTelemetry`; this is the call the robot (or the on-robot bridge) makes
to pick up teleop commands.

**TypeScript:**
```ts
await r.subscribeCommand({ topic: "cmd_vel" }, (cdr, typeName) => {
  drive(cdr);
});
```

**Python:**
```python
r.subscribe_command(topic="cmd_vel", on_message=lambda cdr, t: drive(cdr))
```

**Go:**
```go
err := r.SubscribeCommand("cmd_vel", func(cdr []byte, typeName string) {
    drive(cdr)
})
```

### `close()`

Tear down the underlying MoQT session and all publications / subscriptions.
Idempotent.

**TypeScript:**
```ts
r.close();
```

**Python:**
```python
r.close()
```

**Go:**
```go
r.Close()
```

## Handles

### `RoboticsPublication`

Returned by `publishTelemetry` / `publishCommand`.

<ParamField path="write(cdr: Uint8Array, priority?: number)" type="method">
  Push one typed message. `cdr` is the raw CDR (or whatever matches your
  `typeName`); the SDK prepends the type-name prefix and opens a fresh MoQT group
  per message. `priority` (TypeScript) overrides the QoS default for this single
  frame — `0` highest, `255` lowest. Returns immediately; the MoQT layer handles
  ordering and reliability.
</ParamField>
<ParamField path="close()" type="method">
  Stop publishing this track.
</ParamField>

### `RoboticsSubscription` (TypeScript)

`subscribeTelemetry` / `subscribeCommand` resolve once the subscription is
attached. In the TypeScript SDK the resolved handle exposes `close()` to stop
receiving; in Python / Go the returned subscription object exposes the same.

> **WARNING:**
> Keep the subscription handle alive for as long as you want frames. In
> garbage-collected languages it owns the delivery callback — if it is collected,
> the engine may call into freed memory. Drop the publication handle (or call
> `close`) to stop publishing.

## QoS profile

```ts
interface QoS {
  reliability?: "reliable" | "best_effort"; // default best_effort
  durability?:  "volatile" | "transient_local"; // default volatile
  depth?:       number; // keep-last group window the relay holds (default 10)
}
```

| Field         | Maps to                                       |
| ------------- | --------------------------------------------- |
| `reliability` | subgroup stream (`reliable`) vs QUIC datagram (`best_effort`) |
| `durability`  | `transient_local` adds late-join retain       |
| `depth`       | per-track recent-group window for late subscribers |

The default profile matches ROS 2's `SensorDataQoS` (best-effort, volatile,
keep-last). See [Details → QoS → lanes](/modalities/robotics/details) for the
full capability mapping.

## Events & lifecycle

- **Connection state** — the client surfaces relay session lifecycle
  (`Connecting · Connected · Reconnecting · Closed · Failed`). In TypeScript pass
  `onState` via the underlying options; in other SDKs the connect callback
  reports state codes. Auto-reconnect with capped backoff re-attaches every
  track on its own.
- **Late join** — a subscriber that joins after a publisher immediately receives
  the relay's retained group window (sized by `depth`, latched by
  `transient_local`).
- **Errors** — `onError` (TypeScript) fires on relay close with a reason string;
  individual publish/subscribe calls reject if the session can't be established.

## Wire frame (for non-SDK peers)

If you bridge a non-SDK device, emit the same envelope so SDK subscribers demux:

```
[u16 BE type_name_len] [type_name UTF-8] [CDR payload]
```

The CDR payload is opaque; the prefix is the only contract. Every SDK encodes
and decodes this identically.

## Related

- [Details](/modalities/robotics/details) — wire model, lanes, transports.
- [Cookbook](/modalities/robotics/cookbook) — short task snippets.
- [Recipes](/modalities/robotics/recipes) — full worked examples.
