# `Espex`
[🔗](https://github.com/bbangert/espex/blob/main/lib/espex.ex#L1)

ESPHome Native API server library.

Espex implements the [ESPHome Native API](https://esphome.io/components/api.html)
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

  * [Architecture](architecture.html) — supervision tree, the
    connection/dispatch split, wire protocol, encryption, and the
    `push_state/2` broadcast path
  * [Entity types](entity_types.html) — per-type cookbook for the
    common ESPHome entities (Switch, BinarySensor, Sensor, Button,
    Light, Cover, Climate) with proto structs and example snippets
  * `Espex.SerialProxy`, `Espex.ZWaveProxy`, `Espex.InfraredProxy`,
    `Espex.BluetoothScanner`, `Espex.BluetoothProxy`,
    `Espex.EntityProvider`, `Espex.Mdns` — the seven behaviours,
    each with callback reference and a complete example adapter

## 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, ...}]

# `child_spec`

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

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

# `connected_clients`

```elixir
@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`

```elixir
@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`

```elixir
@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`

```elixir
@spec push_zwave_home_id(atom(), &lt;&lt;_::32&gt;&gt;) :: :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`

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

Start the full Espex supervision tree with the given options.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
