【Ai-WB2高级篇】 HTTP天气请求

[复制链接]
查看739 | 回复5 | 2024-9-14 12:04:55 | 显示全部楼层 |阅读模式
本帖最后由 lovzx 于 2024-9-26 10:14 编辑

WB2-32S HTTP 获取天气数据教程

功能分析

HTTP请求天气数据首先得要有天气数据来源,其次得有网络请求获取到天气数据,天气api接口大多数都是json数据格式,所以还得需要json数据解析
总结一下大概需要以下几个模块
  • HTTP网络请求
  • 天气数据接口
  • JSON数据解析

天气请求接口

天气接口使用的是易客云的免费api官网地址是http://yiketianqi.com/index主要是对个人免费开放,返回的数据是JSON数据格式
7日天气API接口地址为http://v1.yiketianqi.com/api?unescape=1&version=v91&appid=YOU_APPID&appsecret=YOU_APP_SECRET
其中YOU_APPID和YOU_APP_SECRET的具体值可以根据自己申请的账号进行替换(使用我的也可以,用的多了可能就达到次数限制了)。
如果需要可以通过上面易客云的连接注册申请账号,登录后会看到自己的appid和appsecret数据,进行替换就可以了,我自己账号替换后的实际地址是:http://v1.yiketianqi.com/api?une ... &appsecret=ZD9U8Jqt

为了验证接口地址是否正确可以复制替换后的连接到浏览器地址栏粘贴后点击回车看看是否能够得到正确的天气json数据,不出意外就可以看到天气数据了

天气接口默认会请求网络ip所在城市的天气,如果要请求其它地方的天气可以在接口天气city参数,例如请求深圳的地址可以在上面的url后面加上&cityid=101280601具体的接口参数信息可以[点击查看接口文档]http://yiketianqi.com/index/doc?version=v91

什么是JSON数据

JSON(JavaScript Object Notation) 是一种轻量级的数据交换格式,常用于web服务之间的数据传输格式,与XML一样是基于纯文本的数据格式,可以被记事本等文本编辑器打开。JSON数据一般由两种格式

名称:值

  键值对集合,类似于一个key对应一个value,value可以是简单的String,Int或者是一个JSON对象
值的有序列表

  和大部分语言中的数组类似,存放的同一个类型的集合,可以是String,Int或者是JSON对象
详情可以参考[JSON教程]

HTTP
HTTP协议(超文本传输协议Hyper Text Transfer Protocol),是基于TCP协议的应用层协议,简单理解就是客户端和服务端传输数据的一种数据传输规则。计算机中的各种协议就是为了解决不同设备之间的数据传输诞生的。

HTTP是无状态协议,即协议本身并不会对发送过的数据和状态进行持久化处理。这样就可以快速处理大量数据,提高效率
一个HTTP请求主要包含以下几个方面,
HTTP URL、HTTP Request和HTTP Response

  • HTTP URL

URL包含了用于查找某个资源的详细信息,其格式如下
  1. http://host[:port]/[path]
复制代码

请求方法有
GET - 请求指定的资源。
POST - 提交数据以处理请求。
HEAD - 请求资源的响应头信息。
PUT - 上传文件或者更新资源。
DELETE - 删除指定的资源。
OPTIONS - 请求获取服务器支持的请求方法。
TRACE - 回显服务器收到的请求,主要用于诊断。
CONNECT - 建立一个隧道用于代理服务器的通信,通常用于 HTTPS
一般常用的是GET和POST

常见的请求头包括
Host、User-Agent、Accept、Accept-Encoding、Content-Length、Conten-Type等
详情可参考
[http协议]

一个完整的HTTP请求头如下图所示

http_request_head.png

HTTP Response
response的报文格式和请求报文格式类似
http_报文结构.png
可以看到和请求报文格式都是分为开始行,响应头,和响应主,最明显的区别是开始行多了一个状态码,用于标识此次请求的状态。常用的是200 OK,30X表示重定向,40X表示资源找不到,50x表示服务器异常,具体可以参考[HTTP状态码]

使用浏览器查看请求头(以edge浏览器为例,其它都差不多

浏览器打开http链接后点击F12即可打开开发者模式,然后找到网络tab
edge_debug.png
然后在左侧选择所要查看的请求,依次选择标头->请求标头-> 勾选原始可以查看原始的格式,如下图所示
http_报文.png
现在对HTTP有了大致的理解了,可以把上面的天气接口粘贴到浏览器地址栏然后回车就可以看到请求信息了,浏览器看到的是经处理过的数据,去掉了响应头等信息,根据Content-Type来显示响应的具体内容,比如是html就显示解析加载后的html页面,如果是json等纯文本格式就不经过任何处理直接显示纯文本,我们用的天气接口就属于是纯文本类型的json格式

编写代码
如果还没有搭建wb2环境的可以参考这两篇教程

建议新建工程通过复制其它demo里面的程序,这里我复制了helloworld项目,具体步骤如下

  • application\get-started目录下的helloworld项目并修改目录为weather,
  • 进入weather目录同样修改helloworld为weather
  • 如果有删除则build_out目录
  • 打开Makefile文件修改PROJECT_NAME的值为weather
  • 编译烧写重启运行

如果上面能正常运行就可以继续了
新建weather_client文件

再main.c文件同级目录新建分别新建weather_client.h和weather.c文件,weather_client.c包含weather_client.h
1. 首先要定义请求天气的task

  1.     void http_weather_task(void* fun);
复制代码

2. 定义前面提到的接口地址
  1. #define HTTP_HOST "v1.yiketianqi.com"
  2. //需要根据自己的账号来修改appid和appsecret的值,我这个可能会达到限制无法使用
  3. #define HTTP_PATH "api?unescape=1&version=v91&appid=11411413&appsecret=ZD9U8Jqt"
  4. #define PORT 80
复制代码
最后文件如下
weather_client.h
  1. #ifndef _WEATHER_CLIENT_H
  2. #define _WEATHER_CLIENT_H

  3. #define HTTP_HOST "v1.yiketianqi.com"
  4. //需要根据自己的账号来修改appid和appsecret的值,我这个可能会达到限制无法使用
  5. #define HTTP_PATH "api?unescape=1&version=v91&appid=11411413&appsecret=ZD9U8Jqt"
  6. #define PORT 80

  7. typedef void(*callback(char* data));

  8. #ifdef __cplusplus
  9. extern "C" {
  10. #endif
  11.     /**
  12.      * http 请求天气数据任务
  13.      * @param  回调函数
  14.      */
  15.     void http_weather_task(void* fun);

  16. #ifdef __cplusplus
  17. }
  18. #endif

  19. #endif
复制代码
weather_client.c

  1. #include "weather_client.h"
  2. #include "FreeRTOS.h"
  3. #include "task.h"

  4. void http_weather_task(void* argv)
  5. {
  6.     while(1)
  7.     {
  8.         vTaskDelay(1000);
  9.     }
  10. }
复制代码
添加wifi相关依赖

PS: 这里主要是针对新手如果了解怎么去添加依赖可以跳过

sdk目录里面的application都是存放的各种demo有iot、peripherals、protocols、security、storage、system、wifi,都是根据内容进行分类了,见名知意很容易就知道里面大致是什么内容了,需要其它方面学习的可以首先参考到application目录下面查找看看有没有需要的
components下面是各个功能模块的组件,如network、os、platform等

我们首先要添加的就是wifi相关的功能,打开application\protocols\socket\tcp_client\main.c
看名字可以知道这个项目是tcp客户端的项目,而我们的教程HTTP协议也是基于tcp的,所以有很大程度的重合

找到include头文件

  1. #include <FreeRTOS.h>
  2.   #include <task.h>
  3.   #include <stdio.h>
  4.   #include <string.h>
  5.   #include <blog.h>
  6.   #include <aos/yloop.h>
  7.   #include <aos/kernel.h>
  8.   #include <lwip/sockets.h>
  9.   #include <lwip/tcpip.h>
  10.   #include <wifi_mgmr_ext.h>
  11.   #include <cli.h>
  12.   #include <hal_wifi.h>
  13.   #include <lwip/init.h>
  14.   #include "tcp_example.h"
复制代码

和wifi相关的肯定是需要的,先把wifi相关的头文件复制到我们项目的main.c文件里面
lwip是一个开源的轻量化的TCP/IP协议栈,在嵌入式网络模块经常看到他的身影,wb2也不意外,同样是移植了lwip的网络模块,lwip相关的头文件一样要复制到weather_client.c中
打开tcp_client项目中的Makefile文件,可以看到INCLUDE_COMPONENTS设置了很多依赖的库,特别是wifi、lwip和rtos有关的字段,肯定是我们所需要的,同样要按照格式复制到自己项目中的Makefile中

  1. COMPONENTS_NETWORK := sntp dns_server lwip lwip_dhcpd lwip_netif
  2. //后面太多了省略了
  3. INCLUDE_COMPONENTS += freertos_riscv_ram
  4. //.....省略......
  5. INCLUDE_COMPONENTS += $(COMPONENTS_NETWORK)
复制代码
  1. COMPONENTS_BLSYS   := bltime blfdt blmtd bloop loopadc looprt loopset
  2. COMPONENTS_VFS     := romfs
  3. COMPONENTS_NETWORK := sntp dns_server
  4. COMPONENTS_WIFI    := wifi wifi_manager wifi_hosal

  5. INCLUDE_COMPONENTS += freertos_riscv_ram bl602 bl602_std newlibc hosal mbedtls_lts lwip vfs yloop utils cli blog blog_testc coredump
  6. INCLUDE_COMPONENTS += $(COMPONENTS_NETWORK)
  7. INCLUDE_COMPONENTS += $(COMPONENTS_BLSYS)
  8. INCLUDE_COMPONENTS += $(COMPONENTS_VFS)
  9. INCLUDE_COMPONENTS += $(PROJECT_NAME)
  10. INCLUDE_COMPONENTS += ${COMPONENTS_WIFI}
复制代码

为了方便区分,这里定义了COMPONENTS_WIFI变量,这样的更加清晰明了
然后开始编译项目,我这里是用的make命令编译,看自己使用的是那种方式...
不出意外的话要出意外了, 错误如下

  1. D:/vscodeProject/aithinker/Ai-Thinker-WB2/components/network/wifi/include/bl60x_fw_api.h:6:10: fatal error: bl_os_private.h: No such file or directory
  2.     6 | #include "bl_os_private.h"
  3.       |          ^~~~~~~~~~~~~~~~~
  4. compilation terminated.
  5. make[1]: *** [/d/vscodeProject/aithinker/Ai-Thinker-WB2/make_scripts_riscv/component_wrapper.mk:313:mai n.o] 错误 1
  6. make: *** [D:\vscodeProject\aithinker\Ai-Thinker-WB2/make_scripts_riscv/project.mk:579:component-weathe r-build] 错误 2
  7. make: *** 正在等待未完成的任务....
复制代码

讲下我的大致思路,然后根据自己的情况自己去尝试解决错误,无论是学什么,特别是学计算机相关的一定不能害怕错误,错误才是能学到经验的最好过程,还能收获解决问题本身的能力,遇到的多了自然就知道是哪里的问题,凭直觉就可以大致定位到问题然后就是消灭问题

PS: 找错误一定要从上往下找,因为编译顺序问题,后面的错误有可能是前面错误引起的,解决了前面的问题后面可能就没有错误了

上面错误信息是**fatal error: bl_os_private.h: No such file or directory**很明显是找不到这个文件了,我们直接去项目中搜索,我这里用的vscode,直接在搜索栏搜索这个文件
bl_os_private.png

可以看到是有这个文件的,其位置是commponents\bl_os_adapter\include\bl_os_adapter.h
根据这个结构很明显看出来这个文件是属于bl_os_adapter库的,所以我们要在Makefile中加入这个库,我们就加在WIIF变量后面

  1. COMPONENTS_WIFI    := wifi wifi_manager wifi_hosal bl_os_adapter
复制代码
加完后继续make后出现了同样的错误
  1. D:/vscodeProject/aithinker/Ai-Thinker-WB2/components/network/wifi_manager/bl60x_wifi_driver/bl_rx.c:25:10: fatal error: bl_wpa.h: No such file or directory
  2.    25 | #include <bl_wpa.h>
复制代码
依然是找不到文件,是不是轻车熟路了!

按照上面方法找到wpa_supplicant并添加依赖后继续make
是不是还发现了找不到文件?没错还是一样的方法找到后添加依赖,重复上面依赖后终于make成功了
连接wifi
查看tcp_client项目中的main.c文件发现里面除了tcp相关的代码就是连接wifi的代码

  • main函数

main函数中初始化了tcpip_init()函数并且创建了proc_main_entry任务
  • proc_main_entry函数

  注册了wifi事件的回调函数,并其开始了hal_wifi_start_firmware_task任务,然后就删除自身任务了
  • hal_wifi_start_firmware_task

  此任务是lib库里面的,没有源码,看名字也知道是和wifi硬件有关的
  • event_cb_wifi_event

  这个函数是注册wifi事件回调的,里面有个重要的参数event,就是通过event->code值来确认wifi回调的具体事件,常见的有CODE_WIFI_ON_MGMR_DONE wifi_mgmr库初始化完成,然后就可以利用该库提供的函数进行操作,还有CODE_WIFI_ON_GOT_IP用来通知活取到网络ip了,此时就可以进行联网操作了,我们也是在收到此通知后开始请求天气数据的,还有其它的ap事件,扫描事件等等,根据需求在收到对应事件后进行处理其它操作

最后就是把ROUTER_SSID和ROUTER_PWD替换成你自己的wifi热点了,替换完main.c文件如下

  1. /*
  2. * @Author: xuhongv@yeah.net xuhongv@yeah.net
  3. * @Date: 2022-10-03 15:02:19
  4. * @LastEditors: xuhongv@yeah.net xuhongv@yeah.net
  5. * @LastEditTime: 2022-10-08 14:55:16
  6. * @FilePath: \bl_iot_sdk_for_aithinker\applications\get-started\helloworld\helloworld\main.c
  7. * @Description: Hello world
  8. */
  9. #include <stdio.h>
  10. #include <string.h>
  11. #include <FreeRTOS.h>
  12. #include <task.h>
  13. #include <blog.h>
  14. #include "bl_sys.h"
  15. #include <hal_wifi.h>
  16. #include <lwip/init.h>
  17. #include <wifi_mgmr_ext.h>
  18. #include <lwip/sockets.h>
  19. #include <lwip/tcpip.h>

  20. #define ROUTER_SSID "emm"
  21. #define ROUTER_PWD "1234567890"


  22. static wifi_conf_t conf = {
  23.     .country_code = "CN",
  24. };

  25. /**
  26. * @brief wifi_sta_connect
  27. *        wifi station mode connect start
  28. * @param ssid
  29. * @param password
  30. */
  31. static void wifi_sta_connect(char* ssid, char* password)
  32. {
  33.     wifi_interface_t wifi_interface;

  34.     wifi_interface = wifi_mgmr_sta_enable();
  35.     wifi_mgmr_sta_connect(wifi_interface, ssid, password, NULL, NULL, 0, 0);
  36. }

  37. /**
  38.   * @brief event_cb_wifi_event
  39.   *      wifi connet ap event Callback function
  40.   * @param event
  41.   * @param private_data
  42.   */
  43. static void event_cb_wifi_event(input_event_t* event, void* private_data)
  44. {
  45.     static char* ssid;
  46.     static char* password;

  47.     switch (event->code)
  48.     {
  49.         case CODE_WIFI_ON_INIT_DONE:
  50.         {
  51.             printf("[APP] [EVT] INIT DONE %lld\r\n", aos_now_ms());
  52.             wifi_mgmr_start_background(&conf);
  53.         }
  54.         break;
  55.         case CODE_WIFI_ON_MGMR_DONE:
  56.         {
  57.             printf("[APP] [EVT] MGMR DONE %lld\r\n", aos_now_ms());
  58.             //_connect_wifi();

  59.             wifi_sta_connect(ROUTER_SSID, ROUTER_PWD);
  60.         }
  61.         break;
  62.         case CODE_WIFI_ON_SCAN_DONE:
  63.         {
  64.             printf("[APP] [EVT] SCAN Done %lld\r\n", aos_now_ms());
  65.             // wifi_mgmr_cli_scanlist();
  66.         }
  67.         break;
  68.         case CODE_WIFI_ON_DISCONNECT:
  69.         {
  70.             printf("[APP] [EVT] disconnect %lld\r\n", aos_now_ms());
  71.         }
  72.         break;
  73.         case CODE_WIFI_ON_CONNECTING:
  74.         {
  75.             printf("[APP] [EVT] Connecting %lld\r\n", aos_now_ms());
  76.         }
  77.         break;
  78.         case CODE_WIFI_CMD_RECONNECT:
  79.         {
  80.             printf("[APP] [EVT] Reconnect %lld\r\n", aos_now_ms());
  81.         }
  82.         break;
  83.         case CODE_WIFI_ON_CONNECTED:
  84.         {
  85.             printf("[APP] [EVT] connected %lld\r\n", aos_now_ms());

  86.         }
  87.         break;
  88.         case CODE_WIFI_ON_PRE_GOT_IP:
  89.         {
  90.             printf("[APP] [EVT] connected %lld\r\n", aos_now_ms());

  91.         }
  92.         break;
  93.         case CODE_WIFI_ON_GOT_IP:
  94.         {
  95.             printf("[APP] [EVT] GOT IP %lld\r\n", aos_now_ms());
  96.             printf("[SYS] Memory left is %d Bytes\r\n", xPortGetFreeHeapSize());
  97.             //WiFi connection succeeded, create TCP client task
  98.             xTaskCreate(tcp_client_task, (char*)"tcp_client_task", 1024 * 2, NULL, 16, NULL);
  99.         }
  100.         break;
  101.         case CODE_WIFI_ON_PROV_SSID:
  102.         {
  103.             printf("[APP] [EVT] [PROV] [SSID] %lld: %s\r\n",
  104.                 aos_now_ms(),
  105.                 event->value ? (const char*)event->value : "UNKNOWN");
  106.             if (ssid)
  107.             {
  108.                 vPortFree(ssid);
  109.                 ssid = NULL;
  110.             }
  111.             ssid = (char*)event->value;
  112.         }
  113.         break;
  114.         case CODE_WIFI_ON_PROV_BSSID:
  115.         {
  116.             printf("[APP] [EVT] [PROV] [BSSID] %lld: %s\r\n",
  117.                 aos_now_ms(),
  118.                 event->value ? (const char*)event->value : "UNKNOWN");
  119.             if (event->value)
  120.             {
  121.                 vPortFree((void*)event->value);
  122.             }
  123.         }
  124.         break;
  125.         case CODE_WIFI_ON_PROV_PASSWD:
  126.         {
  127.             printf("[APP] [EVT] [PROV] [PASSWD] %lld: %s\r\n", aos_now_ms(),
  128.                 event->value ? (const char*)event->value : "UNKNOWN");
  129.             if (password)
  130.             {
  131.                 vPortFree(password);
  132.                 password = NULL;
  133.             }
  134.             password = (char*)event->value;
  135.         }
  136.         break;
  137.         case CODE_WIFI_ON_PROV_CONNECT:
  138.         {
  139.             printf("[APP] [EVT] [PROV] [CONNECT] %lld\r\n", aos_now_ms());
  140.             printf("connecting to %s:%s...\r\n", ssid, password);
  141.             wifi_sta_connect(ssid, password);
  142.         }
  143.         break;
  144.         case CODE_WIFI_ON_PROV_DISCONNECT:
  145.         {
  146.             printf("[APP] [EVT] [PROV] [DISCONNECT] %lld\r\n", aos_now_ms());
  147.         }
  148.         break;
  149.         default:
  150.         {
  151.             printf("[APP] [EVT] Unknown code %u, %lld\r\n", event->code, aos_now_ms());
  152.             /*nothing*/
  153.         }
  154.     }
  155. }


  156. static void proc_main_entry(void* pvParameters)
  157. {

  158.     aos_register_event_filter(EV_WIFI, event_cb_wifi_event, NULL);
  159.     hal_wifi_start_firmware_task();
  160.     aos_post_event(EV_WIFI, CODE_WIFI_ON_INIT_DONE, 0);
  161.     vTaskDelete(NULL);
  162. }

  163. void main(void)
  164. {

  165.     puts("[OS] Starting weather Stack...\r\n");
  166.     tcpip_init(NULL, NULL);
  167.     puts("[OS] proc_main_entry task...\r\n");
  168.     xTaskCreate(proc_main_entry, (char*)"main_entry", 1024, NULL, 15, NULL);
  169. }
复制代码

然后执行编译命令你会出现另一个错误

  1. D:/vscodeProject/aithinker/Ai-Thinker-WB2/applications/get-started/weather-demo/weather/main.c:49:33: error: unknown type name 'input_event_t'
  2.    49 | static void event_cb_wifi_event(input_event_t* event, void* private_data)
复制代码
  1. #include <aos/yloop.h>
  2. #include <aos/kernel.h>
复制代码
  1. COMPONENTS_BLSYS   := bltime blfdt blmtd bloop loopadc looprt loopset
  2. COMPONENTS_VFS     := romfs
  3. COMPONENTS_NETWORK := sntp dns_server lwip lwip_dhcpd lwip_netif
  4. COMPONENTS_WIFI    := wifi wifi_manager wifi_hosal bl_os_adapter wpa_supplicant blcrypto_suite

  5. INCLUDE_COMPONENTS += freertos_riscv_ram bl602 bl602_std hosal newlibc mbedtls_lts lwip vfs yloop utils cli blog blog_testc coredump
  6. INCLUDE_COMPONENTS += rfparam_adapter_tmp
  7. INCLUDE_COMPONENTS += $(COMPONENTS_BLSYS)
  8. INCLUDE_COMPONENTS += $(COMPONENTS_VFS)
  9. INCLUDE_COMPONENTS += $(COMPONENTS_NETWORK)
  10. INCLUDE_COMPONENTS += ${COMPONENTS_WIFI}
  11. INCLUDE_COMPONENTS += $(PROJECT_NAME)
复制代码
最后编译烧录重启后就可以在串口助手上看到连上wifi的log了

  1. IP:192.168.134.211
  2. MASK: 255.255.255.0
  3. Gateway: 192.168.134.210
  4. [lwip] netif status callback  
  5. IP: 192.168.134.211  
  6. MK: 255.255.255.0  
  7. GW: 192.168.134.210
复制代码

http请求

前面了解过http是基于tcp协议的,lwip中提供了socket库,通过socket可以创建tcp连接,然后对socket按照http报文格式进行读写就可以实现http请求了

sockest.h文件位置为components\network\lwip\src\include\lwip\sockets.h
lwip\apps其实也提供了了http_client.h,大致试过里面的函数,没找到怎么用的,就索性就通过socket自己写了
socket常用的函数有下面几个

  1. //根据指定网络类型协议创建socket,返回socket描述符
  2. int lwip_socket(int domain, int type, int protocol);
  3. //sockfd需要连接的socketfd,name是远程socket的地址信息,
  4. int lwip_connect(int sockfd, const struct sockaddr *name, socklen_t namelen);

  5. //从指定socketfd中读取len字节数据到mem缓存中,返回值是实际读取的字节数
  6. ssize_t lwip_read(int s, void *mem, size_t len);
  7. //从dataptr中读取size个数据到socketfd中,返回读取成功的数据数
  8. ssize_t lwip_write(int s, const void *dataptr, size_t size);

  9. //获取连接到该socke的client信息
  10. int lwip_accept(int s, struct sockaddr *addr, socklen_t *addrlen);
  11. //把ip和端口信息绑定到指定的socket上
  12. int lwip_bind(int s, const struct sockaddr *name, socklen_t namelen);
  13. //监听socket,如果有客户端连接上了该socke就可以通过accpet获取到客户端信息
  14. int lwip_listen(int s, int backlog);
  15. //关闭socket
  16. int lwip_close(int s);
复制代码

我们只需要创建socke并连接到天气服务器提供http服务就可以进行读写操作了,由于socket的接口只支持ip形式的地址,所以在使用socket之前需要把域名转换成IP,需要用到lwip\api.h下面的netconn_gethostbyname函数

最终创建socket代码如下

  1. ip_addr_t dns_ip;
  2.     struct sockaddr_in client_addr;
  3.     //根据服务器域名获取ip
  4.     ret = netconn_gethostbyname(HTTP_HOST, &dns_ip);
  5.     if (ERR_OK != ret) {
  6.         printf("get host ip error\n");
  7.     }
  8.     //把int32类型的ip转换成十进制点分ip
  9.     host_ip = ip_ntoa(&dns_ip);
  10.     printf("host name: %s, ip: %s\n", HTTP_HOST, host_ip);
  11.     while (1)
  12.     {
  13.         if (try_count > 3) {
  14.             goto end;
  15.         }
  16.         //创建socket
  17.         sock = socket(AF_INET, SOCK_STREAM, 0);
  18.         if (sock < 0)
  19.         {
  20.             printf("socket cerate error: %d\n", sock);
  21.             vTaskDelay(10);
  22.             try_count++;
  23.             continue;
  24.         }
  25.         //初始化client
  26.         memset(&client_addr, 0, sizeof(struct sockaddr_in));
  27.         //设置socket为ip4协议
  28.         client_addr.sin_family = AF_INET;
  29.         //把"127.0.0.1"点分十进制ip转换成32位的int整数格式
  30.         client_addr.sin_addr.s_addr = dns_ip.addr;
  31.         //设置端口号
  32.         client_addr.sin_port = htons(PORT);
  33.         //连接socket
  34.         if (connect(sock,
  35.             (struct sockaddr*)&client_addr,
  36.             sizeof(client_addr)) != 0) {
  37.             printf("connect failed!\n");
  38.             closesocket(sock);
  39.             vTaskDelay(10);
  40.             try_count++;
  41.             continue;
  42.         }
  43.     }

复制代码
  1. "GET http://v1.yiketianqi.com:80/api?unescape=1&version=v91&appid=11411413&appsecret=ZD9U8Jqt HTTP/1.0\r\n"
  2. "User-Agent: lwip client\r\n"
  3. "Accept: */*\r\n"
  4. "Host: v1.yiketianqi.com\r\n"
  5. "Connection: Close\r\n"
  6. "\r\n"
复制代码
向socket中写入请求数据

  1. static int create_request_string(const char* host,
  2.     int port,
  3.     const char* uri,
  4.     char* buf, size_t buf_size) {
  5.     return snprintf(buf, buf_size, HTTPC_REQ_11_PROXY_PORT, host, port, uri, "lwip client", host);
  6. }

  7.     //创建请求数据
  8.     len = create_request_string(HTTP_HOST, PORT, HTTP_PATH, send_buf, SEND_BUF_SIZE);
  9.     //向socket写入请求数据
  10.     write(sock, send_buf, len);
复制代码
最后写入成功后开始从socket中读取数据

  1. do {
  2.             /**
  3.              * 每次读取32个字节放入tmp中,不一定够32字节
  4.              * 所以用返回值表示读取的字节数
  5.              * 如果返回0说明读完了,如果返回-1说明出现异常(读完了继续读也是 返回-1)
  6.              */
  7.             len = recv(sock, tmp, 32, 0);
  8.             //计算一共读了多少字节
  9.             sum_len += len;
  10.             if (len < 0) {
  11.                 printf("recv error: %d\n", len);
  12.                 break;
  13.             }
  14.             if (0 == len) {
  15.                 printf("receive successful!\n");
  16.                 break;
  17.             }
  18.             /**
  19.              * 从接收的tmp数组中复制len个字节到recv_buf中
  20.              * 起始地址+已经读的字节就是需要拼接的地方
  21.              * 最后可能只读了1个字节,其余的字节都是之前读的旧数据
  22.              * 所以读了多少字节就复制多少字节,不能全部复制
  23.              */
  24.             strncat(recv_buf, tmp, len);
  25.         } while (1);
复制代码
  1. void weather_callback(char *response)
  2.     {
  3.         //TODO
  4.     }

  5.     //传入回调函数
  6.     xTaskCreate(http_weather_task, (char*)"tcp_client_task", 1024 * 2, weather_request_callback, 16, NULL);

  7.     //处理回调函数
  8.     if (NULL != argv) {
  9.         callback* fun = (callback*)argv;
  10.         fun(recv_buf);
  11.     }
复制代码
  1. INCLUDE_COMPONENTS += cjson
复制代码
在我们的项目main.c文件中添加cSJON.h头文件

  1. #include <cJSON.h>
复制代码
  1. char* body = strstr(response, "\r\n\r\n");
  2.     //把response body数据解析成json对象
  3.     cJSON* root = cJSON_Parse(body + 4);
  4.     //如果不是json对象说明返回的内容不对,跳到退出的地方
  5.     if (root->type != cJSON_Object)
  6.     {
  7.         goto end;
  8.     }
复制代码
  1. //获取city数据
  2.     cJSON* city = cJSON_GetObjectItem(root, "city");
  3.     //获取到data数组,里面是7天的天气数据数据
  4.     cJSON* day_json = cJSON_GetObjectItem(root, "data");
  5.     //获取array大小
  6.     int days = cJSON_GetArraySize(day_json);
  7.     printf("days: %d,city: %s\n\n", days, city->valuestring);
复制代码

最后循环遍历每个数组里面的对象,获取每天的天气信息,结束的时候不能要记得释放内存(前面的response是通过读tcp连接malloc动态申请的内存),完整的处理代码如下
  1. void weather_request_callback(char* response)
  2. {
  3.     /**
  4.      * 响应头和响应体是通过\r\n分割开的,每一行结尾也是一个\r\n
  5.      * 用两个\r\n就可以把字符串分开
  6.      */
  7.     char* body = strstr(response, "\r\n\r\n");
  8.     cJSON* root = cJSON_Parse(body + 4);
  9.     if (root->type != cJSON_Object)
  10.     {
  11.         goto end;
  12.     }

  13.     //获取city数据
  14.     cJSON* city = cJSON_GetObjectItem(root, "city");
  15.     //获取到data数组,里面是7天的天气数据数据
  16.     cJSON* day_json = cJSON_GetObjectItem(root, "data");
  17.     //获取array大小
  18.     int days = cJSON_GetArraySize(day_json);
  19.     printf("days: %d,city: %s\n\n", days, city->valuestring);

  20.     /*
  21.     * 遍历获取到date、week、wea、tem数据
  22.     * 具体里面有什么数据可以看weather.json里面的内容
  23.     */
  24.     for (int i = 0;i < days;i++) {
  25.         //拿到数字里面的对象
  26.         cJSON* item = cJSON_GetArrayItem(day_json, i);
  27.         cJSON* date = cJSON_GetObjectItem(item, "date");
  28.         cJSON* week = cJSON_GetObjectItem(item, "week");
  29.         cJSON* wea = cJSON_GetObjectItem(item, "wea");
  30.         cJSON* tem = cJSON_GetObjectItem(item, "tem");
  31.         //天气信息打印串口
  32.         printf("date: %s\nweek: %s\nwea: %s\ntem: %s℃\n\n",
  33.             date->valuestring,
  34.             week->valuestring,
  35.             wea->valuestring,
  36.             tem->valuestring);
  37.     }
  38. end:
  39.     //释放内存
  40.     vPortFree(response);
  41.     //释放cJSON对象
  42.     cJSON_Delete(root);
  43. }
复制代码

运行效果如下
运行效果.png
到这里简单的http请求天气数据就算是完成了,个人水平有限难免有理解不到位或者写错的地方请多多包含,最后完整代码放到gitee上https://gitee.com/jrobot_Q_Q/wb2-http-weather

bl_os_private.png
http_request.png
input_event_t.png
json数据.png
报文字符串.png

本帖被以下淘专辑推荐:

回复

使用道具 举报

lovzx | 2024-9-14 12:04:56 | 显示全部楼层

<iframe src="https://player.bilibili.com/player.html?isOutside=true&aid=113133834278968&bvid=BV1XqtNecEH2&cid=25652894015&p=1" scrolling="no" border="0" frameborder="no" framespacing="0" allowfullscreen="true"></iframe>

视频地址

回复 支持 反对

使用道具 举报

bzhou830 | 2024-9-14 13:29:37 | 显示全部楼层
选择去发光,而不是被照亮
回复

使用道具 举报

爱笑 | 2024-9-14 13:46:50 | 显示全部楼层
不错不错,写的挺好。
用心做好保姆工作
回复 支持 反对

使用道具 举报

WT_0213 | 2024-9-14 16:18:24 | 显示全部楼层
回复

使用道具 举报

iiv | 2024-9-14 20:30:22 | 显示全部楼层
大佬优秀
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则