From 627a9ac0d6dbe9c76d24796eff3e8b0f5dcee22f Mon Sep 17 00:00:00 2001 From: matzman666 Date: Fri, 16 Mar 2018 21:51:11 +0100 Subject: [PATCH] Initial commit --- NodeManager.cpp | 96 +++ NodeManager.h | 1887 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 1983 insertions(+) create mode 100644 NodeManager.cpp create mode 100644 NodeManager.h diff --git a/NodeManager.cpp b/NodeManager.cpp new file mode 100644 index 0000000..9225e9b --- /dev/null +++ b/NodeManager.cpp @@ -0,0 +1,96 @@ +/* + * 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(int8_t vcc_pin, int8_t ground_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 + _vcc_pin = vcc_pin; + // configure the vcc pin as output and initialize to high (power on) + pinMode(_vcc_pin, OUTPUT); + digitalWrite(_vcc_pin, HIGH); + _ground_pin = ground_pin; + if (_ground_pin >= 0) { + // configure the ground pin as output and initialize to low + 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) return true; + return false; +} + +bool PowerManager::isPowerOn() { + return digitalRead(_vcc_pin); +} + +// 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); +} + + diff --git a/NodeManager.h b/NodeManager.h new file mode 100644 index 0000000..851ff34 --- /dev/null +++ b/NodeManager.h @@ -0,0 +1,1887 @@ +/* + * 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