MicroPython: MQTT — Публикация показаний температуры DS18B20 (ESP32/ESP8266)

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

MicroPython MQTT публикация показаний DS18B20 ESP32 ESP8266

Вас также могут заинтересовать другие руководства по MicroPython и DS18B20:

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

Вот общая схема работы проекта:

Обзор проекта MQTT DS18B20 ESP32 ESP8266 MicroPython
  1. ESP запрашивает показания температуры у датчика DS18B20;

  2. Показания температуры публикуются в MQTT-топик esp/ds18b20/temperature;

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

  4. Показания отображаются на стрелочном индикаторе и графике.

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

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

Прошивка MicroPython

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

MQTT-брокер

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

Логотип Mosquitto MQTT брокер

Примечание

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

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

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

  • ESP32 или ESP8266 (плата разработки)

  • Датчик температуры DS18B20

  • Резистор 4,7 кОм

  • Raspberry Pi (для MQTT-брокера)

  • MicroSD-карта (16 ГБ, Class 10)

  • Блок питания для Raspberry Pi (5 В, 2,5 А)

  • Соединительные провода

  • Макетная плата

Библиотека umqttsimple

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

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

  2. Сохраните файл под именем umqttsimple.py.

  3. Загрузите файл на плату.

Вы также можете скачать библиотеку umqttsimple.py с GitHub.

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":  # 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

    def check_msg(self):
        self.sock.setblocking(False)
        return self.wait_msg()

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

  1. Нажмите кнопку «New File» для создания нового файла.

  2. Скопируйте код библиотеки umqttsimple в новый файл.

  3. Сохраните файл под именем umqttsimple.py.

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

  5. Файл должен появиться в папке устройства.

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

  1. Скопируйте код библиотеки в новый файл.

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

  3. Выберите MicroPython device.

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

  5. Готово! Файл сохранён на устройстве. Проверить это можно через File > Save as… — файл должен быть виден на устройстве MicroPython.

Схемы подключения

Схема подключения DS18B20 к ESP32

Подключите датчик DS18B20 к ESP32 согласно следующей схеме. Датчик подключается к GPIO 4, но вы можете использовать любой другой подходящий GPIO.

Схема подключения DS18B20 к ESP32

Рекомендуем ознакомиться со справочником по распиновке ESP32: ESP32 Pinout Reference.

Схема подключения DS18B20 к ESP8266 NodeMCU

Подключите датчик DS18B20 к ESP8266 NodeMCU согласно следующей схеме. Датчик подключается к GPIO 4, но вы можете использовать любой другой подходящий GPIO.

Схема подключения DS18B20 к ESP8266 NodeMCU

Рекомендуем ознакомиться со справочником по распиновке ESP8266: ESP8266 Pinout Reference.

Код — MQTT публикация показаний DS18B20

Скопируйте следующий код в файл main.py или boot.py на вашей плате ESP32 или ESP8266. Код считывает температуру с датчика DS18B20 и публикует её через MQTT каждые 5 секунд.

# Полное описание проекта: https://RandomNerdTutorials.com/micropython-mqtt-publish-ds18b10-esp32-esp8266/

import time
from umqttsimple import MQTTClient
import ubinascii
import machine
import micropython
import network
import esp
from machine import Pin
import onewire
import ds18x20
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
#mqtt_server = '192.168.1.106'

client_id = ubinascii.hexlify(machine.unique_id())

topic_pub_temp = b'esp/ds18b20/temperature'

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')

ds_pin = machine.Pin(4)
ds_sensor = ds18x20.DS18X20(onewire.OneWire(ds_pin))

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:
    roms = ds_sensor.scan()
    ds_sensor.convert_temp()
    time.sleep_ms(750)
    for rom in roms:
      temp = ds_sensor.read_temp(rom)
      # uncomment for Fahrenheit
      #temp = temp * (9/5) + 32.0
    if (isinstance(temp, float) or (isinstance(temp, int))):
      temp = (b'{0:3.1f},'.format(temp))
      return temp
    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 = read_sensor()
      print(temp)
      client.publish(topic_pub_temp, temp)
      last_message = time.time()
  except OSError as e:
    restart_and_reconnect()

Вы также можете скачать этот код с GitHub.

Как работает код

Давайте разберём, как работает код.

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

Сначала импортируются необходимые библиотеки: umqttsimple для работы с MQTT, machine, network — для работы с Wi-Fi, а также onewire и ds18x20 — для работы с датчиком DS18B20.

Настройка учётных данных

Перед загрузкой кода на плату необходимо ввести свои сетевые учётные данные в следующих переменных:

ssid = 'REPLACE_WITH_YOUR_SSID'
password = 'REPLACE_WITH_YOUR_PASSWORD'

Укажите IP-адрес вашего MQTT-брокера Mosquitto в переменной mqtt_server. Он должен выглядеть примерно так:

mqtt_server = '192.168.1.XXX'

Например, 192.168.1.106.

Идентификатор клиента и топик

Переменная client_id генерирует уникальный идентификатор MQTT-клиента из уникального ID вашей платы ESP:

client_id = ubinascii.hexlify(machine.unique_id())

Переменная topic_pub_temp содержит топик MQTT, в который будут публиковаться показания температуры:

topic_pub_temp = b'esp/ds18b20/temperature'

Интервал отправки сообщений

Переменная message_interval задаёт интервал между отправками сообщений (в секундах). Здесь мы устанавливаем 5 секунд:

message_interval = 5

Подключение к Wi-Fi

Следующие строки подключают ESP к вашей сети Wi-Fi:

station = network.WLAN(network.STA_IF)
station.active(True)
station.connect(ssid, password)

while station.isconnected() == False:
  pass

print('Connection successful')

Инициализация датчика DS18B20

Датчик DS18B20 подключён к GPIO 4. Инициализация выполняется следующим образом:

ds_pin = machine.Pin(4)
ds_sensor = ds18x20.DS18X20(onewire.OneWire(ds_pin))

Подробное руководство по работе с DS18B20 и MicroPython читайте здесь: MicroPython: датчик температуры DS18B20 с ESP32 и ESP8266.

Подключение к 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() вызывается при сбое подключения к MQTT-брокеру. Она ожидает 10 секунд, а затем перезагружает плату:

def restart_and_reconnect():
  print('Failed to connect to MQTT broker. Reconnecting...')
  time.sleep(10)
  machine.reset()

Считывание показаний датчика

Функция read_sensor() считывает температуру с датчика DS18B20:

def read_sensor():
  try:
    roms = ds_sensor.scan()
    ds_sensor.convert_temp()
    time.sleep_ms(750)
    for rom in roms:
      temp = ds_sensor.read_temp(rom)
      # uncomment for Fahrenheit
      #temp = temp * (9/5) + 32.0
    if (isinstance(temp, float) or (isinstance(temp, int))):
      temp = (b'{0:3.1f},'.format(temp))
      return temp
    else:
      return('Invalid sensor readings.')
  except OSError as e:
    return('Failed to read sensor.')

Функция сканирует все подключённые датчики, запускает преобразование температуры, ожидает 750 мс и считывает результат. Если вы хотите получить температуру в градусах Фаренгейта, раскомментируйте строку преобразования.

Публикация сообщений

В главном цикле while True ESP подключается к MQTT-брокеру и каждые 5 секунд публикует новое показание температуры:

try:
  client = connect_mqtt()
except OSError as e:
  restart_and_reconnect()

while True:
  try:
    if (time.time() - last_message) > message_interval:
      temp = read_sensor()
      print(temp)
      client.publish(topic_pub_temp, temp)
      last_message = time.time()
  except OSError as e:
    restart_and_reconnect()

Если подключение к брокеру не удаётся, вызывается функция restart_and_reconnect(), которая перезагружает плату.

Подготовка Node-RED Dashboard

Для визуализации показаний температуры на панели управления нужно установить Node-RED и Node-RED Dashboard на Raspberry Pi.

После установки откройте браузер и перейдите по адресу:

http://<IP-адрес-Raspberry-Pi>:1880

Создание потока (Flow)

Для визуализации показаний датчика на панели управления нужно создать следующие узлы:

Node-RED узлы MQTT In, Gauge и Chart

Перетащите на рабочую область следующие узлы:

  • Один узел mqtt in — для подписки на топик

  • Один узел gauge — для отображения текущей температуры

  • Один узел chart — для построения графика температуры

Настройка узла MQTT In

Дважды кликните на узел mqtt in и настройте его:

Настройка узла MQTT In в Node-RED
  • Server: localhost:1883 (или адрес вашего облачного брокера)

  • Topic: esp/ds18b20/temperature

  • QoS: 1

Нажмите Done.

Настройка узла Gauge

Дважды кликните на узел gauge и настройте его:

Настройка узла Gauge в Node-RED
  • Диапазон: от 0 до 40 °C

  • Цветовое кодирование: зелёный, жёлтый, красный

Настройка узла Chart

Дважды кликните на узел chart и настройте его:

Настройка узла Chart в Node-RED
  • Линейный график (line)

  • Формат времени: HH:mm:ss

Соединение узлов

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

Поток Node-RED для MQTT DS18B20

Развёртывание потока

Нажмите кнопку Deploy в правом верхнем углу, чтобы сохранить и запустить поток.

Кнопка Deploy в Node-RED

Импорт потока Node-RED

Кроме того, вы можете импортировать готовый поток. Перейдите в Menu > Import > Clipboard и вставьте следующий JSON:

[{"id":"3eb4b485.bb948c","type":"mqtt in","z":"b01416d3.f69f38","name":"","topic":"esp/ds18b20/temperature","qos":"1","datatype":"auto","broker":"8db3fac0.99dd48","x":930,"y":120,"wires":[["706fecd4.6f91a4","47ed6377.491d6c"]]},{"id":"706fecd4.6f91a4","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":1190,"y":100,"wires":[]},{"id":"47ed6377.491d6c","type":"ui_chart","z":"b01416d3.f69f38","name":"","group":"2b7ac01b.fc984","order":4,"width":0,"height":0,"label":"Temperature","chartType":"line","legend":"false","xformat":"HH:mm:ss","interpolate":"linear","nodata":"","dot":false,"ymin":"","ymax":"","removeOlder":1,"removeOlderPoints":"","removeOlderUnit":"3600","cutout":0,"useOneColor":false,"colors":["#1f77b4","#aec7e8","#ff7f0e","#2ca02c","#98df8a","#d62728","#ff9896","#9467bd","#c5b0d5"],"useOldStyle":false,"outputs":1,"x":1190,"y":160,"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":"DS18B20","tab":"53b8c8f9.cfbe48","order":1,"disp":true,"width":"6","collapse":false},{"id":"2b7ac01b.fc984","type":"ui_group","z":"","name":"SENSORS","tab":"99ab8dc5.f435c","disp":true,"width":"6","collapse":false},{"id":"53b8c8f9.cfbe48","type":"ui_tab","z":"","name":"Home","icon":"dashboard","order":2,"disabled":false,"hidden":false},{"id":"99ab8dc5.f435c","type":"ui_tab","z":"","name":"HTTP","icon":"dashboard","order":1,"disabled":false,"hidden":false}]

Демонстрация

Загрузите код esp_ds18b20_mqtt.py на вашу плату ESP32 или ESP8266. Убедитесь, что библиотека umqttsimple.py также загружена на плату.

Откройте панель управления Node-RED в браузере:

http://<IP-адрес-Raspberry-Pi>:1880/ui

Вы увидите текущие показания температуры на стрелочном индикаторе и историю показаний на графике:

Панель управления Node-RED с показаниями температуры DS18B20 через MQTT

Заключение

В этом руководстве вы узнали, как публиковать показания температуры датчика DS18B20 по протоколу MQTT с помощью ESP32 или ESP8266, запрограммированных на MicroPython. В качестве примера мы использовали Node-RED для подписки на MQTT-топик и визуализации данных на панели управления.

Основные моменты:

  • Библиотека umqttsimple используется для работы с MQTT на MicroPython

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

  • Интервал публикации настраивается через переменную message_interval

  • При потере связи с брокером плата автоматически перезагружается

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

Надеемся, это руководство было полезным. Узнайте больше о MicroPython с ESP32 и ESP8266: