RadonEye BLE Interface

Work in progress for RadonEye200 Bluetooth LE communication.

##########################
TL:DR:
Send “0x50” to Bluetooth LE GATT characteristic UUID = 00001524-1212-efde-1523-785feabcd123
Receive a hex value like 50 10 AE 47 61 3F 14 AE 87 3F 00 00 00 00 02 00 05 00 00 00
The 3rd to 6th bytes of this are the IEEE 754 representation of the floating point value for the current Radon level (in big-endian format)
##########################

I own a RadonEye200 Bluetooth LE Radon monitor.
It’s a great monitor with a small display on the device, as well as an app that lets you view live readings and download months-long history stored on the device.

I’ve wanted to find a way to get this data into HomeAssistant, but there is no official computer client interface.
I recently learned that Bluetooth LE (BLE) is a pretty basic protocol and completely separate from typical Bluetooth protocols used for audio, keyboards, mice, etc, etc. For starters, BLE devices often do not require pairing in the same way that a normal Bluetooth connection would, given the target use is for IoT devices.

A quick BLE overview is, the device acts as the server, and your phone or data receiver is called the client.
The server advertises ‘characteristics’ that can be written to or read by the client. The server can also send notifications to a connected client. The data is sent by the server in 20 byte chunks. This protocal is called GATT. You can use a program called nRF Connect on Android to view local devices, what characteristics they are advertising, as well as send and receive data.

First, I used Androids built-in Bluetooth packet logger to record a basic session of the RadonEye app communicating with the device, and viewed the log in WireShark.
The app initially sends a series of commands, I believe to verify the services and read some data like the serial number and firmware version. From what I can tell, no authentication is happening.

It then alternates sending two commands, “50” and “51” to elicit data from the device.
These are sent to the bluetooth characteristic UUID = 00001524-1212-efde-1523-785feabcd123

In response to each write on UUID 00001524…, the device quickly sends a notification on a different bluetooth characteristic UUID = 00001525-1212-efde-1523-785feabcd123

The sequence I captured was like this, while the RadonEye displayed 0.92 pCi/L:

Write:  50
Receive:  50 10 1F 85 6B 3F 14 AE 87 3F 00 00 00 00 03 00 02 00 00 00

Write: 51
Receive:  51 0E 02 00 17 25 00 00 87 61 08 00 67 94 ED 3F 02 00 00 00

I captured another sequence while the RadonEye displayed 0.88 pCi/L:

Write (hex):  0x50
Receive (hex):  50 10 AE 47 61 3F 14 AE 87 3F 00 00 00 00 02 00 05 00 00 00

Write (hex):  0x51
Receive (hex):  51 0E 02 00 1E 25 00 00 23 66 08 00 67 94 ED 3F 05 00 00 00

There are some constants between the two, but at this point, it’s not clear what any of this means.

I confirmed using the nRF Connect Android app, that I could send “0x50” or “0x51” to the device and receive data back, so I’m pretty sure there is no handshake/authentication/etc required to read the data.

I have some experience doing static analysis of Android app source code, that seemed like the easiest next step to decode the data being sent out. I used a program called Bytecodeviewer, which decompiles APK files to give you readable java source code. It actually lets you compare the output of several different decompilers, and in this case I found Procyon to be the most readable.

I gathered that, when the app receives a notification, the method onCharacteristicNotified() is triggered in class RD200Manager$1. This splits out the 1st byte to indicate the data-type, the 2nd byte as a data-length variable, and what I call the data payload starts at the 3rd byte.
A few intermediate functions are called that don’t do anything but send the data-type and the data payload as arguments to the method recDataParse() in class ActivityDetail.

recDataParse looks at the data-type byte to decide how to interpret the data payload.
In the case of data type 0x50 (int = 80), it takes the 1st 4 bytes of the data payload, runs them through the method arr2float in class Utils, and assigns the result to this.mDevice.MeasData.readValue

This seems promising, and the rest of the payload is similarly processed and assigned to:
this.mDevice.MeasData.dayValue
this.mDevice.MeasData.monthValue
this.mDevice.MeasData.PulseCount
this.mDevice.MeasData.PulseCount_10min

Focusing on this.mDevice.MeasData.readValue, I assume this will store the current Radon level. Looking at arr2float, what it’s doing is taking the 4 bytes, bit shifting them into a single 32 bit integer, and converting this to a floating point number using the builtin Float.intBitsToFloat() method. Note that the bytes are sent as big-endian for compatibility with Java.

So essentially, the 3rd to 6th bytes of the original value sent by the RadonEye in response to the command “0x50” are the IEEE 754 representation of the floating point value for the current Radon level (in big-endian format).

In the 1st data I captured above, these bytes are 1F 85 6B 3F (0x3F6B851F as little-endian), and that is the IEE754 representation of ‘0.92’

Looking at how recDataParse treats values starting with 0x51(int=81), it seems to be a bunch of other configuration information, like what the units are, information about the optional Alarm settings, etc.

That’s as far as I’ve gotten.
It looks like it will be pretty straightforward now to read this data from the device. The only issue I’m not sure of, is the device timeout. It seems to drop the connection after ~30-60s of no command sent by the client. If that can’t be changed the service will have to query more quickly than that, or just set up the connection every time.

My ultimate goal, however, is to get this work on FreeBSD (FreeNAS), which is where my homeassistant install lives. FreeBSD has pretty minimal bluetooth drivers, and pybluez doesn’t support FreeBSD. There are some options for USB Bluetooth LE devices that expose an AT-command interface to their onboard Bluetooth stack over a serial connection, so no drivers are required. There are several potential options but the ESP32 seems to be the best. https://github.com/espressif/esp32-at/blob/master/docs/ESP32_AT_Commands_Set.md

If anyone has suggestions for this please post here!!

Some other options:

There may be a way with the HM-10/AT-09 modules based on CC2540
http://www.martyncurrey.com/hm-10-bluetooth-4ble-modules/#Using_The_HM-10_With_non-HM-10_Modules

Although it seems a bit ridiculous to bridge BLE to wifi (MQTT), its an option as well
ESP32 BLE to MQTT bridge:


Or similar on rPi

(No Central profile by default): https://learn.adafruit.com/introducing-adafruit-ble-bluetooth-low-energy-friend/faq

This is promising but I haven’t found an easy USB version:
https://www.telit.com/m2m-iot-products/wifi-bluetooth-modules/bluemods/

Hello,

I’m interested in your project regarding RadonEye BT connection because I’m going to start a similar project. I do not own such a sensor yet , but I have ordered one and I expect it in a few days.

However, my preferred solution is a bit different from yours. Several years ago, I started an own project to be able to control different sensors and actors in my house. Therefore the foresight are possibly a bit different. Nevertheless, I’m very interested in your result if you find a solution.

Just to allow you to figure out how I plan to solve the problem, I’ll give some information about my intentions.
I do not know if they are correct and realistic. I appreciate any hints and concerns because you or possibly any reader has already experiences with the Radon sensor regarding my intention.

My plan is to connect the sensor on a lower level as BT. I assume the RadonEye is equipped with the Radon sensor RD200M by FTL from South Korea. There are data sheets and descriptions of this sensor and the documents tell me the sensor has a simple serial connection like RS232 but just on TTL level. Also the commands are know to setup
and readout the sensor (they are different on what you discovered). That’s what I would like to try to use. From an earlier project I have access to a small MC board with ETH connection with several serial ports and a simple WEB server. Of cause, I possible have to drop the BT connection and also the display of the sensor, but if this would be handled intelligent, both
could be used anyway.

Don’t hesitate to correct me if my idea seems to be wrong. I appreciate any helpful thoughts and doubts.

Please keep me informed.

Thanks and Regards

Rob

Addendum:
(Just for information)
Meanwhile I found a hint about anyone who did something similar as I want to do.
He removed the electronic of the sensor and added an ESP8266 wifi module instead.
But there are no details.

Hello, I wonder if somebody here can confirm that the above bluetooth protocol works as expected in a stable way. I’m about to buy the Radon Eye but want to make sure I can read the readings programmatically.

Also interested if this has gone any further, looking to integrate a new RadonEye200.

@skis4hire Hi!

Do you have any update in this project?

Thanks.

Hi!

Just created an account as I wanted to update this community on my results. The info here helped me set up my radon eye and connect it to my monitoring system at home. I can confirm the BT connection works and is stable. I have my radon eye connected over BLE to a raspberry pi B3 for a little over a week now and have not found any issue so far. The connection is stable and I poll the device every 5 minutes (the reading updates every 10 minutes but I want to give it 2 try’s just in case). The radon eye is located about 2ft away, I have not tested it any further yet but I think it should be fine. I like the BLE approach as it won’t require me to do any “demolition” of the device leaving the warranty intact and make me able to switch back to the app or resell it in a year once I’m convinced my averages are good.

The raspberry pi runs a TIG setup (telegraf, influx, grafana) The way I get the data is by several local bash/python/expect scripts (i’m not a developer and it ain’t pretty but it works). Telegraf [inputs.exec] launches a bash script that in turn runs a 10 line expect script that uses bluetoothctl to connect, write the 0x50 and read the results and disconnect from the device. Some creative grep, awk and sed give on that result give me the little endian 4 byte hex value I need. This hex value goes thru a small python script to make it a float which is then echo’d to stdout. From there telegraf picks it up, adds it to InfluxDB and Grafana shows me awesome lines.

Some day I’ll change it all to python but I have a lot to learn before that day comes. This way I could make it work fast, I was not impressed by the app provided and wanted something where I have more control.

@joey thanks for reply. I’m also don’t want to do a hardware mod on RadonEye. Then I’ll going to use my EmonCMS to collect data from RadonEye via BLE + python script.
I still not received my RadonEye but I already write something and I’m using struct.unpack to convert single IEEE 754 to decimal.
Sample:

struct.unpack('<f','\x1F\x85\x6B\x3F')[0]

Can you share your scripts? I would really appreciate it. Thanks.

Hi Scott, sure I added them below. I’m running it on a raspberry pi 3b. I want to redo it all in python but right now I don’t have the time nor python experience to start that path. I don’t recall having to install anything but you’ll need to have expect, bluetoothctl and python3 on the raspberry for this to work. You also need the MAC address of the radon eye and change that in the expect script. bluetoothctl has a scan option making this easy to find.

If anyone can rewrite it to python I’d love to get a copy, especially the part I’m doing with expect right now.

All 3 script are executable and located in /usr/local/bin from where telegraf runs radon.sh every 5 minutes (the radon eye only updates the value every 10min so there are always duplicates).

filename: radon.sh

#!/bin/bash

# step 1, grab radon number using expect script over BTL connection & grep the radon value we want.
radon=$(/usr/local/bin/radon/btread.sh | grep '50 10' | awk '{print $6,$7,$8,$9}' | sed 's/ //g' | tail -1)

# step2, convert little endian hex to floating point using python 3 script.
#export var to envvar so python can use it.
export radonhex=$radon
#run thru python, get float into variable
radonfloat=$(python3 radonhex.py)

# step3, cleanup float a bit, has a comma and (), need clean output for telegraf.
float=$(echo $radonfloat | sed 's/,//g' | sed 's/(//g' | sed 's/)//g' )
echo $float

btread.sh:

#!/usr/bin/expect
#log_file /home/pi/btlog

set timeout 1
spawn bluetoothctl
send "connect MAC-ADDRESS-OF-RADONEYE\r"
expect "]#\r"
send "select-attribute /org/bluez/hci0/dev_DC_D4_14_98_C1_58/service0009/char000a\r"
expect "]#\r"
send "write 0x50\r"
expect "]#\r"
send "select-attribute /org/bluez/hci0/dev_DC_D4_14_98_C1_58/service0009/char000c\r"
expect "]#\r"
send "read\r"
expect "]#\r"
send "quit\r"
expect eof

radonhex.py

#!/usr/bin/env python3

import struct
import os

hex_encoded = os.getenv("radonhex")
binary_data = bytes.fromhex(hex_encoded)
FLOAT = 'f'
fmt = '<' + FLOAT * (len(binary_data) // struct.calcsize(FLOAT))
numbers = struct.unpack(fmt, binary_data)
print (numbers)
os.environ['numbers'] = 'radonfloat'
1 Like

Hi Carlos,
I’m using struct.unpack as well to convert. I just added my scripts in a comment. If you are coding it all in python I would appreciate it if you could share your scripts when you have it working.

Have you received your RadonEye yet?

Thanks!!! I will give it a try.

Joey,
I make a Shell script using ganttool (only 2 Lines needed, one write, one read) + python to convert float.
My code is still bit trashy but I’m plan to create a python only code , but… I do not have experience on bluetooth python lib , and not have a lot time right now. But If I got some time, I going to clean up a little actual script and I’ll post It here.

@Djow there’s my python code. :point_up_2:

I will give this a go. I may even try to add mqtt, try is the key word :slight_smile:

Thanks Carlos! appreciate it.

(update)
Hi People!

There’s my cleaned python only script:

Fully working on python 2.7.9 with bluepy lib.

#!/usr/bin/python
#
# RadonEye RD200 Reader (Bluetooth/BLE)
#
# Version: 0.1c - 2019-07-12
#
# Author: Carlos Andre - candrecn at hotmail dot com
#
 
import binascii
import struct
import time

from bluepy import btle
from time import sleep

# Put here your RadonEye Bluetooth Address
RadonEyeBTAddress = "E4:34:1D:xx:xx:xx"

# Choose Unit of Measurement - pCi/L (True) or Bq/m^3 (False)
picoCurie = True

# Verbose - Enable (True) or Disable (False)
Verbose = False

# Output - Only Radon Value or Full (timestamp and Unit)
OnlyValue = False

def GetRadonValue():

    if Verbose: 
        print ("Connecting...")
    DevBT = btle.Peripheral(RadonEyeBTAddress, "random")
    RadonEye = btle.UUID("00001523-1212-efde-1523-785feabcd123")
    RadonEyeService = DevBT.getServiceByUUID(RadonEye)

    # Write 0x50 to 00001524-1212-efde-1523-785feabcd123
    if Verbose: 
        print ("Writing...")
    uuidWrite  = btle.UUID("00001524-1212-efde-1523-785feabcd123")
    RadonEyeWrite = RadonEyeService.getCharacteristics(uuidWrite)[0]
    RadonEyeWrite.write(bytes("\x50"))

    # Read from 3rd to 6th byte of 00001525-1212-efde-1523-785feabcd123
    if Verbose: 
        print ("Reading...")
    uuidRead  = btle.UUID("00001525-1212-efde-1523-785feabcd123")
    RadonEyeValue = RadonEyeService.getCharacteristics(uuidRead)[0]
    RadonValue = RadonEyeValue.read()
    RadonValue = struct.unpack('<f',RadonValue[2:6])[0]
   
    if picoCurie:
        Unit="pCi/L"
    else:
        Unit="Bq/m^3"
        RadonValue = RadonValue * 37
 
    if OnlyValue:
        print ("%0.2f" % (RadonValue))
    else: 
        print ("%s - Radon Value: %0.2f %s" % (time.strftime("%Y-%m-%d [%H:%M:%S]"),RadonValue,Unit))
    
try:
    GetRadonValue()
except:
    if Verbose: 
        print ("Connection failed, trying again...")

    sleep(5)

    try: 
        GetRadonValue()
    except:
        print ("Connection failed.")

:slight_smile:

1 Like
1 Like

Thanks a lot for this. it works great. I used this and added MQTT so it publishes the value to Home-assitant. I am hesitant to share because I am not a python developer and I am sure there are many issues. It seems to work, hasn’t crashed anything yet.

1 Like

@stogs @Djow I just upped to my Git repo lastest version. Now it works using args instead editing file vars.

1 Like

Any interest in seeing the mqtt stuff, it is rather basic, I just added a function that calls your function to get the radonvalue and then publishes that value via mqtt. I would offer it up to anyone that knows how to do it right… :slight_smile: