Ultrahuman Protocol¶
This describes the Bluetooth attribute protocol used between the Ultrahuman Air smart ring (firmware version 02.00.07.46) and the Ultrahuman Android application (version 2.52.2.0, package com.ultrahuman.android).
Device Identification¶
The BLE device name matches the Regex pattern UH_[0-9A-F]{12}
. For example "UH_FE16F0F1E2D3".
Device Information service¶
The standard BLE device information service 0000180a-0000-1000-8000-00805f9b34fb
supports reading - Read Request (0x0a) / Read Response (0x0b):
Characteristic UUID | Description | Data Sample |
---|---|---|
00002a24-0000-1000-8000-00805f9b34fb
| Model Number String | R1.00.00.01 |
00002a25-0000-1000-8000-00805f9b34fb
| Serial Number String | XX-XXX-XXX-XX-XXXX-XXX |
00002a26-0000-1000-8000-00805f9b34fb
| Firmware Revision String | 02.00.07.46 |
00002a27-0000-1000-8000-00805f9b34fb
| Hardware Revision String | R1.01.01.01 |
00002a29-0000-1000-8000-00805f9b34fb
| Manufacturer Name String | Ultrahuman Healthcare Pvt Ltd |
Ultrahuman Device State service¶
The custom BLE service 86f61000-f706-58a0-95b2-1fb9261e4dc7
supports one characteristic 86f61001-f706-58a0-95b2-1fb9261e4dc7
.
This characteristic can be read - Read Request (0x0a) / Read Response (0x0b). In addition Ultrahuman Command requests also trigger typically between 0 and 2 notifications of the characteristic - Handle Value Notification (0x1b)
payload structure¶
Offset | Data Type | Description |
---|---|---|
0 | uint8 | battery level (0x64 = 100%, 0x00 = 0%) |
1..4 | ? | ? |
5 | uint8 | device state (0x00 = discharging, 0x03 = charging) |
6 | uint8 | device temperature in °C; temperatures below 0°C are reported as 0°C |
examples:
4c4c2900000317
= battery level 79% (0x4c), device state "charging" (0x03), device temperature 23°C (0x17)
00000000000000
= unknown device state - sometimes received in response to read requests
Ultrahuman Command Response service¶
This custom BLE service 86f65000-f706-58a0-95b2-1fb9261e4dc7
allows writing commands 86f65001-f706-58a0-95b2-1fb9261e4dc7
and receiving their responses via value notifications 86f65002-f706-58a0-95b2-1fb9261e4dc7
.
The first byte of both the Write Command (0x52) and the Handle Value Notification (0x1b) payload encodes the Ultrahuman opcode.
Opcode | Description |
---|---|
0x02
| set time |
0x04
| get recordings |
0x05
| get time |
0x07
| get earliest recording index |
0x08
| get latest recording index |
0x70
| activate airplane mode |
0x98
| reset device |
0xD1
| disable power saving mode |
0xD2
| enable power saving mode |
0x02 Set Time Command¶
Sets the current time.
Offset | Data Type | Description |
---|---|---|
0 | uint8 | opcode |
1 | uint32_le | time, seconds since 1970-01-01 00:00 |
example:
020001a5c4ac67966c
= opcode 0x02, time 2025-02-12 15:56:21 (0x67acc4a5)
0x02 Set Time Response¶
Response echos back the effective time.
Offset | Data Type | Description |
---|---|---|
0 | uint8 | opcode |
1 | uint8 | result: success (0x00) |
2 | uint8 | details: OK (0x01) |
3 | uint32_le | time, seconds since 1970-01-01 00:00 |
7..8 | ? | ? - the last 2 bytes of the message are likely a check sum |
example:
020001a5c4ac67966c
= opcode 0x02, success (0x00), OK (0x01), time 2025-02-12 16:56:56 (0x67acc45c)
0x04 Get Recordings Command¶
Requests historical sensor recordings.
Normally a new entry is recorded every ca 5 minutes. In Workout mode a new entry is recorded every ca 2 seconds.
Offset | Data Type | Description |
---|---|---|
0 | uint8 | opcode |
1-2 | uint16_le | first recording to retrieve |
examples:
040100
= opcode 0x04; request recording 1 (0x0001) and newer
042f0f
= opcode 0x04; request recording 3887 (0x0f2f) and newer
0x04 Get Recordings Response¶
One or more responses are received for each command. The response contains 0 or more, at least up to 7, records.
Offset | Data Type | Description |
---|---|---|
0 | uint8 | opcode |
1 | uint8 | result: success (0x00), no data (0xee), failure (0xff) |
2 | uint8 | number of recordings in this response |
3+r*32 | recording | a 32 byte recording, see dedicated recording chapter for details |
3+r*32+30 | uint16_le | index of the recoding, a "Soft Reset" resets the index to 1 |
len-2 | 2 bytes | ? - the last 2 bytes of the message are likely a check sum |
examples:
04ee00ffff
= opcode 0x04; 0 recordings
040001451da3670000000a451da3670000000000000000451da3670000000000006a00d0ae
= opcode 0x04, 1 recordings, empty record 106 (0x006a)
0400028a99a5670000000a8a99a56700000000000000008a99a5670000000000007206ae9aa56700000064ae9aa5670000000000000000ae9aa5670000000000007306adad
= opcode 0x04, 2 recordings,
empty record 1650 (0x0672), empty record 1651 (0x0673)
040001ac23a56747520005ac23a56734340b42fec50942ac23a5670000000052003205759a
= opcode 0x04, 1 recordings, record 1330 (0x0532) with sensor readings
recording structure¶
The recording consists of 3 sensor groups (A, B and C), each starting with its own timestamp. Depending on operation mode the timestamps are identical or different.
Offset | Data Type | Description |
---|---|---|
0 | uint32_le | timestamp A, seconds since 1970-01-01 00:00 |
4 | uint8 | heartrate (HR) in BPM, 0 if not measured |
5 | uint8 | heartrate variability (HRV), 0 if not measured |
6 | uint8 | blood oxygen level (SpO2) in %, 0 if not measured |
7 | uint8 | type of measurement: 1 = normal, 5 = exercise, 6 = breathing, 100 = not on finger |
8 | uint32_le | timestamp for group B, seconds since 1970-01-01 00:00 |
12 | float32_le | max skin temperatur in °C |
16 | float32_le | min skin temperatur in °C |
20 | uint32_le | timestamp for group C, seconds since 1970-01-01 00:00 |
24 | uint16_le | activity level (1 - 150), 0 = no data |
26 | uint16_le | steps since last record |
28 | uint16_le | stress (1 - 255), 0 = no data |
example:
E945AD67443362018546AD67785B0E4230D20C428546AD67960025003300
= time A 2025-02-13 01:07:53 (0x67AD45E9), heart reate 68 BPM (0x44), heart rate variability 51 (0x33), SpO2 98% (0x62), time B 2025-02-13 01:10:29 (0x67AD8546), max skin temperatur 35.58°C (0x420E5B78), min skin temperature 35.21°C (0x420CD230), time C 2025-02-13 01:10:29 (0x67AD4685), activity level 150 (0x0096), steps 37 (0x0025), stress 51 (0x0033)
0x05 Get Time Command¶
Retrieve the current device time.
Offset | Data Type | Description |
---|---|---|
0 | uint8 | opcode |
example:
05
= opcode 0x05
0x05 Get Time Response¶
The current UTC time of the device.
Offset | Data Type | Description |
---|---|---|
0 | uint8 | opcode |
1 | uint8 | result: success (0x00) |
2 | uint8 | details: OK (0x01) |
3 | uint32_le | time, seconds since 1970-01-01 00:00 |
7..8 | ? | ? - the last 2 bytes of the message are likely a check sum |
example:
050001d8d2ac67a6e6
= opcode 0x05, success (0x00), OK (0x01), time 2025-02-12 16:56:56 (0x67acd2d8)
0x07 Get Earliest Recording Index Command¶
Request the index of the oldest recording retrivable via 0x04 Get Recordings.
Offset | Data Type | Description |
---|---|---|
0 | uint8 | opcode |
example:
07
= opcode 0x07
0x07 Get Earliest Recording Index Response¶
Offset | Data Type | Description |
---|---|---|
0 | uint8 | opcode |
1 | uint8 | result: success (0x00), no data (0xee), failure (0xff) |
2 | uint8 | details: OK (0x01) |
3 | uint16_le | index of oldest recording |
5..6 | ? | ? - the last 2 bytes of the message are likely a check sum |
example:
070001c401bed7
= opcode 0x07, success (0x00), OK (0x01), first recording 452 (0x001c4)
0x08 Get Latest Recording Index Command¶
Request the index of the newest record retriveable via 0x04 Get Recordings.
Offset | Data Type | Description |
---|---|---|
0 | uint8 | opcode |
example:
08
= opcode 0x08
0x08 Get Latest Recording Index Response¶
Offset | Data Type | Description |
---|---|---|
0 | uint8 | opcode |
1 | uint8 | result: success (0x00), no data (0xee), failure (0xff) |
2 | uint8 | details: OK (0x01) |
3 | uint16_le | index of latest recording |
5..6 | ? | ? - the last 2 bytes of the message are likely a check sum |
example:
0800017306c320
= opcode 0x08, success (0x00), OK (0x01), latest recording 1651 (0x0673)
0x70 Activate Airplane Mode Command¶
Airplane Mode disables the BLE radio on the ring. The radio can be re-enabled by charging the ring.
Offset | Data Type | Description |
---|---|---|
0 | uint8 | opcode |
example:
70
= opcode 0x70
0x70 Activate Airplane Mode Response¶
The ring will switch of it's radio immediately after sending a success response.
Offset | Data Type | Description |
---|---|---|
0 | uint8 | opcode |
1 | uint8 | result: success (0x00), no data (0xee), failure (0xff) |
2 | uint8 | details: OK (0x01), ring is charging (0x02), ring is fully charged (0x03) |
3 | ? | ? |
4..5 | ? | ? - the last 2 bytes of the message are likely a check sum |
example:
70000161779d
= opcode 0x70, success (0x00), OK (0x01)
70ff02ff00ff
= opcode 0x70, failure (0xFF), ring is charging (0x02)
0x98 Reset Device Command¶
Purges recorded data from the device and reboots immediately without generating a response.
Offset | Data Type | Description |
---|---|---|
0 | uint8 | opcode |
example:
98
= opcode 0x98
0xD1 Disable Power Saving Mode Command¶
Enables the SpO2 sensing.
Offset | Data Type | Description |
---|---|---|
0 | uint8 | opcode |
example:
D1
= opcode 0xD1
0xD1 Disable Power Saving Mode Response¶
If successful, the next recordings are going to try to sense SpO2.
Offset | Data Type | Description |
---|---|---|
0 | uint8 | opcode |
1 | uint8 | result: success (0x00), no data (0xee), failure (0xff) |
2 | uint8 | details: OK (0x01) |
3 | ? | ? |
4..5 | ? | ? - the last 2 bytes of the message are likely a check sum |
example:
d10001d1ac3a
= opcode 0xD1, success (0x00), OK (0x01)
0xD2 Enable Power Saving Mode Command¶
Disable SpO2 sensing.
Offset | Data Type | Description |
---|---|---|
0 | uint8 | opcode |
example:
D2
= opcode 0xD2
0xD2 Enable Power Saving Mode Response¶
If successful, the next recordings are NOT going to try to sense SPO2.
Offset | Data Type | Description |
---|---|---|
0 | uint8 | opcode |
1 | uint8 | result: success (0x00), no data (0xee), failure (0xff) |
2 | uint8 | details: OK (0x01) |
3 | ? | ? |
4..5 | ? | ? - the last 2 bytes of the message are likely a check sum |
example:
d2000100f0e1
= opcode 0xD2, success (0x00), OK (0x01)