From 06032fd28f555caf3a1204d9a01ee85407337899 Mon Sep 17 00:00:00 2001
From: user2684 <user2684@users.noreply.github.com>
Date: Wed, 31 May 2017 22:57:20 +0200
Subject: [PATCH] Added Timer class and fixed #127 #131 #107 #105

* Added functions to get sleep mode from NM

* Implemented Timer class

* Commented the Timer class

* Improved Timer class

* Replaced battery report with Timer

* Using Timer in SensorRainGauge class

* Add custom report time to Sensor

* Used Timer for force update

* Added safeguard to SensorDigitalOutput

* Added capability to turn on with a timer for SensorDigitalOutput

* Tested all the new functions and fixed the issues

* Stopping the timer if setInputIsElapsed is on and a LOW value is set
---
 NodeManager.cpp | 397 +++++++++++++++++++++++++++++++++++++-----------
 NodeManager.h   |  92 +++++++++--
 NodeManager.ino |   2 +-
 README.md       |  23 ++-
 4 files changed, 410 insertions(+), 104 deletions(-)

diff --git a/NodeManager.cpp b/NodeManager.cpp
index 6aeb15b..0002d32 100644
--- a/NodeManager.cpp
+++ b/NodeManager.cpp
@@ -87,6 +87,113 @@ void PowerManager::powerOff() {
   digitalWrite(_vcc_pin, LOW);
 }
 
+/******************************************
+    Timer
+*/
+
+Timer::Timer(NodeManager* node_manager) {
+  _node_manager = node_manager;
+}
+
+// start the timer
+void Timer::start(long target, int unit) {
+  set(target,unit);
+  start();
+}
+void Timer::start() {
+  if (_is_configured) _is_running = true;
+}
+
+// stop the timer
+void Timer::stop() {
+  _is_running = false;
+}
+
+// setup the timer
+void Timer::set(long target, int unit) {
+  // reset the timer
+  _elapsed = 0;
+  _use_millis = false;
+  _last_millis = 0;
+  _sleep_time = 0;
+  // save the settings
+  _target = target;
+  _unit = unit;
+  if (_unit == MINUTES) {
+    if (_node_manager->isSleepingNode()) {
+      // this is a sleeping node and millis() is not reliable so calculate how long a sleep/wait cycle would last
+      int sleep_unit = _node_manager->getSleepUnit();
+      _sleep_time = (float)_node_manager->getSleepTime();
+      if (sleep_unit == SECONDS) _sleep_time = _sleep_time/60;
+      else if (sleep_unit == HOURS) _sleep_time = _sleep_time*60;
+      else if (sleep_unit == DAYS) _sleep_time = _sleep_time*1440;
+    }
+    else {
+      // this is not a sleeping node, use millis() to keep track of the elapsed time
+      _use_millis = true;
+    }
+  }
+  _is_configured = true;
+}
+
+// update the timer at every cycle
+void Timer::update() {
+  if (! isRunning()) return;
+  if (_unit == CYCLES) {
+    // if not a sleeping node, counting the cycles do not make sense
+    if (! _node_manager->isSleepingNode()) return;
+    // just increase the cycle counter
+    _elapsed++;
+  }
+  else if (_unit == MINUTES) {
+    // if using millis(), calculate the elapsed minutes, otherwise add a sleep interval
+    if (_use_millis) {
+      _elapsed = (float)(millis() - _last_millis)/1000/60;
+    }
+    else {
+      _elapsed += _sleep_time;
+    }
+  }
+}
+
+// return true if the time is over
+bool Timer::isOver() {
+  if (! isRunning()) return false;
+  // time has elapsed
+  if (_elapsed >= _target) return true;
+  // millis has started over
+  if (_elapsed < 0 ) return true;
+  return false;
+}
+
+// return true if the timer is running
+bool Timer::isRunning() {
+  return _is_running;
+}
+
+// return true if the time is configured
+bool Timer::isConfigured() {
+  return _is_configured;
+}
+
+// restart the timer
+void Timer::restart() {
+  if (! isRunning()) return;
+  // reset elapsed
+  _elapsed = 0;
+  // if using millis, keep track of the now timestamp
+  if (_use_millis) _last_millis = millis();
+}
+
+// return elapsed minutes so far
+float Timer::getElapsed() {
+  return _elapsed;
+}
+
+// return the configured unit
+int Timer::getUnit() {
+  return _unit;
+}
 
 /******************************************
     Sensors
@@ -101,6 +208,8 @@ Sensor::Sensor(NodeManager* node_manager, int child_id, int pin) {
   _child_id = child_id;
   _pin = pin;
   _msg = MyMessage(_child_id, _type);
+  _report_timer = new Timer(_node_manager);
+  _force_update_timer = new Timer(_node_manager);
 }
 
 // setter/getter
@@ -144,11 +253,17 @@ void Sensor::setSamples(int value) {
 void Sensor::setSamplesInterval(int value) {
   _samples_interval = value;
 }
-void Sensor::setTackLastValue(bool value) {
+void Sensor::setTrackLastValue(bool value) {
   _track_last_value = value;
 }
 void Sensor::setForceUpdate(int value) {
-  _force_update = value;
+  setForceUpdateCycles(value);
+}
+void Sensor::setForceUpdateCycles(int value) {
+  _force_update_timer->start(value,CYCLES);
+}
+void Sensor::setForceUpdateMinutes(int value) {
+  _force_update_timer->start(value,MINUTES);
 }
 void Sensor::setValueType(int value) {
   _value_type = value;
@@ -192,6 +307,16 @@ char* Sensor::getValueString() {
   return _last_value_string;
 }
 
+// After how many cycles the sensor will report back its measure (default: 1 cycle)
+void Sensor::setReportIntervalCycles(int value) {
+  _report_timer->start(value,CYCLES);
+}
+
+// After how many minutes the sensor will report back its measure (default: 1 cycle)
+void Sensor::setReportIntervalMinutes(int value) {
+  _report_timer->start(value,MINUTES);
+}
+
 // present the sensor to the gateway and controller
 void Sensor::presentation() {
   #if DEBUG == 1
@@ -218,6 +343,16 @@ void Sensor::setup() {
 // call the sensor-specific implementation of loop
 void Sensor::loop(const MyMessage & message) {
   if (_pin == -1) return;
+  // update the timers if within a loop cycle
+  if (! _isReceive(message)) {
+    if (_report_timer->isRunning()) {
+      // update the timer
+      _report_timer->update();
+      // if it is not the time yet to report a new measure, just return
+      if (! _report_timer->isOver()) return;
+    }
+    if (_force_update_timer->isRunning()) _force_update_timer->update();
+  }
   #if POWER_MANAGER == 1
     // turn the sensor on
     if (_auto_power_pins) powerOn();
@@ -225,31 +360,29 @@ void Sensor::loop(const MyMessage & message) {
   // for numeric sensor requiring multiple samples, keep track of the total
   float total = 0;
   // keep track of the number of cycles since the last update
-  if (_force_update > 0) _cycles++;
   // collect multiple samples if needed
   for (int i = 0; i < _samples; i++) {
     // call the sensor-specific implementation of the main task which will store the result in the _value variable
-    if (message.sender == 0 && message.sensor == 0 && message.getCommand() == 0 && message.type == 0) {
-      // empty message, we'be been called from loop()
-      onLoop();
-    }
-    else {
+    if (_isReceive(message)) {
       // we've been called from receive(), pass the message along
       onReceive(message);
     }
+    else {
+      // we'be been called from loop()
+      onLoop();
+    }
     // for integers and floats, keep track of the total
     if (_value_type == TYPE_INTEGER) total += (float)_value_int;
     else if (_value_type == TYPE_FLOAT) total += _value_float;
     // wait between samples
     if (_samples_interval > 0) wait(_samples_interval);
   }
-  // process the result and send a response back. 
+  // process the result and send a response back
   if (_value_type == TYPE_INTEGER && total > -1) {
     // if the value is an integer, calculate the average value of the samples
     int avg = (int) (total / _samples);
     // if track last value is disabled or if enabled and the current value is different then the old value, send it back
-    if (! _track_last_value || (_track_last_value && avg != _last_value_int) || (_track_last_value && _force_update > 0 && _cycles > _force_update)) {
-      _cycles = 0;
+    if (_isReceive(message) || _isWorthSending(avg != _last_value_int))  {
       _last_value_int = avg;
       _send(_msg.set(avg));
     }
@@ -258,9 +391,8 @@ void Sensor::loop(const MyMessage & message) {
   else if (_value_type == TYPE_FLOAT && total > -1) {
     // calculate the average value of the samples
     float avg = total / _samples;
-    // if track last value is disabled or if enabled and the current value is different then the old value, send it back
-    if (! _track_last_value || (_track_last_value && avg != _last_value_float) || (_track_last_value && _cycles >= _force_update)) {
-      _cycles = 0;
+    // report the value back
+    if (_isReceive(message) || _isWorthSending(avg != _last_value_float))  {
       _last_value_float = avg;
       _send(_msg.set(avg, _float_precision));
     }
@@ -268,8 +400,7 @@ void Sensor::loop(const MyMessage & message) {
   // process a string value
   else if (_value_type == TYPE_STRING) {
     // if track last value is disabled or if enabled and the current value is different then the old value, send it back
-    if (! _track_last_value || (_track_last_value && strcmp(_value_string, _last_value_string) != 0) || (_track_last_value && _cycles >= _force_update)) {
-      _cycles = 0;
+    if (_isReceive(message) || _isWorthSending(strcmp(_value_string, _last_value_string) != 0))  {
       _last_value_string = _value_string;
       _send(_msg.set(_value_string));
     }
@@ -278,6 +409,8 @@ void Sensor::loop(const MyMessage & message) {
   #if POWER_MANAGER == 1
     if (_auto_power_pins) powerOff();
   #endif
+  // restart the report timer if over
+  if (! _isReceive(message) && _report_timer->isRunning() && _report_timer->isOver()) _report_timer->restart();
 }
 
 // receive a message from the radio network
@@ -314,6 +447,28 @@ void Sensor::_send(MyMessage & message) {
   }
 }
 
+// return true if the message is coming from the radio network
+bool Sensor::_isReceive(const MyMessage & message) {
+  if (message.sender == 0 && message.sensor == 0 && message.getCommand() == 0 && message.type == 0) return false;
+  return true;
+}
+
+// determine if a value is worth sending back to the controller
+bool Sensor::_isWorthSending(bool comparison) {
+  // track last value is disabled
+  if (! _track_last_value) return true;
+  // track value is enabled and the current value is different then the old value
+  if (_track_last_value && comparison) return true;
+  // track value is enabled and the timer is over
+  if (_track_last_value && _force_update_timer->isRunning() && _force_update_timer->isOver()) {
+    // restart the timer
+    _force_update_timer->restart();
+    return true;
+  }
+  return false;
+}
+
+
 /*
    SensorAnalogInput
 */
@@ -598,7 +753,8 @@ SensorRainGauge::SensorRainGauge(NodeManager* node_manager, int child_id, int pi
   setPresentation(S_RAIN);
   setType(V_RAIN);
   setValueType(TYPE_FLOAT);
-
+  // create the timer
+  _timer = new Timer(node_manager);
 }
 
 // initialize static variables
@@ -619,6 +775,8 @@ void SensorRainGauge::onBefore() {
   pinMode(_pin, INPUT_PULLUP);
   // attach to the pin's interrupt and execute the routine on falling
   attachInterrupt(digitalPinToInterrupt(_pin), _onTipped, FALLING);
+  // start the timer
+  _timer->start(_report_interval,MINUTES);
 }
 
 // what to do during setup
@@ -643,24 +801,19 @@ void SensorRainGauge::_onTipped() {
 void SensorRainGauge::onLoop() {
   // avoid reporting the same value multiple times
   _value_float = -1;
-  long now = millis();
-  // time elapsed since the last report
-  long elapsed = now - _last_report;
-  // minimum time interval between reports
-  long min_interval = ((long)_report_interval*1000)*60;
-  // time to report or millis() reset
-  if ( (elapsed > min_interval) || (now < _last_report)) {
+  _timer->update();
+  // time to report 
+  if (_timer->isOver()) {
     // report the total amount of rain for the last period
-    _value_float = _count*_single_tip;
+    _value_float = _count * _single_tip;
     #if DEBUG == 1
       Serial.print(F("RAIN I="));
       Serial.print(_child_id);
       Serial.print(F(" T="));
       Serial.println(_value_float);
     #endif
-    // reset the counters
-    _count = 0;
-    _last_report = now;
+    // reset the timer
+    _timer->restart();
   }
 }
 
@@ -668,7 +821,7 @@ void SensorRainGauge::onLoop() {
 void SensorRainGauge::onReceive(const MyMessage & message) {
   if (message.getCommand() == C_REQ) {
     // report the total amount of rain for the last period
-    _value_float = _count*_single_tip;    
+    _value_float = _count * _single_tip;    
   }
 }
 
@@ -893,6 +1046,7 @@ void SensorDigitalInput::onReceive(const MyMessage & message) {
 
 // contructor
 SensorDigitalOutput::SensorDigitalOutput(NodeManager* node_manager, int child_id, int pin): Sensor(node_manager,child_id, pin) {
+  _safeguard_timer = new Timer(node_manager);
 }
 
 // what to do during before
@@ -903,6 +1057,7 @@ void SensorDigitalOutput::onBefore() {
   digitalWrite(_pin, _state);
   // the initial value is now the current value
   _value_int = _initial_value;
+  // create the safeguard timer
 }
 
 // what to do during setup
@@ -922,45 +1077,36 @@ void SensorDigitalOutput::setOnValue(int value) {
 void SensorDigitalOutput::setLegacyMode(bool value) {
   _legacy_mode = value;
 }
+void SensorDigitalOutput::setSafeguard(int value) {
+  _safeguard_timer->set(value,MINUTES);
+}
+int SensorDigitalOutput::getState() {
+  return _state;
+}
+void SensorDigitalOutput::setInputIsElapsed(bool value) {
+  _input_is_elapsed = value;
+}
 
 // main task
 void SensorDigitalOutput::onLoop() {
-  // do nothing on loop
+    // set the value to -1 so to avoid reporting to the gateway during loop
+    _value_int = -1;
+    _last_value_int = -1;
+  // if a safeguard is set, check if it is time for it
+  if (_safeguard_timer->isRunning()) {
+    // update the timer
+    _safeguard_timer->update();
+    // if the time is over, turn the output off
+    if (_safeguard_timer->isOver()) set(LOW);
+  }
 }
 
 // what to do as the main task when receiving a message
 void SensorDigitalOutput::onReceive(const MyMessage & message) {
   // by default handle a SET message but when legacy mode is set when a REQ message is expected instead
   if ( (message.getCommand() == C_SET && ! _legacy_mode) || (message.getCommand() == C_REQ && _legacy_mode)) {
-    // retrieve from the message the value to set
-    int value = message.getInt();
-    if (value != 0 && value != 1) return;
-    #if DEBUG == 1
-      Serial.print(F("DOUT I="));
-      Serial.print(_child_id);
-      Serial.print(F(" P="));
-      Serial.print(_pin);
-      Serial.print(F(" V="));
-      Serial.print(value);
-      Serial.print(F(" P="));
-      Serial.println(_pulse_width);
-    #endif
-    // reverse the value if needed
-    int value_to_write = value;
-    if (_on_value == LOW) {
-      if (value == HIGH) value_to_write = LOW;
-      if (value == LOW) value_to_write = HIGH;
-    }
-    // set the value
-    digitalWrite(_pin, value_to_write);
-    if (_pulse_width > 0) {
-      // if this is a pulse output, restore the value to the original value after the pulse
-      wait(_pulse_width);
-      digitalWrite(_pin, value_to_write == 0 ? HIGH: LOW);
-    }
-    // store the current value so it will be sent to the controller
-    _state = value;
-    _value_int = value;
+    // switch the output
+    set(message.getInt());
   }
   if (message.getCommand() == C_REQ && ! _legacy_mode) {
     // return the current status
@@ -968,6 +1114,51 @@ void SensorDigitalOutput::onReceive(const MyMessage & message) {
   }
 }
 
+// write the value to the output
+void SensorDigitalOutput::set(int value) {
+  if (_input_is_elapsed) {
+    if (value == LOW) {
+      // stop the timer
+      _safeguard_timer->stop();
+    } else {
+      // configure and start the timer
+      _safeguard_timer->start(value,MINUTES);
+      // if the input is an elapsed time, unless the value is LOW, the output will be always on
+      value = HIGH;
+    }
+  } else {
+    // if turning the output on and a safeguard timer is configured, start it
+    if (value == HIGH && _safeguard_timer->isConfigured() && ! _safeguard_timer->isRunning()) _safeguard_timer->start();
+  }
+  #if DEBUG == 1
+    Serial.print(F("DOUT I="));
+    Serial.print(_child_id);
+    Serial.print(F(" P="));
+    Serial.print(_pin);
+    Serial.print(F(" V="));
+    Serial.print(value);
+    Serial.print(F(" P="));
+    Serial.println(_pulse_width);
+  #endif
+  // reverse the value if needed
+  int value_to_write = value;
+  if (_on_value == LOW) {
+    if (value == HIGH) value_to_write = LOW;
+    if (value == LOW) value_to_write = HIGH;
+  }
+  // set the value
+  digitalWrite(_pin, value_to_write);
+  if (_pulse_width > 0) {
+    // if this is a pulse output, restore the value to the original value after the pulse
+    wait(_pulse_width);
+    digitalWrite(_pin, value_to_write == 0 ? HIGH: LOW);
+  }
+  // store the current value so it will be sent to the controller
+  _state = value;
+  _value_int = value;
+}
+
+
 /*
    SensorRelay
 */
@@ -978,13 +1169,14 @@ SensorRelay::SensorRelay(NodeManager* node_manager, int child_id, int pin): Sens
   setPresentation(S_BINARY);
   setType(V_STATUS);
 }
-
+/*
 // define what to do during loop
 void SensorRelay::onLoop() {
     // set the value to -1 so to avoid reporting to the gateway during loop
     _value_int = -1;
+    _last_value_int = -1;
 }
-
+*/
 /*
    SensorLatchingRelay
 */
@@ -1884,7 +2076,10 @@ void NodeManager::setRetries(int value) {
     _battery_max = value;
   }
   void NodeManager::setBatteryReportCycles(int value) {
-    _battery_report_cycles = value;
+    _battery_report_timer.set(value,CYCLES);
+  }
+  void NodeManager::setBatteryReportMinutes(int value) {
+    _battery_report_timer.set(value,MINUTES);
   }
   void NodeManager::setBatteryInternalVcc(bool value) {
     _battery_internal_vcc = value;
@@ -1905,12 +2100,21 @@ void NodeManager::setSleepMode(int value) {
 void NodeManager::setMode(int value) {
   setSleepMode(value);
 }
+int NodeManager::getMode() {
+  return _sleep_mode;
+}
 void NodeManager::setSleepTime(int value) {
   _sleep_time = value;
 }
+int NodeManager::getSleepTime() {
+  return _sleep_time;
+}
 void NodeManager::setSleepUnit(int value) {
   _sleep_unit = value;
 }
+int NodeManager::getSleepUnit() {
+  return _sleep_unit;
+}
 void NodeManager::setSleep(int value1, int value2, int value3) {
   _sleep_mode = value1;
   _sleep_time = value2;
@@ -1966,6 +2170,12 @@ float NodeManager::celsiusToFahrenheit(float temperature) {
   return temperature * 1.8 + 32;
 }
 
+// return true if sleep or wait is configured and hence this is a sleeping node
+bool NodeManager::isSleepingNode() {
+  if (_sleep_mode == SLEEP || _sleep_mode == WAIT) return true;
+  return false;
+}
+
 // register a sensor to this manager
 int NodeManager::registerSensor(int sensor_type, int pin, int child_id) {
   // get a child_id if not provided by the user
@@ -2256,6 +2466,9 @@ void NodeManager::before() {
   #if BATTERY_MANAGER == 1 && !defined(MY_GATEWAY_ESP8266)
     // set analogReference to internal if measuring the battery through a pin
     if (! _battery_internal_vcc && _battery_pin > -1) analogReference(INTERNAL);
+    // if not configured report battery every 10 cycles
+    if (! _battery_report_timer.isConfigured()) _battery_report_timer.set(60,MINUTES);
+    _battery_report_timer.start();
   #endif
   // setup individual sensors
   for (int i = 0; i < MAX_SENSORS; i++) {
@@ -2323,25 +2536,39 @@ void NodeManager::loop() {
   if (_sleep_mode == IDLE) return;
   // if sleep time is not set, do nothing
   if ((_sleep_mode == SLEEP || _sleep_mode == WAIT) &&  _sleep_time == 0) return;
-    #if POWER_MANAGER == 1
-      // turn on the pin powering all the sensors
-      if (_auto_power_pins) powerOn();
-    #endif
-    // run loop for all the registered sensors
-    for (int i = 0; i < MAX_SENSORS; i++) {
-      // skip not configured sensors
-      if (_sensors[i] == 0) continue;
-      // if waking up from an interrupt skip all the sensor without that interrupt configured
-      if (_last_interrupt_pin != -1 && _sensors[i]->getInterruptPin() != _last_interrupt_pin) continue;
-      // call each sensor's loop()
-      _sensors[i]->loop(empty);
+  #if BATTERY_MANAGER == 1
+    // update the timer for battery report
+    if (_battery_report_timer.getUnit() == MINUTES) _battery_report_timer.update();
+    if (_battery_report_timer.getUnit() == CYCLES && (_last_interrupt_pin == -1 || _battery_report_with_interrupt)) _battery_report_timer.update();
+    // keep track of the number of sleeping cycles (ignoring if )
+    if (_last_interrupt_pin == -1 || _battery_report_with_interrupt) 
+    // if it is time to report the battery level
+    if (_battery_report_timer.isOver()) {
+      // time to report the battery level again
+      _process("BATTERY");
+      // restart the timer
+      _battery_report_timer.restart();
     }
-    #if POWER_MANAGER == 1
-      // turn off the pin powering all the sensors
-      if (_auto_power_pins) powerOff();
-    #endif
-    // continue/start sleeping as requested
-    if (_sleep_mode == SLEEP || _sleep_mode == WAIT) _sleep();
+  #endif
+  #if POWER_MANAGER == 1
+    // turn on the pin powering all the sensors
+    if (_auto_power_pins) powerOn();
+  #endif
+  // run loop for all the registered sensors
+  for (int i = 0; i < MAX_SENSORS; i++) {
+    // skip not configured sensors
+    if (_sensors[i] == 0) continue;
+    // if waking up from an interrupt skip all the sensor without that interrupt configured
+    if (_last_interrupt_pin != -1 && _sensors[i]->getInterruptPin() != _last_interrupt_pin) continue;
+    // call each sensor's loop()
+    _sensors[i]->loop(empty);
+  }
+  #if POWER_MANAGER == 1
+    // turn off the pin powering all the sensors
+    if (_auto_power_pins) powerOff();
+  #endif
+  // continue/start sleeping as requested
+  if (_sleep_mode == SLEEP || _sleep_mode == WAIT) _sleep();
 }
 
 // dispacth inbound messages
@@ -2650,16 +2877,6 @@ void NodeManager::_sleep() {
     // notify the controller I am awake
     _send(_msg.set("AWAKE"));
   #endif
-  #if BATTERY_MANAGER == 1
-    // keep track of the number of sleeping cycles (ignoring if woke up by an interrupt)
-    if (interrupt == -1 || _battery_report_with_interrupt) _cycles++;
-    // battery has to be reported after the configured number of sleep cycles
-    if (_battery_report_cycles == _cycles) {
-      // time to report the battery level again
-      _process("BATTERY");
-      _cycles = 0;
-    }
-  #endif
 }
 
 // present the service
diff --git a/NodeManager.h b/NodeManager.h
index c1ccddf..224e3e5 100644
--- a/NodeManager.h
+++ b/NodeManager.h
@@ -24,6 +24,7 @@
 #define MINUTES 1
 #define HOURS 2
 #define DAYS 3
+#define CYCLES 4
 
 // define value type
 #define TYPE_INTEGER 0
@@ -314,9 +315,13 @@ class PowerManager {
     PowerManager() {};
     // to save battery the sensor can be optionally connected to two pins which will act as vcc and ground and activated on demand
     void setPowerPins(int ground_pin, int vcc_pin, int wait_time = 50);
+    // turns the power pins on
     void powerOn();
+    // turns the power pins on
     void powerOff();
+    // returns the Vcc voltge
     float getVcc();
+    // turns true if power pins are configured
     bool isConfigured();
   private:
     int _vcc_pin = -1;
@@ -324,7 +329,47 @@ class PowerManager {
     long _wait = 0;
 };
 
+/*
+   Timer
+*/
 
+class Timer {
+  public:
+    Timer(NodeManager* node_manager);
+    // start the timer which will be over when interval passes by. Unit can be either CYCLES or MINUTES
+    void start(long target, int unit);
+    void start();
+    // stop the timer
+    void stop();
+    // set the timer configuration but do not start it
+    void set(long target, int unit);
+    // update the timer. To be called at every cycle
+    void update();
+    // returns true if the time is over
+    bool isOver();
+    // return true if the timer is running
+    bool isRunning();
+    // returns true if the timer has been configured
+    bool isConfigured();
+    // reset the timer and start over
+    void restart();
+    // return the current elapsed time
+    float getElapsed();
+    // return the configured unit
+    int getUnit();
+    // return the configured target
+    int getTarget();
+   private:
+    NodeManager* _node_manager;
+    long _target = 0;
+    int _unit = 0;
+    float _elapsed = 0;
+    bool _use_millis = false;
+    long _last_millis = 0;
+    float _sleep_time = 0;
+    bool _is_running = false;
+    bool _is_configured = false;
+};
 /***************************************
    Sensor: generic sensor class
 */
@@ -353,10 +398,13 @@ class Sensor {
     void setSamples(int value);
     // If more then one sample has to be taken, set the interval in milliseconds between measurements (default: 0)
     void setSamplesInterval(int value);
-    // if true will report the measure only if different then the previous one (default: false)
-    void setTackLastValue(bool value);
+    // if true will report the measure only if different than the previous one (default: false)
+    void setTrackLastValue(bool value);
     // if track last value is enabled, force to send an update after the configured number of cycles (default: -1)
     void setForceUpdate(int value);
+    void setForceUpdateCycles(int value);
+    // if track last value is enabled, force to send an update after the configured number of minutes (default: -1)
+    void setForceUpdateMinutes(int value);
     // the value type of this sensor (default: TYPE_INTEGER)
     void setValueType(int value);
     int getValueType();
@@ -381,6 +429,10 @@ class Sensor {
     int getValueInt();
     float getValueFloat();
     char* getValueString();
+    // After how many cycles the sensor will report back its measure (default: 1 cycle)
+    void setReportIntervalCycles(int value);
+    // After how many minutes the sensor will report back its measure (default: 1 cycle)
+    void setReportIntervalMinutes(int value);
     // define what to do at each stage of the sketch
     virtual void before();
     virtual void presentation();
@@ -406,8 +458,6 @@ class Sensor {
     int _samples = 1;
     int _samples_interval = 0;
     bool _track_last_value = false;
-    int _cycles = 0;
-    int _force_update = -1;
     int _value_type = TYPE_INTEGER;
     int _float_precision = 2;
     int _value_int = -1;
@@ -421,7 +471,11 @@ class Sensor {
       PowerManager _powerManager;
       bool _auto_power_pins = true;
     #endif
+    Timer* _report_timer;
+    Timer* _force_update_timer;
     void _send(MyMessage & msg);
+    bool _isReceive(const MyMessage & message);
+    bool _isWorthSending(bool comparison);
 };
 
 /*
@@ -591,9 +645,9 @@ class SensorACS712: public Sensor {
 class SensorRainGauge: public Sensor {
   public:
     SensorRainGauge(NodeManager* node_manager, int child_id, int pin);
-    // set how frequently to report back to the controller in minutes. After reporting the measure is resetted (default: 60);
+    // set how frequently to report back to the controller in minutes. After reporting the measure is resetted (default: 60)
     void setReportInterval(int value);
-    // set how many mm of rain to count for each tip (default: 0.11);
+    // set how many mm of rain to count for each tip (default: 0.11)
     void setSingleTip(float value);
     // define what to do at each stage of the sketch
     void onBefore();
@@ -607,7 +661,7 @@ class SensorRainGauge: public Sensor {
   protected:
     int _report_interval = 60;
     float _single_tip = 0.11;
-    long _last_report = 0;
+    Timer* _timer;
 };
 
 /*
@@ -653,6 +707,14 @@ class SensorDigitalOutput: public Sensor {
     void setOnValue(int value);
     // when legacy mode is enabled expect a REQ message to trigger, otherwise the default SET (default: false)
     void setLegacyMode(bool value);
+    // automatically turn the output off after the given number of minutes
+    void setSafeguard(int value);
+    // if true the input value becomes a duration in minutes after which the output will be automatically turned off (default: false)
+    void setInputIsElapsed(bool value);
+    // manually switch the output to the provided value
+    void set(int value);
+    // get the current state
+    int getState();
     // define what to do at each stage of the sketch
     void onBefore();
     void onSetup();
@@ -664,6 +726,8 @@ class SensorDigitalOutput: public Sensor {
     int _state = 0;
     int _pulse_width = 0;
     bool _legacy_mode = false;
+    bool _input_is_elapsed = false;
+    Timer* _safeguard_timer;
 };
 
 
@@ -674,7 +738,7 @@ class SensorRelay: public SensorDigitalOutput {
   public:
     SensorRelay(NodeManager* node_manager, int child_id, int pin);
     // define what to do at each stage of the sketch
-    void onLoop();
+    //void onLoop();
 };
 
 /*
@@ -1005,8 +1069,10 @@ class NodeManager {
       void setBatteryMin(float value);
       // the expected vcc when the batter is fully charged, used to calculate the percentage (default: 3.3)
       void setBatteryMax(float value);
-      // after how many sleeping cycles report the battery level to the controller. When reset the battery is always reported (default: 10)
+      // after how many sleeping cycles report the battery level to the controller. When reset the battery is always reported (default: -)
       void setBatteryReportCycles(int value);
+      // after how many minutes report the battery level to the controller. When reset the battery is always reported (default: 60)
+      void setBatteryReportMinutes(int value);
       // if true, the battery level will be evaluated by measuring the internal vcc without the need to connect any pin, if false the voltage divider methon will be used (default: true)
       void setBatteryInternalVcc(bool value);
       // if setBatteryInternalVcc() is set to false, the analog pin to which the battery's vcc is attached (https://www.mysensors.org/build/battery) (default: -1)
@@ -1019,10 +1085,13 @@ class NodeManager {
     // define the way the node should behave. It can be IDLE (stay awake withtout executing each sensors' loop), SLEEP (go to sleep for the configured interval), WAIT (wait for the configured interval), ALWAYS_ON (stay awake and execute each sensors' loop)
     void setSleepMode(int value);
     void setMode(int value);
+    int getMode();
     // define for how long the board will sleep (default: 0)
     void setSleepTime(int value);
+    int getSleepTime();
     // define the unit of SLEEP_TIME. It can be SECONDS, MINUTES, HOURS or DAYS (default: MINUTES)
     void setSleepUnit(int value);
+    int getSleepUnit();
     // configure the node's behavior, parameters are mode, time and unit
     void setSleep(int value1, int value2, int value3);
     // if enabled, when waking up from the interrupt, the board stops sleeping. Disable it when attaching e.g. a motion sensor (default: true)
@@ -1063,6 +1132,8 @@ class NodeManager {
     bool getIsMetric();
     // Convert a temperature from celsius to fahrenheit depending on how isMetric is set
     float celsiusToFahrenheit(float temperature);
+    // return true if sleep or wait is configured and hence this is a sleeping node
+    bool isSleepingNode();
     // hook into the main sketch functions
     void before();
     void presentation();
@@ -1074,12 +1145,11 @@ class NodeManager {
     #if BATTERY_MANAGER == 1
       float _battery_min = 2.6;
       float _battery_max = 3.3;
-      int _battery_report_cycles = 10;
+      Timer _battery_report_timer = Timer(this);
       bool _battery_report_with_interrupt = true;
       bool _battery_internal_vcc = true;
       int _battery_pin = -1;
       float _battery_volts_per_bit = 0.003363075;
-      int _cycles = 0;
       float _getVcc();
     #endif
     #if POWER_MANAGER == 1
diff --git a/NodeManager.ino b/NodeManager.ino
index 6422f43..4e6def9 100644
--- a/NodeManager.ino
+++ b/NodeManager.ino
@@ -36,6 +36,7 @@ void before() {
   
   
   
+  
   /*
    * Register above your sensors
   */
@@ -58,7 +59,6 @@ void setup() {
 void loop() {
   // call NodeManager loop routine
   nodeManager.loop();
-
 }
 
 // receive
diff --git a/README.md b/README.md
index a67ffbb..f9c299d 100644
--- a/README.md
+++ b/README.md
@@ -202,8 +202,10 @@ Node Manager comes with a reasonable default configuration. If you want/need to
       void setBatteryMin(float value);
       // the expected vcc when the batter is fully charged, used to calculate the percentage (default: 3.3)
       void setBatteryMax(float value);
-      // after how many sleeping cycles report the battery level to the controller. When reset the battery is always reported (default: 10)
+      // after how many sleeping cycles report the battery level to the controller. When reset the battery is always reported (default: -)
       void setBatteryReportCycles(int value);
+      // after how many minutes report the battery level to the controller. When reset the battery is always reported (default: 60)
+      void setBatteryReportMinutes(int value);
       // if true, the battery level will be evaluated by measuring the internal vcc without the need to connect any pin, if false the voltage divider methon will be used (default: true)
       void setBatteryInternalVcc(bool value);
       // if setBatteryInternalVcc() is set to false, the analog pin to which the battery's vcc is attached (https://www.mysensors.org/build/battery) (default: -1)
@@ -263,6 +265,8 @@ Node Manager comes with a reasonable default configuration. If you want/need to
     bool getIsMetric();
     // Convert a temperature from celsius to fahrenheit depending on how isMetric is set
     float celsiusToFahrenheit(float temperature);
+    // return true if sleep or wait is configured and hence this is a sleeping node
+    bool isSleepingNode();
 ~~~
 
 For example
@@ -371,9 +375,12 @@ The following methods are available for all the sensors:
     // If more then one sample has to be taken, set the interval in milliseconds between measurements (default: 0)
     void setSamplesInterval(int value);
     // if true will report the measure only if different then the previous one (default: false)
-    void setTackLastValue(bool value);
+    void setTrackLastValue(bool value);
     // if track last value is enabled, force to send an update after the configured number of cycles (default: -1)
     void setForceUpdate(int value);
+    void setForceUpdateCycles(int value);
+    // if track last value is enabled, force to send an update after the configured number of minutes (default: -1)
+    void setForceUpdateMinutes(int value);
     // the value type of this sensor (default: TYPE_INTEGER)
     void setValueType(int value);
 	int getValueType();
@@ -398,6 +405,10 @@ The following methods are available for all the sensors:
     int getValueInt();
     float getValueFloat();
     char* getValueString();
+    // After how many cycles the sensor will report back its measure (default: 1 cycle)
+    void setReportIntervalCycles(int value);
+    // After how many minutes the sensor will report back its measure (default: 1 cycle)
+    void setReportIntervalMinutes(int value);
 ~~~
 
 #### Sensor's specific configuration
@@ -468,6 +479,14 @@ Each sensor class can expose additional methods.
     void setOnValue(int value);
     // when legacy mode is enabled expect a REQ message to trigger, otherwise the default SET (default: false)
     void setLegacyMode(bool value);
+    // automatically turn the output off after the given number of minutes
+    void setSafeguard(int value);
+    // if true the input value becomes a duration in minutes after which the output will be automatically turned off (default: false)
+    void setInputIsElapsed(bool value);
+    // manually switch the output to the provided value
+    void set(int value);
+    // get the current state
+    int getState();
 ~~~
 
 *  SensorSwitch / SensorDoor / SensorMotion
-- 
GitLab