Trying to get data from bluetooth into Hubitat: Ecosense RD200 Radon sensor

So, I'm getting started on bringing in radon data from an Ecosense RD200.

My current plan is to get a-hold of a Raspberry Pi Zero 2 W (when they're finally in stock), and pull the data with gatttool, maybe a few times a day, then transfer that data to Hubitat and plot with Hubigraphs. I might need to use something like pygatt instead, since expect might have a problem when it starts responding with more data (up to about 29000 characters maybe).

So far, I have an expect script I can run from a Linux PC, which returns the data I'm interested in receiving via gatttool:

#!/usr/bin/expect -f
log_user 0
## That removes the output from STDOUT, except for puts

set device "94:3C:C6:DE:31:7A"

spawn gatttool -b $device -I
expect "> "
sleep 1
send -- "connect\r"
expect "Connection successful"
sleep 1
send -- "mtu 507\r"
expect "MTU was exchanged successfully"
sleep 1

#match_max 10000
## Might need to increase match to receive all the response once it stores more data. Maybe this won't work once a year of data is stored.

send -- "char-write-cmd 0x002a 41\r"

set message1 ""

expect {
    -re "Notification handle = 0x002f value: (.+\n)" {
        set message1 ${message1}$expect_out(1,string)
        exp_continue
    }
    "^\[.+\]\[LE\]> $" {
        send -- "disconnect\r"
    }
}

puts "$message1"

So here's what I get:

$ ./test-gatttool.sh 
41 02 01 fa 19 00 30 00 30 00 26 00 2e 00 2e 00 2d 00 23 00 20 00 1d 00 2a 00 17 00 17 00 12 00 1d 00 1d 00 2b 00 34 00 2e 00 37 00 27 00 21 00 30 00 37 00 3a 00 2d 00 2b 00 1a 00 30 00 16 00 10 00 13 00 12 00 0f 00 17 00 16 00 24 00 19 00 16 00 26 00 1c 00 20 00 34 00 3f 00 26 00 35 00 3f 00 30 00 31 00 38 00 3f 00 44 00 38 00 26 00 19 00 24 00 2a 00 2e 00 21 00 20 00 1c 00 24 00 1c 00 26 00 38 00 2e 00 48 00 34 00 35 00 37 00 38 00 44 00 44 00 31 00 41 00 44 00 3b 00 27 00 2b 00 27 00 1d 00 17 00 2d 00 2b 00 27 00 26 00 2e 00 3b 00 44 00 44 00 3e 00 45 00 4e 00 37 00 34 00 44 00 2e 00 24 00 30 00 24 00 2b 00 2d 00 2a 00 30 00 1c 00 30 00 23 00 23 00 1c 00 23 00 31 00 20 00 3a 00 3e 00 3f 00 38 00 45 00 55 00 45 00 3a 00 42 00 2a 00 37 00 42 00 
41 02 02 42 26 00 26 00 10 00 1a 00 13 00 20 00 2b 00 31 00 3e 00 34 00 19 00 35 00 35 00 2e 00 30 00 37 00 24 00 2a 00 30 00 21 00 26 00 23 00 1c 00 19 00 1d 00 17 00 21 00 20 00 1d 00 2a 00 3b 00 24 00 20 00 26 00 24 00 30 00 2e 00 26 00 38 00 30 00 31 00 2a 00 2d 00 2b 00 2b 00 2b 00 26 00 2a 00 1d 00 24 00 1a 00 23 00 2e 00 30 00 45 00 41 00 2e 00 2b 00 34 00 38 00 3e 00 45 00 2d 00 42 00 35 00 2d 00 

The first line is the first set of 250 Radon measurements as 2-byte int (in Bq/m^3), following x41 x02 x01 xFA.

The first four bytes will be (I think): x41; x??; x01-x24; and x01-xFA. The first byte returns the value of the request (send -- "char-write-cmd 0x002a 41\r"). I don't know about the second, the third byte is the number of the measured data response, starting from x01 (oldest) to newest, then the fourth byte contains the number of measurements in that response.

The second line contains up to an additional 250 measurements. In this case, it contains x42 (66) measurements. Every time x41 is sent to 0x002a I will get up to a year of measurements in responses of up to 250 (one per hour saved for up to a year).

I know @iharyadi has done some bluetooth integration, but I'm wondering what things I'll need to watch for. Has anyone else used a Raspberry Pi for integrating bluetooth? I'm also considering using an old Android phone instead of a Raspberry Pi, but I don't know if I can manage that without root.

I'm really hoping to find some code for requesting data from a Pi doing something like this a few times a day that I can hack, since I'm not an actual developer.

Thanks for any help/pointers!

The simplest method of getting data into Hubitat, without having to write any Groovy code, is to simply use Hubitat’s built-in Maker API app. You can simply manually create a virtual device, and then update its value from your RPi script.

1 Like

Thanks for the hint!

I haven't done anything with Maker API, so I'll take a look at it. Since I'll definitely be writing something on a Pi it should be easy to add in an update from there. Reduces the number of syntax errors I'll be making...

1 Like

I recently bought Airthings Wave with co2 voc and radon. Also using raspberry pi and makerAPI to integrate with Hubitat.
It has been great. The device seems cheaper than yours.

1 Like

I've looked at the Maker API docs, and tried messing with a couple virtual devices as well as a device I wrote for storing COVID numbers, and I can't quite figure out how to update an attribute.

My COVID device contains a list of numbers and a list of dates, which I use to plot the history of COVID values for my state, pulled from the state's server. I couldn't figure out how to use MakerAPI to update the lists.

So, for example, how would use MakerAPI to change the ValueList in this device:

id	"379"
name	"MD-COVID"
label	"MD-COVID"
type	"get-MD-COVID"
attributes	
0	
name	"Date"
currentValue	"Tue Jun 14 10:00:00 EDT 2022"
dataType	"DATE"
1	
name	"ValueList"
currentValue	"[32.74, 34.31, 35.38, 36.71, 38, 37, 36.94, 37.11, 37.57, 37.55, 37.2, 36.4, 36.44, 36.41, 36.24, 33.07, 32.58, 29.93, 29.49, 29.87, 29.02, 28.14, 29.75, 28.77, 29.36, 27.15, 26.01, 25.24, 24.73, 23.58]"
dataType	"STRING"
2	
name	"DateList"
currentValue	"[Mon May 16 10:00:00 EDT 2022, Tue May 17 10:00:00 EDT 2022, Wed May 18 10:00:00 EDT 2022, Thu May 19 10:00:00 EDT 2022, Fri May 20 10:00:00 EDT 2022, Sat May 21 10:00:00 EDT 2022, Sun May 22 10:00:00 EDT 2022, Mon May 23 10:00:00 EDT 2022, Tue May 24 10:00:00 EDT 2022, Wed May 25 10:00:00 EDT 2022, Thu May 26 10:00:00 EDT 2022, Fri May 27 10:00:00 EDT 2022, Sat May 28 10:00:00 EDT 2022, Sun May 29 10:00:00 EDT 2022, Mon May 30 10:00:00 EDT 2022, Tue May 31 10:00:00 EDT 2022, Wed Jun 01 10:00:00 EDT 2022, Thu Jun 02 10:00:00 EDT 2022, Fri Jun 03 10:00:00 EDT 2022, Sat Jun 04 10:00:00 EDT 2022, Sun Jun 05 10:00:00 EDT 2022, Mon Jun 06 10:00:00 EDT 2022, Tue Jun 07 10:00:00 EDT 2022, Wed Jun 08 10:00:00 EDT 2022, Thu Jun 09 10:00:00 EDT 2022, Fri Jun 10 10:00:00 EDT 2022, Sat Jun 11 10:00:00 EDT 2022, Sun Jun 12 10:00:00 EDT 2022, Mon Jun 13 10:00:00 EDT 2022, Tue Jun 14 10:00:00 EDT 2022]"
dataType	"DATE"
3	
name	"Statewide"
currentValue	23.58
dataType	"NUMBER"
capabilities	
0	"Polling"
1	"Sensor"
commands	
0	"poll"

If I can update the ValueList and DateList attributes for this device, I can certainly manage the same for another device containing Radon values. I just don't see how that is done with MakerAPI.

I might instead start graphing with Grafana instead of Hubigraphs. I'd still need to learn about how to manage showing that from a Hubitat dashboard.

Can you post step by step directions to do this for a total newbie?
Assuming since you are using Raspberry Pi, you are collecting data direct from the Wave, and not picking up the data from Airthings API?

create a device with the following driver:

/**
heavily modified from https://github.com/LostJen/Hubitat/blob/master/Drivers/Virtual%20Air%20Quality%20Monitor.groovy
 */
metadata {
	definition (name: "AirThings Wave Plus", namespace: "chen", author: "Chen") {
        capability "Sensor"
        capability "Battery"
        capability "TemperatureMeasurement"
        capability "RelativeHumidityMeasurement"
        capability "PressureMeasurement"
        capability "CarbonDioxideMeasurement"
        capability "IlluminanceMeasurement"
        capability "PresenceSensor"
        
        attribute "VOC", "number"
        attribute "RadonShortTermAvg", "number"
        attribute "RadonLongTermAvg", "number"
        attribute "absoluteHumidity", "number"
        command "setValues", [[type:"STRING"],[type:"STRING"],[type:"STRING"],[type:"STRING"],[type:"STRING"],[type:"STRING"],[type:"STRING"],[type:"STRING"],[type:"STRING"]]
        command "errorNotFound", []
    }
	preferences {
        input ("presenceInterval", "enum", title: "Device will be Not Present if no activiies wihin x minutes", 
			   options: ["10", "20", "30", "40"], defaultValue: "30")
	}
}

def errorNotFound()
{
    log.error("WavePlus not found via BlueTooth")
}

def setValues(String temperature, String humidity, String pressure, String co2, String voc, String radonST, String radonLT, String battery, String illuminance)
{
    temp = temperature.toDouble()
    if(getTemperatureScale() == "F")
        temp = celsiusToFahrenheit(temp).toDouble()
    
    sendEvent2(change: 0.1, name: "temperature", value: temp.round(1), unit: "°"+getTemperatureScale())
    sendEvent2(change: 0.5, name: "humidity", value: humidity, unit: "%")
    sendEvent2(change: 0.1, name: "pressure", value: (pressure.toDouble()/10).round(1), unit: "kPa")
    sendEvent2(change: 0.5, name: "carbonDioxide", value: co2.toDouble().round(), unit: "ppm")
    sendEvent2(change: 1.0, name: "VOC", value: voc.toDouble().round(), unit: "ppb")
    sendEvent2(change: 0.01, name: "RadonShortTermAvg", value: radonST, unit: "pCi/L")
    sendEvent2(change: 0.01, name: "RadonLongTermAvg",  value: radonLT, unit: "pCi/L")
    sendEvent2(change: 1.0, name: "battery", value: battery, unit: "%")
    sendEvent2(change: 5.0, name: "illuminance", value: illuminance, unit: "lux" )
    sendEventAbsHumidity(temp, humidity.toDouble())
    sendEvent(name: "presence", value: "present")
}

def sendEventAbsHumidity(BigDecimal deviceTempInCelsius, BigDecimal relativeHumidity)
{
    BigDecimal numerator = (6.112 * Math.exp((17.67 * deviceTempInCelsius) / (deviceTempInCelsius + 243.5)) * relativeHumidity * 2.1674) 
    BigDecimal denominator = deviceTempInCelsius + 273.15 
    BigDecimal absHumidity = numerator / denominator
    String cubeChar = String.valueOf((char)(179))
    absHumidity = absHumidity.setScale(1, BigDecimal.ROUND_HALF_UP)
    sendEvent2(change: 0.1, name: "absoluteHumidity", value: absHumidity, unit: "g/m${cubeChar}")
}

def sendEvent2(Map data)
{
    Value0 = device.currentValue(data.name)?.toDouble()
    Value1 = data.value.toDouble()
    if(Value0 == null || Math.abs(Value1 - Value0) > data.change)
    {
        sendEvent(name: data.name, value: data.value, unit: data.unit)
        pauseExecution(15)//avoid race condition in influx logger
    }
}

def resetPresence()
{
 	unschedule()  
    runIn(60*presenceInterval.toInteger(), 'sendEvent', [data: [name:"presence", value: "not present"]])
}```
  1. get the maker API appid, token, deviceid etc.

3.download the airthings APIs
https://raw.githubusercontent.com/custom-components/sensor.airthings_wave/master/custom_components/airthings_wave/airthings.py

write below python3 code:

import time
import requests
# The period between two measurements in seconds (Default: 300)
SamplePeriod = 300
MAC = 'xxxxxxxxxxxxxxxx'

# The hostname or IP address of the MQTT broker Hubitat hub to connect
makerAPIHostname = "192.168.1.xxx"
makerAPIAppID    = "xxx"
makerAPIToken    = "xxxxxxxxxxxxxxxxxxxxxxxxxxx"
makerAPIDeviceID = "xxx"

from airthings import AirthingsWaveDetect
scan_interval = 120
airthingsdetect = AirthingsWaveDetect(scan_interval, MAC)
V_MAX = 3.0
V_MIN = 2.0

    #---- Initialize ----#
URL = "http://{}/apps/api/{}/devices/{}/{}?access_token={}".format(makerAPIHostname, makerAPIAppID, makerAPIDeviceID, '{}/{}', makerAPIToken)
devices_sensors = airthingsdetect.get_sensors()
while True:
    try:
        data = airthingsdetect.get_sensor_data()[MAC]
    except:
        print( 'Read Error' )
        pass
    else:
        sensorData = "{},{},{},{},{},{},{},{},{}".format(
                data['temperature'],
                data['humidity'],
                data['rel_atm_pressure'],
                data['co2'],
                data['voc'],
                round(data['radon_1day_avg']/37.,2),
                round(data['radon_longterm_avg']/37.,2),
                max(0, min(100, round( (data['battery']-V_MIN)/(V_MAX-V_MIN)*100))),
                round(data['illuminance'])
                )
        #print( sensorData )
        try:
            request = URL.format('setValues', sensorData)
            requests.get(request)
        except:
            pass
    finally:
        time.sleep(SamplePeriod)

set it to run when the pi boots up
add something like
/usr/bin/python3 /root/readwaveplus.py > /dev/null &
to /etc/rc.local

this device handler will provide co2, voc, radon, humidity, temperature, absolute humidity, air pressure, illuminance, and battery level.

Thanks Chen, when running It I have this error message :

File "readwaveplus.py", line 377, in
from airthings import AirthingsWaveDetect
ModuleNotFoundError: No module named 'airthings'

Any idea ?

For those trying to find the MAC address : How to use Airthings Wave Radon with your Raspberry Pi

did you do step 3?

Hi Chen, thanks a lot for your quick answer. I did step 3, the first part of the python code is the APIs code then I did add your code under It.

When I run It I get :

So It is able to pull the value from Airthings.
Any idea ?

I did sudo pip3 install airthings.

I have a new error message now :

@Chen555
Still trying, new error code :


Any idea ?