Skip to content

Commit 724d721

Browse files
committed
Initial test added for energy direction alignment on ZCL clusters
1 parent d764ea5 commit 724d721

File tree

1 file changed

+274
-0
lines changed

1 file changed

+274
-0
lines changed

tests/test_tuya_energy_meter.py

Lines changed: 274 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,274 @@
1+
"""Tests for Tuya quirks."""
2+
3+
import pytest
4+
from zigpy.zcl.clusters.homeautomation import ElectricalMeasurement
5+
from zigpy.zcl.clusters.smartenergy import Metering
6+
7+
from tests.common import ClusterListener
8+
import zhaquirks
9+
from zhaquirks import LocalDataCluster
10+
import zhaquirks.tuya
11+
from zhaquirks.tuya.mcu import TuyaMCUCluster
12+
13+
zhaquirks.setup()
14+
15+
16+
@pytest.mark.parametrize(
17+
"model,manuf,channels,bidirectional",
18+
[
19+
(
20+
"_TZE204_cjbofhxw",
21+
"TS0601",
22+
{1},
23+
False,
24+
),
25+
("_TZE204_ac0fhfiq", "TS0601", {1}, True),
26+
("_TZE200_rks0sgb7", "TS0601", {1, 2, 11}, True),
27+
("_TZE204_81yrt3lo", "TS0601", {1, 2, 11}, True),
28+
],
29+
)
30+
async def test_tuya_energy_meter_quirk_energy_direction_align(
31+
zigpy_device_from_v2_quirk,
32+
model: str,
33+
manuf: str,
34+
channels,
35+
bidirectional: bool,
36+
):
37+
"""Test Tuya Energy Meter Quirk energy direction align in ElectricalMeasurement and Metering clusters."""
38+
quirked_device = zigpy_device_from_v2_quirk(model, manuf)
39+
40+
ENERGY_DIRECTION_ATTR = "energy_direction"
41+
ENERGY_DIRECTION_ATTR_B = "energy_direction_ch_b"
42+
FORWARD = 0
43+
REVERSE = 1
44+
45+
CH_A = 1
46+
CH_B = 2
47+
CH_AB = 11
48+
49+
UNSIGNED_ATTR_SUFFIX = "_attr_unsigned"
50+
51+
CURRENT = 5
52+
POWER = 100
53+
VOLTAGE = 230
54+
SUMM_RECEIVED = 15000
55+
DIRECTION_A = REVERSE
56+
DIRECTION_B = FORWARD
57+
58+
ep = quirked_device.endpoints[1]
59+
60+
assert ep.tuya_manufacturer is not None
61+
assert isinstance(ep.tuya_manufacturer, TuyaMCUCluster)
62+
mcu_listener = ClusterListener(ep.tuya_manufacturer)
63+
64+
listeners = {}
65+
for channel in channels:
66+
channel_ep = quirked_device.endpoints.get(channel, None)
67+
assert channel_ep is not None
68+
69+
assert channel_ep.electrical_measurement is not None
70+
assert isinstance(channel_ep.electrical_measurement, ElectricalMeasurement)
71+
72+
assert channel_ep.smartenergy_metering is not None
73+
assert isinstance(channel_ep.smartenergy_metering, Metering)
74+
75+
listeners[channel] = {
76+
"metering": ClusterListener(channel_ep.smartenergy_metering),
77+
"electrical_measurement": ClusterListener(
78+
channel_ep.electrical_measurement
79+
),
80+
}
81+
82+
if bidirectional:
83+
# verify the direction attribute is present
84+
attr = getattr(ep.tuya_manufacturer.AttributeDefs, ENERGY_DIRECTION_ATTR, None)
85+
assert attr is not None
86+
87+
# set the initial direction
88+
ep.tuya_manufacturer.update_attribute(ENERGY_DIRECTION_ATTR, DIRECTION_A)
89+
assert len(mcu_listener.attribute_updates) == 1
90+
assert mcu_listener.attribute_updates[0][0] == attr.id
91+
assert mcu_listener.attribute_updates[0][1] == DIRECTION_A
92+
else:
93+
# verify the direction & direction B attributes are not present
94+
attr = getattr(ep.tuya_manufacturer.AttributeDefs, ENERGY_DIRECTION_ATTR, None)
95+
assert attr is None
96+
attr = getattr(
97+
ep.tuya_manufacturer.AttributeDefs,
98+
ENERGY_DIRECTION_ATTR_B,
99+
None,
100+
)
101+
assert attr is None
102+
103+
if bidirectional and CH_B in channels:
104+
# verify the direction B attribute is present
105+
attr = getattr(
106+
ep.tuya_manufacturer.AttributeDefs,
107+
ENERGY_DIRECTION_ATTR_B,
108+
None,
109+
)
110+
assert attr is not None
111+
112+
# set the initial direction
113+
ep.tuya_manufacturer.update_attribute(ENERGY_DIRECTION_ATTR_B, DIRECTION_B)
114+
assert len(mcu_listener.attribute_updates) == 2
115+
assert mcu_listener.attribute_updates[1][0] == attr.id
116+
assert mcu_listener.attribute_updates[1][1] == DIRECTION_B
117+
118+
if CH_AB in channels:
119+
# verify the config cluster is present
120+
channel_ep = quirked_device.endpoints[1]
121+
assert channel_ep.energy_meter_config is not None
122+
assert isinstance(channel_ep.energy_meter_config, LocalDataCluster)
123+
124+
config_listener = ClusterListener(ep.energy_meter_config)
125+
126+
# set the initial virtual channel calculation method (sum A and B)
127+
channel_ep.energy_meter_config.update_attribute(
128+
channel_ep.energy_meter_config.AttributeDefs.virtual_channel_config.id,
129+
channel_ep.energy_meter_config.VirtualChannelConfig.A_plus_B,
130+
)
131+
assert len(config_listener.attribute_updates) == 1
132+
assert (
133+
config_listener.attribute_updates[0][0]
134+
== channel_ep.energy_meter_config.AttributeDefs.virtual_channel_config.id
135+
)
136+
assert (
137+
config_listener.attribute_updates[0][1]
138+
== channel_ep.energy_meter_config.VirtualChannelConfig.A_plus_B
139+
)
140+
141+
for channel in channels:
142+
if channel == CH_A:
143+
direction = DIRECTION_A
144+
elif channel == CH_B:
145+
direction = DIRECTION_B
146+
elif channel == CH_AB:
147+
# updates to channel AB will occur as a result of the device updates to channels A & B
148+
continue
149+
assert direction is not None
150+
151+
channel_ep = quirked_device.endpoints[channel]
152+
153+
# update ElectricalMeasurement attributes
154+
channel_ep.electrical_measurement.update_attribute(
155+
ElectricalMeasurement.AttributeDefs.rms_current.name, CURRENT
156+
)
157+
channel_ep.electrical_measurement.update_attribute(
158+
ElectricalMeasurement.AttributeDefs.rms_voltage.name, VOLTAGE
159+
)
160+
channel_ep.electrical_measurement.update_attribute(
161+
# The UNSIGNED_ATTR_SUFFIX applies energy direction on bidirectional devices
162+
ElectricalMeasurement.AttributeDefs.active_power.name
163+
+ UNSIGNED_ATTR_SUFFIX,
164+
POWER,
165+
)
166+
167+
# verify the ElectricalMeasurement attributes were updated correctly
168+
assert len(listeners[channel]["electrical_measurement"].attribute_updates) == 4
169+
assert (
170+
listeners[channel]["electrical_measurement"].attribute_updates[0][0]
171+
== ElectricalMeasurement.AttributeDefs.rms_current.id
172+
)
173+
assert (
174+
listeners[channel]["electrical_measurement"].attribute_updates[0][1]
175+
== CURRENT
176+
)
177+
assert (
178+
listeners[channel]["electrical_measurement"].attribute_updates[1][0]
179+
== ElectricalMeasurement.AttributeDefs.rms_voltage.id
180+
)
181+
assert (
182+
listeners[channel]["electrical_measurement"].attribute_updates[1][1]
183+
== VOLTAGE
184+
)
185+
assert (
186+
listeners[channel]["electrical_measurement"].attribute_updates[2][0]
187+
== ElectricalMeasurement.AttributeDefs.active_power.id
188+
)
189+
assert (
190+
listeners[channel]["electrical_measurement"].attribute_updates[2][1]
191+
== POWER
192+
if not bidirectional or direction == FORWARD
193+
else -POWER
194+
)
195+
assert (
196+
listeners[channel]["electrical_measurement"].attribute_updates[3][0]
197+
== ElectricalMeasurement.AttributeDefs.measurement_type.id
198+
)
199+
assert (
200+
listeners[channel]["electrical_measurement"].attribute_updates[3][1]
201+
== ElectricalMeasurement.MeasurementType.Active_measurement_AC
202+
| ElectricalMeasurement.MeasurementType.Phase_A_measurement # updated by the _update_measurement_type function
203+
)
204+
205+
# update Metering attributes
206+
channel_ep.smartenergy_metering.update_attribute(
207+
Metering.AttributeDefs.instantaneous_demand.name + UNSIGNED_ATTR_SUFFIX,
208+
POWER,
209+
)
210+
channel_ep.smartenergy_metering.update_attribute(
211+
# The UNSIGNED_ATTR_SUFFIX applies energy direction on bidirectional devices
212+
Metering.AttributeDefs.current_summ_received.name,
213+
SUMM_RECEIVED,
214+
)
215+
216+
# verify the Metering attributes were updated correctly
217+
assert len(listeners[channel]["metering"].attribute_updates) == 2
218+
assert (
219+
listeners[channel]["metering"].attribute_updates[0][0]
220+
== Metering.AttributeDefs.instantaneous_demand.id
221+
)
222+
assert (
223+
listeners[channel]["metering"].attribute_updates[0][1] == POWER
224+
if not bidirectional or direction == FORWARD
225+
else -POWER
226+
)
227+
assert (
228+
listeners[channel]["metering"].attribute_updates[1][0]
229+
== Metering.AttributeDefs.current_summ_received.id
230+
)
231+
assert listeners[channel]["metering"].attribute_updates[1][1] == SUMM_RECEIVED
232+
233+
if CH_AB in channels:
234+
# verify the ElectricalMeasurement attributes were updated correctly
235+
assert len(listeners[CH_AB]["electrical_measurement"].attribute_updates) == 3
236+
assert (
237+
listeners[CH_AB]["electrical_measurement"].attribute_updates[0][0]
238+
== ElectricalMeasurement.AttributeDefs.rms_current.name
239+
)
240+
assert (
241+
listeners[CH_AB]["electrical_measurement"].attribute_updates[0][1]
242+
== -CURRENT + CURRENT # -CURRENT + CURRENT = 0
243+
)
244+
assert (
245+
listeners[CH_AB]["electrical_measurement"].attribute_updates[1][0]
246+
== ElectricalMeasurement.AttributeDefs.active_power.name
247+
)
248+
assert (
249+
listeners[CH_AB]["electrical_measurement"].attribute_updates[1][1] == 0
250+
) # -POWER + POWER = 0
251+
assert (
252+
listeners[CH_AB]["electrical_measurement"].attribute_updates[2][0]
253+
== ElectricalMeasurement.AttributeDefs.measurement_type.name
254+
)
255+
assert (
256+
listeners[CH_AB]["electrical_measurement"].attribute_updates[2][1]
257+
== ElectricalMeasurement.MeasurementType.Active_measurement_AC
258+
| ElectricalMeasurement.MeasurementType.Phase_A_measurement # updated by the _update_measurement_type function
259+
)
260+
261+
# verify the Metering attributes were updated correctly
262+
assert len(listeners[CH_AB]["metering"].attribute_updates) == 2
263+
assert (
264+
listeners[CH_AB]["metering"].attribute_updates[0][0]
265+
== Metering.AttributeDefs.instantaneous_demand.name
266+
)
267+
assert (
268+
listeners[CH_AB]["metering"].attribute_updates[0][1] == 0
269+
) # -POWER + POWER = 0
270+
assert (
271+
listeners[CH_AB]["metering"].attribute_updates[1][0]
272+
== Metering.AttributeDefs.current_summ_received.name
273+
)
274+
assert listeners[CH_AB]["metering"].attribute_updates[1][1] == SUMM_RECEIVED

0 commit comments

Comments
 (0)