Summary
AirHistoryDecoder currently reconstructs VOC and NOx history values by treating the corresponding Flags bit as the most significant bit of the 9-bit value:
value_bit9 = (flags >> flag_bit) & 0x01
value = value_byte | (value_bit9 << 8)
This is inconsistent with the Ruuvi data format encoding and with the existing DF6 / E1 advertisement decoders in this project.
For Ruuvi Air VOC and NOx values, the value byte contains bits [8:1], while the corresponding Flags bit contains value bit [0] (LSB).
The correct reconstruction is:
value_lsb = (flags >> flag_bit) & 0x01
value = (value_byte << 1) | value_lsb
Specifically:
voc = (voc_byte << 1) | ((flags >> 6) & 0x01)
nox = (nox_byte << 1) | ((flags >> 7) & 0x01)
Affected code
The issue is in:
ruuvitag_sensor/decoders/air_history_decoder.py
in:
AirHistoryDecoder._get_9bit_value()
Why this is incorrect
The Ruuvi advertisement decoders in this project already implement the encoding correctly.
For example, the E1 decoder reconstructs VOC as:
voc_high_bits = data[9]
voc_lsb = (data[14] >> 6) & 0x01
voc = (voc_high_bits << 1) | voc_lsb
and NOx similarly.
The Ruuvi reference history decoder also uses the LSB-in-Flags interpretation:
https://github.com/ruuvi/ruuvi.air.ble_nus/blob/master/scripts/ruuvi_ble_nus_read_hist.py
The generic Ruuvi endpoint decoder uses the same scheme:
https://github.com/ruuvi/ruuvi.endpoints.c/blob/master/src/ruuvi_endpoints_internal.h
Example
For:
VOC byte = 0x01
Flags bit 6 = 1
the correct result is:
The current Air history decoder instead produces:
Likewise, for:
NOx byte = 0x02
Flags bit 7 = 1
the correct result is:
while the current decoder returns 258.
Tests
The existing Air history tests currently encode the same incorrect interpretation, so they need to be updated together with the decoder.
In particular, test_decode_voc_nox_with_flags() currently treats the Flags bit as value bit [8].
The regression test should verify values where the flag bit is set and distinguish clearly between the two interpretations, for example:
VOC: byte=0x01, Flags b6=1 -> 3
NOx: byte=0x02, Flags b7=1 -> 5
The invalid value remains unchanged:
byte=0xFF, flag=1
=> (0xFF << 1) | 1
=> 0x1FF
=> invalid
Expected fix
Change:
value_bit9 = (flags >> flag_bit) & 0x01
value = value_byte | (value_bit9 << 8)
to:
value_lsb = (flags >> flag_bit) & 0x01
value = (value_byte << 1) | value_lsb
and update the affected Air history test vectors accordingly.
Summary
AirHistoryDecodercurrently reconstructs VOC and NOx history values by treating the corresponding Flags bit as the most significant bit of the 9-bit value:This is inconsistent with the Ruuvi data format encoding and with the existing DF6 / E1 advertisement decoders in this project.
For Ruuvi Air VOC and NOx values, the value byte contains bits
[8:1], while the corresponding Flags bit contains value bit[0](LSB).The correct reconstruction is:
Specifically:
Affected code
The issue is in:
in:
Why this is incorrect
The Ruuvi advertisement decoders in this project already implement the encoding correctly.
For example, the E1 decoder reconstructs VOC as:
and NOx similarly.
The Ruuvi reference history decoder also uses the LSB-in-Flags interpretation:
https://github.com/ruuvi/ruuvi.air.ble_nus/blob/master/scripts/ruuvi_ble_nus_read_hist.py
The generic Ruuvi endpoint decoder uses the same scheme:
https://github.com/ruuvi/ruuvi.endpoints.c/blob/master/src/ruuvi_endpoints_internal.h
Example
For:
the correct result is:
The current Air history decoder instead produces:
Likewise, for:
the correct result is:
while the current decoder returns
258.Tests
The existing Air history tests currently encode the same incorrect interpretation, so they need to be updated together with the decoder.
In particular,
test_decode_voc_nox_with_flags()currently treats the Flags bit as value bit[8].The regression test should verify values where the flag bit is set and distinguish clearly between the two interpretations, for example:
The invalid value remains unchanged:
Expected fix
Change:
to:
and update the affected Air history test vectors accordingly.