#include <WiFi.h>
#include <PubSubClient.h>
// WiFi 與 MQTT 設定
const char* ssid = "Wokwi-GUEST";
const char* password = "";
const char* mqtt_server = "broker.mqtt-dashboard.com";
WiFiClient espClient;
PubSubClient client(espClient);
// 引腳定義
const int ledPin = 2;
// 運行模式列舉
enum LedMode { MODE_OFF, MODE_ON, MODE_FLASH, MODE_TIMER };
LedMode currentMode = MODE_OFF;
// 時間控制變數
unsigned long previousMillis = 0;
const long flashInterval = 300; // 閃爍間隔 300ms
unsigned long timerStartMillis = 0;
const long timerDuration = 5000; // 5秒定時
bool flashState = LOW;
// 發送 MQTT JSON 狀態訊息:指定的 activeMode 發行 true,其餘 3 個發行 false
void updateAllStatusTopics(LedMode activeMode) {
currentMode = activeMode;
const char* topics[4] = {
"alex9ufo/led1status", // ON 狀態 (MODE_ON = 1)
"alex9ufo/led2status", // OFF 狀態 (MODE_OFF = 0)
"alex9ufo/led3status", // FLASH 狀態 (MODE_FLASH = 2)
"alex9ufo/led4status" // TIMER 狀態 (MODE_TIMER = 3)
};
LedMode modes[4] = { MODE_ON, MODE_OFF, MODE_FLASH, MODE_TIMER };
Serial.println("----------------------------------------");
Serial.print("[狀態更新] 當前啟用模式: ");
switch(activeMode) {
case MODE_ON: Serial.println("ON (常亮)"); break;
case MODE_OFF: Serial.println("OFF (關閉)"); break;
case MODE_FLASH: Serial.println("FLASH (閃爍)"); break;
case MODE_TIMER: Serial.println("TIMER (5秒計時)"); break;
}
// 循環發行 4 個主題的狀態
for (int i = 0; i < 4; i++) {
if (modes[i] == activeMode) {
client.publish(topics[i], "{\"value\":true}");
Serial.print("[PUB 發行] ");
Serial.print(topics[i]);
Serial.println(" -> {\"value\":true}");
} else {
client.publish(topics[i], "{\"value\":false}");
Serial.print("[PUB 發行] ");
Serial.print(topics[i]);
Serial.println(" -> {\"value\":false}");
}
}
Serial.println("----------------------------------------");
}
// 解析 JSON 字串是否包含 "value": true 或 "value":true
bool parseJsonValueIsTrue(String msg) {
msg.replace(" ", "");
msg.replace("\n", "");
msg.replace("\r", "");
msg.toLowerCase();
return (msg.indexOf("\"value\":true") != -1);
}
// MQTT 訊息接收回調函數
void callback(char* topic, byte* payload, unsigned int length) {
String message = "";
for (int i = 0; i < length; i++) {
message += (char)payload[i];
}
String strTopic = String(topic);
Serial.println();
Serial.print("[SUB 收到訂閱訊息] Topic: ");
Serial.print(strTopic);
Serial.print(" | Payload: ");
Serial.println(message);
// 判斷 value 是否為 true,若為 false 或解析失敗則忽略不處理
if (!parseJsonValueIsTrue(message)) {
Serial.println(">> value 為 false 或格式無效,忽略不發行。");
return;
}
// 接收到 true 命令時先重置 LED 引腳
digitalWrite(ledPin, LOW);
// 切換模式並一次發行 1 個 true 與 3 個 false
if (strTopic == "alex9ufo/led1") {
digitalWrite(ledPin, HIGH);
updateAllStatusTopics(MODE_ON);
}
else if (strTopic == "alex9ufo/led2") {
digitalWrite(ledPin, LOW);
updateAllStatusTopics(MODE_OFF);
}
else if (strTopic == "alex9ufo/led3") {
updateAllStatusTopics(MODE_FLASH);
}
else if (strTopic == "alex9ufo/led4") {
digitalWrite(ledPin, HIGH);
timerStartMillis = millis();
updateAllStatusTopics(MODE_TIMER);
}
}
void reconnect() {
while (!client.connected()) {
Serial.print("正在嘗試連接 MQTT Broker: ");
Serial.println(mqtt_server);
String clientId = "ESP32Client-" + String(random(0xffff), HEX);
if (client.connect(clientId.c_str())) {
Serial.println(">> MQTT 連線成功!");
client.subscribe("alex9ufo/led1");
client.subscribe("alex9ufo/led2");
client.subscribe("alex9ufo/led3");
client.subscribe("alex9ufo/led4");
Serial.println(">> 已成功訂閱主題: alex9ufo/led1 ~ led4");
// 連線成功時預設進入 OFF 狀態,發行 led2status=true,其餘=false
digitalWrite(ledPin, LOW);
updateAllStatusTopics(MODE_OFF);
} else {
Serial.print(">> 連線失敗, rc=");
Serial.print(client.state());
Serial.println(" 5 秒後重試...");
delay(5000);
}
}
}
void setup() {
Serial.begin(115200);
delay(500);
Serial.println("\n=== ESP32 MQTT LED 控制系統 (互斥狀態發行模式) ===");
pinMode(ledPin, OUTPUT);
digitalWrite(ledPin, LOW);
Serial.print("正在連接 WiFi: ");
Serial.println(ssid);
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println("\n>> WiFi 連線成功!");
Serial.print(">> ESP32 IP 位址: ");
Serial.println(WiFi.localIP());
client.setServer(mqtt_server, 1883);
client.setCallback(callback);
}
void loop() {
if (!client.connected()) {
reconnect();
}
client.loop();
unsigned long currentMillis = millis();
// 1. FLASH 閃爍模式處理
if (currentMode == MODE_FLASH) {
if (currentMillis - previousMillis >= flashInterval) {
previousMillis = currentMillis;
flashState = !flashState;
digitalWrite(ledPin, flashState);
}
}
// 2. TIMER 定時 5 秒模式處理
if (currentMode == MODE_TIMER) {
if (currentMillis - timerStartMillis >= timerDuration) {
Serial.println("\n[TIMER 倒數結束] 5 秒時間到,自動關閉並切換為 OFF 狀態");
digitalWrite(ledPin, LOW);
updateAllStatusTopics(MODE_OFF); // 時間到後自動切換為 OFF,更新狀態
}
}
}
沒有留言:
張貼留言