IoT: What Can You Do with Your Stack?

Use your own stack to build an IoT application

Before we start, we need to know what exactly IoT is. Let’s use the definition from Oracle’s website:

The Internet of Things (IoT) describes the network of physical objects—“things”—that are embedded with sensors, software, and other technologies for the purpose of connecting and exchanging data with other devices and systems over the internet.

This post intends to present you with the possibilities of development in the field of Internet of Things. The goal is to leverage your pre-existing knowledge, no matter your stack or main programming language.

At the end of the reading, backend developers will be able to create an API capable of receiving data from a temperature sensor, and do something with the data on the server-side. If you are a frontend developer, you will not be left behind; we will make an application capable of reading the sensor data using MQTT.

Presenting the hardware

In this post, we will use two main components: a development board and a sensor. To do the assembly of the circuit, we will need two other components that will be used in both projects: breadboard and jumpers.

The breadboard serves to make the connection between circuit components. Inside the plastic part, we have metallic rails to connect them all, as you can see in the image below. If you want more details on how it works, you can read this very complete tutorial article by SparkFun.

Jumpers are wires that serve to make a connection between metal rails that have no contact with each other. As the name suggests, it jumps from one bar to another on the breadboard.

Here is the list of all componentes we will use:

  • NodeMCU ESP32 (Development board);
  • DHT-11 Module (Sensor);
  • Breadboard;
  • Jumpers.

You can find them all for less than $10

ESP

So let’s talk a little about ESP. It’s a microcontroller, modules, and SoCs ecosystem made by Espressif. It’s very notable for the cost-benefit it delivers, standing out among other development boards such as Arduino and offering connectivity possibilities for creating IoT systems.

For this post, we will use ESP32, which is a feature-rich microcontroller with integrated 2.4GHz Wi-Fi and Bluetooth Low-Energy plus a dual-core processor. To make it easier, we will use a development board with the built-in ESP32 microcontroller: that board is the NodeMCU ESP32.

The development board has several pins of different categories, including:

  • Power: 3V3 provides a voltage of 3.3V, and GND is the ground of the circuit;
  • GPIO (General Purpose Input/Output): These are the pins responsible for receiving data from our sensors or sending commands to our actuators;
  • RX/TX: Are the data transmission pins in Serial format.

NodeMCU ESP32

DHT

DHT-11 is a very cheap and simple to use humidity and temperature sensor. It has a temperature range from 0 to 50ºC, with ±2% accuracy and relative humidity range from 20 to 90%, with ±5% accuracy. We will use a module that facilitates the use of this sensor, as it has only 3 connection terminals, discarding the need to use a resistor.

Hands on

We will use the Arduino IDE and set it up to work with ESP using this guide.

You can check out the complete code and projects in this GitHub repo.

Arduino IDE

A project in Arduino IDE is made of C++ code and the Arduino library, which is the base of Arduino functions. Code files use the .ino extension. Every project compiled in it will have the following structure:

Arduino IDE

Inside of setup() is code that will run only once when starting the board. Generally, pin definition, initialization of serial communication (we will talk about that later), etc. On the other hand, loop() corresponds to a while(true) loop that will run repeatedly until the board is turned off.

The next important step is to define the board that we will work on, so go to Tools > Board and select ESP32 Dev Module.

To debug our project, we will use Serial Monitor, which is the log console of the board present in Arduino IDE. To carry out this communication, we need the board and the software to communicate at the same speed, which we call baud rate. Baud rate is the number of bits per second that can be exchanged. For ESP, the default is 115200. During the post, we’ll see how Serial Monitor is used.

Serial Monitor

Base circuit

So, let’s work. First, we have to create our base circuit, which will be used by both the backend and frontend projects. The diagram of the circuit is depicted below:

Circuit

The module’s signal terminal (marked with an S next to it) must be connected to a GPIO pin. In this case, we will use D21. The positive supply terminal (the middle one) must be connected to the 3V3 pin of ESP32. Finally, the negative supply terminal (marked with a minus sign next to it) must be connected to the circuit ground, marked with the GND pin on our development board.

The signal terminal needs to be placed in a GPIO pin because we will communicate with the sensor as follows (the library will handle this, so we will not go into further detail):

DHT11 Behavior

This behavior is not possible on a power pin as it provides constant voltage. The VCC in the graph is the voltage that we are going to apply to the positive power pin – in this case 3.3V – because it is the maximum that the ESP32 board offers us in its terminals. However, it is possible, for example, to connect to an external battery, as long as the circuit grounds are connected. If you check the specifications of the sensor, you can see that it operates between 3 and 5V, so we will have no problem feeding it into the 3V3 port. GND is the 0v reference of our circuit, so we cannot change that pin.

The assembled circuit will look like this:

Assembled Circuit

Backend project (ESP project + API project)

In our first IoT project, we will create a program in ESP32 capable of sending temperature and humidity data gleaned from the DHT sensor to an external API to be manipulated and used in different ways.

ESP project

We will need the ArduinoJson, SimpleDHT, and EasyHTTP libraries, but that’s easy to include. Go to Sketch > Include Library > Manage Libraries..., search and install the libraries and then restart your IDE.

  • ArduinoJson is a JSON serializer library for Arduino;
  • SimpleDHT is a library to use with the DHT-11 sensor;
  • EasyHTTP is a library that I’ve been working on recently. It is in very early stage and inspired by axios. Feel free to contribute to the official repository.

First, we will include the libraries:

#include <EasyHTTP.h>
#include <ArduinoJson.h>
#include <SimpleDHT.h>

[...]

To test if the libraries were imported successfully, try the Compile button at the top of the IDE Compile button and check if it runs without errors.

Now let’s declare our variables:

[...] // importing libraries

char* ssid = "your network ssid";
char* password = "your network password";

String baseURL = "http://yourapiurl.com";

EasyHTTP http(ssid, password);
SimpleDHT11 dht(21);

These are our network variables and instances. For the constructor of the dht object (of SimpleDHT11 type), we are passing the GPIO pin that the signal terminal is connected to (in this case, it’s D21, which is represented by the 21 integer) and for EasyHTTP we are passing the network information that will connect the board to the internet.

Inside of setup() let’s initialize our Serial communication and WiFi connection.

[...] // variables and instances

void setup() {
    Serial.begin(115200);
    http.connectWiFi();
    http.setBaseURL(baseURL);
}

The argument passed to Serial.begin() is the baud rate used to communicate with the board. In the case of ESP32, we use 115200 baud.

After that, we connect to WiFi and set our API URL using the http EasyHTTP instance variable.

Now let’s define the program loop:

[...] // setup()

void loop() {
    DynamicJsonDocument doc(32);
    String payload = "";

    byte temperature = 0;
    byte humidity = 0;

    dht.read(&temperature, &humidity, NULL);

    doc["temperature"] = temperature;
    doc["humidity"] = humidity;
    serializeJson(doc, payload);

    String response = http.post("/sensor", payload);
    Serial.println(response);

    delay(3000);
}

First, we create an instance of the ArduinoJson library (DynamicJsonDocument), a string to store our JSON payload, and variables of the byte type (integers from 0 to 255) to store our sensor values.

Then we use the read() method from the dht object library to read our sensor values and store them into their respective variables.

After reading the values from the sensor, we assign them to the doc object and then use serializeJson() to store the JSON in the payload string variable.

And finally, we make a POST request to /sensor route with the payload as our body, store the response in a variable and print it out to our IDE’s Serial monitor.

delay(3000) is an Arduino method to wait for 3000 milliseconds before executing the next line (in this case, return to the top of loop())

API project

Now we will build an API to get the sensor data that is sent via HTTP from our ESP board. For this, I will use NodeJS, but feel free to use any language or stack you want.

Create a new folder and initialize the project using npm:

$ npm init -y

Install express, also using npm:

$ npm install express

Create an index.js file in your folder and let’s code.

// index.js
const express = require('express');
const app = express();

First, we will import the express module and assign it to app.

[...]

app.use(express.json());

app.post('/sensor', (req, res) => {
  console.log(req.body);
  res.json({ ok: true });
});

Declare that we will work with the JSON format and create a route called sensor that will receive the request body and log it to the console, returning to our client a response with a simple object. However, keep in mind that you can do whatever manipulation you want with this data, for example, saving it to a time series database.

app.listen(3000, () => {
  console.log("Server running on port 3000");
});

And finally, just listen to port 3000 and voi-la!

Running the final project

First, let’s run our API. For that, execute the following command from your project directory:

$ node index.js

Then, compile the ESP code and send it to our board. To do that, connect the board via USB to your PC and in your IDE go to Tools > Board and select ESP32 Dev Module. So, go to Tools > Port and select the USB port that your board is connected to.

Now just click on the upload button Upload button or press CTRL + U

With the board still connected, open Serial Monitor to debug by clicking on its button Serial monitor button or pressing CTRL + SHIFT + M, and select 115200 baud rate.

That’s it! Your first IoT project should be running correctly by now.

Frontend project (ESP project + Mobile app project)

Now we are going to make a mobile application capable of communicating directly with ESP, receiving data from the sensors and showing it to the user.

Communication will be done through MQTT, which is a messaging protocol based on TCP/IP and optimized for sensors and small mobile devices, which is exactly what we need.

It works based on the model of publishing (sending a message to a topic) and subscribing (listening to the messages of a topic). The messages are sent to a broker, which intermediates the parties and is responsible for managing publications and subscriptions. A topic acts as a route to the MQTT.

Basically, our ESP board will publish the values read from the sensor to a topic, and the application will have a subscription on the same topic in order to receive the information.

ESP project

We will need the PubSubClient, SimpleDHT, and EspMQTTClient libraries, but that’s easy. Go to Sketch > Include Library > Manage Libraries..., search and install the libraries and then restart your IDE.

  • PubSubClient is a dependency of EspMQTTClient. It provides publish and subscribe functionality to MQTT in Arduino.
  • EspMQTTClient is a library to encapsulate the handling of WiFi and MQTT connections of an ESP.

First, we will include the libraries.

#include <WiFi.h>
#include "EspMQTTClient.h"
#include <SimpleDHT.h>

[...]

WiFi is a library automatically imported along with ESP setup.

To test if the libraries were included with success, try the Compile button at the top of IDE Compile button and check if it runs without errors.

Now, we will instantiate our objects.

SimpleDHT11 dht(21);

EspMQTTClient client(
    "Your WiFI SSID",
    "Your WiFi Password",
    "broker.hivemq.com", // MQTT Broker
    "iot-your-stack", // Client name
);

For this post, we will use the HiveMQ broker, but it is not recommended for final projects due to it being a public broker where other people can interfere with our project. For the client name, you can use whatever you want. In our example, we are using iot-yout-stack

In our setup(), the only thing we will do is begin the Serial communication.

[...] // libraries instances

void setup() {
  Serial.begin(115200);
}

Now we will create a function needed for EspMQTTClient called onConnectionEstablished().

[...] // void setup()

void onConnectionEstablished() {
  Serial.println("Connected to MQTT");
}

In the main loop(), let’s read our sensor and publish the temperature data to the MQTT Broker at every 5 seconds:

[...] // void onConnectionEstablished()

void loop() {
  byte temperature = 0;
  byte humidity = 0 ;
  dht.read(&temperature, &humidity, NULL);

  client.publish("iot-your-stack/temperature", String(temperature));
  client.publish("iot-your-stack/humidity", String(humidity)); 

  delay(5000);
  client.loop();
}

We are using the publish method to send a message to our MQTT topic, which corresponds to our sensor value. delay is a built-in function to wait (in milliseconds) to run the next line. client.loop() is defined in the EspMQTTClient documentation.

Mobile app project

Expo is a tool to facilitate the development of React Native applications. To create the mobile project, let’s use Expo bare workflow, which allows us to work with native code. However, you can use managed workflow as well, where Expo takes care of the native part for you. It is important that you think about the scalability of your application before choosing between bare or managed workflow.

$ expo init espcontrol

Expo options

Use the down arrow and select minimal with the Enter key.

With the project created, let’s add our dependences using npm. We will need React Native Async Storage and React Native MQTT

$ npm install @react-native-async-storage/async-storage react_native_mqtt --save

Now start by writing the following code in App.js:

import { StatusBar } from 'expo-status-bar';
import React, { useState } from 'react';
import { StyleSheet, Text, View } from 'react-native';

export default function App() {
  const [temperature, setTemperature] = useState('X');
  const [humidity, setHumidity] = useState('X');

  return (
    <View style={styles.container}>
      <StatusBar style="auto" />
      <Text style={styles.text}>Ambient Temperature: {temperature}°C</Text>
      <Text style={styles.text}>Relative Humidity: {humidity}%</Text>
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#fff',
    alignItems: 'center',
    justifyContent: 'center',
  },
  text: {
    fontSize: 24,
    marginTop: 10,
  }
});

As you can see, it’s just a normal React native function component with two states to store our sensor values received from ESP, and two labels to show the information with some style for View and Text.

Now we have to import the necessary libraries, including useEffect from react:

import React, { useEffect, useState } from 'react';
import AsyncStorage from '@react-native-async-storage/async-storage';
import init from 'react_native_mqtt';

Finally, let’s write the remaining code inside the component’s function. The code must go after the state declaration and before returning the component:

    [...] // state declaration

    function onConnect(client) {
        client.subscribe('iot-your-stack/temperature');
        client.subscribe('iot-your-stack/humidity');
      }

    function onMessageArrived(message) {
        switch(message.topic) {
            case 'iot-your-stack/temperature':
                setTemperature(message.payloadString);
                break;
            case 'iot-your-stack/humidity':
                setHumidity(message.payloadString);
                break;
        }
    }

    useEffect(() => {
        init({
            size: 10000,
            storageBackend: AsyncStorage, // If we do not use storage here, a warning will be shown, but it will not affect the operation of the application
            defaultExpires: 1000 * 3600 * 24,
            enableCache: true,
            reconnect: true,
            sync : {}
        });

        const client = new Paho.MQTT.Client('broker.hivemq.com', 8000, 'uname');
        client.onMessageArrived = onMessageArrived;
        client.connect({ onSuccess: () => onConnect(client), useSSL: false });
    }, []);

    [...] // return component

It’s not hard to understand what’s going on here. First, we create the onConnect function to subscribe our MQTT Client to the topics that our ESP board publishes to, when the app successfully connects to the broker. Next, there’s the onMessageArrived function, which receives messages and sets the values from the sensor to our state. In useEffect, we call the init function imported from react_native_mqtt according to the documentation, instantiate our MQTT client, and assign our callbacks to it.

You also can try a demo using Expo snack.

Running the final project

Let’s run it!

First, let’s compile the ESP code and send it to our board.

To do that, connect the board via USB to your PC, and in your IDE go to Tools > Board and select ESP32 Dev Module.

Go to Tools > Port and select the USB port that your board is connected to.

Now just click on the upload button Upload button or press CTRL + U

If you want to debug your code with the board still connected, open Serial Monitor by clicking on the Serial monitor button or press CTRL + SHIFT + M and select 115200 baud rate.

For the Android app, we have to build and then start Metro Bundler:

$ npm run android
$ npm start

Done!

Conclusion

Internet of Things gives us many possibilities to work, regardless of which stack is your main one. Some alternatives were presented in this post, but feel invited to explore new possibilities and integrate with what you already know, or even take the chance to learn something new.

Enjoy the fact that you can work with almost any language. For example, use a bot library for Discord or Telegram with NodeJS and make a project to control an actuator capable of controlling the lights in your house; a project using Python to control something in your computer; a robotics project; a website using your favorite framework.

Let your imagination handle the possibilities and create something!

We want to work with you. Check out our "What We Do" section!