/*
 * NodeManager
 */

#include "NodeManager.h"

/***************************************
   Global functions
*/

// return vcc in V
float getVcc() {
  #ifndef MY_GATEWAY_ESP8266
    // Measure Vcc against 1.1V Vref
    #if defined(__AVR_ATmega32U4__) || defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__)
      ADMUX = (_BV(REFS0) | _BV(MUX4) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1));
    #elif defined (__AVR_ATtiny24__) || defined(__AVR_ATtiny44__) || defined(__AVR_ATtiny84__)
      ADMUX = (_BV(MUX5) | _BV(MUX0));
    #elif defined (__AVR_ATtiny25__) || defined(__AVR_ATtiny45__) || defined(__AVR_ATtiny85__)
      ADMUX = (_BV(MUX3) | _BV(MUX2));
    #else
      ADMUX = (_BV(REFS0) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1));
    #endif
    // Vref settle
    wait(70);
    // Do conversion
    ADCSRA |= _BV(ADSC);
    while (bit_is_set(ADCSRA, ADSC)) {};
    // return Vcc in mV
    return (float)((1125300UL) / ADC) / 1000;
  #else
    return (float)0;
  #endif
}


/***************************************
   PowerManager
*/

// set the vcc and ground pin the sensor is connected to
void PowerManager::setPowerPins(int ground_pin, int vcc_pin, int wait_time) {
  #if DEBUG == 1
    Serial.print(F("PWR G="));
    Serial.print(ground_pin);
    Serial.print(F(" V="));
    Serial.println(vcc_pin);
  #endif
  // configure the vcc pin as output and initialize to high (power on)
  _vcc_pin = vcc_pin;
  pinMode(_vcc_pin, OUTPUT);
  digitalWrite(_vcc_pin, HIGH);
  // configure the ground pin as output and initialize to low
  _ground_pin = ground_pin;
  pinMode(_ground_pin, OUTPUT);
  digitalWrite(_ground_pin, LOW);
  _wait = wait_time;
}

// return true if power pins have been configured
bool PowerManager::isConfigured() {
  if (_vcc_pin != -1 && _ground_pin != -1) return true;
  return false;
}

// turn on the sensor by activating its power pins
void PowerManager::powerOn() {
  if (! isConfigured()) return;
  #if DEBUG == 1
    Serial.print(F("ON P="));
    Serial.println(_vcc_pin);
  #endif
  // power on the sensor by turning high the vcc pin
  digitalWrite(_vcc_pin, HIGH);
  // wait a bit for the device to settle down
  if (_wait > 0) wait(_wait);
}

// turn off the sensor
void PowerManager::powerOff() {
  if (! isConfigured()) return;
  #if DEBUG == 1
    Serial.print(F("OFF P="));
    Serial.println(_vcc_pin);
  #endif
  // power off the sensor by turning low the vcc pin
  digitalWrite(_vcc_pin, LOW);
}


/******************************************
    Sensors
*/

/*
   Sensor class
*/
// constructor
Sensor::Sensor(int child_id, int pin) {
  _child_id = child_id;
  _pin = pin;
  _msg = MyMessage(_child_id, _type);
}

// setter/getter
void Sensor::setPin(int value) {
  _pin = value;
}
int Sensor::getPin() {
  return _pin;
}
void Sensor::setChildId(int value) {
  _child_id = value;
}
int Sensor::getChildId() {
  return _child_id;
}
void Sensor::setPresentation(int value) {
  _presentation = value;
}
int Sensor::getPresentation() {
  return _presentation;
}
void Sensor::setType(int value) {
  _type = value;
  _msg.setType(_type);
}
int Sensor::getType() {
  return _type;
}
void Sensor::setDescription(char* value) {
  _description = value;
}
void Sensor::setAck(bool value) {
  _ack = value;
}
void Sensor::setRetries(int value) {
  _retries = value;
}
void Sensor::setSamples(int value) {
  _samples = value;
}
void Sensor::setSamplesInterval(int value) {
  _samples_interval = value;
}
void Sensor::setTackLastValue(bool value) {
  _track_last_value = value;
}
void Sensor::setForceUpdate(int value) {
  _force_update = value;
}
void Sensor::setValueType(int value) {
  _value_type = value;
}
int Sensor::getValueType() {
  return _value_type;
}
void Sensor::setFloatPrecision(int value) {
  _float_precision = value;
}
#if POWER_MANAGER == 1
    void Sensor::setPowerPins(int ground_pin, int vcc_pin, int wait_time) {
      _powerManager.setPowerPins(ground_pin, vcc_pin, wait_time);
    }
    void Sensor::setAutoPowerPins(bool value) {
      _auto_power_pins = value;
    }
    void Sensor::powerOn() {
      _powerManager.powerOn();
    }
    void Sensor::powerOff() {
      _powerManager.powerOff();
    }
#endif
void Sensor::setSleepBetweenSend(int value) {
  _sleep_between_send = value;
}
void Sensor::setInterruptPin(int value) {
  _interrupt_pin = value;
}
int Sensor::getInterruptPin() {
  return _interrupt_pin;
}
int Sensor::getValueInt() {
  return _last_value_int;
}
float Sensor::getValueFloat() {
  return _last_value_float;
}
char* Sensor::getValueString() {
  return _last_value_string;
}

// present the sensor to the gateway and controller
void Sensor::presentation() {
  #if DEBUG == 1
    Serial.print(F("PRES I="));
    Serial.print(_child_id);
    Serial.print(F(" T="));
    Serial.println(_presentation);
  #endif
  present(_child_id, _presentation,_description,_ack);
}

// call the sensor-specific implementation of before
void Sensor::before() {
  if (_pin == -1) return;
  onBefore();
}

// call the sensor-specific implementation of setup
void Sensor::setup() {
  if (_pin == -1) return;
  onSetup();
}

// call the sensor-specific implementation of loop
void Sensor::loop(const MyMessage & message) {
  if (_pin == -1) return;
  #if POWER_MANAGER == 1
    // turn the sensor on
    if (_auto_power_pins) powerOn();
  #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
  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 {
      // we've been called from receive(), pass the message along
      onReceive(message);
    }
    // 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. 
  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;
      _last_value_int = avg;
      _send(_msg.set(avg));
    }
  }
  // process a float value
  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;
      _last_value_float = avg;
      _send(_msg.set(avg, _float_precision));
    }
  }
  // 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;
      _last_value_string = _value_string;
      _send(_msg.set(_value_string));
    }
  }
  // turn the sensor off
  #if POWER_MANAGER == 1
    if (_auto_power_pins) powerOff();
  #endif
}

// receive a message from the radio network
void Sensor::receive(const MyMessage &message) {
  // return if not for this sensor
  if (message.sensor != _child_id || message.type != _type) return;
  // a request would make the sensor executing its main task passing along the message
  loop(message);
}

// send a message to the network
void Sensor::_send(MyMessage & message) {
  // send the message, multiple times if requested
  for (int i = 0; i < _retries; i++) {
    // if configured, sleep beetween each send
    if (_sleep_between_send > 0) sleep(_sleep_between_send);
    #if DEBUG == 1
      Serial.print(F("SEND D="));
      Serial.print(message.destination);
      Serial.print(F(" I="));
      Serial.print(message.sensor);
      Serial.print(F(" C="));
      Serial.print(message.getCommand());
      Serial.print(F(" T="));
      Serial.print(message.type);
      Serial.print(F(" S="));
      Serial.print(message.getString());
      Serial.print(F(" I="));
      Serial.print(message.getInt());
      Serial.print(F(" F="));
      Serial.println(message.getFloat());
    #endif
    send(message,_ack);
  }
}

/*
   SensorAnalogInput
*/

// contructor
SensorAnalogInput::SensorAnalogInput(int child_id, int pin): Sensor(child_id, pin) {
}

// setter/getter
void SensorAnalogInput::setReference(int value) {
  _reference = value;
}
void SensorAnalogInput::setReverse(bool value) {
  _reverse = value;
}
void SensorAnalogInput::setOutputPercentage(bool value) {
  _output_percentage = value;
}
void SensorAnalogInput::setRangeMin(int value) {
  _range_min = value;
}
void SensorAnalogInput::setRangeMax(int value) {
  _range_max = value;
}

// what to do during before
void SensorAnalogInput::onBefore() {
  // prepare the pin for input
  pinMode(_pin, INPUT);
}

// what to do during setup
void SensorAnalogInput::onSetup() {
}

// what to do during loop
void SensorAnalogInput::onLoop() {
  // read the input
  int adc = _getAnalogRead();
  // calculate the percentage
  int percentage = 0;
  if (_output_percentage) percentage = _getPercentage(adc);
  #if DEBUG == 1
    Serial.print(F("A-IN I="));
    Serial.print(_child_id);
    Serial.print(F(" V="));
    Serial.print(adc);
    Serial.print(F(" %="));
    Serial.println(percentage);
  #endif
  // store the result
  _value_int = _output_percentage ? percentage : adc;
}

// what to do during loop
void SensorAnalogInput::onReceive(const MyMessage & message) {
  if (message.getCommand() == C_REQ) onLoop();
}

// read the analog input
int SensorAnalogInput::_getAnalogRead() {
  #ifndef MY_GATEWAY_ESP8266
    // set the reference
    if (_reference != -1) {
      analogReference(_reference);
      wait(100);
    }
  #endif
  // read and return the value
  int value = analogRead(_pin);
  if (_reverse) value = _range_max - value;
  return value;
}

// return a percentage from an analog value
int SensorAnalogInput::_getPercentage(int adc) {
  float value = (float)adc;
  // restore the original value
  if (_reverse) value = 1024 - value;
  // scale the percentage based on the range provided
  float percentage = ((value - _range_min) / (_range_max - _range_min)) * 100;
  if (_reverse) percentage = 100 - percentage;
  if (percentage > 100) percentage = 100;
  if (percentage < 0) percentage = 0;
  return (int)percentage;
}

/*
   SensorLDR
*/

// contructor
SensorLDR::SensorLDR(int child_id, int pin): SensorAnalogInput(child_id, pin) {
  // set presentation and type and reverse (0: no light, 100: max light)
  setPresentation(S_LIGHT_LEVEL);
  setType(V_LIGHT_LEVEL);
  setReverse(true);
}

/*
   SensorThermistor
*/

// contructor
SensorThermistor::SensorThermistor(int child_id, int pin): Sensor(child_id, pin) {
  // set presentation, type and value type
  setPresentation(S_TEMP);
  setType(V_TEMP);
  setValueType(TYPE_FLOAT);
}

// setter/getter
void SensorThermistor::setNominalResistor(long value) {
  _nominal_resistor = value;
}
void SensorThermistor::setNominalTemperature(int value) {
  _nominal_temperature = value;
}
void SensorThermistor::setBCoefficient(int value) {
  _b_coefficient = value;
}
void SensorThermistor::setSeriesResistor(long value) {
  _series_resistor = value;
}
void SensorThermistor::setOffset(float value) {
  _offset = value;
}

// what to do during before
void SensorThermistor::onBefore() {
  // set the pin as input
  pinMode(_pin, INPUT);
}

// what to do during setup
void SensorThermistor::onSetup() {
}

// what to do during loop
void SensorThermistor::onLoop() {
  // read the voltage across the thermistor
  float adc = analogRead(_pin);
  // calculate the temperature
  float reading = (1023 / adc)  - 1;
  reading = _series_resistor / reading;
  float temperature;
  temperature = reading / _nominal_resistor;     // (R/Ro)
  temperature = log(temperature);                  // ln(R/Ro)
  temperature /= _b_coefficient;                   // 1/B * ln(R/Ro)
  temperature += 1.0 / (_nominal_temperature + 273.15); // + (1/To)
  temperature = 1.0 / temperature;                 // Invert
  temperature -= 273.15;                         // convert to C
  if (! getControllerConfig().isMetric) temperature = temperature * 1.8 + 32;
  #if DEBUG == 1
    Serial.print(F("THER I="));
    Serial.print(_child_id);
    Serial.print(F(" V="));
    Serial.print(adc);
    Serial.print(F(" T="));
    Serial.print(temperature);
    Serial.print(F(" M="));
    Serial.println(getControllerConfig().isMetric);
  #endif
  // store the value
  _value_float = temperature;
}

// what to do as the main task when receiving a message
void SensorThermistor::onReceive(const MyMessage & message) {
  if (message.getCommand() == C_REQ) onLoop();
}


/*
   SensorML8511
*/

// contructor
SensorML8511::SensorML8511(int child_id, int pin): Sensor(child_id, pin) {
  // set presentation, type and value type
  setPresentation(S_UV);
  setType(V_UV);
  setValueType(TYPE_FLOAT);
}

// what to do during before
void SensorML8511::onBefore() {
  // set the pin as input
  pinMode(_pin, INPUT);
}

// what to do during setup
void SensorML8511::onSetup() {
}

// what to do during loop
void SensorML8511::onLoop() {
  // read the voltage 
  int uvLevel = analogRead(_pin);
  int refLevel = getVcc()*1024/3.3;
  //Use the 3.3V power pin as a reference to get a very accurate output value from sensor
  float outputVoltage = 3.3 / refLevel * uvLevel;
  //Convert the voltage to a UV intensity level
  float uvIntensity = _mapfloat(outputVoltage, 0.99, 2.8, 0.0, 15.0); 
  #if DEBUG == 1
    Serial.print(F("UV I="));
    Serial.print(_child_id);
    Serial.print(F(" V="));
    Serial.print(outputVoltage);
    Serial.print(F(" I="));
    Serial.println(uvIntensity);
  #endif
  // store the value
  _value_float = uvIntensity;
}

// what to do as the main task when receiving a message
void SensorML8511::onReceive(const MyMessage & message) {
  if (message.getCommand() == C_REQ) onLoop();
}

// 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;
}

/*
   SensorACS712
*/

// contructor
SensorACS712::SensorACS712(int child_id, int pin): Sensor(child_id, pin) {
  // set presentation, type and value type
  setPresentation(S_MULTIMETER);
  setType(V_CURRENT);
  setValueType(TYPE_FLOAT);
}

// setter/getter
void SensorACS712::setmVPerAmp(int value) {
  _mv_per_amp = value;
}
void SensorACS712::setOffset(int value) {
  _ACS_offset = value;
}

// what to do during before
void SensorACS712::onBefore() {
  // set the pin as input
  pinMode(_pin, INPUT);
}

// what to do during setup
void SensorACS712::onSetup() {
}

// what to do during loop
void SensorACS712::onLoop() {
  int value = analogRead(_pin);
  // convert the analog read in mV
  double voltage = (value / 1024.0) * 5000; 
  // convert voltage in amps
  _value_float = ((voltage - _ACS_offset) / _mv_per_amp);
  #if DEBUG == 1
    Serial.print(F("ACS I="));
    Serial.print(_child_id);
    Serial.print(F(" A="));
    Serial.println(_value_float);
  #endif
}

// what to do as the main task when receiving a message
void SensorACS712::onReceive(const MyMessage & message) {
  if (message.getCommand() == C_REQ) onLoop();
}

/*
   SensorRainGauge
*/

// contructor
SensorRainGauge::SensorRainGauge(int child_id, int pin): Sensor(child_id, pin) {
  // set presentation, type and value type
  setPresentation(S_RAIN);
  setType(V_RAIN);
  setValueType(TYPE_FLOAT);

}

// 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;
}

// 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);
}

// 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;
  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)) {
    // 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 counters
    _count = 0;
    _last_report = now;
  }
}

// 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;    
  }
}


/*
 * SensorMQ
 */
SensorMQ::SensorMQ(int child_id, int pin): Sensor(child_id,pin) {
  setPresentation(S_AIR_QUALITY);
  setType(V_LEVEL);
}

//setter/getter
void SensorMQ::setRlValue(float value) {
  _rl_value = value;
}
void SensorMQ::setRoValue(float value) {
  _ro = value;
}
void SensorMQ::setCleanAirFactor(float value) {
  _ro_clean_air_factor = value;
}
void SensorMQ::setCalibrationSampleTimes(int value) {
  _calibration_sample_times = value;
}
void SensorMQ::setCalibrationSampleInterval(int value){
  _calibration_sample_interval = value;
}
void SensorMQ::setReadSampleTimes(int value) {
  _read_sample_times = value;
}
void SensorMQ::setReadSampleInterval(int value) {
  _read_sample_interval = value;
}
void SensorMQ::setLPGCurve(float *value) {
  _LPGCurve[0] = value[0];
  _LPGCurve[2] = value[1];
  _LPGCurve[2] = value[2];
}
void SensorMQ::setCOCurve(float *value) {
  _COCurve[0] = value[0];
  _COCurve[2] = value[1];
  _COCurve[2] = value[2];
}
void SensorMQ::setSmokeCurve(float *value) {
  _SmokeCurve[0] = value[0];
  _SmokeCurve[2] = value[1];
  _SmokeCurve[2] = value[2];
}

// what to do during before
void SensorMQ::onBefore() {
  // prepare the pin for input
  pinMode(_pin, INPUT);
}

// what to do during setup
void SensorMQ::onSetup() {
  _ro = _MQCalibration();
}

// what to do during loop
void SensorMQ::onLoop() {
  if (_pin == -1) return;
  // calculate rs/ro
  float mq = _MQRead()/_ro;
  // calculate the ppm
  float lpg = _MQGetGasPercentage(mq,_gas_lpg);
  float co = _MQGetGasPercentage(mq,_gas_co);
  float smoke = _MQGetGasPercentage(mq,_gas_smoke);
  // assign to the value the requested gas
  uint16_t value;
  if (_target_gas == _gas_lpg) value = lpg;
  if (_target_gas == _gas_co) value = co;
  if (_target_gas == _gas_smoke) value = smoke;
  #if DEBUG == 1
    Serial.print(F("MQ I="));
    Serial.print(_child_id);
    Serial.print(F(" V="));
    Serial.print(value);
    Serial.print(F(" LPG="));
    Serial.print(lpg);
    Serial.print(F(" CO="));
    Serial.print(co);
    Serial.print(F(" SMOKE="));
    Serial.println(smoke);
  #endif
  // store the value
  _value_int = (int16_t)ceil(value);
}

// what to do as the main task when receiving a message
void SensorMQ::onReceive(const MyMessage & message) {
  if (message.getCommand() == C_REQ) onLoop();
}

// returns the calculated sensor resistance
float SensorMQ::_MQResistanceCalculation(int raw_adc) {
  return ( ((float)_rl_value*(1023-raw_adc)/raw_adc));
}

//  This function assumes that the sensor is in clean air
float SensorMQ::_MQCalibration() {
  int i;
  float val=0;
  //take multiple samples
  for (i=0; i< _calibration_sample_times; i++) {  
    val += _MQResistanceCalculation(analogRead(_pin));
    wait(_calibration_sample_interval);
  }
  //calculate the average value
  val = val/_calibration_sample_times;                   
  //divided by RO_CLEAN_AIR_FACTOR yields the Ro
  val = val/_ro_clean_air_factor;
  //according to the chart in the datasheet
  return val;
}

// This function use MQResistanceCalculation to caculate the sensor resistenc (Rs).
float SensorMQ::_MQRead() {
  int i;
  float rs=0;
  for (i=0; i<_read_sample_times; i++) {
    rs += _MQResistanceCalculation(analogRead(_pin));
    wait(_read_sample_interval);
  }
  rs = rs/_read_sample_times;
  return rs;
}

// This function passes different curves to the MQGetPercentage function which calculates the ppm (parts per million) of the target gas.
int SensorMQ::_MQGetGasPercentage(float rs_ro_ratio, int gas_id) {
  if ( gas_id == _gas_lpg ) {
    return _MQGetPercentage(rs_ro_ratio,_LPGCurve);
  } else if ( gas_id == _gas_co) {
    return _MQGetPercentage(rs_ro_ratio,_COCurve);
  } else if ( gas_id == _gas_smoke) {
    return _MQGetPercentage(rs_ro_ratio,_SmokeCurve);
  }
  return 0;
}

// returns ppm of the target gas
int SensorMQ::_MQGetPercentage(float rs_ro_ratio, float *pcurve) {
  return (pow(10,( ((log10(rs_ro_ratio)-pcurve[1])/pcurve[2]) + pcurve[0])));
}


/*
   SensorDigitalInput
*/

// contructor
SensorDigitalInput::SensorDigitalInput(int child_id, int pin): Sensor(child_id, pin) {
}

// what to do during before
void SensorDigitalInput::onBefore() {
  // set the pin for input
  pinMode(_pin, INPUT);
}

// what to do during setup
void SensorDigitalInput::onSetup() {
}

// what to do during loop
void SensorDigitalInput::onLoop() {
  // read the value
  int value = digitalRead(_pin);
  #if DEBUG == 1
    Serial.print(F("D-IN I="));
    Serial.print(_child_id);
    Serial.print(F(" P="));
    Serial.print(_pin);
    Serial.print(F(" V="));
    Serial.println(value);
  #endif
  // store the value
  _value_int = value;
}

// what to do as the main task when receiving a message
void SensorDigitalInput::onReceive(const MyMessage & message) {
  if (message.getCommand() == C_REQ) onLoop();
}


/*
   SensorDigitalOutput
*/

// contructor
SensorDigitalOutput::SensorDigitalOutput(int child_id, int pin): Sensor(child_id, pin) {
}

// what to do during before
void SensorDigitalOutput::onBefore() {
  // set the pin as output and initialize it accordingly
  pinMode(_pin, OUTPUT);
  _state = _initial_value == LOW ? LOW : HIGH;
  digitalWrite(_pin, _state);
  // the initial value is now the current value
  _value_int = _initial_value;
}

// what to do during setup
void SensorDigitalOutput::onSetup() {
}

// setter/getter
void SensorDigitalOutput::setInitialValue(int value) {
  _initial_value = value;
}
void SensorDigitalOutput::setPulseWidth(int value) {
  _pulse_width = value;
}
void SensorDigitalOutput::setOnValue(int value) {
  _on_value = value;
}
void SensorDigitalOutput::setLegacyMode(bool value) {
  _legacy_mode = value;
}

// main task
void SensorDigitalOutput::onLoop() {
  // do nothing on loop
}

// 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;
  }
  if (message.getCommand() == C_REQ && ! _legacy_mode) {
    // return the current status
    _value_int = _state;
  }
}

/*
   SensorRelay
*/

// contructor
SensorRelay::SensorRelay(int child_id, int pin): SensorDigitalOutput(child_id, pin) {
  // set presentation and type
  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;
}

/*
   SensorLatchingRelay
*/

// contructor
SensorLatchingRelay::SensorLatchingRelay(int child_id, int pin): SensorRelay(child_id, pin) {
  // like a sensor with a default pulse set
  setPulseWidth(50);
}

/*
   SensorDHT
*/
#if MODULE_DHT == 1
// contructor
SensorDHT::SensorDHT(int child_id, int pin, DHT* dht, int sensor_type, int dht_type): Sensor(child_id, pin) {
  // store the dht object
  _dht = dht;
  _sensor_type = sensor_type;
  _dht_type = dht_type;
  if (_sensor_type == SensorDHT::TEMPERATURE) {
    // temperature sensor
    setPresentation(S_TEMP);
    setType(V_TEMP);
    setValueType(TYPE_FLOAT);
  }
  else if (_sensor_type == SensorDHT::HUMIDITY) {
    // humidity sensor
    setPresentation(S_HUM);
    setType(V_HUM);
    setValueType(TYPE_FLOAT);
  }
}

// what to do during before
void SensorDHT::onBefore() {
    // initialize the dht library
    _dht->begin();
}

// what to do during setup
void SensorDHT::onSetup() {
}

// what to do during loop
void SensorDHT::onLoop() {
  // temperature sensor
  if (_sensor_type == SensorDHT::TEMPERATURE) {
    // read the temperature
    float temperature = _dht->readTemperature();
    // convert it
    if (! getControllerConfig().isMetric) temperature = temperature * 1.8 + 32;
    #if DEBUG == 1
      Serial.print(F("DHT I="));
      Serial.print(_child_id);
      Serial.print(F(" T="));
      Serial.println(temperature);
    #endif
    // store the value
    if (! isnan(temperature)) _value_float = temperature;
  }
  // humidity sensor
  else if (_sensor_type == SensorDHT::HUMIDITY) {
    // read humidity
    float humidity = _dht->readHumidity();
    if (isnan(humidity)) return;
    #if DEBUG == 1
      Serial.print(F("DHT I="));
      Serial.print(_child_id);
      Serial.print(F(" H="));
      Serial.println(humidity);
    #endif
    // store the value
    if (! isnan(humidity)) _value_float = humidity;
  }
}

// what to do as the main task when receiving a message
void SensorDHT::onReceive(const MyMessage & message) {
  if (message.getCommand() == C_REQ) onLoop();
}
#endif

/*
   SensorSHT21
*/
#if MODULE_SHT21 == 1
// contructor
SensorSHT21::SensorSHT21(int child_id, int sensor_type): Sensor(child_id,A2) {
  // store the sensor type (0: temperature, 1: humidity)
  _sensor_type = sensor_type;
  if (_sensor_type == SensorSHT21::TEMPERATURE) {
    // temperature sensor
    setPresentation(S_TEMP);
    setType(V_TEMP);
    setValueType(TYPE_FLOAT);
  }
  else if (_sensor_type == SensorSHT21::HUMIDITY) {
    // humidity sensor
    setPresentation(S_HUM);
    setType(V_HUM);
    setValueType(TYPE_FLOAT);
  }
}

// what to do during before
void SensorSHT21::onBefore() {
  // initialize the library
  Wire.begin();
}

// what to do during setup
void SensorSHT21::onSetup() {
}

// what to do during loop
void SensorSHT21::onLoop() {
  // temperature sensor
  if (_sensor_type == SensorSHT21::TEMPERATURE) {
    // read the temperature
    float temperature = SHT2x.GetTemperature();
    // convert it
    if (! getControllerConfig().isMetric) temperature = temperature * 1.8 + 32;
    #if DEBUG == 1
      Serial.print(F("SHT I="));
      Serial.print(_child_id);
      Serial.print(F(" T="));
      Serial.println(temperature);
    #endif
    // store the value
    if (! isnan(temperature)) _value_float = temperature;
  }
  // Humidity Sensor
  else if (_sensor_type == SensorSHT21::HUMIDITY) {
    // read humidity
    float humidity = SHT2x.GetHumidity();
    if (isnan(humidity)) return;
    #if DEBUG == 1
      Serial.print(F("SHT I="));
      Serial.print(_child_id);
      Serial.print(F(" H="));
      Serial.println(humidity);
    #endif
    // store the value
    if (! isnan(humidity)) _value_float = humidity;
  }
}

// what to do as the main task when receiving a message
void SensorSHT21::onReceive(const MyMessage & message) {
  if (message.getCommand() == C_REQ) onLoop();
}
#endif

/*
 * SensorHTU21D
 */
 #if MODULE_SHT21 == 1
// constructor
SensorHTU21D::SensorHTU21D(int child_id, int pin): SensorSHT21(child_id, pin) {
}
#endif 

/*
 * SensorSwitch
 */
SensorSwitch::SensorSwitch(int child_id, int pin): Sensor(child_id,pin) {
  setType(V_TRIPPED);
}

// setter/getter
void SensorSwitch::setMode(int value) {
  _mode = value;
}
int SensorSwitch::getMode() {
  return _mode;
}
void SensorSwitch::setDebounce(int value) {
  _debounce = value;
}
void SensorSwitch::setTriggerTime(int value) {
  _trigger_time = value;
}
void SensorSwitch::setInitial(int value) {
  _initial = value;
}
int SensorSwitch::getInitial() {
  return _initial;
}

// what to do during before
void SensorSwitch::onBefore() {
  // initialize the value
  if (_mode == RISING) _value_int = LOW;
  else if (_mode == FALLING) _value_int = HIGH;
}

// what to do during setup
void SensorSwitch::onSetup() {
}

// what to do during loop
void SensorSwitch::onLoop() {
  // wait to ensure the the input is not floating
  if (_debounce > 0) wait(_debounce);
  // read the value of the pin
  int value = digitalRead(_pin);
  // process the value
  if ( (_mode == RISING && value == HIGH ) || (_mode == FALLING && value == LOW) || (_mode == CHANGE) )  {
    #if DEBUG == 1
      Serial.print(F("SWITCH I="));
      Serial.print(_child_id);
      Serial.print(F(" P="));
      Serial.print(_pin);
      Serial.print(F(" V="));
      Serial.println(value);
    #endif
    _value_int = value;
    // allow the signal to be restored to its normal value
    if (_trigger_time > 0) wait(_trigger_time);
  } else {
    // invalid
    _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();
}

/*
 * SensorDoor
 */
SensorDoor::SensorDoor(int child_id, int pin): SensorSwitch(child_id,pin) {
  setPresentation(S_DOOR);
}

/*
 * SensorMotion
 */
SensorMotion::SensorMotion(int child_id, int pin): SensorSwitch(child_id,pin) {
  setPresentation(S_MOTION);
  // capture only when it triggers
  setMode(RISING);
  // set initial value to LOW
  setInitial(LOW);
}

/*
   SensorDs18b20
*/
#if MODULE_DS18B20 == 1
// contructor
SensorDs18b20::SensorDs18b20(int child_id, int pin, DallasTemperature* sensors, int index): Sensor(child_id, pin) {
  setPresentation(S_TEMP);
  setType(V_TEMP);
  setValueType(TYPE_FLOAT);
  _index = index;
  _sensors = sensors;
  // retrieve and store the address from the index
  _sensors->getAddress(_device_address, index);
}

// what to do during before
void SensorDs18b20::onBefore() {
}

// what to do during setup
void SensorDs18b20::onSetup() {
}

// what to do during loop
void SensorDs18b20::onLoop() {
  // do not wait for conversion, will sleep manually during it
  if (_sleep_during_conversion) _sensors->setWaitForConversion(false);
  // request the temperature
  _sensors->requestTemperatures();
  if (_sleep_during_conversion) {
    // calculate conversion time and sleep
    int16_t conversion_time = _sensors->millisToWaitForConversion(_sensors->getResolution());
    sleep(conversion_time);
  }
  // read the temperature
  float temperature = _sensors->getTempCByIndex(_index);
  // convert it
  if (! getControllerConfig().isMetric) temperature = temperature * 1.8 + 32;
  #if DEBUG == 1
    Serial.print(F("DS18B20 I="));
    Serial.print(_child_id);
    Serial.print(F(" T="));
    Serial.println(temperature);
  #endif
  // store the value
  _value_float = temperature;
}

// what to do as the main task when receiving a message
void SensorDs18b20::onReceive(const MyMessage & message) {
  if (message.getCommand() == C_REQ) onLoop();
}

// function to print a device address
DeviceAddress* SensorDs18b20::getDeviceAddress() {
  return &_device_address;
}

// returns the sensor's resolution in bits
int SensorDs18b20::getResolution() {
  return _sensors->getResolution(_device_address);
}

// set the sensor's resolution in bits
void SensorDs18b20::setResolution(int value) {
   _sensors->setResolution(_device_address, value);
}

// sleep while DS18B20 calculates temperature
void SensorDs18b20::setSleepDuringConversion(bool value) {
   _sleep_during_conversion = value;
}

#endif

/*
   SensorBH1750
*/
#if MODULE_BH1750 == 1
// contructor
SensorBH1750::SensorBH1750(int child_id): Sensor(child_id,A4) {
  setPresentation(S_LIGHT_LEVEL);
  setType(V_LEVEL);
  _lightSensor = new BH1750();
}

// what to do during before
void SensorBH1750::onBefore() {
  _lightSensor->begin();
}

// what to do during setup
void SensorBH1750::onSetup() {
}

// what to do during loop
void SensorBH1750::onLoop() {
  // request the light level
  _value_int = _lightSensor->readLightLevel();
  #if DEBUG == 1
    Serial.print(F("BH1 I="));
    Serial.print(_child_id);
    Serial.print(F(" L="));
    Serial.println(_value_int);
  #endif
}

// what to do as the main task when receiving a message
void SensorBH1750::onReceive(const MyMessage & message) {
  if (message.getCommand() == C_REQ) onLoop();
}
#endif

/*
   SensorMLX90614
*/
#if MODULE_MLX90614 == 1
// contructor
SensorMLX90614::SensorMLX90614(int child_id, Adafruit_MLX90614* mlx, int sensor_type): Sensor(child_id,A4) {
  _sensor_type = sensor_type;
  _mlx = mlx;
  // set presentation and type
  setPresentation(S_TEMP);
  setType(V_TEMP);
  setValueType(TYPE_FLOAT);
}

// what to do during before
void SensorMLX90614::onBefore() {
  // initialize the library
  _mlx->begin();
}

// what to do during setup
void SensorMLX90614::onSetup() {
}

// what to do during loop
void SensorMLX90614::onLoop() {
  float temperature = _sensor_type == SensorMLX90614::TEMPERATURE_OBJECT ? _mlx->readAmbientTempC() : _mlx->readObjectTempC();
  // convert it
  if (! getControllerConfig().isMetric) temperature = temperature * 1.8 + 32;
  #if DEBUG == 1
    Serial.print(F("MLX I="));
    Serial.print(_child_id);
    Serial.print(F(" T="));
    Serial.println(temperature);
  #endif
  if (! isnan(temperature)) _value_float = temperature;
}

// what to do as the main task when receiving a message
void SensorMLX90614::onReceive(const MyMessage & message) {
  if (message.getCommand() == C_REQ) onLoop();
}
#endif


/*
   SensorBosch
*/
#if MODULE_BME280 == 1 || MODULE_BMP085 == 1
// contructor
SensorBosch::SensorBosch(int child_id, int sensor_type): Sensor(child_id,A4) {
  _sensor_type = sensor_type;
  if (_sensor_type == SensorBosch::TEMPERATURE) {
    // temperature sensor
    setPresentation(S_TEMP);
    setType(V_TEMP);
    setValueType(TYPE_FLOAT);
  }
  else if (_sensor_type == SensorBosch::HUMIDITY) {
    // humidity sensor
    setPresentation(S_HUM);
    setType(V_HUM);
    setValueType(TYPE_FLOAT);
  }
  else if (_sensor_type == SensorBosch::PRESSURE) {
    // pressure sensor
    setPresentation(S_BARO);
    setType(V_PRESSURE);
    setValueType(TYPE_FLOAT);
  }
  else if (_sensor_type == SensorBosch::FORECAST) {
    // pressure sensor
    setPresentation(S_BARO);
    setType(V_FORECAST);
    setValueType(TYPE_STRING);
  }
}

// setter/getter
void SensorBosch::setForecastSamplesCount(int value) {
  _forecast_samples_count = value;
}

// what to do during before
void SensorBosch::onBefore() {
  // initialize the forecast samples array
  _forecast_samples = new float[_forecast_samples_count];
}

// what to do during setup
void SensorBosch::onSetup() {
}

// what to do during loop
void SensorBosch::onLoop() {
}

// what to do as the main task when receiving a message
void SensorBosch::onReceive(const MyMessage & message) {
  if (message.getCommand() == C_REQ) onLoop();
}


void SensorBosch::_forecast(float pressure) {
  if (isnan(pressure)) return;
  // Calculate the average of the last n minutes.
  int index = _minute_count % _forecast_samples_count;
  _forecast_samples[index] = pressure;
  _minute_count++;
  if (_minute_count > 185) _minute_count = 6;
  if (_minute_count == 5) _pressure_avg = _getLastPressureSamplesAverage();
  else if (_minute_count == 35) {
    float last_pressure_avg = _getLastPressureSamplesAverage();
    float change = (last_pressure_avg - _pressure_avg) * 0.1;
    // first time initial 3 hour
    if (_first_round) _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 (_minute_count == 65) {
    float last_pressure_avg = _getLastPressureSamplesAverage();
    float change = (last_pressure_avg - _pressure_avg) * 0.1;
    //first time initial 3 hour
    if (_first_round) _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 (_minute_count == 95) {
    float last_pressure_avg = _getLastPressureSamplesAverage();
    float change = (last_pressure_avg - _pressure_avg) * 0.1;
    // first time initial 3 hour
    if (_first_round)_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 (_minute_count == 125) {
    float last_pressure_avg = _getLastPressureSamplesAverage();
    // store for later use.
    _pressure_avg2 = last_pressure_avg; 
    float change = (last_pressure_avg - _pressure_avg) * 0.1;
    if (_first_round) _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 (_minute_count == 155) {
    float last_pressure_avg = _getLastPressureSamplesAverage();
    float change = (last_pressure_avg - _pressure_avg) * 0.1;
    if (_first_round) _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 (_minute_count == 185) {
    float last_pressure_avg = _getLastPressureSamplesAverage();
    float change = (last_pressure_avg - _pressure_avg) * 0.1;
    if (_first_round) _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
  }
  // Equating the pressure at 0 to the pressure at 2 hour after 3 hours have past.
  _pressure_avg = _pressure_avg2; 
  // flag to let you know that this is on the past 3 hour mark. Initialized to 0 outside main loop.
  _first_round = false; 
  // calculate the forecast (STABLE = 0, SUNNY = 1, CLOUDY = 2, UNSTABLE = 3, THUNDERSTORM = 4, UNKNOWN = 5)
  int forecast = 5;
  //if time is less than 35 min on the first 3 hour interval.
  if (_minute_count < 35 && _first_round) forecast = 5;
  else if (_dP_dt < (-0.25)) forecast = 5;
  else if (_dP_dt > 0.25) forecast = 4;
  else if ((_dP_dt > (-0.25)) && (_dP_dt < (-0.05))) forecast = 2;
  else if ((_dP_dt > 0.05) && (_dP_dt < 0.25)) forecast = 1;
  else if ((_dP_dt >(-0.05)) && (_dP_dt < 0.05)) forecast = 0;
  else forecast = 5;
  _value_string = _weather[forecast];
  #if DEBUG == 1
    Serial.print(F("BMP I="));
    Serial.print(_child_id);
    Serial.print(F(" M="));
    Serial.print(_minute_count);
    Serial.print(F(" dP="));
    Serial.print(_dP_dt);
    Serial.print(F(" F="));
    Serial.println(_value_string);
  #endif
}

// returns the average of the latest pressure samples
float SensorBosch::_getLastPressureSamplesAverage() {
  float avg = 0;
  for (int i = 0; i < _forecast_samples_count; i++) avg += _forecast_samples[i];
  avg /= _forecast_samples_count;
  return avg;
}

// search for a given chip on i2c bus
uint8_t SensorBosch::GetI2CAddress(uint8_t chip_id) {
  uint8_t addresses[] = {0x77, 0x76};
  uint8_t register_address = 0xD0;
  for (int i = 0; i <= sizeof(addresses); i++) { 
    uint8_t i2c_address = addresses[i];
    uint8_t value;
    Wire.beginTransmission((uint8_t)i2c_address);
    Wire.write((uint8_t)register_address);
    Wire.endTransmission();
    Wire.requestFrom((uint8_t)i2c_address, (byte)1);
    value = Wire.read();
    if (value == chip_id) {
      #if DEBUG == 1
        Serial.print(F("I2C=")); 
        Serial.println(i2c_address);
      #endif
      return i2c_address;
    }
  }
  return addresses[0]; 
}
#endif

/*
 * SensorBME280
 */
#if MODULE_BME280 == 1
SensorBME280::SensorBME280(int child_id, Adafruit_BME280* bme, int sensor_type): SensorBosch(child_id,sensor_type) {
  _bme = bme;
}

void SensorBME280::onLoop() {
  // temperature sensor
  if (_sensor_type == SensorBME280::TEMPERATURE) {
    // read the temperature
    float temperature = _bme->readTemperature();
    // convert it
    if (! getControllerConfig().isMetric) temperature = temperature * 1.8 + 32;
    #if DEBUG == 1
      Serial.print(F("BME I="));
      Serial.print(_child_id);
      Serial.print(F(" T="));
      Serial.println(temperature);
    #endif
    if (isnan(temperature)) return;
    // store the value
    _value_float = temperature;
  }
  // Humidity Sensor
  else if (_sensor_type == SensorBME280::HUMIDITY) {
    // read humidity
    float humidity = _bme->readHumidity();
    #if DEBUG == 1
      Serial.print(F("BME I="));
      Serial.print(_child_id);
      Serial.print(F(" H="));
      Serial.println(humidity);
    #endif
    if (isnan(humidity)) return;
    // store the value
    _value_float = humidity;
  }
  // Pressure Sensor
  else if (_sensor_type == SensorBME280::PRESSURE) {
    // read pressure
    float pressure = _bme->readPressure() / 100.0F;
    if (isnan(pressure)) return;
    #if DEBUG == 1
      Serial.print(F("BME I="));
      Serial.print(_child_id);
      Serial.print(F(" P="));
      Serial.println(pressure);
    #endif
    if (isnan(pressure)) return;
    // store the value
    _value_float = pressure;
  }
  // Forecast Sensor
  else if (_sensor_type == SensorBME280::FORECAST) {
    float pressure = _bme->readPressure() / 100.0F;
    _forecast(pressure);
  }
}
#endif

/*
   SensorBMP085
*/
#if MODULE_BMP085 == 1
// contructor
SensorBMP085::SensorBMP085(int child_id, Adafruit_BMP085* bmp, int sensor_type): SensorBosch(child_id,sensor_type) {
  _bmp = bmp;
}

// what to do during loop
void SensorBMP085::onLoop() {
  // temperature sensor
  if (_sensor_type == SensorBMP085::TEMPERATURE) {
    // read the temperature
    float temperature = _bmp->readTemperature();
    // convert it
    if (! getControllerConfig().isMetric) temperature = temperature * 1.8 + 32;
    #if DEBUG == 1
      Serial.print(F("BMP I="));
      Serial.print(_child_id);
      Serial.print(F(" T="));
      Serial.println(temperature);
    #endif
    if (isnan(temperature)) return;
    // store the value
    _value_float = temperature;
  }
  // Pressure Sensor
  else if (_sensor_type == SensorBMP085::PRESSURE) {
    // read pressure
    float pressure = _bmp->readPressure() / 100.0F;
    #if DEBUG == 1
      Serial.print(F("BMP I="));
      Serial.print(_child_id);
      Serial.print(F(" P="));
      Serial.println(pressure);
    #endif
    if (isnan(pressure)) return;
    // store the value
    _value_float = pressure;
  }
  // Forecast Sensor
  else if (_sensor_type == SensorBMP085::FORECAST) {
    float pressure = _bmp->readPressure() / 100.0F;
    _forecast(pressure);    
  }
}
#endif


/*
   SensorSonoff
*/
#if MODULE_SONOFF == 1
// contructor
SensorSonoff::SensorSonoff(int child_id): Sensor(child_id,1) {
  setPresentation(S_BINARY);
  setType(V_STATUS);
}

// setter/getter
void SensorSonoff::setButtonPin(int value) {
    _button_pin = value;
}
void SensorSonoff::setRelayPin(int value) {
    _relay_pin = value;
}
void SensorSonoff::setLedPin(int value) {
    _led_pin = value;
}

// what to do during before
void SensorSonoff::onBefore() {
}

// what to do during setup
void SensorSonoff::onSetup() {
  // Setup the button
  pinMode(_button_pin, INPUT_PULLUP);
  // After setting up the button, setup debouncer
  _debouncer.attach(_button_pin);
  _debouncer.interval(5);
  // Make sure relays and LED are off when starting up
  digitalWrite(_relay_pin, _relay_off);
  digitalWrite(_led_pin, _led_off);
  // Then set relay pins in output mode
  pinMode(_relay_pin, OUTPUT);
  pinMode(_led_pin, OUTPUT);
  _blink();
}

// 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();
  if (value != _old_value && value == 0) {
    // button pressed, toggle the state
    _toggle();
  }
  _old_value = value;
}

// what to do as the main task when receiving a message
void SensorSonoff::onReceive(const MyMessage & message) {
  if (message.getCommand() == C_SET) {
    // retrieve from the message the value to set
    int value = message.getInt();
    if (value != 0 && value != 1 || value == _state) return;
    // toggle the state
    _toggle();
  }
  if (message.getCommand() == C_REQ) {
    // return the current state
    _value_int = _state;
  }
}

// toggle the state
void SensorSonoff::_toggle() {
  // toggle the state
  _state = _state ? false : true;
  // Change relay state
  digitalWrite(_relay_pin, _state? _relay_on: _relay_off);
  // Change LED state
  digitalWrite(_led_pin, _state? _led_on: _led_off);
  #if DEBUG == 1
    Serial.print(F("SONOFF I="));
    Serial.print(_child_id);
    Serial.print(F(" V="));
    Serial.println(_state);
  #endif
  _value_int = _state;
}

// blink the led
void SensorSonoff::_blink() {
  digitalWrite(_led_pin, digitalRead(_led_pin) ? _led_on : _led_off);
  wait(200);
  digitalWrite(_led_pin, digitalRead(_led_pin) ? _led_on : _led_off);
  wait(200);
  digitalWrite(_led_pin, digitalRead(_led_pin) ? _led_on : _led_off);
  wait(200);
  digitalWrite(_led_pin, digitalRead(_led_pin) ? _led_on : _led_off);
}
#endif


/*
   SensorHCSR04
*/
#if MODULE_HCSR04 == 1
// contructor
SensorHCSR04::SensorHCSR04(int child_id, int pin): Sensor(child_id, pin) {
  // set presentation and type
  setPresentation(S_DISTANCE);
  setType(V_DISTANCE);
  _trigger_pin = pin;
  _echo_pin = pin;
}

// what to do during before
void SensorHCSR04::onBefore() {
  // initialize the library
  _sonar = new NewPing(_trigger_pin,_echo_pin,_max_distance);
}

// setter/getter
void SensorHCSR04::setTriggerPin(int value) {
  _trigger_pin = value;
}
void SensorHCSR04::setEchoPin(int value) {
  _echo_pin = value;
}
void SensorHCSR04::setMaxDistance(int value) {
  _max_distance = value;
}

// what to do during setup
void SensorHCSR04::onSetup() {
}

// what to do during loop
void SensorHCSR04::onLoop() {
  int distance = getControllerConfig().isMetric ? _sonar->ping_cm() : _sonar->ping_in();
  #if DEBUG == 1
    Serial.print(F("HC I="));
    Serial.print(_child_id);
    Serial.print(F(" D="));
    Serial.println(distance);
  #endif
  _value_int = distance;
}

// what to do as the main task when receiving a message
void SensorHCSR04::onReceive(const MyMessage & message) {
  if (message.getCommand() == C_REQ) onLoop();
}
#endif

/*
   SensorMCP9808
*/
#if MODULE_MCP9808 == 1
// contructor
SensorMCP9808::SensorMCP9808(int child_id, Adafruit_MCP9808* mcp): Sensor(child_id,A2) {
  _mcp = mcp;
  setPresentation(S_TEMP);
  setType(V_TEMP);
  setValueType(TYPE_FLOAT);
}

// what to do during before
void SensorMCP9808::onBefore() {
}

// what to do during setup
void SensorMCP9808::onSetup() {
}

// what to do during loop
void SensorMCP9808::onLoop() {
  float temperature = _mcp->readTempC();
  // convert it
  if (! getControllerConfig().isMetric) temperature = temperature * 1.8 + 32;
  #if DEBUG == 1
    Serial.print(F("MCP I="));
    Serial.print(_child_id);
    Serial.print(F(" T="));
    Serial.println(temperature);
  #endif
  // store the value
  if (! isnan(temperature)) _value_float = temperature;
}

// what to do as the main task when receiving a message
void SensorMCP9808::onReceive(const MyMessage & message) {
  if (message.getCommand() == C_REQ) onLoop();
}
#endif

/*******************************************
   NodeManager
*/

// initialize the node manager
NodeManager::NodeManager() {
  // setup the service message container
  _msg = MyMessage(CONFIGURATION_CHILD_ID, V_CUSTOM);
}

// setter/getter
void NodeManager::setRetries(int value) {
  _retries = value;
}
#if BATTERY_MANAGER == 1
  void NodeManager::setBatteryMin(float value) {
    _battery_min = value;
  }
  void NodeManager::setBatteryMax(float value) {
    _battery_max = value;
  }
  void NodeManager::setBatteryReportCycles(int value) {
    _battery_report_cycles = value;
  }
  void NodeManager::setBatteryInternalVcc(bool value) {
    _battery_internal_vcc = value;
  }
  void NodeManager::setBatteryPin(int value) {
    _battery_pin = value;
  }
  void NodeManager::setBatteryVoltsPerBit(float value) {
    _battery_volts_per_bit = value;
  }
  void NodeManager::setBatteryReportWithInterrupt(bool value) {
    _battery_report_with_interrupt = value;
  }
#endif
void NodeManager::setSleepMode(int value) {
  _sleep_mode = value;
}
void NodeManager::setMode(int value) {
  setSleepMode(value);
}
void NodeManager::setSleepTime(int value) {
  _sleep_time = value;
}
void NodeManager::setSleepUnit(int value) {
  _sleep_unit = value;
}
void NodeManager::setSleep(int value1, int value2, int value3) {
  _sleep_mode = value1;
  _sleep_time = value2;
  _sleep_unit = value3;
}
void NodeManager::setSleepInterruptPin(int value) {
  _sleep_interrupt_pin = value;
}
void NodeManager::setInterrupt(int pin, int mode, int pull) {
  if (pin == INTERRUPT_PIN_1) {
    _interrupt_1_mode = mode;
    _interrupt_1_pull = pull;
  }
  if (pin == INTERRUPT_PIN_2) {
    _interrupt_2_mode = mode;
    _interrupt_2_pull = pull;
  }
}
#if POWER_MANAGER == 1
  void NodeManager::setPowerPins(int ground_pin, int vcc_pin, int wait_time) {
    _powerManager.setPowerPins(ground_pin, vcc_pin, wait_time);
  }
  void NodeManager::setAutoPowerPins(bool value) {
    _auto_power_pins = value;
  }
  void NodeManager::powerOn() {
    _powerManager.powerOn();
  }
  void NodeManager::powerOff() {
    _powerManager.powerOff();
  }
#endif
void NodeManager::setSleepBetweenSend(int value) {
  _sleep_between_send = value;
}
void NodeManager::setAck(bool value) {
    _ack = value;
}

// 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
  if (child_id < 0) child_id = _getAvailableChildId();
  // based on the given sensor type instantiate the appropriate class
  if (sensor_type == 0) return -1;
  #if MODULE_ANALOG_INPUT == 1
    else if (sensor_type == SENSOR_ANALOG_INPUT) return registerSensor(new SensorAnalogInput(child_id, pin));
    else if (sensor_type == SENSOR_LDR) return registerSensor(new SensorLDR(child_id, pin));
    else if (sensor_type == SENSOR_THERMISTOR) return registerSensor(new SensorThermistor(child_id, pin));
    else if (sensor_type == SENSOR_MQ) return registerSensor(new SensorMQ(child_id, pin));
    else if (sensor_type == SENSOR_ML8511) return registerSensor(new SensorML8511(child_id, pin));
    else if (sensor_type == SENSOR_ACS712) return registerSensor(new SensorACS712(child_id, pin));
    else if (sensor_type == SENSOR_RAIN_GAUGE) return registerSensor(new SensorRainGauge(child_id, pin));
  #endif
  #if MODULE_DIGITAL_INPUT == 1
    else if (sensor_type == SENSOR_DIGITAL_INPUT) return registerSensor(new SensorDigitalInput(child_id, pin));
  #endif
  #if MODULE_DIGITAL_OUTPUT == 1
    else if (sensor_type == SENSOR_DIGITAL_OUTPUT) return registerSensor(new SensorDigitalOutput(child_id, pin));
    else if (sensor_type == SENSOR_RELAY) return registerSensor(new SensorRelay(child_id, pin));
    else if (sensor_type == SENSOR_LATCHING_RELAY) return registerSensor(new SensorLatchingRelay(child_id, pin));
  #endif
  #if MODULE_DHT == 1
    else if (sensor_type == SENSOR_DHT11 || sensor_type == SENSOR_DHT22) {
      int dht_type = sensor_type == SENSOR_DHT11 ? DHT11 : DHT22;
      DHT* dht = new DHT(pin,dht_type);
      // register temperature sensor
      registerSensor(new SensorDHT(child_id,pin,dht,SensorDHT::TEMPERATURE,dht_type));
      // register humidity sensor
      child_id = _getAvailableChildId();
      return registerSensor(new SensorDHT(child_id,pin,dht,SensorDHT::HUMIDITY,dht_type));
    }
  #endif
  #if MODULE_SHT21 == 1
    else if (sensor_type == SENSOR_SHT21) {
      // register temperature sensor
      registerSensor(new SensorSHT21(child_id,SensorSHT21::TEMPERATURE));
      // register humidity sensor
      child_id = _getAvailableChildId();
      return registerSensor(new SensorSHT21(child_id,SensorSHT21::HUMIDITY));
    }
    else if (sensor_type == SENSOR_HTU21D) {
      // register temperature sensor
      registerSensor(new SensorHTU21D(child_id,SensorHTU21D::TEMPERATURE));
      // register humidity sensor
      child_id = _getAvailableChildId();
      return registerSensor(new SensorHTU21D(child_id,SensorHTU21D::HUMIDITY));
    }
  #endif
  #if MODULE_SWITCH == 1
    else if (sensor_type == SENSOR_SWITCH || sensor_type == SENSOR_DOOR || sensor_type == SENSOR_MOTION) {
      // ensure an interrupt pin is provided
      if (pin != INTERRUPT_PIN_1 && pin != INTERRUPT_PIN_2) return -1;
      // register the sensor
      int index = 0;
      if (sensor_type == SENSOR_SWITCH) index = registerSensor(new SensorSwitch(child_id, pin));
      else if (sensor_type == SENSOR_DOOR) index = registerSensor(new SensorDoor(child_id, pin));
      else if (sensor_type == SENSOR_MOTION) index = registerSensor(new SensorMotion(child_id, pin));
      // set an interrupt on the pin and set the initial value
      SensorSwitch* sensor = (SensorSwitch*)getSensor(index);
      sensor->setInterruptPin(pin);
      setInterrupt(pin,sensor->getMode(),sensor->getInitial());
      return index;
    }
  #endif
  #if MODULE_DS18B20 == 1
    else if (sensor_type == SENSOR_DS18B20) {
      // initialize the library
      OneWire* oneWire = new OneWire(pin);
      DallasTemperature* sensors = new DallasTemperature(oneWire);
      // initialize the sensors
      sensors->begin();
      int index = 0;
      // register a new child for each sensor on the bus
      for(int i = 0; i < sensors->getDeviceCount(); i++) {
        if (i > 0) child_id = _getAvailableChildId();
        index = registerSensor(new SensorDs18b20(child_id,pin,sensors,i));
      }
      return index;
    }
  #endif
  #if MODULE_BH1750 == 1
    else if (sensor_type == SENSOR_BH1750) {
      return registerSensor(new SensorBH1750(child_id));
    }
  #endif
  #if MODULE_MLX90614 == 1
    else if (sensor_type == SENSOR_MLX90614) {
      Adafruit_MLX90614* mlx = new Adafruit_MLX90614();
      // register ambient temperature sensor
      registerSensor(new SensorMLX90614(child_id,mlx,SensorMLX90614::TEMPERATURE_AMBIENT));
      // register object temperature sensor
      child_id = _getAvailableChildId();
      return registerSensor(new SensorMLX90614(child_id,mlx,SensorMLX90614::TEMPERATURE_OBJECT));
    }
  #endif
  #if MODULE_BME280 == 1
    else if (sensor_type == SENSOR_BME280) {
      Adafruit_BME280* bme = new Adafruit_BME280();
      if (! bme->begin(SensorBosch::GetI2CAddress(0x60))) {
        #if DEBUG == 1
          Serial.println(F("NO BME"));
        #endif
        return -1;
      }
      // register temperature sensor
      registerSensor(new SensorBME280(child_id,bme,SensorBME280::TEMPERATURE));
      child_id = _getAvailableChildId();
      // register humidity sensor
      registerSensor(new SensorBME280(child_id,bme,SensorBME280::HUMIDITY));
      // register pressure sensor
      child_id = _getAvailableChildId();
      registerSensor(new SensorBME280(child_id,bme,SensorBME280::PRESSURE));
      // register forecast sensor
      child_id = _getAvailableChildId();
      return registerSensor(new SensorBME280(child_id,bme,SensorBME280::FORECAST));
    }
  #endif
  #if MODULE_SONOFF == 1
    else if (sensor_type == SENSOR_SONOFF) {
      return registerSensor(new SensorSonoff(child_id));
    }
  #endif
  #if MODULE_BMP085 == 1
    else if (sensor_type == SENSOR_BMP085) {
      Adafruit_BMP085* bmp = new Adafruit_BMP085();
      if (! bmp->begin(SensorBosch::GetI2CAddress(0x55))) {
        #if DEBUG == 1
          Serial.println(F("NO BMP"));
        #endif
        return -1;
      }
      // register temperature sensor
      registerSensor(new SensorBMP085(child_id,bmp,SensorBMP085::TEMPERATURE));
      // register pressure sensor
      child_id = _getAvailableChildId();
      registerSensor(new SensorBMP085(child_id,bmp,SensorBMP085::PRESSURE));
      // register forecast sensor
      child_id = _getAvailableChildId();
      return registerSensor(new SensorBMP085(child_id,bmp,SensorBMP085::FORECAST));
    }
  #endif
  #if MODULE_HCSR04 == 1
    else if (sensor_type == SENSOR_HCSR04) {
      return registerSensor(new SensorHCSR04(child_id, pin));
    }
  #endif
  #if MODULE_MCP9808 == 1
    else if (sensor_type == SENSOR_MCP9808) {
      Adafruit_MCP9808 * mcp = new Adafruit_MCP9808();
      if (! mcp->begin()) {
        #if DEBUG == 1
          Serial.println(F("NO MCP"));
        #endif
        return -1;
      }
      // register temperature sensor
      registerSensor(new SensorMCP9808(child_id,mcp));
    }
  #endif
  else {
    #if DEBUG == 1
      Serial.print(F("INVALID "));
      Serial.println(sensor_type);
    #endif
    return -1;
  };
}

// attach a built-in or custom sensor to this manager
int NodeManager::registerSensor(Sensor* sensor) {
  #if DEBUG == 1
    Serial.print(F("REG I="));
    Serial.print(sensor->getChildId());
    Serial.print(F(" P="));
    Serial.print(sensor->getPin());
    Serial.print(F(" P="));
    Serial.print(sensor->getPresentation());
    Serial.print(F(" T="));
    Serial.println(sensor->getType());
  #endif
  #if POWER_MANAGER == 1
    // set auto power pin
    sensor->setAutoPowerPins(_auto_power_pins);
  #endif
  // add the sensor to the array of registered sensors
  _sensors[sensor->getChildId()] = sensor;
  // return the child_id
  return sensor->getChildId();
}

// un-register a sensor to this manager
void NodeManager::unRegisterSensor(int sensor_index) {
  // unlink the pointer to this sensor
  _sensors[sensor_index] == 0;
}

// return a sensor given its index
Sensor* NodeManager::get(int child_id) {
  // return a pointer to the sensor from the given child_id
  return _sensors[child_id];
}
Sensor* NodeManager::getSensor(int child_id) {
  return get(child_id);
}

// assign a different child id to a sensor'
bool NodeManager::renameSensor(int old_child_id, int new_child_id) {
  // ensure the old id exists and the new is available
  if (_sensors[old_child_id] == 0 || _sensors[new_child_id] != 0) return false;
  // assign the sensor to new id
  _sensors[new_child_id] = _sensors[old_child_id];
  // set the new child id
  _sensors[new_child_id]->setChildId(new_child_id);
  // free up the old id
  _sensors[old_child_id] = 0;
  return true;
}

// setup NodeManager
void NodeManager::before() {
  #if DEBUG == 1
    Serial.print(F("NodeManager v"));
    Serial.println(VERSION);
  #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 REMOTE_CONFIGURATION == 1 && PERSIST == 1
    // restore sleep configuration from eeprom
    if (loadState(EEPROM_SLEEP_SAVED) == 1) {
      // sleep settings found in the eeprom, restore them
      _sleep_mode = loadState(EEPROM_SLEEP_MODE);
      _sleep_time = loadState(EEPROM_SLEEP_TIME_MINOR);
      int major = loadState(EEPROM_SLEEP_TIME_MAJOR);
      if (major == 1) _sleep_time =  _sleep_time + 250;
      else if (major == 2) _sleep_time =  _sleep_time + 250 * 2;
      else if (major == 3) _sleep_time =  _sleep_time + 250 * 3;
      _sleep_unit = loadState(EEPROM_SLEEP_UNIT);
      #if DEBUG == 1
        Serial.print(F("LOADSLP M="));
        Serial.print(_sleep_mode);
        Serial.print(F(" T="));
        Serial.print(_sleep_time);
        Serial.print(F(" U="));
        Serial.println(_sleep_unit);
      #endif
    }
  #endif
  #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);
  #endif
  // setup individual sensors
  for (int i = 0; i < 255; i++) {
    if (_sensors[i] == 0) continue;
    // call each sensor's setup()
    _sensors[i]->before();
  }
}

// present NodeManager and its sensors
void NodeManager::presentation() {
  #if DEBUG == 1
    Serial.println(F("RADIO OK"));
  #endif
  // Send the sketch version information to the gateway and Controller
  if (_sleep_between_send > 0) sleep(_sleep_between_send);
  sendSketchInfo(SKETCH_NAME,SKETCH_VERSION);
  // present the service as a custom sensor to the controller
  _present(CONFIGURATION_CHILD_ID, S_CUSTOM);
  #if BATTERY_MANAGER == 1 && BATTERY_SENSOR == 1
    // present the battery service
    _present(BATTERY_CHILD_ID, S_MULTIMETER);
    // report battery level
    _process("BATTERY");
  #endif
  // present each sensor
  for (int i = 0; i < 255; i++) {
    if (_sensors[i] == 0) continue;
    // call each sensor's presentation()
    if (_sleep_between_send > 0) sleep(_sleep_between_send);
    _sensors[i]->presentation();
  }
  #if DEBUG == 1
    Serial.println(F("READY"));
    Serial.println("");
  #endif
}


// setup NodeManager
void NodeManager::setup() {
  #if DEBUG == 1
    Serial.print(F("MY I="));
    Serial.print(getNodeId());
    Serial.print(F(" M="));
    Serial.println(getControllerConfig().isMetric);
  #endif
  #if SERVICE_MESSAGES == 1
    _send(_msg.set("STARTED"));
  #endif
  // run setup for all the registered sensors
  for (int i = 0; i < 255; i++) {
    if (_sensors[i] == 0) continue;
    // call each sensor's setup()
    _sensors[i]->setup();
  }
}

// run the main function for all the register sensors
void NodeManager::loop() {
  MyMessage empty;
  // 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 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 < 255; 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
void NodeManager::receive(const MyMessage &message) {
  #if DEBUG == 1
    Serial.print(F("RECV S="));
    Serial.print(message.sender);
    Serial.print(F(" I="));
    Serial.print(message.sensor);
    Serial.print(F(" C="));
    Serial.print(message.getCommand());
    Serial.print(F(" T="));
    Serial.print(message.type);
    Serial.print(F(" P="));
    Serial.println(message.getString());
  #endif
  // process incoming service messages
  if (message.sensor == CONFIGURATION_CHILD_ID && message.getCommand() == C_REQ && message.type == V_CUSTOM) {
    _process(message.getString());
  }
  // dispatch the message to the registered sensor
  else if (_sensors[message.sensor] != 0) {
    #if POWER_MANAGER == 1
      // turn on the pin powering all the sensors
      if (_auto_power_pins) powerOn();
    #endif
    // call the sensor's receive()
     _sensors[message.sensor]->receive(message);
    #if POWER_MANAGER == 1
      // turn off the pin powering all the sensors
      if (_auto_power_pins) powerOff();
    #endif
  }
}

// request and return the current timestamp from the controller
long NodeManager::getTimestamp() {
  int retries = 3;
  _timestamp = -1;
  while (_timestamp == -1 && retries > 0) {
    #if DEBUG == 1
      Serial.println(F("TIME"));
    #endif
    // request the time to the controller
    requestTime();
    // keep asking every 1 second
    wait(1000);
    retries--;
  }  
  return _timestamp;
}

// receive the time from the controller and save it
void NodeManager::receiveTime(unsigned long ts) {
  _timestamp = ts;
  #if DEBUG == 1
    Serial.print(F("TIME T="));
    Serial.print(_timestamp);
  #endif
}

// send a message to the network
void NodeManager::_send(MyMessage & message) {
  // send the message, multiple times if requested
  for (int i = 0; i < _retries; i++) {
    // if configured, sleep beetween each send
    if (_sleep_between_send > 0) sleep(_sleep_between_send);
    #if DEBUG == 1
      Serial.print(F("SEND D="));
      Serial.print(message.destination);
      Serial.print(F(" I="));
      Serial.print(message.sensor);
      Serial.print(F(" C="));
      Serial.print(message.getCommand());
      Serial.print(F(" T="));
      Serial.print(message.type);
      Serial.print(F(" S="));
      Serial.print(message.getString());
      Serial.print(F(" I="));
      Serial.print(message.getInt());
      Serial.print(F(" F="));
      Serial.println(message.getFloat());
    #endif
    send(message,_ack);
  }
}

// process a service message
void NodeManager::_process(const char * message) {
  // HELLO: hello request
  if (strcmp(message, "HELLO") == 0) {
    _send(_msg.set(message));
  }
  #if BATTERY_MANAGER == 1
    // BATTERY: return the battery level
    else if (strcmp(message, "BATTERY") == 0) {
      // measure the board vcc
      float volt = 0;
      if (_battery_internal_vcc || _battery_pin == -1) volt = getVcc();
      else volt = analogRead(_battery_pin) * _battery_volts_per_bit;
      // calculate the percentage
      int percentage = ((volt - _battery_min) / (_battery_max - _battery_min)) * 100;
      if (percentage > 100) percentage = 100;
      if (percentage < 0) percentage = 0;
      #if DEBUG == 1
        Serial.print(F("BATT V="));
        Serial.print(volt);
        Serial.print(F(" P="));
        Serial.println(percentage);
      #endif
      #if BATTERY_SENSOR == 1
        // report battery voltage
        MyMessage battery_msg(BATTERY_CHILD_ID, V_VOLTAGE);
        _send(battery_msg.set(volt, 2));
      #endif
      // report battery level percentage
      sendBatteryLevel(percentage,_ack);
    }
  #endif
  #ifndef MY_GATEWAY_ESP8266
    // REBOOT: reboot the board
    else if (strcmp(message, "REBOOT") == 0) {
      #if DEBUG == 1
        Serial.println(F("REBOOT"));
      #endif
      // set the reboot pin connected to RST to low so to reboot the board
      _send(_msg.set(message));
      // Software reboot with watchdog timer. Enter Watchdog Configuration mode:
      WDTCSR |= (1<<WDCE) | (1<<WDE);
      // Reset enable
      WDTCSR= (1<<WDE);
      // Infinite loop until watchdog reset after 16 ms
      while(true){}
    }
  #endif
  // CLEAR: clear the user's eeprom
  else if (strcmp(message, "CLEAR") == 0) {
    #if DEBUG == 1
      Serial.println(F("CLEAR"));
    #endif
    for (int i = 0; i <= EEPROM_LAST_ID; i++) saveState(i, 0xFF);
    _send(_msg.set(message));
  }
  // VERSION: send back the extension's version
  else if (strcmp(message, "VERSION") == 0) {
    _send(_msg.set(VERSION));
  }
  #if REMOTE_CONFIGURATION == 1
    // IDxxx: change the node id to the provided one. E.g. ID025: change the node id to 25. Requires a reboot/restart
    else if (strlen(message) == 5 && strncmp("ID", message, strlen("ID")) == 0) {
      // extract the node id
      char s[4];
      s[0] = message[2];
      s[1] = message[3];
      s[2] = message[4];
      s[3] = '\0';
      int node_id = atoi(s);
      #if DEBUG == 1
        Serial.print(F("MY I="));
        Serial.println(node_id);
      #endif
      #ifndef MY_GATEWAY_ESP8266
        // Save static ID to eeprom
        //hwWriteConfig(EEPROM_NODE_ID_ADDRESS, (uint8_t)node_id);
      #endif
      // reboot the board
      _process("REBOOT");
    }
    // MODEx: change the way the node behaves. 0: stay awake withtout executing each sensors' loop(), 1: go to sleep for the configured interval, 2: wait for the configured interval, 3: stay awake and execute each sensors' loop() (e.g. MODE1)
    else if (strlen(message) == 5 && strncmp("MODE", message, strlen("MODE")) == 0) {
      // extract mode
      char s[2];
      s[0] = message[4];
      s[1] = '\0';
      _sleep_mode = atoi(s);
      #if DEBUG == 1
        Serial.print(F("SLEEP M="));
        Serial.println(_sleep_mode);
      #endif
      #if PERSIST == 1
        // save it to the eeprom
        saveState(EEPROM_SLEEP_SAVED, 1);
        saveState(EEPROM_SLEEP_MODE, _sleep_mode);
      #endif
      _send(_msg.set(message));
    }
    // INTVLnnnX: set and save the wait/sleep interval to nnn where X is S=Seconds, M=mins, H=Hours, D=Days. E.g. INTVL010M would be 10 minutes
    else if (strlen(message) == 9 && strncmp("INTVL", message, strlen("INTVL")) == 0) {
      // parse and set the sleep interval
      int offset = 5;
      // extract the unit (S=secs, M=mins, H=hours, D=Days)
      char unit[2];
      sprintf(unit, "%c", message[3 + offset]);
      unit[1] = '\0';
      if (strcmp(unit, "S") == 0) _sleep_unit = SECONDS;
      else if (strcmp(unit, "M") == 0) _sleep_unit = MINUTES;
      else if (strcmp(unit, "H") == 0) _sleep_unit = HOURS;
      else if (strcmp(unit, "D") == 0) _sleep_unit = DAYS;
      else return;
      // extract the requested time
      char s[4];
      s[0] = message[0 + offset];
      s[1] = message[1 + offset];
      s[2] = message[2 + offset];
      s[3] = '\0';
      _sleep_time = atoi(s);
      #if DEBUG == 1
        Serial.print(F("SLEEP T="));
        Serial.print(_sleep_time);
        Serial.print(F(" U="));
        Serial.println(_sleep_unit);
      #endif
      #if PERSIST == 1
        // save it to eeprom
        saveState(EEPROM_SLEEP_UNIT, _sleep_unit);
        // encode sleep time
        int major = 0;
        if (_sleep_time > 750) major = 3;
        else if (_sleep_time > 500) major = 2;
        else if (_sleep_time > 250) major = 1;
        int minor = _sleep_time - 250 * major;
        saveState(EEPROM_SLEEP_SAVED, 1);
        saveState(EEPROM_SLEEP_TIME_MINOR, minor);
        saveState(EEPROM_SLEEP_TIME_MAJOR, major);
      #endif
      // interval set, reply back with the same message to acknowledge.
      _send(_msg.set(message));
    }
  #endif
  // WAKEUP: when received after a sleeping cycle or during wait, abort the cycle and stay awake
  else if (strcmp(message, "WAKEUP") == 0) {
    #if DEBUG == 1
      Serial.println(F("WAKEUP"));
    #endif
    _send(_msg.set(message));
    _sleep_mode = IDLE;
  }
}

// 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;
  else if (_sleep_unit == HOURS) sleep_sec = sleep_sec * 3600;
  else if (_sleep_unit == DAYS) sleep_sec = sleep_sec * 43200;
  long sleep_ms = sleep_sec * 1000;
  #if DEBUG == 1
    Serial.print(F("SLEEP "));
    Serial.print(sleep_sec);
    Serial.println(F("s"));
  #endif
  #if SERVICE_MESSAGES == 1
    // notify the controller I'm going to sleep
    _send(_msg.set("SLEEPING"));
  #endif
  #if DEBUG == 1
    // print a new line to separate the different cycles
    Serial.println("");
  #endif
  // go to sleep
  int interrupt = -1;
  if (_sleep_mode == WAIT) {
    // wait for the given interval
    wait(sleep_ms);
    // send heartbeat to the controller
    sendHeartbeat(_ack);
  }
  else if (_sleep_mode == SLEEP) {
    // setup interrupt pins
    int interrupt_1_pin = _interrupt_1_mode == MODE_NOT_DEFINED ? INTERRUPT_NOT_DEFINED  : digitalPinToInterrupt(INTERRUPT_PIN_1);
    int interrupt_2_pin = _interrupt_2_mode == MODE_NOT_DEFINED ? INTERRUPT_NOT_DEFINED  : digitalPinToInterrupt(INTERRUPT_PIN_2);
    // enter smart sleep for the requested sleep interval and with the configured interrupts
    interrupt = sleep(interrupt_1_pin,_interrupt_1_mode,interrupt_2_pin,_interrupt_2_mode,sleep_ms, true);
    if (interrupt > -1) {
      // woke up by an interrupt
      int pin_number = -1;
      int interrupt_mode = -1;
      // map the interrupt to the pin
      if (digitalPinToInterrupt(INTERRUPT_PIN_1) == interrupt) {
        pin_number = INTERRUPT_PIN_1;
        interrupt_mode = _interrupt_1_mode;
      }
      if (digitalPinToInterrupt(INTERRUPT_PIN_2) == interrupt) {
        pin_number = INTERRUPT_PIN_2;
        interrupt_mode = _interrupt_2_mode;
      }
      _last_interrupt_pin = pin_number;
      #if DEBUG == 1
        Serial.print(F("WAKE P="));
        Serial.print(pin_number);
        Serial.print(F(", M="));
        Serial.println(interrupt_mode);
      #endif
      // when waking up from an interrupt on the wakup pin, stop sleeping
      if (_sleep_interrupt_pin == pin_number) _sleep_mode = IDLE;
    }
  }
  // coming out of sleep
  #if DEBUG == 1
    Serial.println(F("AWAKE"));
  #endif
  #if SERVICE_MESSAGES == 1
    // 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
void NodeManager::_present(int child_id, int type) {
  #if DEBUG == 1
    Serial.print(F("PRES I="));
    Serial.print(child_id);
    Serial.print(F(", T="));
    Serial.println(type);
  #endif
  if (_sleep_between_send > 0) sleep(_sleep_between_send);
  present(child_id,type,"",_ack);
}

// return the next available child_id
int NodeManager::_getAvailableChildId() {
  for (int i = 1; i < 255; i++) {
    if (i == CONFIGURATION_CHILD_ID) continue;
    if (i == BATTERY_CHILD_ID) continue;
    // empty place, return it
    if (_sensors[i] == 0) return i;
  }
}

// guess the initial value of a digital output based on the configured interrupt mode
int NodeManager::_getInterruptInitialValue(int mode) {
  if (mode == RISING) return LOW; 
  if (mode == FALLING) return HIGH; 
  return -1;
}


