Interrupted sleep



  • Hi guys.

    I have a sketch for a battery powered node that sleeps a certain amount of time, let's say 2 minutes. unsigned long SLEEP_TIME = 120000; // Wait time between reads (in milliseconds) When it wakes up, it will check a few sensors and report new values (if any) to the gateway. So far so good.

    However (there's always a but, isn't there?), the 2 minute sleep is allowed to be interrupted by the change of a pin. That pin is connected to a rain meter. Measuring the rain must be done instantly.

    Every time that the loop() function runs, other sensors are checked.

    That's OK if it rains normally but sometimes it rains like h*ll. I'm concerned that the rain meter will be tripped so frequently that there is no time to check other sensors and report them to the gateway.

    I'm useless when it comes to explain things. But I'd like the rain sensor to report whenever the rain sensor is tripped. I would like the other sensors to report at the 2 minutes interval regardless of the rain sensor. As it is now, every time the rain sensor interrupts the sleep, all sensors gets checked. I would like it to report the rain and go back to sleep the REMAINING SLEEP TIME. I understand if my explanation is beyond what ca be understood (it's worse than Kurt Olsson) so I add the source below:

    /**
     * WeatherStation
     * 
     * DESCRIPTION
     * Arduino BH1750FVI Light sensor
     * communicate using I2C Protocol
     * this library enable 2 slave device addresses
     * Main address  0x23
     * secondary address 0x5C
     * connect the sensor as follows :
     *
     *   VCC  >>> 5V
     *   Gnd  >>> Gnd
     *   ADDR >>> NC or GND  
     *   SCL  >>> A5
     *   SDA  >>> A4
     * http://www.mysensors.org/build/light
    
    
    https://forum.mysensors.org/topic/9359/soft-wdt-reset-on-esp8266-rfm69-gateway-after-find-parent/20
    Connecting the BME280 Sensor:
    Sensor              ->  Board
    -----------------------------
    Vin (Voltage In)    ->  3.3V
    Gnd (Ground)        ->  Gnd
    SDA (Serial Data)   ->  A4
    SCK (Serial Clock)  ->  A5
    
    
    For temperature measurements we've selected the standard Dallas DS18B20.
    
    */
    
    #define MY_NODE_ID 15
    #define SKETCH_NAME "Weather Station"
    #define SKETCH_VERSION "1.1"
    #define DWELL_TIME 200  // this allows for radio to come back to power after a transmission, ideally 0
    
    // Enable debug Serial.prints to serial monitor
    //#define MY_DEBUG 
    
    #if defined MY_DEBUG
    
    #define Sprintln(a) (Serial.println(a))
    #define Sprint(a) (Serial.print(a))
    #else 
    #define Sprintln(a)
    #define Sprint(a)
    #endif
    
    // Enable and select radio type attached
    #define MY_RADIO_NRF24
    //#define MY_RADIO_RFM69
    
    #include <SPI.h>                                  // A communication backbone, the Serial Peripheral Interface.
    #include <MySensors.h>                            // The MySensors library.
    #include <Wire.h>                                 // Enables the Wire communication protocol.
    #include <DallasTemperature.h>
    #include <OneWire.h>
    #include <BH1750.h>
    //#include <BME280I2C.h> // From Library Manager
    #include <Adafruit_Sensor.h>
    #include <Adafruit_BME280.h>
    #undef BME280_ADDRESS         // Undef BME280_ADDRESS from the BME280 library to easily override I2C address
    #define BME280_ADDRESS (0x76) // Low = 0x76 , High = 0x77 (default on adafruit and sparkfun BME280 modules, default for library)
    
    uint8_t tipSensorPin = 3; // Pin the tipping bucket is connected to. Must be interrupt capable pin
    
    #define CHILD_ID_LIGHT 0
    #define CHILD_ID_TEMP_1 1
    #define CHILD_ID_TEMP_2 2 // Dallas DS18B20
    #define CHILD_ID_HUM 3
    #define CHILD_ID_BARO 4
    #define CHILD_ID_RAIN 5
    
    unsigned long SLEEP_TIME = 120000; // Wait time between reads (in milliseconds)
    #define FORCE_TRANSMIT_CYCLE 30
    uint8_t cycleCountLux = 0;
    uint8_t cycleCountPressure = 0;
    uint8_t cycleCountTemp2 = 0;
    uint8_t cycleCountHum = 0;
    
    int8_t interruptedBy = -1;
    
    Adafruit_BME280 bme; // I2C
    BH1750 lightSensor;
    
    #define ONE_WIRE_BUS 5 // Digital pin where dallas sensor is connected 
    OneWire oneWire(ONE_WIRE_BUS); // Setup a oneWire instance to communicate with any OneWire devices (not just Maxim/Dallas temperature ICs)
    DallasTemperature DS18B20sensor(&oneWire); // Pass the oneWire reference to Dallas Temperature. 
    
    uint16_t lastLux;
    float lastPressure;
    //float lastTemp1; // Temp from the BME280 Temp Hum Bar Sensor
    float lastTemp2; // Dallas DS18B20. Temp
    float lastHum;
    
    float tempThreshold = 0.2;                        // How big a temperature difference has to minimally  be before an update is sent. Makes the sensor less precise, but also less jittery, and can save battery.
    uint8_t humThreshold = 1;                         // How big a humidity difference has to minimally be before an update is sent. Makes the sensor less precise, but also less jittery, and can save battery.
    uint8_t presThreshold = 1;
    uint8_t luxThreshold = 1;
    
    MyMessage msgLight(CHILD_ID_LIGHT, V_LEVEL);  
    MyMessage msgTemp1(CHILD_ID_TEMP_1, V_TEMP);
    MyMessage msgTemp2(CHILD_ID_TEMP_2, V_TEMP);
    MyMessage msgHum(CHILD_ID_HUM, V_HUM);
    MyMessage msgPressure(CHILD_ID_BARO, V_PRESSURE);
    MyMessage msgRain(CHILD_ID_RAIN, V_TRIPPED);
    
    void setup()  
    {
      Wire.begin(); // Wire.begin(sda, scl) // starts the wire communication protocol, used to chat with the BME280 sensor.
      Sprint(SKETCH_NAME);
      Sprint(F(" version "));
      Sprint(SKETCH_VERSION);
      Sprint(F(" (using MY_NODE_ID: "));
      Sprint(MY_NODE_ID);
      Sprintln(F(") says hello!"));
      delay(500);// just in case
      Sprintln(F("Running bme.begin()"));
      if (!bme.begin())
       {
        Serial.println("BME init failed!");
       }
      else Sprintln("BME init success!");
      Sprintln(F("Running lightSensor.begin()"));
      lightSensor.begin();
      Sprintln(F("Running DS18B20sensor.begin()"));
      DS18B20sensor.begin(); // Startup up the OneWire library used for Dallas DS18B20 Temp
      DS18B20sensor.setWaitForConversion(false); // requestTemperatures() will not block current thread
      pinMode(tipSensorPin, INPUT); // sets the rain sensor digital pin as input
    }
    
    void presentation()  {
      // Send the sketch version information to the gateway and Controller
      sendSketchInfo(SKETCH_NAME, SKETCH_VERSION);
      wait(DWELL_TIME);
      // Register all sensors to gateway (they will be created as child devices)
      present(CHILD_ID_LIGHT, S_LIGHT_LEVEL);
      wait(DWELL_TIME);
      //present(CHILD_ID_TEMP_1, S_TEMP); //  Temp sensor on the BME280 multi sensor
      //wait(DWELL_TIME);
      present(CHILD_ID_TEMP_2, S_TEMP); // Dallas DS18B20. Temp
      wait(DWELL_TIME);
      present(CHILD_ID_HUM, S_HUM);
      wait(DWELL_TIME);
      present(CHILD_ID_BARO, S_BARO);
      wait(DWELL_TIME);
      present(CHILD_ID_RAIN, S_MOTION);
      wait(DWELL_TIME);
    }
    
    void loop()      
    {
      bool rainBucketTripped = (interruptedBy == digitalPinToInterrupt(tipSensorPin));
    
      wait(500); // Give radio some time to warm up
    
      cycleCountLux++;
      cycleCountPressure++;
      cycleCountTemp2++;
      cycleCountHum++;
    
      if (rainBucketTripped) {
        Sprintln("Tipping bucket rain sensor was tripped");
        send(msgRain.set(1));  // Send tripped value to gw
        wait(DWELL_TIME);
      }
    
      // Fetch temperature from Dallas DS18B20 Temp sensor
      DS18B20sensor.requestTemperatures();
    
      // query conversion time and sleep until conversion completed
      int16_t conversionTime = DS18B20sensor.millisToWaitForConversion(DS18B20sensor.getResolution());
      // sleep() call can be replaced by wait() call if node need to process incoming messages (or if node is repeater)
      sleep(conversionTime);
      // Fetch and round temperature to one decimal
      float temp2 = static_cast<float>(static_cast<int>((getControllerConfig().isMetric?DS18B20sensor.getTempCByIndex(0):DS18B20sensor.getTempFByIndex(0)) * 10.)) / 10.;
      if (isnan(temp2)) {
        Sprintln("Failed reading temp2");
      } else if (((abs(temp2 - lastTemp2) >= tempThreshold) or (cycleCountTemp2 >= FORCE_TRANSMIT_CYCLE)) && temp2 != -127.00 && temp2 != 85.00) {
        // Only send temp2 if it changed since the last measurement
        lastTemp2 = temp2;
        cycleCountTemp2 = 0;
        Sprint("Temperature: ");
        Sprint(temp2);
        Sprintln("°C");
        send(msgTemp2.set(temp2 ,1));
        wait(DWELL_TIME);
      }
    
      uint16_t lux = lightSensor.readLightLevel();// Get Lux value
      if (isnan(lux)) {
        Sprintln("Failed reading lux");
      } else if ((abs(lux - lastLux) >= luxThreshold) or  (cycleCountLux >= FORCE_TRANSMIT_CYCLE)){
        // Only send Lux if it changed since the last measurement
        lastLux = lux;
        cycleCountLux = 0;
        Sprint("Light: ");
        Sprint(lux);
        Sprintln(" Lux");
        send(msgLight.set(lux));
        wait(DWELL_TIME);
      }
    
      double pres, hum;
      pres=bme.readPressure()/100.0;
      hum=bme.readHumidity();
    
      if (isnan(hum)) {
        Sprintln("Failed reading humidity");
      } else {
        hum = round(hum);
        if ((abs(hum - lastHum) >= humThreshold) or (cycleCountHum >= FORCE_TRANSMIT_CYCLE)){
          // Only send humidity if it changed since the last measurement
          lastHum = hum;
          cycleCountHum = 0;
          Sprint("Humidity: ");
          Sprint(hum);
          Sprintln("% RH");
          send(msgHum.set(hum, 0));
          wait(DWELL_TIME);
        }
      }
    
      if (isnan(pres)) {
        Sprintln("Failed reading pressure");
      } else {
        pres = round(pres);
        if ((abs(pres - lastPressure) >= presThreshold) or (cycleCountPressure >= FORCE_TRANSMIT_CYCLE)) {
          // Only send pressure if it changed since the last measurement
          lastPressure = pres;
          cycleCountPressure = 0;
          Sprint("Pressure: ");
          Sprint(pres);
          Sprintln(" hPa");
          send(msgPressure.set(pres, 0));
          wait(DWELL_TIME);
        }
      }
    
      // Sleep until interrupt comes in on rain tip bucket sensor. Send update every SLEEP_TIME.
      Sprintln(F("Waiting ")); Sprint(SLEEP_TIME /1000); Sprintln(F(" seconds before next reading."));
      interruptedBy = sleep(digitalPinToInterrupt(tipSensorPin), CHANGE, SLEEP_TIME);
    }
    

    Another try to explain: When it rains heavily, I don't want to check and report humidity and temp. Humidity and temp should be reported in the interval that I've set in SLEEP_TIME.

    alt text

    Can it be done?


  • Mod

    @รอเร-อ I think it would as simple as changing

      if (rainBucketTripped) {
        Sprintln("Tipping bucket rain sensor was tripped");
        send(msgRain.set(1));  // Send tripped value to gw
        wait(DWELL_TIME);
      }
    

    to

      if (rainBucketTripped) {
        Sprintln("Tipping bucket rain sensor was tripped");
        send(msgRain.set(1));  // Send tripped value to gw
        wait(DWELL_TIME);
      } else {
    

    (add else { at end)
    and change

      // Sleep until interrupt comes in on rain tip bucket sensor. Send update every SLEEP_TIME.
      Sprintln(F("Waiting ")); Sprint(SLEEP_TIME /1000); Sprintln(F(" seconds before next reading."));
    

    to

    }
      // Sleep until interrupt comes in on rain tip bucket sensor. Send update every SLEEP_TIME.
      Sprintln(F("Waiting ")); Sprint(SLEEP_TIME /1000); Sprintln(F(" seconds before next reading."));
    

    (add } at top)

    That will make the sensor reading code only run if the bucket was not tripped.

    If you want to guarantee that heavy rain doesn't stop the other sensors from being read, modify the existing if... code to this:

    #define MAX_NUMBER_OF_TRIPS_WITHOUT_SENSOR_REPORT 50 #Or whatever number you think is appropriate
    static unsigned int rainBucketTrippedCounter = 0;
      if (rainBucketTripped) {
        Sprintln("Tipping bucket rain sensor was tripped");
        send(msgRain.set(1));  // Send tripped value to gw
        rainBucketTrippedCounter++;
        wait(DWELL_TIME);
      } else if (rainBucketTrippedCounter>MAX_NUMBER_OF_TRIPS_WITHOUT_SENSOR_REPORT) {
        // Time to check the sensors
        rainBucketTrippedCounter = 0;
    

  • Mod

    @mfalkvidd isn't this just the returning question how to keep track of time when using sleep() with interrupts?


  • Mod

    @yveaux yes. I have a hacked version of the MySensors sleep function that does keep time, but as discussed earlier it is hard to make it intuitive and easy to use.



  • @mfalkvidd Thanks a lot for your suggestion. I will think this through during the day. It might still be problems due to the dynamic nature of the rain.

    It would be hard for me to define a reasonable good value for MAX_NUMBER_OF_TRIPS_WITHOUT_SENSOR_REPORT when rain in the interval ranging from 11 mm per hour up to maybe as much as 240 mm per hour (extreme but not impossible) would disrupt the readings of other sensors.

    I also have a doubt that my sketch will work well in a case where the Arduino is already awake (handling other sensors) when the rain bucket tips. It's just a guess but I believe that the tipping will not be counted at all in such a scenario.

    What do you think? If so my sketch is badly designed (only myself to blame) and maybe I should consider giving the rain gauge it's own Arduino board. It would make things much cleaner. What do you think?

    @Yveaux, you are right.



  • I had the same issue some time ago, measuring time while asleep and running on batteries. I ended up building this project: Battery operated rain gauge..
    The time between reports is controlled by a CMOS 555 timer, independently from the Arduino. Needless to say I did not include any other sensors.



  • @yveaux
    possible so:

    void loop() {
      if ((w_battetyTime >= send_battety) || (w_battetyTime == 0))    {
        battery = readBatLev();
        if (old_battery != battery) {
          old_battery = battery;
          sendBatteryLevel(battery);
          change = 1;
        }
        if (w_battetyTime >= send_battety)
        {
          w_battetyTime = 0;
        }
      }
    
    sleep(SLEEP_TIME);
      w_battetyTime = w_battetyTime + SLEEP_TIME;
    }
    

    Yes, it is not high-precision for a battery device without a quartz generator, but in 99% of cases it is enough.



  • @mfalkvidd
    I think there is no need to hack the function, you can just add sleep time to the variable, you can use the flag and not let the program start a new sleep as much as necessary. You can also make a clock for a sleeping node without RTC https://translate.google.ru/translate?sl=ru&tl=en&js=y&prev=_t&hl=ru&ie=UTF-8&u=http%3A%2F%2Fmysensors.ru%2Fforum%2Fviewtopic.php%3Ff%3D5%26t%3D444&edit-text=


  • Mod

    @berkseo the troubling case (without rtc) is if the node sleeps let’s say 30 minutes and the rain causes an interruppt every 1-29 minutes. In that case, sleep will always return woken up by interrupt. If the rain keep like this for 5 hours, the other sensors will not be activated.



  • @mfalkvidd
    In my previous post, I rather answered @yveaux's question, but I didn't fully understand what @yveaux was paying attention to ... interrupts. An example from the RTC will not work with regular interruptions. No examples of sleep time tracking will work. As for this topic, Your version is the solution. I can also offer a solution below. If it suddenly started to rain then change the sleep mode, do without interruption for the next 2 minutes. In the next 2 minutes, it doesn't matter if rain ends or continues. Here, the subtle point is only that in the worst case (for example, the rain went on 119 seconds) the interval between the data will be 4 minutes.

    const uint32_t  SLEEP_TIME = 120000; // Wait time between reads (in milliseconds)
    int w;
    
    #define MY_DEBUG
    #define MY_RADIO_NRF24
    #define MY_RF24_CHANNEL 76
    #define DIGITAL_INPUT_SENSOR1 2
    #include <MySensors.h>
    
    void setup() {
      pinMode(2, INPUT_PULLUP);
    }
    
    void loop() {
      CORE_DEBUG(PSTR("Sending data from other sensors\n"));
    
      w = sleep(digitalPinToInterrupt(DIGITAL_INPUT_SENSOR1), RISING, SLEEP_TIME, false);
      if (w == -1) {
        CORE_DEBUG(PSTR("Fell asleep by standard sleep with interrupt\n"));
      }
    
      if (w == 0) {
        CORE_DEBUG(PSTR("Sending message about rain\n"));
        sleep(SLEEP_TIME, false);
        CORE_DEBUG(PSTR("It's raining, changing the sleep mode, sleeping quietly for two minutes.\n"));
      }
    }
    

  • Mod

    @รอเร-อ @mfalkvidd @berkseo Resurrecting this old thread; I experimented in this PR https://github.com/mysensors/MySensors/pull/1286 by adding a function to MySensors that will allow requesting the time remaining when returning from sleep. Ofcourse this is 0 when returning on timeout, but will be the (approximate) remaining time in ms when wake up was caused by an interrupt.
    It should be possible to use it like this:

    #define SLEEP_TIME_MS (60*1000ul)
    
    void loop()
    {
        // Variable that keeps track of time remaining in one cycle
        static uint32_t sleepTimeMs = SLEEP_TIME_MS ;
    
        // Sleep for the remaining time in the cycle, or when interrupted
        uint8_t result = sleep(digitalPinToInterrupt(PIN), RISING, sleepTimeMs);
    
        // Request how much of the time in the cycle is remaining
        sleepTimeMs = getSleepRemaining();
    
        if (sleepTimeMs == 0ul)
        {
            // Do something smart when SLEEP_TIME_MS has elapsed
            // ...
            // Restart the cycle
            sleepTimeMs = SLEEP_TIME_MS;
        }
    }
    

    A working example sketch can be found here: https://github.com/mysensors/MySensorsArduinoExamples/blob/master/examples/MotionSensorBatteryReport/MotionSensorBatteryReport.ino


Log in to reply
 

Suggested Topics

0
Online

11.4k
Users

11.1k
Topics

112.7k
Posts