Device library
-
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()); } } //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
Interesting..
Fits into this discussion
http://forum.mysensors.org/topic/2464/sketch-generator