Arduino – Object Oriented HCS – Part 2


In my Part 1 article (Arduino – Object Oriented HCS – Part 1) I outlined a few of the software objects and the reasoning behind them for my Home Control System. The design goals where clean simple code and use appropriate OO techniques to make the code small and robust. I also wanted as much “off the shelf” hardware that was or could be DIN rail mounted.

The code introduced so far has been:

  • Controller Object – Response for creating IO device objects and state machines.
  • TimerObject – supplied by the Arduino supported library.
  • IODevice Objects, Relay & Switch Objects.
  • StateMachine Object – abstract base class for State Machines.

Memory Footprint

I need to first divert us to an issue that arose on many forums, using the c++ keyword “new” and its effect on memory usage!. Many people were critical of using the C++ keyword “new”. Firstly, the creation of objects is static in this application, that means an object is created and lives forever, unlike many apps in the non-embedded world where creating objects and deleting them on the fly is the norm and can be very prolific! Here our memory usage is consistent and relatively static so I thought it important to include some simple code to show the memory usage.

I included some code to track memory usage and dump the output to the Serial port. Below is the code added in setup (I added the lines in blue):

    Serial.begin(9600);
    Serial.print(“MEM (before): “);
    Serial.print(memoryFree());
    Serial.println(” bytes”);
    pCtl = new Controller();
    pCtl->init();
    Serial.print(“MEM (after controller): “);
    Serial.print(memoryFree());
    Serial.println(” bytes”);

The code as it is used in the 1second timer routine:

void Timer1sec()
{
Serial.println(“Seconds Loop Running”);
pCtl->Timer1sec();
Serial.print(“Free Memory: “);
Serial.print(memoryFree());
Serial.println(” bytes”);
//pRadio->BroadcastStatus();    // RF24 library call to send status out
}

After inserting the memory dumping code the output for a Mega 1280 was:

MEM (before): 6989 bytes
Controller::init()
SMBackDoor Constructor()
SMBackDoor::init()
MEM (after controller): 6665 bytes
Transition to ON state
Seconds Loop Running
Free Memory: 6629 bytes

We started with 6989 free bytes after the initialization of the serial library, then we initialized all the objects in the controller and ended up with 6665 bytes free, then we initialize the timer objects and 1 second later we have 6629 bytes free.This means using “new” and creating objects cost us 360 bytes so far. About 5% used, so “new” and how objects are created in the AVR gcc compiler is pretty efficient as new actually uses malloc to create the data area for the object. All objects use the same derived code so only the “extra” code for the child object is different and a small bit of data for each object and some space for a pointer table to the different objects.

Even using the Duemilanove returned the following output:

MEM (before): 1355 bytes
Controller::init()
SMBackDoor Constructor()
SMBackDoor::init()
MEM (after controller): 1031 bytes
Transition to ON state
Seconds Loop Running
Free Memory: 995 bytes

A nano returned the following output:

MEM (before): 1355 bytes
Controller::init()
SMBackDoor Constructor()
SMBackDoor::init()
MEM (after controller): 1031 bytes
Transition to ON state
Seconds Loop Running
Free Memory: 995 bytes

State Machine Code and Logic

Lets touch on a typical State Machine object, first the base class file StateMachine.h:

#ifndef _HDR_STATEMACHINE_
#define _HDR_STATEMACHINE_

class StateMachine
{
public:
    StateMachine() {};
    virtual ~StateMachine() {};
    virtual void init()=0;
    virtual void run()=0;
    virtual int  getState()=0;
    virtual void Timer1sec()=0;
    virtual void Timer1ms()=0;
};
#endif

All state machine objects derive from this class and most of the derived classes work is going to be inside the run() method. While the timer routines will increment and decrement counters as required automatically the real work is in run().

As can be seen in the code above, the base case has pure virtual methods (they end in “=0;”), these methods need to exist in the derived class (and have some code in them). The real reason for using pure virtual methods is to be able to Dynamically Bind to the run time State Machine Object’s methods (this is done when the Controller’s run() method is invoked).

A very simple state machine for a switch and a light will most likely only have 2 states, lets cal them SWON and SWOFF so we could code a number of these IO checks into 1 state machine object. If we wanted to add support for remote control we might add a third state “SWREMOTE”. The header file for our state machine might look like this:

#ifndef _HDR_SM_SWITCH_IO_
#define _HDR_SM_SWITCH_IO_

#include “StateMachine.h”
#include “Controller.h”

class SMSwitchIO : public StateMachine
{
public:
    SMSwitchIO(Controller *p);
    virtual ~SMSwitchIO();
    void init();
    void run(); 
    void Timer1sec(); // 1 second timer entry point
    void Timer1ms(); // 1 ms timer entry point
    int getState(); // will do nothing in this SMachine

private:
    unsigned int _c_state;
    Switch *pThermalDetector1;
    Switch *pThermalDetector2;
    Switch *pThermalDetector3;
    Relay  *pBackDeckLight;
    Relay  *pFloodLight1;
    Relay  *pFloodLight2;
    Relay  *pGardenLights;
    uint32 _on_timer;        // count down timer
    uint32 _off_timer;    // count up timer
    uint32 _retrigger_timer;    // countdown timer
    uint32 _retrig_value;    // 60 sec
    uint32 _on_timer_value;    // time to turn on by 60s
};
#endif

/*
* end of file
*/

The header file basically shows we will monitor 3 thermal detector inputs and be able to switch on 3 sets of lights, but the first draft of the code will just select one flood light to switch ON and we will expand on it later.

Lets create some logic to monitor a few thermal sensors. Firstly the sensors trip for 40 seconds and then turn off, they cant be altered but we want the lights to come on for at least 1 minute, and if they re-trigger in the 60 seconds after the sensor switches off we want to re-trigger the light for longer. Each time it re-triggers, we want it to come ON longer and longer then when there is no more re-triggering we want it to go OFF and reset the counters back to an initial default count (60 seconds).

This will involve some counters, all measuring in seconds, some count up and most count down. If the sensors are off the OFF counter increments, this might be handy later so we include it in.

We also need to track a “State”, OFF_STATE, ON_STATE and RETRIGGER. So lets look at the whole app as it stands in development phase so far, we will modify this during testing but here is draft 1:

#include “SMBackDoor.h”
#include “Controller.h”

/* ——– Constructors & Destructors ————*/

SMBackDoor::SMBackDoor(Controller *pCtl)
{
    Serial.println(“SMBackDoor Constructor()”);
    init();
    pThermalDetector1 = (Switch *) pCtl->FindInputDevice(“TD1”);
    pThermalDetector2 = (Switch *) pCtl->FindInputDevice(“TD2”);
    pThermalDetector3 = (Switch *) pCtl->FindInputDevice(“TD3”);
    pFloodLight1 = (Relay *) pCtl->FindOutputDevice(“FL1”);
    pFloodLight2 = (Relay *) pCtl->FindOutputDevice(“FL2”);
    pGardenLights= (Relay *) pCtl->FindOutputDevice(“GL1”);
    pBackDeckLight= (Relay *) pCtl->FindOutputDevice(“BD1”);
}

SMBackDoor::~SMBackDoor() {}

/*—————————————-
 *
 * init()
 *
 * setup the default values for everything
 */
void SMBackDoor::init()
{
    Serial.println(“SMBackDoor::init()”);
    _on_timer=0;
    _off_timer=0;
    _retrigger_timer=0;
    _retrig_value=DEF_RETRIGGER_TIME;
    _on_timer_value=DEF_ON_TIMER_TIME;
    this->setState(OFF_STATE);
}

/*—————————————-
 *
 * run()
 *
 * Called constantly from controller’s run() method
 * apply logic when timers and state are appropriate.
 */
void SMBackDoor::run()
{
    switch(this->getState())
    {
        // if we are in OFF state and input is HIGH,
        // set ON_STATE true and turn ON_TIMER to MAX
        // and retrigger to 0

        case OFF_STATE:
            this->pBackDeckLight->OFF();
            if(pThermalDetector1->getState()==1)
            {
                Serial.println(“Transition to ON state”);
                this->setState(ON_STATE);
                this->_on_timer = this->_on_timer_value;
                _retrigger_timer = 0;
            }
            break;

        // if we are in ON state and INPUT goes low (switch off)
        // and “on” timer >0 exit

        case ON_STATE:
            pBackDeckLight->ON();
            if((pThermalDetector1->getState()== 0) && (_on_timer>0)) return;

            // if we are in ON state and
            // INPUT goes low and
            // timer == 0 and
            // retrigger ==0
            // then start retrigger timer and
            // set RETRIGGER STATE and
            // turn light OFF
            
            if((pThermalDetector1->getState()== 0) && (_on_timer==0) && (_retrigger_timer==0))
            {
                _retrigger_timer = _retrig_value;
                Serial.println(“Transition to RETRIGGER state”);
                this->setState(RETRIGGER);
                return;
            }
            break;

        // if we are in RETRIGGER and input = 0 and retrigger timer == 0 then OFF_STATE
        case RETRIGGER:
            if((_retrigger_timer==0)&&(pThermalDetector1->getState()== 0))
            {
                Serial.println(“Transition to OFF state”);
                init();
                return;
            }
            
            // if we are in RETRIGGER and sensor goes on then
            // switch on (but for longer).
            if((_retrigger_timer>0)&&(pThermalDetector1->getState()== 1))
            {
                _retrigger_timer = 0;
                _on_timer_value +=60;
                Serial.println(“Transition to ON state – for longer”);
                this->setState(ON_STATE);
                return;
            }
            break;
        
        default:
            Serial.println(“Unknown RUN state”);
            this->setState(OFF_STATE);
            break;
    }
}

/*—————————————-
 *
 * Timer1ms()
 *
 * Called during a 1 ms timer hook from controller
 * DOES NOTHING BUT STILL NEEDED!
 */
void SMBackDoor::Timer1ms() {}

/*—————————————-
 *
 * Timer1sec()
 *
 * Called during a 1 second timer hook from controller
 */
void SMBackDoor::Timer1sec()
{
    if(this->getState()==OFF_STATE)
        this->_off_timer++;
    else
        this->_off_timer=0;
    if(this->_on_timer>0)
        this->_on_timer–;
    if(this->_retrigger_timer>0)
        this->_retrigger_timer–;
}

int SMBackDoor::getState() { return this->_c_state;}
/*
 * end of file
 */

I will elaborate more on the state machines in each part.

The hardware

Designing the hardware has taken some time, not because its difficult but because I wanted a professional layout that was easy to install, easy to troubleshoot and had some expansion for the first cabinet as work progresses on the house and more lighting and sensors are installed in proximity to this cabinet. I should point out that other lighting will go to other smaller satellite enclosures and it is worth noting that this is NOT a central node, that’s still in the works as I decide on a suitable hardware for it.

My first placement attempt was not to bad, some of the DIN rail terminals have not arrived so the blue terminals (4mm wire) will swap for 2.5mm beige ones. Initially I was going to look at the mounting of the Arduino directly to the backing plate but after I checked what I had in the cupboard I remembered the rep from ERNI once gave me some sample cases, I also bought a few extras so they look like a better option to mount the Arduino PCB inside.

I still have not decided which specific model to go with.

SAM_1472

Initial placement. the white rulers will be the slotted channel holding the power cables, 240V on the outer edge and 24vDC in the middle?

 

SAM_7589

Some of the cases I had in stock, not sure if these are easy to obtain still but for this project they will be fine.

 

Starting to look better, but the ERNI case should be in the middle. Need to get the slotted channel and try again.

Starting to look better, but the ERNI case should be in the middle. Need to get the slotted channel and try again.

Final Layout

After some trial and error placements I have decided that the outer edge will have the 240VAC cables in slotted conduit. The middle will be the Arduino and 24VDC cables, with the GPO to be placed at top right.

The 240VAC Input cables will connect to the 240VAC relays at the bottom via non-fused DIN terminals, 240VAC output relays will be at the top, they will have 24VDC relay coils.

The relay board in the middle will provide additional mechanical isolation of the 240VAC (DC coils) as the relay board is 5VDC powered directly from the Arduino, I also have the option to use the 4 channel replay boards I purchased a few months ago.

After some playing I found the 4 Channel relay shield can be stacked with some small mods to both boards. What I do lack is sufficient inputs on the small Arduino Modules so its now looking like a MEGA1280 or Mega2560 model is the minimum spec.

 

Whats coming in Part 3

Next article I will try to cover:

  • Interfacing to a front end
  • REST api
  • More on the DIN rail hardware and
  • Interfacing to RF communications.

 

Update 2015-01-15
Sid Young

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s