Airthings Wave Plus

From Leo's Notes
Last edited on 25 September 2022, at 23:29.

Airthings Wave Plus is a air quality monitor with Bluetooth Low Energy capability. It's capable of measuring temperature, humidity and also CO2, Radon, and VOC. The unit runs on 2 AA batteries. You can poll its status via Bluetooth LE periodically using any device that supports Bluetooth including a Raspberry Pi or an ESP32.

Bluetooth LE

The Airthings Wave Plus uses the service UUID b42e1c08-ade7-11e4-89d3-123b93f75cba. The measurements can be read through the characteristic UUID b42e2a68-ade7-11e4-89d3-123b93f75cba.

You will have to look at the Bluetooth LE advertisements to see which MAC address your unit has. The serial number on the device doesn't seem to correspond to the MAC address it has.

Push readings via MQTT

Using OpenMQTTGateway on a ESP32, we can read the unit's readings via Bluetooth LE and push it to a MQTT server.

Create a MQTTtoBT command. You should publish this message to something like: openmqtt/ESP32/commands/MQTTtoBT/config.

{
"ble_read_address":"D8:71:4D:A9:CB:C3",
"ble_read_service":"b42e1c08-ade7-11e4-89d3-123b93f75cba",
"ble_read_char":"b42e2a68-ade7-11e4-89d3-123b93f75cba",
"value_type":"HEX",
"ttl":10,
"immediate":true
}

You should then hopefully be able to hear back once a connection gets established.

{
"id":"D8:71:4D:A9:CB:C3",
"service":"b42e1c08-ade7-11e4-89d3-123b93f75cba",
"characteristic":"b42e2a68-ade7-11e4-89d3-123b93f75cba",
"read":"015f220073008400ba07ddab5a0247000000ac05",
"success":true
}

To get periodic measurements, I've set up a recurring node in NodeRED that polls the device every 5 minutes and dumps the data into InfluxDB.

Decoding the raw Bluetooth LE data

The data returned back is 40 bytes long. We need to decode this using the following data structure:

typedef struct
{
  uint8_t version = 0;
  uint8_t humidity = 0;
  uint8_t ambientLight = 0;
  uint8_t unused01 = 0;
  uint16_t radon = 0;
  uint16_t radonLongterm = 0;
  uint16_t temperature = 0;
  uint16_t pressure = 0;
  uint16_t co2 = 0;
  uint16_t voc = 0;
} WavePlusRawReadings;

In the example above, we'll break the incoming data 015f220073008400ba07ddab5a0247000000ac05 into their individual fields and decode it into useful measurements.

Hex Name Final reading
01 Version Version 1
5f Humidity 0x5f = 95 / 2 = 47.5%
22 Ambient Light 0x22 = 34
00 Unused
7300 Radon, 24 hours 0x0073 = 115 Bq
8400 Radon, long term 0x0084 = 132 Bq
ba07 Temperature 0x07ba = 1978 / 100 = 19.78 C
ddab Air Pressure 0xabdd = 43997 / 50 = 879.94 Pa
5a02 CO2 0x025a = 602 ppm
4700 VOC 0x0047 = 71 ppb
0000 ??
ac05 ?? 0x05ac = 1452

NodeRED function

Here's the decode function I used. I'm sure there's a better way of doing it, but this should work.

var version  = parseInt(msg.payload.read.substring(0, 2), 16);
var humidity = parseInt(msg.payload.read.substring(2, 4), 16) / 2.0;
var light    = parseInt(msg.payload.read.substring(4, 6), 16);
var radon24  = parseInt(msg.payload.read.substring(10, 12) + msg.payload.read.substring(8, 10), 16);
var radonlt  = parseInt(msg.payload.read.substring(14, 16) + msg.payload.read.substring(12, 14), 16);
var temp     = parseInt(msg.payload.read.substring(18, 20) + msg.payload.read.substring(16, 18), 16) / 100.0;
var pressure = parseInt(msg.payload.read.substring(22, 24) + msg.payload.read.substring(20, 22), 16) / 50.0;
var co2      = parseInt(msg.payload.read.substring(26, 28) + msg.payload.read.substring(24, 26), 16);
var voc      = parseInt(msg.payload.read.substring(30, 32) + msg.payload.read.substring(28, 30), 16);
var a        = parseInt(msg.payload.read.substring(34, 36) + msg.payload.read.substring(30, 32), 16);
var b        = parseInt(msg.payload.read.substring(38, 40) + msg.payload.read.substring(36, 38), 16);

ESPHome integration

I tried using ESPHome using the following configs, but had issues getting it to read the Bluetooth data properly. For some reason, my ESP32 kept on getting a lld_pdu_get_tx_flush_nb HCI packet count mismatch (0, 1) error. The same device running the AirthingsMQTT Bridge sketch or the OpenMQTTGateway firmware had no such issues.

sensor:
  - platform: airthings_wave_plus
    ble_client_id: airthings
    update_interval: 1min # default
    temperature:
      name: "Airthings Wave Plus Temperature"
    radon:
      name: "Airthings Wave Plus Radon"
    radon_long_term:
      name: "Airthings Wave Plus Radon Long Term"
    pressure:
      name: "Airthings Wave Plus Pressure"
    humidity:
      name: "Airthings Wave Plus Humidity"
    co2:
      name: "Airthings Wave Plus CO2"
    tvoc:
      name: "Airthings Wave Plus VOC"

ble_client:
  - mac_address: d8:71:4d:a9:cb:c3
    id: airthings

esp32_ble_tracker:

Regardless, I like the more intuitive way of dealing with just MQTT messages using OpenMQTTGateway rather than ESPHome. The whole Home Assistant integration thing with ESPHome just didn't work out properly for me (for one thing, the ESP device kept appearing offline periodically? Adding additional BLE sensors requires rebuilding the sketch/reuploading? OMG just reports back everything it can see without needing to rewrite/reflash anything and is superior in that regard). Gah!

See also