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

Data structure representing the ESPHome device identity advertised by this
server.

Holds all the fields reported in a `DeviceInfoResponse` when a client
queries the device over the ESPHome Native API, plus the TCP port the
server listens on.

`zwave_feature_flags` and `zwave_home_id` are populated by the consumer
(typically from an `Espex.ZWaveProxy.Adapter`) before building a
`DeviceInfoResponse` — the struct itself is pure.

## `project_name` format

If set, `project_name` must follow the ESPHome `"author.project"`
convention — e.g. `"mycompany.thermostat"`. Home Assistant's ESPHome
integration splits this string on `.` and indexes `[1]` to derive the
model name shown in its device registry; a bare token like `"espex"`
(no dot) makes HA's setup task crash silently and the device never
registers. The default is `""`, which is what HA treats as "no project"
and safely skips.

## `psk` (Noise encryption pre-shared key)

Leave `:psk` as `nil` (the default) to run plaintext only. To enable
`Noise_NNpsk0_25519_ChaChaPoly_SHA256` encryption — matching the
ESPHome Native API's encrypted transport — set `:psk` either to:

  * a raw 32-byte binary, or
  * a base64-encoded string (44 chars incl. `=` padding, matching the
    format used in ESPHome YAML config).

`new/1` normalises both forms to the raw binary. When a PSK is set,
the server rejects plaintext clients — they have to use the key.

# `t`

```elixir
@type t() :: %Espex.DeviceConfig{
  compilation_time: String.t(),
  devices: [Espex.DeviceConfig.Device.t()],
  esphome_version: String.t(),
  friendly_name: String.t(),
  mac_address: String.t(),
  manufacturer: String.t(),
  model: String.t(),
  name: String.t(),
  port: non_neg_integer(),
  project_name: String.t(),
  project_version: String.t(),
  psk: &lt;&lt;_::256&gt;&gt; | nil,
  suggested_area: String.t(),
  zwave_feature_flags: non_neg_integer(),
  zwave_home_id: non_neg_integer()
}
```

# `api_version_major`

```elixir
@spec api_version_major() :: non_neg_integer()
```

Returns the API version major number this server advertises.

# `api_version_minor`

```elixir
@spec api_version_minor() :: non_neg_integer()
```

Returns the API version minor number this server advertises.

# `detect_mac_address`

```elixir
@spec detect_mac_address() :: String.t()
```

Detect the MAC address from the first available network interface.

Tries `eth0`, `end0`, and `wlan0` in order, then falls back to the
first non-loopback interface with a non-zero hardware address. Returns
the address as an `"AA:BB:CC:DD:EE:FF"` string, or
`"00:00:00:00:00:00"` if no suitable interface is found.

# `encrypted?`

```elixir
@spec encrypted?(t()) :: boolean()
```

Whether encryption is enabled (i.e. a PSK is configured).

# `new`

```elixir
@spec new(keyword()) :: t()
```

Build a new `%DeviceConfig{}` from keyword options.

Any key not provided uses the default value. If `:mac_address` is not
provided, the hardware address of the first network interface is
detected automatically at runtime.

## Examples

    Espex.DeviceConfig.new(name: "my-device", port: 6054)

# `server_info`

```elixir
@spec server_info(t()) :: String.t()
```

Build the server info string used in `HelloResponse`.

# `to_device_info_response`

```elixir
@spec to_device_info_response(t(), [struct()]) :: Espex.Proto.DeviceInfoResponse.t()
```

Convert this config to a `DeviceInfoResponse` protobuf struct.

Accepts an optional list of `%SerialProxyInfo{}` structs to populate
the `serial_proxies` field in the response. Sub-devices (if any) come
from the `:devices` field on the config.

# `to_mdns_service`

```elixir
@spec to_mdns_service(t()) :: map()
```

Build an mDNS service map suitable for `MdnsLite.add_mdns_service/1`.

Advertises the `_esphomelib._tcp` service with TXT records containing
the device identity fields that ESPHome clients use for discovery.

---

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