Skip to content

Commit 119c33f

Browse files
committed
Add test for energy direction delay mitigation
1 parent e2b76b6 commit 119c33f

File tree

2 files changed

+178
-28
lines changed

2 files changed

+178
-28
lines changed

tests/test_tuya_energy_meter.py

Lines changed: 178 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
"""Tests for Tuya quirks."""
22

33
import pytest
4+
from zigpy.zcl.clusters.general import Basic
45
from zigpy.zcl.clusters.homeautomation import ElectricalMeasurement
56
from zigpy.zcl.clusters.smartenergy import Metering
67

@@ -42,9 +43,9 @@ async def test_tuya_energy_meter_quirk_energy_direction_align(
4243
FORWARD = 0
4344
REVERSE = 1
4445

45-
CH_A = 1
46-
CH_B = 2
47-
CH_AB = 11
46+
CHANNEL_A = 1
47+
CHANNEL_B = 2
48+
CHANNEL_AB = 11
4849

4950
UNSIGNED_ATTR_SUFFIX = "_attr_unsigned"
5051

@@ -100,7 +101,7 @@ async def test_tuya_energy_meter_quirk_energy_direction_align(
100101
)
101102
assert attr is None
102103

103-
if bidirectional and CH_B in channels:
104+
if bidirectional and CHANNEL_B in channels:
104105
# verify the direction B attribute is present
105106
attr = getattr(
106107
ep.tuya_manufacturer.AttributeDefs,
@@ -115,7 +116,7 @@ async def test_tuya_energy_meter_quirk_energy_direction_align(
115116
assert mcu_listener.attribute_updates[1][0] == attr.id
116117
assert mcu_listener.attribute_updates[1][1] == DIRECTION_B
117118

118-
if CH_AB in channels:
119+
if CHANNEL_AB in channels:
119120
# verify the config cluster is present
120121
channel_ep = quirked_device.endpoints[1]
121122
assert channel_ep.energy_meter_config is not None
@@ -139,11 +140,11 @@ async def test_tuya_energy_meter_quirk_energy_direction_align(
139140
)
140141

141142
for channel in channels:
142-
if channel == CH_A:
143+
if channel == CHANNEL_A:
143144
direction = DIRECTION_A
144-
elif channel == CH_B:
145+
elif channel == CHANNEL_B:
145146
direction = DIRECTION_B
146-
elif channel == CH_AB:
147+
elif channel == CHANNEL_AB:
147148
# updates to channel AB will occur as a result of the device updates to channels A & B
148149
continue
149150
assert direction is not None
@@ -230,40 +231,198 @@ async def test_tuya_energy_meter_quirk_energy_direction_align(
230231
)
231232
assert listeners[channel]["metering"].attribute_updates[1][1] == SUMM_RECEIVED
232233

233-
if CH_AB in channels:
234+
if CHANNEL_AB in channels:
234235
# verify the ElectricalMeasurement attributes were updated correctly
235-
assert len(listeners[CH_AB]["electrical_measurement"].attribute_updates) == 3
236236
assert (
237-
listeners[CH_AB]["electrical_measurement"].attribute_updates[0][0]
237+
len(listeners[CHANNEL_AB]["electrical_measurement"].attribute_updates) == 3
238+
)
239+
assert (
240+
listeners[CHANNEL_AB]["electrical_measurement"].attribute_updates[0][0]
238241
== ElectricalMeasurement.AttributeDefs.rms_current.id
239242
)
240243
assert (
241-
listeners[CH_AB]["electrical_measurement"].attribute_updates[0][1]
244+
listeners[CHANNEL_AB]["electrical_measurement"].attribute_updates[0][1]
242245
== -CURRENT + CURRENT # -CURRENT + CURRENT = 0
243246
)
244247
assert (
245-
listeners[CH_AB]["electrical_measurement"].attribute_updates[1][0]
248+
listeners[CHANNEL_AB]["electrical_measurement"].attribute_updates[1][0]
246249
== ElectricalMeasurement.AttributeDefs.active_power.id
247250
)
248251
assert (
249-
listeners[CH_AB]["electrical_measurement"].attribute_updates[1][1] == 0
252+
listeners[CHANNEL_AB]["electrical_measurement"].attribute_updates[1][1] == 0
250253
) # -POWER + POWER = 0
251254
assert (
252-
listeners[CH_AB]["electrical_measurement"].attribute_updates[2][0]
255+
listeners[CHANNEL_AB]["electrical_measurement"].attribute_updates[2][0]
253256
== ElectricalMeasurement.AttributeDefs.measurement_type.id
254257
)
255258
assert (
256-
listeners[CH_AB]["electrical_measurement"].attribute_updates[2][1]
259+
listeners[CHANNEL_AB]["electrical_measurement"].attribute_updates[2][1]
257260
== ElectricalMeasurement.MeasurementType.Active_measurement_AC
258261
| ElectricalMeasurement.MeasurementType.Phase_A_measurement # updated by the _update_measurement_type function
259262
)
260263

261264
# verify the Metering attributes were updated correctly
262-
assert len(listeners[CH_AB]["metering"].attribute_updates) == 1
265+
assert len(listeners[CHANNEL_AB]["metering"].attribute_updates) == 1
263266
assert (
264-
listeners[CH_AB]["metering"].attribute_updates[0][0]
267+
listeners[CHANNEL_AB]["metering"].attribute_updates[0][0]
265268
== Metering.AttributeDefs.instantaneous_demand.id
266269
)
267270
assert (
268-
listeners[CH_AB]["metering"].attribute_updates[0][1] == 0
271+
listeners[CHANNEL_AB]["metering"].attribute_updates[0][1] == 0
269272
) # -POWER + POWER = 0
273+
274+
275+
@pytest.mark.parametrize(
276+
"model,manuf,mitigation_config,basic_cluster_match",
277+
[
278+
("_TZE204_cjbofhxw", "TS0601", 0, None), # Automatic
279+
("_TZE204_ac0fhfiq", "TS0601", 0, None), # Automatic
280+
("_TZE200_rks0sgb7", "TS0601", 1, None), # Disabled
281+
("_TZE204_81yrt3lo", "TS0601", 2, None), # Enabled
282+
(
283+
"_TZE204_81yrt3lo",
284+
"TS0601",
285+
0, # Automatic
286+
{
287+
"app_version": 74,
288+
"hw_version": 1,
289+
"stack_version": 0,
290+
},
291+
),
292+
],
293+
)
294+
async def test_tuya_energy_meter_quirk_energy_direction_delay_mitigation(
295+
zigpy_device_from_v2_quirk,
296+
model: str,
297+
manuf: str,
298+
mitigation_config: None | int,
299+
basic_cluster_match: dict,
300+
):
301+
"""Test Tuya Energy Meter Quirk energy direction report mitigation."""
302+
quirked_device = zigpy_device_from_v2_quirk(model, manuf)
303+
304+
UNSIGNED_ATTR_SUFFIX = "_attr_unsigned"
305+
306+
POWER_1 = 100
307+
POWER_2 = 200
308+
POWER_3 = 300
309+
310+
AUTOMATIC = 0
311+
DISABLED = 1
312+
313+
ep = quirked_device.endpoints[1]
314+
315+
# verify the config cluster is present
316+
assert ep.energy_meter_config is not None
317+
assert isinstance(ep.energy_meter_config, LocalDataCluster)
318+
319+
# set the mitigation config value
320+
config_listener = ClusterListener(ep.energy_meter_config)
321+
ep.energy_meter_config.update_attribute(
322+
ep.energy_meter_config.AttributeDefs.energy_direction_mitigation.id,
323+
mitigation_config,
324+
)
325+
assert len(config_listener.attribute_updates) == 1
326+
assert (
327+
config_listener.attribute_updates[0][0]
328+
== ep.energy_meter_config.AttributeDefs.energy_direction_mitigation.id
329+
)
330+
assert config_listener.attribute_updates[0][1] == mitigation_config
331+
332+
if basic_cluster_match:
333+
# verify the basic cluster is present
334+
assert ep.basic is not None
335+
assert isinstance(ep.basic, Basic)
336+
337+
# populate match details for automatic mitigation
338+
basic_listener = ClusterListener(ep.basic)
339+
ep.basic.update_attribute(
340+
Basic.AttributeDefs.app_version.id,
341+
basic_cluster_match["app_version"],
342+
)
343+
ep.basic.update_attribute(
344+
Basic.AttributeDefs.hw_version.id,
345+
basic_cluster_match["hw_version"],
346+
)
347+
ep.basic.update_attribute(
348+
Basic.AttributeDefs.stack_version.id,
349+
basic_cluster_match["stack_version"],
350+
)
351+
assert len(basic_listener.attribute_updates) == 3
352+
assert (
353+
basic_listener.attribute_updates[0][0]
354+
== ep.energy_meter_config.AttributeDefs.app_version.id
355+
)
356+
assert (
357+
basic_listener.attribute_updates[0][1] == basic_cluster_match["app_version"]
358+
)
359+
assert (
360+
basic_listener.attribute_updates[1][0]
361+
== ep.energy_meter_config.AttributeDefs.hw_version.id
362+
)
363+
assert (
364+
basic_listener.attribute_updates[1][1] == basic_cluster_match["hw_version"]
365+
)
366+
assert (
367+
basic_listener.attribute_updates[2][0]
368+
== ep.energy_meter_config.AttributeDefs.stack_version.id
369+
)
370+
assert (
371+
basic_listener.attribute_updates[2][1]
372+
== basic_cluster_match["stack_version"]
373+
)
374+
375+
# verify the reporting cluster is present
376+
assert ep.smartenergy_metering is not None
377+
assert isinstance(ep.smartenergy_metering, Metering)
378+
379+
# update the reporting cluster
380+
metering_listener = ClusterListener(ep.smartenergy_metering)
381+
ep.smartenergy_metering.update_attribute(
382+
Metering.AttributeDefs.instantaneous_demand.name + UNSIGNED_ATTR_SUFFIX,
383+
POWER_1,
384+
)
385+
ep.smartenergy_metering.update_attribute(
386+
Metering.AttributeDefs.instantaneous_demand.name + UNSIGNED_ATTR_SUFFIX,
387+
POWER_2,
388+
)
389+
ep.smartenergy_metering.update_attribute(
390+
Metering.AttributeDefs.instantaneous_demand.name + UNSIGNED_ATTR_SUFFIX,
391+
POWER_3,
392+
)
393+
394+
# cluster values are delayed until their next update when the mitigation is active
395+
assert len(metering_listener.attribute_updates) == 3
396+
assert (
397+
metering_listener.attribute_updates[0][0]
398+
== Metering.AttributeDefs.instantaneous_demand.active_power.id
399+
)
400+
assert (
401+
metering_listener.attribute_updates[0][1] == POWER_1
402+
if mitigation_config == DISABLED
403+
or mitigation_config == AUTOMATIC
404+
and not basic_cluster_match
405+
else None
406+
)
407+
assert (
408+
metering_listener.attribute_updates[1][0]
409+
== Metering.AttributeDefs.instantaneous_demand.active_power.id
410+
)
411+
assert (
412+
metering_listener.attribute_updates[1][1] == POWER_2
413+
if mitigation_config == DISABLED
414+
or mitigation_config == AUTOMATIC
415+
and not basic_cluster_match
416+
else POWER_1
417+
)
418+
assert (
419+
metering_listener.attribute_updates[2][0]
420+
== Metering.AttributeDefs.instantaneous_demand.active_power.id
421+
)
422+
assert (
423+
metering_listener.attribute_updates[2][1] == POWER_3
424+
if mitigation_config == DISABLED
425+
or mitigation_config == AUTOMATIC
426+
and not basic_cluster_match
427+
else POWER_2
428+
)

zhaquirks/tuya/ts0601_energy_meter.py

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -220,15 +220,6 @@ def energy_direction(self) -> TuyaEnergyDirection | None:
220220
except KeyError:
221221
return None
222222

223-
@energy_direction.setter
224-
def energy_direction(self, value: TuyaEnergyDirection):
225-
"""Update the channel energy direction."""
226-
if not self.mcu_cluster:
227-
return
228-
self.mcu_cluster.update_attribute(
229-
ENERGY_DIRECTION + Channel.attr_suffix(self.channel)
230-
)
231-
232223
def energy_direction_handler(self, attr_name: str, value) -> tuple[str, Any]:
233224
"""Unsigned attributes are aligned with energy direction."""
234225
if attr_name.endswith(self.UNSIGNED_ATTR_SUFFIX):

0 commit comments

Comments
 (0)