WeatherDuino Forum
Oregon to WeatherDuino gateway - Printable Version

+- WeatherDuino Forum (https://www.meteocercal.info/forum)
+-- Forum: Weather Stations - WeatherDuino Pro2 Line (https://www.meteocercal.info/forum/forum-4.html)
+--- Forum: General Talking (https://www.meteocercal.info/forum/forum-23.html)
+--- Thread: Oregon to WeatherDuino gateway (/thread-437.html)



Oregon to WeatherDuino gateway - laulau - 24-12-2015

Hi,
I assembled pieces of code that could be named "OregonDuino".
This is based on some code found on the internet (decoding Oregon V2 RF protocol) and "send_SensorID0 ()" code of the WD TX software.
My code starts from here :
Arduino intercepting Oregon sensor
This allows me with an Arduino, a transmitter and a receiver to send the data from my Oregon additional sensors to WeatherDuino.
The code takes about 48% of an arduino memory, i think it's not possible to add that in the WD RX software even without the transmit part of the code but i'm not a programing expert.
There is also the display/debug part which can be removed.
Only V2 protocol is included, checksum fonction isn't yet.
The other problem i can see is after a battery change the Oregon sensors change their Ids and the soft is based on the sensor ID to manage many identical sensor (with same identification code).
If you only have one sensor you can do without the ID selection. On some extra temp/hum sensor (THGR228)you can select the channel, this allow three sensors based on their channels without detecting their Ids.
I hope my explanation is understandable.

If you have any questions, I'll try to answer them.

Laurent


RE: Oregon to WeatherDuino gateway - laulau - 15-04-2016

Hi,

Hardware is very basic: Arduino Nanao, Rx module, Tx module. Same as in WD project.

RX data connected to D3, RX led (with apropriate resistor) connected to D4, TX data connected to A0.
proto board :
   

Only Oregon V2 RF protocol is decoded (not all sensors).
OD soft:
Code:
// Oregon V2 decoder modfied - Olivier Lebrun
// Oregon V2 decoder added - Dominique Pierre
// New code to decode OOK signals from weather sensors, etc.
// 2010-04-11 <jcw@equi4.com> http://opensource.org/licenses/mit-license.php
// $Id: ookDecoder.pde 5331 2010-04-17 10:45:17Z jcw $
#include <Wire.h>
//#include <LiquidCrystal.h>
#include <LiquidCrystal_I2C.h>    // Library for LCD

#include <VirtualWire.h>        // library for RF RX/TX
//LiquidCrystal lcd(15, 14, 7, 6, 5, 4);
//LiquidCrystal_I2C lcd(0x27, 2, 1, 0, 4, 5, 6, 7, 3, POSITIVE);  // Set the LCD I2C address
LiquidCrystal_I2C lcd(0x27, 20, 4);

#define StationID  0xA1
char DataPacket[32];
int T_Out;
int H_Out;
bool new_data;
int GustNow=-999;
int AverageNow=-999;
int DirectionNow=-999;
int BarometerNow=-999;
int WindBat=100;
int RainRateNow, RainBat;
int RainTotal = -1;
const char ExtraSensors[6][9]= { "Shirla:","Etage:","Chamb.:","Cave:","Cellier:","Ext.:" };
//const byte ExtraSensor[5][2] = ExtraSensors;
int T_Ext[10];
int H_Ext[10];
byte count = 0;
unsigned long last_UpdateDisplay;
    
class DecodeOOK {
protected:
    byte total_bits, Min_bits, bits, flip, state, pos, data[25];

    virtual char decode (word width) =0;

public:

    enum { UNKNOWN, T0, T1, T2, T3, OK, DONE };

    DecodeOOK () { resetDecoder(); }

    bool nextPulse (word width) {
        if (state != DONE)

            switch (decode(width)) {
                case -1: resetDecoder(); break;
                case 1:  done(); break;
            }
        return isDone();
    }

    bool isDone () const { return state == DONE; }

    const byte* getData (byte& count) const {
        count = pos;
        return data;
    }

    void resetDecoder () {
        total_bits = bits = pos = flip = 0;
        state = UNKNOWN;
    }

    // add one bit to the packet data buffer

    virtual void gotBit (char value) {
        total_bits++;
        byte *ptr = data + pos;
        *ptr = (*ptr >> 1) | (value << 7);

        if (++bits >= 8) {
            bits = 0;
            if (++pos >= sizeof data) {
                resetDecoder();
                return;
            }
        }
        state = OK;
    }

    // store a bit using Manchester encoding
    void manchester (char value) {
        flip ^= value; // manchester code, long pulse flips the bit
        gotBit(flip);
    }

    // move bits to the front so that all the bits are aligned to the end
    void alignTail (byte max =0) {
        // align bits
        if (bits != 0) {
            data[pos] >>= 8 - bits;
            for (byte i = 0; i < pos; ++i)
                data[i] = (data[i] >> bits) | (data[i+1] << (8 - bits));
            bits = 0;
        }
        // optionally shift bytes down if there are too many of 'em
        if (max > 0 && pos > max) {
            byte n = pos - max;
            pos = max;
            for (byte i = 0; i < pos; ++i)
                data[i] = data[i+n];
        }
    }

    void reverseBits () {
        for (byte i = 0; i < pos; ++i) {
            byte b = data[i];
            for (byte j = 0; j < 8; ++j) {
                data[i] = (data[i] << 1) | (b & 1);
                b >>= 1;
            }
        }
    }

    void reverseNibbles () {
        for (byte i = 0; i < pos; ++i)
            data[i] = (data[i] << 4) | (data[i] >> 4);
    }

    void done () {
        while (bits)
            gotBit(0); // padding
        state = DONE;
    }
};

class OregonDecoderV2 :
public DecodeOOK {
public:
  OregonDecoderV2() {
    Min_bits = 160;
  }

  // add one bit to the packet data buffer
  virtual void gotBit (char value) {
    if(!(total_bits & 0x01))
    {
      data[pos] = (data[pos] >> 1) | (value ? 0x80 : 00);
    }
    total_bits++;
    pos = total_bits >> 4;
    if(2 == pos)
    {
      // 80 * 2
      Min_bits = 160;
      if(0xEA == data[0])  Min_bits = 136;
      if(0x5A == data[0])  Min_bits = 176;
      //if(0x2A == data[0])  Min_bits = 168;
    }
    if (pos >= sizeof data) {
      resetDecoder();
      return;
    }
    state = OK;
  }

  virtual char decode (word width) {
    if (200 <= width && width < 1200) {
      byte w = width >= 700;
      switch (state) {
      case UNKNOWN:
        if (w != 0) {
          // Long pulse
          ++flip;
        }
        else if (16 <= flip) {
          // Short pulse, start bit
          flip = 0;
          state = T0;
        }
        else {
          // Reset decoder
          return -1;
        }
        break;
      case OK:
        if (w == 0) {
          // Short pulse
          state = T0;
        }
        else {
          // Long pulse
          manchester(1);
        }
        break;
      case T0:
        if (w == 0) {
          // Second short pulse
          manchester(0);
        }
        else {
          // Reset decoder
          return -1;
        }
        break;
      }
    }
    else {
      return -1;
    }
    return total_bits == Min_bits ? 1: 0;
  }
};

OregonDecoderV2 orscV2;

volatile word pulse;

void ext_int_1(void)
{
    static word last;
    // determine the pulse length in microseconds, for either polarity
    pulse = micros() - last;
    last += pulse;
}
float temperature(const byte* data)
{
    int sign = (data[6]&0x8) ? -1 : 1;
    float temp = ((data[5]&0xF0) >> 4)*10 + (data[5]&0xF) + (float)(((data[4]&0xF0) >> 4) / 10.0);
    return sign * temp;
}

byte humidity(const byte* data)
{
    return (data[7]&0xF) * 10 + ((data[6]&0xF0) >> 4);
}

int barometer(const byte* data)
{
    return (data[8])+856+28;//28=correction altitude
}

// Ne retourne qu'un apercu de l'etat de la baterie : 10 = faible
byte battery(const byte* data)
{
    return (data[4] & 0x4) ? 10 : 90;
}

byte channel(const byte* data)
{
    byte channel;
    switch (data[2])
    {
        case 0x10:
            channel = 1;
            break;
        case 0x20:
            channel = 2;
            break;
        case 0x40:
            channel = 3;
            break;
     }

     return channel;
}


void reportSerial_new (const char* s, class DecodeOOK& decoder)
{
    byte bid = 0;
    byte bid1 = 0;
    byte PacketID, UnitID;

    
    byte pos;
    const byte* data = decoder.getData(pos);
    Serial.print(s);
    Serial.print(' ');
    for (byte i = 0; i < pos; ++i) {
        Serial.print(data[i] >> 4, HEX);
        Serial.print(data[i] & 0x0F, HEX);
        
    }
    Serial.println();

          UnitID = 2;
          PacketID = UnitID << 4 | 1;   // Hi nibble UnitID, Lo nibble SensorID
  
  
  

  
    
     // Inside Temp-Hygro : THGR228N,...
    if(data[0] == 0x1A && data[1] == 0x2D)
    {

      switch (data[3])
      {
        case 0xA3:
        {
          T_Ext[0] = int(temperature(data) * 100.0);
          H_Ext[0] = int(humidity(data) * 100.0);
        
         sendData(StationID, PacketID, T_Ext[0], H_Ext[0], 3, 0);

          Serial.print("Shirla, Id:");          
          break;
        }
        case 0xE4:
        {
          T_Ext[1] = int(temperature(data) * 100.0);
          H_Ext[1] = int(humidity(data) * 100.0);
          
          Serial.print("Etage, Id:");          
          break;
        }
        case 0xF1:
        {
          T_Ext[2] = int(temperature(data) * 100.0);
          H_Ext[2] = int(humidity(data) * 100.0);
          sendData(StationID, PacketID, T_Ext[2], H_Ext[2], 2, 0);
          Serial.print("Chambre, Id:");          
          break;
        }
        case 0x32:
        {
          T_Ext[3] = int(temperature(data) * 100.0);
          H_Ext[3] = int(humidity(data) * 100.0);

        
         sendData(StationID, PacketID, T_Ext[3], H_Ext[3], 0, 0);
          Serial.print("Cave, Id:");          
          break;
        }
        case 0x7D:
        {
          T_Ext[4] = int(temperature(data) * 100.0);
          H_Ext[4] = int(humidity(data) * 100.0);
          sendData(StationID, PacketID, T_Ext[4], H_Ext[4], 1, 0);

          Serial.print("Cellier, Id:");          
          break;
        }
        case 0x45:
        {
          T_Ext[5] = int(temperature(data) * 100.0);
          H_Ext[5] = int(humidity(data) * 100.0);

          Serial.print("Extérieur, Id:");          
          break;
        }
        case 0xC3:
        {
          T_Ext[6] = int(temperature(data) * 100.0);
          H_Ext[6] = int(humidity(data) * 100.0);
          sendData(StationID, PacketID, T_Ext[6], H_Ext[6], 4, 0);

          Serial.print("Soute, Id:");          
          break;
        }
        
      }

        
      
       //Serial.print("[THGR228N,...] Id:");
       Serial.print(data[3], HEX);
       Serial.print(" ,Channel:");
       Serial.print(channel(data));
       Serial.print(" ,temp:");
       Serial.print(temperature(data));
       Serial.print(" ,hum:");
       Serial.print(humidity(data));
       Serial.print(" ,bat:");
       Serial.print(battery(data));
      

        new_data = true;

        Serial.println();
      
    }
    
     // inside Temp-Hygro-Baro : BTHR918
     else if (data[0] == 0x5A && data[1] == 0x6D)
    {

       Serial.print("Rdc, Id:");
       //Serial.print("[BTHR918,...] Id:");
       Serial.print(data[3], HEX);
       Serial.print(" ,Channel:");
       Serial.print(channel(data));
       Serial.print(" ,temp:");
       Serial.print(temperature(data));
       Serial.print(" ,hum:");
       Serial.print(humidity(data));
       Serial.print(" ,baro:");
       Serial.print(barometer(data));
       Serial.print(" ,bat:");
       Serial.print(battery(data));
       Serial.println();
      
       T_Ext[7] = int(temperature(data) * 100.0);
       H_Ext[7] = int(humidity(data) * 100.0);
       BarometerNow = barometer(data);
       new_data = true;

    }
    // outside Temp-Hygro : THGR918,...
    else if(data[0] == 0x1A && data[1] == 0x3D)
    {
       Serial.print("EXT Id:");
       Serial.print(data[3], HEX);
       Serial.print(" ,Channel:");
       Serial.print(channel(data));
       Serial.print(" ,temp:");
       Serial.print(temperature(data));
       Serial.print(" ,hum:");
       Serial.print(humidity(data));
       Serial.print(" ,bat:");
       Serial.print(battery(data));
       Serial.println();
       T_Out = int(temperature(data) * 100.0);
       H_Out = int(humidity(data) * 100.0);
    }
     // Rain
    else if(data[0] == 0x2A && data[1] == 0x1D)
    {
       bid = 8;
       Serial.print("[RGR918,...] Id:");
       Serial.print(data[3], HEX);
       Serial.print(" Rr:");
       RainRateNow = ((data[5]>>4) * 100)  + ((data[5] & 0x0F) * 10) + (data[4] >> 4);
       Serial.print(RainRateNow);
       RainTotal = ((data[8] & 0x0F) * 1000)+ ((data[7]  >> 4) * 100)  + ((data[7] & 0x0F) * 10)+ (data[6]>>4);
       Serial.print(" ,To:");
       Serial.print(RainTotal);
       Serial.print("mm ,bat:");
       RainBat=battery(data);
       Serial.print(RainBat);
       Serial.println();
    }
    // Wind
    else if(data[0] == 0x3A && data[1] == 0x0D)
    {

       Serial.print("Vent Id:");
       Serial.print(data[3], HEX);
      Serial.print(" Direction ");
      DirectionNow = ((data[5]>>4) * 100)  + ((data[5] & 0x0F) * 10) + (data[4] >> 4);    
      Serial.print(DirectionNow);
      Serial.print(" degrees  Rafale:");
      GustNow = ((data[7] & 0x0F) * 100)  + ((data[6]>>4) * 10)  + ((data[6] & 0x0F)) ;
      Serial.print(float(GustNow)/10,1);  
      Serial.print("m/s  Moyenne:");
      AverageNow = ((data[8]>>4) * 100)  + ((data[8] & 0x0F) * 10)+((data[7] >>4)) ;      
      Serial.print(float(AverageNow)/10,1);
      Serial.print("m/s  Bat:");      
      WindBat=battery(data);
      Serial.print(WindBat);
      //Serial.println("%");
       Serial.println();
    }
    // THN132
    else if(data[0] == 0xEA && data[1] == 0x4C)
    {

    switch (data[3])
      {
        case 0xBB:
        {
          T_Ext[8] = int(temperature(data) * 100.0);
        
        
         //sendData(StationID, PacketID, T_Ext[8], 0, 7, 0);

          Serial.print("Congel, Id:");          
          break;
        }
     }

        
      
       Serial.print("[THN132,...] Id:");
       Serial.print(data[3], HEX);
       Serial.print(" ,Channel:");
       Serial.print(channel(data));
       Serial.print(" ,temp:");
       Serial.print(temperature(data));

       Serial.print(" ,bat:");
       Serial.print(battery(data));
      

        

        Serial.println();
  }

    decoder.resetDecoder();
}


// -----------------------------------------------------------------------------------------
// -------- Function to send data to RF Module ---------------------------------------------

void sendData (int data0, int data1, long data2, long data3, long data4, long data5)
{
  memset(DataPacket,0,sizeof(DataPacket));        // clean str array
  sprintf(DataPacket, "%d,%d,%ld,%ld,%ld,%ld", data0, data1, data2, data3, data4, data5);

  PORTC |= B10;  //digitalWrite(TX_Power_PIN, HIGH); - Turn on power from TX module
  delay(50);                                     // Wait for power to stabilize
  vw_send((uint8_t *)DataPacket, strlen(DataPacket));
  vw_wait_tx();
}
// ---------------------- Update Display -------------------------
void Update_Display()
{
   byte PacketID, UnitID;
   lcd.setCursor(0, 0);
   lcd.print("                ");
   lcd.setCursor(0, 0);
   lcd.print(ExtraSensors[count]);
   lcd.print(T_Ext[count]/100.0,1);
   lcd.write(B11011111);
   lcd.print(";");
   lcd.print(H_Ext[count]/100);

  switch (count){
    


    case 0 :
    {
       lcd.setCursor(0, 1);
       lcd.print("                ");
       lcd.setCursor(0, 1);
       lcd.print("Out:");
       lcd.print(T_Out/100.0,1);
       lcd.write(B11011111);
       lcd.print(";");
       lcd.print(H_Out/100);
       break;
    }
        case 1 :
    {
       lcd.setCursor(0, 1);
       lcd.print("                ");
       lcd.setCursor(0, 1);
       lcd.print("Pression:");
       lcd.print(BarometerNow,1);

       break;
    }
    case 2 :
    {

      
       lcd.setCursor(0, 1);
       lcd.print("                ");
       lcd.setCursor(0, 1);
       lcd.print("Vent:");
       lcd.print(DirectionNow);
       lcd.print(";");
       lcd.print(GustNow/10.0,1);
       lcd.print("/");
       lcd.print(AverageNow/10.0,1);
       lcd.print(" m/s");      
       break;
    }
    case 3 :
    {
       lcd.setCursor(0, 1);
       lcd.print("                ");
       lcd.setCursor(0, 1);
       lcd.print("Pl.:");
       if (RainTotal != -1){
        lcd.print(RainTotal);
        lcd.print("mm; ");
        lcd.print(RainRateNow);        
       }
      else{
        lcd.print("NADA");
      }
      
       break;
    }
    
    case 5 :
    {

       break;
    }
  }
  count ++;
  if (count > 4) count = 0;
  last_UpdateDisplay = millis();
}

// ---------------------------------------------------------------
void setup ()
{
    Serial.begin(115200);
    Serial.println("\n[ookDecoder]");
    attachInterrupt(1, ext_int_1, CHANGE); // 0=>pin2  1=> pin3

    //------------------------------------------------------------
    // Setup the transmitter
    //------------------------------------------------------------
    //DDRC = DDRC | B11;      // pinMode(TX_Power_PIN, OUTPUT); & pinMode(TX_PIN, OUTPUT);
    pinMode(14, OUTPUT);
    pinMode(4, OUTPUT);
    vw_set_ptt_pin(4);     // Transmitter ptt connected to pin A1
    vw_set_tx_pin(14);      // Transmitter connected to pin A0
    

    vw_setup(1000);           // Bits per second
    //------------------------------------------------------------


    lcd.begin();
    lcd.print("Coucou");
    delay(1000);
    lcd.setCursor(0, 0);
    lcd.print("          ");

}

void loop () {
    static int i = 0;
    cli();
    word p = pulse;

    pulse = 0;
    sei();

    if (p != 0)
    {
        if (orscV2.nextPulse(p))
            reportSerial_new("OSV2", orscV2);  
    }
    
  //if (millis() - last_UpdateDisplay > 4000)
    //Update_Display();

}

Only Temp/hum sensors can be send to WeatherDuino

In
reportSerial_new subroutine you have select which sensor type and ID you like to send to WD. The function call is :


sendData(StationID, PacketID, Temp, Hum, Extra_sens_N, 0);

StationID is basically 0xA1
PacketID = UnitID << 4 | 1; // Hi nibble UnitID, Lo nibble SensorID
UnitID should be an unused TX ID (<=3)
Lo nibble = 1 = SensorID (not used in WD RX software)
0 < Extra_sens_N < 5 (N°-1 of extra sensor in CMX)


To see the OS V2 sensors that are decoded, just use a serial monitoring software (115200 bauds). you have to retain the ID of the sensors you want to send to WeatherDuino.
Serial output :
   
highlighted is a new OS temp sensor not yet recognised by the software.
that happens when battery is changed.

WD RX software modification
I use SensorID 1 in RX software to write the T° and RH % in loopdata to have Extra sensor 1 to 5 available in Cumulus.
V2.00 RX_TX.ino modification :
~line 95
change
Code:
#else
  else if (SensorID == 2 && UnitID == WIND_OutUnit )
#endif
to :
Code:
#else
// LM Oregon sensor to loopdata extra 1-5 sensors for Cumulus
  else if (SensorID == 1)
  {  
    //-------------- Process data from SensorID 1 --------------------------------------

    byte ExtraSensorNum                      = RX_Data[4];
    if (ExtraSensorNum < 5) {
        if (RX_Data[3] <= 9990) loopData.extraHumidities[ExtraSensorNum]   = RX_Data[3] / 10 * 0.1;
        else loopData.extraHumidities[ExtraSensorNum] = 999;

        loopData.extraTemperatures[ExtraSensorNum] = (RX_Data[2] / 10 * 0.18)  + 122.5;
        
        #if Relay_Data == 1
          sendData(Relay_ID, 140 + ExtraSensorNum, RX_Data[2] / 10, RX_Data[3] / 10, 0, 0);
        #endif  
    }

  } // -------- End processing Sensors ID1 ------------------------------------  

//LM
  else if (SensorID == 2 && UnitID == WIND_OutUnit )
#endif

No modification in config_RX.h required

Result in CMX
   

Laurent


RE: Oregon to WeatherDuino gateway - laulau - 21-05-2016

Hi,
On a RX test board i managed to receive the Oregon sensors from my old WMR928NX station.
A little hardware modification is needed for the receiver input pin that use an interrupt to decode OS RF packets.
No mix (Oregon and WD TX) is possible for the moment.
don't hesitate to contact me for more information.
Laurent


RE: Oregon to WeatherDuino gateway - werk_ag - 21-05-2016

Good work Laurent.
Feel free to use the forum to talk about it.