物联网 == Internet of things【IOT】
注: Intenet 的 I 是大写,
internet = 互连网 将计算机网络互连的任意网络,不一定遵循TCP/IP协议组的通信规则
Internet = 使用TCP/IP协议族作为通信规则的特殊互连网(即互联网)
物联网最重要的属性,就是将设备接入互联网
例:两个设备使用2.4G模块,进行无线通信,这不算为【物联网】
多个设备接入wifi路由器,在局域网内传输数据,这不算为【物联网】
因为虽然设备实现了【物联】,但是没有实现设备接入【互联网】,所以不能称之为【物联网】
设备接入互联网的作用:
设备接入互联网后,可以将数据上报给物联网平台,也可以接受物联网平台的指令。
没有一种通信方式可以满足所有的物联网通信,所以根据不同场景选用不同的通信方式。
例:工业现场一般采用以太网的方式进行通信;家庭智能家电采用wifi的方式;共享单车采用3G/4G的方式,等……
本次学习采用wifi的通信方式,CPU为乐鑫ESP8266 WIFI芯片,模组为安信可ESP-12F。
乐鑫官网对ESP9266的介绍 :https://www.espressif.com/zh-hans/products/modules/esp8266
安信可官网对ESP-12F的介绍:https://docs.ai-thinker.com/esp8266
ESP-12F模组:
电源3.3V,外部晶振26MHz,外部4M Flash,PCB板载天线
1)AT指令
使用额外的单片机,通过串口发送AT指令(串口数据)到ESP8266,ESP8266则会执行相应功能,如连接wifi、发送网络数据等。缺点:需要额外单片机增加成本,效率低
2)SDK编程
ESP8266集成32bit内核处理器,带片上SRAM,可以通过GPIO等外设连接传感器和其他设备。可以将ESP8266独立应用,程序存放在外部Flash,ESP8266读取外部Flash中的程序,执行相应功能。优点:无需额外增加单片机,节省成本,效率高。
SDK包中文件夹的应用
1)乐鑫提供的编译环境需要使用linux操作系统,需安装虚拟机
2)安信可提供windows操作系统下的一体化编译环境。
下载网址:https://docs.ai-thinker.com/%E5%BC%80%E5%8F%91%E5%B7%A5%E5%85%B72
ESP8266的编程是以SDK包为基础的。
1)ESP8266的SDK包配置
1.复制一个SDK软件包根据自己的需求进行重命名
2.将SDK包中的driver_lib重命名为app
3.将SDK包中的exaples目录中的IoT_demo复制到app目录中
选择替换
将examples文件夹删除掉
将third_party目录中的makefile文件修改为makefile.bak
2)导入SDK工程
1.在IDE的project区域右键选择import->C/C+±>Existing Code as Makefile Project
导入刚刚配置的SDK包
3)编译SDK工程
编译前需要“Clean Project”,清除之前的编译残留。
4)尝试编译成功
5)SDK包文件整改
根据图中所示将SDK包中的文件进行删减和修改
ESP-12F模组外部Flash = 4MB = 4096KB = 32Mbit
4MB Flash = 0x000 000 ~ 0x3FF FFF
扇区编号:0x000 ~ 0x3FF 【Flash扇区 = 4KB】
外部Flash布局
1)eagle.flash.bin系统程序: 存放运行系统必要的固件
2)eagle.irom0.text.bin系统程序: 存放用户编写的程序
3)用户数据: 存放用户的参数/数据
4)RF_CAL参数: 存放系统自动保存校准后的RF参数
5)默认RF参数: 存放默认的RF参数信息(将ep_int_data_default.bin)下载到该区
6)系统参数: 存放系统参数信息(将blank.bin下载到该区)
需要烧录到ESP8266的文件
文件的下载地址
下载固件
双击ESP_DOWANLOAD_TOOL.exe 打开flash下载
下载固件的配置
点击下载,下载工具提示下载完成,开发板按RESET键即可。
GPIO00 = 1、GPIO02 = 1、GPIO15 = 0 ->程序运行模式
GPIO00 = 0、GPIO02 = 1、GPIO15 = 0 ->串口下载模式
注意点:
包含头文件
#include "osapi.h"
#include "user_interface.h"
API接口
void os_printf(const char *s) //格式化呼出,打印字符串
例程:
os_printf("SDK version:%s\n",system_get_sdk_version()); // 串口打印SDK版本
其中system_get_sdk_version()表示返回SDK版本号
驱动的源文件
SDK包->app(原名driver_lib)->driver->uart.c
SDK包->app(原名driver_lib)->incude->driver->uart.h和uart_register.h
串口初始化:
void uart_init(UartBautRate uart0_br, UartBautRate uart1_br);//传参uart_0波特率,uart_1波特率
uart_0打印函数:
void uart0_sendStr(const char *str);//串口0打印字符串
数据位长度
void UART_SetWordLength(uint8 uart_no, UartBitsNum4Char len);//传参uart_0/1,数据位长度
注:
1.默认从UART_0打印 //一般常用UART_0
2.波特率默认值 = 74880 //ESP12F模组的晶振=26MHz:74880
3.数据格式:数据位=8、停止位=1、无校验位、无数据流控制
1)定义软件定时器
os_timer_t OS_Timer_1;//定义软件定时器(os_timer_t型结构体)
//注:OS_Timer_1 必须定义为全局变量,因为ESP8266的内核还要使用
2)软件定时的回调函数
void ICACHE_FLASH_ATTR OS_Timer_1_cb(void)
{
LED=!LED;
GPIO_OUTPUT_SET(GPIO_ID_PIN(4),LED);
os_printf("OS_Timer_1_cb\n");
}
3)软件定时器初始化(ms)
void ICACHE_FLASH_ATTR OS_Timer_1_Init(u32 time_ms,u8 time_repetitive)
{
//关闭定时器
//参数1:要关闭的定时器
os_time_disarm(&OS_Timer_1);
//设置定时器
//参数1:要设置的定时器;参数2:回调函数(需类型转换);参数三:回调函数的参数
//os_timer_setfn必须在软件定时器未使能的情况下使用
os_timer_setfn(&OS_Timer_1,(os_timer_func_t *)OS_Timer_1_cb,NULL);
//设置定时器参数并使能ms定时器
//参数1:要使能的定时器;参数2:定时时间(单位:ms);参数三:1-重复0-只运行一次
os_timer_arm(&OS_Timer_1,time_ms,time_repetitive);
//未调用system_timer_reinit,可支持[5ms-6870947ms]
//调用system_timer_reinit,可支持[100ms-428496ms]
}
4)在user_main.c文件中初始化
OS_Timer_1_Init(500,1)//表示500ms运行一次,可重复定时
注:
软件定时器接口位于ESP8266_NONOS_SDK/include/osapi.h。上述使用的定时器是由软件实现,定时器的函数在任务中被执行。因为任务可能被中断或者被其他高优先级的任务延迟,因此os_timer系列的接口并不能保证定时器精确运行,精准的定时器要硬件定时器。
1)将ESP8266_NONOS_SDK/driver_lib/driver/hw_timer.c文件放到自建工程的app/driver/目录下
2)硬件定时回调函数
//注意中断函数前不要有"ICACHE_FLASH_ATTR"
void HW_Timer_INT(void)
{
LED = !LED;
GPIO_OUTPUT_SET(GPIO_ID_PIN(4),LED);
os_printf("HW_Timer_INT\n");
}
3)硬件定时器初始化
//初始化硬件定时器 参数1:中断源 参数2:是否重复
//使用NMI中断源,那么该定时器将为最高优先级,可打断其他中断(NMI_SOURCE = 1)
//使用FRC1中断源,那么该定时器无法打断其他中断(FRC1_SOURCE = 0)
hw_timer_init(0,1);
//注册回调函数
hw_timer_set_func(HW_Timer_INT);
//设置定时器参数(单位us,参数必须<=1677721)
hw_timer_arm(500000);
无操作系统的SDK并不能像有操作系统的SDK那样支持任务调度,无操作系统的SDK中使用四种类型的函数:
应用函数;回调函数;用户任务;中断服务程序
其中无操作系统的SDK中用户任务最多设三个优先级:0,1,2。任务优先级为2>1>0
函数与任务的区别:
调用函数:立即进入该函数,执行函数语句,实现函数功能
安排任务:给系统安排任务,当系统空闲时,才会执行任务
使用system_os_post()API给系统安排任务时
参数2(消息类型)传递给任务函数形参->sig
参数3(消息参数)传递给任务函数形参->par
调用任务时需给出任务参数(参数1:任务等级;参数2:消息类型;参数3:消息参数)
不论系统是否还有未完成的任务,用户都可以继续安排任务(任务可叠加),当系统空闲时会依次执行任务函数。
注:任务虽然可以叠加,但是叠加次数不能超过设定值(消息队列深度)。超过设定值(消息队列深度)的任务将被丢弃
创建系统任务API
bool system_os_task(os_task_t task,uint8 prio,os_event_t *queue,uint8 qlen)
//os_task_t task:任务函数
//uint8 prio:任务优先级,可为0,1,2;0:为最低优先级,这表示最多只支持建立3个任务
//os_event_t *queue:消息队列指针
//uint8 qlen:消息队列深度
给系统安排任务API
bool system_os_post(uint8 prio,os_signal_t sig,os_param_t par)
//uint8 prio:任务优先级,与建立时的任务优先级对应
//os_signal_t sig:消息类型
//os_param_t par:消息参数
例程
1.定义消息队列深度
MESSAGE_QUEUE_LEN
2.定义任务指针
os_event_t *pointer_task
3.分配任务空间
pointer_task = (os_event_t*)os_malloc((sizeof(os_event_t))*MESSAGE_QUEUE_LEN)
4.创建任务执行函数
void func_task(os_event_t *Task_message)
{
os_printf("消息类型%d,消息参数%c\n",Task_message->sig,Task_message->par);
}
5.创建任务
system_os_task(func_task,USER_TASK_PRIO_0,pointer_task,MESSAGE_QUEUE_LEN);
6.安排任务
u8 message_sig = 1;
u8 message_par = 'A';
system_os_post(USER_TASK_PRIO_0,message_sig++,message_par++);
ESP-12F模组的外部Flash,除了存储系统程序,系统参数外,还可以用来存储用户数据,复位/掉电也不会丢失用户数据
ESP-12F模组的外部Flash = 32Mbit = 4MB
Flash_4MB的地址 = 0x000 000 ~ 0x3FF FFF
扇区编号 = 0x000 ~ 0x3FFF[一个扇区4KB]
注意:
读/写Flash的地址,不能和系统程序区冲突,可以放在[0x70 000]地址后
Flash读写,必须[4字节]对齐
向Flash某扇区写数据前,必须将此扇区擦除,Flash参数函数的参数 [扇区编号]
读写Flash数据函数的参数 [字节地址] (扇区编号*扇区大小4096)
Flash相关API
查询SPI FLASH的ID
uint32 spi_flash_get_id(void)
擦除Flash扇区
SpiFlashOpResult spi_flash_erase_sector(uint16 sec)
写数据到Flash
SpiFlashOpResult spi_flash_write(uint32 des_addr,uint32 *src_addr,uint32 size)
//uint32 des_addr:写入Flash目的地址
//uint32 *src_addr:写入数据指针
//uint32 size:数据长度,单位byte,必须4字节对齐进行读写
从Flash读数据
SpiFlashOpResult spi_flash_read(uint32 src_addr,uint32 *des_addr,uint32 size)
//uint32 src_addr:读取Flash目标地址
//uint32 *des_addr:读取数据指针
//uint32 size:数据长度,单位byte,必须4字节对齐进行读写
例程
1.定义Flash扇区大小
FLASH_SIZE 4096
2.定义操作的扇区编号
u16 FLASH_SEC = 0x77
3.定义写入Flash数组
u32 flash_w[16]={1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16}
4.定义读取Flash数组
u32 flash_r[16] = {0};
5.先擦除该扇区内容
spi_flash_erase_sector(0x77); 参数 = 扇区编号
6.向Flash写数据
spi_flash_write(0x77*4096,(uint32*)flash_w,sizeof(flash_w));参数1:字节地址=扇区编号*扇区大小
7.从0x77开始读取数据
spi_flash_read(0x77*4096,(uint32*)flash_r,sizeof(flash_w))
应用层:通过应用进程间的交互来完成特定的【网络应用】
运输层:负责向【两台主机进程】之间的通信提供通用的【数据传输】服务
网络层:为分组交换网上不同【主机】提供通信服务
数据链路层:把网络层交下来的数据构成【帧】发送到链路上,以及把接收到的【帧】中数据取出上交到网络层
物理层:解决怎样才能在连接计算机的传输媒体上(双绞线,光纤,无线电)传输【比特流】
WIFI:使用802.11系列协议的局域网称为WIFI
网际协议IP:TCP/IP体系中两个最主要的协议之一,也是最重要的互联网标准协议之一。
IPV4:IP数据报,划分子网,构造超网等。
IP地址:就是给互联网上每一台主机(或路由器)的每一个接口分配一个在全世界范围内唯一的32位的标识符(每8位加一个点)。
IP地址的标志方法:点分十进制
IP地址的作用:只有知道主机的IP地址,才能将消息发送给对应的主机
IP地址中,IP数据报的目标IP地址中,如果主机号全为1,则表示向目标IP网络中的所有主机发送广播消息。
外网IP:全球唯一IP,可以使用此IP访问互联网
内网IP:只在本地机构(或局域网)有效的IP地址(本地地址)
专用地址:
10.0.0.0 ~ 10.255.255.255
172.16.0.0 ~ 172.31.255.255
192.168.0.0 ~ 192.168.255.255
以上地址只能用于一个机构(局域网)的内部通信,而不能用于和互联网上的主机通信。
在互联网中的所有路由器,对目的地址是专用地址的数据报一律不进行转发。
端口(运输层)
端口号:服务器端使用的端口号+客户端使用的端口号
服务器端使用的端口号:熟知端口号(系统端口号) + 登记端口号
常用的熟知端口号:
应用程序 | FTP | TELNET | SMTP | DNS | TFTP | HTTP | SNMP | SNMP(trap) | HTTPS |
---|---|---|---|---|---|---|---|---|---|
熟知端口号 | 21 | 23 | 25 | 53 | 69 | 80 | 161 | 162 | 443 |
登记端口号:1024~49151,这类端口号是为没有熟知端口号的应用程序使用,使用这类端口号必须在IANA按照规定的手续登记,以防止重复。
客户端使用的端口号:49152 ~ 65536,这类端口号仅在客户进程运行时才动态选择,因此又叫短暂端口号。这类端口号是留给客户进程选择暂时使用,服务器接收到客户进程的报文时,就知道了客户进程所使用的端口号
通过【IP地址+端口】,才能完成【主机进程 ~ 主机进程】之间的通信
UDP:用户数据报协议
1.UDP无连接
2.UDP使用尽最大努力交付,即不保证可靠交付
3.UDP面向报文,应用层给UDP多长的报文,UDP就照样发送
4.UDP没有拥塞控制
5.UDP支持1对1,1对多,多对多
TCP:传输控制协议
1.TCP面向连接,使用TCP前必须建立TCP连接
2.TCP只支持1对1连接
3.TCP提供可靠交付
4.TCP提供全双工通信
5.TCP面向字节流
TCP连接
每一条TCP连接有两个端点,TCP连接的端点叫套接字(socket)或插口
套接字 socket = IP地址+端口号
应用 | 应用层协议 | 传输层协议 |
---|---|---|
万维网 | HTTP | TCP |
客户程序:在用户调用后运行,在通信时主动向远程服务器发起通信(请求服务),客户程序必须知道远程服务器的地址;不需要特殊的硬件和复杂的操作系统
服务器程序:专门用来提供某种服务的程序,可同时处理多个远程或本地客户的请求;服务器程序不需要知道客户程序的地址;需要强大的硬件和高级操作系统支持;
客户和服务器的通信建立后,通信可以时双向的,客户和服务器都可发送和接收数据。
主动发起连接建立的应用进程叫客户,被动等待连接建立的进程叫服务器
动态主机配置协议(DHCP)
作用:自动获取IP地址、子网掩码、默认网关、DNS服务器地址
ESP8266创建局域网,设备接入ESP8266创建的局域网中。
struct softap_config AP_Config;//AP参数结构体
1.wifi_set_opmode(0x02);//设置AP模式,并保存到Flash
2.
os_memset(&AP_Config,0,sizeof(softap_config));//AP参数结构体重置0
os_strcpy(AP_Config.ssid,ESP8266_AP_SSID);//设置SSID
os_strcpy(AP_Config.password,ESP8266_AP_PASS);//设置密码
AP_Config.ssid_len = os_strlen(ESP8266_AP_SSID);//设置SSID长度
AP_Config.channel = 1;//通道号1-13
AP_Config.authmode = AUTH_WPA2_PSK;//设置加密模式
AP_Config.ssid_hidden = 0;//不隐藏SSID
AP_Config.max_connection = 4;//最大连接数
AP_Config.beacon_interval = 100;//信标间隔时槽100-60000ms
wifi_softap_set_config(&AP_Config);//设置soft-ap模式,并保存到Flash,默认开启DHCP,IP=192.168.4.1
3.
wifi_get_ip_info(SOFTAP_IF,&ESP8266_IP);//参数2:IP信息结构体指针
wifi_softap_get_station_num();//获取连接设备数量
//注:ESP8266的softap模式建立,并不是立即执行的。
ESP8266建立AP模式,ESP8266作为服务器,网络调试助手作为客户端。
例程
1.定义espconn型结构体
struct espconn ST_NetCon;//必须为全局变量,内核将会使用此变量
2.结构体赋值
ST_NetCon.type = ESPCONN_UDP;//设置UDP模式
ST_NetCon.proto.udp = (esp_udp *)os_zmalloc(sizeof(esp_udp));//申请内存
ST_NetCon.proto.udp->local_port = 8266;//设置本地端口。因为8266用作服务器,所以设定固定端口号
3.注册/定义回调函数
void ICACHE_FLASH_ATTR ESP8266_WIFI_Send_Cb(void *arg);//参数1:网络传输结构体espconn指针
void ICACHE_FLASH_ATTR ESP8266_WIFI_Recv_Cb(void *arg,char *pdata,unsigned short len)//参数1:网络传输结构体espconn指针,参数2:网络传输数据指针,参数3:数据长度
espconn_get_connection_info();//获取连接客户端的信息
espconn_send();//向客户端发送消息
espconn_regist_sentcb(&ST_NetCon,ESP8266_WIFI_Send_Cb);//注册网络数据发送成功的回调函数
espconn_regist_recvcb(&ST_NetCon,ESP8266_WIFI_Recv_Cb);//注册网络数据接收成功的回调函数
4.调用UDP初始化API
espconn_create(&ST_NetCon);
ESP8266只是逻辑上作为客户端,还是需要ESP8266建立AP模式,电脑接入ESP8266创建的WIFI网络。ESP8266作为客户端,网络调试助手作为服务器。
区别:
此处的8266作为客户端,需要预先知道server端的IP地址和端口号——在程序中写死
1.添加30S定时再进行连接,目的是为网络调试助手建立服务器提供时间
2.定义espconn型结构体
struct espconn ST_NetCon;//必须为全局变量,内核将会使用此变量
3.结构体赋值
ST_NetCon.type = ESPCONN_UDP;//设置UDP模式
ST_NetCon.proto.udp = (esp_udp *)os_zmalloc(sizeof(esp_udp));//申请内存
4.此处的8266作为客户端,需要预先知道server端的IP地址和端口号(解决方法:在程序中写死)
ST_NetCon.proto.udp->local_port = 8266;//设置本地端口号(随意设置-ESP8266)
ST_NetCon.proto.udp->remote_port = 8888;//设置目标端口号(随意设置-网路调试助手)
ST_NetCon.proto.udp->remote_ip[0] = 192;
ST_NetCon.proto.udp->remote_ip[1] = 168;
ST_NetCon.proto.udp->remote_ip[2] = 4;
ST_NetCon.proto.udp->remote_ip[3] = 2;
5.定义/注册回调函数
void ESP8266_WIFI_Recv_Cb(void *arg,char *pdata,unsigned short len)
{
struct espconn *T_arg = arg;//缓存网络连接结构体指针
remot_info *P_port_info = NULL;//定义远端连接信息指针
//根据接收到网络调试助手消息进行处理
if(pdata[0] == 'K' || pdata[0] == 'k')
{
LED_ON;
}
else if(pdata[0] == 'G' || pdata[0] == 'g')
{
LED_OFF;
}
else
{}
os_printf("ESP8266 receive data = %s\n",pdata);
//获取远端信息(UDP通信是无连接,向远端主机回应时需要获取对方的IP/端口信息
if(espconn_get_connection_info(T_arg,&P_port_info,0) == ESPCONN_OK)
{
T_arg->proto.udp->remote_port = P_port_info->remote_port;
os_memcpy(T_arg->proto.udp->remote_ip,P_port_info->remote_ip,4);
}
espconn_regist_sentcb(&ST_NetCon,ESP8266_WIFI_Send_Cb);//注册网络数据发送成功的回调函数
espconn_regist_recvcb(&ST_NetCon,ESP8266_WIFI_Recv_Cb);//注册网络数据接收成功的回调函数
6.调用UDP初始化API
espconn_create(&ST_NetCon);
7.调用网络发送数据API
espconn_send(&ST_NetCon,"Hello,ESP8266",os_strlen("Hello,ESP8266"));//主动向server发送数据
1.定义espconn型结构体
struct espconn ST_NetCon;//必须为全局变量,内核将会使用此变量
2.结构体赋值
ST_NetCon.type = ESPCONN_TCP;//设置TCP模式
ST_NetCon.proto.tcp = (esp_tcp *)os_zalloc(sizeof(esp_tcp));//申请内存
3.ESP8266作为服务器,不需预先知道Client的IP/端口
ST_NetCon.proto.tcp->local_port = 8266;//设置本地端口
4.定义/注册回调函数
void ICACHE_FLASH_ATTR ESP8266_WIFI_Send_Cb(void *arg)
{
os_printf("ESP8266_WIFI_Send_OK\n");
}
void ICACHE_FLASH_ATTR ESP8266_WIFI_Recv_Cb(void *arg,char *pdata,unsigned short len)
{
struct espconn *T_arg = arg;
//根据接收到网络调试助手消息进行处理
if(pdata[0] == 'K' || pdata[0] == 'k')
{
LED_ON;
}
else if(pdata[0] == 'G' || pdata[0] == 'g')
{
LED_OFF;
}
else
{}
os_printf("ESP8266 receive data = %s\n",pdata);
//TCP是面向连接的通信,向远端主机回应时可直接使用T_arg结构体指针指向的IP信息
espconn_send(T_arg,"ESP8266_WIFI_Recv_OK",os_strlen("ESP8266_WIFI_Recv_OK"));//向对方发送应答
}
void ICACHE_FLASH_ATTR ESP8266_TCP_Disconnect_Cb(void *arg)
{
os_printf("ESP8266_TCP_Disconnect_OK\n");
}
void ICACHE_FLASH_ATTR_ESP8266_TCP_Connect_Cb(void *arg)
{
espconn_regist_sentcb((struct espconn *)arg,ESP8266_WIFI_Send_Cb);//注册网络数据发送成功的回调函数
espconn_regist_recvcb((struct espconn *)arg,ESP8266_WIFI_Recv_Cb);//注册网络数据接收成功的回调函数
espconn_regist_disconcb((struct espconn*)arg,ESP8266_TCP_Disconnect_Cb);//注册成功断开TCP连接的回调函数
}
void ICACHE_FLASH_ATTR ESP8266_TCP_Break_Cb(void *arg)
{
os_printf("ESP8266_TCP_Break\n");
}
espconn_regist_connectcb(&ST_NetCon,ESP8266_TCP_Connect_Cb);//注册TCP连接成功的回调函数
espconn_regist_reconcb(&ST_NetCon,ESP8266_TCP_Break_Cb);//注册TCP连接异常的回调函数
5.创建TCP_server,建立侦听
espconn_accept(&ST_NetCon);
6.设置超时断开时间
espconn_regist_time(&ST_NetCon,300,0);//单位为秒,最大值=7200
//注:
//在espconn_accept之后,连接未建立之前,调用本接口
//如果超时时间设为0,ESP8266 TCP_server将始终不会断开已经不与它通信的TCP_client,不建议使用0
1.添加30S定时再连接,为电脑创建TCP SERVER提供时间
2.结构体赋值
ST_NetCon.type = ESPCONN_TCP;//设置TCP模式
ST_NetCon.proto.tcp = (esp_tcp *)os_zalloc(sizeof(esp_tcp));//申请内存
3.ESP8266作为客户端,需预先知道Server端的IP/端口
ST_NetCon.proto.tcp->local_port = 8266;//设置本地端口
ST_NetCon.proto.tcp->remote_port = 8888;//设置目标端口
u8 remote_ip[4] = {192.168.4.2};//设置目标IP
os_memcpy(ST_NetCon.proto.tcp->remote_ip,remote_ip,4);//拷贝目标IP
4.定义/注册回调函数
void ICACHE_FLASH_ATTR ESP8266_WIFI_Send_Cb(void *arg)
{
os_printf("ESP8266_WIFI_Send_OK\n");
}
void ICACHE_FLASH_ATTR ESP8266_WIFI_Recv_Cb(void *arg,char *pdata,unsigned short len)
{
struct espconn *T_arg = arg;
//根据接收到网络调试助手消息进行处理
if(pdata[0] == 'K' || pdata[0] == 'k')
{
LED_ON;
}
else if(pdata[0] == 'G' || pdata[0] == 'g')
{
LED_OFF;
}
else
{}
os_printf("ESP8266 receive data = %s\n",pdata);
//TCP是面向连接的通信,向远端主机回应时可直接使用T_arg结构体指针指向的IP信息
espconn_send(T_arg,"ESP8266_WIFI_Recv_OK",os_strlen("ESP8266_WIFI_Recv_OK"));//向对方发送应答
}
void ICACHE_FLASH_ATTR ESP8266_TCP_Disconnect_Cb(void *arg)
{
os_printf("ESP8266_TCP_Disconnect_OK\n");
}
void ICACHE_FLASH_ATTR_ESP8266_TCP_Connect_Cb(void *arg)
{
espconn_regist_sentcb((struct espconn *)arg,ESP8266_WIFI_Send_Cb);//注册网络数据发送成功的回调函数
espconn_regist_recvcb((struct espconn *)arg,ESP8266_WIFI_Recv_Cb);//注册网络数据接收成功的回调函数
espconn_regist_disconcb((struct espconn*)arg,ESP8266_TCP_Disconnect_Cb);//注册成功断开TCP连接的回调函数
espconn_send((struct espconn*)arg,"HELLO ESP8266",os_strlen("HELLO ESP8266"));//向server发起通信
}
void ICACHE_FLASH_ATTR ESP8266_TCP_Break_Cb(void *arg)
{
os_printf("ESP8266_TCP_Break\n");
espconn_connect(&ST_NetCon);//重新连接TCP_Server
}
espconn_regist_connectcb(&ST_NetCon,ESP8266_TCP_Connect_Cb);//注册TCP连接成功的回调函数
espconn_regist_reconcb(&ST_NetCon,ESP8266_TCP_Break_Cb);//注册TCP连接异常的回调函数
5.连接TCP SERVER
espconn_connect(&ST_NetCon);
路由器创建局域网,ESP8266连接到路由器创建的局域网
1.结构体定义
//STA参数结构体
struct station_config STA_Config;
//STA信息结构体
struct ip_info ST_ESP8266_IP;
2.设置ESP8266为STA模式,并保存到Flash
wifi_set_opmode(0x01);
3.ESP8266默认开启DHCP Client,接入WIFI会自动分配IP地址
//以下操作是手动设置IP地址
wifi_station_dhcpc_stop();//关闭DHCP Client
IP4_ADDR(&ST_ESP8266_IP.ip,192.168.8.88);//配置IP地址
IP4_ADDR(&ST_ESP8266_IP.netmask,255.255.255.0);//配置子网掩码
IP4_ADDR(&ST_ESP8266_IP.gw,192.168.8.1);//配置网关
wifi_set_ip_info(STATION_IF,&ST_ESP8266_IP);//设置STA模式下的IP地址
4.结构体赋值,配置STA模式参数
os_memset(&STA_Config,0,sizeof(struct station_config));//STA参数结构体清零
os_strcpy(STA_Config,ssid,ESP8266_STA_SSID);//设置WIFI名(宏定义表示)
os_strcpy(STA_Config,password,ESP8266_STA_PASS);//设置WIFI密码(宏定义表示)
wifi_station_set_config(&STA_Config);//设置STA参数,并保存到Flash
//注
//如果wifi_station_set_config在user_init中调用,则ESP8266 Station接口会在系统初始化完成后,自动连接AP(路由),无需再调用wifi_station_connect.
//wifi_station_connect不允许在user_init中调用,需在ESP8266 Station使能并初始化完成后调用
5.查询STA接入状态
wifi_station_get_connect_status();
//返回值
//0 = STATION_IDLE = STATION闲置
//1 = STATION_CONNECTING = 正在连接WIFI
//2 = STATION_WRONG_PASSWORD = WIFI密码错误
//3 = STATION_NO_AP_FOUND = 未发现制定WIFI
//4 = STATION_CONNECT_FAIL = 连接失败
//5 = STATION_GOT_IP = 获得IP,连接成功
1.设置ESP8266为STA模式
2.判断ESP8266是否获得路由自动分配的IP地址
3.ESP8266在逻辑上作为服务端,不需要预先知道client端的IP/端口号
4.初始化网络连接-UDP通信
1.设置ESP8266为STA模式
2.判断ESP8266是否获得路由自动分配的IP地址
3.ESP8266在逻辑上作为客户端,需要预先知道server端的IP/端口号
4.初始化网络连接-UDP通信
1.设置ESP8266为STA模式
2.判断ESP8266是否获得路由自动分配的IP地址
3.ESP8266在逻辑上作为服务器端,不需要预先知道client端的IP/端口号
4.初始化网络连接-TCP通信
5.建立侦听
6.设置超时时间
ESP8266将以这种方式接入物联网云平台
1.设置ESP8266为STA模式
2.判断ESP8266是否获得路由自动分配的IP地址
3.ESP8266在逻辑上作为客户端,需要预先知道服务器端的IP/端口号
4.初始化网络连接-TCP通信
Domain Name (域名),一串用 点 分隔的字符,是互联网上某台/某组计算机名称。
DNS = Domain Name System (域名系统),可以直接使用域名访问网上的计算机或者服务器。
例:
访问百度
使用IP地址访问:123.125.115.110
使用域名访问:www.baidu.com
推荐使用域名的方式来访问互联网上的服务器
1.服务器就是计算机,可能会坏,可能会更换,IP地址可能会变 。
2.如果服务器的IP地址变了,那么烧录到芯片的IP地址需要变更,需重新烧录软件。
3.域名是不会轻易改变的,只需要知道域名,就可以知道服务器的IP。
//程序中写入网址的域名,解析其IP,使用解析后的IP连接到网址
1.设置ESP8266为STA模式
2.结构体赋值
ST_NetCon.type = ESPCONN_TCP;//设置为TCP协议
ST_NetCon.proto.tcp = (esp_tcp*)os_zalloc(sizeof(esp_tcp));//开辟内存空间
//ESP8266作为TCP client,需要预先知道TCP server的IP地址
ST_NetCon.proto.tcp->local_port = espconn_port();//本地端口
ST_NetCon.proto.tcp->remote_port = 80;//目标端口
//使用API获取域名对应的IP地址
//参数1:网络连接结构体指针,参数2:域名字符串指针(宏定义),参数3:IP地址结构体指针(ip_addr_t类型),参数4:回调函数
espconn_gethostbyname(&ST_NetCon,DN_Server,&IP_Server,DNS_Over_Cb);
3.域名解析回调函数
//参数1:域名字符串指针,参数2:IP地址结构体指针,参数3:网络连接结构体指针
void ICACHE_FLASH_ATTR DNS_Over_Cb(const char *name,ip_addr_t *ipaddr,void *arg)
{
struct espconn *T_arg = (struct espconn *)arg;//缓存网络连接结构体指针
if(ipaddr == NULL)//域名解析失败
{
os_printf("Domain Name Analyse Filed\n");
return;
}
else if(ipaddr != NULL && ipaddr->addr != 0)//域名解析成功
{
os_printf("Domain Name Analyse Success\n");
IP_Server.addr = ipaddr->addr;//将获取的服务器IP地址进行保存
//将解析到的服务器IP地址设为TCP连接的远端IP地址
os_memcpy(T_arg->proto.tcp->remote_ip,&IP_Server,addr,4);
//连接TCP Server
espconn_connect(T_arg);//在获取到服务器IP后再进行TCP连接
}
}
HTTP = 超文本传输协议
浏览器和服务器之间的请求和响应的交互,必须按照规定的格式和遵循一定的规则,这个格式和规则就是超文本传输协议。
HTTP使用了面向连接的TCP作为传输层协议,保证了数据的可靠性
监听端口:TCP端口80
HTTP报文分类
1.请求报文——从客户从服务器发送请求报文
需加截图
2.响应报文——从服务器到客户的回答
需加截图
HTTP请求报文的一些方法,在此处只使用GET方法(请求读取由URL所标志的信息)
需加截图
响应报文中的常见状态行
HTTP/1.1 202 Accepted (接受)
HTTP/1.1 400 Bad Request (错误的请求)
HTTP/1.1 404 Not Found (找不到)
//通过HTTP获取网页内容
1.设置ESP8266为STA模式
2.通过域名解析IP地址
3.获得IP地址后进行网络连接初始化连接到TCP server
4.发送HTTP请求报文
#define HTTP_Message "Get http://www.rationmcu.com/elecjc/2397.html HTTP/1.1 \r\n" \
"Host:www.rationmcu.com \r\n" \
"Connection:close \r\n\r\n" \
//HTTP_Message 定义格式:
//操作 统一资源定位符 HTTP/1.1 (回车)Host: 域名 (回车)Connection:close (回车)(回车)
espconn_send((struct espconn *)arg,HTTP_Message,os_strlen(HTTP_Message));
SNTP = simple network time protocol (简单网络时钟协议)
1.设置ESP8266为STA模式
2.初始化网络连接
3.初始化SNTP
//ESP8266最多设置3个SNTP服务器
void ICACHE_FLASH_ATTR ESP8266_SNTP_Init(void)
{
ip_addr_t *addr = (ip_addr_t *)os_zalloc(sizeof(ip_addr_t));
sntp_setservername(0,"us.pool.ntp.org");//服务器0(主服务器)
sntp_setservernamr(1,"ntp.sjtu.edu.cn");//服务器1(备用服务器)
ipaddr_aton("210.72.145.44",addr);//点分十进制->32位二进制
sntp_setserver(2,addr);//服务器2(备用服务器)
os_free(addr);//释放addr
sntp_init();//SNTP初始化API
OS_Timer_SNTP_Init();//1S重复定时,目的获取SNTP数据
}
4.定时回调函数
uint32 TimeStamp;//时间戳
char *Str_RealTime;//实际时间
//查询当前距离基准时间(1970.01.01 00:00:00 GNT+8)的时间戳,时间为秒
TimeStamp = sntp_get_current_timestamp();
if(TimeStamp)
{
Str_RealTime = sntp_get_real_time(TimeStamp);//查询实际时间
}
JSON = javascript object notation (JS 对象简谱)本质就是一种数据交换格式
优点:完全独立于编程语言,方便读写,也方便机器解析和生成。
作用:存储数据和表示数据,通信双方可以使用JSON字符串的格式传输文本信息
JSON由多个元素组成,元素使用特定的符号标注
{} 大括号表示对象
[] 中括号表示数组
“” 双引号内是属性(键)或值
: 冒号前表示属性(键)
: 冒号后表示这个属性的具体值
例
{“temp” : “30℃”} “temp” 是一个属性,表示温度。 "30℃"是这个属性的值,表示当前温度为30
注:
属性(键):必须是 “字符串”
值:可以是 “字符串” 、数组[]、对象{}、数字(数字必须是ASCII码形式的)
//JSON_API
//使用JSON相关的API需要添加 user_json.c 和 user_json.h
//JSON树相关设置
//设置 创建JSON树、解析JSON树 的回调函数
struct jsontree_callback JSON_Tree_Set = JSONTREE_CALLBACK(JSON_AssignValue,JSON_Tree_Parse);//结构体定义
//生成JSON对象
JSONTREE_OBJECT(V_Key_1,JSONTREE_PAIR("temp",&JSON_Tree_Set),JSONTREE_PAIR("humid",&JSON_Tree_Set));
JSONTREE_OBJECT(V_Key_2,JSONTREE_PAIR("temp",&JSON_Tree_Set),JSONTREE_PAIR("humid",&JSON_Tree_Set));
//对象名:V_JSON 键值对:"Shanghai":{&V_Key_1}, "Shenzhen":{&V_Key_2},"result":{&JSON_Tree_Set}
JSONTREE_OBJECT(V_JSON,JSONTREE_PAIR("Shanghai",&V_Key_1),
JSONTREE_PAIR("Shenzhen",&V_Key_2),
JSONTREE_PAIR("result",&JSON_Tree_Set));
//JSONTREE_OBJECT参数1:生成的JSON树对象的名称,参数2:键值对,参数3:键值对,…
//JSONTREE_PAIR():参数1:键值对,参数2,键值对的值
//注:第一个的JSON对象名,“键”都不显示,只显示[&V_JSON]
JSONTREE_OBJECT(Object_JSON,JSONTREE_PAIR("K_JOSN",&V_JSON));
//创建JSON树
void ICACHE_FLASH_ATTR Setup_JSON_Tree(void)
{
//参数1:首JSON对象的指针,参数2:首JSON对象的键,参数3:JSON树缓存指针
json_ws_send(struct jsontree_value *)&Object_JOSN,"K_JOSN",A_JSON_Tree);
}
//解析JSON树
void ICACHE_FLASH_ATTR Parse_JSON_Tree(void)
{
struct jsontree_context jd;//JSON树环境结构体
//与要解析的JSON树建立联系
//参数1:JSON树环境结构体指针,参数2:JSON树的第二个对象名指针,参数2:json_putchar函数
jsontree_setup(&js,(struct jsontree_value *)&V_JSON,json_putchar);
//解析JSON树
//参数1:JSON树环境结构体指针,参数2:要解析的JSON树的指针
json_parse(&js,A_JSON_Tree);//执行这条语句,将会调用对应的JSON树解析回调函数
}
乐鑫提供的JSON相关API对于JSON的创建和解析相对繁琐,建议使用 C函数库中的字符串函数 进行创建和解析
注:
C语言中" “中要显示” “,在显示的 " 前加转义字符 \ ,即”
方法:
通过字符串赋值的方法(%s),对JSON树中的值进行赋值
功能:
接受设备上报的数据,向设备下发数据,对数据进行转发/分析/计算/显示、管理设备……
常见的物联网云平台:
私有物联网云平台
通用物联网云平台
设备:
云下设备:真实存在的设备/模拟器模拟的设备
云端设备:在云平台中,与云下设备相对应虚拟设备
iot.espressif.cn 时区:中国北京时间
iot.espressif.com 时区:格林尼治标准时间
创建云端设备,参考《ESP8266 Non-OS SDK IoT_Demo指南》的2.3 云端创建设备
云下设备在与乐鑫云平台建立网络连接后,只要按照乐鑫云平台规定好的数据格式,来收发网络数据,云下设备就能与乐鑫云平台进行交互,实现设备接入物联网云平台。
为了验证设备/保护数据,一般情况下,物联网云平台需要鉴别设备身份(设备密钥,证书…)
ESP8266支持的配网方式:网页配网、APP配网、微信配网。
使用微信配网,调用配网函数之前,必须声明【#include “smartcofig.h”】
关注公众号
实现原理:
1)smartconfig过程中,ESP8266开启sniffer模式,监听可以接收到的所有网络数据不论(数据是否打算发送给8266)
2)用户通过手机/电脑广播发送加密的SSID和Password信息
3)ESP8266抓取并解密空中SSID和Password信息,进而连接到WIFI
要想实现一次配网,长期有效,设计方法:
1)wifi_set_opmode(0x01); //设置为STA模式,并保存到Flash。注意:不要设置SID和Password
2)8266每次连接wifi时,检查wifi连接情况,如果wifi连接成功,则正常执行程序。(SNTP、UDP、TCP、DNS)
3)如果wifi名 wifi密码错误,则进入微信配网模式。微信配网成功后,将wifi名 wifi密码保存,正常执行程序。
4)当使用微信配网成功后,只要wifi环境不变,即使8266重新复位,它也能成功连接到配置过的wifi,正常执行程序。
例程重点截图
未完待续。。。。