跳转到内容

MQTT 远程控制

MQTT 远程控制

本节介绍如何通过 MQTT 协议远程控制 ESP32 音频播放器。学习完成后,您将能够:

  • 设计音频控制的 MQTT Topic 结构
  • 实现 MQTT 订阅以接收控制命令
  • 将 MQTT 消息与音频播放逻辑关联
  • 发布播放状态和元数据回 MQTT

在开始本节之前,请确保:

  • 已掌握 MQTT 协议基础
  • 音频基本播放功能正常工作
  • Mosquitto 或 EMQX Broker 已运行
  • PubSubClient 库已安装
Topic数据类型功能说明
factory/broadcast/controlJSON综合控制包含所有控制命令
factory/broadcast/playString播放控制”play” / “stop” / “pause” / “resume”
factory/broadcast/stationInteger切换电台电台索引号 (0-3)
factory/broadcast/volumeInteger音量控制0-250
factory/broadcast/muteBoolean静音切换true / false
Topic数据类型功能说明
factory/broadcast/statusJSON播放器状态当前状态、音量、电台
factory/broadcast/streamtitleString当前曲目电台当前播放的曲目标题
factory/broadcast/streameinfoString流信息比特率、格式等
factory/broadcast/{command}
分层说明:
factory → 工厂级前缀
broadcast → 广播系统
{command} → 具体命令
#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();
}
}
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()
// 定期检查并发布流元数据
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);
}
}
控制能力实现方式买家价值
远程开关MQTT 播放/停止命令”可远程启停车间广播”
音源切换MQTT 电台索引命令”可切换不同播放内容”
音量调节MQTT 音量值”可远程调节音量”
状态监控MQTT 状态发布”可实时查看设备状态”
元数据显示MQTT 曲目标题”可显示当前播放内容”

本节介绍了通过 MQTT 远程控制 ESP32 音频播放器:

  1. Topic 设计:使用 factory/broadcast/{command} 层级结构
  2. 控制命令:播放/停止、切换电台、音量调节、静音
  3. 状态发布:定期推送设备状态和流元数据
  4. 回调处理:MQTT 消息触发音频控制函数