Introduction: UCL - IIot - Smartplant

This is a school project for 3rd semester at UCL.

Electives: Ilot (Industrial Internet of Things).

The project is gonna be an extension of an earlier project at last semester.

Made by Emil and Nicolai.

(Sorry that some of the pictures and video is in danish)

Step 1: Overview

Our goal with this project is that we want to collect data from our two plants. The data we collect are moisture in the soil, day light, humidity and temperatur to our database "MySQL". These data we also want to use on our dashboard.

First video

For a quickly overview of our porject result. We have uploaded a video that give you guys at short overview of our project.

Second video

Also, we want to be able to water the plants automatically, by setting a limit for the moisture in the soil, or manually water the plant.

Step 2: List of Programs and Parts for This Project

Programs/Software

  • node-red
    • node-red-dashboard
    • node-red-node-mysql
    • node-red-node-serialport
  • Arduino
    • add DHTlib
  • Wampserver64

Parts

  • Arduino mega 2560
  • Arduino Ethernet Shield
  • 2x Light Sensor
  • 6x 220 ohm resistance
  • 2x Green LED
  • 2x Red LED
  • 2x Yellow LED
  • Alot of wire
  • USB cable
  • Lan cable

Step 3: Wiring Diagram (Fritzing)

Step 4: Arduino Code

We have kept working on the old project program. The reason we keep using arduino is that it gives us good oppunities for data collecting.

From the last project we have modified our program. So it now uploads its data to a html site. Also the arduino will be able to read data recieved from node-red. Remember to add DHTlib. You can find it in step 2.

Main.ino

<p>#include "math.h" //importer math library<br>#include <spi.h>
#include <ethernet.h>
#include <sd.h>
#include <dht.h></dht.h></sd.h></ethernet.h></spi.h></p><p>dht DHT;
#define REQ_BUF_SZ 60// size of buffer used to capture HTTP requests
#define DHT11_PIN 22</p><p>//Pin slot LED (Digital pin)
int GLED_1= 13; // Bekrafter om motor_1 kører Digital PIN D13
int YLED_1= 12; // Bekrafter om motor_1 kører på Digital PIN D12
int RLED_1= 11; // Bekrafter om motor_1 ikke kører Digital PIN D11
int GLED_2= 5; // Bekrafter om motor_2 kører Digital PIN D10
int YLED_2= 9; // Bekrafter om motor_2 kører på Digital PIN D9
int RLED_2= 8; // Bekrafter om motor_2 ikke kører Digital PIN D8</p><p>//Pint slot sensor og TransistorPin
//Analog pin
int SENSE_Moisture_1 = 0; //Bestemmer pin slot
int SENSE_Moisture_2 = 1; //Bestemmer pin slot
int SENSE_Light_1 = 2; //Bestemmer pin slot
int SENSE_Light_2= 3; //Bestemmer pin slot
//Digital pin
int TransistorPin_1 = 7; //Motor 1
int TransistorPin_2 = 6; //Motor 2</p><p>//Værdi holdere
int count_1 = 1; //tæller
int count_2 = 1; //tæller
int value_1= 0; //til sensor værdier
int value_2= 0; //til sensor værdier
int value_3= 0; //til sensor værdier
int value_4= 0; //til sensor værdier
int value_5= 0; //til sensor værdier
bool pumpe_1 = false;
bool pumpe_2 = false;
String str_1; //Til server tekst
String str_2; //Til server tekst
String str_3; //Til server tekst
String str_4; //Til server tekst
String str_5; //Til server tekst
String str_55; //Til server tekst
String str_6; //Til server tekst
String str_7; //Til server tekst
String str_8; //Til server tekst
String str_9; //Til server tekst
String str_10 = ";"; //Til server tekst
String str_total_1; //Til server tekst
String str_total_2; //Til server tekst
int limit_1 = 40; //defualt fugtigheds grænse, kan ændres i node-red
int limit_2 = 40; //defualt fugtigheds grænse, kan ændres i node-red
int speed_1 = 20; //defualt hastigheds grænse, kan ændres i node-red
int speed_2 = 20; //defualt hastigheds grænse, kan ændres i node-red</p><p>//andre variabler
int Moisture_maxV= 102; //maximale værdi sensor måler (dirakte tør luft)
int Moisture_minV= 25; //minimale værdi sensor måler (direkte vand)
int Light_maxV= 972; //maximale værdi sensor måler (dirakte mørke)
int Light_minV= 8; //minimale værdi sensor måler (direkte lys)</p><p>unsigned long previousMillis_1 = 0; //til delay ved hjælp af millis
const long interval_1 = 1000; //til delay  ved hjælp af millis
unsigned long previousMillis_2 = 0; //til delay ved hjælp af millis
const long interval_2 = 5000; //til delay  ved hjælp af millis
unsigned long previousMillis_3 = 0; //til delay ved hjælp af millis
const long interval_3 = 1; //til delay  ved hjælp af millis
unsigned long previousMillis_4 = 0; //til delay ved hjælp af millis</p><p>//Internet setting
byte mac[] = { 0x90, 0xA2, 0xDA, 0x0D, 0x99, 0xB1 };
IPAddress ip(192, 168, 0, 20); // IP address, may need to change depending on network
EthernetServer server(80);  // create a server at port 80</p><p>void setup() {
   Serial.begin(250000); //bps
   Serial.flush();
   Ethernet.begin(mac, ip);
   server.begin();
   pinMode(GLED_1, OUTPUT); //status på pin
   pinMode(RLED_1, OUTPUT); //status på pin
   pinMode(YLED_1, OUTPUT); //status på pin
   pinMode(GLED_2, OUTPUT); //status på pin
   pinMode(RLED_2, OUTPUT); //status på pin
   pinMode(YLED_2, OUTPUT); //status på pin
   pinMode(TransistorPin_1, OUTPUT); //status på motor_1
   pinMode(TransistorPin_2, OUTPUT); //status på motor_2
}
void loop() 
{
   Sensor();  //aflæser og skrive nuværende status
   pumpe(speed_1, speed_2, TransistorPin_1, TransistorPin_2, RLED_1, RLED_2, YLED_1, YLED_2); //køre pumpe function med bestemt hastighed
   Lys(value_1, GLED_1, RLED_1, limit_1); //funktion der styre lysene
   Lys(value_2, GLED_2, RLED_2, limit_2); //funktion der styre lysene
   ServerUpdate();
   Option();
}</p>

Main program call all the functions we need.

Sensor.ino

<p>void Sensor()<br>{
   unsigned long currentMillis_1 = millis();//til at lave delay
   
   if (currentMillis_1 - previousMillis_1 >= interval_1) //når den når et bestemt til inden for millis, skal den køre
   {
     
     previousMillis_1 = currentMillis_1; //gemmer tidligere millis
     value_1= analogRead(SENSE_Moisture_1); //Læser signal fra analog pin slot
     value_1= value_1/10; //for at arbejde med minde tal
     value_2= analogRead(SENSE_Moisture_2); //Læser signal fra analog pin slot
     value_2= value_2/10; //for at arbejde med minde tal
     value_3= analogRead(SENSE_Light_1); //Læser signal fra analog pin slot
     value_4= analogRead(SENSE_Light_2); //Læser signal fra analog pin slot</p><p>     str_1 = Calculate(value_1,Moisture_maxV,Moisture_minV);
     str_2 = Calculate(value_2,Moisture_maxV,Moisture_minV);
     str_3 = Calculate(value_3,Light_maxV,Light_minV);
     str_4 = Calculate(value_4,Light_maxV,Light_minV);
     Temp_Humi();
     str_6 = limit_1;
     str_7 = limit_2;
     str_8 = speed_1;
     str_9 = speed_2;
     str_total_1 = str_1 + str_10 + str_2 + str_10 + str_3 + str_10 + str_4 + str_10 + str_5 + str_10 + str_55 + str_10 + str_6 + str_10 +str_7 + str_10 + str_8 + str_10 +str_9;
     //Serial.println(str_total_1); //Testing
     str_total_2 = "Moisture_1;Moisture_2;Light_1;Light_2;Temp;Humi;Limit_1;Limit_2;Speed_1;Speed_2";
     //Serial.println(str_total_2); //Testing
   }    
}</p>

This function generate our datastring we use and send to the html site.

Pumpe.ino

<p>void pumpe(float H_1, float H_2, int T_1, int T_2, int R_1,int R_2, int Y_1, int Y_2) <br>{
  unsigned long currentMillis_2 = millis();//til at lave delay
  int check_1=digitalRead(R_1); //variable der ser status for RLED pin
  int check_2=digitalRead(R_2); //variable der ser status for RLED pin
  float Pspeed_1= 255*(H_1/100); //regner den bestemte hastighed om til et tal mellem 0-255
  float Pspeed_2= 255*(H_1/100); //regner den bestemte hastighed om til et tal mellem 0-255
  
  if (currentMillis_2 - previousMillis_2 >= interval_2) //køre hvergang der er gået 5 sec
  {
    previousMillis_2 = currentMillis_2; //gemmer tidligere millis
   if(pumpe_1 == false)
   {
    if(check_1 == HIGH and count_1 == 1) //count bruges så den kun køre hver 5 gang (25sec)
    {
      analogWrite(T_1, Pspeed_1); //aktiver transistorPin med en hastighed
      digitalWrite(Y_1, HIGH);
      count_1 = 5;
    }
    else if (count_1 > 1)
    {
      analogWrite(T_1, 0);
      digitalWrite(Y_1, LOW);
      count_1 = count_1 - 1;
    }
    else
    {
      analogWrite(T_1, 0);
      digitalWrite(Y_1, LOW);
    }
   }
   if(pumpe_2 == false)
   {
    if(check_2 == HIGH and count_2 == 1) //count bruges så den kun køre hver 5 gang (25sec)
    {
      analogWrite(T_2, Pspeed_2); //aktiver transistorPin med en hastighed
      digitalWrite(Y_2, HIGH);
      count_2 = 5;
    }
    else if (count_2 > 1)
    {
      analogWrite(T_2, 0);
      digitalWrite(Y_2, LOW);
      count_2 = count_2 - 1;
    }
    else
    {
      analogWrite(T_2, 0);
      digitalWrite(Y_2, LOW);
    }
   }
    //Serial.print(count_1); //Testing
    //Serial.println(count_2); //Testing
  }
  if (pumpe_1 == true)
  {
    analogWrite(T_2, Pspeed_2); //aktiver transistorPin med en hastighed
    digitalWrite(Y_2, HIGH);
  }
  if (pumpe_2 == true)
  {
    analogWrite(T_1, Pspeed_2); //aktiver transistorPin med en hastighed
    digitalWrite(Y_1, HIGH);
  }
}</p>

This function helps us to control the pump automatically or manually.

ServerUpdate.ino

void ServerUpdate()<br>{
  unsigned long currentMillis_3 = millis();//til at lave delay
   
   if (currentMillis_3 - previousMillis_3 >= interval_3) //når den når et bestemt til inden for millis, skal den køre
   {
      previousMillis_3 = currentMillis_3;
      // listen for incoming clients
      EthernetClient client = server.available();
      if (client) 
      {
        boolean currentLineIsBlank = true;// an http request ends with a blank line
        while (client.connected()) 
        {
          if (client.available()) 
          {
            char c = client.read();
            if (c == '\n' && currentLineIsBlank) 
            {
              client.println("HTTP/1.1 200 OK");
              client.println("Content-Type: text/html");
              client.println();
              client.println("

<div class="line_1"><p>" + str_total_1 + "</p></div>
<div class="line_2"><p>" + str_total_2 + "</p></div>
<p>");
              break;
            }
            if (c == '\n') 
            {
              currentLineIsBlank = true;// you're starting a new line
            }
            else if (c != '\r') {
              currentLineIsBlank = false;// you've gotten a character on the current line
            }
          }
        }
        client.stop();// close the connection:
      }
  }
}</p>

This function upload our data string to the html site 192.168.0.20

Option.ino

<p>void Option() //funktion til at ændre fugtighed og speed<br>{
  String serialInput = ""; //tom string til serial.read
  while (Serial.available() > 0)
  {
      serialInput += (char) Serial.read(); // Read in one char at a time
      delay(5); // Delay for 5 ms so the next char has time to be received
  }
  String type = serialInput.substring(0, 7); //Deler string op
  String number = serialInput.substring(8); //Deler string op
  if (type == "limit_1") //Almindelig If programmering
  {
    limit_1 = number.toInt();
  }
  else if (type == "limit_2")
  {
    limit_2 = number.toInt();
  }
  else if (type == "speed_1")
  {
    speed_1 = number.toInt();
  }
  else if (type == "speed_2")
  {
    speed_2 = number.toInt();
  }
  if (type == "pumpe_1")
  {
    if (number == "true")
    {
      pumpe_1 = true;
    }
    else if (number == "false")
    {
      pumpe_1 = false;
    }
  }
  if (type == "pumpe_2")
  {
    if (number == "true")
    {
      pumpe_2 = true;
    }
    else if (number == "false")
    {
      pumpe_2 = false;
    }
  }
}</p>

This function read the date we recieve from node-red and act if possible.

Calculate.ino

<p>Omregner moisture value to procent<br>float Calculate(int V, int maxV, int minV) V =  sensor værdi
{
  float range = maxV - minV;
  float NewSensorV = V - minV;
  float procent = ((1 - NewSensorV  range)100);
   
  return procent;
}</p>

Calculates our data into understandable data.

LysKontrol.ino

<p>void Lys(float N_1, float G, float R, float K) //N_1 = sensor, G = grøn pin slot, R = rød pin slot og K kontrol punkt.<br>{
   float kontrol = (((1-(K/100))*(Moisture_maxV-Moisture_minV))+Moisture_minV);
   //Serial.println(kontrol);//testing
   if(N_1<kontrol)  =""  {="" digitalwrite(g,="" high);="" digitalwrite(r,="" low);="" serial.println("true");="" testing="" phase=""  }=""  else="" serial.println("false");="" }<="" p=""></kontrol)></p>

Control our LED lamps.

Temp_Humi.ino

<p>void Temp_Humi()<br>{
  int chk = DHT.read11(DHT11_PIN);
  if (DHT.temperature != -999.00)
  {
  str_5 = (DHT.temperature - 3);
  }
   if (DHT.humidity != -999.00)
  {
  str_55 = (DHT.humidity);
  }
}</p>

Make us able to read the signal from DHT11.

Step 5: Node-red (Retrieving and Storing Data, Controlling "motor")

We use node-red to log and display our data, which we retrieve from a very simple homepage arduino hosts (picture 3) and updates continuously. We also use node-red to control the “motors” through the dashboard, right now the “motors” are represented by the 2 sets of green, yellow and red lamps.

So to explain the node-red program, we start from the top and go down.

The first injection “Updates ever 2 sec” connects to a homepage request, which requests to the homepage our arduino hosts. The homepage consists of 2 strings, which hold all the data and the other show what order the data comes in. Those 2 strings we retrieve with “Data” and “Location of data”, their output is one message as array, which we convert with the function after to string again. Since the function after needs the data as a string, so we can separate it by every “ ; “. The separation function looks like this:

<p>var m1 = {payload: msg.payload.split(";")[0]};<br>var m2 = {payload: msg.payload.split(";")[1]};
var m3 = {payload: msg.payload.split(";")[2]};
var m4 = {payload: msg.payload.split(";")[3]};
var m5 = {payload: msg.payload.split(";")[4]};
var m6 = {payload: msg.payload.split(";")[5]};
var m7 = {payload: msg.payload.split(";")[6]};
var m8 = {payload: msg.payload.split(";")[7]};
var m9 = {payload: msg.payload.split(";")[8]};
var m10 = {payload: msg.payload.split(";")[9]};
return [m1, m2, m3, m4, m5, m6, m7, m8, m9, m10];</p>

So we split by every “ ; “ and the number in the end indicates which “ ; “ we split by. Then we give the function 10 outputs. The rest coming after the separation are to display the data on node-red-dashboard.

To explain the next 3 with a button first, we first look at the next one after these, which are the injection “MySQL log every min”. This one is the same as the very first line, except the last 2 connections “Node-RED MySQL” and “nodered”. The line stores data in MySQL database every minute. The function looks like this:

<p>var data = msg.payload.split(";");<br>var Moisture_1 = data[0];
var Moisture_2 = data[1];
var Light_1 = data[2];
var Light_2 = data[3];
var Temp = data[4];
var Humi = data[5];
var Limit_1 = data[6];
var Limit_2 = data[7];
var Speed_1 = data[8];
var Speed_2 = data[9];
var out = "INSERT INTO nodered.projectLogging (time,Moisture_1,Moisture_2,Light_1,Light_2,Temp,Humi,Limit_1,Limit_2,Speed_1,Speed_2) VALUES('"+new Date().toISOString().slice(0, 19).replace('T', ' ')+"','"+Moisture_1+"','"+Moisture_2+"','"+Light_1+"','"+Light_2+"','"+Temp+"','"+Humi+"','"+Limit_1+"','"+Limit_2+"','"+Speed_1+"','"+Speed_2+"')";
msg.topic = out;
return msg;</p>

We define our data, create a timestamp and sends it to MySQL. The “INSERT INTO nodered.projectLogging” defines where the data is put in MySQL, so in our case, we have a folder called “nodered” and a file called “projectLogging” where the file is set up to contain all the different data. The “nodered” is to make connection with MySQL and contains information about host ip, port, username, password and the name of the database we want to log the data in.

Now back to the 3 with a button first. These 3 are for displaying all data that are logged in MySQL separated for each plant and temperature+humidity for itself on the node-red-dashboard. The button can be clicked on the dashboard to refresh the data from MySQL and the function coming after makes the data be displayed with the latest data first. Which looks like this:

msg.topic = "SELECT * FROM `projectlogging` ORDER BY time DESC"
return msg;

after that, the “Data for Plant 1 + 2, Temp + Humi” are just to make connection with MySQL. In the end we use a node-red-dashboard template to define how our data are displayed on the dashboard. They are all similar, but this template defines what data are displayed, since they all 3 basically receive all data from the MySQL file, but each template takes out what's needed in each case.

The last part of the node-red code is to control the “motor” through the node-red-dashboard. Here is used some dashboard components, sliders and a text to indicate what it’s for. This is then connected to the arduino through serialport (USB). In the program it’s set to COM5, which might have to be changed dependent on which USB port the arduino is connected to on your computer.

Step 6: Node-red Dashboard

To explain the dashboard we will use some pictures since it makes it a lot easier. So basicly all the blue nodes on the first picture has something to do with the dashboard. The next picture shows the editing of the layout for the dashboard, where we can see we got 5 tabs for our homepage, picture 3 shows what it looks like on the dashboard site. So we can edit it and drag them up and down on picture 2, to determine in which order we want them. That is basically also what we can do with what's on the tabs, which is called groupes. For example in the tab "Live data for plants" there is 3 groupes. Those 3 groupes can be seen on picture 4. Picture 4 also shows what's in these groupes. The groupes can also be expanded on picture 2 and what's in the groupes, can be all the single blue notes on picture 1. Again these can be dragged to determine thier order as the other 2 and when you hold over these single data, they will be marked with a red dotted line around the node on the nodes scheme.

So picture 4 shows what’s on the tab “Live data for plants”, this data is being updated every 2 secounds.
Picture 5 shows what the tab "Detailed data plant 2" looks like, it shows data on a chart for the last 24 hours. The tab “Detailed data plant 1" are the same, just data for plant 1. Picture 6 shows all logged data from MySQL with the latest first and updates when the button is pressed. Picture 7 shows what the “Option” tab looks like, where the “motors” can be controlled with sliders.

The easiest way to see what data is used where, is to get the node-red file and go to the layout tab and expand them all, to see whats in them and what order they come in.

Step 7: MySQL

To access MySQL when have the wampserver running. For our project we only needed a folder and a file to log our data. We use the folder nodered and a file called projectlogging. The file cotains an array of 11, with time, Moisture_1, Moisture_2, Light_1, Light_2, Temp, Humi, Limit_1, Limit_2, Speed_1, Speed_2. With the time as datatype longtext and the rest as mediumtext. The structure can be seen on picture 1 and an example of data within the file on picture 2.