本實驗使用Node-RED透過MQTT訊息傳送來控制遠端的MCU以便收集溫溼度與控制電燈開關,MQTT使用publish/subscribe方式來傳送訊息,因此本實驗的設計邏輯就以訊息驅動思維來設計。
一、使用元件:
- Raspberry Pi 3B+,安裝mosquitto broker and NODE-RED
- ESP8266(WeMos D1 Mini)
- DHT11
- LED x 1, NPN電晶體 x 1,LM393 x 1, 200歐姆 x 2, 4.7K歐姆 x 1
二、設計思維:
MQTT使用publish/subscribe方式來傳送訊息,設計邏輯就以訊息驅動思維來設計。本實驗最後目標是以Node-RED Dashboard來收集並展示遠端溫濕度資料與遠端控制電燈開關,首先定義完成目標所需要的Topics,再定義Publish/Subscribe Topics message flow以及兩端點須完成的動作,最後就能輕易使用Node-RED完成設計的需求。
- 定義系所需的Topics:
遠端有兩個元件: DHT11(Sensor)與電燈(LED Device)需要控制,另需要知道網路的狀態,因此定義出下列六個Topics與每個Topic的Payloads:
遠端設備狀態資料的更新可以用interrupt或polling方式,本實驗以polling方式來更新,因此ESP8266/Sensor/RefreshRate即為更新頻率的秒數。 - Node-RED Dashboard所需UI Nodes
- 定義端點間Topics Message flow與需完成的動作
三、遠端設備線路圖:
LM393 OPA當成電位比較器,用來檢測LED的狀態,當LED在ON時,LM393 pin 1輸出應為高電位,若低電位時,則LED是處於斷路狀態,因此publish Light/Status為FAILURE。
四、ESP8266端程式碼:
使用EspMQTTClient and DHTesp library。#include "EspMQTTClient.h"
#include "DHTesp.h" // Click here to get the library: http://librarymanager/All#DHTesp
#ifdef ESP32
#pragma message(THIS EXAMPLE IS FOR ESP8266 ONLY!)
#error Select ESP8266 board.
#endif
DHTesp dht;
#define LIGHTPIN D5
#define DHTPIN 12 //DHT11, D6 pin
#define LIGHT_STATUS_PIN D7
#define LIGHT_ON 1
#define LIGHT_OFF 2
#define LIGHT_FAILURE 3
byte bLightStatus=LIGHT_OFF;
byte bSwitchStatus=LIGHT_OFF;
int publishDelaySeconds=2;
///////////////////
EspMQTTClient client(
"your-ssid",
"your-pwd",
"192.168.1.1", // MQTT Broker server ip
"MQTTUsername", // Can be omitted if not needed
"MQTTPassword", // Can be omitted if not needed
"ESP8266Client", // Client name that uniquely identify your device
1883 // The MQTT port, default to 1883. this line can be omitted
);
byte checkLightStatus() {
digitalWrite(LIGHTPIN, HIGH);
delay(1000);
if (digitalRead(LIGHT_STATUS_PIN) == HIGH) {
bLightStatus=LIGHT_ON;
} else {
bLightStatus = LIGHT_FAILURE;
}
return bLightStatus;
}
void setup() {
//Serial.begin(115200);
pinMode(LIGHTPIN, OUTPUT);
pinMode(LIGHT_STATUS_PIN, INPUT_PULLUP);
checkLightStatus();
dht.setup(12, DHTesp::DHT11); // Connect DHT sensor to GPIO 17
client.enableLastWillMessage("ESP8266/Network/Status", "OFFLINE");
client.setMaxPacketSize(5120);
client.setKeepAlive(20);
client.enableMQTTPersistence();
digitalWrite(LIGHTPIN, LOW);
bLightStatus = LIGHT_OFF;
}
void onTopicMessageReceived(const String& topic, const String& msg)
{
if (topic == "ESP8266/Sensor/Light/Switch") { // switch on: turn on light and check light status
if (msg == "ON") {
bSwitchStatus=LIGHT_ON;
digitalWrite(LIGHTPIN, HIGH);
delay(10);
if (digitalRead(LIGHT_STATUS_PIN) == LOW) {
client.publish("ESP8266/Sensor/Light/Status", "FAILURE");
bLightStatus=LIGHT_FAILURE;
} else {
client.publish("ESP8266/Sensor/Light/Status", "ON");
bLightStatus= LIGHT_ON;
}
} else {
bSwitchStatus=LIGHT_OFF;
digitalWrite(LIGHTPIN, LOW);
bLightStatus= LIGHT_OFF;
client.publish("ESP8266/Sensor/Light/Status", "OFF");
}
}
if (topic == "ESP8266/Sensor/RefreshRate") {
publishDelaySeconds=msg.toInt();
}
}
void publishTempHumLS() {
char buff[100];
String DHTStatus=dht.getStatusString();
delay(dht.getMinimumSamplingPeriod());
if (DHTStatus == "OK") {
//float humidity = dht.getHumidity();
//float temperature = dht.getTemperature();
sprintf(buff, "{\"H\":%.1f,\"T\":%.1f}", dht.getHumidity(), dht.getTemperature());
client.publish("ESP8266/Sensor/DHT/Data", buff);
} else {
client.publish("ESP8266/Sensor/DHT/Status",DHTStatus); // DHT/Status不送出OK。
}
switch(bSwitchStatus) {
case LIGHT_ON:
if (digitalRead(LIGHT_STATUS_PIN) == LOW){
client.publish("ESP8266/Sensor/Light/Status", "FAILURE");
bLightStatus =LIGHT_FAILURE;
} else {
client.publish("ESP8266/Sensor/Light/Status", "ON");
bLightStatus =LIGHT_ON;
}
break;
case LIGHT_OFF:
client.publish("ESP8266/Sensor/Light/Status", "OFF");
break;
}
client.executeDelayed(publishDelaySeconds*1000, publishTempHumLS);
}
void onConnectionEstablished() {
client.subscribe("ESP8266/Sensor/Light/Switch", onTopicMessageReceived);
client.subscribe("ESP8266/Sensor/RefreshRate", onTopicMessageReceived);
client.publish("ESP8266/Network/Status", "CONNECTED");
switch(bLightStatus) {
case LIGHT_ON:
client.publish("ESP8266/Sensor/Light/Status", "ON");
break;
case LIGHT_OFF:
client.publish("ESP8266/Sensor/Light/Status", "OFF");
break;
case LIGHT_FAILURE:
client.publish("ESP8266/Sensor/Light/Status", "FAILURE");
break;
}
client.executeDelayed(1000, publishTempHumLS);
}
void loop() {
client.loop();
}









































