Impressum Projects Music Research Code About
About Research Music Projects Code Thingiverse Impressum

 

 

 

 

 

Fetching Time and Weather Data Online

I wanted to get started on an ESP 32 project for some time now. The ESP32 is an Arduino like microcontroller (MCU) from chinese manufacturer Espressif. The main selling point being, that it comes with WiFi abilities. Now, before the board I ordered arrived, I wanted to try to implement the basic features in a C++ library.

If you work on a linux system, you probably know, that accessing data on the internet can usually be done easily with wget or curl bash commands. So it seems like a good starting point to get libcurl running in a C++ environment. This is also where I started. Libcurl needs to be implemented in a certain way and is also originally designed for C, not C++. The following code is available on gitlab.

class curlwrap
{
  public:
   curlwrap();
   ~curlwrap();
   void apicall(std::string &url, std::stringstream &ss);

  private:
   CURL *curl;
   static size_t curl_callback(void*, size_t, size_t, void*);
};

The curlwrap class has, apart from constructor and destructor only one public memeber funciotn and that is the apicall function. The way this class works is that after creating an alement, you call the apicall function and pass a string which contains the URL you want to call and a stringstream, where the callback function can store the answer of the URL.
The private members deal with the libcurl library directly. One is a CURL Object and the other is the so called callback function, which deals with the answer of the URL which was called.
In a minimal example, we can use this library to get the time from worldtimeapi.com:

#include "curlwrap.h"
#include <string.h>
#include <sstream>
#include <stdio.h>

int main(){
  curlwrap cw;
  std::stringstream back;
  std::string timeurl="http://worldtimeapi.org/api/ip";
  cw.apicall(timeurl, back);
  std::cout<<back.str()<<std::endl;
  return(1);
}

When compiling, don't forget to include libcurl:

$ g++ main.cpp -lcurl -std=c++11

And it leads to the following output, which is readable, but a bit confusing:

$ ./a.out
{"abbreviation":"CEST","client_ip":"11.111.111.11","datetime":"2020-09-13T11:49:00.117195+02:00","day_of_week":0,"day_of_year":257,"dst":true,"dst_from":"2020-03-29T01:00:00+00:00","dst_offset":3600,"dst_until":"2020-10-25T01:00:00+00:00","raw_offset":3600,"timezone":"Europe/Berlin","unixtime":1599990540,"utc_datetime":"2020-09-13T09:49:00.117195+00:00","utc_offset":"+02:00","week_number":37}

To print the output in a pretty way, you can pipe it into pythons json.tool, which breaks down the JSON and makes it much clearer:

$ ./a.out | python -m json.tool
{
  "abbreviation": "CEST",
  "client_ip": "11.111.111.11",
  "datetime": "2020-09-13T12:01:06.473158+02:00",
  "day_of_week": 0,
  "day_of_year": 257,
  "dst": true,
  "dst_from": "2020-03-29T01:00:00+00:00",
  "dst_offset": 3600,
  "dst_until": "2020-10-25T01:00:00+00:00",
  "raw_offset": 3600,
  "timezone": "Europe/Berlin",
  "unixtime": 1599991266,
  "utc_datetime": "2020-09-13T10:01:06.473158+00:00",
  "utc_offset": "+02:00",
  "week_number": 37
}

Alright, so fetching data works. But also, the data comes as a JSON and needs to be parsed somehow to make working with it somehow pleasant. For that, using a library for JSONs is very helpful and nlohmann/json seems to be a good choice. Adjusting the code from above to incorporate nlohmann/json is pretty simple, because you can just stream the stringstream to a nlohmann::json object:

int main(){
  curlwrap cw;
  nlohmann::json timedata;
  std::stringstream back;
  std::string timeurl="http://worldtimeapi.org/api/ip";
  cw.apicall(timeurl, back);
  back>>timedata;
  std::cout<<timedata["datetime"]<<std::endl;
  return(1);
}

The output is then pretty much how you would expect ist, given the python generated output from above:

$ ./a.out
"2020-09-13T12:01:06.473158+02:00"

Alright, now we are able to make a request to a web page and receive a data package containing time, date and a lot more. We are able to extract what we need and print it to the screen. The next step is, to find a way to get the weather data we want.

One possibility and probably the most present on the net is Open Weather Map (OWM). On OWM you can create an account and can request data for a limited amount of times per minute and per day. with your account comes an ID, a unique identifier which you add to your requests which enables you to retrieve data. But if, for example, you want to make an app and use this ID for everybody to retrieve weather data the limit will be reached soon and you would be well advised to purchase a more advanced license.

For OWM you have a few parameters you can choose. One is of course the location, you need the forecast for. The other is the type of forecast you wnat. For this project we want to get the momentary weather and the prediction for the next few hours in three hour steps.

To make the procedure easier, cereating another class is handy. It will incorporate the curlwrap class from before and use nlohmann/json to decipher the data from OWM. The idea is, that you can later use a few commands to get your weather data and will know how warm, cold or rainy it is, without opening the blinds.

class weatherstation
{
  public:
   weatherstation(std::string town, std::string owmId);
   ~weatherstation();
   void pull_weatherdata();
   void pull_forecastdata();
   void pull_time();

   std::string year;
   std::string month;
   std::string day;
   std::string time;

   std::string temperature;
   std::string description;

   std::vector <std::string> fc_desc;
   std::vector <std::string> fc_temp;
   std::vector <std::string> fc_times;

  private:
   nlohmann::json weatherdata;
   nlohmann::json forecastdata;
   nlohmann::json timedata;
   std::string weatherurl;
   std::string forecasturl;
   std::string timeurl{"http://worldtimeapi.org/api/ip"};
   curlwrap curl;
   std::stringstream ss;
};

Now, in this class, we have the constructor, which needs your location (or town name) and your OWM ID. Also, three functions for updating the data and then a bunch of variables, the data will be stored in.
Nevertheless, extracting the data can be a bit of a hassle, if you only want a few parts of data to display. Getting the weather looks like this in the pull_weatherdata() function:

void weatherstation::pull_weatherdata(){
  double buff;
  // use curl to call the api and get the weatherdata, it will be written to the stringstream ss
  curl.apicall(weatherurl, ss);
  // write the contents of ss to the nlohmann::json weatherdata
  ss>>weatherdata;
  // It is convenient for output to be stored as string. For formatting and typeconversion it is easier to
  // work with a buffer because temperature is stored as a floating point number:
  buff = weatherdata["main"]["temp"];
  buff = std::round(buff);
  temperature = std::to_string(int(buff));
  // The weatherdescription is stored as a string, so we can write it directly to the descripion variable
  description = weatherdata["weather"][0]["description"];
}

If you want to have a look at the pull_forecastdata() function, visit the gitlab repository. You need to play around a bit with the JSON objects to get the data you want from it.
Implementing the weatherstation class from here is fairly straightforward. The following main() shows how it is done:

int main() {
  weatherstation ws("", "");
  ws.pull_time();
  ws.pull_forecastdata();
  ws.pull_weatherdata();
  std::cout<<ws.day<<"."<<ws.month<<"."<<ws.year<<"\t"<<ws.time<<std::endl;
  std::cout<<"Right now: "<<ws.description<<" at "<<ws.temperature<<"°C"<<std::endl;
  std::cout<<std::endl;
  std::cout<<"The weather today:"<<std::endl;
  std::cout<< ws.fc_times.at(0) << "h:\t" << ws.fc_desc.at(0) << "\t" << ws.fc_temp.at(0) << "°C" << std::endl;
  std::cout<< ws.fc_times.at(1) << "h:\t" << ws.fc_desc.at(1) << "\t" << ws.fc_temp.at(1) << "°C" << std::endl;
  std::cout<< ws.fc_times.at(2) << "h:\t" << ws.fc_desc.at(2) << "\t" << ws.fc_temp.at(2) << "°C" << std::endl;
  return 0;
}

Well, and this is it. This piece of code, most of it beeind formatting of the output, will give you the weatherforecast for your chosen place. And it will probably look something like this:

13.09.2020 19:44:52
Right now: overcast clouds at 25°C

The weather today:
21h: broken clouds 19°C
0h: scattered clouds 16°C
3h: scattered clouds 15°C