Ring / wearable model
SY01 - RWfit
Advertised BLE name
SY01
Primary BLE service UUID(s)
0000a00a-0000-1000-8000-00805f9b34fb
Characteristics (write / notify UUIDs)
Used claude + android debugging and de-compiled the official apk. had it do a whole write up of the connectivity and data. I dont have any sleep data just yet.
https://www.aliexpress.us/item/3256811727783582.html Ring in question.
SY01 Ring — Protocol Notes
How PulseLoop talks to the SY01 ring: the SDK underneath it, the connection sequence, the raw
bytes that come off the ring, how those bytes get parsed, and exactly what PulseLoop reads out of
them. Everything marked VERIFIED was checked byte-for-byte against a real reading during
reverse-engineering; everything marked UNVERIFIED is a documented guess PulseLoop does not act
on yet.
Source material: a BLE HCI snoop capture of the official RWFit app (com.rw.revivalfit) talking to
a real SY01, cross-checked against a decompile of that app's classes.dex.
1. What SDK this actually is
The ring's MCU is a Zhuhai Jieli Technology ("Jieli") chip — a very common BLE SoC used across
cheap Chinese wearables. Two pieces of evidence nail this down:
- The APK ships native libraries
libjl_ota_auth.so and libjl_bmp_convert.so (the jl_ prefix is
Jieli's signature), plus a com.jieli.jl_bt_ota package handling firmware OTA updates.
- The app's internal class/field naming is full of
JL-prefixed identifiers for sync data —
JLStepSyncBean, JLSleepSyncBean — matching Jieli's reference naming conventions.
Important distinction: Jieli's public SDK covers the BLE transport and OTA firmware update
mechanism — that part is genuinely "Jieli's SDK." The actual health-data protocol (the
CMD/Key/KeyFlag framing and everything below in this doc) is not part of any public Jieli SDK —
it's custom code the RWFit app itself implements on top of the Jieli transport. There was no
existing documentation or open-source project (unlike, say, the Colmi/QRing family, which
Gadgetbridge already reverse-engineered) — this was reconstructed from scratch from the capture
and decompile.
2. How PulseLoop connects to it
Standard BLE GATT, no pairing/bonding, no authentication:
-
Scan for a peripheral advertising GATT service 0000a00a-0000-1000-8000-00805f9b34fb.
(We don't have a captured advertised local name to match on — unlike the jring's SMART_RING or
Colmi's R02_* patterns — so PulseLoop currently recognizes it by service UUID alone.)
-
Connect and discover services. The ring exposes several GATT services, only one of which
matters for health data:
| Service UUID |
What it is |
1800, 1801 |
Standard BLE generic access/attribute services — ignored |
1812 |
HID over GATT — the ring's tap/touch gesture interface, unrelated to health data |
ff00 |
Custom service, characteristics ff01/ff02 — present but never used in any capture |
a00a |
The real health-data service |
0bc0 |
Jieli OTA firmware-update service (00010203-...-1912/...-2b12) — separate from health data entirely |
-
Within service a00a: write to characteristic b002, enable notifications on
characteristic b003 (standard CCCD write to 2902). Every command goes out on b002; every
reply/push comes back on b003.
-
No GATT Battery Service (180F/2a19) exists — battery comes back in-band over the same
protocol, like everything else.
There's no encryption, no bonding, no authentication handshake beyond a capability/bind exchange
(§6). Anyone in BLE range who knows this protocol can read your ring's data — the same is true of
essentially every cheap BLE wearable in this price bracket.
3. The raw wire format
Every message in both directions is a 0xAB-prefixed frame:
byte 0: 0xAB magic / frame start
byte 1: type 0x01 on every outbound command; inbound varies 0x01/0x11
bytes 2-3: declared length big-endian u16 (see note below)
bytes 4-5: CRC16 big-endian, computed over bytes 6+ (see §4)
byte 6: CMD coarse category
byte 7: Key the real command selector
byte 8: KeyFlag 0x10 = start/query, 0x30 = stop, 0x00 = plain
byte 9+: data payload, meaning depends on (CMD, Key)
Example — a real captured frame, the battery reply that decoded to 83%:
ab 11 00 06 48 bd 02 03 10 53 10 a4
│ │ │ │ │ │ │ │ │ └──────┘
│ │ │ │ │ │ │ │ │ data: [0x53, 0x10, 0xa4] → battery% = data[0] = 0x53 = 83
│ │ │ │ │ │ │ │ └─ KeyFlag = 0x10 (query)
│ │ │ │ │ │ │ └──── Key = 0x03
│ │ │ │ │ │ └─────── CMD = 0x02 ("system")
│ │ │ │ └──┴────────── CRC16 = 0x48bd, over bytes [02 03 10 53 10 a4]
│ │ └──┴───────────────── declared length = 0x0006 = 6 bytes (matches the 6 payload bytes here)
│ └────────────────────── type = 0x11 (data reply)
└───────────────────────── 0xAB magic
The "declared length" field is not fully reliable. For several commands (steps, sleep) it
declares a much larger length (e.g. 51 bytes) than what's actually delivered in the physical BLE
notification (e.g. 14 bytes) — and no continuation packet ever follows. Every capture we took
showed the app (and now PulseLoop) simply treating one physical notification as the complete
message, with the undelivered tail implicitly zero. This is different from, e.g., the Colmi ring in
the same app, which genuinely fragments large replies across multiple notifications that must be
reassembled — the SY01 doesn't appear to do that in practice.
4. "Decryption" — there isn't any, just a checksum
To be precise about terminology: nothing on this ring is encrypted. There's no cipher, no key
exchange, no signing. The only integrity mechanism is a CRC16 checksum, which exists to catch
transmission errors, not to hide or authenticate anything.
The CRC is CRC-16/ARC: polynomial 0xA001 (reflected), initial value 0x0000, no final XOR —
a standard, textbook variant. We extracted the exact 256-entry lookup table and algorithm straight
out of the app's classes.dex (Ly5/d;->a([B)Ljava/lang/String;), then independently verified it
by computing the same CRC in Python over three different real captured frames and getting an exact
match every time before shipping it. The algorithm:
crc = 0x0000
for each byte in (CMD, Key, KeyFlag, data...):
index = (crc XOR byte) AND 0xFF
crc = (crc >> 8) XOR table[index]
# result is stored big-endian in bytes 4-5 of the frame
So "decoding" a frame is really: read the header → verify (or in PulseLoop's case, trust and just
parse) the CRC → look at (CMD, Key) to know what kind of message this is → read the data bytes at
the offsets known for that specific command.
5. What data the ring actually sends, and what PulseLoop reads
Every row below other than Sleep has been matched against a real on-screen reading — not inferred
from the app's decompiled field names alone.
| Metric |
CMD / Key |
KeyFlag |
Data layout |
Verified against |
| Time sync (outbound, sets the ring's clock) |
02 / 01 |
00 |
[year-2000, month, day, hour, min, sec], 6 bytes |
Decoded value matched the capture's own connection timestamp, twice, on two different connections |
| Battery |
02 / 03 |
10 |
data[0] = percent (0–100) |
83% × 3 separate reads |
| Heart rate |
05 / 03 (on-demand) or 02 / 24 (live push) |
10 |
[0x31, 0xDF, counter_hi, counter_lo, VALUE, 0x00] — bpm at data[4] |
93, 94, 93 bpm |
| SpO2 |
05 / 09 (on-demand) or 02 / 4e (live push) |
10 |
Same shape as HR, value at data[4] |
99%, then 98% |
| HRV |
05 / 0a (on-demand) or 02 / 69 (live push) |
10 |
Same shape, value at data[4], in ms |
37 ms |
| Stress |
05 / 0d |
10 |
Same shape, value at data[4], 0–100 scale |
35, then 42 |
| Steps + calories |
05 / 1a |
10 |
[0x31,0xDF,counter,counter, steps: u32-BE @ bytes 4-7, 0x00,0x00, calories_raw @ byte 10], kcal = calories_raw / 40.0 |
41, 79, 95 steps; 2.75→"2.8" and 3.3 kcal exactly |
| Sleep |
05 / 05 |
10 |
Unknown — command confirmed to exist and ack, but no real sleep session has been captured yet, so no byte offsets are claimed |
Not yet verified |
| Blood pressure, blood sugar |
05 / 04, 05 / 10 |
— |
Not implemented |
The ring likely doesn't have these sensors at all — command slots exist because they're part of the shared Jieli-adjacent app codebase used across multiple ring models, not because this hardware supports them |
Distance is not transmitted by the ring at all. At 95 steps, RWFit showed 0.07 km — that implies
~0.74 m/stride, a perfectly normal stride length, and there's no unaccounted byte left in the steps
payload for a distance field. RWFit is almost certainly computing distance client-side from
steps × your stored height/stride, and PulseLoop does the same rather than reading a wire field
that doesn't exist.
Starting a live stream: heart rate, SpO2, and HRV readings can be requested two ways — a single
on-demand read (KeyFlag=0x10 then 0x30 to stop), or a continuous live stream, kicked off by a
separate trigger command: CMD=06, Key=09, data [sensorType, 0x05, enable], where sensorType is
the same Key byte as the metric's on-demand command (0x03=HR, 0x09=SpO2, 0x0a=HRV, 0x0d=stress).
This was captured immediately before/after every live reading for all four metrics.
6. The one thing PulseLoop sends that isn't a sensor read
Right after connecting, before any sensor query, the ring and app exchange a handshake:
CMD=03, Key=01 — the ring replies with what looks like a capability/feature descriptor (the
decompile calls the corresponding data class BindInfoBean, with flags like setIsHeartRate,
setIsBo (SpO2), setIsStep, setIsSleep, setIsBp, setIsHrv, setIsPressure (stress),
setIsBloodSugar). This is present and confirmed to exist on the wire, but PulseLoop doesn't parse
it yet — it's how the official app would find out which sensors a given ring model actually has,
which would be useful if this driver ever needs to support ring variants with a different sensor
set under the same protocol family.
7. Where this lives in PulseLoop
| File |
Role |
Sy01Protocol.swift |
UUIDs, CRC16 table, Sy01Frame (parse/wrap), the (CMD,Key) command dictionary |
Sy01Encoder.swift |
Builds outbound logical commands (time sync, battery/HR/SpO2/steps/HRV/stress/sleep queries, realtime-stream triggers) |
Sy01Decoder.swift |
Maps a parsed frame to the shared RingDecodedEvent — the only place byte offsets are read |
Sy01Driver.swift |
WearableDriver conformance: BLE topology, frame wrapping, notification routing |
Sy01SyncEngine.swift |
Connect-time handshake sequence + live-stream start/stop |
Sy01Coordinator.swift |
Recognizes the ring by service UUID, declares which capabilities are safe to show in the UI |
Sy01Coordinator.capabilities only declares heart rate, SpO2, steps, battery, HRV, and stress —
sleep and blood pressure/blood sugar are deliberately left off so the app never shows a UI card
backed by a guessed decode.
Which capabilities does this device have? (best guess is fine)
Known packet formats / captures
Vendor app it ships with
No response
Do you have this device and can you test a driver?
Yes — I own it and can test builds
Would you like to write the driver yourself?
None
Ring / wearable model
SY01 - RWfit
Advertised BLE name
SY01
Primary BLE service UUID(s)
0000a00a-0000-1000-8000-00805f9b34fb
Characteristics (write / notify UUIDs)
Used claude + android debugging and de-compiled the official apk. had it do a whole write up of the connectivity and data. I dont have any sleep data just yet.
https://www.aliexpress.us/item/3256811727783582.html Ring in question.
SY01 Ring — Protocol Notes
How PulseLoop talks to the SY01 ring: the SDK underneath it, the connection sequence, the raw
bytes that come off the ring, how those bytes get parsed, and exactly what PulseLoop reads out of
them. Everything marked VERIFIED was checked byte-for-byte against a real reading during
reverse-engineering; everything marked UNVERIFIED is a documented guess PulseLoop does not act
on yet.
Source material: a BLE HCI snoop capture of the official RWFit app (
com.rw.revivalfit) talking toa real SY01, cross-checked against a decompile of that app's
classes.dex.1. What SDK this actually is
The ring's MCU is a Zhuhai Jieli Technology ("Jieli") chip — a very common BLE SoC used across
cheap Chinese wearables. Two pieces of evidence nail this down:
libjl_ota_auth.soandlibjl_bmp_convert.so(thejl_prefix isJieli's signature), plus a
com.jieli.jl_bt_otapackage handling firmware OTA updates.JL-prefixed identifiers for sync data —JLStepSyncBean,JLSleepSyncBean— matching Jieli's reference naming conventions.Important distinction: Jieli's public SDK covers the BLE transport and OTA firmware update
mechanism — that part is genuinely "Jieli's SDK." The actual health-data protocol (the
CMD/Key/KeyFlag framing and everything below in this doc) is not part of any public Jieli SDK —
it's custom code the RWFit app itself implements on top of the Jieli transport. There was no
existing documentation or open-source project (unlike, say, the Colmi/QRing family, which
Gadgetbridge already reverse-engineered) — this was reconstructed from scratch from the capture
and decompile.
2. How PulseLoop connects to it
Standard BLE GATT, no pairing/bonding, no authentication:
Scan for a peripheral advertising GATT service
0000a00a-0000-1000-8000-00805f9b34fb.(We don't have a captured advertised local name to match on — unlike the jring's
SMART_RINGorColmi's
R02_*patterns — so PulseLoop currently recognizes it by service UUID alone.)Connect and discover services. The ring exposes several GATT services, only one of which
matters for health data:
1800,18011812ff00ff01/ff02— present but never used in any capturea00a0bc000010203-...-1912/...-2b12) — separate from health data entirelyWithin service
a00a: write to characteristicb002, enable notifications oncharacteristic
b003(standard CCCD write to2902). Every command goes out onb002; everyreply/push comes back on
b003.No GATT Battery Service (
180F/2a19) exists — battery comes back in-band over the sameprotocol, like everything else.
There's no encryption, no bonding, no authentication handshake beyond a capability/bind exchange
(§6). Anyone in BLE range who knows this protocol can read your ring's data — the same is true of
essentially every cheap BLE wearable in this price bracket.
3. The raw wire format
Every message in both directions is a
0xAB-prefixed frame:Example — a real captured frame, the battery reply that decoded to 83%:
The "declared length" field is not fully reliable. For several commands (steps, sleep) it
declares a much larger length (e.g. 51 bytes) than what's actually delivered in the physical BLE
notification (e.g. 14 bytes) — and no continuation packet ever follows. Every capture we took
showed the app (and now PulseLoop) simply treating one physical notification as the complete
message, with the undelivered tail implicitly zero. This is different from, e.g., the Colmi ring in
the same app, which genuinely fragments large replies across multiple notifications that must be
reassembled — the SY01 doesn't appear to do that in practice.
4. "Decryption" — there isn't any, just a checksum
To be precise about terminology: nothing on this ring is encrypted. There's no cipher, no key
exchange, no signing. The only integrity mechanism is a CRC16 checksum, which exists to catch
transmission errors, not to hide or authenticate anything.
The CRC is CRC-16/ARC: polynomial
0xA001(reflected), initial value0x0000, no final XOR —a standard, textbook variant. We extracted the exact 256-entry lookup table and algorithm straight
out of the app's
classes.dex(Ly5/d;->a([B)Ljava/lang/String;), then independently verified itby computing the same CRC in Python over three different real captured frames and getting an exact
match every time before shipping it. The algorithm:
So "decoding" a frame is really: read the header → verify (or in PulseLoop's case, trust and just
parse) the CRC → look at (CMD, Key) to know what kind of message this is → read the data bytes at
the offsets known for that specific command.
5. What data the ring actually sends, and what PulseLoop reads
Every row below other than Sleep has been matched against a real on-screen reading — not inferred
from the app's decompiled field names alone.
02/0100[year-2000, month, day, hour, min, sec], 6 bytes02/0310data[0]= percent (0–100)05/03(on-demand) or02/24(live push)10[0x31, 0xDF, counter_hi, counter_lo, VALUE, 0x00]— bpm atdata[4]05/09(on-demand) or02/4e(live push)10data[4]05/0a(on-demand) or02/69(live push)10data[4], in ms05/0d10data[4], 0–100 scale05/1a10[0x31,0xDF,counter,counter, steps: u32-BE @ bytes 4-7, 0x00,0x00, calories_raw @ byte 10], kcal =calories_raw / 40.005/051005/04,05/10Distance is not transmitted by the ring at all. At 95 steps, RWFit showed 0.07 km — that implies
~0.74 m/stride, a perfectly normal stride length, and there's no unaccounted byte left in the steps
payload for a distance field. RWFit is almost certainly computing distance client-side from
steps × your stored height/stride, and PulseLoop does the same rather than reading a wire field
that doesn't exist.
Starting a live stream: heart rate, SpO2, and HRV readings can be requested two ways — a single
on-demand read (
KeyFlag=0x10then0x30to stop), or a continuous live stream, kicked off by aseparate trigger command:
CMD=06, Key=09, data[sensorType, 0x05, enable], wheresensorTypeisthe same Key byte as the metric's on-demand command (
0x03=HR,0x09=SpO2,0x0a=HRV,0x0d=stress).This was captured immediately before/after every live reading for all four metrics.
6. The one thing PulseLoop sends that isn't a sensor read
Right after connecting, before any sensor query, the ring and app exchange a handshake:
CMD=03, Key=01— the ring replies with what looks like a capability/feature descriptor (thedecompile calls the corresponding data class
BindInfoBean, with flags likesetIsHeartRate,setIsBo(SpO2),setIsStep,setIsSleep,setIsBp,setIsHrv,setIsPressure(stress),setIsBloodSugar). This is present and confirmed to exist on the wire, but PulseLoop doesn't parseit yet — it's how the official app would find out which sensors a given ring model actually has,
which would be useful if this driver ever needs to support ring variants with a different sensor
set under the same protocol family.
7. Where this lives in PulseLoop
Sy01Protocol.swiftSy01Frame(parse/wrap), the (CMD,Key) command dictionarySy01Encoder.swiftSy01Decoder.swiftRingDecodedEvent— the only place byte offsets are readSy01Driver.swiftWearableDriverconformance: BLE topology, frame wrapping, notification routingSy01SyncEngine.swiftSy01Coordinator.swiftSy01Coordinator.capabilitiesonly declares heart rate, SpO2, steps, battery, HRV, and stress —sleep and blood pressure/blood sugar are deliberately left off so the app never shows a UI card
backed by a guessed decode.
Which capabilities does this device have? (best guess is fine)
Known packet formats / captures
Vendor app it ships with
No response
Do you have this device and can you test a driver?
Yes — I own it and can test builds
Would you like to write the driver yourself?
None