当前位置: 首页 > news >正文

零基础入门学用物联网(ESP8266) 第二部分 MQTT基础篇(五)

参考教程:https://www.bilibili.com/video/BV1L7411c7jw/?spm_id_from=333.1387.favlist.content.click

十二、MQTT遗嘱

1、意外掉线与遗嘱公布

(1)心跳机制可以让MQTT服务端随时掌握MQTT客户端连接情况,当检测到心跳停止,MQTT服务端就会判定MQTT客户端意外断线(相当于意外死亡),如果在意外断线前MQTT客户端曾在MQTT服务端“立遗嘱”,那么MQTT服务端就会把意外断线客户端的遗嘱公布给订阅了该客户端遗嘱消息的其它客户端。

(2)当MQTT客户端正常断开连接时,会向MQTT服务端发送DISCONNECT报文,MQTT服务端接收到该报文后,就知道MQTT客户端是正常断开连接,而并非意外断开连接,也就不会公布该客户端的遗嘱消息了。

(3)MQTT服务端识别MQTT客户端意外断开连接的情况:

①在MQTT客户端与MQTT服务端之间的运输层连接(比如TCP连接)未断开的时候,超过1.5倍心跳时间间隔,MQTT服务端未收到MQTT客户端发布任何消息,也没收到MQTT客户端发送的心跳请求,则判定MQTT客户端意外断开连接。

②在MQTT服务端收到DISCONNECT报文前,MQTT客户端与MQTT服务端之间的运输层连接(比如TCP连接)断开,MQTT服务端将感知到运输层连接被异常关闭,同样会判定MQTT客户端意外断开连接。(比如直接关闭电脑端的MQTTfx进程,而不先断开与MQTT客户端的连接,就会导致未发送DISCONNECT报文即断开TCP连接)

2、客户端将遗嘱消息发送给服务端

(1)MQTT客户端在向MQTT服务端发送CONNECT报文时,就可以在报文中添加遗嘱信息配置。

(2)CONNECT报文中遗嘱信息的相关配置项:

①lastWillTopic ——遗嘱主题:

遗嘱消息和普通的MQTT消息很相似,也有主题和正文内容,lastWillTopic的作用正是告知MQTT服务端,本MQTT客户端的遗嘱主题是什么,只有那些订阅了这一遗嘱主题的MQTT客户端才会收到本MQTT客户端的遗嘱消息

②lastWillMessage——遗嘱消息:

lastWillTopic定义了遗嘱消息内容,也就是遗嘱正文

③lastWillQoS——遗嘱QoS:

对于遗嘱消息来说,同样可以使用服务质量来控制遗嘱消息的传递和接收,这里的服务质量与普通MQTT消息的服务质量是一样的概念,也可以设置为0、1、2

④lastWillRetain——遗嘱保留:

置为True,当MQTT客户端异常断线且仍未重连时,MQTT服务端会将遗嘱消息保存,只要有其它MQTT客户端订阅该主题,MQTT服务端马上将该主题信息发给它;置为False,当MQTT客户端异常断线且仍未重连时,MQTT服务端不会将遗嘱消息保存,只会在该客户端掉线的时候把其遗嘱消息公布给当时订阅了该客户端遗嘱消息的其它客户端,然后丢弃遗嘱消息

(3)MQTTfx中,可以在MQTT连接详情页的“LWT”选项卡下设置电脑MQTT客户端遗嘱消息。

3、ESP8266 MQTT遗嘱基本应用

(1)先前已经介绍了PubSubClient类connect函数的其中两种形式,实际上该函数还有第三种形式,函数参数依次为客户端ID、遗嘱主题、遗嘱QoS、遗嘱保留、遗嘱信息。

(2)PubSubClient类提供了成员函数setKeepAlive,该函数的函数参数为心跳时间间隔,调用它可以设置与MQTT服务器连接的心跳时间间隔。

(3)ESP8266 MQTT遗嘱基本应用示例代码:

#include <ESP8266WiFi.h> #include <PubSubClient.h> const char* ssid = "Zevalin_Computer"; const char* password = "00114514"; const char* mqttServer = "test.mosquitto.org"; //如无法使用,可更换为其它公用MQTT服务器地址 WiFiClient wifiClient; PubSubClient mqttClient(wifiClient); // 遗嘱设置 const char* willMsg = "CLIENT-OFFLINE"; // 遗嘱消息内容 const int willQoS = 0; // 遗嘱QoS const bool willRetain = false; // 遗嘱保留 void setup() { Serial.begin(9600); WiFi.mode(WIFI_STA); connectWifi(); mqttClient.setServer(mqttServer, 1883); // 设置MQTT服务器和端口号 mqttClient.setKeepAlive(10); // 设置心跳时间间隔 connectMQTTserver(); // 连接MQTT服务器 } void loop() { // 如果开发板未能成功连接服务器,则尝试连接服务器 if (!mqttClient.connected()) { connectMQTTserver(); } // 处理信息以及心跳 mqttClient.loop(); } // 连接MQTT服务器并订阅信息 void connectMQTTserver(){ // 根据ESP8266的MAC地址生成客户端ID(避免与其它ESP8266的客户端ID重名) String clientId = "esp8266-" + WiFi.macAddress(); // 建立遗嘱主题,主题名称以Taichi-Maker-为前缀,后面添加设备的MAC地址,最后以“-Will”结尾,这是为确保不同ESP8266客户端的遗嘱主题名称各不相同 String willString = "Taichi-Maker-" + WiFi.macAddress() + "-Will"; char willTopic[willString.length() + 1]; strcpy(willTopic, willString.c_str()); // 连接MQTT服务器,在连接过程中提供以下参数:客户端ID、遗嘱主题、遗嘱QoS、遗嘱保留、遗嘱信息 if (mqttClient.connect(clientId.c_str(), willTopic, willQoS, willRetain, willMsg)){ Serial.println("MQTT Server Connected."); Serial.print("Server Address: ");Serial.println(mqttServer); Serial.print("ClientId: ");Serial.println(clientId); Serial.print("Will Topic: ");Serial.println(willTopic); } else{ Serial.print("MQTT Server Connect Failed. Client State:"); Serial.println(mqttClient.state()); delay(5000); } } void connectWifi(){ WiFi.begin(ssid, password); while (WiFi.status() != WL_CONNECTED) { delay(1000); Serial.print("."); } Serial.println(""); Serial.println("WiFi Connected!"); Serial.println(""); }

4、利用MQTT遗嘱实现设备在线状态发布

(1)方案策略说明:

①假设现在有一台MQTT客户端,它的客户端ID是“client”,它的遗嘱主题是“client-will”。

②当client连接服务端时,CONNECT报文中的遗嘱消息是“offline”,并且它的遗嘱保留设置为true。

③当client成功连接服务端后,立即向遗嘱主题“client-will”发布消息“online”,同时在发布此消息时,保留标志设置为true,这样,只要client在线,那么任何设备一订阅(或者已订阅)“client-will”就能收到设备在线的消息“online”。

④如果client发生意外离线,那么任何设备一订阅(或者已订阅)“client-will”就会收到设备离线的消息“offline”。

⑤如果client-1恢复连接,那么它会将遗嘱主题“client-will”的保留消息更改为“online”,这样任何设备一订阅(或者已订阅)“client-will”就能收到设备在线的消息“online”。(相当于跳回②,重复执行②③④)

(2)ESP8266利用MQTT遗嘱实现设备在线状态发布示例代码:

#include <ESP8266WiFi.h> #include <PubSubClient.h> const char* ssid = "Zevalin_Computer"; const char* password = "00114514"; const char* mqttServer = "test.mosquitto.org"; //如无法使用,可更换为其它公用MQTT服务器地址 WiFiClient wifiClient; PubSubClient mqttClient(wifiClient); // 遗嘱设置 const char* willMsg = "CLIENT-OFFLINE"; // 遗嘱消息内容 const int willQoS = 0; // 遗嘱QoS const bool willRetain = false; // 遗嘱保留 void setup() { Serial.begin(9600); WiFi.mode(WIFI_STA); connectWifi(); mqttClient.setServer(mqttServer, 1883); // 设置MQTT服务器和端口号 mqttClient.setKeepAlive(10); // 设置心跳时间间隔 connectMQTTserver(); // 连接MQTT服务器 } void loop() { // 如果开发板未能成功连接服务器,则尝试连接服务器 if (!mqttClient.connected()) { connectMQTTserver(); } // 处理信息以及心跳 mqttClient.loop(); } // 连接MQTT服务器并订阅信息 void connectMQTTserver(){ // 根据ESP8266的MAC地址生成客户端ID(避免与其它ESP8266的客户端ID重名) String clientId = "esp8266-" + WiFi.macAddress(); // 建立遗嘱主题,主题名称以Taichi-Maker-为前缀,后面添加设备的MAC地址,最后以“-Will”结尾,这是为确保不同ESP8266客户端的遗嘱主题名称各不相同 String willString = "Taichi-Maker-" + WiFi.macAddress() + "-Will"; char willTopic[willString.length() + 1]; strcpy(willTopic, willString.c_str()); // 连接MQTT服务器,在连接过程中提供以下参数:客户端ID、遗嘱主题、遗嘱QoS、遗嘱保留、遗嘱信息 if (mqttClient.connect(clientId.c_str(), willTopic, willQoS, willRetain, willMsg)){ Serial.println("MQTT Server Connected."); Serial.print("Server Address: ");Serial.println(mqttServer); Serial.print("ClientId: ");Serial.println(clientId); Serial.print("Will Topic: ");Serial.println(willTopic); publishOnlineStatus(); //发布在线状态 } else{ Serial.print("MQTT Server Connect Failed. Client State:"); Serial.println(mqttClient.state()); delay(5000); } } void publishOnlineStatus(){ // 建立遗嘱主题,主题名称以Taichi-Maker-为前缀,后面添加设备的MAC地址,最后以“-Will”结尾,这是为确保不同ESP8266客户端的遗嘱主题名称各不相同 String willString = "Taichi-Maker-" + WiFi.macAddress() + "-Will"; char willTopic[willString.length() + 1]; strcpy(willTopic, willString.c_str()); // 建立设备在线的消息,此信息将以保留形式向遗嘱主题发布 String onlineMessageString = "CLIENT-ONLINE"; char onlineMsg[onlineMessageString.length() + 1]; strcpy(onlineMsg, onlineMessageString.c_str()); // 向遗嘱主题发布设备在线消息 if(mqttClient.publish(willTopic, onlineMsg, true)){ Serial.print("Published Online Message: ");Serial.println(onlineMsg); } else{ Serial.println("Online Message Publish Failed."); } } void connectWifi(){ WiFi.begin(ssid, password); while (WiFi.status() != WL_CONNECTED) { delay(1000); Serial.print("."); } Serial.println(""); Serial.println("WiFi Connected!"); Serial.println(""); }

十三、MQTT用户密码认证

1、用户名与密码

(1)有些MQTT服务端需要MQTT客户端在连接时提供用户名和密码,只有MQTT客户端正确提供了用户名和密码后,才能连接MQTT服务端,否则MQTT服务端将会拒绝MQTT客户端连接,那么MQTT客户端也就无法发布和订阅消息了。

(2)MQTT客户端可以将用户名(username)和密码(password)通过CONNECT报文发送给MQTT服务端。需要注意的是,用户名和密码是可选信息),也就是说,有些MQTT服务端开启了客户端用户密码认证,这种MQTT服务端需要MQTT客户端在连接时正确提供认证信息才能连接,当然,那些没有开启用户密码认证的MQTT服务端则无需客户端提供用户名和密码认证信息。

(3)用户名和密码除了有以上功能外,有些公用MQTT服务端也利用此信息来识别MQTT客户端属于哪一个用户,从而对MQTT客户端进行管理,比如用户可以拥有私人主题,这些主题只有该用户可以发布和订阅,对于私人主题,MQTT服务端可以利用MQTT客户端连接时的用户名和密码来判断该客户端是否有发布或订阅该用户私人主题的权限。

(4)MQTTfx中,可以在MQTT连接详情页的“User Credentials”选项卡下设置MQTT连接的用户名和密码(如无需配置则空着即可)。

2、ESP8266进行用户密码认证

(1)先前已经介绍了PubSubClient类connect函数的其中三种形式,实际上该函数还有第四种形式,函数参数依次为客户端ID、用户名和密码。

(2)ESP8266进行用户密码认证示例代码:

#include <ESP8266WiFi.h> #include <PubSubClient.h> #include <Ticker.h> // 设置Wi-Fi接入信息 const char* ssid = "Zevalin_Computer"; const char* password = "00114514"; const char* mqttServer = "test.mosquitto.org"; //如无法使用,可更换为其它公用MQTT服务端地址 // MQTT服务端连接用户名密码 const char* mqttUserName = "test-user"; const char* mqttPassword = "mosquitto-iot"; Ticker ticker; WiFiClient wifiClient; PubSubClient mqttClient(wifiClient); int count; // Ticker计数用变量 void setup() { Serial.begin(9600); WiFi.mode(WIFI_STA); //设置ESP8266工作模式为无线终端模式 connectWifi(); //连接Wi-Fi mqttClient.setServer(mqttServer, 1883); //设置MQTT服务端和端口号 connectMQTTServer(); //连接MQTT服务端 ticker.attach(1, tickerCount); //每隔1秒,count计数加1 } void loop() { if (mqttClient.connected()) { // 如果开发板成功连接服务器 // 每隔3秒钟发布一次信息 if (count >= 3){ pubMQTTmsg(); count = 0; } // 保持心跳 mqttClient.loop(); } else { // 如果开发板未能成功连接服务器 connectMQTTServer(); // 不断尝试连接服务器 } } void tickerCount(){ count++; } void connectMQTTServer(){ // 根据ESP8266的MAC地址生成客户端ID(避免与其它ESP8266的客户端ID重名) String clientId = "esp8266-" + WiFi.macAddress(); // 连接MQTT服务端,此处使用了程序首部定义的用户名和密码来实现MQTT服务端认证 if (mqttClient.connect(clientId.c_str(), mqttUserName, mqttPassword)) { Serial.println("MQTT Server Connected."); Serial.println("Server Address: "); Serial.println(mqttServer); Serial.println("ClientId:"); Serial.println(clientId); } else { Serial.print("MQTT Server Connect Failed. Client State:"); Serial.println(mqttClient.state()); delay(3000); } } void pubMQTTmsg(){ // 发布信息 static int value; // 客户端发布信息的内容之一 // 建立发布主题,主题名称以Taichi-Maker-为前缀,后面添加设备的MAC地址 // 这么做是为确保不同用户进行MQTT信息发布时,ESP8266客户端名称各不相同 String topicString = "Taichi-Maker-Pub-" + WiFi.macAddress(); char publishTopic[topicString.length() + 1]; //创建字符数组(末位为'/0') strcpy(publishTopic, topicString.c_str()); //字符串拷贝到字符数组中 // 建立发布信息,信息内容以Hello World为起始,后面添加发布次数 String messageString = "Hello World " + String(value++); char publishMsg[messageString.length() + 1]; //创建字符数组(末位为'/0') strcpy(publishMsg, messageString.c_str()); //字符串拷贝到字符数组中 // ESP8266向主题发布信息 if(mqttClient.publish(publishTopic, publishMsg)) { Serial.println("Publish Topic:");Serial.println(publishTopic); Serial.println("Publish message:");Serial.println(publishMsg); } else { Serial.println("Message Publish Failed."); } } void connectWifi(){ // ESP8266连接Wi-Fi WiFi.begin(ssid, password); //等待Wi-Fi连接,成功连接后输出成功信息 while (WiFi.status() != WL_CONNECTED) { delay(1000); Serial.print("."); } Serial.println(""); Serial.println("WiFi Connected!"); Serial.println(""); }
http://www.jsqmd.com/news/562400/

相关文章:

  • Ubuntu环境下CloudCompare点云处理实战指南
  • Agent-S实战指南:突破性智能体框架如何实现72.6%人类级计算机交互性能
  • Qwen1.5-1.8B GPTQ开发环境配置:IntelliJ IDEA插件开发初探
  • 基于STM32F103C8与CAN总线的步科步进电机PDO映射实战解析
  • GHelper深度解析:重新定义华硕笔记本性能控制体验
  • PCB板验证
  • 操作系统冷知识:为什么你的电脑能‘一心多用’?揭秘多道程序设计的魔法
  • 别再被机械按键坑了!FPGA消抖模块Verilog代码保姆级解析(附仿真波形)
  • 不只是下载:深入理解WebRTC源码仓库结构与版本管理(从M79到最新版)
  • FoldingNet实战:用Python复现CVPR‘18点云自编码器(附PyTorch代码)
  • 【机器人导航】Ubuntu16.04下北斗星通接收机硬件连接与串口配置指南
  • 模型热切换演示:OpenClaw无缝升级nanobot底层架构
  • 终极Python自动化抢票神器:如何用DamaiHelper告别演唱会门票焦虑
  • 4步掌握MZmine 3:开源质谱数据分析工具从入门到精通
  • AIGlasses OS Pro 智能视觉作品集:多场景图像生成与风格迁移效果
  • DiffBIR实战:用Stable Diffusion 2.1修复模糊老照片(附完整配置流程)
  • 终极免费图像浏览器:90+格式支持与专业体验指南
  • 前端部署:从开发到生产的最后一公里
  • 用51单片机和ADC0809做个简易电压表,Proteus仿真+LCD1602显示,附完整代码
  • 从零开发MCP Server:原理、用法与手写实战全解析
  • OV5640 DVP与MIPI接口配置详解:从寄存器到720p@60Hz实战(附完整代码)
  • 如何让桌面歌词成为你的音乐伴侣:LyricsX深度体验指南
  • [特殊字符] 即梦AI(Dreamina)完全指南:字节跳动的AI创作神器有多强?
  • Python面向对象编程(OOP)基础详解
  • fibjs Addons开发:如何用C++扩展fibjs功能的完整教程
  • 5分钟搞定UniApp连接芯烨热敏打印机:安卓SDK服务绑定全流程解析
  • 二阶RC电池模型参数在线辨识:最小二乘法FFRLSBMS的探索
  • 智能需求工程与文档自动化革新指南:用claude-code-requirements-builder提升开发效率
  • 开源AI新选择:Ollama部署Llama-3.2-3B,性能实测与体验
  • ZYNQ双核通信必看:共享内存的Cache一致性处理实战