Project tutorial

IoT Made Easy w/ UNO, ESP-01, ThingSpeak & MIT App Inventor © GPL3+

Our project will capture local data, send them to the cloud, where everyone can see them through the internet, acting remotely from there.

  • 13,201 views
  • 11 comments
  • 27 respects

Components and supplies

Apps and online services

About this project

Our goal will be to basically collect information from a local unit and send it to the internet. A user anywhere on the planet looking at this information will make decisions by sending remote commands to the actuators, which will also be in this local unit. Any sensor or actuator could be used.

The great majority of my work on IoT arena was using NodeMCU and more recently, the ESP32. But, I think important not forget my early steps, a couple of years ago where I start learning IoT using an Arduino UNO and the old and good ESP8266-01.

So, I decide here to return to that time (with a little bit more of experience now) and explore again those great devices, connecting them to the cloud, using the ThingSpeak.com web service. We will also explore to control things remotely, be using an Android App developed using the MIT AppInventor.

The "Center of our IoT project" will be the ThingSpeak.com. The local unit (UNO/ESP-01) will capture data from sensors and actuator status, send them to the Internet, "writing" on a specific ThingSpeak.com Status Channel. The local unit will also receive data from the internet, "reading" them from specific ThingSpeak Actuator Channels.

An Android App will be also be reading those data from the ThingSpeak.com Status Channel and displaying them for the user. Same way, the user, based on that status info, can send commands to actuators, writing them on ThingSpeak Actuator Channels (See the above Block diagram to better understand the flow of data).

So, what we will do? The block diagram shown in next step will give us an overview of the final project:

Step 1: Introduction

Using common sensors, our project will capture several data, sending them to the cloud, where everyone can see them thru the internet. To work those data, we will use the service provided by ThingSpeak.com, an open IoT platform that will permit us to collect, analyze and act on those data.

The data to be collected by sensors will be:

  • Air Temperature and relative humidity
  • Soil Temperature and humidity
  • Luminosity

The project will have 2 actuators:

  • Electrical Pump
  • Electrical Lamp

The status of those actuators ("ON/OFF"), should be also sent to the cloud.

So, the idea will be to capture those data from the sensors, for example, a plantation and send them to cloud. Based on those data, a user should take the decision based on those statements:

  • Turn ON the Pump if the soil humidity is too low
  • Turn ON the Lamp if the soil temperature is too low

To remotely command our actuators, we will use an Android App.

Step 2: BoM - Bill of Material

Some of the most important components listed here, has a link and an indicative price in USD associated. Those links are for information only.

  • DHT22 (Air and Relative Humidity Sensor) - $9.00
  • DS18B20 (1-Wire Digital Temperature sensor for use on soil) - $6.00
  • LDR (Luminosity Sensor) - $0.20
  • 2 x LEDs (Red and Green)
  • 220V Lamp
  • 2 x 330 ohm resistor (to be used with LEDs)
  • 2 x 10K ohm resistor (to be used with DHT22 and LDR)
  • 1 x 4K7 ohm resistor (to be used with DS18B20
  • Protoboard
  • Jumpers
  • External 5V DC Power for Relays

Step 3: The Hardware

Let's assembly the Project HW. The ideal is install and test our project by parts. As a suggestion, we can follow the steps:

  • Install and test locally all sensors
  • Install and configure the ESP-01 (BareMinimum)
  • Change the ESP-01 installation for its final configuration and test it
  • Configure the ThingsPeak Status Channel
  • Install ThingsPeak code in your Arduino and check the Sensors status on the Cloud
  • Develop the first version of Android App to display Status and messages
  • Install actuators (LEDs and Relays)
  • Configure ThingSpeak Actuators channels
  • Install and test Arduino Code with Actuators
  • Develop the final Android App Version

Step 4: Connecting Sensors

We must have some libraries installed on our IDE to read the sensors properly. Check if you have all libraries installed. Their initial configuration should be:

// DS18B20
#include <OneWire.h>
#include <DallasTemperature.h>
#define ONE_WIRE_BUS 5 // DS18B20 on pin D5 
OneWire oneWire(ONE_WIRE_BUS);
DallasTemperature DS18B20(&oneWire);
int soilTemp = 0;
//DHT
#include "DHT.h"
#include <stdlib.h>
int pinoDHT = 11;
int tipoDHT =  DHT22;
DHT dht(pinoDHT, tipoDHT); 
int airTemp = 0;
int airHum = 0;
// LDR (Light)
#define ldrPIN 1
int light = 0;
// Soil humidity
#define soilHumPIN 0
int soilHum = 0;

On Setup and loop, let's write:

void setup()
{
  Serial.begin(9600); 
  DS18B20.begin();
  dht.begin();
}
void loop()
{
  readSensors();
  displaySensors();
  delay (10000);
}

And at last, let's write two specific functions, one for reading our sensors and another to display their values on Serial Monitor:

/********* Read Sensors value *************/
void readSensors(void)
{
  airTemp = dht.readTemperature();
  airHum = dht.readHumidity();
  DS18B20.requestTemperatures(); 
  soilTemp = DS18B20.getTempCByIndex(0); // Sensor 0 will capture Soil Temp in Celcius
  
  soilHum = map(analogRead(soilHumPIN), 1023, 0, 0, 100);             
  light = map(analogRead(ldrPIN), 1023, 0, 0, 100); //LDRDark:0  ==> light 100%  
}
/********* Display Sensors value *************/
void displaySensors(void)
{
  Serial.print ("airTemp  (oC): ");
  Serial.println (airTemp);
  Serial.print ("airHum    (%): ");
  Serial.println (airHum);
  Serial.print ("soilTemp (oC): ");
  Serial.println (soilTemp);
  Serial.print ("soilHum   (%): ");
  Serial.println (soilHum);
  Serial.print ("light     (%): ");
  Serial.println (light);
  Serial.println ("");
}

The below Serial Monitor picture shows us the sensor values.

The code can be downloaded from my GITHUB: Sens ors_Test.ino

Step 5: ​ESP8266-01 Initial Configuration

The ESP-01 will be used as a Serial Bridge, what means that we will program it using " AT commands". The first thing is to be sure that your ESP-01 is on the correct Baud Rate communication speed. In our case, 9,600 bauds. Usually, the ESP-01 come programmed from the factory with 115,200 baud and we must change it to 9,600 baud.

First, you must connect the ESP-01 as shown above.

Note that the ESP-01 TX pin is connected to UNO Tx pin, same for RX. This will be changed later.

Then connect the Arduino to your computer, open the IDE and load the example that is in File> Examples> 01.Basics> BareMinimum. This is an empty code, to ensure that there will be no communication conflict between Arduino and ESP.

We have transferred this code to the Arduino before connecting it to the ESP-01S to ensure that the Arduino will not use serial communication (TX and RX). This is important for ESP to be able to communicate properly.

Open your IDE Serial Monitor and change the speed to 115,200 baud Start sending an "AT" command on your IDE Serial Monitor. The ESP-01 should return "OK"

Next, let's change the speed. For that, you can use the command:

AT + CIOBAUD = 9600

Note that the ESP-01 may return to factory programming (I do not know if this is due to the FW version). At least in my case, I had to use a different command to change BaudRate definitively:

AT+ UART_DEF=<baudrate>,<databits>,<stopbits>,<parity>,<flow control>

For example: 9600 baud / 8 data bits / 1 stop bits and none parity and flow control:

AT + UART_DEF = 9600,8,1,0,0

In the selection box at the bottom of your Serial Monitor, change the speed to "9600 baud". Test the communication: on the upper side of the window type AT and see the answer OK. Now you must configure the module in Station Mode to act as Clientof your Wi-Fi network. Use the command:

 AT + CWMODE = 1

Now we must connect the module to your Wi-Fi network.

To do this, use the commands below, replacing "network_name" with the name of your Wi-Fi network and "network_name" with your password. Keep the quotation marks.

AT + CWJAP = "network_name", "network_name" 

If you see the answer below, your connection has been established correctly:

WIFI CONNECTED WIFI GOT IP

To find the IP, run the command:

AT + CIFSR 

And take a note of IP address that will appear in your Serial Monitor. You may need it in the future.

Step 6: Testing the ESP-01

Once we have the ESP-01 configured, we must install it on its final circuit. For that we must CHANGE the wiring done before and connect the ESP-01 to our UNO as below:

  • ESP-01 RX (Yellow) to UNO Pin D7
  • ESP-01 TX (Orange) to UNO Pin D6
  • ESP-01 Ch-Pd (Brown) to Vcc (3.3V)
  • ESP-01 Reset (Blue) to UNO Pin D8
  • ESP-01 Vcc (Red) to 3.3V
  • ESP-01 Gnd (Black) to UNO GND
Note that we will use the Arduino library Software Serial, having UNO pin D7 as Tx connected to ESP-01 Rx pin and UNO pin D6 as Rx connected to ESP-01 Tx pin.

Let's do a simple test to check if our ESP-01 is corrected installed and tested. Enter the below code:

#include <SoftwareSerial.h>   
SoftwareSerial esp8266(6,7);  //Rx ==> Pin 6; TX ==> Pin7 
#define speed8266 9600 
void setup() 
{
  esp8266.begin (speed8266); 
  Serial.begin(speed8266);
  Serial.println("ESP8266 Setup test - use AT coomands");
}
void loop() 
{
  while(esp8266.available())
  {
    Serial.write(esp8266.read());
  }
  while(Serial.available())
  {
    esp8266.write(Serial.read());
  }
}

Now, try some AT commands and see the result on your Serial monitor:

*    AT     =====> ESP8266 returns OK
*    AT+RST =====> ESP8266 restart and returns OK
*    AT+GMR =====> ESP8266 returns AT Version; SDK version; id; OK
*    AT+CWMODE? => ESP8266 returns mode type
*    AT+CWLAP ===> ESP8266 returs close access points
*    AT+CIFSR ===> ESP8266 returs designided IP

The code can be downloaded from my GITHUB: ESP_AT_Config.ino

If you want to connect to the WiFi network every time a reset occurs (or your Arduino is turned off / on) and entering your credentials, add a call to the connectWiFi () function at the end of the setup () function:

setup()
{  
   ...   
   connectWiFi(); 
}

The connectWiFi () function should be at the end of your main code .ino:

void connectWiFi(void)
{
 sendData("AT+RST\r\n", 2000, 0); // reset
 sendData("AT+CWJAP=\"YOUR USERNAME\",\"YOUR PASSWORD\"\r\n", 2000, 0); //Connect network
 delay(3000);
 sendData("AT+CWMODE=1\r\n", 1000, 0);
 sendData("AT+CIFSR\r\n", 1000, 0); // Show IP Adress
 Serial.println("8266 Connected");
}
You must enter the "YOUR USERNAME \" and "YOUR PASSWORD \" credentials directly into the function, replacing the generic strings.

Note that the above function calls another sendData (data) function, which should also be located in your code:

String sendData(String command, const int timeout, boolean debug)
{
 String response = "";
 EspSerial.print(command);
 long int time = millis();
 while ( (time + timeout) > millis())
 {
   while (EspSerial.available())
   {
     // The esp has data so display its output to the serial window
     char c = EspSerial.read(); // read the next character.
     response += c;
   }
 }
 if (debug)
 {
   Serial.print(response);
 }
 return response;
} 
In my GITHub you will find a complete version of the project (v1.1), which includes the WiFi network connection with credentials.

Step 7: Connecting Sensors and ESP-01

Once we have all sensors installed and tested and also our ESP-01 working properly, let's see all together and prepared to send data to the internet.

Step 8: The ThingSpeak

One of the most important parts of our project is the ThingSpeak, an open IoT platform that will permit us to collect, analyze and act on collected data. If you do not have yet, please go to ThingSpeak sign up and create your account.

Next, create a new Channel where we will have our 2 actuators, 5 sensors and a spare field status:

  • Field 1: Actuator 1
  • Field 2: Actuator 2
  • Field 3: Air Temperature in oC
  • Filed 4: Air Relative Humidity in %
  • Field 5: Soil Temperature in oC
  • Field 6: Soil Humidity in %
  • Field 7: Luminosity in %
  • Field 8: Spare

Field 8 will be left as a spare to be used for future expansion or for debug purpose. For example, I will use it as a "counter" for each communication error that happens during Arduino/ESP-01 handshake with ThingSpeak.com.

Once you create your channel (in this case will be our Status Channel), will be important take a note of your keys, as shown below.

Step 9: Sending Status to the Cloud

At this point, we have our Cloud Service available and our sensors capturing data locally. Let's take those values and sent them to the ThingSpeak.com. We will WRITE on ThingSpeak channel and for that, we will need to send a GET string. We will do in 3 parts:

We will send a "Start cmd":

AT+CIPSTART="TCP","184.106.153.149",80

Following the "length" of the command:

AT+CIPSEND=116

And the GET string itself, that will write on the appropriated fields on the Status Channel:

GET /update?api_key=YOUR_WRITE_KEY_HERE&field1=pump&fieldlamp=0&field3=airTemp&field4=airHum&field5=soilTemp&field6=soilHum&field7=light&field8=spare<br>
Note that we must not write on ThingSpeak channel on intervals lower than 16 seconds

The below code will do the work for us and the above PrintScreen shows the result on Serial Monitor:

// Thingspeak  
String statusChWriteKey = "6SRPQQKIE6AJVQE6";  // Status Channel id: 385184
#include <SoftwareSerial.h>
SoftwareSerial EspSerial(6, 7); // Rx,  Tx
#define HARDWARE_RESET 8
// DS18B20
#include <OneWire.h>
#include <DallasTemperature.h>
#define ONE_WIRE_BUS 5 // DS18B20 on pin D5 
OneWire oneWire(ONE_WIRE_BUS);
DallasTemperature DS18B20(&oneWire);
int soilTemp = 0;
//DHT
#include "DHT.h"
#include <stdlib.h>
int pinoDHT = 11;
int tipoDHT =  DHT22;
DHT dht(pinoDHT, tipoDHT); 
int airTemp = 0;
int airHum = 0;
// LDR (Light)
#define ldrPIN 1
int light = 0;
// Soil humidity
#define soilHumPIN 0
int soilHum = 0;
// Variables to be used with timers
long writeTimingSeconds = 17; // ==> Define Sample time in seconds to send data
long startWriteTiming = 0;
long elapsedWriteTime = 0;
// Variables to be used with Actuators
boolean pump = 0; 
boolean lamp = 0; 
int spare = 0;
boolean error;
void setup()
{
  Serial.begin(9600);
  
  pinMode(HARDWARE_RESET,OUTPUT);
  
  digitalWrite(HARDWARE_RESET, HIGH);
  
  DS18B20.begin();
  dht.begin();
  EspSerial.begin(9600); // Comunicacao com Modulo WiFi
  EspHardwareReset(); //Reset do Modulo WiFi
  startWriteTiming = millis(); // starting the "program clock"
}
void loop()
{
  start: //label 
  error=0;
  
  elapsedWriteTime = millis()-startWriteTiming; 
  
  if (elapsedWriteTime > (writeTimingSeconds*1000)) 
  {
    readSensors();
    writeThingSpeak();
    startWriteTiming = millis();   
  }
  
  if (error==1) //Resend if transmission is not completed 
  {       
    Serial.println(" <<<< ERROR >>>>");
    delay (2000);  
    goto start; //go to label "start"
  }
}
/********* Read Sensors value *************/
void readSensors(void)
{
  airTemp = dht.readTemperature();
  airHum = dht.readHumidity();
  DS18B20.requestTemperatures(); 
  soilTemp = DS18B20.getTempCByIndex(0); // Sensor 0 will capture Soil Temp in Celcius
             
  light = map(analogRead(ldrPIN), 1023, 0, 0, 100); //LDRDark:0  ==> light 100%  
  soilHum = map(analogRead(soilHumPIN), 1023, 0, 0, 100); 
}
/********* Conexao com TCP com Thingspeak *******/
void writeThingSpeak(void)
{
  startThingSpeakCmd();
  // preparacao da string GET
  String getStr = "GET /update?api_key=";
  getStr += statusChWriteKey;
  getStr +="&field1=";
  getStr += String(pump);
  getStr +="&field2=";
  getStr += String(lamp);
  getStr +="&field3=";
  getStr += String(airTemp);
  getStr +="&field4=";
  getStr += String(airHum);
  getStr +="&field5=";
  getStr += String(soilTemp);
  getStr +="&field6=";
  getStr += String(soilHum);
  getStr +="&field7=";
  getStr += String(light);
  getStr +="&field8=";
  getStr += String(spare);
  getStr += "\r\n\r\n";
  sendThingSpeakGetCmd(getStr); 
}
/********* Reset ESP *************/
void EspHardwareReset(void)
{
  Serial.println("Reseting......."); 
  digitalWrite(HARDWARE_RESET, LOW); 
  delay(500);
  digitalWrite(HARDWARE_RESET, HIGH);
  delay(8000);//Tempo necessário para começar a ler 
  Serial.println("RESET"); 
}
/********* Start communication with ThingSpeak*************/
void startThingSpeakCmd(void)
{
  EspSerial.flush();//limpa o buffer antes de começar a gravar
  
  String cmd = "AT+CIPSTART=\"TCP\",\"";
  cmd += "184.106.153.149"; // Endereco IP de api.thingspeak.com
  cmd += "\",80";
  EspSerial.println(cmd);
  Serial.print("enviado ==> Start cmd: ");
  Serial.println(cmd);
  if(EspSerial.find("Error"))
  {
    Serial.println("AT+CIPSTART error");
    return;
  }
}
/********* send a GET cmd to ThingSpeak *************/
String sendThingSpeakGetCmd(String getStr)
{
  String cmd = "AT+CIPSEND=";
  cmd += String(getStr.length());
  EspSerial.println(cmd);
  Serial.print("enviado ==> lenght cmd: ");
  Serial.println(cmd);
  if(EspSerial.find((char *)">"))
  {
    EspSerial.print(getStr);
    Serial.print("enviado ==> getStr: ");
    Serial.println(getStr);
    delay(500);//tempo para processar o GET, sem este delay apresenta busy no próximo comando
    String messageBody = "";
    while (EspSerial.available()) 
    {
      String line = EspSerial.readStringUntil('\n');
      if (line.length() == 1) 
      { //actual content starts after empty line (that has length 1)
        messageBody = EspSerial.readStringUntil('\n');
      }
    }
    Serial.print("MessageBody received: ");
    Serial.println(messageBody);
    return messageBody;
  }
  else
  {
    EspSerial.println("AT+CIPCLOSE");     // alert user
    Serial.println("ESP8266 CIPSEND ERROR: RESENDING"); //Resend...
    spare = spare + 1;
    error=1;
    return "error";
  } 
} 

The code can be downloaded from my GITHUB: SendingStatusTS_EXT.ino

Step 10: The Android App First Part - Status Monitoring

Let's create our 1st part of the Android App.

First, we design the user interface. The above Print Screen above show the main visible and non-visible elements. After that, we must design the blocks (the below numbers are correspondent to the above figures):

At every 2 seconds (defined by Clock1), we will call a procedure named: "readArduino".

  • The return of such procedure will be the value for each one of the Status variable that should be displayed on the screen.
  • Note that we will "convert" the values "0" and "1" from actuators status for "OFF" and "ON" for better understanding.
  • Those values ("Status") will be displayed on correspondent "labels"
  • The status variables must be declared as Global.
  • The procedure "readArduino" will, in fact, read the Status Channel at ThingSpeak. So, we must define the URL to be sent to Thingspeak. For that 3 Global variables must be declared and joined to create the URL to be sent to TS. A GET should be sent to the Web Component named "ArduFarmBotStatusCh"
  • The text got from the previous command, will arrive as JSon format. This text must be processed having each field to be read and stored on the correspondent global variable.
  • The last thing to be done is call the "Alarm" procedure, that will analyze the status of two soil sensors. If the temperature is too low (in our case 10oC), a message must be displayed. Same for humidity, if it is lower than 60%. Note that we have defined another timer (Clock2), programmed to be fired each 1 second. This is only to "toggle" the color of message text (from white to red). This will make the message "blink".

The last photo above shows the final App working.

The App code can be downloaded from my GITHUB: ArduFarmBot_Status_App_EXT.aia

Step 11: Installing Actuators (LEDs and Relays)

Let's complete our HW.

For that, we must install our Actuators. As you remember, we will remotely receive commands to Turn ON and OFF a Pump and a Lamp. The Arduino output will activate a Relay (and a LED) to get those actions.

We will use a Relay module that has an Optocoupler Low-Level Trigger. Also, we will supply 5V to it through a separate pin, so we don't have to provide the required current on the input pin. The module does that for us.

The above figure shows how the actuators must be connected. Note that the Realy GND is NOT CONNECTED to Arduino GND. This will help not to introduce noise when the relay works.

For simplicity, I took out from the diagram, the sensors. But you can add the actuators circuitry to your project w/o taking out the sensors that you have already installed and tested.

Step 12: Configuring the ThingSpeak Actuators Channels

The same way that we did for the Status, we will create 2 new channels one for each actuator. From each one of them, take note of Channel ID, Read and Write keys. We will write only on Field 1 of each one of those channels. So, in my case for example:

Channel ID 375598 ==> LED Red (Pump)

  • Field1 = 0 ==> Pump OFF
  • Field1 = 1 ==> Pump ON

Channel ID 375599 ==> LED Green (Lamp)

  • Field1 = 0 ==> Lamp OFF
  • Field1 = 1 ==> Lamp ON

Step 13: Installing and Testing Arduino Code with Actuators

When we sent data to the web, what we did was to WRITE on a ThingSpeak channel (Channel status) to "transmit" (upload) data. Now we should READ from a ThingSpeak channel (Actuator Channel) to "receive" (download) data.

We will READ from a ThingSpeak channel and for that, we will need to send a GET string. We will do in 3 parts:

We will send a "Start cmd":

AT+CIPSTART="TCP","184.106.153.149",80

Following the "length" of the command:

AT+CIPSEND=36

And the GET string itself, that will read from field 1 on each one of the Actuator Channel:

GET /channels/375598/fields/1/last

We will be reading from ThingSpeak channels on intervals of 10 seconds. After we send the GET command above, that is calling for the "LAST VALUE STORED ON FIELD 1 we will receive an answer from ThingSpeak that should be "1" or "0" on a specific position of the answer. If anything different from that arrived, we must ignore it.

The main difference between this part of the code and the previous one (to Send Status data), is the function:

readThingSpeak(String channelID)

The below code will do the work for us and the above PrintScreen shows the result on Serial Monitor:

// Thingspeak  
String canalID1 = "375598"; //Actuator1
String canalID2 = "375599"; //Actuator2
#include <SoftwareSerial.h>
SoftwareSerial EspSerial(6, 7); // Rx,  Tx
#define HARDWARE_RESET 8
// Variables to be used with timers
long readTimingSeconds = 10; // ==> Define Sample time in seconds to receive data
long startReadTiming = 0;
long elapsedReadTime = 0;
//Relays
#define ACTUATOR1 10 // RED LED   ==> Pump
#define ACTUATOR2 12 // GREEN LED ==> Lamp
boolean pump = 0; 
boolean lamp = 0; 
int spare = 0;
boolean error;
void setup()
{
  Serial.begin(9600);
  
  pinMode(ACTUATOR1,OUTPUT);
  pinMode(ACTUATOR2,OUTPUT);
  pinMode(HARDWARE_RESET,OUTPUT);
  digitalWrite(ACTUATOR1, HIGH); //o módulo relé é ativo em LOW
  digitalWrite(ACTUATOR2, HIGH); //o módulo relé é ativo em LOW
  digitalWrite(HARDWARE_RESET, HIGH);
  EspSerial.begin(9600); // Comunicacao com Modulo WiFi
  EspHardwareReset(); //Reset do Modulo WiFi
  startReadTiming = millis(); // starting the "program clock"
}
void loop()
{
  start: //label 
  error=0;
  
  elapsedReadTime = millis()-startReadTiming; 
  if (elapsedReadTime > (readTimingSeconds*1000)) 
  {
    int command = readThingSpeak(canalID1); 
    if (command != 9) pump = command; 
    delay (5000);
    command = readThingSpeak(canalID2); 
    if (command != 9) lamp = command; 
    takeActions();
    startReadTiming = millis();   
  }
  
  if (error==1) //Resend if transmission is not completed 
  {       
    Serial.println(" <<<< ERROR >>>>");
    delay (2000);  
    goto start; //go to label "start"
  }
}
/********* Take actions based on ThingSpeak Commands *************/
void takeActions(void)
{
  Serial.print("Pump: ");
  Serial.println(pump);
  Serial.print("Lamp: ");
  Serial.println(lamp);
  if (pump == 1) digitalWrite(ACTUATOR1, LOW);
  else digitalWrite(ACTUATOR1, HIGH);
  if (lamp == 1) digitalWrite(ACTUATOR2, LOW);
  else digitalWrite(ACTUATOR2, HIGH);
}
/********* Read Actuators command from ThingSpeak *************/
int readThingSpeak(String channelID)
{
  startThingSpeakCmd();
  int command;
  // preparacao da string GET
  String getStr = "GET /channels/";
  getStr += channelID;
  getStr +="/fields/1/last";
  getStr += "\r\n";
  String messageDown = sendThingSpeakGetCmd(getStr);
  if (messageDown[5] == 49)
  {
    command = messageDown[7]-48; 
    Serial.print("Command received: ");
    Serial.println(command);
  }
  else command = 9;
  return command;
}
/********* Reset ESP *************/
void EspHardwareReset(void)
{
  Serial.println("Reseting......."); 
  digitalWrite(HARDWARE_RESET, LOW); 
  delay(500);
  digitalWrite(HARDWARE_RESET, HIGH);
  delay(8000);//Tempo necessário para começar a ler 
  Serial.println("RESET"); 
}
/********* Start communication with ThingSpeak*************/
void startThingSpeakCmd(void)
{
  EspSerial.flush();//limpa o buffer antes de começar a gravar
  
  String cmd = "AT+CIPSTART=\"TCP\",\"";
  cmd += "184.106.153.149"; // Endereco IP de api.thingspeak.com
  cmd += "\",80";
  EspSerial.println(cmd);
  Serial.print("enviado ==> Start cmd: ");
  Serial.println(cmd);
  if(EspSerial.find("Error"))
  {
    Serial.println("AT+CIPSTART error");
    return;
  }
}
/********* send a GET cmd to ThingSpeak *************/
String sendThingSpeakGetCmd(String getStr)
{
  String cmd = "AT+CIPSEND=";
  cmd += String(getStr.length());
  EspSerial.println(cmd);
  Serial.print("enviado ==> lenght cmd: ");
  Serial.println(cmd);
  if(EspSerial.find((char *)">"))
  {
    EspSerial.print(getStr);
    Serial.print("enviado ==> getStr: ");
    Serial.println(getStr);
    delay(500);//tempo para processar o GET, sem este delay apresenta busy no próximo comando
    String messageBody = "";
    while (EspSerial.available()) 
    {
      String line = EspSerial.readStringUntil('\n');
      if (line.length() == 1) 
      { //actual content starts after empty line (that has length 1)
        messageBody = EspSerial.readStringUntil('\n');
      }
    }
    Serial.print("MessageBody received: ");
    Serial.println(messageBody);
    return messageBody;
  }
  else
  {
    EspSerial.println("AT+CIPCLOSE");     // alert user
    Serial.println("ESP8266 CIPSEND ERROR: RESENDING"); //Resend...
    spare = spare + 1;
    error=1;
    return "error";
  } 
}

The code can be downloaded from my GITHUB: ReadingCommandTS_EXT.ino

Step 14: Sending Commands to Actuators

At this point, we have the actuators channels configured on ThingSpeak and changing the value of Field 1 on each channel, we must see the actuators changing accordingly. On our final project we will do this task, using the Android App, but for testing proposes we can also do it using a normal browser. Let's do it.

The commands are:

Turn ON Pump (RED LED):

https://api.thingspeak.com/update?api_key=ACT1_WRITE_KEY&field1=1

Turn OFF Pump (RED LED):

https://api.thingspeak.com/update?api_key=ACT1_WRITE_KEY&field1=0

Turn ON Lamp (GREEN LED):

https://api.thingspeak.com/update?api_key=ACT2_WRITE_KEY&field1=1

Turn OFF Lamp (GREEN LED):

https://api.thingspeak.com/update?api_key=ACT2_WRITE_KEY&field1=0

Above you can see a print screen of a command to TurnOn the Pump sent from a browser and how it will appear at Serial Monitor. Obviously, the LED Red and relay will be also be turned on.

Step 15: Completing the Android APP

Let's complete the APP. Previously we have developed a simple App that gets the status from ThingSpeak (READ from Staus Channel). Now we must WRITE on Actuator channels, so those commands could be read by Arduino and act on Pump and Lamp accordingly.

For a user to pass the commands to the App, we will use "buttons". A pair of buttons (ON and OFF) for each one of the Actuators.

When a button is pressed, the color of its text changes.

  • If ON ==> Blue
  • if OFF ==> Red

Above you can see the set of blocks for each one of the pairs of buttons.

Test the App, sending commands to turn ON and OFF the actuators. Check on Serial Monitor, the messages exchanged between ESP-01 and ThingSpeak.

The complete App code can be downloaded from my GITHUB: ArduFarmBot_V1_EXT.aia

Step 16: Putting All Together

Perfect! At this point, you have a full Android APP, a complete HW but you still do not have a code that will continuously read and write on ThingSpeak. Let's combine all that we have developed previously.

On the final code, you will find additional portions to verify for example if the ESP-01 is not freezing. We will do it, sending an AT command to it before any read or write. As we saw at the very beginning of this tutorial, sending an AT command should return from ESP-01 an OK. If this does not happen, we will proceed with an HW reset, commanded by SW (as we do once during setup phase).

The complete code for our project can be downloaded from my GITHUB: ArduFarmBot_Light_EXT.ino

An additional version (v1.1) has been added to my repository, which includes local WiFi network connection using credentials: version 1.1

Step 17: Conclusion

There is a lot to be explored in IoT arena with those great little devices, the Arduino Uno, and the ESP8266-01. We will return soon with new tutorials! Keep following MJRoBot tutorials!

As always, I hope this project can help others find their way in the exciting world of electronics, robotics, and IoT!

Please visit my GitHub for updated files: ArduFarmBot_Light

For more projects, please visit my blog: MJRoBot.org

Saludos from the south of the world!

See you at my next tutorial!

Thank you,

Marcelo

Code

Code snippet #1Plain text
// DS18B20
#include <OneWire.h>
#include <DallasTemperature.h>
#define ONE_WIRE_BUS 5 // DS18B20 on pin D5 
OneWire oneWire(ONE_WIRE_BUS);
DallasTemperature DS18B20(&oneWire);
int soilTemp = 0;

//DHT
#include "DHT.h"
#include <stdlib.h>
int pinoDHT = 11;
int tipoDHT =  DHT22;
DHT dht(pinoDHT, tipoDHT); 
int airTemp = 0;
int airHum = 0;

// LDR (Light)
#define ldrPIN 1
int light = 0;

// Soil humidity
#define soilHumPIN 0
int soilHum = 0;
Code snippet #2Plain text
void setup()
{
  Serial.begin(9600); 
  DS18B20.begin();
  dht.begin();
}

void loop()
{
  readSensors();
  displaySensors();
  delay (10000);
}
Code snippet #3Plain text
/********* Read Sensors value *************/
void readSensors(void)
{
  airTemp = dht.readTemperature();
  airHum = dht.readHumidity();

  DS18B20.requestTemperatures(); 
  soilTemp = DS18B20.getTempCByIndex(0); // Sensor 0 will capture Soil Temp in Celcius
  
  soilHum = map(analogRead(soilHumPIN), 1023, 0, 0, 100);             
 
  light = map(analogRead(ldrPIN), 1023, 0, 0, 100); //LDRDark:0  ==> light 100%  

}

/********* Display Sensors value *************/
void displaySensors(void)
{
  Serial.print ("airTemp  (oC): ");
  Serial.println (airTemp);
  Serial.print ("airHum    (%): ");
  Serial.println (airHum);
  Serial.print ("soilTemp (oC): ");
  Serial.println (soilTemp);
  Serial.print ("soilHum   (%): ");
  Serial.println (soilHum);
  Serial.print ("light     (%): ");
  Serial.println (light);
  Serial.println ("");
}
Code snippet #11Plain text
#include <SoftwareSerial.h>   
SoftwareSerial esp8266(6,7);  //Rx ==> Pin 6; TX ==> Pin7 

#define speed8266 9600 

void setup() 
{
  esp8266.begin (speed8266); 
  Serial.begin(speed8266);
  Serial.println("ESP8266 Setup test - use AT coomands");
}

void loop() 
{
  while(esp8266.available())
  {
    Serial.write(esp8266.read());
  }
  while(Serial.available())
  {
    esp8266.write(Serial.read());
  }
}
Code snippet #12Plain text
*    AT     =====> ESP8266 returns OK
*    AT+RST =====> ESP8266 restart and returns OK
*    AT+GMR =====> ESP8266 returns AT Version; SDK version; id; OK
*    AT+CWMODE? => ESP8266 returns mode type
*    AT+CWLAP ===> ESP8266 returs close access points
*    AT+CIFSR ===> ESP8266 returs designided IP
Code snippet #16Plain text
// Thingspeak  
String statusChWriteKey = "6SRPQQKIE6AJVQE6";  // Status Channel id: 385184

#include <SoftwareSerial.h>
SoftwareSerial EspSerial(6, 7); // Rx,  Tx
#define HARDWARE_RESET 8

// DS18B20
#include <OneWire.h>
#include <DallasTemperature.h>
#define ONE_WIRE_BUS 5 // DS18B20 on pin D5 
OneWire oneWire(ONE_WIRE_BUS);
DallasTemperature DS18B20(&oneWire);
int soilTemp = 0;

//DHT
#include "DHT.h"
#include <stdlib.h>
int pinoDHT = 11;
int tipoDHT =  DHT22;
DHT dht(pinoDHT, tipoDHT); 
int airTemp = 0;
int airHum = 0;

// LDR (Light)
#define ldrPIN 1
int light = 0;

// Soil humidity
#define soilHumPIN 0
int soilHum = 0;

// Variables to be used with timers
long writeTimingSeconds = 17; // ==> Define Sample time in seconds to send data
long startWriteTiming = 0;
long elapsedWriteTime = 0;

// Variables to be used with Actuators
boolean pump = 0; 
boolean lamp = 0; 

int spare = 0;
boolean error;

void setup()
{
  Serial.begin(9600);
  
  pinMode(HARDWARE_RESET,OUTPUT);
  
  digitalWrite(HARDWARE_RESET, HIGH);
  
  DS18B20.begin();
  dht.begin();

  EspSerial.begin(9600); // Comunicacao com Modulo WiFi
  EspHardwareReset(); //Reset do Modulo WiFi
  startWriteTiming = millis(); // starting the "program clock"
}

void loop()
{
  start: //label 
  error=0;
  
  elapsedWriteTime = millis()-startWriteTiming; 
  
  if (elapsedWriteTime > (writeTimingSeconds*1000)) 
  {
    readSensors();
    writeThingSpeak();
    startWriteTiming = millis();   
  }
  
  if (error==1) //Resend if transmission is not completed 
  {       
    Serial.println(" <<<< ERROR >>>>");
    delay (2000);  
    goto start; //go to label "start"
  }
}

/********* Read Sensors value *************/
void readSensors(void)
{
  airTemp = dht.readTemperature();
  airHum = dht.readHumidity();

  DS18B20.requestTemperatures(); 
  soilTemp = DS18B20.getTempCByIndex(0); // Sensor 0 will capture Soil Temp in Celcius
             
  light = map(analogRead(ldrPIN), 1023, 0, 0, 100); //LDRDark:0  ==> light 100%  
  soilHum = map(analogRead(soilHumPIN), 1023, 0, 0, 100); 

}

/********* Conexao com TCP com Thingspeak *******/
void writeThingSpeak(void)
{

  startThingSpeakCmd();

  // preparacao da string GET
  String getStr = "GET /update?api_key=";
  getStr += statusChWriteKey;
  getStr +="&field1=";
  getStr += String(pump);
  getStr +="&field2=";
  getStr += String(lamp);
  getStr +="&field3=";
  getStr += String(airTemp);
  getStr +="&field4=";
  getStr += String(airHum);
  getStr +="&field5=";
  getStr += String(soilTemp);
  getStr +="&field6=";
  getStr += String(soilHum);
  getStr +="&field7=";
  getStr += String(light);
  getStr +="&field8=";
  getStr += String(spare);
  getStr += "\r\n\r\n";

  sendThingSpeakGetCmd(getStr); 
}

/********* Reset ESP *************/
void EspHardwareReset(void)
{
  Serial.println("Reseting......."); 
  digitalWrite(HARDWARE_RESET, LOW); 
  delay(500);
  digitalWrite(HARDWARE_RESET, HIGH);
  delay(8000);//Tempo necessário para começar a ler 
  Serial.println("RESET"); 
}

/********* Start communication with ThingSpeak*************/
void startThingSpeakCmd(void)
{
  EspSerial.flush();//limpa o buffer antes de começar a gravar
  
  String cmd = "AT+CIPSTART=\"TCP\",\"";
  cmd += "184.106.153.149"; // Endereco IP de api.thingspeak.com
  cmd += "\",80";
  EspSerial.println(cmd);
  Serial.print("enviado ==> Start cmd: ");
  Serial.println(cmd);

  if(EspSerial.find("Error"))
  {
    Serial.println("AT+CIPSTART error");
    return;
  }
}

/********* send a GET cmd to ThingSpeak *************/
String sendThingSpeakGetCmd(String getStr)
{
  String cmd = "AT+CIPSEND=";
  cmd += String(getStr.length());
  EspSerial.println(cmd);
  Serial.print("enviado ==> lenght cmd: ");
  Serial.println(cmd);

  if(EspSerial.find((char *)">"))
  {
    EspSerial.print(getStr);
    Serial.print("enviado ==> getStr: ");
    Serial.println(getStr);
    delay(500);//tempo para processar o GET, sem este delay apresenta busy no próximo comando

    String messageBody = "";
    while (EspSerial.available()) 
    {
      String line = EspSerial.readStringUntil('\n');
      if (line.length() == 1) 
      { //actual content starts after empty line (that has length 1)
        messageBody = EspSerial.readStringUntil('\n');
      }
    }
    Serial.print("MessageBody received: ");
    Serial.println(messageBody);
    return messageBody;
  }
  else
  {
    EspSerial.println("AT+CIPCLOSE");     // alert user
    Serial.println("ESP8266 CIPSEND ERROR: RESENDING"); //Resend...
    spare = spare + 1;
    error=1;
    return "error";
  } 
}
Code snippet #20Plain text
// Thingspeak  
String canalID1 = "375598"; //Actuator1
String canalID2 = "375599"; //Actuator2

#include <SoftwareSerial.h>
SoftwareSerial EspSerial(6, 7); // Rx,  Tx
#define HARDWARE_RESET 8

// Variables to be used with timers
long readTimingSeconds = 10; // ==> Define Sample time in seconds to receive data
long startReadTiming = 0;
long elapsedReadTime = 0;

//Relays
#define ACTUATOR1 10 // RED LED   ==> Pump
#define ACTUATOR2 12 // GREEN LED ==> Lamp
boolean pump = 0; 
boolean lamp = 0; 

int spare = 0;
boolean error;

void setup()
{
  Serial.begin(9600);
  
  pinMode(ACTUATOR1,OUTPUT);
  pinMode(ACTUATOR2,OUTPUT);
  pinMode(HARDWARE_RESET,OUTPUT);

  digitalWrite(ACTUATOR1, HIGH); //o módulo relé é ativo em LOW
  digitalWrite(ACTUATOR2, HIGH); //o módulo relé é ativo em LOW
  digitalWrite(HARDWARE_RESET, HIGH);

  EspSerial.begin(9600); // Comunicacao com Modulo WiFi
  EspHardwareReset(); //Reset do Modulo WiFi
  startReadTiming = millis(); // starting the "program clock"
}

void loop()
{
  start: //label 
  error=0;
  
  elapsedReadTime = millis()-startReadTiming; 

  if (elapsedReadTime > (readTimingSeconds*1000)) 
  {
    int command = readThingSpeak(canalID1); 
    if (command != 9) pump = command; 
    delay (5000);
    command = readThingSpeak(canalID2); 
    if (command != 9) lamp = command; 
    takeActions();
    startReadTiming = millis();   
  }
  
  if (error==1) //Resend if transmission is not completed 
  {       
    Serial.println(" <<<< ERROR >>>>");
    delay (2000);  
    goto start; //go to label "start"
  }
}

/********* Take actions based on ThingSpeak Commands *************/
void takeActions(void)
{
  Serial.print("Pump: ");
  Serial.println(pump);
  Serial.print("Lamp: ");
  Serial.println(lamp);
  if (pump == 1) digitalWrite(ACTUATOR1, LOW);
  else digitalWrite(ACTUATOR1, HIGH);
  if (lamp == 1) digitalWrite(ACTUATOR2, LOW);
  else digitalWrite(ACTUATOR2, HIGH);
}

/********* Read Actuators command from ThingSpeak *************/
int readThingSpeak(String channelID)
{
  startThingSpeakCmd();
  int command;
  // preparacao da string GET
  String getStr = "GET /channels/";
  getStr += channelID;
  getStr +="/fields/1/last";
  getStr += "\r\n";

  String messageDown = sendThingSpeakGetCmd(getStr);
  if (messageDown[5] == 49)
  {
    command = messageDown[7]-48; 
    Serial.print("Command received: ");
    Serial.println(command);
  }
  else command = 9;
  return command;
}

/********* Reset ESP *************/
void EspHardwareReset(void)
{
  Serial.println("Reseting......."); 
  digitalWrite(HARDWARE_RESET, LOW); 
  delay(500);
  digitalWrite(HARDWARE_RESET, HIGH);
  delay(8000);//Tempo necessário para começar a ler 
  Serial.println("RESET"); 
}

/********* Start communication with ThingSpeak*************/
void startThingSpeakCmd(void)
{
  EspSerial.flush();//limpa o buffer antes de começar a gravar
  
  String cmd = "AT+CIPSTART=\"TCP\",\"";
  cmd += "184.106.153.149"; // Endereco IP de api.thingspeak.com
  cmd += "\",80";
  EspSerial.println(cmd);
  Serial.print("enviado ==> Start cmd: ");
  Serial.println(cmd);

  if(EspSerial.find("Error"))
  {
    Serial.println("AT+CIPSTART error");
    return;
  }
}

/********* send a GET cmd to ThingSpeak *************/
String sendThingSpeakGetCmd(String getStr)
{
  String cmd = "AT+CIPSEND=";
  cmd += String(getStr.length());
  EspSerial.println(cmd);
  Serial.print("enviado ==> lenght cmd: ");
  Serial.println(cmd);

  if(EspSerial.find((char *)">"))
  {
    EspSerial.print(getStr);
    Serial.print("enviado ==> getStr: ");
    Serial.println(getStr);
    delay(500);//tempo para processar o GET, sem este delay apresenta busy no próximo comando

    String messageBody = "";
    while (EspSerial.available()) 
    {
      String line = EspSerial.readStringUntil('\n');
      if (line.length() == 1) 
      { //actual content starts after empty line (that has length 1)
        messageBody = EspSerial.readStringUntil('\n');
      }
    }
    Serial.print("MessageBody received: ");
    Serial.println(messageBody);
    return messageBody;
  }
  else
  {
    EspSerial.println("AT+CIPCLOSE");     // alert user
    Serial.println("ESP8266 CIPSEND ERROR: RESENDING"); //Resend...
    spare = spare + 1;
    error=1;
    return "error";
  } 
}

Schematics

Comments

Similar projects you might like

Arduino Atari Adaptor

Project tutorial by Dante Roumega

  • 6,725 views
  • 6 comments
  • 21 respects

Generating Audio with an Arduino and a Resistor Ladder DAC

Project showcase by 3 developers

  • 2,492 views
  • 5 comments
  • 7 respects

Humidity Measuring Molecule

Project showcase by KatjaNiggl

  • 1,220 views
  • 4 comments
  • 13 respects

Electroplating with Copper

Project tutorial by Ryan Gill

  • 6,588 views
  • 8 comments
  • 14 respects

I2C OLED Display Using Arduino/NodeMCU

by Tarantula3

  • 520 views
  • 2 comments
  • 9 respects

PWM Sound Synthesis

by 3 developers

  • 1,088 views
  • 2 comments
  • 3 respects
Add projectSign up / Login