/* * NodeManager */ #ifndef NodeManager_h #define NodeManager_h #include // define NodeManager version #define VERSION "1.5" /*********************************** Constants */ // define sleep mode #define IDLE 0 #define SLEEP 1 #define WAIT 2 #define ALWAYS_ON 3 #define POWER_DOWN_WDT 10 // 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 /************************************ * Include user defined configuration settings * * Needs to be included in the include path */ #include /*********************************** Default configuration settings */ // 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 // last Id: 26 /*********************************** Libraries */ // include supporting libraries #ifdef MY_USE_UDP #include #endif #ifdef MY_GATEWAY_ESP8266 #include #endif #if MODULE_BMP280 == 1 #include #include #endif #if MODULE_DHT == 1 #include #endif #if MODULE_ADS1115 == 1 #include #endif #include #include #include // include MySensors libraries #include /*************************************** Global functions */ // return vcc in V float getVcc(); /************************************** Classes */ /* PowerManager */ class PowerManager { public: PowerManager() {}; // to save battery the sensor can be optionally connected to two pins (or a mosfet) which will act as vcc and ground and activated on demand void setPowerPins(int8_t vcc_pin, int8_t ground_pin = -1, int wait_time = 50); void powerOn(); void powerOff(); bool isConfigured(); bool isPowerOn(); private: int8_t _vcc_pin = -1; int8_t _ground_pin = -1; int _wait = 0; }; /*************************************** Tuple: Efficiently stores objects of arbitrary types thanks to template magic */ // Declaration template struct Tuple; // Zero Element tuple: Base case for recursion template<> struct Tuple<> { Tuple() {} // Functions called by NodeManager template inline void before(_Args...) noexcept {} template inline void setup(_Args...) noexcept {} template inline void presentation(_Args...) noexcept {} template inline void loop(_Args...) noexcept {} template inline bool process(_Args...) noexcept { return false; } }; // Recursive definition template struct Tuple<_Head, _Tail...> : public Tuple<_Tail...> { Tuple() : Tuple<_Tail...>() {} typedef _Head _headType; _headType _head; // Functions called by NodeManager template inline void before(_Args... args) noexcept { _head.before(args...); Tuple<_Tail...>::before(args...); } template inline void setup(_Args... args) noexcept { _head.setup(args...); Tuple<_Tail...>::setup(args...); } template inline void presentation(_Args... args) noexcept { _head.presentation(args...); Tuple<_Tail...>::presentation(args...); } template inline void loop(_Args... args) noexcept { _head.loop(args...); Tuple<_Tail...>::loop(args...); } template inline bool process(_Args... args) noexcept { if (_head.process(args...)) { return true; } else { return Tuple<_Tail...>::process(args...); } } }; // Used to access individual tuple elements template struct _TupleElementAccessor; // element found: base case for recursion template struct _TupleElementAccessor<0, Tuple<_Elem, _Tail...> > { typedef _Elem elemType; typedef Tuple<_Elem, _Tail...> tupleType; }; // recursive definition template struct _TupleElementAccessor<_Idx, Tuple<_Head, _Tail...> > : public _TupleElementAccessor<_Idx - 1, Tuple<_Tail...> > {}; // tuple get function template inline constexpr typename _TupleElementAccessor<_Idx, Tuple<_Sensors...> >::elemType& get(Tuple<_Sensors...>& tuple) noexcept { typedef typename _TupleElementAccessor<_Idx, Tuple<_Sensors...> >::tupleType tupleType; return (((tupleType&)tuple)._head); } // sensor init function template inline _RetVal sensor_init(Tuple<_Sensors...>& sensorTuple, _Params... params) noexcept { typedef typename _TupleElementAccessor<_Idx, Tuple<_Sensors...> >::tupleType tupleType; return ((tupleType&)sensorTuple)._head.init(params...); } // Enable_if: base case template struct enable_if {}; // Partial specialization for true. template struct enable_if { typedef _Type type; }; // Is_same: base case template struct is_same { enum{ value = false }; }; // Partial specialization for true. template struct is_same { enum{ value = true }; }; /*************************************** Sensors */ class Sensor { public: // send a message over the network bool _send(MyMessage & message) { #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 #if MY_MSG_ACK == 1 unsigned i = 0; do { if (send(message, MY_MSG_ACK)) { return true; } i++; #if MY_MSG_RETRY_DELAY > 0 if (i <= MY_MSG_RETRIES) { delay(MY_MSG_RETRY_DELAY); } #endif } while(i <= MY_MSG_RETRIES); return false; #else return send(message, false); #endif } // Send a value over the network // specialization for floats template inline typename enable_if::value, bool>::type _sendValue(ValueType value, uint8_t sensorId, uint8_t dataType, uint8_t dest = 0) { MyMessage message(sensorId, dataType); message.destination = dest; return _send(message.set(value, _tprecision)); } // Send a value over the network // specialization for non-floats template inline typename enable_if::value, bool>::type _sendValue(ValueType value, uint8_t sensorId, uint8_t dataType, uint8_t dest = 0) { MyMessage message(sensorId, dataType); message.destination = dest; return _send(message.set(value)); } }; /** * BMP 280 Pressure/Temperature Sensor */ #if MODULE_BMP280 == 1 template< uint8_t _taddr, uint8_t _ttempId, uint8_t _tbaroId, unsigned long _checkInterval = 2000, unsigned long _msgSendTimeout = 30 > class SensorBMP280 : public Sensor { public: float getTemperature() noexcept { return tempValue; } float getPressure() noexcept { return baroValue; } void before() noexcept {} void setup() noexcept { #if DEBUG == 1 if (!_bmp.begin(_taddr)) { Serial.println(F("NO BMP280")); } #else _bmp.begin(_taddr); #endif } void presentation() noexcept { String tempDesc(F("BMP_Temp")); present(_ttempId, S_TEMP, tempDesc.c_str()); String baroDesc(F("BMP_Baro")); present(_tbaroId, S_BARO, baroDesc.c_str()); } void loop() noexcept { auto time = millis(); if (time - lastCheckTime >= _checkInterval) { float temperature = _bmp.readTemperature(); if (! getControllerConfig().isMetric) { temperature = temperature * 1.8 + 32; } float pressure = _bmp.readPressure() / 100.0F; #if DEBUG == 1 Serial.print(F("BMP I=")); Serial.print(_ttempId); Serial.print(F(" T=")); Serial.println(temperature); Serial.print(F("BMP I=")); Serial.print(_tbaroId); Serial.print(F(" P=")); Serial.println(pressure); #endif if (temperature != tempValue || pressure != baroValue || msgCounter == 0) { tempValue = temperature; baroValue = pressure; _sendValue(tempValue, _ttempId, V_TEMP); _sendValue(baroValue, _tbaroId, V_PRESSURE); msgCounter = _msgSendTimeout; } else if (msgCounter > 0) { msgCounter--; } lastCheckTime = time; } } bool process(const MyMessage& msg) noexcept { bool retval = false; if (msg.sensor == _ttempId && msg.getCommand() == C_REQ) { _sendValue(tempValue, _ttempId, V_TEMP, msg.sender); retval = true; } if (msg.sensor == _tbaroId && msg.getCommand() == C_REQ) { _sendValue(baroValue, _tbaroId, V_PRESSURE, msg.sender); retval = true; } return retval; } protected: Adafruit_BMP280 _bmp; unsigned long lastCheckTime = 0; uint8_t msgCounter = 0; float tempValue = 0.0; float baroValue = 0.0; }; #endif /** * DHT Temperatur/Humidity Sensor */ #if MODULE_DHT == 1 template< uint8_t _ttype, int _tpin, uint8_t _ttempId, uint8_t _thumId, unsigned long _checkInterval = 2000, unsigned long _msgSendTimeout = 30 > class SensorDHT : public Sensor { public: SensorDHT() : Sensor(), dht(_tpin, _ttype) {} float getTemperature() noexcept { return tempValue; } float getHumidity() noexcept { return humValue; } void before() noexcept {} void setup() noexcept { dht.begin(); } void presentation() noexcept { String tempDesc(F("DHT_Temp")); present(_ttempId, S_TEMP, tempDesc.c_str()); String humDesc(F("DHT_Hum")); present(_thumId, S_HUM, humDesc.c_str()); } void loop() noexcept { auto time = millis(); if (time - lastCheckTime >= _checkInterval) { float temperature = dht.readTemperature(!getControllerConfig().isMetric); float hum = dht.readHumidity(); #if DEBUG == 1 Serial.print(F("DHT I=")); Serial.print(_ttempId); Serial.print(F(" T=")); Serial.println(temperature); Serial.print(F("DHT I=")); Serial.print(_thumId); Serial.print(F(" H=")); Serial.println(hum); #endif if (temperature != tempValue || hum != humValue || msgCounter == 0) { tempValue = temperature; humValue = hum; _sendValue(tempValue, _ttempId, V_TEMP); _sendValue(humValue, _thumId, V_HUM); msgCounter = _msgSendTimeout; } else if (msgCounter > 0) { msgCounter--; } lastCheckTime = time; } } bool process(const MyMessage& msg) noexcept { bool retval = false; if (msg.sensor == _ttempId && msg.getCommand() == C_REQ) { _sendValue(tempValue, _ttempId, V_TEMP, msg.sender); retval = true; } if (msg.sensor == _thumId && msg.getCommand() == C_REQ) { _sendValue(humValue, _thumId, V_HUM, msg.sender); retval = true; } return retval; } protected: DHT dht; unsigned long lastCheckTime = 0; uint8_t msgCounter = 0; float tempValue = 0.0; float humValue = 0.0; }; #endif /** * ADS1115 ADC Module */ #if MODULE_ADS1115 == 1 template< typename _SUBMODULES, uint8_t _taddr > class SensorADS1115 : public Sensor { public: // init subsensor template _RetVal initSubmodule(_Params... params) { return sensor_init<_Idx, _RetVal>(submodules, params...); } template _RetVal calibrateSubmodule(_Params... params) { return get<_Idx>(submodules).calibrate(ads, params...); } void before() noexcept { submodules.before(); } void setup() noexcept { ads = Adafruit_ADS1115(_taddr); ads.begin(); submodules.setup(ads); } void presentation() noexcept { submodules.presentation(); } void loop() noexcept { submodules.loop(ads); } bool process(const MyMessage& msg) noexcept { return submodules.process(ads, msg); } protected: Adafruit_ADS1115 ads; _SUBMODULES submodules; }; template< uint8_t _tchannel, uint8_t _tsensorId, uint8_t _tsensorType, uint8_t _tsensorDataType, unsigned long _checkInterval = 2000, unsigned long _msgSendTimeout = 30, typename ValueType = uint16_t, unsigned _tprecision = 4 > class ADS1115Channel : public Sensor { public: void before() noexcept { } void setup(Adafruit_ADS1115&) noexcept { } void presentation() noexcept { String desc(F("Ads")); present(_tsensorId, _tsensorType, desc.c_str()); } virtual ValueType _getValue(Adafruit_ADS1115& _ads) { _ads.setGain(GAIN_TWOTHIRDS); return (ValueType)_ads.readADC_SingleEnded(_tchannel); } void loop(Adafruit_ADS1115& _ads) noexcept { auto time = millis(); if (lastCheckTime == 0 || time - lastCheckTime >= _checkInterval) { auto val = _getValue(_ads); #if DEBUG == 1 Serial.print(F("ADS I=")); Serial.print(_tsensorId); Serial.print(F(" V=")); Serial.println(val); #endif if (val != value || msgCounter == 0) { value = val; _sendValue(value, _tsensorId, _tsensorDataType); msgCounter = _msgSendTimeout; } else if (msgCounter > 0) { msgCounter--; } lastCheckTime = time; } } bool process(Adafruit_ADS1115&, const MyMessage& msg) noexcept { bool retval = false; if (msg.sensor == _tsensorId && msg.getCommand() == C_REQ) { _sendValue(value, _tsensorId, _tsensorDataType, msg.sender); retval = true; } return retval; } protected: unsigned long lastCheckTime = 0; uint8_t msgCounter = 0; ValueType value = ValueType(); }; template< uint8_t _tchannel, uint8_t _tsensorId, unsigned long _checkInterval = 2000, unsigned long _msgSendTimeout = 30 > class ADS1115ChannelVolt : public ADS1115Channel<_tchannel, _tsensorId, S_MULTIMETER, V_VOLTAGE, _checkInterval, _msgSendTimeout, float, 4> { public: void presentation() noexcept { String desc(F("AdsVolt")); present(_tsensorId, S_MULTIMETER, desc.c_str()); } virtual float _getValue(Adafruit_ADS1115& _ads) { _ads.setGain(GAIN_TWOTHIRDS); // the returned value is actually signed, therefore we need a unsigned->signed cast before we cast to float return ((float)((int16_t)_ads.readADC_SingleEnded(_tchannel)) * 6.144)/32768.0; } }; template< uint8_t _tchannel, uint8_t _tsensorId, unsigned _trl = 1000, // load resistor (the shitty module I have has a way too small load resistor) unsigned _teepromBase = 0, // base position in eeprom for persistency unsigned long _checkInterval = 2000, unsigned long _msgSendTimeout = 30 > class ADS1115ChannelMQ135Module : public ADS1115Channel<_tchannel, _tsensorId, S_AIR_QUALITY, V_LEVEL, _checkInterval, _msgSendTimeout, float, 4> { public: void setup(Adafruit_ADS1115&) noexcept { float tmp; hwReadConfigBlock((void*)&tmp, EEPROM_LOCAL_CONFIG_ADDRESS+_teepromBase, sizeof(float)); if (!isnan(tmp) && tmp > 0.0f) { _r0 = tmp; } } void presentation() noexcept { String desc(F("Air")); present(_tsensorId, S_AIR_QUALITY, desc.c_str()); } virtual bool getTempAndHum(float&, float&) { return false; } void calibrate(Adafruit_ADS1115& _ads) { _ads.setGain(GAIN_ONE); float rs = 0.0f; for (unsigned i = 0; i < 32; i++) { rs += _calcCompRs((int16_t)_ads.readADC_SingleEnded(_tchannel)); delay(10); } rs = rs / 32.0f; _r0 = rs * pow(_curve_a/_co2_outside, 1.0f/_curve_b); hwWriteConfigBlock((void*)&_r0, EEPROM_LOCAL_CONFIG_ADDRESS+_teepromBase, sizeof(float)); } virtual float _getValue(Adafruit_ADS1115& _ads) { _ads.setGain(GAIN_ONE); float rs = _calcCompRs((int16_t)_ads.readADC_SingleEnded(_tchannel)); float rs_r0_ratio = rs/_r0; // ppm = a * (rs_ro_ratio) ^ b float ppm = _curve_a * pow(rs_r0_ratio, _curve_b); return ppm; } protected: float _r0 = 31345.97; static constexpr float _co2_outside = 400.0f; // From datasheet // ppm curve parameters: ppm = a * (rs/ro) ^ b static constexpr float _curve_a = 110.5571516; static constexpr float _curve_b = -2.726230391; // temperature curve parameters < 20°C: rt/r0 = a * t + b static constexpr float _cort1_a = -0.021336927; static constexpr float _cort1_b = 1.42673854; // temperature curve parameters >= 20°C: rt/r0 = a * t + b static constexpr float _cort2_a = -0.002974313; static constexpr float _cort2_b = 1.05948626; // humidity compensation parameter: static constexpr float _corh_a = -0.00218788; inline float _calcR(int16_t adcValue) { return (float)_trl * ((26400.0/(float)adcValue)-1.0); } float _calcCompRs(int16_t adcValue) { float rs = _calcR(adcValue); float temp, hum; if (getTempAndHum(temp, hum)) { if (temp < 20.0) { rs = rs/(_cort1_a*temp+_cort1_b); } else { rs = rs/(_cort2_a*temp+_cort2_b); } rs += (65.0 - hum) * _corh_a; } return rs; } }; #endif /** * Analog Input */ template< uint8_t _tpin, uint8_t _tsensorId, uint8_t _tsensorType, uint8_t _tsensorDataType, unsigned long _checkInterval = 2000, unsigned long _msgSendTimeout = 30, typename ValueType = int, unsigned _tprecision = 4 > class SensorAnalogInput : public Sensor { public: template void before(_Args...) noexcept {} template void setup(_Args...) noexcept {} template void presentation(_Args...) noexcept { String desc(F("AIn")); present(_tsensorId, _tsensorType, desc.c_str()); } virtual ValueType _getValue() { return (ValueType)analogRead(_tpin); } template void loop(_Args...) noexcept { auto time = millis(); if (lastCheckTime == 0 || time - lastCheckTime >= _checkInterval) { auto val = _getValue(); #if DEBUG == 1 Serial.print(F("AIn I=")); Serial.print(_tsensorId); Serial.print(F(" V=")); Serial.println(val); #endif if (val != value || msgCounter == 0) { value = val; _sendValue(value, _tsensorId, _tsensorDataType); msgCounter = _msgSendTimeout; } else if (msgCounter > 0) { msgCounter--; } lastCheckTime = time; } } template bool process(const MyMessage& msg, _Args...) noexcept { bool retval = false; if (msg.sensor == _tsensorId && msg.getCommand() == C_REQ) { _sendValue(value, _tsensorId, _tsensorDataType, msg.sender); retval = true; } return retval; } protected: unsigned long lastCheckTime = 0; uint8_t msgCounter = 0; ValueType value = ValueType(); }; /** * Photo-Resistor */ template< uint8_t _tpin, uint8_t _tsensorId, unsigned long _checkInterval = 2000, unsigned long _msgSendTimeout = 30 > class SensorLDR : public SensorAnalogInput<_tpin, _tsensorId, S_LIGHT_LEVEL, V_LIGHT_LEVEL, _checkInterval, _msgSendTimeout, float> { public: void presentation() noexcept { String desc(F("Light")); present(_tsensorId, S_LIGHT_LEVEL, desc.c_str()); } virtual float _getValue() override { return (float)analogRead(_tpin) / 10.23f; } }; /** * Vcc Sensor */ template< uint8_t _tsensorId, unsigned long _checkInterval = 2000, unsigned long _msgSendTimeout = 30 > class SensorVcc : public SensorAnalogInput<0, _tsensorId, S_MULTIMETER, V_VOLTAGE, _checkInterval, _msgSendTimeout, float, 2> { public: void presentation() noexcept { String desc(F("Vcc")); present(_tsensorId, S_MULTIMETER, desc.c_str()); } virtual float _getValue() override { return (float)getVcc(); } }; /** * Digital Input */ template< uint8_t _tpin, uint8_t _tsensorId, uint8_t _tsensorType, uint8_t _tsensorDataType, uint8_t _tpinMode = INPUT_PULLUP, unsigned _tdebounceCycles = 6, unsigned long _tmsgSendInterval = 60000 > class SensorDigitalInput: public Sensor { public: template void before(_Args...) noexcept {} template void setup(_Args...) noexcept { pinMode(_tpin, _tpinMode); } template void presentation(_Args...) noexcept { String desc(F("DIn")); present(_tsensorId, _tsensorType, desc.c_str()); } virtual int _getValue() { return digitalRead(_tpin); } template void loop(_Args...) noexcept { bool msgSend = false; auto val = _getValue(); if (value == -1 || val != value) { if (value == -1 || debounceCounter == 0) { value = val; #if DEBUG == 1 Serial.print(F("Din I=")); Serial.print(_tsensorId); Serial.print(F(" V=")); Serial.println(value); #endif _sendValue(value, _tsensorId, _tsensorDataType); } else { debounceCounter--; } } else { debounceCounter = _tdebounceCycles; } auto time = millis(); if (msgSend) { lastMsgTime = time; } else if (time - lastMsgTime >= _tmsgSendInterval) { _sendValue(value, _tsensorId, _tsensorDataType); lastMsgTime = time; } } template bool process(const MyMessage& msg, _Args...) noexcept { bool retval = false; if (msg.sensor == _tsensorId && msg.getCommand() == C_REQ) { _sendValue(value, _tsensorId, _tsensorDataType, msg.sender); retval = true; } return retval; } protected: unsigned long lastMsgTime = 0; int value = -1; uint8_t debounceCounter = _tdebounceCycles; }; /** * Motion Sensor */ template< uint8_t _tpin, uint8_t _tsensorId, uint8_t _tpinMode = INPUT_PULLUP, unsigned _tdebounceCycles = 6, unsigned long _tmsgSendInterval = 60000 > class SensorMotion : public SensorDigitalInput<_tpin, _tsensorId, S_MOTION, V_TRIPPED, _tpinMode, _tdebounceCycles, _tmsgSendInterval> { public: template void presentation(_Args...) noexcept { String desc(F("Motion")); present(_tsensorId, S_MOTION, desc.c_str()); } }; /** * Door Sensor */ template< uint8_t _tpin, uint8_t _tsensorId, uint8_t _tpinMode = INPUT_PULLUP, unsigned _tdebounceCycles = 6, unsigned long _tmsgSendInterval = 60000 > class SensorDoor : public SensorDigitalInput<_tpin, _tsensorId, S_DOOR, V_TRIPPED, _tpinMode, _tdebounceCycles, _tmsgSendInterval> { public: template void presentation(_Args...) noexcept { String desc(F("Door")); present(_tsensorId, S_DOOR, desc.c_str()); } }; /** * Digital Output */ template< uint8_t _tpin, uint8_t _tsensorId, uint8_t _tsensorType, uint8_t _tsensorDataType, int _tinitialValue = LOW, bool _tinverted = false, unsigned long _tmsgSendInterval = 60000 > class SensorDigitalOutput: public Sensor { public: void before() noexcept {} void setup() noexcept { pinMode(_tpin, OUTPUT); if (_tinverted) { digitalWrite(_tpin, !value); } else { digitalWrite(_tpin, value); } } void presentation() noexcept { String desc(F("DOut")); present(_tsensorId, _tsensorType, desc.c_str()); } void loop() noexcept { auto time = millis(); if (lastMsgTime == 0 || time - lastMsgTime >= _tmsgSendInterval) { #if DEBUG == 1 Serial.print(F("Dout I=")); Serial.print(_tsensorId); Serial.print(F(" V=")); Serial.println(value); #endif _sendValue(value, _tsensorId, _tsensorDataType); lastMsgTime = time; } } bool process(const MyMessage& msg) noexcept { bool retval = false; if (msg.sensor == _tsensorId && msg.getCommand() == C_REQ) { _sendValue(value, _tsensorId, _tsensorDataType, msg.sender); retval = true; } else if (msg.sensor == _tsensorId && msg.getCommand() == C_SET) { value = msg.getBool(); if (_tinverted) { digitalWrite(_tpin, !value); } else { digitalWrite(_tpin, value); } _sendValue(value, _tsensorId, _tsensorDataType, msg.sender); retval = true; } return retval; } protected: unsigned long lastMsgTime = 0; int value = _tinitialValue; }; template< uint8_t _tpin, uint8_t _tsensorId, unsigned long _tmsgSendInterval = 60000 > class SwitchBacklight: public SensorDigitalOutput<_tpin, _tsensorId, S_BINARY, V_STATUS, HIGH, true, _tmsgSendInterval> { public: void presentation() noexcept { String desc(F("Backlight")); present(_tsensorId, S_BINARY, desc.c_str()); } }; /*************************************** NodeManager: manages all the aspects of the node */ template> class NodeManager { public: NodeManager(); template _SensorType& getSensor() { return get<_Idx>(_sensors); } // the pin to connect to the RST pin to reboot the board (default: 4) void setRebootPin(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); // after how many sleeping cycles report the battery level to the controller. When reset the battery is always reported (default: 10) void setBatteryReportCycles(uint8_t value); // if true, the battery level will be evaluated by measuring the internal vcc without the need to connect any pin, if false the voltage divider methon will be used (default: true) void setBatteryInternalVcc(bool value); // if setBatteryInternalVcc() is set to false, the analog pin to which the battery's vcc is attached (https://www.mysensors.org/build/battery) (default: -1) void setBatteryPin(int8_t); // if setBatteryInternalVcc() is set to false, the volts per bit ratio used to calculate the battery voltage (default: 0.003363075) void setBatteryVoltsPerBit(float value); // If true, wake up by an interrupt counts as a valid cycle for battery reports otherwise only uninterrupted sleep cycles would contribute (default: true) void setBatteryReportWithInterrupt(bool value); #endif #if not defined(DISABLE_SLEEP_MODE) // define the way the node should behave. It can be IDLE (stay awake withtout executing each sensors' loop), SLEEP (go to sleep for the configured interval), WAIT (wait for the configured interval), ALWAYS_ON (stay awake and execute each sensors' loop) void setSleepMode(uint8_t value); void setMode(uint8_t value); // define for how long the board will sleep (default: 0) void setSleepTime(uint16_t value); // define the unit of SLEEP_TIME. It can be SECONDS, MINUTES, HOURS or DAYS (default: MINUTES) void setSleepUnit(uint8_t); // configure the node's behavior, parameters are mode, time and unit void setSleep(uint8_t value1, uint16_t value2, uint8_t value3); // 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(int8_t value); // configure the interrupt pin and mode. Mode can be CHANGE, RISING, FALLING (default: MODE_NOT_DEFINED) void setInterrupt(int8_t pin, uint8_t mode, int8_t pull = -1); #endif #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 vcc_pin, int ground_pin = -1, int wait_time = 10); // 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 // request and return the current timestamp from the controller long getTimestamp(); // init sensor template _RetVal initSensor(_Params... params) { return sensor_init<_Idx, _RetVal>(_sensors, params...); } // hook into the main sketch functions template void before(_Args...); template void presentation(_Args...); template void setup(_Args...); template void loop(_Args...); template void receive(const MyMessage & msg, _Args...); template void receiveTime(unsigned long ts, _Args...); protected: #if defined(ENABLE_SLEEP_HOOKS) virtual void _sleep_before() {}; virtual void _sleep_after() {}; #endif #if BATTERY_MANAGER == 1 float _battery_min = 2.6; float _battery_max = 3.3; uint8_t _battery_report_cycles = 10; bool _battery_report_with_interrupt = true; bool _battery_internal_vcc = true; int8_t _battery_pin = -1; float _battery_volts_per_bit = 0.003363075; uint8_t _cycles = 0; float _getVcc(); #endif #if POWER_MANAGER == 1 // to optionally controller power pins PowerManager _powerManager; bool _auto_power_pins = true; virtual void _power_off_before() {}; virtual void _power_off_after() {}; virtual void _power_on_before() {}; virtual void _power_on_after() {}; #endif MyMessage _msg; void _send(MyMessage & msg); #if not defined(DISABLE_SLEEP_MODE) uint8_t _sleep_mode = IDLE; int16_t _sleep_time = 0; uint8_t _sleep_unit = MINUTES; int8_t _sleep_interrupt_pin = -1; uint8_t _interrupt_1_mode = MODE_NOT_DEFINED; uint8_t _interrupt_2_mode = MODE_NOT_DEFINED; int8_t _interrupt_1_pull = -1; int8_t _interrupt_2_pull = -1; int8_t _last_interrupt_pin = -1; #endif long _timestamp = -1; _Sensors _sensors; virtual void processCommand(const char * message); #if not defined(DISABLE_SLEEP_MODE) void _sleep(); #endif void _present(int child_id, int type, const char* desc = ""); }; /******************************************* NodeManager */ // initialize the node manager template NodeManager<_Sensors>::NodeManager() { // setup the service message container _msg = MyMessage(CONFIGURATION_CHILD_ID, V_CUSTOM); } // setter/getter #if BATTERY_MANAGER == 1 template void NodeManager<_Sensors>::setBatteryMin(float value) { _battery_min = value; } template void NodeManager<_Sensors>::setBatteryMax(float value) { _battery_max = value; } template void NodeManager<_Sensors>::setBatteryReportCycles(uint8_t value) { _battery_report_cycles = value; } template void NodeManager<_Sensors>::setBatteryInternalVcc(bool value) { _battery_internal_vcc = value; } template void NodeManager<_Sensors>::setBatteryPin(int8_t value) { _battery_pin = value; } template void NodeManager<_Sensors>::setBatteryVoltsPerBit(float value) { _battery_volts_per_bit = value; } template void NodeManager<_Sensors>::setBatteryReportWithInterrupt(bool value) { _battery_report_with_interrupt = value; } #endif #if not defined(DISABLE_SLEEP_MODE) template void NodeManager<_Sensors>::setSleepMode(uint8_t value) { _sleep_mode = value; } template void NodeManager<_Sensors>::setMode(uint8_t value) { setSleepMode(value); } template void NodeManager<_Sensors>::setSleepTime(uint16_t value) { _sleep_time = value; } template void NodeManager<_Sensors>::setSleepUnit(uint8_t value) { _sleep_unit = value; } template void NodeManager<_Sensors>::setSleep(uint8_t value1, uint16_t value2, uint8_t value3) { _sleep_mode = value1; _sleep_time = value2; _sleep_unit = value3; } template void NodeManager<_Sensors>::setSleepInterruptPin(int8_t value) { _sleep_interrupt_pin = value; } template void NodeManager<_Sensors>::setInterrupt(int8_t pin, uint8_t mode, int8_t 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; } } #endif #if POWER_MANAGER == 1 template void NodeManager<_Sensors>::setPowerPins(int vcc_pin, int ground_pin, int wait_time) { _powerManager.setPowerPins(vcc_pin, ground_pin, wait_time); } template void NodeManager<_Sensors>::setAutoPowerPins(bool value) { _auto_power_pins = value; } template void NodeManager<_Sensors>::powerOn() { _power_on_before(); _powerManager.powerOn(); _power_on_after(); } template void NodeManager<_Sensors>::powerOff() { _power_off_before(); _powerManager.powerOff(); _power_off_after(); } #endif // setup NodeManager template template void NodeManager<_Sensors>::before(_Args... args) { #if DEBUG == 1 Serial.print(F("NodeManager v")); Serial.println(VERSION); #endif #if not defined(DISABLE_SLEEP_MODE) // 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 #endif #if not defined(DISABLE_SLEEP_MODE) && 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 // This doesn't make much sense? /*#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 _sensors.before(args...); } // present NodeManager and its sensors template template void NodeManager<_Sensors>::presentation(_Args... args) { #if DEBUG == 1 Serial.println(F("RADIO OK")); #endif // Send the sketch version information to the gateway and Controller sendSketchInfo(SKETCH_NAME,SKETCH_VERSION); // present the service as a custom sensor to the controller String configDesc(F("NodeCfg")); _present(CONFIGURATION_CHILD_ID, S_CUSTOM, configDesc.c_str()); #if BATTERY_MANAGER == 1 && BATTERY_SENSOR == 1 // present the battery service String batteryDesc(F("VBatt")); _present(BATTERY_CHILD_ID, S_MULTIMETER, batteryDesc.c_str()); // report battery level processCommand("BATTERY"); #endif // present each sensor _sensors.presentation(args...); #if DEBUG == 1 Serial.println(F("READY")); Serial.println(""); #endif } // setup NodeManager template template void NodeManager<_Sensors>::setup(_Args... args) { #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 _sensors.setup(args...); } // run the main function for all the register sensors template template void NodeManager<_Sensors>::loop(_Args... args) { #if not defined(DISABLE_SLEEP_MODE) // 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_mode == POWER_DOWN_WDT) && _sleep_time == 0) return; #if POWER_MANAGER == 1 // turn on the pin powering all the sensors if (_auto_power_pins) powerOn(); #endif #if defined(NODE_SMART_SLEEP_ENABLE) // send heartbeat to the controller sendHeartbeat(MY_MSG_ACK); #endif #endif // run loop for all the registered sensors _sensors.loop(args...); //ToDo: /*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 defined(NODE_SMART_SLEEP_ENABLE) wait(MY_SMART_SLEEP_WAIT_DURATION_MS); // Wait some time, maybe we're getting a message #endif #if POWER_MANAGER == 1 // turn off the pin powering all the sensors if (_auto_power_pins) powerOff(); #endif #if not defined(DISABLE_SLEEP_MODE) // continue/start sleeping as requested if (_sleep_mode == SLEEP || _sleep_mode == WAIT || _sleep_mode == POWER_DOWN_WDT) _sleep(); #endif } // dispacth inbound messages template template void NodeManager<_Sensors>::receive(const MyMessage &message, _Args... args) { #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) { processCommand(message.getString()); } // dispatch the message to the registered sensor else { #if POWER_MANAGER == 1 // turn on the pin powering all the sensors auto powerStatus = _powerManager.isPowerOn(); if (!powerStatus && _auto_power_pins && message.sensor != CONFIGURATION_CHILD_ID) powerOn(); #endif // call the sensor's process() _sensors.process(message, args...); #if POWER_MANAGER == 1 // turn off the pin powering all the sensors if (!powerStatus && _auto_power_pins && message.sensor != CONFIGURATION_CHILD_ID) powerOff(); #endif } } // request and return the current timestamp from the controller template long NodeManager<_Sensors>::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 template template void NodeManager<_Sensors>::receiveTime(unsigned long ts, _Args...) { _timestamp = ts; #if DEBUG == 1 Serial.print(F("TIME T=")); Serial.print(_timestamp); #endif } // send a message to the network template void NodeManager<_Sensors>::_send(MyMessage & message) { #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 #if MY_MSG_ACK == 1 unsigned i = 0; do { if (send(message, MY_MSG_ACK)) { return true; } i++; #if MY_MSG_RETRY_DELAY > 0 if (i <= MY_MSG_RETRIES) { delay(MY_MSG_RETRY_DELAY); } #endif } while(i <= MY_MSG_RETRIES); return false; #else return send(message, false); #endif } // process a service message template void NodeManager<_Sensors>::processCommand(const char * message) { // HELLO: hello request if (strcmp_P(message, PSTR("HELLO")) == 0) { _send(_msg.set(message)); } #if BATTERY_MANAGER == 1 // BATTERY: return the battery level else if (strcmp_P(message, PSTR("BATTERY")) == 0) { // measure the board vcc float volt = 0; if (_battery_internal_vcc || _battery_pin == -1) volt = getVcc(); else volt = analogRead(A1) * _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, MY_MSG_ACK); } #endif #ifndef MY_GATEWAY_ESP8266 // REBOOT: reboot the board else if (strcmp_P(message, PSTR("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: wdt_enable(WDTO_15MS); //WDTCSR |= (1< void NodeManager<_Sensors>::_sleep() { #if defined(ENABLE_SLEEP_HOOKS) _sleep_before(); #endif // 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(""); delay(100); #endif // go to sleep int interrupt = -1; if (_sleep_mode == WAIT) { // wait for the given interval wait(sleep_ms); // send heartbeat to the controller #if defined(SEND_HEARTBEAT_WHEN_WAITING) //sendHeartbeat(MY_MSG_ACK); #endif } 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; // map the interrupt to the pin if (digitalPinToInterrupt(INTERRUPT_PIN_1) == interrupt) { pin_number = INTERRUPT_PIN_1; } else if (digitalPinToInterrupt(INTERRUPT_PIN_2) == interrupt) { pin_number = INTERRUPT_PIN_2; } _last_interrupt_pin = pin_number; #if DEBUG == 1 int interrupt_mode = -1; if (digitalPinToInterrupt(INTERRUPT_PIN_1) == interrupt) { interrupt_mode = _interrupt_1_mode; } else if (digitalPinToInterrupt(INTERRUPT_PIN_2) == interrupt) { interrupt_mode = _interrupt_2_mode; } 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; } } else if (_sleep_mode == POWER_DOWN_WDT) { /* * Goes into power down mode (lowest power consumption) and uses the watchdog for waking up * The current implementation only has a granularity of seconds */ ADCSRA &= ~(1 << ADEN); // Disable ADC power_all_disable(); // Disable all modules uint8_t WDTsave = WDTCSR; // save WDT settings auto remaining_sec = sleep_sec; while (remaining_sec > 0) { // Sleep loop // Setup the WDT if (remaining_sec >= 8) { wdt_enable(WDTO_8S); remaining_sec -= 8; } else if (remaining_sec >= 4) { wdt_enable(WDTO_4S); remaining_sec -= 4; } else { wdt_enable(WDTO_1S); remaining_sec -= 1; } WDTCSR |= _BV(WDIE); /* Enable the WD interrupt. */ set_sleep_mode(SLEEP_MODE_PWR_DOWN); sleep_enable(); sleep_mode(); sleep_disable(); power_usart0_enable(); // Enable usart0, used by Serial power_timer0_enable(); // Enable timer0, used by delay and other timer related Arduino stuff } cli(); wdt_reset(); WDTCSR |= (1 << WDCE) | (1 << WDE); // enable WDT changes WDTCSR = WDTsave; // restore saved WDT settings sei(); // In power down mode time is standing still, therefore we need to adjust the time timer0_millis += sleep_ms; power_usart0_enable(); // Enable usart0, used by Serial power_timer0_enable(); // Enable timer0, used by delay and other timer related Arduino stuff power_adc_enable(); // Enable ADC ADCSRA |= (1 << ADEN); // enable ADC } // 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 defined(ENABLE_SLEEP_HOOKS) _sleep_after(); #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 processCommand("BATTERY"); _cycles = 0; } #endif } #endif // present the service template void NodeManager<_Sensors>::_present(int child_id, int type, const char* desc) { #if DEBUG == 1 Serial.print(F("PRES I=")); Serial.print(child_id); Serial.print(F(", T=")); Serial.println(type); #endif present(child_id,type,desc, MY_MSG_ACK); } #endif