在了解啥是MQTT后,我们理论上了解了。现在我们来实操来使用ESP8266通过MQTT将BME280传感器读数(温度,湿度和压力)发布到任何支持MQTT或任何MQTT客户端的平台。举例来说,我们会将传感器读数发布到Node-RED仪表板,并且将使用Arduino IDE对ESP8266进行编程。

项目大致概况

  1. ESP8266从BME280传感器读取温度/湿度/气压
  2. 温度/湿度/气压读数发布在esp/bme280/temperature esp/bme280/humidity esp/bme280/pressure主题中
  3. Node-RED订阅这些主题
  4. Node-RED接收传感器读数并将其显示在仪表上
  5. 在任何支持MQTT的平台上接收读数,并根据需要处理读数

事前必须

在继续之前,请确定已经做好以下事情.

Arduino IDE

我们将使用Arduino IDE对ESP8266进行编程,因此请确保您已安装ESP8266板库。

MQTT Broker

要使用MQTT,需要一个代理。我们将使用安装在Raspberry Pi上的Mosquitto代理。在Raspberry Pi上安装Mosquitto Broker。
也可以使用任何其他MQTT代理,包括云MQTT代理。稍后,我们将在代码中向您展示如何做到这一点。

MQTT库

要将MQTT与ESP8266配合使用,我们将使用 Async MQTT Client Library项目地址:https://github.com/marvinroger/async-mqtt-client
首先安装好Arduino的Async MQTT Client Library客户端库,以及ESPAsync TCP库:项目地址:https://github.com/me-no-dev/ESPAsyncTCP

BME280库

在ArduinoIDE内搜索Adafruit_BME280 library即可安装,这里不再论述.
上述事项准备完成后再往下继续

所需零件

名称 数量 单位 备注
ESP8266 1
BME280 1个
树莓派 1 已经装好服务器
杜邦线和面包板

接线图

如下图所示,将BME280连接到ESP8266,其中SDA引脚连接到GPIO 4,SCL引脚连接到GPIO 5。

代码

将以下代码复制到您的Arduino IDE。需要插入网络凭据以及MQTT代理详细信息

#include <Wire.h>
#include <Adafruit_Sensor.h>
#include <Adafruit_BME280.h>
#include <ESP8266WiFi.h>
#include <Ticker.h>
#include <AsyncMqttClient.h>

#define WIFI_SSID "REPLACE_WITH_YOUR_SSID"
#define WIFI_PASSWORD "REPLACE_WITH_YOUR_PASSWORD"

// 树莓派 Mosquitto MQTT Broker
#define MQTT_HOST IPAddress(192, 168, 1, XXX)
// 输入云MQTT broker的域名
//#define MQTT_HOST "example.com"
#define MQTT_PORT 1883

// 温度的 MQTT Topics
#define MQTT_PUB_TEMP "esp/bme280/temperature"
#define MQTT_PUB_HUM "esp/bme280/humidity"
#define MQTT_PUB_PRES "esp/bme280/pressure"

// BME280 I2C
Adafruit_BME280 bme;
// 传感器变量
float temp;
float hum;
float pres;

AsyncMqttClient mqttClient;
Ticker mqttReconnectTimer;

WiFiEventHandler wifiConnectHandler;
WiFiEventHandler wifiDisconnectHandler;
Ticker wifiReconnectTimer;

unsigned long previousMillis = 0;   // 存储最后一次的温度数值
const long interval = 10000;        // 发布传感器读数的时间间隔

void connectToWifi() {
  Serial.println("Connecting to Wi-Fi...");
  WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
}

void onWifiConnect(const WiFiEventStationModeGotIP& event) {
  Serial.println("Connected to Wi-Fi.");
  connectToMqtt();
}

void onWifiDisconnect(const WiFiEventStationModeDisconnected& event) {
  Serial.println("Disconnected from Wi-Fi.");
  mqttReconnectTimer.detach(); // ensure we don't reconnect to MQTT while reconnecting to Wi-Fi
  wifiReconnectTimer.once(2, connectToWifi);
}

void connectToMqtt() {
  Serial.println("Connecting to MQTT...");
  mqttClient.connect();
}

void onMqttConnect(bool sessionPresent) {
  Serial.println("Connected to MQTT.");
  Serial.print("Session present: ");
  Serial.println(sessionPresent);
}

void onMqttDisconnect(AsyncMqttClientDisconnectReason reason) {
  Serial.println("Disconnected from MQTT.");

  if (WiFi.isConnected()) {
    mqttReconnectTimer.once(2, connectToMqtt);
  }
}

/*void onMqttSubscribe(uint16_t packetId, uint8_t qos) {
  Serial.println("Subscribe acknowledged.");
  Serial.print("  packetId: ");
  Serial.println(packetId);
  Serial.print("  qos: ");
  Serial.println(qos);
}

void onMqttUnsubscribe(uint16_t packetId) {
  Serial.println("Unsubscribe acknowledged.");
  Serial.print("  packetId: ");
  Serial.println(packetId);
}*/

void onMqttPublish(uint16_t packetId) {
  Serial.print("Publish acknowledged.");
  Serial.print("  packetId: ");
  Serial.println(packetId);
}

void setup() {
  Serial.begin(115200);
  Serial.println();

  // 初始化 BME280 
  if (!bme.begin(0x76)) {
    Serial.println("Could not find a valid BME280 sensor, check wiring!");
    while (1);
  }

  wifiConnectHandler = WiFi.onStationModeGotIP(onWifiConnect);
  wifiDisconnectHandler = WiFi.onStationModeDisconnected(onWifiDisconnect);

  mqttClient.onConnect(onMqttConnect);
  mqttClient.onDisconnect(onMqttDisconnect);
  //mqttClient.onSubscribe(onMqttSubscribe);
  //mqttClient.onUnsubscribe(onMqttUnsubscribe);
  mqttClient.onPublish(onMqttPublish);
  mqttClient.setServer(MQTT_HOST, MQTT_PORT);
  // 如果你的broker需要验证则如下设置
  //mqttClient.setCredentials("用户名", "密码");

  connectToWifi();
}

void loop() {
  unsigned long currentMillis = millis();
  // 每 *秒数(间隔 = 10 秒)
  // 发送新信息
  if (currentMillis - previousMillis >= interval) {
    // 保存上次发布新读数的时间
    previousMillis = currentMillis;
    // 新的BME280 传感器读数
    temp = bme.readTemperature();
    //temp = 1.8*bme.readTemperature() + 32;
    hum = bme.readHumidity();
    pres = bme.readPressure()/100.0F;

    // 推送一个MQTT信息到主题esp/bme280/temperature
    uint16_t packetIdPub1 = mqttClient.publish(MQTT_PUB_TEMP, 1, true, String(temp).c_str());                            
    Serial.printf("Publishing on topic %s at QoS 1, packetId: %i ", MQTT_PUB_TEMP, packetIdPub1);
    Serial.printf("Message: %.2f \n", temp);

    // 推送一个MQTT信息到主题 esp/bme280/humidity
    uint16_t packetIdPub2 = mqttClient.publish(MQTT_PUB_HUM, 1, true, String(hum).c_str());                            
    Serial.printf("Publishing on topic %s at QoS 1, packetId: %i ", MQTT_PUB_HUM, packetIdPub2);
    Serial.printf("Message: %.2f \n", hum);

    // 推送一个MQTT信息到主题 esp/bme280/pressure
    uint16_t packetIdPub3 = mqttClient.publish(MQTT_PUB_PRES, 1, true, String(pres).c_str());                            
    Serial.printf("Publishing on topic %s at QoS 1, packetId: %i ", MQTT_PUB_PRES, packetIdPub3);
    Serial.printf("Message: %.3f \n", pres);
  }
}

代码解释

导入所有必需的库

#include <Wire.h>
#include <Adafruit_Sensor.h>
#include <Adafruit_BME280.h>
#include <ESP8266WiFi.h>
#include <Ticker.h>
#include <AsyncMqttClient.h>

网络信息

#define WIFI_SSID "REPLACE_WITH_YOUR_SSID"
#define WIFI_PASSWORD "REPLACE_WITH_YOUR_PASSWORD"

填入树莓派的IP地址,让ESP8266连接到您的代理

#define MQTT_HOST IPAddress(192, 168, 1, 106)

如果是云MQTT代理,请插入代理域名,例如:

#define MQTT_HOST "example.com"

定义MQTT端口。

#define MQTT_PORT 1883

温度,湿度和压力将在以下主题上发布:

#define MQTT_PUB_TEMP "esp/bme280/temperature"
#define MQTT_PUB_HUM "esp/bme280/humidity"
#define MQTT_PUB_PRES "esp/bme280/pressure"

初始化一个名为bmeAdafruit_BME280对象。

Adafruit_BME280 bme;

使用浮点将temphumpres三个变量保存BME280传感器的温度,湿度和压力值。

float temp;
float hum;
float pres;

创建一个名为mqttClientAsyncMqttClient对象,以处理MQTT客户端,并创建计时器,以便在断开连接时重新连接到MQTT代理和路由器。

AsyncMqttClient mqttClient;
Ticker mqttReconnectTimer;

WiFiEventHandler wifiConnectHandler;
WiFiEventHandler wifiDisconnectHandler;
Ticker wifiReconnectTimer;

然后,创建一些辅助计时器变量以每10秒发布一次读数。您可以在时间间隔(interval)变量上更改延迟时间。

unsigned long previousMillis = 0;
const long interval = 10000; 

MQTT功能:连接到Wi-Fi,连接到MQTT和Wi-Fi events

我们尚未在下一个代码部分中定义的函数中添加任何注释。这些函数随Async Mqtt客户端库一起提供。函数的名称很浅显易懂。

void connectToWifi() {
  Serial.println("Connecting to Wi-Fi...");
  WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
}

connectToMqtt()将ESP8266连接到MQTT代理:

void connectToMqtt() {
  Serial.println("Connecting to MQTT...");
  mqttClient.connect();
}

在与代理开始会话之后,将运行onMqttConnect()函数。

void onMqttConnect(bool sessionPresent) {
  Serial.println("Connected to MQTT.");
  Serial.print("Session present: ");
  Serial.println(sessionPresent);
}

MQTT功能:断开连接和发布

如果ESP8266与MQTT代理失去连接,它将调用onMqttDisconnect函数,该函数在串行监视器中打印该消息。

void onMqttDisconnect(AsyncMqttClientDisconnectReason reason) {
  Serial.println("Disconnected from MQTT.");
  if (WiFi.isConnected()) { 
    xTimerStart(mqttReconnectTimer, 0);
  }
}

当您将消息发布到MQTT主题时,将调用onMqttPublish()函数。它在串行监视器中打印数据包ID。

void onMqttDisconnect(AsyncMqttClientDisconnectReason reason) {
  Serial.println("Disconnected from MQTT.");

  if (WiFi.isConnected()) {
    mqttReconnectTimer.once(2, connectToMqtt);
  }
}

基本上,我们刚才提到的所有这些函数都是回调函数。因此,它们是异步执行的。

setup()函数

接下来的两行创建处理程序,如果连接断开,将允许MQTT代理和Wi-Fi连接重新连接。

wifiConnectHandler = WiFi.onStationModeGotIP(onWifiConnect);
wifiDisconnectHandler = WiFi.onStationModeDisconnected(onWifiDisconnect);

最后,分配所有回调函数。这意味着这些功能将在需要时自动执行。例如,ESP8266连接到代理时,会自动调用onMqttConnect()函数,依此类推。

mqttClient.onConnect(onMqttConnect);
mqttClient.onDisconnect(onMqttDisconnect);
//mqttClient.onSubscribe(onMqttSubscribe);
//mqttClient.onUnsubscribe(onMqttUnsubscribe);
mqttClient.onPublish(onMqttPublish);
mqttClient.setServer(MQTT_HOST, MQTT_PORT);

代理认证

mqttClient.setCredentials("用户名", "密码");

最后,连接到Wi-Fi。
connectToWifi();

loop()函数

loop()中,您将创建一个计时器,该计时器将使您能够从BME280传感器获取新读数,并每隔10秒将它们发布到相应的主题上。

unsigned long currentMillis = millis();
// 每 X 秒数(间隔 = 10 秒)
// 发布一个新的 MQTT 消息
if (currentMillis - previousMillis >= interval) {
  // 保存上次发布新读数的时间
  previousMillis = currentMillis;
  // 新型 BME280 传感器读数
  temp = bme.readTemperature();
  //temp = 1.8*bme.readTemperature() + 32;
  hum = bme.readHumidity();
  pres = bme.readPressure()/100.0F;

发布到主题

要发布有关相应的MQTT主题的阅读材料,请使用以下几行:

uint16_t packetIdPub1 = mqttClient.publish(MQTT_PUB_TEMP, 1, true, String(temp).c_str());
uint16_t packetIdPub2 = mqttClient.publish(MQTT_PUB_HUM, 1, true, String(hum).c_str());
uint16_t packetIdPub3 = mqttClient.publish(MQTT_PUB_PRES, 1, true, String(pres).c_str());

基本上,使用mqttClient对象上的publish()方法发布有关主题的数据。publish()方法按顺序接受以下参数:

  • MQTT topic (const char*)
  • QoS (uint8_t): 服务质量-可以是0、1或2
  • retain flag (bool): 保留flag
  • payload (const char*) – 有效负载对应于传感器读数

QoS(quality of service)是保证消息传递的一种方法。它可以是以下级别之一:

  • 0:该消息将被传递一次或根本不传递。该消息未被确认。没有重复消息的可能性;
  • 1:消息将至少传递一次,但可能会传递更多
  • 2:消息始终只发送一次;

上传代码

(简体)
在树莓派通电并运行Mosquitto MQTT代理的情况下,将代码上传到ESP8266。以115200的波特率打开串行监视器,会看到ESP8266开始发布有关我们先前定义的主题的消息。

准备Node-RED

ESP8266每10秒钟发布esp/bme280/温度esp/bme280/湿度和esp/bme280/压力主题的温度读数。 现在,你可以使用任何支持MQTT的仪表板或任何其他支持MQTT的设备来订阅这些主题并接收读数。

举例来说,我们将使用Node-RED创建一个简单的流程来订阅这些主题并在仪表上显示读数。

如果您尚未安装Node-RED,请遵循以下两篇文章:

在树莓派上运行Node-RED时,请转到Raspberry Pi IP地址,端口:1880。
http://raspberry-pi-ip-address:1880

单击MQTT节点并编辑它的属性。

服务器字段是指MQTT代理。在我们的例子中,MQTT代理是Raspberry Pi,因此将其设置为localhost:1883。如果您使用的是Cloud MQTT代理,则应更改该字段。

插入要订阅的主题和QoS。该先前的MQTT节点已订阅esp /bme280/temperature主题。

单击节点中的另一个MQTT并在同一服务器上编辑其属性,但要注意其他主题:esp/bme280/湿度和esp/bme280/压力。

单击仪表节点并为每个读数编辑其属性。为温度读数设置了以下节点。编辑其他图表节点以获取其他读数。

如下所示连接节点:

最后,部署流程(按右上角的按钮)

或者,您可以转到菜单>导入,然后将以下内容复制到剪贴板以创建Node-RED流。

[{"id":"5a45b8da.52b0d8","type":"mqtt in","z":"b01416d3.f69f38","name":"","topic":"esp/bme280/temperature","qos":"1","datatype":"auto","broker":"8db3fac0.99dd48","x":310,"y":60,"wires":[["3042e15e.80a4ee"]]},{"id":"3042e15e.80a4ee","type":"ui_gauge","z":"b01416d3.f69f38","name":"","group":"37de8fe8.46846","order":2,"width":0,"height":0,"gtype":"gage","title":"Temperature","label":"ºC","format":"{{value}}","min":0,"max":"40","colors":["#00b500","#f7df09","#ca3838"],"seg1":"","seg2":"","x":590,"y":60,"wires":[]},{"id":"8ff168f0.0c74a8","type":"mqtt in","z":"b01416d3.f69f38","name":"","topic":"esp/bme280/humidity","qos":"1","datatype":"auto","broker":"8db3fac0.99dd48","x":300,"y":140,"wires":[["29251f29.6687c"]]},{"id":"29251f29.6687c","type":"ui_gauge","z":"b01416d3.f69f38","name":"","group":"37de8fe8.46846","order":2,"width":0,"height":0,"gtype":"gage","title":"Humidity","label":"%","format":"{{value}}","min":"30","max":"100","colors":["#53a4e6","#1d78a9","#4e38c9"],"seg1":"","seg2":"","x":580,"y":140,"wires":[]},{"id":"294f7eea.999d72","type":"mqtt in","z":"b01416d3.f69f38","name":"","topic":"esp/bme280/pressure","qos":"1","datatype":"auto","broker":"8db3fac0.99dd48","x":300,"y":220,"wires":[["58610d70.bb9764"]]},{"id":"58610d70.bb9764","type":"ui_gauge","z":"b01416d3.f69f38","name":"","group":"37de8fe8.46846","order":4,"width":0,"height":0,"gtype":"gage","title":"Pressure","label":"hPa","format":"{{value}}","min":0,"max":"1200","colors":["#b366ff","#8000ff","#440088"],"seg1":"","seg2":"","x":580,"y":220,"wires":[]},{"id":"8db3fac0.99dd48","type":"mqtt-broker","z":"","name":"","broker":"localhost","port":"1883","clientid":"","usetls":false,"compatmode":false,"keepalive":"60","cleansession":true,"birthTopic":"","birthQos":"0","birthPayload":"","closeTopic":"","closeQos":"0","closePayload":"","willTopic":"","willQos":"0","willPayload":""},{"id":"37de8fe8.46846","type":"ui_group","z":"","name":"BME280","tab":"53b8c8f9.cfbe48","order":1,"disp":true,"width":"6","collapse":false},{"id":"53b8c8f9.cfbe48","type":"ui_tab","z":"","name":"Home","icon":"dashboard","order":2,"disabled":false,"hidden":false}]

示范

转到您的树莓派地址,然后输入:1880/ui。
http://raspberry-pi-ip-address:1880/ui
现在你应该可以访问仪表板上的当前BME280传感器读数。你可以开始使用其他仪表板类型的节点以不同方式显示读数了。

总结

MQTT是一种很棒的通信协议,可以在设备之间交换少量数据。在本文中,讲述了如何将带有ESP8266的BME280传感器的温度,湿度和压力读数发布到不同的MQTT主题。这样你就可以使用任何设备或家庭自动化平台订阅这些主题并接收读数了。还有另外两边文章还没写,其实是应该在本篇之前写的,工作实在太忙,往后我尽快更新写上.


一沙一世界,一花一天堂。君掌盛无边,刹那成永恒。