From b6dc450e8f5df64b662231e61a4210b534ce27ff Mon Sep 17 00:00:00 2001 From: Tor Raswill Date: Wed, 13 Dec 2023 21:36:58 +0100 Subject: [PATCH 1/4] Added quirk for Schneider Wiser outlets Based on the code supplied by krlssn in comment https://github.com/zigpy/zha-device-handlers/issues/1889#issuecomment-1337368444 --- zhaquirks/schneider/__init__.py | 1 + zhaquirks/schneider/outlet.py | 101 ++++++++++++++++++++++++++++++++ 2 files changed, 102 insertions(+) create mode 100644 zhaquirks/schneider/__init__.py create mode 100644 zhaquirks/schneider/outlet.py diff --git a/zhaquirks/schneider/__init__.py b/zhaquirks/schneider/__init__.py new file mode 100644 index 0000000000..1ea68e27f2 --- /dev/null +++ b/zhaquirks/schneider/__init__.py @@ -0,0 +1 @@ +"""Quirks for Schneider devices.""" diff --git a/zhaquirks/schneider/outlet.py b/zhaquirks/schneider/outlet.py new file mode 100644 index 0000000000..51e2c3fcd2 --- /dev/null +++ b/zhaquirks/schneider/outlet.py @@ -0,0 +1,101 @@ +"""Schneider Electric (Wiser) Outlet Quirks.""" +from zigpy.profiles import zha +from zigpy.quirks import CustomCluster, CustomDevice +from zigpy.zcl.clusters.general import ( + Basic, + GreenPowerProxy, + Groups, + Identify, + OnOff, + Ota, + Scenes, +) +from zigpy.zcl.clusters.homeautomation import Diagnostic, ElectricalMeasurement +from zigpy.zcl.clusters.smartenergy import DeviceManagement, Metering + +from zhaquirks.const import ( + DEVICE_TYPE, + ENDPOINTS, + INPUT_CLUSTERS, + MODELS_INFO, + OUTPUT_CLUSTERS, + PROFILE_ID, +) + + +class MeteringCluster(CustomCluster, Metering): + """Fix the Instantaneous Demand value x1000""" + + INSTANTANEOUS_DEMAND = 0x0400 + + def _update_attribute(self, attrid, value): + if attrid == self.INSTANTANEOUS_DEMAND: + value = value / 1000.0 + super()._update_attribute(attrid, value) + + +class SocketOutlet(CustomDevice): + """Schneider Electric Socket outlet WDE002182,WDE002172.""" + + signature = { + MODELS_INFO: [ + ("Schneider Electric", "SOCKET/OUTLET/1"), + ("Schneider Electric", "SOCKET/OUTLET/2"), + ], + ENDPOINTS: { + # + 6: { + PROFILE_ID: 0x0104, + DEVICE_TYPE: zha.DeviceType.MAIN_POWER_OUTLET, + INPUT_CLUSTERS: [ + Basic.cluster_id, + Identify.cluster_id, + Groups.cluster_id, + Scenes.cluster_id, + OnOff.cluster_id, + Metering.cluster_id, + DeviceManagement.cluster_id, + ElectricalMeasurement.cluster_id, + Diagnostic.cluster_id, + 0xFC04, + ], + OUTPUT_CLUSTERS: [Ota.cluster_id], + }, + 242: { + PROFILE_ID: 41440, + DEVICE_TYPE: 97, + INPUT_CLUSTERS: [], + OUTPUT_CLUSTERS: [GreenPowerProxy.cluster_id], + }, + }, + } + + replacement = { + ENDPOINTS: { + 6: { + PROFILE_ID: 0x0104, + DEVICE_TYPE: zha.DeviceType.MAIN_POWER_OUTLET, + INPUT_CLUSTERS: [ + Basic.cluster_id, + Identify.cluster_id, + Groups.cluster_id, + Scenes.cluster_id, + OnOff.cluster_id, + MeteringCluster, + DeviceManagement.cluster_id, + ElectricalMeasurement.cluster_id, + Diagnostic.cluster_id, + 0xFC04, + ], + OUTPUT_CLUSTERS: [Ota.cluster_id], + }, + 242: { + PROFILE_ID: 41440, + DEVICE_TYPE: 97, + INPUT_CLUSTERS: [], + OUTPUT_CLUSTERS: [GreenPowerProxy.cluster_id], + }, + }, + } From a37ccf5dd5b28ad02b91b9201109c4382156d20b Mon Sep 17 00:00:00 2001 From: Tor Raswill Date: Wed, 13 Dec 2023 22:09:11 +0100 Subject: [PATCH 2/4] Update outlet.py tweaked output clusters --- zhaquirks/schneider/outlet.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zhaquirks/schneider/outlet.py b/zhaquirks/schneider/outlet.py index 51e2c3fcd2..0a88f14938 100644 --- a/zhaquirks/schneider/outlet.py +++ b/zhaquirks/schneider/outlet.py @@ -45,7 +45,7 @@ class SocketOutlet(CustomDevice): ENDPOINTS: { # + # input_clusters=[0, 3, 4, 5, 6, 1794, 1800, 2820, 2821, 64516] output_clusters=[25]> 6: { PROFILE_ID: 0x0104, DEVICE_TYPE: zha.DeviceType.MAIN_POWER_OUTLET, From e278b6caaa8f5e3189945a2bb415ad0f46c4a279 Mon Sep 17 00:00:00 2001 From: TheJulianJES Date: Fri, 19 Jan 2024 21:00:39 +0100 Subject: [PATCH 3/4] Clean up quirk file slightly --- zhaquirks/schneider/__init__.py | 2 ++ zhaquirks/schneider/outlet.py | 45 +++++++++++++++++++-------------- 2 files changed, 28 insertions(+), 19 deletions(-) diff --git a/zhaquirks/schneider/__init__.py b/zhaquirks/schneider/__init__.py index 1ea68e27f2..610b5c6cbc 100644 --- a/zhaquirks/schneider/__init__.py +++ b/zhaquirks/schneider/__init__.py @@ -1 +1,3 @@ """Quirks for Schneider devices.""" + +SCHNEIDER = "Schneider Electric" diff --git a/zhaquirks/schneider/outlet.py b/zhaquirks/schneider/outlet.py index 0a88f14938..c4e4ce0688 100644 --- a/zhaquirks/schneider/outlet.py +++ b/zhaquirks/schneider/outlet.py @@ -1,5 +1,5 @@ """Schneider Electric (Wiser) Outlet Quirks.""" -from zigpy.profiles import zha +from zigpy.profiles import zgp, zha from zigpy.quirks import CustomCluster, CustomDevice from zigpy.zcl.clusters.general import ( Basic, @@ -21,33 +21,32 @@ OUTPUT_CLUSTERS, PROFILE_ID, ) +from zhaquirks.schneider import SCHNEIDER class MeteringCluster(CustomCluster, Metering): - """Fix the Instantaneous Demand value x1000""" - - INSTANTANEOUS_DEMAND = 0x0400 + """Custom Metering cluster to fix instantaneous demand value multiplied by 1000.""" def _update_attribute(self, attrid, value): - if attrid == self.INSTANTANEOUS_DEMAND: - value = value / 1000.0 + if attrid == self.AttributeDefs.instantaneous_demand.id: + value = value / 1000 super()._update_attribute(attrid, value) class SocketOutlet(CustomDevice): - """Schneider Electric Socket outlet WDE002182,WDE002172.""" + """Schneider Electric Socket outlet WDE002182, WDE002172.""" signature = { MODELS_INFO: [ - ("Schneider Electric", "SOCKET/OUTLET/1"), - ("Schneider Electric", "SOCKET/OUTLET/2"), + (SCHNEIDER, "SOCKET/OUTLET/1"), + (SCHNEIDER, "SOCKET/OUTLET/2"), ], ENDPOINTS: { # 6: { - PROFILE_ID: 0x0104, + PROFILE_ID: zha.PROFILE_ID, DEVICE_TYPE: zha.DeviceType.MAIN_POWER_OUTLET, INPUT_CLUSTERS: [ Basic.cluster_id, @@ -61,13 +60,17 @@ class SocketOutlet(CustomDevice): Diagnostic.cluster_id, 0xFC04, ], - OUTPUT_CLUSTERS: [Ota.cluster_id], + OUTPUT_CLUSTERS: [ + Ota.cluster_id, + ], }, 242: { - PROFILE_ID: 41440, - DEVICE_TYPE: 97, + PROFILE_ID: zgp.PROFILE_ID, + DEVICE_TYPE: zgp.DeviceType.PROXY_BASIC, INPUT_CLUSTERS: [], - OUTPUT_CLUSTERS: [GreenPowerProxy.cluster_id], + OUTPUT_CLUSTERS: [ + GreenPowerProxy.cluster_id, + ], }, }, } @@ -75,7 +78,7 @@ class SocketOutlet(CustomDevice): replacement = { ENDPOINTS: { 6: { - PROFILE_ID: 0x0104, + PROFILE_ID: zha.PROFILE_ID, DEVICE_TYPE: zha.DeviceType.MAIN_POWER_OUTLET, INPUT_CLUSTERS: [ Basic.cluster_id, @@ -89,13 +92,17 @@ class SocketOutlet(CustomDevice): Diagnostic.cluster_id, 0xFC04, ], - OUTPUT_CLUSTERS: [Ota.cluster_id], + OUTPUT_CLUSTERS: [ + Ota.cluster_id, + ], }, 242: { - PROFILE_ID: 41440, - DEVICE_TYPE: 97, + PROFILE_ID: zgp.PROFILE_ID, + DEVICE_TYPE: zgp.DeviceType.PROXY_BASIC, INPUT_CLUSTERS: [], - OUTPUT_CLUSTERS: [GreenPowerProxy.cluster_id], + OUTPUT_CLUSTERS: [ + GreenPowerProxy.cluster_id, + ], }, }, } From 65b4f4d517febc958b2b51fd3889bd37f7991a5b Mon Sep 17 00:00:00 2001 From: TheJulianJES Date: Fri, 19 Jan 2024 21:00:56 +0100 Subject: [PATCH 4/4] Add test for Schneider plug division --- tests/test_schneider.py | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 tests/test_schneider.py diff --git a/tests/test_schneider.py b/tests/test_schneider.py new file mode 100644 index 0000000000..186c43f311 --- /dev/null +++ b/tests/test_schneider.py @@ -0,0 +1,33 @@ +"""Tests for Schneider.""" + +import pytest +from zigpy.zcl.clusters.smartenergy import Metering + +import zhaquirks.schneider.outlet + +from tests.common import ClusterListener + +zhaquirks.setup() + + +@pytest.mark.parametrize("quirk", (zhaquirks.schneider.outlet.SocketOutlet,)) +async def test_schneider_device_temp(zigpy_device_from_quirk, quirk): + """Test that instant demand is divided by 1000.""" + device = zigpy_device_from_quirk(quirk) + + metering_cluster = device.endpoints[6].smartenergy_metering + metering_listener = ClusterListener(metering_cluster) + instantaneous_demand_attr_id = Metering.AttributeDefs.instantaneous_demand.id + summation_delivered_attr_id = Metering.AttributeDefs.current_summ_delivered.id + + # verify instant demand is divided by 1000 + metering_cluster.update_attribute(instantaneous_demand_attr_id, 25000) + assert len(metering_listener.attribute_updates) == 1 + assert metering_listener.attribute_updates[0][0] == instantaneous_demand_attr_id + assert metering_listener.attribute_updates[0][1] == 25 # divided by 1000 + + # verify other attributes are not modified + metering_cluster.update_attribute(summation_delivered_attr_id, 25) + assert len(metering_listener.attribute_updates) == 2 + assert metering_listener.attribute_updates[1][0] == summation_delivered_attr_id + assert metering_listener.attribute_updates[1][1] == 25 # not modified