Skip to content

Commit

Permalink
Fix iBBq-6 and MiScale v2
Browse files Browse the repository at this point in the history
  • Loading branch information
Ernst79 committed Mar 18, 2022
1 parent 7a9ed1c commit 0646280
Show file tree
Hide file tree
Showing 5 changed files with 111 additions and 14 deletions.
89 changes: 89 additions & 0 deletions custom_components/ble_monitor/ble_parser/ha_ble_encryption.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
"""Example showing encoding and decoding of HA BLE advertisement"""
import binascii
from Cryptodome.Cipher import AES


def parse_value(hexvalue):
"""Parse decrypted payload to readable HA BLE data"""
vlength = len(hexvalue)
if vlength >= 3:
temp = round(int.from_bytes(hexvalue[2:4], "little", signed=False) * 0.01, 2)
humi = round(int.from_bytes(hexvalue[6:8], "little", signed=False) * 0.01, 2)
print("Temperature:", temp, "Humidity:", humi)
return 1
print("MsgLength:", vlength, "HexValue:", hexvalue.hex())
return None


def decrypt_payload(payload, mic, key, nonce):
"""Decrypt payload."""
print("Nonce:", nonce.hex())
print("CryptData:", payload.hex(), "Mic:", mic.hex())
cipher = AES.new(key, AES.MODE_CCM, nonce=nonce, mac_len=4)
cipher.update(b"\x11")
try:
data = cipher.decrypt_and_verify(payload, mic)
except ValueError as error:
print("Decryption failed:", error)
return None
print("DecryptData:", data.hex())
print()
if parse_value(data) is not None:
return 1
print('??')
return None


def decrypt_aes_ccm(key, mac, data):
"""Decrypt AES CCM."""
print("MAC:", mac.hex(), "Bindkey:", key.hex())
print()
adslength = len(data)
if adslength > 15 and data[0] == 0x1E and data[1] == 0x18:
pkt = data[:data[0] + 1]
uuid = pkt[0:2]
encrypted_data = pkt[2:-8]
count_id = pkt[-8:-4]
mic = pkt[-4:]
# nonce: mac [6], uuid16 [2], count_id [4] # 6+2+4 = 12 bytes
nonce = b"".join([mac, uuid, count_id])
return decrypt_payload(encrypted_data, mic, key, nonce)
else:
print("Error: format packet!")
return None


# =============================
# main()
# =============================
def main():
"""Encrypt payload."""
print()
print("====== Test encode -----------------------------------------")
temp = 25.06
humi = 50.55
print("Temperature:", temp, "Humidity:", humi)
print()
data = bytes(bytearray.fromhex('2302CA090303BF13')) # HA BLE data (not encrypted)
count_id = bytes(bytearray.fromhex('00112233')) # count id
mac = binascii.unhexlify('5448E68F80A5') # MAC
uuid16 = b"\x1E\x18"
bindkey = binascii.unhexlify('231d39c1d7cc1ab1aee224cd096db932')
print("MAC:", mac.hex(), "Binkey:", bindkey.hex())
nonce = b"".join([mac, uuid16, count_id]) # 6+2+4 = 12 bytes
cipher = AES.new(bindkey, AES.MODE_CCM, nonce=nonce, mac_len=4)
cipher.update(b"\x11")
ciphertext, mic = cipher.encrypt_and_digest(data)
print("Data:", data.hex())
print("Nonce:", nonce.hex())
print("CryptData:", ciphertext.hex(), "Mic:", mic.hex())
adstruct = b"".join([uuid16, ciphertext, count_id, mic])
print()
print("Encrypted data:", adstruct.hex())
print()
print("====== Test decode -----------------------------------------")
decrypt_aes_ccm(bindkey, mac, adstruct)


if __name__ == '__main__':
main()
2 changes: 1 addition & 1 deletion custom_components/ble_monitor/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@

# Sensors with deviating temperature range
KETTLES = ('YM-K1501', 'YM-K1501EU', 'V-SK152')
PROBES = ('iBBQ-2', 'iBBQ-4', 'H5183')
PROBES = ('iBBQ-2', 'iBBQ-4', 'iBBQ-6', 'H5183')


# Sensor entity description
Expand Down
2 changes: 1 addition & 1 deletion custom_components/ble_monitor/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,6 @@
],
"dependencies": [],
"codeowners": ["@Ernst79", "@Magalex2x14", "@Thrilleratplay"],
"version": "8.0.5-beta",
"version": "8.1.0",
"iot_class": "local_polling"
}
4 changes: 2 additions & 2 deletions custom_components/ble_monitor/sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -906,8 +906,8 @@ def collect(self, data, period_cnt, batt_attr=None):
self._extra_state_attributes["weight removed"] = bool(
data["weight removed"]
)
if "impedance" not in data and data["type"] == "Mi Scale V2":
self._extra_state_attributes["impedance"] = "unavailable"
if "impedance" not in data and data["type"] == "Mi Scale V2" and data["weight removed"] == 0:
self._extra_state_attributes["impedance"] = "unavailable"
if "impedance" in data:
self._extra_state_attributes["impedance"] = data["impedance"]
if "weight unit" in data:
Expand Down
28 changes: 18 additions & 10 deletions docs/ha_ble.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,11 @@ nav_order: 6

### Introduction

BLE monitor has created support for your own DIY sensors, by introducing our own BLE format that can be read by BLE monitor, called `HA BLE`. This format tries to create a flexible format that can be customized to your own needs. At the moment, it is still Work in Progress, as sensors in HA are not created automatically. This will be added in a future release. For testing purposes, you can create sensors in HA by modifying this line in [const.py](https://github.com/custom-components/ble_monitor/blob/master/custom_components/ble_monitor/const.py#L860). Note that this file is overwritten on each BLE monitor update.
BLE monitor has created support for your own DIY sensors, by introducing our own BLE format that can be read by BLE monitor, called `HA BLE`. This format tries to provide an energy efective, but still flexible BLE format that can be customized to your own needs. A proof of concept is the latest [ATC firmware](https://github.com/pvvx/ATC_MiThermometer), which is supporting our new HA BLE format (firmware version 3.7 and up).

The data format has changed from earlier versions, in BLE monitor 8.0 HA BLE is using a new, more efficient, format. Note that The old format will be deprecated soon.
The data format has changed from earlier versions, starting with BLE monitor version 8.1.0 HA BLE is using a new, more efficient, format. Note that The old format will be deprecated soon.

### The HA BLE format

The `HA BLE` format can best be explained with an example. A BLE advertisement is a long message with bytes (bytestring).

Expand All @@ -33,7 +35,7 @@ This message is split up in three parts, the **header**, the **advertising paylo

The first part `043E1B02010000A5808FE648540F` is the header of the message and contains, amongst others

- the length of the message in the 3rd byte (`0x1B` = 27, meaning 27 bytes after the third byte = 30 bytes in total)
- the length of the message in the 3rd byte (`0x1B` in hex is 27 in decimals, meaning 27 bytes after the third byte = 30 bytes in total)
- the MAC address in reversed order in byte 8-13 (`A5808FE64854`, in reversed order, this corresponds to a MAC address `54:48:E6:8F:80:A5`)
- the length of the advertising payload in byte 14 (`0x0F` = 15)

Expand All @@ -43,7 +45,7 @@ The second part `0201060B161C182302C4090303BF13` contains the advertising payloa

- 1st byte: length of the element (excluding the length byte itself)
- 2nd byte: AD type – specifies what data is included in the element
- AD data – one or more bytes - the meaning is defined by AD type
- AD data – one or more bytes - the meaning is defined by the AD type

In the `HA BLE` format, the advertsing payload should contain the following two AD elements:

Expand All @@ -52,16 +54,17 @@ In the `HA BLE` format, the advertsing payload should contain the following two

In the example, we have:

- First AD element: `020106` (always the same)
- First AD element: `020106`
- `0x02` = length (2 bytes)
`0x01` = Flags
`0x06` = in bits, this is `00000110`. Bit 1 and bit 2 are 1, meaning:
Bit 1: “LE General Discoverable Mode”
Bit 2: “BR/EDR Not Supported”
- This part always has to be added, and is always the same (`0x020106`)

- Second AD element: `0B161C182302C4090303BF13` (HA BLE data)
- `0x0B` = length (11 bytes)
`0x16` = UUID16
`0x16` = Service Data - 16-bit UUID
`0x1C182302C4090303BF13` = HA BLE data

#### HA BLE data format (non-encrypted)
Expand Down Expand Up @@ -89,11 +92,11 @@ Lets explain how the last two data packets work. The temperature packet is used

- The second byte `0x02` is defining the type of measurement (temperature, see table below)
- The remaining bytes `0xC409` is the object value (little endian), which will be multiplied with the factor in the table below to get a sufficient number of digits.
- The object length is telling us that the value is 2 bytes long (object length = 3 bytes minus the second byte) and the object format is telling us that the value is an Signed Integer.
- The object length is telling us that the value is 2 bytes long (object length = 3 bytes minus the second byte) and the object format is telling us that the value is an Signed Integer (possitive or negative integer).
- `0xC409` as unsigned integer in little endian is equal to 2500.
- The factor for a temperature measurement is 0.01, resulting in a temperature of 25.00°C

At the moment, the following sensors are supported. An preferred data type is given for your convienience, which should give you a short data message and at the same time a sufficient number of digits to display your data with high accuracy in Home Assistant. But you are free to use a different data type. If you want another sensor, let us know by creating a new issue on Github.
At the moment, the following sensors are supported. A preferred data type is given for your convienience, which should give you a short data message and at the same time a sufficient number of digits to display your data with high accuracy in Home Assistant. But you are free to use a different data type. If you want another sensor, let us know by creating a new issue on Github.

| Object id | Property | Preferred data type | Factor | example | result | Unit in HA | Notes |
| --------- | ----------- | --------------------| -------| ---------------- | -------------| -----------| ----- |
Expand Down Expand Up @@ -128,7 +131,7 @@ The `packet id` is optional and is used to filter duplicate data. This allows yo

***2. weight (unit)***

The `weight unit` is in `kg` by default, but can be set the the weight unit property. Examples of `weight unit` packets are:
The `weight unit` is in `kg` by default, but can be set with the weight unit property. Examples of `weight unit` packets are:
- kg (`63076B67`)
- lbs (`64076C6273`)
- jin (`64076A696E`)
Expand All @@ -137,6 +140,11 @@ The `weight unit` is in `kg` by default, but can be set the the weight unit prop

You don't have to specify the `mac` address in the advertising payload, as it is already included in the [header](#header). However, you can overwrite the `mac` by specifying it in the advertising payload. To do this, set the first byte to `0x86` (meaning: object type = 4 (`mac`) and object length = 6), followed by the MAC in reversed order. No Object id is needed.

***Restore state with restart***

Currently, the state of a sensor is not restored when restarting HA, even when this is enabled in the BLE monitor settings. This will be fixed in a future update.


#### HA BLE data format (encrypted)

You can also choose to send the data in an encrypted way, which gives you extra security. BLE advertisements can be read by everyone, even your neighbour with Home Assistant and BLE Monitor will be able to receive your BLE data. Howevern, when you use encryption, it will be useless for your neighbour, as long as he doesn't have the encryption key. The encryption key should be a 16 bytes long key (32 characters). More information on how to encrypt your messages will be added here soon.
You can also choose to send the data in an encrypted way, which gives you extra security. Unencrypted BLE advertisements can be read by everyone, even your neighbour with Home Assistant and BLE Monitor should in theory be able to receive your BLE data. However, when you use encryption, it will be useless for anyone else, as long as he or she doesn't have the encryption key. The encryption key should be a 16 bytes long key (32 characters). More information on how to encrypt your messages is demonstrated in [this script](https://github.com/custom-components/ble_monitor/blob/master/custom_components/ble_monitor/ble_parser/ha_ble_encryption.py). HA BLE monitor is using an AES encryption (CCM mode). Don't forget to set the encryption key in your BLE monitor device settings.

0 comments on commit 0646280

Please sign in to comment.