The homepage for this project has moved:
Check out the new site at:
http://ardomotic.com/
The new version contains many of the features described in the home page and many others.
Check out the new site at:
http://ardomotic.com/
The new version contains many of the features described in the home page and many others.
Explanation of the code
The code on the Arduino is implemented in a way so that is it easy to add different devices and methods of control. This page explains how the code is organized, and where to change things to add extra features.
Using Ardomotic in another house with the current features is not hard to do, however adding new types of devices/features may requires a much better understanding of how the code works. This page will try to explain most of the base concepts used on the implementation.
There are 3 basic concepts here that are importants to understand: Devices, Events and Schedules.
Using Ardomotic in another house with the current features is not hard to do, however adding new types of devices/features may requires a much better understanding of how the code works. This page will try to explain most of the base concepts used on the implementation.
There are 3 basic concepts here that are importants to understand: Devices, Events and Schedules.
Devices
Devices are the things the system is intended to control. To add a new device, we just need to give it a name, choose a type and a control method. The type of the device depends on how the device works, not how it's going to be controlled. There are three types of devices so far:
SWITCH: A switch device is something that can you can set as ON or OFF. It can be something as a light bulb, a heater, a toaster... Preety much anything that can be controlled with an ON/OFF button.
SLIDER: Sliders are devices that are normally controlled by two switches, where each swith changes the device in a different way. The typical application for this are things like window blinds and gates, where there is a button to open and another to close.
TIMER: Timers are similar to switches, but instead of switching between ON and OFF, they always turn ON for a pre-specified amount of time, and then turn OFF automatically.
In the code, devices are added like this:
ADD_DEVICE_SWITCH( "Pantry Light", DEVICE_CONTROL_DIRECT_PIN, IMAGE_LIGHT_ON, 0, 8);
So basically we need to give the device a name (Hall Light), we need to say how the device will be controlled (DEVICE_CONTROL_DIRECT_PIN), then which image should be used to represent the device ( IMAGE_LIGHT_ON ). The final two parameters (0, 4) are the initial state of the device, and the ID of the device.
For instance, a timer device would have two extra parameters:
ADD_DEVICE_TIMER( "Building Door", DEVICE_CONTROL_DEVICE_CONTROL_DIRECT_PIN, IMAGE_KEY-1, 0, 17, 0, 1);
Those two extra parameters in the end (0, 1) are the number of minutes and seconds the device is supposed to stay ON. Since this device was used to emulate a button click to open a door, the values are 0 minutes 1 seconds.
SWITCH: A switch device is something that can you can set as ON or OFF. It can be something as a light bulb, a heater, a toaster... Preety much anything that can be controlled with an ON/OFF button.
SLIDER: Sliders are devices that are normally controlled by two switches, where each swith changes the device in a different way. The typical application for this are things like window blinds and gates, where there is a button to open and another to close.
TIMER: Timers are similar to switches, but instead of switching between ON and OFF, they always turn ON for a pre-specified amount of time, and then turn OFF automatically.
In the code, devices are added like this:
ADD_DEVICE_SWITCH( "Pantry Light", DEVICE_CONTROL_DIRECT_PIN, IMAGE_LIGHT_ON, 0, 8);
So basically we need to give the device a name (Hall Light), we need to say how the device will be controlled (DEVICE_CONTROL_DIRECT_PIN), then which image should be used to represent the device ( IMAGE_LIGHT_ON ). The final two parameters (0, 4) are the initial state of the device, and the ID of the device.
For instance, a timer device would have two extra parameters:
ADD_DEVICE_TIMER( "Building Door", DEVICE_CONTROL_DEVICE_CONTROL_DIRECT_PIN, IMAGE_KEY-1, 0, 17, 0, 1);
Those two extra parameters in the end (0, 1) are the number of minutes and seconds the device is supposed to stay ON. Since this device was used to emulate a button click to open a door, the values are 0 minutes 1 seconds.
Control Types
Control types are the ways the Arduino connects to the different devices. Any Device can use any Control type. In the previous example:
ADD_DEVICE_SWITCH( "Pantry Light", DEVICE_CONTROL_DIRECT_PIN, IMAGE_LIGHT_ON, 0, 8);
This device is a light that is controlled directlly by the pin 8 on the Arduino. But if the device was created like this:
ADD_DEVICE_SWITCH( "Pantry Light", DEVICE_CONTROL_CHACON, IMAGE_LIGHT_ON, 0, 8);
instead of using the pin 8, we would be sending a wireless signal using the chacon protocol containing the code 8.
Adding a new control type is also very easy, as long as you already have the Arduino code to control that specific device. Let's say we want to control all the lights using an infrared remote controller. It would only be necessary to implement a function such as:
sendInfraredCommand(byte device_id, byte new_device_state)
and then add it to Event.h in the sendOrder() function:
switch (dev_control)
{
case DEVICE_CONTROL_SHIFT_REGISTER: sr.setRegisterNow(controller_id, dev_data); break;
case DEVICE_CONTROL_CHACON: chacon.sendCommand(controller_id, dev_data); break;
case DEVICE_CONTROL_DIRECT_PIN: digitalWrite(controller_id, dev_data); break;
// case YOUR_CONTROL_HERE: myControlSendCommand(byte controller_id, byte device_order); break;
}
Implementing this function can be really easy or really hard, depending on the control type we are talking about. So to someone thinking of using Ardomotic (or any similar project really) I would suggest starting by implementing such a function and make sure it's working properly. I created a small Application called SequenceDecoder using Arduino + Processing that can be used to decode several radio and infrared protocols. It is a very simple application, and designed with the Chacon protocol in mind, but it can give important information to someone else trying to decode a different protocol.
ADD_DEVICE_SWITCH( "Pantry Light", DEVICE_CONTROL_DIRECT_PIN, IMAGE_LIGHT_ON, 0, 8);
This device is a light that is controlled directlly by the pin 8 on the Arduino. But if the device was created like this:
ADD_DEVICE_SWITCH( "Pantry Light", DEVICE_CONTROL_CHACON, IMAGE_LIGHT_ON, 0, 8);
instead of using the pin 8, we would be sending a wireless signal using the chacon protocol containing the code 8.
Adding a new control type is also very easy, as long as you already have the Arduino code to control that specific device. Let's say we want to control all the lights using an infrared remote controller. It would only be necessary to implement a function such as:
sendInfraredCommand(byte device_id, byte new_device_state)
and then add it to Event.h in the sendOrder() function:
switch (dev_control)
{
case DEVICE_CONTROL_SHIFT_REGISTER: sr.setRegisterNow(controller_id, dev_data); break;
case DEVICE_CONTROL_CHACON: chacon.sendCommand(controller_id, dev_data); break;
case DEVICE_CONTROL_DIRECT_PIN: digitalWrite(controller_id, dev_data); break;
// case YOUR_CONTROL_HERE: myControlSendCommand(byte controller_id, byte device_order); break;
}
Implementing this function can be really easy or really hard, depending on the control type we are talking about. So to someone thinking of using Ardomotic (or any similar project really) I would suggest starting by implementing such a function and make sure it's working properly. I created a small Application called SequenceDecoder using Arduino + Processing that can be used to decode several radio and infrared protocols. It is a very simple application, and designed with the Chacon protocol in mind, but it can give important information to someone else trying to decode a different protocol.
Events
Events are the way used to control complex actions on the devices. So far the only events considered are turning something ON or OFF, but more can be added to implement more complex functionality. Each event has a time associated, and is executed when the system time reches it. For instance, with switch devices there would be no need for events. But on a timer device, we set the device ON and then create an Event to turn the device OFF at the desired time.
Schedules
Schedules are used to activate devices at specific times. They work similar to Events, but are more complex, since a Schedule can trigger several Events. For instance, a Schedule of a Timer device can be something like "Turn on the light for 30 seconds at 12:15". This Schedule would cause two events to be created: Turn ON the light at 12:15:00 and turn OFF the light at 12:15:30.
Adding new Device Types
To add a new type of device we only need to add a few lines of code, assuming it's not a very complex device. The most important part of code in in function setDeviceStatus, int the file Device.h.
This is the function that will be called every time any device need to be changed, and decides the sequence of actions to perform depending of the device type. Let's look at the code for the Timer device (remember the Timer is a device that when activated turns on for a specific amount of time and then turns off):
case DEVICE_TYPE_TIMER:
removeEvent(dev->control, dev->data[1]);
sendOrder(dev->control, dev->data[1] , 1);
addEvent(end_time, dev->control, dev->data[1], 0); break;
So first we remove all the queued events relating to that one device (because it might already have been turned ON before, so there will already be an Event programmed to make it go OFF. If exists, that order will be removed so that the timer will start counting again). Then we simply turn the device ON, and add a new Event to make it go OFF after the desired number of seconds.
When planning to add a new device type, the important part is to decide exactly how it's intended to work, and cover all the situations that may happen. Namely, if a device schedules Events, it's important to deal with the possibility of giving new orders while the previous ones are not complete yet.
To make it easiear to use the devices, macros are used to initialize all the variables. For instance:
#define ADD_DEVICE_SWITCH(name, control, image, state, address) ...........
These macros are used for two reasons. First make it simpler to use char arrays in progmem, which is used to save precious RAM. Without these macros, instead of something like:ADD_DEVICE("Kitchen Light", 2, 3);It would be necessary to have something like:P(name_1) = "Kitchen Light"; addDevice(name_1, 2, 3);The second reason is that it also simplifies the usage of structures. In earliear versions of the code, there was a specific data structure for each device type, with separate constructors, and many switch-cases on the code. In the current version, there is only a single structure:
typedef struct{ const prog_uchar* name; // Name of the device
byte type; // Type of the device
byte control; // Type of control used on the device
byte image; // First image associated with the device
int pos_x; // X position of the device in the blueprint
int pos_y; // Y position of the device in the blueprint
byte data[4]; // Generic data, different for each device} tDevice, *pDevice;
Different devices still need different data, so what i did was creating a structure with all the common data in separate variables, and then there's a byte array called "data" which contains all the data that is specific for a device type. The #defines take charge of initializing each device type with the data in the specific positions. This way most of the code is common, the "data" variable is only needed in very few situations.Lastly, the interface may need some changes, for instance if the idea is to add a device to be controlled with a slider bar, or a check-box, or a numerical number input... That really depends a lot on what the device is. For that part it would be necessary to change the functions where each html page is created.
This is the function that will be called every time any device need to be changed, and decides the sequence of actions to perform depending of the device type. Let's look at the code for the Timer device (remember the Timer is a device that when activated turns on for a specific amount of time and then turns off):
case DEVICE_TYPE_TIMER:
removeEvent(dev->control, dev->data[1]);
sendOrder(dev->control, dev->data[1] , 1);
addEvent(end_time, dev->control, dev->data[1], 0); break;
So first we remove all the queued events relating to that one device (because it might already have been turned ON before, so there will already be an Event programmed to make it go OFF. If exists, that order will be removed so that the timer will start counting again). Then we simply turn the device ON, and add a new Event to make it go OFF after the desired number of seconds.
When planning to add a new device type, the important part is to decide exactly how it's intended to work, and cover all the situations that may happen. Namely, if a device schedules Events, it's important to deal with the possibility of giving new orders while the previous ones are not complete yet.
To make it easiear to use the devices, macros are used to initialize all the variables. For instance:
#define ADD_DEVICE_SWITCH(name, control, image, state, address) ...........
These macros are used for two reasons. First make it simpler to use char arrays in progmem, which is used to save precious RAM. Without these macros, instead of something like:ADD_DEVICE("Kitchen Light", 2, 3);It would be necessary to have something like:P(name_1) = "Kitchen Light"; addDevice(name_1, 2, 3);The second reason is that it also simplifies the usage of structures. In earliear versions of the code, there was a specific data structure for each device type, with separate constructors, and many switch-cases on the code. In the current version, there is only a single structure:
typedef struct{ const prog_uchar* name; // Name of the device
byte type; // Type of the device
byte control; // Type of control used on the device
byte image; // First image associated with the device
int pos_x; // X position of the device in the blueprint
int pos_y; // Y position of the device in the blueprint
byte data[4]; // Generic data, different for each device} tDevice, *pDevice;
Different devices still need different data, so what i did was creating a structure with all the common data in separate variables, and then there's a byte array called "data" which contains all the data that is specific for a device type. The #defines take charge of initializing each device type with the data in the specific positions. This way most of the code is common, the "data" variable is only needed in very few situations.Lastly, the interface may need some changes, for instance if the idea is to add a device to be controlled with a slider bar, or a check-box, or a numerical number input... That really depends a lot on what the device is. For that part it would be necessary to change the functions where each html page is created.