Smart radon sensor

Note that you should make the ESP go to deep sleep between the Radon mesarement polls (I have it report every 10 minute), to keep the power consumption low. Internally in the RD200, there’s only linear regulator, which will build too much heat (IMO) if you do not let the ESP deep sleep.

Hello Micael,
Thank you very much for your answer, I appreciate your help.
I’m pretty familiar with soldering and I have also MC experiences. So it should not be
a problem to modify the unit. After some first tests with the first unit, I instantly
ordered a second one. Both work excellent, so far I can value that. I got very interesting results, because I live in an area with over average Radon occurrence.

Am I right that the ionization camber is the same as RD200M ? So I expect the documents I found on the web regarding pin out of the connector and the commands for accessing the sensor, are the same and valid, right ?

After I’ve finished my last measurements, I’ll start to modify one of the units. Now I know
how to open the unit hopefully without damage.
First I’ll try to keep the original electronic to be able to use the unit still for stand alone operation, but I’ll try to bypass it by a switch and route it to one of my own MC boards with ETH connection. That fits better to my own home control solution.

Thanks a lot again for you friendly answer.



Please share you picture … thanks Lorenzo

Yes, the RD200 houses a RD200M, and the doc’s on the web is the correct one. As you have seen, it is quite well documented and easy to get going. But if you measure the comm’s going on with the BT module still in place, you will see undocumented messages. FTLabs confirmed this.
If you want to keep the stand-alone functionality, you will most likely have to have your own board outside the RD200, since it is hard to fit anything in the unit.
Also, I think you should keep in mind, that the RD200 is probably sensitive to noise on the (12V) power line, and probably also emissions from close by electronics.

Here’s som pic’s on the RD200 split. Note the three screws one on top (the hard one!), and the two in the bottom. Once they are removed, you will need to pry it open, starting at one bottom side. The unit is very tight even without the screws, so you will need some screw driver or similar to break it open. Take care not to make too much marks. And take care not to push your tool too far in, in order to keep the chamber untouched.

Hi Micael,
Really great help, thank you very much.

I’ll take care during opening the housing as you mentioned.

You are right, I intend to use an external micro, but I think the serial connection allows
to separate the external HW quite well.
Regarding BT, If possible I plan just to power off the BT unit and by-pass power to the
sensor by a small switch. So I do not expect unknown commands or answers. But this
is a guess.

Thanks a lot again.



If you disable the BT, you will have no problem at all!
Best of luck! (And if you have any more questions, I’m ready! :wink: )

thanks for all information concerning dismounting.
Can you write a guide how to connect it to the ESP and later to OpenHab?
Some picture are also welcome

Sorry to bump a bit of an old thread but I signed up just to respond here after finding this thread on a google search.

You mention that if you were to probe the live serial bus between the included board and the sensor you will see undocumented messages, and that this is confirmed by FTLabs, and earlier you mentioned an averaging mode. Can you elaborate on any of this? I am also working on a project to integrate ESP8266 modules with my RD200 sensors and I found one pdf for the RD200M that is mostly understandable (although what exactly the “transfer period” is still eludes me, changing the value doesn’t seem to do what I expect, oh and why on earth would they choose to use mthread?..) but it really doesn’t list many commands. I feel like there’s a lot more out there. I tried contacting FTLabs without any response several times over the past 2 years.

The goal of my project was to make a replacement board that could operate in standalone mode or utilize my in-home environmental monitoring API (it’s an ugly mess right now so I don’t want to say too much about that side of it) to post data to it. Basically, I wanted sensors that I could use in my house, or lend to a neighbour for a few weeks for them to check out their radon levels if desired.

To show I’m serious and to also share some of my progress with you guys, here’s a comparison picture of the boards I came up with (sorry my hand soldering is pretty messy):

A quick list of hardware specs:
128x64 white 0.96" OLED display (ssd1306)
AT24C256 EEPROM (still not sure why I thought this was a good idea, but hey it’s on there now)
2x push buttons (I was planning to drill the partial existing holes on the top of the enclosure to expose the buttons)
I also sourced the correct Molex Picoblade connectors so that my boards would be plug-and-play.

The end-goal for my project is to open source everything (when it’s in a releasable state). At this point I have a proof-of-concept talking to the RD200M and logging the readings to my monitoring system, but due to me obsessing over ideals I am presently (and have been for a long time) working on a complete re-write of my code base to be more organized and support additional sensor types. My plan was to rework my platform to support temperature (DS18B20), pressure (MPXV7002DP plus external ADC for monitoring sub-slab suction levels), VOC, and PM2.5 sensors. Today I am working on the helper classes for the RD200M which is how I stumbled across this thread. Ideally I’d like to be able to have some kind of status or communication check command I could run against the sensor so that I could detect whether an RD200M is present allowing me to share a single code-base across multiple sensor types, and also it would be great to be able to get a serial number from the sensor itself to uniquely identify each one (as I have noticed a bit of reading variation between units).

Oh, also to help, if concerned about marking the case when opening it, I used a metal spudger to release the clips without any case damage. It’s super thin but when pressed in along the clip itself releases them just great. Unfortunately I totally screwed up all that careful work when peeling back the top sticker as part of it ripped and remained stuck to the housing :frowning:

Great job!!
Can you share the wiring diagram of the PCB and the code.
My target up to now it is just to understand how to connect the ESP to the sensor and how to read the radom value.
I don’t care about the display and the LED but I don’t know where to start and I am afraid to distroy the sensor which it is not cheap !:slight_smile:


Of course I will share code. I tried to attach the PDF I used for reference but the forum considers me too new and will not allow me. I’ll see what I can come up with to show how it’s connected but for now here’s an excerpt from my RD200 helper class. Some of this is stolen directly from the PDF I wanted to attach and some of it is my own creation. This probably references a couple member variables that I forgot to include but if there’s anything more I can elaborate on let me know and I’ll do my best!

void RD200::Handle() {

  if ((millis() - this->lastRequestMillis) > this->pollInterval) {

void RD200::ProcessReceivedData() {
  int recSize = mySerial.available();
  if (recSize < 8) { return; }
  byte recData[8];
  int offset = recSize - 8;
  for (int i = 0; i < recSize; i++) {
    if (i < offset) {
      //skip everything before the final 8 bytes, it's garbage that accumulated in the buffer
      //NOTE: this has never actually happened because we purge the buffer in the request call, it's just paranoia
    } else {
      recData[i - offset] = (byte)this->;
  if (this->CheckSum(recData, 8) == false) {
  this->lastResponseMillis = millis();
  this->outstandingRequest = false;
  this->currentState = recData[3];
  switch (this->currentState) {
    case 0x00:
      this->lastReading = -1.0f;
      break; // Power On ~ 200sec
    case 0x01:
      this->startupMinutesElapsed = recData[4];
      this->lastReading = -1.0f;
      break; // 200sec to 1hour
    case 0x10:
      // according to the guide this means:
      // Measuring time is within 30min and radon count is over 10
      // not really sure if that means less than 30 minutes or between 30 and 60
      this->startupMinutesElapsed = recData[4];
      this->lastReading = -1.0f;
    case 0x02:
      this->lastReading = ((recData[5] * 100) + recData[6]);
      break; // After 1hour
    case 0xE0:
      this->lastReading = -1.0f;
      break; // Detect vibrations

bool RD200::CheckSum(byte data[], int data_size) {
  int sum = 0;
  for (int i = 1; i < data_size - 1; i++) {
    sum += data[i];
    sum %= 256;
  if (255 - sum == data[data_size - 1]) return true;
  else return false;

void RD200::Init() {
  byte period_time = (byte) min(max(this->measurementInterval, 0), 60);
  byte send_data[5] = {0x02, cmdRD200M_SEND_PERIOD_SET, 0x01, period_time, 0xFF - (cmdRD200M_SEND_PERIOD_SET + 0x01 + period_time)};
  this->mySerial.write(send_data, 5);

void RD200::ResetSensor() { // don't do this unless you really want to, it clears all measurement data and restarts the 1 hour timeout
  byte send_data[5] = {0x02, cmdRD200M_RESET, 0x00, 0xFF - (cmdRD200M_RESET)};
  this->mySerial.write(send_data, 5);

void RD200::SetMeasurementInterval(int intervalMinutes) { // to be called BEFORE Init() only
  this->measurementInterval = min(max(intervalMinutes, 0), 60);

void RD200::Request() {
  while (mySerial.available()) { // throw away anything that could be in the buffer;
  byte send_data[4] = {0x02, cmdRD200M_RESULT_QUERY, 0x00, 0xFF - cmdRD200M_RESULT_QUERY};
  mySerial.write(send_data, 4);
  this->lastRequestMillis = millis();
  this->outstandingRequest = true;

Hi Chris,
Your project looks great, and very similar to what I started to do (also I wanted humidity and temp in my design). I see you kept the same regulator - be sure to add significant surface area for cooling, and also make sure to have the ESP to go into its deepest sleep mode (the one re-started with wup/reset, don’t remember the name of this mode).

Regarding the sticker - that is what happened to my first RD200 when I opened it. So much better to ‘drill’ teh hole through the plastic label (make sure the the screw head is fully open before you start unscrewing it, otherwise it will lift the plastic label, causing the same “effect” :slight_smile:

If I can help more, I’d like to, as I also meant to put my solution into opensource, but I got stuck too many times to be able to finish it properly, and now my devices are working.

Yes, I really got lucky when I had some sessions with some engineer over at FTLabs, but he cleared things up very much for me, since I originally intended to let the FTLabs board as is, just sneaking the serial wires also to my ESP. As I understood, the undocumented mode meant that the microcontroller within the chamber component did e.g. averaging work, so the BT module could concentrate on user interface/BT interface. Not sure why, this solution was selected though.

However, just using the serial protocol is fine, if the BT module is off the board. I actually did not encounter any issues at all with it, but I never managed to have the hardware serial ports on my ESP to work well. I spent sooo much time with this, that I got tired of the hole project. Basically I think I a bug in the ESP firmware or hardware. (Hardware developing has been my profession for about 30yrs, so I’m not totally clueless). In the end, I changed my design and code into using a software UART instead, to get away from the stability issues I saw.
So, if you after some random time looses comm’s with the RD200, you might want to put up your scope onto the rx/tx lines - you might see that the impedance of one of the lines gets into some kind of weird lower drive/higher impedance from the ESP, not giving the proper levels. For some reason the ‘random time’ where slightly different on different individuals.

Let’s see if I can find any of my schematics or code, maybe there’s something that can be useful for someone (it is untidy, but after all, my radon sensors has been running for over a year now, so at least proof of concept) :slight_smile:

I have been thinking about changing them into using homie protocol, but that probably won’t happen for a while, since I anyway have them all working now…

OK, so I found my code snippet;

Radoneye RD200 serial to MQTT by Micael Beronius

Debug output on Mini D1 -> "D4"
The rd200 to be connected on "D7" (incoming from rd200) & "D8" (outgoing to rd200).

I had to use sw serial to RD200 to get long term stability, this was because the 
ESP went into some strange mode after a while on the rx pin. Not sure if this is
a hardware of fw bug in some of the lower layers, but any way a bug outside my scope.
Once this happened the RD200 where not able to emit proper levels to the ESP, since its 
tx drive is not very strong (and it should not need to be strong!).

The WAKE pin ("D0") must be connected to RST for wake up. No other change to the D1.
Remove link between "D0" and "RST" when uploading new firmware. 

Sometimes, a 10K pull-down resistor should be tied to D7, to bring down result from RD200.


#include <ESP8266WiFi.h>
#include <PubSubClient.h>
#include <SoftwareSerial.h>

#define CH_STX              0x02
#define RD200_REQUEST_DATA  0x01
#define RD200_RESET         0xA0
#define RD200_SET_PERIOD    0xA1
#define RD200_DATA_RESULT   0x10

* Wifi & MQTT Settings
const char* ssid = "xxxxxxxxx";
const char* password = "yyyyy";
const char* mqtt_server = "your broker server";

const unsigned char rd200_reset[4] =         { 0x02, 0xa0, 0x00, 0x5f };
const unsigned char rd200_set_dt_period[5] = { 0x02, 0xa1, 0x01, 0x0a, 0x53 };
const unsigned char rd200_read_all_data[4] = { 0x02, 0x01, 0x00, 0xfe };
const unsigned char rd200_test_result[8] =   { 0x02, 0x10, 0x04, 0x01, 0x1e, 0x02, 0x3a, 0x90 };

WiFiClient espClient;
PubSubClient client(espClient);
char msg[50];
char topic[50];

SoftwareSerial swSer(4, 5, false, 128 );
void rd200_write( void )
#if 0
    Serial.write(rd200_test_result, sizeof(rd200_test_result));  // Loop test by connecting tx->rx on mini D1.  This echoes back a proper rd200 result
    swSer.write(rd200_read_all_data, sizeof(rd200_read_all_data));

static void  rd200_tx_rx(void )
    char msg[10];
    unsigned char ch_data[10];
    static int ch_count = 0;
    int ch;
    int no_answer, tries = 0;
    char str[24];

    // Send read all data request
    do {
        no_answer = 0;
        Serial1.println("Req data from RD200");        
        while (-1 != )        
        delay( 10 );
        // Wait for result ...
        while ( 0 >= swSer.available() && no_answer++ < 10 ) 
    while ( tries++ < 3 && no_answer >= 10 );

    if (no_answer >= 10 ) // rd200 is not answering. Give up.
        Serial1.println("RD200 did not respond. Giving up!");

    while (-1 != (ch = ))
        sprintf( str, "rx: %02x count=%d", ch, ch_count );
        Serial1.println( str );
        switch (ch_count)
            case 0:
                if (CH_STX == ch)
            case 1:
                if (RD200_DATA_RESULT == ch)
                    ch_count = 0;
            case 2:
                if (0x04 == ch)  // Len is always 4, on result messages
                    ch_count = 0;
            case 3:
            case 4:
            case 5:
            case 6:
                ch_data[ch_count++-3] = ch;
            case 7:     // checksum
                if( ch == (0xff - (0x10+0x04+ch_data[0]+ch_data[1]+ch_data[2]+ch_data[3])))
                    snprintf(msg, 10, "%d.%02d",ch_data[2],ch_data[3] );
                    Serial1.print("Got ");
                    Serial1.println(" pCi/L from RD200");

                    client.publish(topic, msg);
                    Serial1.println("bad checksum");
                ch_count = 0;

                ch_count = 0;

    Serial1.println("leaving rd200 scan");

void setup()

    client.setServer(mqtt_server, 1883);
* Connect to wifi
void setup_wifi()
    WiFi.mode(WIFI_STA);  //set to not broadcast ssid


    // We start by connecting to a WiFi network
    Serial1.print("Connecting to ");
    WiFi.begin(ssid, password);
    while (WiFi.status() != WL_CONNECTED) {
    Serial1.println("WiFi connected");
    Serial1.println("IP address: ");

void loop()
    char mac_string[16];
    unsigned char mac_array[10];
    while (!client.connected()) {
        Serial1.print("Attempting MQTT connection...");
        // Attempt to connect
        if (client.connect("ESP8266Client")) {


    sprintf( mac_string, "%02x%02x%02x", mac_array[3],mac_array[4],mac_array[5] );
    snprintf( topic, 40, "/house/radon/radoneye%s", mac_string );



    while (0) {
        Serial1.println( "Not going to sleep ..");

    Serial1.println( "Going to sleep for 10 minutes, yawn!");

    //Sleep for 10 minutes, then wakeup and send data again
    ESP.deepSleep(600 * 1000000, WAKE_RF_DEFAULT);

Did not find my schematics, neither hand sketch or cad drawings. (Guess I need have a better filing system :slight_smile: )
But I did find a picture of one of my mini D1 boards; Note that the header needs a jumper when “live” - it is the wake-up.

The BT module is desoldererd along with the display and serial memory. Yellow and orange connects to the serial lines on the FTLabs PCB.

That’s a really bare-bones solution! Very functional but minimal!

I have not had any issues with the serial going weird on me but I am using software serial on pins GPIO 4 and 5 which maybe helps? I don’t recall the pinout of the d1 mini (I think that’s the board you are using?) but quickly googling for one it looks like you are using GPIO 13 and 15? You very likely already know this but GPIO 15 is a special purpose pin and could perhaps be part of what is causing your problem? I’d expect the d1 mini has some onboard passive components relating to that pin that could also complicate using it. Speaking of pinouts, I use 100 ohm resistors on the data lines in case the sender/receiver ever disagree and both try to drive the same line, and I have GPIO4 to pin 1 of the 4-pin sensor connector, GPIO5 to pin 2, then 3 is +12V and 4 is GNS.

Thanks for sharing your work as well! I’d be really curious to hear more about the undocumented serial modes. The thing that interests me the most would be seeing more commands. The documentation I have only gives a couple and I really expected there to be more!

Unfortunately for me I seem to have pushed my luck too far. I’m not sure what exactly I did but it would appear I killed my sensor. I last had it connected to my board about 2 months ago (like I said, this re-write is taking forever!) and last night I discovered my sensor now always returns 99.90 pCi/L. Suspecting the fault is with my code I connected the original unmodified board to it and after the normal 1 hour timeout it only shows “Err.” for the reading which is what I have seen it say when the sensor was over-range. Checking my other radon meter the levels in my house were only about 10-15 pCi/L at the time so I wasn’t actually over-range. My sensor is “working” because it is answering normally over serial but is just giving garbage readings. I suspect this is related to me delidding the sensor, something must have gotten damaged when I did that. I’m not sure if I physically damaged it while using a knife to cut the solder holding the lid on or whether it was EMI/RF/electrostatic damage. I did run it briefly with the lid off to investigate the onboard LED behaviour - perhaps that was my mistake. Well, my loss can be somebody else’s gain, here’s a picture from inside the sensor:

Sorry to hear about your broken unit, at least it did not cause the very troublesome smell/smoke that my second unit made when I destroyed it (also I did not know why it broke, I thought I was careful about it). I haven’t bothered to try to open it up, I suspect it takes some time to heat it enough to get the lid off?

I know about the multiple functions with the hw uart pins, I also tried to move the hw uart to its alternative location, but the same thing happened. Using sw uart solved all issues, I suspect if you are alredy using sw serial, this is why it was working fine for you!

I don’t know why you are interested in the undocumented protocol? It is not needed. I mean what else but the readout would you really want? They would not disclose it to me when i asked, and I could not make out what the undocumented messages meant. (I really tried! :slight_smile: The messages looks like this;

0x02 0x13 0x00 0xEC <- Query from Bluetooth module
0x02 0x13 0x08 0x10 0x00 0x00 0x00 0x02 0x00 0x06 0x00 0xCC <- Reply from RD200M  

This is the response I got regarding this some years ago;

“RD200M has two operation modes, RadonEye and Sensor mode. The RadonEye mode is used for the RD200 only, it is not open for users, sorry.”

The document you refer to, is this AN_RD200M_v1.1.pdf and/or datasheet_RD200M_v1.4.pdf ?

I was referencing the AN_RD200M_v1.1pdf, I did not know of this other datasheet!! I found v1.2 of the datasheet now but cannot locate v1.4. I wonder what the differences are?

As for more information I was hoping to get some identifying information from the sensor itself so I could track measurements per sensor not per ESP device, but also I was just curious what else there was because I like knowing things :slight_smile:

For delidding the sensor, I didn’t use heat as I didn’t know what was inside the can and did not want to risk damaging the internals, so I used a sharp knife to cut the solder in the joint repeatedly until the cover came off cleanly. I never stuck the knife beyond the metal can and in my opinion I was very gentle in doing the work - it took almost 30 minutes to cut through all the solder. I did handle the sensor a bit with the cover removed and I did power it up once to see what was up with the LED that I could just barely see underneath the edge of the metal can (which is what tempted me to open it in the first place). In my opinion I was extremely careful in handling the sensor but I suppose given it’s tenuous mounting to the metal can underneath I may have stressed the board at those mounting points, or perhaps some component on that board is extremely sensitive to ESD. I’m not sure if I will ever know… though, thinking about it now I might as well pull the metal lid off again and see if I can isolate the pulse signals that I see mentioned in the datasheet… maybe that will shed some light on where the failure is.

I do have a second (unmodified) RD200 sensor but since it is now my only one I won’t be opening it to continue this project. Hopefully I can find the failure with this one and carry on. If not, I’ll remove the 2 nuts found inside and see what secrets that unveils! Every failure can at least be a learning opportunity.

Hi, thanks for sharing.this help me a lot to clarify the sensor function.
… but I have only one sensor and now I am afraid to destroy it :wink:
By the way I found the data sheet even the 1.4 for RD200 (you can google datasheet_RD200_v1.4_eng.pdf, it is on the supplier web page and does not give any interesting data)

Following the datasheet it is probably better to connect the ESP directly to the sensor cable and provide a 12V output to the sensor itself why connecting directly the UART to a soft serial I/Os so you can use the ESP hardware serial RX/TX to USB in order to debugg in the PC.
The UART is already 3,3V baude rate 19200 so it seems not too difficult… I have just one sensor and probably it is time to order a RD200M.
What do you think?

If you are careful, and you have some experience with soldering, you should be fine. Here’s what you should avoid;
When you runt the unit open, the main issue is that the chamber and screen is metal, and there’s therefore easy to short e.g. power wires/connectors to it, and afaict the chamber is loaded with a high-impedance hi-ish voltage, so you should avoid touching it. Best if you can have the sensor in the housing, maybe just put it in the housing, and securing the two housing parts with tape around it while you are experimenting, and have everything else outside.
In the end, it is worth it, since you’ll have great help getting rid of that radon gas.
If I’m doing this again, I will either finish off my PCB (or if someone else get’s it done before me, I’ll use that :slight_smile: ), or do it as I have done it on my current units. But it can perhaps be a bit more dificult to get rid of the components on the board if you don’t have the proper tools.

Good luck!

Hi guys

I wonder gow this went for those who planned to make this work as a project? Is there anything like instructions+code available on github?

I dont mind reading the data via Bluetooth interface. I do it already with my two Airthings waves but bot sure how to do it with rd200. Any help is appreciated.