Get current weather data using ESP8266

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.

openweathermap.org

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.

  • By city name - You call function with a parameter of your city and get weather information.
  • By city ID - You call function with the identifier of your city and get weather information. You get the identifier on the page so you can search for your city and look at the parameter in the URL. For example.

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 }

Used parts

The following parts are used in the article:

  • NodeMCU 0.9 (link) - But rather buy the version 1.0.
  • 7pin 0.96 in SPI OLED 128x64 White (link) - It is important that the display is marked as SPI. These are significantly faster than the  I2C versions. They are sold in three color modifications. White, blue and two-color. The display can withstand a voltage of 5 V.
  • Breadboard (link) - I use two miniatures because NodeMCU 0.9 does not get on a normal breadboard. Two miniature ones have their advantage in connecting, which is nice visible in the photo.

The connection between the pins is as follows.

NodeMCU OLED
D1 D0
D2 D1
D3 RES
D4 DC
D5 CS
GND GND
3V3 VCC

Used libraries

I used:

Programs

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.

Output to the serial port

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() {
}

Parsing JSON

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() {
}

OLED display

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();
}

Video

The video is located on YouTube.

Zdrojový kód

The source code is located on the GitHub server.



Video


26.05.2018


Menu