# 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 you open scoped publications
and subscriptions. The client holds a single MoQT session and connects
lazily on the first call. It reconnects automatically and replays every
publication and subscription on reconnect. Your code does nothing on a link
flap.

## Import

**TypeScript:**
```ts
import { Robotics } from "clutchcall_connect/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.firstbyte.fast",
  robotId:   "spot-12",
  token:     process.env.CLUTCHCALL_CREDENTIALS!,
});
```

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

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

### `RoboticsOptions`

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

## Methods

### `publishTelemetry(spec)`

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

- **`spec.topic`** (`string`, required) — Track name under the telemetry namespace, for example `"odom"`,   `"battery_state"`.
- **`spec.typeName`** (`string`, required) — Full ROS 2 message type, for example `"nav_msgs/msg/Odometry"`. The SDK   prefixes it on the wire.
- **`spec.qos`** (`QoS`) — QoS profile (below). Omit for the best-effort default.

**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`. The `spec` shape is the
same 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 check and route
messages.

- **`spec.topic`** (`string`, required) — Track name to subscribe under the telemetry namespace.
- **`onMessage`** (`(cdr: Uint8Array, typeName: string) => void`, required) — The SDK invokes this for each delivered message. `cdr` is the opaque   payload. `typeName` is the prefix decoded from the wire.

**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`. The shape is the same as
`subscribeTelemetry`. The robot (or the on-robot bridge) makes this call to
receive 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()`

Close the underlying MoQT session and all publications / subscriptions. The
call is idempotent.

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

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

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

## Handles

### `RoboticsPublication`

Returned by `publishTelemetry` / `publishCommand`.

- **`write(cdr: Uint8Array, priority?: number)`** (`method`) — Send one typed message. `cdr` is the raw CDR (or the bytes that match 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. The call returns immediately.   The MoQT layer handles ordering and reliability.
- **`close()`** (`method`) — Stop publishing this track.

### `RoboticsSubscription` (TypeScript)

`subscribeTelemetry` / `subscribeCommand` resolve when 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 method.

> **WARNING:**
> Keep the subscription handle alive for as long as you want frames. In
> garbage-collected languages, the handle owns the delivery callback. If the
> handle is collected, the engine can 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 shows the 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 cannot be
  established.

## Wire frame (for non-SDK peers)

If you bridge a non-SDK device, send the same envelope so that SDK
subscribers can demux it:

```
[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 frame identically.

## Related

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