From 3e95848d81e2d35eb1413313acd600eab3317cfe Mon Sep 17 00:00:00 2001 From: Andy Mang Date: Wed, 29 Mar 2023 08:33:32 -0400 Subject: [PATCH 1/2] Relying on push instead of polling. Except for the gateway, it will come later once Hilo adds gateway infos to SignalR. Lowering polling frequency since we don't need to poll as much as we are getting data from pushes. --- custom_components/hilo/__init__.py | 21 ++++++++++++++++++--- custom_components/hilo/const.py | 4 ++-- custom_components/hilo/manifest.json | 2 +- 3 files changed, 21 insertions(+), 6 deletions(-) diff --git a/custom_components/hilo/__init__.py b/custom_components/hilo/__init__.py index cf807f5..c269f90 100644 --- a/custom_components/hilo/__init__.py +++ b/custom_components/hilo/__init__.py @@ -243,7 +243,7 @@ def validate_heartbeat(self, event: WebsocketEvent) -> None: LOG.debug(f"Heartbeat: {time_diff(heartbeat_time, event.timestamp)}") @callback - def on_websocket_event(self, event: WebsocketEvent) -> None: + async def on_websocket_event(self, event: WebsocketEvent) -> None: """Define a callback for receiving a websocket event.""" async_dispatcher_send(self._hass, DISPATCHER_TOPIC_WEBSOCKET_EVENT, event) if event.event_type == "COMPLETE": @@ -253,6 +253,12 @@ def on_websocket_event(self, event: WebsocketEvent) -> None: elif event.target == "Heartbeat": self.validate_heartbeat(event) elif event.target == "DevicesValuesReceived": + # When receiving attribute values for unknown devices, assume + # we have refresh the device list. + new_devices = any(self.devices.find_device(item['deviceId']) is None for item in event.arguments[0]) + if new_devices: + await self.devices.update() + updated_devices = self.devices.parse_values_received(event.arguments[0]) # NOTE(dvd): If we don't do this, we need to wait until the coordinator # runs (scan_interval) to have updated data in the dashboard. @@ -260,6 +266,15 @@ def on_websocket_event(self, event: WebsocketEvent) -> None: async_dispatcher_send( self._hass, SIGNAL_UPDATE_ENTITY.format(device.id) ) + elif event.target == "DevicesListChanged": + # DeviceListChanged only triggers when unpairing devices + # Forcing an update when that happens, even tho pyhilo doesn't + # manage device removal currently. + await self.devices.update() + elif event.target == "GatewayValuesReceived": + # Placeholder for new event that will allow Gateway updates without + # calling update_gateway explicitly in async_update + LOG.debug("GatewayValuesReceived") else: LOG.warning(f"Unhandled websocket event: {event}") @@ -405,8 +420,8 @@ async def cancel_websocket_loop(self) -> None: await self._api.websocket.async_disconnect() async def async_update(self) -> None: - """Get updated data from Hilo API.""" - await self.devices.update() + """Updates gateway and tarif periodically.""" + await self.devices.update_gateway() if self.generate_energy_meters: self.check_tarif() diff --git a/custom_components/hilo/const.py b/custom_components/hilo/const.py index 1d0a886..348d197 100644 --- a/custom_components/hilo/const.py +++ b/custom_components/hilo/const.py @@ -32,8 +32,8 @@ CONF_UNTARIFICATED_DEVICES = "untarificated_devices" DEFAULT_UNTARIFICATED_DEVICES = False -DEFAULT_SCAN_INTERVAL = 60 -EVENT_SCAN_INTERVAL = 600 +DEFAULT_SCAN_INTERVAL = 300 +EVENT_SCAN_INTERVAL = 3000 REWARD_SCAN_INTERVAL = 7200 MIN_SCAN_INTERVAL = 15 diff --git a/custom_components/hilo/manifest.json b/custom_components/hilo/manifest.json index 16ebaf2..1207cf7 100644 --- a/custom_components/hilo/manifest.json +++ b/custom_components/hilo/manifest.json @@ -9,7 +9,7 @@ "codeowners": ["@dvd-dev"], "config_flow": true, "documentation": "https://github.com/dvd-dev/hilo", - "iot_class": "cloud_polling", + "iot_class": "cloud_push", "issue_tracker": "https://github.com/dvd-dev/hilo/issues", "requirements": ["python-hilo>=2023.3.1"], "version": "2023.3.2" From 485370e1e562963d08d6a7c115e6212b0bc89ffa Mon Sep 17 00:00:00 2001 From: Andy Mang Date: Wed, 29 Mar 2023 11:22:01 -0400 Subject: [PATCH 2/2] Debounce Light turn on to avoid sending too many API calls on color change / brightness change --- custom_components/hilo/light.py | 31 +++++++++++++++++++++---------- 1 file changed, 21 insertions(+), 10 deletions(-) diff --git a/custom_components/hilo/light.py b/custom_components/hilo/light.py index 94626da..74335cc 100644 --- a/custom_components/hilo/light.py +++ b/custom_components/hilo/light.py @@ -6,13 +6,13 @@ ) from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant +from homeassistant.helpers.debounce import Debouncer from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.util import slugify from . import Hilo, HiloEntity from .const import DOMAIN, LIGHT_CLASSES, LOG - async def async_setup_entry( hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback ) -> None: @@ -21,15 +21,22 @@ async def async_setup_entry( for d in hilo.devices.all: if d.type in LIGHT_CLASSES: - d._entity = HiloLight(hilo, d) + d._entity = HiloLight(hass, hilo, d) entities.append(d._entity) async_add_entities(entities) class HiloLight(HiloEntity, LightEntity): - def __init__(self, hilo: Hilo, device): + def __init__(self, hass: HomeAssistant, hilo: Hilo, device): super().__init__(hilo, device=device, name=device.name) self._attr_unique_id = f"{slugify(device.name)}-light" + self._debounced_turn_on = Debouncer( + hass, + LOG, + cooldown=1, + immediate=True, + function=self._async_debounced_turn_on + ) LOG.debug(f"Setting up Light entity: {self._attr_name}") @property @@ -74,15 +81,19 @@ async def async_turn_off(self, **kwargs): self.async_schedule_update_ha_state(True) async def async_turn_on(self, **kwargs): + self._last_kwargs = kwargs + await self._debounced_turn_on.async_call() + + async def _async_debounced_turn_on(self): LOG.info(f"{self._device._tag} Turning on") await self._device.set_attribute("is_on", True) - if ATTR_BRIGHTNESS in kwargs: + if ATTR_BRIGHTNESS in self._last_kwargs: LOG.info( - f"{self._device._tag} Setting brightness to {kwargs[ATTR_BRIGHTNESS]}" + f"{self._device._tag} Setting brightness to {self._last_kwargs[ATTR_BRIGHTNESS]}" ) - await self._device.set_attribute("intensity", kwargs[ATTR_BRIGHTNESS] / 255) - if ATTR_HS_COLOR in kwargs: - LOG.info(f"{self._device._tag} Setting HS Color to {kwargs[ATTR_HS_COLOR]}") - await self._device.set_attribute("hue", kwargs[ATTR_HS_COLOR][0]) - await self._device.set_attribute("saturation", kwargs[ATTR_HS_COLOR][1]) + await self._device.set_attribute("intensity", self._last_kwargs[ATTR_BRIGHTNESS] / 255) + if ATTR_HS_COLOR in self._last_kwargs: + LOG.info(f"{self._device._tag} Setting HS Color to {self._last_kwargs[ATTR_HS_COLOR]}") + await self._device.set_attribute("hue", self._last_kwargs[ATTR_HS_COLOR][0]) + await self._device.set_attribute("saturation", self._last_kwargs[ATTR_HS_COLOR][1]) self.async_schedule_update_ha_state(True)