From 9b0fcbf65d8ee6944ed1fc53ca2c471670b8f0c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pierre-D=C2=AAvid?= Date: Thu, 30 Oct 2025 08:03:01 -0400 Subject: [PATCH 1/9] Create allia_ht402.py --- zhaquirks/stelpro/allia_ht402.py | 1 + 1 file changed, 1 insertion(+) create mode 100644 zhaquirks/stelpro/allia_ht402.py diff --git a/zhaquirks/stelpro/allia_ht402.py b/zhaquirks/stelpro/allia_ht402.py new file mode 100644 index 0000000000..8b13789179 --- /dev/null +++ b/zhaquirks/stelpro/allia_ht402.py @@ -0,0 +1 @@ + From a4a4eb5fb65187965996bde07d3463ced2627b5e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pierre-D=C2=AAvid?= Date: Thu, 30 Oct 2025 08:07:16 -0400 Subject: [PATCH 2/9] Update allia_ht402.py Initial version that works in HA. --- zhaquirks/stelpro/allia_ht402.py | 60 ++++++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) diff --git a/zhaquirks/stelpro/allia_ht402.py b/zhaquirks/stelpro/allia_ht402.py index 8b13789179..3d3b2b1b53 100644 --- a/zhaquirks/stelpro/allia_ht402.py +++ b/zhaquirks/stelpro/allia_ht402.py @@ -1 +1,61 @@ +# References for attributes:https://github.com/Koenkk/zigbee2mqtt/issues/14651 +from typing import Final +import zigpy.types as t +from zigpy.quirks import CustomCluster +from zigpy.zcl.clusters.hvac import Thermostat +from zigpy.zcl.foundation import ZCLAttributeDef + +from zigpy.quirks.v2 import ( + QuirkBuilder, + ReportingConfig, + SensorDeviceClass, + SensorStateClass, +) +# Units come from the homeassistant submodule (guide uses UnitOfLength similarly) +from zigpy.quirks.v2.homeassistant import UnitOfPower, UnitOfEnergy + +MANUFACTURER: Final = "Stello" # Device manufacturer isn't listed as Stelpro for some reason +MODEL: Final = "HT402" +EP_THERMOSTAT: Final = 25 # Thermostat cluster + +class AlliaThermostatCluster(Thermostat, CustomCluster): + """Thermostat cluster extended with Stello/Allia manufacturer attributes.""" + + class AttributeDefs(Thermostat.AttributeDefs): + # 0x4008: Instant power in Watts (uint16) + allia_power_w = ZCLAttributeDef(id=0x4008, type=t.uint16_t, access="rp") + # 0x4009: Cumulative energy in Watt-hours (uint32) + allia_energy_wh = ZCLAttributeDef(id=0x4009, type=t.uint32_t, access="rp") + + +( + QuirkBuilder(MANUFACTURER, MODEL) + # Replace the Thermostat cluster on the actual endpoint + .replaces(AlliaThermostatCluster, endpoint_id=EP_THERMOSTAT) + # Expose Instant Power (W) + .sensor( + attribute_name=AlliaThermostatCluster.AttributeDefs.allia_power_w.name, + cluster_id=AlliaThermostatCluster.cluster_id, # guide: use target cluster's id + endpoint_id=EP_THERMOSTAT, + device_class=SensorDeviceClass.POWER, + state_class=SensorStateClass.MEASUREMENT, + unit=UnitOfPower.WATT, + reporting_config=ReportingConfig(min_interval=5, max_interval=300, reportable_change=1), + translation_key="allia_power_w", + fallback_name="Allia Power", + ) + # Expose Energy (Wh) + .sensor( + attribute_name=AlliaThermostatCluster.AttributeDefs.allia_energy_wh.name, + cluster_id=AlliaThermostatCluster.cluster_id, + endpoint_id=EP_THERMOSTAT, + device_class=SensorDeviceClass.ENERGY, + state_class=SensorStateClass.TOTAL_INCREASING, + unit=UnitOfEnergy.WATT_HOUR, + reporting_config=ReportingConfig(min_interval=30, max_interval=3600, reportable_change=10), + translation_key="allia_energy_wh", + fallback_name="Allia Energy", + ) + .add_to_registry() +) From f243cf7e0a85186f5f5c7b0a3655da968d047466 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pierre-D=C2=AAvid?= Date: Thu, 30 Oct 2025 08:09:05 -0400 Subject: [PATCH 3/9] Update allia_ht402.py Adding few doc URLs. --- zhaquirks/stelpro/allia_ht402.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/zhaquirks/stelpro/allia_ht402.py b/zhaquirks/stelpro/allia_ht402.py index 3d3b2b1b53..bbd59823fb 100644 --- a/zhaquirks/stelpro/allia_ht402.py +++ b/zhaquirks/stelpro/allia_ht402.py @@ -1,4 +1,6 @@ -# References for attributes:https://github.com/Koenkk/zigbee2mqtt/issues/14651 +# References for attributes of the termostat:https://github.com/Koenkk/zigbee2mqtt/issues/14651 - Also, they show up in the logs when ZHA runs in Debug mode. +# Guide for QuirkV2 https://github.com/zigpy/zha-device-handlers/discussions/4339 + from typing import Final import zigpy.types as t from zigpy.quirks import CustomCluster @@ -35,7 +37,7 @@ class AttributeDefs(Thermostat.AttributeDefs): # Expose Instant Power (W) .sensor( attribute_name=AlliaThermostatCluster.AttributeDefs.allia_power_w.name, - cluster_id=AlliaThermostatCluster.cluster_id, # guide: use target cluster's id + cluster_id=AlliaThermostatCluster.cluster_id, endpoint_id=EP_THERMOSTAT, device_class=SensorDeviceClass.POWER, state_class=SensorStateClass.MEASUREMENT, From 05a51016805003e98487adc54193b98dd2d75fce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pierre-D=C2=AAvid?= Date: Fri, 31 Oct 2025 07:34:40 -0400 Subject: [PATCH 4/9] Create __init__.py --- zhaquirks/stelpro/__init__.py | 1 + 1 file changed, 1 insertion(+) create mode 100644 zhaquirks/stelpro/__init__.py diff --git a/zhaquirks/stelpro/__init__.py b/zhaquirks/stelpro/__init__.py new file mode 100644 index 0000000000..feff84e47a --- /dev/null +++ b/zhaquirks/stelpro/__init__.py @@ -0,0 +1 @@ +"""Quirks for Stelpro devices.""" From 6c6ba06941ffe8cf1c5207bef801ee681955cd6f Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 31 Oct 2025 12:14:11 +0000 Subject: [PATCH 5/9] Apply pre-commit auto fixes --- zhaquirks/stelpro/allia_ht402.py | 27 +++++++++++++++++---------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/zhaquirks/stelpro/allia_ht402.py b/zhaquirks/stelpro/allia_ht402.py index bbd59823fb..9ac8b09c89 100644 --- a/zhaquirks/stelpro/allia_ht402.py +++ b/zhaquirks/stelpro/allia_ht402.py @@ -2,23 +2,27 @@ # Guide for QuirkV2 https://github.com/zigpy/zha-device-handlers/discussions/4339 from typing import Final -import zigpy.types as t -from zigpy.quirks import CustomCluster -from zigpy.zcl.clusters.hvac import Thermostat -from zigpy.zcl.foundation import ZCLAttributeDef +from zigpy.quirks import CustomCluster from zigpy.quirks.v2 import ( QuirkBuilder, ReportingConfig, SensorDeviceClass, SensorStateClass, ) + # Units come from the homeassistant submodule (guide uses UnitOfLength similarly) -from zigpy.quirks.v2.homeassistant import UnitOfPower, UnitOfEnergy +from zigpy.quirks.v2.homeassistant import UnitOfEnergy, UnitOfPower +import zigpy.types as t +from zigpy.zcl.clusters.hvac import Thermostat +from zigpy.zcl.foundation import ZCLAttributeDef -MANUFACTURER: Final = "Stello" # Device manufacturer isn't listed as Stelpro for some reason +MANUFACTURER: Final = ( + "Stello" # Device manufacturer isn't listed as Stelpro for some reason +) MODEL: Final = "HT402" -EP_THERMOSTAT: Final = 25 # Thermostat cluster +EP_THERMOSTAT: Final = 25 # Thermostat cluster + class AlliaThermostatCluster(Thermostat, CustomCluster): """Thermostat cluster extended with Stello/Allia manufacturer attributes.""" @@ -42,7 +46,9 @@ class AttributeDefs(Thermostat.AttributeDefs): device_class=SensorDeviceClass.POWER, state_class=SensorStateClass.MEASUREMENT, unit=UnitOfPower.WATT, - reporting_config=ReportingConfig(min_interval=5, max_interval=300, reportable_change=1), + reporting_config=ReportingConfig( + min_interval=5, max_interval=300, reportable_change=1 + ), translation_key="allia_power_w", fallback_name="Allia Power", ) @@ -54,10 +60,11 @@ class AttributeDefs(Thermostat.AttributeDefs): device_class=SensorDeviceClass.ENERGY, state_class=SensorStateClass.TOTAL_INCREASING, unit=UnitOfEnergy.WATT_HOUR, - reporting_config=ReportingConfig(min_interval=30, max_interval=3600, reportable_change=10), + reporting_config=ReportingConfig( + min_interval=30, max_interval=3600, reportable_change=10 + ), translation_key="allia_energy_wh", fallback_name="Allia Energy", ) .add_to_registry() ) - From 719556db460370d28978c4db3f9e72a5d9e15223 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pierre-D=C2=AAvid?= Date: Fri, 31 Oct 2025 08:30:51 -0400 Subject: [PATCH 6/9] Update allia_ht402.py Adding docstrings to satisfy pydocstyle (pep257) requirements. --- zhaquirks/stelpro/allia_ht402.py | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/zhaquirks/stelpro/allia_ht402.py b/zhaquirks/stelpro/allia_ht402.py index 9ac8b09c89..612b1534c2 100644 --- a/zhaquirks/stelpro/allia_ht402.py +++ b/zhaquirks/stelpro/allia_ht402.py @@ -1,5 +1,9 @@ -# References for attributes of the termostat:https://github.com/Koenkk/zigbee2mqtt/issues/14651 - Also, they show up in the logs when ZHA runs in Debug mode. -# Guide for QuirkV2 https://github.com/zigpy/zha-device-handlers/discussions/4339 +"""ZHA Quirk (v2) for Stello HT402. + +Exposes vendor attributes 0x4008 (instant power, W), 0x4009 (cumulative energy, Wh) +from the Thermostat cluster on endpoint 25 (0x19). + +""" from typing import Final @@ -25,9 +29,14 @@ class AlliaThermostatCluster(Thermostat, CustomCluster): - """Thermostat cluster extended with Stello/Allia manufacturer attributes.""" + """Thermostat cluster extended with Stelpro(Stello)/Allia manufacturer attributes.""" class AttributeDefs(Thermostat.AttributeDefs): + """Vendor-specific attributes added to the Thermostat cluster. + + - 0x4008: instant power (W), uint16 + - 0x4009: cumulative energy (Wh), uint32 + """ # 0x4008: Instant power in Watts (uint16) allia_power_w = ZCLAttributeDef(id=0x4008, type=t.uint16_t, access="rp") # 0x4009: Cumulative energy in Watt-hours (uint32) From 80328ed5d57014defb21302e36fa489e0040fa3f Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 31 Oct 2025 12:32:25 +0000 Subject: [PATCH 7/9] Apply pre-commit auto fixes --- zhaquirks/stelpro/allia_ht402.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/zhaquirks/stelpro/allia_ht402.py b/zhaquirks/stelpro/allia_ht402.py index 612b1534c2..72083c9b80 100644 --- a/zhaquirks/stelpro/allia_ht402.py +++ b/zhaquirks/stelpro/allia_ht402.py @@ -1,6 +1,6 @@ """ZHA Quirk (v2) for Stello HT402. -Exposes vendor attributes 0x4008 (instant power, W), 0x4009 (cumulative energy, Wh) +Exposes vendor attributes 0x4008 (instant power, W), 0x4009 (cumulative energy, Wh) from the Thermostat cluster on endpoint 25 (0x19). """ @@ -37,6 +37,7 @@ class AttributeDefs(Thermostat.AttributeDefs): - 0x4008: instant power (W), uint16 - 0x4009: cumulative energy (Wh), uint32 """ + # 0x4008: Instant power in Watts (uint16) allia_power_w = ZCLAttributeDef(id=0x4008, type=t.uint16_t, access="rp") # 0x4009: Cumulative energy in Watt-hours (uint32) From 07209dd3164fbaa03787a4c481dae4edc81f4872 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pierre-D=C2=AAvid?= Date: Tue, 11 Nov 2025 21:22:06 -0500 Subject: [PATCH 8/9] Update allia_ht402.py Add the outdoor temperature support with the caveat that it is off by x100C as it is the necessary format for the thermostat to display it properly and QuirksV2 don't seem to support inline lambda to_value/from_value in ZCLAttributeDef. --- zhaquirks/stelpro/allia_ht402.py | 52 ++++++++++++++++++++------------ 1 file changed, 33 insertions(+), 19 deletions(-) diff --git a/zhaquirks/stelpro/allia_ht402.py b/zhaquirks/stelpro/allia_ht402.py index 72083c9b80..a8f0294a1b 100644 --- a/zhaquirks/stelpro/allia_ht402.py +++ b/zhaquirks/stelpro/allia_ht402.py @@ -1,43 +1,47 @@ """ZHA Quirk (v2) for Stello HT402. -Exposes vendor attributes 0x4008 (instant power, W), 0x4009 (cumulative energy, Wh) -from the Thermostat cluster on endpoint 25 (0x19). - +Adds manufacturer attributes: +- 0x4008: Instant power (W) +- 0x4009: Cumulative energy (Wh) +- 0x4001: Outdoor temperature (°C or °F, read/write) """ from typing import Final +import zigpy.types as t from zigpy.quirks import CustomCluster +from zigpy.zcl.clusters.hvac import Thermostat +from zigpy.zcl.foundation import ZCLAttributeDef from zigpy.quirks.v2 import ( QuirkBuilder, ReportingConfig, SensorDeviceClass, SensorStateClass, ) +from zigpy.quirks.v2.homeassistant import UnitOfEnergy, UnitOfPower, UnitOfTemperature -# Units come from the homeassistant submodule (guide uses UnitOfLength similarly) -from zigpy.quirks.v2.homeassistant import UnitOfEnergy, UnitOfPower -import zigpy.types as t -from zigpy.zcl.clusters.hvac import Thermostat -from zigpy.zcl.foundation import ZCLAttributeDef - -MANUFACTURER: Final = ( - "Stello" # Device manufacturer isn't listed as Stelpro for some reason -) +MANUFACTURER: Final = "Stello" MODEL: Final = "HT402" -EP_THERMOSTAT: Final = 25 # Thermostat cluster +EP_THERMOSTAT: Final = 25 # 0x19 class AlliaThermostatCluster(Thermostat, CustomCluster): - """Thermostat cluster extended with Stelpro(Stello)/Allia manufacturer attributes.""" + """Thermostat cluster extended with Stello/Allia manufacturer attributes.""" class AttributeDefs(Thermostat.AttributeDefs): """Vendor-specific attributes added to the Thermostat cluster. - - 0x4008: instant power (W), uint16 - - 0x4009: cumulative energy (Wh), uint32 + - 0x4001: outdoor temperature (°C or °F), int16, read/write + - 0x4008: instant power (W), uint16, read-only + - 0x4009: cumulative energy (Wh), uint32, read-only """ + # 0x4001: Outdoor temperature (int16, read/write) + allia_outdoor_temperature = ZCLAttributeDef( + id=0x4001, + type=t.int16s, + access="rpw", # readable, reportable, writable + ) # 0x4008: Instant power in Watts (uint16) allia_power_w = ZCLAttributeDef(id=0x4008, type=t.uint16_t, access="rp") # 0x4009: Cumulative energy in Watt-hours (uint32) @@ -46,9 +50,8 @@ class AttributeDefs(Thermostat.AttributeDefs): ( QuirkBuilder(MANUFACTURER, MODEL) - # Replace the Thermostat cluster on the actual endpoint .replaces(AlliaThermostatCluster, endpoint_id=EP_THERMOSTAT) - # Expose Instant Power (W) + # Instant Power .sensor( attribute_name=AlliaThermostatCluster.AttributeDefs.allia_power_w.name, cluster_id=AlliaThermostatCluster.cluster_id, @@ -62,7 +65,7 @@ class AttributeDefs(Thermostat.AttributeDefs): translation_key="allia_power_w", fallback_name="Allia Power", ) - # Expose Energy (Wh) + # Energy (cumulative) .sensor( attribute_name=AlliaThermostatCluster.AttributeDefs.allia_energy_wh.name, cluster_id=AlliaThermostatCluster.cluster_id, @@ -76,5 +79,16 @@ class AttributeDefs(Thermostat.AttributeDefs): translation_key="allia_energy_wh", fallback_name="Allia Energy", ) + # Outdoor Temperature (0x4001) — read/write + .sensor( + attribute_name=AlliaThermostatCluster.AttributeDefs.allia_outdoor_temperature.name, + cluster_id=AlliaThermostatCluster.cluster_id, + endpoint_id=EP_THERMOSTAT, + device_class=SensorDeviceClass.TEMPERATURE, + state_class=SensorStateClass.MEASUREMENT, + unit=UnitOfTemperature.CELSIUS, # Would need to convert to F + translation_key="allia_outdoor_temperature", + fallback_name="Outdoor Temperature", + ) .add_to_registry() ) From 91df9528b8f5cda6b89e87498565927c77b9a776 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 12 Nov 2025 02:23:01 +0000 Subject: [PATCH 9/9] Apply pre-commit auto fixes --- zhaquirks/stelpro/allia_ht402.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/zhaquirks/stelpro/allia_ht402.py b/zhaquirks/stelpro/allia_ht402.py index a8f0294a1b..666b9170d5 100644 --- a/zhaquirks/stelpro/allia_ht402.py +++ b/zhaquirks/stelpro/allia_ht402.py @@ -8,10 +8,7 @@ from typing import Final -import zigpy.types as t from zigpy.quirks import CustomCluster -from zigpy.zcl.clusters.hvac import Thermostat -from zigpy.zcl.foundation import ZCLAttributeDef from zigpy.quirks.v2 import ( QuirkBuilder, ReportingConfig, @@ -19,6 +16,9 @@ SensorStateClass, ) from zigpy.quirks.v2.homeassistant import UnitOfEnergy, UnitOfPower, UnitOfTemperature +import zigpy.types as t +from zigpy.zcl.clusters.hvac import Thermostat +from zigpy.zcl.foundation import ZCLAttributeDef MANUFACTURER: Final = "Stello" MODEL: Final = "HT402" @@ -86,7 +86,7 @@ class AttributeDefs(Thermostat.AttributeDefs): endpoint_id=EP_THERMOSTAT, device_class=SensorDeviceClass.TEMPERATURE, state_class=SensorStateClass.MEASUREMENT, - unit=UnitOfTemperature.CELSIUS, # Would need to convert to F + unit=UnitOfTemperature.CELSIUS, # Would need to convert to F translation_key="allia_outdoor_temperature", fallback_name="Outdoor Temperature", )