第4章:MQTT协议入门:发布/订阅模型,搭建本地Mosquitto Broker,实现灯光的远程开关
好,咱们继续往前走。
上一章我们把硬件点亮了,也调通了PWM调光。但说实话,那只是单机版。你想想看,如果每次开关灯都要跑到设备跟前去按按钮,那还叫什么智能家居?
这一章,我们来解决远程控制的问题。核心就是MQTT协议。
4.1 为什么是MQTT?不是HTTP?
很多刚入行的朋友会问我:「为什么不用HTTP?RESTful API不是挺成熟的吗?」
嗯,这个问题我当年也纠结过。我在一个早期的物联网项目里,确实尝试过用HTTP做设备控制。结果呢?设备在弱网环境下频繁断连,每次重连都要重新握手,功耗还高得吓人。后来我彻底转向了MQTT。
MQTT和HTTP最大的区别在于:
| 特性 | HTTP | MQTT |
|---|---|---|
| 通信模型 | 请求/响应 | 发布/订阅 |
| 连接开销 | 每次请求都要TCP握手 | 长连接,一次握手 |
| 实时性 | 需要轮询 | 服务器主动推送 |
| 协议开销 | 头部大(几百字节) | 最小仅2字节头部 |
| 适用场景 | Web应用、API调用 | 物联网、传感器、移动端 |
说白了,MQTT就是为物联网而生的。它轻量、省电、支持双向通信。你想想看,一个ESP8266模块,用MQTT发一条控制指令,整个报文可能才十几字节。换成HTTP?光头部就几百字节了。
4.2 发布/订阅模型:核心概念
MQTT的核心思想,就是「发布/订阅」模型。我习惯把它比作一个微信群。
- Broker(代理):就是微信群。它不生产消息,只负责转发。
- Publisher(发布者):在群里说话的人。他发一条消息到群里。
- Subscriber(订阅者):在群里听消息的人。他只关心自己感兴趣的话题。
- Topic(主题):就是话题标签。比如#灯光控制、#温度上报。
举个例子:
你的手机App是发布者,它发了一条消息到主题 home/livingroom/light/set,内容是 {"state": "ON"}。你的ESP8266订阅了这个主题,Broker就把这条消息推送给它。ESP8266收到后,执行开灯操作。
关键点:发布者和订阅者不需要知道对方的存在。它们只跟Broker打交道。这就实现了「解耦」——你换手机App,不影响设备;你换设备,也不影响App。
4.3 Topic的设计:别小看这个
Topic的设计,说实话,比你想的重要得多。我在一个项目中吃过亏——一开始随便起了几个Topic名字,结果设备多了以后,管理起来一团糟。
我建议你遵循这样的层级结构:
home/<房间>/<设备类型>/<设备ID>/<属性>
比如:
home/livingroom/light/001/state—— 上报灯的状态home/livingroom/light/001/set—— 接收控制指令home/livingroom/temperature—— 上报温度
MQTT还支持通配符:
+匹配单层:home/+/light/001/set匹配所有房间的灯#匹配多层:home/livingroom/#匹配客厅所有设备
我的习惯:Topic命名全部用小写字母,单词之间用斜杠分隔。不要用空格、中文、特殊字符。否则后面解析的时候会踩坑。
4.4 搭建本地Mosquitto Broker
好,理论说完了,咱们动手。我选择Mosquitto,因为它轻量、稳定、开源。你在树莓派、Ubuntu、甚至Windows上都能跑。
在Ubuntu/Debian上安装:
sudo apt update
sudo apt install mosquitto mosquitto-clients
启动服务:
sudo systemctl start mosquitto
sudo systemctl enable mosquitto # 开机自启
验证是否运行:
sudo systemctl status mosquitto
看到 active (running) 就对了。
测试一下:
打开两个终端窗口。
终端1(订阅者):
mosquitto_sub -h localhost -t "test/topic"
终端2(发布者):
mosquitto_pub -h localhost -t "test/topic" -m "Hello MQTT"
你会看到终端1收到了消息。嗯,就是这么简单。
注意:默认配置下,Mosquitto允许匿名连接,且不加密。这在局域网内调试没问题。但如果要暴露到公网,一定要配置用户名密码和TLS加密。我后面会专门讲安全配置。
4.5 实现灯光的远程开关
现在,我们把MQTT和之前的灯光硬件结合起来。
硬件接线回顾:
- ESP8266 GPIO4 → LED正极(通过220Ω电阻)
- LED负极 → GND
代码实现:
#include <ESP8266WiFi.h>
#include <PubSubClient.h>
// WiFi配置
const char* ssid = "你的WiFi名称";
const char* password = "你的WiFi密码";
// MQTT Broker配置
const char* mqtt_server = "192.168.1.100"; // 你的Broker IP
const int mqtt_port = 1883;
// Topic定义
const char* topic_state = "home/livingroom/light/001/state";
const char* topic_set = "home/livingroom/light/001/set";
WiFiClient espClient;
PubSubClient client(espClient);
// LED引脚
const int ledPin = 4;
void setup() {
Serial.begin(115200);
pinMode(ledPin, OUTPUT);
digitalWrite(ledPin, LOW);
setup_wifi();
client.setServer(mqtt_server, mqtt_port);
client.setCallback(callback);
}
void setup_wifi() {
delay(10);
Serial.println();
Serial.print("连接到 ");
Serial.println(ssid);
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println("");
Serial.println("WiFi 已连接");
}
void callback(char* topic, byte* payload, unsigned int length) {
Serial.print("收到消息 [");
Serial.print(topic);
Serial.print("] ");
String message;
for (int i = 0; i < length; i++) {
message += (char)payload[i];
}
Serial.println(message);
// 解析控制指令
if (String(topic) == topic_set) {
if (message == "ON") {
digitalWrite(ledPin, HIGH);
client.publish(topic_state, "ON");
Serial.println("灯已打开");
} else if (message == "OFF") {
digitalWrite(ledPin, LOW);
client.publish(topic_state, "OFF");
Serial.println("灯已关闭");
}
}
}
void reconnect() {
while (!client.connected()) {
Serial.print("尝试连接MQTT...");
if (client.connect("ESP8266_Light_001")) {
Serial.println("已连接");
client.subscribe(topic_set);
client.publish(topic_state, "OFF");
} else {
Serial.print("失败,rc=");
Serial.print(client.state());
Serial.println(" 5秒后重试");
delay(5000);
}
}
}
void loop() {
if (!client.connected()) {
reconnect();
}
client.loop();
}
代码要点:
client.setCallback(callback)—— 注册回调函数,收到消息时自动触发client.subscribe(topic_set)—— 订阅控制主题client.publish(topic_state, "ON")—— 发布状态,让App知道灯已经开了client.loop()—— 必须放在loop里,保持MQTT连接活跃
4.6 测试远程开关
上传代码到ESP8266后,打开串口监视器。你会看到它先连WiFi,再连MQTT Broker。
然后,在电脑上执行:
mosquitto_pub -h 192.168.1.100 -t "home/livingroom/light/001/set" -m "ON"
灯亮了!再发一条:
mosquitto_pub -h 192.168.1.100 -t "home/livingroom/light/001/set" -m "OFF"
灯灭了。
你还可以订阅状态主题,看看设备有没有正确回复:
mosquitto_sub -h 192.168.1.100 -t "home/livingroom/light/001/state"
避坑指南:我曾经遇到过一个问题——灯能开不能关。查了半天,发现是回调函数里用 == 比较字符串,但收到的payload末尾有换行符。后来改用 message.trim() 就好了。嗯,细节决定成败。
4.7 本章小结
这一章我们做了三件事:
- 理解了MQTT的发布/订阅模型,以及它为什么适合物联网
- 在本地搭建了Mosquitto Broker
- 用ESP8266实现了灯光的远程开关
你现在已经可以坐在沙发上,用电脑或者手机App远程控制家里的灯了。下一章,我们会加入调光功能,让灯光不只是开关,还能调节亮度。而且,我们会用上MQTT的QoS机制,保证指令不丢失。
准备好了吗?我们继续。