MQTT教學(七):使用Node.js訂閱MQTT訊息

本文將使用MQTT.js套件開發Node.js的MQTT前端應用程式。MQTT伺服器仍採用之前介紹的Mosquitto,附帶一提,有個採用Node.js開發的開放原始碼MQTT伺服器(broker)模組,叫做Mosca,可以獨立運作,也能嵌入Node.js程式執行,有興趣的朋友請參閱Mosca的官網介紹。

透過MQTT.js命令行模式發布和接收MQTT主題

MQTT.js支援在命令行(終端機)中直接輸入命令,藉以發布或接收MQTT主題。若要使用命令行模式,請用全域(global)方式安裝MQTT.js:

npm install mqtt -g

安裝後即可在命令行(終端機)輸入底下的命令,發布一則“home/yard/DHT11”主題訊息

發布一則“home/yard/DHT11”主題訊息的命令

這是透過MQTTLens程式接收此MQTT訊息的畫面:

底下這個命令則可訂閱“home/yard/DHT11”主題

訂閱“home/yard/DHT11”主題的命令

更多命令及其說明可以執行mqtt help,或者mqtt help ‘命令’,例如,mqtt help pub。

透過Node.js程式檔訂閱MQTT主題

本節將使用MQTT.js製作一個在命令行(終端機)顯示訂閱主題訊息的Node.js程式。

在命令行(終端機)顯示訂閱主題訊息的Node.js程式

由於下一節的程式碼將會使用到Express和Socket.io套件,因此筆者選擇在《超圖解物聯網IoT實作入門》書本的socket專案路徑中,輸入底下的命令安裝mqtt.js:

npm install mqtt --save

MQTT.js的程式很簡單,底下是訂閱“home/yard/DHT11”主題的Node.js程式碼,筆者將它命名為mqtt.js。

訂閱“home/yard/DHT11”主題的Node.js程式碼

其中,建立用戶端物件並連線到伺服器的敘述,可以省略「連線參數」物件,它預設將連接到1883埠,並且建立一個以’mqttjs_’起頭,後面跟著8個隨機16進位數字的用戶端ID:

var client = mqtt.connect('mqtt://192.168.1.19');

每當收到新的主題訊息時,“message”事件會自動觸發,因而在命令行(終端機)顯示收到的主題和訊息。上面的程式碼執行結果如下,你可以使用Arduino或者MQTTLens前端發送MQTT訊息測試:

在終端機顯示接收到的MQTT訊息

MQTT用戶端物件的subscribe(訂閱)方法的「主題」參數,可以是字串、陣列或物件類型。若要訂閱多個主題,請將所有主題名稱寫在一個陣列裡面;底下的敘述將訂閱兩個主題:

使用陣列subscribe(訂閱)兩個主題

這是用物件類型訂閱兩個主題的示範。「主題名稱」是物件的「屬性」,QoS則是「值」

使用物件subscribe(訂閱)兩個主題

Node.js的Buffer資料類型

伴隨MQTT.js的message(訊息)事件傳入的訊息內容類型是Node.js的二進制資料,稱為Buffer。Buffer主要用於儲存文字以外的資料,例如:圖片、聲音、視訊…等等,它也能儲存文字。請在命令列(終端機)輸入下圖裡的藍色敘述,進入Node.js的REPL模式,練習建立與操作Buffer類型資料。

操作Buffer類型資料

上面的敘述宣告了一個20位元組空間大小的Buffer類型變數,接著對它寫入一個字串資料。Node.js的字串預設用UTF-8萬國編碼,這是一種可變動長度的編碼,不同的字元或符號可能佔用1~4位元組。英文字的UTF-8編碼相容於ASCII編碼,每個字元佔1個位元組;一個中文字的UTF-8編碼大多佔3個位元組。所以儲存「昨日是歷史、明日是謎團。」這12個字元需要36位元組空間,超出20個位元組以外的數據會被截掉。

僅輸入變數名稱“buff”,可取出它的原始資料值(字串資料的UTF-8編碼)。我們也能在宣告Buffer變數時,直接輸入資料,它將自動預留與資料大小相同的記憶體空間,例如:

操作Buffer類型資料

透過Socket.io即時更新MQTT訊息網頁

本單元的範例結合Express, Socket.io和MQTT.js,在網頁上顯示即時更新的MQTT訊息(溫濕度值),此範例的檔案結構和《超圖解物聯網IoT實作入門》「使用jQuery讀取並解析JSON訊息」一節(2-25頁)相同。

結合Express, Socket.io和MQTT.js,在網頁上顯示即時更新的MQTT訊息

Node.js專案資料夾的結構如下,js資料夾裡面包含jQuery程式庫。

Node.js專案資料夾的結構

底下是mqtt.js當中的socket.io程式碼,它負責把收到的MQTT主題訊息即時轉發給HTML網頁,相關說明請參閱《超圖解物聯網IoT實作入門》「使用Socket.io建立即時連線」單元(5-27頁)。

socket.io程式碼

mqtt.js的完整程式碼如下:

var mqtt = require('mqtt');
var opt = {
  port:1883,
  clientId: 'nodejs'
};
var io = require("socket.io");
var express = require("express");
var app = express();
app.use(express.static('www'));
var server = app.listen(5438);

var client  = mqtt.connect('tcp://192.168.1.19');
var sio = io.listen(server);

client.on('connect', function () {
  console.log('已連接至MQTT伺服器');
  client.subscribe("home/yard/DHT11");
});

sio.on('connection', function(socket){
  client.on('message', function (topic, msg) { 
      console.log('收到 ' + topic + ' 主題,訊息:' + msg.toString());
      socket.emit('mqtt', { 'msg': msg.toString() });
  });
});

HTML和前端JavaScript程式碼請直接參考底下的index.html原始碼;在瀏覽器端解析JSON資料、使用jQuery動態更新網頁內容,請參閱《超圖解物聯網IoT實作入門》第2-23和2-25頁。讀者也能搭配第九章介紹的C3.js或D3.js程式庫,以視覺化圖表呈現動態數據。

<html>
  <head>
    <meta charset="utf-8">
    <title>MQTT即時溫濕度</title>
  </head>
  <body>
    <h1>MQTT即時溫濕度</h1>
    <p>
      溫度:<span id="temp">??</span> &deg; <br>
      濕度:<span id="hum">??</span> %
    </p>

    <script src="/socket.io/socket.io.js"></script>
    <script src="js/jquery-2.1.3.min.js"></script>
    <script>
    $(function(){
      var socket = io.connect();

      socket.on('mqtt', function (data) {
        var json = JSON.parse(data.msg);

        $("#temp").html(json.temp);
        $("#hum").html(json.humid);
      });
    });
    </script>
  </body>
</html>

延伸閱讀

Posts created 467

25 thoughts on “MQTT教學(七):使用Node.js訂閱MQTT訊息

  1. 老師您好
    我用nodejs想訂閱資料遭遇以下問題
    Cannot find module ‘websocket-stream’
    所以我去裝了websocket-stream這個套件
    再跑一次 結果還是有錯誤
    /root/node_modules/mqtt/node_modules/websocket-stream/server.js:6
    class Server extends WebSocketServer{
    ^^^^^
    SyntaxError: Unexpected reserved word

    我是用7688做的,請問是否能提示一下問題點

    1. 因為7688內建的node太老了 不支援2.0以後的mqtt
      一則是想辦法升級node.js
      一則是npm view node version
      找個2.0以前的版本來npm install
      我是走這條路先能跑

    2. 上一回文寫錯,應該是
      npm view mqtt version
      因為是要降MQTT版本

  2. 老師你好,想請問有辦法實現用Node.js擷取mosquitto broker 的資訊嗎?
    例如 連入的clientId,username…等
    一直找不到相關的資訊,
    想請老師給予一些意見

    1. 因為MQTT.js套件是「用戶端」,沒有權限取得妳想要的資訊(請參閱MQTT.js的API指令集),就像一般人不可能要求銀行櫃台交出所有用戶的個人資料,而且還要看看mosquitto的API有沒有公開對應的功能。

      或許妳可以嘗試使用Mosca

      thanks,
      jeffrey

  3. 你好,我想請問一下該如何讓非區網的Client能連到broker?
    電腦是有固定IP的,但我透過學校電腦用MQTTlens連回家裡總是顯示disconnected

  4. 你好,我是新手,第一次玩ARDUINO,我準備用arduino d1和DHT22來檢測溫度和濕度,並以ESP8266連接網絡傳送數據,準備以MQTT方式傳送,伺服器是mosquitto,儲存數據的數據庫是mongodb,我用arduino ide可以檢測到溫度和濕度,問題是我不懂如何以MQTT方式去傳送數據到伺服器

    這是我的program 不懂怎去改
    #include
    #include
    #include
    #include

    #define DHTPIN 4
    #define DHTTYPE DHT22
    const char* ssid = “……”;
    const char* password = …..”;
    const char* host = “184.106.153.149”;
    const char* writeAPIKey = “LAGP*******D2L”;

    DHT_Unified dht(DHTPIN, DHTTYPE);

    uint32_t delayMS;

    WiFiClient client;
    void setup() {
    Serial.begin(9600);
    dht.begin();
    WiFi.begin(ssid,password);
    Serial.println(“”);
    while(WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(“.”);
    }
    Serial.print(“Connected to “);
    Serial.println(ssid);
    Serial.print(“IP Address: “);
    Serial.println(WiFi.localIP());
    }

    void setup() {
    Serial.begin(9600);
    // Initialize device.
    dht.begin();
    Serial.println(“DHT22 Unified Sensor Example”);
    // Print temperature sensor details.
    sensor_t sensor;
    dht.temperature().getSensor(&sensor);
    Serial.println(“————————————“);
    Serial.println(“Temperature”);
    Serial.print (“Sensor: “); Serial.println(sensor.name);
    Serial.print (“Driver Ver: “); Serial.println(sensor.version);
    Serial.print (“Unique ID: “); Serial.println(sensor.sensor_id);
    Serial.print (“Max Value: “); Serial.print(sensor.max_value); Serial.println(” *C”);
    Serial.print (“Min Value: “); Serial.print(sensor.min_value); Serial.println(” *C”);
    Serial.print (“Resolution: “); Serial.print(sensor.resolution); Serial.println(” *C”);
    Serial.println(“————————————“);
    // Print humidity sensor details.
    dht.humidity().getSensor(&sensor);
    Serial.println(“————————————“);
    Serial.println(“Humidity”);
    Serial.print (“Sensor: “); Serial.println(sensor.name);
    Serial.print (“Driver Ver: “); Serial.println(sensor.version);
    Serial.print (“Unique ID: “); Serial.println(sensor.sensor_id);
    Serial.print (“Max Value: “); Serial.print(sensor.max_value); Serial.println(“%”);
    Serial.print (“Min Value: “); Serial.print(sensor.min_value); Serial.println(“%”);
    Serial.print (“Resolution: “); Serial.print(sensor.resolution); Serial.println(“%”);
    Serial.println(“————————————“);
    // Set delay between sensor readings based on sensor details.
    delayMS = sensor.min_delay / 1000;
    }

    void loop() {
    // Delay between measurements.
    delay(delayMS);
    // Get temperature event and print its value.
    sensors_event_t event;
    dht.temperature().getEvent(&event);
    if (isnan(event.temperature)) {
    Serial.println(“Error reading temperature!”);
    }
    else {
    Serial.print(“Temperature: “);
    Serial.print(event.temperature);
    Serial.println(” *C”);
    }
    // Get humidity event and print its value.
    dht.humidity().getEvent(&event);
    if (isnan(event.relative_humidity)) {
    Serial.println(“Error reading humidity!”);
    }
    else {
    Serial.print(“Humidity: “);
    Serial.print(event.relative_humidity);
    Serial.println(“%”);
    }
    }

    void updateTemp(String event.relative_humidity, String event.relative_humidity)
    {
    const int httpPort = 80;
    if (!client.connect(host, httpPort)) {
    return;
    }
    String url = “/update?key=”;
    url+=writeAPIKey;
    url+=”&field1=”;
    url+=event.relative_humidity;
    url+=”&field2=”;
    url+=event.relative_humidity;
    url+=”\r\n”;
    client.print(String(“GET “)+url + ” HTTP/1.1\r\n” +
    “Host: ” + host + “\r\n” +
    “Connection: close\r\n\r\n” );

    }

  5. 老師,想請問在”MQTT教學”(六)跟(七)是這樣子的觀念嗎?
    先透過Arduino得到感測器的類比值,之後透過乙太網路版和Pubsubclient發布MQTT主題訊息給Mosquitto伺服器 (教學六的部分),而教學七則是用mqtt.js去訂閱在Mosquitto伺服器裡面的值,再將其值發布在網頁上呢?也利用socket.io的特性,去做到即時更新的部分,不知道我的理解是否正確呢?麻煩老師了!謝謝您

    1. 對,MQTT教學(六)說明從Arduino發布MQTT訊息的方法,教學(七)則是說明在網站伺服器上接收(註冊)MQTT訊息的方法,socket.io的作用是將動態訊息即時反應在用戶端網頁上。

      thanks,
      jeffrey

  6. 老師您好,我依照您MQTT(六)的教學已經可以將感測器的值從Arduino發佈到Mosquitto伺服器,而MQTTlens上面也可以正常顯示Arduino感測到的讀值,但到(七)時,能在Node視窗顯示的只有”已連接至MQTT伺服器”的字樣,而接收不到其他訊息,目前安裝的mqtt.js是2.18.0版的,不知道是不是版本的問題呢?謝謝老師!

  7. 老師,抱歉打擾您了,我又試了幾次,發現老師上面的程式碼可以執行,應該是我電腦的問題,再次跟老師說聲抱歉了,也很謝謝老師您!

  8. 老師您好,請問一下
    如果有不同的Arduino 傳送不同的資料(例如: Arduino1傳送home/yard/DHT11,Arduino2傳送home/yard/DHT12,Arduino3傳送home/yard/DHT13)給同一個mqtt.js,但是不同的瀏覽器開啟後分別只需讀取某一筆資料(例如: 電腦1瀏覽器只需讀取home/yard/DHT11,電腦2瀏覽器只需讀取home/yard/DHT12,電腦3瀏覽器只需讀取home/yard/DHT13),請問要怎麼做呢? 如果方便可否傳範例到我的mail?
    感謝您,

    TerryChung

  9. 老師您好,
    如果傳給html的數據與,需再傳給C3.js畫圖表,該怎麼動態傳數據給圖表?

    謝謝
    TerryChung

    1. 請參閱《超圖解物聯網IOT實作入門》第九章「顯示動態平移的即時線條圖」的例子。

      thanks,
      jeffrey

  10. 老師您好,
    請問一下,當第二個Arduino加入傳送第二種資料,網頁就變成幾乎當機的狀況,網頁沒有讀第二種資料只讀第一種資料,偶爾還是會更新,但幾乎是當機的狀況。
    如果只有一個Arduino傳遞第一種資料,經過server的mqtt傳給網頁,是順暢的,只是不明白為何同一筆資料會傳3次獲4次。

    謝謝
    TerryChung

  11. 老師您好:
    感謝此篇教學,但我對於ip想請教一下,mqttlens除127.0.0.1與localhost可正常連線,若使用cmd中ipconfig顯示的ipv4位址(我是使用wifi,而且似乎會隨著不同wifi而改變),則始終連不上。這篇所提的host是指用戶端ip,而broker位址位在mqttlens上,對吧? 用戶端分別是顯現在cmd上的js與mqttlens上,但其中的broker我不太了解其所在位置。

    1. broker的IP位址,就是執行它的電腦的IP位址;所有連線裝置都要在相同網域,連不上也有可能是被電腦系統的防火牆擋住了。

      thanks,
      jeffrey

  12. 老師您好:
    我想詢問npm install mqtt -g 和 npm install mqtt –save 差別在哪?
    既然全域已經安裝,為什麼socket 專案還需另外安裝一樣的,實作時我突然想到的,觀念我還是不太明白,麻煩了,感謝。

    1. 為了確保將來佈署的node.js套件版本和開發時採用的一致,在專案資料夾裡面包含一個package.json檔,裡面設置該專案採用的全部套件和版本資訊。save參數代表「將套件名稱和版本寫入package.json」。

      thanks,
      jeffrey

  13. 老師您好:
    想請問一下nodejs程式碼mqtt.js這後端顯示在cmd中的訊息,要如何與html串接,將cmd中的發布訊息打印在網頁伺服器上,看了範例還是不太了解,麻煩指點,謝謝!

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *

Related Posts

Begin typing your search term above and press enter to search. Press ESC to cancel.

Back To Top