MicroPython: MQTT – Публикация показаний DHT11/DHT22 (ESP32/ESP8266)
Узнайте, как запрограммировать платы ESP32 или ESP8266 с помощью MicroPython для публикации показаний датчика DHT11 или DHT22 (температура и влажность) через MQTT на любую платформу, поддерживающую MQTT, или в любой MQTT-клиент. В качестве примера мы будем публиковать показания датчиков в Node-RED Dashboard.
Рекомендуемое чтение: Что такое MQTT и как он работает
Примечание
Это руководство совместимо как с платами ESP32, так и с ESP8266.
Обзор проекта
Следующая диаграмма показывает общий обзор проекта, который мы создадим.
ESP запрашивает показания температуры и влажности у датчика DHT11 или DHT22;
Показания температуры публикуются в топике esp/dht/temperature;
Показания влажности публикуются в топике esp/dht/humidity;
Node-RED подписан на эти топики;
Node-RED получает показания датчиков и отображает их на шкалах (gauges);
Вы можете получать показания на любой другой платформе, поддерживающей MQTT, и обрабатывать их по своему усмотрению.
Предварительные требования
Перед продолжением работы с этим руководством убедитесь, что выполнены следующие предварительные требования.
Для работы с этим руководством вам необходима прошивка MicroPython, установленная на платах ESP32 или ESP8266. Также вам нужна IDE для написания и загрузки кода на плату. Мы предлагаем использовать Thonny IDE или uPyCraft IDE:
Thonny IDE:
uPyCraft IDE:
MQTT-брокер
Для использования MQTT вам нужен брокер. Мы будем использовать брокер Mosquitto, установленный на Raspberry Pi. Прочитайте Как установить брокер Mosquitto на Raspberry Pi.
Если вы не знакомы с MQTT, обязательно прочитайте наше вводное руководство: Что такое MQTT и как он работает
Необходимые компоненты
Для этого руководства вам понадобятся следующие компоненты:
Библиотека umqttsimple
Для использования MQTT с ESP32/ESP8266 и MicroPython мы будем использовать библиотеку umqttsimple.py. Следуйте инструкциям для IDE, которую вы используете:
Загрузка библиотеки umqttsimple с помощью uPyCraft IDE
Загрузка библиотеки umqttsimple с помощью Thonny IDE
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)
#print(hex(len(msg)), hexlify(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
#print(hex(len(pkt)), hexlify(pkt, ":"))
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)
#print(hex(len(pkt)), hexlify(pkt, ":"))
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)
#print(resp)
assert resp[1] == pkt[2] and resp[2] == pkt[3]
if resp[3] == 0x80:
raise MQTTException(resp[3])
return
# Wait for a single incoming MQTT message and process it.
# Subscribed messages are delivered to a callback previously
# set by .set_callback() method. Other (internal) MQTT
# messages processed internally.
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": # PINGRESP
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
# Checks whether a pending message from server is available.
# If not, returns immediately with None. Otherwise, does
# the same processing as wait_msg.
def check_msg(self):
self.sock.setblocking(False)
return self.wait_msg()
A. Загрузка библиотеки umqttsimple с помощью uPyCraft IDE
1. Создайте новый файл, нажав кнопку New File.
2. Скопируйте код библиотеки umqttsimple в него. Вы можете получить код библиотеки umqttsimple по следующей ссылке:
3. Сохраните файл, нажав кнопку Save.
4. Назовите этот новый файл «umqttsimple.py» и нажмите ok.
5. Нажмите кнопку Download and Run.
6. Файл должен быть сохранён в папке device с именем «umqttsimple.py», как показано на рисунке ниже.
Теперь вы можете использовать функционал библиотеки в своём коде, импортировав её.
B. Загрузка библиотеки umqttsimple с помощью Thonny IDE
1. Скопируйте код библиотеки в новый файл. Код библиотеки umqttsimple можно найти здесь.
2. Перейдите в File > Save as…
Если пункт «Save as…» отсутствует, проверьте, что вы правильно настроили Thonny IDE, как описано в следующем руководстве:
3. Выберите сохранение на «MicroPython device»:
4. Назовите файл umqttsimple.py и нажмите кнопку OK:
Вот и всё. Библиотека загружена на вашу плату. Чтобы убедиться, что она загружена успешно, перейдите в File > Save as… и выберите MicroPython device. Ваш файл должен быть в списке:
После загрузки библиотеки на плату вы можете использовать её функционал, импортировав библиотеку.
Схема подключения: ESP32 с DHT11/DHT22
Подключите датчик DHT22 или DHT11 к плате разработки ESP32, как показано на следующей схеме.
В этом примере мы подключаем вывод данных DHT к GPIO 14. Однако вы можете использовать любой другой подходящий цифровой пин.
Узнайте, как использовать GPIO ESP32, из нашего руководства: ESP32 Pinout Reference: какие GPIO-пины использовать?
Схема подключения: ESP8266 NodeMCU с DHT11/DHT22
Если вы используете ESP8266 NodeMCU, следуйте следующей схеме.
В этом примере мы подключаем вывод данных DHT к GPIO 14 (D5). Однако вы можете использовать любой другой подходящий цифровой пин.
Узнайте, как использовать GPIO ESP8266, из нашего руководства: ESP8266 Pinout Reference: какие GPIO-пины использовать?
Код
После загрузки библиотеки на ESP32 или ESP8266 скопируйте следующий код в файл main.py. Он публикует температуру и влажность в топиках esp/dht/temperature и esp/dht/humidity каждые 5 секунд.
# Полное описание проекта: https://RandomNerdTutorials.com/micropython-mqtt-publish-dht11-dht22-esp32-esp8266/
import time
from umqttsimple import MQTTClient
import ubinascii
import machine
import micropython
import network
import esp
from machine import Pin
import dht
esp.osdebug(None)
import gc
gc.collect()
ssid = 'REPLACE_WITH_YOUR_SSID'
password = 'REPLACE_WITH_YOUR_PASSWORD'
mqtt_server = '192.168.1.XXX'
#EXAMPLE IP ADDRESS or DOMAIN NAME
#mqtt_server = '192.168.1.106'
client_id = ubinascii.hexlify(machine.unique_id())
topic_pub_temp = b'esp/dht/temperature'
topic_pub_hum = b'esp/dht/humidity'
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')
sensor = dht.DHT22(Pin(14))
#sensor = dht.DHT11(Pin(14))
def connect_mqtt():
global client_id, mqtt_server
client = MQTTClient(client_id, mqtt_server)
#client = MQTTClient(client_id, mqtt_server, user=your_username, password=your_password)
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_sensor():
try:
sensor.measure()
temp = sensor.temperature()
# uncomment for Fahrenheit
#temp = temp * (9/5) + 32.0
hum = sensor.humidity()
if (isinstance(temp, float) and isinstance(hum, float)) or (isinstance(temp, int) and isinstance(hum, int)):
temp = (b'{0:3.1f},'.format(temp))
hum = (b'{0:3.1f},'.format(hum))
return temp, hum
else:
return('Invalid sensor readings.')
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 = read_sensor()
print(temp)
print(hum)
client.publish(topic_pub_temp, temp)
client.publish(topic_pub_hum, hum)
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 machine import Pin
import dht
esp.osdebug(None)
import gc
gc.collect()
В следующих переменных вам нужно ввести свои сетевые учётные данные и IP-адрес вашего брокера.
ssid = 'REPLACE_WITH_YOUR_SSID'
password = 'REPLACE_WITH_YOUR_PASSWORD'
mqtt_server = 'REPLACE_WITH_YOUR_MQTT_BROKER_IP'
Например, IP-адрес нашего брокера: 192.168.1.106.
mqtt_server = '192.168.1.106'
Примечание
Прочитайте это руководство, чтобы узнать, как получить IP-адрес вашего брокера.
Для создания MQTT-клиента нам нужно получить уникальный ID ESP. Это делается в следующей строке (он сохраняется в переменной client_id).
client_id = ubinascii.hexlify(machine.unique_id())
Далее создайте топики, в которые ваш ESP будет публиковать данные. В нашем примере он будет публиковать температуру в топике esp/dht/temperature и влажность в топике esp/dht/humidity.
topic_pub_temp = b'esp/dht/temperature'
topic_pub_hum = b'esp/dht/humidity'
Затем создайте следующие переменные:
last_message = 0
message_interval = 5
Переменная last_message будет хранить время последней отправки сообщения. Переменная message_interval – это время между каждым отправленным сообщением. Здесь мы устанавливаем его на 5 секунд (это означает, что новое сообщение будет отправляться каждые 5 секунд). Вы можете изменить это значение по желанию.
После этого подключите ESP к локальной сети.
station = network.WLAN(network.STA_IF)
station.active(True)
station.connect(ssid, password)
while station.isconnected() == False:
pass
print('Connection successful')
Инициализируйте датчик DHT, создав экземпляр dht на GPIO 4 следующим образом:
sensor = dht.DHT22(Pin(4))
Если вы используете датчик DHT11, раскомментируйте следующую строку и закомментируйте предыдущую:
sensor = dht.DHT11(Pin(4))
Подключение к MQTT-брокеру
Функция connect_mqtt() создаёт MQTT-клиент и подключается к вашему брокеру.
def connect_mqtt():
global client_id, mqtt_server
client = MQTTClient(client_id, mqtt_server)
#client = MQTTClient(client_id, mqtt_server, user=your_username, password=your_password)
client.connect()
print('Connected to %s MQTT broker' % (mqtt_server))
return client
Если ваш MQTT-брокер требует имя пользователя и пароль, используйте следующую строку для передачи учётных данных.
client = MQTTClient(client_id, mqtt_server, user=your_username, password=your_password)
Перезагрузка и переподключение
Функция restart_and_reconnect() перезагружает плату ESP32/ESP8266. Эта функция вызывается, если мы не можем опубликовать показания через MQTT, в случае отключения от брокера.
def restart_and_reconnect():
print('Failed to connect to MQTT broker. Reconnecting...')
time.sleep(10)
machine.reset()
Чтение датчика DHT
Мы создали функцию read_sensor(), которая возвращает текущую температуру и влажность от датчика DHT и обрабатывает исключения, если не удаётся получить показания.
def read_sensor():
try:
sensor.measure()
temp = sensor.temperature()
hum = sensor.humidity()
if (isinstance(temp, float) and isinstance(hum, float)) or (isinstance(temp, int) and isinstance(hum, int)):
temp = (b'{0:3.1f},'.format(temp))
hum = (b'{0:3.1f},'.format(hum))
# uncomment for Fahrenheit
#temp = temp * (9/5) + 32.0
return temp, hum
else:
return('Invalid sensor readings.')
except OSError as e:
return('Failed to read sensor.')
Публикация MQTT-сообщений
В цикле while мы публикуем новые показания температуры и влажности каждые 5 секунд.
Сначала проверяем, пришло ли время получить новые показания:
if (time.time() - last_message) > message_interval:
Если да, запрашиваем новые показания от датчика DHT, вызывая функцию read_sensor(). Температура сохраняется в переменной temp, а влажность – в переменной hum.
temp, hum = read_sensor()
Наконец, публикуем показания с помощью метода publish() объекта client. Метод publish() принимает в качестве аргументов топик и сообщение:
client.publish(topic_pub_temp, temp)
client.publish(topic_pub_hum, hum)
Наконец, обновляем время последней отправки сообщения:
last_message = time.time()
В случае, если ESP32 или ESP8266 отключается от брокера и мы не можем опубликовать показания, вызывается функция restart_and_reconnect() для перезагрузки платы ESP и попытки переподключения к брокеру.
except OSError as e:
restart_and_reconnect()
После загрузки кода вы должны получать новые показания датчиков в Shell каждые 5 секунд.
Теперь перейдите к следующему разделу, чтобы подготовить Node-RED для получения показаний, которые публикует ESP.
Подготовка Node-RED Dashboard
ESP32 или ESP8266 публикует показания температуры каждые 5 секунд в топиках esp/dht/temperature и esp/dht/humidity. Теперь вы можете использовать любой дашборд с поддержкой MQTT или любое другое устройство с поддержкой MQTT для подписки на эти топики и получения показаний.
В качестве примера мы создадим простой поток (flow) в Node-RED для подписки на эти топики и отображения показаний на шкалах.
Если у вас не установлен Node-RED, следуйте руководствам:
Когда Node-RED запущен на вашем Raspberry Pi, перейдите по IP-адресу Raspberry Pi с портом :1880.
http://raspberry-pi-ip-address:1880
Интерфейс Node-RED должен открыться. Перетащите два узла MQTT in и два узла gauge на поток.
Нажмите на узел MQTT и отредактируйте его свойства.
Поле Server относится к MQTT-брокеру. В нашем случае MQTT-брокер – это Raspberry Pi, поэтому установлено значение localhost:1883. Если вы используете облачный MQTT-брокер, измените это поле.
Введите топик, на который хотите подписаться, и QoS. Предыдущий узел MQTT подписан на топик esp/dht/temperature.
Нажмите на другой узел MQTT in и отредактируйте его свойства с тем же сервером, но для другого топика: esp/dht/humidity.
Нажмите на узлы gauge и отредактируйте их свойства для каждого показания. Следующий узел настроен для показаний температуры. Отредактируйте другой узел для показаний влажности.
Соедините узлы, как показано ниже:
Наконец, разверните поток (нажмите кнопку в правом верхнем углу).
Альтернативно вы можете перейти в Menu > Import и скопировать следующий код в ваш Clipboard для создания потока Node-RED.
[{"id":"59f95d85.b6f0b4","type":"mqtt in","z":"b01416d3.f69f38","name":"","topic":"esp/dht/temperature","qos":"1","datatype":"auto","broker":"8db3fac0.99dd48","x":910,"y":340,"wires":[["2babfd19.559212"]]},{"id":"2babfd19.559212","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":1210,"y":340,"wires":[]},{"id":"b9aa2398.37ca3","type":"mqtt in","z":"b01416d3.f69f38","name":"","topic":"esp/dht/humidity","qos":"1","datatype":"auto","broker":"8db3fac0.99dd48","x":900,"y":420,"wires":[["d0f75e86.1c9ae"]]},{"id":"d0f75e86.1c9ae","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":1200,"y":420,"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}]
Демонстрация
Перейдите по IP-адресу вашего Raspberry Pi с добавлением :1880/ui.
http://raspberry-pi-ip-address:1880/ui
Вы должны получить доступ к текущим показаниям температуры и влажности DHT на Dashboard. Вы можете использовать другие типы узлов дашборда для отображения показаний различными способами.
Вот и всё! Ваши платы ESP32 или ESP8266 публикуют показания температуры и влажности DHT в Node-RED через MQTT, используя MicroPython.
Заключение
MQTT – это отличный протокол связи для обмена небольшими объёмами данных между IoT-устройствами. В этом руководстве вы узнали, как публиковать показания температуры и влажности от датчика DHT с ESP32 и ESP8266, используя MicroPython, в различные MQTT-топики. Затем вы можете использовать любое устройство или платформу домашней автоматизации для подписки на эти топики и получения показаний.
Вместо датчика DHT11 или DHT22 вы можете использовать любой другой датчик, например датчик температуры DS18B20 или датчик BME280.
У нас есть другие проекты/руководства, связанные с датчиком DHT, которые также могут вам понравиться: