MicroPython: MQTT – Публикация показаний DHT11/DHT22 (ESP32/ESP8266)

Узнайте, как запрограммировать платы ESP32 или ESP8266 с помощью MicroPython для публикации показаний датчика DHT11 или DHT22 (температура и влажность) через MQTT на любую платформу, поддерживающую MQTT, или в любой MQTT-клиент. В качестве примера мы будем публиковать показания датчиков в Node-RED Dashboard.

MicroPython MQTT публикация показаний DHT11 DHT22 температура и влажность с ESP32 ESP8266

Рекомендуемое чтение: Что такое MQTT и как он работает

Примечание

Это руководство совместимо как с платами ESP32, так и с ESP8266.

Обзор проекта

Следующая диаграмма показывает общий обзор проекта, который мы создадим.

DHT ESP32 ESP8266 MicroPython MQTT обзор проекта
  • ESP запрашивает показания температуры и влажности у датчика DHT11 или DHT22;

  • Показания температуры публикуются в топике esp/dht/temperature;

  • Показания влажности публикуются в топике esp/dht/humidity;

  • Node-RED подписан на эти топики;

  • Node-RED получает показания датчиков и отображает их на шкалах (gauges);

  • Вы можете получать показания на любой другой платформе, поддерживающей MQTT, и обрабатывать их по своему усмотрению.

Предварительные требования

Перед продолжением работы с этим руководством убедитесь, что выполнены следующие предварительные требования.

Для работы с этим руководством вам необходима прошивка MicroPython, установленная на платах ESP32 или ESP8266. Также вам нужна IDE для написания и загрузки кода на плату. Мы предлагаем использовать Thonny IDE или uPyCraft IDE:

MQTT-брокер

Mosquitto MQTT брокер

Для использования MQTT вам нужен брокер. Мы будем использовать брокер Mosquitto, установленный на Raspberry Pi. Прочитайте Как установить брокер Mosquitto на Raspberry Pi.

Если вы не знакомы с MQTT, обязательно прочитайте наше вводное руководство: Что такое MQTT и как он работает

Необходимые компоненты

Для этого руководства вам понадобятся следующие компоненты:

Библиотека umqttsimple

Для использования MQTT с ESP32/ESP8266 и MicroPython мы будем использовать библиотеку umqttsimple.py. Следуйте инструкциям для IDE, которую вы используете:

    1. Загрузка библиотеки umqttsimple с помощью uPyCraft IDE

    1. Загрузка библиотеки 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.

uPyCraft IDE кнопка New File

2. Скопируйте код библиотеки umqttsimple в него. Вы можете получить код библиотеки umqttsimple по следующей ссылке:

3. Сохраните файл, нажав кнопку Save.

uPyCraft IDE кнопка Save

4. Назовите этот новый файл «umqttsimple.py» и нажмите ok.

Импорт библиотеки umqttsimple

5. Нажмите кнопку Download and Run.

uPyCraft IDE кнопка Download and Run

6. Файл должен быть сохранён в папке device с именем «umqttsimple.py», как показано на рисунке ниже.

Библиотека umqttsimple установлена

Теперь вы можете использовать функционал библиотеки в своём коде, импортировав её.

B. Загрузка библиотеки umqttsimple с помощью Thonny IDE

1. Скопируйте код библиотеки в новый файл. Код библиотеки umqttsimple можно найти здесь.

2. Перейдите в File > Save as…

Thonny IDE сохранить файл на устройство

Если пункт «Save as…» отсутствует, проверьте, что вы правильно настроили Thonny IDE, как описано в следующем руководстве:

3. Выберите сохранение на «MicroPython device»:

Thonny IDE сохранить на устройство MicroPython

4. Назовите файл umqttsimple.py и нажмите кнопку OK:

Файл umqttsimple.py в Thonny IDE

Вот и всё. Библиотека загружена на вашу плату. Чтобы убедиться, что она загружена успешно, перейдите в File > Save as… и выберите MicroPython device. Ваш файл должен быть в списке:

Файл umqttsimple.py создан в Thonny IDE

После загрузки библиотеки на плату вы можете использовать её функционал, импортировав библиотеку.

Схема подключения: ESP32 с DHT11/DHT22

Подключите датчик DHT22 или DHT11 к плате разработки ESP32, как показано на следующей схеме.

Схема подключения MicroPython ESP32 с DHT11 DHT22

В этом примере мы подключаем вывод данных DHT к GPIO 14. Однако вы можете использовать любой другой подходящий цифровой пин.

Узнайте, как использовать GPIO ESP32, из нашего руководства: ESP32 Pinout Reference: какие GPIO-пины использовать?

Схема подключения: ESP8266 NodeMCU с DHT11/DHT22

Если вы используете ESP8266 NodeMCU, следуйте следующей схеме.

Схема подключения MicroPython ESP8266 NodeMCU с DHT11 DHT22

В этом примере мы подключаем вывод данных 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 на поток.

Узлы Node-RED MQTT и gauge

Нажмите на узел MQTT и отредактируйте его свойства.

Свойства узла MQTT In в Node-RED

Поле Server относится к MQTT-брокеру. В нашем случае MQTT-брокер – это Raspberry Pi, поэтому установлено значение localhost:1883. Если вы используете облачный MQTT-брокер, измените это поле.

Введите топик, на который хотите подписаться, и QoS. Предыдущий узел MQTT подписан на топик esp/dht/temperature.

Нажмите на другой узел MQTT in и отредактируйте его свойства с тем же сервером, но для другого топика: esp/dht/humidity.

Нажмите на узлы gauge и отредактируйте их свойства для каждого показания. Следующий узел настроен для показаний температуры. Отредактируйте другой узел для показаний влажности.

Свойства узла Gauge в Node-RED

Соедините узлы, как показано ниже:

Поток Node-RED для ESP32 ESP8266 MQTT DHT

Наконец, разверните поток (нажмите кнопку в правом верхнем углу).

Кнопка Deploy в Node-RED

Альтернативно вы можете перейти в 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 MQTT публикация температуры и влажности DHT Node-RED Dashboard

Вот и всё! Ваши платы ESP32 или ESP8266 публикуют показания температуры и влажности DHT в Node-RED через MQTT, используя MicroPython.

Заключение

MQTT – это отличный протокол связи для обмена небольшими объёмами данных между IoT-устройствами. В этом руководстве вы узнали, как публиковать показания температуры и влажности от датчика DHT с ESP32 и ESP8266, используя MicroPython, в различные MQTT-топики. Затем вы можете использовать любое устройство или платформу домашней автоматизации для подписки на эти топики и получения показаний.

Вместо датчика DHT11 или DHT22 вы можете использовать любой другой датчик, например датчик температуры DS18B20 или датчик BME280.

У нас есть другие проекты/руководства, связанные с датчиком DHT, которые также могут вам понравиться: