mercoledì 1 marzo 2017

/* 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