MicroPython: MQTT — Публикация показаний температуры DS18B20 (ESP32/ESP8266)
В этом руководстве показано, как запрограммировать ESP32 или ESP8266 с помощью MicroPython для публикации показаний датчика температуры DS18B20 по протоколу MQTT. В качестве примера мы будем публиковать показания на панель управления Node-RED, но этот проект совместим с любой платформой, поддерживающей MQTT.
Вас также могут заинтересовать другие руководства по MicroPython и DS18B20:
Обзор проекта
Вот общая схема работы проекта:
ESP запрашивает показания температуры у датчика DS18B20;
Показания температуры публикуются в MQTT-топик
esp/ds18b20/temperature;Node-RED подписан на этот топик;
Показания отображаются на стрелочном индикаторе и графике.
Вы можете получать показания на любой платформе, поддерживающей MQTT, или использовать любые другие датчики, например DHT11/DHT22 или BME280.
Предварительные требования
Прошивка MicroPython
Для выполнения этого руководства вам необходимо установить прошивку MicroPython на вашу плату ESP32 или ESP8266. Вам также понадобится IDE для написания и загрузки кода на плату. Мы рекомендуем использовать Thonny IDE или uPyCraft IDE:
Thonny IDE:
uPyCraft IDE:
MQTT-брокер
Для использования MQTT вам нужен брокер. Мы будем использовать Mosquitto, установленный на Raspberry Pi. Дополнительную информацию читайте в руководстве Как установить Mosquitto Broker на Raspberry Pi.
Примечание
Вы также можете использовать любой другой 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. Следуйте следующим инструкциям, чтобы загрузить библиотеку на вашу плату.
Создайте новый файл и скопируйте в него приведённый ниже код библиотеки
umqttsimple.Сохраните файл под именем
umqttsimple.py.Загрузите файл на плату.
Вы также можете скачать библиотеку 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
Нажмите кнопку «New File» для создания нового файла.
Скопируйте код библиотеки
umqttsimpleв новый файл.Сохраните файл под именем
umqttsimple.py.Нажмите кнопку «Download and Run».
Файл должен появиться в папке устройства.
Загрузка библиотеки с помощью Thonny IDE
Скопируйте код библиотеки в новый файл.
Перейдите в меню File > Save as…
Выберите MicroPython device.
Назовите файл
umqttsimple.pyи нажмите OK.Готово! Файл сохранён на устройстве. Проверить это можно через File > Save as… — файл должен быть виден на устройстве MicroPython.
Схемы подключения
Схема подключения DS18B20 к ESP32
Подключите датчик DS18B20 к ESP32 согласно следующей схеме. Датчик подключается к GPIO 4, но вы можете использовать любой другой подходящий GPIO.
Рекомендуем ознакомиться со справочником по распиновке ESP32: ESP32 Pinout Reference.
Схема подключения DS18B20 к ESP8266 NodeMCU
Подключите датчик DS18B20 к ESP8266 NodeMCU согласно следующей схеме. Датчик подключается к GPIO 4, но вы можете использовать любой другой подходящий GPIO.
Рекомендуем ознакомиться со справочником по распиновке 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)
Для визуализации показаний датчика на панели управления нужно создать следующие узлы:
Перетащите на рабочую область следующие узлы:
Один узел mqtt in — для подписки на топик
Один узел gauge — для отображения текущей температуры
Один узел chart — для построения графика температуры
Настройка узла MQTT In
Дважды кликните на узел mqtt in и настройте его:
Server:
localhost:1883(или адрес вашего облачного брокера)Topic:
esp/ds18b20/temperatureQoS:
1
Нажмите Done.
Настройка узла Gauge
Дважды кликните на узел gauge и настройте его:
Диапазон: от 0 до 40 °C
Цветовое кодирование: зелёный, жёлтый, красный
Настройка узла Chart
Дважды кликните на узел chart и настройте его:
Линейный график (line)
Формат времени: HH:mm:ss
Соединение узлов
Соедините все узлы, как показано на рисунке:
Развёртывание потока
Нажмите кнопку Deploy в правом верхнем углу, чтобы сохранить и запустить поток.
Импорт потока 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
Вы увидите текущие показания температуры на стрелочном индикаторе и историю показаний на графике:
Заключение
В этом руководстве вы узнали, как публиковать показания температуры датчика DS18B20 по протоколу MQTT с помощью ESP32 или ESP8266, запрограммированных на MicroPython. В качестве примера мы использовали Node-RED для подписки на MQTT-топик и визуализации данных на панели управления.
Основные моменты:
Библиотека
umqttsimpleиспользуется для работы с MQTT на MicroPythonПоказания температуры публикуются в топик
esp/ds18b20/temperatureИнтервал публикации настраивается через переменную
message_intervalПри потере связи с брокером плата автоматически перезагружается
Вы можете использовать эту же концепцию для публикации показаний других датчиков, таких как DHT11/DHT22 или BME280.
Надеемся, это руководство было полезным. Узнайте больше о MicroPython с ESP32 и ESP8266: