jheling / freeathome

Free@Home component for Home Assistant
114 stars 41 forks source link

Fix zeroconf multiple configs #204

Closed kingsleyadam closed 1 month ago

kingsleyadam commented 1 month ago

This addresses #176, ZeroConf rediscovering the integration using the ipv6 address.

The primary reason this is happening because the integration is using the zeroconf_info.host to set the uniqueness of the config flow. This forces a new config flow every time the ipv6 address changes. Referencing the home assistant documentation, an ip address should not be used for uniqueness.

I've adjusted this to use the SysAP serial number to define the uniqueness of the config flow. I've done this both on ZeroConf and the User setup flow to ensure a single SysAP can't be setup twice. Also, I've made and adjustment that if the IP address changes, home assistant will pick this up automatically via ZeroConf and would not require any intervention by the user to delete/re-add the integration.

Another issue, Home Assistant will trigger async_step_zeroconf every time it has found any device on the network based on the manifest configuration. This creates unnecessary overhead. I've adjusted the manifest to only trigger the function of the zeroconf name starts with free@home.

Lastly, Home Assistant will trigger the async_step_zeroconf for each IP address it finds, including IPV6. I'm aborting any zeroconf entries that are IPV6, to ensure only IPV4 entries are used.

Note this will trigger a new config entry flow since the uniqueid has changed. But if ignored it will never come back. Or if the old integration is deleted and re-added with this new config, the IP address will be kept up-to-date if anything changes.

Screenshot 2024-10-02 at 15 24 58

Also, sorry for the number of changes, most of them are formatting change as the document wasn't formatted very well. Primary changes are

manifest.json

  "zeroconf": [
    {
      "type": "_http._tcp.local.",
      "name": "free@home*"
    }
  ]

config_flow.py

    async def async_step_zeroconf(self, discovery_info: zeroconf.ZeroconfServiceInfo):
        """Handle zeroconf discovery."""
        if not isinstance(discovery_info.ip_address, IPv4Address):
            return self.async_abort(reason="not_ipv4address")

        friendly_name = discovery_info.name.split(":", 1)[1].split(".", 1)[0]
        freeathome_host = discovery_info.ip_address.exploded

        await self.async_set_unique_id(discovery_info.name)
        self._abort_if_unique_id_configured(updates={CONF_HOST: freeathome_host})

        self.discovered_conf = {
            CONF_NAME: friendly_name,
            CONF_HOST: freeathome_host,
        }

        self.context["title_placeholders"] = self.discovered_conf

        return await self.async_step_user()

User setup flow.

serial_number = settings.get_flag("serialNumber")
await self.async_set_unique_id(
    serial_number if serial_number else ip_adress,
    raise_on_progress=False,
)
jheling commented 1 month ago

Good work!