使用 MQTT + JSON 進行物聯網通信

1. MQTT 通信協議

概述

MQTT(消息隊列遙測傳輸)是目前最重要的物聯網通信協議之一。

IBM公司開發了MQTT協議的第一個版本, 它的設計思想是輕巧、開放、簡單、規範,易於實現。

這些特點使得它對很多場景來說都是很好的選擇,特別是對於受限的環境如機器與機器的通信(M2M)以及物聯網環境(IoT)。

已經有許多工程項目實現了 MQTT協議。如:

Node-RED支持 0.14 版本以上的 MQTT 節點,以便正確配置 TLS 連接。

樹莓派上基於Node.js 的Pimatic 家庭自動化框架提供了 MQTT 插件來完全支持 MQTT 協議。

基本概念

  • 角色

    代理端(Broker), 客戶端(Client)

  • 發布/訂閱

    支持多客戶端採用發布/訂閱(subscribe/publish)形式進行通信, 代理端負責信息中轉:

  • 主題(Topic)

用於對消息進行分類

是一個UTF-8字元串

可進行分級

  • 服務質量(QOS)

    服務質量是為不同應用程序,用戶或數據流提供的不同優先順序的能力:
  • Qos0. 最多一次傳送 (只負責傳送,發送過後就不管數據的傳送情況)
  • Qos1. 至少一次傳送 (確認數據交付)
  • Qos2. 正好一次傳送 (保證數據交付成功)

  • 優勢

    採用代理通信的方式, 解耦了發布消息的客戶(發布者)與訂閱消息的客戶(訂閱者)之間的關係
  • 發布者、訂閱者不必了解彼此,只需認同一個代理
  • 發布者、訂閱者不需要交互,無須等待消息確認
  • 發布者、訂閱者不要要同時在線,可自由選擇時間消費消息

更多資料

MQTT中文文檔: mcxiaoke.gitbooks.io/mq

MQTT維基百科: zh.wikipedia.org/wiki/M

ESP8266社區論壇:github.com/esp8266

2. esp8266-MQTT 示例

伺服器(Broker)

Server: iot.eclipse.org

Port: 1883

網址: https://iot.eclipse.org/

在接下來的例子中, 我們使用 Eclipse IoT, 一個開源的物聯網雲伺服器, 缺點是服務質量不穩定, 容易丟包. 推薦使用實驗室提供的伺服器. 如有條件, 也可購買阿里或騰訊的雲伺服器(10+¥/月)搭建一個雲伺服器.

esp8266 端(client)

PubSubClient 是一個非常好的發布訂閱客戶端庫, 該庫也被集成到了Arduino的庫管理器中,在庫管理器中可下載。

下載完成後,打開示例->pubsubclient->mqtt-esp8266,

填寫esp8266將連接的==wifi名與密碼、連接的代理伺服器地址信息,如下:

// Update these with values suitable for your network.

const char* ssid = "Li-507-2";
const char* password = "blackwalnut";
const char* mqtt_server = "iot.eclipse.org";

運行程序, NodeMCU 每間隔 2S 向伺服器發送 "#hello World" 的信息.

工作原理

  1. 連接 wifi

void setup_wifi() {
delay(10);
WiFi.begin(ssid, password);
}
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}

2. 接收回調函數

void callback(char* topic, byte* payload, unsigned int
length){
//接收消息處理
}

3. MQTT 配置

void setup() {
......
setup_wifi();
client.setServer(mqtt_server, 1883);//伺服器地址+埠
client.setCallback(callback);//設置接收消息回調函數
}

4. MQTT 連接

void reconnect() {
while (!client.connected()) {
//設置客戶端ID,如果重名,伺服器會斷開前一個連接
if (client.connect(ESP.getChipID())) {
//發布消息
client.publish("outTopic", "hello world");
//訂閱消息
client.subscribe("inTopic");
} else {
delay(5000);
}
}
}

5. 消息接收或發送

void loop() {
if (!client.connected()) {
reconnect();
}
//處理訂閱消息
client.loop();
long now = millis();

//通過判斷系統時間延遲信息的發送, 而非通過 delay()函數
//因delay會導致該進程阻塞,導致在延遲期間無法訂閱信息.
if (now - lastMsg > 2000) {
lastMsg = now;
++value;
snprintf (msg, 75, "hello world #%ld", value);
client.publish("outTopic", msg);
//發送主題為「outTopic」的消息到伺服器
}

Android 端(client)

首先,保證你的手機連接上了網際網路。

Mqtt的andoid端軟體測試很多, 操作方式基本相同, 以下應用可在Google Play 下載,此處以MQTT Dashboard為例

與在Arduino端中一樣,配置Andoid端代理伺服器地址與埠:

Server : iot.eclipse.org

Port : 1883

完成上述配置,android手機與esp8266間即可根據彼此確定的主題,進行發布/訂閱雙向通信。

3. JSON:輕鬆打包數據

當我們想要傳輸多組物聯網節點屬性的信息時, 可以對數據進行打包再發布, 在訂閱端進行解包, 這樣做的優點是: 確定我們訂閱的信息歸屬於哪個屬性的, 防止信息被錯誤地歸屬到其他的屬性中, 保證信息的準確性. JSON 為我們提供了一種很好的數據編碼格式.

JSON(JavaScript Object Notation) 是一種輕量級的數據交換格式。易於人閱讀和編寫。同時也易於機器解析和生成。

基礎結構

  • 名稱/值」對的集合(A collection of name/value pairs)
  • 值的有序列表(An ordered list of values)大部分語言中,它被理解為數組(array)

表示對象

JSON最常用的格式是對象的 鍵值對。例如下面這樣:

{"firstName": "Brett", "lastName": "McLaughlin"}

表示數組

和普通的 JS 數組一樣,JSON 表示數組的方式也是使用方括弧 []。

{ "people": [

{ "firstName": "Brett", "lastName":"McLaughlin", "email": "aaaa" },

{ "firstName": "Jason", "lastName":"Hunter", "email": "bbbb"},

{ "firstName": "Elliotte", "lastName":"Harold", "email": "cccc" }

]}

在Arduino中使用:

ArduinoJson官網: arduinojson.org/

在 Arduino 庫管理界面下載 ArduinoJson

打開示例->jsonGeneratorExample/ jsonParseExample, 可以嘗試Json 打包與解包的示例

4. 實例: 訂閱溫濕度信息

DHT11感測器的通信協議是單匯流排協議,連線為:GND ~ GND, VCC ~ 3v3,data~ d×,非常簡明。

在 Arduino 庫管理器中搜索「dht」,下載庫:DHT sensor library for ESP

打開示例->DHT_Test

設置與DHT sensor 連接的引腳, 運行即可在串口監視器看到溫濕度等輸出.

// dht.setup(17);
dht.setup(D1); // Connect DHT sensor to GPIO D1

注: 上述庫在運行時可能出現問題, 推薦下載庫 SampleDHT, 運行該庫中的示例

將溫濕度兩個屬性的值打包成 JSON 格式, 通過 MQTT 協議發送到 Node-RED 平台, 在該平台上繪製出相應的圖表完成數據的可視化. 這就是一個最小的物聯網系統. 只要你的物聯網節點與終端可以連接到 Internet, MQTT 代理伺服器的提供的代理可靠, 你就可以實現對物聯網節點的遠程監控.

根據實例所寫,可發送DHT溫濕度感測器數據的Arduino代碼如下:(要發送不同感測器的信息,只需替換感測器部分的代碼)

#include "DHTesp.h"
#include <ArduinoJson.h>
#include <ESP8266WiFi.h>
#include <PubSubClient.h>

DHTesp dht;

char msg[100]; //存放json數據
float humidity;
float temperature;

const char* ssid = "******"; //你的wifi名
const char* password = "*******"; //你的wifi密碼
const char* mqtt_server = "iot.eclipse.org"; //伺服器地址

WiFiClient espClient;
PubSubClient client(espClient);
long lastMsg = 0;
int value = 0;

void setup()
{
Serial.begin(115200);
Serial.println();
// Serial.println("Status Humidity (%) Temperature (C) (F) HeatIndex (C) (F)");

setup_wifi();
client.setServer(mqtt_server, 1883);
client.setCallback(callback);

dht.setup(D1); // Connect DHT sensor to GPIO D1
}

void loop()
{
delay(dht.getMinimumSamplingPeriod());

humidity = dht.getHumidity();
temperature = dht.getTemperature();

if (!client.connected()) {
reconnect();
}
client.loop();

encodeJson();

long now = millis();
if (now - lastMsg > 2000) {
lastMsg = now;
++value;
// snprintf (msg, 75, "hello world #%ld", value);
Serial.print("Publish message: ");
Serial.println(msg);
client.publish("DHT11", msg);
}

}

//JSON編碼函數
void encodeJson(){
DynamicJsonBuffer jsonBuffer;
JsonObject& root1 = jsonBuffer.createObject();
root1["Humidity"] = humidity;
root1["Temperature"] = temperature;
// root1.prettyPrintTo(Serial);
root1.printTo(msg);
}

//JSON解碼函數
void decodeJson(char msg[100]){
DynamicJsonBuffer jsonBuffer;
JsonObject& root = jsonBuffer.parseObject(msg);
float temp = root["Temperature"];
float hum = root["Humidity"];

Serial.println(temp);
Serial.println(hum);

}

void setup_wifi() {
delay(10);
// We start by connecting to a WiFi network
Serial.println();
Serial.print("Connecting to ");
Serial.println(ssid);

WiFi.begin(ssid, password);

while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}

Serial.println("");
Serial.println("WiFi connected");
Serial.println("IP address: ");
Serial.println(WiFi.localIP());
}

void callback(char* topic, byte* payload, unsigned int length) {
Serial.print("Message arrived [");
Serial.print(topic);
Serial.print("] ");
for (int i = 0; i < length; i++) {
Serial.print((char)payload[i]);
}
Serial.println();
}

void reconnect() {
// Loop until were reconnected
while (!client.connected()) {
// Serial.print("Attempting MQTT connection...");
// Attempt to connect
if (client.connect("ESP8266Client")) {
// Serial.println("connected");
// Once connected, publish an announcement...
client.publish("outTopic", "hello world");
// ... and resubscribe
client.subscribe("inTopic");
} else {
Serial.print("failed, rc=");
Serial.print(client.state());
Serial.println(" try again in 5 seconds");
// Wait 5 seconds before retrying
delay(5000);
}
}
}

推薦閱讀:

TAG:物聯網 | 智能家居 | MQTT |