Oregon Scientific sensors with Raspberry PI

After mixing different source of information, I was able to decode some Oregon Scientific sensors to get Temperature and Humidity indication, over the air, on 433.92MHz, with a Raspberry PI system. This article gives some details of this adventure …

You follow this implementation at your own risk…

If you are interrested in this article, you could also take a look here where you can find a RF433 shield and the associated code

 

RPI B+ & 2

With RPI B+ and RPI 2 the access to the Interrupt at user level is a problem as the response time is changing due to schedulling policy. For this reason the best way is to use a kernel driver : here you can find the elements to make a kernel driver for RF433 to do this.

By the way, this post have all the information you need to get start but basically you should loose some of the message due to scheduling. Kernel driver should fix that.

Hardware stack

The hardware is a simple raspberry PI with a 433Mhz receiver like this one. The reception quality is not the best you can get, but the price is really low cost. I’ll test different receivers in a future paper, so follow my feed to get these results. The rule is to get an AM/ASK receiver for 433,92Mhz (usually call 433 receiver). This article compares the different receivers I used.

The receiver is working in 5V, as the RPI GPIO are 3.3V I had a voltage division on the output. You can also use a 3.3V receiver which is what I’m doing now. You can optionally add the led on the reception pin to see data and noise reception.

GPIO used is the wiringPi GPIO0 corresponding to PIN 11 (GPIO17)

400px-RaspberryPi_GIP_WiringPi_pinout

 

 

 

 

 

Here is some schema of what I made.

photo(2)The 10K and 15K resistor are transforming a +5V output into a +3.3V to be GPIO compliant.

The 1K resistor and Led can be removed they just indicate what is received from the 433 receiver.

The strange stuff on the left side is the antenna.

 

Low level software stack

The interrupt management is coming from RCSwitch project, but as the decoding function differ, I mostly reuse the Interrupt handler. The GPIO / Interrupt mechanism are based on wiringPI library. RCSwitch as been simplified, the encoding/decoding part has been externalized to join the other codec library:

The initialization is confiuring pins & interrupt handler

RCSwitch::RCSwitch(int rxpin, int txpin) {

        RCSwitch::OokAvailableCode = false;
        RCSwitch::OokReceivedCode[0] = '\0';
        rcswp1.configure(1,this);

        if (rxpin != -1 ) {
                this->enableReceive(rxpin);
        } else this->nReceiverInterrupt = -1;

        if (txpin != -1 ) {
                this->enableTransmit(txpin);
        } else this->nTransmitterPin = -1;

}
/**
 * Enable transmissions
 *
 * @param nTransmitterPin    Arduino Pin to which the sender is connected to
 */
void RCSwitch::enableTransmit(int nTransmitterPin) {
  this->nTransmitterPin = nTransmitterPin;
  pinMode(this->nTransmitterPin, OUTPUT);
  digitalWrite(this->nTransmitterPin, LOW);
}
/**
 * Enable receiving data
 */
void RCSwitch::enableReceive(int interrupt) {
  this->nReceiverInterrupt = interrupt;
  this->enableReceive();
}

void RCSwitch::enableReceive() {
  if (this->nReceiverInterrupt != -1) {
    wiringPiISR(this->nReceiverInterrupt, INT_EDGE_BOTH, &handleInterrupt);
  }
}

The interrupt handler manage signal by computing duration between two signal fronts and transfer it to the different codec you want to involved :

// ==============================================
// Interrupt Handler to manage the different protocols
void RCSwitch::handleInterrupt() {

  static unsigned int duration;
   static unsigned long lastTime;

  long time = micros();
  duration = time - lastTime;
  lastTime = time;
  word p = (unsigned short int) duration;

  // Avoid re-entry
  if ( !OokAvailableCode ) {            // avoid reentrance -- wait until data is read
          if (orscV2.nextPulse(p))      { RCSwitch::OokAvailableCode = true; orscV2.sprint("OSV2 ",RCSwitch::OokReceivedCode); orscV2.resetDecoder(); }
          if (orscV3.nextPulse(p))      { RCSwitch::OokAvailableCode = true; orscV3.sprint("OSV3 ",RCSwitch::OokReceivedCode); orscV3.resetDecoder(); }
          if (rcswp1.nextPulse(p))      { RCSwitch::OokAvailableCode = true; rcswp1.sprint("ALRM ",RCSwitch::OokReceivedCode); rcswp1.resetDecoder(); }

        //  if (cres.nextPulse(p))      { cres.print("CRES"); cres.resetDecoder(); }
        //  if (kaku.nextPulse(p))      { kaku.print("KAKU"); kaku.resetDecoder(); }
        //  if (xrf.nextPulse(p))       { xrf.print("XRF"); xrf.resetDecoder(); }
        //  if (hez.nextPulse(p))       { hez.print("HEZ"); hez.resetDecoder(); }
        //  if (viso.nextPulse(p))      { viso.print("VISO"); viso.resetDecoder(); }
        //  if (emx.nextPulse(p))       { emx.print("EMX"); emx.resetDecoder(); }
        //  if (ksx.nextPulse(p))       { ksx.print("KSX"); ksx.resetDecoder(); }
        //  if (fsx.nextPulse(p))       { fsx.print("FSX"); fsx.resetDecoder(); }
  }

}

The full modified RCSwitch.cpp and RCSwitch.h are available for download here :

RCSwitch

The specific Oregon Scientific decoding is operated by another library RcOok made for Arduino, I mostly update this library to make cleaner code and adapt it for C++ and RapsberryPi and add the RCSwitch codec in it.

The library can be found here :

RcOok

 

High level software stack

A thread is waiting for the Interrupt flags to get the decoded data and analyse these data to extract sensor element from it.

The kind of message received from my TNGR222 are like

OSV2 1A2D1002 502060552A4C

The first step is to reverse order of quartet in each byte to get :

OSV2 A 1D20 1 20 0 502 0 655 2A 4C

Last 4 bytes are not swapped. Here is the interpretation of these data:

  • OSV2 – is a string added by decoder to identify the protocol
  • A – is a sync quartet, it is not to be considered a data
  • 1D20 – is the Oregon device ID (here THGR122NX)
  • 1 – is the channel, values are 1,2,4 for channel 1,2,3
  • 20 – is a rolling and random value changed after each reset
  • 0 – is battery flag, battery is low when flag have bit 2 set (&4)
  • 502 – is reversed BCD temperature value (here 20.5°C)
  • 0 – is temperature sign, here “+”
  • 655 – is reversed BCD humidity value (here 55.6%)
  • 2A – is a quartet checksum starting at deviceID
  • 4C – is the crc value starting at deviceID

Validate the received frame with CRC & CheckSum

  • CheckSum is calculated on the following sub string
1D20 1 20 0 502 0 655

The result is the sum of each quartet (1+D+2+0+1+2+0+0+5+0+2+0+6+5+5) = 42 = 0X2A.

  • CRC is a CRC8 calculated with init 0x43 and CrcPoly 0x07. The CRC is taking into consideration not all the fields : rolling id avoid in the computation. so it is based on :
1D20 1 0 502 0 655

As the number of quartet is odd, the program add a CRC8 quartet computation at the end.

This CRC is working correctly on this device, but on a second sensor THGN132N this is not working. That’s why the check has been removed in the code.

See attached Sensor class for details

Exposing the values

The last part is to extract the values from the received data. The included library manage 3 type of sensors :

  • THGR122NX
  • THGN132N
  • THN132N

The decoding process looks like this :

 bool OregonSensorV2::decode_THN132N(char * pt) {

                char channel; int ichannel;                             // values 1,2,4
                char rolling[3]; int irolling;
                char battery; int ibattery;                             // value & 0x4
                char temp[4]; double dtemp;                             // Temp in BCD
                char tempS;     int itempS;                                     // Sign 0 = positif
                char checksum[3]; int ichecksum;
                int  len = strlen(pt);

                if ( len == 16 ) {
                        channel = pt[4];
                        rolling[0]=pt[7]; rolling[1]=pt[6]; rolling[2]='\0';
                        battery = pt[9];
                        temp[0] = pt[10] ; temp[1] = pt[11] ; 
                         temp[2] = pt[8] ; temp[3] = '\0';
                        tempS = pt[13];
                        checksum[0] = pt[15];   checksum[1] = pt[12]; 
                         checksum[2] = '\0';

                        // Conversion to int value
                        ichannel = getIntFromChar(channel);
                        irolling = getIntFromString(rolling);
                        ibattery = getIntFromChar(battery) & 0x04;
                        itempS = getIntFromChar(tempS) & 0x08;
                        ichecksum = getIntFromString(checksum);
                        dtemp = getDoubleFromString(temp);

                        // Check SUM
                        int _sum = getIntFromChar(pt[0]);
                        for ( int i = 2 ; i <= 11 ; i++ ) _sum += getIntFromChar(pt[i]);
                        _sum += getIntFromChar(pt[13]);

The full decoding class can be downloaded here :

Sensor

65 thoughts on “Oregon Scientific sensors with Raspberry PI

  1. Hi,
    Thanks for your great job Dave.
    I managed to get it work on my RPi B 2.
    I dream that someone can adapt this to a jeedom plugin or domoticz 😉
    Another question, which kind of antenna do you use for RF 433 cheap module (433MHz-Superheterodyne-3400-RF-Transmitter-and-Receiver-link).
    I got this one from ebay but I only have a 1 meter signal range with a simple wire plugged to the antenna pin.
    Thanks in advance for your help.
    Regards

  2. Something to keep in mind:

    I’m testing this with a THN802 Oregon Temperature Sensor which doesn’t send Humidity. It runs on OoK OSV3 but the data length isn’t 80 bits long. I notice in the RcOok Class that for the V3 pulse function, the function expects 80 bits of data to enable the OokAvailable flag. This is not the case for the THN802 sensor which doesn’t send Humidity data, so the length for this sensor is 68 bits (the BCD values are 12 bits long, so 12 bits less in this case).

    Someone might come across this too, with sensors that have more or less data.

  3. Hi Paul, thank you for posting up this detail.
    I’ve implemented this on an RPI2 (well, I got it from github OregonPi, looks the same though), and can successfully read from a OS sensor:
    OSV2 1A2D103700500C05319C
    The sensor is on it’s way out (as you may be able to tell), I’m wondering if you have been able to successfully encode any of these sensors (probably in C++, similar to your above work)? I’m trying to perform it in Python, but from the transmitted waveform, it looks like I’m running into hardware issues, where during transmission the IO misses a few beats, which corrupts the signal. Have you had any luck at this? Happy to share this python code if you want to bounce some ideas.
    Thanks in advance,
    -Matthew

    • Following up on my note; I remembered how I accomplished this for other RF devices (powerpoints, fans) and the workaround was to send the code multiple times (I’m told this is also called “repetition coding”) as the hardware doesn’t form the signal properly every time. So using your decoder, I was able to successfully read the code sent from the RPi [finally!]. I sent it around 20 times (that’s 20 pairs of code, as each code is sent twice with a 10.9ms spacing), and received it 4 times. This send/receive ratio didn’t change by much over testing this in multiple ways (eg. changing channel, changing temp/humidity/rolling code), but right now I can only assume it is indicative of the very cheap RF transmitter I’ve bought, as I know (and can see) the GPIO can run much faster than 1024Hz without losing quality.
      Best regards,
      -Matthew

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.