Skip to content

Commit bc5e507

Browse files
author
Marco Berghoff
committed
Copy code from #2109
1 parent aeca76d commit bc5e507

File tree

1 file changed

+173
-1
lines changed

1 file changed

+173
-1
lines changed

zhaquirks/xiaomi/aqara/thermostat_agl001.py

+173-1
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
"""Aqara E1 Radiator Thermostat Quirk."""
22
from __future__ import annotations
33

4-
from functools import reduce
54
import math
65
import struct
6+
import time
77
from typing import Any
88

99
from zigpy.profiles import zha
@@ -48,6 +48,10 @@
4848
SENSOR = 0x027E
4949
BATTERY_PERCENTAGE = 0x040A
5050

51+
SENSOR_TEMP = 0x1392 # Fake address to pass external sensor temperature
52+
SENSOR_ATTR = 0xFFF2
53+
SENSOR_ATTR_NAME = "sensor_attr"
54+
5155
XIAOMI_CLUSTER_ID = 0xFCC0
5256

5357
DAYS_MAP = {
@@ -379,6 +383,8 @@ class AqaraThermostatSpecificCluster(XiaomiAqaraE1Cluster):
379383
SCHEDULE_SETTINGS: ("schedule_settings", ScheduleSettings, True),
380384
SENSOR: ("sensor", t.uint8_t, True),
381385
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),
382388
}
383389
)
384390

@@ -393,6 +399,172 @@ def _update_attribute(self, attrid, value):
393399
)
394400
super()._update_attribute(attrid, value)
395401

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+
396568

397569
class AGL001(XiaomiCustomDevice):
398570
"""Aqara E1 Radiator Thermostat (AGL001) Device."""

0 commit comments

Comments
 (0)