文章程式碼顯示

2018年7月13日 星期五

一起學 Python 113 : Rapsberry pi 與 NodeMCU (ESP8266) 溝通 基於 MQTT

本文將敘述如何在 Raspberry pi 上搭建 MQTT Borker(轉送訊息者)

並讓 Raspberry pi 與 NodeMCU 做為發佈者(Publisher)或訂閱者(Subscriber)
雙向操控(或告知) NodeMCU 上的 LED 燈

MQTT 在訊息傳遞的運作模式可參考此文,簡單的來說就是在一個架構中會存在三種角色

1. Broker(轉送訊息者)
2. 發佈者(Publisher)
3. 訂閱者(Subscriber)

以及一個 "基於目錄架構的主題(topic)"

作為一個訂閱者可以使用

mosquitto_sub -t "/leds/esp8266"

來訂閱一個主題,上述的語句代表讓 Rpi 作為一個訂閱者,訂閱的主題為目錄 leds 下的 esp8266

mosquitto_pub -t "/leds/esp8266" -m "TOOGLE"

作為一個發佈者可以使用

mosquitto_pub -t "/leds/esp8266" -m "TOOGLE"

意謂著將訊息 TOOGLE 發佈到 leds 下的 esp8266 主題

這邊只是先展示關係而已,詳細如何使用還得先建立 MQTT 服務才行,見下文

-------------------------------------------------------------------------------------------

首先 Rpi 的部份要先進行 MQTT 服務的搭建,可參考此文此文以及此文

sudo wget http://repo.mosquitto.org/debian/mosquitto-repo.gpg.key
sudo apt-key add mosquitto-repo.gpg.key
cd /etc/apt/sources.list.d/
sudo wget http://repo.mosquitto.org/debian/mosquitto-wheezy.list
sudo apt-get update

Install Mosquitto Broker
sudo apt-get install mosquitto

Install Mosquitto Client
sudo apt-get install mosquitto-clients

查看 MQTT 狀態

systemctl status mosquitto.service


圖中的綠色部分 active(rinning) 就表示 Rpi 上的 MQTT 服務已經啟動

順帶一提, pi@WYJ-raspi 之中的 'WYJ-raspi' 為你在 Rpi 設的 Host name ,先記起來後面會用到

Hint : 若想在 Python 上面使用 MQTT 服務可以輸入以下

sudo pip install paho-mqtt

但此部分在本文中並不會使用到,可以略過

當我們建立起來 MQTT 的服務之後

首先我們試試看使用 Rpi 來訂閱一個主題

mosquitto_sub -t "/leds/esp8266"

上述的語句代表讓 Rpi 作為一個訂閱者,訂閱的主題為目錄 leds 下的 esp8266

此時你的終端機會卡住,因為它正在聆聽這個主題的最新發佈訊息

接著我們開啟一個新的終端機,同樣進入 Rpi 後輸入

mosquitto_pub -t "/leds/esp8266" -m "TOGGLE"

意謂著將訊息 TOOGLE 發佈到 leds 下的 esp8266 主題

對照兩個終端機的畫面可以看到如下圖


發布訊息 TOOGLE 到 leds 下的 esp8266 主題



訂閱者這邊接收到 TOOGLE 訊息




Raspberry pi 這部分就暫時完成了,將終端機介面留著,拿出 NodeMCU

首先在 Arduino IDE  安裝三個 Library





硬體接線的部分,在 NodeMCU 的 D3 腳位接上 LED ; D2 腳位接上一個按鈕(低位觸發)
圖我就不給了,很簡單的接線

NodeMCU 程式碼的部分為以下

#include "ESP8266WIFI.h"
#include "Adafruit_MQTT.h"
#include "Adafruit_MQTT_Client.h"

// **************** 程式碼修改自 **********************
// http://www.instructables.com/id/Raspberry-Pi-Talking-to-ESP8266-Using-MQTT/

/************************* WiFi Access Point *********************************/ 
#define WLAN_SSID       "WiFi帳號" 
#define WLAN_PASS       "WiFi密碼" 

// 若你的 NodeMCU 與 Rpi 同個網域,可以使用內網的方式連到 Rpi 的 MQTT Broker
#define MQTT_SERVER      "192.168.0.105" // MQTT Broker 所在的 ip 位置
#define MQTT_PORT         1883 // mosquitto 預設的 port 1883

// 若 NodeMCU 與 Rpi 不同網域,則可以將 Rpi 網域的分享器設置虛擬伺服器
// 從外部的某個 port 連到 Rpi 網域的內部 1883 port 
//#define MQTT_SERVER      "實體ip 或網域名稱"
//#define MQTT_PORT        外部port          
         
#define MQTT_USERNAME "" // 預設不需使用帳號密碼來登入 MQTT
#define MQTT_PASSWORD "" 

#define LED_PIN     D3               
#define BUTTON_PIN  D2           

uint32_t x=0;   
  
void MQTT_connect();

/************ Global State ******************/ 
WiFiClient client;  // Create an  WiFiClient class

// Setup the MQTT client class by passing in the WiFi client and MQTT server and login details. 
Adafruit_MQTT_Client mqtt(&client, MQTT_SERVER, MQTT_PORT, MQTT_USERNAME, MQTT_PASSWORD); 

// 實體化esp8266_led_P 用以對 '/leds/esp8266' 發布訊息
Adafruit_MQTT_Publish esp8266_led_P = Adafruit_MQTT_Publish(&mqtt, MQTT_USERNAME "/leds/esp8266"); 

// 實體化 esp8266_led 用以對 '/leds/esp8266' 接收訊息
Adafruit_MQTT_Subscribe esp8266_led = Adafruit_MQTT_Subscribe(&mqtt, MQTT_USERNAME "/leds/esp8266"); 

/*************************** Sketch Code ************************************/ 
// ===== SETUP =====
// =================
void setup() { 
  Serial.begin(115200); 
  delay(10); 
  pinMode(LED_PIN, OUTPUT); 
  digitalWrite(LED_PIN, LOW);
  pinMode(BUTTON_PIN, INPUT); 
  Serial.println(); Serial.println(); 
  Serial.print("Connecting to "); 
  Serial.println(WLAN_SSID); 
  WiFi.begin(WLAN_SSID, WLAN_PASS); 
   while (WiFi.status() != WL_CONNECTED) { 
     delay(500); 
     Serial.print("."); 
   } 
   Serial.println(); 
   Serial.println("WiFi connected"); 
   Serial.println("Local IP address: "); Serial.println(WiFi.localIP()); 
   
   // Setup MQTT subscription for esp8266_led. 
   mqtt.subscribe(&esp8266_led); 
} 

// ===== LOOP =====
// ================
void loop() { 
  // 讀取當前 BUTTON 狀態
  int button_first = digitalRead(BUTTON_PIN); 
 
  // 不斷確定有連接到 MQTT Server 若斷線則自動重連 
  MQTT_connect(); 
 
  // 聆聽各種訂閱的主題
  Adafruit_MQTT_Subscribe *subscription; 
  while ((subscription = mqtt.readSubscription())) { 
    if (subscription == &esp8266_led) { //如果有來自 '/leds/esp8266' 的訊息
      char *message = (char *)esp8266_led.lastread; //抓取訊息
      Serial.print("Got: "); 
      Serial.println(message); // 印出接收到的訊息
      
      // 藉由訊息,來使 LED 做相應的動作
      if (strncmp(message, "ON", 2) == 0) { 
        digitalWrite(LED_PIN, HIGH); 
      } 
      else if (strncmp(message, "OFF", 3) == 0) { 
        digitalWrite(LED_PIN, LOW); 
      } 
      else if (strncmp(message, "TOGGLE", 6) == 0) { 
        digitalWrite(LED_PIN, !digitalRead(LED_PIN)); 
      } 
     } 
  }// 聆聽各種訂閱的主題 END
 
  delay(20); 
  int button_second = digitalRead(BUTTON_PIN);  // 讀取當前 BUTTON 狀態
  if ((button_first == HIGH) && (button_second == LOW)) { //代表按鈕被按下
    Serial.println("Button is pressed!");      
    digitalWrite(LED_PIN,!digitalRead(LED_PIN)); //反轉 LED 燈狀態
    //讀取目前 LED 燈狀態
    if (digitalRead(LED_PIN) == HIGH){ 
      esp8266_led_P.publish("Led trun ON by NodeMCU"); //若目前 LED 亮
    }
    else{
      esp8266_led_P.publish("Led trun OFF by NodeMCU"); //若目前 LED 滅
    }
    
  }
}// void loop() END

// =====  MQTT_connect =====
// =========================
void MQTT_connect() { 
  int8_t ret; 
  // 如果目前已經連上 MQTT Server 則直接返回
   if (mqtt.connected()) { 
     return; 
   } 
  Serial.print("Connecting to MQTT... "); 
  uint8_t retries = 3; 
  while ((ret = mqtt.connect()) != 0) { // connect will return 0 for connected 
    Serial.println(mqtt.connectErrorString(ret)); 
    Serial.println("Retrying MQTT connection in 5 seconds..."); 
    mqtt.disconnect(); 
    delay(5000);  // wait 5 seconds 
    retries--; 
    if (retries == 0) { 
      // 連接至 MQTT 失敗三次,則基本上認為版子掛了      
      while (1); // 故意用一個 while(1) 死迴圈來觸發 NodeMCU 的 Watchdog 來重啟版子
      } 
  } 
 Serial.println("MQTT Connected!"); 
}


關於設定連接 port 的部分請參考
一起學 Python 110 : 外部網路連回家中網路 遠端操控 Raspberry pi (使用 No-ip )

結果展示





關於手機的部分,加入傳參數到 MQTT 的功能,請參考下一章
一起學 Python 114 : Rapsberry pi 與 NodeMCU (ESP8266) 溝通 基於 MQTT - 1

補充指令

mosquitto 設定檔
sudo nano /etc/mosquitto/mosquitto.conf

mosquitto 重啟
service mosquitto restart

mosquitto 刪除
sudo apt-get remove --purge --auto-remove mosquitto

查看連接埠
netstat -a

查看錯誤記錄
nano /var/log/mosquitto/mosquitto.log

ps : 前提是在  /etc/mosquitto/mosquitto.conf 裡面有設定

log_dest file /var/log/mosquitto/mosquitto.log

[20200726 更新]
某次想查閱 /var/log/mosquitto/mosquitto.log 的 Log 時發現怎樣都找不到這個文件
後來發現,我們必須要自己手動產生出這個文件後, mosquitto 才會把 Log 存放到這個文件內
可能是這個套件的一個 bug
正常來說這個文件如果不存在的話應該要由它自動產生的才是

參考連結

RASPBERRY PI TALKING TO ESP8266 USING MQTT
樹莓派安裝 Mosquitto 輕量級 MQTT Broker 教學,連接各種物聯網設備
Mosquitto MQTT 安裝



↓↓↓ 連結到部落格方針與索引 ↓↓↓

Blog 使用方針與索引