From 727a3596b98718e56a38f62d465dbc97ead489ae Mon Sep 17 00:00:00 2001 From: Sten Johnsen Date: Fri, 4 Jan 2019 23:15:57 +0100 Subject: [PATCH 01/14] Modified for Python 3 and added exeption testing --- read_waveplus.py | 69 +++++++++++++++++++++++++----------------------- 1 file changed, 36 insertions(+), 33 deletions(-) diff --git a/read_waveplus.py b/read_waveplus.py index aca19ae..4796698 100644 --- a/read_waveplus.py +++ b/read_waveplus.py @@ -37,27 +37,27 @@ # =============================== if len(sys.argv) < 3: - print "ERROR: Missing input argument SN or SAMPLE-PERIOD." - print "USAGE: read_waveplus.py SN SAMPLE-PERIOD [pipe > yourfile.txt]" - print " where SN is the 10-digit serial number found under the magnetic backplate of your Wave Plus." - print " where SAMPLE-PERIOD is the time in seconds between reading the current values." - print " where [pipe > yourfile.txt] is optional and specifies that you want to pipe your results to yourfile.txt." + print ("ERROR: Missing input argument SN or SAMPLE-PERIOD.") + print ("USAGE: read_waveplus.py SN SAMPLE-PERIOD [pipe > yourfile.txt]") + print (" where SN is the 10-digit serial number found under the magnetic backplate of your Wave Plus.") + print (" where SAMPLE-PERIOD is the time in seconds between reading the current values.") + print (" where [pipe > yourfile.txt] is optional and specifies that you want to pipe your results to yourfile.txt.") sys.exit(1) if sys.argv[1].isdigit() is not True or len(sys.argv[1]) != 10: - print "ERROR: Invalid SN format." - print "USAGE: read_waveplus.py SN SAMPLE-PERIOD [pipe > yourfile.txt]" - print " where SN is the 10-digit serial number found under the magnetic backplate of your Wave Plus." - print " where SAMPLE-PERIOD is the time in seconds between reading the current values." - print " where [pipe > yourfile.txt] is optional and specifies that you want to pipe your results to yourfile.txt." + print ("ERROR: Invalid SN format.") + print ("USAGE: read_waveplus.py SN SAMPLE-PERIOD [pipe > yourfile.txt]") + print (" where SN is the 10-digit serial number found under the magnetic backplate of your Wave Plus.") + print (" where SAMPLE-PERIOD is the time in seconds between reading the current values.") + print (" where [pipe > yourfile.txt] is optional and specifies that you want to pipe your results to yourfile.txt.") sys.exit(1) if sys.argv[2].isdigit() is not True or int(sys.argv[2])<0: - print "ERROR: Invalid SAMPLE-PERIOD. Must be a numerical value larger than zero." - print "USAGE: read_waveplus.py SN SAMPLE-PERIOD [pipe > yourfile.txt]" - print " where SN is the 10-digit serial number found under the magnetic backplate of your Wave Plus." - print " where SAMPLE-PERIOD is the time in seconds between reading the current values." - print " where [pipe > yourfile.txt] is optional and specifies that you want to pipe your results to yourfile.txt." + print ("ERROR: Invalid SAMPLE-PERIOD. Must be a numerical value larger than zero.") + print ("USAGE: read_waveplus.py SN SAMPLE-PERIOD [pipe > yourfile.txt]") + print (" where SN is the 10-digit serial number found under the magnetic backplate of your Wave Plus.") + print (" where SAMPLE-PERIOD is the time in seconds between reading the current values.") + print (" where [pipe > yourfile.txt] is optional and specifies that you want to pipe your results to yourfile.txt.") sys.exit(1) if len(sys.argv) > 3: @@ -66,11 +66,11 @@ Mode = 'terminal' # (default) print to terminal if Mode!='pipe' and Mode!='terminal': - print "ERROR: Invalid piping method." - print "USAGE: read_waveplus.py SN SAMPLE-PERIOD [pipe > yourfile.txt]" - print " where SN is the 10-digit serial number found under the magnetic backplate of your Wave Plus." - print " where SAMPLE-PERIOD is the time in seconds between reading the current values." - print " where [pipe > yourfile.txt] is optional and specifies that you want to pipe your results to yourfile.txt." + print ("ERROR: Invalid piping method.") + print ("USAGE: read_waveplus.py SN SAMPLE-PERIOD [pipe > yourfile.txt]") + print (" where SN is the 10-digit serial number found under the magnetic backplate of your Wave Plus.") + print (" where SAMPLE-PERIOD is the time in seconds between reading the current values.") + print (" where [pipe > yourfile.txt] is optional and specifies that you want to pipe your results to yourfile.txt.") sys.exit(1) SerialNumber = int(sys.argv[1]) @@ -124,8 +124,8 @@ def connect(self): break # exit for loop if (deviceFound is not True): - print "ERROR: Could not find device." - print "GUIDE: (1) Please verify the serial number. (2) Ensure that the device is advertising. (3) Retry connection." + print ("ERROR: Could not find device.") + print ("GUIDE: (1) Please verify the serial number. (2) Ensure that the device is advertising. (3) Retry connection.") sys.exit(1) else: self.periph = Peripheral(MacAddr) @@ -172,8 +172,8 @@ def set(self, rawData): self.sensor_data[SENSOR_IDX_CO2_LVL] = rawData[8]*1.0 self.sensor_data[SENSOR_IDX_VOC_LVL] = rawData[9]*1.0 else: - print "ERROR: Unknown sensor version.\n" - print "GUIDE: Contact Airthings for support.\n" + print ("ERROR: Unknown sensor version.\n") + print ("GUIDE: Contact Airthings for support.\n") sys.exit(1) def conv2radon(self, radon_raw): @@ -194,22 +194,25 @@ def getUnit(self, sensor_index): waveplus.connect() if (Mode=='terminal'): - print "\nPress ctrl+C to exit program\n" + print ("\nPress ctrl+C to exit program\n") - print "Device serial number: %s" %(SerialNumber) + print ("Device serial number: %s" %(SerialNumber)) header = ['Humidity', 'Radon ST avg', 'Radon LT avg', 'Temperature', 'Pressure', 'CO2 level', 'VOC level'] if (Mode=='terminal'): - print tableprint.header(header, width=12) + print (tableprint.header(header, width=12)) elif (Mode=='pipe'): - print header + print (header) while True: - # read values - sensors = waveplus.read() - + try: + # read values + sensors = waveplus.read() + except: + continue + # extract humidity = str(sensors.getValue(SENSOR_IDX_HUMIDITY)) + " " + str(sensors.getUnit(SENSOR_IDX_HUMIDITY)) radon_st_avg = str(sensors.getValue(SENSOR_IDX_RADON_SHORT_TERM_AVG)) + " " + str(sensors.getUnit(SENSOR_IDX_RADON_SHORT_TERM_AVG)) @@ -223,9 +226,9 @@ def getUnit(self, sensor_index): data = [humidity, radon_st_avg, radon_lt_avg, temperature, pressure, CO2_lvl, VOC_lvl] if (Mode=='terminal'): - print tableprint.row(data, width=12) + print (tableprint.row(data, width=12)) elif (Mode=='pipe'): - print data + print (data) time.sleep(SamplePeriod) From 87ae17ad1ac6e58a159854ac95c20bb36bbca5c1 Mon Sep 17 00:00:00 2001 From: Sten Johnsen Date: Fri, 4 Jan 2019 23:19:24 +0100 Subject: [PATCH 02/14] Modified for Python 3 and added exeption testing --- read_waveplus.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/read_waveplus.py b/read_waveplus.py index 4796698..8dddd4b 100644 --- a/read_waveplus.py +++ b/read_waveplus.py @@ -206,13 +206,14 @@ def getUnit(self, sensor_index): print (header) while True: - + sensors = None try: # read values sensors = waveplus.read() - except: + except : + print("Exception when calling waveplus->read") continue - + # extract humidity = str(sensors.getValue(SENSOR_IDX_HUMIDITY)) + " " + str(sensors.getUnit(SENSOR_IDX_HUMIDITY)) radon_st_avg = str(sensors.getValue(SENSOR_IDX_RADON_SHORT_TERM_AVG)) + " " + str(sensors.getUnit(SENSOR_IDX_RADON_SHORT_TERM_AVG)) From 5e35745cfbb640f559a6031fd4a4447337a3eece Mon Sep 17 00:00:00 2001 From: Sten Johnsen Date: Sat, 5 Jan 2019 00:31:03 +0100 Subject: [PATCH 03/14] Updating the try:cach statement --- read_waveplus.py | 38 +++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/read_waveplus.py b/read_waveplus.py index 8dddd4b..8c68179 100644 --- a/read_waveplus.py +++ b/read_waveplus.py @@ -210,27 +210,27 @@ def getUnit(self, sensor_index): try: # read values sensors = waveplus.read() + + # extract + humidity = str(sensors.getValue(SENSOR_IDX_HUMIDITY)) + " " + str(sensors.getUnit(SENSOR_IDX_HUMIDITY)) + radon_st_avg = str(sensors.getValue(SENSOR_IDX_RADON_SHORT_TERM_AVG)) + " " + str(sensors.getUnit(SENSOR_IDX_RADON_SHORT_TERM_AVG)) + radon_lt_avg = str(sensors.getValue(SENSOR_IDX_RADON_LONG_TERM_AVG)) + " " + str(sensors.getUnit(SENSOR_IDX_RADON_LONG_TERM_AVG)) + temperature = str(sensors.getValue(SENSOR_IDX_TEMPERATURE)) + " " + str(sensors.getUnit(SENSOR_IDX_TEMPERATURE)) + pressure = str(sensors.getValue(SENSOR_IDX_REL_ATM_PRESSURE)) + " " + str(sensors.getUnit(SENSOR_IDX_REL_ATM_PRESSURE)) + CO2_lvl = str(sensors.getValue(SENSOR_IDX_CO2_LVL)) + " " + str(sensors.getUnit(SENSOR_IDX_CO2_LVL)) + VOC_lvl = str(sensors.getValue(SENSOR_IDX_VOC_LVL)) + " " + str(sensors.getUnit(SENSOR_IDX_VOC_LVL)) + + # Print data + data = [humidity, radon_st_avg, radon_lt_avg, temperature, pressure, CO2_lvl, VOC_lvl] + + if (Mode=='terminal'): + print (tableprint.row(data, width=12)) + elif (Mode=='pipe'): + print (data) + except : print("Exception when calling waveplus->read") - continue - - # extract - humidity = str(sensors.getValue(SENSOR_IDX_HUMIDITY)) + " " + str(sensors.getUnit(SENSOR_IDX_HUMIDITY)) - radon_st_avg = str(sensors.getValue(SENSOR_IDX_RADON_SHORT_TERM_AVG)) + " " + str(sensors.getUnit(SENSOR_IDX_RADON_SHORT_TERM_AVG)) - radon_lt_avg = str(sensors.getValue(SENSOR_IDX_RADON_LONG_TERM_AVG)) + " " + str(sensors.getUnit(SENSOR_IDX_RADON_LONG_TERM_AVG)) - temperature = str(sensors.getValue(SENSOR_IDX_TEMPERATURE)) + " " + str(sensors.getUnit(SENSOR_IDX_TEMPERATURE)) - pressure = str(sensors.getValue(SENSOR_IDX_REL_ATM_PRESSURE)) + " " + str(sensors.getUnit(SENSOR_IDX_REL_ATM_PRESSURE)) - CO2_lvl = str(sensors.getValue(SENSOR_IDX_CO2_LVL)) + " " + str(sensors.getUnit(SENSOR_IDX_CO2_LVL)) - VOC_lvl = str(sensors.getValue(SENSOR_IDX_VOC_LVL)) + " " + str(sensors.getUnit(SENSOR_IDX_VOC_LVL)) - - # Print data - data = [humidity, radon_st_avg, radon_lt_avg, temperature, pressure, CO2_lvl, VOC_lvl] - - if (Mode=='terminal'): - print (tableprint.row(data, width=12)) - elif (Mode=='pipe'): - print (data) - + time.sleep(SamplePeriod) finally: From 2caaf1012c6e0fe13c88d4ee96dceec3b9c0b472 Mon Sep 17 00:00:00 2001 From: Sten Johnsen Date: Sat, 5 Jan 2019 00:57:02 +0100 Subject: [PATCH 04/14] Adding mqtt broker service --- read_waveplus.py | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/read_waveplus.py b/read_waveplus.py index 8c68179..e0529db 100644 --- a/read_waveplus.py +++ b/read_waveplus.py @@ -31,6 +31,7 @@ import time import struct import tableprint +import paho.mqtt.client as mqtt # =============================== # Script guards for correct usage @@ -62,10 +63,14 @@ if len(sys.argv) > 3: Mode = sys.argv[3].lower() + if Mode == 'mqtt' and len(sys.argv > 4): + Broker = sys.argv[4] + else: + Broker = None else: Mode = 'terminal' # (default) print to terminal -if Mode!='pipe' and Mode!='terminal': +if Mode!='pipe' and Mode!='terminal' and Mode!='mqtt': print ("ERROR: Invalid piping method.") print ("USAGE: read_waveplus.py SN SAMPLE-PERIOD [pipe > yourfile.txt]") print (" where SN is the 10-digit serial number found under the magnetic backplate of your Wave Plus.") @@ -204,6 +209,19 @@ def getUnit(self, sensor_index): print (tableprint.header(header, width=12)) elif (Mode=='pipe'): print (header) + elif Mode == 'mqtt': + sensors = waveplus.read() + topic = "waveplus/{}".format(SerialNumber) + client = mqtt.Client() + client.connect(Broker) + for i in range(NUMBER_OF_SENSORS): + topic = "waveplus/{0}/{1}".format(SerialNumber, header[i].replace(' ','_')) + info = client.publish(topic, sensors.getValue(i), retain=False) + info.wait_for_publish() + time.sleep(0.1) + + client.disconnect() + exit(1) while True: sensors = None From cb0921c7d43fee928cb5cf84cda804a844006122 Mon Sep 17 00:00:00 2001 From: Sten Johnsen Date: Sat, 5 Jan 2019 01:04:02 +0100 Subject: [PATCH 05/14] Fixed typo om checking for mqtt --- read_waveplus.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/read_waveplus.py b/read_waveplus.py index e0529db..495233e 100644 --- a/read_waveplus.py +++ b/read_waveplus.py @@ -63,7 +63,7 @@ if len(sys.argv) > 3: Mode = sys.argv[3].lower() - if Mode == 'mqtt' and len(sys.argv > 4): + if Mode == 'mqtt' and len(sys.argv) > 4: Broker = sys.argv[4] else: Broker = None From b1656fbc04a3cfc90e26ddd7d1db5c7085b72ba8 Mon Sep 17 00:00:00 2001 From: Sten Johnsen Date: Sat, 5 Jan 2019 01:05:38 +0100 Subject: [PATCH 06/14] Fixed another typo --- read_waveplus.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/read_waveplus.py b/read_waveplus.py index 495233e..f241dde 100644 --- a/read_waveplus.py +++ b/read_waveplus.py @@ -63,7 +63,7 @@ if len(sys.argv) > 3: Mode = sys.argv[3].lower() - if Mode == 'mqtt' and len(sys.argv) > 4: + if Mode == 'mqtt': Broker = sys.argv[4] else: Broker = None From c28b982db608762c8b03bf02189da88fae38f981 Mon Sep 17 00:00:00 2001 From: Sten Johnsen Date: Wed, 16 Jan 2019 20:56:42 +0100 Subject: [PATCH 07/14] Updatet readme and added MQTT functionality --- README.md | 44 +++++++++++++++++++++++++++++++++++++++----- read_waveplus.py | 1 - 2 files changed, 39 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 1a4fa92..15a74e7 100644 --- a/README.md +++ b/README.md @@ -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) @@ -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 @@ -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. @@ -182,6 +184,38 @@ 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.py SN SAMPLE-PERIOD mqtt 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, ```SAMPLE_PERIOD``` is ignored and ```SERVER_ADDRESS``` is the ip-address of the mqtt server you want your data posted to. + +The values wil be posted as 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 +``` +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.py 2930002359 60 mqtt 192.168.0.16 +``` + + # Sensor data description | sensor | units | Comments | diff --git a/read_waveplus.py b/read_waveplus.py index f241dde..46bb441 100644 --- a/read_waveplus.py +++ b/read_waveplus.py @@ -211,7 +211,6 @@ def getUnit(self, sensor_index): print (header) elif Mode == 'mqtt': sensors = waveplus.read() - topic = "waveplus/{}".format(SerialNumber) client = mqtt.Client() client.connect(Broker) for i in range(NUMBER_OF_SENSORS): From 10b6caeabd93ad5232259655d6de692580af774e Mon Sep 17 00:00:00 2001 From: Sten Johnsen Date: Wed, 16 Jan 2019 20:58:55 +0100 Subject: [PATCH 08/14] Updated .gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a3062be --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +.vscode/* From 575b2f86f407122dc56a2ab4e97c46b475800eca Mon Sep 17 00:00:00 2001 From: Sten Johnsen Date: Sat, 19 Jan 2019 17:25:49 +0100 Subject: [PATCH 09/14] Added check for missing serial number and continuing with the device list --- read_waveplus.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/read_waveplus.py b/read_waveplus.py index 0f28879..1c40ee5 100644 --- a/read_waveplus.py +++ b/read_waveplus.py @@ -119,10 +119,11 @@ def connect(self): searchCount += 1 for dev in devices: ManuData = dev.getValueText(255) - SN = parseSerialNumber(ManuData) - if (SN == self.SN): - self.MacAddr = dev.addr # exits the while loop on next conditional check - break # exit for loop + if ManuData != None: + SN = 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.") From 78bff5bb09a30abca977681a54f10a2de5bba7df Mon Sep 17 00:00:00 2001 From: Sten Johnsen Date: Mon, 21 Jan 2019 21:24:21 +0100 Subject: [PATCH 10/14] Split into class files and separated the MQTT service --- Sensors.py | 49 +++++++++++++ WavePlus.py | 82 +++++++++++++++++++++ read_waveplus.py | 161 +++--------------------------------------- read_waveplus_mqtt.py | 83 ++++++++++++++++++++++ 4 files changed, 224 insertions(+), 151 deletions(-) create mode 100644 Sensors.py create mode 100644 WavePlus.py create mode 100644 read_waveplus_mqtt.py diff --git a/Sensors.py b/Sensors.py new file mode 100644 index 0000000..6e82117 --- /dev/null +++ b/Sensors.py @@ -0,0 +1,49 @@ + +# =================================== +# 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"] + + 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] diff --git a/WavePlus.py b/WavePlus.py new file mode 100644 index 0000000..815528c --- /dev/null +++ b/WavePlus.py @@ -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 + diff --git a/read_waveplus.py b/read_waveplus.py index 1c40ee5..625ea7d 100644 --- a/read_waveplus.py +++ b/read_waveplus.py @@ -26,12 +26,12 @@ # Module import dependencies # =============================== -from bluepy.btle import UUID, Peripheral, Scanner, DefaultDelegate import sys import time -import struct import tableprint import paho.mqtt.client as mqtt +from Sensors import Sensors +from WavePlus import WavePlus # =============================== # Script guards for correct usage @@ -41,7 +41,6 @@ def print_usage(): print (" where SN is the 10-digit serial number found under the magnetic backplate of your Wave Plus.") print (" where SAMPLE-PERIOD is the time in seconds between reading the current values.") print (" where [pipe > yourfile.txt] is optional and specifies that you want to pipe your results to yourfile.txt.") - print (" or [mqtt serveraddress] is optional and specifies that you want to post the results to an MQTT server on the IP-address serveraddress") if len(sys.argv) < 3: print ("ERROR: Missing input argument SN or SAMPLE-PERIOD.") @@ -67,7 +66,7 @@ def print_usage(): else: Mode = 'terminal' # (default) print to terminal -if Mode!='pipe' and Mode!='terminal' and Mode!='mqtt': +if Mode!='pipe' and Mode!='terminal': print ("ERROR: Invalid piping method.") print_usage() sys.exit(1) @@ -75,130 +74,6 @@ def print_usage(): SerialNumber = int(sys.argv[1]) SamplePeriod = int(sys.argv[2]) -# ==================================== -# Utility functions for WavePlus class -# ==================================== - -def parseSerialNumber(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 - -# =============================== -# Class WavePlus -# =============================== - -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 = 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 - -# =================================== -# Class Sensor and sensor definitions -# =================================== - -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 - -class Sensors(): - def __init__(self): - self.sensor_version = None - self.sensor_data = [None]*NUMBER_OF_SENSORS - self.sensor_units = ["%rH", "Bq/m3", "Bq/m3", "degC", "hPa", "ppm", "ppb"] - - def set(self, rawData): - self.sensor_version = rawData[0] - if (self.sensor_version == 1): - self.sensor_data[SENSOR_IDX_HUMIDITY] = rawData[1]/2.0 - self.sensor_data[SENSOR_IDX_RADON_SHORT_TERM_AVG] = self.conv2radon(rawData[4]) - self.sensor_data[SENSOR_IDX_RADON_LONG_TERM_AVG] = self.conv2radon(rawData[5]) - self.sensor_data[SENSOR_IDX_TEMPERATURE] = rawData[6]/100.0 - self.sensor_data[SENSOR_IDX_REL_ATM_PRESSURE] = rawData[7]/50.0 - self.sensor_data[SENSOR_IDX_CO2_LVL] = rawData[8]*1.0 - self.sensor_data[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] try: #---- Initialize ----# @@ -209,22 +84,6 @@ def getUnit(self, sensor_index): header = ['Humidity', 'Radon ST avg', 'Radon LT avg', 'Temperature', 'Pressure', 'CO2 level', 'VOC level'] - if Mode == 'mqtt': - waveplus.connect() - - # read values - sensors = waveplus.read() - client = mqtt.Client() - client.connect(Broker) - for i in range(NUMBER_OF_SENSORS): - topic = "waveplus/{0}/{1}".format(SerialNumber, header[i].replace(' ','_')) - info = client.publish(topic, sensors.getValue(i), retain=False) - info.wait_for_publish() - time.sleep(0.1) - - client.disconnect() - exit(1) - print ("Device serial number: %s" %(SerialNumber)) if (Mode=='terminal'): @@ -241,13 +100,13 @@ def getUnit(self, sensor_index): sensors = waveplus.read() # extract - humidity = str(sensors.getValue(SENSOR_IDX_HUMIDITY)) + " " + str(sensors.getUnit(SENSOR_IDX_HUMIDITY)) - radon_st_avg = str(sensors.getValue(SENSOR_IDX_RADON_SHORT_TERM_AVG)) + " " + str(sensors.getUnit(SENSOR_IDX_RADON_SHORT_TERM_AVG)) - radon_lt_avg = str(sensors.getValue(SENSOR_IDX_RADON_LONG_TERM_AVG)) + " " + str(sensors.getUnit(SENSOR_IDX_RADON_LONG_TERM_AVG)) - temperature = str(sensors.getValue(SENSOR_IDX_TEMPERATURE)) + " " + str(sensors.getUnit(SENSOR_IDX_TEMPERATURE)) - pressure = str(sensors.getValue(SENSOR_IDX_REL_ATM_PRESSURE)) + " " + str(sensors.getUnit(SENSOR_IDX_REL_ATM_PRESSURE)) - CO2_lvl = str(sensors.getValue(SENSOR_IDX_CO2_LVL)) + " " + str(sensors.getUnit(SENSOR_IDX_CO2_LVL)) - VOC_lvl = str(sensors.getValue(SENSOR_IDX_VOC_LVL)) + " " + str(sensors.getUnit(SENSOR_IDX_VOC_LVL)) + humidity = str(sensors.getValue(sensors.SENSOR_IDX_HUMIDITY)) + " " + str(sensors.getUnit(sensors.SENSOR_IDX_HUMIDITY)) + radon_st_avg = str(sensors.getValue(sensors.SENSOR_IDX_RADON_SHORT_TERM_AVG)) + " " + str(sensors.getUnit(sensors.SENSOR_IDX_RADON_SHORT_TERM_AVG)) + radon_lt_avg = str(sensors.getValue(sensors.SENSOR_IDX_RADON_LONG_TERM_AVG)) + " " + str(sensors.getUnit(sensors.SENSOR_IDX_RADON_LONG_TERM_AVG)) + temperature = str(sensors.getValue(sensors.SENSOR_IDX_TEMPERATURE)) + " " + str(sensors.getUnit(sensors.SENSOR_IDX_TEMPERATURE)) + pressure = str(sensors.getValue(sensors.SENSOR_IDX_REL_ATM_PRESSURE)) + " " + str(sensors.getUnit(sensors.SENSOR_IDX_REL_ATM_PRESSURE)) + CO2_lvl = str(sensors.getValue(sensors.SENSOR_IDX_CO2_LVL)) + " " + str(sensors.getUnit(sensors.SENSOR_IDX_CO2_LVL)) + VOC_lvl = str(sensors.getValue(sensors.SENSOR_IDX_VOC_LVL)) + " " + str(sensors.getUnit(sensors.SENSOR_IDX_VOC_LVL)) # Print data data = [humidity, radon_st_avg, radon_lt_avg, temperature, pressure, CO2_lvl, VOC_lvl] diff --git a/read_waveplus_mqtt.py b/read_waveplus_mqtt.py new file mode 100644 index 0000000..0346f83 --- /dev/null +++ b/read_waveplus_mqtt.py @@ -0,0 +1,83 @@ +# MIT License +# +# Copyright (c) 2018 Airthings AS +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. +# +# https://airthings.com + +# =============================== +# Module import dependencies +# =============================== + +import sys +import time +import socket +import paho.mqtt.client as mqtt +from Sensors import Sensors +from WavePlus import WavePlus + +# =============================== +# Script guards for correct usage +# =============================== +def print_usage(): + print ("USAGE: read_waveplus.py SN SERVERADDRESS") + print (" where SN is the 10-digit serial number found under the magnetic backplate of your Wave Plus") + print (" and SERVERADDRESS specifies the IP-address of the MQTT server you want to post the results to.") + +if len(sys.argv) < 2: + print ("ERROR: Missing input argument SN or SERVERADDRESS.") + print_usage() + sys.exit(1) + +if sys.argv[1].isdigit() is not True or len(sys.argv[1]) != 10: + print ("ERROR: Invalid SN format.") + print_usage() + sys.exit(1) + +SerialNumber = int(sys.argv[1]) +Broker = sys.argv[2] + +try: + socket.inet_aton(Broker) + # legal +except socket.error: + # Not legal + print ("ERROR: Invalid IP address format:", Broker) + print_usage() + sys.exit(1) + +header = ['Humidity', 'Radon ST avg', 'Radon LT avg', 'Temperature', 'Pressure', 'CO2 level', 'VOC level'] + +#---- Initialize ----# +waveplus = WavePlus(SerialNumber) +waveplus.connect() + +# read values +sensors = waveplus.read() +client = mqtt.Client() +client.connect(Broker) +for i in range(sensors.NUMBER_OF_SENSORS): + topic = "waveplus/{0}/{1}".format(SerialNumber, header[i].replace(' ','_')) + info = client.publish(topic, sensors.getValue(i), retain=False) + info.wait_for_publish() + time.sleep(0.1) + +client.disconnect() +waveplus.disconnect() From 4b8428c55e1a68f4064c1bb3b8e290c6b8c8fcbb Mon Sep 17 00:00:00 2001 From: Sten Johnsen Date: Wed, 23 Jan 2019 23:24:00 +0100 Subject: [PATCH 11/14] Removed paho-mqtt import from reader file and improved mqtt posting --- .gitignore | 1 + README.md | 10 ++++++---- Sensors.py | 2 ++ read_waveplus.py | 1 - read_waveplus_mqtt.py | 14 +++++++++++--- 5 files changed, 20 insertions(+), 8 deletions(-) diff --git a/.gitignore b/.gitignore index a3062be..ef7284b 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ .vscode/* +__pycache__/* diff --git a/README.md b/README.md index 0a1b5fc..7685675 100644 --- a/README.md +++ b/README.md @@ -188,12 +188,12 @@ Exit the script using ```Ctrl+C```. 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.py SN SAMPLE-PERIOD mqtt SERVER_ADDRESS +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, ```SAMPLE_PERIOD``` is ignored and ```SERVER_ADDRESS``` is the ip-address of the mqtt server you want your data posted to. +```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 as the following topics: +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 ``` @@ -209,10 +209,12 @@ 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.py 2930002359 60 mqtt 192.168.0.16 +*/5 * * * * sudo python3 /home/pi/waveplus-reader/read_waveplus_mqtt.py 2930002359 192.168.0.16 ``` diff --git a/Sensors.py b/Sensors.py index 6e82117..d4f378f 100644 --- a/Sensors.py +++ b/Sensors.py @@ -20,6 +20,8 @@ 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] diff --git a/read_waveplus.py b/read_waveplus.py index 625ea7d..900644e 100644 --- a/read_waveplus.py +++ b/read_waveplus.py @@ -29,7 +29,6 @@ import sys import time import tableprint -import paho.mqtt.client as mqtt from Sensors import Sensors from WavePlus import WavePlus diff --git a/read_waveplus_mqtt.py b/read_waveplus_mqtt.py index 0346f83..948fac8 100644 --- a/read_waveplus_mqtt.py +++ b/read_waveplus_mqtt.py @@ -29,6 +29,7 @@ import sys import time import socket +import json import paho.mqtt.client as mqtt from Sensors import Sensors from WavePlus import WavePlus @@ -63,21 +64,28 @@ def print_usage(): print_usage() sys.exit(1) -header = ['Humidity', 'Radon ST avg', 'Radon LT avg', 'Temperature', 'Pressure', 'CO2 level', 'VOC level'] - #---- Initialize ----# waveplus = WavePlus(SerialNumber) waveplus.connect() # read values +jsonPackage = dict() sensors = waveplus.read() client = mqtt.Client() client.connect(Broker) for i in range(sensors.NUMBER_OF_SENSORS): - topic = "waveplus/{0}/{1}".format(SerialNumber, header[i].replace(' ','_')) + topic = "waveplus/{0}/{1}".format(SerialNumber, sensors.header[i].replace(' ','_')) info = client.publish(topic, sensors.getValue(i), retain=False) info.wait_for_publish() time.sleep(0.1) + jsonPackage[sensors.header[i].replace(' ','_')]={'value':sensors.getValue(i), 'notation':sensors.sensor_units[i]} + +# Post all values as json object +topic = "waveplus/{0}/".format(SerialNumber) +info = client.publish(topic, json.dumps(jsonPackage), retain=False) +info.wait_for_publish() +time.sleep(0.1) +# End it all client.disconnect() waveplus.disconnect() From f6bb4c0dcd5593d4046cb0204a1755d397333170 Mon Sep 17 00:00:00 2001 From: Paul Vogel Date: Mon, 21 Oct 2019 10:50:41 +0200 Subject: [PATCH 12/14] Change filename in usage message --- read_waveplus_mqtt.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/read_waveplus_mqtt.py b/read_waveplus_mqtt.py index 948fac8..64c3644 100644 --- a/read_waveplus_mqtt.py +++ b/read_waveplus_mqtt.py @@ -38,7 +38,7 @@ # Script guards for correct usage # =============================== def print_usage(): - print ("USAGE: read_waveplus.py SN SERVERADDRESS") + print ("USAGE: read_waveplus_mqtt.py SN SERVERADDRESS") print (" where SN is the 10-digit serial number found under the magnetic backplate of your Wave Plus") print (" and SERVERADDRESS specifies the IP-address of the MQTT server you want to post the results to.") From 4e7d24d0be8eadbc99b735d71df0b3e5ee00dcd0 Mon Sep 17 00:00:00 2001 From: Sten Otto Johnsen Date: Sat, 14 Dec 2019 16:35:27 +0100 Subject: [PATCH 13/14] Added Waveplus mqtt openhab items file --- waveplus.items | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 waveplus.items diff --git a/waveplus.items b/waveplus.items new file mode 100644 index 0000000..0adfe1e --- /dev/null +++ b/waveplus.items @@ -0,0 +1,13 @@ +// Wave Plus +Group WavePlus +Number waveplus_livingroom_humidity "Luftfuktighet i stua" (WavePlus) { mqtt="<[mqtt:waveplus/2930002359/Humidity:state:default]" } +Number waveplus_livingroom_co2level "CO2 i stua" (WavePlus) { mqtt="<[mqtt:waveplus/2930002359/CO2_level:state:default]" } +Number waveplus_livingroom_pressure "Lufttrykk i stua" (WavePlus) { mqtt="<[mqtt:waveplus/2930002359/Pressure:state:default]" } +Number waveplus_livingroom_radon_lta "Radon lang sikt gj.snitt" (WavePlus) { mqtt="<[mqtt:waveplus/2930002359/Radon_LT_avg:state:default]" } +Number waveplus_livingroom_radon_sta "Radon kort sikt gj.snitt" (WavePlus) { mqtt="<[mqtt:waveplus/2930002359/Radon_ST_avg:state:default]" } +Number waveplus_livingroom_temperature "Temperatur i stua" (WavePlus) { mqtt="<[mqtt:waveplus/2930002359/Temperature:state:default]" } +Number waveplus_livingroom_voc_level "TVOC i stua" (WavePlus) { mqtt="<[mqtt:waveplus/2930002359/VOC_level:state:default]" } +String waveplus_temperature "Temperatur [%.2f °C]" (WavePlus) { mqtt="<[mqtt:waveplus/2930002359:state:JSONPATH($.Temperature.value)]" } +String waveplus_radon "Radon [%.0f Bq/m3]" (WavePlus) { mqtt="<[mqtt:waveplus/2930002359:state:JSONPATH($.Radon_ST_avg.value)]" } +String waveplus_total_voc "Total VOC [%.0f ppbC]" (WavePlus) { mqtt="<[mqtt:waveplus/2930002359:state:JSONPATH($.VOC_level.value)]" } + From fb704577fd3c87b70240eae9c39d6b8f56aad0cd Mon Sep 17 00:00:00 2001 From: Sten Otto Johnsen Date: Sat, 14 Dec 2019 16:38:22 +0100 Subject: [PATCH 14/14] Removed device no --- waveplus.items | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/waveplus.items b/waveplus.items index 0adfe1e..371dff5 100644 --- a/waveplus.items +++ b/waveplus.items @@ -1,13 +1,14 @@ // Wave Plus +// Replace with the serial no of your device Group WavePlus -Number waveplus_livingroom_humidity "Luftfuktighet i stua" (WavePlus) { mqtt="<[mqtt:waveplus/2930002359/Humidity:state:default]" } -Number waveplus_livingroom_co2level "CO2 i stua" (WavePlus) { mqtt="<[mqtt:waveplus/2930002359/CO2_level:state:default]" } -Number waveplus_livingroom_pressure "Lufttrykk i stua" (WavePlus) { mqtt="<[mqtt:waveplus/2930002359/Pressure:state:default]" } -Number waveplus_livingroom_radon_lta "Radon lang sikt gj.snitt" (WavePlus) { mqtt="<[mqtt:waveplus/2930002359/Radon_LT_avg:state:default]" } -Number waveplus_livingroom_radon_sta "Radon kort sikt gj.snitt" (WavePlus) { mqtt="<[mqtt:waveplus/2930002359/Radon_ST_avg:state:default]" } -Number waveplus_livingroom_temperature "Temperatur i stua" (WavePlus) { mqtt="<[mqtt:waveplus/2930002359/Temperature:state:default]" } -Number waveplus_livingroom_voc_level "TVOC i stua" (WavePlus) { mqtt="<[mqtt:waveplus/2930002359/VOC_level:state:default]" } -String waveplus_temperature "Temperatur [%.2f °C]" (WavePlus) { mqtt="<[mqtt:waveplus/2930002359:state:JSONPATH($.Temperature.value)]" } -String waveplus_radon "Radon [%.0f Bq/m3]" (WavePlus) { mqtt="<[mqtt:waveplus/2930002359:state:JSONPATH($.Radon_ST_avg.value)]" } -String waveplus_total_voc "Total VOC [%.0f ppbC]" (WavePlus) { mqtt="<[mqtt:waveplus/2930002359:state:JSONPATH($.VOC_level.value)]" } +Number waveplus_livingroom_humidity "Luftfuktighet i stua" (WavePlus) { mqtt="<[mqtt:waveplus//Humidity:state:default]" } +Number waveplus_livingroom_co2level "CO2 i stua" (WavePlus) { mqtt="<[mqtt:waveplus//CO2_level:state:default]" } +Number waveplus_livingroom_pressure "Lufttrykk i stua" (WavePlus) { mqtt="<[mqtt:waveplus//Pressure:state:default]" } +Number waveplus_livingroom_radon_lta "Radon lang sikt gj.snitt" (WavePlus) { mqtt="<[mqtt:waveplus//Radon_LT_avg:state:default]" } +Number waveplus_livingroom_radon_sta "Radon kort sikt gj.snitt" (WavePlus) { mqtt="<[mqtt:waveplus//Radon_ST_avg:state:default]" } +Number waveplus_livingroom_temperature "Temperatur i stua" (WavePlus) { mqtt="<[mqtt:waveplus//Temperature:state:default]" } +Number waveplus_livingroom_voc_level "TVOC i stua" (WavePlus) { mqtt="<[mqtt:waveplus//VOC_level:state:default]" } +String waveplus_temperature "Temperatur [%.2f °C]" (WavePlus) { mqtt="<[mqtt:waveplus/:state:JSONPATH($.Temperature.value)]" } +String waveplus_radon "Radon [%.0f Bq/m3]" (WavePlus) { mqtt="<[mqtt:waveplus/:state:JSONPATH($.Radon_ST_avg.value)]" } +String waveplus_total_voc "Total VOC [%.0f ppbC]" (WavePlus) { mqtt="<[mqtt:waveplus/:state:JSONPATH($.VOC_level.value)]" }