Reverse engineering a BLE clock

Finding Clocks

You now have a pretty good idea of the command format to send to the Bluetooth clock in order to set its time. I'll put this all together and create a Python script to do this without the app. You can do this with Bleak [6], a cross-platform BLE client library for Python. Install it with:

pip3 install bleak

First, you scan for all BLE devices in the vicinity and show their names and addresses. The script find_devices.py (Listing 2) runs the main function asynchronously (because Bleak uses asyncio). This function creates a BleakScanner object, with device_found as a callback function that's called when a BLE advertisement is found. Then the script starts the scanner, waits five seconds, and stops the scanner. The device_found function just adds the device to a set of all found devices. After the scan is complete, the script shows the names and addresses of the devices in the set.

Listing 2

find_devices.py

01 import asyncio
02
03 from bleak import BleakScanner
04 from bleak.backends.device import BLEDevice
05 from bleak.backends.scanner import AdvertisementData
06
07
08 def device_found(device: BLEDevice, advertisement_data: AdvertisementData):
09   devices.add(device)
10
11
12 async def main():
13   scanner = BleakScanner(device_found)
14
15   print("Scanning for devices...")
16   await scanner.start()
17   await asyncio.sleep(5.0)
18   await scanner.stop()
19
20   for device in devices:
21     print(f"{device.name} | {device.address}")
22
23
24 if __name__ == "__main__":
25   devices = set()
26   asyncio.run(main())

If you run this Python script with python find_devices.py, after five seconds it shows the names and addresses of all the BLE devices in your vicinity. For example, my clock is found with the name TP358 (52C6), where 52C6 is the last four hexadecimal digits of the clock's address, so you now know you can search for this type of Bluetooth clock by filtering on the name starting with TP358. You only have to change the callback function to get the script find_clocks.py:

def device_found(device: BLEDevice,advertisement_data: AdvertisementData):
  if device.name.startswith("TP358"):
    devices.add(device)

Sending the Time

Now that you know your clock's Bluetooth address, you can write another script, synchronize_clock.py (Listing 3), which receives this address as a command-line argument and then sets the time of the clock.

Listing 3

synchronize_clock.py

01 import asyncio
02 from datetime import datetime
03 import sys
04 from time import time
05
06 from bleak import BleakClient
07
08
09 COMMAND_CHAR = "00010203-0405-0607-0809-0a0b0c0d2b11"
10
11
12 def get_bytes_from_time(timestamp: float) -> bytes:
13   """Generate the bytes to set the time on a ThermoPro TP358.
14
15   Args:
16     timestamp (float): The time encoded as a Unix timestamp.
17
18   Returns:
19     bytes: The bytes needed to set the time of the device to `timestamp`.
20   """
21   date_time = datetime.fromtimestamp(timestamp)
22   return bytes(
23     [
24       0xA5,
25       date_time.year % 2000,
26       date_time.month,
27       date_time.day,
28       date_time.hour,
29       date_time.minute,
30       date_time.second,
31       date_time.weekday() + 1,  # Monday-Sunday -> 0-6
32       1,  # Use 24-hour format
33       0x5A,
34     ]
35   )
36
37
38 async def main(address: str):
39   print(f"Connecting to {address}...")
40
41   async with BleakClient(address) as client:
42     command = get_bytes_from_time(time())
43     await client.write_gatt_char(COMMAND_CHAR, command)
44
45   print("Time synchronized")
46
47
48 if __name__ == "__main__":
49   try:
50     asyncio.run(main(sys.argv[1]))
51   except IndexError:
52     print("Please provide the Bluetooth address of the device")

With your previous knowledge, this code is fairly straightforward. The script defines the characteristic to send the command and then defines a function, get_bytes_from_time, to convert a Unix timestamp to the bytes that need to be sent to the clock to set its time. The main function just connects to the address set on the command line, computes the correct bytes from the current time, and writes them to the right characteristic.

If you now run this command with your clock's Bluetooth address as an argument, for example,

python synchronize_clock.py E7:2E:00:B1:38:96

it should set your clock's time.

Further Work

This simple script is just the beginning. You can make it much more robust by catching some exceptions, such as connection problems. You also can add other arguments, such as the choice of the 24- or 12-hour format. This is just for one type of clock. You can add support for other types of Bluetooth clocks. I did this for all of my Bluetooth clocks, and I published the result, bluetooth-clocks, on the Python Package Index (PyPI) [7].

After installing the package with

pip3 install bluetooth-clocks

you can discover all your supported clocks as shown in Listing 4. Then you can set the clock's time with a given Bluetooth address as shown in Listing 5. If you want to regularly synchronize the time on the device, you can run Listing 5 as a service, for example, with a systemd timer or in a cron job. See Figure 5 for the result.

Listing 4

Discovering Bluetooth Clocks

01 $ bluetooth-clocks discover
02 Scanning for supported clocks...
03 Found a ThermoPro TP358: address BC:C7:DA:6A:52:C6, name TP358 (52C6)
04 Found a Xiaomi LYWSD02: address E7:2E:00:B1:38:96, name LYWSD02
05 Found a ThermoPro TP393: address 10:76:36:14:2A:3D, name TP393 (2A3D)
06 Found a Qingping BT Clock Lite: address 58:2D:34:54:2D:2C, name Qingping BT Clock Lite
07 Found a Current Time Service: address EB:76:55:B9:56:18, name F15

Listing 5

Setting a Bluetooth Clock's Time

01 $ bluetooth-clocks set -a E7:2E:00:B1:38:96
02 Scanning for device E7:2E:00:B1:38:96...
03 Writing time to device...
04 Synchronized time
Figure 5: You can now keep all your clocks synchronized.

If you have a Bluetooth clock at home that isn't supported yet by bluetooth-clocks, please try the reverse engineering approach demonstrated here and contribute your knowledge to the project.

Finally, if you want to learn more about BLE and how to use this wireless protocol in your own programs, read my book on the topic [8].

The Author

Koen Vervloesem has been writing about Linux and open source, computer security, privacy, programming, artificial intelligence, and the Internet of Things for more than 20 years. You can find more on his website at http://koen.vervloesem.eu.

Buy this article as PDF

Express-Checkout as PDF
Price $2.95
(incl. VAT)

Buy Linux Magazine

SINGLE ISSUES
 
SUBSCRIPTIONS
 
TABLET & SMARTPHONE APPS
Get it on Google Play

US / Canada

Get it on Google Play

UK / Australia

Related content

  • Bluetooth LE

    Bluetooth Low Energy is ideal for networking battery-powered sensors. We show you how to use it on the Raspberry Pi.

  • Bluetooth Security

    Is your address book open to the world? Is your mobile phone calling Russia? Many users don’t know how easy it is for an attacker to target Bluetooth.

  • Bluetooth Hacks

    The user rules in Linux – if you know where you’re going. This month the trail leads deep into the Linux Bluetooth stack.

  • Adding a Bluetooth Speaker to Linux
  • Bluetooth Mobile Phones

    It is becoming increasingly common for new generation mobile phones to have an integrated Bluetooth interface. This article explores how to access your Bluetooth phone using Linux.

comments powered by Disqus
Subscribe to our Linux Newsletters
Find Linux and Open Source Jobs
Subscribe to our ADMIN Newsletters

Support Our Work

Linux Magazine content is made possible with support from readers like you. Please consider contributing when you’ve found an article to be beneficial.

Learn More

News