Espex (espex v0.7.1)

Copy Markdown View Source

ESPHome Native API server library.

Espex implements the ESPHome Native API protocol over TCP, letting an Elixir application expose itself as an ESPHome device to clients such as Home Assistant. The wire protocol, connection lifecycle, and optional Noise-encrypted transport all live here; hardware is plugged in through behaviours.

Documentation map

Quick start

Start under your own supervision tree:

children = [
  {Espex,
   device_config: [name: "my-device", friendly_name: "My Device"],
   serial_proxy: MyApp.MySerialAdapter,
   zwave_proxy: MyApp.MyZWaveAdapter,
   infrared_proxy: MyApp.MyInfraredAdapter,
   entity_provider: MyApp.MyEntities}
]

Supervisor.start_link(children, strategy: :one_for_one)

Any adapter key you omit disables that feature. For encrypted transport, set :psk on :device_config:

device_config: [
  name: "my-device",
  psk: "foIclFXDcBlfzi9oQNegJz/uRG/sgdIc956pX+GrC+A="
]

For the full start option list see Espex.Supervisor.

Pushing state

Call push_state/2 from anywhere in your application to broadcast an entity state update to every currently-connected client:

Espex.push_state(%Espex.Proto.SensorStateResponse{
  key: 1003,
  state: 21.3,
  missing_state: false
})

Connected clients

Enumerate the live native-API connections with connected_clients/1, and subscribe to changes by configuring an Espex.ConnectionListener:

Espex.connected_clients(MyApp.EspexServer)
#=> [%Espex.ClientInfo{peer: "192.168.1.5:54312", encrypted?: true, ...}]

Summary

Functions

child_spec/1 — makes {Espex, opts} usable as a child spec.

List the currently-connected native-API clients as Espex.ClientInfo structs.

Return the running server's %DeviceConfig{}. Accepts an optional server name for non-default supervisor setups.

Broadcast an entity-state struct to every currently-connected client.

Broadcast a Z-Wave home-ID change to every connected client.

Start the full Espex supervision tree with the given options.

Functions

child_spec(opts)

@spec child_spec(keyword()) :: Supervisor.child_spec()

child_spec/1 — makes {Espex, opts} usable as a child spec.

connected_clients(server_name \\ Server)

@spec connected_clients(atom()) :: [Espex.ClientInfo.t()]

List the currently-connected native-API clients as Espex.ClientInfo structs.

This is a non-blocking read of the connection Registry — the source of truth for the live set. Pass a custom :server_name if you started the supervisor with one; it defaults to Espex.Server.

Entries appear from TCP accept (so a client that hasn't finished its HelloRequest yet shows client_info: nil / api_version: nil) and vanish when the connection closes. To be told when the set changes without polling, configure an Espex.ConnectionListener.

Espex.connected_clients(MyApp.EspexServer)
#=> [%Espex.ClientInfo{client_info: "Home Assistant 2026.1.0", ...}]

device_config(server \\ Server)

@spec device_config(GenServer.server()) :: Espex.DeviceConfig.t()

Return the running server's %DeviceConfig{}. Accepts an optional server name for non-default supervisor setups.

push_state(server_name \\ Server, struct)

@spec push_state(
  atom(),
  struct()
) :: :ok

Broadcast an entity-state struct to every currently-connected client.

Pass any %Espex.Proto.*StateResponse{} (e.g. %Espex.Proto.SensorStateResponse{key: k, state: 21.3}). Clients that subscribed via SubscribeStatesRequest will receive the frame over their socket.

server_name defaults to Espex.Server — pass your custom name if you started the supervisor with :server_name.

push_zwave_home_id(server_name \\ Server, home_id_bytes)

@spec push_zwave_home_id(atom(), <<_::32>>) :: :ok

Broadcast a Z-Wave home-ID change to every connected client.

Mirrors ESPHome's APIServer::on_zwave_proxy_request, which sends HOME_ID_CHANGE to all active clients rather than only the subscribed one. Home Assistant learns the network identity from this message: the zwave_js integration starts (or updates) its config-flow the moment it arrives, even on a connection that never issued ZWAVE_PROXY_REQUEST_TYPE_SUBSCRIBE. Broadcasting to all is what lets a controller hot-plugged after a client connected be discovered without a reconnect.

A Z-Wave adapter should call this from its home-ID change path (see Espex.ZWaveProxy) instead of messaging the single subscriber. Pass the 4-byte big-endian home ID; a zeroed value is a valid "network gone" signal and is delivered as-is.

server_name defaults to Espex.Server.

start_link(opts)

@spec start_link(keyword()) :: Supervisor.on_start()

Start the full Espex supervision tree with the given options.