Skip to content

Commit

Permalink
overkiz: Periodically refresh states
Browse files Browse the repository at this point in the history
Somfy gates & garage doors will not automatically send state updates.
They have to be explicitly requested. The TaHoma app/web interface
therefore asks all devices to send their state updates on start.

This change adds a new OverkizDeviceRefreshCoordinator for impacted
devices and will periodically (5min) ask the specific device to send
state updates.

Note that even though these devices typically also provide commands
such as refreshPedestrianPosition/refreshPartialPosition but they do
not cause the device to send its actual state to the gateway.

See Somfy-Developer/Somfy-TaHoma-Developer-Mode#26
and iMicknl/ha-tahoma#167.
  • Loading branch information
fetzerch committed Sep 2, 2023
1 parent 03b1c7a commit bf65818
Show file tree
Hide file tree
Showing 3 changed files with 58 additions and 6 deletions.
26 changes: 21 additions & 5 deletions homeassistant/components/overkiz/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,18 +27,20 @@
DOMAIN,
LOGGER,
OVERKIZ_DEVICE_TO_PLATFORM,
OVERKIZ_REFRESH_DEVICE_STATES_DEVICES,
PLATFORMS,
UPDATE_INTERVAL,
UPDATE_INTERVAL_ALL_ASSUMED_STATE,
)
from .coordinator import OverkizDataUpdateCoordinator
from .coordinator import OverkizDataUpdateCoordinator, OverkizDeviceRefreshCoordinator


@dataclass
class HomeAssistantOverkizData:
"""Overkiz data stored in the Home Assistant data object."""

coordinator: OverkizDataUpdateCoordinator
device_refresh_coordinators: list[OverkizDeviceRefreshCoordinator]
platforms: defaultdict[Platform, list[Device]]
scenarios: list[Scenario]

Expand Down Expand Up @@ -96,12 +98,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
)
coordinator.update_interval = UPDATE_INTERVAL_ALL_ASSUMED_STATE

device_refresh_coordinators: list[OverkizDeviceRefreshCoordinator] = []
platforms: defaultdict[Platform, list[Device]] = defaultdict(list)

hass.data.setdefault(DOMAIN, {})[entry.entry_id] = HomeAssistantOverkizData(
coordinator=coordinator, platforms=platforms, scenarios=scenarios
)

# Map Overkiz entities to Home Assistant platform
for device in coordinator.data.values():
LOGGER.debug(
Expand All @@ -117,6 +116,23 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
) or OVERKIZ_DEVICE_TO_PLATFORM.get(device.ui_class):
platforms[platform].append(device)

if (
device.widget in OVERKIZ_REFRESH_DEVICE_STATES_DEVICES
or device.ui_class in OVERKIZ_REFRESH_DEVICE_STATES_DEVICES
):
device_refresh_coordinator = OverkizDeviceRefreshCoordinator(
hass, LOGGER, client=client, device_url=device.device_url
)
await device_refresh_coordinator.async_request_refresh()
device_refresh_coordinators.append(device_refresh_coordinator)

hass.data.setdefault(DOMAIN, {})[entry.entry_id] = HomeAssistantOverkizData(
coordinator=coordinator,
device_refresh_coordinators=device_refresh_coordinators,
platforms=platforms,
scenarios=scenarios,
)

device_registry = dr.async_get(hass)

for gateway in setup.gateways:
Expand Down
7 changes: 7 additions & 0 deletions homeassistant/components/overkiz/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@
UPDATE_INTERVAL: Final = timedelta(seconds=30)
UPDATE_INTERVAL_ALL_ASSUMED_STATE: Final = timedelta(minutes=60)

REFRESH_DEVICE_STATES_INTERVAL: Final = timedelta(minutes=5)

PLATFORMS: list[Platform] = [
Platform.ALARM_CONTROL_PANEL,
Platform.BINARY_SENSOR,
Expand All @@ -59,6 +61,11 @@
UIClass.POD,
]

OVERKIZ_REFRESH_DEVICE_STATES_DEVICES: list[UIClass | UIWidget] = [
UIClass.GARAGE_DOOR,
UIClass.GATE,
]

# Used to map the Somfy widget and ui_class to the Home Assistant platform
OVERKIZ_DEVICE_TO_PLATFORM: dict[UIClass | UIWidget, Platform | None] = {
UIClass.ADJUSTABLE_SLATS_ROLLER_SHUTTER: Platform.COVER,
Expand Down
31 changes: 30 additions & 1 deletion homeassistant/components/overkiz/coordinator.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
from homeassistant.util.decorator import Registry

from .const import DOMAIN, LOGGER, UPDATE_INTERVAL
from .const import DOMAIN, LOGGER, REFRESH_DEVICE_STATES_INTERVAL, UPDATE_INTERVAL

EVENT_HANDLERS: Registry[
str, Callable[[OverkizDataUpdateCoordinator, Event], Coroutine[Any, Any, None]]
Expand Down Expand Up @@ -209,3 +209,32 @@ async def on_execution_state_changed(
ExecutionState.FAILED,
]:
del coordinator.executions[event.exec_id]


class OverkizDeviceRefreshCoordinator(DataUpdateCoordinator[None]):
"""Class to trigger device state refresh for devices on Overkiz platform."""

def __init__(
self,
hass: HomeAssistant,
logger: logging.Logger,
*,
client: OverkizClient,
device_url: str,
) -> None:
"""Initialize data update coordinator."""
self.client = client
self.device_url = device_url
super().__init__(
hass,
logger,
name=f"device refresh for {device_url}",
update_interval=REFRESH_DEVICE_STATES_INTERVAL,
)

# Ensure this coordinator runs periodically even though there is no listener needed.
self.async_add_listener(lambda: None)

async def _async_update_data(self) -> None:
"""Trigger device state refresh on Overkiz platform."""
await self.client.refresh_device_states(self.device_url)

0 comments on commit bf65818

Please sign in to comment.