嵌入式WIFI芯片通过lwip获取心知天气实时天气信息(包含完整代码)

一、天气API

1. 心知天气的产品简介

        HyperData 是心知天气的高精度气象数据产品,通过标准的 Restful API 接口,提供标准化的数据访问。无论是 APP、智能硬件还是企业级系统都可以轻松接入心知的精细化天气数据。
        HyperData API V4版是当前的最新版本。相比 V3 版的 API 服务,V4 版 API 通过技术架构的重新设计和底层代码的重构重写,在产品使用方式、接口性能、服务稳定性和开发效率上得到大幅改进。新的数据都会直接发布 V4 版接口,老数据会从 V3 版逐步迁移到 V4 版。
以下为V4版和V3版API的主要区别对比:

V4版

V3版

产品简介

心知基于全新架构开发的数据API产品,在产品使用方式、接口性能、服务稳定性和开发效率上得到大幅改进

心知老版数据API产品,将逐步迁移升级到V4版

产品定位

以公里级网格等专业气象数据为主(V3全部迁移后将支持城市级数据)

以城市级基础天气数据为主

鉴权方式

需要公钥私钥配合使用,更安全,
查看 V4鉴权方法

仅需要私钥,使用简单,但泄露后可能造成盗用

如何试用

目前需要 联系我们 人工申请

在官网控制台自助开通试用

产品文档

心知天气 API 使用手册(V4版) · 心知科技

心知天气 API 使用手册(V3版) · 心知科技

        我们这里使用的是心知天气的V3免费版,可以在心知天气的控制台去免费申请,这个免费版不限制使用次数,只限制访问频率(最大1次/秒)。


2. 心知天气API使用

(1) 首先要获取你的账号密钥,查看/修改你的API密钥 · 心知科技,我这里直接使用私钥查询天            气,优点是方便,缺点是不安全,容易泄露私钥。

嵌入式WIFI芯片通过lwip获取心知天气实时天气信息(包含完整代码)_第1张图片

(2)然后通过HTTP GET获取天气信息,URL为:

http://api.seniverse.com/v3/weather/now.json?key=your_api_key&location=ip&language=zh-Hans&unit=c

只需要将URL里面的your_api_key替换为你自己的私钥,就可以自动获取你IP所在地的天气了。如果想获取其他城市的天气只需要将location的参数替换下就可以了。例如:

http://api.seniverse.com/v3/weather/now.json?key=your_api_key&location=beijing&language=zh-Hans&unit=c

嵌入式WIFI芯片通过lwip获取心知天气实时天气信息(包含完整代码)_第2张图片

  可以在你的浏览器来测试下这个链接,我的结果如下:

我的浏览器有JSON解析插件,如果没有的话应该是一行JSON数据

嵌入式WIFI芯片通过lwip获取心知天气实时天气信息(包含完整代码)_第3张图片


二、LWIP实现简单的HTTP GET请求

1. HTTP简单介绍

HTTP协议是互联网上应用最为广泛的一种网络协议,它的全称是Hypertext Transfer Protocol(超文本传输协议),默认的端口号为80。HTTP协议是一种用于由客户端向服务器传输超文本(例如HTML)的协议,它基于TCP/IP通信协议进行数据传输。

HTTP协议的工作流程大致如下:

  1. 客户端(通常是浏览器)与服务器建立连接。这个连接是通过TCP/IP协议建立的,通常需要指定服务器的IP地址和端口号。
  2. 客户端向服务器发送一个HTTP请求。HTTP请求通常包括一个请求行、一些请求头和请求体。请求行包括请求方法(如GET、POST等)、请求的资源路径和HTTP协议的版本。请求头包含一些额外的信息,如请求的资源类型、请求的认证信息等。请求体则包含客户端发送给服务器的实际数据。
  3. 服务器接收到HTTP请求后,根据请求方法和路径查找并返回相应的文件作为应答。这个文件可以是HTML页面、图片、视频等资源。
  4. 客户端与服务器关闭连接。在完成数据传输后,客户端和服务器会关闭连接,释放网络资源。

以上就是HTTP协议的基本工作流程。通过这个协议,客户端可以向服务器请求和接收各种类型的资源,从而实现互联网上的信息共享和交互。
嵌入式WIFI芯片通过lwip获取心知天气实时天气信息(包含完整代码)_第4张图片


2. LWIP实现HTTP GET步骤

  • 首先通过LWIP获取主机(也就是域名)的IP地址。
  • 然后TCP通过80端口连接这个IP地址。
  • 向主机发送数据。
  • 接受主机返回的数据。

下面是具体的代码:

(1)通过LWIP的DNS接口获取主机的IP地址

static OS_Semaphore_t wait_dns_sem;
static ip_addr_t      my_http_ip;

static void dns_found(const char *name, ip_addr_t *host_ip, void *callback_arg)
{
    //获取到DNS的回调函数
    OS_SemaphoreRelease(&wait_dns_sem);    //释放信号量
    my_http_ip.addr = host_ip->addr;       //保存获取到的IP地址
}

/**
 * @brief get ip address by lwip dns
 * 
 * @param hostname host name
 * @param ip host ip address
 * @param cb dns found call back function
 * @return int return execution result
 */
static int ln_drv_get_ip_by_dns(char *hostname,ip_addr_t *ip,dns_found_callback cb)
{
    //创建FreeRTOS的二值信号量
    OS_Status ret_sta = OS_SemaphoreCreateBinary(&wait_dns_sem);
    if (ret_sta != OS_OK) {
        LOG(LOG_LVL_ERROR, "Wait DNS sem creat fail\r\n");
        return HAL_ERROR;
    }
    //通过DNS获取主机地址IP
    err_t ret = dns_gethostbyname(hostname, ip, cb,NULL);;
    if (ret == ERR_INPROGRESS){
        //需要通过向dns服务器发送dns请求数据来获取hostname对应的IP地址
    }else if (ret == ERR_OK){
        //从dns缓存表寻找hostname对应的IP地址
        OS_SemaphoreRelease(&wait_dns_sem);
    }else if(ret == ERR_OK){
        //获取IP失败
        LOG(LOG_LVL_ERROR, "Get dns result err.Error code:%d\n",ret);
        return HAL_ERROR;
    }
    //等待信号量释放
    OS_SemaphoreWait(&wait_dns_sem, 30000);

    if(OS_SemaphoreGetCount(&wait_dns_sem) == 0){
        //信号量被释放,保存IP地址
        ip->addr = my_http_ip.addr;
        return HAL_OK;
    }else{
        //信号量未被释放,等待超时
        LOG(LOG_LVL_ERROR, "Get dns result timeout.\n");
        return HAL_ERROR;
    }
}

(2)LWIP通过TCP Socket连接目标IP地址,且发送和接收数据

/**
 * @brief http get request
 * 
 * @param host_name host name
 * @param url url,resource path
 * @param ret_str the http get return
 * @param ret_str_len the content returned by HTTP GET
 * @param ret_str_max_len the content length returned by HTTP GET
 * @return int return execution result
 */
int ln_drv_http_get(uint8_t *host_name,uint8_t *url,uint8_t *ret_str,uint32_t *ret_str_len,uint32_t ret_str_max_len)
{
    //首先通过DNS获取主机IP
    ip_addr_t host_ip;
    host_ip.addr = 0;
    if(ln_drv_get_ip_by_dns((char*)host_name,&host_ip,(dns_found_callback)dns_found) != HAL_OK){
        LOG(LOG_LVL_ERROR, "Get HOST IP failed.\n");
        return HAL_ERROR;
    }else{
        LOG(LOG_LVL_INFO, "HOST IP:%s.\n",ip4addr_ntoa(&(host_ip.addr)));
    }

    //通过Socket创建TCP连接
    int     ret  = 0;
    struct  sockaddr_in  client_addr;
    int     sock = socket(AF_INET,SOCK_STREAM,0);
    if(sock < 0){
        LOG(LOG_LVL_ERROR, "Socket init failed\n");
        return HAL_ERROR;
    }
    memset(&client_addr,0,sizeof(client_addr));
    client_addr.sin_family = AF_INET;
    client_addr.sin_port = htons(80);
    client_addr.sin_addr.s_addr = host_ip.addr;
    memset(&(client_addr.sin_zero), 0, sizeof(client_addr.sin_zero));
    
    for(int i = 0; i < 10; i ++){
        ret = connect(sock,(struct sockaddr *)&client_addr,sizeof(struct sockaddr));
        if(ret != -1)
            break;
        OS_MsDelay(50);
    }
    
    if(ret == -1){
        LOG(LOG_LVL_ERROR, "TCP Connect failed!\n");
        closesocket(sock);
        OS_MsDelay(10);
        return HAL_ERROR;
    }else{
        //向主机发送GET数据
        ret = write(sock,url,strlen((char*)url));
        if(ret != strlen((char*)url)){
            LOG(LOG_LVL_ERROR, "TCP write data failed!\n");
            closesocket(sock);
            return HAL_ERROR;
        }
        //接受主机返回的数据
        ret = recv(sock, ret_str,ret_str_max_len,0);
        closesocket(sock);
        if(ret <= 0){
            LOG(LOG_LVL_ERROR, "TCP read data failed!\n");
            closesocket(sock);
            return HAL_ERROR;
        }else{
            *ret_str_len = ret;
        }
    }
    closesocket(sock);
    return HAL_OK;
}

测试代码:

#define WEATHER_HOST_NAME   "api.seniverse.com"
#define WEATHER_URL         "GET /v3/weather/now.json?key=your_key=ip&language=zh-Hans&unit=c\r\n\r\n"  
//记得替换里面的your_key
#define GET_NET_DATA_LEN    2048
uint32_t data_len = 0;
uint8_t *ret_data = NULL;
data_len = 0;
ret_data = OS_Malloc(GET_NET_DATA_LEN);
memset(ret_data,0,GET_NET_DATA_LEN);
ln_drv_http_get((uint8_t*)&WEATHER_HOST_NAME,(uint8_t*)&WEATHER_URL,(uint8_t*)ret_data,&data_len,GET_NET_DATA_LEN);
LOG(LOG_LVL_INFO,"HTTP GET RET:%s",(uint8_t *)ret_data);
OS_Free(ret_data);

测试结果:

分析返回数据里面天气以及位置信息:

因为我只需要城市名字、天气代码和温度这三个比较见的信息,所以就只是简单的查询字符串来查找数据,正常应该用CJSON来解析的

static int find_information(uint8_t *data,uint8_t *weather_code,int8_t *weather_tem,uint8_t *position_str)
{
    char    *token = NULL; 
    char    *endptr = NULL; 
    char    delimiters[] = ",";  	
    token = strtok((char*)data,delimiters);
    while(token != NULL){
        token = strtok(NULL,delimiters);
        if(memcmp("name",token+1,strlen("name")) == 0){
            strcpy((char*)position_str,token+8);
						position_str[strlen(position_str)-1] = 0x0;//delete '"'
        }
        if(memcmp("code",token+1,strlen("code")) == 0){
            my_weather_code = strtol(token+8,&endptr,0);
        }
        if(memcmp("temperature",token+1,strlen("temperature")) == 0){
            my_weather_tem = strtol(token+15,&endptr,0);
            return HAL_OK;
        }
    }
    return HAL_ERROR;
}

3. 完整代码

先调用  ln_drv_get_net_data_init  这个函数,这个函数会创建一个Task,每隔几秒定时获取天气信息,在其它任务中通过 ln_drv_get_net_data  这个函数获取天气信息。weather_code,weather_tem,position_str,这三个参数分别是天气代码,温度,城市名字。天气代码说明见:V3天气现象代码说明 · 心知科技

上层封装函数:

#define WEATHER_HOST_NAME   "api.seniverse.com"
#define WEATHER_URL         "GET /v3/weather/now.json?key=yout_key&location=ip&language=zh-Hans&unit=c\r\n\r\n"
#define WEATHER_HOST_IP     "116.62.81.138"
#define GET_NET_DATA_LEN    2048

static OS_Thread_t g_get_net_data_thread;
static uint8_t my_weather_code = 0;
static int8_t  my_weather_tem  = 0;
static uint8_t my_position_str[50] = {0};
static uint8_t cur_status      = 0;
static void get_net_data_task_entry(void *params);


int ln_drv_get_net_data_init(void)
{
    if(OS_OK != OS_ThreadCreate(&g_get_net_data_thread, "Get net data Task", get_net_data_task_entry, NULL, OS_PRIORITY_BELOW_NORMAL, 2*1024)) {
        return HAL_ERROR;
    }
		return HAL_OK;
}

static int find_information(uint8_t *data,uint8_t *weather_code,int8_t *weather_tem,uint8_t *position_str)
{
    char    *token = NULL; 
    char    *endptr = NULL; 
	char 	delimiters[] = ",";  	
    token = strtok((char*)data,delimiters);
    while(token != NULL){
        token = strtok(NULL,delimiters);
        if(memcmp("name",token+1,strlen("name")) == 0){
            strcpy((char*)position_str,token+8);
						position_str[strlen(position_str)-1] = 0x0;//delete '"'
        }
        if(memcmp("code",token+1,strlen("code")) == 0){
            my_weather_code = strtol(token+8,&endptr,0);
        }
        if(memcmp("temperature",token+1,strlen("temperature")) == 0){
            my_weather_tem = strtol(token+15,&endptr,0);
            return HAL_OK;
        }
    }
    return HAL_ERROR;
}
static void get_net_data_task_entry(void *params)
{
    uint32_t data_len = 0;
    uint8_t *ret_data = NULL;
    while (1)
    {
        data_len = 0;
        ret_data = OS_Malloc(GET_NET_DATA_LEN);
        memset(ret_data,0,GET_NET_DATA_LEN);
        if(HAL_OK == ln_drv_http_get((uint8_t*)&WEATHER_HOST_NAME,(uint8_t*)&WEATHER_URL,(uint8_t*)ret_data,&data_len,GET_NET_DATA_LEN)){
            LOG(LOG_LVL_INFO,"HTTP GET RET:%s",(uint8_t *)ret_data);
            if(HAL_OK == find_information(ret_data,&my_weather_code,&my_weather_tem,my_position_str)){
                cur_status = 1;
								//LOG(LOG_LVL_INFO,"my_weather_code:%d\my_weather_tem:%d\my_position_str:%s\n",(uint8_t *)ret_data);
            }
        }else{
            LOG(LOG_LVL_INFO,"Get weather information failed.");
        }
        OS_Free(ret_data);

        //OS_MsDelay(1000 * 60 * 60);
        OS_MsDelay(2000);
    }
    
}

int ln_drv_get_net_data(uint8_t *weather_code,int8_t *weather_tem,uint8_t *position_str)
{
    if(cur_status == 0){
        return HAL_BUSY;
    }else{
        *weather_code = my_weather_code;
        *weather_tem =  my_weather_tem;
        memcpy(position_str,my_position_str,strlen((char*)my_position_str));
        cur_status = 0;
    }
    return HAL_OK;
}

http get代码:

static OS_Semaphore_t wait_dns_sem;
static ip_addr_t      my_http_ip;

static void dns_found(const char *name, ip_addr_t *host_ip, void *callback_arg)
{
    OS_SemaphoreRelease(&wait_dns_sem);
    my_http_ip.addr = host_ip->addr;
}

/**
 * @brief get ip address by lwip dns
 * 
 * @param hostname host name
 * @param ip host ip address
 * @param cb dns found call back function
 * @return int return execution result
 */
static int ln_drv_get_ip_by_dns(char *hostname,ip_addr_t *ip,dns_found_callback cb)
{
    OS_Status ret_sta = OS_SemaphoreCreateBinary(&wait_dns_sem);
    if (ret_sta != OS_OK) {
        LOG(LOG_LVL_ERROR, "Wait DNS sem creat fail\r\n");
        return HAL_ERROR;
    }
    err_t ret = dns_gethostbyname(hostname, ip, cb,NULL);;
    if (ret == ERR_INPROGRESS){
        //需要通过向dns服务器发送dns请求数据来获取hostname对应的IP地址
    }else if (ret == ERR_OK){
        //从dns缓存表寻找hostname对应的IP地址
        OS_SemaphoreRelease(&wait_dns_sem);
    }else if(ret == ERR_OK){
        LOG(LOG_LVL_ERROR, "Get dns result err.Error code:%d\n",ret);
        return HAL_ERROR;
    }
    OS_SemaphoreWait(&wait_dns_sem, 30000);
    if(OS_SemaphoreGetCount(&wait_dns_sem) == 0){
        ip->addr = my_http_ip.addr;
        return HAL_OK;
    }else{
        LOG(LOG_LVL_ERROR, "Get dns result timeout.\n");
        return HAL_ERROR;
    }
}

/**
 * @brief http get request
 * 
 * @param host_name host name
 * @param url url,resource path
 * @param ret_str the http get return
 * @param ret_str_len the content returned by HTTP GET
 * @param ret_str_max_len the content length returned by HTTP GET
 * @return int return execution result
 */
int ln_drv_http_get(uint8_t *host_name,uint8_t *url,uint8_t *ret_str,uint32_t *ret_str_len,uint32_t ret_str_max_len)
{
    //get host ip by dns
    ip_addr_t host_ip;
    host_ip.addr = 0;
    if(ln_drv_get_ip_by_dns((char*)host_name,&host_ip,(dns_found_callback)dns_found) != HAL_OK){
        LOG(LOG_LVL_ERROR, "Get HOST IP failed.\n");
        return HAL_ERROR;
    }else{
        LOG(LOG_LVL_INFO, "HOST IP:%s.\n",ip4addr_ntoa(&(host_ip.addr)));
    }

    //tcp connect by lwip socket
    int     ret  = 0;
    struct  sockaddr_in  client_addr;
    int     sock = socket(AF_INET,SOCK_STREAM,0);
    if(sock < 0){
        LOG(LOG_LVL_ERROR, "Socket init failed\n");
        return HAL_ERROR;
    }
    memset(&client_addr,0,sizeof(client_addr));
    client_addr.sin_family = AF_INET;
    client_addr.sin_port = htons(80);
    client_addr.sin_addr.s_addr = host_ip.addr;
    memset(&(client_addr.sin_zero), 0, sizeof(client_addr.sin_zero));
    
    for(int i = 0; i < 10; i ++){
        ret = connect(sock,(struct sockaddr *)&client_addr,sizeof(struct sockaddr));
        if(ret != -1)
            break;
        OS_MsDelay(50);
    }
    
    if(ret == -1){
        LOG(LOG_LVL_ERROR, "TCP Connect failed!\n");
        closesocket(sock);
        OS_MsDelay(10);
        return HAL_ERROR;
    }else{
        ret = write(sock,url,strlen((char*)url));
        if(ret != strlen((char*)url)){
            LOG(LOG_LVL_ERROR, "TCP write data failed!\n");
            closesocket(sock);
            return HAL_ERROR;
        }
        ret = recv(sock, ret_str,ret_str_max_len,0);
        closesocket(sock);
        if(ret <= 0){
            LOG(LOG_LVL_ERROR, "TCP read data failed!\n");
            closesocket(sock);
            return HAL_ERROR;
        }else{
            *ret_str_len = ret;
        }
    }
    closesocket(sock);
    return HAL_OK;
}

测试结果:

嵌入式WIFI芯片通过lwip获取心知天气实时天气信息(包含完整代码)_第5张图片


        因为本人也在学习,所以文中难免会有错误的地方,欢迎指出。如果本文对你的学习或工作有一定帮助,麻烦帮忙点赞收藏,谢谢!

你可能感兴趣的:(物联网,嵌入式,LWIP,WIFI,HTTP,RTOS)