MQTT 远程控制
MQTT 远程控制
本节介绍如何通过 MQTT 协议远程控制 ESP32 音频播放器。学习完成后,您将能够:
- 设计音频控制的 MQTT Topic 结构
- 实现 MQTT 订阅以接收控制命令
- 将 MQTT 消息与音频播放逻辑关联
- 发布播放状态和元数据回 MQTT
在开始本节之前,请确保:
- 已掌握 MQTT 协议基础
- 音频基本播放功能正常工作
- Mosquitto 或 EMQX Broker 已运行
- PubSubClient 库已安装
MQTT Topic Design
Section titled “MQTT Topic Design”Control Topics
Section titled “Control Topics”| Topic | 数据类型 | 功能 | 说明 |
|---|---|---|---|
factory/broadcast/control | JSON | 综合控制 | 包含所有控制命令 |
factory/broadcast/play | String | 播放控制 | ”play” / “stop” / “pause” / “resume” |
factory/broadcast/station | Integer | 切换电台 | 电台索引号 (0-3) |
factory/broadcast/volume | Integer | 音量控制 | 0-250 |
factory/broadcast/mute | Boolean | 静音切换 | true / false |
Status Topics
Section titled “Status Topics”| Topic | 数据类型 | 功能 | 说明 |
|---|---|---|---|
factory/broadcast/status | JSON | 播放器状态 | 当前状态、音量、电台 |
factory/broadcast/streamtitle | String | 当前曲目 | 电台当前播放的曲目标题 |
factory/broadcast/streameinfo | String | 流信息 | 比特率、格式等 |
Topic Design Principles
Section titled “Topic Design Principles”factory/broadcast/{command}
分层说明:factory → 工厂级前缀broadcast → 广播系统{command} → 具体命令MQTT Control Implementation
Section titled “MQTT Control Implementation”Complete MQTT Control Sketch
Section titled “Complete MQTT Control Sketch”#include <Arduino.h>#include <WiFi.h>#include <PubSubClient.h>#include <AudioOutputI2S.h>#include <AudioGeneratorMP3.h>#include <AudioFileSourceICYStream.h>
// WiFi 配置const char* ssid = "您的WiFi名称";const char* password = "您的WiFi密码";
// MQTT 配置const char* mqttServer = "192.168.1.100";const int mqttPort = 1883;const char* mqttUser = "node_red";const char* mqttPassword = "node_red";const char* deviceID = "esp32_broadcast_01";
// Topic 定义const char* topicPlay = "factory/broadcast/play";const char* topicStation = "factory/broadcast/station";const char* topicVolume = "factory/broadcast/volume";const char* topicMute = "factory/broadcast/mute";const char* topicStatus = "factory/broadcast/status";const char* topicStreamTitle = "factory/broadcast/streamtitle";
// I2S 引脚#define I2S_BCLK 26#define I2S_LRCK 25#define I2S_DIN 22
// 电台列表const char* stationNames[] = {"News", "Music", "Pop", "Classic"};const char* stationURLs[] = { "http://stream1.example.com:8000/news", "http://stream2.example.com:8000/music", "http://stream3.example.com:8000/pop", "http://stream4.example.com:8000/classic"};const int stationCount = 4;int currentStation = 0;
// 音频对象AudioGeneratorMP3* mp3;AudioFileSourceICYStream* file;AudioOutputI2S* output;
// 音量管理int currentVolume = 128;bool isMuted = false;int previousVolume = 128;
// MQTT 客户端WiFiClient wifiClient;PubSubClient mqttClient(wifiClient);
// 状态发布void publishStatus() { StaticJsonDocument<256> doc; doc["device"] = deviceID; doc["station"] = currentStation; doc["station_name"] = stationNames[currentStation]; doc["volume"] = currentVolume; doc["muted"] = isMuted; doc["playing"] = mp3 ? mp3->isRunning() : false; doc["wifi_rssi"] = WiFi.RSSI();
char buffer[256]; serializeJson(doc, buffer); mqttClient.publish(topicStatus, buffer);}
// 音量设置void setVolume(int vol) { currentVolume = constrain(vol, 0, 250); output->SetVolume(currentVolume); isMuted = false; publishStatus();}
// 静音切换void toggleMute() { if (isMuted) { currentVolume = previousVolume; output->SetVolume(currentVolume); isMuted = false; } else { previousVolume = currentVolume; output->SetVolume(0); isMuted = true; } publishStatus();}
// 切换电台void switchStation(int stationIndex) { if (stationIndex < 0 || stationIndex >= stationCount) return;
if (mp3 && mp3->isRunning()) mp3->stop(); if (file) file->close();
currentStation = stationIndex; file->open(stationURLs[currentStation]); mp3->begin(file, output);
mqttClient.publish(topicStreamTitle, stationNames[currentStation]); publishStatus();}
// MQTT 回调处理void mqttCallback(char* topic, byte* payload, unsigned int length) { String message; for (unsigned int i = 0; i < length; i++) { message += (char)payload[i]; }
Serial.printf("收到消息: [%s] %s\n", topic, message.c_str());
if (strcmp(topic, topicPlay) == 0) { if (message == "play") { switchStation(currentStation); } else if (message == "stop") { if (mp3 && mp3->isRunning()) mp3->stop(); } else if (message == "pause") { if (mp3 && mp3->isRunning()) mp3->stop(); // 简单实现:停止 } } else if (strcmp(topic, topicStation) == 0) { int station = message.toInt(); switchStation(station); } else if (strcmp(topic, topicVolume) == 0) { int vol = message.toInt(); setVolume(vol); } else if (strcmp(topic, topicMute) == 0) { if (message == "true" || message == "1") { if (!isMuted) toggleMute(); } else { if (isMuted) toggleMute(); } }}
// MQTT 重连void reconnectMQTT() { while (!mqttClient.connected()) { Serial.print("尝试 MQTT 连接..."); if (mqttClient.connect(deviceID, mqttUser, mqttPassword)) { Serial.println("已连接");
// 订阅控制 Topic mqttClient.subscribe(topicPlay); mqttClient.subscribe(topicStation); mqttClient.subscribe(topicVolume); mqttClient.subscribe(topicMute);
// 发布在线状态 mqttClient.publish(topicStatus, "{\"device\":\"" + String(deviceID) + "\",\"status\":\"online\"}"); } else { Serial.printf("失败 (rc=%d),5秒后重试\n", mqttClient.state()); delay(5000); } }}
void setup() { Serial.begin(115200);
// 连接 WiFi WiFi.begin(ssid, password); while (WiFi.status() != WL_CONNECTED) { delay(1000); Serial.print("."); } Serial.println("\nWiFi 已连接");
// 初始化音频 output = new AudioOutputI2S(); output->SetPinout(I2S_BCLK, I2S_LRCK, I2S_DIN); output->SetVolume(currentVolume); output->begin();
mp3 = new AudioGeneratorMP3(); file = new AudioFileSourceICYStream();
// 初始电台 file->open(stationURLs[currentStation]); mp3->begin(file, output);
// MQTT 配置 mqttClient.setServer(mqttServer, mqttPort); mqttClient.setCallback(mqttCallback);}
void loop() { // MQTT 保活 if (!mqttClient.connected()) { reconnectMQTT(); } mqttClient.loop();
// 音频播放循环 if (mp3->isRunning()) { if (!mp3->loop()) { mp3->stop(); Serial.println("播放结束,5秒后重连"); delay(5000); switchStation(currentStation); } }
// 定期更新流标题 static unsigned long lastTitleCheck = 0; if (millis() - lastTitleCheck > 15000) { lastTitleCheck = millis(); if (file && file->getStreamTitle()) { mqttClient.publish(topicStreamTitle, file->getStreamTitle()); } publishStatus(); }}MQTT Control Flow
Section titled “MQTT Control Flow”Control Flow Diagram
Section titled “Control Flow Diagram”Node-RED Dashboard / Mobile App │ │ MQTT Publish ▼ Mosquitto Broker │ │ Topic: factory/broadcast/play ▼ ESP32 (PubSubClient) │ │ mqttCallback() ▼ ESP32 Audio Controller │ ├── play → mp3->begin(file, output) ├── stop → mp3->stop() ├── station → switchStation(index) ├── volume → output->SetVolume(n) └── mute → toggleMute()Stream Title Publishing
Section titled “Stream Title Publishing”Metadata Publishing
Section titled “Metadata Publishing”// 定期检查并发布流元数据void publishStreamMetadata() { if (!file) return;
// 获取流标题(当前歌曲/节目名) const char* title = file->getStreamTitle(); if (title && strlen(title) > 0) { mqttClient.publish(topicStreamTitle, title); Serial.printf("当前播放: %s\n", title); }
// 获取流信息(比特率、格式等) const char* info = file->getStreamInfo(); if (info && strlen(info) > 0) { mqttClient.publish("factory/broadcast/streaminfo", info); }}Pre-sales Key Points
Section titled “Pre-sales Key Points”MQTT Control Capabilities
Section titled “MQTT Control Capabilities”| 控制能力 | 实现方式 | 买家价值 |
|---|---|---|
| 远程开关 | MQTT 播放/停止命令 | ”可远程启停车间广播” |
| 音源切换 | MQTT 电台索引命令 | ”可切换不同播放内容” |
| 音量调节 | MQTT 音量值 | ”可远程调节音量” |
| 状态监控 | MQTT 状态发布 | ”可实时查看设备状态” |
| 元数据显示 | MQTT 曲目标题 | ”可显示当前播放内容” |
Summary
Section titled “Summary”本节介绍了通过 MQTT 远程控制 ESP32 音频播放器:
- Topic 设计:使用
factory/broadcast/{command}层级结构 - 控制命令:播放/停止、切换电台、音量调节、静音
- 状态发布:定期推送设备状态和流元数据
- 回调处理:MQTT 消息触发音频控制函数