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

This entry was posted in Hardware and tagged , , . Bookmark the permalink.

65 Responses to Oregon Scientific sensors with Raspberry PI

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.