Espex.Supervisor (espex v0.3.0)

Copy Markdown View Source

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,
  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.

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.

Summary

Functions

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

Returns a specification to start this module under a supervisor.

Return the conventional Registry name for a given server name.

Functions

bound_port(supervisor)

@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(init_arg)

Returns a specification to start this module under a supervisor.

See Supervisor.

registry_name(server_name)

@spec registry_name(atom()) :: atom()

Return the conventional Registry name for a given server name.

start_link(opts)

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