逆向学习物联网-网关ESP8266-03软件编程实现
1.技术原理及流程
1) MQTT数据通讯原理
2).网关协议运行状态机
3). 主程序流程
2.关键程序代码实现
1)主程序
2)网络状态机的处理程序
详细的处理程序如下:
void Mqtt_Net_Init ( void ) //网络初始化,COM2 ,AT { Net_DefPara(); g_SystemStus = STA_WIFI_STARTCONNECT; } //---------------------------------------- void fun_Wifi_StartConnect ( void ) { if ( ESP8266_Init() == 0 ) { g_SystemStus = STA_WIFI_CONNECTED; } else//等会重新连接 { g_SystemStus = STA_WIFI_WAITCONNECT; gg_oldtime = millis();//获取滴答时钟 } } void fun_Wifi_WaitConnect ( void ) { if ( millis() - gg_oldtime > 5000 ) //5秒超时,等会重新连接 { g_SystemStus = STA_WIFI_STARTCONNECT; } } void fun_Wifi_Connected ( void ) { //连接云服务 if ( ESP8266_ConnectCloud ( ) == 0 ) { g_SystemStus = STA_MQTT_STARTCONNECT; } else { //原地踏步 } } void fun_Mqtt_StartConnect ( void ) { if ( OneNet_DevLink ( ) == 0 ) { g_SystemStus = STA_MQTT_CONNECTED;//连接成功, } else { g_SystemStus = STA_MQTT_WAITCONNECT;//超时重新连接 gg_oldtime = millis();//获取滴答时钟 } } void fun_Mqtt_WaitConnect ( void ) { if ( millis() - gg_oldtime > 5000 ) //5秒超时,等会重新连接 { g_SystemStus = STA_WIFI_STARTCONNECT; } } void fun_Mqtt_Subscribe ( void ) { //订阅主题 if ( OneNet_Subscribe ( g_SysPara.mqtt_subtopic1 ) == 0 ) { if ( OneNet_Subscribe ( g_SysPara.mqtt_subtopic2 ) == 0 ) { g_SystemStus = STA_SYSTEM_WORK; gg_oldtime = millis();//获取滴答时钟 return; } } //订阅失败 g_SystemStus = STA_MQTT_WAITSUBCRIBE; gg_oldtime = millis();//获取滴答时钟 } void fun_Mqtt_WaitSubcribe ( void ) { if ( millis() - gg_oldtime > 5000 ) //5秒超时,等会重新连接 { g_SystemStus = STA_MQTT_CONNECTED; } } void fun_System_Work ( void ) { unsigned char *cmd = NULL; //心跳包,正常数据传输,每3秒 if ( millis() - gg_oldtime > 120000L ) //120秒超时,等会重新连接, 实际后台是keep alive 256s { OneNet_HeartBeat(); gg_oldtime = millis(); } //3.接收到网络数据的处理 :订阅的数据 if ( ( cmd = ESP8266_GetIPD_main() ) != NULL ) //5秒判断接收的数据 { OneNet_RevPro ( cmd ); //接收到数据,解码处理,转发串口 //超过多少次,如果还收不到信息,可能是网络断了,需要复位 } }
3)串口接收处理程序
串口接收数据终端服务程序:
4)循环数据缓冲池(循环队列):用于串口接收
void Que_Init(STRUCT_QUEUE *dd) { dd->begin=0; dd->end=0; dd->count=0; } unsigned long Que_GetCount(STRUCT_QUEUE *dd) { return dd->count; } void Que_AddDataOne(STRUCT_QUEUE *dd,unsigned char ch) { if(dd->count>QUE_SIZE)return; dd->buffer[dd->end]=ch; dd->end=(dd->end+1)%QUE_SIZE; dd->count++; } //读取1个数值 unsigned char Que_ReadOne(STRUCT_QUEUE *dd) { unsigned char ch=dd->buffer[dd->begin]; dd->begin=(dd->begin+1)%QUE_SIZE; dd->count--; return ch; }
5)MQTT开发包头文件说明
#ifndef _MQTTKIT_H_ #define _MQTTKIT_H_ #include "Common.h" //=============================配置============================== //===========可以提供RTOS的内存管理方案,也可以使用C库的========= //RTOS #include#define MQTT_MallocBuffer malloc #define MQTT_FreeBuffer free //========================================================== #define MOSQ_MSB(A) (uint8)((A & 0xFF00) >> 8) #define MOSQ_LSB(A) (uint8)(A & 0x00FF) /*--------------------------------内存分配方案标志--------------------------------*/ #define MEM_FLAG_NULL 0 #define MEM_FLAG_ALLOC 1 #define MEM_FLAG_STATIC 2 typedef struct Buffer { uint8 *_data; //协议数据 uint32 _len; //写入的数据长度 uint32 _size; //缓存总大小 uint8 _memFlag; //内存使用的方案:0-未分配 1-使用的动态分配 2-使用的固定内存 } MQTT_PACKET_STRUCTURE; /*--------------------------------固定头部消息类型--------------------------------*/ enum MqttPacketType { MQTT_PKT_CONNECT = 1, /**< 连接请求数据包 */ MQTT_PKT_CONNACK, /**< 连接确认数据包 */ MQTT_PKT_PUBLISH, /**< 发布数据数据包 */ MQTT_PKT_PUBACK, /**< 发布确认数据包 */ MQTT_PKT_PUBREC, /**< 发布数据已接收数据包,Qos 2时,回复MQTT_PKT_PUBLISH */ MQTT_PKT_PUBREL, /**< 发布数据释放数据包, Qos 2时,回复MQTT_PKT_PUBREC */ MQTT_PKT_PUBCOMP, /**< 发布完成数据包, Qos 2时,回复MQTT_PKT_PUBREL */ MQTT_PKT_SUBSCRIBE, /**< 订阅数据包 */ MQTT_PKT_SUBACK, /**< 订阅确认数据包 */ MQTT_PKT_UNSUBSCRIBE, /**< 取消订阅数据包 */ MQTT_PKT_UNSUBACK, /**< 取消订阅确认数据包 */ MQTT_PKT_PINGREQ, /**< ping 数据包 */ MQTT_PKT_PINGRESP, /**< ping 响应数据包 */ MQTT_PKT_DISCONNECT, /**< 断开连接数据包 */ //新增 MQTT_PKT_CMD /**< 命令下发数据包 */ }; /*--------------------------------MQTT QOS等级--------------------------------*/ enum MqttQosLevel { MQTT_QOS_LEVEL0, /**< 最多发送一次 */ MQTT_QOS_LEVEL1, /**< 最少发送一次 */ MQTT_QOS_LEVEL2 /**< 只发送一次 */ }; /*--------------------------------MQTT 连接请求标志位,内部使用--------------------------------*/ enum MqttConnectFlag { MQTT_CONNECT_CLEAN_SESSION = 0x02, MQTT_CONNECT_WILL_FLAG = 0x04, MQTT_CONNECT_WILL_QOS0 = 0x00, MQTT_CONNECT_WILL_QOS1 = 0x08, MQTT_CONNECT_WILL_QOS2 = 0x10, MQTT_CONNECT_WILL_RETAIN = 0x20, MQTT_CONNECT_PASSORD = 0x40, MQTT_CONNECT_USER_NAME = 0x80 }; /*--------------------------------消息的packet ID,可自定义--------------------------------*/ #define MQTT_PUBLISH_ID 10 #define MQTT_SUBSCRIBE_ID 20 #define MQTT_UNSUBSCRIBE_ID 30 /*--------------------------------删包--------------------------------*/ void MQTT_DeleteBuffer ( MQTT_PACKET_STRUCTURE *mqttPacket ); /*--------------------------------解包--------------------------------*/ uint8 MQTT_UnPacketRecv ( uint8 *dataPtr ); /*--------------------------------登录组包--------------------------------*/ uint8 MQTT_PacketConnect ( const int8 *user, const int8 *password, const int8 *devid, uint16 cTime, uint1 clean_session, uint1 qos, const int8 *will_topic, const int8 *will_msg, int32 will_retain, MQTT_PACKET_STRUCTURE *mqttPacket ); /*--------------------------------断开连接组包--------------------------------*/ uint1 MQTT_PacketDisConnect ( MQTT_PACKET_STRUCTURE *mqttPacket ); /*--------------------------------连接响应解包--------------------------------*/ uint8 MQTT_UnPacketConnectAck ( uint8 *rev_data ); /*--------------------------------命令下发解包--------------------------------*/ uint8 MQTT_UnPacketCmd ( uint8 *rev_data, int8 **cmdid, int8 **req, uint16 *req_len ); /*--------------------------------命令回复组包--------------------------------*/ uint1 MQTT_PacketCmdResp ( const int8 *cmdid, const int8 *req, MQTT_PACKET_STRUCTURE *mqttPacket ); /*--------------------------------订阅主题组包--------------------------------*/ uint8 MQTT_PacketSubscribe ( uint16 pkt_id, enum MqttQosLevel qos, const char *topic, MQTT_PACKET_STRUCTURE *mqttPacket ); /*--------------------------------订阅主题回复解包--------------------------------*/ uint8 MQTT_UnPacketSubscribe ( uint8 *rev_data ); /*--------------------------------取消订阅组包--------------------------------*/ uint8 MQTT_PacketUnSubscribe ( uint16 pkt_id, const int8 *topics[], uint8 topics_cnt, MQTT_PACKET_STRUCTURE *mqttPacket ); /*--------------------------------取消订阅回复解包--------------------------------*/ uint1 MQTT_UnPacketUnSubscribe ( uint8 *rev_data ); /*--------------------------------发布主题组包--------------------------------*/ uint8 MQTT_PacketPublish ( uint16 pkt_id, const int8 *topic, const int8 *payload, uint32 payload_len, enum MqttQosLevel qos, int32 retain, int32 own, MQTT_PACKET_STRUCTURE *mqttPacket ); /*--------------------------------发布消息回复解包--------------------------------*/ uint8 MQTT_UnPacketPublish ( uint8 *rev_data, int8 **topic, uint16 *topic_len, int8 **payload, uint16 *payload_len, uint8 *qos, uint16 *pkt_id ); /*--------------------------------发布消息的Ack组包--------------------------------*/ uint1 MQTT_PacketPublishAck ( uint16 pkt_id, MQTT_PACKET_STRUCTURE *mqttPacket ); /*--------------------------------发布消息的Ack解包--------------------------------*/ uint1 MQTT_UnPacketPublishAck ( uint8 *rev_data ); /*--------------------------------发布消息的Rec组包--------------------------------*/ uint1 MQTT_PacketPublishRec ( uint16 pkt_id, MQTT_PACKET_STRUCTURE *mqttPacket ); /*--------------------------------发布消息的Rec解包--------------------------------*/ uint1 MQTT_UnPacketPublishRec ( uint8 *rev_data ); /*--------------------------------发布消息的Rel组包--------------------------------*/ uint1 MQTT_PacketPublishRel ( uint16 pkt_id, MQTT_PACKET_STRUCTURE *mqttPacket ); /*--------------------------------发布消息的Rel解包--------------------------------*/ uint1 MQTT_UnPacketPublishRel ( uint8 *rev_data, uint16 pkt_id ); /*--------------------------------发布消息的Comp组包--------------------------------*/ uint1 MQTT_PacketPublishComp ( uint16 pkt_id, MQTT_PACKET_STRUCTURE *mqttPacket ); /*--------------------------------发布消息的Comp解包--------------------------------*/ uint1 MQTT_UnPacketPublishComp ( uint8 *rev_data ); /*--------------------------------心跳请求组包--------------------------------*/ uint1 MQTT_PacketPing ( MQTT_PACKET_STRUCTURE *mqttPacket ); #endif
6)Esp8266AT指令使用程序代码
//单片机头文件 #include "main.h" extern volatile STRUCT_QUEUE g_Queue_Esp; void ISR_USART3_Recv_Fun ( void ); //========================================================== // 函数名称: ESP8266_Clear // // 函数功能: 清空缓存 // // 入口参数: 无 // // 返回参数: 无 // // 说明: //========================================================== void ESP8266_Clear ( void ) { Que_Init ( ( STRUCT_QUEUE * ) &g_Queue_Esp ); } u8 MYstrstr ( unsigned char *strSrc, u16 num, unsigned char *str ) { u16 i; u16 count = strlen ( ( char * ) str ); for ( i = 0; i < num; i++ ) { if ( memcmp ( ( strSrc + i ), str, count ) == 0 ) { return 0; } } return 1; } //========================================================== // 函数名称: ESP8266_SendCmd // // 函数功能: 发送命令 // // 入口参数: cmd:命令 // res:需要检查的返回指令 // // 返回参数: 0-成功 1-失败 // // 说明: //========================================================== _Bool ESP8266_SendCmd ( char *cmd, char *res, u16 dly ) { unsigned int timeOut = dly; u16 num = 0; ESP8266_Clear(); printf ( "%s\n", cmd ); Usart2_SendString ( ( unsigned char * ) cmd, strlen ( ( const char * ) cmd ) ); while ( timeOut-- ) { num = Que_GetCount ( ( STRUCT_QUEUE * ) &g_Queue_Esp ); if ( num ) //如果收到数据 { if ( MYstrstr ( ( unsigned char * ) g_Queue_Esp.buffer, num, ( unsigned char * ) res ) == 0 ) //如果检索到关键词 { //等待回车换行接收完成 delay_ms ( 10 ); num = Que_GetCount ( ( STRUCT_QUEUE * ) &g_Queue_Esp ); Debug_Print ( ( unsigned char * ) g_Queue_Esp.buffer, num ); ESP8266_Clear(); return 0; } } delay_ms ( 1 ); } delay_ms ( 10 ); Debug_Print ( ( unsigned char * ) g_Queue_Esp.buffer, num ); ESP8266_Clear(); return 1; } //========================================================== // 函数名称: ESP8266_SendData // // 函数功能: 发送数据 // // 入口参数: data:数据 // len:长度 // // 返回参数: 无 // // 说明: //========================================================== void ESP8266_SendData ( unsigned char *data, unsigned short len ) { char cmdBuf[32]; ESP8266_Clear(); //清空接收缓存 sprintf ( cmdBuf, "AT+CIPSEND=%d\r\n", len ); //发送命令 if ( !ESP8266_SendCmd ( cmdBuf, ">", 400 ) ) //收到‘>’时可以发送数据 { delay_ms ( 10 );//非常重要,否则手机模块传送不回来。!!!! Usart2_SendString ( data, len ); //发送设备连接请求数据 } else { //if(strstr ( ( char * ) g_Queue_Esp.buffer, "link is not valid" ))//断网检测 //网络不在线,重头开始连接网络 g_SystemStus = STA_WIFI_STARTCONNECT; } } //========================================================== // 函数名称: ESP8266_GetIPD_link // // 函数功能: 获取平台返回的数据 // // 入口参数: 等待的时间(乘以10ms) // // 返回参数: 平台返回的原始数据 // // 说明: 不同网络设备返回的格式不同,需要去调试 // 如ESP8266的返回格式为 "+IPD,x:yyy" x代表数据长度,yyy是数据内容 //========================================================== unsigned char *ESP8266_GetIPD_link ( unsigned short timeOut ) { unsigned long num = 0; char *ptrIPD = NULL; do { num = Que_GetCount ( ( STRUCT_QUEUE * ) &g_Queue_Esp ); if ( num > 0 ) { delay_ms ( 100 ); ptrIPD = strstr ( ( char * ) g_Queue_Esp.buffer, "IPD," ); if ( ptrIPD != NULL ) { ptrIPD = strchr ( ptrIPD, ':' ); //找到':' if ( ptrIPD != NULL ) { ptrIPD++; return ( unsigned char * ) ( ptrIPD ); } } } delay_ms ( 2 ); //延时等待 } while ( timeOut-- ); return NULL; //超时还未找到,返回空指针 } unsigned char *ESP8266_GetIPD_main ( void ) { char *ptrIPD = NULL; unsigned long num = 0; static int timeout = 0; timeout++; if ( timeout < 5 ) { return NULL; } timeout = 0; num = Que_GetCount ( ( STRUCT_QUEUE * ) &g_Queue_Esp ); if ( num ) //如果接收完成 { delay_ms ( 200 ); ptrIPD = strstr ( ( char * ) g_Queue_Esp.buffer, "IPD," ); //搜索“IPD”头 if ( ptrIPD == NULL ) //如果没找到,可能是IPD头的延迟,还是需要等待一会,但不会超过设定的时间 { //printf ( "\"IPD\" not found\r\n" ); return NULL; } else { ptrIPD = strchr ( ptrIPD, ':' ); //找到':' if ( ptrIPD != NULL ) { ptrIPD++; return ( unsigned char * ) ( ptrIPD ); } else { return NULL; } } } return NULL; //超时还未找到,返回空指针 } //========================================================== // 函数名称: ESP8266_Init // // 函数功能: 初始化ESP8266 // // 入口参数: 无 // // 返回参数: 无 // // 说明: //========================================================== u8 ESP8266_Init ( void ) { char strESP8266_WIFI_INFO[100] = {0}; // GPIO_InitTypeDef GPIO_Initure; sprintf ( strESP8266_WIFI_INFO, "AT+CWJAP=\"%s\",\"%s\"\r\n", g_SysPara.wifi_ssid, g_SysPara.wifi_password ); Usart2_Init ( 115200 );//USART2:串口接ZIGBEE协调器115200 Usart2_Int ( 1 ); // RCC_APB2PeriphClockCmd ( RCC_APB2Periph_GPIOA, ENABLE ); // //ESP8266复位引脚 // GPIO_Initure.GPIO_Mode = GPIO_Mode_Out_PP; // GPIO_Initure.GPIO_Pin = GPIO_Pin_0; //GPIOC14-复位 // GPIO_Initure.GPIO_Speed = GPIO_Speed_50MHz; // GPIO_Init ( GPIOA, &GPIO_Initure ); // GPIO_WriteBit ( GPIOA, GPIO_Pin_0, Bit_RESET ); // delay_ms ( 50 ); // GPIO_WriteBit ( GPIOA, GPIO_Pin_0, Bit_SET ); // delay_ms ( 5000 ); ESP8266_Clear(); if ( ESP8266_SendCmd ( "AT\r\n", "OK", 100 ) ) { delay_ms ( 1500 ); if ( ESP8266_SendCmd ( "AT\r\n", "OK", 100 ) ) { //异常 return 1; //while ( 1 ); } } if ( ESP8266_SendCmd ( "ATE0\r\n", "OK", 100 ) ) { delay_ms ( 1500 ); if ( ESP8266_SendCmd ( "AT\r\n", "OK", 100 ) ) { //异常 return 1; //while ( 1 ); } } if ( ESP8266_SendCmd ( "AT+CWMODE=1\r\n", "OK", 100 ) ) // 设置为 station 模式 { return 1; } if ( ESP8266_SendCmd ( "AT+CIPMODE=0\r\n", "OK", 100 ) ) // 0-非透传模式 { return 1; } if ( ESP8266_SendCmd ( strESP8266_WIFI_INFO, "CONNECTED", 13000 ) ) //"GOT IP" 连接路由器 { if ( ESP8266_SendCmd ( strESP8266_WIFI_INFO, "CONNECTED", 13000 ) ) { return 1; } } delay_ms ( 5000 );//WIFI GOT IP,这里如果时间短了,就会出现冲突,发起2次连接的问题。 ESP8266_Clear(); return 0; } //AT+CIPSTART="TCP","183.230.40.39",6002 注意端口 u8 ESP8266_ConnectCloud ( void ) { char strstrESP8266_WIFI_INFO[100] = {0}; sprintf ( strstrESP8266_WIFI_INFO, "AT+CIPSTART=\"TCP\",\"%s\",%s\r\n", g_SysPara.host_server, g_SysPara.host_port ); delay_ms ( 1500 ); if ( ESP8266_SendCmd ( strstrESP8266_WIFI_INFO, "CONNECT", 22500 ) ) { delay_ms ( 3500 ); if ( ESP8266_SendCmd ( strstrESP8266_WIFI_INFO, "CONNECT", 22500 ) ) { //PIN_LED = 1; return 1; //while ( 1 ); } } return 0; }
7)初始化参数
3.完整程序下载及说明
程序网盘地址:
链接:https://pan.baidu.com/s/1fHd4xYe_gNJy0xns0iSE4g
提取码:jpxd
程序依赖于硬件(光明顶开发板-WCH32F103C8T6),可以联系作者购买。