1
1
"""Tests for Tuya quirks."""
2
2
3
3
import pytest
4
+ from zigpy .zcl .clusters .general import Basic
4
5
from zigpy .zcl .clusters .homeautomation import ElectricalMeasurement
5
6
from zigpy .zcl .clusters .smartenergy import Metering
6
7
@@ -42,9 +43,9 @@ async def test_tuya_energy_meter_quirk_energy_direction_align(
42
43
FORWARD = 0
43
44
REVERSE = 1
44
45
45
- CH_A = 1
46
- CH_B = 2
47
- CH_AB = 11
46
+ CHANNEL_A = 1
47
+ CHANNEL_B = 2
48
+ CHANNEL_AB = 11
48
49
49
50
UNSIGNED_ATTR_SUFFIX = "_attr_unsigned"
50
51
@@ -100,7 +101,7 @@ async def test_tuya_energy_meter_quirk_energy_direction_align(
100
101
)
101
102
assert attr is None
102
103
103
- if bidirectional and CH_B in channels :
104
+ if bidirectional and CHANNEL_B in channels :
104
105
# verify the direction B attribute is present
105
106
attr = getattr (
106
107
ep .tuya_manufacturer .AttributeDefs ,
@@ -115,7 +116,7 @@ async def test_tuya_energy_meter_quirk_energy_direction_align(
115
116
assert mcu_listener .attribute_updates [1 ][0 ] == attr .id
116
117
assert mcu_listener .attribute_updates [1 ][1 ] == DIRECTION_B
117
118
118
- if CH_AB in channels :
119
+ if CHANNEL_AB in channels :
119
120
# verify the config cluster is present
120
121
channel_ep = quirked_device .endpoints [1 ]
121
122
assert channel_ep .energy_meter_config is not None
@@ -139,11 +140,11 @@ async def test_tuya_energy_meter_quirk_energy_direction_align(
139
140
)
140
141
141
142
for channel in channels :
142
- if channel == CH_A :
143
+ if channel == CHANNEL_A :
143
144
direction = DIRECTION_A
144
- elif channel == CH_B :
145
+ elif channel == CHANNEL_B :
145
146
direction = DIRECTION_B
146
- elif channel == CH_AB :
147
+ elif channel == CHANNEL_AB :
147
148
# updates to channel AB will occur as a result of the device updates to channels A & B
148
149
continue
149
150
assert direction is not None
@@ -230,40 +231,198 @@ async def test_tuya_energy_meter_quirk_energy_direction_align(
230
231
)
231
232
assert listeners [channel ]["metering" ].attribute_updates [1 ][1 ] == SUMM_RECEIVED
232
233
233
- if CH_AB in channels :
234
+ if CHANNEL_AB in channels :
234
235
# verify the ElectricalMeasurement attributes were updated correctly
235
- assert len (listeners [CH_AB ]["electrical_measurement" ].attribute_updates ) == 3
236
236
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 ]
238
241
== ElectricalMeasurement .AttributeDefs .rms_current .id
239
242
)
240
243
assert (
241
- listeners [CH_AB ]["electrical_measurement" ].attribute_updates [0 ][1 ]
244
+ listeners [CHANNEL_AB ]["electrical_measurement" ].attribute_updates [0 ][1 ]
242
245
== - CURRENT + CURRENT # -CURRENT + CURRENT = 0
243
246
)
244
247
assert (
245
- listeners [CH_AB ]["electrical_measurement" ].attribute_updates [1 ][0 ]
248
+ listeners [CHANNEL_AB ]["electrical_measurement" ].attribute_updates [1 ][0 ]
246
249
== ElectricalMeasurement .AttributeDefs .active_power .id
247
250
)
248
251
assert (
249
- listeners [CH_AB ]["electrical_measurement" ].attribute_updates [1 ][1 ] == 0
252
+ listeners [CHANNEL_AB ]["electrical_measurement" ].attribute_updates [1 ][1 ] == 0
250
253
) # -POWER + POWER = 0
251
254
assert (
252
- listeners [CH_AB ]["electrical_measurement" ].attribute_updates [2 ][0 ]
255
+ listeners [CHANNEL_AB ]["electrical_measurement" ].attribute_updates [2 ][0 ]
253
256
== ElectricalMeasurement .AttributeDefs .measurement_type .id
254
257
)
255
258
assert (
256
- listeners [CH_AB ]["electrical_measurement" ].attribute_updates [2 ][1 ]
259
+ listeners [CHANNEL_AB ]["electrical_measurement" ].attribute_updates [2 ][1 ]
257
260
== ElectricalMeasurement .MeasurementType .Active_measurement_AC
258
261
| ElectricalMeasurement .MeasurementType .Phase_A_measurement # updated by the _update_measurement_type function
259
262
)
260
263
261
264
# 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
263
266
assert (
264
- listeners [CH_AB ]["metering" ].attribute_updates [0 ][0 ]
267
+ listeners [CHANNEL_AB ]["metering" ].attribute_updates [0 ][0 ]
265
268
== Metering .AttributeDefs .instantaneous_demand .id
266
269
)
267
270
assert (
268
- listeners [CH_AB ]["metering" ].attribute_updates [0 ][1 ] == 0
271
+ listeners [CHANNEL_AB ]["metering" ].attribute_updates [0 ][1 ] == 0
269
272
) # -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
+ )
0 commit comments