Skip to content

Reverse-engineered Bluetooth Low Energy protocol documentation for the Wellue O2Ring pulse oximeter

License

Notifications You must be signed in to change notification settings

farolone/wellue-o2ring-protocol

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 

Repository files navigation

Wellue O2Ring Bluetooth Low Energy Protocol

Reverse-engineered BLE protocol documentation for the Wellue O2Ring pulse oximeter.

Overview

The O2Ring communicates via Bluetooth Low Energy (BLE) using a custom GATT service. This document describes the protocol for:

  • Retrieving device information
  • Reading real-time sensor data
  • Downloading stored measurement files

BLE Service & Characteristics

Type UUID
Service 14839ac4-7d7e-415c-9a42-167340cf2339
Notify (read responses) 0734594a-a8e7-4b1a-a6b1-cd5243059a57
Write (send commands) 8b00ace7-eb0b-49b0-bbe9-9aee0a26e1a3

Packet Structure

All packets follow this structure:

┌──────┬─────┬──────────┬───────┬────────┬──────────┬─────┐
│ 0xAA │ CMD │ CMD^0xFF │ BLOCK │ LENGTH │ DATA ... │ CRC │
└──────┴─────┴──────────┴───────┴────────┴──────────┴─────┘
  1B     1B      1B        2B       2B      var       1B
Field Size Description
Header 1 byte Always 0xAA
CMD 1 byte Command code
CMD XOR 1 byte CMD ^ 0xFF (validation)
Block 2 bytes Block number (little-endian), used for file reads
Length 2 bytes Data length (little-endian)
Data variable Command-specific payload
CRC 1 byte CRC-8 checksum

CRC-8 Algorithm

def crc8(data):
    crc = 0
    for b in data:
        chk = crc ^ b
        crc = 0
        if chk & 0x01: crc = 0x07
        if chk & 0x02: crc ^= 0x0e
        if chk & 0x04: crc ^= 0x1c
        if chk & 0x08: crc ^= 0x38
        if chk & 0x10: crc ^= 0x70
        if chk & 0x20: crc ^= 0xe0
        if chk & 0x40: crc ^= 0xc7
        if chk & 0x80: crc ^= 0x89
    return crc

Commands

Code Name Description
0x14 (20) INFO Get device info (JSON response)
0x17 (23) READ_SENSORS Real-time SpO2/pulse reading
0x03 (3) FILE_OPEN Open a stored file for reading
0x04 (4) FILE_READ Read next block from open file
0x05 (5) FILE_CLOSE Close the open file

INFO Command (0x14)

Request device information including battery status and file list.

Request: Empty data payload

Response: JSON string with device info:

{
  "CurBAT": "75%",
  "FileList": "20260116233312.vld,20260115221045.vld",
  "Model": "O2Ring",
  "SN": "XXXX"
}

FILE_OPEN Command (0x03)

Open a stored measurement file for download.

Request: Filename as ASCII string, must be null-terminated (\x00)

Response:

  • Byte 1: Status (0 = success)
  • Bytes 7-10: File size (32-bit little-endian)

Important: The null terminator is required, otherwise the device returns error code 9.

FILE_READ Command (0x04)

Read a block of data from the currently open file.

Request: Block number in the BLOCK field (starts at 0)

Response: File data in the DATA field

Continue incrementing the block number until you've read file_size bytes.

FILE_CLOSE Command (0x05)

Close the currently open file.

Request: Empty data payload

Response: Status confirmation

VLD File Format (Version 3)

The O2Ring stores measurements in .vld files with the following binary format:

Header (26 bytes)

Offset Size Type Description
0 2 uint16_le Version (3 for VLD3)
2 2 uint16_le Year
4 1 uint8 Month
5 1 uint8 Day
6 1 uint8 Hour
7 1 uint8 Minute
8 1 uint8 Second
9-17 9 - Reserved/flags
18 2 uint16_le Duration in seconds
20-25 6 - Reserved

Measurement Records (starting at offset 40)

Each record is 5 bytes:

Offset Size Type Description
0 1 uint8 SpO2 percentage (70-100 valid, 0xFF = no finger)
1 1 uint8 Heart rate BPM (40-200 valid, 0xFF = no finger)
2 1 bool Invalid flag
3 1 uint8 Motion indicator
4 1 uint8 Vibration alert status

Calculating Timestamps

The time interval between records can be calculated as:

interval = duration_seconds / record_count

Typically ~4 seconds per record for overnight recordings.

Device States

The O2Ring must be in Standby Mode to accept BLE connections for data sync.

Entering Standby Mode

After a recording session:

Remove ring from finger → Saves data → Countdown 10→0 → "END" → Standby

From powered-off state:

Insert finger briefly and remove → Shows time/battery → Standby

Note: The device automatically powers off after ~2 minutes without a BLE connection.

Implementation Notes

BLE Write Chunking

BLE characteristic writes are limited to 20 bytes. Split larger packets:

for i in range(0, len(packet), 20):
    await client.write_gatt_char(WRITE_UUID, packet[i:i+20], response=False)
    await asyncio.sleep(0.02)  # Small delay between chunks

Response Assembly

Responses may arrive in multiple notifications. Accumulate data until you have:

  • At least 7 bytes (header complete)
  • Total length matches: 7 + data_length + 1 (header + data + CRC)

References

License

This documentation is provided for educational and interoperability purposes.

Contributing

Contributions welcome! If you discover additional commands or file format details, please submit a PR.

About

Reverse-engineered Bluetooth Low Energy protocol documentation for the Wellue O2Ring pulse oximeter

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Contributors 2

  •  
  •