IoT watering
This project implements a remote-controlled distributed watering system which:
- waters plants periodically and automatically
- collects data from the environment: moisture, humidity and temperature
- sends a daily report about the overall status
- lets users access data at any time
The acquisition of environmental information may let users carry on post analysis.
For instance, identifying the best location to place succulent plants to have a dry soil.
The wireless power supplier is mainly used to simplify synchronization among components and reduce maintenance (due to battery replacement).
This non-conventional use of this technology has been investigated to provide additional user cases instead of the classical charging station.
The overall system includes a data collector (which leads operations and aggregates all data) and a set of distributed units (see Figure 1).
Here after, they are named supervisor and watering units respectively.
The wireless power supplier is controlled by the supervisor to activate the watering units when needed.
Moreover, the supervisor sends data to the cloud to store all the sampled information (making it available to the users at any time).
The supervisor and watering units talk each other through a wireless connection using ssWi as protocol.
This was developed for a previous project so it was already available and tested.
This has been the motivation for other design choices, described later, to reduce the overall cost of this project.
Supervisor
The supervisor, shown in Figure 2, is composed of the following
components:
- a mbed board: this leads the watering and data acquisition process
- a PC: this collects data periodically from the mbed board, updates an internal database and sends data to the cloud
The PC and the mbed board are connected using a USB cable and
communicates using the mass memory available on the latter.
Note that, a Raspberry board would be able to do everything while
requiring less space.
However, the mentioned hardware was already available without need to buy anything else.
Let us start from the mbed board. It is in charge on periodically waking up the watering units, leading the sampling step and, eventually, making them water the flowers.
This task is executed every interval Tsamp whose duration (in
minutes) can be configured by the user.
The main components are the following:
- board
- mbed NXP LPC1768 – ARM Cortex M-3 MCU
- powered by USB from the PC
- wireless power supplier
- enabled by digital out on pin 17
- this module is powered by an additional USB link to the PC
- transceiver
- Xbee 802.15.4 using ssWi protocol
The circuit of the board is reported in Figure 3.
As already said, this board is powered by USB (plugged directly to the board) and the additional power is provided by another USB port.
The (single-thread) software running on the board performes the following steps:
1.read from file the system configuration
1.1.execution interval Tsamp (in minutes)
1.2.number of watering units
1.3.for each watering unit:
1.3.1.network identifier
1.3.2.watering period Twater (number of Tsamp between two consecutive waterings)
1.3.3.watering duration
2.start network
3.while true
3.1.wait Tsamp (in minutes)
3.2.lock busy file (the PC cannot access memory on the board)
3.3.read counter from file
3.4.power on the watering units
3.5.wait 20 seconds
3.6.sampling…
3.6.1.send START_SAMPLING command
3.6.2.wait 60 seconds
3.6.3.send STOP_SAMPLING command
3.6.4.wait 2 seconds
3.6.5.for each watering unit
3.6.5.1.read feedback
3.6.5.2.if feedback is OK, read temperature, humidity and moisture
3.6.5.3.store data to file
3.7.watering…
3.7.1.for each watering unit i
3.7.1.1.if the number of interval since last watering is lower than configured, skip this node
3.7.1.2.send Twater of the i-th unit
3.7.1.3.wait 5 seconds
3.7.1.4.send the watering command for the i-th unit
3.7.1.5.wait Twater plus 5 more seconds
3.7.1.6.read feedback
3.7.1.7.store data to file
3.8.increase counter
3.9.serialize counter
3.10.send SHUTDOWN command
3.11.wait 5 seconds
3.12.power off the watering units
3.13.release busy file (now the PC can read the files)
The file containing the sensor values has a line for each sample.
A line contains the internal counter value, the watering unit network
ID, the correctness of the value (1 if data is correct, 0 means that an error happened during the communication), temperature, humidity and moisture.
A line-based structure is also used for the watering file.
An entry contains the counter value, the watering unit network ID, the result of the operation (1 all correct, 0 for errors) and the watering seconds.
Data within files is separated by a single space (no special characters).
The software is available in the supervisor folder and it depends on mbed drivers, mbed RTOS and the ssWi library for the communication protocol.
The software running on the PC is a script (update.sh) that every
day collects the acquired data from the board and process this
information.
The script running on the PC executes the following steps:
1.while true
1.1.wait 1 day
1.2.wait until the board is not busy
1.3.wait 5 seconds
1.4.copy the sensor and watering files locally
1.5.remove the counter file
1.6.process data…
1.7.for each line in the sensor file
1.7.1.parse data
1.7.2.convert relative time to absolute
1.7.3.save data in the database
1.7.4.send data to the cloud
1.8.for each line in the watering file
1.8.1.parse data
1.8.2.convert relative time to absolute
1.8.3.save data in the database
1.9.generate report
1.10.send emails
1.11.update time offset
Step 1.6 is executed by running the program reader.
It takes as input the supervisor configuration file, the database, the
timestamp (in minutes) of the last iteration (one day ago) and the files copied from the board.
The database (sqlite3) is composed of two tables as detailed in Figure 4 and it contains all information available in the files provided by the board.
The only different is the time value as it is a timestamp (in minutes)
and not a relative value.
It is computed by adding to the read offset the read index times the
interval duration.
This application is also in charge of uploading data to the cloud.
The program shower generates the daily report to be sent by
email.
The inputs are the database file, the timestamp of last update and the list of emails.
The generated output is a gnuplot script that, when executed,
generates the temperature/humidity/moisture graphs of the last day.
Then, the plots are collected in a single pdf file.
In addition, a file with the email body is generated, containing the
number of sensor failures and if there is any water tank to refill. Then, the file containing the list of email is opened and, for each line, mutt is invoked to send an email with the text body and the
report as attachment.
More generally, the software running on the PC is in the folder pc
and it contains the following tree:
- ./config.sh: script that lets users create the supervisor
configuration file - ./update.sh: script that periodically queries the board to copy the
files with data, update the database, send data to the cloud and send the daily report - ./makefile: main makefile to compile the code
- ./src: folder containing the source files
- ./config
- cfg.txt: supervisor configuration file – do not edit manually but use ./config.sh
- cloud.txt: file containing the API key to send data to the cloud
- emails.txt: list of emails to send the daily report to
- offset.txt: timestamp (in minutes) of the last report – do not edit manually
- ./bin: do not use the following binary files directly
- configuration: binary that generates the supervisor config file
- reader: binary that sends data from the files on the board to the database and cloud
- shower: binary that generates the gnuplot script
- ./data: folder containing temporary files – do not edit manually
- ./main.db: database file using sqlite3 – remove this file if you
want to restart data history
This software depends on the following additional software: gnuplot, mutt and libcurl.
Figure 5 and Figure 6 show the whole supervisor system, including the PC and the mbed board.
Watering units
The main task of the watering units is to wait supervisor commands and then: reading from the sensors and watering the plants.
The structure of the watering unit is reported in Figure 7, while Figure 8 shows circuit. The list of component is the following:
- main board
- mbed NXP LPC1768 – ARM Cortex M-3 MCU
- powered by the wireless power receiver
- sensors
- humidity and temperature: HDT11 on digital pin 17
- humidity is in [0.0, 1.0] representing [0, 100]% humidity
level - temperature is in Celcius degree
- humidity is in [0.0, 1.0] representing [0, 100]% humidity
- moisture sensor: analog input from pin 18 (value in [0.0, 1.0]
representing [0, 100]%) - water-level sensor: digital input on pin14 (0: normal level,
1: low level)
- humidity and temperature: HDT11 on digital pin 17
- actuator
- pump: brushless DC motor – 240 liters per hour
- controlled by digital out pin 25, connected to SRD-05VCD relay
- powered by a power converted (230V to 12V)
- transceiver
- Xbee 802.15.4 using ssWi protocol
When the unit is waken up, the systems run the following three threads:
1.main thread (normal priority)
1.1.wait 10 seconds to have a stable power signal
1.2.read the network address from file
1.3.configure the communication transceiver and network
1.4.create and run the sensing and watering (aperiodic) threads
1.5.if shutdown command is received:
1.5.1.terminate all threads
1.5.2.clean communication ports
1.5.3.wait until power off
2.Sensing thread (high priority)
2.1.wait the start command
2.2.wait 2 seconds to stabilize inputs
2.3.read from the sensors every 2.5 seconds for 10 times
2.3.1.if this lasts more than 60 seconds, send SAMPLING_OUT and exit
2.4.compute the average for each input
2.5.wait the stop command
2.6.send average values
2.7.if any error occurred, send SAMPLING_KO, SAMPLING_OK
otherwise
3.watering thread (highest priority)
3.1.close pump
3.2.wait until the supervisor send the watering command to the unit
3.3.read the watering interval (in seconds) VAR_TOTAL_SECONDS
3.4.clear local variable VAR_ACTUAL_SECONDS
3.5.iterate while VAR_ACTUAL_SECONDS is less than VAR_TOTAL_SECONDS
3.5.1.read the water-level sensor
3.5.2.if the level is lower than the security threshold
3.5.2.1.close pump
3.5.2.2.send WATERING_LOW
3.5.2.3.exit
3.5.3.set t as VAR_TOTAL_SECONDS minus VAR_ACTUAL_SECONDS
3.5.4.if t is greater than 1, assign 1 to t
3.5.5.open pump
3.5.6.wait for t
3.5.7.add t to VAR_ACTUAL_SECONDS
3.6.close pump
3.7.send WATERING_OK
The configuration file is cfg.txt and it contains an integer
representing the node address in the network.
The code is available in the watering unit folder and it depends on mbed drivers, mbed RTOS, ssWi and DHT11 driver.
The measured power consumption of a watering unit is in the range [130, 190]mA.
The deployed system is shown in Figure 9 and Figure 10.
Cloud
The chosen cloud service is ThingSpeaks as it is powerful and for
free.
The channel IoT_watering is created to contains the following fields:
1.node
2.humidity
3.temperature
4.moisture
5.valid
The fields contain the same information stored in the data files copied from the supervisor board and read by the watering units.
Three scripts contained in the <i>cloud</i> folder are provided to plot the temperature, humidity and moisture of the last seven days,
Note that, in order to generate a meaningful graph, invalid samples are stored but discarded from the plotting.
Communication protocol
The chosen communication protocol is https://developer.mbed.org/users/mariob/code/ssWi/
(Figure 11). It only depends on the library MODSERIAL.
The concept of Port is introduced as a virtual memory area shared among all the nodes of the wireless network: each node can subscribe to a port by creating a socket which exploits broadcast messages to synchronize the port value.
Ports are (signed) 4-byte integers and are bi-directional.
Note that the read and write operations have a different buffer: if a
node writes on a port, only other nodes can read it – a node cannot read from a port what it has written.
Ports are identified by a numeric identifier between 0 and 255.
Further explanations are out of the scope of this report.
The supervisor has a COMMAND port to write commands that are read from all other watering units. This port has address equal to 0. Each watering unit has 5 ports:
- feedback: the watering unit writes the status of the last
operation (e.g.: low-water level WATERING_LOW, error while sampling SAMPLING_KO) - temperature: the watering unit writes the sampled temperature
- humidity: the watering unit writes the sampled humidity
- moisture:the watering unit writes the sampled moisture level
- water: the watering unit reads the seconds to water
The addresses of this ports are computed as the board network identifier times 5 plus the port entry number.
The values sent through the humidity, temperature and moisture ports are multiplied by 10 (as the stored value is an integer).
The possible commands on the COMMAND port sent from the supervisor are:
- 0x0000: no value – default state
- 0xAAAA: start sampling – START_SAMPLING
- 0x1111: stop sampling – STOP_SAMPLING
- 0xFF00 + id: start watering for watering unit with network
identifier equal to id - 0x00FF: shutdown
The values sent from the watering units to the supervisor on the
feedback port are:
- 0xBBBB: correct sampling – SAMPLING_OK
- 0x2222: sampling not correct – SAMPLING_KO
- 0x5555: timeout while sampling – SAMPLING_OUT
- 0x3333: correct watering – WATERING_OK
- 0xCCCC: watering not correct – WATERING_KO
- 0x7777: low-level water tank – WATERING_LOW
User interface
Users can check that a communication between the supervisor and watering units is in progress by looking at the fourth led on the supervisor board.
A script is provide to generate and copy the configuration file for the supervisor board. To run the script, users must type:
$ ./config.sh /media/MBED
The script will ask information about how long an interval lasts, the
number of watering units and their characteristics.
Then, the file is copied automatically in the config directory and on
the board once it is not busy.
The first argument is the folder where the memory of the board is mounted on the PC.
In ./config/cloud.txt, users specifies to API key to be used while
updating data on the cloud.
The file format is simple as the key is the first non-empty line that
does not start with #.
In ./config/emails.txt, users can specify the list of email
addresses to notify periodically the state of the system.
To modifiy the file, edit it manually by appending the email addresses without spaces or empty lines. For example:
$ cat ./config/emails.txt
person1@gmail.com
person2@gmail.com
person3@gmail.com
The generated email contains the following text as message:
+++++++++++++++++++++++++++++++++
Update watering system
Last update : 18/02/2017 00:20:00
Current time: 18/02/2017 00:21:00
Sensing fail report
Unit Fails Total Ratio
0 4 12 33.33%
Low-water report
Watering unit 0: OK
+++++++++++++++++++++++++++++++++
The text explains when the report was generated the last time and the actual time.
Moreover, the message reports the total number of sampling errors (since the last email) and if any water tank is empty.
The attachment contains three plots of the temperature, humidity and moisture (see Figure 12).
Emails are sent using mutt whose configuration is in
~/.muttrc and it looks like:
set from = “username@gmail.com”
set realname = “IoT Watering”
set postponed =”+[Gmail]/Drafts”
set header_cache =~/.mutt/cache/headers
set message_cachedir =~/.mutt/cache/bodies
set smtp_url = “smtp://username@smtp.gmail.com:587/”
set smtp_pass = “hoyrlibtdzptpxnh”
set imap_keepalive = 900
Note that, in case you are using gmail, you should activate the access from non-protected applications.
On the cloud, the following graphs are generated, reporting the
sensed temperature, humidity and moisture level for each watering
unit in the last seven days.
Some examples are reported in Figure 13, Figure 14 and Figure 15.
The moisture level increases as the unit has watered the flower.
Concerning the watering units, the configuration file is created manually and it contains only an integer representing the unit network identifier.
Due to space reasons, only the main source code of the supervisor, watering unit and cloud is attached. The other code is fully available online at this https://github.com/mbambagini/IoT_watering (tag 1.0).
Looking at the submission date, you can check that the code was submitted by the 28th February.
Supervisor board
#include “mbed.h”
#include “rtos.h”
#include “config.h”
#include “xbee.hpp”
#include “ssWiSocket.hpp”
#ifndef DEBUG
#define printf(fmt,…)
#endif
// global configuration
global_confg_t global_config;
// ssWi sockets
ssWiSocket* socket_command;
ssWiSocket* socket_moisture[MAX_NUM_NODES];
ssWiSocket* socket_temperature[MAX_NUM_NODES];
ssWiSocket* socket_humidity[MAX_NUM_NODES];
ssWiSocket* socket_response[MAX_NUM_NODES];
ssWiSocket* socket_water[MAX_NUM_NODES];
LocalFileSystem local(“local”);
// read counter value from file
bool read_counter();
// read board configuration from file
bool read_configuration();
// return true if a message has been received, false otherwise
bool read_from_port(ssWiSocket *socket, int *value);
// blocking read – timeout after 10 seconds
bool read_timeout(ssWiSocket *socket, int *value);
void do_sampling();
void do_watering();
int main() {
DigitalOut power_device(POWER_EN_PIN);
power_device = POWER_OFF;
// supervisor configuration
printf(“SUPERVISOR – configrn”);
// read configuration
if (!read_configuration())
error(“Impossible to read configuration”);
// network configuration
XBeeModule xbee(XBEE_PIN_TX, XBEE_PIN_RX, PAN_ID, CHANNEL_ID);
xbee.init(XBEE_TX_PER_SECOND, XBEE_RX_PER_SECOND);
socket_command = ssWiSocket::createSocket(PORT_COMMANDS);
for (int i = 0; i < global_config.num_units; i++) {
socket_moisture[i] = ssWiSocket::createSocket(
global_config.nodes[i].address * 5 + PORT_MOISTURE_OFFSET);
socket_temperature[i] = ssWiSocket::createSocket(
global_config.nodes[i].address * 5 + PORT_TEMPERATURE_OFFSET);
socket_humidity[i] = ssWiSocket::createSocket(
global_config.nodes[i].address * 5 + PORT_HUMIDITY_OFFSET);
socket_response[i] = ssWiSocket::createSocket(
global_config.nodes[i].address * 5 + PORT_RESPONSE_OFFSET);
socket_water[i] = ssWiSocket::createSocket(
global_config.nodes[i].address * 5 + PORT_WATER_OFFSET);
}
// start
printf(“SUPERVISOR – startrn”);
while(1) {
int minute_counters = 0;
printf(“SUPERVISOR – waitingrn”);
do {
// wait 1 minute
Thread::wait(INTERVAL_60_SECONDS * 1000);
minute_counters++;
} while (minute_counters < global_config.wait_minutes);
printf(“SUPERVISOR – activern”);
// mark as busy
DigitalOut led_busy(LED4);
led_busy = 1;
FILE* fp_busy = fopen(FILE_BSY, “w”);
// power watering units
power_device = POWER_ON;
wait(INTERVAL_POWER_START);
read_counter();
// sample and water
printf(“SUPERVISOR – samplingrn”);
do_sampling();
printf(“SUPERVISOR – wateringrn”);
do_watering();
// increment counter
global_config.count++;
FILE* fp = fopen(FILE_CNT, “w”);
fprintf(fp, “%dn”, global_config.count);
fclose(fp);
// send shutdown
printf(“SUPERVISOR – shutdownrn”);
wait(INTERVAL_SYNC);
socket_command->write(COMM_SHUTDOWN);
wait(INTERVAL_SYNC * 2);
// power off devices
power_device = POWER_OFF;
// mark as not busy
led_busy = 0;
fclose(fp_busy);
}
}
void do_sampling () {
FILE* fp = fopen(FILE_SNS, “a”);
socket_command->write(COMM_START_SAMPLING);
wait(INTERVAL_SAMPLING);
socket_command->write(COMM_STOP_SAMPLING);
wait(INTERVAL_SYNC);
for (int i = 0; i < global_config.num_units; i++) {
int temp = 0, humi = 0, mois = 0, sampling_feedback = COMM_NO_VALUE;
int code;
if (!read_timeout(socket_response[i], &sampling_feedback))
code = 1;
else {
switch (sampling_feedback) {
case COMM_SAMPLING_OK:
code = (read_timeout(socket_temperature[i], &temp) &&
read_timeout(socket_humidity[i], &humi) &&
read_timeout(socket_moisture[i], &mois)) ? 0 : 2;
break;
case COMM_SAMPLING_KO: code = 3; break;
case COMM_SAMPLING_OUT: code = 4; break;
default: code = 5;
}
}
fprintf(fp, “%d %d %d %4.2f %4.2f %4.2fn”, global_config.count,
global_config.nodes[i].address,
code,
(double)humi/10.0,
(double)temp/10.0,
(double)mois/10.0);
}
fclose(fp);
}
void do_watering () {
FILE* fp = fopen(FILE_WTR, “a”);
for (int i = 0; i < global_config.num_units; i++) {
if (global_config.count % global_config.nodes[i].watering_wait)
continue;
// write watering time in seconds
socket_water[i]->write(global_config.nodes[i].watering_seconds);
wait(INTERVAL_SYNC);
// send watering command
socket_command->write(COMM_START_WATERING_OFFSET +
global_config.nodes[i].address);
wait(global_config.nodes[i].watering_seconds + INTERVAL_SYNC +
INTERVAL_SYNC);
int watering_response = 0;
int code = 4;
if (read_timeout(socket_response[i], &watering_response)) {
switch(watering_response) {
case COMM_WATERING_KO: code = 1; break;
case COMM_WATERING_OK: code = 0; break;
case COMM_LOW_WATER_LEVEL: code = 2; break;
default: code = 3;
}
}
fprintf(fp, “%d %d %d %dn”, global_config.count,
global_config.nodes[i].address,
code,
global_config.nodes[i].watering_seconds);
}
fclose(fp);
}
bool read_from_port(ssWiSocket *socket, int *value) {
int prev = *value;
*value = socket->read();
return (*value) != prev;
}
bool read_timeout(ssWiSocket *socket, int *value) {
Timer t;
t.start();
bool ret;
int v = COMM_NO_VALUE;
double start = t.read();
do {
if ((t.read() – start) > TIMEOUT_READ)
return false;
ret = read_from_port(socket, &v);
} while(!ret);
t.stop();
*value = v;
return true;
}
bool read_configuration() {
int state = 0;
int n_unit = 0;
FILE *fp = fopen(FILE_CFG, “r”);
if(fp == NULL)
return false;
char line[250];
while(fgets(line, sizeof(line), fp)) {
if (line[0] == ‘#’)
continue;
switch(state) {
case 0: //read interval length
sscanf(line, “%dr”, &global_config.wait_minutes);
state = 1;
break;
case 1: //read number of watering units
sscanf(line, “%dr”, &global_config.num_units);
state = 2;
break;
case 2: //read number of watering units
sscanf(line, “%d %d %dr”,
&global_config.nodes[n_unit].address,
&global_config.nodes[n_unit].watering_wait,
&global_config.nodes[n_unit].watering_seconds);
n_unit++;
if (n_unit >= global_config.num_units || n_unit >=MAX_NUM_NODES)
state = 3;
break;
}
}
fclose(fp);
return true;
}
bool read_counter () {
FILE *fp = fopen(FILE_CNT, “r”);
if(fp == NULL) {
fp = fopen(FILE_CNT, “w”);
if(fp == NULL)
return false;
global_config.count = 0;
fprintf(fp, “0n”);
} else
fscanf(fp, “%dn”, &global_config.count);
fclose(fp);
return true;
}
Supervisor PC (update script)
#! /bin/sh
# check parameters
if [ $# -ne 1 ];then
echo “Error: inputs not valid”
echo “usage: ./update.sh <supervisor board directory>”
exit
fi
echo “######################################”
echo “#### READER SCRIPT ####”
echo “######################################”
echo “##### board: $1”
# infinitive loop
until [ 1 -eq 0 ];do
# update time offset
timestamp=`date +%s`
timestamp=$(($timestamp / 60))
echo $timestamp > ./config/offset.txt
# sleep until next update
echo “#### WAIT 1 DAY ####”
# 60 seconds x 60 minutes x 24 hours = 86400 seconds
sleep 86400
# wait until the board has provided data
echo “#### WAIT BOARD ####”
# wait until the board is idle
busy=1
until [ $busy -eq 0 ];do
ls $1 > /dev/null 2> /dev/null
busy=$?
done
# wait 5 additional seconds
sleep 5
# copy and remove files from the board
echo “#### COPY FILES ####”
if [ -f $1/SNS.TXT ]; then
mv $1/SNS.TXT ./data/sns.txt
else
touch ./data/sns.txt
fi
if [ -f $1/WTR.TXT ]; then
mv $1/WTR.TXT ./data/wtr.txt
else
touch ./data/wtr.txt
fi
rm $1/CNT.TXT
# process data: save into database and send to the cloud
echo “#### PROCESS AND SAVE DATA ####”
./bin/reader main.db ./data/sns.txt ./data/wtr.txt
./config/cfg.txt ./config/offset.txt
./config/cloud.txt > /dev/null 2> /dev/null
# remove temporary files
rm -f ./data/sns.txt ./data/wtr.txt
# write and send email
./bin/shower main.db ./config/cfg.txt ./config/offset.txt
./data/script.gpl ./data/data.res
./data/email.txt > /dev/null 2> /dev/null
if [ $? -eq 0 ];then
gnuplot ./data/script.gpl
epstopdf humi.eps
epstopdf temp.eps
epstopdf mois.eps
pdfjoin humi.pdf temp.pdf mois.pdf
–outfile report.pdf > /dev/null 2> /dev/null
rm -f humi.eps temp.eps mois.eps humi.pdf temp.pdf mois.pdf
cat ./config/emails.txt | while read line
do
mutt -s “Daily report” $line -a report.pdf < ./data/email.txt
done
rm -f ./data/email.txt
rm -f ./data/script.gpl
rm -f ./data/data.res
rm -f report.pdf
fi
done
Watering unit
#include “mbed.h”
#include “rtos.h”
#include “config.hpp”
#include “DHT11.h”
#include “xbee.hpp”
#include “ssWiSocket.hpp”
#ifndef DEBUG
#define printf(fmt,…)
#endif
LocalFileSystem local(“local”);
// global configuration
watering_unit_config_t global_config;
// ssWi sockets
ssWiSocket* socket_moisture = NULL;
ssWiSocket* socket_temperature = NULL;
ssWiSocket* socket_humidity = NULL;
ssWiSocket* socket_response = NULL;
ssWiSocket* socket_water = NULL;
ssWiSocket* socket_command = NULL;
// thread functions
void thread_sensing_fcn();
void thread_watering_fcn();
// return true if a new message has been received, false otherwise
bool read_from_port(ssWiSocket *socket, int *value);
int main() {
Timer t;
t.start();
printf(“MAIN – configurationrn”);
// read configuration
FILE *fp = fopen(“/local/cfg.txt”, “r”);
if (fp == NULL)
error(“missing configuration filern”);
fscanf(fp, “%d”, &global_config.address);
fclose(fp);
global_config.moisture_port = GET_MOISTURE_PORT(global_config);
global_config.temperature_port = GET_TEMPERATURE_PORT(global_config);
global_config.humidity_port = GET_HUMIDITY_PORT(global_config);
global_config.response_port = GET_RESPONSE_PORT(global_config);
global_config.water_port = GET_WATER_PORT(global_config);
// configure network
XBeeModule xbee(XBEE_PIN_TX, XBEE_PIN_RX, PAN_ID, CHANNEL_ID);
xbee.init(XBEE_TX_PER_SECOND, XBEE_RX_PER_SECOND);
socket_moisture = ssWiSocket::createSocket(global_config.moisture_port);
socket_temperature =
ssWiSocket::createSocket(global_config.temperature_port);
socket_humidity = ssWiSocket::createSocket(global_config.humidity_port);
socket_response = ssWiSocket::createSocket(global_config.response_port);
socket_water = ssWiSocket::createSocket(global_config.water_port);
socket_command = ssWiSocket::createSocket(PORT_COMMANDS);
// threads
Thread thread_sensing(osPriorityAboveNormal);
thread_sensing.start(&thread_sensing_fcn);
Thread thread_watering(osPriorityHigh);
thread_watering.start(&thread_watering_fcn);
// wait 10 seconds since the board started
while(t.read() < TIMEOUT_CONF);
t.stop();
printf(“MAIN – board readyrn”);
// handle messages
bool recv_start_sampling = false;
bool recv_stop_sampling = false;
bool recv_watering = false;
int msg = COMM_NO_VALUE;
while(true) {
if (!read_from_port(socket_command, &msg))
continue;
if (!recv_start_sampling && msg == COMM_START_SAMPLING) {
recv_start_sampling = true;
printf(“MAIN – sampling start commandrn”);
thread_sensing.signal_set(SIGNAL_START_ARRIVED);
continue;
}
if (!recv_stop_sampling && msg == COMM_STOP_SAMPLING) {
recv_stop_sampling = true;
printf(“MAIN – sampling stop commandrn”);
thread_sensing.signal_set(SIGNAL_STOP_ARRIVED);
continue;
}
if (!recv_watering && msg == GET_WATERING_COMMAND(global_config)) {
recv_watering = true;
printf(“MAIN – watering start commandrn”);
thread_watering.signal_set(SIGNAL_WATERING_ARRIVED);
continue;
}
if (msg == COMM_SHUTDOWN) {
printf(“MAIN – shutdown commandrn”);
recv_start_sampling = false;
recv_stop_sampling = false;
recv_watering = false;
socket_moisture->write(COMM_NO_VALUE);
socket_temperature->write(COMM_NO_VALUE);
socket_humidity->write(COMM_NO_VALUE);
socket_response->write(COMM_NO_VALUE);
continue;
}
}
printf(“MAIN – join threads and endrn”);
thread_sensing.join();
thread_watering.join();
return 0;
}
void thread_sensing_fcn () {
AnalogIn sensor_moisture(HW_PIN_MOISTURE);
while (1) {
int msg = COMM_SAMPLING_OK;
DHT11 sensor_dht(HW_PIN_TEMPERATURE);
printf(“SAMP – waiting…rn”);
// wait supervisor message
Thread::signal_wait(SIGNAL_START_ARRIVED);
printf(“SAMP – startrn”);
DigitalOut l(LED3);
l = 1;
Timer t;
t.start();
// wait two seconds for HDT sensor
wait(2.0);
// sample values
double sens_mois = 0.0;
double sens_temp = 0.0;
double sens_humi = 0.0;
for (int i = 0; i < NUM_SAMPLES; i++) {
if (sensor_dht.readData() != 0) {
printf(“SAMP – error %drn”, error);
msg = COMM_SAMPLING_KO;
break;
}
sens_temp += sensor_dht.readTemperature();
sens_humi += sensor_dht.readHumidity();
sens_mois += sensor_moisture.read() * 100;
if (t.read() > TIMEOUT_SAMPLING) {
// timeout expired, exit
msg = COMM_SAMPLING_OUT;
break;
}
Thread::wait(INTERVAL_SAMPLING);
}
t.stop();
// compute averages
sens_mois = (sens_mois / NUM_SAMPLES) * 10.0;
sens_temp = (sens_temp / NUM_SAMPLES) * 10.0;
sens_humi = (sens_humi / NUM_SAMPLES) * 10.0;
printf(“SAMP – %f, %f, %frn”, sens_mois, sens_temp, sens_humi);
// wait supervisor stop message
Thread::signal_wait(SIGNAL_STOP_ARRIVED);
// write averages and response
int value = (int)sens_mois;
socket_moisture->write(value == 0 ? 1 : value);
value = (int)sens_temp;
socket_temperature->write(value == 0 ? 1 : value);
value = (int)sens_humi;
socket_humidity->write(value == 0 ? 1 : value);
socket_response->write(msg);
printf(“SAMP – endrn”);
l = 0;
}
}
void thread_watering_fcn() {
DigitalOut pump(HW_PIN_PUMP);
DigitalIn level_sens(HW_PIN_LOW_WATER_LEVEL);
while (1) {
pump = 0;
printf(“WATR – waiting…rn”);
// wait watering command
Thread::signal_wait(SIGNAL_WATERING_ARRIVED);
printf(“WATR – startrn”);
Timer t;
t.start();
DigitalOut l(LED2);
l = 1;
// read total second to water
int seconds = COMM_NO_VALUE;
while(!read_from_port(socket_water, &seconds) &&
t.read()<TIMEOUT_WATERING);
t.stop();
if (t.read() >= TIMEOUT_WATERING) {
socket_response->write(COMM_WATERING_KO);
continue;
}
printf(“WATR – required time %dsrn”, seconds);
// start watering
int msg = COMM_WATERING_OK;
double actual_seconds = 0.0;
while (actual_seconds < seconds) {
if (!level_sens) {
// low water, stop pump and exit
pump = 0;
printf(“WATR – watering level errorrn”);
msg = COMM_LOW_WATER_LEVEL;
break;
}
// water for 1 second
t.reset(); t.start();
double scale = seconds > (actual_seconds + 1.0) ? 1.0 : seconds –
actual_seconds;
pump = 1;
Thread::wait((unsigned int)(scale * INTERVAL_WATERING));
t.stop();
actual_seconds += t.read();
}
pump = 0;
printf(“WATR – elapsed time %fsrn”, actual_seconds);
socket_response->write(msg);
l = 0;
}
}
bool read_from_port(ssWiSocket *socket, int *value) {
int prev = *value;
*value = socket->read();
return (*value) != prev;
}
Cloud – Temperature script
% plot temperature of the last 7 days
readChannelID = 000000;
fields = [1, 3, 5];
readAPIKey = ‘XXXXXXXXXXXXXXXX’;
% read data
[values, time] = thingSpeakRead(readChannelID, ‘Fields’, fields,…
‘NumDays’, 7,…
‘ReadKey’, readAPIKey);
if ~isempty(values)
node = values(:, 1);
temp = values(:, 2);
vali = values(:, 3);
% get only valid data
validIdxs = vali == 1;
node = node(validIdxs);
temp = temp(validIdxs);
time = time(validIdxs);
% get data for each watering unit
n = unique(node);
matrix_time = cell(length(n));
matrix_temp = cell(length(n));
matrix_name = cell(length(n));
for i=1:length(n)
ids = node == n(i);
matrix_time{i} = time(ids);
matrix_temp{i} = temp(ids);
matrix_name{i} = [‘Watering unit #’ int2str(n(i))];
end
% plot
hold on
p = [];
for i=1:length(n)
p(end+1) = plot(matrix_time{i}, matrix_temp{i},…
‘LineStyle’, ‘-‘, ‘Marker’, ‘*’);
end
legend(p, matrix_name);
hold off
end