ESP8266 Projeto - Consultando cotações em APIs de criptomoedas
Neste post, vamos abordar como usar o ESP-01 para consultar dados de uma API de criptomoedas e mostrar no DISPLAY OLED 1306. Fazendo uma requisição https, para a API economia.awesomeapi.com.br e manipulando os dados com a ArduinoJson.
|
Assumindo que você já sabe como preparar a Arduino IDE para gravar no ESP-01, inicie um novo projeto, crie um arquivo opcional chamado WebSecrets.h, nós vamos definir umas constantes pra ele.
Alguns sites só podem ser acessados por meio do https, por questões de segurança. Desta forma, o metodo padrão que usamos no ESP para fazer requisições http não podem ser usadas, então como fazemos isso? Primeiro vamos obter o fingerprint da página web.
Fingerprint?
O fingerprint é basicamente um resumo do certificado da página, na maioria dos navegadores podemos obter de forma fácil ao acessar qualquer pagina web. No firefox por exemplo fazemos o seguinte, primeiro acessamos a pagina e sem seguida clicamos no "cadeado".
Na sequencia acessamos Connection Secure, ou Conexão segura, e buscamos por mais informações. Com isso teremos a seguinte aba na forma de popup.
Então, vamos clicar em View Certificate, (Ver certificado) e descemos a página até encontrarmos o Fingerprint na forma de SHA-256 e SHA-1.
Vamos copiar o SHA-1 por ser o mais menor, e vamos escrever ele na seguinte forma sem os dois pontos “:”.
#define WIFI_SSID "SSID"
#define WIFI_PASS "PASSWORD"
/**
* To access sites with ssl certificates, needs the fingerprint resume certificate
* to allow access. This is the fingerprint of economia.awesomeapi.com.br
**/
#define FINGERPRINT "D7 7C CA 8B 0D 5B 32 4D AC A6 73 5E FA 96 57 FB 80 B1 98 9B";
Em seguida, no seu arquivo principal, inclua as bibliotecas do Display SSD1306, ArduinoJson e a nossa WebSecret.h, caso não tenha as duas primeiras instaladas, basta fazer o download delas pelo proprio Arduino IDE. O Esp8266 não possui uma conexão i2c reservada como no Arduino, então vamos usa a biblioteca Wire para definir quais pinos vamos usar.
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <ESP8266WiFi.h>
#include <ESP8266HTTPClient.h>
#include <WiFiClient.h>
#include <WiFiClientSecure.h>
#include "ArduinoJson.h"
#include "WebSecret.h"
Em seguida, logo abaixo vamos definir quais são os nossos pinos, como o ESP01 só tem 2 pinos normais, vamos usar o pino GPIO2 e GPIO0, e definimos o tamanho do nosso display e criamos a variavel do display.
#define DISPLAY_SDA 2
#define DISPLAY_SCL 0
#define SCREEN_WIDTH 128 // OLED display width, in pixels
#define SCREEN_HEIGHT 64 // OLED display height, in pixels
#define OLED_RESET -1
#define SCREEN_ADDRESS 0x3C
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
Após isso, vamos usar algumas imagens das logos das criptomoeadas, o Display OLED SSD1306 é um display monocromático, para poder usar uma imagem, devemos não só reduzir o seu tamanho de resolução, como devemos converter ela para um bitmap monocromático e extrair os bytes dela.
É possivel converter para monocromático usando o Paint do Windows e usar um programa externo para extrair os bytes, mas existem muitos sites, que já extraem os bytes e permitem mecher um pouco, como o http://javl.github.io/image2cpp/ e o https://mischianti.org/images-to-byte-array-online-converter-cpp-arduino/ (Pessoalmente eu gosto mais deste). Com isso ao usar as logos do Bitcoin e Ethereum, reduzir sua resolução, converter para monocromatico e extrair os bytes, temos as seguintes constantes.
No Arduino, usamos o PROGMEM, para armazenar uma constante na memoria interna, e não na flash. Assim é possivel ter variaveis que são “grandes” demais para serem usadas somente quando forem requisitadas. No arduino por não ter uma memoria grande, não é possivel usar esta estratégia com strings, mas para o ESP01 que tem 1MB de memoria interna, dá pra guardar páginas html inteiras (Aguardem os proximos posts :) ), então nós vamos usar-los.
É também importante, definir o tamanho da imagem monocromatica que estamos usando, neste caso eu converti ambas as logos para 32x32.
const unsigned char btc_logo [] PROGMEM = {
0x00, 0x0f, 0xf0, 0x00, 0x00, 0x7f, 0xfe, 0x00, 0x01, 0xff, 0xff, 0x80, 0x03,
0xff, 0xff, 0xc0, 0x07, 0xff, 0xff, 0xe0, 0x0f, 0xff, 0xff, 0xf0, 0x1f, 0xfe,
0x5f, 0xf8, 0x3f, 0xfe, 0x4f, 0xfc, 0x3f, 0xe0, 0xcf, 0xfc, 0x7f, 0xe0, 0x0f,
0xfe, 0x7f, 0xf8, 0x07, 0xfe, 0x7f, 0xf8, 0xc1, 0xfe, 0xff, 0xf0, 0xe1, 0xff,
0xff, 0xf1, 0xf1, 0xff, 0xff, 0xf1, 0xe1, 0xff, 0xff, 0xf0, 0x03, 0xff, 0xff,
0xe1, 0x07, 0xff, 0xff, 0xe3, 0xc3, 0xff, 0xff, 0xe3, 0xe1, 0xff, 0xff, 0x03,
0xe1, 0xff, 0x7f, 0x01, 0xc3, 0xfe, 0x7f, 0xe0, 0x03, 0xfe, 0x7f, 0xe4, 0x07,
0xfe, 0x3f, 0xe4, 0xff, 0xfc, 0x3f, 0xec, 0xff, 0xfc, 0x1f, 0xfc, 0xff, 0xf8,
0x0f, 0xff, 0xff, 0xf0, 0x07, 0xff, 0xff, 0xe0, 0x03, 0xff, 0xff, 0xc0, 0x01,
0xff, 0xff, 0x80, 0x00, 0x7f, 0xfe, 0x00, 0x00, 0x0f, 0xf0, 0x00
};
const unsigned char eth_logo [] PROGMEM = {
0x00, 0x0f, 0xf0, 0x00, 0x00, 0x7f, 0xfe, 0x00, 0x01, 0xff, 0xff, 0x80, 0x03,
0xff, 0xff, 0xc0, 0x07, 0xff, 0xff, 0xe0, 0x0f, 0xff, 0xff, 0xf0, 0x1f, 0xfe,
0x7f, 0xf8, 0x3f, 0xfe, 0x7f, 0xfc, 0x3f, 0xfc, 0x3f, 0xfc, 0x7f, 0xf8, 0x1f,
0xfe, 0x7f, 0xf8, 0x1f, 0xfe, 0x7f, 0xf0, 0x0f, 0xfe, 0xff, 0xf0, 0x0f, 0xff,
0xff, 0xe1, 0x87, 0xff, 0xff, 0xc4, 0x23, 0xff, 0xff, 0xe0, 0x07, 0xff, 0xff,
0xc0, 0x03, 0xff, 0xff, 0xe0, 0x07, 0xff, 0xff, 0xd8, 0x1b, 0xff, 0xff, 0xe6,
0x67, 0xff, 0x7f, 0xe3, 0xc7, 0xfe, 0x7f, 0xf0, 0x0f, 0xfe, 0x7f, 0xf8, 0x1f,
0xfe, 0x3f, 0xfc, 0x3f, 0xfc, 0x3f, 0xfc, 0x3f, 0xfc, 0x1f, 0xfe, 0x7f, 0xf8,
0x0f, 0xff, 0xff, 0xf0, 0x07, 0xff, 0xff, 0xe0, 0x03, 0xff, 0xff, 0xc0, 0x01,
0xff, 0xff, 0x80, 0x00, 0x7f, 0xfe, 0x00, 0x00, 0x0f, 0xf0, 0x00
};
const int logo_size_h = 32;
const int logo_size_w = 32;
Agora vamos passar as nossas constantes e definir o host da api a qual vamos fazer a requisição. Por isso a parte do WebSecrets.h é opcional, já que dá pra definir no codigo principal, mas para propositos de localização eu uso o WebSecrets.h
const char *ssid = WIFI_SSID;
const char *pass = WIFI_PASS;
const char *server_host = "economia.awesomeapi.com.br";
const char *fingerprint = FINGERPRINT;
Agora vem a parte “boa”, se fizermos a requisição na API, vamos receber uma String que é um Json, para o nosso ESP descerializar esse Json em um objeto, usamos a biblioteca ArduinoJson (https://arduinojson.org/) que é a melhor para esta tarefa. Então vamos criar uma variavel de um documento json de tamanho maximo de 512 bytes. Em seguida descerializamos a string payload na nossa variável "json", isso nos mermite acessar os atributos do nosso json.
Após extrair os dados necessários, nos exibiremos eles no display oled. Na sequencia desenhamos as setas e as logos necessárias. “setas” você pergunta? bom, o nosso display suporta alguns icones por padrão, para acessar eles devemos nos orientar pela tabela de bytes do display.
A seta pra cima, está na linha 1 e coluna 8, então para escrever o byte no display, escrevos display.write(0x18);
e no caso da seta para baixo, é linha 8 e coluna 9, logo, escrevemos display.wirte(0x19);
. Ou como no código abaixo atribuirmos a váriaveis.
void drawCoin(String payload) {
StaticJsonDocument<512> json;
deserializeJson(json, payload);
String create_date = json["create_date"];
String code = json["code"];
String codeIn = json["codein"];
String high = json["high"];
String low = json["low"];
String pctChange = json["pctChange"];
String ask = json["ask"];
display.setTextSize(1);
display.setCursor(0, 0);
display.print(create_date);
display.display();
delay(1500);
display.setCursor(logo_size_w + 8, 18);
display.setTextSize(1);
display.print(String(code) + " to " + String(codeIn));
display.display();
display.setCursor(logo_size_w + 8, 30);
display.setTextSize(1);
display.print("$: " + String(ask));
display.display();
display.setCursor(logo_size_w + 8, 40);
display.setTextSize(1);
display.print("%: " + String(pctChange) );
display.display();
display.setCursor(logo_size_w + 80, 40);
display.setTextSize(1);
int arrow_up = 0x18;
int arrow_down = 0x19;
if (String(pctChange).toFloat() > 0) {
display.write(arrow_up);
}
else {
display.write(arrow_down);
}
display.display();
if (code == "BTC") {
display.drawBitmap(0, 18, btc_logo, logo_size_w, logo_size_h, 1);
}
else if (code == "ETH") {
display.drawBitmap(0, 18, eth_logo, logo_size_w, logo_size_h, 1);
}
display.display();
delay(3500);
display.clearDisplay();
}
Bom, agora temos o código para pegar o payload e escrever no display, mas como pegar esse payload?. É agora, vamos criar uma função para fazer uma requisição na API de forma segura e retornar o payload.
Primeiro, como sempre criamos a variavel, neste caso o WiFiClientSecure https, em seguida vamos atribuir o fingerprint do site. Na sequencia, vamos estabelecer uma conexão com a API, com no maximo de 30 tentativas, caso a conexão seja efetuada vamos mandar uma requisição do tipo GET REQUEST para a rota /last/BTC-USD,ETH-USD. Descrevendo no cabeçalho da requisição que os dados são do tipo application/json e que a requisição é um HTTP 1.1.
Após esse tipo de requisição a conexão é fechada, e recebemos uma string que é a requisição de resposta, como só queremos o Corpo da requisição. O "body", nós vamos precisar ir pulando a string lida até termos um “\r”.
Em seguida, vamos verificar todo o resto da string até ver se recebemos um JSON, localizando o indíce do "{", se ele existir então nós temos um Texto no formato de JSON que vamos retornar, caso contrário vamos encerrar o loop.
String payloadRequestSecure() {
String line;
int r = 0;
WiFiClientSecure https;
https.setFingerprint(fingerprint);
https.setTimeout(5000); // 5 Seconds
delay(1000);
Serial.print("Try connecting");
int retryCounter = 0;
while ((!https.connect(server_host, 443)) && (retryCounter < 30)) { // 30 trys
delay(100);
Serial.print(".");
retryCounter++;
}
if (r >= 30) {
Serial.println("Connection failed");
}
else {
Serial.println("Connected, starting request.");
https.print("GET /last/BTC-USD,ETH-USD"); https.print(" HTTP/1.1\r\n");
https.print("Host: "); https.print(server_host); https.print("\r\n");
https.print("Content-Type: application/json"); https.print("\r\n");
https.print("Connection: close"); https.print("\r\n\r\n");
Serial.println("request sent");
while (https.connected()) {
String line = https.readStringUntil('\n');
if (line == "\r") {
Serial.println("headers received");
break;
}
}
Serial.println("Response payload >>>>");
Serial.println();
while (https.available()) {
line = https.readStringUntil('\n');
if (line.indexOf("{") > -1) {
Serial.println(line); //Print response
break;
}
}
Serial.println();
Serial.println("<<<<.");
Serial.println("End of connection.");
}
return line;
}
Bom, agora no nosso setup, vamos iniciar o nosso monitor serial com o Serial.begin(9600); só para acompanhar e ver se tá tudo indo bem. Em seguida configuramos a conexão WiFi e verificamos se estamos conectados na rede. Por fim, só precisamos iniciar a biblioteca do nosso display.
void setup() {
Serial.begin(9600);
WiFi.mode(WIFI_STA);
WiFi.begin(ssid, pass);
Serial.println();
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println();
Serial.print("Connected to: ");
Serial.print(ssid);
Serial.print(" IP address: ");
Serial.println(WiFi.localIP());
Wire.begin(DISPLAY_SDA, DISPLAY_SCL); //INICIALIZA A BIBLIOTECA WIRE
display.begin(SSD1306_SWITCHCAPVCC, SCREEN_ADDRESS);
display.setTextColor(WHITE);
display.clearDisplay();
}
É importante chamar o display.clearDisplay(); porque se não o nosso display vai só ir adicionado imagem por cima da outra ao desenhar, e isso não é legal. Para dar a impressão de atualização em tempo real temos que sempre limpar o display.
Já no nosso loop, chamamos a função que faz a nossa requisição do nosso payload, após obter ele nós vamos descerializar ele no nosso StaticJsonDocument para acessar os atributos das criptos. Então vamos agora serializar novamente em uma string para passar no nosso drawCoin.
void loop() {
display.clearDisplay();
const String payload = payloadRequestSecure();
StaticJsonDocument<1024> json;
deserializeJson(json, payload);
String btcUSD;
btcUSD.reserve(512);
serializeJson(json["BTCUSD"], btcUSD);
drawCoin(btcUSD);
String ethUSD;
ethUSD.reserve(512);
serializeJson(json["ETHUSD"], ethUSD);
drawCoin(ethUSD);
Serial.println(payload);
Serial.println(btcUSD);
Serial.println(ethUSD);
delay(100); // New Request delay
}
Agora, para gravar no ESP01, é só ver uma das opções abordadas no post específico sobre o ESP01, após gravar o código, devemos montar o nosso projeto da seguinte forma. Como o ESP01 só suporta 3.3 volts e o Display tem suporte de 3 à 5 volts, podemos ligar-los juntos como mostra o diagrama abaixo.
O resultado ficará assim: