跳转至

Mqtt

概要

本节课讲了MQTT协议的特性, MQTT网络里面的组成部分,以及MQTT通信数据传输的流程讲解。

keywords mqtt tcp/ip qos publisher subscriber

MQTT协议简介

ESP32是一款物联网(IOT, internet of things)模块. 所谓物联就是机器与机器之间的通信, 互联互通之后,设备之间就可以协同工作。 ESP32作为一个单片机,其网络环境可能是不可靠的, 如果我们采用原始的socket通信,并不能保障信息可以到达接收方,数据的可靠性包括实时性都会有一定的影响, 所以这个时候就需要一种网络通信协议Protocal 来保障信息的传递, 保障服务质量(Qos: Quality of Service)。

互联网的基础网络协议是 TCP/IP。MQTT(消息队列遥测传输) 是基于 TCP/IP 协议栈而构建的,已成为 IoT 通信的标准

MQTT 是一种轻量级的, 灵活的网络协议,致力于为 IoT 开发人员实现适当的平衡:

  • 这个轻量级协议可在严重受限的设备硬件和高延迟/带宽有限的网络上实现。

  • 它的灵活性使得为 IoT 设备和服务的多样化应用场景提供支持成为可能。

熟悉Web开发的同学,可能会有想法用Http协议来开发物联网应用, 为什么要用MQTT而不是HTTP,可以参考IBM的这篇文章, 讲的比较详细:初识 MQTT-IBM

总结下来MQTT有如下特性/优势:

  • 异步消息协议

  • 面向长连接

  • 双向数据传输

  • 协议轻量级

  • 被动数据获取

MQTT的发布和订阅模型

MQTT_DHT_02-400x258

注: 图片来自REF 4

在基于MQTT协议的IOT网络里面里面有这么几个角色:

a1

  • 发布者 Publisher 负责发布消息, 例如传感器采集数据,然后发送当前传感器的信息

  • 订阅者 Subscriber 订阅消息,根据获得的传感器数据做出对应的动作。

  • *服务器 Server * 信息的中转站,负责将信息从发布者传递到订阅者。

其中发布者与订阅者统称为客户端 Client

MQTT_DHT_01-400x157

注: 图片来自REF 4

注意, 这个划分并不是说三个角色必须是不同的实体设备,这里只是根据功能划分。有时候一个设备可以同时为发布者或订阅者, 就连服务器自己也可以作为客户端。

注意MQTT是协议,基于TCP/IP, MQTT协议在客户端的实现称之为MQTT Client,

MQTT客户端的各种实现见: mqtt/libs

MQTT协议在服务器端的实现称之为MQTT Broker. MQTT Broker基于各种语言(JAVA, C/C++)的实现,比较流行的MQTT Broker列表见:mqtt server/brokers

为什么MQTT会被广泛用于IOT开发?主要还是归功于它的发布者与订阅者的设计思想。

A3

一个核心思想是,能力越大, 责任越大 .

我们拿PC与单片机举例子,从价格上还有硬件的操控能力,传感器数据读取方面, 单片机更合适, 如果一个地方需要采集100个位点的温度信息,可能会选择使用单片机来读取,那这么多的单片机之间如何进行通信呢?

单片机硬件资源有限(内存,带宽), 就决定了单片机很难与多个客户端进行通信,同时与多个客户端建立长连接。 从内存占用,通信时延, 还有数据的稳定性上来讲,显然我们更相信PC在网络吞吐方面的能力。 另外PC还可以对数据进行数据预处理,然后作为Client发布处理后的数据。

所以根据各自能力的特点, 我们做如下职责划分:

A4

每个单片机(Client)仅与PC(Server)保持一个长连接, 有什么数据就告诉Server, 如果有其他单片机或者PC跟这个单片机通信, 也只能通过这个Server来获取, 同时也要注意,这个数据获取的过程是被动的, 单片机没有主动轮询, 整个过程是异步的, 数据传过来,自动调用回调函数, 所以Server就成了这个单片机与这个周边设备通信的唯一的渠道,这个机制使得整个过程更加轻量级与高效 。

有点像一个地下组织的老大带领着一帮小弟的感觉, 小弟之间互不联系, 直接向老大传递信息, 老大像小弟传递指令。

dataframe

通信所用的数据帧 Data Frame主要由主题编号 Topic ID 还有信息 Message 两部分组成。

发布者与订阅者之间是没办法直接感知到对方的存在的, 订阅者与发布者之间通过数据帧 Data Frame 里面的主题编号Topic ID 来获取自己想要的数据。

MQTT通信流程详细描述

举一个远程控制LED灯亮灭的实际例子, 我们这里梳理一下过程。

ESP32与PC连入同一个局域网下, 获取PC 的IP地址

局域网

PC开启 MQTT Broker, 开启Server模式

mqtt broker

ESP32传入PC的IP地址还有端口号,创建一个MQTT_Client

ESP32的MQTT_Client与PC上的MQTT_Server创建一个长连接

ESP32的MQTT_Client 订阅Topic LED_CONTROL

6-4

PC上创建一个CLIENT, Client里面传入本地IP与MQTT Broker服务的端口号, 与PC上面的Server建立一个长连接

img

PC上的Client, 发送数据帧 Topic ID + 指令, Topic ID为LED Control

数据帧: TOPIC_ID: LED_CONTROL, MESSAGE: LED_ON

send dataframe

数据发送给Server, Server发现ESP32开发板订阅了LED_CONTROL 这个主题, 然后就通过ESP32与Server创建的连接发送该数据帧。

ESP32接收到这个数据帧,发现TOPIC_ID: LED_CONTROL, 于是知道这个是跟LED控制相关的指令。

读取到MESSAGE是LED_ON, ESP32执行指令led.on() , LED打开。

led on

读完这篇文章,相信你对MQTT已经跃跃欲试了,下一节课,阿凯带你在局域网下用Python实现MQTT通信。见课程: MQTT入门之项目实战

MQTT实战演练

Mosquitto

安装Mosquitto

在Ubuntu上面搭建MQTT的开发环境,可以选择Mosquitto, Mosquitto是Eclipse开源的项目, 官网: mosquitto.org

其中mosquitto就是MQTT Broker的实现, mosquitto-clients是MQTT客户端的实现。

sudo apt-get install mosquitto mosquitto-clients 

mosquitto_pub

-t 代表指定topic

-m 代表message信息

mosquitto_pub -t 'pyespcar_basic_control' -m 'MOVE_FORWARD'

在中端上执行上面的这条信息,等于在主题pyespcar_basic_control 下发布一条信息MOVE_FORWARD

mosquitto_sub

安装成功之后, 你可以通过mosquitto_sub 指令, 在中端获取特定Topic的数据。

mosquitto_sub  -t 'pyespcar_basic_control'

综合实验

打开终端的两个窗口, 首先开启接收者的服务。

mosquitto_sub  -t 'pyespcar_basic_control'

然后尝试在另外一个窗口发送信息:

mosquitto_pub -t 'pyespcar_basic_control' -m 'MOVE_FORWARD'

img

注: 因为Server的默认IP就是localhost, IP默认就是1883,所以这里不需要指定。 更详细的参数介绍见官方文档:

paho-mqtt

安装paho-mqtt

另外我们还希望可以使用Python 进行基于MQTT的物联网开发, 这就需要用使用pip3安装另外一个库 paho-mqtt , 官网https://www.eclipse.org/paho/.

The Eclipse Paho project provides open-source client implementations of MQTT and MQTT-SN messaging protocols aimed at new, existing, and emerging applications for the Internet of Things (IoT).

sudo pip3 install paho-mqtt

使用paho-mqtt实现接收者

pc/paho-mqtt-subsriber.py
import paho.mqtt.client as mqtt

def on_message(client, userdata, msg):
    '''处理message回调'''
    print('topic: {}'.format(msg.topic))
    print('message: {}'.format(str(msg.payload)))

# 建立一个MQTT的客户端
client = mqtt.Client()
# 绑定数据接收回调函数
client.on_message = on_message

HOST_IP = 'localhost' # Server的IP地址
HOST_PORT = 1883 # mosquitto 默认打开端口
TOPIC_ID = 'pyespcar_basic_control' # TOPIC的ID

# 连接MQTT服务器
client.connect(HOST_IP, HOST_PORT, 60)
# 订阅主题
client.subscribe(TOPIC_ID)

# 阻塞式, 循环往复,一直处理网络数据,断开重连
client.loop_forever()

使用paho-mqtt实现发布者

pc/paho-mqtt-publisher.py
import paho.mqtt.client as mqtt
import time

HOST_IP = 'localhost' # Server的IP地址
HOST_PORT = 1883 # mosquitto 默认打开端口
TOPIC_ID = 'pyespcar_basic_control' # TOPIC的ID

# 创建一个客户端
client = mqtt.Client()
# 连接到服务器(本机)
client.connect(HOST_IP, HOST_PORT, 60)

count = 0
while True:
    count += 1
    # 待发送的数据
    message = 'MOVE FRORWORD,{}'.format(count)   
    # 通过mqtt协议发布数据给server
    client.publish(TOPIC_ID, message)
    # 打印日志
    print('SEND: {}'.format(message))
    # 延时1s
    time.sleep(1)

综合实验

可以在本地的终端打开两个串口,分别输入指令:

# 运行订阅者
python3 paho-mqtt-subsriber.py
# 运行发布者
python3 paho-mqtt-subsriber.py

功能其实跟上文的Mosquitto例程差不多。

paho-mqtt_pub_sub

左边是接收者的进程, 右边是发送者的进程, 这里大家留意一下,接收者在接收的时候数据打印出来是这样的:

topic: pyespcar_basic_control
message: b'MOVE FRORWORD,175'

这里的b'MOVE FRORWORD,175' 是字节bytes类型的数据, 在Http通信的过程中数据以utf-8 编码的方式,传递字节数据。

通过decode方法, 可以把bytes类型的数据转换为字符串。

In [1]: bdata = b'MOVE FRORWORD,175'
In [2]: bdata.decode('utf-8')
Out[2]: 'MOVE FRORWORD,175'

MQTT与ESP32-MicroPython

之前的历程都是在Ubuntu的本机上测试的, 真正的物联网怎么少的了单片机呢, 我们这里把单片机(MicroPython-ESP32)结合进来。

在ESP32上安装MQTT库

首先,我们需要在ESP32上面安装mqtt的库。(MQTT客户端在ESP32上面的实现)

首先确认ESP32-MicroPython已经连接上了热点!!!, 通过REPL控制ESP32。

引入upip包管理器

>>> import upip
>>> upip.install('micropython-umqtt.simple')
Installing to: /lib/
Installing micropython-umqtt.simple 1.3.4 from https://files.pythonhosted.org/packages/bd/cf/697e3418b2f44222b3e848078b1e33ee76aedca9b6c2430ca1b1aec1ce1d/micropython-umqtt.simple-1.3.4.tar.gz

这样umqtt.simple这个包就安装好了。

查看Server的IP地址

查看PC当前的IP, 在Ubuntu(作为Server)的命令行里面执行指令:

ifconfig
➜  下载 ifconfig 
enp3s0    Link encap:以太网  硬件地址 5c:f9:dd:49:4b:ad  
          UP BROADCAST MULTICAST  MTU:1500  跃点数:1
          接收数据包:0 错误:0 丢弃:0 过载:0 帧数:0
          发送数据包:0 错误:0 丢弃:0 过载:0 载波:0
          碰撞:0 发送队列长度:1000 
          接收字节:0 (0.0 B)  发送字节:0 (0.0 B)
          中断:16 

lo        Link encap:本地环回  
          inet 地址:127.0.0.1  掩码:255.0.0.0
          inet6 地址: ::1/128 Scope:Host
          UP LOOPBACK RUNNING  MTU:65536  跃点数:1
          接收数据包:256668 错误:0 丢弃:0 过载:0 帧数:0
          发送数据包:256668 错误:0 丢弃:0 过载:0 载波:0
          碰撞:0 发送队列长度:1000 
          接收字节:138568580 (138.5 MB)  发送字节:138568580 (138.5 MB)

wlp2s0    Link encap:以太网  硬件地址 68:5d:43:ec:d3:58  
          inet 地址:192.168.43.16  广播:192.168.43.255  掩码:255.255.255.0
          inet6 地址: fe80::47ef:2ce1:f8e9:b0c2/64 Scope:Link
          UP BROADCAST RUNNING MULTICAST  MTU:1500  跃点数:1
          接收数据包:146459 错误:0 丢弃:0 过载:0 帧数:0
          发送数据包:137348 错误:0 丢弃:0 过载:0 载波:0
          碰撞:0 发送队列长度:1000 
          接收字节:147948142 (147.9 MB)  发送字节:20083083 (20.0 MB)

192.168.43.16 当前PC在局域网的IP地址为

使用umqtt实现接收者

esp32/subscriber.py
from umqtt.simple import MQTTClient
import time

SERVER = '192.168.43.16'
CLIENT_ID = 'PYESPCAR_A0'
TOPIC = b'pyespcar_basic_control'

def mqtt_callback(topic, msg):
    print('topic: {}'.format(topic))
    print('msg: {}'.format(msg))


client = MQTTClient(CLIENT_ID, SERVER)
client.set_callback(mqtt_callback)
client.connect()

client.subscribe(TOPIC)


while True:
    # 查看是否有数据传入
    # 有的话就执行 mqtt_callback
    client.check_msg()
    time.sleep(1)

使用umqtt实现发送者

esp32/publisher.py
from umqtt.simple import MQTTClient
import time

SERVER = '192.168.43.16'
CLIENT_ID = 'PYESPCAR_A0' # 客户端的ID
TOPIC = b'pyespcar_basic_control' # TOPIC的ID

client = MQTTClient(CLIENT_ID, SERVER)
client.connect()


while True:
    client.publish(TOPIC, 'helloworld')
    time.sleep(1)

注意在Esp32里面TOPIC需要是bytes类型。

综合实验

你可以结合paho-mqtt里面的发送者与esp32里面的接收者进行测试。

也可以使用paho-mqtt里面的接收者与esp32里面的发送者进行测试。

作业

在PC上封装一个库,可以通过MQTT远程控制ESP32上面的LED亮灭。

上文MQTT入门之概念解析,已经把程序的整个流程列给你了。