The ESPHome API Client is organized into distinct layers, each with a single responsibility. This page explains how those layers fit together, how messages flow from your application down to the wire, and what concurrency guarantees you can rely on.Documentation Index
Fetch the complete documentation index at: https://mintlify.com/richard87/esphome-apiclient/llms.txt
Use this file to discover all available pages before exploring further.
Layer overview
Package layout
| Import path | Contents |
|---|---|
github.com/richard87/esphome-apiclient | Client, EntityRegistry, Router, framer, commands |
github.com/richard87/esphome-apiclient/pb | Generated protobuf types from api.proto |
github.com/richard87/esphome-apiclient/transport | PlainTransport and NoiseTransport |
github.com/richard87/esphome-apiclient/codec | Frame encoding and decoding |
github.com/richard87/esphome-apiclient/cmd/esphome-cli | Bundled CLI tool |
Core components
Transport layer
Abstracts the underlying connection so the rest of the client is encryption-agnostic. Both implementations satisfy the same interface:
PlainTransport— wraps a barenet.Connwith no additional framing.NoiseTransport— performs the Noise Protocol handshake (Noise_NNpsk0_25519_ChaChaPoly_SHA256) using a pre-shared key viagithub.com/flynn/noise, then wraps the encrypted channel. All subsequent reads and writes are transparently encrypted.
Frame codec
Implements the ESPHome binary framing format on top of the transport:
Encode produces 0x00 + VarInt(len(Data)) + VarInt(Type) + Data. Decode reads that same layout back into a Frame. See the protocol reference for full framing details.Message router
Maintains a registry that maps message type IDs to sets of handlers and dispatches decoded messages to all registered handlers for that type.Fan-out is supported: multiple handlers can subscribe to the same message type. For example, both the internal state cache and a user-supplied callback can listen for
SensorStateResponse simultaneously. Calling the function returned by On removes only that specific subscription.Handlers are called synchronously in the read goroutine. They must not block. Offload any long-running work to a separate goroutine.
Entity registry
Caches entity metadata (populated by Each entity domain has its own typed struct so you never have to cast untyped interface values. See Entity registry for the full list of entity types and their fields.
ListEntities) and the latest known state (updated by SubscribeStates). Provides type-safe accessors per entity domain:Client (public API)
The main entry point. Orchestrates the connection lifecycle, entity discovery, and subscriptions:Behaviour is controlled through functional options passed to See Options for the full reference.
Dial:Concurrency model
| Concern | Mechanism |
|---|---|
| Frame reading | Single goroutine; all dispatch happens here |
| Write serialization | sync.Mutex — safe to call SendCommand from multiple goroutines |
| Keepalive | Background goroutine sends periodic PingRequest; monitors PingResponse to detect dead connections |
| Reconnect | Optional exponential backoff loop; re-discovers entities and re-subscribes to states after reconnecting |
Handlers registered with
Router.On are called in the read goroutine. A blocking handler stalls all incoming message processing. Use a buffered channel or go func() to offload work.Error handling
- If the Hello handshake fails due to a version mismatch, the connection is closed immediately without sending
DisconnectRequest. - On unexpected TCP close, the disconnect callback (set via
WithOnDisconnect) is invoked, and the optional reconnect loop is triggered. - The client checks
api_version_majorandapi_version_minorfromHelloResponseand rejects connections with incompatible major versions.
Supported entity domains
| Domain | ListEntities response | State response | Command request |
|---|---|---|---|
| Binary Sensor | ListEntitiesBinarySensorResponse | BinarySensorStateResponse | — |
| Cover | ListEntitiesCoverResponse | CoverStateResponse | CoverCommandRequest |
| Fan | ListEntitiesFanResponse | FanStateResponse | FanCommandRequest |
| Light | ListEntitiesLightResponse | LightStateResponse | LightCommandRequest |
| Sensor | ListEntitiesSensorResponse | SensorStateResponse | — |
| Switch | ListEntitiesSwitchResponse | SwitchStateResponse | SwitchCommandRequest |
| Text Sensor | ListEntitiesTextSensorResponse | TextSensorStateResponse | — |
| Camera | ListEntitiesCameraResponse | CameraImageResponse | CameraImageRequest |
| Climate | ListEntitiesClimateResponse | ClimateStateResponse | ClimateCommandRequest |
| Water Heater | ListEntitiesWaterHeaterResponse | WaterHeaterStateResponse | WaterHeaterCommandRequest |
| Number | ListEntitiesNumberResponse | NumberStateResponse | NumberCommandRequest |
| Select | ListEntitiesSelectResponse | SelectStateResponse | SelectCommandRequest |
| Siren | ListEntitiesSirenResponse | SirenStateResponse | SirenCommandRequest |
| Lock | ListEntitiesLockResponse | LockStateResponse | LockCommandRequest |
| Button | ListEntitiesButtonResponse | — | ButtonCommandRequest |
| Media Player | ListEntitiesMediaPlayerResponse | MediaPlayerStateResponse | MediaPlayerCommandRequest |
| Alarm Control Panel | ListEntitiesAlarmControlPanelResponse | AlarmControlPanelStateResponse | AlarmControlPanelCommandRequest |
| Text | ListEntitiesTextResponse | TextStateResponse | TextCommandRequest |
| Date | ListEntitiesDateResponse | DateStateResponse | DateCommandRequest |
| Time | ListEntitiesTimeResponse | TimeStateResponse | TimeCommandRequest |
| DateTime | ListEntitiesDateTimeResponse | DateTimeStateResponse | DateTimeCommandRequest |
| Event | ListEntitiesEventResponse | EventResponse | — |
| Valve | ListEntitiesValveResponse | ValveStateResponse | ValveCommandRequest |
| Update | ListEntitiesUpdateResponse | UpdateStateResponse | UpdateCommandRequest |
| Infrared | ListEntitiesInfraredResponse | InfraredRFReceiveEvent | InfraredRFTransmitRawTimingsRequest |
Code generation
Protobuf code is generated fromapi.proto using:
protoc and protoc-gen-go. The generated code lands in pb/api.pb.go. Do not edit pb/api.pb.go by hand — regenerate it from api.proto instead.