From 59fb844a402f58d50b415bb9f1597f1eacb96a15 Mon Sep 17 00:00:00 2001 From: Aleksey Makarenko Date: Tue, 3 Mar 2020 20:31:59 +0300 Subject: [PATCH 01/32] Initial commit --- custom_components/mitemp_bt/manifest.json | 2 +- custom_components/mitemp_bt/sensor.py | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/custom_components/mitemp_bt/manifest.json b/custom_components/mitemp_bt/manifest.json index 71334f19e..367f21f05 100644 --- a/custom_components/mitemp_bt/manifest.json +++ b/custom_components/mitemp_bt/manifest.json @@ -2,7 +2,7 @@ "domain": "mitemp_bt", "name": "Xiaomi passive BLE monitor sensor integration", "documentation": "https://github.com/custom-components/sensor.mitemp_bt", - "requirements": ["aioblescan==0.2.4"], + "requirements": ["aioblescan>=0.2.4", "pycryptodomex>=3.9.7"], "dependencies": [], "codeowners": [ "@Magalex2x14", diff --git a/custom_components/mitemp_bt/sensor.py b/custom_components/mitemp_bt/sensor.py index 4f18c04cc..dfec5c8a7 100644 --- a/custom_components/mitemp_bt/sensor.py +++ b/custom_components/mitemp_bt/sensor.py @@ -8,6 +8,7 @@ from time import sleep import aioblescan as aiobs +from Cryptodome.Cipher import AES import voluptuous as vol from homeassistant.const import ( From 13f5d3bc4b720be8d9f0acad49d6952cd60bc834 Mon Sep 17 00:00:00 2001 From: Aleksey Makarenko Date: Wed, 4 Mar 2020 01:11:40 +0300 Subject: [PATCH 02/32] New option "encryptors" (dictionary with MAC:key pairs). Where MAC - mac-address of the sensor, key - AES128 (16bytes) key. Both the poppy address and the key are checked for validity: key length, a-f 0-9 characters, mac address format. --- custom_components/mitemp_bt/const.py | 1 + custom_components/mitemp_bt/sensor.py | 11 +++++++++++ 2 files changed, 12 insertions(+) diff --git a/custom_components/mitemp_bt/const.py b/custom_components/mitemp_bt/const.py index a76b7f270..d94b1a63e 100644 --- a/custom_components/mitemp_bt/const.py +++ b/custom_components/mitemp_bt/const.py @@ -9,6 +9,7 @@ CONF_ACTIVE_SCAN = "active_scan" CONF_HCI_INTERFACE = "hci_interface" CONF_BATT_ENTITIES = "batt_entities" +CONF_ENCRYPTORS = "encryptors" # Default values for configuration options DEFAULT_ROUNDING = True diff --git a/custom_components/mitemp_bt/sensor.py b/custom_components/mitemp_bt/sensor.py index dfec5c8a7..53fbb5458 100644 --- a/custom_components/mitemp_bt/sensor.py +++ b/custom_components/mitemp_bt/sensor.py @@ -41,6 +41,7 @@ CONF_ACTIVE_SCAN, CONF_HCI_INTERFACE, CONF_BATT_ENTITIES, + CONF_ENCRYPTORS, CONF_TMIN, CONF_TMAX, CONF_HMIN, @@ -51,6 +52,15 @@ _LOGGER = logging.getLogger(__name__) +MAC_REGEX = "(?i)^(?:[0-9A-F]{2}[:]){5}(?:[0-9A-F]{2})$" +AES128KEY_REGEX = "(?i)^[A-F0-9]+$" + +ENCRYPTORS_LIST_SCHEMA = vol.Schema( + { + cv.matches_regex(MAC_REGEX): cv.matches_regex(AES128KEY_REGEX) + } +) + PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( { vol.Optional(CONF_ROUNDING, default=DEFAULT_ROUNDING): cv.boolean, @@ -63,6 +73,7 @@ CONF_HCI_INTERFACE, default=[DEFAULT_HCI_INTERFACE] ): vol.All(cv.ensure_list, [cv.positive_int]), vol.Optional(CONF_BATT_ENTITIES, default=DEFAULT_BATT_ENTITIES): cv.boolean, + vol.Optional(CONF_ENCRYPTORS, default={}): ENCRYPTORS_LIST_SCHEMA } ) From ad24dc5be0b1a39c3d03acf6abecda761d212d0c Mon Sep 17 00:00:00 2001 From: Aleksey Makarenko Date: Thu, 5 Mar 2020 01:16:41 +0300 Subject: [PATCH 03/32] BLE ADV decryption debug --- custom_components/mitemp_bt/const.py | 16 +++--- custom_components/mitemp_bt/sensor.py | 83 ++++++++++++++++++++++++--- 2 files changed, 85 insertions(+), 14 deletions(-) diff --git a/custom_components/mitemp_bt/const.py b/custom_components/mitemp_bt/const.py index d94b1a63e..3ea07feae 100644 --- a/custom_components/mitemp_bt/const.py +++ b/custom_components/mitemp_bt/const.py @@ -32,12 +32,13 @@ # Xiaomi sensor types dictionary with offset for adv parser XIAOMI_TYPE_DICT = { - b'\x20\x98\x00': ["HHCCJCY01", 1], - b'\x20\xAA\x01': ["LYWSDCGQ", 0], - b'\x20\x5B\x04': ["LYWSD02", 1], - b'\x30\x47\x03': ["CGG1", 0], - b'\x20\x5D\x01': ["HHCCPOT002", 1], - b'\x20\xBC\x03': ["GCLS002", 1] + b'\x98\x00': ["HHCCJCY01", 1], + b'\xAA\x01': ["LYWSDCGQ", 0], + b'\x5B\x04': ["LYWSD02", 1], + b'\x47\x03': ["CGG1", 0], + b'\x5D\x01': ["HHCCPOT002", 1], + b'\xBC\x03': ["GCLS002", 1], + b'\x5B\x05': ["LYWSD03MMC", 0], } @@ -50,5 +51,6 @@ 'HHCCPOT002': [9, 9, 0, 1, 9, 9], 'LYWSDCGQ' : [0, 1, 9, 9, 9, 2], 'LYWSD02' : [0, 1, 9, 9, 9, 9], - 'CGG1' : [0, 1, 9, 9, 9, 2] + 'CGG1' : [0, 1, 9, 9, 9, 2], + 'LYWSD03MMC': [0, 1, 9, 9, 9, 2] } diff --git a/custom_components/mitemp_bt/sensor.py b/custom_components/mitemp_bt/sensor.py index 53fbb5458..e062df771 100644 --- a/custom_components/mitemp_bt/sensor.py +++ b/custom_components/mitemp_bt/sensor.py @@ -174,9 +174,27 @@ def parse_xiaomi_value(hexvalue, typecode): return {"illuminance": illum} return {} +def decrypt_payload(encrypted_payload, key, nonce): + """Decrypt payload""" + aad = b"\x11" + token = encrypted_payload[-4:] + payload_counter = encrypted_payload[-7:-4] + nonce = b"".join([nonce, payload_counter]) + cipherpayload = encrypted_payload[:-7] + cipher = AES.new(key, AES.MODE_CCM, nonce=nonce, mac_len = 4) + cipher.update(aad) + try: + plaindata = cipher.decrypt_and_verify(cipherpayload, token) + except ValueError as error: + _LOGGER.debug("Decryption failed, %s", error) + _LOGGER.debug("Token: %s", token.hex()) + _LOGGER.debug("nonce: %s", nonce.hex()) + _LOGGER.debug("encrypted_payload: %s", encrypted_payload.hex()) + _LOGGER.debug("cipherpayload: %s", cipherpayload.hex()) + return None + return plaindata - -def parse_raw_message(data): +def parse_raw_message(data, aeskeyslist): """Parse the raw data.""" if data is None: return None @@ -203,7 +221,7 @@ def parse_raw_message(data): return None try: sensor_type, toffset = XIAOMI_TYPE_DICT[ - data[xiaomi_index + 4:xiaomi_index + 7] + data[xiaomi_index + 5:xiaomi_index + 7] ] except KeyError: _LOGGER.debug( @@ -233,7 +251,34 @@ def parse_raw_message(data): "type": sensor_type, "packet": packet_id, } - + #Frame control bits + framectrl, = struct.unpack(' Date: Thu, 5 Mar 2020 01:33:34 +0300 Subject: [PATCH 04/32] Strange indwx error debug --- custom_components/mitemp_bt/sensor.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/custom_components/mitemp_bt/sensor.py b/custom_components/mitemp_bt/sensor.py index e062df771..d9533b980 100644 --- a/custom_components/mitemp_bt/sensor.py +++ b/custom_components/mitemp_bt/sensor.py @@ -240,7 +240,7 @@ def parse_raw_message(data, aeskeyslist): if xdata_length < 3: return None xdata_point = xiaomi_index + (14 + toffset) - xnext_point = xdata_point + 3 + #xnext_point = xdata_point + 3 # check if xiaomi data start and length is valid if xdata_length != len(data[xdata_point:-1]): return None @@ -290,6 +290,15 @@ def parse_raw_message(data, aeskeyslist): _LOGGER.error("xvalue_length conv. error: %s", error) result = {} break + except IndexError as error: + _LOGGER.error("xvalue_length conv. error: %s", error) + result = {} + _LOGGER.error(data.hex()) + _LOGGER.error("xdata_point %s", xdata_point) + _LOGGER.error("xvalue_typecode %s", xvalue_typecode) + _LOGGER.error("msg_length %s", msg_length) + _LOGGER.error("xdata_length %s", xdata_length) + break xnext_point = xdata_point + 3 + xvalue_length xvalue = data[xdata_point + 3:xnext_point] res = parse_xiaomi_value(xvalue, xvalue_typecode) From fb739e3341551ac7f1aba5bbd30e1a619e8e3500 Mon Sep 17 00:00:00 2001 From: Aleksey Makarenko Date: Thu, 5 Mar 2020 05:27:18 +0300 Subject: [PATCH 05/32] index_error fixed, precursive debug cleanup --- custom_components/mitemp_bt/sensor.py | 52 ++++++++++++++------------- 1 file changed, 28 insertions(+), 24 deletions(-) diff --git a/custom_components/mitemp_bt/sensor.py b/custom_components/mitemp_bt/sensor.py index d9533b980..4aebeac4b 100644 --- a/custom_components/mitemp_bt/sensor.py +++ b/custom_components/mitemp_bt/sensor.py @@ -186,11 +186,11 @@ def decrypt_payload(encrypted_payload, key, nonce): try: plaindata = cipher.decrypt_and_verify(cipherpayload, token) except ValueError as error: - _LOGGER.debug("Decryption failed, %s", error) - _LOGGER.debug("Token: %s", token.hex()) - _LOGGER.debug("nonce: %s", nonce.hex()) - _LOGGER.debug("encrypted_payload: %s", encrypted_payload.hex()) - _LOGGER.debug("cipherpayload: %s", cipherpayload.hex()) + _LOGGER.debug("Decryption failed: %s", error) + #_LOGGER.debug("Token: %s", token.hex()) + #_LOGGER.debug("nonce: %s", nonce.hex()) + #_LOGGER.debug("encrypted_payload: %s", encrypted_payload.hex()) + #_LOGGER.debug("cipherpayload: %s", cipherpayload.hex()) return None return plaindata @@ -224,9 +224,9 @@ def parse_raw_message(data, aeskeyslist): data[xiaomi_index + 5:xiaomi_index + 7] ] except KeyError: - _LOGGER.debug( - "Unknown sensor type: %s", ''.join('{:02x}'.format(x) for x in data[xiaomi_index + 4:xiaomi_index + 7]), - ) + #_LOGGER.debug( + # "Unknown sensor type: %s", ''.join('{:02x}'.format(x) for x in data[xiaomi_index + 4:xiaomi_index + 7]), + #) return None # xiaomi data length = message length # -all bytes before XiaomiUUID @@ -257,10 +257,10 @@ def parse_raw_message(data, aeskeyslist): if framectrl & 0x4800: #try to find encryption key for current device try: - key = aeskeyslist[xiaomi_mac_reversed.hex().upper()] + key = aeskeyslist[xiaomi_mac_reversed.hex()] except KeyError: - _LOGGER.debug("No encryption key found for %s", xiaomi_mac_reversed.hex().upper()) - _LOGGER.debug(aeskeyslist) + #_LOGGER.debug("No encryption key found for %s", xiaomi_mac_reversed.hex().upper()) + #_LOGGER.debug(aeskeyslist) return None nonce = b"".join( [ @@ -275,10 +275,15 @@ def parse_raw_message(data, aeskeyslist): if decrypted_payload is None: _LOGGER.debug("Decryption failed for %s", result["mac"]) return None + #_LOGGER.error(decrypted_payload.hex()) + #_LOGGER.error(data.hex()) + #_LOGGER.error("orig msg_length %s", msg_length) #replace cipher with decrypted data - data = b"".join((data[:xdata_point],decrypted_payload,data[-1:])) - xdata_length = len(decrypted_payload) msg_length -= len(data[xdata_point:msg_length-1]) + data = b"".join((data[:xdata_point],decrypted_payload,data[-1:])) + msg_length += len(decrypted_payload) + #_LOGGER.error(data.hex()) + #_LOGGER.error("decr msg_length %s", msg_length) # loop through xiaomi payload # assume that the data may have several values of different types, # although I did not notice this behavior with my LYWSDCGQ sensors @@ -288,16 +293,15 @@ def parse_raw_message(data, aeskeyslist): xvalue_length = data[xdata_point + 2] except ValueError as error: _LOGGER.error("xvalue_length conv. error: %s", error) + _LOGGER.error("xdata_point: %s", xdata_point) + _LOGGER.error("data: %s", data.hex()) result = {} break except IndexError as error: - _LOGGER.error("xvalue_length conv. error: %s", error) + _LOGGER.error("Wrong xdata_point: %s", error) + _LOGGER.error("xdata_point: %s", xdata_point) + _LOGGER.error("data: %s", data.hex()) result = {} - _LOGGER.error(data.hex()) - _LOGGER.error("xdata_point %s", xdata_point) - _LOGGER.error("xvalue_typecode %s", xvalue_typecode) - _LOGGER.error("msg_length %s", msg_length) - _LOGGER.error("xdata_length %s", xdata_length) break xnext_point = xdata_point + 3 + xvalue_length xvalue = data[xdata_point + 3:xnext_point] @@ -365,14 +369,14 @@ def reverse_mac(rmac): hass.bus.listen("homeassistant_stop", scanner.shutdown_handler) scanner.start(config) sensors_by_mac = {} - #prepare device:key list + #prepare device:key list to speedup parser aeskeys = {} for mac in config[CONF_ENCRYPTORS]: - aeskeys[reverse_mac(mac.replace(":", ""))] = bytes.fromhex( - config[CONF_ENCRYPTORS][mac] - ) + p_mac = reverse_mac(mac.replace(":", "")).lower() + p_key = bytes.fromhex(config[CONF_ENCRYPTORS][mac].lower()) + aeskeys[p_mac] = p_key sleep(1) - _LOGGER.debug(aeskeys) + #_LOGGER.debug(aeskeys) def calc_update_state(entity_to_update, sensor_mac, config, measurements_list): """Averages according to options and updates the entity state.""" From d66e5445543627614b46bb6839cf6582179c8cba Mon Sep 17 00:00:00 2001 From: Aleksey Makarenko Date: Thu, 5 Mar 2020 05:28:02 +0300 Subject: [PATCH 06/32] Fix for "Attribute hass is None for..."? --- custom_components/mitemp_bt/sensor.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/custom_components/mitemp_bt/sensor.py b/custom_components/mitemp_bt/sensor.py index 4aebeac4b..c0699bcf5 100644 --- a/custom_components/mitemp_bt/sensor.py +++ b/custom_components/mitemp_bt/sensor.py @@ -407,7 +407,7 @@ def calc_update_state(entity_to_update, sensor_mac, config, measurements_list): "median" ] = state_median getattr(entity_to_update, "_device_state_attributes")["mean"] = state_mean - entity_to_update.async_schedule_update_ha_state() + entity_to_update.schedule_update_ha_state() success = True except AttributeError: _LOGGER.debug("Sensor %s not yet ready for update", sensor_mac) @@ -550,7 +550,7 @@ def discover_ble_devices(config, aeskeyslist): if config[CONF_BATT_ENTITIES]: try: setattr(sensors[b_i], "_state", batt[mac]) - sensors[b_i].async_schedule_update_ha_state() + sensors[b_i].schedule_update_ha_state() except AttributeError: _LOGGER.debug("BatterySensor %s not yet ready for update", mac) except RuntimeError as err: From 54628017eca86d8c8477fc97e8905ba3092723c4 Mon Sep 17 00:00:00 2001 From: Aleksey Makarenko Date: Thu, 5 Mar 2020 06:21:53 +0300 Subject: [PATCH 07/32] Sensors batt attribute must be updated even if there is no corresponding measurement in the current period --- custom_components/mitemp_bt/sensor.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/custom_components/mitemp_bt/sensor.py b/custom_components/mitemp_bt/sensor.py index c0699bcf5..be3e6a260 100644 --- a/custom_components/mitemp_bt/sensor.py +++ b/custom_components/mitemp_bt/sensor.py @@ -562,6 +562,19 @@ def discover_ble_devices(config, aeskeyslist): getattr(sensor, "_device_state_attributes")[ ATTR_BATTERY_LEVEL ] = batt[mac] + try: + sensor.schedule_update_ha_state() + except AttributeError: + _LOGGER.debug( + "Sensor %s (%s, temp.) not yet ready for update", + mac, + stype[mac] + ) + except RuntimeError as err: + _LOGGER.error( + "Sensor %s (%s, batt.attr.) update error:", mac, stype[mac] + ) + _LOGGER.error(error) # averaging and states updating if mac in temp_m_data: success, error = calc_update_state( From 61c9585205a34bd419c55ce1b10b50bd96b8d682 Mon Sep 17 00:00:00 2001 From: Aleksey Makarenko Date: Thu, 5 Mar 2020 16:32:45 +0300 Subject: [PATCH 08/32] Seems working now... Extensive testing needed --- custom_components/mitemp_bt/sensor.py | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/custom_components/mitemp_bt/sensor.py b/custom_components/mitemp_bt/sensor.py index be3e6a260..00471c093 100644 --- a/custom_components/mitemp_bt/sensor.py +++ b/custom_components/mitemp_bt/sensor.py @@ -181,16 +181,17 @@ def decrypt_payload(encrypted_payload, key, nonce): payload_counter = encrypted_payload[-7:-4] nonce = b"".join([nonce, payload_counter]) cipherpayload = encrypted_payload[:-7] + #_LOGGER.error(len(cipherpayload)) cipher = AES.new(key, AES.MODE_CCM, nonce=nonce, mac_len = 4) cipher.update(aad) try: plaindata = cipher.decrypt_and_verify(cipherpayload, token) except ValueError as error: - _LOGGER.debug("Decryption failed: %s", error) - #_LOGGER.debug("Token: %s", token.hex()) - #_LOGGER.debug("nonce: %s", nonce.hex()) - #_LOGGER.debug("encrypted_payload: %s", encrypted_payload.hex()) - #_LOGGER.debug("cipherpayload: %s", cipherpayload.hex()) + _LOGGER.error("Decryption failed: %s", error) + _LOGGER.error("Token: %s", token.hex()) + _LOGGER.error("nonce: %s", nonce.hex()) + _LOGGER.error("encrypted_payload: %s", encrypted_payload.hex()) + _LOGGER.error("cipherpayload: %s", cipherpayload.hex()) return None return plaindata @@ -547,6 +548,7 @@ def discover_ble_devices(config, aeskeyslist): ) getattr(sensor, "_device_state_attributes")["sensor type"] = stype[mac] if mac in batt: + #_LOGGER.error("Battery %s", mac) if config[CONF_BATT_ENTITIES]: try: setattr(sensors[b_i], "_state", batt[mac]) @@ -556,6 +558,9 @@ def discover_ble_devices(config, aeskeyslist): except RuntimeError as err: _LOGGER.debug("BatterySensor %s not yet ready for update:", mac) _LOGGER.debug(err) + # redundant schedule_update_ha_state, +                # but guarantees that the attribute will be updated in all entities, +                # even if the corresponding measurement is not in the current data for sensor in sensors: if isinstance(sensor, BatterySensor): continue @@ -566,7 +571,7 @@ def discover_ble_devices(config, aeskeyslist): sensor.schedule_update_ha_state() except AttributeError: _LOGGER.debug( - "Sensor %s (%s, temp.) not yet ready for update", + "Sensor %s (%s, batt.attr.) not yet ready for update", mac, stype[mac] ) From b9462f6a58e17bf6de111498c948b6ede0d6755e Mon Sep 17 00:00:00 2001 From: Aleksey Makarenko Date: Thu, 5 Mar 2020 16:49:22 +0300 Subject: [PATCH 09/32] invalid characters removed --- custom_components/mitemp_bt/sensor.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/custom_components/mitemp_bt/sensor.py b/custom_components/mitemp_bt/sensor.py index 00471c093..7d1f702c2 100644 --- a/custom_components/mitemp_bt/sensor.py +++ b/custom_components/mitemp_bt/sensor.py @@ -559,8 +559,8 @@ def discover_ble_devices(config, aeskeyslist): _LOGGER.debug("BatterySensor %s not yet ready for update:", mac) _LOGGER.debug(err) # redundant schedule_update_ha_state, -                # but guarantees that the attribute will be updated in all entities, -                # even if the corresponding measurement is not in the current data + # but guarantees that the attribute will be updated in all entities, + # even if the corresponding measurement is not in the current data for sensor in sensors: if isinstance(sensor, BatterySensor): continue From 51eb3e51064729499a5c15f6d0040bcc9c6353bf Mon Sep 17 00:00:00 2001 From: Aleksey Makarenko Date: Fri, 6 Mar 2020 12:31:01 +0300 Subject: [PATCH 10/32] Optimizations and comments cleanup --- custom_components/mitemp_bt/sensor.py | 101 ++++++++++---------------- 1 file changed, 38 insertions(+), 63 deletions(-) diff --git a/custom_components/mitemp_bt/sensor.py b/custom_components/mitemp_bt/sensor.py index 7d1f702c2..6d917e114 100644 --- a/custom_components/mitemp_bt/sensor.py +++ b/custom_components/mitemp_bt/sensor.py @@ -52,6 +52,7 @@ _LOGGER = logging.getLogger(__name__) +# regex constants for configuration schema MAC_REGEX = "(?i)^(?:[0-9A-F]{2}[:]){5}(?:[0-9A-F]{2})$" AES128KEY_REGEX = "(?i)^[A-F0-9]+$" @@ -225,9 +226,6 @@ def parse_raw_message(data, aeskeyslist): data[xiaomi_index + 5:xiaomi_index + 7] ] except KeyError: - #_LOGGER.debug( - # "Unknown sensor type: %s", ''.join('{:02x}'.format(x) for x in data[xiaomi_index + 4:xiaomi_index + 7]), - #) return None # xiaomi data length = message length # -all bytes before XiaomiUUID @@ -241,7 +239,6 @@ def parse_raw_message(data, aeskeyslist): if xdata_length < 3: return None xdata_point = xiaomi_index + (14 + toffset) - #xnext_point = xdata_point + 3 # check if xiaomi data start and length is valid if xdata_length != len(data[xdata_point:-1]): return None @@ -258,10 +255,9 @@ def parse_raw_message(data, aeskeyslist): if framectrl & 0x4800: #try to find encryption key for current device try: - key = aeskeyslist[xiaomi_mac_reversed.hex()] + key = aeskeyslist[xiaomi_mac_reversed] except KeyError: - #_LOGGER.debug("No encryption key found for %s", xiaomi_mac_reversed.hex().upper()) - #_LOGGER.debug(aeskeyslist) + #No encryption key found return None nonce = b"".join( [ @@ -276,15 +272,10 @@ def parse_raw_message(data, aeskeyslist): if decrypted_payload is None: _LOGGER.debug("Decryption failed for %s", result["mac"]) return None - #_LOGGER.error(decrypted_payload.hex()) - #_LOGGER.error(data.hex()) - #_LOGGER.error("orig msg_length %s", msg_length) #replace cipher with decrypted data msg_length -= len(data[xdata_point:msg_length-1]) data = b"".join((data[:xdata_point],decrypted_payload,data[-1:])) msg_length += len(decrypted_payload) - #_LOGGER.error(data.hex()) - #_LOGGER.error("decr msg_length %s", msg_length) # loop through xiaomi payload # assume that the data may have several values of different types, # although I did not notice this behavior with my LYWSDCGQ sensors @@ -337,7 +328,6 @@ def start(self, config): _LOGGER.debug("Starting HCIdump thread for hci%s", hci_int) dumpthread.start() _LOGGER.debug("HCIdump threads count = %s", len(self.dumpthreads)) - def stop(self): """Stop HCIdump thread(s).""" @@ -373,7 +363,7 @@ def reverse_mac(rmac): #prepare device:key list to speedup parser aeskeys = {} for mac in config[CONF_ENCRYPTORS]: - p_mac = reverse_mac(mac.replace(":", "")).lower() + p_mac = bytes.fromhex(reverse_mac(mac.replace(":", "")).lower()) p_key = bytes.fromhex(config[CONF_ENCRYPTORS][mac].lower()) aeskeys[p_mac] = p_key sleep(1) @@ -408,7 +398,7 @@ def calc_update_state(entity_to_update, sensor_mac, config, measurements_list): "median" ] = state_median getattr(entity_to_update, "_device_state_attributes")["mean"] = state_mean - entity_to_update.schedule_update_ha_state() + #entity_to_update.schedule_update_ha_state() success = True except AttributeError: _LOGGER.debug("Sensor %s not yet ready for update", sensor_mac) @@ -538,48 +528,6 @@ def discover_ble_devices(config, aeskeyslist): continue sensors_by_mac[mac] = sensors add_entities(sensors) - # append joint attributes - for sensor in sensors: - getattr(sensor, "_device_state_attributes")["last packet id"] = lpacket[ - mac - ] - getattr(sensor, "_device_state_attributes")["rssi"] = round( - sts.mean(rssi[mac]) - ) - getattr(sensor, "_device_state_attributes")["sensor type"] = stype[mac] - if mac in batt: - #_LOGGER.error("Battery %s", mac) - if config[CONF_BATT_ENTITIES]: - try: - setattr(sensors[b_i], "_state", batt[mac]) - sensors[b_i].schedule_update_ha_state() - except AttributeError: - _LOGGER.debug("BatterySensor %s not yet ready for update", mac) - except RuntimeError as err: - _LOGGER.debug("BatterySensor %s not yet ready for update:", mac) - _LOGGER.debug(err) - # redundant schedule_update_ha_state, - # but guarantees that the attribute will be updated in all entities, - # even if the corresponding measurement is not in the current data - for sensor in sensors: - if isinstance(sensor, BatterySensor): - continue - getattr(sensor, "_device_state_attributes")[ - ATTR_BATTERY_LEVEL - ] = batt[mac] - try: - sensor.schedule_update_ha_state() - except AttributeError: - _LOGGER.debug( - "Sensor %s (%s, batt.attr.) not yet ready for update", - mac, - stype[mac] - ) - except RuntimeError as err: - _LOGGER.error( - "Sensor %s (%s, batt.attr.) update error:", mac, stype[mac] - ) - _LOGGER.error(error) # averaging and states updating if mac in temp_m_data: success, error = calc_update_state( @@ -590,7 +538,6 @@ def discover_ble_devices(config, aeskeyslist): "Sensor %s (%s, temp.) update error:", mac, stype[mac] ) _LOGGER.error(error) - continue if mac in hum_m_data: success, error = calc_update_state( sensors[h_i], mac, config, hum_m_data @@ -598,7 +545,6 @@ def discover_ble_devices(config, aeskeyslist): if not success: _LOGGER.error("Sensor %s (%s, hum.) update error:", mac, stype[mac]) _LOGGER.error(error) - continue if mac in moist_m_data: success, error = calc_update_state( sensors[m_i], mac, config, moist_m_data @@ -608,7 +554,6 @@ def discover_ble_devices(config, aeskeyslist): "Sensor %s (%s, moist.) update error:", mac, stype[mac] ) _LOGGER.error(error) - continue if mac in cond_m_data: success, error = calc_update_state( sensors[c_i], mac, config, cond_m_data @@ -618,7 +563,6 @@ def discover_ble_devices(config, aeskeyslist): "Sensor %s (%s, cond.) update error:", mac, stype[mac] ) _LOGGER.error(error) - continue if mac in illum_m_data: success, error = calc_update_state( sensors[i_i], mac, config, illum_m_data @@ -628,8 +572,39 @@ def discover_ble_devices(config, aeskeyslist): "Sensor %s (%s, illum.) update error:", mac, stype[mac] ) _LOGGER.error(error) - continue - # scanner.start(config) - moved earlier (before dump parser loop) + # append joint attributes + if mac in batt: + #_LOGGER.error("Battery %s", mac) + if config[CONF_BATT_ENTITIES]: + setattr(sensors[b_i], "_state", batt[mac]) + for sensor in sensors: + if isinstance(sensor, BatterySensor): + continue + getattr(sensor, "_device_state_attributes")[ + ATTR_BATTERY_LEVEL + ] = batt[mac] + #states updating + for sensor in sensors: + getattr(sensor, "_device_state_attributes")["last packet id"] = lpacket[ + mac + ] + getattr(sensor, "_device_state_attributes")["rssi"] = round( + sts.mean(rssi[mac]) + ) + getattr(sensor, "_device_state_attributes")["sensor type"] = stype[mac] + try: + sensor.schedule_update_ha_state() + except AttributeError: + _LOGGER.debug( + "Sensor %s (%s) not yet ready for update", + mac, + stype[mac] + ) + except RuntimeError as err: + _LOGGER.error( + "Sensor %s (%s) update error:", mac, stype[mac] + ) + _LOGGER.error(err) _LOGGER.debug( "Finished. Parsed: %i hci events, %i xiaomi devices.", len(hcidump_raw), From e9c9a991abaac69b21fee3681696fcfc665cca0a Mon Sep 17 00:00:00 2001 From: Aleksey Makarenko Date: Fri, 6 Mar 2020 13:59:14 +0300 Subject: [PATCH 11/32] README.md, info.md, faq.md updated --- README.md | 16 ++++++++++++++++ faq.md | 7 ++++++- info.md | 24 ++++++++++++++++++++++-- sensors.jpg | Bin 20362 -> 23492 bytes 4 files changed, 44 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index ceb2f56b8..91f15a8eb 100644 --- a/README.md +++ b/README.md @@ -44,6 +44,10 @@ This custom component is an alternative for the standard build in [mitemp_bt](ht (FlowerPot, RoPot, broadcasts moisture and conductivity, 2 readings per minute, no battery info with firmware v1.2.6) +- LYWSD03MMC + + (small square body, segment LCD, broadcasts temperature and humidity once about a 10 minutes and battery once a hour, advertisements are encrypted, key needed! See [encryptors](#configuration-variables) option. + *The amount of actually received data is highly dependent on the reception conditions (like distance and electromagnetic ambiance), readings numbers are indicated for good RSSI (Received Signal Strength Indicator) of about -75 till -70dBm.* ## HOW TO INSTALL @@ -159,6 +163,18 @@ sensor: (boolean)(Optional) By default, the battery information will be presented only as a sensor attribute called `battery level`. If you set this parameter to `True`, then the battery sensor entity will be additionally created - `sensor.mi_batt_ `. Default value: False +#### encryptors + + (dictionary)(Optional) This option is used to describe the correspondence between the mac-address of the sensor broadcasting encrypted advertisements and the encryption key (32 characters = 16 bytes). The case of characters does not matter. Information on where to get the key [here](https://github.com/custom-components/sensor.mitemp_bt/blob/master/faq.md#my-sensors-ble-advertisements-are-encrypted-how-can-i-get-the-key). Default value: Empty + +```yaml +sensor: + - platform: mitemp_bt + encryptors: + 'A4:C1:38:2F:86:6C': '217C568CF5D22808DA20181502D84C1B' + 'A4:C1:38:D1:61:7D': 'C99D2313182473B38001086FEBF781BD' +``` + ## FREQUENTLY ASKED QUESTIONS Still having questions or issues? Please first have a look on our [Frequently Asked Questions (FAQ) page](faq.md) to see if your question is already answered. There are some useful tips also. diff --git a/faq.md b/faq.md index 25f477436..e8bfd73d3 100644 --- a/faq.md +++ b/faq.md @@ -11,6 +11,7 @@ - [RECEPTION ISSUES](#reception-issues) - [My sensor doesn't receive any readings from my sensors anymore or only occasionally](#my-sensor-doesnt-receive-any-readings-from-my-sensors-anymore-or-only-occasionally) - [How to increase coverage](#how-to-increase-coverage) + - [My sensor's BLE advertisements are encrypted, how can I get the key?](my-sensors-ble-advertisements-are-encrypted-how-can-i-get-the-key) - [OTHER ISSUES](#other-issues) - [Conflicts with other components using the same BT interface](#conflicts-with-other-components-using-the-same-bt-interface) - [My sensor stops receiving updates some time after the system restart](my-sensor-stops-receiving-updates-some-time-after-the-system-restart) @@ -127,6 +128,10 @@ There are several ways to increase coverage: - use multiple spaced BT-dongles. You can experiment with various extension cords (for example, inexpensive [USB-RJ45 extenders](https://sc01.alicdn.com/kf/HTB1q0VKodcnBKNjSZR0q6AFqFXae.jpg) in combination with a regular ethernet cable). - use additional devices with their own BT-interface, and connect them to Home Assistant. For example, it could be another raspberrypi with Home Assistant and our component, connected to the main host using the [remote_homeassistant](https://github.com/lukas-hetzenecker/home-assistant-remote) component, which links multiple Home Assistant instances together. +### My sensor's BLE advertisements are encrypted, how can I get the key? + +At the moment, the only way I know is to get the key from the MiHome application traffic (in violation of the user agreement terms). Instructions for iOS and search for other ways [here](https://github.com/custom-components/sensor.mitemp_bt/issues/7#issuecomment-595327131). + ## OTHER ISSUES ### Conflicts with other components using the same BT interface @@ -136,7 +141,7 @@ A reliable, but not the only way out of this situation (apart from the refusal t ### My sensor stops receiving updates some time after the system restart -Most often, the cause of this is the presence of the bugs in the system components responsible for the BT operation (kernel modules, firmwares, etc). As a rule, in such cases, the corresponding entries appear in the system log. Please carefully review the contents of your `syslog`, and try searching the Internet for a solution - with high probability you are not alone in this. +Most often, the cause of this is the presence of the bugs in the system components responsible for the BT operation (kernel modules, firmwares, etc). As a rule, in such cases, the corresponding entries appear in the system log. Please carefully review the contents of your `syslog`, and try searching the Internet for a solution - with high probability you are not alone in this. Пример решения проблемы - [BT problem, Raspberry PI3 and Hass.io](https://github.com/custom-components/sensor.mitemp_bt/issues/31#issuecomment-595417222) ## DEBUG diff --git a/info.md b/info.md index 0ef019cc9..7b553346e 100644 --- a/info.md +++ b/info.md @@ -8,9 +8,13 @@ {% endif %} {% if installed or pending_update %} -# Changes since 0.5.6 +# Changes since 0.5.7 -- VegTrug Grow Care Garden support (plant sensor, similar to MiFlora HHCCJCY01) +- encrypted BLE ADV payload decryption implemented +- added new option `encryptors` to describe the correspondence between the mac-address of the sensor broadcasting encrypted adverts and the encryption key +- LYWSD03MMC support ("encryptor") +- error "Attribute hass is None for Entity..." fixed +- other minor optimizations and improvements --- {% endif %} @@ -61,6 +65,10 @@ This custom component is an alternative for the standard build in [mitemp_bt](ht (FlowerPot, RoPot, broadcasts moisture and conductivity, 2 readings per minute, no battery info with firmware v1.2.6) +- LYWSD03MMC + + (small square body, segment LCD, broadcasts temperature and humidity once about a 10 minutes and battery once a hour, advertisements are encrypted, key needed! See [encryptors](#configuration-variables) option. + *The amount of actually received data is highly dependent on the reception conditions (like distance and electromagnetic ambiance), readings numbers are indicated for good RSSI (Received Signal Strength Indicator) of about -70dBm till -75dBm.* ## HOW TO INSTALL @@ -174,6 +182,18 @@ sensor: (boolean)(Optional) By default, the battery information will be presented only as a sensor attribute called `battery level`. If you set this parameter to `True`, then the battery sensor entity will be additionally created - `sensor.mi_batt_ `. Default value: False +#### encryptors + + (dictionary)(Optional) This option is used to describe the correspondence between the mac-address of the sensor broadcasting encrypted advertisements and the encryption key (32 characters = 16 bytes). The case of characters does not matter. Information on where to get the key [here](https://github.com/custom-components/sensor.mitemp_bt/blob/master/faq.md#my-sensors-ble-advertisements-are-encrypted-how-can-i-get-the-key). Default value: Empty + +```yaml +sensor: + - platform: mitemp_bt + encryptors: + 'A4:C1:38:2F:86:6C': '217C568CF5D22808DA20181502D84C1B' + 'A4:C1:38:D1:61:7D': 'C99D2313182473B38001086FEBF781BD' +``` + ## FREQUENTLY ASKED QUESTIONS Still having questions or issues? Please first have a look on our [Frequently Asked Questions (FAQ) page](https://github.com/custom-components/sensor.mitemp_bt/blob/master/faq.md) to see if your question is already answered. diff --git a/sensors.jpg b/sensors.jpg index f9f84108a335ea201c24247a8833594bdaa7c9bb..80fba3309d79e8d95c5ae254b7dcd0dfcd1bf899 100644 GIT binary patch delta 3292 zcmaJ@XH-*L7Cs?TM4EzP1c?*{MVcZ_FmaIfco-2SG~r1RG+;!El)zP#-b54xm0lG@ zx}Xwz5g`l+O5h2ECNOA_;)R6V3BEUL=GUyd&RS>h`<=D-Is04t+cBA_)>_p40;S`6 z>RJahj_Ii%*VEJh0G)*bXaE3;`++PTuYiA_5vn(KJl1CW@XZb`=4O2pBt*2%8?&U_(E9ydC=c z_(=%Z&X>s<+gvtbE9`?M_(viprzeftA;)vUL_r9ly2W7avN?6w2!&0(vDs7~S_j@-GRFZD*N9 z7Eg>)mT0B@9(X%+Uglv{WInj52h;m9FSXY-4b>CGHW#2=qBi}uem8^AHpWLeoJfH7 zn0wD$i(4S?uHp;adj{oyK#V88(KVwGePYICtrE62KBGtF04zt>(U0ZO`T&k?oM`u! z$%1T@XL^cGioU;nY8U|#74Ss_==(uzfMefGgh`NExYKfr4FZ}15it2^NxREfvhN)K z2$nhIBzT&ZTmp^1MZnT~OO`D~NpaJfd*85S1av4fJQY1dCGymQ0Lz85uX}>P{xOIE zCg6g7p%z=p5d>t|{Ypr+;m~?Wv41HDnV6u`T_z%9^o=f0#41v2-+r976vJQlq$|;_ zSSD0)6+LiboiP46m9xh^%mI~DG8~71#)Z|(XN>@nn@!@)kO2f3+O1|g zngt_3W^t9UDNGt_0}_@-0IDP&p#S$-9c5X}5UCB~$xD(^&=4LsjO(qV1pzpDtqK82 ziA~tmZc7C8WtGKH$m8*Zwo;O6Dx;Lxa?C!gbTd=#-a{EZL;^bKW7UN+ z@=TM>n;&s2O3LG84CTy{MQSYnod&GOIZ}G-o@mIo_7=_+idLBli7ux`*M>~A^J!$< zNV7bPowFP~xs#~?sh)0`|O^<=@gf#pW~%<0h{ zh7(RTI431j69PJXRMO{zX!kh#7DD$AiC-L~_?5z8I)h;&mpWrnSN4w1-&ILyeCnP- zN`vdX8B${ySh9((^(HXO@MU*sCBeDL?St40D0@9ZKspZVu-b+rz^O|+Og^oI9A~(P zFKUu3f~IWfH22`OLYCEGH<8B^0{ki&B0Bnv$adRmDS|ZRB$fGR{WU$d_P)sBqXp2R z?RRRWx_j8XCI?(*=8_0mxspA_PZ(tp8R|@0#0h9;VVmeqhnm!@2A$Cv3PcUrKk{!w z3OptXK9{OrQW@sdFgs$DS3%%v@H5q+s|T@^$}H7&tnbfGA)oevWXF(6Xcxt|j(Po! z*GpLW_lg0xS9SJjlONd~G2)mn^N-ilOk{&Ilt)itjNCl7_0XoQ{is{y9Z8Ol#D%cWPU>?))+Dek#!f5U1Vm z;-+$8H8jUX3)BbTeS8+HHU!g(xCA&g?C)@%0?k4qI$vk4zGkIxA&bm zvh%;R4wXlMEkCQEj+*ZqD)(z&xc*5;!~@O1(FbtMqyTx`a`!ndv$M-SIPD6c8p03@ zKks;8$*_kvek^iPB`2VC>gn4s=T_9dS|CDjx#Ls_RTr}3LboH}rClT^oR{(`Ibl0b zvQe|OKG_gnW_GvQL8Ud(h4bS1gOh>YfondEakDw8zVz*1j~Nqo&+Id4CG22p!Np5T z0X6oqF3;z+D(s_5uVe-pT}NxVoCn>11TD`w*-qDA*K~~&?vNYvRt~7czH50^3;iX?d^G2 zYxOD!YQqIbOUk2e$dA08=6vJZOKF@;-?*zXe}iOD9inki<5V_Jr$~Q6Knssf6K`ea zwWH^Yhp}1#W0SKj22fMbT6n>iOVwYpFZ+-Mf_FT$Qb>Aq&ZYo#%?Ir63vjn!vSs+- zAdX6ePL7_ZH}tzI$EI?3u|~f|u>%U}I1aR08K?4X7at6$Bp3I7)_emgEJ2u0?r z$j;%V6S+N(t4sA`y>$a~+>S^6%lbe(iO22=9m?e#5k+TixEDb>$_(@1(tj*4tZT~O znC(f-r7LL7{3A*K=K~AnlYSUE1$^tST%GSu*EvJfAL_6?r|ufAR}ki?iFEVFD=s-m z0;9mfHaC6Vv$o@p@?s$TaAR}lys_?N|D9Z1^V*o$(eDZfxL5ImcCMyAwif{=hT?V( zdu9YW*wv9&*Ik{Z)pfV2(=!xn;Jbd5#EFw{PAZdh-uBfBx@y--Q9qj9VLkRwxO8(m zN(W`?)pN^i(C=cn@4K6mMj9zchnOSpUHSJ4&*o0Y3#Z|kw;Orub(zYoLNv_^V9mDC2B9v9Lc%5nDdDFqN8{IEdRAG*h9Vw4xET} ze;so^-HY0rAgF!;=_WS(csXaqeqpM|nnQ)zqAZ``I)xN%k)pNdO@1boN>cnLjyLix! zycnx8jS_Ua)l((oosp*77W$%S+&S%_Jp$lW@^k1b$?>&v=l8m5^3UY|Wgs%RS$Wf- zlpiwePr1L6C1TjBc#M0d9(v~UPPFdjT}N7)=qvJ!GDruo`&w(A1u;A`yTqhsK<#PPhtRZkDQA2>8jsaSkQ36wSD} z9S~~?ivV#v_I@Vwfd{m_73?r>;$R;FW`E#VU!nNW24UqbsMOz3^UdTu1kmDZH{nDC zL}!V1s=;QyxOH|SX9GKF&K(ecMNL8=FlNXmtuC+>O}CDdS<73I&T_i?q1pBk4%-9) zt$qB{+AWKAG%L7x{DpzmX&N*gw(>c z6+Ghaessg1Aio0_8o0VZ{96~^K45VSqEdiN+A{q@55-PPR-CyZy!2gp)>(o6s1aT9R z%-Kz}3iW$G0>T00t$XTY!5b&oSYLO5b!?2YsRnZkCqRwFeZ`Ow5Sq$XLdUQ@Hi+E0 z`!6qd&ffBJ%jwjTxgu1H0IE4h0@db<>MBZ~`J$0_@Qlp2-1YQt9T5~A+ze(5pUghu z+gD}U>+-6WkY|d^2RDL#$Bm@EdUDwF-Lto`3dy3c+eD_%7)Z@DIqLuy-4pYggBS6; fjlo_JWK{%mVwBL88^6c%$O|gv-2Q(HhYbH4bP|2R delta 162 zcmX@Iov~{^ZNAIp$;7C$S)F$i6J!16-~0`1jP{$I#D8$I1iFeaZthgN t1!d{$3qo0bR*GCeS%&(}&)qh#vuG@8u-_~maE%?PUnH`ZiP`@DO#n()D%}78 From a671c2f6a9ec5aed4be07eefcca0e6f9efca2bbe Mon Sep 17 00:00:00 2001 From: Aleksey Makarenko Date: Fri, 6 Mar 2020 14:04:07 +0300 Subject: [PATCH 12/32] FAQ TOC links fixed --- faq.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/faq.md b/faq.md index e8bfd73d3..268c2dd09 100644 --- a/faq.md +++ b/faq.md @@ -11,10 +11,10 @@ - [RECEPTION ISSUES](#reception-issues) - [My sensor doesn't receive any readings from my sensors anymore or only occasionally](#my-sensor-doesnt-receive-any-readings-from-my-sensors-anymore-or-only-occasionally) - [How to increase coverage](#how-to-increase-coverage) - - [My sensor's BLE advertisements are encrypted, how can I get the key?](my-sensors-ble-advertisements-are-encrypted-how-can-i-get-the-key) + - [My sensor's BLE advertisements are encrypted, how can I get the key?](#my-sensors-ble-advertisements-are-encrypted-how-can-i-get-the-key) - [OTHER ISSUES](#other-issues) - [Conflicts with other components using the same BT interface](#conflicts-with-other-components-using-the-same-bt-interface) - - [My sensor stops receiving updates some time after the system restart](my-sensor-stops-receiving-updates-some-time-after-the-system-restart) + - [My sensor stops receiving updates some time after the system restart](#my-sensor-stops-receiving-updates-some-time-after-the-system-restart) - [DEBUG](#debug) From 802909ecb5cc63d7262cc09a5cab475a877cc478 Mon Sep 17 00:00:00 2001 From: Aleksey Makarenko Date: Fri, 6 Mar 2020 14:11:01 +0300 Subject: [PATCH 13/32] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 91f15a8eb..00b75175c 100644 --- a/README.md +++ b/README.md @@ -165,7 +165,7 @@ sensor: #### encryptors - (dictionary)(Optional) This option is used to describe the correspondence between the mac-address of the sensor broadcasting encrypted advertisements and the encryption key (32 characters = 16 bytes). The case of characters does not matter. Information on where to get the key [here](https://github.com/custom-components/sensor.mitemp_bt/blob/master/faq.md#my-sensors-ble-advertisements-are-encrypted-how-can-i-get-the-key). Default value: Empty + (dictionary)(Optional) This option is used to describe the correspondence between the mac-address of the sensor broadcasting encrypted advertisements and the encryption key (32 characters = 16 bytes). The case of characters does not matter. The keys below are for the sample, you need yours! Information on where to get the key [here](https://github.com/custom-components/sensor.mitemp_bt/blob/master/faq.md#my-sensors-ble-advertisements-are-encrypted-how-can-i-get-the-key). Default value: Empty ```yaml sensor: From 90da9b21e5c0d5e4c3cc674fc60b53fa6fdd2990 Mon Sep 17 00:00:00 2001 From: Aleksey Makarenko Date: Fri, 6 Mar 2020 14:12:21 +0300 Subject: [PATCH 14/32] Update info.md --- info.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/info.md b/info.md index 7b553346e..85d7f5055 100644 --- a/info.md +++ b/info.md @@ -184,7 +184,7 @@ sensor: #### encryptors - (dictionary)(Optional) This option is used to describe the correspondence between the mac-address of the sensor broadcasting encrypted advertisements and the encryption key (32 characters = 16 bytes). The case of characters does not matter. Information on where to get the key [here](https://github.com/custom-components/sensor.mitemp_bt/blob/master/faq.md#my-sensors-ble-advertisements-are-encrypted-how-can-i-get-the-key). Default value: Empty + (dictionary)(Optional) This option is used to describe the correspondence between the mac-address of the sensor broadcasting encrypted advertisements and the encryption key (32 characters = 16 bytes). The case of characters does not matter. The keys below are for the sample, you need yours! Information on where to get the key [here](https://github.com/custom-components/sensor.mitemp_bt/blob/master/faq.md#my-sensors-ble-advertisements-are-encrypted-how-can-i-get-the-key). Default value: Empty ```yaml sensor: From b570411cd120dc55aadc11ea6f97327642b92b7e Mon Sep 17 00:00:00 2001 From: Aleksey Makarenko Date: Fri, 6 Mar 2020 15:03:27 +0300 Subject: [PATCH 15/32] Store last_packet to proper check frame sequence and ignore "cross-period" duplicates --- custom_components/mitemp_bt/sensor.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/custom_components/mitemp_bt/sensor.py b/custom_components/mitemp_bt/sensor.py index 6d917e114..b73fa87fc 100644 --- a/custom_components/mitemp_bt/sensor.py +++ b/custom_components/mitemp_bt/sensor.py @@ -367,6 +367,8 @@ def reverse_mac(rmac): p_key = bytes.fromhex(config[CONF_ENCRYPTORS][mac].lower()) aeskeys[p_mac] = p_key sleep(1) + #storage for last_packet per mac + lpacket = {} # last packet number storage #_LOGGER.debug(aeskeys) def calc_update_state(entity_to_update, sensor_mac, config, measurements_list): @@ -411,7 +413,7 @@ def calc_update_state(entity_to_update, sensor_mac, config, measurements_list): error = err return success, error - def discover_ble_devices(config, aeskeyslist): + def discover_ble_devices(config, aeskeyslist, lpacket): """Discover Bluetooth LE devices.""" _LOGGER.debug("Discovering Bluetooth LE devices") log_spikes = config[CONF_LOG_SPIKES] @@ -423,7 +425,6 @@ def discover_ble_devices(config, aeskeyslist): moist_m_data = {} cond_m_data = {} batt = {} # battery - lpacket = {} # last packet number rssi = {} macs = {} # all found macs _LOGGER.debug("Getting data from HCIdump thread") @@ -618,7 +619,7 @@ def update_ble(now): _LOGGER.debug("update_ble called") try: - discover_ble_devices(config, aeskeys) + discover_ble_devices(config, aeskeys, lpacket) except RuntimeError as error: _LOGGER.error("Error during Bluetooth LE scan: %s", error) track_point_in_utc_time( From 5515d7ae10ae7503a690502e5eaf398723d1b219 Mon Sep 17 00:00:00 2001 From: Aleksey Makarenko Date: Fri, 6 Mar 2020 20:07:35 +0300 Subject: [PATCH 16/32] It was a bad idea with redundant updating for attributes - this leads to false state updates of the sensor, even if in fact there was no measurement. Rollback. --- custom_components/mitemp_bt/sensor.py | 107 +++++++++++++++----------- 1 file changed, 61 insertions(+), 46 deletions(-) diff --git a/custom_components/mitemp_bt/sensor.py b/custom_components/mitemp_bt/sensor.py index b73fa87fc..acf391cb9 100644 --- a/custom_components/mitemp_bt/sensor.py +++ b/custom_components/mitemp_bt/sensor.py @@ -355,6 +355,17 @@ def reverse_mac(rmac): + rmac[2:4] + rmac[0:2]) + def lpacket(mac=None, packet=None): + """last_packet static storage""" + if packet: + lpacket.cntr[mac] = packet + else: + try: + cntr = lpacket.cntr[mac] + except KeyError: + cntr = None + return cntr + _LOGGER.debug("Starting") scanner = BLEScanner() hass.bus.listen("homeassistant_stop", scanner.shutdown_handler) @@ -366,9 +377,8 @@ def reverse_mac(rmac): p_mac = bytes.fromhex(reverse_mac(mac.replace(":", "")).lower()) p_key = bytes.fromhex(config[CONF_ENCRYPTORS][mac].lower()) aeskeys[p_mac] = p_key + lpacket.cntr = {} sleep(1) - #storage for last_packet per mac - lpacket = {} # last packet number storage #_LOGGER.debug(aeskeys) def calc_update_state(entity_to_update, sensor_mac, config, measurements_list): @@ -400,7 +410,7 @@ def calc_update_state(entity_to_update, sensor_mac, config, measurements_list): "median" ] = state_median getattr(entity_to_update, "_device_state_attributes")["mean"] = state_mean - #entity_to_update.schedule_update_ha_state() + entity_to_update.schedule_update_ha_state() success = True except AttributeError: _LOGGER.debug("Sensor %s not yet ready for update", sensor_mac) @@ -413,7 +423,7 @@ def calc_update_state(entity_to_update, sensor_mac, config, measurements_list): error = err return success, error - def discover_ble_devices(config, aeskeyslist, lpacket): + def discover_ble_devices(config, aeskeyslist): """Discover Bluetooth LE devices.""" _LOGGER.debug("Discovering Bluetooth LE devices") log_spikes = config[CONF_LOG_SPIKES] @@ -436,15 +446,11 @@ def discover_ble_devices(config, aeskeyslist, lpacket): if data and "mac" in data: # ignore duplicated message packet = int(data["packet"]) - if data["mac"] in lpacket: - prev_packet = lpacket[data["mac"]] - else: - prev_packet = None + prev_packet = lpacket(mac = data["mac"]) if prev_packet == packet: # _LOGGER.debug("DUPLICATE: %s, IGNORING!", data) continue - # _LOGGER.debug("NEW DATA: %s", data) - lpacket[data["mac"]] = packet + lpacket(data["mac"], packet) # store found readings per device if "temperature" in data: if CONF_TMAX >= data["temperature"] >= CONF_TMIN: @@ -503,7 +509,7 @@ def discover_ble_devices(config, aeskeyslist, lpacket): sensors = sensors_by_mac[mac] else: try: - if stype[mac] == "HHCCJCY01": + if stype[mac] == "HHCCJCY01" or stype[mac] == "GCLS002": sensors = [None] * 4 sensors[t_i] = TemperatureSensor(mac) sensors[m_i] = MoistureSensor(mac) @@ -529,7 +535,50 @@ def discover_ble_devices(config, aeskeyslist, lpacket): continue sensors_by_mac[mac] = sensors add_entities(sensors) + # append joint attributes + for sensor in sensors: + getattr(sensor, "_device_state_attributes")["last packet id"] = lpacket( + mac + ) + getattr(sensor, "_device_state_attributes")["rssi"] = round( + sts.mean(rssi[mac]) + ) + getattr(sensor, "_device_state_attributes")["sensor type"] = stype[mac] + if not isinstance(sensor, BatterySensor) and mac in batt: + getattr(sensor, "_device_state_attributes")[ + ATTR_BATTERY_LEVEL + ] = batt[mac] + #try: + # sensor.schedule_update_ha_state() + #except AttributeError: + # _LOGGER.debug( + # "Sensor %s (%s) not yet ready for update", + # mac, + # stype[mac] + # ) + #except RuntimeError as err: + # _LOGGER.error( + # "Sensor %s (%s) update error:", mac, stype[mac] + # ) + # _LOGGER.error(err) # averaging and states updating + if mac in batt: + #_LOGGER.error("Battery %s", mac) + if config[CONF_BATT_ENTITIES]: + setattr(sensors[b_i], "_state", batt[mac]) + try: + sensors[b_i].schedule_update_ha_state() + except AttributeError: + _LOGGER.debug( + "Sensor %s (%s, batt.) not yet ready for update", + mac, + stype[mac] + ) + except RuntimeError as err: + _LOGGER.error( + "Sensor %s (%s, batt.) update error:", mac, stype[mac] + ) + _LOGGER.error(err) if mac in temp_m_data: success, error = calc_update_state( sensors[t_i], mac, config, temp_m_data @@ -573,39 +622,6 @@ def discover_ble_devices(config, aeskeyslist, lpacket): "Sensor %s (%s, illum.) update error:", mac, stype[mac] ) _LOGGER.error(error) - # append joint attributes - if mac in batt: - #_LOGGER.error("Battery %s", mac) - if config[CONF_BATT_ENTITIES]: - setattr(sensors[b_i], "_state", batt[mac]) - for sensor in sensors: - if isinstance(sensor, BatterySensor): - continue - getattr(sensor, "_device_state_attributes")[ - ATTR_BATTERY_LEVEL - ] = batt[mac] - #states updating - for sensor in sensors: - getattr(sensor, "_device_state_attributes")["last packet id"] = lpacket[ - mac - ] - getattr(sensor, "_device_state_attributes")["rssi"] = round( - sts.mean(rssi[mac]) - ) - getattr(sensor, "_device_state_attributes")["sensor type"] = stype[mac] - try: - sensor.schedule_update_ha_state() - except AttributeError: - _LOGGER.debug( - "Sensor %s (%s) not yet ready for update", - mac, - stype[mac] - ) - except RuntimeError as err: - _LOGGER.error( - "Sensor %s (%s) update error:", mac, stype[mac] - ) - _LOGGER.error(err) _LOGGER.debug( "Finished. Parsed: %i hci events, %i xiaomi devices.", len(hcidump_raw), @@ -617,9 +633,8 @@ def update_ble(now): """Lookup Bluetooth LE devices and update status.""" period = config[CONF_PERIOD] _LOGGER.debug("update_ble called") - try: - discover_ble_devices(config, aeskeys, lpacket) + discover_ble_devices(config, aeskeys) except RuntimeError as error: _LOGGER.error("Error during Bluetooth LE scan: %s", error) track_point_in_utc_time( From 2df7ff494a73b98680c378b50ef7a9bd3e5fb9a3 Mon Sep 17 00:00:00 2001 From: Aleksey Makarenko Date: Fri, 6 Mar 2020 20:51:10 +0300 Subject: [PATCH 17/32] bind_key capture methods --- faq.md | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/faq.md b/faq.md index 268c2dd09..723eb3771 100644 --- a/faq.md +++ b/faq.md @@ -130,7 +130,14 @@ There are several ways to increase coverage: ### My sensor's BLE advertisements are encrypted, how can I get the key? -At the moment, the only way I know is to get the key from the MiHome application traffic (in violation of the user agreement terms). Instructions for iOS and search for other ways [here](https://github.com/custom-components/sensor.mitemp_bt/issues/7#issuecomment-595327131). +There are several ways: + +1. Get the key from the MiHome application traffic (in violation of the Xiaomi user agreement terms): + + - iOS: Instruction [here](https://github.com/custom-components/sensor.mitemp_bt/issues/7#issuecomment-595327131). + - Android: I am not aware of successful interceptions on Android, but there are applications for this (Packet Capture, for example). + +2. Android only. Get the key with the customized [MiHome mod](https://github.com/custom-components/sensor.mitemp_bt/issues/7#issuecomment-595874419). ## OTHER ISSUES From bd74c29a7bc9cb230d8ea964ca4b8bec1aa72b50 Mon Sep 17 00:00:00 2001 From: Aleksey Makarenko Date: Fri, 6 Mar 2020 22:05:21 +0300 Subject: [PATCH 18/32] Key length must be 16bytes! FAQ updated --- custom_components/mitemp_bt/sensor.py | 2 +- faq.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/custom_components/mitemp_bt/sensor.py b/custom_components/mitemp_bt/sensor.py index acf391cb9..d780195a0 100644 --- a/custom_components/mitemp_bt/sensor.py +++ b/custom_components/mitemp_bt/sensor.py @@ -54,7 +54,7 @@ # regex constants for configuration schema MAC_REGEX = "(?i)^(?:[0-9A-F]{2}[:]){5}(?:[0-9A-F]{2})$" -AES128KEY_REGEX = "(?i)^[A-F0-9]+$" +AES128KEY_REGEX = "(?i)^[A-F0-9]{32}$" ENCRYPTORS_LIST_SCHEMA = vol.Schema( { diff --git a/faq.md b/faq.md index 723eb3771..1972ac614 100644 --- a/faq.md +++ b/faq.md @@ -134,7 +134,7 @@ There are several ways: 1. Get the key from the MiHome application traffic (in violation of the Xiaomi user agreement terms): - - iOS: Instruction [here](https://github.com/custom-components/sensor.mitemp_bt/issues/7#issuecomment-595327131). + - iOS: Two known working options - [using Charles proxy](https://github.com/custom-components/sensor.mitemp_bt/issues/7#issuecomment-595327131)), or [Stream - Network Debug Tool](https://github.com/custom-components/sensor.mitemp_bt/issues/7#issuecomment-595885296). - Android: I am not aware of successful interceptions on Android, but there are applications for this (Packet Capture, for example). 2. Android only. Get the key with the customized [MiHome mod](https://github.com/custom-components/sensor.mitemp_bt/issues/7#issuecomment-595874419). From b999099f8d04642ea5e743b15306b544dfed8b64 Mon Sep 17 00:00:00 2001 From: Aleksey Makarenko Date: Fri, 6 Mar 2020 23:20:28 +0300 Subject: [PATCH 19/32] minor changes --- custom_components/mitemp_bt/sensor.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/custom_components/mitemp_bt/sensor.py b/custom_components/mitemp_bt/sensor.py index d780195a0..057a1b533 100644 --- a/custom_components/mitemp_bt/sensor.py +++ b/custom_components/mitemp_bt/sensor.py @@ -185,11 +185,12 @@ def decrypt_payload(encrypted_payload, key, nonce): #_LOGGER.error(len(cipherpayload)) cipher = AES.new(key, AES.MODE_CCM, nonce=nonce, mac_len = 4) cipher.update(aad) + plaindata = None try: plaindata = cipher.decrypt_and_verify(cipherpayload, token) except ValueError as error: _LOGGER.error("Decryption failed: %s", error) - _LOGGER.error("Token: %s", token.hex()) + _LOGGER.error("token: %s", token.hex()) _LOGGER.error("nonce: %s", nonce.hex()) _LOGGER.error("encrypted_payload: %s", encrypted_payload.hex()) _LOGGER.error("cipherpayload: %s", cipherpayload.hex()) @@ -270,7 +271,7 @@ def parse_raw_message(data, aeskeyslist): data[xdata_point:msg_length-1], key, nonce ) if decrypted_payload is None: - _LOGGER.debug("Decryption failed for %s", result["mac"]) + _LOGGER.error("Decryption failed for %s, decrypted payload is None", result["mac"]) return None #replace cipher with decrypted data msg_length -= len(data[xdata_point:msg_length-1]) From 8279faa2dae16c0c22c699048a38e874e9203e21 Mon Sep 17 00:00:00 2001 From: Aleksey Makarenko Date: Sat, 7 Mar 2020 01:28:06 +0300 Subject: [PATCH 20/32] LYWSD03MMC "jagged" humidity workaround --- custom_components/mitemp_bt/sensor.py | 29 +++++++++++++++++---------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/custom_components/mitemp_bt/sensor.py b/custom_components/mitemp_bt/sensor.py index 057a1b533..7cb546a0b 100644 --- a/custom_components/mitemp_bt/sensor.py +++ b/custom_components/mitemp_bt/sensor.py @@ -277,6 +277,7 @@ def parse_raw_message(data, aeskeyslist): msg_length -= len(data[xdata_point:msg_length-1]) data = b"".join((data[:xdata_point],decrypted_payload,data[-1:])) msg_length += len(decrypted_payload) + #_LOGGER.error("%s - %s - %s - %s", xiaomi_mac_reversed.hex(), data.hex(), decrypted_payload.hex(), packet_id) # loop through xiaomi payload # assume that the data may have several values of different types, # although I did not notice this behavior with my LYWSDCGQ sensors @@ -382,22 +383,28 @@ def lpacket(mac=None, packet=None): sleep(1) #_LOGGER.debug(aeskeys) - def calc_update_state(entity_to_update, sensor_mac, config, measurements_list): + def calc_update_state(entity_to_update, sensor_mac, config, measurements_list, stype = None): """Averages according to options and updates the entity state.""" textattr = "" success = False error = "" + # LYWSD03MMC "jagged" humidity workaround + if stype == "LYWSD03MMC": + #_LOGGER.error("JAGGED!") + measurements = [int(item) for item in measurements_list] + else: + measurements = measurements_list try: if config[CONF_ROUNDING]: state_median = round( - sts.median(measurements_list[sensor_mac]), config[CONF_DECIMALS] + sts.median(measurements), config[CONF_DECIMALS] ) state_mean = round( - sts.mean(measurements_list[sensor_mac]), config[CONF_DECIMALS] + sts.mean(measurements), config[CONF_DECIMALS] ) else: - state_median = sts.median(measurements_list[sensor_mac]) - state_mean = sts.mean(measurements_list[sensor_mac]) + state_median = sts.median(measurements) + state_mean = sts.mean(measurements) if config[CONF_USE_MEDIAN]: textattr = "last median of" setattr(entity_to_update, "_state", state_median) @@ -405,7 +412,7 @@ def calc_update_state(entity_to_update, sensor_mac, config, measurements_list): textattr = "last mean of" setattr(entity_to_update, "_state", state_mean) getattr(entity_to_update, "_device_state_attributes")[textattr] = len( - measurements_list[sensor_mac] + measurements ) getattr(entity_to_update, "_device_state_attributes")[ "median" @@ -582,7 +589,7 @@ def discover_ble_devices(config, aeskeyslist): _LOGGER.error(err) if mac in temp_m_data: success, error = calc_update_state( - sensors[t_i], mac, config, temp_m_data + sensors[t_i], mac, config, temp_m_data[mac] ) if not success: _LOGGER.error( @@ -591,14 +598,14 @@ def discover_ble_devices(config, aeskeyslist): _LOGGER.error(error) if mac in hum_m_data: success, error = calc_update_state( - sensors[h_i], mac, config, hum_m_data + sensors[h_i], mac, config, hum_m_data[mac], stype[mac] ) if not success: _LOGGER.error("Sensor %s (%s, hum.) update error:", mac, stype[mac]) _LOGGER.error(error) if mac in moist_m_data: success, error = calc_update_state( - sensors[m_i], mac, config, moist_m_data + sensors[m_i], mac, config, moist_m_data[mac] ) if not success: _LOGGER.error( @@ -607,7 +614,7 @@ def discover_ble_devices(config, aeskeyslist): _LOGGER.error(error) if mac in cond_m_data: success, error = calc_update_state( - sensors[c_i], mac, config, cond_m_data + sensors[c_i], mac, config, cond_m_data[mac] ) if not success: _LOGGER.error( @@ -616,7 +623,7 @@ def discover_ble_devices(config, aeskeyslist): _LOGGER.error(error) if mac in illum_m_data: success, error = calc_update_state( - sensors[i_i], mac, config, illum_m_data + sensors[i_i], mac, config, illum_m_data[mac] ) if not success: _LOGGER.error( From bb95f559e77865b12e8ff3f03a7cd01639240ac3 Mon Sep 17 00:00:00 2001 From: Aleksey Makarenko Date: Sat, 7 Mar 2020 23:35:55 +0300 Subject: [PATCH 21/32] Now the parser uses the xiaomi FrameControl bits Some optimizations --- custom_components/mitemp_bt/const.py | 14 ++++---- custom_components/mitemp_bt/sensor.py | 46 +++++++++++++++++---------- 2 files changed, 36 insertions(+), 24 deletions(-) diff --git a/custom_components/mitemp_bt/const.py b/custom_components/mitemp_bt/const.py index 3ea07feae..88ae633bb 100644 --- a/custom_components/mitemp_bt/const.py +++ b/custom_components/mitemp_bt/const.py @@ -32,13 +32,13 @@ # Xiaomi sensor types dictionary with offset for adv parser XIAOMI_TYPE_DICT = { - b'\x98\x00': ["HHCCJCY01", 1], - b'\xAA\x01': ["LYWSDCGQ", 0], - b'\x5B\x04': ["LYWSD02", 1], - b'\x47\x03': ["CGG1", 0], - b'\x5D\x01': ["HHCCPOT002", 1], - b'\xBC\x03': ["GCLS002", 1], - b'\x5B\x05': ["LYWSD03MMC", 0], + b'\x98\x00': "HHCCJCY01", + b'\xAA\x01': "LYWSDCGQ", + b'\x5B\x04': "LYWSD02", + b'\x47\x03': "CGG1", + b'\x5D\x01': "HHCCPOT002", + b'\xBC\x03': "GCLS002", + b'\x5B\x05': "LYWSD03MMC" } diff --git a/custom_components/mitemp_bt/sensor.py b/custom_components/mitemp_bt/sensor.py index 7cb546a0b..ea54e7153 100644 --- a/custom_components/mitemp_bt/sensor.py +++ b/custom_components/mitemp_bt/sensor.py @@ -223,11 +223,22 @@ def parse_raw_message(data, aeskeyslist): if not 0 >= rssi >= -127: return None try: - sensor_type, toffset = XIAOMI_TYPE_DICT[ + sensor_type = XIAOMI_TYPE_DICT[ data[xiaomi_index + 5:xiaomi_index + 7] ] except KeyError: return None + #Frame control bits + framectrl, = struct.unpack('>H', data[xiaomi_index + 3:xiaomi_index + 5]) + #check data is present + if not (framectrl & 0x4000): + return None + xdata_length = 0 + xdata_point = 0 + #check capability byte present + if framectrl & 0x2000: + xdata_length = -1 + xdata_point = 1 # xiaomi data length = message length # -all bytes before XiaomiUUID # -3 bytes Xiaomi UUID + ADtype @@ -235,25 +246,18 @@ def parse_raw_message(data, aeskeyslist): # -3+1 bytes sensor type # -1 byte packet_id # -6 bytes MAC - # - sensortype offset - xdata_length = msg_length - xiaomi_index - 15 - toffset + # - capability byte offset + xdata_length += msg_length - xiaomi_index - 15 if xdata_length < 3: return None - xdata_point = xiaomi_index + (14 + toffset) + xdata_point += xiaomi_index + 14 + #_LOGGER.error("%s - type: %s - point: %s - len: %s - offset?: %s", data.hex(), sensor_type, xdata_point, xdata_length, framectrl & 0x4000) # check if xiaomi data start and length is valid if xdata_length != len(data[xdata_point:-1]): return None - packet_id = data[xiaomi_index + 7] - result = { - "rssi": rssi, - "mac": ''.join('{:02X}'.format(x) for x in xiaomi_mac_reversed[::-1]), - "type": sensor_type, - "packet": packet_id, - } - #Frame control bits - framectrl, = struct.unpack(' Date: Sun, 8 Mar 2020 01:40:48 +0300 Subject: [PATCH 22/32] Minor fixes, cleanup --- custom_components/mitemp_bt/sensor.py | 28 ++++----------------------- info.md | 2 +- 2 files changed, 5 insertions(+), 25 deletions(-) diff --git a/custom_components/mitemp_bt/sensor.py b/custom_components/mitemp_bt/sensor.py index ea54e7153..02d2129db 100644 --- a/custom_components/mitemp_bt/sensor.py +++ b/custom_components/mitemp_bt/sensor.py @@ -173,7 +173,7 @@ def parse_xiaomi_value(hexvalue, typecode): if typecode == 0x07: (illum,) = ILL_STRUCT.unpack(hexvalue + b'\x00') return {"illuminance": illum} - return {} + return None def decrypt_payload(encrypted_payload, key, nonce): """Decrypt payload""" @@ -182,7 +182,6 @@ def decrypt_payload(encrypted_payload, key, nonce): payload_counter = encrypted_payload[-7:-4] nonce = b"".join([nonce, payload_counter]) cipherpayload = encrypted_payload[:-7] - #_LOGGER.error(len(cipherpayload)) cipher = AES.new(key, AES.MODE_CCM, nonce=nonce, mac_len = 4) cipher.update(aad) plaindata = None @@ -251,11 +250,9 @@ def parse_raw_message(data, aeskeyslist): if xdata_length < 3: return None xdata_point += xiaomi_index + 14 - #_LOGGER.error("%s - type: %s - point: %s - len: %s - offset?: %s", data.hex(), sensor_type, xdata_point, xdata_length, framectrl & 0x4000) # check if xiaomi data start and length is valid if xdata_length != len(data[xdata_point:-1]): return None - #_LOGGER.error("Type - %s %s, Frame ctrl - %s", data[xiaomi_index + 5:xiaomi_index + 7].hex(), sensor_type, data[xiaomi_index + 3: xiaomi_index + 5].hex()) #check encrypted data flags if framectrl & 0x0800: #try to find encryption key for current device @@ -281,7 +278,6 @@ def parse_raw_message(data, aeskeyslist): msg_length -= len(data[xdata_point:msg_length-1]) data = b"".join((data[:xdata_point],decrypted_payload,data[-1:])) msg_length += len(decrypted_payload) - #_LOGGER.error("%s - %s - %s - %s", xiaomi_mac_reversed.hex(), data.hex(), decrypted_payload.hex(), int(data[xiaomi_index + 7])) packet_id = data[xiaomi_index + 7] result = { "rssi": rssi, @@ -370,7 +366,7 @@ def reverse_mac(rmac): def lpacket(mac, packet=None): """last_packet static storage""" - if packet: + if packet is not None: lpacket.cntr[mac] = packet else: try: @@ -392,7 +388,6 @@ def lpacket(mac, packet=None): aeskeys[p_mac] = p_key lpacket.cntr = {} sleep(1) - #_LOGGER.debug(aeskeys) def calc_update_state(entity_to_update, sensor_mac, config, measurements_list, stype = None): """Averages according to options and updates the entity state.""" @@ -401,7 +396,6 @@ def calc_update_state(entity_to_update, sensor_mac, config, measurements_list, s error = "" # LYWSD03MMC "jagged" humidity workaround if stype == "LYWSD03MMC": - #_LOGGER.error("JAGGED!") measurements = [int(item) for item in measurements_list] else: measurements = measurements_list @@ -460,12 +454,11 @@ def discover_ble_devices(config, aeskeyslist): scanner.stop() hcidump_raw = [*scanner.hcidump_data] scanner.start(config) # minimum delay between HCIdumps - #_LOGGER.error(lpacket.cntr) for msg in hcidump_raw: data = parse_raw_message(msg, aeskeyslist) if data and "mac" in data: # ignore duplicated message - packet = int(data["packet"]) + packet = data["packet"] prev_packet = lpacket(mac = data["mac"]) if prev_packet == packet: # _LOGGER.debug("DUPLICATE: %s, IGNORING!", data) @@ -568,22 +561,9 @@ def discover_ble_devices(config, aeskeyslist): getattr(sensor, "_device_state_attributes")[ ATTR_BATTERY_LEVEL ] = batt[mac] - #try: - # sensor.schedule_update_ha_state() - #except AttributeError: - # _LOGGER.debug( - # "Sensor %s (%s) not yet ready for update", - # mac, - # stype[mac] - # ) - #except RuntimeError as err: - # _LOGGER.error( - # "Sensor %s (%s) update error:", mac, stype[mac] - # ) - # _LOGGER.error(err) + # averaging and states updating if mac in batt: - #_LOGGER.error("Battery %s", mac) if config[CONF_BATT_ENTITIES]: setattr(sensors[b_i], "_state", batt[mac]) try: diff --git a/info.md b/info.md index 85d7f5055..23ab89e4f 100644 --- a/info.md +++ b/info.md @@ -14,7 +14,7 @@ - added new option `encryptors` to describe the correspondence between the mac-address of the sensor broadcasting encrypted adverts and the encryption key - LYWSD03MMC support ("encryptor") - error "Attribute hass is None for Entity..." fixed -- other minor optimizations and improvements +- other minor bugfixes and improvements --- {% endif %} From 07b3e16917c35c5fbd87a9e614b61b62b327416e Mon Sep 17 00:00:00 2001 From: Aleksey Makarenko Date: Sun, 8 Mar 2020 03:49:06 +0300 Subject: [PATCH 23/32] New option: report_unknown --- README.md | 7 +++++-- custom_components/mitemp_bt/const.py | 2 ++ faq.md | 22 +++++++++++++++++++++- info.md | 6 ++++++ 4 files changed, 34 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 00b75175c..fb534e337 100644 --- a/README.md +++ b/README.md @@ -109,10 +109,9 @@ sensor: active_scan: False hci_interface: 0 batt_entities: False + report_unknown: False ``` - - ### Configuration Variables #### rounding @@ -175,6 +174,10 @@ sensor: 'A4:C1:38:D1:61:7D': 'C99D2313182473B38001086FEBF781BD' ``` +#### report_unknown + + (boolean)(Optional) This option is needed primarily for those who want to request an implementation of device support that is not in the list of [supported ones](#supported-sensors). If you set this parameter to `True`, then the component will begin to output to the Home Assitant log all messages from unknown Xiaomi ecosystem devices. **Attention!** Enabling this option can lead to huge output to the Home Assistant log, do not enable it if you do not need it! Details in the [FAQ](https://github.com/custom-components/sensor.mitemp_bt/blob/master/faq.md#my-sensor-from-the-xiaomi-ecosystem-is-not-in-the-list-of-supported-ones-how-to-request-implementation). Default value: False + ## FREQUENTLY ASKED QUESTIONS Still having questions or issues? Please first have a look on our [Frequently Asked Questions (FAQ) page](faq.md) to see if your question is already answered. There are some useful tips also. diff --git a/custom_components/mitemp_bt/const.py b/custom_components/mitemp_bt/const.py index 88ae633bb..cb5047e31 100644 --- a/custom_components/mitemp_bt/const.py +++ b/custom_components/mitemp_bt/const.py @@ -10,6 +10,7 @@ CONF_HCI_INTERFACE = "hci_interface" CONF_BATT_ENTITIES = "batt_entities" CONF_ENCRYPTORS = "encryptors" +CONF_REPORT_UNKNOWN = "report_unknown" # Default values for configuration options DEFAULT_ROUNDING = True @@ -20,6 +21,7 @@ DEFAULT_ACTIVE_SCAN = False DEFAULT_HCI_INTERFACE = 0 DEFAULT_BATT_ENTITIES = False +DEFAULT_REPORT_UNKNOWN = False """Fixed constants.""" diff --git a/faq.md b/faq.md index 1972ac614..7eb4474b6 100644 --- a/faq.md +++ b/faq.md @@ -15,6 +15,7 @@ - [OTHER ISSUES](#other-issues) - [Conflicts with other components using the same BT interface](#conflicts-with-other-components-using-the-same-bt-interface) - [My sensor stops receiving updates some time after the system restart](#my-sensor-stops-receiving-updates-some-time-after-the-system-restart) + - [My sensor from the Xiaomi ecosystem is not in the list of supported ones. How to request implementation?](#my-sensor-from-the-xiaomi-ecosystem-is-not-in-the-list-of-supported-ones-how-to-request-implementation) - [DEBUG](#debug) @@ -148,7 +149,26 @@ A reliable, but not the only way out of this situation (apart from the refusal t ### My sensor stops receiving updates some time after the system restart -Most often, the cause of this is the presence of the bugs in the system components responsible for the BT operation (kernel modules, firmwares, etc). As a rule, in such cases, the corresponding entries appear in the system log. Please carefully review the contents of your `syslog`, and try searching the Internet for a solution - with high probability you are not alone in this. Пример решения проблемы - [BT problem, Raspberry PI3 and Hass.io](https://github.com/custom-components/sensor.mitemp_bt/issues/31#issuecomment-595417222) +Most often, the cause of this is the presence of the bugs in the system components responsible for the BT operation (kernel modules, firmwares, etc). As a rule, in such cases, the corresponding entries appear in the system log. Please carefully review the contents of your `syslog`, and try searching the Internet for a solution - with high probability you are not alone in this. For example, here is an issue with a typical Raspberry PI problem - [BT problem, Raspberry PI3 and Hass.io](https://github.com/custom-components/sensor.mitemp_bt/issues/31#issuecomment-595417222) + +### My sensor from the Xiaomi ecosystem is not in the list of supported ones. How to request implementation? + +- [Install the component](https://github.com/custom-components/sensor.mitemp_bt/blob/master/README.md#how-to-install) if you have not already done so. +- Make sure you have [logger](https://www.home-assistant.io/integrations/logger/) enabled, and logging enabled for `info` level (globally or just for `custom_components.mitemp_bt`). For example: + +```yaml +logger: + default: warn + logs: + custom_components.mitemp_bt: info +``` + +- Place your sensor extremely close to the HA host (BT interface). +- [Enable option](https://github.com/custom-components/sensor.mitemp_bt/blob/master/README.md#configuration) `report_unknown`. +- Wait until a number of "BLE ADV from UNKNOWN" messages accumulate in the log. The component will put these messages at the `info` level! +- Create [issue](https://github.com/custom-components/sensor.mitemp_bt/issues), write there everything you know about your sensor and attach obtained log. +- Do not forget to disable the `report_unknown` option (delete it or set it to False and restart HA). +- Wait for a response from the developers ) ## DEBUG diff --git a/info.md b/info.md index 23ab89e4f..a4b817b8a 100644 --- a/info.md +++ b/info.md @@ -13,6 +13,7 @@ - encrypted BLE ADV payload decryption implemented - added new option `encryptors` to describe the correspondence between the mac-address of the sensor broadcasting encrypted adverts and the encryption key - LYWSD03MMC support ("encryptor") +- added new option `report_unknown` designed to enable logging of all BLE ADV messages from unsupported Xiaomi ecosystem devices - error "Attribute hass is None for Entity..." fixed - other minor bugfixes and improvements @@ -130,6 +131,7 @@ sensor: active_scan: False hci_interface: 0 batt_entities: False + report_unknown: False ``` ### Configuration Variables @@ -194,6 +196,10 @@ sensor: 'A4:C1:38:D1:61:7D': 'C99D2313182473B38001086FEBF781BD' ``` +#### report_unknown + + (boolean)(Optional) This option is needed primarily for those who want to request an implementation of device support that is not in the list of [supported ones](#supported-sensors). If you set this parameter to `True`, then the component will begin to output to the Home Assitant log all messages from unknown Xiaomi ecosystem devices. **Attention!** Enabling this option can lead to huge output to the Home Assistant log, do not enable it if you do not need it! Details in the [FAQ](https://github.com/custom-components/sensor.mitemp_bt/blob/master/faq.md#my-sensor-from-the-xiaomi-ecosystem-is-not-in-the-list-of-supported-ones-how-to-request-implementation). Default value: False + ## FREQUENTLY ASKED QUESTIONS Still having questions or issues? Please first have a look on our [Frequently Asked Questions (FAQ) page](https://github.com/custom-components/sensor.mitemp_bt/blob/master/faq.md) to see if your question is already answered. From c9db9db6cb870efb111cff7e98137b4e052948ed Mon Sep 17 00:00:00 2001 From: Aleksey Makarenko Date: Sun, 8 Mar 2020 03:49:26 +0300 Subject: [PATCH 24/32] report_unknown option --- custom_components/mitemp_bt/sensor.py | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/custom_components/mitemp_bt/sensor.py b/custom_components/mitemp_bt/sensor.py index 02d2129db..5bb456c3c 100644 --- a/custom_components/mitemp_bt/sensor.py +++ b/custom_components/mitemp_bt/sensor.py @@ -33,6 +33,7 @@ DEFAULT_ACTIVE_SCAN, DEFAULT_HCI_INTERFACE, DEFAULT_BATT_ENTITIES, + DEFAULT_REPORT_UNKNOWN, CONF_ROUNDING, CONF_DECIMALS, CONF_PERIOD, @@ -42,6 +43,7 @@ CONF_HCI_INTERFACE, CONF_BATT_ENTITIES, CONF_ENCRYPTORS, + CONF_REPORT_UNKNOWN, CONF_TMIN, CONF_TMAX, CONF_HMIN, @@ -74,7 +76,8 @@ CONF_HCI_INTERFACE, default=[DEFAULT_HCI_INTERFACE] ): vol.All(cv.ensure_list, [cv.positive_int]), vol.Optional(CONF_BATT_ENTITIES, default=DEFAULT_BATT_ENTITIES): cv.boolean, - vol.Optional(CONF_ENCRYPTORS, default={}): ENCRYPTORS_LIST_SCHEMA + vol.Optional(CONF_ENCRYPTORS, default={}): ENCRYPTORS_LIST_SCHEMA, + vol.Optional(CONF_REPORT_UNKNOWN, default=DEFAULT_REPORT_UNKNOWN): cv.boolean, } ) @@ -196,7 +199,7 @@ def decrypt_payload(encrypted_payload, key, nonce): return None return plaindata -def parse_raw_message(data, aeskeyslist): +def parse_raw_message(data, aeskeyslist, report_unknown = False): """Parse the raw data.""" if data is None: return None @@ -226,6 +229,13 @@ def parse_raw_message(data, aeskeyslist): data[xiaomi_index + 5:xiaomi_index + 7] ] except KeyError: + if report_unknown: + _LOGGER.info( + "BLE ADV from UNKNOWN: RSSI: %s, MAC: %s, ADV: %s", + rssi, + ''.join('{:02X}'.format(x) for x in xiaomi_mac_reversed[::-1]), + data.hex() + ) return None #Frame control bits framectrl, = struct.unpack('>H', data[xiaomi_index + 3:xiaomi_index + 5]) @@ -380,6 +390,8 @@ def lpacket(mac, packet=None): hass.bus.listen("homeassistant_stop", scanner.shutdown_handler) scanner.start(config) sensors_by_mac = {} + if config[CONF_REPORT_UNKNOWN]: + _LOGGER.info("Attention! Option report_unknown is enabled, be ready for a huge output...") #prepare device:key list to speedup parser aeskeys = {} for mac in config[CONF_ENCRYPTORS]: @@ -454,8 +466,9 @@ def discover_ble_devices(config, aeskeyslist): scanner.stop() hcidump_raw = [*scanner.hcidump_data] scanner.start(config) # minimum delay between HCIdumps + report_unknown = config[CONF_REPORT_UNKNOWN] for msg in hcidump_raw: - data = parse_raw_message(msg, aeskeyslist) + data = parse_raw_message(msg, aeskeyslist, report_unknown) if data and "mac" in data: # ignore duplicated message packet = data["packet"] From c6a21059c7c69ff251ef2529a756ed5994606b0d Mon Sep 17 00:00:00 2001 From: Aleksey Makarenko Date: Sun, 8 Mar 2020 05:00:57 +0300 Subject: [PATCH 25/32] More links to FAQ --- README.md | 2 ++ info.md | 3 +++ 2 files changed, 5 insertions(+) diff --git a/README.md b/README.md index fb534e337..34854bc62 100644 --- a/README.md +++ b/README.md @@ -50,6 +50,8 @@ This custom component is an alternative for the standard build in [mitemp_bt](ht *The amount of actually received data is highly dependent on the reception conditions (like distance and electromagnetic ambiance), readings numbers are indicated for good RSSI (Received Signal Strength Indicator) of about -75 till -70dBm.* +**In the [FAQ](https://github.com/custom-components/sensor.mitemp_bt/blob/master/faq.md#my-sensor-from-the-xiaomi-ecosystem-is-not-in-the-list-of-supported-ones-how-to-request-implementation) you can read about how to request the implementation of support for other sensors.** + ## HOW TO INSTALL **1. Grant permissions for Python rootless access to HCI interface (usually not needed on HASSio):** diff --git a/info.md b/info.md index a4b817b8a..a7bb1c24d 100644 --- a/info.md +++ b/info.md @@ -15,6 +15,7 @@ - LYWSD03MMC support ("encryptor") - added new option `report_unknown` designed to enable logging of all BLE ADV messages from unsupported Xiaomi ecosystem devices - error "Attribute hass is None for Entity..." fixed +- fix for VegTrug Grow Care Garden sensor - other minor bugfixes and improvements --- @@ -72,6 +73,8 @@ This custom component is an alternative for the standard build in [mitemp_bt](ht *The amount of actually received data is highly dependent on the reception conditions (like distance and electromagnetic ambiance), readings numbers are indicated for good RSSI (Received Signal Strength Indicator) of about -70dBm till -75dBm.* +**In the [FAQ](https://github.com/custom-components/sensor.mitemp_bt/blob/master/faq.md#my-sensor-from-the-xiaomi-ecosystem-is-not-in-the-list-of-supported-ones-how-to-request-implementation) you can read about how to request the implementation of support for other sensors.** + ## HOW TO INSTALL **1. Grant permissions for Python rootless access to HCI interface (usually not needed on HASSio):** From 727c2cbfc630a5d93f88f5410378fe3d5c315437 Mon Sep 17 00:00:00 2001 From: Ernst Klamer Date: Sun, 8 Mar 2020 10:02:32 +0100 Subject: [PATCH 26/32] Update instructions in info.md --- info.md | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/info.md b/info.md index a7bb1c24d..8f4b8c9a1 100644 --- a/info.md +++ b/info.md @@ -69,11 +69,11 @@ This custom component is an alternative for the standard build in [mitemp_bt](ht - LYWSD03MMC - (small square body, segment LCD, broadcasts temperature and humidity once about a 10 minutes and battery once a hour, advertisements are encrypted, key needed! See [encryptors](#configuration-variables) option. + (small square body, segment LCD, broadcasts temperature and humidity once in about 10 minutes and battery once in an hour, advertisements are encrypted, therefore you need to set the key in your configuration, see for instructions the [encryptors](#configuration-variables) option. *The amount of actually received data is highly dependent on the reception conditions (like distance and electromagnetic ambiance), readings numbers are indicated for good RSSI (Received Signal Strength Indicator) of about -70dBm till -75dBm.* -**In the [FAQ](https://github.com/custom-components/sensor.mitemp_bt/blob/master/faq.md#my-sensor-from-the-xiaomi-ecosystem-is-not-in-the-list-of-supported-ones-how-to-request-implementation) you can read about how to request the implementation of support for other sensors.** +**Do you want to request support for a new sensor? In the [FAQ](https://github.com/custom-components/sensor.mitemp_bt/blob/master/faq.md#my-sensor-from-the-xiaomi-ecosystem-is-not-in-the-list-of-supported-ones-how-to-request-implementation) you can read instructions how to request support for other sensors.** ## HOW TO INSTALL @@ -134,9 +134,12 @@ sensor: active_scan: False hci_interface: 0 batt_entities: False + encryptors: 'A4:C1:38:2F:86:6C': '217C568CF5D22808DA20181502D84C1B' report_unknown: False ``` +Note: The encryptors parameter is only needed for LYWSD03MMC. + ### Configuration Variables #### rounding @@ -189,7 +192,7 @@ sensor: #### encryptors - (dictionary)(Optional) This option is used to describe the correspondence between the mac-address of the sensor broadcasting encrypted advertisements and the encryption key (32 characters = 16 bytes). The case of characters does not matter. The keys below are for the sample, you need yours! Information on where to get the key [here](https://github.com/custom-components/sensor.mitemp_bt/blob/master/faq.md#my-sensors-ble-advertisements-are-encrypted-how-can-i-get-the-key). Default value: Empty + (dictionary)(Optional) This option is used to link the mac-address of the sensor broadcasting encrypted advertisements to the encryption key (32 characters = 16 bytes). This is only needed for LYWSD03MMC sensors. The case of the characters does not matter. The keys below are an example, you need your own key(s)! Information on how to get your key(s) can be found [here](https://github.com/custom-components/sensor.mitemp_bt/blob/master/faq.md#my-sensors-ble-advertisements-are-encrypted-how-can-i-get-the-key). Default value: Empty ```yaml sensor: @@ -201,7 +204,7 @@ sensor: #### report_unknown - (boolean)(Optional) This option is needed primarily for those who want to request an implementation of device support that is not in the list of [supported ones](#supported-sensors). If you set this parameter to `True`, then the component will begin to output to the Home Assitant log all messages from unknown Xiaomi ecosystem devices. **Attention!** Enabling this option can lead to huge output to the Home Assistant log, do not enable it if you do not need it! Details in the [FAQ](https://github.com/custom-components/sensor.mitemp_bt/blob/master/faq.md#my-sensor-from-the-xiaomi-ecosystem-is-not-in-the-list-of-supported-ones-how-to-request-implementation). Default value: False + (boolean)(Optional) This option is needed primarily for those who want to request an implementation of device support that is not in the list of [supported sensors](#supported-sensors). If you set this parameter to `True`, then the component will log all messages from unknown Xiaomi ecosystem devices to the Home Assitant log. **Attention!** Enabling this option can lead to huge output to the Home Assistant log, do not enable it if you do not need it! Details in the [FAQ](https://github.com/custom-components/sensor.mitemp_bt/blob/master/faq.md#my-sensor-from-the-xiaomi-ecosystem-is-not-in-the-list-of-supported-ones-how-to-request-implementation). Default value: False ## FREQUENTLY ASKED QUESTIONS From 005d49a7f9b27ebb59b295a963be3f040090dc25 Mon Sep 17 00:00:00 2001 From: Ernst Klamer Date: Sun, 8 Mar 2020 10:03:07 +0100 Subject: [PATCH 27/32] Update instructions in readme --- README.md | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 34854bc62..38224617a 100644 --- a/README.md +++ b/README.md @@ -46,11 +46,11 @@ This custom component is an alternative for the standard build in [mitemp_bt](ht - LYWSD03MMC - (small square body, segment LCD, broadcasts temperature and humidity once about a 10 minutes and battery once a hour, advertisements are encrypted, key needed! See [encryptors](#configuration-variables) option. + (small square body, segment LCD, broadcasts temperature and humidity once in about 10 minutes and battery once in an hour, advertisements are encrypted, therefore you need to set the key in your configuration, see for instructions the [encryptors](#configuration-variables) option. *The amount of actually received data is highly dependent on the reception conditions (like distance and electromagnetic ambiance), readings numbers are indicated for good RSSI (Received Signal Strength Indicator) of about -75 till -70dBm.* -**In the [FAQ](https://github.com/custom-components/sensor.mitemp_bt/blob/master/faq.md#my-sensor-from-the-xiaomi-ecosystem-is-not-in-the-list-of-supported-ones-how-to-request-implementation) you can read about how to request the implementation of support for other sensors.** +**Do you want to request support for a new sensor? In the [FAQ](https://github.com/custom-components/sensor.mitemp_bt/blob/master/faq.md#my-sensor-from-the-xiaomi-ecosystem-is-not-in-the-list-of-supported-ones-how-to-request-implementation) you can read instructions how to request support for other sensors.** ## HOW TO INSTALL @@ -111,9 +111,12 @@ sensor: active_scan: False hci_interface: 0 batt_entities: False + encryptors: 'A4:C1:38:2F:86:6C': '217C568CF5D22808DA20181502D84C1B' report_unknown: False ``` +Note: The encryptors parameter is only needed for LYWSD03MMC. + ### Configuration Variables #### rounding @@ -166,7 +169,7 @@ sensor: #### encryptors - (dictionary)(Optional) This option is used to describe the correspondence between the mac-address of the sensor broadcasting encrypted advertisements and the encryption key (32 characters = 16 bytes). The case of characters does not matter. The keys below are for the sample, you need yours! Information on where to get the key [here](https://github.com/custom-components/sensor.mitemp_bt/blob/master/faq.md#my-sensors-ble-advertisements-are-encrypted-how-can-i-get-the-key). Default value: Empty + (dictionary)(Optional) This option is used to link the mac-address of the sensor broadcasting encrypted advertisements to the encryption key (32 characters = 16 bytes). This is only needed for LYWSD03MMC sensors. The case of the characters does not matter. The keys below are an example, you need your own key(s)! Information on how to get your key(s) can be found [here](https://github.com/custom-components/sensor.mitemp_bt/blob/master/faq.md#my-sensors-ble-advertisements-are-encrypted-how-can-i-get-the-key). Default value: Empty ```yaml sensor: @@ -178,7 +181,7 @@ sensor: #### report_unknown - (boolean)(Optional) This option is needed primarily for those who want to request an implementation of device support that is not in the list of [supported ones](#supported-sensors). If you set this parameter to `True`, then the component will begin to output to the Home Assitant log all messages from unknown Xiaomi ecosystem devices. **Attention!** Enabling this option can lead to huge output to the Home Assistant log, do not enable it if you do not need it! Details in the [FAQ](https://github.com/custom-components/sensor.mitemp_bt/blob/master/faq.md#my-sensor-from-the-xiaomi-ecosystem-is-not-in-the-list-of-supported-ones-how-to-request-implementation). Default value: False + (boolean)(Optional) This option is needed primarily for those who want to request an implementation of device support that is not in the list of [supported sensors](#supported-sensors). If you set this parameter to `True`, then the component will log all messages from unknown Xiaomi ecosystem devices to the Home Assitant log. **Attention!** Enabling this option can lead to huge output to the Home Assistant log, do not enable it if you do not need it! Details in the [FAQ](https://github.com/custom-components/sensor.mitemp_bt/blob/master/faq.md#my-sensor-from-the-xiaomi-ecosystem-is-not-in-the-list-of-supported-ones-how-to-request-implementation). Default value: False ## FREQUENTLY ASKED QUESTIONS From f52c21cd85463b28f9eb7b7df359910be0769283 Mon Sep 17 00:00:00 2001 From: Ernst Klamer Date: Sun, 8 Mar 2020 10:23:58 +0100 Subject: [PATCH 28/32] Small changed after review faq --- faq.md | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/faq.md b/faq.md index 7eb4474b6..206be51a9 100644 --- a/faq.md +++ b/faq.md @@ -34,8 +34,6 @@ I’m not sure that I have listed all the advantages resulting from the passivit ### I get a PermissionError in Home Assistant after the installation or python upgrade -Note: This answer is only applicable for version 0.5 and higher. - Python needs root access to access the HCI interface. If Python doesn't have root access, you will get an error message in Home Assistant which ends with: ```shell @@ -135,7 +133,7 @@ There are several ways: 1. Get the key from the MiHome application traffic (in violation of the Xiaomi user agreement terms): - - iOS: Two known working options - [using Charles proxy](https://github.com/custom-components/sensor.mitemp_bt/issues/7#issuecomment-595327131)), or [Stream - Network Debug Tool](https://github.com/custom-components/sensor.mitemp_bt/issues/7#issuecomment-595885296). + - iOS: Two known working options - [using Charles proxy](https://github.com/custom-components/sensor.mitemp_bt/issues/7#issuecomment-595327131), or [Stream - Network Debug Tool](https://github.com/custom-components/sensor.mitemp_bt/issues/7#issuecomment-595885296). - Android: I am not aware of successful interceptions on Android, but there are applications for this (Packet Capture, for example). 2. Android only. Get the key with the customized [MiHome mod](https://github.com/custom-components/sensor.mitemp_bt/issues/7#issuecomment-595874419). @@ -149,7 +147,7 @@ A reliable, but not the only way out of this situation (apart from the refusal t ### My sensor stops receiving updates some time after the system restart -Most often, the cause of this is the presence of the bugs in the system components responsible for the BT operation (kernel modules, firmwares, etc). As a rule, in such cases, the corresponding entries appear in the system log. Please carefully review the contents of your `syslog`, and try searching the Internet for a solution - with high probability you are not alone in this. For example, here is an issue with a typical Raspberry PI problem - [BT problem, Raspberry PI3 and Hass.io](https://github.com/custom-components/sensor.mitemp_bt/issues/31#issuecomment-595417222) +Most often, the cause of this is the presence of bugs in the system components responsible for the BT operation (kernel modules, firmwares, etc). As a rule, in such cases, the corresponding entries appear in the system log. Please carefully review the contents of your `syslog`, and try searching the Internet for a solution - with high probability you are not alone in this. For example, here is an issue with a typical Raspberry PI problem - [BT problem, Raspberry PI3 and Hass.io](https://github.com/custom-components/sensor.mitemp_bt/issues/31#issuecomment-595417222) ### My sensor from the Xiaomi ecosystem is not in the list of supported ones. How to request implementation? @@ -164,11 +162,11 @@ logger: ``` - Place your sensor extremely close to the HA host (BT interface). -- [Enable option](https://github.com/custom-components/sensor.mitemp_bt/blob/master/README.md#configuration) `report_unknown`. +- [Enable the option](https://github.com/custom-components/sensor.mitemp_bt/blob/master/README.md#configuration) `report_unknown`. - Wait until a number of "BLE ADV from UNKNOWN" messages accumulate in the log. The component will put these messages at the `info` level! -- Create [issue](https://github.com/custom-components/sensor.mitemp_bt/issues), write there everything you know about your sensor and attach obtained log. -- Do not forget to disable the `report_unknown` option (delete it or set it to False and restart HA). -- Wait for a response from the developers ) +- Create a new [issue](https://github.com/custom-components/sensor.mitemp_bt/issues), write everything you know about your sensor and attach the obtained log. +- Do not forget to disable the `report_unknown` option (delete it or set it to `False` and restart HA). +- Wait for a response from the developers. ## DEBUG @@ -188,4 +186,4 @@ For example, using the command btmon -t -w problem.log ``` -You can write to the problem.log file the moment the problem occurs, and attach this file to your issue. +You can write to the problem.log file the moment the problem occurs and attach this file to your issue. From 6e777ae317fabd0b236ee8636359dd1e22ef6b55 Mon Sep 17 00:00:00 2001 From: Ernst Klamer Date: Sun, 8 Mar 2020 11:08:35 +0100 Subject: [PATCH 29/32] Update sensor.py --- custom_components/mitemp_bt/sensor.py | 82 ++++++++++++++------------- 1 file changed, 43 insertions(+), 39 deletions(-) diff --git a/custom_components/mitemp_bt/sensor.py b/custom_components/mitemp_bt/sensor.py index 5bb456c3c..f570c8e42 100644 --- a/custom_components/mitemp_bt/sensor.py +++ b/custom_components/mitemp_bt/sensor.py @@ -178,14 +178,15 @@ def parse_xiaomi_value(hexvalue, typecode): return {"illuminance": illum} return None + def decrypt_payload(encrypted_payload, key, nonce): - """Decrypt payload""" + """Decrypt payload.""" aad = b"\x11" token = encrypted_payload[-4:] payload_counter = encrypted_payload[-7:-4] nonce = b"".join([nonce, payload_counter]) cipherpayload = encrypted_payload[:-7] - cipher = AES.new(key, AES.MODE_CCM, nonce=nonce, mac_len = 4) + cipher = AES.new(key, AES.MODE_CCM, nonce=nonce, mac_len=4) cipher.update(aad) plaindata = None try: @@ -199,7 +200,8 @@ def decrypt_payload(encrypted_payload, key, nonce): return None return plaindata -def parse_raw_message(data, aeskeyslist, report_unknown = False): + +def parse_raw_message(data, aeskeyslist, report_unknown=False): """Parse the raw data.""" if data is None: return None @@ -237,14 +239,14 @@ def parse_raw_message(data, aeskeyslist, report_unknown = False): data.hex() ) return None - #Frame control bits + # frame control bits framectrl, = struct.unpack('>H', data[xiaomi_index + 3:xiaomi_index + 5]) - #check data is present + # check data is present if not (framectrl & 0x4000): return None xdata_length = 0 xdata_point = 0 - #check capability byte present + # check capability byte present if framectrl & 0x2000: xdata_length = -1 xdata_point = 1 @@ -263,13 +265,13 @@ def parse_raw_message(data, aeskeyslist, report_unknown = False): # check if xiaomi data start and length is valid if xdata_length != len(data[xdata_point:-1]): return None - #check encrypted data flags + # check encrypted data flags if framectrl & 0x0800: - #try to find encryption key for current device + # try to find encryption key for current device try: key = aeskeyslist[xiaomi_mac_reversed] except KeyError: - #No encryption key found + # no encryption key found return None nonce = b"".join( [ @@ -282,11 +284,14 @@ def parse_raw_message(data, aeskeyslist, report_unknown = False): data[xdata_point:msg_length-1], key, nonce ) if decrypted_payload is None: - _LOGGER.error("Decryption failed for %s, decrypted payload is None", ''.join('{:02X}'.format(x) for x in xiaomi_mac_reversed[::-1])) + _LOGGER.error( + "Decryption failed for %s, decrypted payload is None", + "".join("{:02X}".format(x) for x in xiaomi_mac_reversed[::-1]), + ) return None - #replace cipher with decrypted data + # replace cipher with decrypted data msg_length -= len(data[xdata_point:msg_length-1]) - data = b"".join((data[:xdata_point],decrypted_payload,data[-1:])) + data = b"".join((data[:xdata_point], decrypted_payload, data[-1:])) msg_length += len(decrypted_payload) packet_id = data[xiaomi_index + 7] result = { @@ -364,18 +369,13 @@ def setup_platform(hass, config, add_entities, discovery_info=None): """Set up the sensor platform.""" def reverse_mac(rmac): - """change LE order to BE""" - if len(rmac)!=12: + """Change LE order to BE.""" + if len(rmac) != 12: return None - return (rmac[10:12] - + rmac[8:10] - + rmac[6:8] - + rmac[4:6] - + rmac[2:4] - + rmac[0:2]) + return rmac[10:12] + rmac[8:10] + rmac[6:8] + rmac[4:6] + rmac[2:4] + rmac[0:2] def lpacket(mac, packet=None): - """last_packet static storage""" + """Last_packet static storage.""" if packet is not None: lpacket.cntr[mac] = packet else: @@ -391,8 +391,10 @@ def lpacket(mac, packet=None): scanner.start(config) sensors_by_mac = {} if config[CONF_REPORT_UNKNOWN]: - _LOGGER.info("Attention! Option report_unknown is enabled, be ready for a huge output...") - #prepare device:key list to speedup parser + _LOGGER.info( + "Attention! Option report_unknown is enabled, be ready for a huge output..." + ) + # prepare device:key list to speedup parser aeskeys = {} for mac in config[CONF_ENCRYPTORS]: p_mac = bytes.fromhex(reverse_mac(mac.replace(":", "")).lower()) @@ -401,7 +403,9 @@ def lpacket(mac, packet=None): lpacket.cntr = {} sleep(1) - def calc_update_state(entity_to_update, sensor_mac, config, measurements_list, stype = None): + def calc_update_state( + entity_to_update, sensor_mac, config, measurements_list, stype=None + ): """Averages according to options and updates the entity state.""" textattr = "" success = False @@ -472,7 +476,7 @@ def discover_ble_devices(config, aeskeyslist): if data and "mac" in data: # ignore duplicated message packet = data["packet"] - prev_packet = lpacket(mac = data["mac"]) + prev_packet = lpacket(mac=data["mac"]) if prev_packet == packet: # _LOGGER.debug("DUPLICATE: %s, IGNORING!", data) continue @@ -578,20 +582,20 @@ def discover_ble_devices(config, aeskeyslist): # averaging and states updating if mac in batt: if config[CONF_BATT_ENTITIES]: - setattr(sensors[b_i], "_state", batt[mac]) - try: - sensors[b_i].schedule_update_ha_state() - except AttributeError: - _LOGGER.debug( - "Sensor %s (%s, batt.) not yet ready for update", - mac, - stype[mac] - ) - except RuntimeError as err: - _LOGGER.error( - "Sensor %s (%s, batt.) update error:", mac, stype[mac] - ) - _LOGGER.error(err) + setattr(sensors[b_i], "_state", batt[mac]) + try: + sensors[b_i].schedule_update_ha_state() + except AttributeError: + _LOGGER.debug( + "Sensor %s (%s, batt.) not yet ready for update", + mac, + stype[mac], + ) + except RuntimeError as err: + _LOGGER.error( + "Sensor %s (%s, batt.) update error:", mac, stype[mac] + ) + _LOGGER.error(err) if mac in temp_m_data: success, error = calc_update_state( sensors[t_i], mac, config, temp_m_data[mac] From dbff8739b614578a8c562fc7be6ff04130003cb8 Mon Sep 17 00:00:00 2001 From: Ernst Klamer Date: Sun, 8 Mar 2020 11:17:28 +0100 Subject: [PATCH 30/32] Logger level from info to debug _LOGGER.info is reserved for the core, use _LOGGER.debug for anything else. --- custom_components/mitemp_bt/sensor.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/custom_components/mitemp_bt/sensor.py b/custom_components/mitemp_bt/sensor.py index f570c8e42..5df528aa5 100644 --- a/custom_components/mitemp_bt/sensor.py +++ b/custom_components/mitemp_bt/sensor.py @@ -232,7 +232,7 @@ def parse_raw_message(data, aeskeyslist, report_unknown=False): ] except KeyError: if report_unknown: - _LOGGER.info( + _LOGGER.debug( "BLE ADV from UNKNOWN: RSSI: %s, MAC: %s, ADV: %s", rssi, ''.join('{:02X}'.format(x) for x in xiaomi_mac_reversed[::-1]), @@ -391,7 +391,7 @@ def lpacket(mac, packet=None): scanner.start(config) sensors_by_mac = {} if config[CONF_REPORT_UNKNOWN]: - _LOGGER.info( + _LOGGER.debug( "Attention! Option report_unknown is enabled, be ready for a huge output..." ) # prepare device:key list to speedup parser From 9ee445bd181c0b6f00bba6b59bb98ec2dbd0fb1a Mon Sep 17 00:00:00 2001 From: Ernst Klamer Date: Sun, 8 Mar 2020 11:19:17 +0100 Subject: [PATCH 31/32] change info level to debug level --- faq.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/faq.md b/faq.md index 206be51a9..6128050c5 100644 --- a/faq.md +++ b/faq.md @@ -152,13 +152,13 @@ Most often, the cause of this is the presence of bugs in the system components r ### My sensor from the Xiaomi ecosystem is not in the list of supported ones. How to request implementation? - [Install the component](https://github.com/custom-components/sensor.mitemp_bt/blob/master/README.md#how-to-install) if you have not already done so. -- Make sure you have [logger](https://www.home-assistant.io/integrations/logger/) enabled, and logging enabled for `info` level (globally or just for `custom_components.mitemp_bt`). For example: +- Make sure you have [logger](https://www.home-assistant.io/integrations/logger/) enabled, and logging enabled for `debug` level (globally or just for `custom_components.mitemp_bt`). For example: ```yaml logger: default: warn logs: - custom_components.mitemp_bt: info + custom_components.mitemp_bt: debug ``` - Place your sensor extremely close to the HA host (BT interface). From 3e00adeb6924b320a27e6784bd22ce159aeed052 Mon Sep 17 00:00:00 2001 From: Aleksey Makarenko Date: Sun, 8 Mar 2020 16:16:03 +0300 Subject: [PATCH 32/32] debug level logging instead of info --- faq.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/faq.md b/faq.md index 6128050c5..2c52c4569 100644 --- a/faq.md +++ b/faq.md @@ -163,7 +163,7 @@ logger: - Place your sensor extremely close to the HA host (BT interface). - [Enable the option](https://github.com/custom-components/sensor.mitemp_bt/blob/master/README.md#configuration) `report_unknown`. -- Wait until a number of "BLE ADV from UNKNOWN" messages accumulate in the log. The component will put these messages at the `info` level! +- Wait until a number of "BLE ADV from UNKNOWN" messages accumulate in the log. - Create a new [issue](https://github.com/custom-components/sensor.mitemp_bt/issues), write everything you know about your sensor and attach the obtained log. - Do not forget to disable the `report_unknown` option (delete it or set it to `False` and restart HA). - Wait for a response from the developers.