|
34 | 34 | import time |
35 | 35 | from enum import Enum |
36 | 36 |
|
37 | | -__version__ = '1.0.7.1' |
| 37 | +__version__ = '1.0.7.2' |
38 | 38 | MODULE = __name__ |
39 | 39 | PORT = 4000 |
40 | 40 |
|
@@ -652,6 +652,10 @@ def update_status(self): |
652 | 652 |
|
653 | 653 | :return: |
654 | 654 | """ |
| 655 | + if not [addr for addr in self.__lights |
| 656 | + if addr in self.__conn.lights()]: |
| 657 | + return |
| 658 | + |
655 | 659 | features = [self.__conn.lights()[addr].supported_features() |
656 | 660 | for addr in self.__lights if addr in self.__conn.lights()] |
657 | 661 | self.__supported_features = set.union(*features) |
@@ -1297,25 +1301,36 @@ def update_group_list(self, throttling_interval=None): |
1297 | 1301 | command = self.build_group_list() |
1298 | 1302 | data = self.send(command) |
1299 | 1303 |
|
1300 | | - (num,) = struct.unpack('<H', data[7:9]) |
1301 | | - self.__logger.debug('Number of groups: %d', num) |
1302 | | - new_groups = {} |
1303 | | - |
1304 | | - for i in range(0, num): |
1305 | | - pos = 9 + i * 18 |
1306 | | - payload = data[pos:pos + 18] |
1307 | | - |
1308 | | - (idx, name) = struct.unpack('<H16s', payload) |
1309 | | - name = name.decode('utf-8').replace('\0', '') |
| 1304 | + try: |
| 1305 | + (num,) = struct.unpack('<H', data[7:9]) |
| 1306 | + self.__logger.debug('Number of groups: %d', num) |
| 1307 | + if len(data) < 8 + num * 18: |
| 1308 | + raise struct.error('Incorrect data length for {} records:' |
| 1309 | + ' {}'.format(num, len(data))) |
| 1310 | + |
| 1311 | + new_groups = {} |
| 1312 | + for i in range(0, num): |
| 1313 | + pos = 9 + i * 18 |
| 1314 | + payload = data[pos:pos + 18] |
| 1315 | + self.__logger.debug('Group payload: %d', i) |
| 1316 | + |
| 1317 | + (idx, name) = struct.unpack('<H16s', payload) |
| 1318 | + name = name.decode('utf-8').replace('\0', '') |
| 1319 | + |
| 1320 | + if (name in self.__groups and |
| 1321 | + self.__groups[name].idx() == idx): |
| 1322 | + group = self.__groups[name] |
| 1323 | + self.__logger.debug('Old group %d: %s', idx, name) |
| 1324 | + else: |
| 1325 | + group = Group(self, idx, name) |
| 1326 | + self.__logger.debug('New group %d: %s', idx, name) |
1310 | 1327 |
|
1311 | | - if name in self.__groups and self.__groups[name].idx() == idx: |
1312 | | - group = self.__groups[name] |
1313 | | - self.__logger.debug('Old group %d: %s', idx, name) |
1314 | | - else: |
1315 | | - group = Group(self, idx, name) |
1316 | | - self.__logger.debug('New group %d: %s', idx, name) |
| 1328 | + new_groups[name] = group |
1317 | 1329 |
|
1318 | | - new_groups[name] = group |
| 1330 | + except (struct.error, UnicodeDecodeError) as err: |
| 1331 | + self.__logger.error('Couldn\'t parse group status: %s', err) |
| 1332 | + self.__logger.error('Data: %s', binascii.hexlify(data)) |
| 1333 | + return {} |
1319 | 1334 |
|
1320 | 1335 | for name in self.__groups: |
1321 | 1336 | if (name not in new_groups or |
@@ -1397,30 +1412,40 @@ def update_scene_list(self, throttling_interval=None): |
1397 | 1412 | command = self.build_scene_list() |
1398 | 1413 | data = self.send(command) |
1399 | 1414 |
|
1400 | | - (num,) = struct.unpack('<H', data[7:9]) |
1401 | | - self.__logger.debug('Number of scenes: %d', num) |
1402 | | - new_scenes = {} |
1403 | | - |
1404 | | - for i in range(0, num): |
1405 | | - pos = 9 + i * 20 |
1406 | | - payload = data[pos:pos + 20] |
1407 | | - |
1408 | | - (idx, name, group) = struct.unpack('<Bx16sH', payload) |
1409 | | - name = name.decode('utf-8').replace('\0', '') |
1410 | | - group = 16 - format(group, '016b').index('1') |
| 1415 | + try: |
| 1416 | + (num,) = struct.unpack('<H', data[7:9]) |
| 1417 | + self.__logger.debug('Number of scenes: %d', num) |
| 1418 | + if len(data) < 8 + num * 20: |
| 1419 | + raise struct.error('Incorrect data length for {} records:' |
| 1420 | + ' {}'.format(num, len(data))) |
| 1421 | + |
| 1422 | + new_scenes = {} |
| 1423 | + for i in range(0, num): |
| 1424 | + pos = 9 + i * 20 |
| 1425 | + payload = data[pos:pos + 20] |
| 1426 | + self.__logger.debug('Scene payload: %d', i) |
| 1427 | + |
| 1428 | + (idx, name, group) = struct.unpack('<Bx16sH', payload) |
| 1429 | + name = name.decode('utf-8').replace('\0', '') |
| 1430 | + group = 16 - format(group, '016b').index('1') |
| 1431 | + |
| 1432 | + if (name in self.__scenes and |
| 1433 | + self.__scenes[name].idx() == idx and |
| 1434 | + self.__scenes[name].group() == group): |
| 1435 | + scene = self.__scenes[name] |
| 1436 | + self.__logger.debug('Old scene %d: %s, group: %d', idx, |
| 1437 | + name, group) |
| 1438 | + else: |
| 1439 | + scene = Scene(self, idx, name, group) |
| 1440 | + self.__logger.debug('New scene %d: %s, group: %d', idx, |
| 1441 | + name, group) |
1411 | 1442 |
|
1412 | | - if (name in self.__scenes and |
1413 | | - self.__scenes[name].idx() == idx and |
1414 | | - self.__scenes[name].group() == group): |
1415 | | - scene = self.__scenes[name] |
1416 | | - self.__logger.debug('Old scene %d: %s, group: %d', idx, |
1417 | | - name, group) |
1418 | | - else: |
1419 | | - scene = Scene(self, idx, name, group) |
1420 | | - self.__logger.debug('New scene %d: %s, group: %d', idx, |
1421 | | - name, group) |
| 1443 | + new_scenes[name] = scene |
1422 | 1444 |
|
1423 | | - new_scenes[name] = scene |
| 1445 | + except (struct.error, UnicodeDecodeError, ValueError) as err: |
| 1446 | + self.__logger.error('Couldn\'t parse scene status: %s', err) |
| 1447 | + self.__logger.error('Data: %s', binascii.hexlify(data)) |
| 1448 | + return {} |
1424 | 1449 |
|
1425 | 1450 | for name in self.__scenes: |
1426 | 1451 | if (name not in new_scenes or |
@@ -1541,74 +1566,75 @@ def update_all_light_status(self, throttling_interval=None): |
1541 | 1566 | self.__lights_updated = time.time() |
1542 | 1567 | return {} |
1543 | 1568 |
|
1544 | | - (num,) = struct.unpack('<H', data[7:9]) |
1545 | | - self.__logger.debug('Number of lights: %d', num) |
1546 | | - new_lights = {} |
1547 | | - |
1548 | | - for i in range(0, num): |
1549 | | - pos = 9 + i * 50 |
1550 | | - payload = data[pos:pos + 50] |
1551 | | - self.__logger.debug('Light payload: %d %d %d', i, pos, |
1552 | | - len(payload)) |
| 1569 | + try: |
| 1570 | + (num,) = struct.unpack('<H', data[7:9]) |
| 1571 | + self.__logger.debug('Number of lights: %d', num) |
| 1572 | + if len(data) < 8 + num * 50: |
| 1573 | + raise struct.error('Incorrect data length for {} records:' |
| 1574 | + ' {}'.format(num, len(data))) |
| 1575 | + |
| 1576 | + new_lights = {} |
| 1577 | + for i in range(0, num): |
| 1578 | + pos = 9 + i * 50 |
| 1579 | + payload = data[pos:pos + 50] |
| 1580 | + self.__logger.debug('Light payload: %d', i) |
1553 | 1581 |
|
1554 | | - try: |
1555 | 1582 | (addr, stat, name, last_seen) = struct.unpack( |
1556 | 1583 | '<2xQ16s16sI4x', payload) |
1557 | | - except struct.error as err: |
1558 | | - self.__logger.warning( |
1559 | | - 'Couldn\'t unpack light status packet:') |
1560 | | - self.__logger.warning('struct.error: %s', err) |
1561 | | - self.__logger.warning('payload: %s', |
1562 | | - binascii.hexlify(payload)) |
1563 | | - return {} |
1564 | | - |
1565 | | - (type_id, version, reachable, groups, onoff, lum, temp, red, |
1566 | | - green, blue, alpha) = struct.unpack('<B4sBH2BH4B', stat) |
1567 | | - name = name.decode('utf-8').replace('\0', '') |
1568 | | - groups = [16 - j for j, val |
1569 | | - in enumerate(format(groups, '016b')) if val == '1'] |
1570 | | - version = format(struct.unpack('>I', version)[0], '032b') |
1571 | | - version = ''.join('{0:01X}'.format( |
1572 | | - int(version[i * 4:(i + 1) * 4], 2)) for i in range(8)) |
1573 | | - |
1574 | | - if addr in self.__lights: |
1575 | | - light = self.__lights[addr] |
1576 | | - self.__logger.debug('Old light: %x', addr) |
1577 | | - else: |
1578 | | - if type_id not in self.__device_types: |
1579 | | - self.__logger.warning( |
1580 | | - 'Unknown device type id: %s. Please report to ' |
1581 | | - 'https://github.com/tfriedel/python-lightify', |
1582 | | - type_id) |
1583 | | - if (red, green, blue) == NO_RGB_VALUES: |
1584 | | - type_id_assumed = TYPE_LIGHT_TUNABLE_WHITE |
1585 | | - else: |
1586 | | - type_id_assumed = TYPE_LIGHT_RGBW |
| 1584 | + (type_id, version, reachable, groups, |
| 1585 | + onoff, lum, temp, red, |
| 1586 | + green, blue, alpha) = struct.unpack('<B4sBH2BH4B', stat) |
| 1587 | + name = name.decode('utf-8').replace('\0', '') |
| 1588 | + groups = [16 - j for j, val |
| 1589 | + in enumerate(format(groups, '016b')) |
| 1590 | + if val == '1'] |
| 1591 | + version = format(struct.unpack('>I', version)[0], '032b') |
| 1592 | + version = ''.join('{0:01X}'.format( |
| 1593 | + int(version[i * 4:(i + 1) * 4], 2)) for i in range(8)) |
| 1594 | + |
| 1595 | + if addr in self.__lights: |
| 1596 | + light = self.__lights[addr] |
| 1597 | + self.__logger.debug('Old light: %x', addr) |
1587 | 1598 | else: |
1588 | | - type_id_assumed = type_id |
1589 | | - |
1590 | | - light = Light(self, addr, type_id, type_id_assumed) |
1591 | | - self.__logger.debug('New light: %x', addr) |
1592 | | - |
1593 | | - self.__logger.debug('name: %s', name) |
1594 | | - self.__logger.debug('reachable: %d', reachable) |
1595 | | - self.__logger.debug('last seen: %d', last_seen) |
1596 | | - self.__logger.debug('onoff: %d', onoff) |
1597 | | - self.__logger.debug('lum: %d', lum) |
1598 | | - self.__logger.debug('temp: %d', temp) |
1599 | | - self.__logger.debug('red: %d', red) |
1600 | | - self.__logger.debug('green: %d', green) |
1601 | | - self.__logger.debug('blue: %d', blue) |
1602 | | - self.__logger.debug('alpha: %d', alpha) |
1603 | | - self.__logger.debug('type id: %d', type_id) |
1604 | | - self.__logger.debug('groups: %s', groups) |
1605 | | - self.__logger.debug('version: %s', version) |
1606 | | - self.__logger.debug('idx: %s', i) |
1607 | | - |
1608 | | - light.update_status(reachable, last_seen, onoff, lum, temp, |
1609 | | - red, green, blue, alpha, name, groups, |
1610 | | - version, i) |
1611 | | - new_lights[addr] = light |
| 1599 | + if type_id not in self.__device_types: |
| 1600 | + self.__logger.warning( |
| 1601 | + 'Unknown device type id: %s. Please report to ' |
| 1602 | + 'https://github.com/tfriedel/python-lightify', |
| 1603 | + type_id) |
| 1604 | + if (red, green, blue) == NO_RGB_VALUES: |
| 1605 | + type_id_assumed = TYPE_LIGHT_TUNABLE_WHITE |
| 1606 | + else: |
| 1607 | + type_id_assumed = TYPE_LIGHT_RGBW |
| 1608 | + else: |
| 1609 | + type_id_assumed = type_id |
| 1610 | + |
| 1611 | + light = Light(self, addr, type_id, type_id_assumed) |
| 1612 | + self.__logger.debug('New light: %x', addr) |
| 1613 | + |
| 1614 | + self.__logger.debug('name: %s', name) |
| 1615 | + self.__logger.debug('reachable: %d', reachable) |
| 1616 | + self.__logger.debug('last seen: %d', last_seen) |
| 1617 | + self.__logger.debug('onoff: %d', onoff) |
| 1618 | + self.__logger.debug('lum: %d', lum) |
| 1619 | + self.__logger.debug('temp: %d', temp) |
| 1620 | + self.__logger.debug('red: %d', red) |
| 1621 | + self.__logger.debug('green: %d', green) |
| 1622 | + self.__logger.debug('blue: %d', blue) |
| 1623 | + self.__logger.debug('alpha: %d', alpha) |
| 1624 | + self.__logger.debug('type id: %d', type_id) |
| 1625 | + self.__logger.debug('groups: %s', groups) |
| 1626 | + self.__logger.debug('version: %s', version) |
| 1627 | + self.__logger.debug('idx: %s', i) |
| 1628 | + |
| 1629 | + light.update_status(reachable, last_seen, onoff, lum, temp, |
| 1630 | + red, green, blue, alpha, name, groups, |
| 1631 | + version, i) |
| 1632 | + new_lights[addr] = light |
| 1633 | + |
| 1634 | + except (struct.error, UnicodeDecodeError) as err: |
| 1635 | + self.__logger.error('Couldn\'t parse light status: %s', err) |
| 1636 | + self.__logger.error('Data: %s', binascii.hexlify(data)) |
| 1637 | + return {} |
1612 | 1638 |
|
1613 | 1639 | for addr in self.__lights: |
1614 | 1640 | if addr not in new_lights: |
|
0 commit comments