Add LittleMeatball R10M (YCBT) support#97
Open
saksham2001 wants to merge 2 commits into
Open
Conversation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
iOS port of PulseLoopAndroid#31 — hardware-validated LittleMeatball R10M support (validated there on R10M FCF4, firmware 2.32).
The R10M speaks YCBT / SmartHealth, the same wire protocol iOS already implements for the TK5 and Colmi-SmartHealth families, so this is a delta port rather than a new protocol stack. Two things made it more than a catalog entry:
New family
RingDeviceType.ycbt(appended — it is persisted onDevice.deviceTypeRaw),.fullsupport level.YCBTCoordinator— rejects a QRing service outright, then matches the catalog, thebe940000service, or^R10M(?:[ _-][0-9A-Z]+)?$. Registered ahead of both Colmi coordinators, becauseColmiSmartHealthCoordinator's<MODEL> <4 hex>convention acceptsR10M FCF4. Deliberately does not claimTK5/R0xnames or the shared1078company ID, so uncataloged rings still reach the family whose baseline was written for them.WearableModel.r10m—R10M (LittleMeatball), brandLittleMeatball, pattern^R10M[ _][0-9A-F]{4}$(both separators observed)..spo2Historyis in neither set — that is what stops the05 1aquery being issued; the R10M's all-day SpO₂ rides the05 09combined record.Firmware quirks —
YCBTFamilyProfileNew profile struct threaded coordinator → driver → engine → encoder. TK5 and Colmi-SmartHealth pass both flags
true, so their behaviour is byte-identical to before.queryChipSchemeAtStartup: false— the R10M closes an otherwise healthy link with HCI0x13on the informational02 1b.supportsBloodPressureMonitor: false— the ring has the BP sensor but not the all-day01 1cmonitor, so the flag is separate from the capability.R10M startup is now exactly
02 00→02 01→02 07→01 12→01 04→01 0c→01 26→01 03→03 09.Shared YCBT fixes (these also improve TK5 / Colmi-SmartHealth)
YCBTProtocolYCBTBytes.dateresolved the UTC offset from today rather than the record's own date, silently shifting every record across a DST boundary by an hour. Now symmetric withringSeconds, which was already date-aware.YCBTHealthRecordsYCBTHealthRecords05 09record no longer publishes its steps field. It is the ring's cumulative day counter and.activityUpdateis a per-day max ratchet, so the oldest record in a dump spanning midnight pinned today's total to yesterday's count for the rest of the day. Activity comes from the05 02buckets and the live06 00counter.YCBTHistoryTransferYCBTHistoryTransferappend(types:)— extends the queue without disturbing the in-flight block, for capabilities the bitmap unlocks mid-walk.YCBTDecoder06 00required only 2 payload bytes while reading 6;u16returns 0 past the end, so a short frame published a valid-looking activity row with zeroed distance and calories. Now requires all three fields.YCBTEncoder01 xxmonitors are capability-filtered — a ring is no longer asked to run a sensor it doesn't declare.YCBTDriverYCBTSyncEngine[0x02, 0x04, 0x06, 0x09].YCBTSyncEngine03 09is re-issued once after the startup walk and ahead of pull-to-refresh — some R10M firmware ACKs the handshake's subscription without publishing until its dump finishes, leaving today's steps frozen.RingBLEClient— required-subscription gateWearableDrivergainsrequiredSubscriptionsBeforeConnected(default[]) andimmediatePostSubscriptionCommands()(default[]), so jring/Colmi/LuckRing are untouched..connectedpreviously fired on the first notifying characteristic. YCBT splits its protocol across two — command replies onbe940001, live/history onbe940003— so the handshake could begin half-subscribed and silently lose whichever half was still pending. The client now waits for every declared channel, andprependWritesputs the driver's own handshake (02 03name read, then01 00SetTime) ahead of the engine's startup sequence. The clock leads deliberately: records are stamped from the ring's RTC in local wall-clock, so a clock written after the walk begins mis-stamps everything read before it.A ring missing a declared channel now fails the 20 s connect-attempt timeout with the existing user-facing message instead of hanging half-connected.
Ring art
PulseLoop/Assets.xcassets/r10m.imageset/— 600×600 transparent RGBA PNG matching thetk5/colmi-*convention, background-removed by edge-connected flood fill (a global white threshold would punch holes through the ring's own white enamel pattern). Verified composited on the dark theme for fringing. Note the Android PR shipped no art for this ring and falls back to Colmi R10 art on its hero card, which contradicts its own catalog comment; iOS gives the R10M its own.Deliberate divergences from the Android PR
Not gaps — either Android catching up to behaviour iOS already had, or moot under CoreBluetooth:
recoverWedgedLink, MTU/priority skip, stale-GATT guards, process-wide GATT ownership — Android GATT-stack plumbing. CoreBluetooth queues internally and the iOS path already makes no MTU/priority request; the Android PR cites iOS as its reference for exactly this.SubscriptionMode.INDICATIONCCCD values —setNotifyValuepicks notify-vs-indicate from the characteristic's properties. Only the readiness half of the gate is portable.isHistoryflags on BP/temperature/stress events — iOS already separates history from live by which case is emitted (.historyMeasurementupserts,.bloodPressureSampleappends). Adding the flag would be a second mechanism for the same thing.PulseEventBuschannel rework — the iOS bus is already anactorwith anAsyncStream.MeasurementRejectedpromoted to a real event — iOS already decodes.measurementRejected(mode:)and the spot-measurement path already consumes it.Verification
xcodebuild test— 756 tests, 0 failures (was 723; +33 new). Every existing TK5 / Colmi-SmartHealth / LuckRing / jring suite stays green.swiftlint— 478 violations, unchanged frommain(no new ones).YCBTCoordinatorTests; extendedYCBTEncoderTests(quirk suppression, handshake order, monitor filtering),YCBTHistoryTransferTests(terminal cross-check,append, gated queue, live-status re-issue),YCBTHealthRecordsTests(sleep clamp, no stale steps row),YCBTDecoderTests(DST-correct timestamps, short06 00),YCBTDriverTests(capability filtering across the bitmap and a reconnect).05 09record's step row.No R10M hardware was available for this port. The wire-level claims are carried by the Android PR's hardware validation plus the golden-vector tests above — not by a device session on iOS.