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