Como utilizar uma API no ESP8266 NodeMCU: boas práticas para integrações 13

O ESP8266 é um dos módulos com conexão WiFi mais utilizados por makers em soluções de IoT nos dias de hoje. Destaca-se pelo baixo custo, tamanho reduzido e a facilidade de integração com outras soluções. A sua utilização é simples: para fazê-lo consumir um serviço externo basta escrever algumas linhas de códigos e pronto! Você já consegue receber dados, por exemplo, de um serviço de monitoramento do clima. Entretanto, fazer está integração direta com um serviço de terceiro pode causar algum trabalho caso queira fazer a troca do provedor ou os parâmetros fornecidos por ele se modifiquem. A intenção deste post é mostrar que criar uma API intermediária, entre o ESP8266 e serviços externos, pode ser uma opção para evitar alguns transtornos, entre eles, mudanças no projeto e alterações feitas pelo provedor do serviço. Como exemplo de aplicação, desenvolveremos uma API para o ESP8266 que consome dados de clima de dois fornecedores diferentes.

Imagem 1- Uso de uma API no ESP8266

Dentre os diversos módulos ESP existentes, vamos utilizar neste post o ESP8266 NodeMCU ESP-12. Este módulo é uma placa que tem o ESP8266 embutido e possui uma interface USB/Serial, o que facilita bastante o programação do dispositivo via USB. Para saber mais sobre o NodeMCU e como programá-lo, acesse o post Como programar o NodeMCU com IDE Arduino.

Materiais Necessários

Você sabe o que é uma API?

Application Programming Interface, ou API é um conjunto de padrões de programação que permite a construção de aplicativos. Basicamente é um sistema que você utiliza para integrar a um sistema maior. Por exemplo, imagine que você esteja construindo um formulário de cadastro de endereço e gostaria que após preencher o CEP todos os outros campos, Rua/Cidade/Bairro, fossem preenchidos automaticamente. Bom, existe um sistema chamado ViaCep que informando o CEP da rua ele retorna os dados da Rua, cidade, bairro e outros relacionados. Seria interessante fazer com que a sua aplicação se comunicasse com o ViaCep, certo? O ViaCep disponibiliza um serviço, uma API, que permite que você faça estas consultas diretamente pelo seu sistema e assim preenchendo os campos que você deseja.

Uso de uma API no ESP8266 NodeMCU – Problema

Para exemplificar nossa problemática com o uso de API em um ESP8266, apresentaremos uma aplicação convencional cuja intenção é consumir dados climáticos de uma cidade.

Imagem 2 - Problema

A verdade é que não há nada de errado nesta implementação. Mas imagine que um dia você queira mudar o provider da API (quem disponibiliza o serviço), o Apikey (dado para validação de acesso ao serviço) de acesso ou algum dado que a API espera. Ou ainda, que por algum motivo, o response seja alterado e seu código não saiba como lidar com a resposta. Vamos ter que mudar o firmware, correto?

Nestes casos a quantidade de trabalho pode variar muito, pois irá depender de diversos fatores. Mas você não concorda que ficar alterando o firmware devido a mudanças que estão fora do nosso controle não é um pouco desconfortável?

Uso de uma API no ESP8266 NodeMCU – Solução

A sugestão para contornar este problema é desenvolver a sua própria API. Esta seria responsável por comunicar com uma ou várias API’s externas, formatar os dados e disponibilizá-los da maneira que seu firmware aguarda. Desta forma, você pode optar por qual API vai querer utilizar, seja por custo ou qualidade do serviço, e trabalhar os dados recebidos de acordo com suas regras de negócios e evitar realizar mudanças no firmware. A imagem abaixo representa a proposta.

Imagem 3 - Uso de uma API no ESP8266

Para exemplificar a proposta, desenvolvi uma API em NodeJs que consome dados de clima de dois fornecedores, o Open Weather e o AccurWeather. Estas API’s fornecem dados climáticos de diversas cidades do mundo. Para ativar a conta e obter o token/appID para utilização basta seguir a documentação que é bem completa.

Desenvolvimento da API intermedária

Vamos começar construindo nossa API em NodeJs. Se você não tem familiaridade com o Node, pode começar a estudar por este post “Criando uma API Node em 10 passos com Express.js”.

Tendo em vista que você já implementou o básico da API como o server, rota e controller, criaremos o nosso modelo. Ele será a estrutura da resposta para o nosso firmware. O nosso modelo possui apenas o nome da cidade, temperatura e umidade.

WeatherModel.Js

class Wheater {
    constructor(city, temperature, humidity){
        this.city = city;
        this.temperature = temperature;
        this.humidity = humidity;
    }
}
module.exports = Wheater;

Agora serão criados os nossos serviços, são dois: um para o OpenWeather e outro AccurWeather.

AccurWeatherService.js

const axios = require('axios');
var 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);
        });
}

Para determinar qual fornecedor será utilizado passaremos o nome por parâmetro na nossa função, assim será instanciado o serviço desejado.

O código está na classe abaixo:

WeatherService.js

const weatherFactory = require("./weatherFactory");

exports.getWeather = () => {
    return weatherFactory("open_weather_map"); 
}

No controller importamos a classe WeatherService e nela definimos que desejamos obter os dados da API AccurWeather. Neste instante, quando a chamada chegar ao nosso controller, ele irá direcionar para o serviço escolhido e aguardará a resposta. Caso seja obtido uma resposta válida, os dados do clima da cidade serão usados para formatar uma resposta que contenha apenas o nome da cidade, temperatura e umidade. Se ocorrer alguma falha na comunicação ou erro na resposta da API do Accur, será criada uma resposta específica informando que não foi possível obter as informações.

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;

Pronto, nossa API está implementada. Execute e verifique o resultado, o Postman irá nos ajudar nesta parte.

Imagem 4 - Uso de uma API no ESP8266

Código fonte do para uso da API no ESP8266 NodeMCU

Inicialmente, estamos utilizando um display LCD, logo devemos importar a library LiquidCrystal.

O código fonte completo está aqui:

#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 = "SEU_SSID";
const char * password = "SENHA_DA_REDE";
byte grau[8] ={ B00001100,
                B00010010,
                B00010010,
                B00001100,
                B00000000,
                B00000000,
                B00000000,
                B00000000,};
                
void setup() {

  //configura a comunicação serial, LCD 
  Serial.begin(115200);
  WiFi.begin(ssid, password);

  Wire.begin(D2, D1); 
  lcd.begin(20, 4);

  lcd.backlight();
  lcd.home();
  //Cria o caractere customizado com o simbolo do grau
  lcd.createChar(0, grau);
  

  //limpa o tela e escreve os textos iniciais 
  lcd.clear();    
  lcd.setCursor(1,1);
  lcd.print("SSID: ");

  lcd.setCursor(7,1);
  lcd.print(ssid);
    
  lcd.setCursor(1,2);
  lcd.print("Conectando...");  

  //Verifica se o esp está conectado na rede, caso contrário realiza a tentaiva a cada 2 seg.
  while (WiFi.status() != WL_CONNECTED) {  
    delay(2000);
    Serial.println(WiFi.status());
  }
}

void loop() {

  // Verifica se o esp está conectado
  if (WiFi.status() == WL_CONNECTED) {  
    
    //cria a requisição http passando o URL da api node
    HTTPClient http;
    http.begin("URL_DA_API");    
    int httpCode = http.GET();    
    if (httpCode > 0) {      

      //difinindo o tamanho do buffer para o objeto json
      const size_t bufferSize = JSON_OBJECT_SIZE(3);
    
      //realizando o parse do json para um JsonObject
      DynamicJsonBuffer jsonBuffer(bufferSize);
      JsonObject & root = jsonBuffer.parseObject(http.getString());

      //carregando os valores nas variaveis 
      const char * temp = root["temperature"];
      const char * city = root["city"];
      const char * humidity = root["humidity"];

      lcd.clear();
      lcd.setCursor(0, 0);
      lcd.print(city);
     
      //criando a string que irá exibir os dados da temperatura e umidade
      String message = "Temperatura: ";
      message += temp;
      lcd.setCursor(0, 2);
      lcd.print(message);
      //Mostra o simbolo do grau formado pelo array
      lcd.write((byte)0);      
      message = "Umidade: ";
      message += humidity;
      message += "%";
      lcd.setCursor(0, 3);
      lcd.print(message);
    }    
    //fechando a conexão
    http.end();
  }
  delay(60000);
}

No setup, iniciamos a conexão do ESP8266 com a rede WiFI informando o SSID e a senha (é necessário alterar tais parâmetros com as informações da sua rede WiFi). Em seguida, configuramos o LCD para realizar as operações de escrita.

No loop, verificamos se a conexão com a rede WiFi é válida, se sim, realizamos o request para a nossa API e aguardamos o resultado. A resposta vem no formato Json, logo devemos deserializar para um objeto. Neste momento utilizamos a library ArduinoJson, ela permite definir do tamanho do buffer e realiza o parse do json recebido para um objeto ou array.

Após o processo de deserialização podemos extrair os dados do objeto, lembrando que os mesmos nomes que foi definido no modelo da API Node devem ser utilizados neste ponto. Em seguida, escrevemos estes valores no display LCD e encerramos a conexão HTTP.

Conclusão

É isto pessoal, espero que esta sugestão seja útil para vocês. Existem diversas formas para esta implementação e como eu disse no início, não existe o modo certo, mas utilizar boas práticas torna nossa vida bem mais fácil para manter a manutenção e evolução do código.

Gostou? Ajude-nos a melhorar o Blog comentando abaixo sobre este tutorial.

Faça seu comentário

Acesse sua conta e participe

13 Comentários

  1. estamos avançando:

    “Criando uma API Node em 10 passos com Express.js”.
    Ficou bem explicado em termos de pastas e distribuição dos arquivos de mcv e o app final.

    mesmo dando pau, o github salvou:

    git clone https://github.com/programadriano/node-express.git e ja éra. rodou blz!!!

    Faltou fazer o mesmo na hora de implementar a aplicação arduino. Aliás,deveria ser padrão todo código e solução arduino no github:

    Abs

  2. Olá, boa noite.
    Vocês tem algum exemplo de como exibir as redes wifi no LCD para que o usuário possa selecionar a rede e informar a senha ao invés de estar hardcode?
    Obrigado!

    1. Olá Alexandre!

      Não temos exemplo com LCD, mas temos utilizando um celular: https://www.makerhero.com/blog/aprenda-a-configurar-a-rede-wifi-do-esp32-pelo-smartphone/

      O exemplo utiliza o ESP32, mas a biblioteca também serve para o ESP8266.

      Abraços!
      Vinícius – Equipe MakerHero

    2. Boa tarde.
      Eu fiz a muito tempo algo do tipo. Se o pessoal quiser posso postar um artigo aqui.
      Ou posso tbm subir pro git.

  3. Interessante demais o artigo mano!

    Uma pergunta, é possível fazer um post com body utilizando essa biblioteca? Ou passando parâmetros, se não for possível utilizar body. Abraço!

  4. show. como consumir dados entre módulos nodemcu?

    1. Olá!

      Sua ideia é comunicar um NodeMCU com outro?

      Abraço!
      Rosana – Equipe MakerHero

      1. Exato Rosana.

        Para dados in, tenho um modulo nodemcu alimentando o thingspeak, pensei em outro out consumindo e acionando coisas, mas acho muito pesado alem do delay, consumir apis para isto se ambos são in & out data e nodemcu wifi…

        já para conversar com api de terceiros como no exemplo faz sentido, dai vem que….

        dois nodemcu conversando entre si vao precisar fofocar para alguem de carne e osso, no watsapp “corre lá com um galão de diesel que o gerador disparou”.

        coisas notificando pessoas, wattsapp eh fundamenta, mas temos entre o gadget e o ser pensante de 4 patas, tem tres apis mais a api do Watts, então uma unica api nodejs para torcar com o gad e o wapp faz todo o sentido, até porque toda api eh um microserviço remunerado, eh, se ja pago o a conta do gad e do watts…..iot ficou cara.

        const accountSid = ‘xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx’;
        const authToken = ‘[AuthToken]’;
        const client = require(‘WattsApp’)(accountSid, authToken);

        client.messages
        .create({
        body: ‘teste iot 5.0’,
        messagingServiceSid: ‘yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy’,
        to: ‘+5511zzzzzzzzzzzzz’
        })
        .then(message => console.log(message.sid))
        .done();

        Response

        {
        “sid”: “uuuuuuuuuuuuuuuuuuuuuuuuuuuuu”,
        “date_created”: “Tue, 23 Feb 2021 14:09:47 +0000”,
        “date_updated”: “Tue, 23 Feb 2021 14:09:47 +0000”,
        “date_sent”: null,
        “account_sid”: “uuuuuuuuuuuuuuuuuuuuuuuuuuuu”,
        “to”: “+5511ttttttttttt”,
        “from”: null,
        “messaging_service_sid”: “eeeeeeeeeeeeeeeeeeeeeeeeeee”,
        “body”: “teste iot 5.0”,
        “status”: “accepted”,
        “num_segments”: “0”,
        “num_media”: “0”,
        “direction”: “outbound-api”,
        “api_version”: “2010-04-01”,
        “price”: null,
        “price_unit”: null,
        “error_code”: null,
        “error_message”: null,
        “uri”: “/2010-04-01/Accounts/ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff.json”,
        “subresource_uris”: {
        “media”: “/2010-04-01/Accounts/kkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkk.json”
        }
        }

        1. Olá,

          Se eu entendi bem o que você deseja fazer, acredito que nesse caso não é necessário o uso de uma API.
          Procure por tutorial de comunicação ponto-aponto com NodeMCU, acho que essa é uma solução.

          Abraço!
          Rosana Guse – Equipe MakerHero

          1. Obrigado Rosane, minha ficha começou a cair aqui:

            1 – gero e subo dados de um nodemcu com sensor de temperatura e umidade (iaq);
            os dados são obtidos com o método localmente via ….. bme.temperature….bme.umidity…. e disponibilizados em
            https://thingspeak.com/channels/1313259

            2. os dados
            Write a Channel Feed
            GET https://api.thingspeak.com/update?api_key=9N4J196I210V6S51&field1=0

            Read a Channel Feed
            GET https://api.thingspeak.com/channels/1313259/feeds.json?results=2

            Read a Channel Field
            GET https://api.thingspeak.com/channels/1313259/fields/1.json?results=2

            Read Channel Status Updates
            GET https://api.thingspeak.com/channels/1313259/status.json

            3 . um segundo node atua sobre um rele de estado sólido com base no set point (22 oC por exemplo) porém a variável controlada via pid vem de 2.

            4 . Como faço para trocar em 3 o método local temperature = bme.temperature para temperature = temperature(GET campo temperatura via rest…..)

            Valew

  5. Sr, eu fiz um simples projeto de acender Lampada+esp8266+aplicativo Android só pra entender como funciona um IOT. Para ser direto… ele funciona, mas existe um porém, ele se desconecta em um tempo indeterminado em segundos, e depois reconecta e apagando automaticamente a lampada. Uso pelo IDE Arduino, e visualizei pelo monitor serial isso:
    Exception (9):
    epc1=0x402113ac epc2=0x00000000 epc3=0x00000000 excvaddr=0x02a80283 depc=0x00000000

    >>>stack>>>

    ctx: cont
    sp: 3ffffb00 end: 3fffffc0 offset: 01a0
    3ffffca0: 00000003 00000005 3fff04f8 40204870
    3ffffcb0: 3ffffd00 3fffff80 3fff04f8 40205740
    3ffffcc0: 3fffdad0 3fffff80 3fffff30 402041ac
    3ffffcd0: 3fffdad0 3ffffd00 3ffffd00 40202914
    3ffffce0: 00000002 3fffff80 3fffff30 40202984
    3ffffcf0: 3fffdad0 3fffff80 3ffe878a 4020407c
    3ffffd00: 00464600 38830022 80c14d73 40211ab8
    3ffffd10: 3fff0c89 4bc6a7f0 ee978d4f 00000000
    3ffffd20: 00000000 4bc6a7f0 3645a1ca 40207493
    3ffffd30: 01860017 00000000 00000005 402107f8
    3ffffd40: 00000008 00000000 3fff0bac 40226e4f
    3ffffd50: 00000000 00000008 3ffffda0 4020676c
    3ffffd60: 00261efe 40257870 3ffffda0 402069e4
    3ffffd70: 007a1200 be193c0d 3ffffd00 00000005
    3ffffd80: 3ffffdd8 0000016d 0000016d 40100770
    3ffffd90: 00005230 00000000 00000000 01000000
    3ffffda0: 3ffffdd8 00000001 3fff0bac 00000005
    3ffffdb0: 000000c8 3fff0784 00000020 40100a30
    3ffffdc0: 3fff0c89 00000001 3fff0b2c 402109c7
    3ffffdd0: 00001568 000002ad 000002ad 40207493
    3ffffde0: 01860017 00000000 00000005 402107f8
    3ffffdf0: 000000c8 00000000 3fff0bac 40226e4f
    3ffffe00: 3fff4464 3fff0774 00000005 40205210
    3ffffe10: 3fff4400 000e000f 3fffff00 4021a8b5
    3ffffe20: 007a1200 be198acc 3fff0700 402038ec
    3ffffe30: 3ffffe88 00000000 00000001 4010017c
    3ffffe40: 00005230 00000a46 00000a46 40100770
    3ffffe50: 3ffffe88 00000000 3fff0bac 3ffeec30
    3ffffe60: 3fffff50 3fffff8c 00000020 401009fb
    3ffffe70: 3fffff50 00000000 3fff056c 402066f0
    3ffffe80: 00000000 3ffe878a 3fffff00 4021a8b5
    3ffffe90: 3ffe878a 00000000 00000000 402038ec
    3ffffea0: 00000000 3fffff0c 3fff04f8 4020390e
    3ffffeb0: 3fffff50 3fffff8c 3fff04f8 4020392a
    3ffffec0: 00000000 3fffff8c 3fffff00 402041ac
    3ffffed0: 3fffff50 3fffff8c 3fffff28 4020650d
    3ffffee0: 3fff04f4 3fffff8c 3ffeeaa8 4020652c
    3ffffef0: 00000011 3fffff8c 3ffeeaa8 40203f97
    3fffff00: 3ffe8a09 00000000 7468000a 402090f9
    3fffff10: 3ffe8a09 00000000 00000000 00000002
    3fffff20: 3ffffd00 3fff4400 3fffff80 80feec30
    3fffff30: 40211a50 3fff04f4 00000000 3ffeeac8
    3fffff40: 3fff0488 40211a10 3ffe8a07 00000000
    3fffff50: 00000100 3ffeeac8 3fffff00 402066f0
    3fffff60: 80ffdad0 3fffff8c 3ffe8744 40206726
    3fffff70: 00000000 00000000 3ffeebf0 40201180
    3fffff80: 0046464f 00000000 83000001 5f44454c
    3fffff90: 54415453 8a005355 3ffeebf0 4010019d
    3fffffa0: 3fffdad0 00000000 3ffeebf0 40207554
    3fffffb0: feefeffe feefeffe 3ffe8570 40100cbd
    <<<stack<<<
    x)⸮⸮⸮@⸮⸮⸮Connecting to dlink-C9B0………..
    Connected to dlink-C9B0

    O que preciso fazer ?

    1. Israel,

      Aconselho que você leve essa dúvida para nossa Comunidade. Lá, poste também seu código pra gente ter uma ideia da situação 😉

      Abraços!
      Diogo – Equipe MakerHero