From 4fcfef30ce0a0294a44c7239316fd2b50c9f23b2 Mon Sep 17 00:00:00 2001 From: MrCodeLabs <21204857+MrCodeLabs@users.noreply.github.com> Date: Mon, 2 Nov 2026 21:03:12 +0100 Subject: [PATCH] Replace cached write results with fresh polled status --- .../pygoecharger_ha/__init__.py | 14 ++++++++-- tests/integration/test_bridge_polling.py | 28 +++++++++++++++++++ 2 files changed, 40 insertions(+), 2 deletions(-) diff --git a/custom_components/goecharger_api2/pygoecharger_ha/__init__.py b/custom_components/goecharger_api2/pygoecharger_ha/__init__.py index 0493dac..14c848f 100644 --- a/custom_components/goecharger_api2/pygoecharger_ha/__init__.py +++ b/custom_components/goecharger_api2/pygoecharger_ha/__init__.py @@ -332,6 +332,7 @@ async def read_all(self) -> dict: async def read_all_states(self): do_minimal_status_update: bool = False + updated_states = {} if self.isCharger: # ok we are in idle state - so we do not need all states... [but 5 minutes (=300sec) do a full update] if Tag.CAR.key in self._states and self._states[Tag.CAR.key] == CAR_VALUES.IDLE.value: @@ -353,6 +354,7 @@ async def read_all_states(self): idle_states = self._read_filtered_data_from_ws_source(filters=filter, log_info="read_idle_states") else: idle_states = await self._read_filtered_data(filters=filter, log_info="read_idle_states") + updated_states = idle_states if len(idle_states) > 0: # copy all fields from 'idle_states' to self._states @@ -370,15 +372,23 @@ async def read_all_states(self): await self.read_all_states() else: + filter = self._FILTER_ALL_STATES if self.ws_connected: - self._states = self._read_filtered_data_from_ws_source(filters=self._FILTER_ALL_STATES, log_info="read_all_states") + self._states = self._read_filtered_data_from_ws_source(filters=filter, log_info="read_all_states") else: - ret = await self._read_filtered_data(filters=self._FILTER_ALL_STATES, log_info="read_all_states") + ret = await self._read_filtered_data(filters=filter, log_info="read_all_states") if ret is not None and len(ret) > 0 and isinstance(ret, NonNullDict): self._states = ret + updated_states = ret if len(self._states) > 0: self._LAST_FULL_STATE_UPDATE_TS = time.time() + if not self.ws_connected and updated_states: + # Fresh polled states replace cached write results, including expiry to null. + for a_key in filter.split(","): + if a_key in updated_states: + self._ws_states.pop(a_key, None) + async def force_config_update(self): self._LAST_CONFIG_UPDATE_TS = 0 self._LAST_FULL_STATE_UPDATE_TS = 0 diff --git a/tests/integration/test_bridge_polling.py b/tests/integration/test_bridge_polling.py index 951f0f9..42bcb93 100644 --- a/tests/integration/test_bridge_polling.py +++ b/tests/integration/test_bridge_polling.py @@ -5,6 +5,7 @@ """ import asyncio import time +from collections import ChainMap from types import SimpleNamespace from unittest.mock import AsyncMock, patch @@ -202,6 +203,33 @@ async def test_force_config_update_over_websocket_still_works(hass, aioclient_mo # ----------------------------------------------------------------------------- read_all +@pytest.mark.parametrize("car", [1, 4]) +@pytest.mark.parametrize("reported", [None, 8]) +async def test_poll_replaces_cached_tcl_write_with_reported_value(hass, aioclient_mock, car, reported): + coordinator = make_coordinator(hass) + bridge = coordinator.bridge + bridge._states = NonNullDict({"car": car, "tcl": 6}) + bridge._config = NonNullDict({"amp": 16, "ama": 16}) + bridge._LAST_CONFIG_UPDATE_TS = bridge._LAST_FULL_STATE_UPDATE_TS = time.time() + coordinator.data = ChainMap(bridge._ws_states, bridge._config, bridge._states, bridge._versions) + coordinator.handle_write_result("single", 6, "tcl", {"tcl": 6}, None) + assert bridge._ws_states["tcl"] == 6 + aioclient_mock.get(STATUS, json={"car": car, "tcl": reported}) + data = await bridge.read_all() + assert data["tcl"] == reported and "tcl" not in bridge._ws_states + assert data["amp"] == data["ama"] == 16 + + +async def test_failed_poll_keeps_cached_tcl_write(hass, aioclient_mock): + bridge = make_bridge(hass) + bridge._states = NonNullDict({"car": 4, "tcl": 6}) + bridge._ws_states["tcl"] = 6 + bridge._LAST_CONFIG_UPDATE_TS = time.time() + aioclient_mock.get(STATUS, status=503) + assert (await bridge.read_all())["tcl"] == 6 + assert bridge._ws_states["tcl"] == 6 + + async def test_read_all_returns_a_merged_view_with_websocket_values_first(hass, aioclient_mock): aioclient_mock.get(STATUS, json={"car": 1, "amp": 6}) bridge = make_bridge(hass)