/* Excelvan Weather Station
* Nibbles :
* AAA598EF00703E0460190B
* 0011223344556677889900 Bytes
* PPPPDDLL?TTTHHRRRWWDCC
* 0123456789012345678901 Nibbles
* 0 1 2
* P = Prolog
* D = Device
* LL = Rolling code (used to select hash & checksum
* T = Temp 4 nibbles 2's complement (FF51) in 0.1 C steps
* H = Humidity 2 Nibbles
* R = Rain. Cumulative No. of buckets, 1 bucket = 0.79mm
* W = Wind speed (KM)
* D = Wind direction :
* 0 N
* 1 NNE
* 2 NE
* 3 ENE
* 4 E
* 5 ESE
* 6 SE
* 7 SSE
* 8 S
* 9 SSW
* A SW
* B WSW
* C W
* D WNW
* E NW
* F NNW
* C = Checksum
*
* AAA598190FF05421F04105,OK
*T:408.0 = -1.6
*H:83
*R:0
*W:3
*D:S
*TI:14.1
*P:973
*S:0
*/
// Arduino/Genuino Uno
#include <EEPROM.h>
#include <Wire.h>
#define DATAPIN 4 // GPIO4 D2
#define SWTHRIDLE 0
#define SWTHRGO 1
#define SWTHRDONE 2
// Timings
#define LONGPULSE 1000
#define SHORTPULSE 500
#define PULSEMARGIN 150
#define STARTPULSE 5000
#define STARTMARGIN 500
// EEPROM
#define store_rain 0
#define store_devid 4
unsigned long lastTime = micros();
int lastT = 0;
#define NOTDEF 0XFFFF
byte weatherState = SWTHRIDLE;
unsigned long delayTime = 0;
unsigned long histTime = 0;
byte weatherIndex = 0; // Bit Index
byte intNo = DATAPIN; // Interrupt No.
unsigned int lastRain = NOTDEF;
unsigned long seq = 0;
#define NO_BYTES 11 // 11 bytes (10 bytes data, 1 byte checksum)
#define NO_BITS (NO_BYTES*8)
byte bits[NO_BYTES]; // = {0XAA, 0XA5, 0X98, 0X5B, 0X00, 0XE0, 0X41, 0X00, 0X00, 0X00, 0X3D};
const byte hash[32] = { // Upper nibble of rolling code
0XB7, 0X3F, // 0
0XA6, 0X2E, // 1
0X95, 0X1D, // 2
0X84, 0X0C, // 3
0XF3, 0X7B, // 4
0XE2, 0X6A, // 5
0XD1, 0X59, // 6
0XC0, 0X48, // 7
0X3F, 0XB7, // 8
0X2E, 0XA6, // 9
0X1D, 0X95, // A
0X0C, 0X84, // B
0X7B, 0XF3, // C
0X6A, 0XE2, // D
0X59, 0XD1, // E
0X48, 0XC0 // F
};
const char* dirs[] = {"N","NNE","NE","ENE","E","ESE","SE","SSE","S","SSW","SW","WSW","W","WNW","NW","NNW"};
// BMP085
#define BMP085_ADDRESS 0x77 // I2C address of BMP085
const unsigned char OSS = 0; // Oversampling Setting
// Calibration values
int ac1;
int ac2;
int ac3;
unsigned int ac4;
unsigned int ac5;
unsigned int ac6;
int b1;
int b2;
int mb;
int mc;
int md;
// b5 is calculated in bmp085GetTemperature(...), this variable is also used in bmp085GetPressure(...)
// so ...Temperature(...) must be called before ...Pressure(...).
long b5;
float temperature;
float pressure;
byte nibble_get(const byte a[], int n)
{
int i = n / 2;
byte b = a[i];
if (n & 1)
{
return b & 0X0F;
}
return (b >> 4) & 0X0F;
}
void bit_set(byte a[] ,unsigned int n, byte b)
{
if (b == 0)
{
int m = 0xFF7F;
a[n/8] &= (m >> (n % 8)) ;
}
else
{
byte m = 0x80;
a[n/8] |= (m >> (n % 8)) ;
}
}
bool is_set(const byte a[] ,unsigned int n)
{
byte m = 0x80;
return (a[n/8] & (m >> (n % 8))) != 0;
}
// Interrupt Request Handler
void irh()
{
// Calculate time from last change
long time = micros();
int t = time - lastTime;
lastTime = time;
if (digitalRead(DATAPIN) == HIGH) // Was low
{
t = 0 - t;
}
// Avoid lost interrupts
if ((lastT < 0 && t < 0) || (lastT > 0 && t > 0))
{
weatherIndex = 0;
lastT = t;
return;
}
lastT = t;
decodeWeather(t);
}
void decodeWeather(int t)
{
// Look for start
if (t > 0-(STARTPULSE + STARTMARGIN) && t < 0-(STARTPULSE - STARTMARGIN))
{
// Start
weatherState = SWTHRGO;
weatherIndex = 0;
return;
}
if (weatherState == SWTHRGO)
{
if (t < 0-(LONGPULSE - PULSEMARGIN) && t > 0-(LONGPULSE + PULSEMARGIN))
{
// Long Low Pulse
return;
}
if (t < 0-(SHORTPULSE - PULSEMARGIN) && t > 0-(SHORTPULSE + PULSEMARGIN))
{
// Short Low pulse
return;
}
int oldIndex = weatherIndex;
if (t > (LONGPULSE - PULSEMARGIN) && t < (LONGPULSE + PULSEMARGIN))
{
// Long High Pulse
bit_set(bits, weatherIndex++, 1);
}
if (t > (SHORTPULSE - PULSEMARGIN) && t < (SHORTPULSE + PULSEMARGIN))
{
// Short High pulse
bit_set(bits, weatherIndex++, 0);
}
// Check if inserted bit
if (oldIndex != weatherIndex)
{
// Check if first two bytes are AA A5 (leader)
if (weatherIndex == 16)
{
if (bits[0] != 0XAA ||
bits[1] != 0XA5)
{
// Invalid, start again
weatherIndex = 0;
weatherState = SWTHRIDLE;
return;
}
}
// Check if complete message received
if (weatherIndex >= NO_BITS)
{
weatherState = SWTHRDONE;
detachInterrupt(intNo);
}
return;
}
}
// Invalid, start again
weatherIndex = 0;
weatherState = SWTHRIDLE;
return;
}
void setup() {
pinMode(BUILTIN_LED, OUTPUT);
intNo = digitalPinToInterrupt(DATAPIN);
digitalWrite(BUILTIN_LED,LOW);
Serial.begin(2400);
Serial.println("Started.");
pinMode(DATAPIN, INPUT);
weatherState = SWTHRIDLE;
weatherIndex = 0;
seq = 0;
// Get rain for use in power up
EEPROM.get(store_rain, lastRain);
attachInterrupt(intNo, irh, CHANGE);
}
void loop() {
if (weatherState == SWTHRDONE)
{
// Disable interrupt
detachInterrupt(intNo);
// loop over buffer data
#if 0
for(unsigned int i=0; i < weatherIndex; i++)
{
if (is_set(bits, i))
{
Serial.print("1");
}
else
{
Serial.print("0");
}
}
Serial.print(" ");
#endif
// data[3] = rolling code
byte hashIndex = ((bits[3] >> 2) & 0X3C); // Upper nibble rolling code * 4
byte chk = (bits[3] & 0X0F) ^ 0X07; // Lower nibble rolling code
for (int m = 4; m < NO_BYTES - 1; m++)
{
byte b = bits[m];
chk ^= b;
// every other byte
if ((m & 1) == 0)
{
byte t = (((0 - b) << 4) & 0XF0);
chk ^= t;
switch (b & 0X0F)
{
case 0X05:
case 0X0D:
chk ^= nibble_get(hash, hashIndex + 0) << 4;
break;
case 0X02:
case 0X03:
case 0X0A:
case 0X0B :
chk ^= nibble_get(hash, hashIndex + 1) << 4;
break;
case 0X01:
case 0X09:
chk ^= nibble_get(hash, hashIndex + 2) << 4;
break;
default:
chk ^= nibble_get(hash, hashIndex + 3) << 4 ;
break;
}
}
}
byte n = weatherIndex / 4;
for(byte b = 0; b < n; ++b)
{
byte nibble = nibble_get(bits, b);
Serial.print(nibble,HEX);
}
if (chk != bits[10])
{
weatherIndex = 0;
Serial.println(",NOK");
}
else
{
weatherIndex = 0;
Serial.println(",OK");
/*
* 0 1 2 3 4 5 6 7 8 9 0 Byte (bits)
* PPPPDDLLTTTTHHRRRWWDCC
* 0123456789012345678901 Nibble
* 0 1 2
*
*/
byte devid = bits[2];
byte hum = bits[6];
unsigned int rain = (bits[7] << 4) | nibble_get(bits, 16);
byte wind = (nibble_get(bits, 17) << 4) | nibble_get(bits, 18);
byte dir = nibble_get(bits, 19);
int temp = (nibble_get(bits, 9) << 8) | bits[5];
// Extend sign
if (temp & 0X800)
{
temp |= 0XFFFFF000;
}
// If new devid then reset store_rain
byte tmpDevid;
EEPROM.get(store_devid, tmpDevid);
if (devid != tmpDevid)
{
EEPROM.put(store_devid, devid);
lastRain = 0;
}
// Max humidity is 99%
if (hum > 99)
{
hum = 99;
}
// calculate difference & check for overflow
unsigned int rainDelta = rain >= lastRain ? rain - lastRain : rain + (0XFFF - lastRain);
lastRain = rain;
if (rainDelta > 0)
{
// Save rain for use in power up
EEPROM.put(store_rain, lastRain);
}
Serial.print("T:");
if (temp < 0)
{
Serial.print("-");
temp = 0 - temp;
}
Serial.print(temp/10);
Serial.print(".");
Serial.println(temp % 10);
Serial.print("H:");
Serial.println(hum);
Serial.print("R:");
Serial.println(rainDelta);
Serial.print("W:");
Serial.println(wind);
Serial.print("D:");
Serial.println(dirs[dir]);
delay(1000);
// BMP085
// bmp085Calibration();
// getStuff();
// printStuff();
Serial.print("S:");
Serial.println(seq++);
}
// re-enable interrupt
lastTime = micros();
attachInterrupt(intNo, irh, CHANGE);
weatherState = SWTHRIDLE;
return;
}
}
void printStuff()
{
Serial.print("TI:");
Serial.println(temperature, 1);
Serial.print("P:");
Serial.println(pressure/100.0, 0);
}
void getStuff()
{
temperature = bmp085GetTemperature(bmp085ReadUT());
pressure = bmp085GetPressure(bmp085ReadUP());
}
// Stores all of the bmp085's calibration values into global variables
// Calibration values are required to calculate temp and pressure
// This function should be called at the beginning of the program
void bmp085Calibration()
{
ac1 = bmp085ReadInt(0xAA);
ac2 = bmp085ReadInt(0xAC);
ac3 = bmp085ReadInt(0xAE);
ac4 = bmp085ReadInt(0xB0);
ac5 = bmp085ReadInt(0xB2);
ac6 = bmp085ReadInt(0xB4);
b1 = bmp085ReadInt(0xB6);
b2 = bmp085ReadInt(0xB8);
mb = bmp085ReadInt(0xBA);
mc = bmp085ReadInt(0xBC);
md = bmp085ReadInt(0xBE);
}
// Calculate temperature given ut.
// Value returned will be in units of 0.1 deg C
float bmp085GetTemperature(unsigned int ut)
{
long x1, x2;
x1 = (((long)ut - (long)ac6)*(long)ac5) >> 15;
x2 = ((long)mc << 11)/(x1 + md);
b5 = x1 + x2;
return ((float)((b5 + 8)>>4)) / 10;
}
// Calculate pressure given up
// calibration values must be known
// b5 is also required so bmp085GetTemperature(...) must be called first.
// Value returned will be pressure in units of Pa.
long bmp085GetPressure(unsigned long up)
{
long x1, x2, x3, b3, b6, p;
unsigned long b4, b7;
b6 = b5 - 4000;
// Calculate B3
x1 = (b2 * (b6 * b6)>>12)>>11;
x2 = (ac2 * b6)>>11;
x3 = x1 + x2;
b3 = (((((long)ac1)*4 + x3)<<OSS) + 2)>>2;
// Calculate B4
x1 = (ac3 * b6)>>13;
x2 = (b1 * ((b6 * b6)>>12))>>16;
x3 = ((x1 + x2) + 2)>>2;
b4 = (ac4 * (unsigned long)(x3 + 32768))>>15;
b7 = ((unsigned long)(up - b3) * (50000>>OSS));
if (b7 < 0x80000000)
p = (b7<<1)/b4;
else
p = (b7/b4)<<1;
x1 = (p>>8) * (p>>8);
x1 = (x1 * 3038)>>16;
x2 = (-7357 * p)>>16;
p += (x1 + x2 + 3791)>>4;
return p;
}
// Read 1 byte from the BMP085 at 'address'
char bmp085Read(unsigned char address)
{
unsigned char data;
Wire.beginTransmission(BMP085_ADDRESS);
Wire.write(address);
Wire.endTransmission();
Wire.requestFrom(BMP085_ADDRESS, 1);
while(!Wire.available())
;
return Wire.read();
}
// Read 2 bytes from the BMP085
// First byte will be from 'address'
// Second byte will be from 'address'+1
int bmp085ReadInt(unsigned char address)
{
unsigned char msb, lsb;
Wire.beginTransmission(BMP085_ADDRESS);
Wire.write(address);
Wire.endTransmission();
Wire.requestFrom(BMP085_ADDRESS, 2);
while(Wire.available()<2)
;
msb = Wire.read();
lsb = Wire.read();
return (int) msb<<8 | lsb;
}
// Read the uncompensated temperature value
unsigned int bmp085ReadUT()
{
unsigned int ut;
// Write 0x2E into Register 0xF4
// This requests a temperature reading
Wire.beginTransmission(BMP085_ADDRESS);
Wire.write(0xF4);
Wire.write(0x2E);
Wire.endTransmission();
// Wait at least 4.5ms
delay(5);
// Read two bytes from registers 0xF6 and 0xF7
ut = bmp085ReadInt(0xF6);
return ut;
}
// Read the uncompensated pressure value
unsigned long bmp085ReadUP()
{
unsigned char msb, lsb, xlsb;
unsigned long up = 0;
// Write 0x34+(OSS<<6) into register 0xF4
// Request a pressure reading w/ oversampling setting
Wire.beginTransmission(BMP085_ADDRESS);
Wire.write(0xF4);
Wire.write(0x34 + (OSS<<6));
Wire.endTransmission();
// Wait for conversion, delay time dependent on OSS
delay(2 + (3<<OSS));
// Read register 0xF6 (MSB), 0xF7 (LSB), and 0xF8 (XLSB)
Wire.beginTransmission(BMP085_ADDRESS);
Wire.write(0xF6);
Wire.endTransmission();
Wire.requestFrom(BMP085_ADDRESS, 3);
// Wait for data to become available
while(Wire.available() < 3)
;
msb = Wire.read();
lsb = Wire.read();
xlsb = Wire.read();
up = (((unsigned long) msb << 16) | ((unsigned long) lsb << 8) | (unsigned long) xlsb) >> (8-OSS);
return up;
}
Nessun commento:
Posta un commento