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.