diff options
| author | Arslaan Pathan <[email protected]> | 2026-03-29 14:53:48 +1300 |
|---|---|---|
| committer | Arslaan Pathan <[email protected]> | 2026-03-29 14:53:48 +1300 |
| commit | 6f1e4aa35cb206c066e8395489c3d514b8079375 (patch) | |
| tree | 56e3412b0378df698e9a6050980baa2c485ed3b5 | |
| download | zwzn-freefit-re-6f1e4aa35cb206c066e8395489c3d514b8079375.tar.xz zwzn-freefit-re-6f1e4aa35cb206c066e8395489c3d514b8079375.zip | |
Initial commit
| -rw-r--r-- | FereFit_BLE_test.py | 26 | ||||
| -rw-r--r-- | README.md | 59 |
2 files changed, 85 insertions, 0 deletions
diff --git a/FereFit_BLE_test.py b/FereFit_BLE_test.py new file mode 100644 index 0000000..59efac0 --- /dev/null +++ b/FereFit_BLE_test.py @@ -0,0 +1,26 @@ +import asyncio +from bleak import BleakScanner, BleakClient +import time, calendar + +async def sync(device_name: str): + print(f"Scanning for {device_name}...") + device = await BleakScanner.find_device_by_name(device_name, timeout=10) + if not device: + print("Watch not found!") + return + print(f"Found at {device.address}") + try: + async with BleakClient(device) as client: + ts = int(time.time()) + offset = calendar.timegm(time.localtime()) - calendar.timegm(time.gmtime()) + packet = bytes([0x01]) + ts.to_bytes(4,'big') + offset.to_bytes(4,'big') + bytes([0,0,0]) + await client.write_gatt_char("6E40FC20-B5A3-F393-E0A9-E50E24DCCA9E", packet) + print(f"Time synced! ts={ts} offset={offset}") + except Exception as e: + print(f"Failed: {e}") + +if __name__ == "__main__": + watch_name = input("Enter watch name shown in BLE discovery (default: Watch ULTRA): ") + if watch_name == "": + watch_name = "Watch ULTRA" + asyncio.run(sync(watch_name)) diff --git a/README.md b/README.md new file mode 100644 index 0000000..21dbd27 --- /dev/null +++ b/README.md @@ -0,0 +1,59 @@ +# freefit (also known as: FereFit, Lyne_Wearables, HomieFit, zwsvibe, WIRCASS) + +Reverse-engineering a sketchy Chinese watch app + +Documentation/protocols derived from decompiled FereFit Android app (jadx) +(com.czw.freefit, SHENZHEN ZHONGWEI INTELLIGENT TECHNOLOGY Co.,Ltd) + +## BLE characteristics + +### Main characteristics (presumably Nordic UART/Nordic semiconductor chips) +- Service UUID: 6E40FC00-B5A3-F393-E0A9-E50E24DCCA9E +- Write characteristic UUID: 6E40FC20-B5A3-F393-E0A9-E50E24DCCA9E +- Notify characteristic UUID: 6E40FC21-B5A3-F393-E0A9-E50E24DCCA9E + +### Jieli/JL chip characteristics (untested) +- Service UUID: 0000ae00-0000-1000-8000-00805f9b34fb +- Write characteristic UUID: 0000ae01-0000-1000-8000-00805f9b34fb +- Notify characteristic UUID: 0000ae02-0000-1000-8000-00805f9b34fb + +## Protocols/functions + +### Battery status/notification (read/6E40FC21) + +Sent by watch if command is invalid. TODO find proper trigger for this +byte[0] = 0x94 (BATTERY command/response) +byte[1] = battery percentage (0-100) + +### syncTime + +Time sync packet structure (write/6E40FC20) +byte[0] = 0x01 (command) +byte[1-4] = Unix timestamp (big endian, seconds) +byte[5-8] = timezone offset (big endian, seconds) +byte[9] = i (unknown param, use 0x00) +byte[10] = language code +byte[11] = 0x01 if traditional Chinese, else 0x00 + +Response: 0x81 0x00 (success, notify/6E40FC21) + +### enterMakeDial (watch face) + +Watch face header packet (write/6EFC20) +byte[0] = 0xE4 (ZK_DIAL command) +byte[1] = 0x51 (mode flag) +byte[2] = 0x01 (start) +byte[3] = 0x00 +byte[4-5] = total packet count (big endian) +byte[6-9] = total image bytes (big endian) +byte[10] = 0x00 +byte[11-12] = MTU size (big endian) +byte[13] = rotation flag +byte[14] = 0x01 +byte[15] = time text direction +byte[16] = 0x00 +byte[17-18] = transparent color (RGB565) +byte[19-20] = checksum (sum of all image bytes, big endian) +byte[21] = show date (0x01 = yes, 0x00 = no) + +Header packet is followed by chunked RGB565 data, 1 chunk = MTU-14 bytes |
