本實驗使用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(); }