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());
      }
    }
    //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    
    

  • Admin

    Interesting..

    Fits into this discussion
    http://forum.mysensors.org/topic/2464/sketch-generator


Log in to reply
 

Suggested Topics

  • 1
  • 6
  • 3
  • 1
  • 3
  • 1

2
Online

11.4k
Users

11.1k
Topics

112.7k
Posts