Feed on
Posts
Comments

In this and the next two three blog posts (Part 2, Part 3, and Part 4), I will describe how I reverse engineered a few off-the-shelf wireless temperature, humidity, and rain sensors, and used an Arduino (Update: RPi is also supported now!) to listen to and decode the sensor data. This has been a really fun journey and I will document the process as thoroughly as I can. Because there are lots of details, I don’t want to jam everything into a single post, so I have split the whole story into three posts. The Arduino and RPi programs are provided at the end of each post.

Introduction

The first question to ask is always: why am I doing this? Well, for good reasons: these off-the-shelf sensors are cheap, well-built, outdoor-proof, battery-driven and power efficient. If your project needs local weather data and you don’t want to spend time building your own transmitter units (which would bring up a whole bunch of engineering issues), these cheap sensors are the way to go. My original plan was to make use of the sensor data for sprinkler control systems. I actually set this as my next challenge to tackle in a blog post I wrote two years ago. It’s a shame that I lost track of it since then. But hey, two years later, I finally finished it. Better late than never!

Here are the three sensors that I gathered and will use as examples in the following. They all work in the 433MHz frequency band.

Disclaimer: I am not associated with Acu-Rite in any ways, I just picked these sensors because they are common in retail stores.

IMG_0040IMG_0041

Preparation

The tools involved are quite simple: I used an Arduino and a 433MHz receiver. You should use the superheterodyne type of receiver as it has very good signal-to-noise-ratio. The super-regenerative type is too noisy and will only work in short range.

Also, to obtain an initial waveform in order to bootstrap the process, I used a RF sniffing circuit from my previous blog post (about interfacing with remote power sockets), a PC with line-in port, a 3.5mm audio cable, and the free Audacity software. If you don’t have a PC with line-in port (most laptops these days don’t have), you can buy a USB sound card which has line-in port.

IMG_0052IMG_0051


Wireless Temperature Sensor

Raw Waveform. The temperature sensor is the simplest, so let’s take it down first. What I have at hand is an Acu-Rite 00782W3 indoor/outdoor temperature sensor. The package includes a receiver display unit, and a transmitter which sends a temperature reading every 1 to 2 minutes. Pop in the battery, power on the RF sniffing circuit, and launch the Audacity recording software, I got a waveform like the one shown on the right image below.

IMG_0043temp_sensor_waveform

By carefully looking at the waveform, I found the following patterns:

  • Each transmission consists of 8 repetitions of the same signal.
  • Every two repetitions are separated by a constant low sync signal that’s roughly 400 samples (in Audacity you can select a region and see the sample count). Given that the sampling rate is 44.1kHz, this is roughly 9.0ms (400 / 44.1 = 9.07).
  • The bit patterns are pretty clear: logic 1 is a constant low of about 180 samples (4.1ms), and logic 0 is a constant low of about 95 samples (2.1ms). Every two bit is separated by a constant high of about 24 samples (0.54ms).

arduino
Given the patterns, I then manually wrote down entire sequence of bits. For example, the image above shows a signal that’s:

11100110 10000000 11111010 01001011

I grouped them into bits of 8 so it’s easy to see the byte values. I also recorded the reference temperature displayed on the receiver unit at the time of capture, which is 77 degree Fahrenheit. At this point we don’t know yet how the bits are encoded (i.e. how they translate to 77). None of the bytes is directly equal to 77. But this is ok — if it was that easy, it wouldn’t have been fun any more 🙂

Create Temperature Variations. In the next step, I will create temperature variations so I can get a lot of different signals and reference readings. By checking how the signal changes, hopefully I can decipher the coding pattern. How do I vary the temperature? Simple: use a hair blower to increase the temperature, and throw the sensor into a fridge to decrease the temperature.

But I am not in a hurry to do that just yet — manually translating the waveform into bits is very tedious, and I worry that if I make a mistake that can compromise the analysis. So I need a way to automate the signal capturing process. Since I already know the signal timings, I can create an Arduino program to automatically translate the waveform into bits. This will make the capturing process a lot faster.

An Arduino Program for Bits Conversion. To get started, I connected the VCC, GND, and DATA pins of the RF receiver to Arduino’s 5V, GND, and D3 (interrupt 1). Then I wrote an interrupt handler to process the captured signal and convert it to bits. Earlier I’ve studied the RCSwitch library, which gave me a good idea of implementation.

Technically, the interrupt function is triggered every time the signal changes from high to low (falling edge) or low to high (rising edge). A ring buffer is used to track the timing between every two triggers. It then checks the timing to see if a sync signal (roughly 9.0ms) is present. Because the signal may still be noisy the first time the sync signal is received, we will wait till the second or third time it’s received to actually do the bits conversion.

Collect and Analyze Data. Now let the fun begin. With the Arduino program, I gathered a lot of data under various temperatures. Make sure to also gather some low temperature reading by putting the sensor in a fridge: I was surprised that the signal can actually go through a fridge door! Here is a selected list. The number at the end of each line is the reference temperature value shown on the display receiver.

10001011 10000001 00111110 00111101 (89°F)
10001011 10000000 11100100 01010001 (73°F)
10001011 10000000 10101101 00011011 (63°F)
10001011 10000000 00000111 00001010 (33°F)
10001011 10000000 00000001 01010000 (32°F)
10001011 10001111 11111011 00010111 (31°F)
10001011 10001111 11100010 10111010 (26°F)
10001011 10001111 11001011 10100101 (22°F)
10001011 10001111 01111100 10011010 ( 8°F)

Now the coding pattern is a lot more clear. The first byte is always the same, so it’s probably a signature or channel byte. This is used to avoid interference among multiple transmitters. Note that this byte is different from the one I manually wrote down, so I suspect the transmitter changes the signature every time it’s powered on. The second and third bytes show a clear trend as the temperature goes down. The last byte has no clear trend. It’s possibly some sort of CRC checking byte.

So how do the middle two bytes translate to the temperature values? Well, if you look at the 32°F line: the middle two bytes are very close to 0 (ignoring the leading 1). Since 32°F is roughly 0°C (Celsius), is it possible that the middle two bytes give the temperature in Celsius? After converting the reference temperature values to Celsius, the puzzle is instantly solved (the arrow below points to the decimal value of 12 bits shown in blue):

10001011 10000001 00111110 00111101 (32°C) -> 318
10001011 10000000 11100100 01010001 (23°C) -> 228
10001011 10000000 10101101 00011011 (17°C) -> 173
10001011 10000000 00000111 00001010 ( 1°C) -> 7
10001011 10000000 00000001 01010000 ( 0°C) -> 1
10001011 10001111 11111011 00010111 (-1°C) -> -5 (two's complement)
10001011 10001111 11100010 10111010 (-3°C) -> -30 (two's complement)
10001011 10001111 11001011 10100101 (-5°C) -> -53 (two's complement)
10001011 10001111 01111100 10011010(-13°F) ->-132 (two's complement)

So the temperature, in Celsius, is given by the 12 bits (shown in blue), divided by 10, and rounded to the nearest integer. Aha, that’s it! Now I can modify the Arduino program to not only print out the bits, but also decode the signal and get the real temperature values displayed onto the serial monitor.

Update: the code is adapted to RPi as well, using wiringPi. The code below uses wiringPi GPIO 2 (P1.13) for data pin.

Note that the program uses pretty tight margins (1ms to 2ms) for screening the signal timings. Depending on the quality of your 433MHz RF receiver, you may have to increase the margin to improve error tolerance.

I did some quick testing by hanging the temperature sensor outside on a tree. Then I compared the temperature value reported on the serial monitor and the reference temperature displayed on the receiver unit. The two matches very well. Cool, mission accomplished! 🙂

IMG_0045IMG_0047IMG_0049

Summary of the Process
  • Use the RF sniffing circuit and Audacity to capture an example signal; examine the signal and estimate timing information.
  • Write an Arduino program to automatically capture signals and convert them into bits.
  • Collect a lot of data, record the bits and write down the reference temperatures.
  • Examine the changes in the bits as the reference temperature changes, identify those bits that matter, and reason about how they translate to the reference value.
  • Modify the Arduino program to decode the bits into temperature value.

Continue to Part 2, Part 3, and Part 4.


23 Responses to “Reverse Engineer Wireless Temperature / Humidity / Rain Sensors — Part 1”

  1. Lamby says:

    This looks like the sensor to use!:
    http://www.acurite.com/acurite-5-in-1-sensor

    I started reading where you assumed the measurements would be in Fahrenheit and knew that was not going to end well 🙂 The rest of us use metric.

    Great post by the way.

  2. […] wireless power socket and playback to simulate the remote. It’s the same guy that has written about using Arduino to interface with off-the-shelf wireless temperature, humidity, rain, and soil m…. I think this gadget can be very useful for me while experimenting with remote sensors for my […]

  3. vpspr says:

    Is it possible to have multiple 433 temp sensors and one rpi receiver? I looking to have 6-8 temp sensors around the house.

    Thanks

  4. Terence says:

    Hi Ray,

    I bought a RFtoy from you recently but i’m new to arduino and trying to understand more by reading a lot of infos.

    Would like to ask if I could sniff 2.4G signals instead of 433mhz?

    I found a LED down light which can change from cool white to warm white and can be dimmed as well. It response to a 2.4G remote.

    I am thinking if I could use send out the 2.4G codes then it will allow me to interface it into my Vera HA controller.

    What i want to achieve is to use nRF24L01 to sniff the remote codes then use Vera + (Arduino+nRF24L01) to control my down lights to change from cool to warm whites and also to dim the lights using the learned remote codes.

    Could you give me some advice on how to achieve the above?

    Thank you

    • ray says:

      To sniff 2.4G signal, you need a 2.4G transceiver, such as the nRF24L01. But keep in mind that how feasible it is to sniff the signal depends on the encoding scheme — if the signal is encrypted, it will be very difficult to reverse engineer the data. If your LED light has an open API, it may be easier to direct use the open API to operate it.

  5. Brett says:

    With reference to my previous comment I can confirm that 24vac electrothermic actuators (as used on radiator valves and underfloor heating manifolds etc) work remakably well when driven directly from Opensprinklers 9vdc station outputs. You don’t even need the boost. The only measurable difference in performance is an increase in the response time (specifically the dead period) when exciting from cold. However when controlling around a setpoint I can achieve an almost instant response with full valve travel complete in around 1.5 mins (exceeding the standard specification of 3mins). Ok now to decode a few RF sensors with the RFToy…

  6. RoSchmi says:

    Very helpful, thank you.

  7. Maxzillian says:

    Just thought I’d lend some recent experience with this. I bought a temperature/humidity sensor (https://www.acurite.com/digital-indoor-outdoor-temperature-humidity-monitor-00611a2.html) from the local Menards and you’re work was a tremendous help in getting data from the sensor!

    That said, I found that it oddly followed the communication protocol as you have documented here in part 1 instead of what you found in part 2. The only real difference is there being 5 bytes sent instead of 4, the 4th byte containing the humidity. As was pointed out in comments in your later work, the checksum is just a simple additive check; I found the easiest way to calculate it was to add bytes 1-4 (or 1-3 in the case of temperature only) to a byte variable. This automatically keeps it truncated to one byte and makes for a very quick and simple check.

    I did try using the receiver that came within the display, finding it was a separate module, but ended up changing to a SparkFun unit as the acu-rite one received too much noise; especially from one of my monitors.

    I did deviate in the code a bit using the Timer1 module to measure pulse widths, running it at a prescaler of 256. Oddly the temp/humidity sensor had the same sync period, but the high pulses and data bits ran at twice the speed. Ultimately I found good timings at:

    high-pulse: 20-40 cycles
    Sync: 550-556
    Bit 1: 115-126
    Bit 0: 53-63

    Once again, thanks for all the work you did on this as it definitely made development a lot faster!

  8. Jorginho says:

    Hi,
    Interesting subject. May be to take it a step further: is it possible to actually build your own wireless sensors. So say you buy a sensirion sensor, put it in the field and make it work like the wirelesss sensors you now use? I would like to do that…

    What I would like to do in fact is this:
    – SHT31 sensor in the field with 3V LiIon recharegable battery, small solar panel and send data to a reciver hooked to an Arduino. Send data every 12 s. Averages of 1 minute are the real temperature
    – Anemometer: send data every 0,25 s. 3 s average are gusts, 10 minute averages the averae windspeed.
    You get the picture. Can anyone tell me if this is difficult. Because just hooking up a sensor to a transceiver I think means little.The tranceiver on its own probably won’t know what to do with the signals? How do you set them to send every few seconds?? Etc?

    Thx!!!

  9. Picard0403 says:

    How can I compile the RPI version ?

    • Picard0403 says:

      I’m getting the folloing errors

      /tmp/cceeR9WV.o: In function isSync(unsigned int)':
      temperature_display.cpp:(.text+0x70): undefined reference to
      digitalRead’
      /tmp/cceeR9WV.o: In function handler()':
      temperature_display.cpp:(.text+0xd4): undefined reference to
      micros’
      /tmp/cceeR9WV.o: In function main':
      temperature_display.cpp:(.text+0x28c): undefined reference to
      wiringPiSetup’
      temperature_display.cpp:(.text+0x2c8): undefined reference to wiringPiISR'
      temperature_display.cpp:(.text+0x5b4): undefined reference to
      delay’
      temperature_display.cpp:(.text+0x5c4): undefined reference to `wiringPiISR’
      collect2: error: ld returned 1 exit status

    • Picard0403 says:

      Hi, I get the programm work, but can’t read from my sensor…..
      It is a TFA Nexus 433 Mhz Sensor – Maybe with this it will not working

  10. Adrian says:

    Dear Ray,

    I did my best, but am unable to receive any data with the program for RPi.

    The receiver/transmitter is working, because I am able to receive/send signal with 433Utils (RFSniffer) from my ON/OFF RF switch.

    I build a sniffer from a cheap USB sound card, here is the result:

    https://f001.backblazeb2.com/file/mihalkodropshare/weather_sensor.wav

    If am not wrong, I red these values from Audacity:

    sync_length (selection start)20223-19823(selection end) = 400 / 44.1 = 9.07ms
    0 (or 1?) 20337-20255 = 82 / 44.1 = 1.85ms
    1 (or 0?) 20528-20360 = 168 / 44.1 = 3.80ms
    sep length 20359-20335 = 24 / 44.1 = 0.54ms

    First, I tried your default app where you have nearly same values: not working.
    Then, I modified the values to:

    #define RING_BUFFER_SIZE 256
    #define SYNC_LENGTH 9000
    #define SEP_LENGTH 500
    #define BIT0_LENGTH 3800
    #define BIT1_LENGTH 1850

    Still nothing. 🙁

    Then I fired up my RTL SDR device and rtl_433 utils, with this, I am able to receive and decode signal. The sensor is recognized as a prologue sensor, here is the source of this module:

    https://github.com/merbanan/rtl_433/blob/master/src/devices/prologue.c

    From the description in the code:

    “the sensor sends 36 bits 7 times, before the first packet there is a sync pulse
    * the packets are ppm modulated (distance coding) with a pulse of ~500 us
    * followed by a short gap of ~2000 us for a 0 bit or a long ~4000 us gap for a
    * 1 bit, the sync gap is ~9000 us.”

    …so I am working with correct values.

    But then why I am unable to receive anything with your code?

    Thank you,
    Adrian

    • ray says:

      My best suggestion is to insert debugging code to help you figure out why it’s not parsing. The easiest way is to insert printf’s in the code. This will help you figure out if the code can detect the sync signal, and each of the signals following it. There is no generally agreed protocol for wireless sensors — the code I published is written specifically for the sensor I bought, and your sensor may have a completely different protocol.

  11. Mac says:

    Using your code, my 2 606TX sensors work just fine. In order to tell them apart, I added the ability to turn the first 8 bits in to the rolling code number. Now, I know which unit is sending the temperature. Problem is, sometimes an erroneous bit stream comes in and it gets decoded with a different rolling code number. Other times, the temp is WAY off. It has been mentioned that there is a CRC number sent, but I find no one with code to decipher and my efforts so far have been for naught. Any clues on how to weed out the bad data streams?

  12. Swilson says:

    The conversion to F seems to be messed up if Celcius is a negative number. For example if Celcius is -4 then it doesn’t take the – into account and does the math for just 4C which comes out to 39F. Any thoughts, I am researching now.

    • Swilson says:

      I figured it out. Add int Celcius = 0; just under loop and in if (negative) add the following two lines.

      Celcius = ((temp+5)/10);
      Celcius *= -1;

      Thanks for the code. Looking forward to getting my Weather Station up and going.

    • ray says:

      The code takes into account negative values — search ‘negative’ in the source code and you will find how it’s handling it. Not all wireless sensors use the same encoding, so you have to look at your specific sensor to see how it encodes negative values.

  13. Hanz_Grau says:

    Many many many thanks for this tutorial. Without You I would not come at it. Have a nice day.

  14. Simon Baxter says:

    Hello
    This is a great tutorial.
    Question for you, which WiringPi libraries are you using? I’ve just compiled with the libraries out of git://git.drogon.net/wiringPi and then compiled your Pi C with:
    gcc -L/usr/lib -lwiringPi temperature_display.cpp -o temperature_display

    And I’m getting:
    gpio: Symbol piModelNames' has different size in shared object, consider re-linking
    gpio: Symbol
    piRevisionNames’ has different size in shared object, consider re-linking
    gpio: Symbol `piMakerNames’ has different size in shared object, consider re-linking

    Thanks

Leave a Reply