基于MQTT协议与ESP8266平台的家庭环境监控实现

在智能家居的应用场景中,传感器一直是非常重要的组成部分。比如传感器报告的温度高了可以自动关窗开空调。在一般的使用场景中,众多的传感器和开关会分散在房子中的不同位置,通过布线连接是不现实的,理想的情况当然是通过已有的无线网络进行连接,网络传输就需要特定的协议,MQTT协议就是一个不错的选择。

手头刚好有一些传感器和一个ESP8266芯片的单片机(WeMos D1),做了一个家庭温湿度、光照及空气质量监控器,并通过MQTT协议推送数据到Home Assistant平台。

MQTT

MQTT(Message Queue Telemetry Transport)是一个基于TCP/IP协议的轻量级的发布/订阅消息通讯协议,其最初就是设计来适应嵌入式设备通讯中遇到的硬件计算能力有限,网络带宽低且不可靠等问题,因此它具有开销小(固定长度的头部只有2字节)及异常中断处理等特性。它在IoT领域被大量的应用,尤其在传感器的监测数据推送方面。如Amazon IoT、Microsoft Azure IoT Hub等都把MQTT做为其主要支持的协议。

MQTT需要一个Broker服务器才可以正常工作,我选择了一个开源的MQTT服务器EMQ,可以通过Docker来部署:

1
docker run -d --name=emq -d -p 18083:18083 -p 1883:1883 -p 8083:8083 -p 8443:8443 --restart=always devicexx/emqttd

硬件

下边是硬件的BOM,价格是我购买时的淘宝价:

硬件 价格 说明
WeMos D1 ¥16.5 支持Wifi的单片机,可以使用Arduino的IDE进行开发
DHT22 ¥13.7 可能是使用最广泛的温湿度模块之一,测量结果准确
Arduino光敏电阻模块 ¥3.49 量程较短,弱光测量效果不理想
攀藤科技G5 ¥130 淘宝上能找到的比较靠谱的空气质量传感器

装起来就是这个样子的:

IMG_9566.JPG

下边是代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
#include <Arduino.h>
#include <SoftwareSerial.h>
#include <ESP8266WiFi.h>
#include <MQTTClient.h>
#include <Adafruit_Sensor.h>
#include <DHT.h>
#include <DHT_U.h>
WiFiClient net;
MQTTClient client;
// DHT22传感器连在Pin D4
DHT_Unified dht(D4, DHT22);
// 攀藤科技G5使用serial连接,这里使用Pin D1与D2做Software Serial
SoftwareSerial pmSerial(D1, D2);
const char ssid[] = "你的WIFI网络名称";
const char pass[] = "你的WIFI密码";
#define LENG 31 //0x42 + 31 bytes equal to 32 bytes
unsigned char buf[LENG];
int PM01Value = 0; // PM1值
int PM2_5Value = 0; // PM2.5值
int PM10Value = 0; // PM10值
unsigned long lastMillis = 0;
void connect(); // <- predefine connect() for setup()
void setup() {
Serial.begin(115200);
pmSerial.begin(9600);
pmSerial.setTimeout(1500);
dht.begin();
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("------------------------------------");
WiFi.begin(ssid, pass);
// Note: Local domain names (e.g. "Computer.local" on OSX) are not supported by Arduino.
// You need to set the IP address directly.
client.begin("MQTT服务器地址", net);
client.onMessage(messageReceived);
connect();
}
void connect() {
Serial.print("checking wifi...");
while (WiFi.status() != WL_CONNECTED) {
Serial.print(".");
delay(1000);
}
Serial.print("\nconnecting...");
while (!client.connect("arduino", "try", "try")) {
Serial.print(".");
delay(1000);
}
Serial.println("\nconnected!");
// 这里可以订阅自己发布的消息,调试用
// client.subscribe("/home/livingroom/brightness");
// client.subscribe("/home/livingroom/temperature");
// client.subscribe("/home/livingroom/humidity");
// client.subscribe("/home/livingroom/air");
}
void loop() {
client.loop();
delay(10); // <- fixes some issues with WiFi stability
if (!client.connected()) {
connect();
}
// 每5秒推送一次状态
if (millis() - lastMillis > 5000) {
lastMillis = millis();
client.publish("/home/livingroom/brightness", "{\"value\": " + String(1024 - analogRead(A0)) + "}");
sensors_event_t event;
dht.temperature().getEvent(&event);
if (isnan(event.temperature)) {
Serial.println("Error reading temperature!");
}
else {
client.publish("/home/livingroom/temperature", "{\"value\": " + String(event.temperature) + "}");
}
// Get humidity event and print its value.
dht.humidity().getEvent(&event);
if (isnan(event.relative_humidity)) {
Serial.println("Error reading humidity!");
}
else {
client.publish("/home/livingroom/humidity", "{\"value\": " + String(event.relative_humidity) + "}");
}
if (pmSerial.find(0x42)) { //start to read when detect 0x42
pmSerial.readBytes(buf, LENG);
if (buf[0] == 0x4d) {
if (checkValue(buf, LENG)) {
PM01Value = transmitPM01(buf); //count PM1.0 value of the air detector module
PM2_5Value = transmitPM2_5(buf); //count PM2.5 value of the air detector module
PM10Value = transmitPM10(buf); //count PM10 value of the air detector module
client.publish("/home/livingroom/air", "{\"pm1\":" + String(PM01Value) + ", \"pm25\": " + String(PM2_5Value) + ", \"pm10\": " + String(PM10Value) + "}");
}
}
}
}
}
void messageReceived(String &topic, String &payload) {
Serial.println("incoming: " + topic + " - " + payload);
}
char checkValue(unsigned char *thebuf, char leng)
{
char receiveflag = 0;
int receiveSum = 0;
for (int i = 0; i < (leng - 2); i++) {
receiveSum = receiveSum + thebuf[i];
}
receiveSum = receiveSum + 0x42;
if (receiveSum == ((thebuf[leng - 2] << 8) + thebuf[leng - 1])) //check the serial data
{
receiveSum = 0;
receiveflag = 1;
}
return receiveflag;
}
int transmitPM01(unsigned char *thebuf)
{
int PM01Val;
PM01Val = ((thebuf[3] << 8) + thebuf[4]); //count PM1.0 value of the air detector module
return PM01Val;
}
int transmitPM2_5(unsigned char *thebuf)
{
int PM2_5Val;
PM2_5Val = ((thebuf[5] << 8) + thebuf[6]); //count PM2.5 value of the air detector module
return PM2_5Val;
}
int transmitPM10(unsigned char *thebuf)
{
int PM10Val;
PM10Val = ((thebuf[7] << 8) + thebuf[8]); //count PM10 value of the air detector module
return PM10Val;
}

编译烧录还需要在Arduino IDE的库管器中安装下边的库:

  • Adafruit Unified Sensor
  • DHT sensor library
  • MQTT

Home Assistant

关于Home Assistant介绍与安装可以参考使用Docker安装Home Bridge与Home Assistant,用Siri控制智能家居

Home Assistant的配置如下,需要替换“MQTT服务器的IP地址”为真实的服务器地址:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
homeassistant:
customize:
sensor.brightness_sensor:
friendly_name: '光照强度'
sensor.temperature_sensor:
friendly_name: '室内温度'
sensor.humidity_sensor:
friendly_name: '室内湿度'
sensor.pm1_sensor:
friendly_name: 'PM1.0'
sensor.pm25_sensor:
friendly_name: 'PM2.5'
sensor.pm10_sensor:
friendly_name: 'PM10'
mqtt:
broker: MQTT服务器的IP地址
discovery: true
discovery_prefix: homeassistant
birth_message:
topic: 'hass/status'
payload: 'online'
will_message:
topic: 'hass/status'
payload: 'offline'
sensor:
- platform: mqtt
name: "Brightness Sensor"
state_topic: "/home/livingroom/brightness"
value_template: "{{ value_json.value }}"
- platform: mqtt
name: "Temperature Sensor"
state_topic: "/home/livingroom/temperature"
unit_of_measurement: "°C"
value_template: "{{ value_json.value }}"
- platform: mqtt
name: "Humidity Sensor"
state_topic: "/home/livingroom/humidity"
unit_of_measurement: "%"
value_template: "{{ value_json.value }}"
- platform: mqtt
name: "PM1 Sensor"
state_topic: "/home/livingroom/air"
unit_of_measurement: "ug/m3"
value_template: "{{ value_json.pm1 }}"
- platform: mqtt
name: "PM2.5 Sensor"
state_topic: "/home/livingroom/air"
unit_of_measurement: "ug/m3"
value_template: "{{ value_json.pm25 }}"
- platform: mqtt
name: "PM10 Sensor"
state_topic: "/home/livingroom/air"
unit_of_measurement: "ug/m3"
value_template: "{{ value_json.pm10 }}"

在Home Assistant中看到的效果是这样的:

IMG_0942.PNG

参考