Setting up the software
Now, it's time to think about the software needed to implement the desired functioning, that is, checking the gas concentrations, logging them, and eventually activating the alarms. We need the following:
- A periodic procedure (
read_sensors.php
) that periodically scans all the sensors and then logs their data into a database. - A periodic procedure (
monitor.php
) that reads the sensors' data, checks them against preset thresholds, and then sets some internal status. - A periodic procedure (
write_actuators.php
) that enables the alarms according to the previously saved status.
The following diagram shows the situation:
The core of the system is the database, where we store both the data we wish to log and the system's status. In this manner, all periodic functions can be realized as separate tasks that talk to each other by using the database itself. Also, we can control all the tasks from the system console by just altering the config
table at runtime.
I used MySQL
to implement the database system, and the preceding configuration can be created by using the my_init.sh
script, where we define the proper tables.
Tip
The MySQL daemon can be installed by using the aptitude
command as follows:
root@beaglebone:~# aptitude install mysql-client mysql-server
Here is a snippet of the script:
CREATE TABLE status ( n VARCHAR(64) NOT NULL, v VARCHAR(64) NOT NULL, PRIMARY KEY (n) ) ENGINE=MEMORY; # Setup default values INSERT INTO status (n, v) VALUES('alarm', 'off'); # # Create the system configuration table # CREATE TABLE config ( n VARCHAR(64) NOT NULL, v VARCHAR(64) NOT NULL, PRIMARY KEY (n) ); # Setup default values INSERT INTO config (n, v) VALUES('sms_delay_s', '300'); INSERT INTO config (n, v) VALUES('mq2_gain', '1'); INSERT INTO config (n, v) VALUES('mq4_gain', '1'); INSERT INTO config (n, v) VALUES('mq5_gain', '1'); INSERT INTO config (n, v) VALUES('mq7_gain', '1'); INSERT INTO config (n, v) VALUES('mq2_off', '0'); INSERT INTO config (n, v) VALUES('mq4_off', '0'); INSERT INTO config (n, v) VALUES('mq5_off', '0'); INSERT INTO config (n, v) VALUES('mq7_off', '0'); INSERT INTO config (n, v) VALUES('mq2_th_ppm', '2000'); INSERT INTO config (n, v) VALUES('mq4_th_ppm', '2000'); INSERT INTO config (n, v) VALUES('mq5_th_ppm', '2000'); INSERT INTO config (n, v) VALUES('mq7_th_ppm', '2000'); # # Create one table per sensor data # CREATE TABLE MQ2_log ( t DATETIME NOT NULL, d float, PRIMARY KEY (t) ); CREATE TABLE MQ4_log ( t DATETIME NOT NULL, d float, PRIMARY KEY (t) ); CREATE TABLE MQ5_log ( t DATETIME NOT NULL, d float, PRIMARY KEY (t) ); CREATE TABLE MQ7_log ( t DATETIME NOT NULL, d float, PRIMARY KEY (t) );
Note
The my_init.sh
script is stored in the chapter_01/my_init.sh
file in the book's example code repository.
The reader should notice that we define a status
table with the MEMORY
storage engine since we don't need to preserve it at reboot but need a good performance in accessing it, while the config
table and the per-sensor logging tables (MQ2_log
, MQ4_log
, MQ5_log
, and MQ7_log
) are defined as normal tables since we need to save these data even during a complete restart. Note that we defined one table per variable in order to easily get access to the logged data; however, nothing changes, even if we decide to keep the logged data into a global logging table.
Note also that during the database initialization, we can define some default settings by simply recording these values by using an INSERT
command. For the status
table, we just need the alarm
variable to be set to off
, while into the config
table, we can set up the minimum delay in seconds (sms_delay_s
) to wait before resending a new SMS alarm, the gain/offset translation couple variables (mq2_gain
/mq2_off
and friends), and the per-sensor threshold variables (mq2_th_ppm
and friends) to be used to activate the alarms.
Managing the ADCs
Now, to get data from the
ADC and save them into the database, we have to write a periodic task. This is quite easy and the following code snippet shows a PHP implementation of the main function of the file read_sensors.php
, which does this:
function daemon_body() { global $loop_time; global $sensors; # The main loop dbg("start main loop (loop_time=${loop_time}s)"); while (sleep($loop_time) == 0) { dbg("loop start"); # Read sensors foreach ($sensors as $s) { $name = $s['name']; $file = $s['file']; $var = $s['var']; $log = $s['log']; # Get the converting values $gain = db_get_config($var . "_gain"); $off = db_get_config($var . "_off"); dbg("gain[$var]=$gain off[$var]=$off"); # Read the ADC file $val = file_get_data($file); if ($val === false) { err("unable to read sensor $name"); continue; } # Do the translation $ppm = $val * $gain + $off; dbg("file=$file val=$val ppm=$ppm"); # Store the result into the status table $ret = db_set_status($var, $ppm); if (!$ret) { err("unable to save $name status db_err=%s", mysql_error()); continue; } # Store the result into the proper log table $ret = db_log_var($log, $ppm); if (!$ret) err("unable to save $name log db_err=%s", mysql_error()); } dbg("loop end"); } }
Note
The complete script is stored in the chapter_01/read_sensors.php
file in the book's example code repository.
The function is quite simple. It starts the main loop to periodically read the ADC data, get the gain and offset conversion values for the current variable needed to convert it into the corresponding ppm number, then alters the current status
variables, and adds a new value into the logging table of the read sensor.
If we execute the script enabling all debugging command-line options, we get:
root@beaglebone:~# ./read_sensors.php -d -f -l -T 5 read_sensors.php[5388]: signals traps installed read_sensors.php[5388]: start main loop (loop_time=5s) read_sensors.php[5388]: loop start read_sensors.php[5388]: gain[mq2]=0.125 off[mq2]=0 read_sensors.php[5388]: file=/sys/devices/ocp.3/helper.12/AIN0 val=810 ppm=101.25 read_sensors.php[5388]: gain[mq4]=1 off[mq4]=0 read_sensors.php[5388]: file=/sys/devices/ocp.3/helper.12/AIN2 val=1477 ppm=1477 read_sensors.php[5388]: gain[mq5]=1 off[mq5]=0 read_sensors.php[5388]: file=/sys/devices/ocp.3/helper.12/AIN6 val=816 ppm=816 read_sensors.php[5388]: gain[mq7]=1 off[mq7]=0 read_sensors.php[5388]: file=/sys/devices/ocp.3/helper.12/AIN4 val=572 ppm=572 read_sensors.php[5388]: loop end read_sensors.php[5388]: loop start read_sensors.php[5388]: gain[mq2]=0.125 off[mq2]=0 read_sensors.php[5388]: file=/sys/devices/ocp.3/helper.12/AIN0 val=677 ppm=84.625 read_sensors.php[5388]: gain[mq4]=1 off[mq4]=0 read_sensors.php[5388]: file=/sys/devices/ocp.3/helper.12/AIN2 val=1456 ppm=1456 read_sensors.php[5388]: gain[mq5]=1 off[mq5]=0 read_sensors.php[5388]: file=/sys/devices/ocp.3/helper.12/AIN6 val=847 ppm=847 read_sensors.php[5388]: gain[mq7]=1 off[mq7]=0 read_sensors.php[5388]: file=/sys/devices/ocp.3/helper.12/AIN4 val=569 ppm=569 read_sensors.php[5388]: loop end ...
Tip
Note that only the first sensor has been (more or less) calibrated!
The process can be stopped as usual with the CTRL + C sequence.
Now, we can read the system status (in this case, the last read sensors datum) by using the my_dump.sh
script, as follows:
root@beaglebone:~# ./my_dump.sh status n v alarm off mq2 84.625 mq4 1456 mq5 815 mq7 569
Note
The my_dump.sh
script is stored in the chapter_01/my_dump.sh
file in the book's example code repository.
The same script can be used to dump a logging table too. For instance, if we wish to see the MQ-2 logged data, we can use the following command:
root@beaglebone:~# ./my_dump.sh mq2_log t v 2015-05-15 17:39:36 101.25 2015-05-15 17:39:41 84.625 2015-05-15 17:39:46 84.625
Managing the actuators
When a sensor detects a dangerous gas concentration, the alarm
status variable is set to on state. Therefore, when this happens, we have to turn both the LED and the buzzer on, and we must send an SMS message to the user's predefined number.
In order to do these actions, we have to properly set up the GPIO lines that manage the LED and the buzzer as shown previously, and then we have to talk with the GSM module through the serial port to send the SMS message. To do this last step, we have to to install the gsm-utils
package where we can find the gsmsendsms
command, which is used to actually send the SMS. In order to install the package, we use the following command:
root@beaglebone:~# aptitude install gsm-utils
Then, after placing a functioning SIM into the module, we can verify to be able to talk with the GSM module with the gsmctl
command, as shown in the following code:
root@beaglebone:~# gsmctl -d /dev/ttyO1 me <ME0> Manufacturer: Telit <ME1> Model: GL865-QUAD <ME2> Revision: 10.00.144 <ME3> Serial Number: 356308042878501
Then, we can verify the current PIN status by using the following command:
root@beaglebone:~# gsmctl -d /dev/ttyO1 pin <PIN0> READY
The preceding message shows us that the GSM module is correctly configured and the SIM in it is ready to operate; however, the SIM must be enabled by inserting the proper PIN number if we get the following message:
gsmsendsms[ERROR]: ME/TA error 'SIM PIN required' (code 311)
In this case, we must use the following command:
root@beaglebone:~# gsmctl -d /dev/ttyO1 -I "+cpin=NNNN"
In the preceding command, NNNN
is the PIN number of your SIM. If the command hangs with no output at all, it means that the connection is wrong.
Now that we've checked the connection and the SIM is enabled, we can start to send SMS messages by using the following command:
root@beaglebone:~# gsmsendsms -d /dev/ttyO1 "+NNNNNNNNNNNN" 'Hello world!'
In the preceding command, the NNNNNNNNNNNN
string is the number where the SMS must be sent.
Tip
If the module answers is as follows it means that SMS Service Centre Address (SCA); which is the phone number of the centre that is accepting SMS for delivery is not set correctly in your phone:
gsmsendsms[ERROR]: ME/TA error 'Unidentified subscriber' (code 28)
In this case, you should ask to your GSM operator and then try the following command:
root@beaglebone:~# gsmctl -o setsca "+SSSSSSSSSSSS"
In the preceding command, the SSSSSSSSSSSS
string is the number of your centre.
Okay, now we have all the needed information to control our actuators. A possible implementation of main function of the managing task is as follows:
function daemon_body() { global $loop_time; global $actuators; $sms_delay = db_get_config("sms_delay_s"); $old_alarm = 0; $sms_time = strtotime("1970"); # The main loop dbg("start main loop (loop_time=${loop_time}s)"); while (sleep($loop_time) == 0) { dbg("loop start"); # Get the "alarm" status and set all alarms properly $alarm = db_get_status("alarm"); foreach ($actuators as $a) { $name = $a['name']; $file = $a['file']; dbg("file=$file alarm=$alarm"); $ret = gpio_set($file, $alarm); if (!$ret) err("unable to write actuator $name"); } # Send the SMS only during off->on transition if ($alarm == "on" && $old_alarm == "off" && strtotime("-$sms_time seconds") > $sms_delay) { do_send_sms(); $sms_time = strtotime("now"); } $old_alarm = $alarm; dbg("loop end"); } }
Note
The complete script is stored in the chapter_01/write_actuators.php
file in the book's example code repository.
Again, the function is really simple—we simply have to read the current alarm
variable status from the database and then set up the actuators according to it. Note that a special job must be done for the SMS management; in fact, the system must send one SMS at time and only during the off-to-on transition and not before sms_delay
seconds. To do the trick, we use the old_alarm
and sms_time
variables to save the last loop status.
To test the code, we can control the alarm
variable by using the my_set.sh
command as follows:
root@beaglebone:~# ./my_set.sh status alarm on root@beaglebone:~# ./my_set.sh status alarm off
Note
The script is stored in the chapter_01/my_set.sh
file in the book's example code repository.
So, let's start the script with the command:
root@beaglebone:~# ./write_actuators.php -d -f -l -T 5 write_actuators.php[5474]: signals traps installed write_actuators.php[5474]: start main loop (loop_time=5s) write_actuators.php[5474]: loop start write_actuators.php[5474]: file=/sys/class/gpio/gpio68 alarm=off write_actuators.php[5474]: file=/sys/class/gpio/gpio69 alarm=off write_actuators.php[5474]: loop end write_actuators.php[5474]: loop start write_actuators.php[5474]: file=/sys/class/gpio/gpio68 alarm=off write_actuators.php[5474]: file=/sys/class/gpio/gpio69 alarm=off write_actuators.php[5474]: loop end
On another terminal, we can change the alarm
variable, as already stated, by using the following command:
root@beaglebone:~# ./my_set.sh status alarm on
After this we notice that the script does its job:
write_actuators.php[5474]: loop start write_actuators.php[5474]: file=/sys/class/gpio/gpio68 alarm=on write_actuators.php[5474]: file=/sys/class/gpio/gpio69 alarm=on write_actuators.php[5474]: send SMS... write_actuators.php[5474]: loop end
Regarding how to send an SMS message in PHP, I simply used the following code:
function do_send_sms() { dbg("send SMS..."); system('gsmsendsms -d /dev/ttyO1 "' . PHONE_NUM . '" "GAS alarm!"'); }
Basically, here we use the system()
function to call the gsmsendsms
command.
Note
You may note that gsmsendsms
takes a while to send the SMS. It's normal.
Controlling the environment
Now, we only need the glue between the sensors and actuators managing tasks, that is, a periodic function that according to the user inputs periodically checks whether the alarms must be activated according to the information read, or not.
A possible implementation of the main function of the monitor.php
script is as follows:
function daemon_body() { global $loop_time; global $actuators; # The main loop dbg("start main loop (loop_time=${loop_time}s)"); while (sleep($loop_time) == 0) { dbg("loop start"); # Get the gas concentrations and set the "alarm" variable $mq2 = db_get_status("mq2"); $mq2_th_ppm = db_get_config("mq2_th_ppm"); dbg("mq2/mq2_th_ppm=$mq2/$mq2_th_ppm"); $mq4 = db_get_status("mq4"); $mq4_th_ppm = db_get_config("mq4_th_ppm"); dbg("mq4/mq4_th_ppm=$mq4/$mq4_th_ppm"); $mq5 = db_get_status("mq5"); $mq5_th_ppm = db_get_config("mq5_th_ppm"); dbg("mq5/mq5_th_ppm=$mq5/$mq5_th_ppm"); $mq7 = db_get_status("mq7"); $mq7_th_ppm = db_get_config("mq7_th_ppm"); dbg("mq7/mq7_th_ppm=$mq7/$mq7_th_ppm"); $alarm = $mq2 >= $mq2_th_ppm || $mq2 >= $mq2_th_ppm || $mq2 >= $mq2_th_ppm || $mq2 >= $mq2_th_ppm ? 1 : 0; db_set_status("alarm", $alarm); dbg("alarm=$alarm"); dbg("loop end"); } }
Note
The complete script is stored in the chapter_01/monitor.php
file in the book's example code repository.
The function starts the main
loop where, after getting the sensors' thresholds, it simply gets the last sensor's values and sets up the alarm
variable accordingly.
Again, we can change the gas concentration thresholds by using the my_set.sh
command as follows:
root@beaglebone:~# ./my_set.sh config mq2_th_ppm 5000
We can test the script by executing it in the same manner as the previous two, as follows:
root@beaglebone:~# ./monitor.php -d -f -l -T 5 monitor.php[5819]: signals traps installed monitor.php[5819]: start main loop (loop_time=5s) monitor.php[5819]: loop start monitor.php[5819]: mq2/mq2_th_ppm=84.625/5000 monitor.php[5819]: mq4/mq4_th_ppm=1456/2000 monitor.php[5819]: mq5/mq5_th_ppm=815/2000 monitor.php[5819]: mq7/mq7_th_ppm=569/2000 monitor.php[5819]: alarm=0 monitor.php[5819]: loop end monitor.php[5819]: loop start monitor.php[5819]: mq2/mq2_th_ppm=84.625/5000 monitor.php[5819]: mq4/mq4_th_ppm=1456/2000 monitor.php[5819]: mq5/mq5_th_ppm=815/2000 monitor.php[5819]: mq7/mq7_th_ppm=569/2000 monitor.php[5819]: alarm=0 monitor.php[5819]: loop end ...
To stop the test, just use the CTRL + C sequence. You should get an output as follows:
^Cmonitor.php[5819]: signal trapped!