Repository: home-assistant/core
File: homeassistant/components/velux/entity.py (Python)

Code before:
                self._availability_timer = None

            self._last_update = dt_util.utcnow()

            # Schedule timer to mark unavailable after timeout
            # this is necessary because the Velux API itself has
            # no (pushed) availability updates and simply setting available
            # to false then never makes it to the state machine
            self._availability_timer = async_call_later(
                self.hass,
                AVAILABILITY_TIMEOUT,
                self._async_mark_unavailable,
            )

            # Log when entity comes back online after being unavailable
            if self._unavailable_logged:
                _LOGGER.info("Entity %s is back online", self.entity_id)
                self._unavailable_logged = False

        self.async_write_ha_state()

    @callback
    def _async_mark_unavailable(self, _: datetime) -> None:
        """Mark entity as unavailable after timeout."""
        self._availability_timer = None
        # Will re-evaluate available property and log if needed
        self.async_write_ha_state()

    async def async_added_to_hass(self) -> None:
        """Register callback and store reference for cleanup."""
        self.update_callback = self.after_update_callback
        self.node.register_device_updated_cb(self.update_callback)

    async def async_will_remove_from_hass(self) -> None:
        """Clean up registered callbacks."""
        if self.update_callback:
            self.node.unregister_device_updated_cb(self.update_callback)
            self.update_callback = None

        if self._availability_timer:
            self._availability_timer()
            self._availability_timer = None


Review comment (suggestion):
I would consider this very complex, while I think in the end it shouldn't have to be that complex.

To boil these 2 rules down
1. Whenever a device is offline, we should set it to unavailable. In your comments I read that we don't get availability status updates for devices. If we can't get that, we can't show that either. Not great, but we did what we could. However, a possibility would also be that we lose connection with the KLF200, because maybe we pulled the plug. In that case we should also turn unavailable.
2. for the log when unavailable, I would consider this to happen when the entry is unavailable. So in this context it'd be if we lose connection to the hub, we should log. definitely not for every entity. Now that I type this, I probably have to review the docs of the rule, will put that on my list

Code after:
"""Support for VELUX KLF 200 devices."""

from collections.abc import Awaitable, Callable
import logging

from pyvlx import Node

from homeassistant.helpers.device_registry import DeviceInfo
from homeassistant.helpers.entity import Entity

from .const import DOMAIN

_LOGGER = logging.getLogger(__name__)


class VeluxEntity(Entity):
    """Abstraction for all Velux entities."""

    _attr_should_poll = False
    _attr_has_entity_name = True
    update_callback: Callable[["Node"], Awaitable[None]] | None = None
    _attr_available = True
    _unavailable_logged = False

    def __init__(self, node: Node, config_entry_id: str) -> None:
        """Initialize the Velux device."""
        self.node = node
        unique_id = (
            node.serial_number
            if node.serial_number
            else f"{config_entry_id}_{node.node_id}"
        )
        self._attr_unique_id = unique_id
        self.unsubscribe = None

        self._attr_device_info = DeviceInfo(
            identifiers={
                (
                    DOMAIN,
                    unique_id,
                )
            },
            name=node.name if node.name else f"#{node.node_id}",
            serial_number=node.serial_number,
            via_device=(DOMAIN, f"gateway_{config_entry_id}"),
        )

    async def after_update_callback(self, node) -> None:
        """Call after device was updated."""
        self._attr_available = self.node.pyvlx.get_connected()
        if not self._attr_available:
            if not self._unavailable_logged:
                _LOGGER.info("Entity %s is unavailable", self.entity_id)
                self._unavailable_logged = True
        elif self._unavailable_logged:
            _LOGGER.info("Entity %s is back online", self.entity_id)
            self._unavailable_logged = False

        self.async_write_ha_state()

    async def async_added_to_hass(self) -> None:
        """Register callback and store reference for cleanup."""

        self.update_callback = self.after_update_callback
        self.node.register_device_updated_cb(self.update_callback)

    async def async_will_remove_from_hass(self) -> None:
        """Clean up registered callbacks."""
        if self.update_callback:
            self.node.unregister_device_updated_cb(self.update_callback)
            self.update_callback = None