Use of BH1750 light sensor in low power node


  • Hero Member

    Hello,
    I'm making a node powered with a CR2032 cell using a BME 280 and a BH1750 to measure pressure, temperature, humidity and light level in a tiny cube enclosure (from a cheap <2$ mp3 player, 3x3x3cm).
    Main points :

    • I use a 8MHz pro mini, I didn't change the frequency just updated the fuses to set BOD at 1.8V
    • I'm using the BH1750 from MySensors libraries (and Sparkfun library for BME280)
    • I do not shut down the sensors, I use their sleep modes and the power consumption is low enough for me (18uA in total when node is sleeping)
    • Every five minutes I measure and send changed values

    It's doing the job, low power and sending the values... but the light measurements are erratic as can be seen in the Domoticz graph. I have no idea how I should make the measurement to have a reliable value and save battery at the same time. My only viable option is to use the "one time" measurement modes so the sensor will go back to sleep after the measurement, but it seems the duration after which I read the value after starting the measurement is important. So, has someone managed to use the BH1750 in a very low power mode and succeeded in getting reliable data ? I have of course tried without the enclosure just in case and I have the same results.
    Thank you in advance for any help/suggestions/test cases. Below are the obviously non-final sketch, the graph from Domoticz and... a picture of the sensor :)

    /**
       The MySensors Arduino library handles the wireless radio link and protocol
       between your home built sensors/actuators and HA controller of choice.
       The sensors forms a self healing radio network with optional repeaters. Each
       repeater and gateway builds a routing tables in EEPROM which keeps track of the
       network topology allowing messages to be routed to nodes.
    
       Created by Henrik Ekblad <henrik.ekblad@mysensors.org>
       Copyright (C) 2013-2015 Sensnology AB
       Full contributor list: https://github.com/mysensors/Arduino/graphs/contributors
    
       Documentation: http://www.mysensors.org
       Support Forum: http://forum.mysensors.org
    
       This program is free software; you can redistribute it and/or
       modify it under the terms of the GNU General Public License
       version 2 as published by the Free Software Foundation.
    
     *******************************
    
       REVISION HISTORY
       Based on Henrik Ekblad pressure sensor (using BMP085)
       Version 0.1 - Changed to environment sensor using BME280 and BH1750
    
       DESCRIPTION
       Pressure, Temperature, Humidity, Light level sensor using BME280 + BH1750
       http://www.mysensors.org/build/pressure
    
    */
    
    /*
     ***********************************************************************
                           SENSOR CONFIGURATION
     ***********************************************************************
    */
    
    // Enable debug prints to serial monitor
    //#define MY_DEBUG
    
    // Comment if you are not running on battery/coin cell/... but on main power
    #define BATTERY_SENSOR
    
    // Enables/disables sleeps between sendings to optimize for CR2032 or similar coin cell, if you are not using AA/AAA battery this is not necessary
    #define USE_COIN_CELL
    
    //. If you don't want to manage weather forecasting, comment line below
    #define WEATHER_FORECAST_ENABLED
    
    // Time between sending of data to the gateway/controller.
    // If WEATHER_FORECAST_ENABLED is defined then the sending will be done in round minute intervals, as the forecast algorithm checks
    #define SLEEP_TIME_BETWEEN_DATASEND 300000 // (value is in ms)
    
    
    // Enable and select radio type attached
    #define MY_RADIO_NRF24
    
    /*
     ***********************************************************************
    */
    
    
    
    
    #include <SPI.h>
    #include <MySensors.h>
    #include <Wire.h>
    #include "SparkFunBME280.h"
    
    // Necessary to read Vcc (only for battery)
    #ifdef BATTERY_SENSOR
    #include <SystemStatus.h>
    // Parameters for VCC measurement
    const int VccMin        = 2400;  // Minimum expected Vcc level, in Volts. At 2V the cell should be dead
    const int VccMax        = 2900;  // Maximum expected Vcc level, in Volts.
    
    SystemStatus vcc();
    int LastBatteryPercent = 200; // so we are sure to send the battery level at first check
    
    #endif
    
    
    // Sleep time between reads (in seconds). Do not change this value as the forecast algorithm needs a sample every minute.
    unsigned long SLEEP_TIME = 60000;
    unsigned long SleepTimeSum = SLEEP_TIME_BETWEEN_DATASEND;
    
    // Configuration for barometric pressure, weather forecast, temperature, humidity
    #define CHILD_ID_BARO 1
    #define CHILD_ID_TEMP 2
    #define CHILD_ID_HUM 3
    
    const float ALTITUDE = 0; // <-- adapt this value to your own location's altitude.
    
    #ifdef WEATHER_FORECAST_ENABLED
    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)
    };
    #endif
    
    BME280 bmp;      // Digital Pressure/Temp/Hum Sensor
    
    float lastPressure = -1;
    float lastTemp = -1;
    byte lastHum = -1;
    
    #ifdef WEATHER_FORECAST_ENABLED
    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;
    #endif
    
    
    float dP_dt;
    bool metric;
    MyMessage pressureMsg(CHILD_ID_BARO, V_PRESSURE);
    #ifdef WEATHER_FORECAST_ENABLED
    MyMessage forecastMsg(CHILD_ID_BARO, V_FORECAST);
    #endif
    MyMessage tempMsg(CHILD_ID_TEMP, V_TEMP);
    MyMessage humMsg(CHILD_ID_HUM, V_HUM);
    
    // Light sensor configuration and variables
    #include <BH1750.h>
    BH1750 lightSensor;
    #define CHILD_ID_LIGHT 4
    MyMessage lightMsg(CHILD_ID_LIGHT, V_LEVEL);
    uint16_t lastlux = -1;
    
    
    // Sleep between sendings to preserve coin cell
    //  if not using button cell just make sure the #define USE_COIN_CELL is commented at the beginning of the sketch and it will do nothin
    void sleepForCoinCell() {
    #ifdef USE_COIN_CELL
      sleep(400);
    #endif
    }
    
    
    void setup()
    {
    
      // If we are not using the weather forecast function, then we can override the SLEEP_TIME with SLEEP_TIME_BETWEEN_DATASEND as there is no use for wake ups between data sending
    #ifndef WEATHER_FORECAST_ENABLED
      SLEEP_TIME = SLEEP_TIME_BETWEEN_DATASEND;
    #endif
    
      //***Driver settings for BME280 (from I2C_ReadAllData example from Sparkfun ***********************//
      bmp.settings.commInterface = I2C_MODE;
      bmp.settings.I2CAddress = 0x76;   // Address might be 0x77...
    
      //***Operation settings*****************************//
    
      //tStandby can be:
      //  0, 0.5ms
      //  1, 62.5ms
      //  2, 125ms
      //  3, 250ms
      //  4, 500ms
      //  5, 1000ms
      //  6, 10ms
      //  7, 20ms
      bmp.settings.tStandby = 0;
    
      //filter can be off or number of FIR coefficients to use:
      //  0, filter off
      //  1, coefficients = 2
      //  2, coefficients = 4
      //  3, coefficients = 8
      //  4, coefficients = 16
      bmp.settings.filter = 0;
    
      //tempOverSample can be:
      //  0, skipped
      //  1 through 5, oversampling *1, *2, *4, *8, *16 respectively
      bmp.settings.tempOverSample = 1;
    
      //pressOverSample can be:
      //  0, skipped
      //  1 through 5, oversampling *1, *2, *4, *8, *16 respectively
      bmp.settings.pressOverSample = 1;
    
      //humidOverSample can be:
      //  0, skipped
      //  1 through 5, oversampling *1, *2, *4, *8, *16 respectively
      bmp.settings.humidOverSample = 1;
    
      wait(10);  //Make sure sensor had enough time to turn on. BME280 requires 2ms to start up.
    
      metric = getConfig().isMetric;
    
    }
    
    void presentation()  {
      sleepForCoinCell();
      // Send the sketch version information to the gateway and Controller
      sendSketchInfo("Cube Sensor", "1.1");
      sleepForCoinCell();
    
      // Register sensors to gw (they will be created as child devices)
      present(CHILD_ID_BARO, S_BARO);
      sleepForCoinCell();
      present(CHILD_ID_TEMP, S_TEMP);
      sleepForCoinCell();
      present(CHILD_ID_HUM, S_HUM);
      sleepForCoinCell();
    
      present(CHILD_ID_LIGHT, S_LIGHT_LEVEL);
    
      lightSensor.begin(BH1750_ONE_TIME_HIGH_RES_MODE);
    
    #ifdef USE_COIN_CELL
      sleep(5000); // Sleep 5 seconds if using coin cell
    #endif
    }
    
    float pressure;
    float temperature;
    float humidity;
    byte lux;
    bool oneValueSent;  // will tell us if at least one value was sent by the sensor, if not we will send heartbeat to tell controller we are still alive
    
    void loop()
    {
      oneValueSent = false;
    
      //runMode can be:
      //  0, Sleep mode
      //  1 or 2, Forced mode
      //  3, Normal mode
      bmp.settings.runMode = 3; //Sleep mode
    #ifdef MY_DEBUG
      Serial.print("Starting BME280... result of .begin(): 0x");
      Serial.println(bmp.begin(), HEX);
    #else
      bmp.begin();
    #endif
    
      // in all situations, we read the pressure as we need to feed the weather forecast algorithm
      pressure = bmp.readFloatPressure() / 100;
    
      // if we have reached the configured number of sleeps, we process the other measurements and send values when necessary
      if (SleepTimeSum >= SLEEP_TIME_BETWEEN_DATASEND) {
        SleepTimeSum = 0;
    
        // Measure all other values from BME sensor
        if (metric)
        {
          temperature = bmp.readTempC();
        }
        else
        {
          temperature = bmp.readTempF();
        }
        humidity = bmp.readFloatHumidity();
        // Set BME sensor back to sleep mode as fast as possible
        bmp.settings.runMode = 0; //Sleep mode
        bmp.begin(); // apply new mode
    
        // Configure sensor to make one measurement in high res mode
        lightSensor.begin(BH1750_ONE_TIME_HIGH_RES_MODE);
        // Get initial time when we configured the BH1750 sensor + add max time of high res measurement (180ms from datasheet)
        // This will be the minimum time before we try to make the measurement
        unsigned long minimumMillisForLightMeasurement = millis() + 200;
    
    #ifdef WEATHER_FORECAST_ENABLED
        int forecast = sample(pressure);
    #endif
    
    #ifdef MY_DEBUG
        Serial.print("Temperature = ");
        Serial.print(temperature);
        Serial.println(metric ? " *C" : " *F");
        Serial.print("Pressure = ");
        Serial.print(pressure);
        Serial.println(" hPa");
    #ifdef WEATHER_FORECAST_ENABLED
        Serial.print("Forecast = ");
        Serial.println(weather[forecast]);
    #endif
    #endif
    
    
        if (temperature != lastTemp)
        {
          oneValueSent = true;
          send(tempMsg.set(temperature, 1));
          sleepForCoinCell();
          lastTemp = temperature;
        }
    
        if (humidity != lastHum)
        {
          oneValueSent = true;
          send(humMsg.set(humidity, 1));
          sleepForCoinCell();
          lastHum = humidity;
        }
    
        if (pressure != lastPressure)
        {
          oneValueSent = true;
          send(pressureMsg.set(pressure, 0));
          sleepForCoinCell();
          lastPressure = pressure;
        }
    #ifdef WEATHER_FORECAST_ENABLED
        if (forecast != lastForecast)
        {
          oneValueSent = true;
          send(forecastMsg.set(weather[forecast]));
          lastForecast = forecast;
        }
    #endif
        // if we have not reached the minimum time before we can read measurement of light, we sleep for the remaining ms
        if (minimumMillisForLightMeasurement > millis()) {
          sleep(minimumMillisForLightMeasurement - millis());
          wait(10);
        }
        lux = lightSensor.readLightLevel();// Get Lux value
        if (lux != lastlux) {
          oneValueSent = true;
          send(lightMsg.set(lux));
          lastlux = lux;
        }
    #ifdef MY_DEBUG
        Serial.print("Lux = ");
        Serial.println(lux);
    #endif
    
    #ifdef BATTERY_SENSOR
        int currentBatteryPercent = SystemStatus().getVCCPercent(VccMin, VccMax);
        if (currentBatteryPercent != LastBatteryPercent) {
          LastBatteryPercent = currentBatteryPercent;
          sendBatteryLevel(currentBatteryPercent);
          oneValueSent = true;
        }
    #endif
    
        // if nothing has been sent, we send heartbeat to tell controller we are still alive
        if (oneValueSent == false) {
          sendHeartbeat();
        }
    
      }
      else {
        // Set BME sensor back to sleep mode as fast as possible
        bmp.settings.runMode = 0; //Sleep mode
        bmp.begin(); // apply new mode
      }
    
      sleep(SLEEP_TIME);
      SleepTimeSum += SLEEP_TIME;
    }
    
    
    #ifdef WEATHER_FORECAST_ENABLED
    
    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;
      }
    
    #ifdef MY_DEBUG
      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]);
    #endif
      return forecast;
    }
    
    #endif
    

    0_1474559694923_lux.png
    0_1474559866025_image-0-02-01-2b22b107ca5b14ca5672cacea0cb17fe665beecff0289f848d63c93522998113-V(1).jpg


  • Mod

    @Nca78 I think the answer is right there in your code:

    wait(10);  //Make sure sensor had enough time to turn on. BME280 requires 2ms to start up.
    

    I think this needs to be done every time the sensor is set to non-sleep mode. The code you have only waits during setup. Try adding a wait in the loop, after bmp.settings.runMode = 3

    The debug output should have told you that the result of begin was bad though, so I might be on the wrong track.


  • Hero Member

    @mfalkvidd thank you for your answer but unfortunately I think that you are on the wrong track yes, as the BME sensor returns perfectly regular results, problem is with the BH1750 :)


  • Mod

    @Nca78 you should get some extra info from the BH1750 lib if you do #define BH1750_DEBUG
    Maybe that can give a clue?


  • Hero Member

    @mfalkvidd said:

    @Nca78 you should get some extra info from the BH1750 lib if you do #define BH1750_DEBUG
    Maybe that can give a clue?

    Unfortunately it's just printing the light level, not very useful in my case :(
    But I'm thinking it might be related to the falling voltage in the CR2032 as I do the data sending from BME280 sensor while the BH1750 is performing the measurement. If Vcc falls in the middle of the measurement process this result could make sense.
    I will power from li-ion battery + regulator and see if it's more stable, then it will mean I need to split the sleeping time in two and process alternatively the BME and BH1750 sensors.


  • Mod

    @Nca78 for debugging, you could try skip sending, to see if the power drain is the problem. Just print the value on serial instead of sending.


  • Hero Member

    @mfalkvidd
    Have you considered sleeping your sensors rather than turning them off? I measured the standby current on a BME280 to be 148na using a Dave Jones Microcurrent. I don't know what your light sensor would measure out at though, but you may burn less current sleeping your sensors than going through a higher drain startup, depending on how long you sleep them for.

    Have you considered using a simple photo-resistor connected to the ADC on your atmega328?


  • Hero Member

    @NeverDie said:

    @mfalkvidd
    Have you considered sleeping your sensors rather than turning them off? I measured the standby current on a BME280 to be 148na using a Dave Jones Microcurrent. I don't know what your light sensor would measure out at though, but you may burn less current sleeping your sensors than going through a higher drain startup, depending on how long you sleep them for.

    Have you considered using a simple photo-resistor connected to the ADC on your atmega328?

    The idea of using BH1750 is to have precise lux measurement.
    Didn't have time to test with another sensor but I will do it soon, maybe that one is just faulty as I see no logical reason for it to behave that way, even in continuous measurement mode and battery powered it's doing the same.



  • You can measure voltage and see if it drops too much during sending. Unfortunately, CR2032 is not a very powerful power source.
    Another idea would bee having sensor measurements done desperately. I have several sensors (BH1750 + TSL2561 + VEML6070 UV(coming shortly) MOD-1016 (lightning sensor)) running from a tiny Li-on 3.7v 350mA battery without any issues.


  • Hero Member

    It may be a long-shot, but I wonder if in fact your sensor is actually working perfectly and your lighting is the kind that flickers on and off at a very fast rate, tricking your eye into believing that it's continuously on? If your sensor were to sample fast enough, perhaps it is reporting the amount of light that is actually there during each brief sample interval rather than the average that you were expecting.

    Incidently, how are you liking Domoticz? I'm looking for something that will log and graph my sensor data, and I like how Domoticz appears to do that for everything by default. Is it easy to learn, or is it a steep learning curve?


  • Hero Member

    @NeverDie sampling time for measurement is long (>100ms), flicker in lighting that could influence it would be clearly visible with human eye. It also does the sames with natural light which can vary with cloud coverage but not that wildly :)

    For Domoticz it's not very difficult to learn I think, but the log for sensor data is not complete. It keeps values at regular intervals (not all values sent): every 5 minutes for the last day by default, it can be increased to 7 days in the configuration but after that it will only keep average and min/max for each day.
    I'm also a bit frustrated with the limited interface too, I would like to be able to combine switches/selectors/etc into one block (for example all settings to control an AC) but that is not possible.


  • Hero Member

    @NeverDie we should start a separate thread on controller experiences. But for here, and the way I see it : Domoticz is a do it all, easy to use controller with interface to excellent data handling (influxdb, etc). Works out of the box in most cases. I have recently started to look at mycontroller which is geared to MySensors and is more configurable.


Log in to reply
 

Looks like your connection to MySensors Forum was lost, please wait while we try to reconnect.