prettyprint

2021年9月26日 星期日

使用NODE-RED Dashboard 以MQTT訊息驅動來控制MCU上的感應器或設備(Controlling MCU/Sensor/Device with Node-RED Dashboard via MQTT)

 本實驗使用Node-RED透過MQTT訊息傳送來控制遠端的MCU以便收集溫溼度與控制電燈開關,MQTT使用publish/subscribe方式來傳送訊息,因此本實驗的設計邏輯就以訊息驅動思維來設計。

一、使用元件:

  1. Raspberry Pi 3B+,安裝mosquitto broker and NODE-RED
  2. ESP8266(WeMos D1 Mini)
  3. DHT11
  4. 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與需完成的動作






根據上面的Flow以Node-RED來實現,最後完成的Flow如下圖。


手機上Node-RED Dashboard畫面


  • Node-RED詳細步驟如下影片所展示:


三、遠端設備線路圖:



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();

}

沒有留言:

張貼留言