From 059a375f4eb8ae3adb97c628e0791daf06b15eeb Mon Sep 17 00:00:00 2001 From: jbouwh Date: Fri, 7 Jun 2024 12:18:38 +0000 Subject: [PATCH 1/8] Slugify API boiler status and error codes --- incomfortclient/__init__.py | 58 ++++++++++++++++++------------------- 1 file changed, 29 insertions(+), 29 deletions(-) diff --git a/incomfortclient/__init__.py b/incomfortclient/__init__.py index 6ca851f..c1a7268 100644 --- a/incomfortclient/__init__.py +++ b/incomfortclient/__init__.py @@ -32,41 +32,41 @@ # key label: displ_code DISPLAY_CODES: list[int, str] = { 0: "opentherm", - 15: "boiler ext.", + 15: "boiler_ext", 24: "frost", - 37: "central heating rf", - 51: "tapwater int.", + 37: "central_heating_rf", + 51: "tapwater_int", 85: "sensortest", - 102: "central heating", + 102: "central_heating", 126: "standby", - 153: "postrun boiler", + 153: "postrun_boiler", 170: "service", 204: "tapwater", - 231: "postrun ch", - 240: "boiler int.", + 231: "postrun_ch", + 240: "boiler_int", 255: "buffer", } FAULT_CODES: list[int, str] = { - 0: "Sensor fault after self check", - 1: "Temperature too high", - 2: "S1 and S2 interchanged", - 4: "No flame signal", - 5: "Poor flame signal", - 6: "Flame detection fault", - 8: "Incorrect fan speed", - 10: "Sensor fault S1", - 11: "Sensor fault S1", - 12: "Sensor fault S1", - 13: "Sensor fault S1", - 14: "Sensor fault S1", - 20: "Sensor fault S2", - 21: "Sensor fault S2", - 22: "Sensor fault S2", - 23: "Sensor fault S2", - 24: "Sensor fault S2", - 27: "Shortcut outside sensor temperature", - 29: "Gas valve relay faulty", - 30: "Gas valve relay faulty", + 0: "sensor_fault_after_self_check", + 1: "temperature_too_high", + 2: "s1_and_S2_interchanged", + 4: "no_flame_signal", + 5: "poor_flame_signal", + 6: "flame_detection_fault", + 8: "incorrect fan_speed", + 10: "sensor_fault_s1", + 11: "sensor_fault_s1", + 12: "sensor_fault_s1", + 13: "sensor_fault_s1", + 14: "sensor_fault_s1", + 20: "sensor_fault_s2", + 21: "sensor_fault_s2", + 22: "sensor_fault_s2", + 23: "sensor_fault_s2", + 24: "sensor_fault_s2", + 27: "shortcut_outside_sensor temperature", + 29: "gas_valve_relay_faulty", + 30: "gas_valve_relay_faulty", } # "0.0": "Low system pressure" HEATER_ATTRS: tuple[str] = ( @@ -349,11 +349,11 @@ async def set_override(self, setpoint: float) -> None: try: assert OVERRIDE_MIN_TEMP <= setpoint <= OVERRIDE_MAX_TEMP - except AssertionError: + except AssertionError as exc: raise ValueError( "The setpoint is outside of it's valid range, " f"{OVERRIDE_MIN_TEMP}-{OVERRIDE_MAX_TEMP}." - ) + ) from exc url = "data.json?heater={self._heater._heater_idx}" url += f"&thermostat={int(self.room_no) - 1}" From 6394a33314147c4d271b5491775ae0fb9b0a8c01 Mon Sep 17 00:00:00 2001 From: jbouwh Date: Sat, 8 Jun 2024 09:29:19 +0000 Subject: [PATCH 2/8] Correct capital --- incomfortclient/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/incomfortclient/__init__.py b/incomfortclient/__init__.py index c1a7268..0feac0c 100644 --- a/incomfortclient/__init__.py +++ b/incomfortclient/__init__.py @@ -49,7 +49,7 @@ FAULT_CODES: list[int, str] = { 0: "sensor_fault_after_self_check", 1: "temperature_too_high", - 2: "s1_and_S2_interchanged", + 2: "s1_and_s2_interchanged", 4: "no_flame_signal", 5: "poor_flame_signal", 6: "flame_detection_fault", From 42fed265492708d848e580084cc698389a194160 Mon Sep 17 00:00:00 2001 From: jbouwh Date: Sat, 8 Jun 2024 09:30:41 +0000 Subject: [PATCH 3/8] replace space --- incomfortclient/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/incomfortclient/__init__.py b/incomfortclient/__init__.py index 0feac0c..75e0641 100644 --- a/incomfortclient/__init__.py +++ b/incomfortclient/__init__.py @@ -64,7 +64,7 @@ 22: "sensor_fault_s2", 23: "sensor_fault_s2", 24: "sensor_fault_s2", - 27: "shortcut_outside_sensor temperature", + 27: "shortcut_outside_sensor_temperature", 29: "gas_valve_relay_faulty", 30: "gas_valve_relay_faulty", } # "0.0": "Low system pressure" From 4336dede6b0f31a30630d3cc35da41a104add4bb Mon Sep 17 00:00:00 2001 From: jbouwh Date: Sat, 8 Jun 2024 09:38:18 +0000 Subject: [PATCH 4/8] rename to cv_temperature_too_high --- incomfortclient/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/incomfortclient/__init__.py b/incomfortclient/__init__.py index 75e0641..c5dd48c 100644 --- a/incomfortclient/__init__.py +++ b/incomfortclient/__init__.py @@ -48,7 +48,7 @@ } FAULT_CODES: list[int, str] = { 0: "sensor_fault_after_self_check", - 1: "temperature_too_high", + 1: "cv_temperature_too_high", 2: "s1_and_s2_interchanged", 4: "no_flame_signal", 5: "poor_flame_signal", From 49f39e6cf1034c55192c4476e8c298e4aa693b94 Mon Sep 17 00:00:00 2001 From: jbouwh Date: Sat, 8 Jun 2024 09:48:52 +0000 Subject: [PATCH 5/8] Ensure the default value for deplay_text is slugified too --- incomfortclient/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/incomfortclient/__init__.py b/incomfortclient/__init__.py index c5dd48c..428d640 100644 --- a/incomfortclient/__init__.py +++ b/incomfortclient/__init__.py @@ -251,7 +251,7 @@ def display_text(self) -> None | str: """Return the display code as text rather than a code.""" code = self.display_code code_map = FAULT_CODES if self.is_failed else DISPLAY_CODES - return code_map.get(code, f"unknown/other, code = '{code}'") + return code_map.get(code, f"code_{code}") @property def fault_code(self) -> None | int: From 8ebb1143aa19157f33a0a71ce583a0a3b64e28b6 Mon Sep 17 00:00:00 2001 From: jbouwh Date: Sat, 8 Jun 2024 21:18:42 +0000 Subject: [PATCH 6/8] Use IntEnum --- incomfortclient/__init__.py | 145 ++++++++++++++++++++++++------------ 1 file changed, 96 insertions(+), 49 deletions(-) diff --git a/incomfortclient/__init__.py b/incomfortclient/__init__.py index 80c23ba..5a87351 100644 --- a/incomfortclient/__init__.py +++ b/incomfortclient/__init__.py @@ -10,6 +10,8 @@ from __future__ import annotations import logging +from enum import IntEnum +from typing import Any import aiohttp @@ -29,45 +31,54 @@ BITMASK_PUMP = 0x02 # pump state: on / off BITMASK_TAP = 0x04 # tap (DHW) state: function on / off -# key label: displ_code -DISPLAY_CODES: dict[int, str] = { - 0: "opentherm", - 15: "boiler_ext", - 24: "frost", - 37: "central_heating_rf", - 51: "tapwater_int", - 85: "sensortest", - 102: "central_heating", - 126: "standby", - 153: "postrun_boiler", - 170: "service", - 204: "tapwater", - 231: "postrun_ch", - 240: "boiler_int", - 255: "buffer", -} -FAULT_CODES: dict[int, str] = { - 0: "sensor_fault_after_self_check", - 1: "cv_temperature_too_high", - 2: "s1_and_s2_interchanged", - 4: "no_flame_signal", - 5: "poor_flame_signal", - 6: "flame_detection_fault", - 8: "incorrect fan_speed", - 10: "sensor_fault_s1", - 11: "sensor_fault_s1", - 12: "sensor_fault_s1", - 13: "sensor_fault_s1", - 14: "sensor_fault_s1", - 20: "sensor_fault_s2", - 21: "sensor_fault_s2", - 22: "sensor_fault_s2", - 23: "sensor_fault_s2", - 24: "sensor_fault_s2", - 27: "shortcut_outside_sensor_temperature", - 29: "gas_valve_relay_faulty", - 30: "gas_valve_relay_faulty", -} # "0.0": "Low system pressure" +ISSUE_URL = "https://github.com/jbouwh/incomfort-client/issues" + + +class DiplayCode(IntEnum): + """Label to display code.""" + + UNKNOWN = -1 + OPENTHERM = 0 + BOILET_EXT = 15 + FROST = 24 + CENNTRAL_HEATING_RF = 37 + TAPWATER_INT = 51 + SENSOR_TEST = 85 + CENNTRAL_HEATING = 102 + STANDBY = 126 + POSTRUN_BOYLER = 153 + SERVICE = 170 + TAPWATER = 204 + POSTRUN_CH = 231 + BOILER_INT = 240 + BUFFER = 255 + + +class FaultCode(IntEnum): + """Label to fault code.""" + + UNKNOWN = -1 + SENSOR_FAULT_AFTER_SELF_CHECK_E0: 0 + CV_TEMPERATURE_TOO_HIGH_E1: 1 + S1_AND_S2_INTERCHANGED_E2: 2 + NO_FLAME_SIGNAL_E4: 4 + POOR_FLAME_SIGNAL_E5: 5 + FLAME_DETECTION_FAULT_E6: 6 + INCORRECT_FAN_SPEED_E8: 8 + SENSOR_FAULT_S1_E10: 10 + SENSOR_FAULT_S1_E11: 11 + SENSOR_FAULT_S1_E12: 12 + SENSOR_FAULT_S1_E13: 13 + SENSOR_FAULT_S1_E14: 14 + SENSOR_FAULT_S2_E20: 20 + SENSOR_FAULT_S2_E21: 21 + SENSOR_FAULT_S2_E22: 22 + SENSOR_FAULT_S2_E23: 23 + SENSOR_FAULT_S2_E24: 24 + SHORTCUT_OUTSIDE_SENSOR_TEMPERATURE_E27: 27 + GAS_VALVE_RELAY_FAULTY_E29: 29 + GAS_VALVE_RELAY_FAULTY_E30: 30 + HEATER_ATTRS: tuple[str, ...] = ( "display_code", @@ -221,11 +232,45 @@ def __init__(self, serial_no: str, idx: int, gateway: Gateway) -> None: self._data: dict = {} self._status: dict = {} self._rooms: list[Room] = None + self._display_code: DiplayCode | None = None + self._fault_code: FaultCode | None = None + self._last_display_code: int | None = None async def update(self) -> None: """Retrieve the Heater's latest status from the Gateway.""" self._data = await self._get(f"data.json?heater={self._heater_idx}") + code: int = self._data["displ_code"] + if self.is_failed: + self._display_code = None + try: + self._fault_code = FaultCode(code) + except ValueError: + self._fault_code = FaultCode.UNKNOWN + if self._last_display_code != code: + _LOGGER.warning( + "Unknown fault code %s reported by heater %s. " + "Log an issue at %s to report the unknown fault code", + code, + self._serial_no, + ISSUE_URL, + ) + else: + self._fault_code = None + try: + self._display_code = DiplayCode(code) + except ValueError: + self._display_code = DiplayCode.UNKNOWN + if self._last_display_code != code: + _LOGGER.warning( + "Unknown operation code %s reported by heater %s" + "Log an issue at %s to report the unknown display code", + code, + self._serial_no, + ISSUE_URL, + ) + self._last_display_code = code + self._status = {} for attr in HEATER_ATTRS: @@ -237,26 +282,28 @@ async def update(self) -> None: _LOGGER.debug("Heater(%s).status() = %s", self._serial_no, self._status) @property - def status(self) -> dict: + def status(self) -> dict[str, Any]: """Return the current state of the heater.""" return self._status @property - def display_code(self) -> int: + def display_code(self) -> DiplayCode | None: """Return the display code, 'displ_code'.""" - return self._data["displ_code"] + return self._display_code @property - def display_text(self) -> None | str: - """Return the display code as text rather than a code.""" - code = self.display_code - code_map = FAULT_CODES if self.is_failed else DISPLAY_CODES - return code_map.get(code, f"code_{code}") + def display_text(self) -> str: + """Return the display or fault code as text label rather than a code.""" + if self.is_failed: + fault_code = self._fault_code or FaultCode.UNKNOWN + return fault_code.name.lower() + display_code = self._display_code or DiplayCode.UNKNOWN + return display_code.name.lower() @property - def fault_code(self) -> None | int: + def fault_code(self) -> FaultCode | None: """Return the fault code when the heater is in a failed state.""" - return self._data["displ_code"] if self.is_failed else None + return self._fault_code @property def is_burning(self) -> bool: From 3c7a5bab558111bc711333dd1f77eecfc823c219 Mon Sep 17 00:00:00 2001 From: jbouwh Date: Sun, 9 Jun 2024 11:35:35 +0000 Subject: [PATCH 7/8] Simplify properties --- incomfortclient/__init__.py | 32 ++++++++++---------------------- 1 file changed, 10 insertions(+), 22 deletions(-) diff --git a/incomfortclient/__init__.py b/incomfortclient/__init__.py index 5a87351..9094f9f 100644 --- a/incomfortclient/__init__.py +++ b/incomfortclient/__init__.py @@ -232,8 +232,8 @@ def __init__(self, serial_no: str, idx: int, gateway: Gateway) -> None: self._data: dict = {} self._status: dict = {} self._rooms: list[Room] = None - self._display_code: DiplayCode | None = None - self._fault_code: FaultCode | None = None + self.display_code: DiplayCode | None = None + self.fault_code: FaultCode | None = None self._last_display_code: int | None = None async def update(self) -> None: @@ -242,11 +242,11 @@ async def update(self) -> None: code: int = self._data["displ_code"] if self.is_failed: - self._display_code = None + self.display_code = None try: - self._fault_code = FaultCode(code) + self.fault_code = FaultCode(code) except ValueError: - self._fault_code = FaultCode.UNKNOWN + self.fault_code = FaultCode.UNKNOWN if self._last_display_code != code: _LOGGER.warning( "Unknown fault code %s reported by heater %s. " @@ -256,11 +256,11 @@ async def update(self) -> None: ISSUE_URL, ) else: - self._fault_code = None + self.fault_code = None try: - self._display_code = DiplayCode(code) + self.display_code = DiplayCode(code) except ValueError: - self._display_code = DiplayCode.UNKNOWN + self.display_code = DiplayCode.UNKNOWN if self._last_display_code != code: _LOGGER.warning( "Unknown operation code %s reported by heater %s" @@ -286,24 +286,12 @@ def status(self) -> dict[str, Any]: """Return the current state of the heater.""" return self._status - @property - def display_code(self) -> DiplayCode | None: - """Return the display code, 'displ_code'.""" - return self._display_code - @property def display_text(self) -> str: """Return the display or fault code as text label rather than a code.""" if self.is_failed: - fault_code = self._fault_code or FaultCode.UNKNOWN - return fault_code.name.lower() - display_code = self._display_code or DiplayCode.UNKNOWN - return display_code.name.lower() - - @property - def fault_code(self) -> FaultCode | None: - """Return the fault code when the heater is in a failed state.""" - return self._fault_code + return self.fault_code.name.lower() if self.fault_code else None + return self.display_code.name.lower() if self.display_code else None @property def is_burning(self) -> bool: From 1bd793f4304018e6a0c2acf77ceffc0d72db50f2 Mon Sep 17 00:00:00 2001 From: jbouwh Date: Sun, 9 Jun 2024 11:38:16 +0000 Subject: [PATCH 8/8] Correct return type for display_text propery --- incomfortclient/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/incomfortclient/__init__.py b/incomfortclient/__init__.py index 9094f9f..b8f04e5 100644 --- a/incomfortclient/__init__.py +++ b/incomfortclient/__init__.py @@ -287,7 +287,7 @@ def status(self) -> dict[str, Any]: return self._status @property - def display_text(self) -> str: + def display_text(self) -> str | None: """Return the display or fault code as text label rather than a code.""" if self.is_failed: return self.fault_code.name.lower() if self.fault_code else None