MicroPython: MQTT – Публикация показаний BME680 (ESP32/ESP8266)
Узнайте, как программировать платы ESP32 или ESP8266 с MicroPython для публикации показаний датчика BME680 (температура, влажность, давление и сопротивление газа) по MQTT на любую платформу, поддерживающую MQTT, или любой MQTT-клиент. В качестве примера мы будем публиковать показания датчика в Node-RED Dashboard.
Рекомендуем прочитать: Что такое MQTT и как он работает
Примечание
Это руководство совместимо как с ESP32, так и с ESP8266.
Обзор проекта
На следующей диаграмме показан общий обзор проекта, который мы создадим.
ESP запрашивает показания температуры, влажности, давления и газа с датчика BME680;
Показания температуры публикуются в топик esp/bme680/temperature;
Показания влажности публикуются в топик esp/bme680/humidity;
Показания давления публикуются в топик esp/bme680/pressure;
Показания газа публикуются в топик esp/bme680/gas;
Node-RED подписан на эти топики;
Node-RED получает показания датчика и отображает их на шкалах (gauges);
Вы можете получать показания на любой другой платформе, поддерживающей MQTT, и обрабатывать данные по своему усмотрению.
Предварительные требования
Прежде чем продолжить это руководство, убедитесь, что вы выполнили следующие предварительные требования.
Для этого руководства вам нужна прошивка MicroPython, установленная на ESP32 или ESP8266. Также нужна среда разработки для написания и загрузки кода. Мы рекомендуем Thonny IDE или uPyCraft IDE:
Thonny IDE:
uPyCraft IDE:
MQTT-брокер
Для использования MQTT вам нужен брокер. Мы будем использовать брокер Mosquitto, установленный на Raspberry Pi. Прочитайте Как установить Mosquitto Broker на Raspberry Pi.
Если вы не знакомы с MQTT, обязательно прочитайте наше вводное руководство: Что такое MQTT и как он работает.
Необходимые компоненты
Для этого руководства вам понадобятся следующие компоненты:
Библиотека umqttsimple
Для использования MQTT с ESP32/ESP8266 и MicroPython мы будем использовать библиотеку umqttsimple.py. Следуйте инструкциям для вашей IDE:
Загрузка библиотеки umqttsimple с помощью uPyCraft IDE
Загрузка библиотеки umqttsimple с помощью Thonny IDE
A. Загрузка библиотеки umqttsimple с помощью uPyCraft IDE
Создайте новый файл в uPyCraft IDE, нажав File > New.
Скопируйте следующий код библиотеки
umqttsimpleв этот файл.
try:
import usocket as socket
except:
import socket
import ustruct as struct
from ubinascii import hexlify
class MQTTException(Exception):
pass
class MQTTClient:
def __init__(self, client_id, server, port=0, user=None, password=None, keepalive=0,
ssl=False, ssl_params={}):
if port == 0:
port = 8883 if ssl else 1883
self.client_id = client_id
self.sock = None
self.server = server
self.port = port
self.ssl = ssl
self.ssl_params = ssl_params
self.pid = 0
self.cb = None
self.user = user
self.pswd = password
self.keepalive = keepalive
self.lw_topic = None
self.lw_msg = None
self.lw_qos = 0
self.lw_retain = False
def _send_str(self, s):
self.sock.write(struct.pack("!H", len(s)))
self.sock.write(s)
def _recv_len(self):
n = 0
sh = 0
while 1:
b = self.sock.read(1)[0]
n |= (b & 0x7f) << sh
if not b & 0x80:
return n
sh += 7
def set_callback(self, f):
self.cb = f
def set_last_will(self, topic, msg, retain=False, qos=0):
assert 0 <= qos <= 2
assert topic
self.lw_topic = topic
self.lw_msg = msg
self.lw_qos = qos
self.lw_retain = retain
def connect(self, clean_session=True):
self.sock = socket.socket()
addr = socket.getaddrinfo(self.server, self.port)[0][-1]
self.sock.connect(addr)
if self.ssl:
import ussl
self.sock = ussl.wrap_socket(self.sock, **self.ssl_params)
premsg = bytearray(b"\x10\0\0\0\0\0")
msg = bytearray(b"\x04MQTT\x04\x02\0\0")
sz = 10 + 2 + len(self.client_id)
msg[6] = clean_session << 1
if self.user is not None:
sz += 2 + len(self.user) + 2 + len(self.pswd)
msg[6] |= 0xC0
if self.keepalive:
assert self.keepalive < 65536
msg[7] |= self.keepalive >> 8
msg[8] |= self.keepalive & 0x00FF
if self.lw_topic:
sz += 2 + len(self.lw_topic) + 2 + len(self.lw_msg)
msg[6] |= 0x4 | (self.lw_qos & 0x1) << 3 | (self.lw_qos & 0x2) << 3
msg[6] |= self.lw_retain << 5
i = 1
while sz > 0x7f:
premsg[i] = (sz & 0x7f) | 0x80
sz >>= 7
i += 1
premsg[i] = sz
self.sock.write(premsg, i + 2)
self.sock.write(msg)
self._send_str(self.client_id)
if self.lw_topic:
self._send_str(self.lw_topic)
self._send_str(self.lw_msg)
if self.user is not None:
self._send_str(self.user)
self._send_str(self.pswd)
resp = self.sock.read(4)
assert resp[0] == 0x20 and resp[1] == 0x02
if resp[3] != 0:
raise MQTTException(resp[3])
return resp[2] & 1
def disconnect(self):
self.sock.write(b"\xe0\0")
self.sock.close()
def ping(self):
self.sock.write(b"\xc0\0")
def publish(self, topic, msg, retain=False, qos=0):
pkt = bytearray(b"\x30\0\0\0")
pkt[0] |= qos << 1 | retain
sz = 2 + len(topic) + len(msg)
if qos > 0:
sz += 2
assert sz < 2097152
i = 1
while sz > 0x7f:
pkt[i] = (sz & 0x7f) | 0x80
sz >>= 7
i += 1
pkt[i] = sz
self.sock.write(pkt, i + 1)
self._send_str(topic)
if qos > 0:
self.pid += 1
pid = self.pid
struct.pack_into("!H", pkt, 0, pid)
self.sock.write(pkt, 2)
self.sock.write(msg)
if qos == 1:
while 1:
op = self.wait_msg()
if op == 0x40:
sz = self.sock.read(1)
assert sz == b"\x02"
rcv_pid = self.sock.read(2)
rcv_pid = rcv_pid[0] << 8 | rcv_pid[1]
if pid == rcv_pid:
return
elif qos == 2:
assert 0
def subscribe(self, topic, qos=0):
assert self.cb is not None, "Subscribe callback is not set"
pkt = bytearray(b"\x82\0\0\0")
self.pid += 1
struct.pack_into("!BH", pkt, 1, 2 + 2 + len(topic) + 1, self.pid)
self.sock.write(pkt)
self._send_str(topic)
self.sock.write(qos.to_bytes(1, "little"))
while 1:
op = self.wait_msg()
if op == 0x90:
resp = self.sock.read(4)
assert resp[1] == pkt[2] and resp[2] == pkt[3]
if resp[3] == 0x80:
raise MQTTException(resp[3])
return
def wait_msg(self):
res = self.sock.read(1)
self.sock.setblocking(True)
if res is None:
return None
if res == b"":
raise OSError(-1)
if res == b"\xd0":
sz = self.sock.read(1)[0]
assert sz == 0
return None
op = res[0]
if op & 0xf0 != 0x30:
return op
sz = self._recv_len()
topic_len = self.sock.read(2)
topic_len = (topic_len[0] << 8) | topic_len[1]
topic = self.sock.read(topic_len)
sz -= topic_len + 2
if op & 6:
pid = self.sock.read(2)
pid = pid[0] << 8 | pid[1]
sz -= 2
msg = self.sock.read(sz)
self.cb(topic, msg)
if op & 6 == 2:
pkt = bytearray(b"\x40\x02\0\0")
struct.pack_into("!H", pkt, 2, pid)
self.sock.write(pkt)
elif op & 6 == 4:
assert 0
def check_msg(self):
self.sock.setblocking(False)
return self.wait_msg()
Сохраните файл, нажав File > Save.
Назовите файл
umqttsimple.pyи нажмите OK.
Нажмите кнопку Download and Run.
Файл должен появиться в устройстве в папке /. Он также был загружен на плату.
B. Загрузка библиотеки umqttsimple с помощью Thonny IDE
Перейдите в репозиторий GitHub Raw umqttsimple и скопируйте код библиотеки.
В Thonny IDE создайте новый файл (File > New). Вставьте скопированный код.
Перейдите в File > Save as… и выберите сохранение на устройство MicroPython device.
Назовите файл
umqttsimple.pyи нажмите OK.
Файл должен появиться на устройстве. Библиотека загружена на плату.
Библиотека BME680
Вам также нужно загрузить на плату библиотеку bme680.py. Следуйте тем же инструкциям, что и для библиотеки umqttsimple, но для следующего кода. Сохраните файл с именем bme680.py.
import time
import math
from micropython import const
from ubinascii import hexlify as hex
try:
import struct
except ImportError:
import ustruct as struct
_BME680_CHIPID = const(0x61)
_BME680_REG_CHIPID = const(0xD0)
_BME680_BME680_COEFF_ADDR1 = const(0x89)
_BME680_BME680_COEFF_ADDR2 = const(0xE1)
_BME680_BME680_RES_HEAT_0 = const(0x5A)
_BME680_BME680_GAS_WAIT_0 = const(0x64)
_BME680_REG_SOFTRESET = const(0xE0)
_BME680_REG_CTRL_GAS = const(0x71)
_BME680_REG_CTRL_HUM = const(0x72)
_BME280_REG_STATUS = const(0xF3)
_BME680_REG_CTRL_MEAS = const(0x74)
_BME680_REG_CONFIG = const(0x75)
_BME680_REG_PAGE_SELECT = const(0x73)
_BME680_REG_MEAS_STATUS = const(0x1D)
_BME680_REG_PDATA = const(0x1F)
_BME680_REG_TDATA = const(0x22)
_BME680_REG_HDATA = const(0x25)
_BME680_SAMPLERATES = (0, 1, 2, 4, 8, 16)
_BME680_FILTERSIZES = (0, 1, 3, 7, 15, 31, 63, 127)
_BME680_RUNGAS = const(0x10)
_LOOKUP_TABLE_1 = (2147483647.0, 2147483647.0, 2147483647.0, 2147483647.0, 2147483647.0,
2126008810.0, 2147483647.0, 2130303777.0, 2147483647.0, 2147483647.0,
2143188679.0, 2136746228.0, 2147483647.0, 2126008810.0, 2147483647.0,
2147483647.0)
_LOOKUP_TABLE_2 = (4096000000.0, 2048000000.0, 1024000000.0, 512000000.0, 255744255.0, 127110228.0,
64000000.0, 32258064.0, 16016016.0, 8000000.0, 4000000.0, 2000000.0, 1000000.0,
500000.0, 250000.0, 125000.0)
def _read24(arr):
ret = 0.0
for b in arr:
ret *= 256.0
ret += float(b & 0xFF)
return ret
class Adafruit_BME680:
def __init__(self, *, refresh_rate=10):
self._write(_BME680_REG_SOFTRESET, [0xB6])
time.sleep(0.005)
chip_id = self._read_byte(_BME680_REG_CHIPID)
if chip_id != _BME680_CHIPID:
raise RuntimeError('Failed 0x%x' % chip_id)
self._read_calibration()
self._write(_BME680_BME680_RES_HEAT_0, [0x73])
self._write(_BME680_BME680_GAS_WAIT_0, [0x65])
self.sea_level_pressure = 1013.25
self._pressure_oversample = 0b011
self._temp_oversample = 0b100
self._humidity_oversample = 0b010
self._filter = 0b010
self._adc_pres = None
self._adc_temp = None
self._adc_hum = None
self._adc_gas = None
self._gas_range = None
self._t_fine = None
self._last_reading = 0
self._min_refresh_time = 1000 / refresh_rate
@property
def pressure_oversample(self):
return _BME680_SAMPLERATES[self._pressure_oversample]
@pressure_oversample.setter
def pressure_oversample(self, sample_rate):
if sample_rate in _BME680_SAMPLERATES:
self._pressure_oversample = _BME680_SAMPLERATES.index(sample_rate)
else:
raise RuntimeError("Invalid")
@property
def humidity_oversample(self):
return _BME680_SAMPLERATES[self._humidity_oversample]
@humidity_oversample.setter
def humidity_oversample(self, sample_rate):
if sample_rate in _BME680_SAMPLERATES:
self._humidity_oversample = _BME680_SAMPLERATES.index(sample_rate)
else:
raise RuntimeError("Invalid")
@property
def temperature_oversample(self):
return _BME680_SAMPLERATES[self._temp_oversample]
@temperature_oversample.setter
def temperature_oversample(self, sample_rate):
if sample_rate in _BME680_SAMPLERATES:
self._temp_oversample = _BME680_SAMPLERATES.index(sample_rate)
else:
raise RuntimeError("Invalid")
@property
def filter_size(self):
return _BME680_FILTERSIZES[self._filter]
@filter_size.setter
def filter_size(self, size):
if size in _BME680_FILTERSIZES:
self._filter = _BME680_FILTERSIZES[size]
else:
raise RuntimeError("Invalid")
@property
def temperature(self):
self._perform_reading()
calc_temp = (((self._t_fine * 5) + 128) / 256)
return calc_temp / 100
@property
def pressure(self):
self._perform_reading()
var1 = (self._t_fine / 2) - 64000
var2 = ((var1 / 4) * (var1 / 4)) / 2048
var2 = (var2 * self._pressure_calibration[5]) / 4
var2 = var2 + (var1 * self._pressure_calibration[4] * 2)
var2 = (var2 / 4) + (self._pressure_calibration[3] * 65536)
var1 = (((((var1 / 4) * (var1 / 4)) / 8192) *
(self._pressure_calibration[2] * 32) / 8) +
((self._pressure_calibration[1] * var1) / 2))
var1 = var1 / 262144
var1 = ((32768 + var1) * self._pressure_calibration[0]) / 32768
calc_pres = 1048576 - self._adc_pres
calc_pres = (calc_pres - (var2 / 4096)) * 3125
calc_pres = (calc_pres / var1) * 2
var1 = (self._pressure_calibration[8] * (((calc_pres / 8) * (calc_pres / 8)) / 8192)) / 4096
var2 = ((calc_pres / 4) * self._pressure_calibration[7]) / 8192
var3 = (((calc_pres / 256) ** 3) * self._pressure_calibration[9]) / 131072
calc_pres += ((var1 + var2 + var3 + (self._pressure_calibration[6] * 128)) / 16)
return calc_pres/100
@property
def humidity(self):
self._perform_reading()
temp_scaled = ((self._t_fine * 5) + 128) / 256
var1 = ((self._adc_hum - (self._humidity_calibration[0] * 16)) -
((temp_scaled * self._humidity_calibration[2]) / 200))
var2 = (self._humidity_calibration[1] *
(((temp_scaled * self._humidity_calibration[3]) / 100) +
(((temp_scaled * ((temp_scaled * self._humidity_calibration[4]) / 100)) /
64) / 100) + 16384)) / 1024
var3 = var1 * var2
var4 = self._humidity_calibration[5] * 128
var4 = (var4 + ((temp_scaled * self._humidity_calibration[6]) / 100)) / 16
var5 = ((var3 / 16384) * (var3 / 16384)) / 1024
var6 = (var4 * var5) / 2
calc_hum = (((var3 + var6) / 1024) * 1000) / 4096
calc_hum /= 1000
if calc_hum > 100:
calc_hum = 100
if calc_hum < 0:
calc_hum = 0
return calc_hum
@property
def altitude(self):
pressure = self.pressure
return 44330 * (1.0 - math.pow(pressure / self.sea_level_pressure, 0.1903))
@property
def gas(self):
self._perform_reading()
var1 = ((1340 + (5 * self._sw_err)) * (_LOOKUP_TABLE_1[self._gas_range])) / 65536
var2 = ((self._adc_gas * 32768) - 16777216) + var1
var3 = (_LOOKUP_TABLE_2[self._gas_range] * var1) / 512
calc_gas_res = (var3 + (var2 / 2)) / var2
return int(calc_gas_res)
def _perform_reading(self):
if (time.ticks_diff(self._last_reading, time.ticks_ms()) * time.ticks_diff(0, 1)
< self._min_refresh_time):
return
self._write(_BME680_REG_CONFIG, [self._filter << 2])
self._write(_BME680_REG_CTRL_MEAS,
[(self._temp_oversample << 5)|(self._pressure_oversample << 2)])
self._write(_BME680_REG_CTRL_HUM, [self._humidity_oversample])
self._write(_BME680_REG_CTRL_GAS, [_BME680_RUNGAS])
ctrl = self._read_byte(_BME680_REG_CTRL_MEAS)
ctrl = (ctrl & 0xFC) | 0x01
self._write(_BME680_REG_CTRL_MEAS, [ctrl])
new_data = False
while not new_data:
data = self._read(_BME680_REG_MEAS_STATUS, 15)
new_data = data[0] & 0x80 != 0
time.sleep(0.005)
self._last_reading = time.ticks_ms()
self._adc_pres = _read24(data[2:5]) / 16
self._adc_temp = _read24(data[5:8]) / 16
self._adc_hum = struct.unpack('>H', bytes(data[8:10]))[0]
self._adc_gas = int(struct.unpack('>H', bytes(data[13:15]))[0] / 64)
self._gas_range = data[14] & 0x0F
var1 = (self._adc_temp / 8) - (self._temp_calibration[0] * 2)
var2 = (var1 * self._temp_calibration[1]) / 2048
var3 = ((var1 / 2) * (var1 / 2)) / 4096
var3 = (var3 * self._temp_calibration[2] * 16) / 16384
self._t_fine = int(var2 + var3)
def _read_calibration(self):
coeff = self._read(_BME680_BME680_COEFF_ADDR1, 25)
coeff += self._read(_BME680_BME680_COEFF_ADDR2, 16)
coeff = list(struct.unpack('<hbBHhbBhhbbHhhBBBHbbbBbHhbb', bytes(coeff[1:39])))
coeff = [float(i) for i in coeff]
self._temp_calibration = [coeff[x] for x in [23, 0, 1]]
self._pressure_calibration = [coeff[x] for x in [3, 4, 5, 7, 8, 10, 9, 12, 13, 14]]
self._humidity_calibration = [coeff[x] for x in [17, 16, 18, 19, 20, 21, 22]]
self._gas_calibration = [coeff[x] for x in [25, 24, 26]]
self._humidity_calibration[1] *= 16
self._humidity_calibration[1] += self._humidity_calibration[0] % 16
self._humidity_calibration[0] /= 16
self._heat_range = (self._read_byte(0x02) & 0x30) / 16
self._heat_val = self._read_byte(0x00)
self._sw_err = (self._read_byte(0x04) & 0xF0) / 16
def _read_byte(self, register):
return self._read(register, 1)[0]
def _read(self, register, length):
raise NotImplementedError()
def _write(self, register, values):
raise NotImplementedError()
class BME680_I2C(Adafruit_BME680):
def __init__(self, i2c, address=0x77, debug=False, *, refresh_rate=10):
self._i2c = i2c
self._address = address
self._debug = debug
super().__init__(refresh_rate=refresh_rate)
def _read(self, register, length):
result = bytearray(length)
self._i2c.readfrom_mem_into(self._address, register & 0xff, result)
if self._debug:
print("\t${:x} read ".format(register), " ".join(["{:02x}".format(i) for i in result]))
return result
def _write(self, register, values):
if self._debug:
print("\t${:x} write".format(register), " ".join(["{:02x}".format(i) for i in values]))
for value in values:
self._i2c.writeto_mem(self._address, register, bytearray([value & 0xFF]))
register += 1
Если вы хотите узнать больше о датчике BME680, прочитайте наше руководство: MicroPython: BME680 с ESP32 и ESP8266.
Схемы подключения
Подключите датчик BME680 к ESP32 или ESP8266 по I2C.
ESP32 с BME680
Подключите BME680 к ESP32 следующим образом:
BME680 |
ESP32 |
|---|---|
SCL |
GPIO 22 |
SDA |
GPIO 21 |
VCC |
3.3V |
GND |
GND |
ESP8266 NodeMCU с BME680
Подключите BME680 к ESP8266 следующим образом:
BME680 |
ESP8266 |
|---|---|
SCL |
GPIO 5 (D1) |
SDA |
GPIO 4 (D2) |
VCC |
3.3V |
GND |
GND |
Код – MicroPython MQTT публикация показаний BME680
Скопируйте следующий код в файл main.py вашей платы. Этот код считывает показания с датчика BME680 и публикует их по MQTT каждые 5 секунд.
import time
from umqttsimple import MQTTClient
import ubinascii
import machine
import micropython
import network
import esp
from bme680 import *
from machine import Pin, I2C
esp.osdebug(None)
import gc
gc.collect()
ssid = 'REPLACE_WITH_YOUR_SSID'
password = 'REPLACE_WITH_YOUR_PASSWORD'
mqtt_server = 'XXX.XXX.XXX.XXX'
client_id = ubinascii.hexlify(machine.unique_id())
topic_pub_temp = b'esp/bme680/temperature'
topic_pub_hum = b'esp/bme680/humidity'
topic_pub_pres = b'esp/bme680/pressure'
topic_pub_gas = b'esp/bme680/gas'
last_message = 0
message_interval = 5
station = network.WLAN(network.STA_IF)
station.active(True)
station.connect(ssid, password)
while station.isconnected() == False:
pass
print('Connection successful')
# ESP32 - Pin assignment
#i2c = I2C(scl=Pin(22), sda=Pin(21))
# ESP8266 - Pin assignment
i2c = I2C(scl=Pin(5), sda=Pin(4))
bme = BME680_I2C(i2c=i2c)
def connect_mqtt():
global client_id, mqtt_server
client = MQTTClient(client_id, mqtt_server)
client.connect()
print('Connected to %s MQTT broker' % (mqtt_server))
return client
def restart_and_reconnect():
print('Failed to connect to MQTT broker. Reconnecting...')
time.sleep(10)
machine.reset()
def read_bme_sensor():
try:
temp = (b'{:.2f}'.format(bme.temperature))
hum = (b'{:.2f}'.format(bme.humidity))
pres = (b'{:.2f}'.format(bme.pressure))
gas = (b'{:.2f}'.format(bme.gas/1000))
return temp, hum, pres, gas
except OSError as e:
return('Failed to read sensor.')
try:
client = connect_mqtt()
except OSError as e:
restart_and_reconnect()
while True:
try:
if (time.time() - last_message) > message_interval:
temp, hum, pres, gas = read_bme_sensor()
print(temp)
print(hum)
print(pres)
print(gas)
client.publish(topic_pub_temp, temp)
client.publish(topic_pub_hum, hum)
client.publish(topic_pub_pres, pres)
client.publish(topic_pub_gas, gas)
last_message = time.time()
except OSError as e:
restart_and_reconnect()
Как работает код
Давайте рассмотрим, как работает этот код.
Начнём с импорта необходимых библиотек:
import time
from umqttsimple import MQTTClient
import ubinascii
import machine
import micropython
import network
import esp
from bme680 import *
from machine import Pin, I2C
Задайте ваши сетевые учётные данные в следующих переменных, чтобы ESP могла подключиться к вашему роутеру:
ssid = 'REPLACE_WITH_YOUR_SSID'
password = 'REPLACE_WITH_YOUR_PASSWORD'
Введите IP-адрес вашего MQTT-брокера Raspberry Pi. Он должен быть в следующем формате:
mqtt_server = 'XXX.XXX.XXX.XXX'
Например: mqtt_server = '192.168.1.106'
Если ваш MQTT-брокер требует аутентификацию по имени пользователя и паролю, вам нужно создать объект MQTTClient следующим образом:
client = MQTTClient(client_id, mqtt_server, user=b'YOUR_USERNAME', password=b'YOUR_PASSWORD')
Создаётся уникальный идентификатор клиента MQTT. Мы используем ubinascii.hexlify() для получения уникального ID в hex-формате:
client_id = ubinascii.hexlify(machine.unique_id())
Далее создаются четыре переменные для хранения MQTT-топиков, в которые будут публиковаться показания: температура, влажность, давление и газ.
topic_pub_temp = b'esp/bme680/temperature'
topic_pub_hum = b'esp/bme680/humidity'
topic_pub_pres = b'esp/bme680/pressure'
topic_pub_gas = b'esp/bme680/gas'
Переменная message_interval определяет интервал между каждой публикацией в секундах. Здесь мы установили 5 секунд. При необходимости измените это значение.
last_message = 0
message_interval = 5
Далее код подключается к вашей сети Wi-Fi:
station = network.WLAN(network.STA_IF)
station.active(True)
station.connect(ssid, password)
while station.isconnected() == False:
pass
print('Connection successful')
Создайте объект I2C с назначением пинов для вашей платы. Если вы используете ESP32, раскомментируйте первую строку и закомментируйте вторую. Если используете ESP8266, оставьте как есть.
# ESP32 - Pin assignment
#i2c = I2C(scl=Pin(22), sda=Pin(21))
# ESP8266 - Pin assignment
i2c = I2C(scl=Pin(5), sda=Pin(4))
Затем создайте объект BME680_I2C с именем bme:
bme = BME680_I2C(i2c=i2c)
Функция connect_mqtt() создаёт клиент MQTT и подключается к вашему брокеру:
def connect_mqtt():
global client_id, mqtt_server
client = MQTTClient(client_id, mqtt_server)
client.connect()
print('Connected to %s MQTT broker' % (mqtt_server))
return client
Функция restart_and_reconnect() сбрасывает плату ESP, если не удаётся подключиться к MQTT-брокеру:
def restart_and_reconnect():
print('Failed to connect to MQTT broker. Reconnecting...')
time.sleep(10)
machine.reset()
Функция read_bme_sensor() считывает показания температуры, влажности, давления и газа с датчика BME680 и возвращает их:
def read_bme_sensor():
try:
temp = (b'{:.2f}'.format(bme.temperature))
hum = (b'{:.2f}'.format(bme.humidity))
pres = (b'{:.2f}'.format(bme.pressure))
gas = (b'{:.2f}'.format(bme.gas/1000))
return temp, hum, pres, gas
except OSError as e:
return('Failed to read sensor.')
В основном цикле while мы проверяем, прошёл ли интервал, и публикуем показания по MQTT. Каждое показание публикуется в свой отдельный топик:
while True:
try:
if (time.time() - last_message) > message_interval:
temp, hum, pres, gas = read_bme_sensor()
print(temp)
print(hum)
print(pres)
print(gas)
client.publish(topic_pub_temp, temp)
client.publish(topic_pub_hum, hum)
client.publish(topic_pub_pres, pres)
client.publish(topic_pub_gas, gas)
last_message = time.time()
except OSError as e:
restart_and_reconnect()
В случае ошибки (например, если MQTT-брокер недоступен) вызывается функция restart_and_reconnect(), которая перезагружает плату через 10 секунд.
Подготовка Node-RED Dashboard
Для визуализации показаний датчика мы будем использовать Node-RED Dashboard. Если у вас нет Node-RED, сначала установите его на Raspberry Pi.
Настройка потока (Flow)
Перетащите на рабочую область четыре узла mqtt in (по одному для каждого топика) и соответствующие узлы отображения – два узла gauge (для температуры и влажности) и два узла text (для давления и газа).
Дважды щёлкните по первому узлу mqtt in и настройте его. Добавьте новый сервер mqtt-broker, указав IP-адрес
localhostи порт1883. Укажите топикesp/bme680/temperature.
Настройте узел gauge для температуры. Установите диапазон от 0 до 40 и единицу измерения.
Повторите аналогичные настройки для остальных узлов: влажность (gauge, диапазон 30–100), давление (text, hPa) и газ (text, KOhm). Соедините узлы между собой.
Нажмите кнопку Deploy в правом верхнем углу, чтобы применить изменения.
Импорт потока Node-RED
Вместо ручной настройки вы также можете импортировать готовый поток. Перейдите в Menu > Import и вставьте следующий JSON:
[{"id":"3b7f947c.9759ec","type":"mqtt in","z":"254c9c97.f85b34","name":"","topic":"esp/bme680/temperature","qos":"1","datatype":"auto","broker":"8db3fac0.99dd48","x":470,"y":2640,"wires":[["b87b21c3.96672"]]},{"id":"b87b21c3.96672","type":"ui_gauge","z":"254c9c97.f85b34","name":"","group":"37de8fe8.46846","order":2,"width":0,"height":0,"gtype":"gage","title":"Temperature","label":"\u00baC","format":"{{value}}","min":0,"max":"40","colors":["#00b500","#f7df09","#ca3838"],"seg1":"","seg2":"","x":690,"y":2640,"wires":[]},{"id":"f92248f4.545778","type":"mqtt in","z":"254c9c97.f85b34","name":"","topic":"esp/bme680/humidity","qos":"1","datatype":"auto","broker":"8db3fac0.99dd48","x":460,"y":2700,"wires":[["4114a401.5ac69c"]]},{"id":"4114a401.5ac69c","type":"ui_gauge","z":"254c9c97.f85b34","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":680,"y":2700,"wires":[]},{"id":"ad51f895.2c2848","type":"mqtt in","z":"254c9c97.f85b34","name":"","topic":"esp/bme680/pressure","qos":"1","datatype":"auto","broker":"8db3fac0.99dd48","x":460,"y":2760,"wires":[["3a95123b.66405e"]]},{"id":"c074e688.198b78","type":"mqtt in","z":"254c9c97.f85b34","name":"","topic":"esp/bme680/gas","qos":"1","datatype":"auto","broker":"8db3fac0.99dd48","x":440,"y":2820,"wires":[["d3539c06.00a17"]]},{"id":"3a95123b.66405e","type":"ui_text","z":"254c9c97.f85b34","group":"37de8fe8.46846","order":2,"width":0,"height":0,"name":"","label":"Pressure","format":"{{msg.payload}} hPa","layout":"row-spread","x":680,"y":2760,"wires":[]},{"id":"d3539c06.00a17","type":"ui_text","z":"254c9c97.f85b34","group":"37de8fe8.46846","order":3,"width":0,"height":0,"name":"","label":"Gas","format":"{{msg.payload}} KOhm","layout":"row-spread","x":670,"y":2820,"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":"BME680","tab":"53b8c8f9.cfbe48","order":1,"disp":true,"width":"6","collapse":false},{"id":"53b8c8f9.cfbe48","type":"ui_tab","z":"","name":"Home","icon":"dashboard","order":5,"disabled":false,"hidden":false}]
Демонстрация
После загрузки всех файлов (umqttsimple.py, bme680.py и main.py) на плату ESP, откройте браузер и перейдите по адресу:
http://<ip-адрес-raspberry-pi>:1880/ui
Вы увидите панель мониторинга Node-RED с показаниями температуры, влажности, давления и газа, которые обновляются каждые 5 секунд.
Связанные руководства
Заключение
В этом руководстве вы узнали, как публиковать показания датчика BME680 (температура, влажность, давление, газ) по протоколу MQTT с помощью MicroPython на ESP32 или ESP8266. Node-RED использовался для визуализации данных на панели мониторинга. Этот подход применим для различных сценариев домашней автоматизации и мониторинга окружающей среды.