From 2d832bdd9fa758bd549678b7a26a09c3b02d0da2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20RAMAGE?= Date: Tue, 15 Jan 2019 16:01:35 +0100 Subject: [PATCH 01/34] Next release --- zigate/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zigate/version.py b/zigate/version.py index 39416d4b..845896a9 100644 --- a/zigate/version.py +++ b/zigate/version.py @@ -5,4 +5,4 @@ # file that was distributed with this source code. # -__version__ = '0.25.1' +__version__ = '0.26.0.dev0' From 9e27a2fcdb7056604d924e22e166f2983596254d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20RAMAGE?= Date: Tue, 15 Jan 2019 16:54:22 +0100 Subject: [PATCH 02/34] Basic support for cluster Thermostat 0x0201 --- zigate/clusters.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/zigate/clusters.py b/zigate/clusters.py index ed1e3027..f4641106 100644 --- a/zigate/clusters.py +++ b/zigate/clusters.py @@ -328,6 +328,17 @@ class C0101(Cluster): } +@register_cluster +class C0201(Cluster): + cluster_id = 0x0201 + type = 'Thermostat' + attributes_def = {0x0000: {'name': 'local_temperature', 'value': 'value/100.', + 'unit': '°C', 'type': float}, + 0x000b: {'name': 'heating_setpoint', 'value': 'value/100.', + 'unit': '°C', 'type': float}, + } + + @register_cluster class C0300(Cluster): cluster_id = 0x0300 From 860cd15b618197b1e887bf65aa62c2f6a8d22af2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20RAMAGE?= Date: Tue, 15 Jan 2019 17:08:46 +0100 Subject: [PATCH 03/34] start working with asyncio... --- zigate/core.py | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/zigate/core.py b/zigate/core.py index dbd867b5..f48afa27 100644 --- a/zigate/core.py +++ b/zigate/core.py @@ -33,6 +33,7 @@ from enum import Enum import colorsys import datetime +import asyncio LOGGER = logging.getLogger('zigate') @@ -161,7 +162,9 @@ def __init__(self, port='auto', path='~/.zigate.json', self._started = False self._no_response_count = 0 - self._event_thread = threading.Thread(target=self._event_loop, + loop = asyncio.new_event_loop() + loop.create_task(self._event_loop(loop)) + self._event_thread = threading.Thread(target=loop.run_forever, name='ZiGate-Event Loop') self._event_thread.setDaemon(True) self._event_thread.start() @@ -185,18 +188,19 @@ def start_adminpanel(self): from .adminpanel import start_adminpanel start_adminpanel(self) - def _event_loop(self): + async def _event_loop(self, loop): while True: if self.connection and not self.connection.received.empty(): packet = self.connection.received.get() dispatch_signal(ZIGATE_PACKET_RECEIVED, self, packet=packet) - t = threading.Thread(target=self.decode_data, args=(packet,), - name='ZiGate-Decode data') - t.setDaemon(True) - t.start() + loop.create_task(self.decode_data(packet)) +# t = threading.Thread(target=self.decode_data, args=(packet,), +# name='ZiGate-Decode data') +# t.setDaemon(True) +# t.start() # self.decode_data(packet) else: - sleep(SLEEP_INTERVAL) + asyncio.sleep(SLEEP_INTERVAL) def setup_connection(self): self.connection = ThreadSerialConnection(self, self._port) @@ -417,7 +421,7 @@ def send_data(self, cmd, data="", wait_response=None, wait_status=True): return status return False - def decode_data(self, packet): + async def decode_data(self, packet): ''' Decode raw packet message ''' From c33eb71af67af2be42674ed27447f9f4b6042269 Mon Sep 17 00:00:00 2001 From: doudz Date: Tue, 15 Jan 2019 20:10:39 +0100 Subject: [PATCH 04/34] revert last change --- zigate/core.py | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/zigate/core.py b/zigate/core.py index f48afa27..dbd867b5 100644 --- a/zigate/core.py +++ b/zigate/core.py @@ -33,7 +33,6 @@ from enum import Enum import colorsys import datetime -import asyncio LOGGER = logging.getLogger('zigate') @@ -162,9 +161,7 @@ def __init__(self, port='auto', path='~/.zigate.json', self._started = False self._no_response_count = 0 - loop = asyncio.new_event_loop() - loop.create_task(self._event_loop(loop)) - self._event_thread = threading.Thread(target=loop.run_forever, + self._event_thread = threading.Thread(target=self._event_loop, name='ZiGate-Event Loop') self._event_thread.setDaemon(True) self._event_thread.start() @@ -188,19 +185,18 @@ def start_adminpanel(self): from .adminpanel import start_adminpanel start_adminpanel(self) - async def _event_loop(self, loop): + def _event_loop(self): while True: if self.connection and not self.connection.received.empty(): packet = self.connection.received.get() dispatch_signal(ZIGATE_PACKET_RECEIVED, self, packet=packet) - loop.create_task(self.decode_data(packet)) -# t = threading.Thread(target=self.decode_data, args=(packet,), -# name='ZiGate-Decode data') -# t.setDaemon(True) -# t.start() + t = threading.Thread(target=self.decode_data, args=(packet,), + name='ZiGate-Decode data') + t.setDaemon(True) + t.start() # self.decode_data(packet) else: - asyncio.sleep(SLEEP_INTERVAL) + sleep(SLEEP_INTERVAL) def setup_connection(self): self.connection = ThreadSerialConnection(self, self._port) @@ -421,7 +417,7 @@ def send_data(self, cmd, data="", wait_response=None, wait_status=True): return status return False - async def decode_data(self, packet): + def decode_data(self, packet): ''' Decode raw packet message ''' From 6de4e5bfd5a70a0ae7bd9e204121b65dad09fb7f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20RAMAGE?= Date: Wed, 16 Jan 2019 12:15:53 +0100 Subject: [PATCH 05/34] Add remove_device_ieee --- zigate/core.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/zigate/core.py b/zigate/core.py index dbd867b5..d4789626 100644 --- a/zigate/core.py +++ b/zigate/core.py @@ -857,6 +857,12 @@ def remove_device(self, addr): data = struct.pack('!QQ', zigate_ieee, ieee) return self.send_data(0x0026, data) + def remove_device_ieee(self, ieee): + ''' remove device ''' + device = self.get_device_from_ieee(ieee) + if device: + self.remove_device(device.addr) + def enable_permissions_controlled_joins(self, enable=True): ''' Enable Permissions Controlled Joins From 560cb25d3ab2078bb4eefa8bc891edbeb3ddb465 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20RAMAGE?= Date: Thu, 17 Jan 2019 09:55:11 +0100 Subject: [PATCH 06/34] Add raw_aps_data_request --- zigate/core.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/zigate/core.py b/zigate/core.py index d4789626..8c7812e4 100644 --- a/zigate/core.py +++ b/zigate/core.py @@ -1832,6 +1832,16 @@ def action_lock(self, addr, endpoint, lock): data = struct.pack('!BHBBB', 2, addr, 1, endpoint, lock) self.send_data(0x00f0, data) + def raw_aps_data_request(self, addr, endpoint, profile, cluster, security, radius, payload): + ''' + Send raw APS Data request + ''' + addr = self.__addr(addr) + length = len(payload) + data = struct.pack('!BHBBHHBBB{}s'.format(length), 2, addr, 1, endpoint, + profile, cluster, security, radius, length, payload) + return self.send_data(0x0530, data) + def start_mqtt_broker(self, host='localhost:1883', username=None, password=None): ''' Start a MQTT broker in a new thread From 216da7046024d2b6867a3556658a1d4825eb539f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20RAMAGE?= Date: Thu, 17 Jan 2019 10:03:44 +0100 Subject: [PATCH 07/34] store full status response, not only status code --- tests/test_core.py | 4 ++++ zigate/core.py | 6 +++--- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/tests/test_core.py b/tests/test_core.py index 884bf891..01d6bf46 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -288,6 +288,10 @@ def test_reporting_request(self): b'0212340103030000000000020020000000010e100000000020000100010e10000000' ) + def test_raw_aps_data(self): + r = self.zigate.raw_aps_data_request('1234', 1, 0x0104, 0x0006, 0, 0, b'payload') + self.assertEqual(r.sequence, 1) + def test_assumed_state(self): device = core.Device({'addr': '1234', 'ieee': '0123456789abcdef'}) device.set_attribute(3, 6, {'attribute': 0, 'rssi': 255, 'data': False}) diff --git a/zigate/core.py b/zigate/core.py index 8c7812e4..8d7cc120 100644 --- a/zigate/core.py +++ b/zigate/core.py @@ -457,7 +457,7 @@ def interpret_response(self, response): LOGGER.error('Command 0x{:04x} failed {} : {}'.format(response['packet_type'], response.status_text(), response['error'])) - self._last_status[response['packet_type']] = response['status'] + self._last_status[response['packet_type']] = response elif response.msg == 0x8015: # device list keys = set(self._devices.keys()) known_addr = set([d['addr'] for d in response['devices']]) @@ -1075,7 +1075,7 @@ def _add_group(self, cmd, addr, endpoint, group=None): src_endpoint, endpoint, group) r = self.send_data(cmd, data) group_addr = self.__haddr(group) - if r == 0: + if r.status == 0: self.__add_group(group_addr, self.__haddr(addr), endpoint) return group_addr @@ -1171,7 +1171,7 @@ def remove_group(self, addr, endpoint, group=None): data = struct.pack('!BHBBH', addr_mode, addr, src_endpoint, endpoint, group) r = self.send_data(0x0063, data) - if r == 0: + if r.status == 0: self.__remove_group(group_addr, self.__haddr(addr), endpoint) return r From 81c760df0df4ad52f83e19549dffdb06022fb755 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20RAMAGE?= Date: Thu, 17 Jan 2019 10:10:49 +0100 Subject: [PATCH 08/34] Provide zigate ieee and addr --- zigate/core.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/zigate/core.py b/zigate/core.py index 8d7cc120..2839f322 100644 --- a/zigate/core.py +++ b/zigate/core.py @@ -178,6 +178,14 @@ def __init__(self, port='auto', path='~/.zigate.json', if auto_save: self.start_auto_save() + @property + def ieee(self): + return self._ieee + + @property + def addr(self): + return self._addr + def start_adminpanel(self): ''' Start Admin panel in other thread From 001d868a342ce19374434cf840885e8c59e5995e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20RAMAGE?= Date: Thu, 17 Jan 2019 10:45:59 +0100 Subject: [PATCH 09/34] Improve raw data request --- zigate/core.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/zigate/core.py b/zigate/core.py index 2839f322..980e2227 100644 --- a/zigate/core.py +++ b/zigate/core.py @@ -1840,13 +1840,14 @@ def action_lock(self, addr, endpoint, lock): data = struct.pack('!BHBBB', 2, addr, 1, endpoint, lock) self.send_data(0x00f0, data) - def raw_aps_data_request(self, addr, endpoint, profile, cluster, security, radius, payload): + def raw_aps_data_request(self, addr, src_ep, dst_ep, profile, cluster, payload, security=0x01 | 0x02): ''' Send raw APS Data request ''' addr = self.__addr(addr) length = len(payload) - data = struct.pack('!BHBBHHBBB{}s'.format(length), 2, addr, 1, endpoint, + radius = 0 + data = struct.pack('!BHBBHHBBB{}s'.format(length), 2, addr, src_ep, dst_ep, profile, cluster, security, radius, length, payload) return self.send_data(0x0530, data) From 0c3b9af8a75b28758e2c6b6f35131fa76c28e8ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20RAMAGE?= Date: Thu, 17 Jan 2019 10:46:50 +0100 Subject: [PATCH 10/34] update tests --- tests/test_core.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_core.py b/tests/test_core.py index 01d6bf46..b13b8be7 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -289,7 +289,7 @@ def test_reporting_request(self): ) def test_raw_aps_data(self): - r = self.zigate.raw_aps_data_request('1234', 1, 0x0104, 0x0006, 0, 0, b'payload') + r = self.zigate.raw_aps_data_request('1234', 1, 1, 0x0104, 0x0006, b'payload', 3) self.assertEqual(r.sequence, 1) def test_assumed_state(self): From be8fb4fd022473fe6191e1a505f602a9876849a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20RAMAGE?= Date: Thu, 17 Jan 2019 10:48:08 +0100 Subject: [PATCH 11/34] remove duplicate --- zigate/core.py | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/zigate/core.py b/zigate/core.py index 980e2227..aed41194 100644 --- a/zigate/core.py +++ b/zigate/core.py @@ -698,18 +698,6 @@ def __haddr(self, int_addr, length=4): ''' convert int addr to hex ''' return '{0:0{1}x}'.format(int_addr, length) - @property - def ieee(self): - if not self._ieee: - self.get_network_state() - return self._ieee - - @property - def addr(self): - if not self._addr: - self.get_network_state() - return self._addr - @property def devices(self): return list(self._devices.values()) From b650077f865e82f789ce6f23fbfbc6bccfcb4c03 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20RAMAGE?= Date: Thu, 17 Jan 2019 14:00:39 +0100 Subject: [PATCH 12/34] Add basic support for cluster 0x0102 --- zigate/clusters.py | 31 +++++++++++++++++++++++++++++++ zigate/core.py | 12 +++++++++++- 2 files changed, 42 insertions(+), 1 deletion(-) diff --git a/zigate/clusters.py b/zigate/clusters.py index f4641106..34ffd58f 100644 --- a/zigate/clusters.py +++ b/zigate/clusters.py @@ -299,6 +299,21 @@ def __init__(self, endpoint=None): } +class C000f(Cluster): + cluster_id = 0x000f + type = 'Binary Input (Basic)' + attributes_def = {0x0004: {'name': 'active_text', 'value': 'value'}, + 0x001c: {'name': 'description', 'value': 'value'}, + 0x002e: {'name': 'inactive_text', 'value': 'value'}, + 0x0051: {'name': 'out_of_service', 'value': 'value', 'type': bool}, + 0x0054: {'name': 'polarity', 'value': 'value'}, + 0x0055: {'name': 'present_value', 'type': bool}, + 0x0067: {'name': 'reliability', 'value': 'value'}, + 0x006f: {'name': 'status_flags', 'value': 'value'}, + 0x0100: {'name': 'application_type', 'value': 'value'}, + } + + def vibration_decode(value): ''' Special decoder for XIAOMI Vibration sensor @@ -328,6 +343,22 @@ class C0101(Cluster): } +@register_cluster +class C0102(Cluster): + cluster_id = 0x0102 + type = 'Window covering' + attributes_def = {0x0000: {'name': 'window_covering_type', 'value': 'value'}, + 0x0001: {'name': 'physical_close_limit_lift_cm', 'value': 'value'}, + 0x0002: {'name': 'physical_close_limit_tilt_ddegree', 'value': 'value'}, + 0x0003: {'name': 'current_position_lift_cm', 'value': 'value'}, + 0x0004: {'name': 'current_position_tilt_ddegree', 'value': 'value'}, + 0x0005: {'name': 'num_of_actuation_lift', 'value': 'value'}, + 0x0007: {'name': 'config_status', 'value': 'value'}, + 0x0008: {'name': 'current_position_lift_percentage', 'value': 'value'}, + 0x0009: {'name': 'current_position_tilt_percentage', 'value': 'value'}, + } + + @register_cluster class C0201(Cluster): cluster_id = 0x0201 diff --git a/zigate/core.py b/zigate/core.py index aed41194..7470a1ee 100644 --- a/zigate/core.py +++ b/zigate/core.py @@ -48,7 +48,7 @@ ACTUATORS = [0x0010, 0x0051, 0x010a, 0x0100, 0x0101, 0x0102, 0x0103, 0x0105, 0x0110, - 0x0200, 0x0210, 0x0220] + 0x0200, 0x0202, 0x0210, 0x0220] # On/off light 0x0000 # On/off plug-in unit 0x0010 # Dimmable light 0x0100 @@ -2000,6 +2000,16 @@ def _bind_report(self, enpoint_id=None): self._zigate.bind_addr(self.addr, endpoint_id, 0x0008) self._zigate.reporting_request(self.addr, endpoint_id, 0x0008, (0x0000, 0x20)) + if 0x000f in endpoint['in_clusters']: + LOGGER.debug('bind and report for cluster 0x000f') + self._zigate.bind_addr(self.addr, endpoint_id, 0x000f) + self._zigate.reporting_request(self.addr, endpoint_id, + 0x000f, (0x0055, 0x10)) + if 0x0102 in endpoint['in_clusters']: + LOGGER.debug('bind and report for cluster 0x0102') + self._zigate.bind_addr(self.addr, endpoint_id, 0x0102) + self._zigate.reporting_request(self.addr, endpoint_id, + 0x0102, (0x0007, 0x20)) # TODO : auto select data type if 0x0300 in endpoint['in_clusters']: LOGGER.debug('bind and report for cluster 0x0300') From b7b4170d9e5cb7296c7f18dbb6ada3d140f4968f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20RAMAGE?= Date: Thu, 17 Jan 2019 15:12:49 +0100 Subject: [PATCH 13/34] Improve initial connection failure --- tests/test_transport.py | 14 ++++++++++++++ zigate/transport.py | 17 ++++++++++------- 2 files changed, 24 insertions(+), 7 deletions(-) diff --git a/tests/test_transport.py b/tests/test_transport.py index 9f6c6dad..35607e3f 100644 --- a/tests/test_transport.py +++ b/tests/test_transport.py @@ -70,6 +70,20 @@ def test_packet(self): self.assertEqual(b'\x01123\x03', connection.received.get()) self.assertEqual(b'\x01456\x03', connection.received.get()) + def test_initial_failed(self): + success = False + try: + connection = transport.ThreadSerialConnection(None, '/dummyport') + except transport.ZIGATE_CANNOT_CONNECT: + success = True + self.assertTrue(success) + success = False + try: + connection = transport.ThreadSocketConnection(None, 'fake.address') + except transport.ZIGATE_CANNOT_CONNECT: + success = True + self.assertTrue(success) + if __name__ == '__main__': unittest.main() diff --git a/zigate/transport.py b/zigate/transport.py index 8ebc202b..7d034903 100644 --- a/zigate/transport.py +++ b/zigate/transport.py @@ -154,7 +154,7 @@ def __init__(self, device, port=None): self._port = port self.device = device self._running = True - self.reconnect() + self.reconnect(False) self.thread = threading.Thread(target=self.listen, name='ZiGate-Listen') self.thread.setDaemon(True) @@ -164,7 +164,7 @@ def initSerial(self): self._port = self._find_port(self._port) return serial.Serial(self._port, 115200) - def reconnect(self): + def reconnect(self, retry=True): delay = 1 while True: try: @@ -174,13 +174,16 @@ def reconnect(self): LOGGER.error('ZiGate has not been found, please check configuration.') sys.exit(2) except Exception: + if not retry: + LOGGER.error('Cannot connect to ZiGate using port {}'.format(self._port)) + raise ZIGATE_CANNOT_CONNECT('Cannot connect to ZiGate using port {}'.format(self._port)) + sys.exit(2) msg = 'Failed to connect, retry in {} sec...'.format(delay) dispatcher.send(ZIGATE_FAILED_TO_CONNECT, message=msg) LOGGER.error(msg) time.sleep(delay) if delay < 60: delay *= 2 - return self.serial def listen(self): while self._running: @@ -243,13 +246,13 @@ def initSerial(self): for port in ports: try: s = socket.create_connection((host, port), 10) - LOGGER.debug('ZiGate found on port {}'.format(port)) + LOGGER.debug('ZiGate found on {} port {}'.format(host, port)) return s except Exception: - LOGGER.debug('ZiGate not found on port {}'.format(port)) + LOGGER.debug('ZiGate not found on {} port {}'.format(host, port)) continue - LOGGER.error('Cannot connect to ZiGate using port {}'.format(self._port)) - raise ZIGATE_CANNOT_CONNECT('Cannot connect to ZiGate using port {}'.format(self._port)) + LOGGER.error('Cannot connect to ZiGate using {} port {}'.format(self._host, self._port)) + raise ZIGATE_CANNOT_CONNECT('Cannot connect to ZiGate using {} port {}'.format(self._host, self._port)) def _find_host(self, host): host = host or 'auto' From 8d761844e6027e33820df65466da028e0179247b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20RAMAGE?= Date: Thu, 17 Jan 2019 15:15:52 +0100 Subject: [PATCH 14/34] fix flake8 --- tests/test_transport.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_transport.py b/tests/test_transport.py index 35607e3f..1bac570e 100644 --- a/tests/test_transport.py +++ b/tests/test_transport.py @@ -73,13 +73,13 @@ def test_packet(self): def test_initial_failed(self): success = False try: - connection = transport.ThreadSerialConnection(None, '/dummyport') + transport.ThreadSerialConnection(None, '/dummyport') except transport.ZIGATE_CANNOT_CONNECT: success = True self.assertTrue(success) success = False try: - connection = transport.ThreadSocketConnection(None, 'fake.address') + transport.ThreadSocketConnection(None, 'fake.address') except transport.ZIGATE_CANNOT_CONNECT: success = True self.assertTrue(success) From 7eda6a51aaf94696afa019c37a5ec707c44d9420 Mon Sep 17 00:00:00 2001 From: ISO-B <3048685+ISO-B@users.noreply.github.com> Date: Thu, 17 Jan 2019 19:21:28 +0200 Subject: [PATCH 15/34] Added responses for Ikea remote button presses --- zigate/responses.py | 65 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) diff --git a/zigate/responses.py b/zigate/responses.py index fa31d6b7..f5ea782b 100644 --- a/zigate/responses.py +++ b/zigate/responses.py @@ -624,6 +624,48 @@ class R8063(R8061): type = 'Remove group response' +@register_response +class R8085(Response): + msg = 0x8085 + type = 'Remote button pressed (MOVE_TO_LEVEL_UPDATE)' + s = OrderedDict([('sequence', 'B'), + ('endpoint', 'B'), + ('cluster', 'H'), + ('address_mode', 'B'), + ('addr', 'H'), + ('cmd', 'B'), + ]) + + def decode(self): + Response.decode(self) + press_type = {2: 'click', 1: 'hold', 3: 'release',} + if self.data['cmd'] in (1, 2, 3): + self.data['button'] = 'down' + self.data['type'] = press_type.get(self.data['cmd'], self.data['cmd']) + elif self.data['cmd'] in (5, 6, 7): + self.data['button'] = 'up' + self.data['type'] = press_type.get(self.data['cmd'] - 4, self.data['cmd']) + + +@register_response +class R8085(Response): + msg = 0x8095 + type = 'Remote button pressed (ONOFF_UPDATE)' + s = OrderedDict([('sequence', 'B'), + ('endpoint', 'B'), + ('cluster', 'H'), + ('address_mode', 'B'), + ('addr', 'H'), + ('cmd', 'B'), + ]) + + def decode(self): + Response.decode(self) + press_type = {2: 'click'} + self.data['button'] = 'middle' + self.data['type'] = press_type.get(self.data['cmd'], self.data['cmd']) + + @register_response class R80A0(Response): msg = 0x80A0 @@ -690,6 +732,29 @@ class R80A6(Response): ]) +@register_response +class R80A7(Response): + msg = 0x80A7 + type = 'Remote button pressed (LEFT/RIGHT)' + s = OrderedDict([('sequence', 'B'), + ('endpoint', 'B'), + ('cluster', 'H'), + ('address_mode', 'B'), + ('addr', 'H'), + ('cmd', 'B'), + ('direction', 'B'), + ]) + + def decode(self): + Response.decode(self) + directions = {0: 'right', 1: 'left', 2: 'middle'} + press_type = {7: 'click', 8: 'hold', 9: 'release'} + self.data['button'] = directions.get(self.data['direction'], self.data['direction']) + self.data['type'] = press_type.get(self.data['cmd'], self.data['cmd']) + if self.data['type'] == 'release': + self.data['button'] = 'previous' + + @register_response class R8100(Response): msg = 0x8100 From 852226d4bb10b955ec985304a3e501b87223bc52 Mon Sep 17 00:00:00 2001 From: ISO-B <3048685+ISO-B@users.noreply.github.com> Date: Thu, 17 Jan 2019 19:32:19 +0200 Subject: [PATCH 16/34] Travis fixes --- zigate/responses.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/zigate/responses.py b/zigate/responses.py index f5ea782b..3a540385 100644 --- a/zigate/responses.py +++ b/zigate/responses.py @@ -635,10 +635,10 @@ class R8085(Response): ('addr', 'H'), ('cmd', 'B'), ]) - + def decode(self): Response.decode(self) - press_type = {2: 'click', 1: 'hold', 3: 'release',} + press_type = {2: 'click', 1: 'hold', 3: 'release'} if self.data['cmd'] in (1, 2, 3): self.data['button'] = 'down' self.data['type'] = press_type.get(self.data['cmd'], self.data['cmd']) @@ -648,7 +648,7 @@ def decode(self): @register_response -class R8085(Response): +class R8095(Response): msg = 0x8095 type = 'Remote button pressed (ONOFF_UPDATE)' s = OrderedDict([('sequence', 'B'), From ec2f11fdcd4184568227afd9d8a34cd79b0b5ce9 Mon Sep 17 00:00:00 2001 From: ISO-B <3048685+ISO-B@users.noreply.github.com> Date: Sat, 19 Jan 2019 12:14:39 +0200 Subject: [PATCH 17/34] Changed R8702 to covert dst_address based on dst_address_mode. - dst_address can sometimes be 16-bit instead of 64-bit. --- zigate/responses.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/zigate/responses.py b/zigate/responses.py index 3a540385..20ae3e28 100644 --- a/zigate/responses.py +++ b/zigate/responses.py @@ -924,3 +924,9 @@ class R8702(Response): ('dst_address', 'Q'), ('sequence', 'B') ]) + + def decode(self): + Response.decode(self) + if self.data['dst_address_mode'] == 2: + self.data['dst_address'] = hex(self.data['dst_address'])[:6] + From 7a781a3a2fb29fc3788feeecb937cd5d5074721c Mon Sep 17 00:00:00 2001 From: ISO-B <3048685+ISO-B@users.noreply.github.com> Date: Sat, 19 Jan 2019 12:47:28 +0200 Subject: [PATCH 18/34] Travis fixes --- zigate/responses.py | 1 - 1 file changed, 1 deletion(-) diff --git a/zigate/responses.py b/zigate/responses.py index 20ae3e28..7f03e01e 100644 --- a/zigate/responses.py +++ b/zigate/responses.py @@ -929,4 +929,3 @@ def decode(self): Response.decode(self) if self.data['dst_address_mode'] == 2: self.data['dst_address'] = hex(self.data['dst_address'])[:6] - From 47018d602ac6e4c41a765d3881c4a2fc088b3d26 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20RAMAGE?= Date: Mon, 21 Jan 2019 13:35:55 +0100 Subject: [PATCH 19/34] Don't rely on dispatcher to interpret_response --- zigate/core.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/zigate/core.py b/zigate/core.py index 7470a1ee..81987b93 100644 --- a/zigate/core.py +++ b/zigate/core.py @@ -166,8 +166,6 @@ def __init__(self, port='auto', path='~/.zigate.json', self._event_thread.setDaemon(True) self._event_thread.start() - dispatcher.connect(self.interpret_response, ZIGATE_RESPONSE_RECEIVED) - self._ota_reset_local_variables() if adminpanel: @@ -457,6 +455,7 @@ def decode_data(self, packet): LOGGER.warning('Unknown response 0x{:04x}'.format(msg_type)) LOGGER.debug(response) self._last_response[msg_type] = response + self.interpret_response(response) dispatch_signal(ZIGATE_RESPONSE_RECEIVED, self, response=response) def interpret_response(self, response): From 70a066d096f19e30873687d2450edf2f2452678f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20RAMAGE?= Date: Mon, 21 Jan 2019 13:44:10 +0100 Subject: [PATCH 20/34] Fix TRADFRI outlet doesn't have level control --- zigate/core.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/zigate/core.py b/zigate/core.py index 81987b93..1ada4433 100644 --- a/zigate/core.py +++ b/zigate/core.py @@ -46,7 +46,7 @@ # Device id ACTUATORS = [0x0010, 0x0051, - 0x010a, + 0x010a, 0x010b, 0x0100, 0x0101, 0x0102, 0x0103, 0x0105, 0x0110, 0x0200, 0x0202, 0x0210, 0x0220] # On/off light 0x0000 @@ -1944,7 +1944,9 @@ def available_actions(self, endpoint_id=None): if ep_id != 1 and self.get_property_value('type') == 'lumi.ctrl_neutral1': ep_id -= 1 actions[ep_id].append(ACTIONS_ONOFF) - if 0x0008 in endpoint['in_clusters']: + if 0x0008 in endpoint['in_clusters'] and endpoint['device'] != 0x010a: + # except device 0x010a because Tradfri Outlet don't have level control + # but still have endpoint 8... actions[ep_id].append(ACTIONS_LEVEL) if 0x0101 in endpoint['in_clusters']: actions[ep_id].append(ACTIONS_LOCK) From 16e71679b8b2a84c6a0a7a551987e034a6524b5d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20RAMAGE?= Date: Mon, 21 Jan 2019 14:47:06 +0100 Subject: [PATCH 21/34] Add reporting for cluster 0x0201 --- zigate/clusters.py | 4 +++- zigate/core.py | 16 ++++++++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/zigate/clusters.py b/zigate/clusters.py index 34ffd58f..74a79cf5 100644 --- a/zigate/clusters.py +++ b/zigate/clusters.py @@ -365,8 +365,10 @@ class C0201(Cluster): type = 'Thermostat' attributes_def = {0x0000: {'name': 'local_temperature', 'value': 'value/100.', 'unit': '°C', 'type': float}, - 0x000b: {'name': 'heating_setpoint', 'value': 'value/100.', + 0x0008: {'name': 'heating_demand', 'value': 'value'}, + 0x0012: {'name': 'heating_setpoint', 'value': 'value/100.', 'unit': '°C', 'type': float}, + 0x001C: {'name': 'system_mode', 'value': 'value'}, } diff --git a/zigate/core.py b/zigate/core.py index 1ada4433..8730bdbb 100644 --- a/zigate/core.py +++ b/zigate/core.py @@ -1991,6 +1991,11 @@ def _bind_report(self, enpoint_id=None): for endpoint_id, endpoint in endpoints_list: if endpoint['device'] in ACTUATORS: # light LOGGER.debug('Start automagic bind and report process for device {}'.format(self)) + if 0x0001 in endpoint['in_clusters']: + LOGGER.debug('bind and report for cluster 0x0001') + self._zigate.bind_addr(self.addr, endpoint_id, 0x0001) + self._zigate.reporting_request(self.addr, endpoint_id, + 0x0001, (0x0020, 0x20)) # TODO: auto select data type if 0x0006 in endpoint['in_clusters']: LOGGER.debug('bind and report for cluster 0x0006') self._zigate.bind_addr(self.addr, endpoint_id, 0x0006) @@ -2011,6 +2016,17 @@ def _bind_report(self, enpoint_id=None): self._zigate.bind_addr(self.addr, endpoint_id, 0x0102) self._zigate.reporting_request(self.addr, endpoint_id, 0x0102, (0x0007, 0x20)) + if 0x0201 in endpoint['in_clusters']: + LOGGER.debug('bind and report for cluster 0x0201') + self._zigate.bind_addr(self.addr, endpoint_id, 0x0201) + self._zigate.reporting_request(self.addr, endpoint_id, + 0x0201, (0x0000, 0x29)) + self._zigate.reporting_request(self.addr, endpoint_id, + 0x0201, (0x0008, 0x20)) + self._zigate.reporting_request(self.addr, endpoint_id, + 0x0201, (0x0012, 0x29)) + self._zigate.reporting_request(self.addr, endpoint_id, + 0x0201, (0x001C, 0x20)) # TODO : auto select data type if 0x0300 in endpoint['in_clusters']: LOGGER.debug('bind and report for cluster 0x0300') From d1ae6a25c3a6a4c68ff343f201f4a52e25f6665a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20RAMAGE?= Date: Mon, 21 Jan 2019 15:11:19 +0100 Subject: [PATCH 22/34] Add reporting for cluster 0x0201 --- zigate/core.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zigate/core.py b/zigate/core.py index 8730bdbb..094979cc 100644 --- a/zigate/core.py +++ b/zigate/core.py @@ -2026,7 +2026,7 @@ def _bind_report(self, enpoint_id=None): self._zigate.reporting_request(self.addr, endpoint_id, 0x0201, (0x0012, 0x29)) self._zigate.reporting_request(self.addr, endpoint_id, - 0x0201, (0x001C, 0x20)) + 0x0201, (0x001C, 0x30)) # TODO : auto select data type if 0x0300 in endpoint['in_clusters']: LOGGER.debug('bind and report for cluster 0x0300') From 8ade9a5aa407344e97e7676347a9355745535a77 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20RAMAGE?= Date: Mon, 21 Jan 2019 15:12:36 +0100 Subject: [PATCH 23/34] Enable reporting for all devices --- zigate/core.py | 194 ++++++++++++++++++++++++------------------------- 1 file changed, 97 insertions(+), 97 deletions(-) diff --git a/zigate/core.py b/zigate/core.py index 094979cc..dfc732ac 100644 --- a/zigate/core.py +++ b/zigate/core.py @@ -1989,103 +1989,103 @@ def _bind_report(self, enpoint_id=None): else: endpoints_list = self.endpoints.items() for endpoint_id, endpoint in endpoints_list: - if endpoint['device'] in ACTUATORS: # light - LOGGER.debug('Start automagic bind and report process for device {}'.format(self)) - if 0x0001 in endpoint['in_clusters']: - LOGGER.debug('bind and report for cluster 0x0001') - self._zigate.bind_addr(self.addr, endpoint_id, 0x0001) - self._zigate.reporting_request(self.addr, endpoint_id, - 0x0001, (0x0020, 0x20)) # TODO: auto select data type - if 0x0006 in endpoint['in_clusters']: - LOGGER.debug('bind and report for cluster 0x0006') - self._zigate.bind_addr(self.addr, endpoint_id, 0x0006) - self._zigate.reporting_request(self.addr, endpoint_id, - 0x0006, (0x0000, 0x10)) # TODO: auto select data type - if 0x0008 in endpoint['in_clusters']: - LOGGER.debug('bind and report for cluster 0x0008') - self._zigate.bind_addr(self.addr, endpoint_id, 0x0008) - self._zigate.reporting_request(self.addr, endpoint_id, - 0x0008, (0x0000, 0x20)) - if 0x000f in endpoint['in_clusters']: - LOGGER.debug('bind and report for cluster 0x000f') - self._zigate.bind_addr(self.addr, endpoint_id, 0x000f) - self._zigate.reporting_request(self.addr, endpoint_id, - 0x000f, (0x0055, 0x10)) - if 0x0102 in endpoint['in_clusters']: - LOGGER.debug('bind and report for cluster 0x0102') - self._zigate.bind_addr(self.addr, endpoint_id, 0x0102) - self._zigate.reporting_request(self.addr, endpoint_id, - 0x0102, (0x0007, 0x20)) - if 0x0201 in endpoint['in_clusters']: - LOGGER.debug('bind and report for cluster 0x0201') - self._zigate.bind_addr(self.addr, endpoint_id, 0x0201) - self._zigate.reporting_request(self.addr, endpoint_id, - 0x0201, (0x0000, 0x29)) - self._zigate.reporting_request(self.addr, endpoint_id, - 0x0201, (0x0008, 0x20)) - self._zigate.reporting_request(self.addr, endpoint_id, - 0x0201, (0x0012, 0x29)) - self._zigate.reporting_request(self.addr, endpoint_id, - 0x0201, (0x001C, 0x30)) - # TODO : auto select data type - if 0x0300 in endpoint['in_clusters']: - LOGGER.debug('bind and report for cluster 0x0300') - self._zigate.bind_addr(self.addr, endpoint_id, 0x0300) - if endpoint['device'] in (0x0105,): - self._zigate.reporting_request(self.addr, - endpoint_id, - 0x0300, (0x0000, 0x20)) - self._zigate.reporting_request(self.addr, - endpoint_id, - 0x0300, (0x0001, 0x20)) - elif endpoint['device'] in (0x010D, 0x0210): - # self._zigate.reporting_request(self.addr, - # endpoint_id, - # 0x0300, [(0x0000, 0x20), - # (0x0001, 0x20), - # (0x0003, 0x21), - # (0x0004, 0x21), - # (0x0007, 0x21), - # ]) - self._zigate.reporting_request(self.addr, - endpoint_id, - 0x0300, (0x0000, 0x20)) - self._zigate.reporting_request(self.addr, - endpoint_id, - 0x0300, (0x0001, 0x20)) - self._zigate.reporting_request(self.addr, - endpoint_id, - 0x0300, (0x0003, 0x21)) - self._zigate.reporting_request(self.addr, - endpoint_id, - 0x0300, (0x0004, 0x21)) - self._zigate.reporting_request(self.addr, - endpoint_id, - 0x0300, (0x0007, 0x21)) - elif endpoint['device'] in (0x0102, 0x010C, 0x0220): - self._zigate.reporting_request(self.addr, - endpoint_id, - 0x0300, (0x0007, 0x21)) - else: # 0x0200 - # self._zigate.reporting_request(self.addr, - # endpoint_id, - # 0x0300, [(0x0000, 0x20), - # (0x0001, 0x20), - # (0x0003, 0x21), - # (0x0004, 0x21), - # ]) - self._zigate.reporting_request(self.addr, - endpoint_id, - 0x0300, (0x0000, 0x20)) - self._zigate.reporting_request(self.addr, - endpoint_id, - 0x0300, (0x0001, 0x20)) - self._zigate.reporting_request(self.addr, - endpoint_id, - 0x0300, (0x0003, 0x21)) - self._zigate.reporting_request(self.addr, - endpoint_id, - 0x0300, (0x0004, 0x21)) +# if endpoint['device'] in ACTUATORS: # light + LOGGER.debug('Start automagic bind and report process for device {}'.format(self)) + if 0x0001 in endpoint['in_clusters']: + LOGGER.debug('bind and report for cluster 0x0001') + self._zigate.bind_addr(self.addr, endpoint_id, 0x0001) + self._zigate.reporting_request(self.addr, endpoint_id, + 0x0001, (0x0020, 0x20)) # TODO: auto select data type + if 0x0006 in endpoint['in_clusters']: + LOGGER.debug('bind and report for cluster 0x0006') + self._zigate.bind_addr(self.addr, endpoint_id, 0x0006) + self._zigate.reporting_request(self.addr, endpoint_id, + 0x0006, (0x0000, 0x10)) # TODO: auto select data type + if 0x0008 in endpoint['in_clusters']: + LOGGER.debug('bind and report for cluster 0x0008') + self._zigate.bind_addr(self.addr, endpoint_id, 0x0008) + self._zigate.reporting_request(self.addr, endpoint_id, + 0x0008, (0x0000, 0x20)) + if 0x000f in endpoint['in_clusters']: + LOGGER.debug('bind and report for cluster 0x000f') + self._zigate.bind_addr(self.addr, endpoint_id, 0x000f) + self._zigate.reporting_request(self.addr, endpoint_id, + 0x000f, (0x0055, 0x10)) + if 0x0102 in endpoint['in_clusters']: + LOGGER.debug('bind and report for cluster 0x0102') + self._zigate.bind_addr(self.addr, endpoint_id, 0x0102) + self._zigate.reporting_request(self.addr, endpoint_id, + 0x0102, (0x0007, 0x20)) + if 0x0201 in endpoint['in_clusters']: + LOGGER.debug('bind and report for cluster 0x0201') + self._zigate.bind_addr(self.addr, endpoint_id, 0x0201) + self._zigate.reporting_request(self.addr, endpoint_id, + 0x0201, (0x0000, 0x29)) + self._zigate.reporting_request(self.addr, endpoint_id, + 0x0201, (0x0008, 0x20)) + self._zigate.reporting_request(self.addr, endpoint_id, + 0x0201, (0x0012, 0x29)) + self._zigate.reporting_request(self.addr, endpoint_id, + 0x0201, (0x001C, 0x30)) + # TODO : auto select data type + if 0x0300 in endpoint['in_clusters']: + LOGGER.debug('bind and report for cluster 0x0300') + self._zigate.bind_addr(self.addr, endpoint_id, 0x0300) + if endpoint['device'] in (0x0105,): + self._zigate.reporting_request(self.addr, + endpoint_id, + 0x0300, (0x0000, 0x20)) + self._zigate.reporting_request(self.addr, + endpoint_id, + 0x0300, (0x0001, 0x20)) + elif endpoint['device'] in (0x010D, 0x0210): + # self._zigate.reporting_request(self.addr, + # endpoint_id, + # 0x0300, [(0x0000, 0x20), + # (0x0001, 0x20), + # (0x0003, 0x21), + # (0x0004, 0x21), + # (0x0007, 0x21), + # ]) + self._zigate.reporting_request(self.addr, + endpoint_id, + 0x0300, (0x0000, 0x20)) + self._zigate.reporting_request(self.addr, + endpoint_id, + 0x0300, (0x0001, 0x20)) + self._zigate.reporting_request(self.addr, + endpoint_id, + 0x0300, (0x0003, 0x21)) + self._zigate.reporting_request(self.addr, + endpoint_id, + 0x0300, (0x0004, 0x21)) + self._zigate.reporting_request(self.addr, + endpoint_id, + 0x0300, (0x0007, 0x21)) + elif endpoint['device'] in (0x0102, 0x010C, 0x0220): + self._zigate.reporting_request(self.addr, + endpoint_id, + 0x0300, (0x0007, 0x21)) + else: # 0x0200 + # self._zigate.reporting_request(self.addr, + # endpoint_id, + # 0x0300, [(0x0000, 0x20), + # (0x0001, 0x20), + # (0x0003, 0x21), + # (0x0004, 0x21), + # ]) + self._zigate.reporting_request(self.addr, + endpoint_id, + 0x0300, (0x0000, 0x20)) + self._zigate.reporting_request(self.addr, + endpoint_id, + 0x0300, (0x0001, 0x20)) + self._zigate.reporting_request(self.addr, + endpoint_id, + 0x0300, (0x0003, 0x21)) + self._zigate.reporting_request(self.addr, + endpoint_id, + 0x0300, (0x0004, 0x21)) @staticmethod def from_json(data, zigate_instance=None): From b64e1f7ba6ff109543bee1559c90ce1b2e56507d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20RAMAGE?= Date: Mon, 21 Jan 2019 15:24:17 +0100 Subject: [PATCH 24/34] Fix test --- tests/test_devices.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_devices.py b/tests/test_devices.py index 7bc8ca14..3dad3346 100644 --- a/tests/test_devices.py +++ b/tests/test_devices.py @@ -116,7 +116,7 @@ def test_template(self): ) def test_inverse_bool(self): - device = core.Device({'addr': '1234', 'ieee': '0123456789abcdef'}) + device = core.Device({'addr': '1234', 'ieee': '0123456789abcdef'}, self.zigate) device.set_attribute(1, 0, {'attribute': 5, 'rssi': 255, 'data': 'lumi.sensor_switch.aq2'}) device.set_attribute(1, 6, {'attribute': 0, 'rssi': 255, 'data': True}) self.assertTrue(device.get_property_value('onoff')) From 55167642e4cdbdacf957f79c3c60db905d044684 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20RAMAGE?= Date: Mon, 21 Jan 2019 15:26:56 +0100 Subject: [PATCH 25/34] rename BIND_REPORT_LIGHT to BIND_REPORT --- zigate/core.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/zigate/core.py b/zigate/core.py index dfc732ac..1e495c16 100644 --- a/zigate/core.py +++ b/zigate/core.py @@ -39,7 +39,7 @@ AUTO_SAVE = 5 * 60 # 5 minutes -BIND_REPORT_LIGHT = True # automatically bind and report state for light +BIND_REPORT = True # automatically bind and report state for light SLEEP_INTERVAL = 0.1 ACTIONS = {} WAIT_TIMEOUT = 3 @@ -1982,7 +1982,7 @@ def _bind_report(self, enpoint_id=None): ''' automatically bind and report data for light ''' - if not BIND_REPORT_LIGHT: + if not BIND_REPORT: return if enpoint_id: endpoints_list = [(enpoint_id, self.endpoints[enpoint_id])] From f51c9fe441ca0b41035a99245d82c2db27371c60 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20RAMAGE?= Date: Mon, 21 Jan 2019 15:32:48 +0100 Subject: [PATCH 26/34] Fix travis --- zigate/core.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zigate/core.py b/zigate/core.py index 1e495c16..a91e1ad8 100644 --- a/zigate/core.py +++ b/zigate/core.py @@ -1989,7 +1989,7 @@ def _bind_report(self, enpoint_id=None): else: endpoints_list = self.endpoints.items() for endpoint_id, endpoint in endpoints_list: -# if endpoint['device'] in ACTUATORS: # light + # if endpoint['device'] in ACTUATORS: # light LOGGER.debug('Start automagic bind and report process for device {}'.format(self)) if 0x0001 in endpoint['in_clusters']: LOGGER.debug('bind and report for cluster 0x0001') From c50f38dd01470efbaaaa47de1c2265ae21a5d2ce Mon Sep 17 00:00:00 2001 From: doudz Date: Mon, 21 Jan 2019 20:55:15 +0100 Subject: [PATCH 27/34] Handle response 0x8085 and 0x8095 --- tests/test_core.py | 20 ++++++++++++++++++++ zigate/clusters.py | 4 ++++ zigate/core.py | 4 ++-- zigate/responses.py | 14 ++++++++++++++ 4 files changed, 40 insertions(+), 2 deletions(-) diff --git a/tests/test_core.py b/tests/test_core.py index b13b8be7..bfb72c2d 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -327,6 +327,26 @@ def test_assumed_state(self): # {'attribute': 0, 'data': False, 'name': 'onoff', 'value': False, 'type': bool, # 'state': 'assumed'}) + def test_handle_response_8085(self): + device = core.Device({'addr': '1234', 'ieee': '0123456789abcdef'}, + self.zigate) + self.zigate._devices['1234'] = device + msg_data = b'\x01\x01\x00\x08\x02\x124\x01' + r = responses.R8085(msg_data, 255) + self.zigate.interpret_response(r) + self.assertEqual(device.get_property_value('remote_level_button'), + 'down_hold') + + def test_handle_response_8095(self): + device = core.Device({'addr': '1234', 'ieee': '0123456789abcdef'}, + self.zigate) + self.zigate._devices['1234'] = device + msg_data = b'\x01\x01\x00\x06\x02\x124\x02' + r = responses.R8095(msg_data, 255) + self.zigate.interpret_response(r) + self.assertEqual(device.get_property_value('remote_onoff_button'), + 'middle_click') + if __name__ == '__main__': unittest.main() diff --git a/zigate/clusters.py b/zigate/clusters.py index 74a79cf5..73525095 100644 --- a/zigate/clusters.py +++ b/zigate/clusters.py @@ -211,6 +211,8 @@ class C0006(Cluster): attributes_def = {0x0000: {'name': 'onoff', 'value': 'value', 'type': bool}, 0x8000: {'name': 'multiclick', 'value': 'value', 'type': int, 'expire': 2}, + 0x00ff: {'name': 'remote_onoff_button', 'value': 'value', + 'type': str, 'expire': 2} } @@ -219,6 +221,8 @@ class C0008(Cluster): cluster_id = 0x0008 type = 'General: Level control' attributes_def = {0x0000: {'name': 'current_level', 'value': 'int(value*100/254)', 'type': int}, + 0x00ff: {'name': 'remote_level_button', 'value': 'value', + 'type': str, 'expire': 2} } diff --git a/zigate/core.py b/zigate/core.py index a91e1ad8..f2bef8c4 100644 --- a/zigate/core.py +++ b/zigate/core.py @@ -519,8 +519,8 @@ def interpret_response(self, response): elif response.msg == 0x8062: # Get group membership response data = response.cleaned_data() self._sync_group_membership(data['addr'], data['endpoint'], data['groups']) - elif response.msg in (0x8100, 0x8102, 0x8110, 0x8401): # attribute report or IAS Zone status change - if response['status'] != 0: + elif response.msg in (0x8100, 0x8102, 0x8110, 0x8401, 0x8085, 0x8095): # attribute report or IAS Zone status change + if response.get('status', 0) != 0: LOGGER.debug('Received Bad status') return device = self._get_device(response['addr']) diff --git a/zigate/responses.py b/zigate/responses.py index 7f03e01e..458ec3a6 100644 --- a/zigate/responses.py +++ b/zigate/responses.py @@ -646,6 +646,13 @@ def decode(self): self.data['button'] = 'up' self.data['type'] = press_type.get(self.data['cmd'] - 4, self.data['cmd']) + def cleaned_data(self): + # fake attribute + self.data['attribute'] = 0x00ff + self.data['data'] = '{}_{}'.format(self.data['button'], + self.data['type']) + return self._filter_data(['attribute', 'data']) + @register_response class R8095(Response): @@ -665,6 +672,13 @@ def decode(self): self.data['button'] = 'middle' self.data['type'] = press_type.get(self.data['cmd'], self.data['cmd']) + def cleaned_data(self): + # fake attribute + self.data['attribute'] = 0x00ff + self.data['data'] = '{}_{}'.format(self.data['button'], + self.data['type']) + return self._filter_data(['attribute', 'data']) + @register_response class R80A0(Response): From 1bd3d3dc357f017f12d605c0f56f5e9be60fb59b Mon Sep 17 00:00:00 2001 From: doudz Date: Mon, 21 Jan 2019 21:07:06 +0100 Subject: [PATCH 28/34] Handle 0x80A7 response --- tests/test_core.py | 10 ++++++++++ zigate/clusters.py | 10 ++++++++++ zigate/core.py | 2 +- zigate/responses.py | 7 +++++++ 4 files changed, 28 insertions(+), 1 deletion(-) diff --git a/tests/test_core.py b/tests/test_core.py index bfb72c2d..1aee32fa 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -347,6 +347,16 @@ def test_handle_response_8095(self): self.assertEqual(device.get_property_value('remote_onoff_button'), 'middle_click') + def test_handle_response_80A7(self): + device = core.Device({'addr': '1234', 'ieee': '0123456789abcdef'}, + self.zigate) + self.zigate._devices['1234'] = device + msg_data = b'\x01\x01\x00\x05\x02\x124\x07\x01' + r = responses.R80A7(msg_data, 255) + self.zigate.interpret_response(r) + self.assertEqual(device.get_property_value('remote_scene_button'), + 'left_click') + if __name__ == '__main__': unittest.main() diff --git a/zigate/clusters.py b/zigate/clusters.py index 73525095..899af1c6 100644 --- a/zigate/clusters.py +++ b/zigate/clusters.py @@ -204,6 +204,16 @@ class C0001(Cluster): # E_CLD_PWRCFG_BATTERY_SIZE_UNKNOWN = 0xff, +@register_cluster +class C0005(Cluster): + cluster_id = 0x0005 + type = 'General: Scenes' + attributes_def = { + 0x00ff: {'name': 'remote_scene_button', 'value': 'value', + 'type': str, 'expire': 2} + } + + @register_cluster class C0006(Cluster): cluster_id = 0x0006 diff --git a/zigate/core.py b/zigate/core.py index f2bef8c4..2e9b2ef5 100644 --- a/zigate/core.py +++ b/zigate/core.py @@ -519,7 +519,7 @@ def interpret_response(self, response): elif response.msg == 0x8062: # Get group membership response data = response.cleaned_data() self._sync_group_membership(data['addr'], data['endpoint'], data['groups']) - elif response.msg in (0x8100, 0x8102, 0x8110, 0x8401, 0x8085, 0x8095): # attribute report or IAS Zone status change + elif response.msg in (0x8100, 0x8102, 0x8110, 0x8401, 0x8085, 0x8095, 0x80A7): # attribute report or IAS Zone status change if response.get('status', 0) != 0: LOGGER.debug('Received Bad status') return diff --git a/zigate/responses.py b/zigate/responses.py index 458ec3a6..2e9881b9 100644 --- a/zigate/responses.py +++ b/zigate/responses.py @@ -768,6 +768,13 @@ def decode(self): if self.data['type'] == 'release': self.data['button'] = 'previous' + def cleaned_data(self): + # fake attribute + self.data['attribute'] = 0x00ff + self.data['data'] = '{}_{}'.format(self.data['button'], + self.data['type']) + return self._filter_data(['attribute', 'data']) + @register_response class R8100(Response): From 182b4983576a3b4d8fe9285e30dfcce66c7e97ed Mon Sep 17 00:00:00 2001 From: doudz Date: Mon, 21 Jan 2019 21:07:53 +0100 Subject: [PATCH 29/34] Fix travis --- zigate/core.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/zigate/core.py b/zigate/core.py index 2e9b2ef5..12582ba7 100644 --- a/zigate/core.py +++ b/zigate/core.py @@ -519,7 +519,8 @@ def interpret_response(self, response): elif response.msg == 0x8062: # Get group membership response data = response.cleaned_data() self._sync_group_membership(data['addr'], data['endpoint'], data['groups']) - elif response.msg in (0x8100, 0x8102, 0x8110, 0x8401, 0x8085, 0x8095, 0x80A7): # attribute report or IAS Zone status change + elif response.msg in (0x8100, 0x8102, 0x8110, 0x8401, + 0x8085, 0x8095, 0x80A7): # attribute report or IAS Zone status change if response.get('status', 0) != 0: LOGGER.debug('Received Bad status') return From aab3695db34ad8553d149fc0ab6de4afe46c7e22 Mon Sep 17 00:00:00 2001 From: doudz Date: Mon, 21 Jan 2019 22:07:22 +0100 Subject: [PATCH 30/34] Fix travis --- zigate/clusters.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/zigate/clusters.py b/zigate/clusters.py index 899af1c6..cf0529f4 100644 --- a/zigate/clusters.py +++ b/zigate/clusters.py @@ -208,8 +208,7 @@ class C0001(Cluster): class C0005(Cluster): cluster_id = 0x0005 type = 'General: Scenes' - attributes_def = { - 0x00ff: {'name': 'remote_scene_button', 'value': 'value', + attributes_def = {0x00ff: {'name': 'remote_scene_button', 'value': 'value', 'type': str, 'expire': 2} } From ca8607ef2ca7b554e681de046d0b3b564d07e9bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20RAMAGE?= Date: Tue, 22 Jan 2019 08:58:21 +0100 Subject: [PATCH 31/34] Rename attribute 0x00ff to 0xfff0 to avoid collision --- zigate/clusters.py | 6 +++--- zigate/responses.py | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/zigate/clusters.py b/zigate/clusters.py index cf0529f4..5eecaaf6 100644 --- a/zigate/clusters.py +++ b/zigate/clusters.py @@ -208,7 +208,7 @@ class C0001(Cluster): class C0005(Cluster): cluster_id = 0x0005 type = 'General: Scenes' - attributes_def = {0x00ff: {'name': 'remote_scene_button', 'value': 'value', + attributes_def = {0xfff0: {'name': 'remote_scene_button', 'value': 'value', 'type': str, 'expire': 2} } @@ -220,7 +220,7 @@ class C0006(Cluster): attributes_def = {0x0000: {'name': 'onoff', 'value': 'value', 'type': bool}, 0x8000: {'name': 'multiclick', 'value': 'value', 'type': int, 'expire': 2}, - 0x00ff: {'name': 'remote_onoff_button', 'value': 'value', + 0xfff0: {'name': 'remote_onoff_button', 'value': 'value', 'type': str, 'expire': 2} } @@ -230,7 +230,7 @@ class C0008(Cluster): cluster_id = 0x0008 type = 'General: Level control' attributes_def = {0x0000: {'name': 'current_level', 'value': 'int(value*100/254)', 'type': int}, - 0x00ff: {'name': 'remote_level_button', 'value': 'value', + 0xfff0: {'name': 'remote_level_button', 'value': 'value', 'type': str, 'expire': 2} } diff --git a/zigate/responses.py b/zigate/responses.py index 2e9881b9..ce341880 100644 --- a/zigate/responses.py +++ b/zigate/responses.py @@ -648,7 +648,7 @@ def decode(self): def cleaned_data(self): # fake attribute - self.data['attribute'] = 0x00ff + self.data['attribute'] = 0xfff0 self.data['data'] = '{}_{}'.format(self.data['button'], self.data['type']) return self._filter_data(['attribute', 'data']) @@ -674,7 +674,7 @@ def decode(self): def cleaned_data(self): # fake attribute - self.data['attribute'] = 0x00ff + self.data['attribute'] = 0xfff0 self.data['data'] = '{}_{}'.format(self.data['button'], self.data['type']) return self._filter_data(['attribute', 'data']) @@ -770,7 +770,7 @@ def decode(self): def cleaned_data(self): # fake attribute - self.data['attribute'] = 0x00ff + self.data['attribute'] = 0xfff0 self.data['data'] = '{}_{}'.format(self.data['button'], self.data['type']) return self._filter_data(['attribute', 'data']) From 144c0c88df4c58629c6def42de1dae73598910c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20RAMAGE?= Date: Tue, 22 Jan 2019 11:22:46 +0100 Subject: [PATCH 32/34] Add window covering command --- zigate/const.py | 8 +++++ zigate/core.py | 83 ++++++++++++++++++++++++++++++------------------- 2 files changed, 59 insertions(+), 32 deletions(-) diff --git a/zigate/const.py b/zigate/const.py index ab3d3bf9..f3486d75 100644 --- a/zigate/const.py +++ b/zigate/const.py @@ -32,6 +32,13 @@ TOGGLE = 2 LOCK = 0 UNLOCK = 1 +OPEN = 0x00 +CLOSE = 0x01 +STOP = 0x02 +LIFT_VALUE = 0x04 +LIFT_PERCENT = 0x05 +TILT_VALUE = 0x07 +TILT_PERCENT = 0x08 STATUS_CODES = {0: 'Success', 1: 'Invalid parameters', 2: 'Unhandled command', 3: 'Command failed', @@ -43,6 +50,7 @@ ACTIONS_TEMPERATURE = 'temperature' ACTIONS_HUE = 'hue' ACTIONS_LOCK = 'lock' +ACTIONS_COVER = 'cover' DATA_TYPE = {0x00: None, 0x10: '?', # bool diff --git a/zigate/core.py b/zigate/core.py index 12582ba7..2d9c3e90 100644 --- a/zigate/core.py +++ b/zigate/core.py @@ -17,7 +17,7 @@ from .transport import (ThreadSerialConnection, ThreadSocketConnection) from .responses import (RESPONSES, Response) from .const import (ACTIONS_COLOR, ACTIONS_LEVEL, ACTIONS_LOCK, ACTIONS_HUE, - ACTIONS_ONOFF, ACTIONS_TEMPERATURE, + ACTIONS_ONOFF, ACTIONS_TEMPERATURE, ACTIONS_COVER, OFF, ON, TYPE_COORDINATOR, STATUS_CODES, ZIGATE_ATTRIBUTE_ADDED, ZIGATE_ATTRIBUTE_UPDATED, ZIGATE_DEVICE_ADDED, ZIGATE_DEVICE_REMOVED, @@ -1635,13 +1635,7 @@ def action_onoff(self, addr, endpoint, onoff, on_time=0, off_time=0, effect=0, g elif effect: cmd = 0x0094 data = struct.pack('!BHBBBB', 2, addr, 1, endpoint, effect, gradient) - self.send_data(cmd, data) -# device = self._devices.get(self.__haddr(addr)) -# if device: -# if device.is_assumed(endpoint, 6, 0): -# value = device.get_attribute(endpoint, 6, 0) -# value['data'] = {0: False, 1: True, 2: not value.get('data', False)}.get(onoff, 0) -# device.set_attribute(endpoint, 6, value) + return self.send_data(cmd, data) @register_actions(ACTIONS_LEVEL) def action_move_level(self, addr, endpoint, onoff=OFF, mode=0, rate=0): @@ -1651,7 +1645,7 @@ def action_move_level(self, addr, endpoint, onoff=OFF, mode=0, rate=0): ''' addr = self.__addr(addr) data = struct.pack('!BHBBBBB', 2, addr, 1, endpoint, onoff, mode, rate) - self.send_data(0x0080, data) + return self.send_data(0x0080, data) @register_actions(ACTIONS_LEVEL) def action_move_level_onoff(self, addr, endpoint, onoff=OFF, level=0, transition_time=0): @@ -1662,7 +1656,7 @@ def action_move_level_onoff(self, addr, endpoint, onoff=OFF, level=0, transition addr = self.__addr(addr) level = int(level * 254 // 100) data = struct.pack('!BHBBBBH', 2, addr, 1, endpoint, onoff, level, transition_time) - self.send_data(0x0081, data) + return self.send_data(0x0081, data) @register_actions(ACTIONS_LEVEL) def action_move_step(self, addr, endpoint, onoff=OFF, step_mode=0, step_size=0, transition_time=0): @@ -1671,7 +1665,7 @@ def action_move_step(self, addr, endpoint, onoff=OFF, step_mode=0, step_size=0, ''' addr = self.__addr(addr) data = struct.pack('!BHBBBBBH', 2, addr, 1, endpoint, onoff, step_mode, step_size, transition_time) - self.send_data(0x0082, data) + return self.send_data(0x0082, data) @register_actions(ACTIONS_LEVEL) def action_move_stop(self, addr, endpoint): @@ -1680,7 +1674,7 @@ def action_move_stop(self, addr, endpoint): ''' addr = self.__addr(addr) data = struct.pack('!BHBB', 2, addr, 1, endpoint) - self.send_data(0x0083, data) + return self.send_data(0x0083, data) @register_actions(ACTIONS_LEVEL) def action_move_stop_onoff(self, addr, endpoint): @@ -1689,10 +1683,10 @@ def action_move_stop_onoff(self, addr, endpoint): ''' addr = self.__addr(addr) data = struct.pack('!BHBB', 2, addr, 1, endpoint) - self.send_data(0x0084, data) + return self.send_data(0x0084, data) @register_actions(ACTIONS_HUE) - def actions_move_hue(self, addr, endpoint, hue, direction=0, transition=0): + def action_move_hue(self, addr, endpoint, hue, direction=0, transition=0): ''' move to hue hue 0-360 in degrees @@ -1703,10 +1697,10 @@ def actions_move_hue(self, addr, endpoint, hue, direction=0, transition=0): hue = int(hue * 254 // 360) data = struct.pack('!BHBBBBH', 2, addr, 1, endpoint, hue, direction, transition) - self.send_data(0x00B0, data) + return self.send_data(0x00B0, data) @register_actions(ACTIONS_HUE) - def actions_move_hue_saturation(self, addr, endpoint, hue, saturation=100, transition=0): + def action_move_hue_saturation(self, addr, endpoint, hue, saturation=100, transition=0): ''' move to hue and saturation hue 0-360 in degrees @@ -1718,19 +1712,19 @@ def actions_move_hue_saturation(self, addr, endpoint, hue, saturation=100, trans saturation = int(saturation * 254 // 100) data = struct.pack('!BHBBBBH', 2, addr, 1, endpoint, hue, saturation, transition) - self.send_data(0x00B6, data) + return self.send_data(0x00B6, data) @register_actions(ACTIONS_HUE) - def actions_move_hue_hex(self, addr, endpoint, color_hex, transition=0): + def action_move_hue_hex(self, addr, endpoint, color_hex, transition=0): ''' move to hue color in #ffffff transition in second ''' rgb = hex_to_rgb(color_hex) - self.actions_move_hue_rgb(addr, endpoint, rgb, transition) + return self.actions_move_hue_rgb(addr, endpoint, rgb, transition) @register_actions(ACTIONS_HUE) - def actions_move_hue_rgb(self, addr, endpoint, rgb, transition=0): + def action_move_hue_rgb(self, addr, endpoint, rgb, transition=0): ''' move to hue (r,g,b) example : (1.0, 1.0, 1.0) transition in second @@ -1740,10 +1734,10 @@ def actions_move_hue_rgb(self, addr, endpoint, rgb, transition=0): saturation = int(saturation * 100) level = int(level * 100) self.action_move_level_onoff(addr, endpoint, ON, level, 0) - self.actions_move_hue_saturation(addr, endpoint, hue, saturation, transition) + return self.actions_move_hue_saturation(addr, endpoint, hue, saturation, transition) @register_actions(ACTIONS_COLOR) - def actions_move_colour(self, addr, endpoint, x, y, transition=0): + def action_move_colour(self, addr, endpoint, x, y, transition=0): ''' move to colour x y x, y can be integer 0-65536 or float 0-1.0 @@ -1756,10 +1750,10 @@ def actions_move_colour(self, addr, endpoint, x, y, transition=0): addr = self.__addr(addr) data = struct.pack('!BHBBHHH', 2, addr, 1, endpoint, x, y, transition) - self.send_data(0x00B7, data) + return self.send_data(0x00B7, data) @register_actions(ACTIONS_COLOR) - def actions_move_colour_hex(self, addr, endpoint, color_hex, transition=0): + def action_move_colour_hex(self, addr, endpoint, color_hex, transition=0): ''' move to colour #ffffff convenient function to set color in hex format @@ -1769,7 +1763,7 @@ def actions_move_colour_hex(self, addr, endpoint, color_hex, transition=0): return self.actions_move_colour(addr, endpoint, x, y, transition) @register_actions(ACTIONS_COLOR) - def actions_move_colour_rgb(self, addr, endpoint, rgb, transition=0): + def action_move_colour_rgb(self, addr, endpoint, rgb, transition=0): ''' move to colour (r,g,b) example : (1.0, 1.0, 1.0) convenient function to set color in hex format @@ -1779,7 +1773,7 @@ def actions_move_colour_rgb(self, addr, endpoint, rgb, transition=0): return self.actions_move_colour(addr, endpoint, x, y, transition) @register_actions(ACTIONS_TEMPERATURE) - def actions_move_temperature(self, addr, endpoint, mired, transition=0): + def action_move_temperature(self, addr, endpoint, mired, transition=0): ''' move colour to temperature mired color temperature @@ -1788,10 +1782,10 @@ def actions_move_temperature(self, addr, endpoint, mired, transition=0): addr = self.__addr(addr) data = struct.pack('!BHBBHH', 2, addr, 1, endpoint, mired, transition) - self.send_data(0x00C0, data) + return self.send_data(0x00C0, data) @register_actions(ACTIONS_TEMPERATURE) - def actions_move_temperature_kelvin(self, addr, endpoint, temperature, transition=0): + def action_move_temperature_kelvin(self, addr, endpoint, temperature, transition=0): ''' move colour to temperature temperature unit is kelvin @@ -1799,10 +1793,10 @@ def actions_move_temperature_kelvin(self, addr, endpoint, temperature, transitio convenient function to use kelvin instead of mired ''' temperature = int(1000000 // temperature) - self.actions_move_temperature(addr, endpoint, temperature, transition) + return self.actions_move_temperature(addr, endpoint, temperature, transition) @register_actions(ACTIONS_TEMPERATURE) - def actions_move_temperature_rate(self, addr, endpoint, mode, rate, min_temperature, max_temperature): + def action_move_temperature_rate(self, addr, endpoint, mode, rate, min_temperature, max_temperature): ''' move colour temperature in specified rate towards given min or max value Available modes: @@ -1817,7 +1811,7 @@ def actions_move_temperature_rate(self, addr, endpoint, mode, rate, min_temperat max_temperature = int(1000000 // max_temperature) addr = self.__addr(addr) data = struct.pack('!BHBBBHHH', 2, addr, 1, endpoint, mode, rate, min_temperature, max_temperature) - self.send_data(0x00C1, data) + return self.send_data(0x00C1, data) @register_actions(ACTIONS_LOCK) def action_lock(self, addr, endpoint, lock): @@ -1826,7 +1820,32 @@ def action_lock(self, addr, endpoint, lock): ''' addr = self.__addr(addr) data = struct.pack('!BHBBB', 2, addr, 1, endpoint, lock) - self.send_data(0x00f0, data) + return self.send_data(0x00f0, data) + + @register_actions(ACTIONS_COVER) + def action_cover(self, addr, endpoint, cmd, param=None): + ''' + Open, close, move, ... + cmd could be : + OPEN = 0x00 + CLOSE = 0x01 + STOP = 0x02 + LIFT_VALUE = 0x04 + LIFT_PERCENT = 0x05 + TILT_VALUE = 0x07 + TILT_PERCENT = 0x08 + ''' + fmt = '!BHBBB' + addr = self.__addr(addr) + args = [2, addr, 1, endpoint, cmd] + if cmd in (0x04, 0x07): + fmt += 'H' + args.append(param) + elif cmd in (0x05, 0x08): + fmt += 'B' + args.append(param) + data = struct.pack(fmt, *args) + return self.send_data(0x00fa, data) def raw_aps_data_request(self, addr, src_ep, dst_ep, profile, cluster, payload, security=0x01 | 0x02): ''' From 32832d5fa54decf493df4e7e0a76a2f0d753f676 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20RAMAGE?= Date: Tue, 22 Jan 2019 11:23:17 +0100 Subject: [PATCH 33/34] Ready for release 0.26.0 --- zigate/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zigate/version.py b/zigate/version.py index 845896a9..a309a7c9 100644 --- a/zigate/version.py +++ b/zigate/version.py @@ -5,4 +5,4 @@ # file that was distributed with this source code. # -__version__ = '0.26.0.dev0' +__version__ = '0.26.0' From 2da3a9dd52e24637a2bee2e1193492c077d7e192 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20RAMAGE?= Date: Tue, 22 Jan 2019 11:24:30 +0100 Subject: [PATCH 34/34] Add ACTIONS_COVER --- zigate/core.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/zigate/core.py b/zigate/core.py index 2d9c3e90..80af80c0 100644 --- a/zigate/core.py +++ b/zigate/core.py @@ -1970,6 +1970,8 @@ def available_actions(self, endpoint_id=None): actions[ep_id].append(ACTIONS_LEVEL) if 0x0101 in endpoint['in_clusters']: actions[ep_id].append(ACTIONS_LOCK) + if 0x0102 in endpoint['in_clusters']: + actions[ep_id].append(ACTIONS_COVER) if 0x0300 in endpoint['in_clusters']: # if endpoint['device'] in (0x0102, 0x0105): if endpoint['device'] in (0x0105,):