> ## Documentation Index
> Fetch the complete documentation index at: https://docs.elata.bio/llms.txt
> Use this file to discover all available pages before exploring further.

# Transport Contract

> Implement the shared Elata transport and frame interfaces for your device.

This is the contract your integration must satisfy.

Apps should see a stable Elata transport surface, not vendor-specific packet
formats or Bluetooth details.

## The Core Rule

Your device adapter should emit `HeadbandFrameV1` frames through a
`HeadbandTransport`.

That is the compatibility boundary for browser apps and downstream analysis.

<Info>
  If the frame contract is correct, app code can stay simple. If the frame
  contract is unstable, everything above it becomes harder to trust.
</Info>

## `HeadbandFrameV1`

Every transport produces the same top-level frame shape:

```ts theme={null}
interface HeadbandFrameV1 {
  schemaVersion: "v1";
  source: string;
  sequenceId: number;
  emittedAtMs: number;
  eeg: HeadbandSignalBlock;
  ppgRaw?: HeadbandSignalBlock;
  optics?: HeadbandSignalBlock;
  accgyro?: HeadbandSignalBlock;
  battery?: HeadbandBatteryBlock;
}
```

In practice, most integrations should treat `eeg` as the required block and add
other blocks only when the device exposes them clearly.

## `HeadbandSignalBlock`

The EEG payload should be normalized into this shape:

```ts theme={null}
interface HeadbandSignalBlock {
  sampleRateHz: number;
  channelNames: string[];
  channelCount: number;
  samples: number[][];
  timestampsMs?: number[];
  clockSource?: "device" | "local";
}
```

## What Must Stay Stable

| Field          | What good looks like                                      |
| -------------- | --------------------------------------------------------- |
| `source`       | A stable, readable identifier such as `vendor-ble`        |
| `sequenceId`   | Monotonic within the running session                      |
| `sampleRateHz` | Matches the actual device behavior                        |
| `channelNames` | Stable and documented                                     |
| `channelCount` | Always matches the real row width                         |
| `samples`      | Each row is one time step and uses the same channel order |
| `timestampsMs` | Optional, but aligned with the emitted rows when present  |
| `clockSource`  | Explicitly documented as `device` or `local`              |

## `HeadbandTransport`

All transports implement the same lifecycle surface:

```ts theme={null}
interface HeadbandTransport {
  onFrame?: (frame: HeadbandFrameV1) => void;
  onStatus?: (status: HeadbandTransportStatus) => void;
  connect(): Promise<void>;
  disconnect(): Promise<void>;
  start(): Promise<void>;
  stop(): Promise<void>;
}
```

## Lifecycle Expectations

| Method         | Expected behavior                                    |
| -------------- | ---------------------------------------------------- |
| `connect()`    | Pair or attach to the device and prepare the session |
| `start()`      | Begin streaming valid frames                         |
| `stop()`       | Stop streaming without corrupting the session        |
| `disconnect()` | Release the session and underlying resources         |

The transition rules matter as much as the methods themselves. A clean
integration should make it obvious whether the transport is idle, connected,
streaming, degraded, reconnecting, disconnected, or in error.

## `HeadbandTransportStatus`

Status updates should be explicit enough for apps to react correctly:

```ts theme={null}
interface HeadbandTransportStatus {
  state: HeadbandTransportState;
  atMs: number;
  reason?: string;
  errorCode?: string;
  recoverable?: boolean;
  details?: Record<string, unknown>;
}
```

## Practical Rules for Integrators

<Steps>
  <Step title="Keep channel order fixed">
    If the device streams `TP9, AF7, AF8, TP10`, every emitted row should use
    that order consistently.
  </Step>

  <Step title="Make row width match channel count">
    A row should never contain more or fewer EEG values than `channelCount`.
  </Step>

  <Step title="Use the right sample rate">
    Do not hardcode a nominal value if the actual output rate differs.
  </Step>

  <Step title="Document timestamp behavior">
    State whether timestamps come from the device clock or a local browser clock.
  </Step>

  <Step title="Surface failures through status updates">
    Apps need clear signals for disconnects, retries, and unrecoverable errors.
  </Step>
</Steps>

## Minimal Usage Pattern

```ts theme={null}
const transport: HeadbandTransport = /* BleTransport or another transport */;

transport.onFrame = (frame) => {
  const eegRows = frame.eeg.samples;
  console.log(eegRows.length);
};

transport.onStatus = (status) => {
  console.log(status.state, status.reason ?? "");
};

await transport.connect();
await transport.start();

// ... later
await transport.stop();
await transport.disconnect();
```

## Related Docs

* [Headband Transport](/sdk/eeg-web/headband-transport)
* [Protocol Requirements](./protocol-requirements)
* [Adapter Implementation](./adapter-implementation)
* [Testing and Validation](./testing-and-validation)

## Next

<CardGroup cols={2}>
  <Card title="Protocol Requirements" icon="arrow-right" href="./protocol-requirements">
    Gather the packet and metadata details needed to satisfy the contract.
  </Card>

  <Card title="Adapter Implementation" icon="arrow-right" href="./adapter-implementation">
    Wire the contract into a real device adapter.
  </Card>
</CardGroup>
