ESP-12S学习(5)--Get拿天气数据

本文参考Esp8266学习之旅⑤ 8266原生乐鑫SDK高级使用之封装Post与Get请求云端,拿到“天气预报信息”
当然最后肯定不是拿天气预报这么简单啦

一、预备知识点

有几个关键的知识点需要了解下:

Point 1:

GetPost都是HTTP协议对网页操作的两个方法
Get:从指定的资源请求数据
Post:向指定的资源提交要被处理的数据

比如说你访问https://www.baidu.com/index.html,这是Get index.html这个页面,GET/POST都是TCP链接


Point 2:

这里涉及到url的格式问题

https://www.baidu.com:80/index.html
协议名域名端口号文件名

先说一下什么是端口号,端口号是具有网络功能的应用软件的标志符

电脑A打开了可以被电脑B访问的软件,比如共享文件的文件系统软件,还有其他的软件,然后电脑A电脑B访问,这个时候,电脑A就需要知道这些数据究竟是给哪个软件的,就需要端口号来识别了

访问服务器拿去数据,通过HTTP协议,服务器就知道这个数据是给用HTTP协议的软件的

HTTP协议软件对应的端口号是80

然后看到域名www.baidu.com,域名对应的就是IP地址了,我们要把域名转为IP地址才能知道服务器的所在,涉及到DNS,域名系统(服务)协议(DNS)是一种分布式网络目录服务,主要用于域名与 IP 地址的相互转换

最后就是文件名了,文件名之前可能会有路径,例如:https://www.baidu.com/search/error.htmlsearch


二、ESP8266访问url的流程
Created with Raphaël 2.2.0 开始 设置ESP8266 的工作模式 设置wifi的密码名字 查询工作状态 解析url DNS解析ip 初始化连接 通过TCP的方式连接 接收数据 断开连接 结束

子流程:解析URL
解析urlDNS解析ip都是为了下面的初始化连接做准备,初始化连接需要对espconn类型的结构体赋值

espconn结构体如下:

struct espconn {
    /** type of the espconn (TCP, UDP) 选择是TCP、UDP连接*/
    enum espconn_type type;
    /** current state of the espconn  当前的espconn结构体的状态,还在配置的过程就选择ESPCONN_NONE(具体的类型看ctrl+左键espconn_state)*/
    enum espconn_state state;
    /**这个看下面的esp_tcp结构体*/
    union {
        esp_tcp *tcp;
        esp_udp *udp;
    } proto;
    /****************************初始化上面的***********************************/
    /** A callback function that is informed about events for this espconn */
    espconn_recv_callback recv_callback;
    espconn_sent_callback sent_callback;
    uint8 link_cnt;
    void *reverse;
};

/*这个就是上面的union里面的esp_tcp成员*/
typedef struct _esp_tcp {
    int remote_port;		// 访问的服务器的端口(80)
    int local_port;			// 本地的端口espconn_port()获取
    uint8 local_ip[4];		// 本地的ip地址
    uint8 remote_ip[4];		// 访问的服务器的ip地址,DNS解析获取
    /****************************初始化上面的***********************************/
    espconn_connect_callback connect_callback;
    espconn_reconnect_callback reconnect_callback;
    espconn_connect_callback disconnect_callback;
	espconn_connect_callback write_finish_fn;
} esp_tcp;

由上面的初始化结构体可以看到需要DNS解析获得远端ip地址,DNS解析函数是

espconn_gethostbyname(struct espconn *pespconn, const char *hostname, ip_addr_t *addr, dns_found_callback found)

需要定义一个espconn 结构体,通过host去解析,得到ip地址,成功或者超时后调用回调函数,也就是说需要先得到host,再往上看一步,解析url,这里就需要获得host了,当然这里有错误处理的部分是没有列举出来的,为了方便调试,错误的时候可以打印出信息
这样思路就很清晰了,具体的代码看第四部分


三、部分函数的解析
Function 1:wifi_set_opmode()

这个是设置wifi工作模式的函数
在这里插入图片描述


Function 2:wifi_station_set_config()

wifi的配置,如名称,密码
在这里插入图片描述
可以看一下station_config 结构体

struct station_config {
    uint8 ssid[32];
    uint8 password[64];
    uint8 bssid_set;	// Note: If bssid_set is 1, station will just connect to the router
                        // with both ssid[] and bssid[] matched. Please check about this.
    uint8 bssid[6];
};

Function 3:wifi_station_get_connect_status()

检查wifi连接状态
在这里插入图片描述


Function 4:espconn_gethostbyname()

看名字,获取host通过域名,很明显是获取ip地址的函数
在这里插入图片描述
在这里插入图片描述
注意下参数dns_found_callback found,这是成功找到ip后的回调函数,比如说我成功通过域名解析到了对方的ip,这个时候就需要去初始化连接的参数了


Function 5:espconn_regist_connectcb()

这是个注册函数,注册TCB成功建立连接之后的回调函数,便于进行下一步,成功连接后接收数据
在这里插入图片描述


Function 6:espconn_regist_reconcb()

注册连接出错的回调函数,用户可以在注册的函数里面进行重新连接
在这里插入图片描述


Function 7:espconn_connect()

建立TCB连接的api
在这里插入图片描述
在这里插入图片描述


Function 8:espconn_regist_recvcb()

注册成功接收数据后的回调函数
在这里插入图片描述


Function 9:espconn_regist_sentcb()

注册成功发送数据后的回调函数
在这里插入图片描述


Function 10:espconn_regist_disconcb()

TCB在接收完数据之后就是正常的断开连接了
在这里插入图片描述


###### Function 11:espconn_sent()

TCB握手成功之后,发送数据请求给服务器,发送成功后转到上面注册的函数,然后wifi接受到服务器返回来的数据会自行跳转到接收成功注册的函数,在接收成功的注册函数里面可以打印或者是解析数据

在这里插入图片描述


四、代码的实现
user_main.c
void ICACHE_FLASH_ATTR Check(void)
{
	ip_addr_t addr;
	struct espconn pespconn;
    os_printf("Check!\n");
    /*查询wifi的状态*/
    if (wifi_station_get_connect_status() == STATION_GOT_IP)
    {
    	os_timer_disarm(&ledtimer);
    	if (wifi_station_get_connect_status() == STATION_GOT_IP)	// 再次确认
    	{
    		os_printf("succeed!\n");
    		os_printf("wifi_name:TP-LINK-8266\n");
    		startHttpQuestByGET(url);
    	}

    }
    else
    {
    	os_printf("fail!\n");
    }
}

/*
 * @name:	user_init[用户程序入口]
 */
void ICACHE_FLASH_ATTR
user_init(void)
{
	system_timer_reinit();
	uart_init(74880, 74880);	//设置串口0和串口1的波特率
    os_printf("SDK version:%s\n", system_get_sdk_version());
    os_printf("Hello8266!\n");

    wifi_set_sleep_type(NONE_SLEEP_T);	// 关闭睡眠

    // 设置wifi的工作模式
    wifi_set_opmode(0x01);	// Station模式

    os_printf("Config!\n");
    /*设置wifi密码名字*/
    os_strcpy(wifi_config.ssid, "TP-LINK-8266");
    os_strcpy(wifi_config.password, "82668266");
    wifi_station_set_config(&wifi_config);
    wifi_station_connect();

	/*软件定时器*/
	os_timer_disarm(&ledtimer);									// 关闭定时器
	os_timer_setfn(&ledtimer, (os_timer_func_t *)Check, (void*)0);			// 设置定时器的回调函数
	os_timer_arm_us(&ledtimer, 500000, 1);						// 打开定时器,500ms,重复
}

http_parse.c
#include "osapi.h"
#include "http_parse.h"
#include "espconn.h"
#include "c_types.h"
#include "mem.h"
#include "user_interface.h"
#include "driver/uart.h"

uint8	host[32] = {0};			// 放域名
uint8	filename[100] = {0};	// 放文件名
uint16	port = 80;				// 端口号,默认是80
uint8	buffer[1024];			// 数据缓存端

struct espconn pespconn;

void ICACHE_FLASH_ATTR my_http_parse(uint8 *url)
{
	uint8 *url1;
	uint8 *url2;

	if (*url == 0)
	{
		os_printf("url不存在\n");
		return ;
	}

	/*获取host*/
	url1 = url;
	if (strncmp(url1, "http://", strlen("http://")) == 0)
	{
		url1 = url1 + strlen("http://");	// 记录的是域名的开始
	}
	else if (strncmp(url1, "https://", strlen("https://")) == 0)
	{
		url1 = url1 + strlen("https://");	// 记录的是域名的开始
	}
	
	url2 = strchr(url1, (int)'/');			// 需查找的字符不能"/"双引号

	if (url2 == NULL)
	{
		os_printf("host获取失败!!!\n");
		return;
	}

	memcpy(host, url1, strlen(url1) - strlen(url2));	// 从url1复制域名到host
	url2 += 1;
	host[strlen(url1) - strlen(url2)] = 0;				// host的结尾加0

	/*获取文件名*/
	memcpy(filename, url2, strlen(url2));		// 从url2复制文件名到filename
	filename[strlen(url2)] = 0;					// filename数组的文件名长度最后面一个元素为0


	/*获取端口号*/
	url2 = strchr(url1, (int)':');
	if (url2 != NULL)
		port = atoi(url2 + 1);		// 将:后面的数字字符串转为整形,只会转换最近的数字

	os_printf("parse succeed!!!\n");
}


void ICACHE_FLASH_ATTR startHttpQuestByGET(uint8 *url)
{
	ip_addr_t remote_ip;
	memset(buffer, 0, 1024);
	os_printf("start parse!!!\n");
	my_http_parse(url);
	os_sprintf(buffer, GET, filename, host);
	os_printf("host:%s\n", host);
	wifi_set_sleep_type(NONE_SLEEP_T);	// 关闭睡眠
	espconn_gethostbyname(&pespconn, host, &remote_ip, DNS_Get_Ip_Fcb);
}

void DNS_Get_Ip_Fcb(const char *name, ip_addr_t *remote_ip, void *callback_arg)
{
	if (remote_ip)
		os_printf("DSN ip get succeed!!!\n");
	else
		os_printf("DSN ip get fail!!!\n");
	struct ip_info ststion_info;
	wifi_get_ip_info(0x00, &ststion_info);
	Init_Connect(remote_ip, &ststion_info.ip, port);
}

void ICACHE_FLASH_ATTR Init_Connect(ip_addr_t *remote_ip, ip_addr_t *local_ip, int remote_port)
{
	os_printf("init espconn struct!!!\n");
	/*配置连接结构体*/
	pespconn.type = ESPCONN_TCP;
	pespconn.state = ESPCONN_NONE;

	pespconn.proto.tcp = (esp_tcp *) os_zalloc(sizeof(esp_tcp));	// 分配内存填充0
	os_memcpy(pespconn.proto.tcp->local_ip, local_ip, 4);
	os_memcpy(pespconn.proto.tcp->remote_ip, remote_ip, 4);
	pespconn.proto.tcp->local_port = espconn_port();
	pespconn.proto.tcp->remote_port = remote_port;

	/*注册*/
	espconn_regist_connectcb(&pespconn, Sent_Data);
	espconn_regist_reconcb(&pespconn, Tcp_Disconect);

	/*连接服务器*/
	os_printf("start connect!!!\n");
	espconn_connect(&pespconn);
}

void Sent_Data()
{
	os_printf("connect succeed!!!\n");
	espconn_regist_sentcb(&pespconn, Sent_Succeed);	// 注册发送成功
	espconn_regist_recvcb(&pespconn, Receive_Succeed);	// 注册接收成功
	espconn_regist_disconcb(&pespconn, Release_Succeed);	// 注册断开

	os_printf("Start request\n");
	os_printf("%s\n", buffer);
	os_printf("Over request\n");
	espconn_send(&pespconn, buffer, strlen(buffer));				// 发送
}

void Tcp_Disconect(void *arg, sint8 err)
{
	os_printf("Connect fail!!!\n");
	os_printf("Reconnect!!!\n");
	espconn_connect(&pespconn);
}
void Sent_Succeed()
{
	os_printf("Send succeed!!!\n");
}
void Receive_Succeed(void *arg, char *pdata, unsigned short len)
{
	os_printf("Receive succeed!!!\n");
	os_printf("Data:%s\n", pdata);
}
void Release_Succeed()
{
	os_printf("Release succeed!!!\n");
}

http_parse.c的函数不单纯是解析的,还包括了连接的,我懒得去新建一个文件,所以直接写在一个文件里面,也不是很多的函数


http_parse.h
#ifndef _HTTP_PARSE_
#define _HTTP_PARSE_

#include "ip_addr.h"

#define GET "GET /%s HTTP/1.1\r\nContent-Type: text/html;charset=utf-8\r\nAccept: */*\r\nHost: %s\r\nConnection: Keep-Alive\r\n\r\n"

void ICACHE_FLASH_ATTR my_http_parse(uint8 *url);
void ICACHE_FLASH_ATTR startHttpQuestByGET(uint8 *url);
void DNS_Get_Ip_Fcb(const char *name, ip_addr_t *ipaddr, void *callback_arg);
void ICACHE_FLASH_ATTR Init_Connect(ip_addr_t *remote_ip, ip_addr_t *local_ip, int remote_port);
void  Sent_Data();
void  Tcp_Disconect(void *arg, sint8 err);
void  Sent_Succeed();
void  Receive_Succeed(void *arg, char *pdata, unsigned short len);
void  Release_Succeed();

#endif

上面的代码包含了调试内容,忽略即可


五、调试问题

Q1:url解析的问题
字符串比较函数strncmp不要用strcmp去代替,后者难实现,指定长度可以减少很多的工作量

Q2:DNS解析问题
先看一下DNS解析函数的接口,可以看到这里面有4个参数,参数1是连接结构体的地址,参数2是域名的地址,参数3是获取的ip的存放地址,参数4是回调函数

err_t espconn_gethostbyname(struct espconn *pespconn, const char *hostname, ip_addr_t *addr, dns_found_callback found);

解析错误极有可能是调用函数传参的问题,很明显,接口是没有定义struct espconnip_addr_t 结构体的,只是定义了结构体指针,那么你在调用的时候就是需要定义一个清晰的结构体,而不是指针,然后再取结构体的指针传参,如下面

ip_addr_t remote_ip;
espconn_gethostbyname(&pespconn, host, &remote_ip, DNS_Get_Ip_Fcb);

2020.6.2
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值