前缀
在某招聘网站上看到的esp32网络编程入门面试题。
一、实现功能
- 启动后连接WIFI,WIFI账号密码分别为"UPGRADE_AP",“TEST1234”,你可以用手机热点的方式模拟WIFI账号密码
- 连接上WIFI后,访问URL:https://dummyjson.com/products/1 获取其中字段 “brand” 的值,并使用 ESP_LOGI 或者其他输出函数输出到调试终端(串口)
- 关闭WIFI
额外要求:
- 使用一个动态分配的内存来存储URL获取的内容,并妥善处理善后。
二、实现过程
2.1Wi-Fi连接部分
- 初始化nvs分区
ESP_ERROR_CHECK(nvs_flash_init());
- 初始化WiFi网络接口
ESP_ERROR_CHECK(esp_netif_init());
- 初始化系统任务函数和Wi-Fi事件处理
//创建默认事件循环。
ESP_ERROR_CHECK(esp_event_loop_create_default());
// 创建STA类型的网卡。
esp_netif_create_default_wifi_sta();
- 初始化WiFi设置默认值
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
ESP_ERROR_CHECK(esp_wifi_init(&cfg));
- 注册WiFi事件回调任务
//获取wifi事件中的任何事件
ESP_ERROR_CHECK( esp_event_handler_register(WIFI_EVENT,
ESP_EVENT_ANY_ID,
&event_handler,
NULL) );
//获取ip地址事件
ESP_ERROR_CHECK( esp_event_handler_register(IP_EVENT,
IP_EVENT_STA_GOT_IP,
&event_handler,
NULL) );
- 配置STA的相关参数及启动WiFi
wifi_config_t wifi_config={
.sta={
.ssid = "UPGRADE_AP",
.password = "TEST1234"
}
};
ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA) );
ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &wifi_config) );
//启动wifi,将会启动wifi回调事件函数 ’event_handler‘
ESP_ERROR_CHECK(esp_wifi_start() );
- WiFi回调函数
event_handler
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) {
//WiFi事件启动后开始尝试连接WiFi
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
{
ESP_LOGI(TAG,"connect to the AP fail");
//xEventGroupSetBits(wifi_event_group, WIFI_FAIL_BIT);
}
} 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(wifi_event_group, WIFI_CONNECTED_BIT);
}
else{
printf("%s,%d\r\n",event_base,event_id);
}
}
2.2https请求部分
在ESP32的官方例程中是提供有关https request 例程,具体位置在esp-idf中examples下protocols\http_server\simple文件夹中,该例程简单演示了espidf多种方式使用esp32作为http客户端请求服务器。
https_get_request_using_crt_bundle
主要使用crt_bundle_attach方式,该方式是ESP x509 证书捆绑包 API 提供了一种简单的方法来包含用于 TLS 服务器验证的自定义 x509 根证书捆绑包。捆绑包主要过menuconfig配置完成的,然后通过cmake生成捆绑包并将其嵌入。
#define http_url "https://dummyjson.com/products/1"
static const char HOWSMYSSL_REQUEST[] = "GET https://dummyjson.com/products/1 HTTP/1.1\r\n"
"Host: dummyjson.com\r\n"
"User-Agent: esp-idf/1.0 esp32\r\n"
"\r\n";
static void https_get_request_using_crt_bundle(void)
{
ESP_LOGI(TAG, "https_request using crt bundle");
esp_tls_cfg_t cfg = {
.crt_bundle_attach = esp_crt_bundle_attach,
};
https_get_request(cfg, http_url, HOWSMYSSL_REQUEST);
}
- 参考例程
https_get_request
函数进行修改,将接收到的数据进行处理ProcessFunc(buf,len)
static void https_get_request(esp_tls_cfg_t cfg, const char *WEB_SERVER_URL, const char *REQUEST)
{
char buf[4096];
int ret, len;
struct esp_tls *tls = esp_tls_conn_http_new(WEB_SERVER_URL, &cfg);
if (tls != NULL) {
ESP_LOGI(TAG, "Connection established...");
} else {
ESP_LOGE(TAG, "Connection failed...");
goto exit;
}
size_t written_bytes = 0;
do {
ret = esp_tls_conn_write(tls,
REQUEST + written_bytes,
strlen(REQUEST) - written_bytes);
if (ret >= 0) {
ESP_LOGI(TAG, "%d bytes written", ret);
written_bytes += ret;
} else if (ret != ESP_TLS_ERR_SSL_WANT_READ && ret != ESP_TLS_ERR_SSL_WANT_WRITE) {
ESP_LOGE(TAG, "esp_tls_conn_write returned: [0x%02X](%s)", ret, esp_err_to_name(ret));
goto exit;
}
} while (written_bytes < strlen(REQUEST));
ESP_LOGI(TAG, "Reading HTTPS response...");
do {
len = sizeof(buf) - 1;
bzero(buf, sizeof(buf));
ret = esp_tls_conn_read(tls, (char *)buf, len);
if (ret == ESP_TLS_ERR_SSL_WANT_WRITE || ret == ESP_TLS_ERR_SSL_WANT_READ) {
continue;
}
if (ret < 0) {
ESP_LOGI(TAG, "esp_tls_conn_read returned [-0x%02X](%s)", -ret, esp_err_to_name(ret));
break;
}
if (ret == 0) {
ESP_LOGI(TAG, "connection closed");
break;
}
len = ret;
ESP_LOGI(TAG, "%d bytes read", len);
//处理接收数据函数
ProcessFunc(buf,len);
break; //
/* Print response directly to stdout as it is read */
} while (0);
//关闭
exit:
esp_tls_conn_delete(tls);
for (int countdown = 1; countdown >= 0; countdown--) {
// ESP_LOGI(TAG, "%d...", countdown);
vTaskDelay(1000 / portTICK_PERIOD_MS);
}
}
- ProcessFunc(buf,len)
处理数据,发现返回数据为json格式且带有相应标头等数据
{ "id":1,
"title":"iPhone 9",
"description":"An apple mobile which is nothing like apple",
"price":549,
"discountPercentage":12.96,
"rating":4.69,
"stock":94,
"brand":"Apple",
"category":"smartphones",
"thumbnail":"https://i.dummyjson.com/data/products/1/thumbnail.jpg",
"images":[
"https://i.dummyjson.com/data/products/1/1.jpg",
"https://i.dummyjson.com/data/products/1/2.jpg",
"https://i.dummyjson.com/data/products/1/3.jpg",
"https://i.dummyjson.com/data/products/1/4.jpg",
"https://i.dummyjson.com/data/products/1/thumbnail.jpg"
]
}
static void ProcessFunc(char *buf,int len)
{
//过滤掉无用数据,保留json部分内容
int i,rev_len;
for ( i = 0; i < len; i++)
{
/* code */
if(buf[i]=='{')
break;
}
rev_len = len - i + 1;
char * rev_buf = (char *)malloc(rev_len*sizeof(char));
memcpy(rev_buf,buf + i,rev_len);
printf("rev:%s\r\n",rev_buf);
//json解析
cJSON* cjson_body = NULL;
cJSON* cjson_brand = NULL;
cjson_body = cJSON_Parse(rev_buf);
if(cjson_body == NULL)
{
printf("cjson_body parse fail.\n");
return ;
}
cjson_brand = cJSON_GetObjectItem(cjson_body, "brand");
if(cjson_brand == NULL)
{
printf("cjson_brand parse fail.\n");
return ;
}
ESP_LOGI("brand","%s",cjson_brand->valuestring);
// printf("brand: %s\n", cjson_brand->valuestring);
//释放资源
cJSON_Delete(cjson_body);
free(rev_buf);
rev_buf = NULL;
}