Thursday, July 13, 2023

Sensor Less Weather Station using ESP32 Open Weather Map Website and LVGL

Hello Everyone, In this post I will show you guys, how to make a Sensorless Weather Station using Open Weather Map website and display the temperature, humidity and pressure values on the LCD along with the city image and name, as shown in the below images.

Delhi Weather Information

Leh Weather Information

Shimla Weather Information

Jaipur Weather Information



This post is sponsored by PCBWay, with more than a decade in the field of PCB prototype and fabrication, PCBWay is committed to meeting the needs of their customers from different industries in terms of quality, delivery, cost-effectiveness and any other demanding requests. 
As one of the most experienced PCB manufacturers in the World, PCBWay prides itself to be your best business partner as well as a good friend in every aspect of your PCB needs.


In the past, I had done a similar project but using Arduino Mega connected to ESP8266 (with AT command firmware), to get similar data and display it using OLED or on a TFT screen, links are given below.

Weather Station using Arduino and ESP8266 with Open Weather API using AT Command - Embedded Laboratory

In this project, I will just use the ESP32 connected to 3.2 inch LCD, to make the connections a little less complex, I am using ESP32 Kaluga Development Kit. This is a low-cost development kit with an onboard 3.2-inch LCD, audio support, camera support etc.

You can watch the following video to understand or read the below-mentioned post.


Open Weather Map

OpenWeatherMap is an online service, owned by OpenWeather Ltd, that provides global weather data via API, including current weather data, forecasts, nowcasts and historical weather data for any geographical location. 
The company provides a minute-by-minute hyperlocal precipitation forecast for any location. The convolutional machine learning model is used to utilise meteorological broadcast services and data from airport weather stations, on-ground radar stations, weather satellites, remote sensing satellites, METAR and automated weather stations.
The company has more than 2 million customers, ranging from independent developers to Fortune 500 companies.
I am not going to share how to create an account on the "OpenWeatherMap" website, but in case you want to have some more information, you can read the above-mentioned post and also have a look at the mentioned videos in that post.
Let's say you wanted to get the weather information of the "Leh" city in India, you can simply type the following line in your web browser and you get the JSON data, as shown below.

http://api.openweathermap.org/data/2.5/weather?q=leh&appid={API_KEY}=metric

{
  "coord":{"lon":77.5833,"lat":34.1667},
  "weather":
  [
    {"id":804,"main":"Clouds","description":"overcast clouds","icon":"04n"}
  ],
  "base":"stations",
  "main":{"temp":9.05,"feels_like":8.67,"temp_min":9.05,"temp_max":9.05,"pressure":1016,"humidity":91,"sea_level":1016,"grnd_level":676},
  "visibility":9138,
  "wind":{"speed":1.41,"deg":226,"gust":1.33},
  "clouds":{"all":100},"dt":1688915861,
  "sys":{"country":"IN","sunrise":1688859900,"sunset":1688911451},
  "timezone":19800,"id":1264976,"name":"Leh","cod":200
}

And then using a JSON parser we can decode this data to extract the "temperature", "humidity" and pressure information, which can be displayed on the 3.2-inch LCD using the LVGL graphics library.

Software Architecture

The software is written using the ESP-IDF framework and is available on my GitHub page free of charge.
Main Code Flow

The above Activity Diagram is used to explain the steps of this small project.

  • The first step is to connect with the router so that ESP32 can be connected to the internet to get the data from the "OpenWeatherMap" website.
  • In the second step, the "OpenWeatherMap" software module is initialized, which doesn't do much, it will just initialize the names of the city (this also can be configured, but I have kept them hard-coded inside the module)
  • In the third step, the "Display" software module is initialized, it will initialize the 3.2 Inch LCD and also initialize the LVGL Graphics Library, in the background a FreeRTOS task is created and this task keeps on refreshing the display by calling the LVGL functions.
  • So now only two FreeRTOS tasks running, one is "app_main" which is also known as the main task and another task is the "LVGL" task which is hidden from us directly, ideally we can create another task also dedicated to "Open Weather Map" but I avoided to do so, as of now.
Main Code:

// Main Program Starts from Here
void app_main(void)
{
  int64_t current_time = 0;
  esp_err_t ret = nvs_flash_init();
  if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND)
  {
    ESP_ERROR_CHECK(nvs_flash_erase());
    ret = nvs_flash_init();
  }
  ESP_ERROR_CHECK(ret);

  // Connect to Wireless Access Point
  ret = connect_wifi();
  if( ret == WIFI_SUCCESS )
  {
    // Initialize the OpenWeatherMap module
    openweathermap_init();
    // Initialize the Display Manager
    display_init();
    openweathermap_timestamp = esp_timer_get_time();
    display_timestamp = esp_timer_get_time();

    while( true )
    {
      current_time = esp_timer_get_time();

      // OpenWeatherMap Management
      if( (current_time - openweathermap_timestamp) > OPENWEATHERMAP_MNG_EXEC_RATE )
      {
        openweathermap_timestamp = current_time;
        openweathermap_mng();
      }

      // Display Management
      if( (current_time - display_timestamp) > DISPLAY_MNG_EXEC_RATE )
      {
       display_timestamp = current_time;
       display_mng();
      }
      vTaskDelay(MAIN_TASK_EXEC_RATE / portTICK_PERIOD_MS);
    }
  }
}

Open Weather Map HTTP Client Request

The above Activity Diagram shows how the "OpenWeather Map" software module us handling the different cases to send the HTTP GET requests and get the data from the "Open Weather Map" website using API calls.
And once the data is received which is in JSON format, the "cJSON" inbuilt library is used to extract the "Temperature", "Pressure" and "Humidity" data from it, and saved in the global structures.

Open Weather Map Module:
void openweathermap_init(void)
{
  // Initialize the City Names
  strcpy(city_weather[0].city_name, "delhi");
  strcpy(city_weather[1].city_name, "shimla");
  strcpy(city_weather[2].city_name, "jaipur");
  strcpy(city_weather[3].city_name, "leh");
}

// OpenWeatherMap Manager
void openweathermap_mng(void)
{
  if( request_in_process == false )
  {
    request_in_process = true;
    openweathermap_send_request();
  }
}

// OpenWeatherMap Task (optional, if we don't want to use above two functions manually)
void openweathermap_task(void *pvParameters)
{
  openweathermap_init();
  for(;;)
  {
    openweathermap_mng();
    vTaskDelay(HTTP_REQ_EXEC_RATE/portTICK_PERIOD_MS);

  }
  vTaskDelete(NULL);
}

// Private Function Definitions
static void openweathermap_send_request(void)
{
  char openweathermap_url[200];
  snprintf( openweathermap_url, sizeof(openweathermap_url), \
            "%s%s%s", CLIENT_REQ_PRE, city_weather[city_weather_index].city_name, CLIENT_REQ_POST);

  esp_http_client_config_t config =
  {
    .url = openweathermap_url,
    .method = HTTP_METHOD_GET,
    .event_handler = openweathermap_event_handler,
  };

  // ESP_LOGI(TAG, "URL:%s", openweathermap_url);
  ESP_LOGI(TAG, "Free Heap: %lu", esp_get_free_heap_size() );

  esp_http_client_handle_t client = esp_http_client_init(&config);
  esp_http_client_set_header(client, CLIENT_KEY, CLIENT_VALUE);
  esp_err_t err = esp_http_client_perform(client);
  if( err == ESP_OK )
  {
    int status = esp_http_client_get_status_code(client);
    if(status == 200)
    {
      ESP_LOGI(TAG, "Message Sent Successfully");
    }
    else
    {
      ESP_LOGI(TAG, "Message Sent Failed");
    }
  }
  else
  {
    ESP_LOGI(TAG, "Message Sent Failed");
  }
  esp_http_client_cleanup(client);
}

static esp_err_t openweathermap_event_handler(esp_http_client_event_t *event)
{
  switch(event->event_id)
  {
    case HTTP_EVENT_ON_DATA:
      // Copy the Data to response_data buffer
      memcpy(response_data+response_data_idx, event->data, event->data_len);
      // Update the Length
      response_data_idx += event->data_len;
      break;
    case HTTP_EVENT_ON_FINISH:
      // Decode/Parse the weather data from the response data
      openweathermap_get_weather(response_data, &city_weather[city_weather_index]);
      // reset the response buffer and also the length to initial state
      openweathermap_reset_buffer();
      ESP_LOGI( TAG, "City=%s, Temp=%d, Pressure=%d, Humidity=%d", \
                city_weather[city_weather_index].city_name,   \
                city_weather[city_weather_index].temperature, \
                city_weather[city_weather_index].pressure,    \
                city_weather[city_weather_index].humidity);
      city_weather_index++;
      // Reset back to Initial Position
      if( city_weather_index >= NUM_OF_CITIES )
      {
        city_weather_index = 0;
      }
      // Free the system for next requests
      request_in_process = false;
      break;
    case HTTP_EVENT_ERROR:
      // In case of Error, exit
      openweathermap_reset_buffer();
      // Free the system for next requests
      request_in_process = false;
      break;
    default:
      break;
  }
  return ESP_OK;
}

static void openweathermap_get_weather(const char *json_string, weather_data_t *weather_data)
{
  cJSON *root = cJSON_Parse(json_string);
  cJSON *obj = cJSON_GetObjectItemCaseSensitive(root, "main");
  weather_data->temperature = cJSON_GetObjectItemCaseSensitive(obj, "temp")->valueint;
  weather_data->pressure = cJSON_GetObjectItemCaseSensitive(obj, "pressure")->valueint;
  weather_data->humidity = cJSON_GetObjectItemCaseSensitive(obj, "humidity")->valueint;
  cJSON_Delete(root);
}


Display Refreshing Logic

And then in another function "display_mng" is used to update the "Temperature", "Pressure", "Humidity" and "City Name" with the city image on the display using LVGL functions.

The code snippet attached above is just for reference, click here to get the code from GitHub.

Thanks

No comments:

Post a Comment