Repository navigation
Oura: read-only SpO2 / real-steps feature-status probe (confirm the server-flag gate) - #710
Merged
Merged
Conversation
… the server-flag gate)
SpO2 (0x6F etc.) and real_steps (0x7E/0x7F) never appear for a NOOP-only ring because
they are server-flag features (`health/spo2`, `activity/real_steps`) set by Oura's cloud
ClientConfiguration - the ring gatekeeps them and no local SetFeatureMode can enable them
offline [open_oura-feat]. This adds a READ-ONLY diagnostic that has the ring confirm that
from its own mouth, so a capture shows "not a bug, it's a gate" instead of just "absent".
At connect (once HR streams), send the feature-status READ verb - the SAME `2f 02 20 <id>`
as `dhr_read`, NEVER the `2f 03 22` set-mode enable - for SpO2 (0x04) and real_steps (0x0b),
decode the `0x21` reply (feature/mode/status/state/subscription), and log once per feature:
`Oura: feature status SpO2 (0x04) mode=.. status=.. state=.. subscription=0 - server-gated OFF`
OuraProtocol (pure, both platforms):
- OuraCommands.featureRealSteps + spo2ReadStatus()/realStepsReadStatus() (read verb only).
- OuraFeatureStatus + OuraDecoders.decodeFeatureStatus (nil/null on a short body).
- OuraDriver.handleSecureFrame splits 0x21: the daytime-HR read (0x02) still returns
.enableAck (advances the live-HR triplet), a diagnostic read (0x04/0x0b) returns the new
.featureStatus; an undecodable 0x21 falls back to .enableAck so the triplet never stalls.
App (both platforms): OuraLiveSource sends the two reads once streaming starts and logs the
decoded status once per feature. Read-only - fits the BLE safety contract (no write-to-enable,
no destructive command); never scored, never stored.
Verified: swift test OuraProtocol 117 incl. 6 new; xcodebuild Strand macOS BUILD SUCCEEDED;
Android compileFullDebugKotlin OK + testFullDebugUnitTest OuraFeatureStatusTest 6/6.
Hardware validation (next connect): the two `feature status …` lines print the ring's own
subscription bytes.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016DN6cU2ERWZUS9tWYCjPvi
DX23876
pushed a commit
to DX23876/noop
that referenced
this pull request
Jul 22, 2026
Oura: read-only SpO2 / real-steps feature-status probe (confirm the server-flag gate)
Merged
ryanbr
pushed a commit
that referenced
this pull request
Jul 24, 2026
…enums in the #710 probe log (#747) * docs(oura): correct + expand the feature-ID table and mode/subscription enums from the Ring-4 APK Cross-checked against Defying/oura-ring4-ble (APK static-analysis + Ring-4 captures) [ring4-ble]: - §7.1 feature IDs: CORRECTION — Ambient light is `0x0F`, not `0x10`; `0x10` is special_feature. Added the APK's full list: `0x0E` charging_control, `0x0F` ambient_light, `0x10` special_feature, `0x11` raw_data_sampler, `0x12` atlas, `0x16` long_events. - Feature MODE enum: `0x03` is `connected_live` (an earlier draft read "requested-subscription"); added the SUBSCRIPTION enum (`0` off / `1` state / `2` latest / `4` feature_specific_data — NOOP subscribes live HR with `2`=latest) and the feature request-status codes. - `0x03` Exercise HR: its data arrives as `0x73` ehr_trace / `0x74` ehr_acm_intensity (never observed — server-gated like SpO2/steps). - §9: `0x6d` is `measurement_quality` (24-bit signed), superseding the earlier gravity/accel guess. - Also notes the repo independently confirms the framing/auth/tags are generation-invariant (Ring 4 == Ring 3) and that the app derives BPM as round(60000/ibi_ms) — the same formula #728 uses. Facts-only clean-room citation; no code copied. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * diag(oura): name the mode/subscription enums in the #710 feature-status probe log The read-only SpO2/real_steps feature-status probe (#710, merged) logged the raw enum bytes: `mode=0 status=0 state=0 subscription=0`. With the enum meanings now pinned (OURA_PROTOCOL.md s7.1 [ring4-ble]), name them inline so the log is self-evident: `mode=0 (off) status=0 state=0 subscription=0 (off) - INACTIVE (server-gated off; …)` and the active daytime-HR reads `mode=1 (automatic)`. Mode: 0 off / 1 automatic / 2 requested / 3 connected_live. Subscription: 0 off / 1 state / 2 latest / 4 feature_data. Log-string only, both platforms (Swift Strand/BLE + Kotlin com.noop.ble twin); no behaviour change, no new writes to the ring. Follow-up to the merged #710. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
simoncad7
pushed a commit
to simoncad7/noop
that referenced
this pull request
Aug 17, 2026
Oura: read-only SpO2 / real-steps feature-status probe (confirm the server-flag gate)
simoncad7
pushed a commit
to simoncad7/noop
that referenced
this pull request
Aug 17, 2026
…enums in the ryanbr#710 probe log (ryanbr#747) * docs(oura): correct + expand the feature-ID table and mode/subscription enums from the Ring-4 APK Cross-checked against Defying/oura-ring4-ble (APK static-analysis + Ring-4 captures) [ring4-ble]: - §7.1 feature IDs: CORRECTION — Ambient light is `0x0F`, not `0x10`; `0x10` is special_feature. Added the APK's full list: `0x0E` charging_control, `0x0F` ambient_light, `0x10` special_feature, `0x11` raw_data_sampler, `0x12` atlas, `0x16` long_events. - Feature MODE enum: `0x03` is `connected_live` (an earlier draft read "requested-subscription"); added the SUBSCRIPTION enum (`0` off / `1` state / `2` latest / `4` feature_specific_data — NOOP subscribes live HR with `2`=latest) and the feature request-status codes. - `0x03` Exercise HR: its data arrives as `0x73` ehr_trace / `0x74` ehr_acm_intensity (never observed — server-gated like SpO2/steps). - §9: `0x6d` is `measurement_quality` (24-bit signed), superseding the earlier gravity/accel guess. - Also notes the repo independently confirms the framing/auth/tags are generation-invariant (Ring 4 == Ring 3) and that the app derives BPM as round(60000/ibi_ms) — the same formula ryanbr#728 uses. Facts-only clean-room citation; no code copied. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * diag(oura): name the mode/subscription enums in the ryanbr#710 feature-status probe log The read-only SpO2/real_steps feature-status probe (ryanbr#710, merged) logged the raw enum bytes: `mode=0 status=0 state=0 subscription=0`. With the enum meanings now pinned (OURA_PROTOCOL.md s7.1 [ring4-ble]), name them inline so the log is self-evident: `mode=0 (off) status=0 state=0 subscription=0 (off) - INACTIVE (server-gated off; …)` and the active daytime-HR reads `mode=1 (automatic)`. Mode: 0 off / 1 automatic / 2 requested / 3 connected_live. Subscription: 0 off / 1 state / 2 latest / 4 feature_data. Log-string only, both platforms (Swift Strand/BLE + Kotlin com.noop.ble twin); no behaviour change, no new writes to the ring. Follow-up to the merged ryanbr#710. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
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
SpO2 (
0x6F…) and real_steps (0x7E/0x7F) never appear for a NOOP-only ring because they're server-flag features (health/spo2,activity/real_steps) set by Oura's cloudClientConfiguration— the ring gatekeeps them and no localSetFeatureModecan enable them offline ([open_oura-feat]). This adds aread-only diagnostic that has the ring confirm that from its own mouth, so a capture shows "not a bug, it's a gate" instead of just "absent".
What it does
Once HR streams, send the feature-status READ verb — the same
2f 02 20 <id>asdhr_read, never the2f 03 22set-mode enable — for SpO2 (0x04) and real_steps (0x0b), decode the0x21reply, and log once per feature: Oura: feature status SpO2 (0x04) mode=.. status=.. state=.. subscription=0 - server-gated OFF; not emitted offlineChanges (both platforms)
OuraProtocol:featureRealSteps+spo2ReadStatus()/realStepsReadStatus();OuraFeatureStatus+decodeFeatureStatus(nil on short body);handleSecureFramesplits0x21— daytime-HR read (0x02) still.enableAck(advances the live-HR triplet), a diagnostic read (0x04/0x0b) returns the new.featureStatus; an undecodable0x21falls back to.enableAckso the triplet never stalls.OuraLiveSourcesends the two reads once, logs once per feature.Safety
Read-only — fits the BLE contract (no write-to-enable, no destructive command); never scored, never stored.
Verification
swift testOuraProtocol 117 (incl. 6 new);xcodebuildStrand macOS BUILD SUCCEEDED.compileFullDebugKotlinOK;testFullDebugUnitTest OuraFeatureStatusTest6/6.feature status …lines print the ring's own subscription bytes.