Skip to content
This repository was archived by the owner on Nov 21, 2025. It is now read-only.
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
.vscode/*
__pycache__/*
46 changes: 41 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ temperature, air pressure, humidity, TVOCs and CO2.
* [Usage](#usage)
* [Printing data to the terminal window](#printing-data-to-the-terminal-window)
* [Piping data to a text-file](#piping-data-to-a-text-file)
* [Posting regularly to MQTT Server](#posting-regularly-to-mqtt-server)
* [Sensor data description](#sensor-data-description)
* [Contribution](#contribution)
* [Release notes](#release-notes)
Expand All @@ -36,8 +37,8 @@ The following tables shows a compact overview of dependencies for this project.

| package | version | Comments |
|-------------|-------------|-------------|
| python | 2.7 | Tested with python 2.7.13
| python-pip | | pip for python2.7
| python | 2.7 or 3.6 | Tested with python 2.7.13 and 3.6.5
| python-pip | | pip for python 2.7 or python 3.6
| git | | To download this project
| libglib2.0-dev | | For bluepy module

Expand Down Expand Up @@ -104,11 +105,12 @@ or install git to be able to clone this repo.
pi@raspberrypi:~$ sudo apt-get install git
```

Additionally, the ```read_waveplus.py``` script depends on the ```tableprint``` module
to print nicely formated sensor data to the Raspberry Pi terminal at run-time.
Additionally, the ```read_waveplus.py``` script depends on the ```tableprint``` module to print nicely formated sensor data to the Raspberry Pi terminal at run-time and the ```paho.mqtt``` module for communication with an MQTT server.

```
pi@raspberrypi:~$ sudo pip2 install tableprint==0.8.0
pi@raspberrypi:~$ sudo pip install tableprint==0.8.0
pi@raspberrypi:~$ sudo pip install paho-mqtt

```

> **Note:** The ```read_waveplus.py``` script has been tested with bluepy==1.2.0 and tableprint==0.8.0. You may download the latest versions at your own risk.
Expand Down Expand Up @@ -182,6 +184,40 @@ where you change ```SN``` with the 10-digit serial number, and change ```SAMPLE-

Exit the script using ```Ctrl+C```.

## Posting regularly to MQTT Server

If you need the data available on an home automation system, you can set up the script to post data read from the Airthings Wave plus to an MQTT server (like mosquitto). Invoking the read_waveplus.py with
```
pi@raspberrypi:~/waveplus-reader $ sudo python3 read_waveplus_mqtt.py SN SERVER_ADDRESS
```
will read values from Airthings Wave Plus and post all values on the given mqtt server, assuming there is no login required.
```SN``` should be the serial number of your device and ```SERVER_ADDRESS``` is the ip-address of the mqtt server you want your data posted to.

The values wil be posted both as a json object containing all values and as individual individual values the following topics:
```
waveplus/SN/MEASUREMENT_TYPE
```
```MEASUREMENT_TYPE``` would be modified name of the measurements. Examples of topics would be
```
waveplus/2930002359/Humidity
waveplus/2930002359/Radon_ST_avg
...
waveplus/2930002359/VOC_level
```

Practical setup would be to add a line to your cron table:
```
pi@raspberrypi:~ $ sudo crontab -e
```
Note the use of 'sudo': needed for the scripts access to the BTLE device.

The following line will make a read and post every 5 minutes from your Raspberry Pi to the mqtt server located at ```192.168.0.16```:

```
*/5 * * * * sudo python3 /home/pi/waveplus-reader/read_waveplus_mqtt.py 2930002359 192.168.0.16
```


# Sensor data description

| sensor | units | Comments |
Expand Down
51 changes: 51 additions & 0 deletions Sensors.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@

# ===================================
# Class Sensor and sensor definitions
# ===================================

import sys

class Sensors():

NUMBER_OF_SENSORS = 7
SENSOR_IDX_HUMIDITY = 0
SENSOR_IDX_RADON_SHORT_TERM_AVG = 1
SENSOR_IDX_RADON_LONG_TERM_AVG = 2
SENSOR_IDX_TEMPERATURE = 3
SENSOR_IDX_REL_ATM_PRESSURE = 4
SENSOR_IDX_CO2_LVL = 5
SENSOR_IDX_VOC_LVL = 6

def __init__(self):
self.sensor_version = None
self.sensor_data = [None]*self.NUMBER_OF_SENSORS
self.sensor_units = ["%rH", "Bq/m3", "Bq/m3", "degC", "hPa", "ppm", "ppb"]
self.header = ['Humidity', 'Radon ST avg', 'Radon LT avg', 'Temperature', 'Pressure', 'CO2 level', 'VOC level']


def set(self, rawData):
self.sensor_version = rawData[0]
if (self.sensor_version == 1):
self.sensor_data[self.SENSOR_IDX_HUMIDITY] = rawData[1]/2.0
self.sensor_data[self.SENSOR_IDX_RADON_SHORT_TERM_AVG] = self.conv2radon(rawData[4])
self.sensor_data[self.SENSOR_IDX_RADON_LONG_TERM_AVG] = self.conv2radon(rawData[5])
self.sensor_data[self.SENSOR_IDX_TEMPERATURE] = rawData[6]/100.0
self.sensor_data[self.SENSOR_IDX_REL_ATM_PRESSURE] = rawData[7]/50.0
self.sensor_data[self.SENSOR_IDX_CO2_LVL] = rawData[8]*1.0
self.sensor_data[self.SENSOR_IDX_VOC_LVL] = rawData[9]*1.0
else:
print ("ERROR: Unknown sensor version.\n")
print ("GUIDE: Contact Airthings for support.\n")
sys.exit(1)

def conv2radon(self, radon_raw):
radon = "N/A" # Either invalid measurement, or not available
if 0 <= radon_raw <= 16383:
radon = radon_raw
return radon

def getValue(self, sensor_index):
return self.sensor_data[sensor_index]

def getUnit(self, sensor_index):
return self.sensor_units[sensor_index]
82 changes: 82 additions & 0 deletions WavePlus.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
# ===============================
# Class WavePlus
# ===============================

import sys
import struct
from Sensors import Sensors
from bluepy.btle import UUID, Peripheral, Scanner, DefaultDelegate

class WavePlus():

def __init__(self, SerialNumber):
self.periph = None
self.curr_val_char = None
self.MacAddr = None
self.SN = SerialNumber
self.uuid = UUID("b42e2a68-ade7-11e4-89d3-123b93f75cba")

def connect(self):
# Auto-discover device on first connection
if (self.MacAddr is None):
scanner = Scanner().withDelegate(DefaultDelegate())
searchCount = 0
while self.MacAddr is None and searchCount < 50:
devices = scanner.scan(0.1) # 0.1 seconds scan period
searchCount += 1
for dev in devices:
ManuData = dev.getValueText(255)
if ManuData != None:
SN = self.parseSerialNumber(ManuData)
if (SN == self.SN):
self.MacAddr = dev.addr # exits the while loop on next conditional check
break # exit for loop

if (self.MacAddr is None):
print ("ERROR: Could not find device.")
print ("GUIDE: (1) Please verify the serial number.")
print (" (2) Ensure that the device is advertising.")
print (" (3) Retry connection.")
sys.exit(1)

# Connect to device
if (self.periph is None):
self.periph = Peripheral(self.MacAddr)
if (self.curr_val_char is None):
self.curr_val_char = self.periph.getCharacteristics(uuid=self.uuid)[0]

def read(self):
if (self.curr_val_char is None):
print ("ERROR: Devices are not connected.")
sys.exit(1)
rawdata = self.curr_val_char.read()
rawdata = struct.unpack('BBBBHHHHHHHH', rawdata)
sensors = Sensors()
sensors.set(rawdata)
return sensors

def disconnect(self):
if self.periph is not None:
self.periph.disconnect()
self.periph = None
self.curr_val_char = None

# ====================================
# Utility functions for WavePlus class
# ====================================

def parseSerialNumber(self, ManuDataHexStr):
if (ManuDataHexStr == "None"):
SN = "Unknown"
else:
ManuData = bytearray.fromhex(ManuDataHexStr)

if (((ManuData[1] << 8) | ManuData[0]) == 0x0334):
SN = ManuData[2]
SN |= (ManuData[3] << 8)
SN |= (ManuData[4] << 16)
SN |= (ManuData[5] << 24)
else:
SN = "Unknown"
return SN

Loading