Zápisník experimentátora
Hierarchy: ESP8266
This article will show you how to download online weather data using the ESP8266 microcontroller. We will use openweathermap.org to do this. We will do it in Arduino IDE. This is the second article in the series that includes Node.js, ESP8266 and Arduino MKR1000. Here are three examples in this article. In the examples, we will show a simple download of response from a JSON server and an output to the serial port. In the last example, we create a small meteorological station using the OLED display.
This server provides weather data. You can sign up for the server and then get the API key to download weather data from the server and use it for your own purposes. Registration is free of charge if you satisfy with the basic API features. For a list of services, visit openweathermap.org/price.
For our demonstration we will use API functions API Current weather data
. For a complete description, visit openweathermap.org/current. We will use two functions.
The server returns weather data in JSON format. You can use some parameters to set up some data. For example, you can change the temperature display in Kelvin, Celsius or Fahrenheit. The returned data looks like this.
{ coord: { lon: 19.15, lat: 48.74 },
weather:
[ { id: 801,
main: 'Clouds',
description: 'few clouds',
icon: '02n' } ],
base: 'stations',
main:
{ temp: 19.43,
pressure: 1011,
humidity: 64,
temp_min: 19,
temp_max: 20 },
visibility: 10000,
wind: { speed: 0.5 },
clouds: { all: 20 },
dt: 1525030200,
sys:
{ type: 1,
id: 5909,
message: 0.004,
country: 'SK',
sunrise: 1524972341,
sunset: 1525024594 },
id: 3061186,
name: 'Banska Bystrica',
cod: 200 }
The following parts are used in the article:
The connection between the pins is as follows.
NodeMCU | OLED |
---|---|
D1 | D0 |
D2 | D1 |
D3 | RES |
D4 | DC |
D5 | CS |
GND | GND |
3V3 | VCC |
I used:
I wrote three examples. I wanted to show them how we are gradually developing a program from a simple function to a more complex example that solves more functions at a time.
The first example sends the data obtained from the server to the serial port. First, we define several constants for the API. Other constants are found in the arduino_secret.h
file, which is not listed here. You can find it on GitHub and include definitions of WiFi connection. The result is a variable param
that contains the entire URL that we send to the server with HTTPS and GET.
We use the class WiFiClientSecure
to connect to the server. The class also includes HTTPS certificate verification, but I did not use these features. It is not necessary, because in this example we do not play secret agents with paranoia. We will be satisfied that we have an encrypted connection and believe that the correct server is on the other side.
When you send a request, you see a code that first checks the correct header for the response, finds the end of the header using the client.find command ("\r\n\r\n")
and downloads the body of the response from the server using the class DynamicJsonBuffer
. The response is formatted with function prettyPrintTo
and is sent to the serial port.
#include <ESP8266WiFi.h>
#include <WiFiClientSecure.h>
#include <ArduinoJson.h>
#include "arduino_secret.h"
const char* host = "api.openweathermap.org";
const int httpsPort = 443;
const char* url = "/data/2.5/weather";
const char* openweathermapid = "3061186"; // Banska Bystrica
const char* openweathermapq = "Banska%20Bystrica"; // Banska Bystrica
const char* openweathermapunits = "metric"; // Celsius
//String param = String(url) +
// "?id=" + openweathermapid +
// "&units=" + openweathermapunits +
// "&APPID=" + APPID;
String param = String(url) +
"?q=" + openweathermapq +
"&units=" + openweathermapunits +
"&APPID=" + APPID;
String line;
void setup() {
Serial.begin(115200);
Serial.println();
Serial.print("connecting to ");
Serial.println(ssid);
WiFi.mode(WIFI_STA);
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println("");
Serial.println("WiFi connected");
Serial.println("IP address: ");
Serial.println(WiFi.localIP());
// Use WiFiClientSecure class to create TLS connection
WiFiClientSecure client;
Serial.print("connecting to ");
Serial.println(host);
if (!client.connect(host, httpsPort)) {
Serial.println("connection failed");
return;
}
Serial.print("requesting URL: ");
Serial.println(url);
client.print(String("GET ") + param + " HTTP/1.1\r\n" +
"Host: " + host + "\r\n" +
"User-Agent: ESP8266\r\n" +
"Connection: close\r\n\r\n");
Serial.println("request sent");
line = client.readStringUntil('\n');
if (line != "HTTP/1.1 200 OK\r") {
Serial.print("Unexpected response: ");
Serial.println(line);
return;
}
if (client.find("\r\n\r\n")) {
DynamicJsonBuffer jsonBuffer(4096);
JsonObject& root = jsonBuffer.parseObject(client);
root.prettyPrintTo(Serial);
}
}
void loop() {
}
The start of the second program is identical to the first program. Only the ending where the weather data is parsed is varies. All functions look very similar. For example, the temperature is obtained by using const double temp = root["main"]["temp"];
. Some data is in the form of a real number and therefore their listing on the serial port may vary. The core version 2.3.0 must use the function dtostrf
. The core version 2.4.1 can use printf
.
#include <ESP8266WiFi.h>
#include <WiFiClientSecure.h>
#include <ArduinoJson.h>
#include "arduino_secret.h"
const char* host = "api.openweathermap.org";
const int httpsPort = 443;
const char* url = "/data/2.5/weather";
const char* openweathermapid = "3061186"; // Banska Bystrica
const char* openweathermapq = "Banska%20Bystrica"; // Banska Bystrica
const char* openweathermapunits = "metric"; // Celsius
//String param = String(url) +
// "?id=" + openweathermapid +
// "&units=" + openweathermapunits +
// "&APPID=" + APPID;
String param = String(url) +
"?q=" + openweathermapq +
"&units=" + openweathermapunits +
"&APPID=" + APPID;
String line;
void setup() {
Serial.begin(115200);
Serial.println();
Serial.print("connecting to WiFi");
//Serial.println(ssid);
WiFi.mode(WIFI_STA);
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println("");
Serial.println("WiFi connected");
//Serial.println("IP address: ");
//Serial.println(WiFi.localIP());
// Use WiFiClientSecure class to create TLS connection
WiFiClientSecure client;
Serial.print("connecting to ");
Serial.println(host);
if (!client.connect(host, httpsPort)) {
Serial.println("connection failed");
return;
}
Serial.print("requesting URL: ");
Serial.println(url);
client.print(String("GET ") + param + " HTTP/1.1\r\n" +
"Host: " + host + "\r\n" +
"User-Agent: ESP8266\r\n" +
"Connection: close\r\n\r\n");
Serial.println("request sent");
line = client.readStringUntil('\n');
if (line != "HTTP/1.1 200 OK\r") {
Serial.print("Unexpected response: ");
Serial.println(line);
return;
}
if (client.find("\r\n\r\n")) {
DynamicJsonBuffer jsonBuffer(4096);
JsonObject& root = jsonBuffer.parseObject(client);
//root.prettyPrintTo(Serial);
// parsed output
const char* name = root["name"];
Serial.print("City: ");Serial.println(name);
const double temp = root["main"]["temp"];
char buffer[64];
sprintf(buffer, "Temperature: %.02f", temp);
Serial.println(buffer);
const int humidity = root["main"]["humidity"];
sprintf(buffer, "Humidity: %d", humidity);
Serial.println(buffer);
const int pressure = root["main"]["pressure"];
sprintf(buffer, "Pressure: %d", pressure);
Serial.println(buffer);
const double wind = root["wind"]["speed"];
sprintf(buffer, "Wind speed: %.02f m/s", wind);
Serial.println(buffer);
const char* weather = root["weather"][0]["main"];
const char* description = root["weather"][0]["description"];
sprintf(buffer, "Weather: %s (%s)", weather, description);
Serial.println(buffer);
}
}
void loop() {
}
The last example uses an OLED display. The display rotates several weather values. The program is an example of a state machine that downloads current data from the server and rotates it on the OLED display. This is made by two enums. The enum oledState
is a finite state machine that takes care of downloading data from the server. The enum temperatureState
is the state machine that takes care of displaying data.
I used several functions in the program to make the program clearer.
loop
- The function checks for a WiFi connection and decides whether to re-connect or display data on the OLED display.connectWiFi
- The function provides a WiFi connection.showTemperature
- Once every two minutes, it will download new weather data and control the finite state machine to display the data on the display.downloadData
- The function will download new weather data from the server. The download description is in the previous examples.drawFrame
- Draw the data on the display. According to the finite state machine, the corresponding screen is drawn.This example only works for core 2.3.0. Core 2.4.1 can not connect to WiFi as soon as the OLED initialization function is used. I did not have enough time to investigate the problem, so I do not know if there is a problem on ESP8266 or in U8g2.
#include <ESP8266WiFi.h>
#include <WiFiClientSecure.h>
#include <ArduinoJson.h>
#include <U8g2lib.h>
#include "arduino_secret.h"
U8G2_SSD1306_128X64_NONAME_F_4W_SW_SPI u8g2(U8G2_R0, /* clock=*/ D1, /* data=*/ D2, /* cs=*/ D5, /* dc=*/ D4, /* reset=*/ D3);
enum oledState {oledBoot, oledConnected, oledRequest, oledParsed, oledError};
enum temperatureState {tempTemperature, tempHumidity, tempPressure, tempWind};
int bootCount = 0;
temperatureState tstate = tempTemperature;
int tick = 0;
struct myTemperature {
double temperature;
int humidity;
int pressure;
double wind;
char city[64];
};
myTemperature my_temperature;
char buff[64];
const char* host = "api.openweathermap.org";
const int httpsPort = 443;
const char* url = "/data/2.5/weather";
const char* openweathermapid = "3061186"; // Banska Bystrica
const char* openweathermapq = "Banska%20Bystrica"; // Banska Bystrica
const char* openweathermapunits = "metric"; // Celsius
//String param = String(url) +
// "?id=" + openweathermapid +
// "&units=" + openweathermapunits +
// "&APPID=" + APPID;
String param = String(url) +
"?q=" + openweathermapq +
"&units=" + openweathermapunits +
"&APPID=" + APPID;
String line;
void setup() {
Serial.begin(115200);
delay(10);
Serial.println();
Serial.print("connecting to WiFi ");
Serial.println(ssid);
u8g2.begin();
WiFi.mode(WIFI_STA);
}
void loop() {
if (WiFi.status() != WL_CONNECTED)
connectWiFi();
else
showTemperature();
}
void connectWiFi() {
bootCount = 0;
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
bootCount++;
if (bootCount % 25 == 0)
Serial.println();
drawFrame(oledBoot);
}
drawFrame(oledConnected);
Serial.println("");
Serial.println("WiFi connected");
Serial.println("IP address: ");
Serial.println(WiFi.localIP());
}
void showTemperature() {
if (tick % 60 == 0) { // once per 2 minutes
downloadData();
}
tick++;
drawFrame(oledParsed);
delay(2000);
switch (tstate) {
case tempTemperature:
tstate = tempHumidity;
break;
case tempHumidity:
tstate = tempPressure;
break;
case tempPressure:
tstate = tempWind;
break;
case tempWind:
tstate = tempTemperature;
break;
}
}
void downloadData() {
// Use WiFiClientSecure class to create TLS connection
WiFiClientSecure client;
Serial.print("connecting to ");
Serial.println(host);
if (!client.connect(host, httpsPort)) {
Serial.println("connection failed");
drawFrame(oledError);
return;
}
drawFrame(oledRequest);
Serial.print("requesting URL: ");
Serial.println(url);
client.print(String("GET ") + param + " HTTP/1.1\r\n" +
"Host: " + host + "\r\n" +
"User-Agent: ESP8266\r\n" +
"Connection: close\r\n\r\n");
Serial.println("request sent");
line = client.readStringUntil('\n');
if (line != "HTTP/1.1 200 OK\r") {
Serial.print("Unexpected response: ");
Serial.println(line);
drawFrame(oledError);
return;
}
if (client.find("\r\n\r\n")) {
DynamicJsonBuffer jsonBuffer(4096);
JsonObject& root = jsonBuffer.parseObject(client);
//root.prettyPrintTo(Serial);
// parsed output
const char* name = root["name"];
strcpy(my_temperature.city, name);
Serial.print("City: "); Serial.println(name);
my_temperature.temperature = root["main"]["temp"];
//sprintf(buff, "Temperature: %.02f", my_temperature.temperature);
dtostrf(my_temperature.temperature, 2, 2, buff);
Serial.print("Temperature: ");
Serial.println(buff);
my_temperature.humidity = root["main"]["humidity"];
sprintf(buff, "Humidity: %d", my_temperature.humidity);
Serial.println(buff);
my_temperature.pressure = root["main"]["pressure"];
sprintf(buff, "Pressure: %d", my_temperature.pressure);
Serial.println(buff);
my_temperature.wind = root["wind"]["speed"];
//sprintf(buff, "Wind speed: %.02f m/s", my_temperature.wind);
dtostrf(my_temperature.wind, 2, 1, buff);
strcat(buff, " m/s");
Serial.print("Wind speed: ");
Serial.println(buff);
const char* weather = root["weather"][0]["main"];
const char* description = root["weather"][0]["description"];
sprintf(buff, "Weather: %s (%s)", weather, description);
Serial.println(buff);
}
}
void drawFrame(oledState state) {
u8g2.clearBuffer();
u8g2.setFont(u8g2_font_ncenB10_tr);
switch (state) {
case oledBoot:
sprintf(buff, "boot(%d)...", bootCount);
u8g2.drawStr(0, 32, buff);
break;
case oledConnected:
sprintf(buff, "connected...");
u8g2.drawStr(0, 32, buff);
break;
case oledRequest:
sprintf(buff, "request...");
u8g2.drawStr(0, 32, buff);
break;
case oledParsed:
switch (tstate) {
case tempTemperature:
sprintf(buff, "Temperature");
u8g2.drawStr(0, 12, buff);
u8g2.drawStr(0, 62, my_temperature.city);
//sprintf(buff, "%.02f", my_temperature.temperature);
dtostrf(my_temperature.temperature, 2, 2, buff);
strcat(buff, " C");
u8g2.setFont(u8g2_font_ncenB18_tr);
u8g2.drawStr(0, 44, buff);
break;
case tempHumidity:
sprintf(buff, "Humidity");
u8g2.drawStr(0, 12, buff);
u8g2.drawStr(0, 62, my_temperature.city);
sprintf(buff, "%d %%", my_temperature.humidity);
u8g2.setFont(u8g2_font_ncenB18_tr);
u8g2.drawStr(0, 44, buff);
break;
case tempPressure:
sprintf(buff, "Pressure");
u8g2.drawStr(0, 12, buff);
u8g2.drawStr(0, 62, my_temperature.city);
sprintf(buff, "%d hpa", my_temperature.pressure);
u8g2.setFont(u8g2_font_ncenB18_tr);
u8g2.drawStr(0, 44, buff);
break;
case tempWind:
sprintf(buff, "Wind");
u8g2.drawStr(0, 12, buff);
u8g2.drawStr(0, 62, my_temperature.city);
//sprintf(buff, "%1.1f m/s", my_temperature.wind);
dtostrf(my_temperature.wind, 2, 1, buff);
strcat(buff, " m/s");
u8g2.setFont(u8g2_font_ncenB18_tr);
u8g2.drawStr(0, 44, buff);
break;
}
break;
}
u8g2.sendBuffer();
}
The video is located on YouTube.
The source code is located on the GitHub server.
26.05.2018