#include <WiFi.h>
#include <PubSubClient.h>
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
#include "DHT.h"
// --- 網路與 MQTT 設定 ---
const char* ssid = "Wokwi-GUEST";
const char* password = "";
const char* mqtt_server = "mqtt-dashboard.com";
const int mqtt_port = 1883;
// --- MQTT 主題設定 ---
const char* TOPIC_TEMP = "alex9ufo/temp"; // 映射至 EasyBuilder Pro LW-10
const char* TOPIC_HUMI = "alex9ufo/humi"; // 映射至 EasyBuilder Pro LW-12
// 控制主題 (LB-1 ~ LB-5)
const char* subTopics[5] = {
"alex9ufo/led1", // LB-1
"alex9ufo/led2", // LB-2
"alex9ufo/led3", // LB-3
"alex9ufo/led4", // LB-4
"alex9ufo/allled" // LB-5 (全開/全關)
};
// 狀態回傳主題 (對應 EasyBuilder Pro 指示燈位址)
const char* pubTopics[5] = {
"alex9ufo/led1status", // 對應 LB-11 (LED 1 狀態)
"alex9ufo/led2status", // 對應 LB-12 (LED 2 狀態)
"alex9ufo/led3status", // 對應 LB-13 (LED 3 狀態)
"alex9ufo/led4status", // 對應 LB-14 (LED 4 狀態)
"alex9ufo/allledstatus" // 對應 LB-15 / LB-5 (全區狀態)
};
// --- 硬體腳位定義 ---
#define DHTPIN 4
#define DHTTYPE DHT22
const int relayPins[4] = {27, 14, 12, 13}; // Relay 1~4 (GPIO 27, 14, 12, 13)
// --- 元件初始化 ---
DHT dht(DHTPIN, DHTTYPE);
LiquidCrystal_I2C lcd(0x27, 16, 2);
WiFiClient espClient;
PubSubClient client(espClient);
unsigned long lastMsg = 0;
float currentTemp = 0.0;
float currentHumi = 0.0;
// LCD 更新顯示函式
void updateLCD() {
lcd.setCursor(0, 0);
lcd.print("Temp: ");
lcd.print(currentTemp, 1);
lcd.print((char)223); // 顯示 ° 符號
lcd.print("C ");
lcd.setCursor(0, 1);
lcd.print("Humi: ");
lcd.print(currentHumi, 1);
lcd.print(" % ");
}
// Wi-Fi 連線設定
void setup_wifi() {
delay(10);
Serial.println();
Serial.print("[WiFi] Connecting to ");
Serial.println(ssid);
WiFi.mode(WIFI_STA);
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println("\n[WiFi] WiFi connected!");
Serial.print("[WiFi] IP address: ");
Serial.println(WiFi.localIP());
}
// 發行無 [ ] 的 JSON 狀態給 EasyBuilder Pro 位元指示燈
void publishStatus(int index, bool isOn) {
// 建立無 [] 符號的標準 JSON Payload
String payload = isOn ? "{\"value\":true}" : "{\"value\":false}";
client.publish(pubTopics[index], payload.c_str());
Serial.print("[MQTT TX] Published JSON status '");
Serial.print(payload);
Serial.print("' to topic: ");
Serial.println(pubTopics[index]);
}
// MQTT 接收控制指令處理 (Callback)
void callback(char* topic, byte* payload, unsigned int length) {
String message = "";
for (int i = 0; i < length; i++) {
message += (char)payload[i];
}
message.trim();
message.toLowerCase();
Serial.println("----------------------------------------");
Serial.print("[MQTT RX] Topic: ");
Serial.println(topic);
Serial.print("[MQTT RX] Payload: ");
Serial.println(message);
// 判斷接收到的值是 true (開啟) 還是 false (關閉)
bool targetState = (message.indexOf("true") != -1 || message == "1" || message == "on");
// 1. 全區控制 (LB-5 -> alex9ufo/allled)
if (String(topic) == subTopics[4]) {
for (int i = 0; i < 4; i++) {
digitalWrite(relayPins[i], targetState ? HIGH : LOW);
publishStatus(i, targetState); // 更新各個別燈號 (alex9ufo/led1status~4status)
}
// 回傳全區狀態 (alex9ufo/allledstatus)
publishStatus(4, targetState);
Serial.print("[Hardware] ALL LEDs set to: ");
Serial.println(targetState ? "ON" : "OFF");
}
// 2. 個別控制 (LB-1~4 -> alex9ufo/led1~4)
else {
for (int i = 0; i < 4; i++) {
if (String(topic) == subTopics[i]) {
digitalWrite(relayPins[i], targetState ? HIGH : LOW);
Serial.print("[Hardware] LED ");
Serial.print(i + 1);
Serial.print(" (GPIO ");
Serial.print(relayPins[i]);
Serial.print(") set to: ");
Serial.println(targetState ? "ON" : "OFF");
// 回傳該路 JSON 狀態給 EasyBuilder Pro
publishStatus(i, targetState);
break;
}
}
}
Serial.println("----------------------------------------");
}
// MQTT 自動重連機制
void reconnect() {
while (!client.connected()) {
Serial.print("[MQTT] Attempting MQTT connection to ");
Serial.print(mqtt_server);
Serial.print("...");
String clientId = "ESP32Client-";
clientId += String(random(0xffff), HEX);
if (client.connect(clientId.c_str())) {
Serial.println(" connected!");
// 訂閱 LB-1 ~ LB-5 的所有控制主題
for (int i = 0; i < 5; i++) {
client.subscribe(subTopics[i]);
Serial.print("[MQTT] Subscribed to: ");
Serial.println(subTopics[i]);
}
} else {
Serial.print(" failed, rc=");
Serial.print(client.state());
Serial.println(" try again in 5 seconds");
delay(5000);
}
}
}
void setup() {
Serial.begin(115200);
// 初始化 LCD (GPIO 21=SDA, GPIO 22=SCL)
Wire.begin(21, 22);
lcd.init();
lcd.backlight();
lcd.setCursor(0, 0);
lcd.print("Connecting WiFi...");
// 初始化繼電器腳位
for (int i = 0; i < 4; i++) {
pinMode(relayPins[i], OUTPUT);
digitalWrite(relayPins[i], LOW);
}
// 初始化 DHT 感測器與 Wi-Fi
dht.begin();
setup_wifi();
lcd.clear();
lcd.print("WiFi Connected!");
delay(1000);
lcd.clear();
client.setServer(mqtt_server, mqtt_port);
client.setCallback(callback);
}
void loop() {
if (!client.connected()) {
reconnect();
}
client.loop();
// 每 2 秒讀取感測器、更新 LCD 與發行 JSON 格式 MQTT 數據
unsigned long now = millis();
if (now - lastMsg > 2000) {
lastMsg = now;
float h = dht.readHumidity();
float t = dht.readTemperature();
if (!isnan(h) && !isnan(t)) {
currentTemp = t;
currentHumi = h;
// 打包成不帶 [] 的 JSON 格式字串
String tempJson = "{\"value\":" + String(t, 2) + "}";
String humiJson = "{\"value\":" + String(h, 2) + "}";
// 發行至 MQTT (對應 EasyBuilder Pro LW-10, LW-12)
client.publish(TOPIC_TEMP, tempJson.c_str());
client.publish(TOPIC_HUMI, humiJson.c_str());
// 印出發行紀錄至 Serial Monitor
Serial.print("[MQTT TX JSON] ");
Serial.print(TOPIC_TEMP);
Serial.print(" -> ");
Serial.println(tempJson);
Serial.print("[MQTT TX JSON] ");
Serial.print(TOPIC_HUMI);
Serial.print(" -> ");
Serial.println(humiJson);
// 更新 16x2 LCD
updateLCD();
} else {
Serial.println("[DHT22 ERROR] Failed to read from DHT sensor!");
}
}
}
沒有留言:
張貼留言