Read logged history - Ruuvi Air
Related Ruuvi Devices: Ruuvi Air
Overview
Ruuvi Air supports reading logged sensor history data over a Bluetooth Low Energy (BLE) connection using the Nordic UART Service (NUS). This allows mobile applications to retrieve historical air quality measurements stored on the device.
The protocol is similar to RuuviTag log reading, but uses a different data format (E1) that includes additional air quality measurements such as PM2.5, CO₂, VOC and NOx levels.
Log read flow
The mobile app connects to Ruuvi Air over BLE
The app negotiates a larger MTU (247+ bytes recommended)
The app subscribes to NUS TX characteristic notifications
The app sends a log read command to the NUS RX characteristic
Ruuvi Air responds with multiple packets containing logged records
When all records are sent, Ruuvi Air sends an end-of-transmission marker
Request format
The log read request is an 11-byte message with the following structure:
0
Destination
0x3B (Air Quality endpoint)
1
Source
0x3B (Air Quality endpoint)
2
Operation
0x21 (Multi-record log read) or 0x11 (Single-record log read)
3-6
Current time
Current Unix timestamp (big-endian, seconds since epoch)
7-10
Start time
Start Unix timestamp to read from (big-endian, seconds since epoch)
The multi-record read (0x21) is recommended as it packs multiple records per BLE packet for faster transfer.
Example request
Reading all air quality data from timestamp 1733760000 (2024-12-09 16:00:00 UTC) with current time 1733763600 (2024-12-09 17:00:00 UTC):
0x3B 3B: Destination and Source = Air Quality0x21: Multi-record log read operation0x6757 01B0: Current time = 17337636000x6756 F200: Start time = 1733760000
Response format
Multi-record response packet
When using multi-record read (0x21), each response packet contains multiple records:
0
Destination
Source index from request
1
Source
0x3B (Air Quality endpoint)
2
Operation
0x20 (Multi-record log write)
3
Num records
Number of records in this packet
4
Record length
Length of each record (38 bytes)
5+
Records
Packed record data
Record data format (38 bytes per record)
Each record contains a timestamp followed by E1 data format payload:
0-3
Timestamp
uint32_t BE
Unix timestamp in seconds
4
Data format
uint8_t
0xE1
5-6
Temperature
int16_t BE
Temperature × 200 (°C)
7-8
Humidity
uint16_t BE
Humidity × 400 (%)
9-10
Pressure
uint16_t BE
(Pressure - 50000) Pa
11-12
PM1.0
uint16_t BE
PM1.0 × 10 (µg/m³)
13-14
PM2.5
uint16_t BE
PM2.5 × 10 (µg/m³)
15-16
PM4.0
uint16_t BE
PM4.0 × 10 (µg/m³)
17-18
PM10.0
uint16_t BE
PM10.0 × 10 (µg/m³)
19-20
CO₂
uint16_t BE
CO₂ (ppm)
21
VOC
uint8_t
VOC index (0-500), bit 9 in flags
22
NOx
uint8_t
NOx index (0-500), bit 9 in flags
23-25
Reserved
uint24_t BE
Reserved
26
Reserved
uint8_t
Reserved
27
Reserved
uint8_t
Reserved
28
Reserved
uint8_t
Reserved
29-31
Sequence cnt
uint24_t BE
Measurement sequence counter
32
Flags
uint8_t
Extended bits for 9-bit values
33-37
Reserved
-
Reserved for future use
Flags byte interpretation
The flags byte contains the 9th bit for values that need more than 8 bits of precision:
6
VOC bit 9
7
NOx bit 9
Decoding formulas
Temperature
raw / 200.0
°C
-163.840 to +163.830
Humidity
raw / 400.0
%
0 to 100
Pressure
raw + 50000
Pa
50000 to 115534
PM values
raw / 10.0
µg/m³
0 to 6553.4
CO₂
raw
ppm
0 to 65534
VOC/NOx
(byte | (flag_bit9 << 8))
index
0 to 500
Invalid values
When a sensor value is unavailable or invalid, the following special values are used:
Temperature
0x8000
Humidity
0xFFFF
Pressure
0xFFFF
PM values
0xFFFF
CO₂
0xFFFF
VOC/NOx
0x1FF (511)
Sequence
0xFFFFFF
End of transmission
When all records have been sent, Ruuvi Air sends a final packet with:
num_records = 0record_length = 38
Example end marker:
This indicates no more records are available.
Example communication
Central
0x3B 3B 21 67 57 01 B0 67 56 F2 00
"Read air quality log. Current time: 2024-12-09 17:00, start from: 2024-12-09 16:00"
Peripheral
0x3B 3B 20 06 26 ...
"6 records of 38 bytes each in this packet"
...
...
Additional packets with records
Peripheral
0x3B 3B 20 00 26
"No more records (end of log)"
You can try the communication out quickly with our sample script
Implementation notes
MTU considerations
Negotiating a larger MTU (247+ bytes) is recommended for optimal throughput. A minimum MTU of ~46 bytes is required to fit a single 38-byte record with headers. With a larger MTU, BLE Data Length Extension (DLE) enables up to 244 bytes per notification. With 38 bytes per record and a 5-byte packet header, up to 6 records can be packed in a single BLE packet:
Time synchronization
The protocol uses a time offset calculation to handle clock drift between the device and the mobile app:
The app sends its current Unix time in the request
The device calculates the offset:
offset = app_time - device_timeTimestamps in responses are adjusted by this offset
This ensures the returned timestamps are relative to the app's clock rather than the device's internal clock.
Record interval
Ruuvi Air logs measurements once per 5 minutes. A full day of history contains approximately 288 records.
Code references
Firmware: ruuvi.air.main/src/nus.c
Android: NordicGattManager.kt
iOS: BTServices.swift
Data format: ruuvi.endpoints.c
Related documentation
Last updated