Skip to content

Latest commit

 

History

History
196 lines (145 loc) · 6.17 KB

File metadata and controls

196 lines (145 loc) · 6.17 KB

Bridge WebSocket protocol

Protocol version 1. Clients should read protocol from the hello message and refuse to run against a major version they do not know.

Framing

The two WebSocket frame types carry different things, and never mix:

Frame type Direction Meaning
Binary both Raw UART payload, byte-for-byte. No header, no encoding.
Text both A single JSON object: a control message.

A binary frame sent by a client is written to the device. A binary frame sent by the server is data the device emitted. Nothing is added or stripped, so a client that only ever sends and receives binary frames is a transparent pipe.

Chunking and message boundaries

BLE UART is a stream, not a datagram service. The bridge splits outbound payloads larger than the negotiated MTU across several BLE writes, and emits one binary frame per inbound BLE notification. So:

  • A binary frame you send may arrive at the peripheral as several writes.
  • Device output arrives as however many notifications the peripheral sent; a logical message may be split across binary frames, or several may share one.

If your firmware protocol needs framing (length prefixes, newlines, COBS), implement it on top. Do not assume one WebSocket frame equals one device message.

max_write in hello and state reports the largest single BLE write, which is useful if you want to pre-split payloads yourself.

Connecting

ws://127.0.0.1:8765

If the bridge is configured with a token, present it either as a header

Authorization: Bearer <token>

or, when the client library cannot set handshake headers (browsers, and some Unity WebSocket implementations), as a query parameter:

ws://127.0.0.1:8765/?token=<token>

The header is preferable — URLs tend to end up in logs and proxy history.

Failures happen during the HTTP handshake, before the WebSocket opens:

Status Meaning
401 Missing or wrong token
503 max_clients already connected

Correlation

Any client control message may carry an id (string or number). The reply to it carries the same id. Unsolicited server messages (device state changes) have no id. Pick unique ids if you have more than one request in flight.

Client → server

type Fields Effect
ping Server replies pong. Application-level; separate from WebSocket ping frames.
status Server replies with a state message.
scan timeout (seconds, optional) Discover nearby devices; replies scan_result.
connect address and/or name (optional) Target a device and start connecting. With neither field, uses the configured target.
disconnect Drop the current device and stop reconnecting.
write data (base64 string) Same as sending a binary frame. For clients that cannot send binary frames.
{"type": "connect", "address": "AA:BB:CC:DD:EE:FF", "id": 1}
{"type": "scan", "timeout": 5, "id": 2}
{"type": "write", "data": "aGVsbG8=", "id": 3}

connect replies ok as soon as the request is accepted. That is not a successful connection — watch the state messages that follow.

Server → client

hello

Sent once, immediately on connect.

{
  "type": "hello",
  "protocol": 1,
  "server": "uart-ble-websocket/0.1.0",
  "framing": "binary",
  "target": {"address": null, "name": "MyDevice"},
  "state": "disconnected",
  "device": null,
  "max_write": null
}

state

Sent unsolicited on every transition, and as the reply to status.

{
  "type": "state",
  "state": "connected",
  "device": {"address": "AA:BB:CC:DD:EE:FF", "name": "MyDevice"},
  "max_write": 244
}

state is one of disconnected, scanning, connecting, connected. device is null unless a device is known. max_write is non-null only while connected. An error field appears when the transition was caused by a failure:

{"type": "state", "state": "disconnected", "device": null, "max_write": null,
 "error": "no device found with name 'MyDevice' within 10s"}

Only connected means binary frames will reach the device. The bridge does not buffer while disconnected — writes fail with not_connected instead of silently queueing, so your application decides what to do about it.

scan_result

{"type": "scan_result", "id": 2, "devices": [
  {"address": "AA:BB:CC:DD:EE:FF", "name": "MyDevice", "rssi": -47},
  {"address": "11:22:33:44:55:66", "name": null, "rssi": -83}
]}

Strongest signal first. name may be null. Not every peripheral advertises its service UUIDs, so a UART device may look unremarkable in this list.

ok

Acknowledges a command that produced no other reply. write adds a bytes count.

{"type": "ok", "id": 1}
{"type": "ok", "id": 3, "bytes": 5}

pong

{"type": "pong", "id": 7}

error

Carries the id of the offending request when there was one. An error never closes the connection.

{"type": "error", "code": "not_connected", "message": "no device connected", "id": 4}
code Meaning
bad_request Malformed JSON, or a field of the wrong type
unsupported_type Unknown type
not_connected A write arrived while no device was connected
busy A scan is already running
scan_failed The adapter refused to scan (often: Bluetooth is off)
connect_failed Device not found, or connection refused
write_failed The write was rejected or the device vanished mid-write
internal_error A bug in the bridge — please report it

Close codes

Code Meaning
1000 Normal shutdown
1013 The client could not keep up with device output and was dropped (slow_client_policy = "close")

A minimal client

  1. Connect; read hello; check protocol.
  2. If state is not connected, send connect (or wait, if the bridge auto-connects).
  3. Wait for a state message with "state": "connected".
  4. Send binary frames to transmit; handle incoming binary frames as device output.
  5. Treat any state other than connected as "the link is down" — the bridge reconnects on its own, so just wait for the next connected.