The ESP8266 is one of the most wired WiFi modules in use by makers of IoT solutions today. It stands out for its low cost, small size and ease of integration with other solutions. Its use is simple: to make it consume an external service just write a few lines of code and you're done! You can already receive data, for example, from a weather monitoring service. However, doing this direct integration with a third party service can cause some work if you want to change the provider or the parameters provided by it change.
The intent of this post is to show that creating an intermediate API between ESP8266 and external services can be an option to avoid some inconvenience, including design changes and changes made by the service provider. As an example application, we will develop an intermediate API that consumes weather data from two different vendors.
Among the many existing ESP modules, we will use in this post the ESP8266 NodeMCU ESP-12. This module is a board that has the ESP8266 built in and has a USB / Serial interface, which greatly facilitates device programming via USB. To learn more about NodeMCU and how to program it, visit the post How to Program NodeMCU with Arduino IDE.
Do you know what an API is?Application Programming Interface, or API, is a set of programming standards that allows building applications. It is basically a system that you use to integrate into a larger system. For example, suppose you are building an address registration form and would like that after completing the zip code all other fields, Street / City / Neighborhood, would be completed automatically. Well, there is a system called ViaCep that informing the street zip code it returns the data of the street, city, neighborhood and other related. It would be interesting to make your application communicate with ViaCep, right? ViaCep provides a service, an API, that allows you to make these queries directly from your system and thus fill in the fields you want.
ProblemTo illustrate our problem with using an ESP8266 API we will present a conventional application intended to consume a city's climate data.
The truth is that there is nothing wrong with this implementation. But imagine that one day you want to change the API provider (who makes the service available), the Apikey (data for validating access to the service) access or some data that the API expects. Or, for some reason, the response changes and your code doesn't know how to handle the response. We will have to change the firmware, correct?
In these cases the amount of work can vary greatly as it will depend on several factors. But don't you agree that changing the firmware due to changes beyond our control is not a little uncomfortable?
SolutionThe suggestion to work around this problem is to develop your own API. This would be responsible for communicating with one or more external APIs, formatting the data and making it available the way your firmware expects. This way, you can choose which API you want to use, either for cost or quality of service, and work out the data received according to your business rules and avoid making changes to the firmware. The image below represents the proposal.
To exemplify the proposal, I developed an NodeJs API that consumes weather data from two vendors, Open Weather and AccurWeather. These APIs provide weather data from various cities around the world. To activate the account and get the token / appId for use just follow the very complete documentation.
API DevelopmentLet's start by building our API on NodeJs.
Since you have already implemented the basics of the API like server, route and controller, we will create our model. It will be the answer structure for our firmware. Our model has only the city name, temperature and humidity.
WeatherModel.Js
class Wheater {
constructor (city, temperature, humidity) {
this.city = city;
this.temperature = temperature;
this.humidity = humidity;
}
}
module.exports = Wheater;
Now our services will be created, there are two: one for OpenWeather and another AccurWeather.
AccurWeatherService.js
const axios = require ('axios');
const fahrenheitToCelsius = require ('fahrenheit-to-celsius');
const Weather = require ("../ models / weatherModel");
const {API_KEY_ACCUR_WEATHER, URL_ACCUR_WEATHER} = require ('../../ config');
exports.getWeather = () => {
let token = API_KEY_ACCUR_WEATHER;
let url = URL_ACCUR_WEATHER;
return axios.get(url.concat (token))
.then (response => {
let weather = new Weather ("Belo Horizonte",
parseInt (fahrenheitToCelsius(response.data.DailyForecasts [0] .Temperature.Maximum.Value)), 0);
return weather;
}).catch (error => {
console.log (error);
});
}
OpenWeatherMapService.js
const axios = require('axios');
const kelvinToCelsius = require('kelvin-to-celsius');
const Weather = require("../ models / weatherModel");
const {
API_KEY_OPEN_WEATHER,
URL_OPEN_WEATHER
} = require('../../ config');
exports.getWeather = () => {
let token = API_KEY_OPEN_WEATHER;
let url = URL_OPEN_WEATHER;
return axios.get(url.concat(token))
.then(response => {
let code = response.data.cod;
if (code == 200) {
let weather = new Weather(response.data.name,
parseInt(kelvinToCelsius(response.data.main.temp)),
response.data.main.humidity);
return weather;
}
})
.catch(error => {
console.log(error);
});
}
To determine which provider will be used we will pass the name by parameter in our function, thus will be instantiated the desired service.
The code is in the class below:
WeatherService.js
const weatherFactory = require ("./ weatherFactory");
exports.getWeather = () => {
return weatherFactory ("open_weather_map");
}
In the controller we import the WeatherService class and in it we define that we want to get the data from the AccurWeather API. At this point, when the call arrives at our controller, it will direct you to the chosen service and wait for the answer. If a valid answer is obtained, the city climate data will be used to format a response that contains only the city name, temperature and humidity. If there is a communication failure or error in the Accur API response, a specific response will be created stating that the information could not be obtained.
WeatherController.js
'use scrict'
const service = require ('../ service / weather');
const express = require ('express');
const router = express.Router ();
router.get ('/ temperatures', service.getWeather);
module.exports = router;
Okay, our API is implemented. Run and verify the result, Postman will help us in this part.
Initially, we are using an LCD display, so we should import the LiquidCrystal library.
The full source code is here:
#include <LiquidCrystal_I2C.h>
#include <ESP8266WiFi.h>
#include <WiFiClientSecure.h>
#include <ESP8266HTTPClient.h>
#include <ArduinoJson.h>
#include <Wire.h>
LiquidCrystal_I2C lcd (0x27, 16, 2);
const char * ssid = "YOUR_SSID";
const char * password = "NETWORK PASSWORD";
degree byte [8] = {B00001100,
B00010010,
B00010010,
B00001100,
B00000000,
B00000000,
B00000000,
B00000000,};
void setup () {
// set up serial communication, LCD
Serial.begin (115200);
WiFi.begin (ssid, password);
Wire.Begin (D2, D1);
lcd.begin (20,4);
lcd.backlight ();
lcd.home ();
// Create custom character with degree symbol
lcd.createChar (0, degree);
// clear the screen and write the initial texts
lcd.clear ();
lcd.setCursor (1,1);
lcd.print ("SSID:");
lcd.setCursor (7.1);
lcd.print (ssid);
lcd.setCursor (1,2);
lcd.print ("Connecting ...");
// Check if esp is connected to the network, otherwise try every 2 sec.
while (WiFi.status ()! = WL_CONNECTED) {
delay (2000);
Serial.println (WiFi.status ());
}
}
void loop () {
// Check if esp is connected
if (WiFi.status () == WL_CONNECTED) {
// create http request by passing api node URL
HTTPClient http;
http.begin ("URL_DA_API");
int httpCode = http.GET ();
if (httpCode> 0) {
// differing buffer size for json object
const size_t bufferSize = JSON_OBJECT_SIZE (3);
// parsing json for a JsonObject
DynamicJsonBuffer jsonBuffer (bufferSize);
JsonObject & root = jsonBuffer.parseObject (http.getString ());
// loading values into variables
const char * temp = root ["temperature"];
const char * city = root ["city"];
const char * humidity = root ["humidity"];
lcd.clear ();
lcd.setCursor (0.0);
lcd.print (city);
// creating the string that will display temperature and humidity data
String message = "Temperature:";
message + = temp;
lcd.setCursor (0.2);
lcd.print (message);
// Shows the degree symbol formed by the array
lcd.write ((byte) 0);
message = "Humidity:";
message + = humidity;
message + = "%";
lcd.setCursor (0,3);
lcd.print (message);
}
// closing the connection
http.end ();
}
delay (60000); //bad praticle heheheh :/
}
In the setup, we started theESP8266 connection to the WiFI network by entering the SSID and password. We then set the LCD to perform the write operations.
In the loop, we check if the connection to the WiFi network is valid, if so, we make the request to our API and wait for the result. The answer comes in Json format, so we should deserialize to an object. At this time we use the ArduinoJson library, it allows to define the buffer size and parse the received json for an object or array.
After the deserialization process we can extract the data from the object, remembering that the same names that were defined in the Node API model should be used at this point. We then write these values on the LCD display and end the HTTP connection.
This is it guys, I hope this suggestion is helpful to you. There are several ways to implement this, and as I said at the beginning, there is no right way, but using good practices makes our lives a lot easier to maintain and maintain code.
Comments
Please log in or sign up to comment.