xZetsubou / hass-localtuya

🔹 A Home Assistant integration to handle Tuya devices locally "fork from localtuya"
https://xzetsubou.github.io/hass-localtuya/
GNU General Public License v3.0
437 stars 54 forks source link

[Bug]: Disabling a device has no effect #397

Open Lurker00 opened 1 week ago

Lurker00 commented 1 week ago

LocalTuya Version

2024.9.0

Home Assistant Version

2024.11.2

Environment

What happened?

I can disable a device via HA UI: Снимок экрана 2024-11-22 142621

The expected behavior is LocalTuya restart with the device disabled, but it does not happen. But if I enable a previously disabled device, LocalTuya restarts!

Usually I need to disable a device when it is disconnected and I know it couldn't be connected for a long time (e.g. its battery died). Not a big issue, of course, to restart LocalTuya manually, I just wonder why it works in one direction and does not work backward.

I tried to search in HA developers documentation and found nothing to implement. In general, it states that an integration that supports reloading, is reloaded, but, in fact, not always.

Having spare time, please take a look on this.

Steps to reproduce.

Try to disable a device.

Relevant log output

No response

Diagnostics information.

No response

xZetsubou commented 1 week ago

It shouldn't works either disable/enable it shouldn't reload entry, however it possible to avoid reloading while entry with this logic:

        if device_registry.disabled:
            if not device.sub_devices:
                await device.close()
        else:
            if device._is_closing:
                device._is_closing = False
                await device.async_connect(wait=False)

                await hass.config_entries.async_unload_platforms(
                    entry, PLATFORMS.values()
                )
                await hass.config_entries.async_forward_entry_setups(
                    entry, PLATFORMS.values()
                )

This logic can only reload the entry when "enabled only"

        if device_registry.disabled:
            if not device.sub_devices:
                await device.close()
        else:
            await hass.config_entries.async_reload(entry.entry_id)

but is it worth it or just reload the entry is enough tho reloading entry each time disabling the device isn't ideal.

Lurker00 commented 1 week ago

Sorry, I don't understand in which method this logic could be implemented! I found no device_registry in the LocalTuya source code.

either disable/enable it shouldn't reload entry

I refer to the developers docs, which states quite opposite: "_Entity disabling works with entities provided via a config entry or via an entry in configuration.yaml. If your integration is set up via a config entry and supports unloading, Home Assistant will be able to reload your integration after entities have been enabled/disabled to apply the changes without a restart._"

xZetsubou commented 1 week ago

Hm I thought that doesn't effect "device state" and only "entity state" well that makes sense, The delay "30 seconds" after enabling device then reload confused me. I guess this is enough then.

        if device_registry.disabled:
            if not device.sub_devices:
                await device.close()

full code block

# __init__.py
def _run_async_listen(hass: HomeAssistant, entry: ConfigEntry):
    """Start the listing events"""

    @callback
    def _event_filtter(data: dr.EventDeviceRegistryUpdatedData) -> bool:
        identifiers = dr.async_get(hass).async_get(data["device_id"]).identifiers
        is_localtuya = identifiers and DOMAIN in list(identifiers)[0]
        return bool(data["action"] == "update" and is_localtuya)

    async def device_state_changed(event: Event[dr.EventDeviceRegistryUpdatedData]):
        """Close connection if device disabled."""
        if not "disabled_by" in event.data["changes"]:
            return

        device_registry = dr.async_get(hass).async_get(event.data["device_id"])

        if device_registry.primary_config_entry != entry.entry_id:
            return

        hass_localtuya: HassLocalTuyaData = hass.data[DOMAIN][entry.entry_id]

        dev_id = _device_id_by_identifiers(device_registry.identifiers)
        host_ip = entry.data[CONF_DEVICES][dev_id][CONF_HOST]
        device = hass_localtuya.devices.get(host_ip)

        if device_registry.disabled:
            if not device.sub_devices:
                await device.close()
        else:
            # Update now instead of waiting 30sec.
            await hass.config_entries.async_reload(entry.entry_id)

    return hass.bus.async_listen(
        dr.EVENT_DEVICE_REGISTRY_UPDATED, device_state_changed, _event_filtter
    )

def _device_id_by_identifiers(identifiers):
    """Return localtuya device ID by device registry identifiers."""
    return list(identifiers)[0][1].split("_")[-1]

at the end of async_entry_setup add

entry.async_on_unload(_run_async_listen(hass, entry))
Lurker00 commented 1 week ago

Thank you! I'll look into it.