From 01bc9d8a1fc890f27483f6b9ad425321318c6cd9 Mon Sep 17 00:00:00 2001
From: user2684 <user2684@users.noreply.github.com>
Date: Sun, 9 Jul 2017 19:26:39 +0200
Subject: [PATCH] New interrupt handling framework which covers both sleeping
 and NOT sleeping nodes  (#142, #149, #166, #154)

---
 NodeManager.cpp | 317 ++++++++++++++++++++++++++++++++----------------
 NodeManager.h   |  54 +++++++--
 NodeManager.ino |   2 +
 README.md       |  22 +++-
 4 files changed, 279 insertions(+), 116 deletions(-)

diff --git a/NodeManager.cpp b/NodeManager.cpp
index 1e32a47..21d6451 100644
--- a/NodeManager.cpp
+++ b/NodeManager.cpp
@@ -323,6 +323,12 @@ void Sensor::setReportIntervalMinutes(int value) {
   _report_timer->start(value,MINUTES);
 }
 
+// listen for interrupts on the given pin so interrupt() will be called when occurring
+void Sensor::setInterrupt(int pin, int mode, int initial) {
+  _interrupt_pin = pin;
+  _node_manager->setInterrupt(pin,mode,initial);
+}
+
 // present the sensor to the gateway and controller
 void Sensor::presentation() {
   #if DEBUG == 1
@@ -365,7 +371,6 @@ void Sensor::loop(const MyMessage & message) {
   #endif
   // 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
   // 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
@@ -391,6 +396,7 @@ void Sensor::loop(const MyMessage & message) {
     if (_isReceive(message) || _isWorthSending(avg != _last_value_int))  {
       _last_value_int = avg;
       _send(_msg.set(avg));
+      _value_int = -1;
     }
   }
   // process a float value
@@ -401,6 +407,7 @@ void Sensor::loop(const MyMessage & message) {
     if (_isReceive(message) || _isWorthSending(avg != _last_value_float))  {
       _last_value_float = avg;
       _send(_msg.set(avg, _float_precision));
+      _value_float = -1;
     }
   }
   // process a string value
@@ -409,6 +416,7 @@ void Sensor::loop(const MyMessage & message) {
     if (_isReceive(message) || _isWorthSending(strcmp(_value_string, _last_value_string) != 0))  {
       _last_value_string = _value_string;
       _send(_msg.set(_value_string));
+      _value_string = "";
     }
   }
   // turn the sensor off
@@ -419,6 +427,12 @@ void Sensor::loop(const MyMessage & message) {
   if (! _isReceive(message) && _report_timer->isRunning() && _report_timer->isOver()) _report_timer->restart();
 }
 
+// receive and handle an interrupt
+void Sensor::interrupt() {
+  // call the implementation of onInterrupt()
+  onInterrupt();
+}
+
 // receive a message from the radio network
 void Sensor::receive(const MyMessage &message) {
   // return if not for this sensor
@@ -587,6 +601,10 @@ void SensorAnalogInput::onProcess(Request & request) {
   _send(_msg_service.set(function));
 }
 
+// what to do when receiving an interrupt
+void SensorAnalogInput::onInterrupt() {
+}
+
 // read the analog input
 int SensorAnalogInput::_getAnalogRead() {
   #ifndef MY_GATEWAY_ESP8266
@@ -712,6 +730,10 @@ void SensorThermistor::onProcess(Request & request) {
   _send(_msg_service.set(function));
 }
 
+// what to do when receiving an interrupt
+void SensorThermistor::onInterrupt() {
+}
+
 /*
    SensorML8511
 */
@@ -764,6 +786,10 @@ void SensorML8511::onReceive(const MyMessage & message) {
 void SensorML8511::onProcess(Request & request) {
 }
 
+// what to do when receiving an interrupt
+void SensorML8511::onInterrupt() {
+}
+
 // The Arduino Map function but for floats
 float SensorML8511::_mapfloat(float x, float in_min, float in_max, float out_min, float out_max) {
   return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min;
@@ -830,6 +856,10 @@ void SensorACS712::onProcess(Request & request) {
   _send(_msg_service.set(function));
 }
 
+// what to do when receiving an interrupt
+void SensorACS712::onInterrupt() {
+}
+
 /*
    SensorRainGauge
 */
@@ -840,75 +870,47 @@ 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
-long SensorRainGauge::_last_tip = 0;
-long SensorRainGauge::_count = 0;
-
 // setter/getter
-void SensorRainGauge::setReportInterval(int value) {
-  _report_interval = value;
-}
 void SensorRainGauge::setSingleTip(float value) {
   _single_tip = value;
 }
+void SensorRainGauge::setInitialValue(int value) {
+  _initial_value = value;
+}
 
 // what to do during before
 void SensorRainGauge::onBefore() {
-  // set the pin as input and enabled pull up
-  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);
+  // configure the interrupt pin so onInterrupt() will be called on tip
+  setInterrupt(_pin,FALLING,_initial_value);
 }
 
 // what to do during setup
 void SensorRainGauge::onSetup() {
 }
 
-// what to do when when receiving an interrupt
-void SensorRainGauge::_onTipped() {
-  long now = millis();
-  // on tipping, two consecutive interrupts are received, ignore the second one
-  if ( (now - _last_tip > 100) || (now < _last_tip) ){
-    // increase the counter
-    _count++;
-    #if DEBUG == 1
-      Serial.println(F("RAIN+"));
-    #endif
-  }
-  _last_tip = now;
-}
-
 // what to do during loop
 void SensorRainGauge::onLoop() {
-  // avoid reporting the same value multiple times
-  _value_float = -1;
-  _timer->update();
-  // time to report 
-  if (_timer->isOver()) {
-    // report the total amount of rain for the last period
-    _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 timer
-    _timer->restart();
-  }
+  // do not execute loop if called by an interrupt
+  if (_node_manager->getLastInterruptPin() == _interrupt_pin) return;
+  // time to report the rain so far
+  _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 counter
+  _count = 0;
 }
 
 // what to do as the main task when receiving a message
 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;
   }
 }
 
@@ -916,13 +918,21 @@ void SensorRainGauge::onReceive(const MyMessage & message) {
 void SensorRainGauge::onProcess(Request & request) {
   int function = request.getFunction();
   switch(function) {
-    case 101: setReportInterval(request.getValueInt()); break;
     case 102: setSingleTip(request.getValueFloat()); break;
     default: return;
   }
   _send(_msg_service.set(function));
 }
 
+// what to do when receiving an interrupt
+void SensorRainGauge::onInterrupt() {
+  // increase the counter
+  _count++;
+  #if DEBUG == 1
+    Serial.println(F("RAIN+"));
+  #endif
+}
+
 /*
    SensorRain
 */
@@ -998,6 +1008,10 @@ void SensorDigitalInput::onReceive(const MyMessage & message) {
 // what to do when receiving a remote message
 void SensorDigitalInput::onProcess(Request & request) {
 }
+
+// what to do when receiving an interrupt
+void SensorDigitalInput::onInterrupt() {
+}
 #endif
 
 
@@ -1080,6 +1094,10 @@ void SensorDigitalOutput::onProcess(Request & request) {
   _send(_msg_service.set(function));
 }
 
+// what to do when receiving an interrupt
+void SensorDigitalOutput::onInterrupt() {
+}
+
 // write the value to the output
 void SensorDigitalOutput::setStatus(int value) {
   // pre-process the input value
@@ -1301,6 +1319,10 @@ void SensorDHT::onReceive(const MyMessage & message) {
 // what to do when receiving a remote message
 void SensorDHT::onProcess(Request & request) {
 }
+
+// what to do when receiving an interrupt
+void SensorDHT::onInterrupt() {
+}
 #endif
 
 /*
@@ -1376,6 +1398,10 @@ void SensorSHT21::onReceive(const MyMessage & message) {
 // what to do when receiving a remote message
 void SensorSHT21::onProcess(Request & request) {
 }
+
+// what to do when receiving an interrupt
+void SensorSHT21::onInterrupt() {
+}
 #endif
 
 /*
@@ -1415,8 +1441,7 @@ void SensorSwitch::onBefore() {
   if (_mode == RISING) _value_int = LOW;
   else if (_mode == FALLING) _value_int = HIGH;
   // set the interrupt pin so it will be called only when waking up from that interrupt
-  _interrupt_pin = _pin;
-  _node_manager->setInterrupt(_pin,_mode,_initial);
+  setInterrupt(_pin,_mode,_initial);
 }
 
 // what to do during setup
@@ -1425,6 +1450,30 @@ void SensorSwitch::onSetup() {
 
 // what to do during loop
 void SensorSwitch::onLoop() {
+}
+
+// what to do as the main task when receiving a message
+void SensorSwitch::onReceive(const MyMessage & message) {
+  if (message.getCommand() == C_REQ) {
+    _value_int = digitalRead(_pin);
+  }
+}
+
+// what to do when receiving a remote message
+void SensorSwitch::onProcess(Request & request) {
+  int function = request.getFunction();
+  switch(function) {
+    case 101: setMode(request.getValueInt()); break;
+    case 102: setDebounce(request.getValueInt()); break;
+    case 103: setTriggerTime(request.getValueInt()); break;
+    case 104: setInitial(request.getValueInt()); break;
+    default: return;
+  }
+  _send(_msg_service.set(function));
+}
+
+// what to do when receiving an interrupt
+void SensorSwitch::onInterrupt() {
   // wait to ensure the the input is not floating
   if (_debounce > 0) wait(_debounce);
   // read the value of the pin
@@ -1447,23 +1496,6 @@ void SensorSwitch::onLoop() {
     _value_int = -1;
   }
 }
-// what to do as the main task when receiving a message
-void SensorSwitch::onReceive(const MyMessage & message) {
-  if (message.getCommand() == C_REQ) onLoop();
-}
-
-// what to do when receiving a remote message
-void SensorSwitch::onProcess(Request & request) {
-  int function = request.getFunction();
-  switch(function) {
-    case 101: setMode(request.getValueInt()); break;
-    case 102: setDebounce(request.getValueInt()); break;
-    case 103: setTriggerTime(request.getValueInt()); break;
-    case 104: setInitial(request.getValueInt()); break;
-    default: return;
-  }
-  _send(_msg_service.set(function));
-}
 
 /*
  * SensorDoor
@@ -1548,6 +1580,10 @@ void SensorDs18b20::onProcess(Request & request) {
   _send(_msg_service.set(function));
 }
 
+// what to do when receiving an interrupt
+void SensorDs18b20::onInterrupt() {
+}
+
 // function to print a device address
 DeviceAddress* SensorDs18b20::getDeviceAddress() {
   return &_device_address;
@@ -1610,6 +1646,11 @@ void SensorBH1750::onReceive(const MyMessage & message) {
 // what to do when receiving a remote message
 void SensorBH1750::onProcess(Request & request) {
 }
+
+
+// what to do when receiving an interrupt
+void SensorBH1750::onInterrupt() {
+}
 #endif
 
 /*
@@ -1658,6 +1699,10 @@ void SensorMLX90614::onReceive(const MyMessage & message) {
 // what to do when receiving a remote message
 void SensorMLX90614::onProcess(Request & request) {
 }
+
+// what to do when receiving an interrupt
+void SensorMLX90614::onInterrupt() {
+}
 #endif
 
 
@@ -1728,6 +1773,10 @@ void SensorBosch::onProcess(Request & request) {
   _send(_msg_service.set(function));
 }
 
+// what to do when receiving an interrupt
+void SensorBosch::onInterrupt() {
+}
+
 // calculate and send the forecast back
 void SensorBosch::_forecast(float pressure) {
   if (isnan(pressure)) return;
@@ -2010,6 +2059,10 @@ void SensorHCSR04::onProcess(Request & request) {
   }
   _send(_msg_service.set(function));
 }
+
+// what to do when receiving an interrupt
+void SensorHCSR04::onInterrupt() {
+}
 #endif
 
 /*
@@ -2055,8 +2108,6 @@ void SensorSonoff::onSetup() {
 
 // what to do during loop
 void SensorSonoff::onLoop() {
-  // set the value to -1 so to avoid reporting to the gateway during loop
-  _value_int = -1;
   _debouncer.update();
   // Get the update value from the button
   int value = _debouncer.read();
@@ -2094,6 +2145,10 @@ void SensorSonoff::onProcess(Request & request) {
   _send(_msg_service.set(function));
 }
 
+// what to do when receiving an interrupt
+void SensorSonoff::onInterrupt() {
+}
+
 // toggle the state
 void SensorSonoff::_toggle() {
   // toggle the state
@@ -2167,6 +2222,10 @@ void SensorMCP9808::onReceive(const MyMessage & message) {
 // what to do when receiving a remote message
 void SensorMCP9808::onProcess(Request & request) {
 }
+
+// what to do when receiving an interrupt
+void SensorMCP9808::onInterrupt() {
+}
 #endif
 
 
@@ -2285,6 +2344,10 @@ void SensorMQ::onProcess(Request & request) {
   _send(_msg_service.set(function));
 }
 
+// what to do when receiving an interrupt
+void SensorMQ::onInterrupt() {
+}
+
 // returns the calculated sensor resistance
 float SensorMQ::_MQResistanceCalculation(int raw_adc) {
   return ( ((float)_rl_value*(1023-raw_adc)/raw_adc));
@@ -2449,6 +2512,11 @@ NodeManager::NodeManager() {
   _msg = MyMessage(CONFIGURATION_CHILD_ID, V_CUSTOM);
 }
 
+int NodeManager::_last_interrupt_pin = -1;
+long NodeManager::_last_interrupt_1 = millis();
+long NodeManager::_last_interrupt_2 = millis();
+long NodeManager::_interrupt_min_delta = 100;
+
 // setter/getter
 void NodeManager::setRetries(int value) {
   _retries = value;
@@ -2511,16 +2579,19 @@ void NodeManager::setSleep(int value1, int value2, int value3) {
 void NodeManager::setSleepInterruptPin(int value) {
   _sleep_interrupt_pin = value;
 }
-void NodeManager::setInterrupt(int pin, int mode, int pull) {
+void NodeManager::setInterrupt(int pin, int mode, int initial) {
   if (pin == INTERRUPT_PIN_1) {
     _interrupt_1_mode = mode;
-    _interrupt_1_pull = pull;
+    _interrupt_1_initial = initial;
   }
-  if (pin == INTERRUPT_PIN_2) {
+  if (pin == INTERRUPT_PIN_2) { 
     _interrupt_2_mode = mode;
-    _interrupt_2_pull = pull;
+    _interrupt_2_initial = initial;
   }
 }
+void NodeManager::setInterruptMinDelta(long value) {
+  _interrupt_min_delta = value;
+}
 #if POWER_MANAGER == 1
   void NodeManager::setPowerPins(int ground_pin, int vcc_pin, int wait_time) {
     _powerManager.setPowerPins(ground_pin, vcc_pin, wait_time);
@@ -2823,26 +2894,6 @@ void NodeManager::before() {
     Serial.print(F(" B="));
     Serial.println(MY_CAP_RXBUF);
   #endif
-  // setup the sleep interrupt pin
-  if (_sleep_interrupt_pin > -1) {
-    // set the interrupt when the pin is connected to ground
-    setInterrupt(_sleep_interrupt_pin,FALLING,HIGH);
-  }
-  // setup the interrupt pins
-  if (_interrupt_1_mode != MODE_NOT_DEFINED) {
-    pinMode(INTERRUPT_PIN_1,INPUT);
-    if (_interrupt_1_pull > -1) digitalWrite(INTERRUPT_PIN_1,_interrupt_1_pull);
-  }
-  if (_interrupt_2_mode != MODE_NOT_DEFINED) {
-    pinMode(INTERRUPT_PIN_2, INPUT);
-    if (_interrupt_2_pull > -1) digitalWrite(INTERRUPT_PIN_2,_interrupt_2_pull);
-  }
-  #if DEBUG == 1
-    Serial.print(F("INT1 M="));
-    Serial.println(_interrupt_1_mode);
-    Serial.print(F("INT2 M="));
-    Serial.println(_interrupt_2_mode);
-  #endif
   #if PERSIST == 1
     // restore the configuration saved in the eeprom
     _loadConfig();
@@ -2860,6 +2911,8 @@ void NodeManager::before() {
     // call each sensor's setup()
     _sensors[i]->before();
   }
+  // setup the interrupt pins
+  setupInterrupts();
 }
 
 // present NodeManager and its sensors
@@ -2919,13 +2972,11 @@ void NodeManager::loop() {
   // if in idle mode, do nothing
   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 (isSleepingNode() &&  _sleep_time == 0) return;
   #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
@@ -2942,17 +2993,19 @@ void NodeManager::loop() {
   for (int i = 1; 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()
+    // if there was an interrupt for this sensor, call the sensor's interrupt()
+    if (_last_interrupt_pin != -1 && _sensors[i]->getInterruptPin() == _last_interrupt_pin) _sensors[i]->interrupt();
+    // call the sensor's loop()
     _sensors[i]->loop(empty);
   }
+  // reset the last interrupt pin
+  _last_interrupt_pin = -1;
   #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();
+  if (isSleepingNode()) _sleep();
 }
 
 // dispacth inbound messages
@@ -3071,6 +3124,7 @@ void NodeManager::process(Request & request) {
     #endif
     case 26: unRegisterSensor(request.getValueInt()); break;
     case 27: saveToMemory(0,request.getValueInt()); break;
+    case 28: setInterruptMinDelta(request.getValueInt()); break;
     default: return; 
   }
   _send(_msg.set(function));
@@ -3178,6 +3232,67 @@ float NodeManager::getVcc() {
   #endif
 }
 
+// setup the interrupt pins
+void NodeManager::setupInterrupts() {
+  // configure wakeup pin if needed
+  if (_sleep_interrupt_pin > -1) {
+    // set the interrupt when the pin is connected to ground
+    setInterrupt(_sleep_interrupt_pin,FALLING,HIGH);
+  }
+  // setup the interrupt pins
+  if (_interrupt_1_mode != MODE_NOT_DEFINED) {
+    pinMode(INTERRUPT_PIN_1,INPUT);
+    if (_interrupt_1_initial > -1) digitalWrite(INTERRUPT_PIN_1,_interrupt_1_initial);
+    // for non sleeping nodes, we need to handle the interrupt by ourselves  
+    if (_sleep_mode != SLEEP) attachInterrupt(digitalPinToInterrupt(INTERRUPT_PIN_1), _onInterrupt_1, _interrupt_1_mode);
+  }
+  if (_interrupt_2_mode != MODE_NOT_DEFINED) {
+    pinMode(INTERRUPT_PIN_2, INPUT);
+    if (_interrupt_2_initial > -1) digitalWrite(INTERRUPT_PIN_2,_interrupt_2_initial);
+    // for non sleeping nodes, we need to handle the interrupt by ourselves  
+    if (_sleep_mode != SLEEP) attachInterrupt(digitalPinToInterrupt(INTERRUPT_PIN_2), _onInterrupt_2, _interrupt_2_mode);
+  }
+  #if DEBUG == 1
+    Serial.print(F("INT P="));
+    Serial.print(INTERRUPT_PIN_1);
+    Serial.print(F(" M="));
+    Serial.println(_interrupt_1_mode);
+    Serial.print(F("INT P="));
+    Serial.print(INTERRUPT_PIN_2);
+    Serial.print(F(" M="));
+    Serial.println(_interrupt_2_mode);
+  #endif
+}
+
+// return the pin from which the last interrupt came
+int NodeManager::getLastInterruptPin() {
+  return _last_interrupt_pin;
+}
+
+// handle an interrupt
+void NodeManager::_onInterrupt_1() {
+  long now = millis();
+  if ( (now - _last_interrupt_1 > _interrupt_min_delta) || (now < _last_interrupt_1) ) {
+    _last_interrupt_pin = INTERRUPT_PIN_1;
+    #if DEBUG == 1
+      Serial.print(F("INT P="));
+      Serial.println(INTERRUPT_PIN_1);
+    #endif
+    _last_interrupt_1 = now;
+  }
+}
+void NodeManager::_onInterrupt_2() {
+  long now = millis();
+  if ( (now - _last_interrupt_2 > _interrupt_min_delta) || (now < _last_interrupt_2) ) {
+    _last_interrupt_pin = INTERRUPT_PIN_2;
+    #if DEBUG == 1
+      Serial.print(F("INT P="));
+      Serial.println(INTERRUPT_PIN_2);
+    #endif
+    _last_interrupt_2 = now;
+  }
+}
+
 // send a message to the network
 void NodeManager::_send(MyMessage & message) {
   // send the message, multiple times if requested
@@ -3206,8 +3321,6 @@ void NodeManager::_send(MyMessage & message) {
 
 // wrapper of smart sleep
 void NodeManager::_sleep() {
-  // reset the last interrupt pin
-  _last_interrupt_pin = -1;
   // calculate the seconds to sleep
   long sleep_sec = _sleep_time;
   if (_sleep_unit == MINUTES) sleep_sec = sleep_sec * 60;
diff --git a/NodeManager.h b/NodeManager.h
index ab3f841..cc96a56 100644
--- a/NodeManager.h
+++ b/NodeManager.h
@@ -474,11 +474,14 @@ class Sensor {
     void process(Request & request);
     // return the pin the interrupt is attached to
     int getInterruptPin();
+    // listen for interrupts on the given pin so interrupt() will be called when occurring
+    void setInterrupt(int pin, int mode, int initial);
     // define what to do at each stage of the sketch
     virtual void before();
     virtual void presentation();
     virtual void setup();
     virtual void loop(const MyMessage & message);
+    virtual void interrupt();
     virtual void receive(const MyMessage & message);
     // abstract functions, subclasses need to implement
     virtual void onBefore() = 0;
@@ -486,6 +489,7 @@ class Sensor {
     virtual void onLoop() = 0;
     virtual void onReceive(const MyMessage & message) = 0;
     virtual void onProcess(Request & request) = 0;
+    virtual void onInterrupt() = 0;
   protected:
     MyMessage _msg;
     MyMessage _msg_service;
@@ -541,6 +545,7 @@ class SensorAnalogInput: public Sensor {
     void onLoop();
     void onReceive(const MyMessage & message);
     void onProcess(Request & request);
+    void onInterrupt();
   protected:
     int _reference = -1;
     bool _reverse = false;
@@ -581,6 +586,7 @@ class SensorThermistor: public Sensor {
     void onLoop();
     void onReceive(const MyMessage & message);
     void onProcess(Request & request);
+    void onInterrupt();
   protected:
     long _nominal_resistor = 10000;
     int _nominal_temperature = 25;
@@ -602,6 +608,7 @@ class SensorML8511: public Sensor {
     void onLoop();
     void onReceive(const MyMessage & message);
     void onProcess(Request & request);
+    void onInterrupt();
   protected:
     float _mapfloat(float x, float in_min, float in_max, float out_min, float out_max);
 };
@@ -623,6 +630,7 @@ class SensorACS712: public Sensor {
     void onLoop();
     void onReceive(const MyMessage & message);
     void onProcess(Request & request);
+    void onInterrupt();
   protected:
     int _ACS_offset = 2500;
     int _mv_per_amp = 185;
@@ -635,24 +643,21 @@ class SensorACS712: public Sensor {
 class SensorRainGauge: public Sensor {
   public:
     SensorRainGauge(NodeManager* node_manager, int child_id, int pin);
-    // [101] set how frequently to report back to the controller in minutes. After reporting the measure is resetted (default: 60)
-    void setReportInterval(int value);
     // [102] set how many mm of rain to count for each tip (default: 0.11)
     void setSingleTip(float value);
+    // set initial value - internal pull up (default: HIGH)
+    void setInitialValue(int value);
     // define what to do at each stage of the sketch
     void onBefore();
     void onSetup();
     void onLoop();
     void onReceive(const MyMessage & message);
     void onProcess(Request & request);
-  public:
-    static void _onTipped();
-    static long _last_tip;
-    static long _count;
+    void onInterrupt();
   protected:
-    int _report_interval = 60;
+    long _count = 0;
     float _single_tip = 0.11;
-    Timer* _timer;
+    int _initial_value = HIGH;
 };
 
 /*
@@ -686,6 +691,7 @@ class SensorDigitalInput: public Sensor {
     void onLoop();
     void onReceive(const MyMessage & message);
     void onProcess(Request & request);
+    void onInterrupt();
 };
 #endif
 
@@ -716,6 +722,7 @@ class SensorDigitalOutput: public Sensor {
     void onLoop();
     void onReceive(const MyMessage & message);
     void onProcess(Request & request);
+    void onInterrupt();
   protected:
     int _on_value = HIGH;
     int _status = OFF;
@@ -773,6 +780,7 @@ class SensorDHT: public Sensor {
     void onLoop();
     void onReceive(const MyMessage & message);
     void onProcess(Request & request);
+    void onInterrupt();
     // constants
     const static int TEMPERATURE = 0;
     const static int HUMIDITY = 1;
@@ -797,6 +805,7 @@ class SensorSHT21: public Sensor {
     void onLoop();
     void onReceive(const MyMessage & message);
     void onProcess(Request & request);
+    void onInterrupt();
     // constants
     const static int TEMPERATURE = 0;
     const static int HUMIDITY = 1;
@@ -836,6 +845,7 @@ class SensorSwitch: public Sensor {
     void onLoop();
     void onReceive(const MyMessage & message);
     void onProcess(Request & request);
+    void onInterrupt();
   protected:
     int _debounce = 0;
     int _trigger_time = 0;
@@ -880,6 +890,7 @@ class SensorDs18b20: public Sensor {
     void onLoop();
     void onReceive(const MyMessage & message);
     void onProcess(Request & request);
+    void onInterrupt();
   protected:
     float _offset = 0;
     int _index;
@@ -902,6 +913,7 @@ class SensorBH1750: public Sensor {
     void onLoop();
     void onReceive(const MyMessage & message);
     void onProcess(Request & request);
+    void onInterrupt();
   protected:
     BH1750* _lightSensor;
 };
@@ -920,6 +932,7 @@ class SensorMLX90614: public Sensor {
     void onLoop();
     void onReceive(const MyMessage & message);
     void onProcess(Request & request);
+    void onInterrupt();
     // constants
     const static int TEMPERATURE_AMBIENT = 0;
     const static int TEMPERATURE_OBJECT = 1;
@@ -946,6 +959,7 @@ class SensorBosch: public Sensor {
     void onLoop();
     void onReceive(const MyMessage & message);
     void onProcess(Request & request);
+    void onInterrupt();
     // constants
     const static int TEMPERATURE = 0;
     const static int HUMIDITY = 1;
@@ -1012,6 +1026,7 @@ class SensorHCSR04: public Sensor {
     void onLoop();
     void onReceive(const MyMessage & message);
     void onProcess(Request & request);
+    void onInterrupt();
   protected:
     int _trigger_pin;
     int _echo_pin;
@@ -1039,6 +1054,7 @@ class SensorSonoff: public Sensor {
     void onLoop();
     void onReceive(const MyMessage & message);
     void onProcess(Request & request);
+    void onInterrupt();
   protected:
     Bounce _debouncer = Bounce();
     int _button_pin = 0;
@@ -1068,6 +1084,7 @@ class SensorMCP9808: public Sensor {
     void onLoop();
     void onReceive(const MyMessage & message);
     void onProcess(Request & request);
+    void onInterrupt();
   protected:
     Adafruit_MCP9808* _mcp;
 };
@@ -1108,6 +1125,7 @@ class SensorMQ: public Sensor {
     void onLoop();
     void onReceive(const MyMessage & message);
     void onProcess(Request & request);
+    void onInterrupt();
   protected:
     float _rl_value = 1.0;
     float _ro_clean_air_factor = 9.83;
@@ -1202,7 +1220,9 @@ class NodeManager {
     // [19] if enabled, when waking up from the interrupt, the board stops sleeping. Disable it when attaching e.g. a motion sensor (default: true)
     void setSleepInterruptPin(int value);
     // configure the interrupt pin and mode. Mode can be CHANGE, RISING, FALLING (default: MODE_NOT_DEFINED)
-    void setInterrupt(int pin, int mode, int pull = -1);
+    void setInterrupt(int pin, int mode, int initial = -1);
+    // [28] ignore two consecutive interrupts if happening within this timeframe in milliseconds (default: 100)
+    void setInterruptMinDelta(long value);
     // [20] optionally sleep interval in milliseconds before sending each message to the radio network (default: 0)
     void setSleepBetweenSend(int value);
     int getSleepBetweenSend();
@@ -1259,6 +1279,10 @@ class NodeManager {
     void saveToMemory(int index, int value);
     // return vcc in V
     float getVcc();
+    // setup the configured interrupt pins
+    void setupInterrupts();
+    // return the pin from which the last interrupt came
+    int getLastInterruptPin();
     // hook into the main sketch functions
     void before();
     void presentation();
@@ -1266,6 +1290,9 @@ class NodeManager {
     void loop();
     void receive(const MyMessage & msg);
     void receiveTime(unsigned long ts);
+    // handle interrupts
+    static void _onInterrupt_1();
+    static void _onInterrupt_2();
   private:
     #if BATTERY_MANAGER == 1
       float _battery_min = 2.6;
@@ -1291,9 +1318,12 @@ class NodeManager {
     int _retries = 1;
     int _interrupt_1_mode = MODE_NOT_DEFINED;
     int _interrupt_2_mode = MODE_NOT_DEFINED;
-    int _interrupt_1_pull = -1;
-    int _interrupt_2_pull = -1;
-    int _last_interrupt_pin = -1;
+    int _interrupt_1_initial = -1;
+    int _interrupt_2_initial = -1;
+    static int _last_interrupt_pin;
+    static long _interrupt_min_delta;
+    static long _last_interrupt_1;
+    static long _last_interrupt_2;
     long _timestamp = -1;
     Sensor* _sensors[MAX_SENSORS+1] = {0};
     bool _ack = false;
diff --git a/NodeManager.ino b/NodeManager.ino
index c864792..32e2fa7 100644
--- a/NodeManager.ino
+++ b/NodeManager.ino
@@ -35,6 +35,8 @@ void before() {
   */
 
 
+
+
   /*
    * Register above your sensors
   */
diff --git a/README.md b/README.md
index 241491c..fe8c0eb 100644
--- a/README.md
+++ b/README.md
@@ -294,6 +294,10 @@ Node Manager comes with a reasonable default configuration. If you want/need to
     void saveToMemory(int index, int value);
     // return vcc in V
     float getVcc();
+    // setup the configured interrupt pins
+    void setupInterrupts();
+    // return the pin from which the last interrupt came
+    int getLastInterruptPin();
 ~~~
 
 For example
@@ -361,6 +365,8 @@ If you want to create a custom sensor and register it with NodeManager so it can
     void onReceive(const MyMessage & message);
 	// define what to do when receiving a remote configuration message
 	void onProcess(Request & request);
+	// define what to do when receiving an interrupt
+	void onInterrupt();
 ~~~
 
 You can then instantiate your newly created class and register with NodeManager:
@@ -434,6 +440,8 @@ The following methods are available for all the sensors:
     void process(Request & request);
     // return the pin the interrupt is attached to
     int getInterruptPin();
+    // listen for interrupts on the given pin so interrupt() will be called when occurring
+    void setInterrupt(int pin, int mode, int initial);
 ~~~
 
 #### Sensor's specific configuration
@@ -504,10 +512,10 @@ Each sensor class can expose additional methods.
 
 * SensorRainGauge
 ~~~c
-    // [101] set how frequently to report back to the controller in minutes. After reporting the measure is resetted (default: 60)
-    void setReportInterval(int value);
     // [102] set how many mm of rain to count for each tip (default: 0.11)
     void setSingleTip(float value);
+    // set initial value - internal pull up (default: HIGH)
+    void setInitialValue(int value);
 ~~~
 
 * SensorDigitalOutput / SensorRelay
@@ -701,6 +709,16 @@ NodeManager::receive():
 Sensor::receive(): 
 * Invoke `Sensor::loop()` which will execute the sensor main taks and eventually call `Sensor::onReceive()`
 
+NodeManager::process():
+* Process an incoming remote configuration request
+
+Sensor::process():
+* Process a sensor-generic incoming remote configuration request
+* Calls onProcess() for sensor-specific incoming remote configuration request
+
+Sensor::interrupt():
+* Calls the sensor's implementation of onInterrupt() to handle the interrupt
+
 ## Examples
 All the examples below takes place within the before() function in the main sketch, just below the "Register below your sensors" comment.
 
-- 
GitLab