1888 lines
52 KiB
C++
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
|