Funpack2-5:基于 ESP32-S3-BOX-LITE 实现气温预报展示功能
使用 ESP32-S3-BOX-LITE 显示四个一线城市的列表,点击按钮选择城市后,请求天气接口获取此城市未来三天的天气预报,并通过显示屏展示未来三天的最高气温和最低气温。
标签
嵌入式系统
Funpack活动
topgear
更新2023-08-02
1087

Funpack2-5:基于 ESP32-S3-BOX-LITE 实现气温预报展示功能

 

一、项目描述

ESP32-S3-BOX AI 语音开发套件是乐鑫打造的一个智能语音设备开发平台,它既可以用于构建智能音箱,也可以赋能更多物联网设备实现人机语音交互。同时,它还集触摸屏控制、传感器、红外控制器和智能网关等多功能于一体,能够作为全屋设备的控制中枢,支持用户通过语音命令控制或触屏控制,轻松实现圈内智能联动。

ESP32-S3-BOX-Lite 轻量级开发套件与 ESP32-S3-BOX 拥有类似的离、在线语音功能,取消了电容触摸面板和 Mute 按键,增加了 3 个由客户自定义的功能按键。

在本项目中,使用了ESP32-S3-BOX-Lite 以下功能:

  • 按键:用于选择和确认城市
  • Wifi :联网
  • 显示屏:展示未来三天的最高和最低气温

二、设计思路

  • 板卡上电后先初始化为 wifi station,连接 AP 接入网络;
  • 然后驱动LCD,在屏幕的主界面展示城市列表主菜单,主菜单包含4个条目,每个条目对应一个城市,模块上电后主菜单默认选中第一个城市,此时条目的背景色为蓝色,条目的字体为白色;
  • 通过左右两个按键实现城市切换;
  • 单击中间的按键,选中该城市,并向天气接口服务请求该城市未来三天的气象预报;
  • 使用 cjson 处理接口响应,解析出未来三天的最高气温和最低气温;
  • 在显示屏上展示最高和最低气温。

三、软件流程

FuEuKS_ISDuXq7rxx9iN_KJ-7hrc

四、开发环境的搭建

  1. ESP-IDF

    乐鑫为 ESP-IDF 开发环境分别提供了Windows、Linux和macOS平台的安装程序,在本次开发中我使用的是 Windows 开发环境,从 https://dl.espressif.cn/dl/esp-idf/?idf=4.4 下载安装了 ESP-IDF v4.4.5 离线安装包。

    下载地址:https://dl.espressif.com/dl/idf-installer/esp-idf-tools-setup-offline-4.4.5.exe

    具体的安装过程见:https://docs.espressif.com/projects/esp-idf/zh_CN/release-v4.4/esp32s3/get-started/windows-setup.html#esp-idf

  2. ESP-BOX-v0.3.0
    git clone -b v0.3.0 https://github.com/espressif/esp-box.git esp-box-v0.3.0
  3. 工程配置方法如下
    • 项目工程基于 esp-box/examples/image_display
      cd esp-box-v0.3.0\examples\
      xcopy /e /i image_display esp_weather_station
    • 进入 esp_weather_station 目录,设置 ESP32-S2 为目标芯片:
      cd esp_weather_station
      idf.py set-target esp32s3
    • 运行工程配置工具 menuconfig:
      idf.py menuconfig
    • 选择ESP32-S3-BOX-Lite:
      #前往选项 HMI Board Config
      
      #选择ESP32-S3-BOX-Lite
      
      #保存设置,退出 menuconfig 界面。
    • 在此工程上修改代码,添加  wifi_sta_init、http_request、cjson_parser 等代码,详细见代码说明和附件;
    • 编译工程:
      idf.py build
    • 运行以下命令,将刚刚生成的二进制文件烧录至您的 ESP32-S3-BOX-Lite 开发板:
      idf.py -p PORT flash

五、申请天气接口API

  1. 采用心知天气API,参考官方API使用说明:未来15天逐日天气预报和昨日天气
  2. 注册心知天气账号,添加API密钥,将私钥填入到天气请求request url中去:

Fl5428N7iHCzwyz696VGUjYP5c5E

六、代码实现和功能展示

  1. wifi station 初始化代码
    • 参考 esp-idf-v4.4.5\examples\wifi\getting_started\station\main\station_example_main.c 实现:
      #include "esp_system.h"
      #include "esp_wifi.h"
      #include "esp_event.h"
      #include "esp_log.h"
      
      #include "nvs_flash.h"
      
      #include "lwip/err.h"
      #include "lwip/sys.h"
      
      
      static const char *TAG = "main";
      
      /* The examples use WiFi configuration that you can set via project configuration menu
      
         If you'd rather not, just change the below entries to strings with
         the config you want - ie #define EXAMPLE_WIFI_SSID "mywifissid"
      */
      #define EXAMPLE_ESP_WIFI_SSID      "Your SSID"
      #define EXAMPLE_ESP_WIFI_PASS      "Your PASSWORD"
      #define EXAMPLE_ESP_MAXIMUM_RETRY  5
      
      #define ESP_WIFI_SCAN_AUTH_MODE_THRESHOLD WIFI_AUTH_WPA_WPA2_PSK
      
      /* FreeRTOS event group to signal when we are connected*/
      static EventGroupHandle_t s_wifi_event_group;
      
      /* The event group allows multiple bits for each event, but we only care about two events:
       * - we are connected to the AP with an IP
       * - we failed to connect after the maximum amount of retries */
      #define WIFI_CONNECTED_BIT BIT0
      #define WIFI_FAIL_BIT      BIT1
      
      
      static int s_retry_num = 0;
      
      
      static void event_handler(void* arg, esp_event_base_t event_base,
                                      int32_t event_id, void* event_data)
      {
          if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_START) {
              esp_wifi_connect();
          } else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED) {
              if (s_retry_num < EXAMPLE_ESP_MAXIMUM_RETRY) {
                  esp_wifi_connect();
                  s_retry_num++;
                  ESP_LOGI(TAG, "retry to connect to the AP");
              } else {
                  xEventGroupSetBits(s_wifi_event_group, WIFI_FAIL_BIT);
              }
              ESP_LOGI(TAG,"connect to the AP fail");
          } else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP) {
              ip_event_got_ip_t* event = (ip_event_got_ip_t*) event_data;
              ESP_LOGI(TAG, "got ip:" IPSTR, IP2STR(&event->ip_info.ip));
              s_retry_num = 0;
              xEventGroupSetBits(s_wifi_event_group, WIFI_CONNECTED_BIT);
          }
      }
      
      void wifi_init_sta(void)
      {
          s_wifi_event_group = xEventGroupCreate();
      
          ESP_ERROR_CHECK(esp_netif_init());
      
          ESP_ERROR_CHECK(esp_event_loop_create_default());
          esp_netif_create_default_wifi_sta();
      
          wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
          ESP_ERROR_CHECK(esp_wifi_init(&cfg));
      
          esp_event_handler_instance_t instance_any_id;
          esp_event_handler_instance_t instance_got_ip;
          ESP_ERROR_CHECK(esp_event_handler_instance_register(WIFI_EVENT,
                                                              ESP_EVENT_ANY_ID,
                                                              &event_handler,
                                                              NULL,
                                                              &instance_any_id));
          ESP_ERROR_CHECK(esp_event_handler_instance_register(IP_EVENT,
                                                              IP_EVENT_STA_GOT_IP,
                                                              &event_handler,
                                                              NULL,
                                                              &instance_got_ip));
      
          wifi_config_t wifi_config = {
              .sta = {
                  .ssid = EXAMPLE_ESP_WIFI_SSID,
                  .password = EXAMPLE_ESP_WIFI_PASS,
                  /* Authmode threshold resets to WPA2 as default if password matches WPA2 standards (pasword len => 8).
                   * If you want to connect the device to deprecated WEP/WPA networks, Please set the threshold value
                   * to WIFI_AUTH_WEP/WIFI_AUTH_WPA_PSK and set the password with length and format matching to
      	     * WIFI_AUTH_WEP/WIFI_AUTH_WPA_PSK standards.
                   */
                  .threshold.authmode = ESP_WIFI_SCAN_AUTH_MODE_THRESHOLD,
                  .sae_pwe_h2e = WPA3_SAE_PWE_BOTH,
              },
          };
          ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA) );
          ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &wifi_config) );
          ESP_ERROR_CHECK(esp_wifi_start() );
      
          ESP_LOGI(TAG, "wifi_init_sta finished.");
      
          /* Waiting until either the connection is established (WIFI_CONNECTED_BIT) or connection failed for the maximum
           * number of re-tries (WIFI_FAIL_BIT). The bits are set by event_handler() (see above) */
          EventBits_t bits = xEventGroupWaitBits(s_wifi_event_group,
                  WIFI_CONNECTED_BIT | WIFI_FAIL_BIT,
                  pdFALSE,
                  pdFALSE,
                  portMAX_DELAY);
      
          /* xEventGroupWaitBits() returns the bits before the call returned, hence we can test which event actually
           * happened. */
          if (bits & WIFI_CONNECTED_BIT) {
              ESP_LOGI(TAG, "connected to ap SSID:%s password:%s",
                       EXAMPLE_ESP_WIFI_SSID, EXAMPLE_ESP_WIFI_PASS);
          } else if (bits & WIFI_FAIL_BIT) {
              ESP_LOGI(TAG, "Failed to connect to SSID:%s, password:%s",
                       EXAMPLE_ESP_WIFI_SSID, EXAMPLE_ESP_WIFI_PASS);
          } else {
              ESP_LOGE(TAG, "UNEXPECTED EVENT");
          }
      }
  2. http 请求天气接口
    • 代码如下:
      #include "esp_http_client.h"
      
      #define MAX_HTTP_RECV_BUFFER 512
      #define MAX_HTTP_OUTPUT_BUFFER 2048
      
      char http_resp[MAX_HTTP_OUTPUT_BUFFER] = {0};   // Buffer to store response of http request
      
      static void get_weather(char *city)
      {
          char url[256] = {0};
          int content_length = 0;
          esp_http_client_config_t config = {
              .event_handler = _http_event_handler,
          };
          memset(url, 0, sizeof(url));
          snprintf(url, sizeof(url), "http://api.seniverse.com/v3/weather/daily.json?key=Sur7NhbElUj3dSgvL&location=%s&language=en&unit=c&start=0&days=3", city);
          config.url = url;
          esp_http_client_handle_t client = esp_http_client_init(&config);
      
          // GET Request
          esp_http_client_set_method(client, HTTP_METHOD_GET);
          esp_err_t err = esp_http_client_open(client, 0);
          if (err != ESP_OK) {
              ESP_LOGE(TAG, "Failed to open HTTP connection: %s", esp_err_to_name(err));
          } else {
              content_length = esp_http_client_fetch_headers(client);
              if (content_length < 0) {
                  ESP_LOGE(TAG, "HTTP client fetch headers failed");
              } else {
                  int data_read = esp_http_client_read_response(client, http_resp, MAX_HTTP_OUTPUT_BUFFER);
                  if (data_read >= 0) {
                      ESP_LOGI(TAG, "HTTP GET Status = %d, content_length = %d",
                      esp_http_client_get_status_code(client),
                      esp_http_client_get_content_length(client));
                      ESP_LOG_BUFFER_HEX(TAG, http_resp, data_read);
                  } else {
                      ESP_LOGE(TAG, "Failed to read response");
                  }
              }
          }
          esp_http_client_close(client);
      }
  3. cjson 解析天气接口请求的响应:
    • 代码如下:
      int user_parse_json(char *json_data)
      {
          uint8_t i,j;
      	uint8_t result_array_size = 0;
      	uint8_t daily_array_size = 0;
      
      	cJSON *item = NULL;
      	cJSON *root = NULL;
      	cJSON *results_root = NULL;
      	cJSON *daily_root = NULL;
          /* print the version */
          printf("Version: %s\n", cJSON_Version());
      	root = cJSON_Parse(json_data);   /*json_data 为心知天气的原始数据*/ 
          if (!root)  {
            printf("Error before: [%s]\n",cJSON_GetErrorPtr());
      	  return  -1;
      	}
             
          printf("%s\n\n", cJSON_Print(root));   /*将完整的数据以JSON格式打印出来*/
      
      	cJSON *Presult = cJSON_GetObjectItem(root, "results");  /*results 的键值对为数组,*/ 
          printf("%s\n", cJSON_Print(Presult));
      
      
      	result_array_size = cJSON_GetArraySize(Presult);  /*求results键值对数组中有多少个元素*/
          printf("Presult array size is %d\n",result_array_size);
      
          for (i = 0;i < result_array_size; i++) {
      		cJSON *item_results = cJSON_GetArrayItem(Presult, i);
      
              char *sresults = cJSON_PrintUnformatted(item_results);
      		results_root = cJSON_Parse(sresults);
      		if (!results_root) {
      	      printf("Error before: [%s]\n",cJSON_GetErrorPtr());
      		  return  -1;
      		}
      
      		cJSON *Plocation = cJSON_GetObjectItem(results_root, "location");
      
      	    item = cJSON_GetObjectItem(Plocation, "id");
      		strncpy(user_sen_config.id, item->valuestring, 23);
              printf("id:%s\n", cJSON_Print(item));	/*逐个打印*/
      
      
      	    item = cJSON_GetObjectItem(Plocation, "name");
      		strncpy(user_sen_config.name, item->valuestring, 23);
              printf("name:%s\n", cJSON_Print(item));	
      
      	    item = cJSON_GetObjectItem(Plocation, "country");
              strncpy(user_sen_config.country, item->valuestring, 23);
              printf("country:%s\n", cJSON_Print(item));	
      
      	    item = cJSON_GetObjectItem(Plocation, "path");
              strncpy(user_sen_config.path, item->valuestring, 23);
              printf("path:%s\n", cJSON_Print(item));	
      
      	    item = cJSON_GetObjectItem(Plocation, "timezone");
      		strncpy(user_sen_config.timezone, item->valuestring, 23);
              printf("timezone:%s\n", cJSON_Print(item));	
      
      		item = cJSON_GetObjectItem(Plocation, "timezone_offset");
      		strncpy(user_sen_config.timezone_offset, item->valuestring, 23);
              printf("timezone_offset:%s\n", cJSON_Print(item));	
      
      		cJSON *Pdaily = cJSON_GetObjectItem(results_root, "daily");
      
      		daily_array_size = cJSON_GetArraySize(Pdaily);
              printf("Pdaily array size is %d\n",daily_array_size);
      
              for (j = 0;j < daily_array_size;j++) {
      			cJSON *item_daily = cJSON_GetArrayItem(Pdaily, j);
      			
      			char *sdaily = cJSON_PrintUnformatted(item_daily);
      
      			daily_root = cJSON_Parse(sdaily);
      
      			if (!daily_root)  {
      		      printf("Error before: [%s]\n",cJSON_GetErrorPtr());
      			  return  -1;
      			}
      
      		    item = cJSON_GetObjectItem(daily_root, "date");
      			strncpy(user_sen_config.day_config[j].date, item->valuestring, 23);
                  printf("date:%s\n", cJSON_Print(item));		
      			
      		    item = cJSON_GetObjectItem(daily_root, "text_day");
      			strncpy(user_sen_config.day_config[j].text_day, item->valuestring, 23);
                  printf("text_day:%s\n", cJSON_Print(item));	
      
      		    item = cJSON_GetObjectItem(daily_root, "code_day");
      		    strncpy(user_sen_config.day_config[j].code_day, item->valuestring, 23);
                  printf("code_day:%s\n", cJSON_Print(item));		
      
      			item = cJSON_GetObjectItem(daily_root, "text_night");
      			strncpy(user_sen_config.day_config[j].text_night, item->valuestring, 23);
                  printf("text_night:%s\n", cJSON_Print(item));		
      
      			item = cJSON_GetObjectItem(daily_root, "code_night");
      			strncpy(user_sen_config.day_config[j].code_night, item->valuestring, 23);
                  printf("code_night:%s\n", cJSON_Print(item));		
      
      			item = cJSON_GetObjectItem(daily_root, "high");
      			strncpy(user_sen_config.day_config[j].high, item->valuestring, 23);
                  printf("high:%s\n", cJSON_Print(item));		
      
      			item = cJSON_GetObjectItem(daily_root, "low");
      			strncpy(user_sen_config.day_config[j].low, item->valuestring, 23);
                  printf("low:%s\n", cJSON_Print(item));		
      
      			item = cJSON_GetObjectItem(daily_root, "rainfall");
      			strncpy(user_sen_config.day_config[j].rainfall, item->valuestring, 23);
                  printf("rainfall:%s\n", cJSON_Print(item));		
      
      			item = cJSON_GetObjectItem(daily_root, "precip");
      			strncpy(user_sen_config.day_config[j].precip, item->valuestring, 23);
                  printf("precip:%s\n", cJSON_Print(item));		
      
      			item = cJSON_GetObjectItem(daily_root, "wind_direction");
      			strncpy(user_sen_config.day_config[j].wind_direction, item->valuestring, 23);
                   printf("wind_direction:%s\n", cJSON_Print(item));		
      
      			item = cJSON_GetObjectItem(daily_root, "wind_direction_degree");
      			strncpy(user_sen_config.day_config[j].wind_direction_degree, item->valuestring, 23);
                  printf("wind_direction_degree:%s\n", cJSON_Print(item));		
      
      			item = cJSON_GetObjectItem(daily_root, "wind_speed");
      			strncpy(user_sen_config.day_config[j].wind_speed, item->valuestring, 23);
                  printf("wind_speed:%s\n", cJSON_Print(item));		
      
      			item = cJSON_GetObjectItem(daily_root, "wind_scale");
      			strncpy(user_sen_config.day_config[j].wind_scale, item->valuestring, 23);
                  printf("wind_scale:%s\n", cJSON_Print(item));		
      
      			item = cJSON_GetObjectItem(daily_root, "humidity");
      			strncpy(user_sen_config.day_config[j].humidity, item->valuestring, 23);
                  printf("humidity:%s\n", cJSON_Print(item));		
      			cJSON_Delete(daily_root);/*每次调用cJSON_Parse函数后,都要释放内存*/
              }
      		item = cJSON_GetObjectItem(results_root, "last_update");
      		strncpy(user_sen_config.last_update, item->valuestring, 23);
              printf("last_update:%s\n", cJSON_Print(item));	
      		cJSON_Delete(results_root);  /*每次调用cJSON_Parse函数后,都要释放内存*/
          }
      
          printf("id:%s\n", user_sen_config.id);	
      	printf("name:%s\n", user_sen_config.name);
      	printf("country:%s\n", user_sen_config.country);
      	printf("path:%s\n", user_sen_config.path);
      	printf("timezone:%s\n", user_sen_config.timezone);
      	printf("timezone_offset:%s\n", user_sen_config.timezone_offset);
      	for (i = 0; i < daily_array_size; i++) {
          	printf("date:%s\n", user_sen_config.day_config[i].date); 
      		printf("text_day:%s\n", user_sen_config.day_config[i].text_day); 
      		printf("code_day:%s\n", user_sen_config.day_config[i].code_day); 
      		printf("text_night:%s\n", user_sen_config.day_config[i].text_night); 
      		printf("code_night:%s\n", user_sen_config.day_config[i].code_night); 
      		printf("high:%s\n", user_sen_config.day_config[i].high); 
      		printf("low:%s\n", user_sen_config.day_config[i].low); 
      		printf("precip:%s\n", user_sen_config.day_config[i].precip); 
      		printf("wind_direction:%s\n", user_sen_config.day_config[i].wind_direction); 
      		printf("wind_direction_degree:%s\n", user_sen_config.day_config[i].wind_direction_degree); 
      		printf("wind_speed:%s\n", user_sen_config.day_config[i].wind_speed); 
      		printf("wind_scale:%s\n", user_sen_config.day_config[i].wind_scale); 
      		printf("humidity:%s\n", user_sen_config.day_config[i].humidity); 
      	}
      	printf("last_update:%s\n", user_sen_config.last_update);
      
      	cJSON_Delete(root); /*每次调用cJSON_Parse函数后,都要释放内存*/
      
      	return  0;
      }

七、功能展示

  • 主菜单

FvIXJgnbAMsyCU4EICWNEGVULU5k

  • 北京天气展示

FkJLcctCJd_-2_sZbi8ZqwGUzYzj

  • 上海天气展示

Fp3vG0iOcP5ICfBwJ3ANMV14pUTo

附件下载
esp_weather_station.zip
项目工程源代码
团队介绍
在职工程师
团队成员
topgear
评论
0 / 100
查看更多
目录
硬禾服务号
关注最新动态
0512-67862536
info@eetree.cn
江苏省苏州市苏州工业园区新平街388号腾飞创新园A2幢815室
苏州硬禾信息科技有限公司
Copyright © 2024 苏州硬禾信息科技有限公司 All Rights Reserved 苏ICP备19040198号