I’m actually planning to either use python or more likely try to hack the one in esphome to work with the new communications protocol
I managed to set something up in python running on my new Raspberry Pi 4B.
Right now it’s working, saving data to InfluxDB… Made progress over the weekend. It also outputs CSV files with the data.
It’s very hacky, but I can upload some code later today. Maybe I’ll comment it a bit more before I forget what everything does!
EDIT:
Here’s the what I managed in python to pull the data. I should probably improve this, but for now it’s working when I run it every 15 minutes on the RPi 4B. I know it’s ugly but it works OK for now. Some better error handling would help, and this does two things when I should split it up to do one thing:
#! /usr/bin/env python3
import pygatt
import time
from struct import unpack
import csv
from datetime import timedelta
from datetime import datetime
import os
from influxdb import InfluxDBClient
## This changes directory to the location of the script when called by crontab (on external storage device):
abspath = os.path.abspath(__file__)
dirname = os.path.dirname(abspath)
os.chdir(dirname)
## Change this to RD200 MAC:
radon_mac = '94:3C:C6:DE:31:7A'
## influx configuration - edit these:
ifuser = "username"
ifpass = "password"
ifdb = "databaseID"
ifhost = "127.0.0.1"
ifport = 8086
measurement_name = "Radon"
## Use GATTTool backend on RPi:
adapter = pygatt.GATTToolBackend()
complete = False
history = []
## Get current time:
now = datetime.now()
## Round to the nearest 5 minutes (not required):
now = now - timedelta(minutes=now.minute % 5,
seconds=now.second,
microseconds=now.microsecond)
## This manages history data and writes to CSV
## Eventually I want to clear/recreate InfluxDB table with new info
## handle_data gets the history values using 0x41:
def handle_data(handle, value):
# print("Received data: %s" % value.hex())
## Using global lets me tell main program that this is finished
global complete
global history
req_no = value.pop(0)
tot_resps = value.pop(0)
this_resp = value.pop(0)
num_values = value.pop(0)
## After removing the first 4 bytes, the rest contain history in Bq/m^3:
Bq_m3 = unpack('<'+'H'*(len(value)//2),value)
# print(len(Bq_m3))
Bq_m3 = list(Bq_m3)
pCi_L = [x/37 for x in Bq_m3]
resp_len = len(Bq_m3)
if num_values != resp_len:
print("Number of values didn't match!")
# print(f"Requested {req_no}, got {tot_resps} replies, this is reply {this_resp} containing {num_values} values.")
# print(f"Values: {value.hex()}")
# print(f"First 4 values out of {resp_len} total in this response: {Bq_m3[0:4]} Bq/m^3")
# print(f"First 4 values out of {resp_len} total in this response: {pCi_L[0:4]} pCi/L")
## This adds the latest values to history:
history.extend(pCi_L)
## If this response is the last response, set complete to True:
if this_resp == tot_resps:
#print("Completed")
complete = True
## handle_current gets the current data using 0x40:
def handle_current(handle, value):
currentTime = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
## take a timestamp for this measurement
time = datetime.utcnow()
#print("Received data: %s" % value.hex())
## global complete lets the main program know this is finished:
global complete
req_no = value.pop(0) # first byte is 0x40 from write
extra_no = value.pop() # don't know what last byte is for... removed it
Bq_m3 = unpack('<'+'H'*(len(value)//2),value)
# print(len(Bq_m3))
resp_len = len(Bq_m3)
# print(f"Values: {value.hex()}")
# print(Bq_m3)
latest = Bq_m3[16]/37 # the latest value is stored at 16
peak = Bq_m3[25]/37 # the peak value is stored at 25
# print(currentTime)
print(f"Latest value = {latest:.2f} pCi/L at {currentTime}")
print(f"Peak value = {peak:.2f} pCi/L")
## Append to CSV file using rounded time (now):
with open("RadonCurrentValues.csv", "a") as csvfile:
writer = csv.writer(csvfile)
writer.writerow([now,latest,peak])
## format the data as a single measurement for influx
body = [
{
"measurement": measurement_name,
"time": time,
"fields": {
"radon": latest,
"peak": peak
}
}
]
## connect to influx
ifclient = InfluxDBClient(ifhost,ifport,ifuser,ifpass,ifdb)
## write the measurement
ifclient.write_points(body)
complete = True
## This is the main program:
try:
adapter.start()
try:
device = adapter.connect(radon_mac,timeout=20)
except:
print("Couldn't connect, retrying...")
device = adapter.connect(radon_mac,timeout=20)
## Set mtu to get the full history responses:
try:
device.exchange_mtu(507)
except:
print("Couldn't set MTU, retrying...")
device.exchange_mtu(507)
print("Subscribing to history response...")
## For some reason there is no subscribe_handle, even though char_write_handle exists... weird.
#device.subscribe_handle(0x002f,
# callback=handle_data,
# indication=False,
# wait_for_response=False)
## Subscribe to UUID:
device.subscribe("00001526-0000-1000-8000-00805f9b34fb",
callback=handle_data,
indication=False,
wait_for_response=False)
## Request history data by sending 0x41:
print("Requesting data...")
device.char_write_handle(0x002a,
bytearray([0x41]),
wait_for_response=False)
## Could also write to UUID instead of handle:
#device.char_write("00001524-0000-1000-8000-00805f9b34fb",
# bytearray([0x41]),
# wait_for_response=False)
while not (complete) :
time.sleep(1) # wait one second until handle_data sets complete to True
# print(f"Complete = {complete}")
device.unsubscribe("00001526-0000-1000-8000-00805f9b34fb")
## This doesn't store dates for history, just one per hour
## Cound history and subtract 1 hour each (assuming device was kept running continuously):
historyCount = len(history)
difference = timedelta(hours=1)
times = [now - x*difference for x in list(range(historyCount-1, -1, -1))]
## Write history values to CSV file or DB here (new file each time):
with open("RadonHistory.csv", "w") as csvfile:
writer = csv.writer(csvfile)
writer.writerow(["Timestamp","Radon (pCi/L)"])
for value in range(len(history)):
writer.writerow([times[value], history[value]])
## Now, set complete to False again to get current values:
complete = False
## Make sure file exists, otherwise write header:
if not os.path.isfile("RadonCurrentValues.csv"):
with open("RadonCurrentValues.csv", "w") as csvfile:
writer = csv.writer(csvfile)
writer.writerow(["Timestamp","Radon (pCi/L)","Peak value (pCi/L)"])
print("Subscribing to current response...")
## Subscribe to 1525 for current data:
device.subscribe("00001525-0000-1000-8000-00805f9b34fb",
callback=handle_current,
indication=False,
wait_for_response=False)
print("Requesting data...")
## Request current data by sending 0x40:
device.char_write_handle(0x002a,
bytearray([0x40]),
wait_for_response=False)
while not (complete) :
time.sleep(1) # wait one second until handle_data sets complete to True
# print(f"Complete = {complete}")
#device.unsubscribe("00001525-0000-1000-8000-00805f9b34fb")
## Why is it not subscribed? Leave it out...
## For some reason that caused an error
finally:
adapter.stop()
# print(f"Historical data (Bq/m^3): {history}")
print(f"Total hours returned for history: {len(history)}")