Files
2018-03-16 21:51:11 +01:00

1888 lines
52 KiB
C++

/*
* NodeManager
*/
#ifndef NodeManager_h
#define NodeManager_h
#include <Arduino.h>
// 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 <nodemanager_config.h>
/***********************************
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 <WiFiUdp.h>
#endif
#ifdef MY_GATEWAY_ESP8266
#include <ESP8266WiFi.h>
#endif
#if MODULE_BMP280 == 1
#include <Wire.h>
#include <Adafruit_BMP280.h>
#endif
#if MODULE_DHT == 1
#include <DHT.h>
#endif
#if MODULE_ADS1115 == 1
#include <Adafruit_ADS1015.h>
#endif
#include <avr/wdt.h>
#include <avr/power.h>
#include <avr/sleep.h>
// include MySensors libraries
#include <core/MySensorsCore.h>
/***************************************
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<typename... _Elems>
struct Tuple;
// Zero Element tuple: Base case for recursion
template<>
struct Tuple<> {
Tuple() {}
// Functions called by NodeManager
template<typename... _Args>
inline void before(_Args...) noexcept {}
template<typename... _Args>
inline void setup(_Args...) noexcept {}
template<typename... _Args>
inline void presentation(_Args...) noexcept {}
template<typename... _Args>
inline void loop(_Args...) noexcept {}
template<typename... _Args>
inline bool process(_Args...) noexcept {
return false;
}
};
// Recursive definition
template<typename _Head, typename... _Tail>
struct Tuple<_Head, _Tail...> : public Tuple<_Tail...> {
Tuple() : Tuple<_Tail...>() {}
typedef _Head _headType;
_headType _head;
// Functions called by NodeManager
template<typename... _Args>
inline void before(_Args... args) noexcept {
_head.before(args...);
Tuple<_Tail...>::before(args...);
}
template<typename... _Args>
inline void setup(_Args... args) noexcept {
_head.setup(args...);
Tuple<_Tail...>::setup(args...);
}
template<typename... _Args>
inline void presentation(_Args... args) noexcept {
_head.presentation(args...);
Tuple<_Tail...>::presentation(args...);
}
template<typename... _Args>
inline void loop(_Args... args) noexcept {
_head.loop(args...);
Tuple<_Tail...>::loop(args...);
}
template<typename... _Args>
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<unsigned _Idx, typename Tuple>
struct _TupleElementAccessor;
// element found: base case for recursion
template<typename _Elem, typename... _Tail>
struct _TupleElementAccessor<0, Tuple<_Elem, _Tail...> > {
typedef _Elem elemType;
typedef Tuple<_Elem, _Tail...> tupleType;
};
// recursive definition
template<unsigned _Idx, typename _Head, typename... _Tail>
struct _TupleElementAccessor<_Idx, Tuple<_Head, _Tail...> >
: public _TupleElementAccessor<_Idx - 1, Tuple<_Tail...> > {};
// tuple get function
template<unsigned _Idx, typename... _Sensors>
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<unsigned _Idx, typename _RetVal, typename... _Sensors, typename... _Params>
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<bool, typename _Type = void>
struct enable_if {};
// Partial specialization for true.
template<typename _Type>
struct enable_if<true, _Type> {
typedef _Type type;
};
// Is_same: base case
template<typename T, typename U>
struct is_same {
enum{ value = false };
};
// Partial specialization for true.
template<typename T>
struct is_same<T, T> {
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<typename ValueType, unsigned _tprecision = 4>
inline typename enable_if<is_same<ValueType, float>::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<typename ValueType, unsigned _tprecision = 0>
inline typename enable_if<!is_same<ValueType, float>::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<float, 2>(tempValue, _ttempId, V_TEMP);
_sendValue<float, 2>(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<float, 2>(tempValue, _ttempId, V_TEMP, msg.sender);
retval = true;
}
if (msg.sensor == _tbaroId && msg.getCommand() == C_REQ) {
_sendValue<float, 2>(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<float, 2>(tempValue, _ttempId, V_TEMP);
_sendValue<float, 2>(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<float, 2>(tempValue, _ttempId, V_TEMP, msg.sender);
retval = true;
}
if (msg.sensor == _thumId && msg.getCommand() == C_REQ) {
_sendValue<float, 2>(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<unsigned _Idx, typename _RetVal = void, typename... _Params>
_RetVal initSubmodule(_Params... params) {
return sensor_init<_Idx, _RetVal>(submodules, params...);
}
template<unsigned _Idx, typename _RetVal = void, typename... _Params>
_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<ValueType, _tprecision>(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<ValueType, _tprecision>(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<typename ..._Args>
void before(_Args...) noexcept {}
template<typename ..._Args>
void setup(_Args...) noexcept {}
template<typename ..._Args>
void presentation(_Args...) noexcept {
String desc(F("AIn"));
present(_tsensorId, _tsensorType, desc.c_str());
}
virtual ValueType _getValue() {
return (ValueType)analogRead(_tpin);
}
template<typename ..._Args>
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<ValueType, _tprecision>(value, _tsensorId, _tsensorDataType);
msgCounter = _msgSendTimeout;
} else if (msgCounter > 0) {
msgCounter--;
}
lastCheckTime = time;
}
}
template<typename ..._Args>
bool process(const MyMessage& msg, _Args...) noexcept {
bool retval = false;
if (msg.sensor == _tsensorId && msg.getCommand() == C_REQ) {
_sendValue<ValueType, _tprecision>(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<typename ..._Args>
void before(_Args...) noexcept {}
template<typename ..._Args>
void setup(_Args...) noexcept {
pinMode(_tpin, _tpinMode);
}
template<typename ..._Args>
void presentation(_Args...) noexcept {
String desc(F("DIn"));
present(_tsensorId, _tsensorType, desc.c_str());
}
virtual int _getValue() {
return digitalRead(_tpin);
}
template<typename ..._Args>
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<typename ..._Args>
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<typename ..._Args>
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<typename ..._Args>
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<typename _Sensors = Tuple<>>
class NodeManager {
public:
NodeManager();
template<unsigned _Idx, typename _SensorType>
_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<unsigned _Idx, typename _RetVal = void, typename... _Params>
_RetVal initSensor(_Params... params) {
return sensor_init<_Idx, _RetVal>(_sensors, params...);
}
// hook into the main sketch functions
template<typename... _Args> void before(_Args...);
template<typename... _Args> void presentation(_Args...);
template<typename... _Args> void setup(_Args...);
template<typename... _Args> void loop(_Args...);
template<typename... _Args> void receive(const MyMessage & msg, _Args...);
template<typename... _Args> 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<typename _Sensors>
NodeManager<_Sensors>::NodeManager() {
// setup the service message container
_msg = MyMessage(CONFIGURATION_CHILD_ID, V_CUSTOM);
}
// setter/getter
#if BATTERY_MANAGER == 1
template<typename _Sensors>
void NodeManager<_Sensors>::setBatteryMin(float value) {
_battery_min = value;
}
template<typename _Sensors>
void NodeManager<_Sensors>::setBatteryMax(float value) {
_battery_max = value;
}
template<typename _Sensors>
void NodeManager<_Sensors>::setBatteryReportCycles(uint8_t value) {
_battery_report_cycles = value;
}
template<typename _Sensors>
void NodeManager<_Sensors>::setBatteryInternalVcc(bool value) {
_battery_internal_vcc = value;
}
template<typename _Sensors>
void NodeManager<_Sensors>::setBatteryPin(int8_t value) {
_battery_pin = value;
}
template<typename _Sensors>
void NodeManager<_Sensors>::setBatteryVoltsPerBit(float value) {
_battery_volts_per_bit = value;
}
template<typename _Sensors>
void NodeManager<_Sensors>::setBatteryReportWithInterrupt(bool value) {
_battery_report_with_interrupt = value;
}
#endif
#if not defined(DISABLE_SLEEP_MODE)
template<typename _Sensors>
void NodeManager<_Sensors>::setSleepMode(uint8_t value) {
_sleep_mode = value;
}
template<typename _Sensors>
void NodeManager<_Sensors>::setMode(uint8_t value) {
setSleepMode(value);
}
template<typename _Sensors>
void NodeManager<_Sensors>::setSleepTime(uint16_t value) {
_sleep_time = value;
}
template<typename _Sensors>
void NodeManager<_Sensors>::setSleepUnit(uint8_t value) {
_sleep_unit = value;
}
template<typename _Sensors>
void NodeManager<_Sensors>::setSleep(uint8_t value1, uint16_t value2, uint8_t value3) {
_sleep_mode = value1;
_sleep_time = value2;
_sleep_unit = value3;
}
template<typename _Sensors>
void NodeManager<_Sensors>::setSleepInterruptPin(int8_t value) {
_sleep_interrupt_pin = value;
}
template<typename _Sensors>
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<typename _Sensors>
void NodeManager<_Sensors>::setPowerPins(int vcc_pin, int ground_pin, int wait_time) {
_powerManager.setPowerPins(vcc_pin, ground_pin, wait_time);
}
template<typename _Sensors>
void NodeManager<_Sensors>::setAutoPowerPins(bool value) {
_auto_power_pins = value;
}
template<typename _Sensors>
void NodeManager<_Sensors>::powerOn() {
_power_on_before();
_powerManager.powerOn();
_power_on_after();
}
template<typename _Sensors>
void NodeManager<_Sensors>::powerOff() {
_power_off_before();
_powerManager.powerOff();
_power_off_after();
}
#endif
// setup NodeManager
template<typename _Sensors>
template<typename... _Args>
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<typename _Sensors>
template<typename... _Args>
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<typename _Sensors>
template<typename... _Args>
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<typename _Sensors>
template<typename... _Args>
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<typename _Sensors>
template<typename... _Args>
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<typename _Sensors>
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<typename _Sensors>
template<typename... _Args>
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<typename _Sensors>
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<typename _Sensors>
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<<WDCE) | (1<<WDE);
// Reset enable
//WDTCSR= (1<<WDE);
// Infinite loop until watchdog reset after 16 ms
while(true){}
}
#endif
// CLEAR: clear the user's eeprom
else if (strcmp_P(message, PSTR("CLEAR")) == 0) {
#if DEBUG == 1
Serial.println(F("CLEAR"));
#endif
for (int i = 0; i <= EEPROM_LAST_ID; i++) saveState(i, 0xFF);
_send(_msg.set(message));
}
// VERSION: send back the extension's version
else if (strcmp_P(message, PSTR("VERSION")) == 0) {
_send(_msg.set(VERSION));
}
#if REMOTE_CONFIGURATION == 1
// IDxxx: change the node id to the provided one. E.g. ID025: change the node id to 25. Requires a reboot/restart
else if (strlen(message) == 5 && strncmp_P(message, PSTR("ID"), 2) == 0) {
#if DEBUG == 1
// extract the node id
char s[4];
s[0] = message[2];
s[1] = message[3];
s[2] = message[4];
s[3] = '\0';
int node_id = atoi(s);
Serial.print(F("MY I="));
Serial.println(node_id);
#endif
#ifndef MY_GATEWAY_ESP8266
// Save static ID to eeprom
//hwWriteConfig(EEPROM_NODE_ID_ADDRESS, (uint8_t)node_id);
#endif
// reboot the board
processCommand("REBOOT");
}
#if not defined(DISABLE_SLEEP_MODE)
// MODEx: change the way the node behaves. 0: stay awake withtout executing each sensors' loop(), 1: go to sleep for the configured interval, 2: wait for the configured interval, 3: stay awake and execute each sensors' loop() (e.g. MODE1)
else if (strlen(message) == 5 && strncmp_P(message, PSTR("MODE"), 4) == 0) {
// extract mode
char s[2];
s[0] = message[4];
s[1] = '\0';
_sleep_mode = atoi(s);
#if DEBUG == 1
Serial.print(F("SLEEP M="));
Serial.println(_sleep_mode);
#endif
#if PERSIST == 1
// save it to the eeprom
saveState(EEPROM_SLEEP_SAVED, 1);
saveState(EEPROM_SLEEP_MODE, _sleep_mode);
#endif
_send(_msg.set(message));
}
// INTVLnnnX: set and save the wait/sleep interval to nnn where X is S=Seconds, M=mins, H=Hours, D=Days. E.g. INTVL010M would be 10 minutes
else if (strlen(message) == 9 && strncmp_P(message, PSTR("INTVL"), 5) == 0) {
// parse and set the sleep interval
int offset = 5;
// extract the unit (S=secs, M=mins, H=hours, D=Days)
char unit[2];
sprintf(unit, "%c", message[3 + offset]);
unit[1] = '\0';
if (strcmp_P(unit, PSTR("S")) == 0) _sleep_unit = SECONDS;
else if (strcmp_P(unit, PSTR("M")) == 0) _sleep_unit = MINUTES;
else if (strcmp_P(unit, PSTR("H")) == 0) _sleep_unit = HOURS;
else if (strcmp_P(unit, PSTR("D")) == 0) _sleep_unit = DAYS;
else return;
// extract the requested time
char s[4];
s[0] = message[0 + offset];
s[1] = message[1 + offset];
s[2] = message[2 + offset];
s[3] = '\0';
_sleep_time = atoi(s);
#if DEBUG == 1
Serial.print(F("SLEEP T="));
Serial.print(_sleep_time);
Serial.print(F(" U="));
Serial.println(_sleep_unit);
#endif
#if PERSIST == 1
// save it to eeprom
saveState(EEPROM_SLEEP_UNIT, _sleep_unit);
// encode sleep time
int major = 0;
if (_sleep_time > 750) major = 3;
else if (_sleep_time > 500) major = 2;
else if (_sleep_time > 250) major = 1;
int minor = _sleep_time - 250 * major;
saveState(EEPROM_SLEEP_SAVED, 1);
saveState(EEPROM_SLEEP_TIME_MINOR, minor);
saveState(EEPROM_SLEEP_TIME_MAJOR, major);
#endif
// interval set, reply back with the same message to acknowledge.
_send(_msg.set(message));
}
// WAKEUP: when received after a sleeping cycle or during wait, abort the cycle and stay awake
else if (strcmp_P(message, PSTR("WAKEUP")) == 0) {
#if DEBUG == 1
Serial.println(F("WAKEUP"));
#endif
_send(_msg.set(message));
_sleep_mode = IDLE;
}
#endif
#endif
}
// Is defined in core/wiring.c
extern volatile unsigned long timer0_millis;
#if not defined(DISABLE_SLEEP_MODE)
// wrapper of smart sleep
template<typename _Sensors>
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<typename _Sensors>
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