From 214ae224f864ef85fb076141e18c8d6045b2775b Mon Sep 17 00:00:00 2001 From: Christophe Gagnier Date: Fri, 4 Apr 2025 03:20:47 +0000 Subject: [PATCH 01/22] Change ruyaml for pyyaml This should hopefully help with data corruption. ruyaml could cause some of it, according to this recent issue: pycontribs/ruyaml#118 --- custom_components/hilo/sensor.py | 10 +++++++--- requirements.txt | 1 + 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/custom_components/hilo/sensor.py b/custom_components/hilo/sensor.py index db59c951..0f6b8c41 100755 --- a/custom_components/hilo/sensor.py +++ b/custom_components/hilo/sensor.py @@ -8,7 +8,7 @@ import aiofiles import homeassistant.util.dt as dt_util -import ruyaml as yaml +import yaml from homeassistant.components.integration.sensor import METHOD_LEFT, IntegrationSensor from homeassistant.components.sensor import ( SensorDeviceClass, @@ -43,7 +43,7 @@ from pyhilo.device import HiloDevice from pyhilo.event import Event from pyhilo.util import from_utc_timestamp -from ruyaml.scanner import ScannerError +from yaml.scanner import ScannerError from . import Hilo from .const import ( @@ -763,7 +763,11 @@ async def _load_history(self) -> list: async def _save_history(self, history: list): async with aiofiles.open(self._history_state_yaml, mode="w") as yaml_file: LOG.debug("Saving history state to yaml file") - await yaml_file.write(yaml.dump(history, Dumper=yaml.RoundTripDumper)) + # TODO: Use asyncio.get_running_loop() and run_in_executor to write + # to the file in a non blocking manner. Currently, the file writes + # are properly async but the yaml dump is done synchroniously on the + # main event loop + await yaml_file.write(yaml.dump(history)) class HiloChallengeSensor(HiloEntity, SensorEntity): diff --git a/requirements.txt b/requirements.txt index 25f0776d..9baff1ae 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,3 +2,4 @@ colorlog==6.9.0 homeassistant~=2025.3.4 pip>=21.3.1 ruff==0.11.2 +pyyaml>=6.0.2 \ No newline at end of file From 3b8294c0bcb684c80d6e1ab72b0ea8262c50b458 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 4 Apr 2025 08:39:35 +0000 Subject: [PATCH 02/22] Bump ruff from 0.11.2 to 0.11.3 Bumps [ruff](https://github.com/astral-sh/ruff) from 0.11.2 to 0.11.3. - [Release notes](https://github.com/astral-sh/ruff/releases) - [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md) - [Commits](https://github.com/astral-sh/ruff/compare/0.11.2...0.11.3) --- updated-dependencies: - dependency-name: ruff dependency-version: 0.11.3 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 9baff1ae..6ea55297 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,5 @@ colorlog==6.9.0 homeassistant~=2025.3.4 pip>=21.3.1 -ruff==0.11.2 +ruff==0.11.3 pyyaml>=6.0.2 \ No newline at end of file From f18a81fc5828a841b5cb4b4f83685896dc85498d Mon Sep 17 00:00:00 2001 From: "Max Y." <44422604+maxyvon@users.noreply.github.com> Date: Wed, 2 Apr 2025 17:46:32 -0400 Subject: [PATCH 03/22] Update des tarifs d'Hydro --- custom_components/hilo/const.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/custom_components/hilo/const.py b/custom_components/hilo/const.py index 6a86e7e9..8763494d 100755 --- a/custom_components/hilo/const.py +++ b/custom_components/hilo/const.py @@ -49,25 +49,25 @@ CONF_TARIFF = { "rate d": { "low_threshold": 40, - "low": 0.06704, - "medium": 0.10342, + "low": 0.06905, + "medium": 0.10652, "high": 0, - "access": 0.44810, + "access": 0.46154, "reward_rate": 0.55, }, "flex d": { "low_threshold": 40, - "low": 0.04719, - "medium": 0.08116, - "high": 0.55132, - "access": 0.44810, + "low": 0.04774, + "medium": 0.08699, + "high": 0.45088, + "access": 0.46154, "reward_rate": 0.55, }, } CONF_HIGH_PERIODS = { "am": {"from": time(6, 00, 00), "to": time(9, 0, 0)}, - "pm": {"from": time(16, 0, 0), "to": time(19, 0, 0)}, + "pm": {"from": time(16, 0, 0), "to": time(20, 0, 0)}, } TARIFF_LIST = ["high", "medium", "low"] From 5627033745048a0e1f492ae3841f93bc7d2fdcd9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 31 Mar 2025 08:14:27 +0000 Subject: [PATCH 04/22] Bump crazy-max/ghaction-github-labeler from 5.2.0 to 5.3.0 Bumps [crazy-max/ghaction-github-labeler](https://github.com/crazy-max/ghaction-github-labeler) from 5.2.0 to 5.3.0. - [Release notes](https://github.com/crazy-max/ghaction-github-labeler/releases) - [Commits](https://github.com/crazy-max/ghaction-github-labeler/compare/v5.2.0...v5.3.0) --- updated-dependencies: - dependency-name: crazy-max/ghaction-github-labeler dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- .github/workflows/labeler.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/labeler.yml b/.github/workflows/labeler.yml index 78777fac..00225021 100644 --- a/.github/workflows/labeler.yml +++ b/.github/workflows/labeler.yml @@ -13,7 +13,7 @@ jobs: - name: Check out the repository uses: actions/checkout@v4.2.2 - name: Run Labeler - uses: crazy-max/ghaction-github-labeler@v5.2.0 + uses: crazy-max/ghaction-github-labeler@v5.3.0 with: configuration-path: .github/labels.yml skip-delete: true From 4bd1947a73e871e96ca73675400aca23d23c9ac8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 31 Mar 2025 08:33:45 +0000 Subject: [PATCH 05/22] Bump flake8 from 7.1.2 to 7.2.0 in /.github/workflows Bumps [flake8](https://github.com/pycqa/flake8) from 7.1.2 to 7.2.0. - [Commits](https://github.com/pycqa/flake8/compare/7.1.2...7.2.0) --- updated-dependencies: - dependency-name: flake8 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- .github/workflows/constraints.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/constraints.txt b/.github/workflows/constraints.txt index 55dfd848..831a15a1 100644 --- a/.github/workflows/constraints.txt +++ b/.github/workflows/constraints.txt @@ -1,5 +1,5 @@ black==25.1.0 pre-commit==4.2.0 pip==25.0.1 -flake8==7.1.2 +flake8==7.2.0 reorder-python-imports==3.14.0 From dc8810064fb9dd165d5cb756c6e2591251b7f1db Mon Sep 17 00:00:00 2001 From: Christophe Gagnier Date: Fri, 28 Mar 2025 21:50:36 +0000 Subject: [PATCH 06/22] Add missing virtual env activation in develop script --- Dockerfile.dev | 4 +++- scripts/develop | 5 ++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/Dockerfile.dev b/Dockerfile.dev index aec87030..8e7dc017 100644 --- a/Dockerfile.dev +++ b/Dockerfile.dev @@ -1,4 +1,6 @@ # From https://github.com/home-assistant/core/blob/dev/Dockerfile.dev +# This gets us an environment as close as possible to the default Home Assistant +# dev environment. FROM mcr.microsoft.com/devcontainers/python:1-3.13 @@ -16,7 +18,7 @@ RUN \ curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - \ && apt-get update \ && DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \ - # Additional library needed by some tests and accordingly by VScode Tests Discovery + # Additional library needed by Home Assistant to simulate a real environment bluez \ ffmpeg \ libudev-dev \ diff --git a/scripts/develop b/scripts/develop index 61a2ab2a..e7dfa5d8 100755 --- a/scripts/develop +++ b/scripts/develop @@ -4,6 +4,9 @@ set -e cd "$(dirname "$0")/.." +# Activate the virtual environment +source "${VIRTUAL_ENV}/bin/activate" + # Create config dir if not present if [[ ! -d "${PWD}/config" ]]; then mkdir -p "${PWD}/config" @@ -19,7 +22,7 @@ export PYTHONPATH="${PYTHONPATH}:${PWD}/custom_components" # Check if python-hilo exists and install it in development mode if [[ -d "/workspaces/python-hilo/pyhilo" ]]; then echo "Installing custom version of python-hilo..." - pip install -e "/workspaces/python-hilo" + uv pip install -e "/workspaces/python-hilo" SKIP_PACKAGES="--skip-pip-packages python-hilo" else echo "Using PyPI version of python-hilo..." From 07a410612d2bbcef068272880bb14e732e0045ff Mon Sep 17 00:00:00 2001 From: Abigail Asselin Date: Sun, 16 Mar 2025 22:25:20 +0000 Subject: [PATCH 07/22] Add GraphQlHelper integration to Hilo component --- custom_components/hilo/__init__.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/custom_components/hilo/__init__.py b/custom_components/hilo/__init__.py index 21807845..d18c26da 100644 --- a/custom_components/hilo/__init__.py +++ b/custom_components/hilo/__init__.py @@ -1,4 +1,5 @@ """Support for Hilo automation systems.""" + from __future__ import annotations import asyncio @@ -44,6 +45,7 @@ from pyhilo import API from pyhilo.device import HiloDevice from pyhilo.devices import Devices +from pyhilo.graphql import GraphQlHelper from pyhilo.event import Event from pyhilo.exceptions import HiloError, InvalidCredentialsError, WebsocketError from pyhilo.util import from_utc_timestamp, time_diff @@ -233,6 +235,7 @@ def __init__(self, hass: HomeAssistant, entry: ConfigEntry, api: API) -> None: self.find_meter(self._hass) self.entry = entry self.devices: Devices = Devices(api) + self.graphql_helper: GraphQlHelper = GraphQlHelper(api, self.devices) self.challenge_id = 0 self._websocket_reconnect_tasks: list[asyncio.Task | None] = [None, None] self._update_task: list[asyncio.Task | None] = [None, None] @@ -552,6 +555,7 @@ async def async_init(self, scan_interval: int) -> None: assert self._api.websocket await self.devices.async_init() + await self.graphql_helper.async_init() _async_register_custom_device( self._hass, self.entry, self.devices.find_device(1) From b933084cfc9e5e3e58772c850d5734f3376117bb Mon Sep 17 00:00:00 2001 From: Abigail Asselin Date: Mon, 24 Mar 2025 23:46:48 +0000 Subject: [PATCH 08/22] Add callback --- custom_components/hilo/__init__.py | 74 +++++++++++++++++------------- custom_components/hilo/entity.py | 2 +- 2 files changed, 44 insertions(+), 32 deletions(-) diff --git a/custom_components/hilo/__init__.py b/custom_components/hilo/__init__.py index d18c26da..02a85f27 100644 --- a/custom_components/hilo/__init__.py +++ b/custom_components/hilo/__init__.py @@ -6,7 +6,7 @@ from collections import OrderedDict from datetime import datetime, timedelta import traceback -from typing import TYPE_CHECKING, Union +from typing import TYPE_CHECKING, List, Optional, Union from homeassistant.components.select import ( ATTR_OPTION, @@ -239,6 +239,7 @@ def __init__(self, hass: HomeAssistant, entry: ConfigEntry, api: API) -> None: self.challenge_id = 0 self._websocket_reconnect_tasks: list[asyncio.Task | None] = [None, None] self._update_task: list[asyncio.Task | None] = [None, None] + self.subscriptions: List[Optional[asyncio.Task]] = [None] self.invocations = { 0: self.subscribe_to_location, 1: self.subscribe_to_challenge, @@ -359,26 +360,26 @@ async def _handle_challenge_events(self, event: WebsocketEvent) -> None: async def _handle_device_events(self, event: WebsocketEvent) -> None: """Handle all device-related websocket events.""" - if event.target == "DevicesValuesReceived": - new_devices = any( - self.devices.find_device(item["deviceId"]) is None - for item in event.arguments[0] - ) - if new_devices: - LOG.warning( - "Device list appears to be desynchronized, forcing a refresh thru the API..." - ) - 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. - for device in updated_devices: - async_dispatcher_send( - self._hass, SIGNAL_UPDATE_ENTITY.format(device.id) - ) - - elif event.target == "DeviceListInitialValuesReceived": + # if event.target == "DevicesValuesReceived": + # new_devices = any( + # self.devices.find_device(item["deviceId"]) is None + # for item in event.arguments[0] + # ) + # if new_devices: + # LOG.warning( + # "Device list appears to be desynchronized, forcing a refresh thru the API..." + # ) + # 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. + # for device in updated_devices: + # async_dispatcher_send( + # self._hass, SIGNAL_UPDATE_ENTITY.format(device.id) + # ) + + if event.target == "DeviceListInitialValuesReceived": await self.devices.update_devicelist_from_signalr(event.arguments[0]) elif event.target == "DeviceListUpdatedValuesReceived": @@ -400,17 +401,17 @@ async def _handle_device_events(self, event: WebsocketEvent) -> None: elif event.target == "DeviceDeleted": LOG.debug("Received 'DeviceDeleted' message, not implemented yet.") - elif event.target == "GatewayValuesReceived": - gateway = self.devices.find_device(1) - if gateway: - gateway.id = event.arguments[0][0]["deviceId"] - LOG.debug(f"Updated Gateway's deviceId from default 1 to {gateway.id}") + # elif event.target == "GatewayValuesReceived": + # gateway = self.devices.find_device(1) + # if gateway: + # gateway.id = event.arguments[0][0]["deviceId"] + # LOG.debug(f"Updated Gateway's deviceId from default 1 to {gateway.id}") - updated_devices = self.devices.parse_values_received(event.arguments[0]) - for device in updated_devices: - async_dispatcher_send( - self._hass, SIGNAL_UPDATE_ENTITY.format(device.id) - ) + # updated_devices = self.devices.parse_values_received(event.arguments[0]) + # for device in updated_devices: + # async_dispatcher_send( + # self._hass, SIGNAL_UPDATE_ENTITY.format(device.id) + # ) @callback async def on_websocket_event(self, event: WebsocketEvent) -> None: @@ -509,6 +510,7 @@ def _get_unknown_source_tracker(self) -> HiloDevice: "supportedAttributes": "Power", "settableAttributes": "", "id": 0, + "hilo_id": "", "identifier": "hass-hilo-unknown_source_tracker", "provider": 0, "model_number": "Hass-hilo-2022.1", @@ -556,6 +558,12 @@ async def async_init(self, scan_interval: int) -> None: await self.devices.async_init() await self.graphql_helper.async_init() + self.subscriptions[0] = asyncio.create_task( + self.graphql_helper.subscribe_to_device_updated( + self.devices.location_hilo_id, + self.handle_subscription_result, + ) + ) _async_register_custom_device( self._hass, self.entry, self.devices.find_device(1) @@ -923,3 +931,7 @@ def async_migrate_unique_id( new_unique_id, ) entity_registry.async_update_entity(entity_id, new_unique_id=new_unique_id) + + @callback + def handle_subscription_result(self, hilo_id: str) -> None: + async_dispatcher_send(self._hass, SIGNAL_UPDATE_ENTITY.format(hilo_id)) diff --git a/custom_components/hilo/entity.py b/custom_components/hilo/entity.py index 7852c0e8..72581e10 100644 --- a/custom_components/hilo/entity.py +++ b/custom_components/hilo/entity.py @@ -83,7 +83,7 @@ async def async_added_to_hass(self): await super().async_added_to_hass() self._remove_signal_update = async_dispatcher_connect( self._hilo._hass, - SIGNAL_UPDATE_ENTITY.format(self._device.id), + SIGNAL_UPDATE_ENTITY.format(self._device.hilo_id), self._update_callback, ) From 334eee3fe35a957d6b9d07262b82b7fb3a4709e8 Mon Sep 17 00:00:00 2001 From: "Ian C." <108159253+ic-dev21@users.noreply.github.com> Date: Mon, 24 Mar 2025 21:18:29 -0400 Subject: [PATCH 09/22] Ignore local python hilo version, fix devcontainer --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 6ea55297..abbf90e7 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,5 @@ colorlog==6.9.0 -homeassistant~=2025.3.4 +homeassistant~=2025.3.3 pip>=21.3.1 ruff==0.11.3 pyyaml>=6.0.2 \ No newline at end of file From db74e920726f2cf669642d7b2a24ed745681091f Mon Sep 17 00:00:00 2001 From: "Ian C." <108159253+ic-dev21@users.noreply.github.com> Date: Tue, 25 Mar 2025 20:15:30 -0400 Subject: [PATCH 10/22] Initial linting --- custom_components/hilo/__init__.py | 2 +- custom_components/hilo/sensor.py | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/custom_components/hilo/__init__.py b/custom_components/hilo/__init__.py index 02a85f27..0a16dfd5 100644 --- a/custom_components/hilo/__init__.py +++ b/custom_components/hilo/__init__.py @@ -45,9 +45,9 @@ from pyhilo import API from pyhilo.device import HiloDevice from pyhilo.devices import Devices -from pyhilo.graphql import GraphQlHelper from pyhilo.event import Event from pyhilo.exceptions import HiloError, InvalidCredentialsError, WebsocketError +from pyhilo.graphql import GraphQlHelper from pyhilo.util import from_utc_timestamp, time_diff from pyhilo.websocket import WebsocketEvent diff --git a/custom_components/hilo/sensor.py b/custom_components/hilo/sensor.py index 0f6b8c41..0286494e 100755 --- a/custom_components/hilo/sensor.py +++ b/custom_components/hilo/sensor.py @@ -28,8 +28,6 @@ UnitOfPower, UnitOfSoundPressure, UnitOfTemperature, -) -from homeassistant.const import ( __short_version__ as current_version, ) from homeassistant.core import HomeAssistant @@ -38,6 +36,7 @@ from homeassistant.helpers.event import async_track_state_change_event from homeassistant.helpers.restore_state import RestoreEntity from homeassistant.util import Throttle, slugify +import homeassistant.util.dt as dt_util from packaging.version import Version from pyhilo.const import UNMONITORED_DEVICES from pyhilo.device import HiloDevice From 9e6ed1f6424e1e202aef764627fe1d618af08407 Mon Sep 17 00:00:00 2001 From: "Ian C." <108159253+ic-dev21@users.noreply.github.com> Date: Tue, 25 Mar 2025 20:22:53 -0400 Subject: [PATCH 11/22] Remove unused imports + isort --- custom_components/hilo/__init__.py | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/custom_components/hilo/__init__.py b/custom_components/hilo/__init__.py index 0a16dfd5..499ca4a1 100644 --- a/custom_components/hilo/__init__.py +++ b/custom_components/hilo/__init__.py @@ -6,7 +6,7 @@ from collections import OrderedDict from datetime import datetime, timedelta import traceback -from typing import TYPE_CHECKING, List, Optional, Union +from typing import TYPE_CHECKING, List, Optional from homeassistant.components.select import ( ATTR_OPTION, @@ -16,7 +16,6 @@ from homeassistant.components.sensor import SensorDeviceClass from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( - ATTR_CONNECTIONS, ATTR_DEVICE_CLASS, ATTR_UNIT_OF_MEASUREMENT, CONF_SCAN_INTERVAL, @@ -32,16 +31,9 @@ device_registry as dr, entity_registry as er, ) -from homeassistant.helpers.dispatcher import ( - async_dispatcher_connect, - async_dispatcher_send, -) -from homeassistant.helpers.entity import DeviceInfo +from homeassistant.helpers.dispatcher import async_dispatcher_send from homeassistant.helpers.event import async_call_later -from homeassistant.helpers.update_coordinator import ( - CoordinatorEntity, - DataUpdateCoordinator, -) +from homeassistant.helpers.update_coordinator import DataUpdateCoordinator from pyhilo import API from pyhilo.device import HiloDevice from pyhilo.devices import Devices From d26aa127ca97c54eb9d4a6b4af369b8518df1136 Mon Sep 17 00:00:00 2001 From: Abigail Asselin Date: Fri, 28 Mar 2025 13:28:40 -0400 Subject: [PATCH 12/22] revert comments --- custom_components/hilo/__init__.py | 62 +++++++++++++++--------------- 1 file changed, 31 insertions(+), 31 deletions(-) diff --git a/custom_components/hilo/__init__.py b/custom_components/hilo/__init__.py index 499ca4a1..f572ebdf 100644 --- a/custom_components/hilo/__init__.py +++ b/custom_components/hilo/__init__.py @@ -352,26 +352,26 @@ async def _handle_challenge_events(self, event: WebsocketEvent) -> None: async def _handle_device_events(self, event: WebsocketEvent) -> None: """Handle all device-related websocket events.""" - # if event.target == "DevicesValuesReceived": - # new_devices = any( - # self.devices.find_device(item["deviceId"]) is None - # for item in event.arguments[0] - # ) - # if new_devices: - # LOG.warning( - # "Device list appears to be desynchronized, forcing a refresh thru the API..." - # ) - # 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. - # for device in updated_devices: - # async_dispatcher_send( - # self._hass, SIGNAL_UPDATE_ENTITY.format(device.id) - # ) - - if event.target == "DeviceListInitialValuesReceived": + if event.target == "DevicesValuesReceived": + new_devices = any( + self.devices.find_device(item["deviceId"]) is None + for item in event.arguments[0] + ) + if new_devices: + LOG.warning( + "Device list appears to be desynchronized, forcing a refresh thru the API..." + ) + 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. + for device in updated_devices: + async_dispatcher_send( + self._hass, SIGNAL_UPDATE_ENTITY.format(device.id) + ) + + elif event.target == "DeviceListInitialValuesReceived": await self.devices.update_devicelist_from_signalr(event.arguments[0]) elif event.target == "DeviceListUpdatedValuesReceived": @@ -393,17 +393,17 @@ async def _handle_device_events(self, event: WebsocketEvent) -> None: elif event.target == "DeviceDeleted": LOG.debug("Received 'DeviceDeleted' message, not implemented yet.") - # elif event.target == "GatewayValuesReceived": - # gateway = self.devices.find_device(1) - # if gateway: - # gateway.id = event.arguments[0][0]["deviceId"] - # LOG.debug(f"Updated Gateway's deviceId from default 1 to {gateway.id}") - - # updated_devices = self.devices.parse_values_received(event.arguments[0]) - # for device in updated_devices: - # async_dispatcher_send( - # self._hass, SIGNAL_UPDATE_ENTITY.format(device.id) - # ) + elif event.target == "GatewayValuesReceived": + gateway = self.devices.find_device(1) + if gateway: + gateway.id = event.arguments[0][0]["deviceId"] + LOG.debug(f"Updated Gateway's deviceId from default 1 to {gateway.id}") + + updated_devices = self.devices.parse_values_received(event.arguments[0]) + for device in updated_devices: + async_dispatcher_send( + self._hass, SIGNAL_UPDATE_ENTITY.format(device.id) + ) @callback async def on_websocket_event(self, event: WebsocketEvent) -> None: From 77cf6f96f26217a508b1578eb88d66a1a303295f Mon Sep 17 00:00:00 2001 From: "Ian C." <108159253+ic-dev21@users.noreply.github.com> Date: Sat, 29 Mar 2025 06:34:53 -0400 Subject: [PATCH 13/22] Add utility_meter to devcontainer config Pour testing --- config/configuration.yaml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/config/configuration.yaml b/config/configuration.yaml index 79295e24..ec728449 100644 --- a/config/configuration.yaml +++ b/config/configuration.yaml @@ -2,6 +2,9 @@ # https://www.home-assistant.io/integrations/default_config/ default_config: +# This will let us test energy meters: +utility_meter: + # https://www.home-assistant.io/integrations/homeassistant/ homeassistant: debug: true @@ -15,4 +18,4 @@ logger: pyhilo: debug # https://www.home-assistant.io/integrations/debugpy/ -debugpy: \ No newline at end of file +debugpy: From e6270653affffd2fc6469c7932e3e620fcf64c75 Mon Sep 17 00:00:00 2001 From: "Ian C." <108159253+ic-dev21@users.noreply.github.com> Date: Sat, 29 Mar 2025 06:35:21 -0400 Subject: [PATCH 14/22] Bump up versions --- custom_components/hilo/manifest.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/custom_components/hilo/manifest.json b/custom_components/hilo/manifest.json index 77349b3f..1f40704f 100755 --- a/custom_components/hilo/manifest.json +++ b/custom_components/hilo/manifest.json @@ -11,6 +11,6 @@ "documentation": "https://github.com/dvd-dev/hilo", "iot_class": "cloud_push", "issue_tracker": "https://github.com/dvd-dev/hilo/issues", - "requirements": ["python-hilo>=2025.2.1"], - "version": "2025.3.1" + "requirements": ["python-hilo>=2025.4.1"], + "version": "2025.4.1" } From 11e9b049f58947f9d832371be1a7cd20ab7ecf3b Mon Sep 17 00:00:00 2001 From: Abigail Asselin Date: Sun, 16 Mar 2025 22:25:20 +0000 Subject: [PATCH 15/22] Add GraphQlHelper integration to Hilo component --- custom_components/hilo/__init__.py | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/custom_components/hilo/__init__.py b/custom_components/hilo/__init__.py index f572ebdf..c47be344 100644 --- a/custom_components/hilo/__init__.py +++ b/custom_components/hilo/__init__.py @@ -37,9 +37,9 @@ from pyhilo import API from pyhilo.device import HiloDevice from pyhilo.devices import Devices +from pyhilo.graphql import GraphQlHelper from pyhilo.event import Event from pyhilo.exceptions import HiloError, InvalidCredentialsError, WebsocketError -from pyhilo.graphql import GraphQlHelper from pyhilo.util import from_utc_timestamp, time_diff from pyhilo.websocket import WebsocketEvent @@ -550,12 +550,6 @@ async def async_init(self, scan_interval: int) -> None: await self.devices.async_init() await self.graphql_helper.async_init() - self.subscriptions[0] = asyncio.create_task( - self.graphql_helper.subscribe_to_device_updated( - self.devices.location_hilo_id, - self.handle_subscription_result, - ) - ) _async_register_custom_device( self._hass, self.entry, self.devices.find_device(1) From 264f4a743b188219178752453061157931235e74 Mon Sep 17 00:00:00 2001 From: Abigail Asselin Date: Mon, 24 Mar 2025 23:46:48 +0000 Subject: [PATCH 16/22] Add callback --- custom_components/hilo/__init__.py | 68 ++++++++++++++++-------------- 1 file changed, 37 insertions(+), 31 deletions(-) diff --git a/custom_components/hilo/__init__.py b/custom_components/hilo/__init__.py index c47be344..e966d5a0 100644 --- a/custom_components/hilo/__init__.py +++ b/custom_components/hilo/__init__.py @@ -6,7 +6,7 @@ from collections import OrderedDict from datetime import datetime, timedelta import traceback -from typing import TYPE_CHECKING, List, Optional +from typing import TYPE_CHECKING, List, Optional, Union from homeassistant.components.select import ( ATTR_OPTION, @@ -352,26 +352,26 @@ async def _handle_challenge_events(self, event: WebsocketEvent) -> None: async def _handle_device_events(self, event: WebsocketEvent) -> None: """Handle all device-related websocket events.""" - if event.target == "DevicesValuesReceived": - new_devices = any( - self.devices.find_device(item["deviceId"]) is None - for item in event.arguments[0] - ) - if new_devices: - LOG.warning( - "Device list appears to be desynchronized, forcing a refresh thru the API..." - ) - 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. - for device in updated_devices: - async_dispatcher_send( - self._hass, SIGNAL_UPDATE_ENTITY.format(device.id) - ) - - elif event.target == "DeviceListInitialValuesReceived": + # if event.target == "DevicesValuesReceived": + # new_devices = any( + # self.devices.find_device(item["deviceId"]) is None + # for item in event.arguments[0] + # ) + # if new_devices: + # LOG.warning( + # "Device list appears to be desynchronized, forcing a refresh thru the API..." + # ) + # 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. + # for device in updated_devices: + # async_dispatcher_send( + # self._hass, SIGNAL_UPDATE_ENTITY.format(device.id) + # ) + + if event.target == "DeviceListInitialValuesReceived": await self.devices.update_devicelist_from_signalr(event.arguments[0]) elif event.target == "DeviceListUpdatedValuesReceived": @@ -393,17 +393,17 @@ async def _handle_device_events(self, event: WebsocketEvent) -> None: elif event.target == "DeviceDeleted": LOG.debug("Received 'DeviceDeleted' message, not implemented yet.") - elif event.target == "GatewayValuesReceived": - gateway = self.devices.find_device(1) - if gateway: - gateway.id = event.arguments[0][0]["deviceId"] - LOG.debug(f"Updated Gateway's deviceId from default 1 to {gateway.id}") + # elif event.target == "GatewayValuesReceived": + # gateway = self.devices.find_device(1) + # if gateway: + # gateway.id = event.arguments[0][0]["deviceId"] + # LOG.debug(f"Updated Gateway's deviceId from default 1 to {gateway.id}") - updated_devices = self.devices.parse_values_received(event.arguments[0]) - for device in updated_devices: - async_dispatcher_send( - self._hass, SIGNAL_UPDATE_ENTITY.format(device.id) - ) + # updated_devices = self.devices.parse_values_received(event.arguments[0]) + # for device in updated_devices: + # async_dispatcher_send( + # self._hass, SIGNAL_UPDATE_ENTITY.format(device.id) + # ) @callback async def on_websocket_event(self, event: WebsocketEvent) -> None: @@ -550,6 +550,12 @@ async def async_init(self, scan_interval: int) -> None: await self.devices.async_init() await self.graphql_helper.async_init() + self.subscriptions[0] = asyncio.create_task( + self.graphql_helper.subscribe_to_device_updated( + self.devices.location_hilo_id, + self.handle_subscription_result, + ) + ) _async_register_custom_device( self._hass, self.entry, self.devices.find_device(1) From 62357ea4e85d8872c8c074ceccc9754b82bda597 Mon Sep 17 00:00:00 2001 From: "Ian C." <108159253+ic-dev21@users.noreply.github.com> Date: Tue, 25 Mar 2025 20:15:30 -0400 Subject: [PATCH 17/22] Initial linting --- custom_components/hilo/__init__.py | 2 +- custom_components/hilo/sensor.py | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/custom_components/hilo/__init__.py b/custom_components/hilo/__init__.py index e966d5a0..ff979dc1 100644 --- a/custom_components/hilo/__init__.py +++ b/custom_components/hilo/__init__.py @@ -37,9 +37,9 @@ from pyhilo import API from pyhilo.device import HiloDevice from pyhilo.devices import Devices -from pyhilo.graphql import GraphQlHelper from pyhilo.event import Event from pyhilo.exceptions import HiloError, InvalidCredentialsError, WebsocketError +from pyhilo.graphql import GraphQlHelper from pyhilo.util import from_utc_timestamp, time_diff from pyhilo.websocket import WebsocketEvent diff --git a/custom_components/hilo/sensor.py b/custom_components/hilo/sensor.py index 0286494e..53142a71 100755 --- a/custom_components/hilo/sensor.py +++ b/custom_components/hilo/sensor.py @@ -7,7 +7,6 @@ from os.path import isfile import aiofiles -import homeassistant.util.dt as dt_util import yaml from homeassistant.components.integration.sensor import METHOD_LEFT, IntegrationSensor from homeassistant.components.sensor import ( From c38f895decb07cbb1696169f1a836a716ed43a3b Mon Sep 17 00:00:00 2001 From: "Ian C." <108159253+ic-dev21@users.noreply.github.com> Date: Tue, 25 Mar 2025 20:22:53 -0400 Subject: [PATCH 18/22] Remove unused imports + isort --- custom_components/hilo/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/custom_components/hilo/__init__.py b/custom_components/hilo/__init__.py index ff979dc1..499ca4a1 100644 --- a/custom_components/hilo/__init__.py +++ b/custom_components/hilo/__init__.py @@ -6,7 +6,7 @@ from collections import OrderedDict from datetime import datetime, timedelta import traceback -from typing import TYPE_CHECKING, List, Optional, Union +from typing import TYPE_CHECKING, List, Optional from homeassistant.components.select import ( ATTR_OPTION, From 4fa59501c4e66c193cc4d38dc34d636654650c56 Mon Sep 17 00:00:00 2001 From: Abigail Asselin Date: Fri, 28 Mar 2025 13:28:40 -0400 Subject: [PATCH 19/22] revert comments --- custom_components/hilo/__init__.py | 62 +++++++++++++++--------------- 1 file changed, 31 insertions(+), 31 deletions(-) diff --git a/custom_components/hilo/__init__.py b/custom_components/hilo/__init__.py index 499ca4a1..f572ebdf 100644 --- a/custom_components/hilo/__init__.py +++ b/custom_components/hilo/__init__.py @@ -352,26 +352,26 @@ async def _handle_challenge_events(self, event: WebsocketEvent) -> None: async def _handle_device_events(self, event: WebsocketEvent) -> None: """Handle all device-related websocket events.""" - # if event.target == "DevicesValuesReceived": - # new_devices = any( - # self.devices.find_device(item["deviceId"]) is None - # for item in event.arguments[0] - # ) - # if new_devices: - # LOG.warning( - # "Device list appears to be desynchronized, forcing a refresh thru the API..." - # ) - # 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. - # for device in updated_devices: - # async_dispatcher_send( - # self._hass, SIGNAL_UPDATE_ENTITY.format(device.id) - # ) - - if event.target == "DeviceListInitialValuesReceived": + if event.target == "DevicesValuesReceived": + new_devices = any( + self.devices.find_device(item["deviceId"]) is None + for item in event.arguments[0] + ) + if new_devices: + LOG.warning( + "Device list appears to be desynchronized, forcing a refresh thru the API..." + ) + 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. + for device in updated_devices: + async_dispatcher_send( + self._hass, SIGNAL_UPDATE_ENTITY.format(device.id) + ) + + elif event.target == "DeviceListInitialValuesReceived": await self.devices.update_devicelist_from_signalr(event.arguments[0]) elif event.target == "DeviceListUpdatedValuesReceived": @@ -393,17 +393,17 @@ async def _handle_device_events(self, event: WebsocketEvent) -> None: elif event.target == "DeviceDeleted": LOG.debug("Received 'DeviceDeleted' message, not implemented yet.") - # elif event.target == "GatewayValuesReceived": - # gateway = self.devices.find_device(1) - # if gateway: - # gateway.id = event.arguments[0][0]["deviceId"] - # LOG.debug(f"Updated Gateway's deviceId from default 1 to {gateway.id}") - - # updated_devices = self.devices.parse_values_received(event.arguments[0]) - # for device in updated_devices: - # async_dispatcher_send( - # self._hass, SIGNAL_UPDATE_ENTITY.format(device.id) - # ) + elif event.target == "GatewayValuesReceived": + gateway = self.devices.find_device(1) + if gateway: + gateway.id = event.arguments[0][0]["deviceId"] + LOG.debug(f"Updated Gateway's deviceId from default 1 to {gateway.id}") + + updated_devices = self.devices.parse_values_received(event.arguments[0]) + for device in updated_devices: + async_dispatcher_send( + self._hass, SIGNAL_UPDATE_ENTITY.format(device.id) + ) @callback async def on_websocket_event(self, event: WebsocketEvent) -> None: From 393aaee71fdd5f9766a71a4f6909bb424c51b906 Mon Sep 17 00:00:00 2001 From: "Ian C." <108159253+ic-dev21@users.noreply.github.com> Date: Fri, 28 Mar 2025 15:20:55 -0400 Subject: [PATCH 20/22] Update requirements.txt --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index abbf90e7..6ea55297 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,5 @@ colorlog==6.9.0 -homeassistant~=2025.3.3 +homeassistant~=2025.3.4 pip>=21.3.1 ruff==0.11.3 pyyaml>=6.0.2 \ No newline at end of file From 6841ba4a1e9561bbc89d52daa91f6e6a1a4e3a05 Mon Sep 17 00:00:00 2001 From: Christophe Gagnier Date: Sun, 6 Apr 2025 21:47:34 +0000 Subject: [PATCH 21/22] Change import order --- custom_components/hilo/sensor.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/custom_components/hilo/sensor.py b/custom_components/hilo/sensor.py index 53142a71..0f6b8c41 100755 --- a/custom_components/hilo/sensor.py +++ b/custom_components/hilo/sensor.py @@ -7,6 +7,7 @@ from os.path import isfile import aiofiles +import homeassistant.util.dt as dt_util import yaml from homeassistant.components.integration.sensor import METHOD_LEFT, IntegrationSensor from homeassistant.components.sensor import ( @@ -27,6 +28,8 @@ UnitOfPower, UnitOfSoundPressure, UnitOfTemperature, +) +from homeassistant.const import ( __short_version__ as current_version, ) from homeassistant.core import HomeAssistant @@ -35,7 +38,6 @@ from homeassistant.helpers.event import async_track_state_change_event from homeassistant.helpers.restore_state import RestoreEntity from homeassistant.util import Throttle, slugify -import homeassistant.util.dt as dt_util from packaging.version import Version from pyhilo.const import UNMONITORED_DEVICES from pyhilo.device import HiloDevice From a661dac0afcb7bf87b9b16cc80ab0ba9e9be73e7 Mon Sep 17 00:00:00 2001 From: "Ian C." <108159253+ic-dev21@users.noreply.github.com> Date: Sun, 6 Apr 2025 18:22:01 -0400 Subject: [PATCH 22/22] isort --- custom_components/hilo/sensor.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/custom_components/hilo/sensor.py b/custom_components/hilo/sensor.py index 0f6b8c41..410632db 100755 --- a/custom_components/hilo/sensor.py +++ b/custom_components/hilo/sensor.py @@ -7,8 +7,6 @@ from os.path import isfile import aiofiles -import homeassistant.util.dt as dt_util -import yaml from homeassistant.components.integration.sensor import METHOD_LEFT, IntegrationSensor from homeassistant.components.sensor import ( SensorDeviceClass, @@ -28,8 +26,6 @@ UnitOfPower, UnitOfSoundPressure, UnitOfTemperature, -) -from homeassistant.const import ( __short_version__ as current_version, ) from homeassistant.core import HomeAssistant @@ -38,11 +34,13 @@ from homeassistant.helpers.event import async_track_state_change_event from homeassistant.helpers.restore_state import RestoreEntity from homeassistant.util import Throttle, slugify +import homeassistant.util.dt as dt_util from packaging.version import Version from pyhilo.const import UNMONITORED_DEVICES from pyhilo.device import HiloDevice from pyhilo.event import Event from pyhilo.util import from_utc_timestamp +import yaml from yaml.scanner import ScannerError from . import Hilo