#include <WiFi.h>
#include <PubSubClient.h>
#include <DHT.h>
#include <DHT_U.h> // 需要同時包含 DHT_U.h
// --- Wi-Fi 設定 ---
const char* ssid = "Wokwi-GUEST";
const char* password = "";
// --- MQTT 設定 ---
const char* mqtt_server = "broker.mqttgo.io"; // 或 "mqtt.eclipseprojects.io"
const int mqtt_port = 1883;
const char* mqtt_client_id = "ESP32_Wokwi_Client";
// MQTT 主題
const char* mqtt_topic_led_control = "wokwi/led/control";
const char* mqtt_topic_temperature = "wokwi/dht/temperature";
const char* mqtt_topic_humidity = "wokwi/dht/humidity";
WiFiClient espClient;
PubSubClient client(espClient);
// --- LED 設定 ---
const int ledPin = 2; // 連接到 GPIO 2
enum LedMode { ON, OFF, FLASH, TIMER };
volatile LedMode currentLedMode = OFF;
volatile unsigned long timerStartTime = 0;
volatile bool ledState = false; // 用於閃爍模式
// --- DHT22 設定 ---
#define DHTPIN 4 // 連接到 GPIO 4
#define DHTTYPE DHT22 // DHT 22 (AM2302), AM2321
DHT dht(DHTPIN, DHTTYPE);
// --- 任務句柄 ---
TaskHandle_t TaskLEDControl = NULL;
TaskHandle_t TaskDHTSensor = NULL;
// --- 函式宣告 ---
void setup_wifi();
void reconnect_mqtt();
void callback(char* topic, byte* payload, unsigned int length);
void ledControlTask(void *pvParameters);
void dhtSensorTask(void *pvParameters);
void setup() {
Serial.begin(115200);
pinMode(ledPin, OUTPUT);
digitalWrite(ledPin, LOW); // 確保初始是關閉的
setup_wifi();
client.setServer(mqtt_server, mqtt_port);
client.setCallback(callback);
dht.begin(); // 初始化 DHT 感測器
// 創建 LED 控制任務,運行在 Core 0
xTaskCreatePinnedToCore(
ledControlTask, /* 任務函式 */
"LED Control", /* 任務名稱 */
2048, /* 堆疊大小 (字節) */
NULL, /* 任務參數 */
1, /* 任務優先級 */
&TaskLEDControl, /* 任務句柄 */
0 /* 運行在 Core 0 */
);
// 創建 DHT 感測器任務,運行在 Core 1
xTaskCreatePinnedToCore(
dhtSensorTask, /* 任務函式 */
"DHT Sensor", /* 任務名稱 */
4096, /* 堆疊大小 (字節) */
NULL, /* 任務參數 */
1, /* 任務優先級 */
&TaskDHTSensor, /* 任務句柄 */
1 /* 運行在 Core 1 */
);
}
void loop() {
// 主循環中只負責維持 MQTT 連線
if (!client.connected()) {
reconnect_mqtt();
}
client.loop(); // 處理 MQTT 訊息
delay(10); // 短暫延遲,避免佔用太多 CPU
}
// --- Wi-Fi 連線函式 ---
void setup_wifi() {
delay(10);
Serial.println();
Serial.print("Connecting to ");
Serial.println(ssid);
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println("");
Serial.println("WiFi connected");
Serial.print("IP address: ");
Serial.println(WiFi.localIP());
}
// --- MQTT 重連函式 ---
void reconnect_mqtt() {
while (!client.connected()) {
Serial.print("Attempting MQTT connection...");
// 嘗試連線
if (client.connect(mqtt_client_id)) {
Serial.println("connected");
// 訂閱 LED 控制主題
client.subscribe(mqtt_topic_led_control);
Serial.print("Subscribed to: ");
Serial.println(mqtt_topic_led_control);
} else {
Serial.print("failed, rc=");
Serial.print(client.state());
Serial.println(" try again in 5 seconds");
// 等待 5 秒後重試
delay(5000);
}
}
}
// --- MQTT 訊息回調函式 ---
void callback(char* topic, byte* payload, unsigned int length) {
Serial.print("Message arrived [");
Serial.print(topic);
Serial.print("] ");
String message = "";
for (int i = 0; i < length; i++) {
message += (char)payload[i];
}
Serial.println(message);
if (String(topic) == mqtt_topic_led_control) {
if (message == "on") {
currentLedMode = ON;
digitalWrite(ledPin, HIGH);
Serial.println("LED Mode: ON");
} else if (message == "off") {
currentLedMode = OFF;
digitalWrite(ledPin, LOW);
Serial.println("LED Mode: OFF");
} else if (message == "flash") {
currentLedMode = FLASH;
Serial.println("LED Mode: FLASH");
} else if (message == "timer") {
currentLedMode = TIMER;
digitalWrite(ledPin, HIGH); // 定時模式開始時先開啟 LED
timerStartTime = millis();
Serial.println("LED Mode: TIMER (10s)");
} else {
Serial.println("Unknown LED command.");
}
}
}
// --- LED 控制任務 (運行在 Core 0) ---
void ledControlTask(void *pvParameters) {
(void) pvParameters; // 避免編譯器警告
for (;;) { // 無限循環
switch (currentLedMode) {
case ON:
// LED 保持亮著,由 callback 函式設置
break;
case OFF:
// LED 保持熄滅,由 callback 函式設置
break;
case FLASH:
digitalWrite(ledPin, ledState);
ledState = !ledState;
vTaskDelay(pdMS_TO_TICKS(500)); // 每 500ms 改變一次狀態
break;
case TIMER:
if (millis() - timerStartTime >= 10000) { // 10 秒
digitalWrite(ledPin, LOW);
currentLedMode = OFF; // 定時結束後轉為 OFF 模式
Serial.println("LED Timer finished. LED OFF.");
}
vTaskDelay(pdMS_TO_TICKS(10)); // 短暫延遲
break;
default:
digitalWrite(ledPin, LOW); // 預設為關閉
break;
}
vTaskDelay(pdMS_TO_TICKS(10)); // 短暫延遲,讓其他任務有機會執行
}
}
// --- DHT 感測器任務 (運行在 Core 1) ---
void dhtSensorTask(void *pvParameters) {
(void) pvParameters; // 避免編譯器警告
for (;;) { // 無限循環
delay(2000); // 每 2 秒讀取一次數據,避免頻繁讀取導致錯誤
float h = dht.readHumidity();
float t = dht.readTemperature(); // 讀取攝氏溫度
// 檢查是否讀取失敗,如果是則嘗試重讀
if (isnan(h) || isnan(t)) {
Serial.println(F("Failed to read from DHT sensor!"));
} else {
Serial.print(F("Humidity: "));
Serial.print(h);
Serial.print(F("% Temperature: "));
Serial.print(t);
Serial.println(F("°C"));
// 發布溫度
char tempString[8];
dtostrf(t, 4, 2, tempString); // 浮點數轉字串
client.publish(mqtt_topic_temperature, tempString);
// 發布濕度
char humString[8];
dtostrf(h, 4, 2, humString); // 浮點數轉字串
delay(250);
client.publish(mqtt_topic_humidity, humString);
}
vTaskDelay(pdMS_TO_TICKS(5000)); // 每 5 秒執行一次,避免 MQTT 發布過於頻繁
}
}
Node-Red程式
[
{
"id": "131047bc20364361",
"type": "tab",
"label": "流程2",
"disabled": false,
"info": "",
"env": []
},
{
"id": "164767289eb7c78d",
"type": "ui_button",
"z": "131047bc20364361",
"name": "ON",
"group": "539f4088006e8909",
"order": 1,
"width": 0,
"height": 0,
"passthru": false,
"label": "ON",
"tooltip": "",
"color": "",
"bgcolor": "",
"className": "",
"icon": "",
"payload": "on",
"payloadType": "str",
"topic": "wokwi/led/control",
"topicType": "str",
"x": 110,
"y": 20,
"wires": [
[
"3c076dc1c02bd5a8",
"7ebc9ae2f8f61bdf"
]
]
},
{
"id": "3c076dc1c02bd5a8",
"type": "mqtt out",
"z": "131047bc20364361",
"name": "LED Control",
"topic": "wokwi/led/control",
"qos": "0",
"retain": "false",
"respTopic": "",
"contentType": "",
"userProps": "",
"correl": "",
"expiry": "",
"broker": "192c2b20bef1e71a",
"x": 390,
"y": 120,
"wires": []
},
{
"id": "1a607adac2bd1c5f",
"type": "ui_button",
"z": "131047bc20364361",
"name": "OFF",
"group": "539f4088006e8909",
"order": 2,
"width": 0,
"height": 0,
"passthru": false,
"label": "OFF",
"tooltip": "",
"color": "",
"bgcolor": "",
"className": "",
"icon": "",
"payload": "off",
"payloadType": "str",
"topic": "wokwi/led/control",
"topicType": "str",
"x": 110,
"y": 80,
"wires": [
[
"3c076dc1c02bd5a8",
"7ebc9ae2f8f61bdf"
]
]
},
{
"id": "20c5d848d874407c",
"type": "ui_button",
"z": "131047bc20364361",
"name": "FLASH",
"group": "539f4088006e8909",
"order": 3,
"width": 0,
"height": 0,
"passthru": false,
"label": "FLASH",
"tooltip": "",
"color": "",
"bgcolor": "",
"className": "",
"icon": "",
"payload": "flash",
"payloadType": "str",
"topic": "wokwi/led/control",
"topicType": "str",
"x": 120,
"y": 140,
"wires": [
[
"3c076dc1c02bd5a8",
"7ebc9ae2f8f61bdf"
]
]
},
{
"id": "5886d355af86e15f",
"type": "ui_button",
"z": "131047bc20364361",
"name": "TIMER (10s)",
"group": "539f4088006e8909",
"order": 4,
"width": 0,
"height": 0,
"passthru": false,
"label": "TIMER (10s)",
"tooltip": "",
"color": "",
"bgcolor": "",
"className": "",
"icon": "",
"payload": "timer",
"payloadType": "str",
"topic": "wokwi/led/control",
"topicType": "str",
"x": 130,
"y": 200,
"wires": [
[
"3c076dc1c02bd5a8",
"7ebc9ae2f8f61bdf"
]
]
},
{
"id": "2fad22cc617b2e3a",
"type": "mqtt in",
"z": "131047bc20364361",
"name": "Temperature",
"topic": "wokwi/dht/temperature",
"qos": "1",
"datatype": "utf8",
"broker": "192c2b20bef1e71a",
"nl": false,
"rap": true,
"rh": 0,
"inputs": 0,
"x": 130,
"y": 340,
"wires": [
[
"4096f840a6a15575",
"0649251b181a8602"
]
]
},
{
"id": "66b7cccf837503a8",
"type": "mqtt in",
"z": "131047bc20364361",
"name": "Humidity",
"topic": "wokwi/dht/humidity",
"qos": "1",
"datatype": "utf8",
"broker": "192c2b20bef1e71a",
"nl": false,
"rap": true,
"rh": 0,
"inputs": 0,
"x": 110,
"y": 420,
"wires": [
[
"6fb678097cc1214a",
"05a8523190b6f301"
]
]
},
{
"id": "c3bb780153f67b32",
"type": "ui_chart",
"z": "131047bc20364361",
"name": "Temperature Chart",
"group": "182b827e8d2e8b0b",
"order": 1,
"width": 5,
"height": 4,
"label": "Temperature (°C)",
"chartType": "line",
"legend": "true",
"xformat": "HH:mm:ss",
"interpolate": "step",
"nodata": "",
"dot": false,
"ymin": "-40",
"ymax": "80",
"removeOlder": "10",
"removeOlderPoints": "",
"removeOlderUnit": "1",
"cutout": "",
"useOneColor": false,
"useUTC": false,
"colors": [
"#ff0000",
"#aec7e8",
"#ff7f0e",
"#2ca02c",
"#98df8a",
"#d62728",
"#ff9896",
"#9467bd",
"#c5b0d5"
],
"outputs": 1,
"useDifferentColor": false,
"className": "",
"x": 540,
"y": 360,
"wires": [
[]
]
},
{
"id": "a3022323c38ba706",
"type": "ui_chart",
"z": "131047bc20364361",
"name": "Humidity Chart",
"group": "182b827e8d2e8b0b",
"order": 2,
"width": 5,
"height": 4,
"label": "Humidity (%)",
"chartType": "line",
"legend": "true",
"xformat": "HH:mm:ss",
"interpolate": "step",
"nodata": "",
"dot": false,
"ymin": "0",
"ymax": "100",
"removeOlder": "10",
"removeOlderPoints": "",
"removeOlderUnit": "1",
"cutout": "",
"useOneColor": false,
"useUTC": false,
"colors": [
"#1f77b4",
"#aec7e8",
"#ff7f0e",
"#2ca02c",
"#98df8a",
"#d62728",
"#ff9896",
"#9467bd",
"#c5b0d5"
],
"outputs": 1,
"useDifferentColor": false,
"className": "",
"x": 530,
"y": 420,
"wires": [
[]
]
},
{
"id": "0649251b181a8602",
"type": "debug",
"z": "131047bc20364361",
"name": "Debug Temperature",
"active": true,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "payload",
"targetType": "msg",
"statusVal": "",
"statusType": "auto",
"x": 310,
"y": 300,
"wires": []
},
{
"id": "05a8523190b6f301",
"type": "debug",
"z": "131047bc20364361",
"name": "Debug Humidity",
"active": true,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "payload",
"targetType": "msg",
"statusVal": "",
"statusType": "auto",
"x": 320,
"y": 480,
"wires": []
},
{
"id": "4096f840a6a15575",
"type": "function",
"z": "131047bc20364361",
"name": "To Number (Temp)",
"func": "msg.payload = parseFloat(msg.payload);\nreturn msg;",
"outputs": 1,
"timeout": "",
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 330,
"y": 360,
"wires": [
[
"c3bb780153f67b32",
"f89803acf4bcc755"
]
]
},
{
"id": "6fb678097cc1214a",
"type": "function",
"z": "131047bc20364361",
"name": "To Number (Hum)",
"func": "msg.payload = parseFloat(msg.payload);\nreturn msg;",
"outputs": 1,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 320,
"y": 420,
"wires": [
[
"a3022323c38ba706",
"0c339df3ab928ff6"
]
]
},
{
"id": "f89803acf4bcc755",
"type": "ui_gauge",
"z": "131047bc20364361",
"name": "",
"group": "182b827e8d2e8b0b",
"order": 3,
"width": 5,
"height": 4,
"gtype": "gage",
"title": "gauge",
"label": "℃",
"format": "{{value}}",
"min": "-40",
"max": "80",
"colors": [
"#00b500",
"#e6e600",
"#ca3838"
],
"seg1": "25",
"seg2": "40",
"diff": false,
"className": "",
"x": 510,
"y": 320,
"wires": []
},
{
"id": "0c339df3ab928ff6",
"type": "ui_gauge",
"z": "131047bc20364361",
"name": "",
"group": "182b827e8d2e8b0b",
"order": 4,
"width": 5,
"height": 4,
"gtype": "gage",
"title": "gauge",
"label": "%",
"format": "{{value}}",
"min": 0,
"max": "100",
"colors": [
"#00b500",
"#e6e600",
"#ca3838"
],
"seg1": "65",
"seg2": "80",
"diff": false,
"className": "",
"x": 510,
"y": 460,
"wires": []
},
{
"id": "7ebc9ae2f8f61bdf",
"type": "debug",
"z": "131047bc20364361",
"name": "debug ",
"active": true,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "payload",
"targetType": "msg",
"statusVal": "",
"statusType": "auto",
"x": 410,
"y": 80,
"wires": []
},
{
"id": "539f4088006e8909",
"type": "ui_group",
"name": "LED Control",
"tab": "31b64e525281561f",
"order": 1,
"disp": true,
"width": 3,
"collapse": false,
"className": ""
},
{
"id": "192c2b20bef1e71a",
"type": "mqtt-broker",
"name": "mqttgo",
"broker": "broker.mqttgo.io",
"port": "1883",
"clientid": "",
"autoConnect": true,
"usetls": false,
"protocolVersion": "4",
"keepalive": "60",
"cleansession": true,
"autoUnsubscribe": true,
"birthTopic": "",
"birthQos": "0",
"birthRetain": "false",
"birthPayload": "",
"birthMsg": {},
"closeTopic": "",
"closeQos": "0",
"closeRetain": "false",
"closePayload": "",
"closeMsg": {},
"willTopic": "",
"willQos": "0",
"willRetain": "false",
"willPayload": "",
"willMsg": {},
"userProps": "",
"sessionExpiry": ""
},
{
"id": "182b827e8d2e8b0b",
"type": "ui_group",
"name": "DHT22 Sensor Data",
"tab": "31b64e525281561f",
"order": 2,
"disp": true,
"width": 10,
"collapse": false,
"className": ""
},
{
"id": "31b64e525281561f",
"type": "ui_tab",
"name": "ESP32 Control",
"icon": "dashboard",
"disabled": false,
"hidden": false
}
]
沒有留言:
張貼留言