Skip to content

Commit 9838136

Browse files
committed
Test for energy direction delay mitigation
Test fixes
1 parent 1378661 commit 9838136

File tree

2 files changed

+185
-28
lines changed

2 files changed

+185
-28
lines changed

tests/test_tuya_energy_meter.py

Lines changed: 185 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,205 @@ 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+
ENABLED = 2
313+
314+
ep = quirked_device.endpoints[1]
315+
316+
# verify the config cluster is present
317+
assert ep.energy_meter_config is not None
318+
assert isinstance(ep.energy_meter_config, LocalDataCluster)
319+
320+
# set the mitigation config value
321+
config_listener = ClusterListener(ep.energy_meter_config)
322+
ep.energy_meter_config.update_attribute(
323+
ep.energy_meter_config.AttributeDefs.energy_direction_mitigation.id,
324+
mitigation_config,
325+
)
326+
assert len(config_listener.attribute_updates) == 1
327+
assert (
328+
config_listener.attribute_updates[0][0]
329+
== ep.energy_meter_config.AttributeDefs.energy_direction_mitigation.id
330+
)
331+
assert config_listener.attribute_updates[0][1] == mitigation_config
332+
333+
if basic_cluster_match:
334+
# verify the basic cluster is present
335+
assert ep.basic is not None
336+
assert isinstance(ep.basic, Basic)
337+
338+
# populate match details for automatic mitigation
339+
basic_listener = ClusterListener(ep.basic)
340+
ep.basic.update_attribute(
341+
Basic.AttributeDefs.app_version.id,
342+
basic_cluster_match["app_version"],
343+
)
344+
ep.basic.update_attribute(
345+
Basic.AttributeDefs.hw_version.id,
346+
basic_cluster_match["hw_version"],
347+
)
348+
ep.basic.update_attribute(
349+
Basic.AttributeDefs.stack_version.id,
350+
basic_cluster_match["stack_version"],
351+
)
352+
assert len(basic_listener.attribute_updates) == 3
353+
assert (
354+
basic_listener.attribute_updates[0][0] == Basic.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] == Basic.AttributeDefs.hw_version.id
361+
)
362+
assert (
363+
basic_listener.attribute_updates[1][1] == basic_cluster_match["hw_version"]
364+
)
365+
assert (
366+
basic_listener.attribute_updates[2][0]
367+
== Basic.AttributeDefs.stack_version.id
368+
)
369+
assert (
370+
basic_listener.attribute_updates[2][1]
371+
== basic_cluster_match["stack_version"]
372+
)
373+
374+
# verify the reporting cluster is present
375+
assert ep.smartenergy_metering is not None
376+
assert isinstance(ep.smartenergy_metering, Metering)
377+
378+
# update the reporting cluster
379+
metering_listener = ClusterListener(ep.smartenergy_metering)
380+
ep.smartenergy_metering.update_attribute(
381+
Metering.AttributeDefs.instantaneous_demand.name + UNSIGNED_ATTR_SUFFIX,
382+
POWER_1,
383+
)
384+
ep.smartenergy_metering.update_attribute(
385+
Metering.AttributeDefs.instantaneous_demand.name + UNSIGNED_ATTR_SUFFIX,
386+
POWER_2,
387+
)
388+
ep.smartenergy_metering.update_attribute(
389+
Metering.AttributeDefs.instantaneous_demand.name + UNSIGNED_ATTR_SUFFIX,
390+
POWER_3,
391+
)
392+
393+
# cluster values are delayed until their next update when the mitigation is active
394+
assert (
395+
len(metering_listener.attribute_updates) == 3
396+
if mitigation_config == DISABLED
397+
or mitigation_config == AUTOMATIC
398+
and not basic_cluster_match
399+
else 2
400+
)
401+
402+
assert (
403+
metering_listener.attribute_updates[0][0]
404+
== Metering.AttributeDefs.instantaneous_demand.id
405+
)
406+
assert (
407+
metering_listener.attribute_updates[0][1] == POWER_1
408+
if mitigation_config == DISABLED
409+
or mitigation_config == AUTOMATIC
410+
and not basic_cluster_match
411+
else None
412+
)
413+
414+
assert (
415+
metering_listener.attribute_updates[1][0]
416+
== Metering.AttributeDefs.instantaneous_demand.id
417+
)
418+
assert (
419+
metering_listener.attribute_updates[1][1] == POWER_2
420+
if mitigation_config == DISABLED
421+
or mitigation_config == AUTOMATIC
422+
and not basic_cluster_match
423+
else POWER_1
424+
)
425+
426+
if (
427+
mitigation_config == ENABLED
428+
or mitigation_config == AUTOMATIC
429+
and basic_cluster_match
430+
):
431+
assert (
432+
metering_listener.attribute_updates[2][0]
433+
== Metering.AttributeDefs.instantaneous_demand.id
434+
)
435+
assert metering_listener.attribute_updates[2][1] == POWER_2

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)