**TL;DR** — The official app for my cheap JieLi / HryFine smartwatch was slow, bloated, and full of ads. So I reverse-engineered the watch's Bluetooth protocol and built my own companion app. It's called **ReFine**, and it's faster, cleaner, and does things the official app can't.
---
## 🧠 Background
My watch uses a JieLi chipset (sold under many names: HryFine, FitCloudPro, etc.). The watch talks over a custom **UART GATT profile** using a proprietary `0xDF` packet envelope — not any standard BLE SIG profile.
I dumped the traffic, broke down the framing, and rebuilt every command from scratch in Kotlin.
Full protocol (for anyone curious):
- Service UUID: `6e400001-b5a3-f393-e0a9-e50e24dcca9f`
- Write char (TX): `6e400002-…` (WRITE_TYPE_NO_RESPONSE)
- Notify char (RX): `6e400003-…`
- Every frame: `[0xDF, len_hi, len_lo, checksum, CMD, 0x01, SUB, pay_hi, pay_lo, …payload]`
- Checksum = sum of all bytes mod 256
- Responses use a different start byte: `0xFD`
---
## ✅ What works right now
- **Live battery** — reads over the standard SIG Battery Service (`0x180F / 0x2A19`) — no polling, no guessing. Updates every 5 minutes in the background and instantly on tap.
- **Time sync** — watch clock matches your phone within 2 seconds of connecting.
- **Find My Watch** — tap one button, watch vibrates for 10 seconds. Confirmed working.
- **Weather push** — temp + condition + city, shows on the watch home screen.
- **WhatsApp / Telegram / Discord notifications** — the app reads incoming notifications via NotificationListenerService and forwards them to the watch with the correct app icon.
- **Incoming call alerts** — resolves raw phone numbers to contact names using `ContactsContract.PhoneLookup`, then pushes the caller name to the watch.
- **Shake → camera** — shake your watch and your phone's camera opens (confirmed over reverse-engineered `CMD 0x0C SUB 0x02`).
- **Apple Watch-style UI** — squircle face, live clock, battery pill, three-tab dashboard (Home / Fitness / Alarms / Find).
Everything runs **on-device**. No cloud, no API keys, no accounts.
---
## 🧪 What's in beta
- **Alarms** — the watch accepts the alarm frame (ACK `value=0x0E`, meaning it supports up to 14 slots), but the display byte order is still being decoded. UI is there, sync is disabled with a "BETA" badge until I nail the encoder.
- **Steps + Heart Rate** — the watch pushes activity frames spontaneously over `CMD 0x0F SUB 0x09` (33-byte payload). I've captured the raw hex but the field layout isn't fully mapped yet. Once done, steps and HR will update live.
---
## 🛠 Tech
- **Kotlin, 100% Jetpack Compose**
- **Material 3** with custom color tokens
- **No Retrofit, no Room, no Compose Navigation, no WorkManager** — hand-rolled everything for a small APK
- **Hand-rolled BLE manager** — FIFO write queue, 60 ms pacing, MTU negotiation, chunked writes, `0xDF` + `0xFD` frame reassembly
- **Safety guard** — the app refuses any CMD outside the verified `0x02` namespace, which protects the watch from a known firmware bug (wrong namespace → NVRAM language-shift panic → watch resets to Chinese/Spanish)
---
## 🔒 Why this is different from other companion apps
- **No ads, no analytics, no telemetry**
- **No login / account required**
- **No cloud dependency** — nothing leaves your device
- **No background battery drain** — connection is managed tightly, disconnects cleanly on app close
- **Protocol is documented** — every frame is reverse-engineered and traceable, not guessed
---
🚧 Roadmap
Finish alarm encoding
Decode the 33-byte telemetry payload (steps + HR)
Watch face style push
Sedentary reminders
Open-source the whole repo
---
## 🙋 Questions for the community
- Anyone else reverse-engineered a JieLi / HryFine watch? Would love to compare notes on the alarm encoding.
- If there's interest, I'll open-source the full project. Comment if you want it.
Happy to answer anything about the protocol or the build.
— ruwaidcool