Espex (espex v0.6.0)

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.

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.

start_link(opts)

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

Start the full Espex supervision tree with the given options.