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

Top-level supervisor for an Espex server instance.

Starts children in `:rest_for_one` order:

  1. `Registry` (duplicate keys) — fan-out point for `Espex.push_state/2`.
  2. A cross-connection state server (internal) — holds the device
     config and adapter modules.
  3. `ThousandIsland` — TCP acceptor pool that spawns a per-client
     connection handler process (internal).
  4. An mDNS advertiser GenServer (internal) — optional, started
     only when `:mdns` is configured.

If the server child crashes, the listener and advertiser restart
too, so live connections drop rather than hold stale references.

Configuration is passed as keyword options:

    Espex.Supervisor.start_link(
      device_config: [name: "my-device"],   # or a %DeviceConfig{}
      port: 6053,                           # overrides device_config.port
      name: :my_espex,                      # supervisor registered name
      server_name: MyApp.EspexServer,       # Espex.Server registered name
      num_acceptors: 10,
      serial_proxy: MyApp.MySerialAdapter,
      zwave_proxy: MyApp.MyZWaveAdapter,
      infrared_proxy: MyApp.MyIRAdapter,
      bluetooth_scanner: MyApp.MyBLEScanner,
      bluetooth_proxy: MyApp.MyBLEProxy,
      entity_provider: MyApp.MyEntities,
      psk_store: MyApp.MyPskStore,        # persists a runtime-provisioned PSK
      connection_listener: MyApp.Tracker, # notified when the connected-client set changes
      mdns: Espex.Mdns.MdnsLite,
      keepalive_idle_ms: 60_000,            # inbound silence before we ping
      keepalive_grace_ms: 60_000,           # further silence before we close
      read_timeout: 180_000                 # hard transport-level backstop
    )

Any adapter key omitted disables that feature. Pass `:mdns` with an
`Espex.Mdns` adapter module (e.g. `Espex.Mdns.MdnsLite`) to advertise
the server over mDNS; omit to skip. Pass `:psk_store` with an
`Espex.PskStore` module to persist a Noise PSK that Home Assistant
provisions or rotates at runtime; omit to apply provisioned keys to
the running server only (lost on restart, with a warning). Pass
`:connection_listener` with an `Espex.ConnectionListener` module to be
notified when the connected-client set changes; start it before the
Espex child (`:rest_for_one`) so it is ready when the listener opens.

## Keepalive

The server pings idle clients the way real ESPHome firmware does:
after `keepalive_idle_ms` without inbound bytes it sends a
`PingRequest`, and closes only after `keepalive_grace_ms` more of
silence. This matters because aioesphomeapi (Home Assistant) skips its
own client→device pings whenever it is *receiving* data — a device that
streams (e.g. BLE advertisements) therefore sees a permanently silent
inbound side on a healthy connection, and any naive read timeout will
cycle it. `read_timeout` (passed through to ThousandIsland) is a hard
backstop that reaps connections wedged at the transport level. Keep it
above `keepalive_idle_ms + keepalive_grace_ms` so a healthy-but-idle
client always gets its full ping-and-grace window before ThousandIsland
times out — the 180 s default clears the 60 s + 60 s keepalive defaults;
if you shorten `read_timeout` or lengthen the keepalive intervals, raise
it to match.

# `bound_port`

```elixir
@spec bound_port(Supervisor.supervisor()) ::
  {:ok, :inet.port_number()} | {:error, term()}
```

Return the port the listener is currently bound to. Useful when
starting the server with `port: 0` (ephemeral) — typically in tests.

# `child_spec`

Returns a specification to start this module under a supervisor.

See `Supervisor`.

# `client_registry_name`

```elixir
@spec client_registry_name(atom()) :: atom()
```

Return the conventional connected-clients Registry name for a given
server name. This is the unique-key registry that backs
`Espex.connected_clients/1`.

# `registry_name`

```elixir
@spec registry_name(atom()) :: atom()
```

Return the conventional Registry name for a given server name.

# `start_link`

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

---

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