内容介绍
内容介绍
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 处理接口响应,解析出未来三天的最高气温和最低气温;
- 在显示屏上展示最高和最低气温。
三、软件流程
四、开发环境的搭建
- 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
- ESP-BOX-v0.3.0
git clone -b v0.3.0 https://github.com/espressif/esp-box.git esp-box-v0.3.0
- 工程配置方法如下
-
- 项目工程基于 esp-box/examples/image_display
cd esp-box-v0.3.0\examples\ xcopy /e /i image_display esp_weather_station
- 项目工程基于 esp-box/examples/image_display
-
- 进入 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
- 进入 esp_weather_station 目录,设置 ESP32-S2 为目标芯片:
五、申请天气接口API
- 采用心知天气API,参考官方API使用说明:未来15天逐日天气预报和昨日天气
- 注册心知天气账号,添加API密钥,将私钥填入到天气请求request url中去:
六、代码实现和功能展示
- 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"); } }
- 参考 esp-idf-v4.4.5\examples\wifi\getting_started\station\main\station_example_main.c 实现:
- 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); }
- 代码如下:
- 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; }
- 代码如下:
七、功能展示
- 主菜单
- 北京天气展示
- 上海天气展示
附件下载
esp_weather_station.zip
项目工程源代码
团队介绍
在职工程师
团队成员
topgear
评论
0 / 100
查看更多
猜你喜欢
[Funpack2-5]基于ESP32-S3的一个语音播报系统Funpack活动项目,基于ESP32-Box-lite实现的语音交互小盒子。使用ESP32的WiFi和TTS功能,实现一个语音播报系统,如联网获取粉丝数并播报或者获取天气并播报。
Hessian
2441
Funpack2-5 基于ESP32-S3-BOX-LITE的天气情况播报Funpack2-5ESP32-S3-BOX-LITE tts esp-idf wifi 心知天气
happy
902
Funpack2-6 基于 nRF7002-DK的远程显示控制功能展示本片文章分享我在Funpack第二季第六期活动中使用nRF7002-DK 实现一个远程控制设备的流程及效果展示
反正都一样
644