Archive
Capture ledger — lane DYN·
Capture ledger — lane DYN — the lane's ledger, verbatim.
In 10 seconds
LoraFit 1.1.4 on a Pixel 10 Pro ↔ ring JY-Y3-011A, 2026-09-19 15:05–15:16Z, three segments over the HCI snoop socket. Everything in the ledger is proven by bytes; the organised result is Live capture.
The ledger, verbatim — GATT handles, frame format, every screen's frames, the SpO₂ 0x19 finding
Source of truth: Android HCI snoop socket (Developer options → "Enable Bluetooth HCI snoop log" = Enabled +
"…snoop log socket" ON → adb forward tcp:8872 tcp:8872, read 127.0.0.1:8872 → btsnoop v1 stream, no bugreport needed).
Files: captures/btsnoop_live_*.log (raw), *.att.jsonl (parsed by tools/btsnoop_att.py), captures/markers.jsonl
(UI action ↔ time). Captured 2026-09-19 15:05–15:16Z, three segments (BT stack restarted twice by a sibling lane).
Everything below is PROVEN by bytes; anything marked inferred is not.
GATT (link LE-encrypted, LTK 16, LE legacy pairing "just works")·
| handle | role | note |
|---|---|---|
| 0x0017 | write (WRITE_REQ, phone→ring) | inferred = Jieli 0000ae01 from APK |
| 0x0019 | notify (ring→phone), CCCD 0x001a ← 01 00 |
inferred = 0000ae02 |
| 0x002c | Battery Level (std 0x2A19), READ → 63 = 99 %; CCCD 0x002d |
app also reads it ~every 2 s |
| — | HOGP present (dumpsys bluetooth_manager: HOGP connection state=2) |
ring is also a HID device (button/tap → phone) |
MTU: ring requests 512 (02 00), phone answers 517. Handles never discovered in-band (Android GATT cache for bonded peer). |
Frame format (both directions, same header)·
10-byte header,00 01 00 01 00 constant in every frame seen (TX and RX). No checksum, no trailer. A reply carries the
same op as the request (echo when the command has no data). This is NOT classic Jieli RCSP (FE DC BA … EF).
Timestamps in payloads are LE32 "local epoch" = unix seconds of the phone's wall clock as if it were UTC
(11:05:25 local encoded as 1789815925 = 2026-09-19T11:05:25Z).
Connect sequence (identical in both handshakes 15:05:23Z and 15:13:59Z)·
TX 001a 0100 enable notify
RX 25 980073010600011adaa501a0 device info: 0x0098?, MAC 73:01:06:00:01:1A (big-endian), da a5 01 a0 (fw/hw? unknown)
RX 0c980073010600011a3a004800 13 bytes w/o header right after 0x25 — second block "98 00 MAC 3a 00 48 00" (unexplained)
RX 44 0a 4a592d59332d30313141 device name, len-prefixed "JY-Y3-011A"
RX 37 0001 state flags (wear/charge?) — value 00 01 both times
RX 1d <LE32 now> + 10×00 status report: time + zeros (steps/kcal/dist? ring is unused → zeros) *inferred*
RX 06 6301 battery: 99 % , 0x01 (charging/state flag?)
TX 2f 0000 → RX 2f 0000 unknown setting, echo
TX 3e (empty) keepalive — app repeats every 2.0 s, ring never answers
TX 3d (empty) → RX 3d 0000
TX 21 <day idx> → RX 2a <LE32 day-start> 0000 day index 0=today…4 = 4 days ago → returns 00:00 of that day + count 0
TX 01 <LE32 now> 000000 → RX 01 ea07 09 13 0b 05 19 SET TIME; ring echoes Y(LE16)=2026 M D h m s
TX 1a 00 9e7906439c013fbd9f507119 → echo 13-byte token (bind/user id?) echoed — *inferred* binding
TX 08 (empty) → RX 08 (empty)
TX 22 00 → RX 22 <LE32 10:59:59> 0000 0002 last-sync / last-activity time + counters (02 = ?)
TX 24 00 → RX 2d <LE32 day-start> 0000 another per-day query (sleep? BP?) — count 0
Heart-rate measurement (tap "Start Measurement", 15:06:08Z)·
TX 07 (empty) → RX 07 (empty) START realtime HR
TX 1e 0000 173b 01 05 → echo auto-HR schedule: 00:00–23:59, enabled=1, interval=5 min *inferred*
Battery·
Two sources: std GATT Battery Level 0x002c (63), and vendor op 0x06 payload 63 01 at connect.
Not yet captured (Pixel handed to lane S 15:16Z)·
find-ring/vibrate, SpO2, steps sync with real data, sleep, AI recording (Opus stream — expect a different op family
and possibly the second 0c… block style), firmware version op, user profile/goals.
Repro·
adb -s 59230DLCH0012Z forward tcp:8872 tcp:8872
python3 -c "import socket;s=socket.create_connection(('127.0.0.1',8872));f=open('cap.log','wb');[f.write(b) for b in iter(lambda:s.recv(65536),b'')]"
python3 tools/btsnoop_att.py cap.log # writes/notifies with handles
python3 tools/btsnoop_att.py cap.log --all # every ATT PDU
svc bluetooth disable) — reconnect and start a new file;
99 % of bytes are LE advertising reports (HCI event 0x3E), ATT is tiny; settings put secure bluetooth_hci_log 1
alone is not enough on this build (Android 17) — the Developer-options toggle + socket toggle are what work.
Capture 2 — 15:36–15:45Z, LoraFit UI walk (captures/btsnoop_lorafit2_20260919T153603Z.*, markers.jsonl)·
Ring on charger the whole time (matters: sensors refuse). Firmware shown by the app: V152 (= devinfo byte 0x98).
| UI action | bytes (write 0x0017 → notify 0x0019) | meaning |
|---|---|---|
| app foreground / home | 24 01 → 2D 06: 80d0ad6a 0000 | health-record sync request → reply LE32 ts + u16 count=0 (no records) |
| SpO2 → Manual Check | 17 01 → echo 17 01, then 19 len 0, app sends 17 00 → echo | 0x17 = SpO2 measure start/stop; 0x19 = ring aborted the measurement (not worn / on charger). 0x18 samples never came. |
| open Quick Notes / device pages | 1D 00 → 1D 0e: f473ae6a 00×10 | 0x1D = status: LE32 ts + steps/kcal/dist zeros (same shape as unsolicited cmd 29) |
| Simultaneous Interpretation start/stop | 2F 02: 0000 → 2F: 0000; 3D 00 → 3D 02: 0000 | audio-state query (0 = idle) + file count (0). The interpreter uses the PHONE mic, no 0x30 to the ring. |
| Device center → Device system settings | – | only Firmware V152 / Reboot / Factory reset — no find-ring, no alarms, no temperature UI in LoraFit v89 → those cmds (0x04, 0x41/0x45, 0x14) exist only at the protocol level. |
| "Unbind device" button | not pressed (would send 0x43) | |
Not captured: on-ring 4-tap recording (needs a human to tap the ring; owner notified 15:42Z, no answer in 3 min) — that is
the only remaining LoraFit-side proof for the Opus path (0x3D count>0 → 0x40/0x34 transfer → 0x36).
Tool: tools/capture.sh start|mark|stop (btsnoop socket reader + action markers).
0x19 confirmed in the APK (hr7.java = JiangYunRingDevice, onDataReceived)·
case 23 (0x17) → log "open/close SpO2 reply"; case 24 (0x18) → getBloodOxygenOnce(true, ts, y,m,d,h,mi,s, spo2) (map keys
timestamp, spo2); case 25 (0x19) → log "device reply 0019, SpO2 test" → setBloodStatus(false) = measurement ended
without a value (ring not worn / on charger). HR path: 7 open-HR echo, 8 close-HR echo, 11 (0x0B) live {timestamp, heartRate}
→ getHeartRateOnce(...); 10 (0x0A) is a multi-sport status {current_sport_id, pause_status}, 27 (0x1B) a sport summary.
There is no HR "fail" op — the ring simply sends no 0x0B frames when not worn (client must time out).