aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorArslaan Pathan <[email protected]>2026-03-29 14:53:48 +1300
committerArslaan Pathan <[email protected]>2026-03-29 14:53:48 +1300
commit6f1e4aa35cb206c066e8395489c3d514b8079375 (patch)
tree56e3412b0378df698e9a6050980baa2c485ed3b5
downloadzwzn-freefit-re-6f1e4aa35cb206c066e8395489c3d514b8079375.tar.xz
zwzn-freefit-re-6f1e4aa35cb206c066e8395489c3d514b8079375.zip
Initial commit
-rw-r--r--FereFit_BLE_test.py26
-rw-r--r--README.md59
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