Hi all,
In order to create devices (relay, button, dallas temperature, and more in the future) in a simple way, I have developped objects. My library compiles fine but I have not yet tested it because i'm waiting from China my Ardino pro mini order
I have added a preprocessor definition called "MAP" in order to use or not the standard STL C++ library. It grows the code around 5% but you can decide to not use it (which is the default choice).
Let me share my work and tell me what do you think about it.
Device.h
#pragma once
//#define MAP
#ifdef MAP
#include <StandardCplusplus.h>
#include <map>
#endif
// CButton
#include <MyMessage.h>
#include <Bounce2.h>
// Dallas temperature
#include <DallasTemperature.h>
#include <OneWire.h>
#include "ElapsedTime.h"
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
#define RELAY_ON 0 // GPIO value to write to turn on attached relay
#define RELAY_OFF 1 // GPIO value to write to turn off attached relay
MySensor* g_pGW;
#define TheSensor g_pGW
class CDevice;
#ifdef MAP
typedef std::map<uint8_t/* id */, CDevice*> MapOfDevice;
MapOfDevice g_deviceMap;
#else
#define DECLARE_NBOFDEVICE(nb) CDevice* g_deviceMap[nb];
extern CDevice* g_deviceMap[];
#endif
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
class CDevice
{
public:
CDevice(uint8_t id, uint8_t pin, int state=0)
: m_id(id)
, m_pin(pin)
, m_state(state)
{
g_deviceMap[m_id] = this;
#ifdef DEBUG
Serial.print("CDevice() id="); Serial.print(m_id);
#endif
}
virtual bool getState() { return m_state; } // generic behavior
virtual void setState(bool bState) { m_state = bState; } // generic behavior
virtual void checkChange() {}; // used in some devices
protected:
uint8_t m_id;
uint8_t m_pin;
int m_state;
};
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
class CRelay : public CDevice
{
public:
CRelay(uint8_t id, uint8_t pin, bool bActiveAtStart, bool bPersistent)
: CDevice(id, pin, RELAY_OFF)
, m_bPersistent(bPersistent)
{
g_pGW->present(m_id, S_LIGHT);
digitalWrite(m_pin, bActiveAtStart ? RELAY_ON : RELAY_OFF); // force value to be written before setting pin mode
pinMode(m_pin, OUTPUT);
if (m_bPersistent)
{
m_state = g_pGW->loadState(m_id); // last known state from previous run
digitalWrite(m_pin, m_state);
}
}
void setState(bool bOn)
{
m_state = bOn ? RELAY_ON : RELAY_OFF;
digitalWrite(m_pin, m_state);
if (m_bPersistent)
{
g_pGW->saveState(m_id, m_state);
}
#ifdef DEBUG
Serial.print("CRelay::set id="); Serial.print(m_id);
Serial.print(", new status="); Serial.println(m_state);
#endif
}
bool getState() { return m_state ? RELAY_ON : RELAY_OFF; }
protected:
bool m_bPersistent;
};
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
class CButton : public CDevice, public MyMessage, public Bounce
{
public:
CButton(uint8_t id, uint8_t pin, uint8_t linkedId/*relayId*/)
: CDevice(id, pin)
, MyMessage(linkedId, V_LIGHT)
, m_linkedId(linkedId)
{
pinMode(m_pin, INPUT);
digitalWrite(m_pin, HIGH); // internal pull-up
attach(m_pin); // Bounce class
interval(5); // Bounce class
};
// should be called periodically in order to check if button is pressed
virtual void checkChange()
{
update(); // Bounce class
int iVal = read(); // Bounce class
if (iVal != m_state)
{
g_pGW->send(set(g_deviceMap[m_linkedId]->getState() ? false : true), true); // change value
m_state = iVal;
}
}
protected:
uint8_t m_linkedId; // Id with which device (button) is associated
};
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
class CDallasT : public CDevice, public MyMessage
{
public:
// period : minimum allowed time between two request of temperature
CDallasT(uint8_t id, uint8_t pin, uint32_t period)
: CDevice(id, pin)
, MyMessage(id, V_TEMP)
, m_oneWire(pin)
, m_dallasT(NULL)
, m_etRequest(period)
, m_etRetrieve(0, false, false) // don't care 2nd param, object started disabled
{
m_dallasT.setOneWire(&m_oneWire);
m_dallasT.setWaitForConversion(false); // requestTemperatures() will not block current thread
g_pGW->present(m_id, S_TEMP);
int16_t conversionTime = m_dallasT.millisToWaitForConversion(m_dallasT.getResolution());
m_etRetrieve.setDelay(conversionTime);
}
// shoud be called periodically in order to check if temperature has to be measured
virtual void checkChange()
{
if (m_etRequest.isElapsed())
{
m_dallasT.requestTemperatures(); // request temperature from Dallas sensor
m_etRetrieve.start(); // 'wait' for end of temperature sampling
m_etRequest.start(); // 'wait' for next request
}
if (m_etRetrieve.isElapsed()) // wait for end of conversion
{
// fetch and round temperature to one decimal
float tp = g_pGW->getConfig().isMetric
? m_dallasT.getTempCByIndex(m_id) : m_dallasT.getTempFByIndex(m_id);
tp = static_cast<float>(static_cast<int>(tp * 10.)) / 10.;
if ((tp != m_fTempPrev) && (tp != -127.00) && (tp != 85.00))
{
g_pGW->send(setSensor(m_id).set(tp, 1));
m_fTempPrev = tp; // Save new temperature for next compare
}
m_etRetrieve.disable(); // once temperature is retrieved, wait for next request to retrieve again
}
}
protected:
OneWire m_oneWire;
DallasTemperature m_dallasT;
float m_fTempPrev;
CElapsedTime m_etRequest;
CElapsedTime m_etRetrieve;
};
ElapsedTime.h
#pragma once
// used to check if set time is elapsed
// limitation : 'isElased' method MUST be called at least 1 time every 49.71 days in order to work properly ;-)
class CElapsedTime
{
public:
// bWait1stTime means ignore timems value the very 1st call to 'isElapsed'
CElapsedTime(uint32_t timems=0, bool bWait1stTime=false, bool bEnabled=true)
: m_delay(timems)
, m_bEnabled(bEnabled)
{
start(bWait1stTime ? timems : 0);
};
~CElapsedTime() {};
// start to count requested 'wait' time
void start(uint32_t timems=0)
{
m_tNow = millis();
m_tEnd = m_tNow + (timems == 0) ? m_delay : timems;
m_bWrapped = (m_tEnd < m_tNow);
m_bEnabled = true;
}
void disable() { m_bEnabled = false; }
void setDelay(uint32_t delay) { m_delay = delay; }
// check if requested time is elapsed since previous call of 'start' method
bool isElapsed()
{
m_tNow = millis();
if (m_bWrapped && (m_tNow < 0x7FFFFFFF))
{
m_bWrapped = false;
}
return m_bEnabled && !m_bWrapped && (m_tNow > m_tEnd);
}
protected:
uint32_t m_tNow; // current 'time'
uint32_t m_tEnd; // computed end of 'time'
uint32_t m_delay; // used to memorize value provided in constructor
bool m_bWrapped; // overflow management (every around 0xFFFFFFFF/1000/3600/24 = 49.71 days)
bool m_bEnabled; // used to know if Elpser is enabled
};
RelayActuatorLrt.ino
// History Lrt
// 2015-12-03 v1.0 code understanding, set output pin to off at start
// 2015-12-04 v1.1 button added
#define WBUTTON
// 2015-12-04 v1.2 dallas temp added
#define WDALLASTEMP
// 2015-12-07 v1.3 relay and button class
// 2015-12-08 v1.4 dallas temperature device
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
#include <MySigningNone.h>
#include <MyTransportNRF24.h>
#include <MyTransportRFM69.h>
#include <MyHwATMega328.h>
#include <MySensor.h>
#include <SPI.h>
#include "Devices.h"
// NRFRF24L01 radio driver (set low transmit power by default)
MyTransportNRF24 radio(RF24_CE_PIN, RF24_CS_PIN, RF24_PA_LEVEL_GW);
//MyTransportRFM69 radio;
// Message signing driver (none default)
//MySigningNone signer;
// Select AtMega328 hardware profile
MyHwATMega328 hw;
// Construct MySensors library
MySensor gw(radio, hw);
#ifndef MAP
const int NBOFDEVICE=3;
DECLARE_NBOFDEVICE(NBOFDEVICE+1); // index 0 is not used
#endif
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
void setup()
{
gw.begin(incomingMessage, AUTO/*, true*/); // Initialize library and add callback for incoming messages
gw.sendSketchInfo("Lrt Device R+B+T", "1.4"); // Send the sketch version information to the gateway and Controller
TheSensor = &gw; // gives MySensor library to Device library
CRelay(1, 3, false, true); // relay where id=1, pin=3, off at start, pesistent
#ifdef WBUTTON
CButton(2, 4, 1); // button where id=2, pin=4 and associated relayid=1
#endif
#ifdef WDALLASTEMP
CDallasT(3, 5, 60000); // Dallas temp where id=3, pin=5 and period temperature retrieve is 1 min.
// CDallasT(4, 6, 60000); // internal module temperature
#endif
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
void loop()
{
gw.process(); // Alway process incoming messages whenever possible
#if defined WBUTTON || defined WDALLASTEMP
// parse all devices in order to know if change occurs
#ifdef MAP
MapOfDevice::iterator it = g_deviceMap.begin();
for(; it != g_deviceMap.end(); ++it)
{
it->second->checkChange();
}
#else
for(int i=1; i<=NBOFDEVICE; i++)
{
g_deviceMap[i]->checkChange();
}
#endif
#endif
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
void incomingMessage(const MyMessage &message) {
#ifdef DEBUG
if (message.isAck())
{
Serial.print("This is an ack from GW");
}
#endif
if (message.type == V_LIGHT) // relay request from GW
{
g_deviceMap[message.sensor]->setState(message.getBool());
}
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~