1
1
"""Aqara E1 Radiator Thermostat Quirk."""
2
2
from __future__ import annotations
3
3
4
- from functools import reduce
5
4
import math
6
5
import struct
6
+ import time
7
7
from typing import Any
8
8
9
9
from zigpy .profiles import zha
48
48
SENSOR = 0x027E
49
49
BATTERY_PERCENTAGE = 0x040A
50
50
51
+ SENSOR_TEMP = 0x1392 # Fake address to pass external sensor temperature
52
+ SENSOR_ATTR = 0xFFF2
53
+ SENSOR_ATTR_NAME = "sensor_attr"
54
+
51
55
XIAOMI_CLUSTER_ID = 0xFCC0
52
56
53
57
DAYS_MAP = {
@@ -379,6 +383,8 @@ class AqaraThermostatSpecificCluster(XiaomiAqaraE1Cluster):
379
383
SCHEDULE_SETTINGS : ("schedule_settings" , ScheduleSettings , True ),
380
384
SENSOR : ("sensor" , t .uint8_t , True ),
381
385
BATTERY_PERCENTAGE : ("battery_percentage" , t .uint8_t , True ),
386
+ SENSOR_TEMP : ("sensor_temp" , t .uint32_t , True ),
387
+ SENSOR_ATTR : (SENSOR_ATTR_NAME , t .LVBytes , True ),
382
388
}
383
389
)
384
390
@@ -393,6 +399,172 @@ def _update_attribute(self, attrid, value):
393
399
)
394
400
super ()._update_attribute (attrid , value )
395
401
402
+ def aqaraHeader (
403
+ self , counter : int , params : bytearray , action : int
404
+ ) -> bytearray :
405
+ """Create Aqara header for setting external sensor."""
406
+ header = bytes ([0xAA , 0x71 , len (params ) + 3 , 0x44 , counter ])
407
+ integrity = 512 - sum (header )
408
+
409
+ return header + bytes ([integrity , action , 0x41 , len (params )])
410
+
411
+ def _float_to_hex (self , f ):
412
+ """Convert float to hex."""
413
+ return hex (struct .unpack ("<I" , struct .pack ("<f" , f ))[0 ])
414
+
415
+ async def write_attributes (
416
+ self , attributes : dict [str | int , Any ], manufacturer : int | None = None
417
+ ) -> list :
418
+ """Write attributes to device with internal 'attributes' validation."""
419
+ sensor = bytearray .fromhex ("00158d00019d1b98" )
420
+ attrs = {}
421
+
422
+ for attr , value in attributes .items ():
423
+ # implemented with help from https://github.com/Koenkk/zigbee-herdsman-converters/blob/master/devices/xiaomi.js
424
+
425
+ if attr == SENSOR_TEMP :
426
+ # set external sensor temp. this function expect value to be passed multiplied by 100
427
+ temperatureBuf = bytearray .fromhex (
428
+ self ._float_to_hex (round (float (value )))[2 :]
429
+ )
430
+
431
+ params = sensor
432
+ params += bytes ([0x00 , 0x01 , 0x00 , 0x55 ])
433
+ params += temperatureBuf
434
+
435
+ attrs = {}
436
+ attrs [SENSOR_ATTR_NAME ] = (
437
+ self .aqaraHeader (0x12 , params , 0x05 ) + params
438
+ )
439
+
440
+ elif attr == SENSOR :
441
+ # set internal/external temperature sensor
442
+ device = bytearray .fromhex (
443
+ ("%s" % (self .endpoint .device .ieee )).replace (":" , "" )
444
+ )
445
+ timestamp = bytes (
446
+ reversed (t .uint32_t (int (time .time ())).serialize ())
447
+ )
448
+
449
+ if value == 0 :
450
+ # internal sensor
451
+ params1 = timestamp
452
+ params1 += bytes ([0x3D , 0x05 ])
453
+ params1 += device
454
+ params1 += bytes (
455
+ [
456
+ 0x00 ,
457
+ 0x00 ,
458
+ 0x00 ,
459
+ 0x00 ,
460
+ 0x00 ,
461
+ 0x00 ,
462
+ 0x00 ,
463
+ 0x00 ,
464
+ 0x00 ,
465
+ 0x00 ,
466
+ 0x00 ,
467
+ 0x00 ,
468
+ ]
469
+ )
470
+
471
+ params2 = timestamp
472
+ params2 += bytes ([0x3D , 0x04 ])
473
+ params2 += device
474
+ params2 += bytes (
475
+ [
476
+ 0x00 ,
477
+ 0x00 ,
478
+ 0x00 ,
479
+ 0x00 ,
480
+ 0x00 ,
481
+ 0x00 ,
482
+ 0x00 ,
483
+ 0x00 ,
484
+ 0x00 ,
485
+ 0x00 ,
486
+ 0x00 ,
487
+ 0x00 ,
488
+ ]
489
+ )
490
+
491
+ attrs1 = {}
492
+ attrs1 [SENSOR_ATTR_NAME ] = (
493
+ self .aqaraHeader (0x12 , params1 , 0x04 ) + params1
494
+ )
495
+ attrs [SENSOR_ATTR_NAME ] = (
496
+ self .aqaraHeader (0x13 , params2 , 0x04 ) + params2
497
+ )
498
+
499
+ result = await super ().write_attributes (attrs1 , manufacturer )
500
+ else :
501
+ # external sensor
502
+ params1 = timestamp
503
+ params1 += bytes ([0x3D , 0x04 ])
504
+ params1 += device
505
+ params1 += sensor
506
+ params1 += bytes ([0x00 , 0x01 , 0x00 , 0x55 ])
507
+ params1 += bytes (
508
+ [
509
+ 0x13 ,
510
+ 0x0A ,
511
+ 0x02 ,
512
+ 0x00 ,
513
+ 0x00 ,
514
+ 0x64 ,
515
+ 0x04 ,
516
+ 0xCE ,
517
+ 0xC2 ,
518
+ 0xB6 ,
519
+ 0xC8 ,
520
+ ]
521
+ )
522
+ params1 += bytes ([0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x01 , 0x3D ])
523
+ params1 += bytes ([0x64 ])
524
+ params1 += bytes ([0x65 ])
525
+
526
+ params2 = timestamp
527
+ params2 += bytes ([0x3D , 0x05 ])
528
+ params2 += device
529
+ params2 += sensor
530
+ params2 += bytes ([0x08 , 0x00 , 0x07 , 0xFD ])
531
+ params2 += bytes (
532
+ [
533
+ 0x16 ,
534
+ 0x0A ,
535
+ 0x02 ,
536
+ 0x0A ,
537
+ 0xC9 ,
538
+ 0xE8 ,
539
+ 0xB1 ,
540
+ 0xB8 ,
541
+ 0xD4 ,
542
+ 0xDA ,
543
+ 0xCF ,
544
+ 0xDF ,
545
+ 0xC0 ,
546
+ 0xEB ,
547
+ ]
548
+ )
549
+ params2 += bytes ([0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x01 , 0x3D ])
550
+ params2 += bytes ([0x04 ])
551
+ params2 += bytes ([0x65 ])
552
+
553
+ attrs1 = {}
554
+ attrs1 [SENSOR_ATTR_NAME ] = (
555
+ self .aqaraHeader (0x12 , params1 , 0x02 ) + params1
556
+ )
557
+ attrs [SENSOR_ATTR_NAME ] = (
558
+ self .aqaraHeader (0x13 , params2 , 0x02 ) + params2
559
+ )
560
+
561
+ result = await super ().write_attributes (attrs1 , manufacturer )
562
+ else :
563
+ attrs [attr ] = value
564
+
565
+ result = await super ().write_attributes (attrs , manufacturer )
566
+ return result
567
+
396
568
397
569
class AGL001 (XiaomiCustomDevice ):
398
570
"""Aqara E1 Radiator Thermostat (AGL001) Device."""
0 commit comments