Boiler control via e-mail and image processing

One of  my problems was waiting for the water inside the boiler to heat so I can take a bath. After starting the boiler I had to wait 30 minutes in the summer and 1 hour in the winter for the water inside the boiler to reach the desired temperature(45 degrees Celsius). Having to wake in the morning an hour earlier to press the button of the boiler or getting home tired and having to wait the water to heat was a pain.

To get rid of this problem the idea came: make a device that could remotely start my boiler and stop it when the water is heated. So it was clear that I was going to need a Raspberry Pi. Being such a small powerful device the Raspberry Pi could use a webcam connected to it’s USB to watch the analog thermometer of the boiler, do some basic image processing, take decisions and stop the boiler. By sending an e-mail to the gmail address that I created for this I can now start or stop my boiler remote.

For this to work the Raspberry Pi runs 2 python scripts and a C++ program that uses the openCV library for image processing. A program written in C is the main daemon that starts the above mentioned python scripts and the image processing program, it takes decisions based on their output and starts/stops the boiler.

HARDWARE side of things:

The boiler is started using a GPIO pin on the Raspberry Pi that commands a relay. The relay is connected to an electrical socket that has the boiler plugged inside.

The schematic is very simple but it involves working with mains voltages. As you know: WORKING WITH MAINS VOLTAGES CAN KILL YOU!!! and you should know what you are doing or request help from someone who knows.

schema_electrica

The relay is switched on/off by the GPIO pin 23(default state is OFF). When outputting a logic 1(3.3 V) on the pin, the transistor is switched on and 5V flow through the relay coil switching the boiler on. The relay is manufactured by Omron and is rated at a current of 10A at 230V. This is enough for me since the boiler is rated at 1500 W which means a current draw of 1500/230 = 6.5 A. The resistor has a value of 22 ohms and the transistor is C945. Any NPN transistor should work.

At first I tried to switch on the relay using an IRF830 mosfet transistor but the gate threshold level was according to the datasheet between 2 – 4 V and it seemed that in my particular case was bigger than 3.3 V so the transistor couldn’t open. I know there are mosfet transistors that have a lower gate threshold voltage like 1.8 V that could be suitable in this case but I adopted the bipolar transistor solution since I had it on hand. The LED connected to the GPIO 24 is an white LED used to illuminate the analog thermometer so the camera can see even in case of darkness.

SOFTWARE side of things:

The python script “mail.py” searches for new mail messages inside the inbox that were sent from my e-mail adress. The mail format is: “start dd/MM/hh/mm” or “stop”. Anything different from this is discarded by the script. The script checks if it is a start condition or a stop condition. For the start condition it checks if the date is valid, if it is in the past, present or future. If all is OK the script writes “start dd/MM/hh/mm” or “stop” in a file in the “/dev/shm/state”.

After the “mail.py” script exits, the script called “check_time.py” is launched. This script checks the “/dev/shm/state” previously written by the “mail.py” script. If the file is empty the script does nothing. The purpose of the script is to check the start time or the stop condition and notify the main daemon if the boiler needs to be started now or stopped.

If “/dev/shm/state” file contains the “stop” string the script writes the char “2″ inside a file “/dev/shm/now”.  If “/dev/shm/state” file contains the “start now” string the script writes the char “1″ inside a file “/dev/shm/now”.  If “/dev/shm/state” file contains the “start dd/MM/hh/mm” string the script checks if this date is right now and if it is, writes the char “1″ inside a file “/dev/shm/now”.

The main daemon launches the python scripts that exit and checks the file “/dev/shm/now”. If it detects the start condition, it will start the boiler and will do the same for a stop condition. Otherwise will loop after waiting 10s. If the start condition is detected the main daemon will turn on the boiler and the LED by putting the GPIO 23 and 24 pins in a high state. The wiringPI library(http://wiringpi.com/download-and-install/) is used to deal with the GPIO pins.

After the boiler is started the main daemon will periodically invoke the “imageproc” binary that captures an image from the USB camera, does some processing on the captured image and takes the decision if the water reached the desired temperature. After the decision the program notifies the main daemon by writing to the “/dev/shm/water_temperature” file. If the temperature was reached a “1″ char will be written, otherwise a “0″ char will be written.

To easily install openCV on Raspberry Pi do the following:

sudo apt-get update
sudo apt-get install cmake
sudo apt-get install libopencv-dev

The webcam captures the picture using the following code:

	for(i = 0; i < 30; i++)
	{
		stream1.read(cameraFrame);
	}

In the for loop 30 images are captured consecutively but only the last capture is used. The reason to capture multiple images is that when the camera starts taking captures it will first adapt to the light conditions. The first images are very dark and incomprehensible. The time needed for light adaptation is about 2s. Capturing 30 frames seems to do the trick.

For the processing part the program takes a sub-image  from the captured one in the place where the indicator needle will be when the temperature reached approx. 45 degree Celsius. From this sub-image the program counts the number of red points(color of the needle) on the white background. If no red points are detected it means that the needle didn’t get at the desired temperature yet.

sub-image

The following is the code that takes the sub-image and counts the number of red pixels:

matrix2 = cameraFrame(Range(127,169), Range(427, 460));
int nrows, ncols;
nrows = matrix2.rows;
ncols = matrix2.cols;
// segmentation for red, and count
for(i = 0; i < ncols + 1; i++)
{
	for(j = 0; j < ncols + 1; j++)
	{
			if((matrix2.at(i, j)[0] < 200) && (matrix2.at(i, j)[1] < 200)) //if colour is not close to white
			{
				if (matrix2.at(i, j)[2] > 200)
					red_quant += 255;
			}
	}
}

The matrix2 variable is the sub-image taken from the webcam capture(contained in the cameraFrame variable). In the for loop the pixels of the images are individually accessed. In openCV the images are held in BGR format so matrix2.at<cv::Vec3b>(i, j)[0] represents the blue pixels, matrix2.at<cv::Vec3b>(i, j)[1] the green ones and matrix2.at<cv::Vec3b>(i, j)[2] the red pixels.

Experimentally I chose a threshold value for the pixel colors of 200. The pixel colors have values in the range of 0 – 255 (8 bits). Because of the white led that illuminates the thermometer the red is seen on the camera capture as pink. If the RGB levels are above 200 it is clear that the color seen is white and not red. So for the red color to be identified the levels of green and blue need to be below 200, and the red needs to be above 200. Every time a red pixel is identified the red_quant variable adds 255 to it’s value(could be any value it depends on how you interpret the result). When the red_quant value will be greater than 2000 the main daemon will be notified that the water is heated at the desired temperature so it can stop the boiler.

I also implemented a timeout function inside the code for safety reasons. If nobody stops the boiler in 90 minutes the boiler will be stopped by our timeout function.

start_timestamp = time(NULL);

Inside the start_boiler() function the start_timestamp variable is loaded with the epoch time.

void check_time_overflows(void)
{
        /* if boiler was started for more than 1.5 hours but no one stopped him 
        it is clear that there is a problem  so we should stop*/
        time_t current_time;
        current_time = time(NULL);
        if((current_time - start_timestamp) >= 5400) //5400 seconds
        {
                printf("!!!! Time overflow, STOP boiler\n");
                stop_boiler();
                start_timestamp = 0;
        }        
}

Every time the check_time_overflows() function is called the difference between the current_time variable and start_timestamp is calculated. If this difference is bigger than 5400 s(90 minutes) the boiler will be stopped.

You can see that all output is written to files in “/dev/shm/” folder. This folder is in fact a location in the RAM memory so writing here is very fast and it doesn’t wear the memory card but the files are not persistent(the files will be erased at reboot/poweroff). The main daemon captures all the output from the python scripts and the image processing program and redirects the output to the “/dev/shm/boil_log” file. I saw that the output from the python scripts and the image processing program were sent to the file but the output of the main daemon was not sent there. When I was manually starting the daemon without output redirection all the output appeared OK. After some googling I stumbled over this Stack Overflow thread http://stackoverflow.com/questions/8004319/redirection-doesnt-work/. So I added in the main daemon code the fflush(stdout) call and all the output appeared in the redirected output file. The main daemon is started at every system boot by an init script called boiler_script.

This project can also be re-used for starting/stopping any other home appliances via e-mail by doing some small modifications.
I know there is a lot of room for improvement but at this moment the code really works. The GitHub repo associated with this project is https://github.com/spanceac/raspboil.

One comment on “Boiler control via e-mail and image processing

Leave a Reply

Your email address will not be published.


*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>