Need an easy wireless sensor solution? We make the process simple with the ESP8266.
BUILD TIME: 2 HOURS
DIFFICULTY RATING: INTERMEDIATE
Once projects start to get complex, we makers tend to move from “do one thing and do it well” to “do lots of things that kind of work”! It’s very easy for a microprocessor project to get bogged down fairly quickly. While this may not always be an issue, if you are doing anything that is time sensitive, such as a data logger or real-time comparisons, it can create some difficulty. The issue here is that the traditional microprocessor environment is based on a single thread. It starts a process, executes it, completes it, and then moves on to the next task. While we can use tricks such as timers and interrupts, often you’ll find what we need is a second processor to “do one thing and do it well”.
THE BROAD OVERVIEW
This project is a way of outsourcing some of the tasks we require and easily getting the information back. There are several different ways to do this, but we will be using JSON. This allows us to not only use many sensors, but to also move beyond just the one type of microprocessor. All single microprocessors function in a similar way. They start, do function A, then B, then C, and so on. If B takes three times longer than A, then C just has to wait. But what if reading that data is critical, or getting an accurate reading takes some time? This network will allow you to set tasks and then work easily with the results, while your microprocessor can get on with the task at hand.
An example of this would be a car with constant 4WD. The Engine Control Unit (ECU) uses the throttle position to control the amount of power being generated.
Power is normally shared equally to each of the four wheels. However, if one wheel starts to lose grip, it will spin and lose traction creating a dangerous situation. The ECU will adjust the power that it’s driving to that wheel which has no grip, and increase the power to the wheels that do have traction. The process happens at such a fast rate that we would be unable to comprehend it.
If we were to construct this with a single microprocessor, we would have some serious issues on our hands. In this case, we have four wheels. They all need to be spinning at the same speed to indicate that the vehicle has equal traction at each wheel. To get this data, we need the rotational speed of each wheel. Let’s assume we have a magnet and hall effect sensor in place. As each rotation happens, we can count it. After one minute of counting, we will know the revolutions per minute (RPM) of that wheel. We then move onto the next wheel and count the number of times the magnet goes past. After one minute we will have the RPM. At this point, two minutes has elapsed, which is way too much time. We can count in smaller intervals and get a similar number, however, the more we decrease the sample period the less accurate the reading becomes. There are much better ways, such as using interrupts or timer functions, but then each microprocessor is only good at doing that one task, so we need to assign a microprocessor for each wheel. Then, each one can be constantly counting one wheel only, and we can go to each wheel to source its rotational speed information. We can then compare that data, and make decisions based on power delivery and how much each wheel requires.
HOW IT WORKS
At a high level, each sensor does some basic measuring or retrieving of data and loops continually until a master unit calls it to deliver the information that it is carrying. We can also query the slave, and execute a function that can turn the GPIO ports on or off.
The system is divided into the master control program (MCP) and remote sensor units (RSU). It has been designed for the RSUs to operate independently; however, you could easily make each unit talk to each other if you needed to negate the MCP. The MCP can be anything you want; for our example, we’ve written a basic GUI interface using Python. This software not only calls and retrieves data, but performs comparisons, and switches the inbuilt LEDs as required.
In our case, the RSU is designed around the ESP8266 so you should be able to compile this against any of that family. Using it with the ESP-01 creates a tiny footprint. The idea here is simple: set the RSU one simple task, do it well, then we ask it to provide us the information, so we can then make a decision.
Parts Required: | Jaycar | Altronics |
---|---|---|
1 x ESP01 | - | - |
1 x NodeMCU | - | - |
1 x 10k Pot | RP7510 | R2243 |
1 x DHT11 | XC4520 | - |
Libraries:
The BUILD
We will be using three different versions of this circuit to enable a small sensor network. As each is independent to one another, and we have used some simple sensors, you can base these around any ESP8266-based microcontrollers.
Our first circuit is the simplest, as it has nothing connected! In this case, we are using the ESP-01 and will just be using the built-in LED to indicate that we are switching the GPIO line.
The second circuit uses the NodeMCU, which is also based on the ESP8266 – specifically, the ESP-12E module. This circuit is designed to be our first traditional sensor. It uses the DHT11 to measure the temperature and humidity, before sending both pieces of data when requested by the MCP. We only need to connect the single data line of the sensor to get both values. Connect the negative of the sensor to the ground of the NodeMCU’s ground, then connect the positive of the sensor to the 3V3 line of the microprocessor. Be mindful that the ESP family of chips are 3.3V. Some sensors will work fine, while others may need to be run off a 5V system. We then connect the data line. On the Duinotech version, this is marked as “out”. To complete the circuit, we then connect this to the D4 pin of the nodeMCU/ESP12e. If you have already installed the DHT library feel free to test it.
Our final RSU is connected to a 10K potentiometer. This is used to simulate an analogue input, such as a strain gauge or other sensor that returns more than an “on” or “off” value. Place the outer pin of the potentiometer to the 3V3, the centre to the A0 and then the final pin to GND. On the NodeMCU this is located on the upper right-hand corner. Be mindful that the ESP-12e only has the one analogue input. Both the NodeMCU-based circuits also use the inbuilt LED.
THE CODE
The code here is designed to be somewhat generic enabling you to modify it to suit your needs. We have reflected this in the three different circuits. The first two are identical code, with the exception of commenting out the updating of the internal data. If we look at a high-level process flow, we start by setting up the WiFi server, create the variables, assign the interrupts then loop updates of any readings we need. When we call the RSU, we cause an interrupt. Depending on your call, our code will trigger an event and/or return a JSON object. All the objects contain at least one variable.
THE SETUP
ESP8266
This code is based upon the ESP8266 libraries and hardware. You will need to have the board libraries installed. To do this start Arduino and open the Preferences window. Enter http://arduino.esp8266.com/stable/package_esp8266com_index.json into the field named “Additional Board Manager URLs”. Open the Boards Manager from the Tools > Board menu and find the ESP8266 platform. Select the version you need from the drop-down box, then click the Install button. Once installed, make sure you select the ESP8266 board from Tools > Board menu. For the ESP-01 we used the generic profile and then NodeMCU V1 for the two NodeMCUs.
DHT
We have used the DHT library. It also requires the Adafruit Sensor library, both of which are included in the downloads.
WIFI
You will need the SSID and the password for your WiFi as a minimum. We have commented out the code to allow DHCP to automatically assign an IP, as you do not want this to change. If you are experimenting, then you will need to uncomment some sections of code, which is discussed later.
If you decide to allocate the IPs, which is what we recommend, then you will also need your local network or subnet and gateway addresses of the network that it will be running on.
THE JSON
JSON is a way of sharing data between devices using a shared syntax. JSON was originally designed for use on the internet as a way of allowing asynchronous data transactions between clients and servers. We can design and use this to allow for easy and verbose sharing of data between devices. To be honest, you could use bit-based transactions to communicate between devices, and it would save time, but by using JSON we open up our device to interface with any object, anywhere in the world that can read a JSON stream. MQTT could be another option here, and we would not discourage its use, but by using JSON and coupled with our network, we do not require a broker and have different control over the payload data.
In our code, we have three calls we can make to each RSU, and each one will return a JSON object. Adding more is quite easy, and we will explore this later in the article.
In our primary sketch we can turn the LED on and off, and we can get the temperature and humidity. We simply call our drive via its IP address. The simplest way to do this is to open the browser and put the following code in:
http://10.0.0.239/json
This will return the following JSON object. You can see here that we have the current temperature, current humidity and the current state of the LED. The temperature and humidity data come from the last sample that was made, which is where we find some of the speed in the system.
[{"temp":25.00, "humid":61.00, "led":"OFF"}]
Our next call controls the LED. In actual fact, it is switching the GPIO that is mapped to the built-in LED pin mapping. This is GPIO2 on the ESP-01, and this code simply checks the variable output and alternates its state. Whilst doing this, it toggles the built-in pin from high to low, or low to high, thus turning the LED on or off. This state is then returned in the JSON output.
http://10.0.0.239/led
[{"LED":"ON"}]
http://10.0.0.239/name
[{"NAME":"Sensor2","IP":"10.0.0.239"}]
Let’s explain the code here, in its simplest form. Our sketch is quite simple. We start out by including the libraries we require; in this case, it’s the two ESP8266 WiFi libraries, and the DHT temperature library. We have defined the input pin and type of DHT we will be using. In this case, we have the DHT11.
Note: this sensor only supports integers for the returning values. However, we do use floats for decimal place variable storage. If you do use the DHT22 that does support a finer resolution, then you will not need to change the code. The code below is truncated, so be sure to refer to the entire sketch to see the complete listing.
#include <ESP8266WiFi.h>
#include <ESP8266WebServer.h>
#include "DHT.h"
#define DHTPIN 2
#define DHTTYPE DHT11
DHT dht(DHTPIN, DHTTYPE);
ESP8266WebServer server(80);
Setting up our sensor, we need to change a few things. Firstly we need to give our sensor a name; in this case, we have called it “Sensor2”. It does not matter what you decide to call it, but this string is returned with the JSON object later, making it easy to identify.
Next, we need to enter the SSID and the password to your WiFi network. Now, the next three lines are optional; if you remove or comment them out, the device will be automatically allocated an IP address, but you will need to echo this to the serial port, in order to know where to find your device. By defining the IP, gateway and subnet, we ensure complete control over where in the network our sensor is located.
String SensorName = "Sensor2";
const char* ssid = "YOURSSID";
const char* password = "PASSWORD";
IPAddress ip(10, 0, 0, 239);
// comment out if static
IPAddress gateway(10, 0, 0, 138);
IPAddress subnet(255, 255, 255, 0);
We have also commented out all the serial outputs. This is due to the ESP-01 having the TX/RX lines sharing the GPIO lines. If you do some debugging using one of these units, then be mindful that things may not work as planned during the debugging process.
Next, we have part of the setup code. We start the WiFi service, and comment out the WiFi.config line if you have removed the IP definitions.
WiFi.config(ip, gateway, subnet);
//comment out if static
WiFi.begin(ssid, password);
//Connect to the WiFi network
The last part of the setup is where the magic happens! We associate the handler function with the path that our server will receive. We will execute a function when each of these URLs is called. For example, if we put http://10.0.0.239/json into our browser, we have defined or “linked” the /JSON to the handleJSON function that occurs later in the code.
server.on("/json", handleJSON);
server.on("/led", handleLED);
server.on("/name", handleNAME);
server.begin();
The main loop now commences, first checking the state of the incoming server. If there is an incoming packet, this only takes a fraction of a millisecond, and then it executes the requested function. This operates very much like an interrupt. Our code then goes on to do the “work” of the sensor. In this case, we are reading the values from the DHT temperature sensor. You will see here that we have commented out a delay statement. You may find that depending on your device and the readings, that it may be too fast to action the event processing correctly.
void loop() {
server.handleClient();
// Handling of incoming requests
// delay(500);
h = dht.readHumidity();
t = dht.readTemperature();
}
Finally, we have one of the three functions of our sketch here. The functions themselves follow a very similar process, each delivering a different JSON payload. In this case, we would consider this the primary JSON object for our sensor. It contains the temperature and humidity data, and it also returns the current state of the LED (i.e., whether it is on or off). They are all read from the three global variables we declared earlier. Note: we are not reading the sensor itself at this point, but the last reading it received. This makes the process much faster.
Our handler starts by checking to make sure that the received request has no data attached to it. Next, we declare the string “message”. This will be where we build our JSON string, ready for transmission. We start with the first part of the JSON string, which contains the label of “temp”; then add the variable “t” which contains the temperature data. We repeat this for the humidity before we check the output variable, which has the current state of our LED. If this string has closed off then we use the “Server. Send” function. We need to give our HTTP transmission a header, which in this case is “200”. We then specify that it is “text/plain” and the body of our transmission, which is the message containing the JSON string.
void handleJSON() {
// Handler for the JSON path
if (server.hasArg("plain") == false) {
// Check if body received
String message;
message += "[{"temp":";
message += t;
message += ", "humid":";
message += h;
message += ", "led":";
if (output == 0) {
message += ""OFF"";
} else {
message += ""ON"";
}
message += "}]";
server.send(200, "text/plain", message);
return;
}
}
ADDING YOUR MODIFICATIONS
This code has been designed to easily allow you to send a command to and from the sensor. If you want a new function on a particular call, just start a new function by using the following code:
void MyNewFunction(){
// your code here
}
Now return to the setup section of our sketch and add:
server.on("/mynewfunction", MyNewFunction);
Now whenever we access our device using the following code, our function will execute.
http://10.0.0.239/mynewfunction
Using JSON makes the whole system platform independent. We could easily use HTML5 or Python or anything that can process a JSON request. Just to make it interesting, we have built a very simple GUI using Python and Tkinter. This will run on any system that can run Python. We have set up the three devices that we discussed earlier, and will read from all of them. At it simplest, we can access and process the RSU from Python with the following: the first three lines will get the JSON data and print it; the final two lines show how to access the individual elements of the JSON object.
In Python we access each element using:
import requests
r = requests.get(url='http://10.0.0.38/json'’)
print(r.json())
print(r.json()[0]['temp'])
print(r.json()[0]['humid'])
We have added some complexity to Python to pull data from all three at once, but we will use the Python code to examine the data from all three units, and make decisions based on this information. This will work on a Raspberry Pi, which will give us access to the GPIO ports, thus enabling even more options.
Here we can see that Sensor1 is not receiving any data regarding the temperature. This is the ESP-01 and as such, it’s not connected. The sensor does have this information attached.
Sensor3 is our RSU with the potentiometer attached, and the bar graph is a representation of that data feed. We have then added some code in the background, which is running on a timer. As we check the value of the Sensor3, we check to see if it is greater then 128, then we switch the LED of Sensor2 on. If it’s less, it is switched off.
WHERE TO FROM HERE?
This distributed sensor network has a lot of potential to create some awesome interconnected peripherals. This would make a great dashboarding tool with some dynamic HTML5 programming. It will also make things such as wiring devices in hard-to-get-to places much easier.
The main limitation of this is that you need to have access to a WiFi access point. The simple way around this, if you are in a situation with a router, would be to setup the Raspberry Pi or Arduino device that is the MCP, to also act as an access point.