Skip to content

Commit c002ae9

Browse files
authored
Merge pull request #1259 from frickeo/enhancement-913_bleak-instead-bluepy
enhancement #913: Updated 'get_beacon_key.py' to use bleak instead of bluepy
2 parents ad2e45c + 478d3f8 commit c002ae9

File tree

2 files changed

+65
-33
lines changed

2 files changed

+65
-33
lines changed

custom_components/ble_monitor/ble_parser/get_beacon_key.py

+64-32
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
#!/usr/bin/env python3
22

33
# Usage:
4-
# pip3 install bluepy
4+
# pip3 install bleak asyncio
55
# python3 get_beacon_key.py <MAC> <PRODUCT_ID>
66
#
77
# List of PRODUCT_ID:
@@ -14,20 +14,24 @@
1414
# Example:
1515
# python3 get_beacon_key.py AB:CD:EF:12:34:56 950
1616

17+
import asyncio
1718
import random
1819
import re
1920
import sys
2021

21-
from bluepy.btle import UUID, DefaultDelegate, Peripheral
22+
from bleak import BleakClient
23+
from bleak.uuids import normalize_uuid_16
2224

2325
MAC_PATTERN = r"^[0-9A-F]{2}:[0-9A-F]{2}:[0-9A-F]{2}:[0-9A-F]{2}:[0-9A-F]{2}:[0-9A-F]{2}$"
2426

2527
UUID_SERVICE = "fe95"
2628

27-
HANDLE_AUTH = 3
28-
HANDLE_FIRMWARE_VERSION = 10
29-
HANDLE_AUTH_INIT = 19
30-
HANDLE_BEACON_KEY = 25
29+
# The characteristics of the 'fe95' service have unique uuid values and thus can be addressed via their uuid
30+
# this can be checked by using the service explorer from https://github.com/hbldh/bleak/blob/master/examples/service_explorer.py
31+
HANDLE_AUTH = normalize_uuid_16(0x0001)
32+
HANDLE_FIRMWARE_VERSION = normalize_uuid_16(0x0004)
33+
HANDLE_AUTH_INIT = normalize_uuid_16(0x0010)
34+
HANDLE_BEACON_KEY = normalize_uuid_16(0x0014)
3135

3236
MI_KEY1 = bytes([0x90, 0xCA, 0x85, 0xDE])
3337
MI_KEY2 = bytes([0x92, 0xAB, 0x54, 0xFA])
@@ -94,7 +98,7 @@ def generateRandomToken() -> bytes:
9498
return token
9599

96100

97-
def get_beacon_key(mac, product_id):
101+
async def get_beacon_key(mac, product_id):
98102
reversed_mac = reverseMac(mac)
99103
token = generateRandomToken()
100104

@@ -103,29 +107,57 @@ def get_beacon_key(mac, product_id):
103107

104108
# Connect
105109
print("Connection in progress...")
106-
peripheral = Peripheral(deviceAddr=mac)
107-
print("Successful connection!")
108-
109-
# Auth (More information: https://github.com/archaron/docs/blob/master/BLE/ylkg08y.md)
110-
print("Authentication in progress...")
111-
auth_service = peripheral.getServiceByUUID(UUID_SERVICE)
112-
auth_descriptors = auth_service.getDescriptors()
113-
peripheral.writeCharacteristic(HANDLE_AUTH_INIT, MI_KEY1, "true")
114-
auth_descriptors[1].write(SUBSCRIBE_TRUE, "true")
115-
peripheral.writeCharacteristic(HANDLE_AUTH, cipher(mixA(reversed_mac, product_id), token), "true")
116-
peripheral.waitForNotifications(10.0)
117-
peripheral.writeCharacteristic(3, cipher(token, MI_KEY2), "true")
118-
print("Successful authentication!")
119-
120-
# Read
121-
beacon_key = cipher(token, peripheral.readCharacteristic(HANDLE_BEACON_KEY)).hex()
122-
firmware_version = cipher(token, peripheral.readCharacteristic(HANDLE_FIRMWARE_VERSION)).decode()
123-
124-
print(f"beaconKey: '{beacon_key}'")
125-
print(f"firmware_version: '{firmware_version}'")
126-
127-
128-
def main(argv):
110+
client = BleakClient(mac)
111+
try:
112+
await client.connect()
113+
print("Successful connection!")
114+
115+
# An asyncio future object is needed for callback handling
116+
future = asyncio.get_event_loop().create_future()
117+
118+
# Auth (More information: https://github.com/archaron/docs/blob/master/BLE/ylkg08y.md)
119+
print("Authentication in progress...")
120+
121+
# Send 0x90, 0xCA, 0x85, 0xDE bytes to authInitCharacteristic.
122+
await client.write_gatt_char(HANDLE_AUTH_INIT, MI_KEY1, True)
123+
# Subscribe authCharacteristic.
124+
# (a lambda callback is used to set the futures result on the notification event)
125+
await client.start_notify(HANDLE_AUTH, lambda _, data: future.set_result(data))
126+
# Send cipher(mixA(reversedMac, productID), token) to authCharacteristic.
127+
await client.write_gatt_char(HANDLE_AUTH, cipher(mixA(reversed_mac, product_id), token), True)
128+
# Now you'll get a notification on authCharacteristic. You must wait for this notification before proceeding to next step
129+
await asyncio.wait_for(future, 10.0)
130+
131+
# The notification data can be ignored or used to check an integrity, this is optional
132+
print(f"notifyData: '{future.result().hex()}'")
133+
# If you want to perform a check, compare cipher(mixB(reversedMac, productID), cipher(mixA(reversedMac, productID), res))
134+
# where res is received payload ...
135+
print(f"cipheredRes: '{cipher(mixB(reversed_mac, product_id), cipher(mixA(reversed_mac, product_id), future.result())).hex()}'")
136+
# ... with your token, they must equal.
137+
print(f"randomToken: '{token.hex()}'")
138+
139+
# Send 0x92, 0xAB, 0x54, 0xFA to authCharacteristic.
140+
await client.write_gatt_char(HANDLE_AUTH, cipher(token, MI_KEY2), True)
141+
print("Successful authentication!")
142+
143+
# Read
144+
beacon_key = cipher(token, await client.read_gatt_char(HANDLE_BEACON_KEY)).hex()
145+
# Read from verCharacteristics. You can ignore the response data, you just have to perform a read to complete authentication process.
146+
# If the data is used, it will show the firmware version
147+
firmware_version = cipher(token, await client.read_gatt_char(HANDLE_FIRMWARE_VERSION)).decode()
148+
149+
print(f"beaconKey: '{beacon_key}'")
150+
print(f"firmware_version: '{firmware_version}'")
151+
152+
print("Disconnection in progress...")
153+
except Exception as e:
154+
print(e)
155+
finally:
156+
await client.disconnect()
157+
print("Disconnected!")
158+
159+
160+
async def main(argv):
129161
# ARGS
130162
if len(argv) <= 2:
131163
print("usage: get_beacon_key.py <MAC> <PRODUCT_ID>\n")
@@ -152,8 +184,8 @@ def main(argv):
152184
return
153185

154186
# BEACON_KEY
155-
get_beacon_key(mac, product_id)
187+
await get_beacon_key(mac, product_id)
156188

157189

158190
if __name__ == '__main__':
159-
main(sys.argv)
191+
asyncio.run(main(sys.argv))

docs/faq.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -336,7 +336,7 @@ We have created a python script that will get the beaconkey by connecting to the
336336
```
337337
wget https://raw.githubusercontent.com/custom-components/ble_monitor/master/custom_components/ble_monitor/ble_parser/get_beacon_key.py
338338
apt-get install python3-pip libglib2.0-dev
339-
pip3 install bluepy
339+
pip3 install bleak asyncio
340340
python3 get_beacon_key.py <MAC> <PRODUCT_ID>
341341
```
342342
Replace `<MAC>` with your MAC address of the remote/dimmer and replace `<PRODUCT_ID>` with one of the following numbers, corresponding to your remote/dimmer.

0 commit comments

Comments
 (0)