实现物联网远程获取家里的数据难吗?本文告诉你不难!!!
简述MQTT
MQTT(Message Queuing Telemetry Transport,消息队列遥测传输协议),是一种基于发布/订阅(publish/subscribe)模式的"轻量级"通讯协议,该协议构建于TCP/IP协议上,由IBM在1999年发布。MQTT最大优点在于,可以以极少的代码和有限的带宽,为连接远程设备提供实时可靠的消息服务。作为一种低开销、低带宽占用的即时通讯协议,使其在物联网、小型设备、移动应用等方面有较广泛的应用。
MQTT是一个基于客户端-服务器的消息发布/订阅传输协议。MQTT协议是轻量、简单、开放和易于实现的,这些特点使它适用范围非常广泛。在很多情况下,包括受限的环境中,如:机器与机器(M2M)通信和物联网(IoT)。其在,通过卫星链路通信传感器、偶尔拨号的医疗设备、智能家居、及一些小型化设备中已广泛使用。(摘自互联网)
对MQTT协议不是很了解的可以参考菜鸟教程,网上也有很多大佬讲得很好,可以去了解一下,这里就默认大家对MQTT有一定的了解哈;
在8266中实现
在8266-RTOS-SDK中有提供两种使用MQTT的API接口,有应该是乐鑫自己封装好的一套,也有IBM paho的MQTT接口,初学者使用乐鑫提供的可能更好上手,所以本文基于乐鑫的来讲解和使用,paho提供的后续再补发一章。
首先需要在 make menuconfig 下修改MQTT的配置:
点击 component config
选中 MQTT
选择需要使用的组件接口,这里选 ESP-MQTT
然后保存退出即可,如果该配置没有配置好,编译代码的时候会报错,会提示某些头文件找不到。
使用
头文件
#include "mqtt_client.h" //ESP-MQTT
也是挑几个用到的函数来讲吧,想了解更多的自行去查阅源码;
MQTT客户端配置结构,重要的有进行注释,不太需要过于关注的就没有中文注释
typedef struct {
mqtt_event_callback_t event_handle; /*!< 针对MQTT事件的回调函数 */
const char *host; /*!< MQTT服务端的IP地址 */
const char *uri; /*!< 完成MQTT代理URI */
uint32_t port; /*!< MQTT服务端端口 */
const char *client_id; /*!< MQTT客户端的ID,可以不写,有默认的 */
const char *username; /*!< MQTT账号(接入平台的话就不是简单的账号名了) */
const char *password; /*!< MQTT密码(接入平台的话就不是简单的密码了) */
const char *lwt_topic; /*!< LWT 临终遗嘱啥的 message topic (NULL by default) */
const char *lwt_msg; /*!< LWT message (NULL by default) */
int lwt_qos; /*!< LWT message qos */
int lwt_retain; /*!< LWT retained message flag */
int lwt_msg_len; /*!< LWT message length */
int disable_clean_session; /*!< mqtt clean session, default clean_session is true */
int keepalive; /*!< mqtt keepalive, default is 120 seconds */
bool disable_auto_reconnect; /*!< this mqtt client will reconnect to server (when errors/disconnect). Set disable_auto_reconnect=true to disable */
void *user_context; /*!< pass user context to this option, then can receive that context in ``event->user_context`` */
int task_prio; /*!< MQTT task priority, default is 5, can be changed in ``make menuconfig`` */
int task_stack; /*!< MQTT task stack size, default is 6144 bytes, can be changed in ``make menuconfig`` */
int buffer_size; /*!< size of MQTT send/receive buffer, default is 1024 */
const char *cert_pem; /*!< Pointer to certificate data in PEM format for server verify (with SSL), default is NULL, not required to verify the server */
const char *client_cert_pem; /*!< Pointer to certificate data in PEM format for SSL mutual authentication, default is NULL, not required if mutual authentication is not needed. If it is not NULL, also `client_key_pem` has to be provided. */
const char *client_key_pem; /*!< Pointer to private key data in PEM format for SSL mutual authentication, default is NULL, not required if mutual authentication is not needed. If it is not NULL, also `client_cert_pem` has to be provided. */
esp_mqtt_transport_t transport; /*!< overrides URI transport */
} esp_mqtt_client_config_t;
MQTT客户端的所有配置信息都在该结构体中进行初始化配置;
MQTT初始化函数
esp_mqtt_client_handle_t esp_mqtt_client_init(const esp_mqtt_client_config_t *config)
参数 | 解析 |
---|---|
config | MQTT客户端配置结构 |
返回值:为初始化好的MQTT客户端的句柄,可以通过这个句柄获取MQTT客户端的所有动作
MQTT客户端启动函数
esp_err_t esp_mqtt_client_start(esp_mqtt_client_handle_t client)
参数 | 解析 |
---|---|
client | MQTT客户端句柄 |
配置好所有基本信息并初始化后调用该函数即可完成启动;
MQTT客户端发布消息
int esp_mqtt_client_publish(esp_mqtt_client_handle_t client, const char *topic, const char *data, int len, int qos, int retain)
参数 | 解析 |
---|---|
client | MQTT客户端句柄 |
topic | 要发布的topic |
data | 要发布的内容 |
len | 内容长度,不确定长度可以填0,函数内部会自动调用strlen进行测量 |
qos | MQTT QOS等级 |
retain | 这个我也不清楚是啥,不好乱猜,填0就好 |
返回:返回的是消息的ID
订阅消息
int esp_mqtt_client_subscribe(esp_mqtt_client_handle_t client, const char *topic, int qos)
参数 | 解析 |
---|---|
client | MQTT客户端句柄 |
topic | 要订阅的topic |
qos | MQTT QOS等级 |
返回:订阅成功的消息ID
取消订阅
esp_err_t esp_mqtt_client_unsubscribe(esp_mqtt_client_handle_t client, const char *topic);
参数 | 解析 |
---|---|
client | MQTT客户端句柄 |
topic | 要订阅的topic |
销毁MQTT客户端
esp_err_t esp_mqtt_client_destroy(esp_mqtt_client_handle_t client);
参数:传入MQTT客户端句柄即可释放资源;
具体代码实现
测试历程实现:
- 实现连接MQTT代理器
- 按键发布消息
- 实时监控连接状态,通过OLED显示
MQTT的初始化配置及开启
static void mqtt_app_start(void)
{
mqtt_event_group = xEventGroupCreate();
esp_mqtt_client_config_t mqtt_cfg = {
.host = "192.168.1.20", //MQTT服务器的IP
.port = 1883, //端口
.username = "esp8266", //接入名
.password = "123456", //密码
.event_handle = mqtt_event_handler,
};
client = esp_mqtt_client_init(&mqtt_cfg);
esp_mqtt_client_start(client);
}
esp_mqtt_client_config_t mqtt_cfg 按需配置即可,不用所有的都要自己写
使用按键发送数据
这里用的是轻触按键,每触碰一次就会通过 topic:/esp8266/post 往MQTT服务器发布一条 “hello” 信息
static void key_init(void)
{
gpio_config_t io_conf;
io_conf.intr_type = GPIO_INTR_DISABLE; //失能中断
io_conf.mode = GPIO_MODE_INPUT; //输入模式
io_conf.pin_bit_mask = SEND_KEY_PIN_SEL;//指定GPIO14作为按键引脚
io_conf.pull_down_en = 1; //下拉
io_conf.pull_up_en = 0;
gpio_config(&io_conf);
}
static void send_hello(void *arg)
{
key_init(); //按键初始化
while(1)
{
if(gpio_get_level(SEND_KEY_PIN) && MQTT_ONLINE == 1){
esp_mqtt_client_publish(client, "/esp8266/post", "hello", 0, 1, 0);
}
vTaskDelay(50 / portTICK_RATE_MS); //给个短延时,避免看门狗超时复位
}
}
MQTT事件回调函数
//MQTT事件回调函数
static esp_err_t mqtt_event_handler(esp_mqtt_event_handle_t event)
{
esp_mqtt_client_handle_t client = event->client;
memset(MQTT_STATUS_BUFF, 0 ,sizeof(MQTT_STATUS_BUFF));
switch (event->event_id) { //监听MQTT事件
case MQTT_EVENT_CONNECTED: //成功连接事件
ESP_LOGI(TAG, "MQTT_EVENT_CONNECTED");
MQTT_ONLINE = 1;
esp_mqtt_client_subscribe(client, "/esp8266/set", 0); //默认订阅
strcpy(MQTT_STATUS_BUFF, "MQTT CONNECTED");
break;
case MQTT_EVENT_DISCONNECTED: //失去连接
ESP_LOGI(TAG, "MQTT_EVENT_DISCONNECTED");
MQTT_ONLINE = 0;
strcpy(MQTT_STATUS_BUFF, "MQTT DISCONNECTED");
break;
case MQTT_EVENT_SUBSCRIBED: //成功订阅
strcpy(MQTT_STATUS_BUFF, "MQTT SUBSCRIBED");
break;
case MQTT_EVENT_UNSUBSCRIBED: //取消订阅
strcpy(MQTT_STATUS_BUFF, "MQTT UNSUBSCRIBED");
break;
case MQTT_EVENT_PUBLISHED: //成功发布
strcpy(MQTT_STATUS_BUFF, "MQTT PUBLISHED");
break;
case MQTT_EVENT_DATA: //接收到数据
ESP_LOGI(TAG, "MQTT_EVENT_DATA");
printf("TOPIC=%.*s\r\n", event->topic_len, event->topic);
printf("DATA=%.*s\r\n", event->data_len, event->data);
strcpy(MQTT_STATUS_BUFF, "MQTT RECEIVE");
break;
case MQTT_EVENT_ERROR: //错误事件
ESP_LOGI(TAG, "MQTT_EVENT_ERROR");
strcpy(MQTT_STATUS_BUFF, "MQTT ERROR");
break;
}
xEventGroupSetBits(mqtt_event_group, MQTT_STATUS_BIT);
return ESP_OK;
}
实物展示:
配合测试的还有MQTT小工具MQTT.fx,两者同时连上我树莓派里的MQTT代理器
ESP8266,OLED显示的是 MQTT ONLINE,这里使用的是轻触按键,具体电路接线看源码
触发开关后,向MQTT代理器推送消息,MQTT.fx会订阅8266发布的topic
MQTT.fx接收:
做到这一步,想想如果我们的MQTT代理器是在云端,然后8266采集的数据是室内传感器采集的数据的话,我们是不是实现了远程监控的功能,前面的铺垫就都有了用武之地了!
以上只是部分代码的解析,具体完整代码见我的GitHub,如有帮助请点个赞,谢谢~