Seems a little strange. I think 0x002a
resets to all 0’s after responding, and the read just gets the current, reset value.
Also:
$ gatttool -i hci0 -b 94:3C:C6:DE:31:7A --mtu=507 --char-write-req --handle=0x002a --value=0x50 --listen
Characteristic value was written successfully
^C
$ gatttool -i hci0 -b 94:3C:C6:DE:31:7A --mtu=507 --char-write-req --handle=0x002a --value=50 --listen
Characteristic value was written successfully
Notification handle = 0x002c value: 50 0a 30 00 3f 00 00 00 04 00 08 00
^C
Seems like writing 0x50
doesn’t work, but writing 50
does, as long as --listen
is used
The other problem is that non-interactive gatttool does not set MTU:
$ gatttool -i hci0 -b 94:3C:C6:DE:31:7A --mtu=507 --char-write-req --handle=0x002a --value=41 --listen
Characteristic value was written successfully
Notification handle = 0x002f value: 41 04 01 fa 19 00 30 00 30 00 26 00 2e 00 2e 00 2d 00 23 00
Notification handle = 0x002f value: 41 04 02 fa 26 00 26 00 10 00 1a 00 13 00 20 00 2b 00 31 00
Notification handle = 0x002f value: 41 04 03 fa 31 00 3b 00 38 00 3e 00 30 00 44 00 31 00 2b 00
Notification handle = 0x002f value: 41 04 04 79 27 00 2b 00 38 00 3b 00 41 00 48 00 44 00 35 00
^C
The first three responses are each supposed to be 507 bytes total transmission (last one = 249), and this only returns 23 bytes (the default, minimum value).
For example, interactive:
$ gatttool -i hci0 -b 94:3C:C6:DE:31:7A -I
[94:3C:C6:DE:31:7A][LE]> connect
Attempting to connect to 94:3C:C6:DE:31:7A
Connection successful
[94:3C:C6:DE:31:7A][LE]> mtu 507
MTU was exchanged successfully: 507
[94:3C:C6:DE:31:7A][LE]> char-write-req 0x002a 41
Characteristic value was written successfully
Notification handle = 0x002f value: 41 04 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 35 00 2e 00 38 00 2a 00 35 00 21 00 21 00 2a 00 26 00 24 00 3b 00 31 00 30 00 3e 00 30 00 3a 00 24 00 37 00 3b 00 37 00 26 00 41 00 30 00 1c 00 34 00 21 00 23 00 34 00 26 00 19 00 23 00 24 00 1d 00 19 00 16 00 2a 00 2e 00 34 00 3e 00 41 00 38 00 30 00 21 00 3a 00 2d 00 2a 00 2b 00 21 00 24 00 24 00 30 00 24 00 19 00 1c 00 16 00 1a 00 1a 00 21 00 27 00 2b 00 23 00 2b 00 27 00 2b 00 2a 00 3b 00 2d 00 2a 00 12 00 17 00 27 00 26 00 24 00 1d 00 27 00 20 00 1a 00 1d 00 20 00 23 00 24 00 31 00 20 00 2a 00 2a 00 31 00 34 00 3a 00 2d 00 42 00 34 00 35 00 34 00 3b 00 30 00 37 00 2e 00 27 00 26 00 17 00 2a 00 2b 00 20 00 19 00 26 00 2d 00 21 00 27 00 2e 00 38 00 37 00 38 00 30 00 27 00 21 00 31 00 24 00 26 00 19 00 2a 00 24 00 1a 00 1c 00 20 00 13 00 2a 00
Notification handle = 0x002f value: 41 04 02 fa 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 2a 00 26 00 1d 00 34 00 2e 00 20 00 26 00 2e 00 21 00 21 00 1c 00 2d 00 4e 00 41 00 45 00 38 00 31 00 3f 00 3a 00 38 00 35 00 23 00 26 00 27 00 24 00 16 00 2d 00 30 00 1c 00 2a 00 10 00 27 00 20 00 20 00 1d 00 26 00 2d 00 31 00 37 00 3e 00 31 00 2e 00 3a 00 2d 00 49 00 4b 00 41 00 37 00 2d 00 3f 00 3b 00 30 00 26 00 31 00 27 00 41 00 30 00 3b 00 41 00 5c 00 5a 00 45 00 44 00 4b 00 49 00 4c 00 45 00 30 00 48 00 45 00 41 00 38 00 2b 00 3a 00 31 00 37 00 24 00 27 00 26 00 17 00 31 00 34 00 23 00 2b 00 45 00 34 00 31 00 35 00 31 00 35 00 30 00 3e 00 42 00 2e 00 3a 00 2e 00 20 00 2a 00 23 00 1d 00 13 00 2e 00 16 00 1d 00 26 00 2a 00 27 00 31 00 37 00 37 00 3f 00 31 00 50 00 37 00 37 00 34 00 26 00 41 00 20 00 2b 00 23 00 2d 00 27 00 2d 00 34 00 2a 00 2b 00 35 00 2a 00 3b 00 42 00 49 00 45 00 50 00 50 00 3e 00 42 00 3a 00 4c 00 52 00 3f 00 41 00 42 00 3a 00 37 00 48 00 31 00 3e 00 30 00 3e 00 3e 00 3f 00 3a 00 38 00 2e 00 45 00 60 00 45 00 63 00 45 00 59 00 4c 00 38 00 52 00 42 00 3b 00 3a 00 35 00 3e 00 2a 00 30 00 26 00 24 00 2e 00 26 00 16 00 20 00 20 00 16 00 24 00 2d 00 35 00 34 00 23 00
Notification handle = 0x002f value: 41 04 03 fa 31 00 3b 00 38 00 3e 00 30 00 44 00 31 00 2b 00 2a 00 37 00 1c 00 31 00 20 00 1d 00 30 00 2a 00 27 00 3a 00 2a 00 30 00 48 00 48 00 44 00 37 00 2d 00 2e 00 35 00 4b 00 3b 00 31 00 35 00 3e 00 1d 00 27 00 2e 00 2d 00 2d 00 2a 00 30 00 20 00 20 00 37 00 35 00 49 00 56 00 4c 00 3a 00 2b 00 3e 00 3a 00 3e 00 4c 00 37 00 30 00 38 00 2a 00 30 00 2d 00 2e 00 1d 00 2b 00 3a 00 3b 00 27 00 49 00 50 00 3b 00 41 00 42 00 5d 00 4c 00 3b 00 3a 00 23 00 30 00 31 00 3a 00 49 00 26 00 2a 00 26 00 2d 00 20 00 20 00 20 00 26 00 1d 00 24 00 1a 00 0f 00 2d 00 23 00 24 00 23 00 26 00 1d 00 34 00 35 00 31 00 34 00 23 00 2a 00 2a 00 26 00 27 00 1d 00 26 00 35 00 30 00 2e 00 13 00 35 00 34 00 24 00 2a 00 26 00 3a 00 34 00 3e 00 3b 00 2d 00 3e 00 44 00 3a 00 2b 00 3b 00 31 00 23 00 35 00 2e 00 30 00 2d 00 30 00 26 00 45 00 3e 00 3f 00 38 00 38 00 37 00 30 00 24 00 2b 00 42 00 41 00 3f 00 3f 00 3b 00 30 00 30 00 3f 00 30 00 35 00 13 00 1a 00 20 00 27 00 3b 00 2d 00 35 00 38 00 41 00 38 00 42 00 3f 00 31 00 3e 00 2e 00 30 00 4e 00 3b 00 38 00 31 00 3a 00 31 00 38 00 2e 00 2b 00 23 00 37 00 2a 00 19 00 2b 00 31 00 2a 00 31 00 52 00 66 00 52 00 5c 00 49 00 4c 00 42 00 52 00 45 00 37 00 37 00 3f 00 4b 00 23 00 3b 00 30 00 27 00 2a 00 2a 00 2d 00 37 00 2a 00 20 00 2a 00 31 00 3b 00 31 00 30 00 31 00 4c 00 3f 00 5f 00 41 00 3b 00 31 00 42 00 3e 00 3a 00 2d 00 24 00 34 00 2d 00 2e 00 21 00 31 00 2e 00 26 00 2d 00 26 00 56 00 3e 00 41 00 3b 00 4b 00 56 00 45 00 4c 00 3f 00 34 00 45 00 3e 00 42 00 42 00 30 00
Notification handle = 0x002f value: 41 04 04 79 27 00 2b 00 38 00 3b 00 41 00 48 00 44 00 35 00 38 00 4e 00 3b 00 3e 00 3e 00 4b 00 71 00 42 00 56 00 52 00 4c 00 37 00 31 00 34 00 30 00 20 00 19 00 24 00 1d 00 2a 00 19 00 24 00 2d 00 1a 00 31 00 2b 00 37 00 34 00 3a 00 41 00 4c 00 38 00 3f 00 3b 00 2d 00 37 00 3f 00 35 00 27 00 2a 00 23 00 2a 00 23 00 2a 00 37 00 3f 00 38 00 4e 00 3e 00 44 00 48 00 49 00 38 00 41 00 38 00 45 00 50 00 41 00 38 00 34 00 3a 00 3f 00 30 00 44 00 21 00 37 00 2d 00 21 00 19 00 3b 00 30 00 2d 00 4e 00 4b 00 59 00 55 00 44 00 3a 00 52 00 50 00 66 00 5d 00 31 00 4c 00 42 00 42 00 2e 00 30 00 2d 00 21 00 24 00 31 00 2a 00 24 00 37 00 24 00 2e 00 42 00 49 00 5c 00 64 00 5a 00 50 00 42 00 48 00 48 00 38 00 41 00 41 00 31 00 52 00 2d 00 31 00
[94:3C:C6:DE:31:7A][LE]> disconnect
(gatttool:2066): GLib-WARNING **: 16:04:05.144: Invalid file descriptor.
[94:3C:C6:DE:31:7A][LE]> exit
Definitely not getting all of the bytes using non-inteactive. Also, trying to read that value (0x002f
) instad of --listen
with non-interactive also fails:
$ gatttool -i hci0 -b 94:3C:C6:DE:31:7A --mtu=507 --char-write-req --handle=0x002a --value=41 --char-read --handle 0x002f
Characteristic value/descriptor: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
I am using a pretty hacked together python3 script that uses pygatt, with a gatttool backend. It works fine, other than its ugliness and lack of error trapping:
#! /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 = 'XX:XX:XX:XX:XX:XX'
## influx configuration - edit these:
ifuser = "username"
ifpass = "password"
ifdb = "dbname"
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
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 subscribe 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)}")
Pygatt required me to subscribe to the response to get the correct data. I don’t know how that relates to using non-interactive gatttool, but just reading from the handle would return 0’s unless it was a response to the request.