Espex.Mdns behaviour (espex v0.1.1)

Copy Markdown View Source

Behaviour for mDNS advertising adapters.

Implement this module to publish the running ESPHome device as a _esphomelib._tcp service over mDNS — that's what Home Assistant and other ESPHome clients use to auto-discover devices on the LAN without the user typing an IP.

Advertising is opt-in. Pass mdns: MyModule to Espex.start_link/1 and espex will start a GenServer as the last child in the supervision tree that calls advertise/1 once the TCP listener has bound (so ephemeral ports work) and calls withdraw/1 on supervisor shutdown.

For the common Nerves case, use the shipped Espex.Mdns.MdnsLite adapter — it wraps the mdns_lite library and needs no subclassing. Write your own adapter only when you're on a platform :mdns_lite doesn't cover.

Callbacks

CallbackPurpose
advertise/1Register the service with the mDNS responder
withdraw/1Deregister the service (idempotent)

The service map

advertise/1 receives a map produced by Espex.DeviceConfig.to_mdns_service/1 with this shape:

%{
  id: :esphomelib,
  instance_name: :unspecified,
  protocol: "esphomelib",
  transport: "tcp",
  port: 6053,
  txt_payload: [
    "mac=AA:BB:CC:DD:EE:FF",
    "version=2026.1.0",
    "friendly_name=My Device",
    "project_name=mycompany.widget",
    "project_version=1.0.0"
  ]
}

The :id you store from this map is what espex later hands back to withdraw/1 for deregistration.

Example: custom backend

defmodule MyApp.MdnsAdapter do
  @behaviour Espex.Mdns

  @impl true
  def advertise(service) do
    case MyMdnsLib.publish(
           type: "_#{service.protocol}._#{service.transport}",
           port: service.port,
           txt: service.txt_payload
         ) do
      {:ok, _handle} -> :ok
      {:error, _} = err -> err
    end
  end

  @impl true
  def withdraw(_service_id) do
    MyMdnsLib.withdraw_all()
    :ok
  end
end

Wiring

Espex.start_link(
  device_config: [name: "my-device"],
  mdns: MyApp.MdnsAdapter
)

Or for the shipped :mdns_lite-based adapter:

{:ok, _} = Application.ensure_all_started(:mdns_lite)

Espex.start_link(
  device_config: [name: "my-device"],
  mdns: Espex.Mdns.MdnsLite
)

Summary

Callbacks

Register the service with the underlying mDNS responder.

Deregister the previously advertised service. Called from the advertiser's terminate/2. Idempotent — may be invoked on an adapter that never successfully advertised (e.g. if advertise/1 returned {:error, _}).

Callbacks

advertise(service)

@callback advertise(service :: map()) :: :ok | {:error, term()}

Register the service with the underlying mDNS responder.

Called once, from the advertiser's handle_continue/2, after the TCP listener has bound and the ephemeral port (if any) has been resolved.

withdraw(service_id)

@callback withdraw(service_id :: term()) :: :ok

Deregister the previously advertised service. Called from the advertiser's terminate/2. Idempotent — may be invoked on an adapter that never successfully advertised (e.g. if advertise/1 returned {:error, _}).