/*
 * NodeManager
 */
 
#ifndef NodeManager_h
#define NodeManager_h

#include <Arduino.h>


/***********************************
   Constants
*/

// define sleep mode
#define IDLE 0
#define SLEEP 1
#define WAIT 2

// define time unit
#define SECONDS 0
#define MINUTES 1
#define HOURS 2
#define DAYS 3

// define value type
#define TYPE_INTEGER 0
#define TYPE_FLOAT 1
#define TYPE_STRING 2

// define interrupt pins
#define INTERRUPT_PIN_1 3
#define INTERRUPT_PIN_2 2

// define eeprom addresses
#define EEPROM_LAST_ID 4
#define EEPROM_SLEEP_SAVED 0
#define EEPROM_SLEEP_MODE 1
#define EEPROM_SLEEP_TIME_MAJOR 2
#define EEPROM_SLEEP_TIME_MINOR 3
#define EEPROM_SLEEP_UNIT 4

// define NodeManager version
#define VERSION 1.2

/************************************
 * Include user defined configuration settings
 */
 
#include "config.h"

/***********************************
   Default configuration settings
*/

// if enabled, will load the sleep manager library. Sleep mode and sleep interval have to be configured to make the board sleeping/waiting
#ifndef SLEEP_MANAGER
  #define SLEEP_MANAGER 1
#endif
// if enabled, enable the capability to power on sensors with the arduino's pins to save battery while sleeping
#ifndef POWER_MANAGER
  #define POWER_MANAGER 1
#endif
// if enabled, will load the battery manager library to allow the battery level to be reported automatically or on demand
#ifndef BATTERY_MANAGER
  #define BATTERY_MANAGER 1
#endif
// if enabled, allow modifying the configuration remotely by interacting with the configuration child id
#ifndef REMOTE_CONFIGURATION
  #define REMOTE_CONFIGURATION 1
#endif
// if enabled, persist the configuration settings on EEPROM
#ifndef PERSIST
  #define PERSIST 0
#endif

// if enabled, enable debug messages on serial port
#ifndef DEBUG
  #define DEBUG 1
#endif

// if enabled, send a SLEEPING and AWAKE service messages just before entering and just after leaving a sleep cycle
#ifndef SERVICE_MESSAGES
  #define SERVICE_MESSAGES 1
#endif
// if enabled, a battery sensor will be created at BATTERY_CHILD_ID and will report vcc voltage together with the battery level percentage
#ifndef BATTERY_SENSOR
  #define BATTERY_SENSOR 1
#endif

// the child id used to allow remote configuration
#ifndef CONFIGURATION_CHILD_ID
  #define CONFIGURATION_CHILD_ID 200
#endif
// the child id used to report the battery voltage to the controller
#ifndef BATTERY_CHILD_ID
  #define BATTERY_CHILD_ID 201
#endif

// Enable this module to use one of the following sensors: SENSOR_ANALOG_INPUT, SENSOR_LDR, SENSOR_THERMISTOR
#ifndef MODULE_ANALOG_INPUT
  #define MODULE_ANALOG_INPUT 0
#endif
// Enable this module to use one of the following sensors: SENSOR_DIGITAL_INPUT
#ifndef MODULE_DIGITAL_INPUT
  #define MODULE_DIGITAL_INPUT 0
#endif
// Enable this module to use one of the following sensors: SENSOR_DIGITAL_OUTPUT, SENSOR_RELAY, SENSOR_LATCHING_RELAY
#ifndef MODULE_DIGITAL_OUTPUT
  #define MODULE_DIGITAL_OUTPUT 0
#endif
// Enable this module to use one of the following sensors: SENSOR_SHT21, SENSOR_HTU21D
#ifndef MODULE_SHT21
  #define MODULE_SHT21 0
#endif
// Enable this module to use one of the following sensors: SENSOR_DHT11, SENSOR_DHT22
#ifndef MODULE_DHT
  #define MODULE_DHT 0
#endif
// Enable this module to use one of the following sensors: SENSOR_SWITCH, SENSOR_DOOR, SENSOR_MOTION
#ifndef MODULE_SWITCH
  #define MODULE_SWITCH 0
#endif
// Enable this module to use one of the following sensors: SENSOR_DS18B20
#ifndef MODULE_DS18B20
  #define MODULE_DS18B20 0
#endif
// Enable this module to use one of the following sensors: SENSOR_BH1750
#ifndef MODULE_BH1750
  #define MODULE_BH1750 0
#endif
// Enable this module to use one of the following sensors: SENSOR_MLX90614
#ifndef MODULE_MLX90614
  #define MODULE_MLX90614 0
#endif

/***********************************
   Sensors types
*/
#if MODULE_ANALOG_INPUT == 1
  // Generic analog sensor, return a pin's analog value or its percentage
  #define SENSOR_ANALOG_INPUT 1
  // LDR sensor, return the light level of an attached light resistor in percentage
  #define SENSOR_LDR 2
  // Thermistor sensor, return the temperature based on the attached thermistor
  #define SENSOR_THERMISTOR 3
#endif
#if MODULE_DIGITAL_INPUT == 1
  // Generic digital sensor, return a pin's digital value
  #define SENSOR_DIGITAL_INPUT 4
#endif
#if MODULE_DIGITAL_OUTPUT == 1
  // Generic digital output sensor, allows setting the digital output of a pin to the requested value
  #define SENSOR_DIGITAL_OUTPUT 5
  // Relay sensor, allows activating the relay
  #define SENSOR_RELAY 6
  // Latching Relay sensor, allows activating the relay with a pulse
  #define SENSOR_LATCHING_RELAY 7
#endif
#if MODULE_DHT == 1
  // DHT11/DHT22 sensors, return temperature/humidity based on the attached DHT sensor
  #define SENSOR_DHT11 8
  #define SENSOR_DHT22 9
#endif
#if MODULE_SHT21 == 1
  // SHT21 sensor, return temperature/humidity based on the attached SHT21 sensor
  #define SENSOR_SHT21 10
  #define SENSOR_HTU21D 15
#endif
#if MODULE_SWITCH == 1
  // Generic switch, wake up the board when a pin changes status
  #define SENSOR_SWITCH 11
  // Door sensor, wake up the board and report when an attached magnetic sensor has been opened/closed
  #define SENSOR_DOOR 12
  // Motion sensor, wake up the board and report when an attached PIR has triggered
  #define SENSOR_MOTION 13
#endif
#if MODULE_DS18B20 == 1
  // DS18B20 sensor, return the temperature based on the attached sensor
  #define SENSOR_DS18B20 14
#endif
#if MODULE_BH1750 == 1
  // BH1750 sensor, return light in lux
  #define SENSOR_BH1750 16
#endif
#if MODULE_MLX90614 == 1
  // MLX90614 sensor, contactless temperature sensor
  #define SENSOR_MLX90614 17
#endif

// last Id: 17
/***********************************
  Libraries
*/

// include MySensors libraries
#include <core/MySensorsCore.h>
#include <core/MyHwAVR.h>

// include third party libraries
#if MODULE_DHT == 1
  #include <DHT.h>
#endif
#if MODULE_SHT21 == 1
  #include <Wire.h>
  #include <Sodaq_SHT2x.h>
#endif
#if MODULE_DS18B20 == 1
  #include <OneWire.h>
  #include <DallasTemperature.h>
#endif
#if MODULE_BH1750 == 1
  #include <BH1750.h>
  #include <Wire.h>
#endif
#if MODULE_MLX90614 == 1
  #include <Wire.h>
  #include <Adafruit_MLX90614.h>
#endif

/**************************************
   Classes
*/

/*
   PowerManager
*/

class PowerManager {
  public:
    PowerManager() {};
    // to save battery the sensor can be optionally connected to two pins which will act as vcc and ground and activated on demand
    void setPowerPins(int ground_pin, int vcc_pin, long wait = 50);
    void powerOn();
    void powerOff();
  private:
    int _vcc_pin = -1;
    int _ground_pin = -1;
    long _wait = 0;
    bool _hasPowerManager();
};


/***************************************
   Sensor: generic sensor class
*/
class Sensor {
  public:
    Sensor(int child_id, int pin);
    // where the sensor is attached to (default: not set)
    void setPin(int value);
    int getPin();
    // child_id of this sensor (default: not set)
    void setChildId(int value);
    int getChildId();
    // presentation of this sensor (default: S_CUSTOM)
    void setPresentation(int value);
    int getPresentation();
    // type of this sensor (default: V_CUSTOM)
    void setType(int value);
    int getType();
    // when queried, send the message multiple times (default: 1)
    void setRetries(int value);
    // For some sensors, the measurement can be queried multiple times and an average is returned (default: 1)
    void setSamples(int value);
    // If more then one sample has to be taken, set the interval in milliseconds between measurements (default: 0)
    void setSamplesInterval(int value);
    // if true will report the measure only if different then the previous one (default: false)
    void setTackLastValue(bool value);
    // if track last value is enabled, force to send an update after the configured number of cycles (default: -1)
    void setForceUpdate(int value);
    // the value type of this sensor (default: TYPE_INTEGER)
    void setValueType(int value);
    // for float values, set the float precision (default: 2)
    void setFloatPrecision(int value);
    // optionally sleep interval in milliseconds before sending each message to the radio network (default: 0)
    void setSleepBetweenSend(int value);
    #if POWER_MANAGER == 1
      // to save battery the sensor can be optionally connected to two pins which will act as vcc and ground and activated on demand
      void setPowerPins(int ground_pin, int vcc_pin, long wait = 50);
      // if enabled the pins will be automatically powered on while awake and off during sleeping (default: true)
      void setAutoPowerPins(bool value);
      // manually turn the power on
      void powerOn();
      // manually turn the power off
      void powerOff();
    #endif
    // define what to do at each stage of the sketch
    virtual void before();
    virtual void presentation();
    virtual void loop(const MyMessage & message);
    virtual void receive(const MyMessage & message);
    // abstract functions, subclasses need to implement
    virtual void onBefore() = 0;
    virtual void onLoop() = 0;
    virtual void onReceive(const MyMessage & message) = 0;
  protected:
    MyMessage _msg;
    int _sleep_between_send = 0;
    int _pin = -1;
    int _child_id;
    int _presentation = S_CUSTOM;
    int _type = V_CUSTOM;
    int _retries = 1;
    int _samples = 1;
    int _samples_interval = 0;
    bool _track_last_value = false;
    int _cycles = 0;
    int _force_update = -1;
    void _send(MyMessage & msg);
    #if POWER_MANAGER  == 1
      PowerManager _powerManager;
      bool _auto_power_pins = true;
    #endif
    int _value_type = TYPE_INTEGER;
    int _float_precision = 2;
    int _value_int = -1;
    float _value_float = -1;
    char * _value_string = "";
    int _last_value_int = -1;
    float _last_value_float = -1;
    char * _last_value_string = "";
};

/*
   SensorAnalogInput: read the analog input of a configured pin
*/
class SensorAnalogInput: public Sensor {
  public:
    SensorAnalogInput(int child_id, int pin);
    // the analog reference to use (default: not set, can be either INTERNAL or DEFAULT)
    void setReference(int value);
    // reverse the value or the percentage (e.g. 70% -> 30%) (default: false)
    void setReverse(bool value);
    // when true returns the value as a percentage (default: true)
    void setOutputPercentage(bool value);
    // minimum value for calculating the percentage (default: 0)
    void setRangeMin(int value);
    // maximum value for calculating the percentage (default: 1024)
    void setRangeMax(int value);
    // define what to do at each stage of the sketch
    void onBefore();
    void onLoop();
    void onReceive(const MyMessage & message);
  protected:
    int _reference = -1;
    bool _reverse = false;
    bool _output_percentage = true;
    int _range_min = 0;
    int _range_max = 1024;
    int _getPercentage(int value);
    int _getAnalogRead();
};

/*
   SensorLDR: return the percentage of light from a Light dependent resistor
*/
class SensorLDR: public SensorAnalogInput {
  public:
    SensorLDR(int child_id, int pin);
};

/*
   SensorThermistor: read the temperature from a thermistor
*/
class SensorThermistor: public Sensor {
  public:
    SensorThermistor(int child_id, int pin);
    // resistance at 25 degrees C (default: 10000)
    void setNominalResistor(int value);
    // temperature for nominal resistance (default: 25)
    void setNominalTemperature(int value);
    // The beta coefficient of the thermistor (default: 3950)
    void setBCoefficient(int value);
    // the value of the resistor in series with the thermistor (default: 10000)
    void setSeriesResistor(int value);
    // set a temperature offset
    void setOffset(float value);
    // define what to do at each stage of the sketch
    void onBefore();
    void onLoop();
    void onReceive(const MyMessage & message);
  protected:
    int _nominal_resistor = 10000;
    int _nominal_temperature = 25;
    int _b_coefficient = 3950;
    int _series_resistor = 10000;
    float _offset = 0;
};

/*
   SensorDigitalInput: read the digital input of the configured pin
*/
class SensorDigitalInput: public Sensor {
  public:
    SensorDigitalInput(int child_id, int pin);
    // define what to do at each stage of the sketch
    void onBefore();
    void onLoop();
    void onReceive(const MyMessage & message);
};

/*
   SensorDigitalOutput: control a digital output of the configured pin
*/
class SensorDigitalOutput: public Sensor {
  public:
    SensorDigitalOutput(int child_id, int pin);
    // set how to initialize the output (default: LOW)
    void setInitialValue(int value);
    // if greater than 0, send a pulse of the given duration in ms and then restore the output back to the original value (default: 0)
    void setPulseWidth(int value);
    // define what to do at each stage of the sketch
    void onBefore();
    void onLoop();
    void onReceive(const MyMessage & message);
  protected:
    int _initial_value = LOW;
    int _pulse_width = 0;
};


/*
   SensorRelay
*/
class SensorRelay: public SensorDigitalOutput {
  public:
    SensorRelay(int child_id, int pin);
    // define what to do at each stage of the sketch
    void onLoop();
};

/*
   SensorLatchingRelay
*/
class SensorLatchingRelay: public SensorRelay {
  public:
    SensorLatchingRelay(int child_id, int pin);
};

/*
   SensorDHT
*/
#if MODULE_DHT == 1
class SensorDHT: public Sensor {
  public:
    SensorDHT(int child_id, int pin, DHT* dht, int sensor_type, int dht_type);
    // define what to do at each stage of the sketch
    void onBefore();
    void onLoop();
    void onReceive(const MyMessage & message);
  protected:
    DHT* _dht;
    int _dht_type = DHT11;
    float _offset = 0;
    int _sensor_type = 0;
};
#endif

/*
   SensorSHT21: temperature and humidity sensor
*/
#if MODULE_SHT21 == 1
class SensorSHT21: public Sensor {
  public:
    SensorSHT21(int child_id, int sensor_type);
    // define what to do at each stage of the sketch
    void onBefore();
    void onLoop();
    void onReceive(const MyMessage & message);
  protected:
    float _offset = 0;
    int _sensor_type = 0;
};

/*
   SensorHTU21D: temperature and humidity sensor
*/

class SensorHTU21D: public SensorSHT21 {
  public:
    SensorHTU21D(int child_id, int pin);
};
#endif

/*
 * SensorSwitch
 */
class SensorSwitch: public Sensor {
  public:
    SensorSwitch(int child_id, int pin);
    // set the interrupt mode. Can be CHANGE, RISING, FALLING (default: CHANGE)
    void setMode(int value);
    int getMode();
    // milliseconds to wait before reading the input (default: 0)
    void setDebounce(int value);
    // time to wait in milliseconds after a change is detected to allow the signal to be restored to its normal value (default: 0)
    void setTriggerTime(int value);
    // define what to do at each stage of the sketch
    void onBefore();
    void onLoop();
    void onReceive(const MyMessage & message);
  protected:
    int _debounce = 0;
    int _trigger_time = 0;
    int _mode = CHANGE;
};

/*
 * SensorDoor
 */
class SensorDoor: public SensorSwitch {
  public:
    SensorDoor(int child_id, int pin);
};

/*
 * SensorMotion
 */
class SensorMotion: public SensorSwitch {
  public:
    SensorMotion(int child_id, int pin);
};

/*
   SensorDs18b20
*/
#if MODULE_DS18B20 == 1
class SensorDs18b20: public Sensor {
  public:
    SensorDs18b20(int child_id, int pin, DallasTemperature* sensors, int index);
    // define what to do at each stage of the sketch
    void onBefore();
    void onLoop();
    void onReceive(const MyMessage & message);
  protected:
    float _offset = 0;
    int _index;
    DallasTemperature* _sensors;
};
#endif

/*
   SensorBH1750
*/
#if MODULE_BH1750 == 1
class SensorBH1750: public Sensor {
  public:
    SensorBH1750(int child_id);
    // define what to do at each stage of the sketch
    void onBefore();
    void onLoop();
    void onReceive(const MyMessage & message);
  protected:
    BH1750* _lightSensor;
};
#endif

/*
   SensorMLX90614
*/
#if MODULE_MLX90614 == 1
class SensorMLX90614: public Sensor {
  public:
    SensorMLX90614(int child_id, Adafruit_MLX90614* mlx, int sensor_type);
    // define what to do at each stage of the sketch
    void onBefore();
    void onLoop();
    void onReceive(const MyMessage & message);
  protected:
    Adafruit_MLX90614* _mlx;
    int _sensor_type;
};
#endif

/***************************************
   NodeManager: manages all the aspects of the node
*/
class NodeManager {
  public:
    NodeManager();
    // the pin to connect to the RST pin to reboot the board (default: 4)
    void setRebootPin(int value);
    // send the same service message multiple times (default: 1)
    void setRetries(int value);
    #if BATTERY_MANAGER == 1
      // the expected vcc when the batter is fully discharged, used to calculate the percentage (default: 2.7)
      void setBatteryMin(float value);
      // the expected vcc when the batter is fully charged, used to calculate the percentage (default: 3.3)
      void setBatteryMax(float value);
      // how frequently (in hours) to report the battery level to the controller. When reset the battery is always reported (default: 1)
      void setBatteryReportCycles(int value);
    #endif
    #if SLEEP_MANAGER == 1
      // define if the board has to sleep every time entering loop (default: IDLE). It can be IDLE (no sleep), SLEEP (sleep at every cycle), WAIT (wait at every cycle
      void setSleepMode(int value);
      // define for how long the board will sleep (default: 0)
      void setSleepTime(int value);
      // define the unit of SLEEP_TIME. It can be SECONDS, MINUTES, HOURS or DAYS (default: MINUTES)
      void setSleep(int value1, int value2, int value3);
      void setSleepUnit(int value);
      // 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);
    #endif
    // 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);
    // optionally sleep interval in milliseconds before sending each message to the radio network (default: 0)
    void setSleepBetweenSend(int value);
    // register a built-in sensor
    int registerSensor(int sensor_type, int pin = -1, int child_id = -1);
    // register a custom sensor
    int registerSensor(Sensor* sensor);
    // return a sensor by its index
    Sensor* get(int sensor_index);
    #if POWER_MANAGER == 1
      // to save battery the sensor can be optionally connected to two pins which will act as vcc and ground and activated on demand
      void setPowerPins(int ground_pin, int vcc_pin, long wait = 50);
      // if enabled the pins will be automatically powered on while awake and off during sleeping (default: true)
      void setAutoPowerPins(bool value);
      // manually turn the power on
      void powerOn();
      // manually turn the power off
      void powerOff();
    #endif
    // hook into the main sketch functions
    void before();
    void presentation();
    void setup();
    void loop();
    void receive(const MyMessage & msg);
  private:
    #if SLEEP_MANAGER == 1
      int _sleep_mode = IDLE;
      int _sleep_time = 0;
      int _sleep_unit = MINUTES;
      int _sleep_interrupt_pin = -1;
    #endif
    #if BATTERY_MANAGER == 1
      float _battery_min = 2.6;
      float _battery_max = 3.3;
      int _battery_report_cycles = 10;
      int _cycles = 0;
      float _getVcc();
    #endif
    #if POWER_MANAGER == 1
      // to optionally controller power pins
      PowerManager _powerManager;
      bool _auto_power_pins = true;
    #endif
    MyMessage _msg;
    void _send(MyMessage & msg);
    int _sleep_between_send = 0;
    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 _reboot_pin = -1;
    Sensor* _sensors[255] = {0};
    void _process(const char * message);
    void _sleep();
    int _getAvailableChildId();
    int _getInterruptInitialValue(int mode);
    bool _startup = true;
};

#endif