## RaydonEye uses BLE LED Button Service (LBS) UUID 00001523-1212-EFDE-1523-785FEABCD123 ## [!] ble only supports a single connection, if RadonEye App is running this will fail import BLE_GATT from gi.repository import GLib import subprocess import signal import time import struct import sqlite3 BLUETOOTH_LBS_SERVICE_CHARACTERISTIC = '00001523-1212-EFDE-1523-785FEABCD123' BLUETOOTH_LBS_NOTIFY_READ_CHARACTERISTIC = '00001525-1212-EFDE-1523-785FEABCD123' BLUETOOTH_LBS_WRITE_CHARACTERISTIC = '00001524-1212-EFDE-1523-785FEABCD123' def connect(radonEyeMac = 'CF:CD:27:79:55:6B', dev = None): """ Use Central Role BlueZ D-Bus API (BLE-GATT) to connect to peripheral (RadonEye) """ print("connecting to radon sensor: ", radonEyeMac) while True: try: dev = BLE_GATT.Central(radonEyeMac) dev.connect() break except: print("device not found, retrying in a second...\n") time.sleep(1) ble_ctl(radonEyeMac) continue return dev # [!] To get this to work bluetoothctl needs to connect to the device before using BLE_GATT.connect def ble_ctl(addr:str, retry = 50): """ use bluetoothctl interactively: scan on, ... wait ..., scan off, connect CF:CD:27:79:55:6B, exit subprocess has a relevant bug in line-buffering, see issue 21471 """ ctl = subprocess.Popen(['bluetoothctl'], stdin = subprocess.PIPE) def writeHelper(proc, str, delay = 1): proc.stdin.write(str.encode('utf-8')) proc.stdin.flush() time.sleep(delay) # send bluetoothctl commands interactively writeHelper(ctl, "scan on\n", 4) writeHelper(ctl, "scan off\n") writeHelper(ctl, "connect " + addr + "\n", 3) writeHelper(ctl, "exit\n") # wait for child subprocess to terminate, or kill after five seconds while True: if None != ctl.poll(): break time.sleep(.1) if 0 >= retry--: print("bluetoothctl did not close in time, terminating...") ctl.terminate() # convert RD200 LBS response to integer value def radonEye_hundredths(res): """ radonEye can only display 0.20 - 99.00 distinct values, internally they store each value as a IEEE754 value (4-byte big-endian float) -- 32 bits is a bit overkill when 12 would suffice, but we are not going to save space, instead, to simplify communication, we are going to send not picocuries per liter, and instead hundredths of picocurries per liter since this is the best resolution the radonEye can support and conversion to picocuries is divide by one hundred """ return round(100 * struct.unpack('