How to get Forecast with BME280 (BMP080/180)

Moderator: leecollings

Post Reply
stlaha2007
Posts: 370
Joined: Monday 05 October 2015 10:16
Target OS: -
Domoticz version:
Contact:

How to get Forecast with BME280 (BMP080/180)

Post by stlaha2007 »

Hi there,

I see several devices with a forecast, like on ForecastIO/WeatherUnderground.

I wonder how to "create" this prediction, so i can modify my python-script to send it together with the other data from this sensor.

Yes i have the json-call correct, it send 0 as in No Info. I want to other values from 1 to 6 (rain/cloudy/partialy cloudy/sunny) pushed.

So i hope one of you know where to find the info or knows how the hardware/devices which do this already can point me into the right direction.


Grtz,
Stephan
alexsh1
Posts: 169
Joined: Wednesday 30 September 2015 11:50
Target OS: Raspberry Pi / ODroid
Domoticz version: v3.8975
Location: United Kingdom
Contact:

Re: How to get Forecast with BME280 (BMP080/180)

Post by alexsh1 »

This is pretty much what I have done with my BMP180 and Mysensors
I have fabricated a node (in the photo) running on two AA batteries and connected BMP180 via I2C. With a credit to some Mysensors guys the sketch below works just fine, which was adopted for my custom built, but with a very minor alternations, the sketch should work on any atmega328 (Arduino Pro Mini)

Have a look at the forecasting algorithm.
You can adopt it for Raspberry Pi.


Code: Select all





 
#include <SPI.h>
#include <MySensor.h>  
#include <Wire.h>
#include <Adafruit_BMP085.h>
#include <avr/power.h>

#define NODE_ADDRESS   5
#define BARO_CHILD 0
#define TEMP_CHILD 1
#define BATT_SENSOR 2 

#define RELEASE "1.2"

const float ALTITUDE = 7; // <-- adapt this value to your own location's altitude.

// Sleep time between reads (in seconds). Do not change this value as the forecast algorithm needs a sample every minute.
const unsigned long SLEEP_TIME = 60000; 
const unsigned long FORCE_TRANSMIT_INTERVAL = SLEEP_TIME;

const char *weather[] = { "stable", "sunny", "cloudy", "unstable", "thunderstorm", "unknown" };
enum FORECAST
{
  STABLE = 0,     // "Stable Weather Pattern"
  SUNNY = 1,      // "Slowly rising Good Weather", "Clear/Sunny "
  CLOUDY = 2,     // "Slowly falling L-Pressure ", "Cloudy/Rain "
  UNSTABLE = 3,   // "Quickly rising H-Press",     "Not Stable"
  THUNDERSTORM = 4, // "Quickly falling L-Press",    "Thunderstorm"
  UNKNOWN = 5     // "Unknown (More Time needed)
};

Adafruit_BMP085 bmp = Adafruit_BMP085();      // Digital Pressure Sensor 
MySensor gw;


// Pins
#define LED_PIN 8


float lastPressure = -1;
float lastTemp = -1;
int lastForecast = -1;

const int LAST_SAMPLES_COUNT = 5;
float lastPressureSamples[LAST_SAMPLES_COUNT];

// this CONVERSION_FACTOR is used to convert from Pa to kPa in forecast algorithm
// get kPa/h be dividing hPa by 10 
#define CONVERSION_FACTOR (1.0/10.0)

int minuteCount = 0;
bool firstRound = true;
// average value is used in forecast algorithm.
float pressureAvg;
// average after 2 hours is used as reference value for the next iteration.
float pressureAvg2;

float dP_dt;
boolean metric;
MyMessage tempMsg(TEMP_CHILD, V_TEMP);
MyMessage pressureMsg(BARO_CHILD, V_PRESSURE);
MyMessage forecastMsg(BARO_CHILD, V_FORECAST);
MyMessage msgBatt(BATT_SENSOR, V_VOLTAGE);

// Global settings
int measureCount = 0;
int sendBattery = 0;
boolean isMetric = true;
boolean highfreq = true;

// Storage of old measurements
float lastTemperature = -100;
int lastHumidity = -100;
long lastBattery = -100;


void setup() 
{
  pinMode(LED_PIN, OUTPUT);
  digitalWrite(LED_PIN, LOW);

  Serial.begin(115200);
  Serial.print(F("Pressure Sensor"));
  Serial.print(RELEASE);
  Serial.flush();

  digitalWrite(LED_PIN, HIGH); 
  
  gw.begin(NULL, NODE_ADDRESS, false);

  // Send the sketch version information to the gateway and Controller
  gw.sendSketchInfo("Pressure Sensor", RELEASE);

  if (!bmp.begin()) 
  {
    Serial.println("Could not find a valid BMP085 sensor, check wiring!");
    while (1) {}
  }

  digitalWrite(LED_PIN, LOW);
  
  // Register sensors to gw (they will be created as child devices)
  gw.present(BARO_CHILD, S_BARO);
  gw.present(TEMP_CHILD, S_TEMP);
  gw.present(BATT_SENSOR, S_POWER);
  
  metric = gw.getConfig().isMetric;
  Serial.print(F("isMetric: ")); Serial.println(isMetric);
  sendBattLevel(false);
}

void loop() 
{

  measureCount ++;
  sendBattery ++;
  bool forceTransmit = false;
  
  if ((measureCount == 5) && highfreq) 
  {
    clock_prescale_set(clock_div_8); // Switch to 1Mhz for the reminder of the sketch, save power.
    highfreq = false;
  } 
  
  if (measureCount > FORCE_TRANSMIT_INTERVAL) { // force a transmission
    forceTransmit = true; 
    measureCount = 0;
  }

  
  float pressure = bmp.readSealevelPressure(ALTITUDE) / 100.0;
  float temperature = bmp.readTemperature();

  if (!metric) 
  {
    // Convert to fahrenheit
    temperature = temperature * 9.0 / 5.0 + 32.0;
  }

  int forecast = sample(pressure);

  Serial.print("Temperature = ");
  Serial.print(temperature);
  Serial.println(metric ? " *C" : " *F");
  Serial.print("Pressure = ");
  Serial.print(pressure);
  Serial.println(" hPa");
  Serial.print("Forecast = ");
  Serial.println(weather[forecast]);


  if (temperature != lastTemp) 
  {
    gw.send(tempMsg.set(temperature, 1));
    lastTemp = temperature;
  }

  if (pressure != lastPressure) 
  {
    gw.send(pressureMsg.set(pressure, 0));
    lastPressure = pressure;
  }

  if (forecast != lastForecast)
  {
    gw.send(forecastMsg.set(weather[forecast]));
    lastForecast = forecast;
  }

  digitalWrite(LED_PIN, HIGH);
  delay(50); 
  digitalWrite(LED_PIN, LOW); 


  if (sendBattery > 60) 
  {
     sendBattLevel(forceTransmit); // Not needed to send battery info that often
     sendBattery = 0;
  }

  gw.sleep(SLEEP_TIME);
}

float getLastPressureSamplesAverage()
{
  float lastPressureSamplesAverage = 0;
  for (int i = 0; i < LAST_SAMPLES_COUNT; i++)
  {
    lastPressureSamplesAverage += lastPressureSamples[i];
  }
  lastPressureSamplesAverage /= LAST_SAMPLES_COUNT;

  return lastPressureSamplesAverage;
}



// Algorithm found here
// http://www.freescale.com/files/sensors/doc/app_note/AN3914.pdf
// Pressure in hPa -->  forecast done by calculating kPa/h
int sample(float pressure)
{
  // Calculate the average of the last n minutes.
  int index = minuteCount % LAST_SAMPLES_COUNT;
  lastPressureSamples[index] = pressure;

  minuteCount++;
  if (minuteCount > 185)
  {
    minuteCount = 6;
  }

  if (minuteCount == 5)
  {
    pressureAvg = getLastPressureSamplesAverage();
  }
  else if (minuteCount == 35)
  {
    float lastPressureAvg = getLastPressureSamplesAverage();
    float change = (lastPressureAvg - pressureAvg) * CONVERSION_FACTOR;
    if (firstRound) // first time initial 3 hour
    {
      dP_dt = change * 2; // note this is for t = 0.5hour
    }
    else
    {
      dP_dt = change / 1.5; // divide by 1.5 as this is the difference in time from 0 value.
    }
  }
  else if (minuteCount == 65)
  {
    float lastPressureAvg = getLastPressureSamplesAverage();
    float change = (lastPressureAvg - pressureAvg) * CONVERSION_FACTOR;
    if (firstRound) //first time initial 3 hour
    {
      dP_dt = change; //note this is for t = 1 hour
    }
    else
    {
      dP_dt = change / 2; //divide by 2 as this is the difference in time from 0 value
    }
  }
  else if (minuteCount == 95)
  {
    float lastPressureAvg = getLastPressureSamplesAverage();
    float change = (lastPressureAvg - pressureAvg) * CONVERSION_FACTOR;
    if (firstRound) // first time initial 3 hour
    {
      dP_dt = change / 1.5; // note this is for t = 1.5 hour
    }
    else
    {
      dP_dt = change / 2.5; // divide by 2.5 as this is the difference in time from 0 value
    }
  }
  else if (minuteCount == 125)
  {
    float lastPressureAvg = getLastPressureSamplesAverage();
    pressureAvg2 = lastPressureAvg; // store for later use.
    float change = (lastPressureAvg - pressureAvg) * CONVERSION_FACTOR;
    if (firstRound) // first time initial 3 hour
    {
      dP_dt = change / 2; // note this is for t = 2 hour
    }
    else
    {
      dP_dt = change / 3; // divide by 3 as this is the difference in time from 0 value
    }
  }
  else if (minuteCount == 155)
  {
    float lastPressureAvg = getLastPressureSamplesAverage();
    float change = (lastPressureAvg - pressureAvg) * CONVERSION_FACTOR;
    if (firstRound) // first time initial 3 hour
    {
      dP_dt = change / 2.5; // note this is for t = 2.5 hour
    }
    else
    {
      dP_dt = change / 3.5; // divide by 3.5 as this is the difference in time from 0 value
    }
  }
  else if (minuteCount == 185)
  {
    float lastPressureAvg = getLastPressureSamplesAverage();
    float change = (lastPressureAvg - pressureAvg) * CONVERSION_FACTOR;
    if (firstRound) // first time initial 3 hour
    {
      dP_dt = change / 3; // note this is for t = 3 hour
    }
    else
    {
      dP_dt = change / 4; // divide by 4 as this is the difference in time from 0 value
    }
    pressureAvg = pressureAvg2; // Equating the pressure at 0 to the pressure at 2 hour after 3 hours have past.
    firstRound = false; // flag to let you know that this is on the past 3 hour mark. Initialized to 0 outside main loop.
  }

  int forecast = UNKNOWN;
  if (minuteCount < 35 && firstRound) //if time is less than 35 min on the first 3 hour interval.
  {
    forecast = UNKNOWN;
  }
  else if (dP_dt < (-0.25))
  {
    forecast = THUNDERSTORM;
  }
  else if (dP_dt > 0.25)
  {
    forecast = UNSTABLE;
  }
  else if ((dP_dt > (-0.25)) && (dP_dt < (-0.05)))
  {
    forecast = CLOUDY;
  }
  else if ((dP_dt > 0.05) && (dP_dt < 0.25))
  {
    forecast = SUNNY;
  }
  else if ((dP_dt >(-0.05)) && (dP_dt < 0.05))
  {
    forecast = STABLE;
  }
  else
  {
    forecast = UNKNOWN;
  }

  // uncomment when debugging
  //Serial.print(F("Forecast at minute "));
  //Serial.print(minuteCount);
  //Serial.print(F(" dP/dt = "));
  //Serial.print(dP_dt);
  //Serial.print(F("kPa/h --> "));
  //Serial.println(weather[forecast]);

  return forecast;
}

/********************************************
 *
 * Sends battery information (battery percentage)
 *
 * Parameters
 * - force : Forces transmission of a value
 *
 *******************************************/
void sendBattLevel(bool force)
{
  if (force) lastBattery = -1;
  long vcc = readVcc();
  if (vcc != lastBattery) {
    lastBattery = vcc;
 
#ifdef BATT_SENSOR
    gw.send(msgBatt.set(vcc));
#endif

    // Calculate percentage

    vcc = vcc - 1800; // subtract 1.9V from vcc, as this is the lowest voltage we will operate at
    
    long percent = vcc / 14.0;
    gw.sendBatteryLevel(percent);
  }
}

/*******************************************
 *
 * Internal battery ADC measuring 
 *
 *******************************************/
long readVcc() {
  // Read 1.1V reference against AVcc
  // set the reference to Vcc and the measurement to the internal 1.1V reference
  #if defined(__AVR_ATmega32U4__) || defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__)
    ADMUX = _BV(REFS0) | _BV(MUX4) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1);
  #elif defined (__AVR_ATtiny24__) || defined(__AVR_ATtiny44__) || defined(__AVR_ATtiny84__)
    ADMUX = _BV(MUX5) | _BV(MUX0);
  #elif defined (__AVR_ATtiny25__) || defined(__AVR_ATtiny45__) || defined(__AVR_ATtiny85__)
    ADcdMUX = _BV(MUX3) | _BV(MUX2);
  #else
    ADMUX = _BV(REFS0) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1);
  #endif  
 
  delay(2); // Wait for Vref to settle
  ADCSRA |= _BV(ADSC); // Start conversion
  while (bit_is_set(ADCSRA,ADSC)); // measuring
 
  uint8_t low  = ADCL; // must read ADCL first - it then locks ADCH  
  uint8_t high = ADCH; // unlocks both
 
  long result = (high<<8) | low;
 
  result = 1125300L / result; // Calculate Vcc (in mV); 1125300 = 1.1*1023*1000
  return result; // Vcc in millivolts
}
Attachments
Node
Node
2015-12-11 09.29.09-1.jpg (191.26 KiB) Viewed 5911 times
stlaha2007
Posts: 370
Joined: Monday 05 October 2015 10:16
Target OS: -
Domoticz version:
Contact:

Re: How to get Forecast with BME280 (BMP080/180)

Post by stlaha2007 »

Thanks, had a quick look this afternoon.
Lot of code, need to dive into deeper as i dont understand (for the moment) how these average values are stored.

And how to implement this into my script which fires every minute to retrieve the measurements.

Can you enlighten me a little bit more, like does it so to say sleep, seen something that looked like a while, so does it need to be backgrounded?



Grtz,
Stephan
alexsh1
Posts: 169
Joined: Wednesday 30 September 2015 11:50
Target OS: Raspberry Pi / ODroid
Domoticz version: v3.8975
Location: United Kingdom
Contact:

Re: How to get Forecast with BME280 (BMP080/180)

Post by alexsh1 »

stlaha2007 wrote:
Can you enlighten me a little bit more, like does it so to say sleep, seen something that looked like a while, so does it need to be backgrounded?

Grtz,
Stephan
I do not know where to start :D :D
Basically, you have to look into the loop, again averages may come in later.

Code: Select all

void loop()
{

  measureCount ++;
  sendBattery ++;
  bool forceTransmit = false;
 
  if ((measureCount == 5) && highfreq)
  {
    clock_prescale_set(clock_div_8); // Switch to 1Mhz for the reminder of the sketch, save power.
    highfreq = false;
  }
 
  if (measureCount > FORCE_TRANSMIT_INTERVAL) { // force a transmission
    forceTransmit = true;
    measureCount = 0;
  }

 
  float pressure = bmp.readSealevelPressure(ALTITUDE) / 100.0;
  float temperature = bmp.readTemperature();

  if (!metric)
  {
    // Convert to fahrenheit
    temperature = temperature * 9.0 / 5.0 + 32.0;
  }

  int forecast = sample(pressure);

  Serial.print("Temperature = ");
  Serial.print(temperature);
  Serial.println(metric ? " *C" : " *F");
  Serial.print("Pressure = ");
  Serial.print(pressure);
  Serial.println(" hPa");
  Serial.print("Forecast = ");
  Serial.println(weather[forecast]);


  if (temperature != lastTemp)
  {
    gw.send(tempMsg.set(temperature, 1));
    lastTemp = temperature;
  }

  if (pressure != lastPressure)
  {
    gw.send(pressureMsg.set(pressure, 0));
    lastPressure = pressure;
  }

  if (forecast != lastForecast)
  {
    gw.send(forecastMsg.set(weather[forecast]));
    lastForecast = forecast;
  }

  digitalWrite(LED_PIN, HIGH);
  delay(50);
  digitalWrite(LED_PIN, LOW);


  if (sendBattery > 60)
  {
     sendBattLevel(forceTransmit); // Not needed to send battery info that often
     sendBattery = 0;
  }

  gw.sleep(SLEEP_TIME);
1) After a certain value, it forces to transmit pressure/temp
2)There is a forecast algorithm; there is a link and I suggest you follow the link to understand how it works.
3) The LED is flashed as soon as gw.send is gone for temp/pressure/forecast
4) battery voltage is sent once in the while, there is no need to send it every time.
5) it goes to sleep for SLEEP_TIME

This is it, the rest is just details. :D
Post Reply

Who is online

Users browsing this forum: No registered users and 1 guest