From f4b24efe83002864869d2fde46dfef0ed6aa7998 Mon Sep 17 00:00:00 2001 From: Antoine Weill--Duflos Date: Mon, 25 Nov 2024 22:35:19 -0500 Subject: [PATCH 01/40] first step to fix dvd-dev/hilo#486 by making room to a second websocket handling challenge events. --- custom_components/hilo/__init__.py | 63 ++++++++++++++++++++---------- 1 file changed, 42 insertions(+), 21 deletions(-) diff --git a/custom_components/hilo/__init__.py b/custom_components/hilo/__init__.py index 983ccd75..817fa6fa 100755 --- a/custom_components/hilo/__init__.py +++ b/custom_components/hilo/__init__.py @@ -232,9 +232,9 @@ def __init__(self, hass: HomeAssistant, entry: ConfigEntry, api: API) -> None: self.find_meter(self._hass) self.entry = entry self.devices: Devices = Devices(api) - self._websocket_reconnect_task: asyncio.Task | None = None - self._update_task: asyncio.Task | None = None - self.invocations = {0: self.subscribe_to_location} + self._websocket_reconnect_tasks: list[asyncio.Task | None] = [None, None] + self._update_task: list[asyncio.Task | None] = [None, None] + self.invocations = {0: self.subscribe_to_location, 1: self.subscribe_to_challenge} self.hq_plan_name = entry.options.get(CONF_HQ_PLAN_NAME, DEFAULT_HQ_PLAN_NAME) self.appreciation = entry.options.get( CONF_APPRECIATION_PHASE, DEFAULT_APPRECIATION_PHASE @@ -348,12 +348,26 @@ async def subscribe_to_location(self, inv_id: int) -> None: [self.devices.location_id], "SubscribeToLocation", inv_id ) + @callback + async def subscribe_to_challenge(self, inv_id: int, event_id: int =0) -> None: + """Sends the json payload to receive updates from the challenge.""" + LOG.debug(f"Subscribing to challenge {event_id} at location {self.devices.location_id}") + await self._api.websocket2.async_invoke( + [event_id, self.devices.location_id], "SubscribeToChallenge", inv_id + ) + @callback async def request_status_update(self) -> None: await self._api.websocket.send_status() for inv_id, inv_cb in self.invocations.items(): await inv_cb(inv_id) + @callback + async def request_status_update_challenge(self) -> None: + await self._api.websocket2.send_status() + for inv_id, inv_cb in self.invocations.items(): + await inv_cb(inv_id) + @callback def _get_unknown_source_tracker(self) -> HiloDevice: return { @@ -426,8 +440,13 @@ async def async_init(self, scan_interval: int) -> None: self._api.websocket.add_connect_callback(self.request_status_update) self._api.websocket.add_event_callback(self.on_websocket_event) - self._websocket_reconnect_task = asyncio.create_task( - self.start_websocket_loop() + self._api.websocket2.add_connect_callback(self.request_status_update_challenge) + self._api.websocket2.add_event_callback(self.on_websocket_event) + self._websocket_reconnect_tasks[0] = asyncio.create_task( + self.start_websocket_loop(self._api.websocket, 0) + ) + self._websocket_reconnect_tasks[1] = asyncio.create_task( + self.start_websocket_loop(self._api.websocket2, 1) ) # asyncio.create_task(self._api.websocket.async_connect()) @@ -450,39 +469,41 @@ async def websocket_disconnect_listener(_: Event) -> None: name="hilo", update_interval=timedelta(seconds=scan_interval), update_method=self.async_update, - ) + ) - async def start_websocket_loop(self) -> None: + async def start_websocket_loop(self, websocket, id) -> None: """Start a websocket reconnection loop.""" if TYPE_CHECKING: - assert self._api.websocket + assert websocket should_reconnect = True try: - await self._api.websocket.async_connect() - await self._api.websocket.async_listen() + await websocket.async_connect() + await websocket.async_listen() except asyncio.CancelledError: LOG.debug("Request to cancel websocket loop received") raise except WebsocketError as err: LOG.error(f"Failed to connect to websocket: {err}", exc_info=err) - await self.cancel_websocket_loop() + await self.cancel_websocket_loop(websocket, id) except InvalidCredentialsError: LOG.warning("Invalid credentials? Refreshing websocket infos") - await self.cancel_websocket_loop() + await self.cancel_websocket_loop(websocket, id) await self._api.refresh_ws_token() except Exception as err: # pylint: disable=broad-except LOG.error( f"Unknown exception while connecting to websocket: {err}", exc_info=err ) - await self.cancel_websocket_loop() + await self.cancel_websocket_loop(websocket, id) + + if should_reconnect: LOG.info("Disconnected from websocket; reconnecting in 5 seconds.") await asyncio.sleep(5) - self._websocket_reconnect_task = self._hass.async_create_task( - self.start_websocket_loop() + self._websocket_reconnect_tasks[id] = self._hass.async_create_task( + self.start_websocket_loop(websocket, id) ) async def cancel_task(self, task) -> None: @@ -496,15 +517,15 @@ async def cancel_task(self, task) -> None: task = None return task - async def cancel_websocket_loop(self) -> None: + async def cancel_websocket_loop(self, websocket, id) -> None: """Stop any existing websocket reconnection loop.""" - self._websocket_reconnect_task = await self.cancel_task( - self._websocket_reconnect_task + self._websocket_reconnect_tasks[id] = await self.cancel_task( + self._websocket_reconnect_tasks[id] ) - self._update_task = await self.cancel_task(self._update_task) + self._update_task[id] = await self.cancel_task(self._update_task[id]) if TYPE_CHECKING: - assert self._api.websocket - await self._api.websocket.async_disconnect() + assert websocket + await websocket.async_disconnect() async def async_update(self) -> None: """Updates tarif periodically.""" From b93d147e7e0d9d8d34c9dc1ba1c92bc79062b1fe Mon Sep 17 00:00:00 2001 From: "Ian C." <108159253+ic-dev21@users.noreply.github.com> Date: Tue, 26 Nov 2024 18:48:32 -0500 Subject: [PATCH 02/40] Initial linting --- custom_components/hilo/__init__.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/custom_components/hilo/__init__.py b/custom_components/hilo/__init__.py index 817fa6fa..e015b03f 100755 --- a/custom_components/hilo/__init__.py +++ b/custom_components/hilo/__init__.py @@ -234,7 +234,10 @@ def __init__(self, hass: HomeAssistant, entry: ConfigEntry, api: API) -> None: self.devices: Devices = Devices(api) self._websocket_reconnect_tasks: list[asyncio.Task | None] = [None, None] self._update_task: list[asyncio.Task | None] = [None, None] - self.invocations = {0: self.subscribe_to_location, 1: self.subscribe_to_challenge} + self.invocations = { + 0: self.subscribe_to_location, + 1: self.subscribe_to_challenge, + } self.hq_plan_name = entry.options.get(CONF_HQ_PLAN_NAME, DEFAULT_HQ_PLAN_NAME) self.appreciation = entry.options.get( CONF_APPRECIATION_PHASE, DEFAULT_APPRECIATION_PHASE @@ -349,9 +352,11 @@ async def subscribe_to_location(self, inv_id: int) -> None: ) @callback - async def subscribe_to_challenge(self, inv_id: int, event_id: int =0) -> None: + async def subscribe_to_challenge(self, inv_id: int, event_id: int = 0) -> None: """Sends the json payload to receive updates from the challenge.""" - LOG.debug(f"Subscribing to challenge {event_id} at location {self.devices.location_id}") + LOG.debug( + f"Subscribing to challenge {event_id} at location {self.devices.location_id}" + ) await self._api.websocket2.async_invoke( [event_id, self.devices.location_id], "SubscribeToChallenge", inv_id ) @@ -469,7 +474,7 @@ async def websocket_disconnect_listener(_: Event) -> None: name="hilo", update_interval=timedelta(seconds=scan_interval), update_method=self.async_update, - ) + ) async def start_websocket_loop(self, websocket, id) -> None: """Start a websocket reconnection loop.""" @@ -497,8 +502,6 @@ async def start_websocket_loop(self, websocket, id) -> None: ) await self.cancel_websocket_loop(websocket, id) - - if should_reconnect: LOG.info("Disconnected from websocket; reconnecting in 5 seconds.") await asyncio.sleep(5) From f06d1c7413ea17f363633da1eaf3bcd3e7efddd2 Mon Sep 17 00:00:00 2001 From: "Ian C." <108159253+ic-dev21@users.noreply.github.com> Date: Fri, 6 Dec 2024 09:26:25 -0500 Subject: [PATCH 03/40] Update __init__.py --- custom_components/hilo/__init__.py | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/custom_components/hilo/__init__.py b/custom_components/hilo/__init__.py index e015b03f..1ebd0410 100755 --- a/custom_components/hilo/__init__.py +++ b/custom_components/hilo/__init__.py @@ -347,7 +347,7 @@ async def on_websocket_event(self, event: WebsocketEvent) -> None: async def subscribe_to_location(self, inv_id: int) -> None: """Sends the json payload to receive updates from the location.""" LOG.debug(f"Subscribing to location {self.devices.location_id}") - await self._api.websocket.async_invoke( + await self._api.websocket_devices.async_invoke( [self.devices.location_id], "SubscribeToLocation", inv_id ) @@ -357,19 +357,19 @@ async def subscribe_to_challenge(self, inv_id: int, event_id: int = 0) -> None: LOG.debug( f"Subscribing to challenge {event_id} at location {self.devices.location_id}" ) - await self._api.websocket2.async_invoke( + await self._api.websocket_challenges.async_invoke( [event_id, self.devices.location_id], "SubscribeToChallenge", inv_id ) @callback async def request_status_update(self) -> None: - await self._api.websocket.send_status() + await self._api.websocket_devices.send_status() for inv_id, inv_cb in self.invocations.items(): await inv_cb(inv_id) @callback async def request_status_update_challenge(self) -> None: - await self._api.websocket2.send_status() + await self._api.websocket_challenges.send_status() for inv_id, inv_cb in self.invocations.items(): await inv_cb(inv_id) @@ -443,25 +443,25 @@ async def async_init(self, scan_interval: int) -> None: self._hass, self.entry, self.unknown_tracker_device ) - self._api.websocket.add_connect_callback(self.request_status_update) - self._api.websocket.add_event_callback(self.on_websocket_event) - self._api.websocket2.add_connect_callback(self.request_status_update_challenge) - self._api.websocket2.add_event_callback(self.on_websocket_event) + self._api.websocket_devices.add_connect_callback(self.request_status_update) + self._api.websocket_devices.add_event_callback(self.on_websocket_event) + self._api.websocket_challenges.add_connect_callback(self.request_status_update_challenge) + self._api.websocket_challenges.add_event_callback(self.on_websocket_event) self._websocket_reconnect_tasks[0] = asyncio.create_task( self.start_websocket_loop(self._api.websocket, 0) ) self._websocket_reconnect_tasks[1] = asyncio.create_task( self.start_websocket_loop(self._api.websocket2, 1) ) - # asyncio.create_task(self._api.websocket.async_connect()) + # asyncio.create_task(self._api.websocket_devices.async_connect()) async def websocket_disconnect_listener(_: Event) -> None: """Define an event handler to disconnect from the websocket.""" if TYPE_CHECKING: assert self._api.websocket - if self._api.websocket.connected: - await self._api.websocket.async_disconnect() + if self._api.websocket_devices.connected: + await self._api.websocket_devices.async_disconnect() self.entry.async_on_unload( self._hass.bus.async_listen_once( From 4e2ac525d403e6e8470d3135fe6c3db2855786a8 Mon Sep 17 00:00:00 2001 From: "Ian C." <108159253+ic-dev21@users.noreply.github.com> Date: Fri, 6 Dec 2024 09:47:26 -0500 Subject: [PATCH 04/40] Update __init__.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Clarifié fonction des 2 websockets avec leur noms --- custom_components/hilo/__init__.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/custom_components/hilo/__init__.py b/custom_components/hilo/__init__.py index 1ebd0410..bc8f794b 100755 --- a/custom_components/hilo/__init__.py +++ b/custom_components/hilo/__init__.py @@ -445,20 +445,22 @@ async def async_init(self, scan_interval: int) -> None: self._api.websocket_devices.add_connect_callback(self.request_status_update) self._api.websocket_devices.add_event_callback(self.on_websocket_event) - self._api.websocket_challenges.add_connect_callback(self.request_status_update_challenge) + self._api.websocket_challenges.add_connect_callback( + self.request_status_update_challenge + ) self._api.websocket_challenges.add_event_callback(self.on_websocket_event) self._websocket_reconnect_tasks[0] = asyncio.create_task( - self.start_websocket_loop(self._api.websocket, 0) + self.start_websocket_loop(self._api.websocket_devices, 0) ) self._websocket_reconnect_tasks[1] = asyncio.create_task( - self.start_websocket_loop(self._api.websocket2, 1) + self.start_websocket_loop(self._api.websocket_challenges, 1) ) # asyncio.create_task(self._api.websocket_devices.async_connect()) async def websocket_disconnect_listener(_: Event) -> None: """Define an event handler to disconnect from the websocket.""" if TYPE_CHECKING: - assert self._api.websocket + assert self._api.websocket_devices if self._api.websocket_devices.connected: await self._api.websocket_devices.async_disconnect() From db898b2a9de444cbc7b7781d3f4433fee2d41eb7 Mon Sep 17 00:00:00 2001 From: "Ian C." <108159253+ic-dev21@users.noreply.github.com> Date: Thu, 26 Dec 2024 13:03:45 -0500 Subject: [PATCH 05/40] Some more progress MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Ça permet les deux connexions maintenant, sans erreur --- custom_components/hilo/__init__.py | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/custom_components/hilo/__init__.py b/custom_components/hilo/__init__.py index bc8f794b..74b88ff8 100755 --- a/custom_components/hilo/__init__.py +++ b/custom_components/hilo/__init__.py @@ -237,6 +237,7 @@ def __init__(self, hass: HomeAssistant, entry: ConfigEntry, api: API) -> None: self.invocations = { 0: self.subscribe_to_location, 1: self.subscribe_to_challenge, + 2: self.subscribe_to_challengelist, } self.hq_plan_name = entry.options.get(CONF_HQ_PLAN_NAME, DEFAULT_HQ_PLAN_NAME) self.appreciation = entry.options.get( @@ -340,6 +341,7 @@ async def on_websocket_event(self, event: WebsocketEvent) -> None: async_dispatcher_send( self._hass, SIGNAL_UPDATE_ENTITY.format(device.id) ) + else: LOG.warning(f"Unhandled websocket event: {event}") @@ -354,11 +356,27 @@ async def subscribe_to_location(self, inv_id: int) -> None: @callback async def subscribe_to_challenge(self, inv_id: int, event_id: int = 0) -> None: """Sends the json payload to receive updates from the challenge.""" + # ic-dev21 : data structure of the message was incorrect, needed the "fixed" strings LOG.debug( f"Subscribing to challenge {event_id} at location {self.devices.location_id}" ) await self._api.websocket_challenges.async_invoke( - [event_id, self.devices.location_id], "SubscribeToChallenge", inv_id + [{"locationId": self.devices.location_id, "eventId": event_id}], + "SubscribeToChallenge", + inv_id, + ) + + @callback + async def subscribe_to_challengelist(self, inv_id: int) -> None: + """Sends the json payload to receive updates from the challenge list.""" + # ic-dev21 this will be necessary to get the challenge list + LOG.debug( + f"Subscribing to challenge list at location {self.devices.location_id}" + ) + await self._api.websocket_challenges.async_invoke( + [{"locationId": self.devices.location_id}], + "SubscribeToChallengeList", + inv_id, ) @callback From 77e21362b6d0306d007ca5fbe9083ca0a85c81f2 Mon Sep 17 00:00:00 2001 From: "Ian C." <108159253+ic-dev21@users.noreply.github.com> Date: Thu, 26 Dec 2024 13:12:37 -0500 Subject: [PATCH 06/40] Ajout de stock dans le callback --- custom_components/hilo/__init__.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/custom_components/hilo/__init__.py b/custom_components/hilo/__init__.py index 74b88ff8..48f0a914 100755 --- a/custom_components/hilo/__init__.py +++ b/custom_components/hilo/__init__.py @@ -306,6 +306,17 @@ async def on_websocket_event(self, event: WebsocketEvent) -> None: new_devices = await self.devices.update_devicelist_from_signalr( event.arguments[0] ) + # ic-dev21: This part needs some further work and parsing but so far it works correctly + elif event.target == "ChallengeDetailsInitialValuesReceived": + LOG.debug("ic-dev21 ChallengeDetailsInitialValuesReceived") + + elif event.target == "ChallengeListInitialValuesReceived": + LOG.debug("ic-dev21 ChallengeListInitialValuesReceived") + + elif event.target == "ChallengeConsumptionUpdatedValuesReceived": + LOG.debug("ic-dev21 ChallengeConsumptionUpdatedValuesReceived") + # id-dev21 end of new code that needs further work. + elif event.target == "DeviceListUpdatedValuesReceived": # This message only contains display information, such as the Device's name (as set in the app), it's groupid, icon, etc. # Updating the device name causes issues in the integration, it detects it as a new device and creates a new entity. From 0a8cc128b1b0d7a23742e3b92db249bcf7a4e816 Mon Sep 17 00:00:00 2001 From: "Ian C." <108159253+ic-dev21@users.noreply.github.com> Date: Sat, 4 Jan 2025 13:56:02 -0500 Subject: [PATCH 07/40] Add logging and callback On subscribe maintenant au bon event --- custom_components/hilo/__init__.py | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/custom_components/hilo/__init__.py b/custom_components/hilo/__init__.py index 48f0a914..0e6a13bd 100755 --- a/custom_components/hilo/__init__.py +++ b/custom_components/hilo/__init__.py @@ -232,6 +232,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.challenge_id = 0 self._websocket_reconnect_tasks: list[asyncio.Task | None] = [None, None] self._update_task: list[asyncio.Task | None] = [None, None] self.invocations = { @@ -244,7 +245,7 @@ def __init__(self, hass: HomeAssistant, entry: ConfigEntry, api: API) -> None: CONF_APPRECIATION_PHASE, DEFAULT_APPRECIATION_PHASE ) self.pre_cold = entry.options.get( - CONF_PRE_COLD_PHASE, DEFAULT_PRE_COLD_PHASE # this is new + CONF_PRE_COLD_PHASE, DEFAULT_PRE_COLD_PHASE ) self.challenge_lock = entry.options.get( CONF_CHALLENGE_LOCK, DEFAULT_CHALLENGE_LOCK @@ -288,7 +289,7 @@ async def on_websocket_event(self, event: WebsocketEvent) -> None: for item in event.arguments[0] ) if new_devices: - LOG.warn( + LOG.warning( "Device list appears to be desynchronized, forcing a refresh thru the API..." ) await self.devices.update() @@ -312,6 +313,14 @@ async def on_websocket_event(self, event: WebsocketEvent) -> None: elif event.target == "ChallengeListInitialValuesReceived": LOG.debug("ic-dev21 ChallengeListInitialValuesReceived") + arguments = event.arguments + challenge = arguments[0][0] + challenge_id = challenge.get('id') + LOG.debug(f"ic-dev21 arguments are {arguments}") + LOG.debug(f"ic-dev21 challenge_id {challenge_id}") + self.challenge_id = challenge.get('id') + LOG.debug(f"ic-dev21 self.challenge_id {self.challenge_id}") + await self.subscribe_to_challenge(1,self.challenge_id) elif event.target == "ChallengeConsumptionUpdatedValuesReceived": LOG.debug("ic-dev21 ChallengeConsumptionUpdatedValuesReceived") @@ -368,6 +377,9 @@ async def subscribe_to_location(self, inv_id: int) -> None: async def subscribe_to_challenge(self, inv_id: int, event_id: int = 0) -> None: """Sends the json payload to receive updates from the challenge.""" # ic-dev21 : data structure of the message was incorrect, needed the "fixed" strings + LOG.debug(f"ic-dev21 subscribe to challenge :{event_id} or {self.challenge_id}") + event_id = event_id or self.challenge_id + LOG.debug( f"Subscribing to challenge {event_id} at location {self.devices.location_id}" ) From c6459d78b5393096c41255c710fb4a5e3b3d45fe Mon Sep 17 00:00:00 2001 From: "Ian C." <108159253+ic-dev21@users.noreply.github.com> Date: Sat, 4 Jan 2025 14:19:47 -0500 Subject: [PATCH 08/40] Update __init__.py --- custom_components/hilo/__init__.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/custom_components/hilo/__init__.py b/custom_components/hilo/__init__.py index 0e6a13bd..2ed3efd7 100755 --- a/custom_components/hilo/__init__.py +++ b/custom_components/hilo/__init__.py @@ -311,6 +311,15 @@ async def on_websocket_event(self, event: WebsocketEvent) -> None: elif event.target == "ChallengeDetailsInitialValuesReceived": LOG.debug("ic-dev21 ChallengeDetailsInitialValuesReceived") + elif event.target == "ChallengeAdded": + LOG.debug("ic-dev21 ChallengeAdded") + arguments = event.arguments + challenge = arguments[0][0] + challenge_id = challenge.get('id') + LOG.debug(f"ic-dev21 challengeAdded arguments are {arguments}") + LOG.debug(f"ic-dev21 challengeAdded challenge_id {challenge_id}") + self.challenge_id = challenge.get('id') + elif event.target == "ChallengeListInitialValuesReceived": LOG.debug("ic-dev21 ChallengeListInitialValuesReceived") arguments = event.arguments From 8427c190bc36f8427db4173288807a2a48b06f62 Mon Sep 17 00:00:00 2001 From: "Ian C." <108159253+ic-dev21@users.noreply.github.com> Date: Sat, 4 Jan 2025 14:28:18 -0500 Subject: [PATCH 09/40] Linting --- custom_components/hilo/__init__.py | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/custom_components/hilo/__init__.py b/custom_components/hilo/__init__.py index 2ed3efd7..7e4cd1fd 100755 --- a/custom_components/hilo/__init__.py +++ b/custom_components/hilo/__init__.py @@ -244,9 +244,7 @@ def __init__(self, hass: HomeAssistant, entry: ConfigEntry, api: API) -> None: self.appreciation = entry.options.get( CONF_APPRECIATION_PHASE, DEFAULT_APPRECIATION_PHASE ) - self.pre_cold = entry.options.get( - CONF_PRE_COLD_PHASE, DEFAULT_PRE_COLD_PHASE - ) + self.pre_cold = entry.options.get(CONF_PRE_COLD_PHASE, DEFAULT_PRE_COLD_PHASE) self.challenge_lock = entry.options.get( CONF_CHALLENGE_LOCK, DEFAULT_CHALLENGE_LOCK ) @@ -315,21 +313,21 @@ async def on_websocket_event(self, event: WebsocketEvent) -> None: LOG.debug("ic-dev21 ChallengeAdded") arguments = event.arguments challenge = arguments[0][0] - challenge_id = challenge.get('id') + challenge_id = challenge.get("id") LOG.debug(f"ic-dev21 challengeAdded arguments are {arguments}") LOG.debug(f"ic-dev21 challengeAdded challenge_id {challenge_id}") - self.challenge_id = challenge.get('id') + self.challenge_id = challenge.get("id") elif event.target == "ChallengeListInitialValuesReceived": LOG.debug("ic-dev21 ChallengeListInitialValuesReceived") arguments = event.arguments challenge = arguments[0][0] - challenge_id = challenge.get('id') + challenge_id = challenge.get("id") LOG.debug(f"ic-dev21 arguments are {arguments}") LOG.debug(f"ic-dev21 challenge_id {challenge_id}") - self.challenge_id = challenge.get('id') + self.challenge_id = challenge.get("id") LOG.debug(f"ic-dev21 self.challenge_id {self.challenge_id}") - await self.subscribe_to_challenge(1,self.challenge_id) + await self.subscribe_to_challenge(1, self.challenge_id) elif event.target == "ChallengeConsumptionUpdatedValuesReceived": LOG.debug("ic-dev21 ChallengeConsumptionUpdatedValuesReceived") From 8edec7ee90797ed0f451481ea3c64a1550e480f3 Mon Sep 17 00:00:00 2001 From: "Ian C." <108159253+ic-dev21@users.noreply.github.com> Date: Sat, 4 Jan 2025 15:00:38 -0500 Subject: [PATCH 10/40] Update __init__.py --- custom_components/hilo/__init__.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/custom_components/hilo/__init__.py b/custom_components/hilo/__init__.py index 7e4cd1fd..1c708616 100755 --- a/custom_components/hilo/__init__.py +++ b/custom_components/hilo/__init__.py @@ -308,6 +308,12 @@ async def on_websocket_event(self, event: WebsocketEvent) -> None: # ic-dev21: This part needs some further work and parsing but so far it works correctly elif event.target == "ChallengeDetailsInitialValuesReceived": LOG.debug("ic-dev21 ChallengeDetailsInitialValuesReceived") + arguments = event.arguments + challenge = arguments[0] + challenge_id = challenge.get('id') + LOG.debug(f"ic-dev21 ChallengeDetailsInitialValuesReceived arguments are {arguments}") + LOG.debug(f"ic-dev21 ChallengeDetailsInitialValuesReceived challenge_id {challenge_id}") + self.challenge_id = challenge.get('id') elif event.target == "ChallengeAdded": LOG.debug("ic-dev21 ChallengeAdded") From adb92d7617cfb657891db3c35c72ea964c5df680 Mon Sep 17 00:00:00 2001 From: "Ian C." <108159253+ic-dev21@users.noreply.github.com> Date: Sun, 5 Jan 2025 07:55:50 -0500 Subject: [PATCH 11/40] One more argument --- custom_components/hilo/__init__.py | 35 +++++++++++++++++++++++++----- 1 file changed, 29 insertions(+), 6 deletions(-) diff --git a/custom_components/hilo/__init__.py b/custom_components/hilo/__init__.py index 1c708616..3c8230ea 100755 --- a/custom_components/hilo/__init__.py +++ b/custom_components/hilo/__init__.py @@ -310,19 +310,37 @@ async def on_websocket_event(self, event: WebsocketEvent) -> None: LOG.debug("ic-dev21 ChallengeDetailsInitialValuesReceived") arguments = event.arguments challenge = arguments[0] - challenge_id = challenge.get('id') - LOG.debug(f"ic-dev21 ChallengeDetailsInitialValuesReceived arguments are {arguments}") - LOG.debug(f"ic-dev21 ChallengeDetailsInitialValuesReceived challenge_id {challenge_id}") - self.challenge_id = challenge.get('id') + challenge_id = challenge.get("id") + LOG.debug( + f"ic-dev21 ChallengeDetailsInitialValuesReceived arguments are {arguments}" + ) + LOG.debug( + f"ic-dev21 ChallengeDetailsInitialValuesReceived challenge_id {challenge_id}" + ) + self.challenge_id = challenge.get("id") + + elif event.target == "ChallengeListUpdatedValuesReceived": + LOG.debug("ic-dev21 ChallengeListUpdatedValuesReceived") + arguments = event.arguments + challenge = arguments[0] + challenge_id = challenge.get("id") + LOG.debug( + f"ic-dev21 ChallengeListUpdatedValuesReceived arguments are {arguments}" + ) + LOG.debug( + f"ic-dev21 ChallengeDetailsInitialValuesReceived challenge_id {challenge_id}" + ) + self.challenge_id = challenge.get("id") elif event.target == "ChallengeAdded": LOG.debug("ic-dev21 ChallengeAdded") arguments = event.arguments challenge = arguments[0][0] challenge_id = challenge.get("id") - LOG.debug(f"ic-dev21 challengeAdded arguments are {arguments}") - LOG.debug(f"ic-dev21 challengeAdded challenge_id {challenge_id}") + LOG.debug(f"ic-dev21 arguments are {arguments}") + LOG.debug(f"ic-dev21 challenge_id {challenge_id}") self.challenge_id = challenge.get("id") + await self.subscribe_to_challenge(1, self.challenge_id) elif event.target == "ChallengeListInitialValuesReceived": LOG.debug("ic-dev21 ChallengeListInitialValuesReceived") @@ -337,6 +355,11 @@ async def on_websocket_event(self, event: WebsocketEvent) -> None: elif event.target == "ChallengeConsumptionUpdatedValuesReceived": LOG.debug("ic-dev21 ChallengeConsumptionUpdatedValuesReceived") + arguments = event.arguments + LOG.debug( + f"ic-dev21 ChallengeConsumptionUpdatedValuesReceived arguments are: {arguments}" + ) + # id-dev21 end of new code that needs further work. elif event.target == "DeviceListUpdatedValuesReceived": From 9e4ea399505aa6346ff760ae58450569e27117ef Mon Sep 17 00:00:00 2001 From: "Ian C." <108159253+ic-dev21@users.noreply.github.com> Date: Sun, 5 Jan 2025 07:59:11 -0500 Subject: [PATCH 12/40] Un autre --- custom_components/hilo/__init__.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/custom_components/hilo/__init__.py b/custom_components/hilo/__init__.py index 3c8230ea..9e358628 100755 --- a/custom_components/hilo/__init__.py +++ b/custom_components/hilo/__init__.py @@ -319,6 +319,13 @@ async def on_websocket_event(self, event: WebsocketEvent) -> None: ) self.challenge_id = challenge.get("id") + elif event.target == "ChallengeDetailsUpdatedValuesReceived": + LOG.debug("ic-dev21 ChallengeDetailsUpdatedValuesReceived") + arguments = event.arguments + LOG.debug( + f"ic-dev21 ChallengeDetailsUpdatedValuesReceived arguments are {arguments}" + ) + elif event.target == "ChallengeListUpdatedValuesReceived": LOG.debug("ic-dev21 ChallengeListUpdatedValuesReceived") arguments = event.arguments From 061e9d3d33068984a68f68096155941eeb69b739 Mon Sep 17 00:00:00 2001 From: "Ian C." <108159253+ic-dev21@users.noreply.github.com> Date: Sun, 5 Jan 2025 16:37:31 -0500 Subject: [PATCH 13/40] This doesn't work causes error --- custom_components/hilo/__init__.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/custom_components/hilo/__init__.py b/custom_components/hilo/__init__.py index 9e358628..7fe4e25f 100755 --- a/custom_components/hilo/__init__.py +++ b/custom_components/hilo/__init__.py @@ -330,14 +330,9 @@ async def on_websocket_event(self, event: WebsocketEvent) -> None: LOG.debug("ic-dev21 ChallengeListUpdatedValuesReceived") arguments = event.arguments challenge = arguments[0] - challenge_id = challenge.get("id") LOG.debug( f"ic-dev21 ChallengeListUpdatedValuesReceived arguments are {arguments}" ) - LOG.debug( - f"ic-dev21 ChallengeDetailsInitialValuesReceived challenge_id {challenge_id}" - ) - self.challenge_id = challenge.get("id") elif event.target == "ChallengeAdded": LOG.debug("ic-dev21 ChallengeAdded") From 609e99897a5793314bab132ad07821fd63cdf402 Mon Sep 17 00:00:00 2001 From: "Ian C." <108159253+ic-dev21@users.noreply.github.com> Date: Sun, 5 Jan 2025 18:17:59 -0500 Subject: [PATCH 14/40] Update __init__.py --- custom_components/hilo/__init__.py | 53 ++++++++++++++---------------- 1 file changed, 24 insertions(+), 29 deletions(-) diff --git a/custom_components/hilo/__init__.py b/custom_components/hilo/__init__.py index 7fe4e25f..f99cf937 100755 --- a/custom_components/hilo/__init__.py +++ b/custom_components/hilo/__init__.py @@ -244,7 +244,9 @@ def __init__(self, hass: HomeAssistant, entry: ConfigEntry, api: API) -> None: self.appreciation = entry.options.get( CONF_APPRECIATION_PHASE, DEFAULT_APPRECIATION_PHASE ) - self.pre_cold = entry.options.get(CONF_PRE_COLD_PHASE, DEFAULT_PRE_COLD_PHASE) + self.pre_cold = entry.options.get( + CONF_PRE_COLD_PHASE, DEFAULT_PRE_COLD_PHASE + ) self.challenge_lock = entry.options.get( CONF_CHALLENGE_LOCK, DEFAULT_CHALLENGE_LOCK ) @@ -310,57 +312,50 @@ async def on_websocket_event(self, event: WebsocketEvent) -> None: LOG.debug("ic-dev21 ChallengeDetailsInitialValuesReceived") arguments = event.arguments challenge = arguments[0] - challenge_id = challenge.get("id") - LOG.debug( - f"ic-dev21 ChallengeDetailsInitialValuesReceived arguments are {arguments}" - ) - LOG.debug( - f"ic-dev21 ChallengeDetailsInitialValuesReceived challenge_id {challenge_id}" - ) - self.challenge_id = challenge.get("id") + challenge_id = challenge.get('id') + LOG.debug(f"ic-dev21 ChallengeDetailsInitialValuesReceived arguments are {arguments}") + LOG.debug(f"ic-dev21 ChallengeDetailsInitialValuesReceived challenge_id {challenge_id}") + self.challenge_id = challenge.get('id') elif event.target == "ChallengeDetailsUpdatedValuesReceived": LOG.debug("ic-dev21 ChallengeDetailsUpdatedValuesReceived") arguments = event.arguments - LOG.debug( - f"ic-dev21 ChallengeDetailsUpdatedValuesReceived arguments are {arguments}" - ) + LOG.debug(f"ic-dev21 ChallengeDetailsUpdatedValuesReceived arguments are {arguments}") elif event.target == "ChallengeListUpdatedValuesReceived": LOG.debug("ic-dev21 ChallengeListUpdatedValuesReceived") arguments = event.arguments challenge = arguments[0] - LOG.debug( - f"ic-dev21 ChallengeListUpdatedValuesReceived arguments are {arguments}" - ) + LOG.debug(f"ic-dev21 ChallengeListUpdatedValuesReceived arguments are {arguments}") + elif event.target == "ChallengeAdded": LOG.debug("ic-dev21 ChallengeAdded") arguments = event.arguments challenge = arguments[0][0] - challenge_id = challenge.get("id") - LOG.debug(f"ic-dev21 arguments are {arguments}") - LOG.debug(f"ic-dev21 challenge_id {challenge_id}") - self.challenge_id = challenge.get("id") - await self.subscribe_to_challenge(1, self.challenge_id) + challenge_id = challenge.get('id') + LOG.debug(f"ic-dev21 ChallengeAdded arguments are {arguments}") + LOG.debug(f"ic-dev21 ChallengeAdded challenge_id {challenge_id}") + self.challenge_id = challenge.get('id') + await self.subscribe_to_challenge(1,self.challenge_id) elif event.target == "ChallengeListInitialValuesReceived": LOG.debug("ic-dev21 ChallengeListInitialValuesReceived") arguments = event.arguments challenge = arguments[0][0] - challenge_id = challenge.get("id") - LOG.debug(f"ic-dev21 arguments are {arguments}") - LOG.debug(f"ic-dev21 challenge_id {challenge_id}") - self.challenge_id = challenge.get("id") - LOG.debug(f"ic-dev21 self.challenge_id {self.challenge_id}") - await self.subscribe_to_challenge(1, self.challenge_id) + challenge_id = challenge.get('id') + LOG.debug(f"ic-dev21 ChallengeListInitialValuesReceived arguments are {arguments}") + LOG.debug(f"ic-dev21 ChallengeListInitialValuesReceived challenge_id {challenge_id}") + self.challenge_phase = challenge.get("currentPhase") + LOG.debug(f"ic-dev21 ChallengeListInitialValuesReceived currentPhase is {self.challenge_phase}") + self.challenge_id = challenge.get('id') + LOG.debug(f"ic-dev21 ChallengeListInitialValuesReceived self.challenge_id {self.challenge_id}") + await self.subscribe_to_challenge(1,self.challenge_id) elif event.target == "ChallengeConsumptionUpdatedValuesReceived": LOG.debug("ic-dev21 ChallengeConsumptionUpdatedValuesReceived") arguments = event.arguments - LOG.debug( - f"ic-dev21 ChallengeConsumptionUpdatedValuesReceived arguments are: {arguments}" - ) + LOG.debug(f"ic-dev21 ChallengeConsumptionUpdatedValuesReceived arguments are: {arguments}") # id-dev21 end of new code that needs further work. From 6fbd36c871155ee759567069942463f88d2377a3 Mon Sep 17 00:00:00 2001 From: "Ian C." <108159253+ic-dev21@users.noreply.github.com> Date: Sun, 5 Jan 2025 22:27:08 -0500 Subject: [PATCH 15/40] Update __init__.py --- custom_components/hilo/__init__.py | 42 ++++++++++++++++-------------- 1 file changed, 23 insertions(+), 19 deletions(-) diff --git a/custom_components/hilo/__init__.py b/custom_components/hilo/__init__.py index f99cf937..6dfbeb67 100755 --- a/custom_components/hilo/__init__.py +++ b/custom_components/hilo/__init__.py @@ -112,7 +112,7 @@ def _async_standardize_config_entry(hass: HomeAssistant, entry: ConfigEntry) -> @callback def _async_register_custom_device( - hass: HomeAssistant, entry: ConfigEntry, device: HiloDevice + hass: HomeAssistant, entry: ConfigEntry, device: HiloDevice ) -> None: """Register a custom device. This is used to register the Hilo gateway and the unknown source tracker.""" @@ -128,7 +128,7 @@ def _async_register_custom_device( async def async_setup_entry( # noqa: C901 - hass: HomeAssistant, entry: ConfigEntry + hass: HomeAssistant, entry: ConfigEntry ) -> bool: """Set up Hilo as config entry.""" HiloFlowHandler.async_register_implementation( @@ -327,7 +327,10 @@ async def on_websocket_event(self, event: WebsocketEvent) -> None: arguments = event.arguments challenge = arguments[0] LOG.debug(f"ic-dev21 ChallengeListUpdatedValuesReceived arguments are {arguments}") - + progress = arguments[0][0]['progress'] + LOG.debug(f"ChallengeListUpdatedValuesReceived progress is {progress}") + if progress == "completed": + LOG.debug(f"ChallengeListUpdatedValuesReceived tells me it has been completed") elif event.target == "ChallengeAdded": LOG.debug("ic-dev21 ChallengeAdded") @@ -337,7 +340,7 @@ async def on_websocket_event(self, event: WebsocketEvent) -> None: LOG.debug(f"ic-dev21 ChallengeAdded arguments are {arguments}") LOG.debug(f"ic-dev21 ChallengeAdded challenge_id {challenge_id}") self.challenge_id = challenge.get('id') - await self.subscribe_to_challenge(1,self.challenge_id) + await self.subscribe_to_challenge(1, self.challenge_id) elif event.target == "ChallengeListInitialValuesReceived": LOG.debug("ic-dev21 ChallengeListInitialValuesReceived") @@ -350,7 +353,7 @@ async def on_websocket_event(self, event: WebsocketEvent) -> None: LOG.debug(f"ic-dev21 ChallengeListInitialValuesReceived currentPhase is {self.challenge_phase}") self.challenge_id = challenge.get('id') LOG.debug(f"ic-dev21 ChallengeListInitialValuesReceived self.challenge_id {self.challenge_id}") - await self.subscribe_to_challenge(1,self.challenge_id) + await self.subscribe_to_challenge(1, self.challenge_id) elif event.target == "ChallengeConsumptionUpdatedValuesReceived": LOG.debug("ic-dev21 ChallengeConsumptionUpdatedValuesReceived") @@ -529,6 +532,7 @@ async def async_init(self, scan_interval: int) -> None: self._websocket_reconnect_tasks[1] = asyncio.create_task( self.start_websocket_loop(self._api.websocket_challenges, 1) ) + # asyncio.create_task(self._api.websocket_devices.async_connect()) async def websocket_disconnect_listener(_: Event) -> None: @@ -637,7 +641,7 @@ def find_meter(self, hass): # ic-dev21: Let's grab the meter from our dict for entity_id, entity_data in sorted_entity_registry_dict.items(): if all( - substring in entity_data["name"] for substring in ["meter", "_power"] + substring in entity_data["name"] for substring in ["meter", "_power"] ): filtered_names.append(entity_data["name"]) @@ -807,9 +811,9 @@ def set_tarif(self, entity, current, new): ) ) if ( - entity.startswith("select.") - and entity.endswith("_hilo_energy") - and current != new + entity.startswith("select.") + and entity.endswith("_hilo_energy") + and current != new ): LOG.debug( f"check_tarif: Changing tarif of {entity} from {current} to {new}" @@ -824,7 +828,7 @@ def set_tarif(self, entity, current, new): @callback def async_migrate_unique_id( - self, old_unique_id: str, new_unique_id: str | None, platform: str + self, old_unique_id: str, new_unique_id: str | None, platform: str ) -> None: """Migrate legacy unique IDs to new format.""" assert new_unique_id is not None @@ -839,14 +843,14 @@ def async_migrate_unique_id( # field for historical reasons since everything used to be # PLATFORM.INTEGRATION instead of INTEGRATION.PLATFORM if ( - entity_id := entity_registry.async_get_entity_id( - platform, DOMAIN, old_unique_id - ) + entity_id := entity_registry.async_get_entity_id( + platform, DOMAIN, old_unique_id + ) ) is None: LOG.debug("Unique ID %s does not need to be migrated", old_unique_id) return if new_entity_id := entity_registry.async_get_entity_id( - platform, DOMAIN, new_unique_id + platform, DOMAIN, new_unique_id ): LOG.debug( ( @@ -870,11 +874,11 @@ class HiloEntity(CoordinatorEntity): """Define a base Hilo base entity.""" def __init__( - self, - hilo: Hilo, - name: Union[str, None] = None, - *, - device: HiloDevice | None = None, + self, + hilo: Hilo, + name: Union[str, None] = None, + *, + device: HiloDevice | None = None, ) -> None: """Initialize.""" assert hilo.coordinator From bceefa5f6498a17c2ef2cf637bea996ae2b87ead Mon Sep 17 00:00:00 2001 From: "Ian C." <108159253+ic-dev21@users.noreply.github.com> Date: Tue, 7 Jan 2025 20:19:18 -0500 Subject: [PATCH 16/40] Logging + linting/refactor --- custom_components/hilo/__init__.py | 211 +++++++++++++++-------------- 1 file changed, 111 insertions(+), 100 deletions(-) diff --git a/custom_components/hilo/__init__.py b/custom_components/hilo/__init__.py index 6dfbeb67..06102060 100755 --- a/custom_components/hilo/__init__.py +++ b/custom_components/hilo/__init__.py @@ -112,7 +112,7 @@ def _async_standardize_config_entry(hass: HomeAssistant, entry: ConfigEntry) -> @callback def _async_register_custom_device( - hass: HomeAssistant, entry: ConfigEntry, device: HiloDevice + hass: HomeAssistant, entry: ConfigEntry, device: HiloDevice ) -> None: """Register a custom device. This is used to register the Hilo gateway and the unknown source tracker.""" @@ -128,7 +128,7 @@ def _async_register_custom_device( async def async_setup_entry( # noqa: C901 - hass: HomeAssistant, entry: ConfigEntry + hass: HomeAssistant, entry: ConfigEntry ) -> bool: """Set up Hilo as config entry.""" HiloFlowHandler.async_register_implementation( @@ -244,9 +244,7 @@ def __init__(self, hass: HomeAssistant, entry: ConfigEntry, api: API) -> None: self.appreciation = entry.options.get( CONF_APPRECIATION_PHASE, DEFAULT_APPRECIATION_PHASE ) - self.pre_cold = entry.options.get( - CONF_PRE_COLD_PHASE, DEFAULT_PRE_COLD_PHASE - ) + self.pre_cold = entry.options.get(CONF_PRE_COLD_PHASE, DEFAULT_PRE_COLD_PHASE) self.challenge_lock = entry.options.get( CONF_CHALLENGE_LOCK, DEFAULT_CHALLENGE_LOCK ) @@ -271,133 +269,146 @@ def validate_heartbeat(self, event: WebsocketEvent) -> None: if self._api.log_traces: LOG.debug(f"Heartbeat: {time_diff(heartbeat_time, event.timestamp)}") - @callback - 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": - cb = self.invocations.get(event.invocation) - if cb: - async_call_later(self._hass, 3, cb(event.invocation)) - 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] + async def _handle_challenge_events(self, event: WebsocketEvent) -> None: + """Handle all challenge-related websocket events.""" + if event.target == "ChallengeDetailsInitialValuesReceived": + LOG.debug("ic-dev21 ChallengeDetailsInitialValuesReceived") + challenge = event.arguments[0] + challenge_id = challenge.get("id") + LOG.debug( + f"ic-dev21 ChallengeDetailsInitialValuesReceived arguments are {event.arguments}" ) - 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": - # This websocket event only happens after calling SubscribeToLocation. - # This triggers an update without throwing an exception - new_devices = await self.devices.update_devicelist_from_signalr( - event.arguments[0] + LOG.debug( + f"ic-dev21 ChallengeDetailsInitialValuesReceived challenge_id {challenge_id}" ) - # ic-dev21: This part needs some further work and parsing but so far it works correctly - elif event.target == "ChallengeDetailsInitialValuesReceived": - LOG.debug("ic-dev21 ChallengeDetailsInitialValuesReceived") - arguments = event.arguments - challenge = arguments[0] - challenge_id = challenge.get('id') - LOG.debug(f"ic-dev21 ChallengeDetailsInitialValuesReceived arguments are {arguments}") - LOG.debug(f"ic-dev21 ChallengeDetailsInitialValuesReceived challenge_id {challenge_id}") - self.challenge_id = challenge.get('id') + self.challenge_id = challenge.get("id") elif event.target == "ChallengeDetailsUpdatedValuesReceived": LOG.debug("ic-dev21 ChallengeDetailsUpdatedValuesReceived") - arguments = event.arguments - LOG.debug(f"ic-dev21 ChallengeDetailsUpdatedValuesReceived arguments are {arguments}") + LOG.debug( + f"ic-dev21 ChallengeDetailsUpdatedValuesReceived arguments are {event.arguments}" + ) elif event.target == "ChallengeListUpdatedValuesReceived": LOG.debug("ic-dev21 ChallengeListUpdatedValuesReceived") - arguments = event.arguments - challenge = arguments[0] - LOG.debug(f"ic-dev21 ChallengeListUpdatedValuesReceived arguments are {arguments}") - progress = arguments[0][0]['progress'] + challenge = event.arguments[0] + LOG.debug( + f"ic-dev21 ChallengeListUpdatedValuesReceived arguments are {event.arguments}" + ) + progress = event.arguments[0][0]["progress"] LOG.debug(f"ChallengeListUpdatedValuesReceived progress is {progress}") + self.challenge_phase = event.arguments[0][0]["currentPhase"] + LOG.debug( + f"ic-dev21 ChallengeListUpdatedValuesReceived currentPhase is {self.challenge_phase}" + ) if progress == "completed": - LOG.debug(f"ChallengeListUpdatedValuesReceived tells me it has been completed") + LOG.debug( + "ChallengeListUpdatedValuesReceived tells me it has been completed" + ) elif event.target == "ChallengeAdded": LOG.debug("ic-dev21 ChallengeAdded") - arguments = event.arguments - challenge = arguments[0][0] - challenge_id = challenge.get('id') - LOG.debug(f"ic-dev21 ChallengeAdded arguments are {arguments}") + challenge = event.arguments[0][0] + challenge_id = challenge.get("id") + LOG.debug(f"ic-dev21 ChallengeAdded arguments are {event.arguments}") LOG.debug(f"ic-dev21 ChallengeAdded challenge_id {challenge_id}") - self.challenge_id = challenge.get('id') + self.challenge_id = challenge.get("id") await self.subscribe_to_challenge(1, self.challenge_id) elif event.target == "ChallengeListInitialValuesReceived": LOG.debug("ic-dev21 ChallengeListInitialValuesReceived") - arguments = event.arguments - challenge = arguments[0][0] - challenge_id = challenge.get('id') - LOG.debug(f"ic-dev21 ChallengeListInitialValuesReceived arguments are {arguments}") - LOG.debug(f"ic-dev21 ChallengeListInitialValuesReceived challenge_id {challenge_id}") + challenge = event.arguments[0][0] + challenge_id = challenge.get("id") + LOG.debug( + f"ic-dev21 ChallengeListInitialValuesReceived arguments are {event.arguments}" + ) + LOG.debug( + f"ic-dev21 ChallengeListInitialValuesReceived challenge_id {challenge_id}" + ) self.challenge_phase = challenge.get("currentPhase") - LOG.debug(f"ic-dev21 ChallengeListInitialValuesReceived currentPhase is {self.challenge_phase}") - self.challenge_id = challenge.get('id') - LOG.debug(f"ic-dev21 ChallengeListInitialValuesReceived self.challenge_id {self.challenge_id}") + LOG.debug( + f"ic-dev21 ChallengeListInitialValuesReceived currentPhase is {self.challenge_phase}" + ) + self.challenge_id = challenge.get("id") + LOG.debug( + f"ic-dev21 ChallengeListInitialValuesReceived self.challenge_id {self.challenge_id}" + ) await self.subscribe_to_challenge(1, self.challenge_id) elif event.target == "ChallengeConsumptionUpdatedValuesReceived": LOG.debug("ic-dev21 ChallengeConsumptionUpdatedValuesReceived") - arguments = event.arguments - LOG.debug(f"ic-dev21 ChallengeConsumptionUpdatedValuesReceived arguments are: {arguments}") + LOG.debug( + f"ic-dev21 ChallengeConsumptionUpdatedValuesReceived arguments are: {event.arguments}" + ) + + 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() - # id-dev21 end of new code that needs further work. + 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 == "DeviceListInitialValuesReceived": + await self.devices.update_devicelist_from_signalr(event.arguments[0]) elif event.target == "DeviceListUpdatedValuesReceived": - # This message only contains display information, such as the Device's name (as set in the app), it's groupid, icon, etc. - # Updating the device name causes issues in the integration, it detects it as a new device and creates a new entity. - # Ignore this call, for now... (update_devicelist_from_signalr does work, but causes the issue above) - # await self.devices.update_devicelist_from_signalr(event.arguments[0]) LOG.debug( "Received 'DeviceListUpdatedValuesReceived' message, not implemented yet." ) + elif event.target == "DevicesListChanged": - # This message only contains the location_id and is used to inform us that devices have been removed from the location. - # Device deletion is not implemented yet, so we just log the message for now. LOG.debug("Received 'DevicesListChanged' message, not implemented yet.") + elif event.target == "DeviceAdded": - # Same structure as DeviceList* but only one device instead of a list - devices = [] - devices.append(event.arguments[0]) - new_devices = await self.devices.update_devicelist_from_signalr(devices) + devices = [event.arguments[0]] + await self.devices.update_devicelist_from_signalr(devices) + elif event.target == "DeviceDeleted": - # Device deletion is not implemented yet, so we just log the message for now. LOG.debug("Received 'DeviceDeleted' message, not implemented yet.") + elif event.target == "GatewayValuesReceived": - # Gateway deviceId hardcoded to 1 as it is not returned by Gateways/Info. - # First time we encounter a GatewayValueReceived event, update device with proper deviceid. 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]) - # 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) ) + @callback + 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": + cb = self.invocations.get(event.invocation) + if cb: + async_call_later(self._hass, 3, cb(event.invocation)) + + elif event.target == "Heartbeat": + self.validate_heartbeat(event) + + elif "Challenge" in event.target: + await self._handle_challenge_events(event) + + elif "Device" in event.target or event.target == "GatewayValuesReceived": + await self._handle_device_events(event) + else: LOG.warning(f"Unhandled websocket event: {event}") @@ -641,7 +652,7 @@ def find_meter(self, hass): # ic-dev21: Let's grab the meter from our dict for entity_id, entity_data in sorted_entity_registry_dict.items(): if all( - substring in entity_data["name"] for substring in ["meter", "_power"] + substring in entity_data["name"] for substring in ["meter", "_power"] ): filtered_names.append(entity_data["name"]) @@ -811,9 +822,9 @@ def set_tarif(self, entity, current, new): ) ) if ( - entity.startswith("select.") - and entity.endswith("_hilo_energy") - and current != new + entity.startswith("select.") + and entity.endswith("_hilo_energy") + and current != new ): LOG.debug( f"check_tarif: Changing tarif of {entity} from {current} to {new}" @@ -828,7 +839,7 @@ def set_tarif(self, entity, current, new): @callback def async_migrate_unique_id( - self, old_unique_id: str, new_unique_id: str | None, platform: str + self, old_unique_id: str, new_unique_id: str | None, platform: str ) -> None: """Migrate legacy unique IDs to new format.""" assert new_unique_id is not None @@ -843,14 +854,14 @@ def async_migrate_unique_id( # field for historical reasons since everything used to be # PLATFORM.INTEGRATION instead of INTEGRATION.PLATFORM if ( - entity_id := entity_registry.async_get_entity_id( - platform, DOMAIN, old_unique_id - ) + entity_id := entity_registry.async_get_entity_id( + platform, DOMAIN, old_unique_id + ) ) is None: LOG.debug("Unique ID %s does not need to be migrated", old_unique_id) return if new_entity_id := entity_registry.async_get_entity_id( - platform, DOMAIN, new_unique_id + platform, DOMAIN, new_unique_id ): LOG.debug( ( @@ -874,11 +885,11 @@ class HiloEntity(CoordinatorEntity): """Define a base Hilo base entity.""" def __init__( - self, - hilo: Hilo, - name: Union[str, None] = None, - *, - device: HiloDevice | None = None, + self, + hilo: Hilo, + name: Union[str, None] = None, + *, + device: HiloDevice | None = None, ) -> None: """Initialize.""" assert hilo.coordinator From 0ed885e7801179473856c1dd50f9506083292693 Mon Sep 17 00:00:00 2001 From: "Ian C." <108159253+ic-dev21@users.noreply.github.com> Date: Wed, 8 Jan 2025 21:32:10 -0500 Subject: [PATCH 17/40] Update __init__.py --- custom_components/hilo/__init__.py | 39 ++++++++++++++++++------------ 1 file changed, 23 insertions(+), 16 deletions(-) diff --git a/custom_components/hilo/__init__.py b/custom_components/hilo/__init__.py index 06102060..3282c700 100755 --- a/custom_components/hilo/__init__.py +++ b/custom_components/hilo/__init__.py @@ -308,38 +308,45 @@ async def _handle_challenge_events(self, event: WebsocketEvent) -> None: elif event.target == "ChallengeAdded": LOG.debug("ic-dev21 ChallengeAdded") - challenge = event.arguments[0][0] - challenge_id = challenge.get("id") + challenge = event.arguments[0] LOG.debug(f"ic-dev21 ChallengeAdded arguments are {event.arguments}") + challenge_id = challenge.get("id") LOG.debug(f"ic-dev21 ChallengeAdded challenge_id {challenge_id}") self.challenge_id = challenge.get("id") await self.subscribe_to_challenge(1, self.challenge_id) elif event.target == "ChallengeListInitialValuesReceived": LOG.debug("ic-dev21 ChallengeListInitialValuesReceived") - challenge = event.arguments[0][0] - challenge_id = challenge.get("id") + challenges = event.arguments[0] # This gets the list of all challenges LOG.debug( f"ic-dev21 ChallengeListInitialValuesReceived arguments are {event.arguments}" ) - LOG.debug( - f"ic-dev21 ChallengeListInitialValuesReceived challenge_id {challenge_id}" - ) - self.challenge_phase = challenge.get("currentPhase") - LOG.debug( - f"ic-dev21 ChallengeListInitialValuesReceived currentPhase is {self.challenge_phase}" - ) - self.challenge_id = challenge.get("id") - LOG.debug( - f"ic-dev21 ChallengeListInitialValuesReceived self.challenge_id {self.challenge_id}" - ) - await self.subscribe_to_challenge(1, self.challenge_id) + + for challenge in challenges: + challenge_id = challenge.get("id") + LOG.debug( + f"ic-dev21 ChallengeListInitialValuesReceived challenge_id {challenge_id}" + ) + self.challenge_phase = challenge.get("currentPhase") + LOG.debug( + f"ic-dev21 ChallengeListInitialValuesReceived currentPhase is {self.challenge_phase}" + ) + self.challenge_id = challenge.get("id") + LOG.debug( + f"ic-dev21 ChallengeListInitialValuesReceived self.challenge_id {self.challenge_id}" + ) + await self.subscribe_to_challenge(1, challenge_id) elif event.target == "ChallengeConsumptionUpdatedValuesReceived": LOG.debug("ic-dev21 ChallengeConsumptionUpdatedValuesReceived") LOG.debug( f"ic-dev21 ChallengeConsumptionUpdatedValuesReceived arguments are: {event.arguments}" ) + consumption_data = event.arguments[0] + current_kwh = ( + consumption_data.get("currentWh", 0) / 1000 + ) # Conversion de Wh en kWh + LOG.debug(f"ic-dev21 Current consumption is {current_kwh} kWh") async def _handle_device_events(self, event: WebsocketEvent) -> None: """Handle all device-related websocket events.""" From 08879075d17f613289e65b338341a32b5cf39967 Mon Sep 17 00:00:00 2001 From: "Ian C." <108159253+ic-dev21@users.noreply.github.com> Date: Thu, 16 Jan 2025 21:08:11 -0500 Subject: [PATCH 18/40] Update labeler.yml --- .github/workflows/labeler.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/labeler.yml b/.github/workflows/labeler.yml index 13824330..393bdd02 100644 --- a/.github/workflows/labeler.yml +++ b/.github/workflows/labeler.yml @@ -11,9 +11,13 @@ jobs: runs-on: ubuntu-latest steps: - name: Check out the repository - uses: actions/checkout@v4 + uses: actions/checkout@v4.2.2 + + - name: Debug Labels YAML + run: cat .github/labels.yml - name: Run Labeler uses: crazy-max/ghaction-github-labeler@v5.1.0 with: + configuration-path: .github/labels.yml skip-delete: true From e04a94c0905e15858a4405a79effce474ef4e3df Mon Sep 17 00:00:00 2001 From: "Ian C." <108159253+ic-dev21@users.noreply.github.com> Date: Thu, 16 Jan 2025 21:12:22 -0500 Subject: [PATCH 19/40] =?UTF-8?q?Websocket=20sensor=20en=20parall=C3=A8le?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- custom_components/hilo/__init__.py | 74 ++++-------- custom_components/hilo/sensor.py | 177 ++++++++++++++++++++++++++++- 2 files changed, 195 insertions(+), 56 deletions(-) diff --git a/custom_components/hilo/__init__.py b/custom_components/hilo/__init__.py index 3282c700..b125ef7b 100755 --- a/custom_components/hilo/__init__.py +++ b/custom_components/hilo/__init__.py @@ -272,82 +272,46 @@ def validate_heartbeat(self, event: WebsocketEvent) -> None: async def _handle_challenge_events(self, event: WebsocketEvent) -> None: """Handle all challenge-related websocket events.""" if event.target == "ChallengeDetailsInitialValuesReceived": - LOG.debug("ic-dev21 ChallengeDetailsInitialValuesReceived") challenge = event.arguments[0] - challenge_id = challenge.get("id") - LOG.debug( - f"ic-dev21 ChallengeDetailsInitialValuesReceived arguments are {event.arguments}" - ) - LOG.debug( - f"ic-dev21 ChallengeDetailsInitialValuesReceived challenge_id {challenge_id}" - ) + LOG.debug(f"ChallengeDetailsInitialValuesReceived, challenge = {challenge}") self.challenge_id = challenge.get("id") + # Update sensor with challenge details + if hasattr(self, "challenge_sensor"): + self.challenge_sensor.handle_challenge_details_update(challenge) elif event.target == "ChallengeDetailsUpdatedValuesReceived": - LOG.debug("ic-dev21 ChallengeDetailsUpdatedValuesReceived") - LOG.debug( - f"ic-dev21 ChallengeDetailsUpdatedValuesReceived arguments are {event.arguments}" - ) + LOG.debug("ChallengeDetailsUpdatedValuesReceived") + if hasattr(self, "challenge_sensor"): + self.challenge_sensor.handle_challenge_details_update( + event.arguments[0] + ) elif event.target == "ChallengeListUpdatedValuesReceived": - LOG.debug("ic-dev21 ChallengeListUpdatedValuesReceived") - challenge = event.arguments[0] - LOG.debug( - f"ic-dev21 ChallengeListUpdatedValuesReceived arguments are {event.arguments}" - ) - progress = event.arguments[0][0]["progress"] - LOG.debug(f"ChallengeListUpdatedValuesReceived progress is {progress}") + LOG.debug("ChallengeListUpdatedValuesReceived") + if hasattr(self, "challenge_sensor"): + self.challenge_sensor.handle_challenge_list_update(event.arguments[0]) self.challenge_phase = event.arguments[0][0]["currentPhase"] - LOG.debug( - f"ic-dev21 ChallengeListUpdatedValuesReceived currentPhase is {self.challenge_phase}" - ) - if progress == "completed": - LOG.debug( - "ChallengeListUpdatedValuesReceived tells me it has been completed" - ) elif event.target == "ChallengeAdded": - LOG.debug("ic-dev21 ChallengeAdded") + LOG.debug("ChallengeAdded") challenge = event.arguments[0] - LOG.debug(f"ic-dev21 ChallengeAdded arguments are {event.arguments}") - challenge_id = challenge.get("id") - LOG.debug(f"ic-dev21 ChallengeAdded challenge_id {challenge_id}") self.challenge_id = challenge.get("id") + if hasattr(self, "challenge_sensor"): + self.challenge_sensor.handle_challenge_added(challenge) await self.subscribe_to_challenge(1, self.challenge_id) elif event.target == "ChallengeListInitialValuesReceived": - LOG.debug("ic-dev21 ChallengeListInitialValuesReceived") - challenges = event.arguments[0] # This gets the list of all challenges - LOG.debug( - f"ic-dev21 ChallengeListInitialValuesReceived arguments are {event.arguments}" - ) + LOG.debug("ChallengeListInitialValuesReceived") + challenges = event.arguments[0] + if hasattr(self, "challenge_sensor"): + self.challenge_sensor.handle_challenge_list_initial(challenges) for challenge in challenges: challenge_id = challenge.get("id") - LOG.debug( - f"ic-dev21 ChallengeListInitialValuesReceived challenge_id {challenge_id}" - ) self.challenge_phase = challenge.get("currentPhase") - LOG.debug( - f"ic-dev21 ChallengeListInitialValuesReceived currentPhase is {self.challenge_phase}" - ) self.challenge_id = challenge.get("id") - LOG.debug( - f"ic-dev21 ChallengeListInitialValuesReceived self.challenge_id {self.challenge_id}" - ) await self.subscribe_to_challenge(1, challenge_id) - elif event.target == "ChallengeConsumptionUpdatedValuesReceived": - LOG.debug("ic-dev21 ChallengeConsumptionUpdatedValuesReceived") - LOG.debug( - f"ic-dev21 ChallengeConsumptionUpdatedValuesReceived arguments are: {event.arguments}" - ) - consumption_data = event.arguments[0] - current_kwh = ( - consumption_data.get("currentWh", 0) / 1000 - ) # Conversion de Wh en kWh - LOG.debug(f"ic-dev21 Current consumption is {current_kwh} kWh") - async def _handle_device_events(self, event: WebsocketEvent) -> None: """Handle all device-related websocket events.""" if event.target == "DevicesValuesReceived": diff --git a/custom_components/hilo/sensor.py b/custom_components/hilo/sensor.py index ed1ac9e6..38c0f117 100755 --- a/custom_components/hilo/sensor.py +++ b/custom_components/hilo/sensor.py @@ -36,7 +36,7 @@ from packaging.version import Version from pyhilo.const import UNMONITORED_DEVICES from pyhilo.device import HiloDevice -from pyhilo.event import Event +from pyhilo.event import Event, EventWebsocket from pyhilo.util import from_utc_timestamp import ruyaml as yaml @@ -99,6 +99,9 @@ def generate_entities_from_device(device, hilo, scan_interval): entities.append( HiloChallengeSensor(hilo, device, scan_interval), ) + entities.append( + HiloChallengeSensorWebsocket(hilo, device, scan_interval), + ) entities.append( HiloRewardSensor(hilo, device, scan_interval), ) @@ -777,6 +780,8 @@ async def async_added_to_hass(self): async def _async_update(self): self._next_events = [] + self._test = self._hilo.challenge_id + LOG.debug(f"ChallengeSensor challenge id is {self._test}") events = await self._hilo._api.get_gd_events(self._hilo.devices.location_id) LOG.debug(f"Events received from Hilo: {events}") for raw_event in events: @@ -789,6 +794,176 @@ async def _async_update(self): self._next_events.append(event.as_dict()) +class HiloChallengeSensorWebsocket(HiloEntity, RestoreEntity, SensorEntity): + """Hilo challenge sensor. + Its state will be either: + - off: no ongoing or scheduled challenge + - scheduled: A challenge is scheduled, details in the next_events + extra attribute + - pre_cold: optional phase to cool further before appreciation + - appreciation: optional phase to pre-heat more before challenge + - pre_heat: Currently in the pre-heat phase + - reduction or on: Challenge is currently active, heat is lowered + - recovery: Challenge is completed, we're reheating. + """ + + def __init__(self, hilo, device, scan_interval): + LOG.debug("ic-dev21 init HiloChallengeSensorWebsocket") + self._attr_name = "Defi Hilo Websocket" + super().__init__(hilo, name=self._attr_name, device=device) + old_unique_id = slugify(self._attr_name) + self._attr_unique_id = ( + f"{slugify(device.identifier)}-{slugify(self._attr_name)}" + ) + hilo.async_migrate_unique_id( + old_unique_id, self._attr_unique_id, Platform.SENSOR + ) + LOG.debug(f"Setting up ChallengeSensorWebsocket entity: {self._attr_name}") + self.scan_interval = timedelta(seconds=EVENT_SCAN_INTERVAL_REDUCTION) + self._state = "off" + self._next_events = [] + self._events = {} # Store active events + self.async_update = Throttle(self.scan_interval)(self._async_update) + + def handle_challenge_added(self, event_data): + """Handle new challenge event.""" + LOG.debug("ic-dev21 handle_challenge_added") + if event_data.get("progress") == "scheduled": + event_id = event_data.get("id") + if event_id: + event = EventWebsocket(**event_data) + if self._hilo.appreciation > 0: + event.appreciation(self._hilo.appreciation) + if self._hilo.pre_cold > 0: + event.pre_cold(self._hilo.pre_cold) + self._events[event_id] = event + self._update_next_events() + + def handle_challenge_list_initial(self, challenges): + LOG.debug("ic-dev21 handle_challenge_list_initial") + """Handle initial challenge list.""" + self._events.clear() + for challenge in challenges: + if challenge.get("progress") == "scheduled": + event_id = challenge.get("id") + if event_id: + event = EventWebsocket(**challenge) + if self._hilo.appreciation > 0: + event.appreciation(self._hilo.appreciation) + if self._hilo.pre_cold > 0: + event.pre_cold(self._hilo.pre_cold) + self._events[event_id] = event + self._update_next_events() + + def handle_challenge_list_update(self, challenges): + LOG.debug("ic-dev21 handle_challenge_list_update") + """Handle challenge list updates.""" + for challenge in challenges: + event_id = challenge.get("id") + progress = challenge.get("progress") + LOG.debug(f"ic-dev21 handle_challenge_list_update progress is {progress}") + if event_id in self._events: + if challenge.get("progress") == "completed": + del self._events[event_id] + else: + current_event = self._events[event_id] + updated_event = EventWebsocket( + **{**current_event.as_dict(), **challenge} + ) + if self._hilo.appreciation > 0: + updated_event.appreciation(self._hilo.appreciation) + if self._hilo.pre_cold > 0: + updated_event.pre_cold(self._hilo.pre_cold) + self._events[event_id] = updated_event + self._update_next_events() + + def handle_challenge_details_update(self, challenge): + LOG.debug("ic-dev21 handle_challenge_details_update") + """Handle challenge detail updates.""" + event_id = challenge.get("id") + progress = challenge.get("progress") + LOG.debug(f"ic-dev21 handle_challenge_details_update progress is {progress}") + if event_id in self._events: + if challenge.get("progress") == "completed": + del self._events[event_id] + else: + current_event = self._events[event_id] + updated_event = EventWebsocket( + **{**current_event.as_dict(), **challenge} + ) + if self._hilo.appreciation > 0: + updated_event.appreciation(self._hilo.appreciation) + if self._hilo.pre_cold > 0: + updated_event.pre_cold(self._hilo.pre_cold) + self._events[event_id] = updated_event + self._update_next_events() + + def _update_next_events(self): + LOG.debug("ic-dev21 sorting events") + """Update the next_events list based on current events.""" + # Sort events by start time + sorted_events = sorted(self._events.values(), key=lambda x: x.preheat_start) + + # Convert events to dictionaries and filter out completed ones + self._next_events = [ + event.as_dict() for event in sorted_events if event.state != "completed" + ] + + # Force an update of the entity + self.async_write_ha_state() + + @property + def state(self): + LOG.debug("ic-dev21 eventwebsocket define state") + """Return the current state based on next events.""" + if len(self._next_events) > 0: + event = EventWebsocket(**{**{"id": 0}, **self._next_events[0]}) + return event.state + return "off" + + @property + def icon(self): + if not self._device.available: + return "mdi:lan-disconnect" + if self.state == "appreciation": + return "mdi:glass-cocktail" + if self.state == "off": + return "mdi:lightning-bolt" + if self.state == "scheduled": + return "mdi:progress-clock" + if self.state == "pre_heat": + return "mdi:radiator" + if self.state in ["reduction", "on"]: + return "mdi:power-plug-off" + if self.state == "recovery": + return "mdi:calendar-check" + if self.state == "pre_cold": + return "mdi:radiator-off" + return "mdi:battery-alert" + + @property + def should_poll(self): + """No need to poll with websockets.""" + return False + + @property + def extra_state_attributes(self): + return {"next_events": self._next_events} + + async def async_added_to_hass(self): + """Handle entity about to be added to hass event.""" + await super().async_added_to_hass() + last_state = await self.async_get_last_state() + if last_state: + self._last_update = dt_util.utcnow() + self._state = last_state.state + self._next_events = last_state.attributes.get("next_events", []) + + async def _async_update(self): + """This method can be kept for fallback but shouldn't be needed with websockets.""" + pass + + class DeviceSensor(HiloEntity, SensorEntity): """Devices like the gateway or Smoke Detectors don't have many attributes, except for the "disconnected" attribute. These entities are monitoring From f655cde00d5c731734a5f2c5951d2cb0ddfc114c Mon Sep 17 00:00:00 2001 From: "Ian C." <108159253+ic-dev21@users.noreply.github.com> Date: Thu, 16 Jan 2025 21:12:53 -0500 Subject: [PATCH 20/40] =?UTF-8?q?Restore=20causait=20probl=C3=A8me=20(?= =?UTF-8?q?=C3=A0=20date)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- custom_components/hilo/sensor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/custom_components/hilo/sensor.py b/custom_components/hilo/sensor.py index 38c0f117..5b8c0dc1 100755 --- a/custom_components/hilo/sensor.py +++ b/custom_components/hilo/sensor.py @@ -794,7 +794,7 @@ async def _async_update(self): self._next_events.append(event.as_dict()) -class HiloChallengeSensorWebsocket(HiloEntity, RestoreEntity, SensorEntity): +class HiloChallengeSensorWebsocket(HiloEntity, SensorEntity): """Hilo challenge sensor. Its state will be either: - off: no ongoing or scheduled challenge From dc79f8af1d7c0003baac1b5823ace9358f903ffd Mon Sep 17 00:00:00 2001 From: "Ian C." <108159253+ic-dev21@users.noreply.github.com> Date: Mon, 20 Jan 2025 21:24:33 -0500 Subject: [PATCH 21/40] Adding websocket listeners Marche pas parfaitement pour le moment mais next_event se fait populer et le state a l'air de se tenir. --- custom_components/hilo/__init__.py | 45 +++++++++++++++++++++++++++++- custom_components/hilo/sensor.py | 16 +++++------ 2 files changed, 52 insertions(+), 9 deletions(-) diff --git a/custom_components/hilo/__init__.py b/custom_components/hilo/__init__.py index b125ef7b..1ec3275d 100755 --- a/custom_components/hilo/__init__.py +++ b/custom_components/hilo/__init__.py @@ -263,15 +263,57 @@ def __init__(self, hass: HomeAssistant, entry: ConfigEntry, api: API) -> None: self._events: dict = {} if self.track_unknown_sources: self._api._get_device_callbacks = [self._get_unknown_source_tracker] + self._websocket_listeners = [] def validate_heartbeat(self, event: WebsocketEvent) -> None: heartbeat_time = from_utc_timestamp(event.arguments[0]) # type: ignore if self._api.log_traces: LOG.debug(f"Heartbeat: {time_diff(heartbeat_time, event.timestamp)}") + + def register_websocket_listener(self, listener): + """Register a listener for websocket events.""" + LOG.debug(f"Registering websocket listener: {listener.__class__.__name__}") + self._websocket_listeners.append(listener) + + async def _handle_websocket_message(self, event): + """Process websocket messages and notify listeners.""" + + LOG.debug(f"Received websocket message type: {event}") + target = event.target + LOG.debug(f"handle_websocket_message_target {target}") + msg_data = event + LOG.debug(f"handle_websocket_message_ msg_data {msg_data}") + + if target == "ChallengeListInitialValuesReceived": + msg_type = 'challenge_list_initial' + elif target == "ChallengeAdded": + msg_type = 'challenge_added' + elif target == "ChallengeListUpdated": + msg_type = 'challenge_list_update' + elif target == "ChallengeDetailsUpdated": + msg_type = 'challenge_details_update' + + # ic-dev21 Notify listeners + for listener in self._websocket_listeners: + handler_name = f'handle_{msg_type}' + if hasattr(listener, handler_name): + handler = getattr(listener, handler_name) + try: + # ic-dev21 Extract the arguments from the WebsocketEvent object + if isinstance(msg_data, WebsocketEvent): + arguments = msg_data.arguments + if arguments: # ic-dev21 check if there are arguments + await handler(arguments[0]) + else: + LOG.warning(f"Received empty arguments for {msg_type}") + else: + await handler(msg_data) + except Exception as e: + LOG.error(f"Error in websocket handler {handler_name}: {e}") async def _handle_challenge_events(self, event: WebsocketEvent) -> None: """Handle all challenge-related websocket events.""" - if event.target == "ChallengeDetailsInitialValuesReceived": + if event.target == "ChallengeDetailsInitialValuesRecei0ved": challenge = event.arguments[0] LOG.debug(f"ChallengeDetailsInitialValuesReceived, challenge = {challenge}") self.challenge_id = challenge.get("id") @@ -376,6 +418,7 @@ async def on_websocket_event(self, event: WebsocketEvent) -> None: elif "Challenge" in event.target: await self._handle_challenge_events(event) + await self._handle_websocket_message(event) elif "Device" in event.target or event.target == "GatewayValuesReceived": await self._handle_device_events(event) diff --git a/custom_components/hilo/sensor.py b/custom_components/hilo/sensor.py index 5b8c0dc1..f00a3dcf 100755 --- a/custom_components/hilo/sensor.py +++ b/custom_components/hilo/sensor.py @@ -824,6 +824,7 @@ def __init__(self, hilo, device, scan_interval): self._next_events = [] self._events = {} # Store active events self.async_update = Throttle(self.scan_interval)(self._async_update) + hilo.register_websocket_listener(self) def handle_challenge_added(self, event_data): """Handle new challenge event.""" @@ -902,11 +903,10 @@ def _update_next_events(self): LOG.debug("ic-dev21 sorting events") """Update the next_events list based on current events.""" # Sort events by start time - sorted_events = sorted(self._events.values(), key=lambda x: x.preheat_start) + sorted_events = sorted(self._events.values(), key=lambda x: x.preheat_start, reverse=True) - # Convert events to dictionaries and filter out completed ones self._next_events = [ - event.as_dict() for event in sorted_events if event.state != "completed" + event.as_dict() for event in sorted_events #if event.state != "completed" ] # Force an update of the entity @@ -953,11 +953,11 @@ def extra_state_attributes(self): async def async_added_to_hass(self): """Handle entity about to be added to hass event.""" await super().async_added_to_hass() - last_state = await self.async_get_last_state() - if last_state: - self._last_update = dt_util.utcnow() - self._state = last_state.state - self._next_events = last_state.attributes.get("next_events", []) + #last_state = await self.async_get_last_state() + #if last_state: + # self._last_update = dt_util.utcnow() + # self._state = last_state.state + # self._next_events = last_state.attributes.get("next_events", []) async def _async_update(self): """This method can be kept for fallback but shouldn't be needed with websockets.""" From b972e5b1786a042ba824262cf2e32b3357c914dc Mon Sep 17 00:00:00 2001 From: "Ian C." <108159253+ic-dev21@users.noreply.github.com> Date: Tue, 21 Jan 2025 20:18:24 -0500 Subject: [PATCH 22/40] Debugging, logging --- custom_components/hilo/__init__.py | 4 ++++ custom_components/hilo/sensor.py | 19 +++++++++++-------- 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/custom_components/hilo/__init__.py b/custom_components/hilo/__init__.py index 1ec3275d..54d274aa 100755 --- a/custom_components/hilo/__init__.py +++ b/custom_components/hilo/__init__.py @@ -292,6 +292,10 @@ async def _handle_websocket_message(self, event): msg_type = 'challenge_list_update' elif target == "ChallengeDetailsUpdated": msg_type = 'challenge_details_update' + elif target == "ChallengeConsumptionUpdatedValuesReceived": + msg_type = 'challenge_details_update' + elif target == "ChallengeDetailsInitialValuesReceived": + msg_type = 'challenge_details_update' # ic-dev21 Notify listeners for listener in self._websocket_listeners: diff --git a/custom_components/hilo/sensor.py b/custom_components/hilo/sensor.py index f00a3dcf..4d0451c3 100755 --- a/custom_components/hilo/sensor.py +++ b/custom_components/hilo/sensor.py @@ -840,12 +840,13 @@ def handle_challenge_added(self, event_data): self._events[event_id] = event self._update_next_events() - def handle_challenge_list_initial(self, challenges): - LOG.debug("ic-dev21 handle_challenge_list_initial") + async def handle_challenge_list_initial(self, challenges): + LOG.debug(f"ic-dev21 handle_challenge_list_initial challenges: {challenges}") """Handle initial challenge list.""" self._events.clear() + LOG.debug(f"ic-dev21 handle_challenge_list_initial events: {self._events}") for challenge in challenges: - if challenge.get("progress") == "scheduled": + if challenge.get("progress") in ["scheduled", "inProgress"]: event_id = challenge.get("id") if event_id: event = EventWebsocket(**challenge) @@ -856,7 +857,7 @@ def handle_challenge_list_initial(self, challenges): self._events[event_id] = event self._update_next_events() - def handle_challenge_list_update(self, challenges): + async def handle_challenge_list_update(self, challenges): LOG.debug("ic-dev21 handle_challenge_list_update") """Handle challenge list updates.""" for challenge in challenges: @@ -878,11 +879,11 @@ def handle_challenge_list_update(self, challenges): self._events[event_id] = updated_event self._update_next_events() - def handle_challenge_details_update(self, challenge): - LOG.debug("ic-dev21 handle_challenge_details_update") + async def handle_challenge_details_update(self, challenge): + LOG.debug(f"ic-dev21 handle_challenge_details_update {challenge}") """Handle challenge detail updates.""" event_id = challenge.get("id") - progress = challenge.get("progress") + progress = challenge.get("progress", "unknown") LOG.debug(f"ic-dev21 handle_challenge_details_update progress is {progress}") if event_id in self._events: if challenge.get("progress") == "completed": @@ -903,7 +904,7 @@ def _update_next_events(self): LOG.debug("ic-dev21 sorting events") """Update the next_events list based on current events.""" # Sort events by start time - sorted_events = sorted(self._events.values(), key=lambda x: x.preheat_start, reverse=True) + sorted_events = sorted(self._events.values(), key=lambda x: x.preheat_start)#, reverse=True) self._next_events = [ event.as_dict() for event in sorted_events #if event.state != "completed" @@ -918,6 +919,8 @@ def state(self): """Return the current state based on next events.""" if len(self._next_events) > 0: event = EventWebsocket(**{**{"id": 0}, **self._next_events[0]}) + LOG.debug(f"def state HiloChallengeSensorWebsocket event: {event}") + LOG.debug(f"def state HiloChallengeSensorWebsocket event: {event.state}") return event.state return "off" From 3329d95c2299fb18184db1d43b10e75540c69d50 Mon Sep 17 00:00:00 2001 From: "Ian C." <108159253+ic-dev21@users.noreply.github.com> Date: Thu, 23 Jan 2025 19:32:56 -0500 Subject: [PATCH 23/40] Exception handling MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Oublié un async. --- custom_components/hilo/__init__.py | 2 ++ custom_components/hilo/sensor.py | 5 ++--- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/custom_components/hilo/__init__.py b/custom_components/hilo/__init__.py index 54d274aa..fc74fb88 100755 --- a/custom_components/hilo/__init__.py +++ b/custom_components/hilo/__init__.py @@ -294,6 +294,8 @@ async def _handle_websocket_message(self, event): msg_type = 'challenge_details_update' elif target == "ChallengeConsumptionUpdatedValuesReceived": msg_type = 'challenge_details_update' + elif target == "ChallengeDetailsUpdatedValuesReceived": + msg_type = 'challenge_details_update' elif target == "ChallengeDetailsInitialValuesReceived": msg_type = 'challenge_details_update' diff --git a/custom_components/hilo/sensor.py b/custom_components/hilo/sensor.py index 4d0451c3..51a558f1 100755 --- a/custom_components/hilo/sensor.py +++ b/custom_components/hilo/sensor.py @@ -826,7 +826,7 @@ def __init__(self, hilo, device, scan_interval): self.async_update = Throttle(self.scan_interval)(self._async_update) hilo.register_websocket_listener(self) - def handle_challenge_added(self, event_data): + async def handle_challenge_added(self, event_data): """Handle new challenge event.""" LOG.debug("ic-dev21 handle_challenge_added") if event_data.get("progress") == "scheduled": @@ -904,7 +904,7 @@ def _update_next_events(self): LOG.debug("ic-dev21 sorting events") """Update the next_events list based on current events.""" # Sort events by start time - sorted_events = sorted(self._events.values(), key=lambda x: x.preheat_start)#, reverse=True) + sorted_events = sorted(self._events.values(), key=lambda x: x.preheat_start) self._next_events = [ event.as_dict() for event in sorted_events #if event.state != "completed" @@ -920,7 +920,6 @@ def state(self): if len(self._next_events) > 0: event = EventWebsocket(**{**{"id": 0}, **self._next_events[0]}) LOG.debug(f"def state HiloChallengeSensorWebsocket event: {event}") - LOG.debug(f"def state HiloChallengeSensorWebsocket event: {event.state}") return event.state return "off" From cee6f14c214101fbb3d55e015bd7d0ee49150119 Mon Sep 17 00:00:00 2001 From: "Ian C." <108159253+ic-dev21@users.noreply.github.com> Date: Thu, 23 Jan 2025 20:06:11 -0500 Subject: [PATCH 24/40] Linting --- custom_components/hilo/__init__.py | 26 +++++++++++++------------- custom_components/hilo/sensor.py | 6 +++--- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/custom_components/hilo/__init__.py b/custom_components/hilo/__init__.py index fc74fb88..a61dcffa 100755 --- a/custom_components/hilo/__init__.py +++ b/custom_components/hilo/__init__.py @@ -269,7 +269,7 @@ def validate_heartbeat(self, event: WebsocketEvent) -> None: heartbeat_time = from_utc_timestamp(event.arguments[0]) # type: ignore if self._api.log_traces: LOG.debug(f"Heartbeat: {time_diff(heartbeat_time, event.timestamp)}") - + def register_websocket_listener(self, listener): """Register a listener for websocket events.""" LOG.debug(f"Registering websocket listener: {listener.__class__.__name__}") @@ -277,7 +277,7 @@ def register_websocket_listener(self, listener): async def _handle_websocket_message(self, event): """Process websocket messages and notify listeners.""" - + LOG.debug(f"Received websocket message type: {event}") target = event.target LOG.debug(f"handle_websocket_message_target {target}") @@ -285,30 +285,30 @@ async def _handle_websocket_message(self, event): LOG.debug(f"handle_websocket_message_ msg_data {msg_data}") if target == "ChallengeListInitialValuesReceived": - msg_type = 'challenge_list_initial' + msg_type = "challenge_list_initial" elif target == "ChallengeAdded": - msg_type = 'challenge_added' + msg_type = "challenge_added" elif target == "ChallengeListUpdated": - msg_type = 'challenge_list_update' + msg_type = "challenge_list_update" elif target == "ChallengeDetailsUpdated": - msg_type = 'challenge_details_update' + msg_type = "challenge_details_update" elif target == "ChallengeConsumptionUpdatedValuesReceived": - msg_type = 'challenge_details_update' - elif target == "ChallengeDetailsUpdatedValuesReceived": - msg_type = 'challenge_details_update' + msg_type = "challenge_details_update" + elif target == "ChallengeDetailsUpdatedValuesReceived": + msg_type = "challenge_details_update" elif target == "ChallengeDetailsInitialValuesReceived": - msg_type = 'challenge_details_update' - + msg_type = "challenge_details_update" + # ic-dev21 Notify listeners for listener in self._websocket_listeners: - handler_name = f'handle_{msg_type}' + handler_name = f"handle_{msg_type}" if hasattr(listener, handler_name): handler = getattr(listener, handler_name) try: # ic-dev21 Extract the arguments from the WebsocketEvent object if isinstance(msg_data, WebsocketEvent): arguments = msg_data.arguments - if arguments: # ic-dev21 check if there are arguments + if arguments: # ic-dev21 check if there are arguments await handler(arguments[0]) else: LOG.warning(f"Received empty arguments for {msg_type}") diff --git a/custom_components/hilo/sensor.py b/custom_components/hilo/sensor.py index 51a558f1..1f30698c 100755 --- a/custom_components/hilo/sensor.py +++ b/custom_components/hilo/sensor.py @@ -907,7 +907,7 @@ def _update_next_events(self): sorted_events = sorted(self._events.values(), key=lambda x: x.preheat_start) self._next_events = [ - event.as_dict() for event in sorted_events #if event.state != "completed" + event.as_dict() for event in sorted_events # if event.state != "completed" ] # Force an update of the entity @@ -955,8 +955,8 @@ def extra_state_attributes(self): async def async_added_to_hass(self): """Handle entity about to be added to hass event.""" await super().async_added_to_hass() - #last_state = await self.async_get_last_state() - #if last_state: + # last_state = await self.async_get_last_state() + # if last_state: # self._last_update = dt_util.utcnow() # self._state = last_state.state # self._next_events = last_state.attributes.get("next_events", []) From a2eade6152fb696a531ba951b9af16b353b42464 Mon Sep 17 00:00:00 2001 From: "Ian C." <108159253+ic-dev21@users.noreply.github.com> Date: Fri, 24 Jan 2025 11:17:22 -0500 Subject: [PATCH 25/40] Attribute removal on completed cahllenge --- custom_components/hilo/__init__.py | 20 +++++--------------- custom_components/hilo/sensor.py | 23 ++++++++++++++++++++--- 2 files changed, 25 insertions(+), 18 deletions(-) diff --git a/custom_components/hilo/__init__.py b/custom_components/hilo/__init__.py index a61dcffa..44f5372a 100755 --- a/custom_components/hilo/__init__.py +++ b/custom_components/hilo/__init__.py @@ -288,16 +288,19 @@ async def _handle_websocket_message(self, event): msg_type = "challenge_list_initial" elif target == "ChallengeAdded": msg_type = "challenge_added" - elif target == "ChallengeListUpdated": - msg_type = "challenge_list_update" elif target == "ChallengeDetailsUpdated": msg_type = "challenge_details_update" elif target == "ChallengeConsumptionUpdatedValuesReceived": msg_type = "challenge_details_update" + elif target == "ChallengeConsumptionUpdatedValuesReceived": + msg_type = "challenge_details_update" elif target == "ChallengeDetailsUpdatedValuesReceived": msg_type = "challenge_details_update" elif target == "ChallengeDetailsInitialValuesReceived": msg_type = "challenge_details_update" + elif target == "ChallengeListUpdatedValuesReceived": + msg_type = "challenge_details_update" + # ic-dev21 Notify listeners for listener in self._websocket_listeners: @@ -323,36 +326,23 @@ async def _handle_challenge_events(self, event: WebsocketEvent) -> None: challenge = event.arguments[0] LOG.debug(f"ChallengeDetailsInitialValuesReceived, challenge = {challenge}") self.challenge_id = challenge.get("id") - # Update sensor with challenge details - if hasattr(self, "challenge_sensor"): - self.challenge_sensor.handle_challenge_details_update(challenge) elif event.target == "ChallengeDetailsUpdatedValuesReceived": LOG.debug("ChallengeDetailsUpdatedValuesReceived") - if hasattr(self, "challenge_sensor"): - self.challenge_sensor.handle_challenge_details_update( - event.arguments[0] - ) elif event.target == "ChallengeListUpdatedValuesReceived": LOG.debug("ChallengeListUpdatedValuesReceived") - if hasattr(self, "challenge_sensor"): - self.challenge_sensor.handle_challenge_list_update(event.arguments[0]) self.challenge_phase = event.arguments[0][0]["currentPhase"] elif event.target == "ChallengeAdded": LOG.debug("ChallengeAdded") challenge = event.arguments[0] self.challenge_id = challenge.get("id") - if hasattr(self, "challenge_sensor"): - self.challenge_sensor.handle_challenge_added(challenge) await self.subscribe_to_challenge(1, self.challenge_id) elif event.target == "ChallengeListInitialValuesReceived": LOG.debug("ChallengeListInitialValuesReceived") challenges = event.arguments[0] - if hasattr(self, "challenge_sensor"): - self.challenge_sensor.handle_challenge_list_initial(challenges) for challenge in challenges: challenge_id = challenge.get("id") diff --git a/custom_components/hilo/sensor.py b/custom_components/hilo/sensor.py index 1f30698c..29317eb3 100755 --- a/custom_components/hilo/sensor.py +++ b/custom_components/hilo/sensor.py @@ -4,7 +4,7 @@ from datetime import datetime, timedelta, timezone from os.path import isfile -import aiofiles +import aiofiles, asyncio from homeassistant.components.integration.sensor import METHOD_LEFT, IntegrationSensor from homeassistant.components.sensor import ( SensorDeviceClass, @@ -846,6 +846,9 @@ async def handle_challenge_list_initial(self, challenges): self._events.clear() LOG.debug(f"ic-dev21 handle_challenge_list_initial events: {self._events}") for challenge in challenges: + event_id = challenge.get("id") + progress = challenge.get("progress") + LOG.debug(f"ic-dev21 handle_challenge_list_initial progress is {progress}") if challenge.get("progress") in ["scheduled", "inProgress"]: event_id = challenge.get("id") if event_id: @@ -863,12 +866,22 @@ async def handle_challenge_list_update(self, challenges): for challenge in challenges: event_id = challenge.get("id") progress = challenge.get("progress") + baselinewH = challenge.get("baselinewH") LOG.debug(f"ic-dev21 handle_challenge_list_update progress is {progress}") + LOG.debug(f"ic-dev21 handle_challenge_list_update baselinewH is {baselinewH}") if event_id in self._events: if challenge.get("progress") == "completed": - del self._events[event_id] + # Find the oldest event based on recovery_end datetime + oldest_event_id = min( + self._events.keys(), + key=lambda key: self._events[key].as_dict().get('phases', {}).get('recovery_end', '') + ) + await asyncio.sleep(300) + del self._events[oldest_event_id] + break else: current_event = self._events[event_id] + LOG.debug(f"ic-dev21 handle_challenge_list_update current event is: {current_event}") updated_event = EventWebsocket( **{**current_event.as_dict(), **challenge} ) @@ -884,6 +897,11 @@ async def handle_challenge_details_update(self, challenge): """Handle challenge detail updates.""" event_id = challenge.get("id") progress = challenge.get("progress", "unknown") + baselinewH = challenge.get("baselinewH") + used_kWh = challenge.get('currentWh', 0) / 1000 + LOG.debug(f"ic-dev21 handle_challenge_details_update progress is {progress}") + LOG.debug(f"ic-dev21 handle_challenge_details_update baselinewH is {baselinewH}") + LOG.debug(f"ic-dev21 handle_challenge_details_update used_kwh is {used_kWh}") LOG.debug(f"ic-dev21 handle_challenge_details_update progress is {progress}") if event_id in self._events: if challenge.get("progress") == "completed": @@ -965,7 +983,6 @@ async def _async_update(self): """This method can be kept for fallback but shouldn't be needed with websockets.""" pass - class DeviceSensor(HiloEntity, SensorEntity): """Devices like the gateway or Smoke Detectors don't have many attributes, except for the "disconnected" attribute. These entities are monitoring From 61c05a6cc4b3c4e8ac5ae8ab2e0ffaab6b17da17 Mon Sep 17 00:00:00 2001 From: "Ian C." <108159253+ic-dev21@users.noreply.github.com> Date: Thu, 30 Jan 2025 15:54:03 -0500 Subject: [PATCH 26/40] Remove dead code cleanup --- custom_components/hilo/sensor.py | 109 +++---------------------------- 1 file changed, 8 insertions(+), 101 deletions(-) diff --git a/custom_components/hilo/sensor.py b/custom_components/hilo/sensor.py index 29317eb3..0624383b 100755 --- a/custom_components/hilo/sensor.py +++ b/custom_components/hilo/sensor.py @@ -36,7 +36,7 @@ from packaging.version import Version from pyhilo.const import UNMONITORED_DEVICES from pyhilo.device import HiloDevice -from pyhilo.event import Event, EventWebsocket +from pyhilo.event import Event from pyhilo.util import from_utc_timestamp import ruyaml as yaml @@ -96,9 +96,6 @@ def validate_tariff_list(tariff_config): def generate_entities_from_device(device, hilo, scan_interval): entities = [] if device.type == "Gateway": - entities.append( - HiloChallengeSensor(hilo, device, scan_interval), - ) entities.append( HiloChallengeSensorWebsocket(hilo, device, scan_interval), ) @@ -703,96 +700,6 @@ async def _save_history(self, history: list): await yaml_file.write(yaml.dump(history, Dumper=yaml.RoundTripDumper)) -class HiloChallengeSensor(HiloEntity, RestoreEntity, SensorEntity): - """Hilo challenge sensor. - Its state will be either: - - off: no ongoing or scheduled challenge - - scheduled: A challenge is scheduled, details in the next_events - extra attribute - - pre_cold: optional phase to cool further before appreciation - - appreciation: optional phase to pre-heat more before challenge - - pre_heat: Currently in the pre-heat phase - - reduction or on: Challenge is currently active, heat is lowered - - recovery: Challenge is completed, we're reheating. - """ - - def __init__(self, hilo, device, scan_interval): - self._attr_name = "Defi Hilo" - super().__init__(hilo, name=self._attr_name, device=device) - old_unique_id = slugify(self._attr_name) - self._attr_unique_id = ( - f"{slugify(device.identifier)}-{slugify(self._attr_name)}" - ) - hilo.async_migrate_unique_id( - old_unique_id, self._attr_unique_id, Platform.SENSOR - ) - LOG.debug(f"Setting up ChallengeSensor entity: {self._attr_name}") - # note ic-dev21: scan time at 5 minutes (300s) will force local update - self.scan_interval = timedelta(seconds=EVENT_SCAN_INTERVAL_REDUCTION) - self._state = "off" - self._next_events = [] - self.async_update = Throttle(self.scan_interval)(self._async_update) - - @property - def state(self): - if len(self._next_events) > 0: - event = Event(**{**{"id": 0}, **self._next_events[0]}) - return event.state - else: - return "off" - - @property - def icon(self): - if not self._device.available: - return "mdi:lan-disconnect" - if self.state == "appreciation": - return "mdi:glass-cocktail" - if self.state == "off": - return "mdi:lightning-bolt" - if self.state == "scheduled": - return "mdi:progress-clock" - if self.state == "pre_heat": - return "mdi:radiator" - if self.state in ["reduction", "on"]: - return "mdi:power-plug-off" - if self.state == "recovery": - return "mdi:calendar-check" - if self.state == "pre_cold": - return "mdi:radiator-off" - return "mdi:battery-alert" - - @property - def should_poll(self): - return True - - @property - def extra_state_attributes(self): - return {"next_events": self._next_events} - - async def async_added_to_hass(self): - """Handle entity about to be added to hass event.""" - await super().async_added_to_hass() - last_state = await self.async_get_last_state() - if last_state: - self._last_update = dt_util.utcnow() - self._state = last_state.state - self._next_events = last_state.attributes.get("next_events", []) - - async def _async_update(self): - self._next_events = [] - self._test = self._hilo.challenge_id - LOG.debug(f"ChallengeSensor challenge id is {self._test}") - events = await self._hilo._api.get_gd_events(self._hilo.devices.location_id) - LOG.debug(f"Events received from Hilo: {events}") - for raw_event in events: - details = await self._hilo.get_event_details(raw_event["id"]) - event = Event(**details) - if self._hilo.appreciation > 0: - event.appreciation(self._hilo.appreciation) - if self._hilo.pre_cold > 0: - event.pre_cold(self._hilo.pre_cold) - self._next_events.append(event.as_dict()) - class HiloChallengeSensorWebsocket(HiloEntity, SensorEntity): """Hilo challenge sensor. @@ -809,7 +716,7 @@ class HiloChallengeSensorWebsocket(HiloEntity, SensorEntity): def __init__(self, hilo, device, scan_interval): LOG.debug("ic-dev21 init HiloChallengeSensorWebsocket") - self._attr_name = "Defi Hilo Websocket" + self._attr_name = "Defi Hilo" super().__init__(hilo, name=self._attr_name, device=device) old_unique_id = slugify(self._attr_name) self._attr_unique_id = ( @@ -832,7 +739,7 @@ async def handle_challenge_added(self, event_data): if event_data.get("progress") == "scheduled": event_id = event_data.get("id") if event_id: - event = EventWebsocket(**event_data) + event = Event(**event_data) if self._hilo.appreciation > 0: event.appreciation(self._hilo.appreciation) if self._hilo.pre_cold > 0: @@ -852,7 +759,7 @@ async def handle_challenge_list_initial(self, challenges): if challenge.get("progress") in ["scheduled", "inProgress"]: event_id = challenge.get("id") if event_id: - event = EventWebsocket(**challenge) + event = Event(**challenge) if self._hilo.appreciation > 0: event.appreciation(self._hilo.appreciation) if self._hilo.pre_cold > 0: @@ -882,7 +789,7 @@ async def handle_challenge_list_update(self, challenges): else: current_event = self._events[event_id] LOG.debug(f"ic-dev21 handle_challenge_list_update current event is: {current_event}") - updated_event = EventWebsocket( + updated_event = Event( **{**current_event.as_dict(), **challenge} ) if self._hilo.appreciation > 0: @@ -908,7 +815,7 @@ async def handle_challenge_details_update(self, challenge): del self._events[event_id] else: current_event = self._events[event_id] - updated_event = EventWebsocket( + updated_event = Event( **{**current_event.as_dict(), **challenge} ) if self._hilo.appreciation > 0: @@ -933,10 +840,10 @@ def _update_next_events(self): @property def state(self): - LOG.debug("ic-dev21 eventwebsocket define state") + LOG.debug("ic-dev21 Event define state") """Return the current state based on next events.""" if len(self._next_events) > 0: - event = EventWebsocket(**{**{"id": 0}, **self._next_events[0]}) + event = Event(**{**{"id": 0}, **self._next_events[0]}) LOG.debug(f"def state HiloChallengeSensorWebsocket event: {event}") return event.state return "off" From 5b705ac6f684dc15b7be9b1e0342dec1375ad166 Mon Sep 17 00:00:00 2001 From: "Ian C." <108159253+ic-dev21@users.noreply.github.com> Date: Thu, 30 Jan 2025 16:01:29 -0500 Subject: [PATCH 27/40] Linting. --- custom_components/hilo/__init__.py | 1 - custom_components/hilo/sensor.py | 32 ++++++++++++++++++------------ 2 files changed, 19 insertions(+), 14 deletions(-) diff --git a/custom_components/hilo/__init__.py b/custom_components/hilo/__init__.py index 44f5372a..9b63d14f 100755 --- a/custom_components/hilo/__init__.py +++ b/custom_components/hilo/__init__.py @@ -301,7 +301,6 @@ async def _handle_websocket_message(self, event): elif target == "ChallengeListUpdatedValuesReceived": msg_type = "challenge_details_update" - # ic-dev21 Notify listeners for listener in self._websocket_listeners: handler_name = f"handle_{msg_type}" diff --git a/custom_components/hilo/sensor.py b/custom_components/hilo/sensor.py index 0624383b..4db32657 100755 --- a/custom_components/hilo/sensor.py +++ b/custom_components/hilo/sensor.py @@ -1,10 +1,11 @@ """Support for various Hilo sensors.""" from __future__ import annotations +import asyncio from datetime import datetime, timedelta, timezone from os.path import isfile -import aiofiles, asyncio +import aiofiles from homeassistant.components.integration.sensor import METHOD_LEFT, IntegrationSensor from homeassistant.components.sensor import ( SensorDeviceClass, @@ -700,7 +701,6 @@ async def _save_history(self, history: list): await yaml_file.write(yaml.dump(history, Dumper=yaml.RoundTripDumper)) - class HiloChallengeSensorWebsocket(HiloEntity, SensorEntity): """Hilo challenge sensor. Its state will be either: @@ -775,23 +775,28 @@ async def handle_challenge_list_update(self, challenges): progress = challenge.get("progress") baselinewH = challenge.get("baselinewH") LOG.debug(f"ic-dev21 handle_challenge_list_update progress is {progress}") - LOG.debug(f"ic-dev21 handle_challenge_list_update baselinewH is {baselinewH}") + LOG.debug( + f"ic-dev21 handle_challenge_list_update baselinewH is {baselinewH}" + ) if event_id in self._events: if challenge.get("progress") == "completed": # Find the oldest event based on recovery_end datetime oldest_event_id = min( - self._events.keys(), - key=lambda key: self._events[key].as_dict().get('phases', {}).get('recovery_end', '') + self._events.keys(), + key=lambda key: self._events[key] + .as_dict() + .get("phases", {}) + .get("recovery_end", ""), ) await asyncio.sleep(300) del self._events[oldest_event_id] break else: current_event = self._events[event_id] - LOG.debug(f"ic-dev21 handle_challenge_list_update current event is: {current_event}") - updated_event = Event( - **{**current_event.as_dict(), **challenge} + LOG.debug( + f"ic-dev21 handle_challenge_list_update current event is: {current_event}" ) + updated_event = Event(**{**current_event.as_dict(), **challenge}) if self._hilo.appreciation > 0: updated_event.appreciation(self._hilo.appreciation) if self._hilo.pre_cold > 0: @@ -805,9 +810,11 @@ async def handle_challenge_details_update(self, challenge): event_id = challenge.get("id") progress = challenge.get("progress", "unknown") baselinewH = challenge.get("baselinewH") - used_kWh = challenge.get('currentWh', 0) / 1000 + used_kWh = challenge.get("currentWh", 0) / 1000 LOG.debug(f"ic-dev21 handle_challenge_details_update progress is {progress}") - LOG.debug(f"ic-dev21 handle_challenge_details_update baselinewH is {baselinewH}") + LOG.debug( + f"ic-dev21 handle_challenge_details_update baselinewH is {baselinewH}" + ) LOG.debug(f"ic-dev21 handle_challenge_details_update used_kwh is {used_kWh}") LOG.debug(f"ic-dev21 handle_challenge_details_update progress is {progress}") if event_id in self._events: @@ -815,9 +822,7 @@ async def handle_challenge_details_update(self, challenge): del self._events[event_id] else: current_event = self._events[event_id] - updated_event = Event( - **{**current_event.as_dict(), **challenge} - ) + updated_event = Event(**{**current_event.as_dict(), **challenge}) if self._hilo.appreciation > 0: updated_event.appreciation(self._hilo.appreciation) if self._hilo.pre_cold > 0: @@ -890,6 +895,7 @@ async def _async_update(self): """This method can be kept for fallback but shouldn't be needed with websockets.""" pass + class DeviceSensor(HiloEntity, SensorEntity): """Devices like the gateway or Smoke Detectors don't have many attributes, except for the "disconnected" attribute. These entities are monitoring From cf0fefca084862b8df6a2901b67b3e3afec26015 Mon Sep 17 00:00:00 2001 From: Boris Fersing Date: Thu, 30 Jan 2025 15:47:46 -0500 Subject: [PATCH 28/40] Reduce the race condition probability --- custom_components/hilo/sensor.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/custom_components/hilo/sensor.py b/custom_components/hilo/sensor.py index ed1ac9e6..5f383563 100755 --- a/custom_components/hilo/sensor.py +++ b/custom_components/hilo/sensor.py @@ -776,7 +776,7 @@ async def async_added_to_hass(self): self._next_events = last_state.attributes.get("next_events", []) async def _async_update(self): - self._next_events = [] + tmp_next_events = [] events = await self._hilo._api.get_gd_events(self._hilo.devices.location_id) LOG.debug(f"Events received from Hilo: {events}") for raw_event in events: @@ -786,7 +786,8 @@ async def _async_update(self): event.appreciation(self._hilo.appreciation) if self._hilo.pre_cold > 0: event.pre_cold(self._hilo.pre_cold) - self._next_events.append(event.as_dict()) + tmp_next_events.append(event.as_dict()) + self._next_events = tmp_next_events class DeviceSensor(HiloEntity, SensorEntity): From 081fac585fbbb67b53810737b7a0857bcef2e135 Mon Sep 17 00:00:00 2001 From: "Ian C." <108159253+ic-dev21@users.noreply.github.com> Date: Thu, 30 Jan 2025 21:40:13 -0500 Subject: [PATCH 29/40] List is not dict --- custom_components/hilo/sensor.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/custom_components/hilo/sensor.py b/custom_components/hilo/sensor.py index 5d9322a1..c951986c 100755 --- a/custom_components/hilo/sensor.py +++ b/custom_components/hilo/sensor.py @@ -813,8 +813,9 @@ async def handle_challenge_list_update(self, challenges): self._update_next_events() async def handle_challenge_details_update(self, challenge): - LOG.debug(f"ic-dev21 handle_challenge_details_update {challenge}") """Handle challenge detail updates.""" + LOG.debug(f"ic-dev21 handle_challenge_details_update {challenge}") + challenge = challenge[0] if isinstance(challenge, list) else challenge event_id = challenge.get("id") progress = challenge.get("progress", "unknown") baselinewH = challenge.get("baselinewH") From 14231bfb59470b9246c60c0abdf68a84da5eaa5b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 31 Jan 2025 08:40:04 +0000 Subject: [PATCH 30/40] Update homeassistant requirement from ~=2025.1.0 to ~=2025.1.4 Updates the requirements on [homeassistant](https://github.com/home-assistant/core) to permit the latest version. - [Release notes](https://github.com/home-assistant/core/releases) - [Commits](https://github.com/home-assistant/core/compare/2025.1.0...2025.1.4) --- updated-dependencies: - dependency-name: homeassistant dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 8c48e819..303553b9 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ colorlog==6.9.0 -homeassistant~=2025.1.0 +homeassistant~=2025.1.4 pip>=21.3.1 ruff==0.9.1 From 25f274e52c66bde8851e87e26e1826f086a8865c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 31 Jan 2025 08:40:15 +0000 Subject: [PATCH 31/40] Bump ruff from 0.9.1 to 0.9.4 Bumps [ruff](https://github.com/astral-sh/ruff) from 0.9.1 to 0.9.4. - [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.9.1...0.9.4) --- updated-dependencies: - dependency-name: ruff 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 8c48e819..f0937ba8 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ colorlog==6.9.0 homeassistant~=2025.1.0 pip>=21.3.1 -ruff==0.9.1 +ruff==0.9.4 From 73fc4c9596a3fdda8d5823c4d4d0197c026c8b2e Mon Sep 17 00:00:00 2001 From: "Ian C." <108159253+ic-dev21@users.noreply.github.com> Date: Fri, 31 Jan 2025 19:38:35 -0500 Subject: [PATCH 32/40] Remove duplicate --- custom_components/hilo/__init__.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/custom_components/hilo/__init__.py b/custom_components/hilo/__init__.py index eaec81fd..bdbf2541 100755 --- a/custom_components/hilo/__init__.py +++ b/custom_components/hilo/__init__.py @@ -292,8 +292,6 @@ async def _handle_websocket_message(self, event): msg_type = "challenge_details_update" elif target == "ChallengeConsumptionUpdatedValuesReceived": msg_type = "challenge_details_update" - elif target == "ChallengeConsumptionUpdatedValuesReceived": - msg_type = "challenge_details_update" elif target == "ChallengeDetailsUpdatedValuesReceived": msg_type = "challenge_details_update" elif target == "ChallengeDetailsInitialValuesReceived": From 8c4ccc16212041c72fddaf19279e5de719e931ce Mon Sep 17 00:00:00 2001 From: "Ian C." <108159253+ic-dev21@users.noreply.github.com> Date: Fri, 31 Jan 2025 20:28:47 -0500 Subject: [PATCH 33/40] Update sensor.py --- custom_components/hilo/sensor.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/custom_components/hilo/sensor.py b/custom_components/hilo/sensor.py index c951986c..6c870baf 100755 --- a/custom_components/hilo/sensor.py +++ b/custom_components/hilo/sensor.py @@ -854,11 +854,10 @@ def _update_next_events(self): @property def state(self): - LOG.debug("ic-dev21 Event define state") """Return the current state based on next events.""" if len(self._next_events) > 0: event = Event(**{**{"id": 0}, **self._next_events[0]}) - LOG.debug(f"def state HiloChallengeSensorWebsocket event: {event}") + LOG.debug(f"def state HiloChallengeSensor event: {event}") return event.state return "off" From 1c96e2269739e96e8aa93c13b71c09357e4a34b9 Mon Sep 17 00:00:00 2001 From: "Ian C." <108159253+ic-dev21@users.noreply.github.com> Date: Fri, 31 Jan 2025 20:38:36 -0500 Subject: [PATCH 34/40] Update sensor.py --- custom_components/hilo/sensor.py | 32 ++++++++++++++------------------ 1 file changed, 14 insertions(+), 18 deletions(-) diff --git a/custom_components/hilo/sensor.py b/custom_components/hilo/sensor.py index 6c870baf..e8ea4922 100755 --- a/custom_components/hilo/sensor.py +++ b/custom_components/hilo/sensor.py @@ -159,17 +159,16 @@ async def async_setup_entry( def create_energy_entity(hilo, device): device._energy_entity = EnergySensor(hilo, device) new_entities.append(device._energy_entity) - energy_unique_id = f"{slugify(device.identifier)}-energy" - if ( - energy_entity := hilo.async_get_entity_id_domain( - Platform.SENSOR, energy_unique_id + energy_entity = f"{slugify(device.name)}_hilo_energy" + if energy_entity == HILO_ENERGY_TOTAL: + LOG.error( + "An hilo entity can't be named 'total' because it conflicts " + "with the generated name for the smart energy meter" ) - ) is None: - energy_entity = f"sensor.{slugify(device.name)}_hilo_energy" - energy_entity = energy_entity.replace("sensor.", "") - + return tariff_list = default_tariff_list if device.type == "Meter": + energy_entity = HILO_ENERGY_TOTAL tariff_list = validate_tariff_list(tariff_config) net_consumption = device.net_consumption utility_manager.add_meter(energy_entity, tariff_list, net_consumption) @@ -288,14 +287,7 @@ def __init__(self, hilo, device): if device.type == "Meter": self._attr_name = HILO_ENERGY_TOTAL - power_unique_id = f"{slugify(device.identifier)}-power" - if ( - power_entity_id := hilo.async_get_entity_id_domain( - Platform.SENSOR, power_unique_id - ) - ) is None: - power_entity_id = f"{Platform.SENSOR}.{slugify(device.name)}_power" - self._source = power_entity_id + self._source = f"sensor.{slugify(device.name)}_power" # ic-dev21: Set initial state and last_valid_state, removes log errors and unavailable states initial_state = 0 self._attr_native_value = initial_state @@ -819,7 +811,11 @@ async def handle_challenge_details_update(self, challenge): event_id = challenge.get("id") progress = challenge.get("progress", "unknown") baselinewH = challenge.get("baselinewH") - used_kWh = challenge.get("currentWh", 0) / 1000 + used_wH = challenge.get("currentWh", 0) + if used_wH is not None and used_wH> 0 : + used_kWh = used_wH / 1000 + else: + used_kWh = 0 LOG.debug(f"ic-dev21 handle_challenge_details_update progress is {progress}") LOG.debug( f"ic-dev21 handle_challenge_details_update baselinewH is {baselinewH}" @@ -857,7 +853,7 @@ def state(self): """Return the current state based on next events.""" if len(self._next_events) > 0: event = Event(**{**{"id": 0}, **self._next_events[0]}) - LOG.debug(f"def state HiloChallengeSensor event: {event}") + LOG.debug(f"def state HiloChallengeSensorWebsocket event: {event}") return event.state return "off" From 2920bd7a7f5259b3e067a0ca1bd8f1832301bdb7 Mon Sep 17 00:00:00 2001 From: "Ian C." <108159253+ic-dev21@users.noreply.github.com> Date: Fri, 31 Jan 2025 21:15:14 -0500 Subject: [PATCH 35/40] Update sensor.py --- custom_components/hilo/sensor.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/custom_components/hilo/sensor.py b/custom_components/hilo/sensor.py index c11c584a..1076ec99 100755 --- a/custom_components/hilo/sensor.py +++ b/custom_components/hilo/sensor.py @@ -889,11 +889,6 @@ def extra_state_attributes(self): async def async_added_to_hass(self): """Handle entity about to be added to hass event.""" await super().async_added_to_hass() - # last_state = await self.async_get_last_state() - # if last_state: - # self._last_update = dt_util.utcnow() - # self._state = last_state.state - # self._next_events = last_state.attributes.get("next_events", []) async def _async_update(self): """This method can be kept for fallback but shouldn't be needed with websockets.""" From 1e04ef06c00dfecfea92fd9ffedf1046411bdb82 Mon Sep 17 00:00:00 2001 From: "Ian C." <108159253+ic-dev21@users.noreply.github.com> Date: Fri, 31 Jan 2025 21:17:20 -0500 Subject: [PATCH 36/40] =?UTF-8?q?M=C3=A9nage=20de=20commentaires?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- custom_components/hilo/sensor.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/custom_components/hilo/sensor.py b/custom_components/hilo/sensor.py index 1076ec99..4cc9295a 100755 --- a/custom_components/hilo/sensor.py +++ b/custom_components/hilo/sensor.py @@ -715,7 +715,6 @@ class HiloChallengeSensorWebsocket(HiloEntity, SensorEntity): """ def __init__(self, hilo, device, scan_interval): - LOG.debug("ic-dev21 init HiloChallengeSensorWebsocket") self._attr_name = "Defi Hilo" super().__init__(hilo, name=self._attr_name, device=device) old_unique_id = slugify(self._attr_name) @@ -748,8 +747,8 @@ async def handle_challenge_added(self, event_data): self._update_next_events() async def handle_challenge_list_initial(self, challenges): - LOG.debug(f"ic-dev21 handle_challenge_list_initial challenges: {challenges}") """Handle initial challenge list.""" + LOG.debug(f"ic-dev21 handle_challenge_list_initial challenges: {challenges}") self._events.clear() LOG.debug(f"ic-dev21 handle_challenge_list_initial events: {self._events}") for challenge in challenges: @@ -768,8 +767,8 @@ async def handle_challenge_list_initial(self, challenges): self._update_next_events() async def handle_challenge_list_update(self, challenges): - LOG.debug("ic-dev21 handle_challenge_list_update") """Handle challenge list updates.""" + LOG.debug("ic-dev21 handle_challenge_list_update") for challenge in challenges: event_id = challenge.get("id") progress = challenge.get("progress") @@ -836,8 +835,8 @@ async def handle_challenge_details_update(self, challenge): self._update_next_events() def _update_next_events(self): - LOG.debug("ic-dev21 sorting events") """Update the next_events list based on current events.""" + LOG.debug("ic-dev21 sorting events") # Sort events by start time sorted_events = sorted(self._events.values(), key=lambda x: x.preheat_start) From fe4958fed64d80447aa18749d3a9fdb4fbe78b23 Mon Sep 17 00:00:00 2001 From: "Ian C." <108159253+ic-dev21@users.noreply.github.com> Date: Sat, 1 Feb 2025 07:38:43 -0500 Subject: [PATCH 37/40] Update sensor.py --- custom_components/hilo/sensor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/custom_components/hilo/sensor.py b/custom_components/hilo/sensor.py index 4cc9295a..7fef287a 100755 --- a/custom_components/hilo/sensor.py +++ b/custom_components/hilo/sensor.py @@ -809,7 +809,7 @@ async def handle_challenge_details_update(self, challenge): challenge = challenge[0] if isinstance(challenge, list) else challenge event_id = challenge.get("id") progress = challenge.get("progress", "unknown") - baselinewH = challenge.get("baselinewH") + baselinewH = challenge.get("baselinewH", 0) used_wH = challenge.get("currentWh", 0) if used_wH is not None and used_wH> 0 : used_kWh = used_wH / 1000 From 0d4eb3fb6833a825c2e07ea43fd2b7ba6a7c4a4f Mon Sep 17 00:00:00 2001 From: "Ian C." <108159253+ic-dev21@users.noreply.github.com> Date: Sat, 1 Feb 2025 07:42:45 -0500 Subject: [PATCH 38/40] Update sensor.py --- custom_components/hilo/sensor.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/custom_components/hilo/sensor.py b/custom_components/hilo/sensor.py index 7fef287a..686d1871 100755 --- a/custom_components/hilo/sensor.py +++ b/custom_components/hilo/sensor.py @@ -98,7 +98,7 @@ def generate_entities_from_device(device, hilo, scan_interval): entities = [] if device.type == "Gateway": entities.append( - HiloChallengeSensorWebsocket(hilo, device, scan_interval), + HiloChallengeSensor(hilo, device, scan_interval), ) entities.append( HiloRewardSensor(hilo, device, scan_interval), @@ -701,7 +701,7 @@ async def _save_history(self, history: list): await yaml_file.write(yaml.dump(history, Dumper=yaml.RoundTripDumper)) -class HiloChallengeSensorWebsocket(HiloEntity, SensorEntity): +class HiloChallengeSensor(HiloEntity, SensorEntity): """Hilo challenge sensor. Its state will be either: - off: no ongoing or scheduled challenge @@ -724,7 +724,7 @@ def __init__(self, hilo, device, scan_interval): hilo.async_migrate_unique_id( old_unique_id, self._attr_unique_id, Platform.SENSOR ) - LOG.debug(f"Setting up ChallengeSensorWebsocket entity: {self._attr_name}") + LOG.debug(f"Setting up ChallengeSensor entity: {self._attr_name}") self.scan_interval = timedelta(seconds=EVENT_SCAN_INTERVAL_REDUCTION) self._state = "off" self._next_events = [] @@ -852,7 +852,7 @@ def state(self): """Return the current state based on next events.""" if len(self._next_events) > 0: event = Event(**{**{"id": 0}, **self._next_events[0]}) - LOG.debug(f"def state HiloChallengeSensorWebsocket event: {event}") + LOG.debug(f"def state HiloChallengeSensor event: {event}") return event.state return "off" From f3dcb5f896195f4903ff1c3e8a08aa9dc15dacf0 Mon Sep 17 00:00:00 2001 From: "Ian C." <108159253+ic-dev21@users.noreply.github.com> Date: Sat, 1 Feb 2025 13:09:28 -0500 Subject: [PATCH 39/40] Clean up excess logging --- custom_components/hilo/sensor.py | 1 - 1 file changed, 1 deletion(-) diff --git a/custom_components/hilo/sensor.py b/custom_components/hilo/sensor.py index 686d1871..364d42e6 100755 --- a/custom_components/hilo/sensor.py +++ b/custom_components/hilo/sensor.py @@ -852,7 +852,6 @@ def state(self): """Return the current state based on next events.""" if len(self._next_events) > 0: event = Event(**{**{"id": 0}, **self._next_events[0]}) - LOG.debug(f"def state HiloChallengeSensor event: {event}") return event.state return "off" From 3fd6d6764e6f38480c44a0ca9f83b081b7a0ea96 Mon Sep 17 00:00:00 2001 From: "Ian C." <108159253+ic-dev21@users.noreply.github.com> Date: Sat, 1 Feb 2025 13:10:12 -0500 Subject: [PATCH 40/40] Linting --- custom_components/hilo/sensor.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/custom_components/hilo/sensor.py b/custom_components/hilo/sensor.py index 364d42e6..cab8839c 100755 --- a/custom_components/hilo/sensor.py +++ b/custom_components/hilo/sensor.py @@ -811,7 +811,7 @@ async def handle_challenge_details_update(self, challenge): progress = challenge.get("progress", "unknown") baselinewH = challenge.get("baselinewH", 0) used_wH = challenge.get("currentWh", 0) - if used_wH is not None and used_wH> 0 : + if used_wH is not None and used_wH > 0: used_kWh = used_wH / 1000 else: used_kWh = 0 @@ -893,7 +893,6 @@ async def _async_update(self): pass - class DeviceSensor(HiloEntity, SensorEntity): """Devices like the gateway or Smoke Detectors don't have many attributes, except for the "disconnected" attribute. These entities are monitoring