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
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].
Infos
- Home Assistant: https://www.home-assistant.io
- Wireshark: https://www.wireshark.org
- Android SDK Platform Tools: https://developer.android.com/studio/releases/platform-tools
- APKPure: https://apkpure.com
- jadx: https://github.com/skylot/jadx
- Bleak: https://bleak.readthedocs.io
- bluetooth-clocks: https://github.com/koenvervloesem/bluetooth-clocks
- Vervloesem, Koen. Develop your own Bluetooth Low Energy Applications. Elektor, https://www.elektor.com/develop-your-own-bluetooth-low-energy-applications
« Previous 1 2 3 4
Buy this article as PDF
(incl. VAT)
Buy Linux Magazine
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.
News
-
Plasma 6.3 Ready for Public Beta Testing
Plasma 6.3 will ship with KDE Gear 24.12.1 and KDE Frameworks 6.10, along with some new and exciting features.
-
Budgie 10.10 Scheduled for Q1 2025 with a Surprising Desktop Update
If Budgie is your desktop environment of choice, 2025 is going to be a great year for you.
-
Firefox 134 Offers Improvements for Linux Version
Fans of Linux and Firefox rejoice, as there's a new version available that includes some handy updates.
-
Serpent OS Arrives with a New Alpha Release
After months of silence, Ikey Doherty has released a new alpha for his Serpent OS.
-
HashiCorp Cofounder Unveils Ghostty, a Linux Terminal App
Ghostty is a new Linux terminal app that's fast, feature-rich, and offers a platform-native GUI while remaining cross-platform.
-
Fedora Asahi Remix 41 Available for Apple Silicon
If you have an Apple Silicon Mac and you're hoping to install Fedora, you're in luck because the latest release supports the M1 and M2 chips.
-
Systemd Fixes Bug While Facing New Challenger in GNU Shepherd
The systemd developers have fixed a really nasty bug amid the release of the new GNU Shepherd init system.
-
AlmaLinux 10.0 Beta Released
The AlmaLinux OS Foundation has announced the availability of AlmaLinux 10.0 Beta ("Purple Lion") for all supported devices with significant changes.
-
Gnome 47.2 Now Available
Gnome 47.2 is now available for general use but don't expect much in the way of newness, as this is all about improvements and bug fixes.
-
Latest Cinnamon Desktop Releases with a Bold New Look
Just in time for the holidays, the developer of the Cinnamon desktop has shipped a new release to help spice up your eggnog with new features and a new look.