For some time I have been planning to convert my external security lighting to computer control. On completion I would have the first stage of my Home Control System based on the Arduino well underway. Finally, as renovations on the building are progressing, the cabling, thermal sensors and external lighting has been installed so construction of the hardware and the software design and coding can start to take shape.
One of the guiding principals would be the use of “off the shelf” DIN rail hardware so the system would look like a professional installation and this would contribute to the reliability. On the software side a fully OO design that could be reused for other nodes was essential. Development has been done using the standard Arduino IDE and v1.0.5
Most Arduino projects look like simplistic code crammed into one file with little to no object oriented structure other than included libraries. I spent a bit of time thinking about how to architect this based on 20 years of embedded control systems design in my youth (before objects and C++).
After some time I decided a central controller object would be ideal and it would then instance IO devices and other objects which would make the main code body small and neat. So the main file looks like the following code block:
pCtl = new Controller();
t1 = new TimerObject(1);
t2 = new TimerObject(1000);
Serial.println(“Seconds Loop Running”);
//pRadio->BroadcastStatus(); // RF24 library call to send status out
// 1ms timer function – debounce for switch objects to use
The main Project file declares 3 objects:
- The main Controller (it should be a singleton but setup() only creates a single instance of it anyway).
- A 1 millisecond timer objects (used for switch input de-bouncing)
- A 1 second timer object (available to the controller for general timers).
setup() creates the objects and initializes them while the main loop is where we call the run() method of the controller and update the timer object loops.
The timer library can be downloaded from:
The timers call two functions, Timer1sec() and Timer1ms() which call the appropriate methods in the Controller object. Provision is being made to add additional functionality to the 1 second loop. For testing, I dump the internals of the controller every 5 seconds, this will be removed as the project progresses but you get the idea.
I decided that Input and Output should be managed via IO Device Objects rather than a big block of hard coded logic. I also decided they should be created from a common base class called IODevice, all input and output objects can inherit virtual functions from this base class and a few more might get added as the project progresses.
We need to do this so virtual methods like init(), run() and getState() can be called by the Controller object. For this project, phase 1 has Relay objects and Switch objects, later “Display” objects and “Transmission” objects can be added.
The Relay object is obvious, we are using physical relays to control the 240VAC lights and the Thermal Motion Sensors are 240VAC output so they trigger Relays as heat loads move in front of them. For the motion sensors, the relay Normally Open contacts are connected to the Arduino, this provides safe mechanical isolation from the mains power, so technically these are “Switch” objects. This also covers manual wall switches in the house which are also represented as Switch objects and physically connected using 240VAC Relays.
IODevice objects are created in the controllers init() method, called from setup(), and these objects are stored in the versatile “vector” STL container. if you are not familiar with the Standard Template Library (STL) then do so, it will save you a lot of programming effort in the long run. A simple vector class is defined for this project.
IODevices need two parameters, a pin they control and a text string by which we associate with them. So “FL1” might mean “Flood Light 1” while “BDSW1” might mean “Back Deck Switch 1”. We do this so we don’t need to hard code identifiers to an object.
The controllers init() method looks like this:
inputs.push_back( new Switch(“TD1”,0) ); // Thermal Detector 1
inputs.push_back( new Switch(“TD2”,1) );
inputs.push_back( new Switch(“TD3”,2) );
inputs.push_back( new Switch(“BDSW1”,3) ); // Back Deck switch
inputs.push_back( new Switch(“GLSW1”,4) ); // garden Lights
inputs.push_back( new Switch(“ALL”,5) ); // ALL lights on
outputs.push_back( new Relay( “FL1”, 13 ) ); // Flood Light 1
outputs.push_back( new Relay( “FL2”, 13 ) ); // Flood Light 2
outputs.push_back( new Relay( “BD1”, 13 ) ); // Back Deck Light
outputs.push_back( new Relay( “GL1”, 13 ) ); // Garden Light 1
// create first Statemachine object
The init() method shown is incomplete but you can see it creates a bunch of input objects and output objects as well as a state machine object. The objects address is stored in the STL container (using the STL push_back() method), each getting pushed into the end. We don’t care about the order, a method is provided in the Controller object to find the relevant IO Object using the identifier string.
State Machine Logic
The object SMBackDoor is a state machine object. This contains the logic specific to handling certain inputs and certain outputs. There will be more than 1 state machine in a typical implementation, in this design we can do some fancy control processes by writing a specific state machine to handle it, let the controller instance it and the State Machines just ask the controller for the specific objects they are interested in.
To exercise the state machines, the controller calls the run() method in each. It should also be noted that when a State Machine object is created, a pointer to the Controller object is passed in the constructor of the state machine, this way it can call the controller object for access to support methods.
I chose individual state machine objects to both minimise the logic contained in one place and because in this application the overlap of tasks is small and simple conflicts like turning something ON or OFF can have rules defined to cover conflicts. If there was one large block of state machine logic, the code would end up convoluted and that was against the principal of a simple clean OO design.
If I expand this principal to controlling HVAC and Equipment like solar controllers and the like then the logic for each will be totally self contained so the design still works for future implementations.
More on Timers
The timers play a critical role in this design, they call the controller object’s methods which loop through all the objects in the appropriate STL vector and call each objects Timer method. So independently each object is getting updated. Lets look at that next before we delve into the State machine.
* Called via main timer loop
* Calls the debounce methods in the “input” objects.
for(int i=0;i<inputs.size(); i++)
for(int i=0;i<outputs.size(); i++)
for(int i=0;i<statemachines.size(); i++)
Since Timer1ms() is a pure virtual function in all the base classes, its called on all the objects. For the Switch object, the Debounce() method is called from the Timer1ms() call. If an object does not need to use the TImer1ms() method it must still provide the method but it can have an empty body.
The run loop
The controllers run() loop could not be simpler, using the same style of logic as the Timer code, looping through the objects allows the controller to call the run() method of every State Machine. As shown in the follow block of code:
for(int i=0; i<statemachines.size() ; i++)
This is possible because the individual State Machines inherit from a virtual base class called StateMachine which defines a pure virtual method called run();
What this allows us to do is define new state machines, create them during the controllers init() method and then rest easy knowing all the Controller support code will automatically call all the required support methods in each object.
The complete Controller.h file:
IODevice * FindInputDevice(char *);
IODevice * FindOutputDevice(char *);
Vector<Switch *> inputs;
Vector<Relay *> outputs;
Vector<StateMachine *> statemachines;
- The Controller in depth
- State Machine logic
- and more!