手机控制的esp8266利用mqtt协议接入百度云智能插座
19年的春节,相信大家和我一样都待在家里,利用在家的时间现学现卖,制作了一款手机控制的智能插座,网上资料很多,我在查询资料中发现,esp8266接入百度云的教程已经过时,今天,我就分享我如何制作智能插座的思路以及学习过程中出现的问题,欢迎大家一起讨论学习,如有不足之处欢迎指正。
学习思路
对于esp8266的使用我则是参考了开发板的视频教程,大家也可以 直接买个开发板先进行学习,某宝有售,五六十就够了,在了解了esp8266这款wifi芯片的工作方式以及控制代码的书写之后便可以进行实践,将理论融入现实。
这是我买的开发板。
所需元器件
首先,考虑到需要用到的元器件。
esp8266-12s(芯片直接上电即可工作,方便使用,体积较小方便放入插座内,我之前就因为没好好了解芯片的使用问题,买回来还得自己接外围电路,在家实属不方便)。
220V转3.3VAC转DC模块(esp8266用3.3V供电,io口检测输入电压也最好3.3V)。这里这个是5V和3.3V双路输出的。这是220V转3.3V的比上面的稍大,但是寿命长。
继电器模块(3.3V供电,我用的是高电平吸合型,注意吸合电流最好10ma一下,因为esp8266io口最大输出电流为16ma,我就憨憨过)。我这是5V供电的。
普通插座(我用的是某牛,六插孔的,,注意螺丝为专业螺丝,需要专业螺丝刀,为了这个我又网上买了一把,疫情严重快递更是…,唉),电烙铁啥的都是基本的就不一一赘述了。
最后这是成品内部图,第一次做,有些凌乱,都已打胶固定,需注意220v的电,用电安全,用粗点的线避免烧毁。
配置百度云端
首先打开百度云官网进行注册,只有实名认证之后才能进行使用,完成实名注册之后打开
产品服务/物联网服务/物接入
点击创建设备(如果需要利用百度云的数据可视化只能创建设备型,而且设备型只能创建一个,具体可自行在物接入里的概览进行帮助查询,里面也有官方教程),这里我们创建一个数据型就够用了。
这里我已经创建好了,可以看见已经分配了给我们连接的地址将
连接地址复制到文本文档
后面接入时方便直接使用。
点击设备进入设备内配置,先配置策略(具体策略是什么请自行参考物接入的帮助文档自行理解),我们利用的是mqtt协议,这里发布与订阅都勾选,主题则是发送和接受消息的一个“过滤器”,如果手机订阅了switch主题那么它将收到设备发布到主题switch的消息。
再配置身份,创建身份绑定刚配置的策略,生成的
密钥先复制到文本文档
里,接入百度云时需要用到。
再进行用户的创建,直接绑定之前的策略以及身份就行,这里的cellphone用户是手机端,我们还需在创建个esp8266接入的用户,我创建的是first_floor,这里的
用户名需复制到文本文档
,接入时会用上。
这里,我们的百度云配置已经完成。
esp8266代码
这是部分代码,只需改几个参数即可接入,完整代码可自行下载。
链接:https://pan.baidu.com/s/1BPnpXsJhk6NQLFOC3nsKhw 提取码:yb9g
mqtt_config.h里的MQTT_HOST地址改成自己的,端口号默认1883不用改,ID不改,MQTT_USER用户名改成自己的,MQTT_PASS密码就是密钥,自己改好,以及wifi名称及密码自己改好。
#ifndef __MQTT_CONFIG_H__
#define __MQTT_CONFIG_H__
typedef enum{
NO_TLS = 0, // 0: disable SSL/TLS, there must be no certificate verify between MQTT server and ESP8266
TLS_WITHOUT_AUTHENTICATION = 1, // 1: enable SSL/TLS, but there is no a certificate verify
ONE_WAY_ANTHENTICATION = 2, // 2: enable SSL/TLS, ESP8266 would verify the SSL server certificate at the same time
TWO_WAY_ANTHENTICATION = 3, // 3: enable SSL/TLS, ESP8266 would verify the SSL server certificate and SSL server would verify ESP8266 certificate
}TLS_LEVEL;
/*IMPORTANT: the following configuration maybe need modified*/
/***********************************************************************************************************************************************************************************************************************************************************/
#define CFG_HOLDER 0x66666666 // 持有人标识(只有更新此数值,系统参数才会更新) /* Change this value to load default configurations */
/*DEFAULT CONFIGURATIONS*/
// 注:【MQTT协议规定:连接服务端的每个客户端都必须有唯一的客户端标识符(ClientId)】。如果两相同ID的客户端不断重连,就会进入互踢死循环
//--------------------------------------------------------------------------------------------------------------------------------------
#define MQTT_HOST "eakpedg.mqtt.iot.gz.baidubce.com" // MQTT服务端域名/IP地址 // the IP address or domain name of your MQTT server or MQTT broker ,such as "mqtt.yourdomain.com"
#define MQTT_PORT 1883 // 网络连接端口号 // the listening port of your MQTT server or MQTT broker
#define MQTT_CLIENT_ID "ESP8266ID0x%x" // 官方例程中是"Device_ID" // 客户端标识符 // the ID of yourself, any string is OK,client would use this ID register itself to MQTT server
#define MQTT_USER "eakpedg/first_floor" // MQTT用户名 // your MQTT login name, if MQTT server allow anonymous login,any string is OK, otherwise, please input valid login name which you had registered
#define MQTT_PASS "***" // MQTT密码 // you MQTT login password, same as above
#define STA_SSID "CMCC_10050_B2674C" // WIFI名称 // your AP/router SSID to config your device networking
#define STA_PASS "***" // WIFI密码 // your AP/router password
#define STA_TYPE AUTH_WPA2_PSK
#define DEFAULT_SECURITY NO_TLS // 加密传输类型【默认不加密】 // very important: you must config DEFAULT_SECURITY for SSL/TLS
#define CA_CERT_FLASH_ADDRESS 0x77 // 【CA证书】烧录地址 // CA certificate address in flash to read, 0x77 means address 0x77000
#define CLIENT_CERT_FLASH_ADDRESS 0x78 // 【设备证书】烧录地址 // client certificate and private key address in flash to read, 0x78 means address 0x78000
/*********************************************************************************************************************************************************************************************************************************************************************************/
/*Please Keep the following configuration if you have no very deep understanding of ESP SSL/TLS*/
#define CFG_LOCATION 0x79 // 系统参数的起始扇区 /* Please don't change or if you know what you doing */
#define MQTT_BUF_SIZE 1024 // MQTT缓存大小
#define MQTT_KEEPALIVE 120 // 保持连接时长 /*second*/
#define MQTT_RECONNECT_TIMEOUT 5 // 重连超时时长 /*second*/
#define MQTT_SSL_ENABLE // SSL使能 //* Please don't change or if you know what you doing */
#define QUEUE_BUFFER_SIZE 2048 // 消息队列的缓存大小
//#define PROTOCOL_NAMEv31 // 使用MQTT协议【v31】版本 /*MQTT version 3.1 compatible with Mosquitto v0.15*/
#define PROTOCOL_NAMEv311 // 使用MQTT协议【v311】版本 /*MQTT version 3.11 compatible with https://eclipse.org/paho/clients/testing/*/
#endif // __MQTT_CONFIG_H__
mqtt.c中void ICACHE_FLASH_ATTR mqtt_timer(void *arg)函数为一秒心跳函数,我进行的是是否有电的检测,里面的主题参数等自己可以照着修改,如果只是控制的话不需要设置,一秒检测一次,因为esp8266进行的是内核回调和普通单片机while循环不一样。
// mqtt.c
#include "user_interface.h"
#include "osapi.h"
#include "espconn.h"
#include "os_type.h"
#include "mem.h"
#include "mqtt_msg.h"
#include "debug.h"
#include "user_config.h"
#include "mqtt.h"
#include "queue.h"
#include "ets_sys.h"
#include "driver/uart.h"
#include "osapi.h"
#include "mqtt.h"
#include "wifi.h"
#include "config.h"
#include "debug.h"
#include "gpio.h"
#include "user_interface.h"
#include "mem.h"
#include "sntp.h"
#include "eagle_soc.h" // GPIO函数、宏定义
#define MQTT_TASK_PRIO 2 // MQTT任务优先级
#define MQTT_TASK_QUEUE_SIZE 1 // MQTT任务消息深度
#define MQTT_SEND_TIMOUT 5 // MQTT发送超时
#ifndef QUEUE_BUFFER_SIZE
#define QUEUE_BUFFER_SIZE 2048 // 队列缓存 = 2048
#endif
unsigned char *default_certificate; // 默认证书指针
unsigned int default_certificate_len = 0; // 默认证书长度
unsigned char *default_private_key; // 默认密钥指针
unsigned int default_private_key_len = 0; // 默认密钥长度
int last_Power;
os_event_t mqtt_procTaskQueue[MQTT_TASK_QUEUE_SIZE]; // MQTT任务结构体(定义为结构体数组的形式)
// 域名解析成功_回调
//=============================================================================================
LOCAL void ICACHE_FLASH_ATTR mqtt_dns_found(const char *name, ip_addr_t *ipaddr, void *arg)
{
struct espconn *pConn = (struct espconn *)arg; // 获取TCP连接指针
MQTT_Client* client = (MQTT_Client *)pConn->reverse; // 获取mqttClient指针
if (ipaddr == NULL) // 域名解析失败
{
INFO("DNS: Found, but got no ip, try to reconnect\r\n");
client->connState = TCP_RECONNECT_REQ; // TCP重连请求(等待5秒)
return;
}
INFO("DNS: found ip %d.%d.%d.%d\n", // 打印域名对应的IP地址
*((uint8 *) &ipaddr->addr),
*((uint8 *) &ipaddr->addr + 1),
*((uint8 *) &ipaddr->addr + 2),
*((uint8 *) &ipaddr->addr + 3));
// 判断IP地址是否正确(?=0)
//----------------------------------------------------------------------------------------
if (client->ip.addr == 0 && ipaddr->addr != 0) // 未保存IP地址:mqttClient->ip.addr == 0
{
os_memcpy(client->pCon->proto.tcp->remote_ip, &ipaddr->addr, 4); // IP赋值
// 根据安全类型,调用不同的TCP连接方式
//-------------------------------------------------------------------------------------------------
if (client->security) // 安全类型 != 0
{
#ifdef MQTT_SSL_ENABLE
if(DEFAULT_SECURITY >= ONE_WAY_ANTHENTICATION ) // 单向认证【ONE_WAY_ANTHENTICATION = 2】
{
espconn_secure_ca_enable(ESPCONN_CLIENT,CA_CERT_FLASH_ADDRESS);
}
if(DEFAULT_SECURITY >= TWO_WAY_ANTHENTICATION) // 双向认证【TWO_WAY_ANTHENTICATION = 3】
{
espconn_secure_cert_req_enable(ESPCONN_CLIENT,CLIENT_CERT_FLASH_ADDRESS);
}
espconn_secure_connect(client->pCon); // 不认证【TLS_WITHOUT_AUTHENTICATION = 1】
#else
INFO("TCP: Do not support SSL\r\n");
#endif
}
else // 安全类型 = 0 = NO_TLS
{
espconn_connect(client->pCon); // TCP连接(作为Client连接Server)
}
client->connState = TCP_CONNECTING; // TCP正在连接
INFO("TCP: connecting...\r\n");
}
system_os_post(MQTT_TASK_PRIO, 0, (os_param_t)client); // 安排任务MQTT_Task
}
//=============================================================================================
// ESP8266获取服务端【PUBLISH】报文的【主题】、【有效载荷】
//===================================================================================================================================
LOCAL void ICACHE_FLASH_ATTR deliver_publish(MQTT_Client* client, uint8_t* message, int length)
{
mqtt_event_data_t event_data;
event_data.topic_length = length; // 主题名长度初始化
event_data.topic = mqtt_get_publish_topic(message, &event_data.topic_length); // 获取【PUBLISH】报文的主题名(指针)、主题名长度
event_data.data_length = length; // 有效载荷长度初始化
event_data.data = mqtt_get_publish_data(message, &event_data.data_length); // 获取【PUBLISH】报文的载荷(指针)、载荷长度
// 进入【接收MQTT的[PUBLISH]数据】函数
//-------------------------------------------------------------------------------------------------------------------------
if (client->dataCb)
client->dataCb((uint32_t*)client, event_data.topic, event_data.topic_length, event_data.data, event_data.data_length);
}
//===================================================================================================================================
// 向MQTT服务器发送【心跳】报文(报文不写入队列,TCP直接发送)
//===================================================================================================================================
void ICACHE_FLASH_ATTR mqtt_send_keepalive(MQTT_Client *client)
{
INFO("\r\nMQTT: Send keepalive packet to %s:%d!\r\n", client->host, client->port);
client->mqtt_state.outbound_message = mqtt_msg_pingreq(&client->mqtt_state.mqtt_connection); // 设置【PINGREQ】报文
client->mqtt_state.pending_msg_type = MQTT_MSG_TYPE_PINGREQ;
client->mqtt_state.pending_msg_type = mqtt_get_type(client->mqtt_state.outbound_message->data); // 获取报文类型
client->mqtt_state.pending_msg_id = mqtt_get_id(client->mqtt_state.outbound_message->data, client->mqtt_state.outbound_message->length);
// TCP发送成功/5秒计时结束 => 报文发送结束(sendTimeout=0)
//-------------------------------------------------------------------------
client->sendTimeout = MQTT_SEND_TIMOUT; // 发送MQTT报文时,sendTimeout=5
INFO("MQTT: Sending, type: %d, id: %04X\r\n", client->mqtt_state.pending_msg_type, client->mqtt_state.pending_msg_id);
err_t result = ESPCONN_OK;
if (client->security) // 安全类型 != 0
{
#ifdef MQTT_SSL_ENABLE
result = espconn_secure_send(client->pCon, client->mqtt_state.outbound_message->data, client->mqtt_state.outbound_message->length);
#else
INFO("TCP: Do not support SSL\r\n");
#endif
}
else // 安全类型 = 0 = NO_TLS
{
result = espconn_send(client->pCon, client->mqtt_state.outbound_message->data, client->mqtt_state.outbound_message->length);
}
client->mqtt_state.outbound_message = NULL; // 报文发送完后,清除出站报文指针
if(ESPCONN_OK == result) // 网络数据发送成功
{
client->keepAliveTick = 0; // 心跳计数 = 0
client->connState = MQTT_DATA; // ESP8266当前状态 = MQTT传输数据
system_os_post(MQTT_TASK_PRIO, 0, (os_param_t)client); // 安排任务MQTT_Task
}
else // 【心跳请求】发送失败
{
client->connState = TCP_RECONNECT_DISCONNECTING; // TCP暂时断开(断开后会重连)
system_os_post(MQTT_TASK_PRIO, 0, (os_param_t)client); // 安排任务MQTT_Task
}
}
//===================================================================================================================================
/**
* @brief Delete tcp client and free all memory
* @param mqttClient: The mqtt client which contain TCP client
* @retval None
*/
// 删除TCP连接、释放pCon内存、清除TCP连接指针
//=======================================================================
void ICACHE_FLASH_ATTR mqtt_tcpclient_delete(MQTT_Client *mqttClient)
{
if (mqttClient->pCon != NULL)
{
INFO("Free memory\r\n");
espconn_delete(mqttClient->pCon); // 删除传输连接
if (mqttClient->pCon->proto.tcp) // 如果是TCP连接
os_free(mqttClient->pCon->proto.tcp); // 释放TCP内存
os_free(mqttClient->pCon); // 释放pCon内存
mqttClient->pCon = NULL; // 清除mqttClient->pCon指针
}
}
//=======================================================================
/**
* @brief Delete MQTT client and free all memory
* @param mqttClient: The mqtt client
* @retval None
*/
// 删除MQTT客户端,并释放所有相关内存
//=====================================================================================
void ICACHE_FLASH_ATTR mqtt_client_delete(MQTT_Client *mqttClient)
{
mqtt_tcpclient_delete(mqttClient); // 删除TCP连接、释放pCon内存、清除TCP连接指针
if (mqttClient->host != NULL)
{
os_free(mqttClient->host); // 释放主机(域名/IP)内存
mqttClient->host = NULL;
}
if (mqttClient->user_data != NULL)
{
os_free(mqttClient->user_data); // 释放用户数据内存
mqttClient->user_data = NULL;
}
if(mqttClient->connect_info.client_id != NULL)
{
os_free(mqttClient->connect_info.client_id);// 释放client_id内存
mqttClient->connect_info.client_id = NULL;
}
if(mqttClient->connect_info.username != NULL)
{
os_free(mqttClient->connect_info.username); // 释放username内存
mqttClient->connect_info.username = NULL;
}
if(mqttClient->connect_info.password != NULL)
{
os_free(mqttClient->connect_info.password); // 释放password内存
mqttClient->connect_info.password = NULL;
}
if(mqttClient->connect_info.will_topic != NULL)
{
os_free(mqttClient->connect_info.will_topic);// 释放will_topic内存
mqttClient->connect_info.will_topic = NULL;
}
if(mqttClient->connect_info.will_message != NULL)
{
os_free(mqttClient->connect_info.will_message);// 释放will_message内存
mqttClient->connect_info.will_message = NULL;
}
if(mqttClient->mqtt_state.in_buffer != NULL)
{
os_free(mqttClient->mqtt_state.in_buffer); // 释放in_buffer内存
mqttClient->mqtt_state.in_buffer = NULL;
}
if(mqttClient->mqtt_state.out_buffer != NULL)
{
os_free(mqttClient->mqtt_state.out_buffer); // 释放out_buffer内存
mqttClient->mqtt_state.out_buffer = NULL;
}
}
//=============================================================================
/**
* @brief Client received callback function.
* @param arg: contain the ip link information
* @param pdata: received data
* @param len: the lenght of received data
* @retval None
*/
// TCP成功接收网络数据:【服务端->客户端ESP8266】
//==================================================================================================================================================
void ICACHE_FLASH_ATTR mqtt_tcpclient_recv(void *arg, char *pdata, unsigned short len)
{
uint8_t msg_type; // 消息类型
uint8_t msg_qos; // 服务质量
uint16_t msg_id; // 消息ID
struct espconn *pCon = (struct espconn*)arg; // 获取TCP连接指针
MQTT_Client *client = (MQTT_Client *)pCon->reverse; // 获取mqttClient指针
client->keepAliveTick = 0; // 只要收到网络数据,客户端(ESP8266)心跳计数 = 0
// 读取数据包
//----------------------------------------------------------------------------------------------------------------------------
READPACKET:
INFO("TCP: data received %d bytes\r\n",len);
//###############################################################################################################################
INFO("TCP: data received %d,%d,%d,%d \r\n", *pdata,*(pdata+1),*(pdata+2),*(pdata+3)); // 查看【确认连接请求】报文的具体值 #
//###############################################################################################################################
if (len<MQTT_BUF_SIZE && len>0) // 接收到的数据长度在允许范围内
{
os_memcpy(client->mqtt_state.in_buffer, pdata, len); // 获取接收数据,存入【入站报文缓存区】
msg_type = mqtt_get_type(client->mqtt_state.in_buffer); // 获取【报文类型】
msg_qos = mqtt_get_qos(client->mqtt_state.in_buffer); // 获取【消息质量】
msg_id = mqtt_get_id(client->mqtt_state.in_buffer, client->mqtt_state.in_buffer_length); // 获取报文中的【报文标识符】
// 根据ESP8266运行状态,执行相应操作
//-------------------------------------------------------------------------
switch (client->connState)
{
case MQTT_CONNECT_SENDING: // 【MQTT_CONNECT_SENDING】
if (msg_type == MQTT_MSG_TYPE_CONNACK) // 判断消息类型!=【CONNACK】
{
// ESP8266发送 != 【CONNECT】报文
//--------------------------------------------------------------
if (client->mqtt_state.pending_msg_type!=MQTT_MSG_TYPE_CONNECT)
{
INFO("MQTT: Invalid packet\r\n"); // 网络数据出错
if (client->security) // 安全类型 != 0
{
#ifdef MQTT_SSL_ENABLE
espconn_secure_disconnect(client->pCon);// 断开TCP连接
#else
INFO("TCP: Do not support SSL\r\n");
#endif
}
else // 安全类型 = 0 = NO_TLS
{
espconn_disconnect(client->pCon); // 断开TCP连接
}
}
// ESP8266发送 == 【CONNECT】报文
//---------------------------------------------------------------------------------
else
{
INFO("MQTT: Connected to %s:%d\r\n", client->host, client->port);
client->connState = MQTT_DATA; // ESP8266状态改变:【MQTT_DATA】
if (client->connectedCb) // 执行[mqttConnectedCb]函数(MQTT连接成功函数)
client->connectedCb((uint32_t*)client); // 参数 = mqttClient
}
}
break;
// 当前ESP8266状态 == MQTT_DATA || MQTT_KEEPALIVE_SEND
//-----------------------------------------------------
case MQTT_DATA:
case MQTT_KEEPALIVE_SEND:
client->mqtt_state.message_length_read = len; // 获取接收网络数据的长度
// 计算接收到的网络数据中,报文的实际长度(通过【剩余长度】得到)
//------------------------------------------------------------------------------------------------------------------------------
client->mqtt_state.message_length = mqtt_get_total_length(client->mqtt_state.in_buffer, client->mqtt_state.message_length_read);
// 当前ESP8266状态 ==【MQTT_DATA || MQTT_KEEPALIVE_SEND】,根据接收的报文类型,决定ESP8266接下来的动作
//-----------------------------------------------------------------------------------------------------------------------
switch (msg_type)
{
// ESP8266接收到【SUBACK】报文:订阅确认
//-----------------------------------------------------------------------------------------------------------------------
case MQTT_MSG_TYPE_SUBACK:
// 判断ESP8266之前发送的报文是否是【订阅主题】
//-------------------------------------------------------------------------------------------------------------
if (client->mqtt_state.pending_msg_type==MQTT_MSG_TYPE_SUBSCRIBE && client->mqtt_state.pending_msg_id==msg_id)
INFO("MQTT: Subscribe successful\r\n"); //
break;
//-----------------------------------------------------------------------------------------------------------------------
// ESP8266接收到【UNSUBACK】报文:取消订阅确认
//-----------------------------------------------------------------------------------------------------------------------
case MQTT_MSG_TYPE_UNSUBACK:
// 判断ESP8266之前发送的报文是否是【订阅主题】
//------------------------------------------------------------------------------------------------------------------
if (client->mqtt_state.pending_msg_type == MQTT_MSG_TYPE_UNSUBSCRIBE && client->mqtt_state.pending_msg_id == msg_id)
INFO("MQTT: UnSubscribe successful\r\n");
break;
//-----------------------------------------------------------------------------------------------------------------------
// ESP8266接收到【PUBLISH】报文:发布消息
//--------------------------------------------------------------------------------------------------------------------------------
case MQTT_MSG_TYPE_PUBLISH:
if (msg_qos == 1) // 【服务端->客户端】发布消息 Qos=1
client->mqtt_state.outbound_message = mqtt_msg_puback(&client->mqtt_state.mqtt_connection, msg_id); // 配置【PUBACK】报文
else if (msg_qos == 2) // 【服务端->客户端】发布消息 Qos=2
client->mqtt_state.outbound_message = mqtt_msg_pubrec(&client->mqtt_state.mqtt_connection, msg_id); // 配置【PUBREC】报文
if (msg_qos == 1 || msg_qos == 2)
{
INFO("MQTT: Queue response QoS: %d\r\n", msg_qos);
// 将ESP8266应答报文(【PUBACK】或【PUBREC】),写入队列
//-------------------------------------------------------------------------------------------------------------------------------
if (QUEUE_Puts(&client->msgQueue, client->mqtt_state.outbound_message->data, client->mqtt_state.outbound_message->length) == -1)
{
INFO("MQTT: Queue full\r\n");
}
}
/// 获取服务端【PUBLISH】报文的【主题】、【有效载荷】
//---------------------------------------------------------------------------------------------
deliver_publish(client, client->mqtt_state.in_buffer, client->mqtt_state.message_length_read);
break;
//-----------------------------------------------------------------------------------------------------------------------
// ESP8266接收到【PUBACK】报文:发布消息应答
//-------------------------------------------------------------------------------------------------------------------
case MQTT_MSG_TYPE_PUBACK:
if (client->mqtt_state.pending_msg_type == MQTT_MSG_TYPE_PUBLISH && client->mqtt_state.pending_msg_id == msg_id)
{
INFO("MQTT: received MQTT_MSG_TYPE_PUBACK, finish QoS1 publish\r\n");
}
break;
//-------------------------------------------------------------------------------------------------------------------
// Qos == 2
//-------------------------------------------------------------------------------------------------------------------------------------
case MQTT_MSG_TYPE_PUBREC:
client->mqtt_state.outbound_message = mqtt_msg_pubrel(&client->mqtt_state.mqtt_connection, msg_id);
if (QUEUE_Puts(&client->msgQueue, client->mqtt_state.outbound_message->data, client->mqtt_state.outbound_message->length) == -1) {
INFO("MQTT: Queue full\r\n");
}
break;
case MQTT_MSG_TYPE_PUBREL:
client->mqtt_state.outbound_message = mqtt_msg_pubcomp(&client->mqtt_state.mqtt_connection, msg_id);
if (QUEUE_Puts(&client->msgQueue, client->mqtt_state.outbound_message->data, client->mqtt_state.outbound_message->length) == -1) {
INFO("MQTT: Queue full\r\n");
}
break;
case MQTT_MSG_TYPE_PUBCOMP:
if (client->mqtt_state.pending_msg_type == MQTT_MSG_TYPE_PUBLISH && client->mqtt_state.pending_msg_id == msg_id) {
INFO("MQTT: receive MQTT_MSG_TYPE_PUBCOMP, finish QoS2 publish\r\n");
}
break;
//-------------------------------------------------------------------------------------------------------------------------------------
// ESP8266接收到【PINGREQ】报文:【这个是服务端发送给客户端的,正常情况下,ESP8266不会收到此报文】
//-------------------------------------------------------------------------------------------------------------------------------------
case MQTT_MSG_TYPE_PINGREQ:
client->mqtt_state.outbound_message = mqtt_msg_pingresp(&client->mqtt_state.mqtt_connection);
if (QUEUE_Puts(&client->msgQueue, client->mqtt_state.outbound_message->data, client->mqtt_state.outbound_message->length) == -1) {
INFO("MQTT: Queue full\r\n");
}
break;
//-------------------------------------------------------------------------------------------------------------------------------------
// ESP8266接收到【PINGRESP】报文:心跳应答
//-----------------------------------------
case MQTT_MSG_TYPE_PINGRESP:
// Ignore // 心跳应答报文则忽略
break;
//-----------------------------------------
}
// NOTE: this is done down here and not in the switch case above
// because the PSOCK_READBUF_LEN() won't work inside a switch
// statement due to the way protothreads resume.
// ESP8266接收到【PUBLISH】报文:发布消息
//-------------------------------------------------------------------------------------
if (msg_type == MQTT_MSG_TYPE_PUBLISH)
{
len = client->mqtt_state.message_length_read;
// 报文实际长度 < 网络数据长度
//------------------------------------------------------------------------------
if (client->mqtt_state.message_length < client->mqtt_state.message_length_read)
{
//client->connState = MQTT_PUBLISH_RECV;
//Not Implement yet
len -= client->mqtt_state.message_length; // 跳过之前解析过的报文
pdata += client->mqtt_state.message_length; // 指向之后为解析的网络数据
INFO("Get another published message\r\n");
goto READPACKET; // 重新解析网络数据
}
}
break; // case MQTT_DATA:
// case MQTT_KEEPALIVE_SEND:
//-------------------------------------------------------------------------------------
}
}
else // 接收到的报文出错
{
INFO("ERROR: Message too long\r\n");
}
system_os_post(MQTT_TASK_PRIO, 0, (os_param_t)client); // 安排任务MQTT_Task
}
//==================================================================================================================================================
/**
* @brief Client send over callback function.
* @param arg: contain the ip link information
* @retval None
*/
// TCP发送成功_回调函数
//================================================================================
void ICACHE_FLASH_ATTR mqtt_tcpclient_sent_cb(void *arg)
{
struct espconn *pCon = (struct espconn *)arg; // 获取TCP连接指针
MQTT_Client* client = (MQTT_Client *)pCon->reverse; // 获取mqttClient指针
INFO("TCP: Sent\r\n");
client->sendTimeout = 0; // TCP发送成功/报文发送5秒计时结束 => 报文发送结束(sendTimeout=0)
client->keepAliveTick =0; // 只要TCP发送成功,客户端(ESP8266)心跳计数 = 0
// 当前是MQTT客户端 发布消息/发送心跳 状态
//--------------------------------------------------------------------------
if ((client->connState==MQTT_DATA || client->connState==MQTT_KEEPALIVE_SEND)
&& client->mqtt_state.pending_msg_type==MQTT_MSG_TYPE_PUBLISH)
{
if (client->publishedCb) // 执行[MQTT发布成功]函数
client->publishedCb((uint32_t*)client);
}
system_os_post(MQTT_TASK_PRIO, 0, (os_param_t)client); // 安排任务MQTT_Task
}
//================================================================================
// MQTT定时(1秒)【功能:心跳计时、重连计时、TCP发送计时】
//================================================================================
void ICACHE_FLASH_ATTR mqtt_timer(void *arg)
{
MQTT_Client* client = (MQTT_Client*)arg;
last_Power = Power;
if( GPIO_INPUT_GET(GPIO_ID_PIN(15)) == 0 ) // 读取GPIO_0电平
{
Power = OFF;
}
else
{
Power = ON;
}
if(last_Power != Power)
{
if(Power == ON)
// ESP8266向主题发布消息:【参数2:主题名 / 参数3:发布消息的有效载荷 / 参数4:有效载荷长度 / 参数5:发布Qos / 参数6:Retain】
{
MQTT_Publish(client, "first_floor_switch", "Power_ON", strlen("Power_ON"), 0, 0); // 向主题"first_floor_switch"发布"Power_ON",Qos=0、retain=0
INFO("\r\n Power ON ...\r\n");
//GPIO_OUTPUT_SET(GPIO_ID_PIN(4),0); // LED亮
}
else
{
MQTT_Publish(client, "first_floor_switch", "Power_OFF", strlen("Power_OFF"), 0, 0); // 向主题"first_floor_switch"发布"Power_OFF",Qos=0、retain=0
INFO("\r\n Power OFF ...\r\n");
//GPIO_OUTPUT_SET(GPIO_ID_PIN(4),1); // LED灭
}
}
// 如果当前是[MQTT_DATA]状态,则进行存活计时操作
//--------------------------------------------------------------------------
if (client->connState == MQTT_DATA) // MQTT_DATA
{
client->keepAliveTick ++; // 客户端(ESP8266)心跳计数++
// 判断客户端(ESP8266)心跳计数 ?> 保持连接的1/2时间
//--------------------------------------------------------------------------------------------------------------------------
if (client->keepAliveTick>(client->mqtt_state.connect_info->keepalive/2)) //【注:官方例程中是:判断是否超过保持连接时长】
{
client->connState = MQTT_KEEPALIVE_SEND; // MQTT_KEEPALIVE_SEND
system_os_post(MQTT_TASK_PRIO,0,(os_param_t)client);// 安排任务
}
}
// 重连等待计时:当进入重连请求状态后,需等待5秒,之后进行重新连接
//--------------------------------------------------------------------------
else if (client->connState == TCP_RECONNECT_REQ) // TCP重连请求(等待5秒)
{
client->reconnectTick ++; // 重连计时++
if (client->reconnectTick > MQTT_RECONNECT_TIMEOUT) // 重连请求超过5秒
{
client->reconnectTick = 0; // 重连计时 = 0
client->connState = TCP_RECONNECT; // TCP重新连接
system_os_post(MQTT_TASK_PRIO, 0, (os_param_t)client); // 安排任务
// 重连等待计时结束
//-----------------------------------------------------------------
if (client->timeoutCb) // 未创建回调函数
client->timeoutCb((uint32_t*)client);
}
}
// TCP发送成功/报文发送5秒计时结束 => 报文发送结束(sendTimeout=0)
//----------------------------------------------------------------
if (client->sendTimeout>0) // 发送MQTT报文时,sendTimeout=5
client->sendTimeout --; // sendTimeout每秒递减(直到=0)
}
//================================================================================
// TCP断开成功_回调函数
//================================================================================
void ICACHE_FLASH_ATTR mqtt_tcpclient_discon_cb(void *arg)
{
struct espconn *pespconn = (struct espconn *)arg; // 获取TCP连接指针
MQTT_Client*client = (MQTT_Client*)pespconn->reverse; // 获取mqttClient指针
INFO("TCP: Disconnected callback\r\n");
// 不执行重连操作
//……………………………………………………………………………………………………
if(TCP_DISCONNECTING == client->connState) // 当前状态如果是:TCP正在断开
{
client->connState = TCP_DISCONNECTED; // ESP8266状态改变:TCP成功断开
}
else if(MQTT_DELETING == client->connState)// 当前状态如果是:MQTT正在删除
{
client->connState = MQTT_DELETED; // ESP8266状态改变:MQTT删除
}
//……………………………………………………………………………………………………
// 只要不是上面两种状态,那么当TCP断开连接后,进入【TCP重连请求(等待5秒)】
//……………………………………………………………………………………………………
else
{
client->connState = TCP_RECONNECT_REQ; // TCP重连请求(等待5秒)
}
if (client->disconnectedCb) // 调用[MQTT成功断开连接]函数
client->disconnectedCb((uint32_t*)client);
//……………………………………………………………………………………………………
system_os_post(MQTT_TASK_PRIO, 0, (os_param_t)client); // 安排任务MQTT_Task
}
//================================================================================
/**
* @brief Tcp client connect success callback function.
* @param arg: contain the ip link information
* @retval None
*/
// TCP连接成功的回调函数
//============================================================================================================================================
void ICACHE_FLASH_ATTR mqtt_tcpclient_connect_cb(void *arg)
{
struct espconn *pCon = (struct espconn *)arg; // 获取TCP连接指针
MQTT_Client* client = (MQTT_Client *)(pCon->reverse);// 获取mqttClient指针
// 注册回调函数
//--------------------------------------------------------------------------------------
espconn_regist_disconcb(client->pCon, mqtt_tcpclient_discon_cb); // TCP断开成功_回调
espconn_regist_recvcb(client->pCon, mqtt_tcpclient_recv); // TCP接收成功_回调
espconn_regist_sentcb(client->pCon, mqtt_tcpclient_sent_cb); // TCP发送成功_回调
INFO("MQTT: Connected to broker %s:%d\r\n", client->host, client->port);
// 【CONNECT】报文发送准备
//……………………………………………………………………………………………………………………………………………………………………………………
// 初始化MQTT报文缓存区
//-----------------------------------------------------------------------------------------------------------------------
mqtt_msg_init(&client->mqtt_state.mqtt_connection, client->mqtt_state.out_buffer, client->mqtt_state.out_buffer_length);
// 配置【CONNECT】控制报文,并获取【CONNECT】报文[指针]、[长度]
//----------------------------------------------------------------------------------------------------------------------------
client->mqtt_state.outbound_message = mqtt_msg_connect(&client->mqtt_state.mqtt_connection, client->mqtt_state.connect_info);
// 获取待发送的报文类型(此处是【CONNECT】报文)
//----------------------------------------------------------------------------------------------
client->mqtt_state.pending_msg_type = mqtt_get_type(client->mqtt_state.outbound_message->data);
// 获取待发送报文中的【报文标识符】(【CONNECT】报文中没有)
//-------------------------------------------------------------------------------------------------------------------------------------
client->mqtt_state.pending_msg_id = mqtt_get_id(client->mqtt_state.outbound_message->data,client->mqtt_state.outbound_message->length);
// TCP发送成功/报文发送5秒计时结束 => 报文发送结束(sendTimeout=0)
//-------------------------------------------------------------------------
client->sendTimeout = MQTT_SEND_TIMOUT; // 发送MQTT报文时,sendTimeout=5
INFO("MQTT: Sending, type: %d, id: %04X\r\n", client->mqtt_state.pending_msg_type, client->mqtt_state.pending_msg_id);
//……………………………………………………………………………………………………………………………………………………………………………………
// TCP:发送【CONNECT】报文
//-----------------------------------------------------------------------------------------------------------------------------
if (client->security) // 安全类型 != 0
{
#ifdef MQTT_SSL_ENABLE
espconn_secure_send(client->pCon, client->mqtt_state.outbound_message->data, client->mqtt_state.outbound_message->length);
#else
INFO("TCP: Do not support SSL\r\n");
#endif
}
else // 安全类型 = 0 = NO_TLS
{
// TCP发送:数据=[client->mqtt_state.outbound_message->data]、长度=[client->mqtt_state.outbound_message->length]
//-----------------------------------------------------------------------------------------------------------------
espconn_send(client->pCon, client->mqtt_state.outbound_message->data, client->mqtt_state.outbound_message->length);
}
//-----------------------------------------------------------------------------------------------------------------------------
client->mqtt_state.outbound_message = NULL; // 报文发送完后,清除出站报文指针
client->connState = MQTT_CONNECT_SENDING; // 状态设为:MQTT【CONNECT】报文发送中【MQTT_CONNECT_SENDING】
system_os_post(MQTT_TASK_PRIO, 0, (os_param_t)client); // 安排任务MQTT_Task
}
//============================================================================================================================================
/**
* @brief Tcp client connect repeat callback function.
* @param arg: contain the ip link information
* @retval None
*/
// TCP异常中断_回调
//==============================================================================
void ICACHE_FLASH_ATTR mqtt_tcpclient_recon_cb(void *arg, sint8 errType)
{
struct espconn *pCon = (struct espconn *)arg; // 获取TCP连接指针
MQTT_Client* client = (MQTT_Client *)pCon->reverse; // 获取mqttClient指针
INFO("TCP: Reconnect to %s:%d\r\n", client->host, client->port);
client->connState = TCP_RECONNECT_REQ; // TCP重连请求(等待5秒)
system_os_post(MQTT_TASK_PRIO, 0, (os_param_t)client); // 安排任务MQTT_Task
}
//==============================================================================
/**
* @brief MQTT publish function.
* @param client: MQTT_Client reference
* @param topic: string topic will publish to
* @param data: buffer data send point to
* @param data_length: length of data
* @param qos: qos
* @param retain: retain
* @retval TRUE if success queue
*/
// ESP8266向主题发布消息:【参数2:主题名 / 参数3:发布消息的有效载荷 / 参数4:有效载荷长度 / 参数5:发布Qos / 参数6:Retain】
//============================================================================================================================================
BOOL ICACHE_FLASH_ATTR MQTT_Publish(MQTT_Client *client, const char* topic, const char* data, int data_length, int qos, int retain)
{
uint8_t dataBuffer[MQTT_BUF_SIZE]; // 解析后报文缓存(1204字节)
uint16_t dataLen; // 解析后报文长度
// 配置【PUBLISH】报文,并获取【PUBLISH】报文[指针]、[长度]
//------------------------------------------------------------------------------------------
client->mqtt_state.outbound_message = mqtt_msg_publish(&client->mqtt_state.mqtt_connection,
topic, data, data_length,
qos, retain,
&client->mqtt_state.pending_msg_id);
if (client->mqtt_state.outbound_message->length == 0) // 判断报文是否正确
{
INFO("MQTT: Queuing publish failed\r\n");
return FALSE;
}
// 串口打印:【PUBLISH】报文长度,(队列装填数量/队列大小)
//--------------------------------------------------------------------------------------------------------------------------------------------------------------------
INFO("MQTT: queuing publish, length: %d, queue size(%d/%d)\r\n", client->mqtt_state.outbound_message->length, client->msgQueue.rb.fill_cnt, client->msgQueue.rb.size);
// 将报文写入队列,并返回写入字节数(包括特殊码)
//----------------------------------------------------------------------------------------------------------------------------------
while (QUEUE_Puts(&client->msgQueue, client->mqtt_state.outbound_message->data, client->mqtt_state.outbound_message->length) == -1)
{
INFO("MQTT: Queue full\r\n"); // 队列已满
// 解析队列中的数据包
//-----------------------------------------------------------------------------------------------
if (QUEUE_Gets(&client->msgQueue, dataBuffer, &dataLen, MQTT_BUF_SIZE) == -1) // 解析失败 = -1
{
INFO("MQTT: Serious buffer error\r\n");
return FALSE;
}
}
system_os_post(MQTT_TASK_PRIO, 0, (os_param_t)client); // 安排任务
return TRUE;
}
//============================================================================================================================================
/**
* @brief MQTT subscibe function.
* @param client: MQTT_Client reference
* @param topic: string topic will subscribe
* @param qos: qos
* @retval TRUE if success queue
*/
// ESP8266订阅主题【参数2:主题过滤器 / 参数3:订阅Qos】
//============================================================================================================================================
BOOL ICACHE_FLASH_ATTR MQTT_Subscribe(MQTT_Client *client, char* topic, uint8_t qos)
{
uint8_t dataBuffer[MQTT_BUF_SIZE]; // 解析后报文缓存(1204字节)
uint16_t dataLen; // 解析后报文长度
// 配置【SUBSCRIBE】报文,并获取【SUBSCRIBE】报文[指针]、[长度]
//----------------------------------------------------------------------------------------------------------------------------------------
client->mqtt_state.outbound_message=mqtt_msg_subscribe(&client->mqtt_state.mqtt_connection,topic, qos,&client->mqtt_state.pending_msg_id);
INFO("MQTT: queue subscribe, topic\"%s\", id: %d\r\n", topic, client->mqtt_state.pending_msg_id);
// 将报文写入队列,并返回写入字节数(包括特殊码)
//----------------------------------------------------------------------------------------------------------------------------------
while(QUEUE_Puts(&client->msgQueue, client->mqtt_state.outbound_message->data, client->mqtt_state.outbound_message->length) == -1)
{
INFO("MQTT: Queue full\r\n");
// 解析队列中的报文
//-----------------------------------------------------------------------------------------------
if (QUEUE_Gets(&client->msgQueue, dataBuffer, &dataLen, MQTT_BUF_SIZE) == -1) // 解析失败 = -1
{
INFO("MQTT: Serious buffer error\r\n");
return FALSE;
}
}
system_os_post(MQTT_TASK_PRIO, 0, (os_param_t)client); // 安排任务MQTT_Task
return TRUE;
}
//============================================================================================================================================
/**
* @brief MQTT un-subscibe function.
* @param client: MQTT_Client reference
* @param topic: String topic will un-subscribe
* @retval TRUE if success queue
*/
// ESP8266取消订阅主题
//============================================================================================================================================
BOOL ICACHE_FLASH_ATTR MQTT_UnSubscribe(MQTT_Client *client, char* topic)
{
uint8_t dataBuffer[MQTT_BUF_SIZE]; // 解析后报文缓存(1204字节)
uint16_t dataLen; // 解析后报文长度
// 配置【UNSUBSCRIBE】报文 【参数2:取消订阅主题 / 参数3:报文标识符】
//----------------------------------------------------------------------------------------------------------------------------------------
client->mqtt_state.outbound_message = mqtt_msg_unsubscribe(&client->mqtt_state.mqtt_connection,topic,&client->mqtt_state.pending_msg_id);
INFO("MQTT: queue un-subscribe, topic\"%s\", id: %d\r\n", topic, client->mqtt_state.pending_msg_id);
// 将报文写入队列,并返回写入字节数(包括特殊码)
//----------------------------------------------------------------------------------------------------------------------------------
while (QUEUE_Puts(&client->msgQueue, client->mqtt_state.outbound_message->data, client->mqtt_state.outbound_message->length) == -1)
{
INFO("MQTT: Queue full\r\n");
// 解析队列中的报文
//-----------------------------------------------------------------------------------------------
if (QUEUE_Gets(&client->msgQueue, dataBuffer, &dataLen, MQTT_BUF_SIZE) == -1) // 解析失败 = -1
{
INFO("MQTT: Serious buffer error\r\n");
return FALSE;
}
}
system_os_post(MQTT_TASK_PRIO, 0, (os_param_t)client); // 安排任务MQTT_Task
return TRUE;
}
//============================================================================================================================================
/**
* @brief MQTT ping function.
* @param client: MQTT_Client reference
* @retval TRUE if success queue
*/
// 向MQTT服务器发送【心跳】报文(将报文写入队列,任务函数中发送)
//============================================================================================================================================
BOOL ICACHE_FLASH_ATTR MQTT_Ping(MQTT_Client *client)
{
uint8_t dataBuffer[MQTT_BUF_SIZE];
uint16_t dataLen;
client->mqtt_state.outbound_message = mqtt_msg_pingreq(&client->mqtt_state.mqtt_connection); // 配置【PINGREQ】报文
if(client->mqtt_state.outbound_message->length == 0) // 报文错误
{
INFO("MQTT: Queuing publish failed\r\n");
return FALSE;
}
INFO("MQTT: queuing publish, length: %d, queue size(%d/%d)\r\n", client->mqtt_state.outbound_message->length, client->msgQueue.rb.fill_cnt, client->msgQueue.rb.size);
// 将报文写入队列,并返回写入字节数(包括特殊码)
//----------------------------------------------------------------------------------------------------------------------------------
while(QUEUE_Puts(&client->msgQueue, client->mqtt_state.outbound_message->data, client->mqtt_state.outbound_message->length) == -1)
{
INFO("MQTT: Queue full\r\n");
// 解析队列中的报文
//-----------------------------------------------------------------------------------------------
if(QUEUE_Gets(&client->msgQueue, dataBuffer, &dataLen, MQTT_BUF_SIZE) == -1) // 解析失败 = -1
{
INFO("MQTT: Serious buffer error\r\n");
return FALSE;
}
}
system_os_post(MQTT_TASK_PRIO, 0, (os_param_t)client); // 安排任务MQTT_Task
return TRUE;
}
//============================================================================================================================================
// MQTT任务函数【任务:根据ESP8266运行状态,执行相应操作】
//--------------------------------------------------------------------------------------------
// TCP_RECONNECT_REQ TCP重连请求(等待5秒) 退出Tsak(5秒后,进入TCP_RECONNECT状态)
//--------------------------------------------------------------------------------------------
// TCP_RECONNECT TCP重新连接 执行MQTT连接准备,并设置ESP8266状态
//--------------------------------------------------------------------------------------------
// MQTT_DELETING MQTT正在删除 TCP断开连接
// TCP_DISCONNECTING TCP正在断开
// TCP_RECONNECT_DISCONNECTING TCP暂时断开(断开后会重连)
//--------------------------------------------------------------------------------------------
// TCP_DISCONNECTED TCP成功断开 删除TCP连接,并释放pCon内存
//--------------------------------------------------------------------------------------------
// MQTT_DELETED MQTT已删除 删除MQTT客户端,并释放相关内存
//--------------------------------------------------------------------------------------------
// MQTT_KEEPALIVE_SEND MQTT心跳 向服务器发送心跳报文
//--------------------------------------------------------------------------------------------
// MQTT_DATA MQTT数据传输 TCP发送队列中的报文
//====================================================================================================================================
void ICACHE_FLASH_ATTR MQTT_Task(os_event_t *e) // 不判断消息类型
{
INFO("\r\n------------- MQTT_Task -------------\r\n");
MQTT_Client* client = (MQTT_Client*)e->par; // 【e->par】 == 【mqttClient指针的值】,所以需类型转换
uint8_t dataBuffer[MQTT_BUF_SIZE]; // 数据缓存区(1204字节)
uint16_t dataLen; // 数据长度
if (e->par == 0) // 没有mqttClient指针,错误
return;
// 根据ESP8266运行状态,执行相应操作
//………………………………………………………………………………………………………………………………………………………………………
switch (client->connState)
{
// TCP重连请求(等待5秒),退出Tsak
//---------------------------------
case TCP_RECONNECT_REQ: break;
//---------------------------------
// TCP重新连接:执行MQTT连接准备,并设置ESP8266状态
//--------------------------------------------------------------------------------
case TCP_RECONNECT:
mqtt_tcpclient_delete(client); // 删除TCP连接、释放pCon内存、清除TCP连接指针
MQTT_Connect(client); // MQTT连接准备:TCP连接、域名解析等
INFO("TCP: Reconnect to: %s:%d\r\n", client->host, client->port);
client->connState = TCP_CONNECTING; // TCP正在连接
break;
//--------------------------------------------------------------------------------
// MQTT正在删除、TCP正在断开、【心跳请求】报文发送失败:TCP断开连接
//------------------------------------------------------------------
case MQTT_DELETING:
case TCP_DISCONNECTING:
case TCP_RECONNECT_DISCONNECTING:
if (client->security) // 安全类型 != 0
{
#ifdef MQTT_SSL_ENABLE
espconn_secure_disconnect(client->pCon);
#else
INFO("TCP: Do not support SSL\r\n");
#endif
}
else // 安全类型 = 0 = NO_TLS
{
espconn_disconnect(client->pCon); // TCP断开连接
}
break;
//------------------------------------------------------------------
// TCP成功断开
//--------------------------------------------------------------------------------
case TCP_DISCONNECTED:
INFO("MQTT: Disconnected\r\n");
mqtt_tcpclient_delete(client); // 删除TCP连接、释放pCon内存、清除TCP连接指针
break;
//--------------------------------------------------------------------------------
// MQTT已删除:ESP8266的状态为[MQTT已删除]后,将MQTT相关内存释放
//--------------------------------------------------------------------
case MQTT_DELETED:
INFO("MQTT: Deleted client\r\n");
mqtt_client_delete(client); // 删除MQTT客户端,并释放相关内存
break;
// MQTT客户端存活报告
//--------------------------------------------------------------------
case MQTT_KEEPALIVE_SEND:
mqtt_send_keepalive(client); // 向MQTT服务器发送【心跳】报文
break;
// MQTT传输数据状态
//-------------------------------------------------------------------------------------------------------------------------------
case MQTT_DATA:
if (QUEUE_IsEmpty(&client->msgQueue) || client->sendTimeout != 0)
{
break; // 【队列为空 || 发送未结束】,不执行操作
}
// 【队列非空 && 发送结束】:解析并发送 队列中的报文
//--------------------------------------------------------------------------------------------------------
if (QUEUE_Gets(&client->msgQueue, dataBuffer, &dataLen, MQTT_BUF_SIZE) == 0) // 解析成功 = 0
{
client->mqtt_state.pending_msg_type = mqtt_get_type(dataBuffer); // 获取报文中的【报文类型】
client->mqtt_state.pending_msg_id = mqtt_get_id(dataBuffer, dataLen); // 获取报文中的【报文标识符】
client->sendTimeout = MQTT_SEND_TIMOUT; // 发送MQTT报文时,sendTimeout=5
INFO("MQTT: Sending, type: %d, id: %04X\r\n", client->mqtt_state.pending_msg_type, client->mqtt_state.pending_msg_id);
// 发送报文
//-----------------------------------------------------------------------
if (client->security) // 安全类型 != 0
{
#ifdef MQTT_SSL_ENABLE
espconn_secure_send(client->pCon, dataBuffer, dataLen);
#else
INFO("TCP: Do not support SSL\r\n");
#endif
}
else // 安全类型 = 0 = NO_TLS
{
espconn_send(client->pCon, dataBuffer, dataLen); // TCP发送数据包
}
client->mqtt_state.outbound_message = NULL; // 报文发送完后,清除出站报文指针
break;
}
break;
}
//………………………………………………………………………………………………………………………………………………………………………
} // 函数【MQTT_Task】结束
//====================================================================================================================================
/**
* @brief MQTT initialization connection function
* @param client: MQTT_Client reference
* @param host: Domain or IP string
* @param port: Port to connect
* @param security: 1 for ssl, 0 for none
* @retval None
*/
// 网络连接参数赋值:服务端域名【mqtt_test_jx.mqtt.iot.gz.baidubce.com】、网络连接端口【1883】、安全类型【0:NO_TLS】
//====================================================================================================================
void ICACHE_FLASH_ATTR MQTT_InitConnection(MQTT_Client *mqttClient, uint8_t* host, uint32_t port, uint8_t security)
{
uint32_t temp;
INFO("MQTT_InitConnection\r\n");
os_memset(mqttClient, 0, sizeof(MQTT_Client)); // 【MQTT客户端】结构体 = 0
temp = os_strlen(host); // 服务端域名/IP的字符串长度
mqttClient->host = (uint8_t*)os_zalloc(temp+1); // 申请空间,存放服务端域名/IP地址字符串
os_strcpy(mqttClient->host, host); // 字符串拷贝
mqttClient->host[temp] = 0; // 最后'\0'
mqttClient->port = port; // 网络端口号 = 1883
mqttClient->security = security; // 安全类型 = 0 = NO_TLS
}
//====================================================================================================================
/**
* @brief MQTT initialization mqtt client function
* @param client: MQTT_Client reference
* @param clientid: MQTT client id
* @param client_user:MQTT client user
* @param client_pass:MQTT client password
* @param client_pass:MQTT keep alive timer, in second
* @retval None
*/
// MQTT连接参数赋值:客户端标识符【..】、MQTT用户名【..】、MQTT密钥【..】、保持连接时长【120s】、清除会话【1:clean_session】
//======================================================================================================================================================
void ICACHE_FLASH_ATTR
MQTT_InitClient(MQTT_Client *mqttClient, uint8_t* client_id, uint8_t* client_user, uint8_t* client_pass, uint32_t keepAliveTime, uint8_t cleanSession)
{
uint32_t temp;
INFO("MQTT_InitClient\r\n");
// MQTT【CONNECT】报文的连接参数 赋值
//---------------------------------------------------------------------------------------------------------------
os_memset(&mqttClient->connect_info, 0, sizeof(mqtt_connect_info_t)); // MQTT【CONNECT】报文的连接参数 = 0
temp = os_strlen(client_id);
mqttClient->connect_info.client_id = (uint8_t*)os_zalloc(temp + 1); // 申请【客户端标识符】的存放内存
os_strcpy(mqttClient->connect_info.client_id, client_id); // 赋值【客户端标识符】
mqttClient->connect_info.client_id[temp] = 0; // 最后'\0'
if (client_user) // 判断是否有【MQTT用户名】
{
temp = os_strlen(client_user);
mqttClient->connect_info.username = (uint8_t*)os_zalloc(temp + 1);
os_strcpy(mqttClient->connect_info.username, client_user); // 赋值【MQTT用户名】
mqttClient->connect_info.username[temp] = 0;
}
if (client_pass) // 判断是否有【MQTT密码】
{
temp = os_strlen(client_pass);
mqttClient->connect_info.password = (uint8_t*)os_zalloc(temp + 1);
os_strcpy(mqttClient->connect_info.password, client_pass); // 赋值【MQTT密码】
mqttClient->connect_info.password[temp] = 0;
}
mqttClient->connect_info.keepalive = keepAliveTime; // 保持连接 = 120s
mqttClient->connect_info.clean_session = cleanSession; // 清除会话 = 1 = clean_session
//--------------------------------------------------------------------------------------------------------------
// 设置mqtt_state部分参数
//------------------------------------------------------------------------------------------------------------------------------------------------------------
mqttClient->mqtt_state.in_buffer = (uint8_t *)os_zalloc(MQTT_BUF_SIZE); // 申请in_buffer内存【入站报文缓存区】
mqttClient->mqtt_state.in_buffer_length = MQTT_BUF_SIZE; // 设置in_buffer大小
mqttClient->mqtt_state.out_buffer = (uint8_t *)os_zalloc(MQTT_BUF_SIZE); // 申请out_buffer内存【出站报文缓存区】
mqttClient->mqtt_state.out_buffer_length = MQTT_BUF_SIZE; // 设置out_buffer大小
mqttClient->mqtt_state.connect_info = &(mqttClient->connect_info); // MQTT【CONNECT】报文的连接参数(指针),赋值给mqttClient->mqtt_state.connect_info
// 初始化MQTT出站报文缓存区
//----------------------------------------------------------------------------------------------------------------------------------
mqtt_msg_init(&mqttClient->mqtt_state.mqtt_connection, mqttClient->mqtt_state.out_buffer, mqttClient->mqtt_state.out_buffer_length);
QUEUE_Init(&mqttClient->msgQueue, QUEUE_BUFFER_SIZE); // 消息队列初始化【队列可以存放一个/多个MQTT报文】
// 创建任务:任务函数【MQTT_Task】、优先级【2】、任务指针【mqtt_procTaskQueue】、消息深度【1】
//---------------------------------------------------------------------------------------------
system_os_task(MQTT_Task, MQTT_TASK_PRIO, mqtt_procTaskQueue, MQTT_TASK_QUEUE_SIZE);
// 安排任务:参数1=任务等级 / 参数2=消息类型 / 参数3=消息参数
//-----------------------------------------------------------------------------------------------
system_os_post(MQTT_TASK_PRIO, 0, (os_param_t)mqttClient); // 参数3的类型必须为【os_param_t】型
}
//======================================================================================================================================================
// 设置遗嘱:遗嘱主题【...】、遗嘱消息【...】、遗嘱质量【Will_Qos=0】、遗嘱保持【Will_Retain=0】
//====================================================================================================================
void ICACHE_FLASH_ATTR
MQTT_InitLWT(MQTT_Client *mqttClient, uint8_t* will_topic, uint8_t* will_msg, uint8_t will_qos, uint8_t will_retain)
{
uint32_t temp;
temp = os_strlen(will_topic);
mqttClient->connect_info.will_topic = (uint8_t*)os_zalloc(temp + 1); // 申请【遗嘱主题】的存放内存
os_strcpy(mqttClient->connect_info.will_topic, will_topic); // 赋值【遗嘱主题】
mqttClient->connect_info.will_topic[temp] = 0; // 最后'\0'
temp = os_strlen(will_msg);
mqttClient->connect_info.will_message = (uint8_t*)os_zalloc(temp + 1);
os_strcpy(mqttClient->connect_info.will_message, will_msg); // 赋值【遗嘱消息】
mqttClient->connect_info.will_message[temp] = 0;
mqttClient->connect_info.will_qos = will_qos; // 遗嘱质量【Will_Qos=0】
mqttClient->connect_info.will_retain = will_retain; // 遗嘱保持【Will_Retain=0】
}
//====================================================================================================================
/**
* @brief Begin connect to MQTT broker
* @param client: MQTT_Client reference
* @retval None
*/
// WIFI连接、SNTP成功后 => MQTT连接准备(设置TCP连接、解析域名)
//============================================================================================================================================
void ICACHE_FLASH_ATTR MQTT_Connect(MQTT_Client *mqttClient)
{
//espconn_secure_set_size(0x01,6*1024); // SSL双向认证时才需使用 // try to modify memory size 6*1024 if ssl/tls handshake failed
// 开始MQTT连接前,判断是否存在MQTT的TCP连接。如果有,则清除之前的TCP连接
//------------------------------------------------------------------------------------
if (mqttClient->pCon)
{
// Clean up the old connection forcefully - using MQTT_Disconnect
// does not actually release the old connection until the
// disconnection callback is invoked.
mqtt_tcpclient_delete(mqttClient); // 删除TCP连接、释放pCon内存、清除TCP连接指针
}
// TCP连接设置
//------------------------------------------------------------------------------------------------------
mqttClient->pCon = (struct espconn *)os_zalloc(sizeof(struct espconn)); // 申请pCon内存
mqttClient->pCon->type = ESPCONN_TCP; // 设为TCP连接
mqttClient->pCon->state = ESPCONN_NONE;
mqttClient->pCon->proto.tcp = (esp_tcp *)os_zalloc(sizeof(esp_tcp)); // 申请esp_tcp内存
mqttClient->pCon->proto.tcp->local_port = espconn_port(); // 获取ESP8266可用端口
mqttClient->pCon->proto.tcp->remote_port = mqttClient->port; // 设置端口号
mqttClient->pCon->reverse = mqttClient; // mqttClient->pCon->reverse 缓存 mqttClient指针
espconn_regist_connectcb(mqttClient->pCon, mqtt_tcpclient_connect_cb); // 注册TCP连接成功的回调函数
espconn_regist_reconcb(mqttClient->pCon, mqtt_tcpclient_recon_cb); // 注册TCP异常中断的回调函数
//---------------------------------------------------------------------------------------------------
mqttClient->keepAliveTick = 0; // MQTT客户端(ESP8266)心跳计数
mqttClient->reconnectTick = 0; // 重连等待计时:当进入重连请求状态后,需等待5秒,之后进行重新连接
// 设置MQTT定时(1秒)【功能:心跳计时、重连计时、TCP发送计时】
//---------------------------------------------------------------------------------------------------
os_timer_disarm(&mqttClient->mqttTimer);
os_timer_setfn(&mqttClient->mqttTimer, (os_timer_func_t *)mqtt_timer, mqttClient); // mqtt_timer
os_timer_arm(&mqttClient->mqttTimer, 1000, 1); // 1秒定时(重复)
// 打印SSL配置:安全类型[NO_TLS == 0]
//--------------------------------------------------------------------------------------------------------------------------------------------------------------
os_printf("your ESP SSL/TLS configuration is %d.[0:NO_TLS\t1:TLS_WITHOUT_AUTHENTICATION\t2ONE_WAY_ANTHENTICATION\t3TWO_WAY_ANTHENTICATION]\n",DEFAULT_SECURITY);
// 解析点分十进制形式的IP地址
//------------------------------------------------------------------------------------------------------------------
if (UTILS_StrToIP(mqttClient->host, &mqttClient->pCon->proto.tcp->remote_ip)) // 解析IP地址(点分十进制字符串形式)
{
INFO("TCP: Connect to ip %s:%d\r\n", mqttClient->host, mqttClient->port); // 打印IP地址
// 根据安全类型,调用不同的TCP连接方式
//-------------------------------------------------------------------------------------------------
if (mqttClient->security) // 安全类型 != 0
{
#ifdef MQTT_SSL_ENABLE
if(DEFAULT_SECURITY >= ONE_WAY_ANTHENTICATION ) // 单向认证【ONE_WAY_ANTHENTICATION = 2】
{
espconn_secure_ca_enable(ESPCONN_CLIENT,CA_CERT_FLASH_ADDRESS);
}
if(DEFAULT_SECURITY >= TWO_WAY_ANTHENTICATION) // 双向认证【TWO_WAY_ANTHENTICATION = 3】
{
espconn_secure_cert_req_enable(ESPCONN_CLIENT,CLIENT_CERT_FLASH_ADDRESS);
}
espconn_secure_connect(mqttClient->pCon); // 不认证【TLS_WITHOUT_AUTHENTICATION = 1】
#else
INFO("TCP: Do not support SSL\r\n");
#endif
}
else // 安全类型 = 0 = NO_TLS
{
espconn_connect(mqttClient->pCon); // TCP连接(作为Client连接Server)
}
}
// 解析域名
//----------------------------------------------------------------------------------------------
else
{
INFO("TCP: Connect to domain %s:%d\r\n", mqttClient->host, mqttClient->port);
espconn_gethostbyname(mqttClient->pCon, mqttClient->host, &mqttClient->ip, mqtt_dns_found);
}
mqttClient->connState = TCP_CONNECTING; // TCP正在连接
}
//============================================================================================================================================
// TCP断开连接(Clean up the old connection forcefully)
//==============================================================================
void ICACHE_FLASH_ATTR MQTT_Disconnect(MQTT_Client *mqttClient)
{
mqttClient->connState = TCP_DISCONNECTING; // ESP8266状态改变:TCP正在断开
system_os_post(MQTT_TASK_PRIO, 0, (os_param_t)mqttClient); // 安排任务
os_timer_disarm(&mqttClient->mqttTimer); // 取消MQTT定时
}
//==============================================================================
// 删除MQTT客户端
//==============================================================================
void ICACHE_FLASH_ATTR MQTT_DeleteClient(MQTT_Client *mqttClient)
{
mqttClient->connState = MQTT_DELETING; // ESP8266状态改变:MQTT正在删除
system_os_post(MQTT_TASK_PRIO, 0, (os_param_t)mqttClient); // 安排任务
os_timer_disarm(&mqttClient->mqttTimer); // 取消MQTT定时
}
//==============================================================================
// 函数调用重定义
//………………………………………………………………………………………………………………………………………
// 执行 mqttClient->connectedCb(...) => mqttConnectedCb(...)
//------------------------------------------------------------------------------------------
void ICACHE_FLASH_ATTR MQTT_OnConnected(MQTT_Client*mqttClient, MqttCallback connectedCb)
{
mqttClient->connectedCb = connectedCb; // 函数名【mqttConnectedCb】
}
// 执行 mqttClient->disconnectedCb(...) => mqttDisconnectedCb(...)
//-------------------------------------------------------------------------------------------------
void ICACHE_FLASH_ATTR MQTT_OnDisconnected(MQTT_Client *mqttClient, MqttCallback disconnectedCb)
{
mqttClient->disconnectedCb = disconnectedCb; // 函数名【mqttDisconnectedCb】
}
// 执行 mqttClient->dataCb(...) => mqttDataCb(...)
//------------------------------------------------------------------------------------
void ICACHE_FLASH_ATTR MQTT_OnData(MQTT_Client *mqttClient, MqttDataCallback dataCb)
{
mqttClient->dataCb = dataCb; // 函数名【mqttDataCb】
}
// 执行 mqttClient->publishedCb(...) => mqttPublishedCb(...)
//-------------------------------------------------------------------------------------------
void ICACHE_FLASH_ATTR MQTT_OnPublished(MQTT_Client *mqttClient, MqttCallback publishedCb)
{
mqttClient->publishedCb = publishedCb; // 函数名【mqttPublishedCb】
}
// 执行 mqttClient->timeoutCb(...) => 【...】未定义函数
//--------------------------------------------------------------------------------------
void ICACHE_FLASH_ATTR MQTT_OnTimeout(MQTT_Client *mqttClient, MqttCallback timeoutCb)
{
mqttClient->timeoutCb = timeoutCb;
}
//………………………………………………………………………………………………………………………………………
user_main.c函数,相当于单片机的主函数,
void mqttConnectedCb(uint32_t *args)这个函数进行主题的订阅,自行照着修改即可,
void mqttDataCb(uint32_t args, const char topic, uint32_t topic_len, const char *data, uint32_t data_len)这个函数是收到主题发布的消息后的回调函数,比如手机向switch主题发布“ON”,在esp8266订阅switch主题的前提下收到“ON”之后执行的函数,例如
if( os_strcmp(topicBuf,"first_floor_switch") == 0 ) // 主题 == "frist_floor_switch"
{
if( os_strcmp(dataBuf,"SWITCH_ON") == 0 ) // 有效载荷 == "SWITCH_ON"
{
GPIO_OUTPUT_SET(GPIO_ID_PIN(0),0); // switch开
}
}
这段代码是在void mqttDataCb(uint32_t args, const char topic, uint32_t topic_len, const char *data, uint32_t data_len)这个函数里的判断语句,如果收到主题为 "frist_floor_switch"的"SWITCH_ON"消息就使GPIO0输出低电平,从而控制继电器开电操作。
void user_init(void)这个函数相当于单片机的主函数,这段代码首先是进行串口的初始化,以及GPIO的初始化,我初始化的是GPIO0为输出控制继电器的IO口,GPIO15为检测是否有电的IO口。
void user_init(void)
{
uart_init(BIT_RATE_115200, BIT_RATE_115200); // 串口波特率设为115200
os_delay_us(60000);
// 初始化(注意【PIN_NAME】、【FUNC】、【gpio_no】不要混淆)
//-------------------------------------------------------------------------
PIN_FUNC_SELECT(PERIPHS_IO_MUX_GPIO0_U,FUNC_GPIO0); // GPIO0输出高 #
GPIO_OUTPUT_SET(GPIO_ID_PIN(0),1); // GPIO_0 = 1
PIN_FUNC_SELECT(PERIPHS_IO_MUX_MTDO_U, FUNC_GPIO15); // GPIO_15设为IO口
GPIO_DIS_OUTPUT(GPIO_ID_PIN(15)); // GPIO_15失能输出(默认)
PIN_PULLUP_DIS(PERIPHS_IO_MUX_MTDO_U); // GPIO_15失能内部上拉(默认)
// PIN_PULLUP_DIS(PERIPHS_IO_MUX_MTDI_U);
// PIN_PULLUP_EN(PERIPHS_IO_MUX_GPIO0_U); // GPIO_0使能内部上拉
CFG_Load(); // 加载/更新系统参数【WIFI参数、MQTT参数】
// 网络连接参数赋值:服务端域名【mqtt_test_jx.mqtt.iot.gz.baidubce.com】、网络连接端口【1883】、安全类型【0:NO_TLS】
//-------------------------------------------------------------------------------------------------------------------
MQTT_InitConnection(&mqttClient, sysCfg.mqtt_host, sysCfg.mqtt_port, sysCfg.security);
// MQTT连接参数赋值:客户端标识符【..】、MQTT用户名【..】、MQTT密钥【..】、保持连接时长【120s】、清除会话【1:clean_session】
//----------------------------------------------------------------------------------------------------------------------------
MQTT_InitClient(&mqttClient, sysCfg.device_id, sysCfg.mqtt_user, sysCfg.mqtt_pass, sysCfg.mqtt_keepalive, 1);
// 设置遗嘱参数(如果云端没有对应的遗嘱主题,则MQTT连接会被拒绝)
//--------------------------------------------------------------
// MQTT_InitLWT(&mqttClient, "Will", "ESP8266_offline", 0, 0);
// 设置MQTT相关函数
//--------------------------------------------------------------------------------------------------
MQTT_OnConnected(&mqttClient, mqttConnectedCb); // 设置【MQTT成功连接】函数的另一种调用方式
MQTT_OnDisconnected(&mqttClient, mqttDisconnectedCb); // 设置【MQTT成功断开】函数的另一种调用方式
MQTT_OnPublished(&mqttClient, mqttPublishedCb); // 设置【MQTT成功发布】函数的另一种调用方式
MQTT_OnData(&mqttClient, mqttDataCb); // 设置【接收MQTT数据】函数的另一种调用方式
// 连接WIFI:SSID[..]、PASSWORD[..]、WIFI连接成功函数[wifiConnectCb]
//--------------------------------------------------------------------------
WIFI_Connect(sysCfg.sta_ssid, sysCfg.sta_pwd, wifiConnectCb);
INFO("\r\nSystem started ...\r\n");
}
烧录进esp8266后进行复位重启,可利用串口调试助手查看esp8266是否接入百度云。
手机app代码
手机的APP我则是利用简单易懂的易安卓e4a语言开发的一款小app,不得不说易安卓简单,易上手,适合轻量级开发,中文编程支持一下,当然新语言还有很不足,不建议大家细学。
大家在安装e4a软件之后,直接打开中级例程里的mqtt通信,
这是例程源码,发送主题以及内容自行修改即可。
事件 按钮1.被单击()
mqtt通讯1.连接服务器(编辑框1.内容,编辑框3.内容,编辑框4.内容,mqtt通讯1.取IMEI码(),真,假,5)
结束 事件
事件 按钮3.被单击()
mqtt通讯1.发送消息("first_floor_switch",文本到字节("msg","UTF-8"),0,真)
结束 事件
事件 按钮4.被单击()
mqtt通讯1.订阅消息("first_floor_switch",0)
结束 事件
事件 按钮5.被单击()
mqtt通讯1.断开连接()
结束 事件
事件 mqtt通讯1.连接成功()
弹出提示("连接成功")
结束 事件
事件 mqtt通讯1.连接失败()
弹出提示("连接失败")
结束 事件
事件 mqtt通讯1.连接断开()
弹出提示("连接断开")
结束 事件
事件 mqtt通讯1.订阅成功()
弹出提示("订阅成功")
结束 事件
事件 mqtt通讯1.订阅失败()
弹出提示("订阅失败")
结束 事件
事件 mqtt通讯1.发送完毕()
弹出提示("发送完毕")
结束 事件
事件 mqtt通讯1.收到消息(消息主题 为 文本型, 消息内容 为 字节型(), 消息策略 为 整数型)
编辑框2.内容 = "主题:" & 消息主题 & "\n内容:" & 字节到文本(消息内容,"UTF-8") & "\n策略:" & 消息策略
结束 事件
例程的app界面
进行编译之后可运行在模拟器上安装,界面如图,第一行填地址,第二行填用户名,第三行填密钥(我也不知道为什么,一开始我就连接不上,后面就一直能连接了)。
输入完后点连接,连接成功的话会有消息提醒。
再进行订阅,最下面会显示订阅的主题以及发送内容,点击发送消息成功后就会弹出提醒,而esp8266则根据主题的消息内容进行判断进而控制继电器控制断电开电。
将所有模块放入插板后,就完成了手机控制的智能插座。
这里我进行了对APP的一些美化,大概这样,大家可根据自己喜好更改完善,中文编程很简单。
右下角是按钮,这是检测为有电的时候。
按下右下角按钮后即可断电,手机拿来拍照,手机上也一样,这里用的是模拟器。