ESP32使用MQTT协议通讯(EMQX)

一、背景介绍

前面完成了ESP32+MicroPython环境的搭建01_ESP32 MicroPython开发环境搭建_eps32开发板-CSDN博客

现在想实现以下功能:

1.通过手机或电脑,远程给ESP32发送相关指令。

2.ESP32接到指令后,做出相应的高低电平输出。

这样就相当于可以远程控制ESP32了。

查询资料了解到,目前物联网差不多都使用MQTT协议来进行设备间通信。所以从协议开始学习。

二、MQTT协议

       MQTT(消息队列遥测传输)是ISO 标准(ISO/IEC PRF 20922)下基于发布/订阅范式的消息协议。它工作在 TCP/IP协议族上,是为硬件性能低下的远程设备以及网络状况糟糕的情况下而设计的发布/订阅型消息协议,为此,它需要一个消息中间件 。
       MQTT是一个基于客户端-服务器的消息发布/订阅传输协议。MQTT协议是轻量、简单、开放和易于实现的,这些特点使它适用范围非常广泛。在很多情况下,包括受限的环境中,如:机器与机器(M2M)通信和物联网(IoT)。

三、MQTT服务器 

现在电脑有了,ESP32有了,要完成手机或电脑与ESP32通信,中间还差了个MQTT服务器。网上搜了一番,发现EMQX提供了一个免费的MQTT服务器-免费的公共 MQTT 服务器 | EMQ。(这里就不截图了,只给链接,免得有广告嫌疑)可以用来测试,后面用于生成环境时,可以考虑自己部署一个MQTT服务器

四、实现步骤

1.准备工作

我们这里使用Thonny+MicroPython来演示通信过程,需要注意的是:这里使用的是MicroPython v1.23.0 on 2024-06-02,不同版本可能有所差异。

我们使用命令 help('modules')查看已安装的模块,其中umqtt/robust,umqtt/simple就是用来进行MQTT协议通信的模块

2.EMQ客户端

为了方便调试程序,我们在电脑端安装一个MQTT的客户端,EMQ提供了一个客户端MQTTX:全功能 MQTT 客户端工具,点击链接下载并安装即可。后面我们就通过这个客户端与ESP32进行通信。

3.ESP32 发布消息,EMQ客户端订阅

这种模式下,ESP32是发布者,EMQClient是订阅者,ESP32发送消息到MQTT服务器,MQTT服务器将消息推送给EMQClient。

EMQ的MQTT服务器是可以不做身份验证的,所以下面代码中:

mqtt_client 可以随便写,只要与现有的不重复就行。

mqtt_user,mqtt_password 保持发布和订阅一致就行。

其中mqtt_topic比较关键,是发布者与订阅者之间的桥梁,ESP32和EMQ客户端两边要一致。

import time
import network
import utime 
from umqtt.robust import MQTTClient
 
# WiFi 连接信息
wifi_ssid = '******' # 请替换为自己的WiFi
wifi_password = '******' # 请替换为自己的WiFi
 
# MQTT 服务器信息
mqtt_server = 'broker.emqx.io'
mqtt_port = 1883
mqtt_user = 'username'
mqtt_password = 'password'
mqtt_client = 'mqttx_20240804-esp32' # 客户端ID
mqtt_topic = b'mqttx_20240804' # 主题
 
# 时间戳
def currentTime():
    # 获取当前时间元组
    current_time = utime.localtime()
    # 解构时间元组
    year, month, day, hour, minute, second, _, _ = current_time
    # 使用字符串格式化功能生成日期时间字符串
    formatted_time = "{:04d}-{:02d}-{:02d} {:02d}:{:02d}:{:02d}".format(year, month, day, hour, minute, second)
    return formatted_time
 
# 连接 WiFi
wifi = network.WLAN(network.STA_IF)
wifi.active(True)
wifi.connect(wifi_ssid, wifi_password)
while not wifi.isconnected():
    time.sleep(1)
 
# 定义消息回调函数
def sub_cb(topic, msg):
    print((topic, msg))
 
# 创建MQTT客户端对象
client = MQTTClient(mqtt_client, mqtt_server, mqtt_port,mqtt_user, mqtt_password)
 
# 设置消息回调函数
client.set_callback(sub_cb)
 
# 连接到MQTT代理
client.connect()
 
# 发布消息 
client.publish(mqtt_topic, b'Hello World! '+currentTime())
 
# 断开与MQTT代理的连接
client.disconnect()
 

在EMQ客户端订阅上面的主题: mqttx_20240804,可以看到这样的结果

1.png

2.png

4.ESP32 订阅消息,EMQ客户端发送消息

这种模式下,ESP32是订阅者,EMQClient是发布者,EMQClient发送消息到MQTT服务器,MQTT服务器将消息推送给ESP32。

import time
import network
import utime 
from umqtt.robust import MQTTClient
 
# WiFi 连接信息
wifi_ssid = '******' # 请替换为自己的WiFi
wifi_password = '******' # 请替换为自己的WiFi
 
# MQTT 服务器信息
mqtt_server = 'broker.emqx.io'
mqtt_port = 1883
mqtt_user = 'username'
mqtt_password = 'password'
mqtt_client = 'mqttx_20240804-esp32' # 客户端ID
mqtt_topic = b'mqttx_20240804' # 主题
 
# 时间戳
def currentTime():
    # 获取当前时间元组
    current_time = utime.localtime()
    # 解构时间元组
    year, month, day, hour, minute, second, _, _ = current_time
    # 使用字符串格式化功能生成日期时间字符串
    formatted_time = "{:04d}-{:02d}-{:02d} {:02d}:{:02d}:{:02d}".format(year, month, day, hour, minute, second)
    return formatted_time
 
# 连接 WiFi
wifi = network.WLAN(network.STA_IF)
wifi.active(True)
wifi.connect(wifi_ssid, wifi_password)
while not wifi.isconnected():
    time.sleep(1)
 
# 定义消息回调函数
def sub_cb(topic, msg):
    print(currentTime()+" 收到主题 "+topic.decode('utf-8')+' 消息 ' +msg.decode('utf-8'))
    
 
# 创建MQTT客户端对象
client = MQTTClient(mqtt_client, mqtt_server, mqtt_port,mqtt_user, mqtt_password)
 
# 设置消息回调函数
client.set_callback(sub_cb)
 
# 连接到MQTT代理
client.connect()
 
# 订阅主题
client.subscribe(mqtt_topic)
print("ESP32已开启消息订阅,请在EMQ客户端发送消息...")
 
# 持续接收消息
while True:
    client.check_msg()
    
# 断开与MQTT代理的连接
client.disconnect()

这时,在EMQ客户端发送消息到对于的主题mqttx_20240804,ESP32就可以收到对于的消息了。

3.png

4.png

至此,ESP32通过MQTT收发消息就实现了,后面将EMQ客户端改为Android客户端发送,就可以实现手机端与ESP32通信了。离手机控制ESP32又进一步了。

五、常见问题

1.ESP32与MQTT服务器连接自动断开

如果客户端长时间未发送或接收数据,MQTT服务器可能会断开连接。这时我们需引入心跳包机制。每隔一段时间发送一条心跳包信息,告诉MQTT服务器,ESP32还活着。

2.切换Wifi连接后,无法正常订阅(subscribe)主题

这是因为ESP32在连接Wifi成功后,会将网络配置进行缓存,我们切换Wifi连接时,需要重置下网络wifi.active(False),重新wifi.active(True)即可。可以在连接成功后将网络信息打印出来,判断网络配置是否生效。

3.完整代码(包含心跳包和Wifi切换处理 )

import time
import network
import utime 
from umqtt.robust import MQTTClient
from machine import Pin
 
#GPIO引脚2,用于开灯或关灯
pin2 = Pin(2, Pin.OUT) 
 
# WiFi 连接信息
wifi_ssid = '******' # 请替换为自己的WiFi
wifi_password = '******' # 请替换为自己的WiFi
 
# MQTT 服务器信息
mqtt_server = 'broker.emqx.io'
mqtt_port = 1883
mqtt_user = 'username'
mqtt_password = 'password'
mqtt_client = 'mqttx_20240805-esp32' # 客户端ID
mqtt_topic = 'mqttx_20240804' # 主题
mqtt_keepalive = 62  # 保持活动的时间间隔(秒) 
 
# 当前时间
def currentTime():
    # 获取当前时间元组
    current_time = utime.localtime()
    # 解构时间元组
    year, month, day, hour, minute, second, _, _ = current_time
    # 使用字符串格式化功能生成日期时间字符串
    formatted_time = "{:04d}-{:02d}-{:02d} {:02d}:{:02d}:{:02d} ".format(year, month, day, hour, minute, second)
    return formatted_time
 
# 连接MQTT服务器
def connect_mqtt():
    #global mqtt_client
    mqtt_client = "esp32" + str(time.ticks_ms())  # 确保每次连接都有唯一的client_id
    client = MQTTClient(mqtt_client, mqtt_server, mqtt_port,mqtt_user, mqtt_password, keepalive=mqtt_keepalive)
    client.set_callback(sub_cb) 
    return client 
        
# 连接 WiFi
def connect_wifi():  
    wifi = network.WLAN(network.STA_IF)    
    #若已连接过Wifi,切换新的Wifi时,需要重置下网络,才能连接成功
    if wifi.isconnected():
        # 断开当前连接
        wifi.disconnect()
        # 关闭接口
        wifi.active(False)
        
    time.sleep(1)  # 休眠1秒
    wifi.active(True)
    print(currentTime()+"connecting to wifi: {0}".format(wifi_ssid))
    wifi.connect(wifi_ssid, wifi_password)
    while not wifi.isconnected():
        time.sleep(1)
    print(currentTime()+"connected to wifi: {0}".format(wifi_ssid))
    print(currentTime()+'network config:', wifi.ifconfig())
 
# 定义消息回调函数
def sub_cb(topic, msg):
    action=msg.decode('utf-8')
    print(currentTime()+"recv: topic("+topic.decode('utf-8')+')->msg:' +action)
    if(action=="on"):
        lightON()
    elif (action=="off"): 
        lightOFF()
        
#亮灯(也可以是其他操作,此处仅做演示)
def lightON():
    pin2.value(1)
    
#关灯(也可以是其他操作,此处仅做演示)
def lightOFF():
    pin2.value(0)
 
#主程序 
connect_wifi()
client=connect_mqtt()
try:
    client.connect()
    print(currentTime()+"ESP32已连接到MQTT服务器"+mqtt_server)
     
    # 发布消息 
    #client.publish(mqtt_topic, b'Hello World! '+currentTime())
 
    # 订阅主题
    client.subscribe(mqtt_topic)
    print(currentTime()+"ESP32已开启消息订阅,请在EMQ客户端发送消息...")
    
except OSError as e:
    print(currentTime()+'Connection or subscription failed: %r' % e)
    
# 持续接收消息
count=0
while True:
    count=count+1
    #每次轮询间隔0.3秒,轮询200次就是60秒,在第200次时发送心跳包
    if(count==199):
        #发送心跳包
        curtime=currentTime()
        client.publish(mqtt_topic, 'heart beat '+curtime)
        print(currentTime()+"send: topic("+mqtt_topic+")->msg:heart beat "+curtime)
        count=0
    client.check_msg()
    time.sleep(0.3)
    
    
# 断开与MQTT代理的连接
client.disconnect()
 
 

运行结果

5.png

补充说明:

MicroPython的umqtt是一个用于MicroPython的简单MQTT客户端,它实现了CPython模块paho-mqtt的一个子集,但也有一些特殊的特点和限制。

umqtt模块的主要特点是:

它支持MQTT v3.1.1协议,但不支持QoS 。
它支持消息回调,并且为接收消息提供了阻塞和非阻塞的两种实现。
它支持SSL/TLS加密连接,但需要额外的ssl模块。
它分为simple和robust两个子模块,robust模块提供了自动重连和延时发送等功能,但也有一些缺陷。

umqtt模块的应用场景是:

在MicroPython中实现与互联网或局域网中的其他设备或服务的通信,如获取天气信息、控制智能家居设备、上传传感器数据等。
在MicroPython中实现基于MQTT协议的客户端或服务器端的应用,如物联网设备、远程控制、消息推送等。

umqtt模块需注意事项是:

由于MicroPython的内存限制,umqtt模块不能处理过大的消息内容,否则可能导致内存不足或异常。
由于MicroPython的网络支持不完善,umqtt模块可能在不同的硬件平台或MicroPython版本上有不同的表现或错误。
由于umqtt.robust模块在弱网或断网后可能出现死锁或无限递归等问题,建议使用micropython-mqtt库5来替代它,该库提供了更稳定和灵活的MQTT客户端实现。 

参考文档

【雕爷学编程】MicroPython手册之MQTT通信 umqtt 模块_micropython mqtt-CSDN博客

发表评论

称呼 *
联系方式 * 方便与您联系,不会对外显示。
内容
验证码