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, и обрабатывать их по своему усмотрению.
Предварительные требования
Прежде чем продолжить это руководство, убедитесь, что вы выполнили следующие предварительные условия.
Для выполнения этого руководства необходимо, чтобы на вашей плате ESP32 или ESP8266 была установлена прошивка MicroPython. Также вам понадобится IDE для написания и загрузки кода на плату. Мы рекомендуем использовать Thonny IDE или uPyCraft IDE:
Thonny IDE:
uPyCraft IDE:
MQTT-брокер
Для использования MQTT необходим брокер. Мы будем использовать брокер Mosquitto, установленный на Raspberry Pi. Читайте Как установить Mosquitto Broker на Raspberry Pi.
Если вы не знакомы с MQTT, обязательно прочитайте наше вводное руководство: Что такое MQTT и как он работает.
Необходимые компоненты
Для этого руководства вам понадобятся следующие компоненты:
Резистор 4.7 кОм
Плата Raspberry Pi
MicroSD-карта – 16 ГБ Class10
Блок питания Raspberry Pi (5V 2.5A)
Соединительные провода
Макетная плата
Библиотека 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 на панели управления. Вы можете использовать другие типы узлов панели управления для отображения показаний различными способами.
Вот и всё! Ваши платы ESP32 или ESP8266 публикуют показания температуры и влажности DHT в Node-RED через MQTT с использованием MicroPython.
Заключение
MQTT – это отличный протокол связи для обмена небольшими объёмами данных между IoT-устройствами. В этом руководстве вы узнали, как публиковать показания температуры и влажности с датчика DHT на ESP32 и ESP8266 с помощью MicroPython в различные MQTT-топики. Затем вы можете использовать любое устройство или платформу домашней автоматизации для подписки на эти топики и получения показаний.
Вместо датчика DHT11 или DHT22 вы можете использовать любой другой датчик, например датчик температуры DS18B20 или датчик BME280.
У нас есть другие проекты/руководства, связанные с датчиком DHT, которые могут вам понравиться: