# Th0rgal/open_oura: a Rust toolkit for the Oura Ring (Gen 3/4/5)

> Source: <https://github.com/Th0rgal/open_oura>
> Published: 2026-06-28 10:02:01+00:00

Reverse-engineering the Oura ring BLE protocol, plus an independent, **cloud-free**
client that reads your data straight from the ring.

Tested live against a Ring 3 Horizon and a Ring 5 (pairing, auth, and event sync confirmed on both). Designed for Ring 3/4/5, which share the same GATT layout, packet framing, and authentication flow.

Straight from the ring, with no Oura account: device info, battery, live heart rate
(IBI to BPM), latest HR / SpO2, and the full history-event stream. That stream
carries raw PPG/IBI/temperature/motion/SpO2 samples plus the ring's **on-device**
sleep stages, activity MET levels, and HRV.

The ring itself does **not** emit the 0-100 Readiness / Sleep / Activity / Stress
scores. But those are **not** computed in
Oura's cloud either: they're computed **on the phone** by the native `ecore`

engine and a set of on-device PyTorch models (the same `.pt`

we run here), then
uploaded; the cloud only stores and syncs them back. So they're reproducible
offline. The one genuine cloud-only step is **workout auto-classification**
(`POST /api/activity-tagging/v2`

). See
[ docs/data-recovery-map.md](/Th0rgal/open_oura/blob/main/docs/data-recovery-map.md),

[, and](/Th0rgal/open_oura/blob/main/docs/algorithms/README.md)

`docs/algorithms/README.md`

[for what runs.](/Th0rgal/open_oura/blob/main/docs/model-runners.md)

`docs/model-runners.md`

Those PyTorch models are Oura's proprietary IP and are NOT included in this repo(gitignored under`notes/models/`

). The runners reference them by path; you decrypt and supply your own locally. Nothing model-related is committed or pushed.

: the Rust client, split by concern (`crates/`

`oura-protocol`

decode,`oura-link`

fetch,`oura-analysis`

metrics,`oura-store`

SQLite,`oura-cli`

). Start here:and`crates/README.md`

.`docs/architecture.md`

: Python research bench for protocol exploration.`tools/`

`oura_protocol.py`

(full command matrix, auth, danger-gated ops, JSONL capture) and`oura_realtime_listener.py`

.: protocol and reverse-engineering reference (index below).`docs/`

: local-only, gitignored. The decompiled app and raw captures (which may contain serials, MACs, and auth keys).`reverse/`

,`captures/`

```
cargo build --release
./target/release/oura scan
./target/release/oura --key-file key.hex info
```

See [ crates/README.md](/Th0rgal/open_oura/blob/main/crates/README.md) for all commands (

`scan`

, `pair`

,
`info`

, `sync`

, `latest`

, `live-hr`

, `accel`

, `viz`

, `game`

, `features`

, `rdata`

,
`events`

, `redecode`

, `sleep-analyze`

, `sessions`

) and the auth-key details. `oura viz`

opens a
real-time 3D motion visualizer in the browser; `oura game`

is a tilt-controlled
asteroid game driven by the ring.

```
python3 -m venv .venv && .venv/bin/pip install -r requirements.txt
.venv/bin/python tools/oura_protocol.py --list
```

State-changing and destructive commands are hidden behind `--include-state`

and
`--include-danger`

. On macOS, grant Bluetooth permission to the terminal.

: the protocol command reference (requests, responses, auth, features), Ring 3.`docs/horizon-ring3-protocol-cheatsheet.md`

: app internals, BLE constants, the auth operations, key generation, and nonce encryption.`docs/android-app-reversing.md`

: what the ring emits vs what only the cloud computes.`docs/data-recovery-map.md`

: when and how the app pulls each data channel, and the minimal client sync recipe.`docs/sync-orchestration.md`

: Ring 5 BLE surface and first-contact findings.`docs/ring-5-observations.md`

: the feature capabilities, runtime modes, what's on by default, and which event each enabled feature produces (incl. what`docs/ring-features.md`

`experimental`

does — and doesn't).: running Oura's decrypted on-device models on your synced data — what runs (activity, sleep, CVA, SpO2) vs what's blocked.`docs/model-runners.md`

: decoding the raw PPG (`docs/cva-cardiovascular-age.md`

`cva_raw_ppg_data`

0x81) and running the cardiovascular-age model.: turning the SpO2 R-ratio into a percentage with Oura's own calibration.`docs/spo2-calibration.md`

: the DFU/OTA opcodes, the working cloud download pipeline + codename map, per-device encryption status, and why the firmware key is unreachable (device-resident; not brute-forceable).`docs/firmware-update.md`

: findings-only notes on the model/firmware encryption (the "what", not the "how" — no keys, endpoints, or procedures).`docs/security-observations.md`

: the fetch/interpret/apply crate layering and where to add things.`docs/architecture.md`

: the on-device ecore metric algorithms (scores, sleep, baselines) and their porting status.`docs/algorithms/README.md`

: porting event-body decoders from the native`docs/native-decoder.md`

`libringeventparser.so`

(how the byte layouts were recovered with Ghidra).

- Prefer passive, read-only requests. reset / DFU / factory-reset / flight-mode are gated behind explicit flags; do not send them during normal use.
- App-gated operations need the ring's 16-byte auth key (re-sent each connection). Captures and keys are gitignored. Never commit a key.

ringverse Oura Ring 4 BLE notes:
[https://github.com/ringverse/protocol/blob/main/oura/BLE.md](https://github.com/ringverse/protocol/blob/main/oura/BLE.md)
