6053 that speaks a lightweight binary protocol. All data is exchanged as length-prefixed protobuf messages. This page covers the wire format, the connection sequence, and versioning behaviour.
Frame format
Every message on the wire is wrapped in a binary frame:| Field | Size | Description |
|---|---|---|
0x00 | 1 byte | Fixed zero byte; marks the start of each frame |
msg_size | 1–5 bytes (varint) | Byte length of the protobuf body |
msg_type | 1–5 bytes (varint) | Message type ID from option (id) in api.proto |
| body | msg_size bytes | Protobuf-encoded message payload |
api.proto is the source of truth for all message type IDs. The option (id) annotation on each message definition is the canonical mapping from name to wire ID.Connection lifecycle
TCP connect
Open a TCP connection to the device on port
6053. No data is exchanged at this stage.If Noise encryption is configured on the device, the Noise handshake (Noise_NNpsk0_25519_ChaChaPoly_SHA256) takes place at the TCP level before any protobuf messages are sent. The handshake uses a pre-shared key provisioned on both sides.Hello exchange
The client sends a The client checks
HelloRequest (type 1) containing the client’s name and API version. The device responds with a HelloResponse (type 2) containing its own API version.api_version_major in the response. If the major version is incompatible, the connection is closed immediately — no DisconnectRequest is sent.Device info
After a successful Hello exchange, the client optionally requests device metadata:
DeviceInfoResponse contains the device name, MAC address, firmware version, compilation time, and capability flags.Entity discovery
The client requests the full list of entities exposed by the device:Each entity domain has its own response type (e.g.
ListEntitiesSensorResponse, ListEntitiesLightResponse). The stream is terminated by ListEntitiesDoneResponse.State subscription
Once entities are known, the client subscribes to state updates:State responses are pushed by the device whenever a sensor value or entity state changes. There is no polling — the stream remains open for the lifetime of the connection.
Keepalive
The client periodically sends A missing
PingRequest to verify the connection is alive:PingResponse within the configured timeout indicates a dead connection and triggers the disconnect callback and optional reconnect logic.Graceful disconnect
To close the connection cleanly, the client sends a
DisconnectRequest and waits for the device to acknowledge:If the TCP connection drops unexpectedly (no
DisconnectRequest received), the client invokes the OnDisconnect callback and, if configured, begins the reconnect loop with exponential backoff.API versioning
TheHelloRequest message carries the client’s supported API version (api_version_major / api_version_minor). The device echoes back its own version in HelloResponse.
The client must:
- Read
api_version_majorfromHelloResponse. - Reject the connection if the major version does not match the expected value — a major version bump indicates breaking wire-format changes.
- Use
api_version_minorto conditionally enable features that are absent in older firmware.
Authentication
Use the Noise transport with a pre-shared key for authenticated, encrypted connections. The key is a base64-encoded 32-byte value configured in the ESPHome device YAML and passed to the client viaWithEncryptionKey.
Key message type IDs
| Message | Type ID |
|---|---|
HelloRequest | 1 |
HelloResponse | 2 |
DisconnectRequest | 5 |
DisconnectResponse | 6 |
PingRequest | 7 |
PingResponse | 8 |
DeviceInfoRequest | 9 |
DeviceInfoResponse | 10 |
ListEntitiesRequest | 11 |
ListEntitiesDoneResponse | 19 |
SubscribeStatesRequest | 20 |
SubscribeLogsRequest | 28 |
SubscribeLogsResponse | 29 |
api.proto is the source of truth for all message type IDs. The table above covers the core connection lifecycle; entity-specific type IDs (sensors, lights, covers, etc.) are defined in api.proto alongside their message definitions.