Skip to content

Archive

Capture ledger — lane DYN·

Capture ledger — lane DYN — the lane's ledger, verbatim.

  • archaeologists — the capture lane ledger, verbatim
  • 1 min read
  • 59 words

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.

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)·

FE FC <op> 00 01 00 01 00 <len LE16> <payload…>
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
Order after 0x1d/0x06: 2f, 3e, 3d, 21×5 (interleaved with 2f/3e/3d), 01, 1a, 08, 22, 24, then 3e forever.

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*
Ring was OFF-finger → no HR data frames within 25 s (UI stayed "Measuring…"). The realtime HR result op is therefore still unknown; needs a worn ring. Stop was tapped at 15:09:19Z but BT was killed by another lane at that instant.

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
Gotchas: the socket dies whenever the BT stack restarts (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 012D 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 001D 0e: f473ae6a 00×10 | 0x1D = status: LE32 ts + steps/kcal/dist zeros (same shape as unsolicited cmd 29) | | Simultaneous Interpretation start/stop | 2F 02: 00002F: 0000; 3D 003D 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).