Raspberry Pi Pico W: начало работы с MQTT (MicroPython)

Это полное руководство по использованию MQTT с Raspberry Pi Pico, программируемым на MicroPython. MQTT — это протокол связи, широко используемый в системах домашней автоматизации и IoT-приложениях для соединения множества устройств. В этом руководстве вы узнаете, как выбрать и настроить MQTT-брокер, а также как публиковать и подписываться на MQTT-сообщения с помощью Raspberry Pi Pico.

Raspberry Pi Pico - начало работы с MQTT MicroPython

Впервые работаете с Raspberry Pi Pico? Начните с руководства по началу работы с Raspberry Pi Pico.

Содержание:

В этом руководстве мы рассмотрим следующие темы:

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

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

Прошивка MicroPython

Для выполнения этого руководства вам необходимо, чтобы прошивка MicroPython была установлена на вашей плате Raspberry Pi Pico. Вам также нужна IDE для написания и загрузки кода на плату.

Рекомендуемая IDE для MicroPython на Raspberry Pi Pico — Thonny IDE. Следуйте приведённому ниже руководству, чтобы узнать, как установить Thonny IDE, прошить прошивку MicroPython и загрузить код на плату.

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

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

  • Raspberry Pi Pico W

  • BME280

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

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


Введение в MQTT

Уже знакомы с MQTT? Перейдите к следующему разделу.

MQTT расшифровывается как Message Queuing Telemetry Transport. MQTT — это простой протокол обмена сообщениями, разработанный для устройств с ограниченными ресурсами и низкой пропускной способностью. Поэтому это идеальное решение для обмена данными между несколькими IoT-устройствами. MQTT-коммуникация работает по системе публикации и подписки. Устройства могут публиковать сообщения по определённому топику. Все устройства, которые подписаны на этот топик, получают сообщение.

Модель публикации и подписки MQTT

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

Основные концепции MQTT

В MQTT есть несколько базовых концепций, которые вам необходимо понять:

  • Публикация/Подписка (Publish/Subscribe)

  • Сообщения (Messages)

  • Топики (Topics)

  • Брокер (Broker)

Публикация/Подписка

Первая концепция — это система публикации и подписки. В системе публикации и подписки устройство может публиковать сообщение по определённому топику или быть подписанным на определённый топик для получения сообщений.

Публикация и подписка на топик MQTT
  • Например, Устройство 1 публикует сообщение в топик;

  • Устройство 2 подписано на тот же топик, в который публикует Устройство 1;

  • Таким образом, Устройство 2 получает сообщение.

Сообщения MQTT

Сообщения — это информация, которой вы хотите обмениваться между устройствами. Это может быть сообщение, например команда для управления выходом, или данные, такие как показания датчиков.

Топики

Ещё одна важная концепция — это топики. Топики — это способ, с помощью которого вы регистрируете интерес к входящим сообщениям или указываете, куда хотите опубликовать сообщение.

Топики представлены строками, разделёнными прямой косой чертой. Каждая прямая косая черта указывает на уровень топика. Вот пример того, как можно создать топик для лампы в вашем домашнем офисе:

Примеры названий MQTT-топиков

Примечание: топики чувствительны к регистру, что делает следующие топики различными.

Примеры названий MQTT-топиков и различия в регистре

Если вы хотите включить лампу в вашем домашнем офисе с помощью MQTT, вы можете представить следующий сценарий:

Raspberry Pi Pico - пример подписки на MQTT-топики
  1. Устройство публикует сообщения «on» и «off» в топик home/office/lamp.

  2. У вас есть устройство, которое управляет лампой (это может быть ваш Raspberry Pi Pico или любая другая плата или устройство). Pico, управляющий вашей лампой, подписан на тот же топик: home/office/lamp.

  3. Таким образом, когда новое сообщение публикуется в этот топик, Pico получает сообщение «on» или «off» и включает или выключает лампу.

Устройство, которое публикует сообщения, может быть другой платой микроконтроллера или платформой управления домашней автоматизацией с поддержкой MQTT, такой как Node-RED, Home Assistant, Adafruit IO, Domoticz или OpenHAB, например.

Брокер

Наконец, ещё одна важная концепция — это брокер.

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

Обзор MQTT-брокера и принцип его работы

Существует несколько брокеров, которые вы можете использовать. Например:

  1. Облачные MQTT-брокеры: вы можете использовать коммерческие облачные MQTT-брокеры, например HiveMQ. Вам не нужно ничего настраивать. Достаточно создать учётную запись, и вы готовы к работе (именно этот вариант мы будем использовать в данном руководстве).

  2. Локальный MQTT-брокер: вы можете установить MQTT-брокер локально на своём компьютере или на Raspberry Pi. MQTT-брокер Mosquitto, развёрнутый на Raspberry Pi, широко используется во многих любительских проектах и является решением, которое мы используем чаще всего.

  3. Облачный MQTT-брокер: в качестве альтернативы предыдущему варианту вы также можете установить MQTT-брокер на собственном облачном сервере.

Подведём итог:

  • MQTT — это протокол связи, очень полезный в проектах Интернета вещей;

  • В MQTT устройства могут публиковать сообщения по определённым топикам и подписываться на топики для получения сообщений;

  • При использовании MQTT вам необходим брокер. Он получает все сообщения и отправляет их подписанным устройствам.

Настройка MQTT-брокера

Для использования MQTT вам нужен MQTT-брокер. Брокер получает все MQTT-сообщения и отправляет их всем подписанным клиентам.

Решения для MQTT-брокера

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

  1. Mosquitto MQTT-брокер, установленный на Raspberry Pi: это MQTT-брокер с открытым исходным кодом, который вы можете установить локально на свой Raspberry Pi (компьютер Raspberry Pi, а не плату Pico). Мы часто используем этот вариант, и он всегда хорошо работал для нас.

  2. Mosquitto MQTT-брокер, установленный на облачном сервере: отличная альтернатива, если вы хотите, чтобы ваш брокер был доступен по всему миру.

  3. HiveMQ Broker: это облачный MQTT-брокер. Вам нужно только создать учётную запись и выбрать тарифный план. Они предоставляют бесплатный план, который подходит для большинства любительских IoT-проектов.

Для простоты в этом руководстве мы будем использовать HiveMQ, потому что вам не нужно ничего устанавливать или настраивать, как в случае с MQTT-брокером Mosquitto. Вам нужно только создать учётную запись, настроить кластер, и вы готовы к работе. В качестве альтернативы, если вы хотите использовать брокер Mosquitto или любой другой брокер по вашему выбору, не беспокойтесь. Код будет совместим с любым брокером, если вы укажете правильные данные брокера.

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

Настройка MQTT-брокера HiveMQ

В этом разделе мы покажем вам, как настроить MQTT-брокер HiveMQ.

1) Сначала вам нужно создать учётную запись. Перейдите на hivemq.com и нажмите Start free.

2) Выберите тарифный план HiveMQ Cloud.

HiveMQ MQTT-брокер - различные тарифные планы

3) Войдите в систему или создайте новую учётную запись и заполните данные для завершения профиля.

4) По умолчанию у вас должен быть создан новый кластер. Нажмите Manage Cluster.

HiveMQ - управление кластером

5) Скопируйте URL кластера в безопасное место, потому что он понадобится вам позже.

HiveMQ - URL кластера

6) Нажмите на вкладку Access Management вверху.

7) Заполните форму именем пользователя и паролем. Вам нужно будет запомнить эти данные, чтобы позже подключиться к MQTT-брокеру. Установите разрешение на Publish and Subscribe.

8) Наконец, нажмите Create Credential.

HiveMQ - управление доступом

Если ваши учётные данные успешно созданы, ваш MQTT-брокер настроен.

Имя пользователя и разрешение HiveMQ

Убедитесь, что у вас есть следующая информация перед переходом к следующему разделу:

  • MQTT-сервер:

    • URL кластера (для HiveMQ)

    • IP-адрес или URL MQTT-брокера (если вы используете другой MQTT-брокер)

  • Имя пользователя MQTT

  • Пароль MQTT

Установка MQTT-модулей MicroPython

Для написания кода на MicroPython для использования протокола MQTT мы будем использовать два MQTT-модуля: umqtt.simple.py и umqtt.robust.py.

Следуйте приведённым ниже шагам, чтобы загрузить модули на ваш Raspberry Pi Pico.

Подключив Raspberry Pi Pico к компьютеру и установив соединение с Thonny IDE, перейдите в View > Files.

Thonny IDE - просмотр файлов

На боковой панели появятся все файлы в файловой системе Raspberry Pi Pico.

Щёлкните правой кнопкой мыши на боковой панели Raspberry Pi Pico и нажмите New directory…

Эта новая директория должна называться umqtt. Нажмите Ok.

Thonny IDE - создание папки umqtt

Новая директория появится на левой боковой панели.

Thonny IDE - папка umqtt создана

Щёлкните правой кнопкой мыши на папке umqtt и выберите New file…

Этот новый файл должен называться simple.py.

Thonny IDE - создание файла umqtt

Скопируйте код simple.py в этот новый файл. Код можно найти по ссылке ниже:

# forked from: https://github.com/micropython/micropython-lib/tree/master/micropython/umqtt.simple
import usocket as 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 ssl

            self.sock = ssl.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
        return op

    # 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()

После копирования кода в файл simple.py сохраните код.

Теперь, если вы развернёте папку umqtt, вы увидите, что файл simple.py находится там.

Thonny IDE - файл simple.py создан

Снова щёлкните правой кнопкой мыши на папке umqtt и создайте ещё один файл, нажав New file…

Этот новый файл должен называться robust.py.

Создание robust.py в Thonny IDE для MQTT

Скопируйте код robust.py в этот новый файл. Код можно найти по ссылке ниже:

# forked from: https://github.com/micropython/micropython-lib/tree/master/micropython/umqtt.robust
import utime
from . import simple

class MQTTClient(simple.MQTTClient):
    DELAY = 2
    DEBUG = False

    def delay(self, i):
        utime.sleep(self.DELAY)

    def log(self, in_reconnect, e):
        if self.DEBUG:
            if in_reconnect:
                print("mqtt reconnect: %r" % e)
            else:
                print("mqtt: %r" % e)

    def reconnect(self):
        i = 0
        while 1:
            try:
                return super().connect(False)
            except OSError as e:
                self.log(True, e)
                i += 1
                self.delay(i)

    def publish(self, topic, msg, retain=False, qos=0):
        while 1:
            try:
                return super().publish(topic, msg, retain, qos)
            except OSError as e:
                self.log(False, e)
            self.reconnect()

    def wait_msg(self):
        while 1:
            try:
                return super().wait_msg()
            except OSError as e:
                self.log(False, e)
            self.reconnect()

    def check_msg(self, attempts=2):
        while attempts:
            self.sock.setblocking(False)
            try:
                return super().wait_msg()
            except OSError as e:
                self.log(False, e)
            self.reconnect()
            attempts -= 1

После копирования кода в файл robust.py сохраните код. На данный момент у вас должна быть папка umqtt с файлами simple.py и robust.py внутри.

MQTT-модули MicroPython в Thonny IDE

Модули, необходимые для MQTT, были успешно загружены на Raspberry Pi Pico.

Создание файла конфигурации

Мы создадим файл конфигурации для сохранения SSID, пароля и данных вашего MQTT-брокера: URL, имени пользователя и пароля.

Создайте новый файл с именем config.py в Thonny IDE и скопируйте следующий код:

wifi_ssid = 'REPLACE_WITH_YOUR_SSID'
wifi_password = 'REPLACE_WITH_YOUR_PASSWORD'
mqtt_server = b'MQTT_BROKER_URL'
mqtt_username = b'BROKER_USERNAME'
mqtt_password = b'BROKER_PASSWORD'

Замените переменные своими собственными данными.

Если вы используете локальный MQTT-брокер Mosquitto, вам следует указать IP-адрес брокера без порта. Например:

mqtt_server = b'192.168.1.79'

Затем перейдите в File > Save as… и выберите Raspberry Pi Pico. Сохраните файл как config.py (перезаписав любые существующие файлы с таким же именем). Этот файл должен быть сохранён в корне Raspberry Pi Pico, а не внутри папки umqtt.

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

В этом примере вы узнаете, как публиковать MQTT-сообщения по определённому топику с помощью вашего Raspberry Pi Pico. В качестве примера мы будем публиковать показания температуры, влажности и давления с датчика BME280. В качестве альтернативы вы можете использовать любой другой датчик или случайные значения для тестирования проекта и концепций.

Перед тем как продолжить, убедитесь, что:

  • Вы подключили датчик BME280 к Raspberry Pi Pico. Используйте GPIO 4 (SDA) и GPIO 5 (SCL);

  • Вы должны загрузить модуль BME280.py для управления BME280 с вашего Raspberry Pi Pico — ознакомьтесь с этим руководством и установите библиотеку, как указано там.

Связанный контент: Raspberry Pi Pico: BME280 — получение температуры, влажности и давления (MicroPython)

Следующий код получает данные с датчика BME280 и публикует показания по различным MQTT-топикам.

# Rui Santos & Sara Santos - Random Nerd Tutorials
# Complete project details at https://RandomNerdTutorials.com/raspberry-pi-pico-w-mqtt-micropython/

from machine import Pin, I2C
from time import sleep
import network
from umqtt.simple import MQTTClient
import config
import BME280

# Constants for MQTT Topics
MQTT_TOPIC_TEMPERATURE = 'pico/temperature'
MQTT_TOPIC_PRESSURE = 'pico/pressure'
MQTT_TOPIC_HUMIDITY = 'pico/humidity'

# MQTT Parameters
MQTT_SERVER = config.mqtt_server
MQTT_PORT = 0
MQTT_USER = config.mqtt_username
MQTT_PASSWORD = config.mqtt_password
MQTT_CLIENT_ID = b"raspberrypi_picow"
MQTT_KEEPALIVE = 7200
MQTT_SSL = True   # set to False if using local Mosquitto MQTT broker
MQTT_SSL_PARAMS = {'server_hostname': MQTT_SERVER}

# Initialize I2C communication
i2c = I2C(id=0, scl=Pin(5), sda=Pin(4), freq=10000)

# Initialize BME280 sensor
bme = BME280.BME280(i2c=i2c, addr=0x76)

def get_sensor_readings():
    temp = bme.temperature[:-1]
    hum = bme.humidity[:-1]
    pres = bme.pressure[:-3]
    return temp, hum, pres

def initialize_wifi(ssid, password):
    wlan = network.WLAN(network.STA_IF)
    wlan.active(True)

    # Connect to the network
    wlan.connect(ssid, password)

    # Wait for Wi-Fi connection
    connection_timeout = 10
    while connection_timeout > 0:
        if wlan.status() >= 3:
            break
        connection_timeout -= 1
        print('Waiting for Wi-Fi connection...')
        sleep(1)

    # Check if connection is successful
    if wlan.status() != 3:
        return False
    else:
        print('Connection successful!')
        network_info = wlan.ifconfig()
        print('IP address:', network_info[0])
        return True

def connect_mqtt():
    try:
        client = MQTTClient(client_id=MQTT_CLIENT_ID,
                            server=MQTT_SERVER,
                            port=MQTT_PORT,
                            user=MQTT_USER,
                            password=MQTT_PASSWORD,
                            keepalive=MQTT_KEEPALIVE,
                            ssl=MQTT_SSL,
                            ssl_params=MQTT_SSL_PARAMS)
        client.connect()
        return client
    except Exception as e:
        print('Error connecting to MQTT:', e)
        raise  # Re-raise the exception to see the full traceback

def publish_mqtt(topic, value):
    client.publish(topic, value)
    print(topic)
    print(value)
    print("Publish Done")

try:
    if not initialize_wifi(config.wifi_ssid, config.wifi_password):
        print('Error connecting to the network... exiting program')
    else:
        # Connect to MQTT broker, start MQTT client
        client = connect_mqtt()
        while True:
            # Read sensor data
            temperature, humidity, pressure = get_sensor_readings()

            # Publish as MQTT payload
            publish_mqtt(MQTT_TOPIC_TEMPERATURE, str(temperature))
            publish_mqtt(MQTT_TOPIC_PRESSURE, str(pressure))
            publish_mqtt(MQTT_TOPIC_HUMIDITY, str(humidity))

            # Delay 10 seconds
            sleep(10)

except Exception as e:
    print('Error:', e)

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

Мы используем следующие MQTT-топики для публикации данных:

  • Для температуры — pico/temperature

  • Для влажности — pico/humidity

  • Для давления — pico/pressure

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

  1. Подключить Pico к интернету;

  2. Подключиться к MQTT-брокеру;

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

Давайте рассмотрим, как работает код.

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

Сначала нам нужно импортировать необходимые библиотеки. Мы импортируем Pin и I2C из модуля machine, и BME280 для взаимодействия с датчиком BME280. Модуль network для подключения к Wi-Fi и класс MQTTClient из umqtt.simple для использования MQTT-функций.

from machine import Pin, I2C
from time import sleep
import network
from umqtt.simple import MQTTClient
import config
import BME280

MQTT-топики

В следующих переменных мы сохраняем топики, в которые хотим публиковать наши сообщения (показания датчиков).

# Constants for MQTT Topics
MQTT_TOPIC_TEMPERATURE = 'pico/temperature'
MQTT_TOPIC_PRESSURE = 'pico/pressure'
MQTT_TOPIC_HUMIDITY = 'pico/humidity'

Данные MQTT

В следующих строках мы настраиваем параметры MQTT для подключения к нашему MQTT-брокеру. Мы импортируем URL сервера, имя пользователя и пароль из файла config.py.

# MQTT Parameters
MQTT_SERVER = config.mqtt_server
MQTT_PORT = 0
MQTT_USER = config.mqtt_username
MQTT_PASSWORD = config.mqtt_password
MQTT_CLIENT_ID = b"raspberrypi_picow"
MQTT_KEEPALIVE = 7200
MQTT_SSL = True
MQTT_SSL_PARAMS = {'server_hostname': MQTT_SERVER}

MQTT_CLIENT_ID должен быть уникальным идентификатором для идентификации MQTT-клиента. Вы можете дать ему любое имя, но оно должно быть уникальным среди MQTT-клиентов, подключённых к вашему брокеру. В нашем случае это raspberrypi_picow.

MQTT_CLIENT_ID = b"raspberrypi_picow"

Если вы используете локальный MQTT-брокер Mosquitto, вам следует установить параметр MQTT_SSL в False.

MQTT_SSL = False

Датчик BME280

В следующих строках мы инициализируем датчик BME280 и создаём функцию get_sensor_readings(), которая получает данные от BME280 и возвращает температуру, влажность и давление.

# Initialize I2C communication
i2c = I2C(id=0, scl=Pin(5), sda=Pin(4), freq=10000)

# Initialize BME280 sensor
bme = BME280.BME280(i2c=i2c, addr=0x76)

def get_sensor_readings():
    temp = bme.temperature[:-1]
    hum = bme.humidity[:-1]
    pres = bme.pressure[:-3]
    return temp, hum, pres

Инициализация Wi-Fi

Функция initialize_wifi() подключает Raspberry Pi Pico к сети. Вам нужно инициализировать Wi-Fi для подключения к MQTT-брокеру и обмена сообщениями.

def initialize_wifi(ssid, password):
    wlan = network.WLAN(network.STA_IF)
    wlan.active(True)

    # Connect to the network
    wlan.connect(ssid, password)

    # Wait for Wi-Fi connection
    connection_timeout = 10
    while connection_timeout > 0:
        if wlan.status() >= 3:
            break
        connection_timeout -= 1
        print('Waiting for Wi-Fi connection...')
        sleep(1)

    # Check if connection is successful
    if wlan.status() != 3:
        return False
    else:
        print('Connection successful!')
        network_info = wlan.ifconfig()
        print('IP address:', network_info[0])
        return True

Подключение к MQTT

Функция connect_mqtt() подключается к MQTT-брокеру, используя данные о MQTT-брокере, которые вы настроили ранее.

def connect_mqtt():
    try:
        client = MQTTClient(client_id=MQTT_CLIENT_ID,
                            server=MQTT_SERVER,
                            port=MQTT_PORT,
                            user=MQTT_USER,
                            password=MQTT_PASSWORD,
                            keepalive=MQTT_KEEPALIVE,
                            ssl=MQTT_SSL,
                            ssl_params=MQTT_SSL_PARAMS)
        client.connect()
        return client
    except Exception as e:
        print('Error connecting to MQTT:', e)
        raise  # Re-raise the exception to see the full traceback

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

Функция publish_mqtt() публикует сообщение в топик. Передайте в качестве аргумента топик и сообщение, которое хотите отправить.

def publish_mqtt(topic, value):
    client.publish(topic, value)
    print(topic)
    print(value)
    print("Publish Done")

Публикация показаний датчиков

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

Сначала мы пытаемся подключиться к Wi-Fi, используя SSID и пароль, хранящиеся в файле config.py.

try:
    if not initialize_wifi(config.wifi_ssid, config.wifi_password):
        print('Error connecting to the network... exiting program')

Если подключение к Wi-Fi прошло успешно, мы можем подключиться к MQTT-брокеру.

else:
    # Connect to MQTT broker, start MQTT client
    client = connect_mqtt()

После подключения мы получаем новые данные датчика и сохраняем их в переменных temperature, humidity и pressure.

# Read sensor data
temperature, humidity, pressure = get_sensor_readings()

Наконец, мы используем функцию publish_mqtt() для публикации показаний в их конкретные топики. Сообщение должно быть строкой. Поэтому нам нужно преобразовать данные датчика с помощью функции str().

# Publish as MQTT payload
publish_mqtt(MQTT_TOPIC_TEMPERATURE, str(temperature))
publish_mqtt(MQTT_TOPIC_PRESSURE, str(pressure))
publish_mqtt(MQTT_TOPIC_HUMIDITY, str(humidity))

Мы публикуем новые показания каждые 10 секунд.

sleep(10)

Тестирование кода

Запустите предыдущий код на вашем Raspberry Pi Pico. Он подключится к интернету и начнёт публиковать сообщения каждые 10 секунд.

RPi Pico Thonny IDE - публикация MQTT-сообщений Raspberry Pi Pico подключён к BME280

Теперь мы можем проверить, можем ли мы получать сообщения на MQTT-клиенте, подписанном на эти топики. HiveMQ предоставляет веб-клиент, который позволяет подписываться и публиковать сообщения в топики для тестирования.

Перейдите в ваш кластер HiveMQ и нажмите на вкладку Web Client. Введите имя пользователя и пароль вашего MQTT-брокера и нажмите Connect Client.

Веб-клиент HiveMQ

Подпишитесь на топики, в которые публикует Raspberry Pi Pico. Введите pico/temperature и затем нажмите +Subscribe.

Веб-клиент HiveMQ - подписка на топики

Повторите процесс для pico/humidity и pico/pressure.

Подписка на топики в HiveMQ

После подписки на эти топики прокрутите страницу вниз. Вы должны начать получать MQTT-сообщения от Raspberry Pi Pico.

Сообщения, полученные через MQTT HiveMQ

Вот и всё. Вы успешно опубликовали MQTT-сообщения с помощью Raspberry Pi Pico. Теперь вы можете использовать любой другой MQTT-клиент для подписки на эти сообщения и получения данных. Например, иметь ноды Node-RED или виджеты Adafruit IO, подписанные на MQTT-топики и отображающие данные на графиках и индикаторах.

Вы также можете подключить к этим топикам любой другой микроконтроллер для получения данных — это может быть другой Raspberry Pi Pico, ESP32, ESP8266 или другая плата.


Подписка на MQTT-топики

Чтобы показать вам, как подписаться на MQTT-топики с помощью Raspberry Pi Pico, мы создадим простой пример, в котором Pico подписан на топик pico/led. Затем, с помощью веб-клиента HiveMQ, мы будем публиковать сообщения ON и OFF в этот топик. Pico будет получать сообщения и управлять светодиодом соответственно.

Raspberry Pi Pico - подписка на MQTT-сообщения

Вот рабочий процесс подписки на MQTT-топики:

  1. Подключить Raspberry Pi Pico к интернету;

  2. Подключиться к MQTT-брокеру;

  3. Подписаться на MQTT-топик;

  4. Создать и назначить функцию обратного вызова (callback), которая будет выполняться при получении сообщения;

  5. Создать цикл, который постоянно проверяет наличие новых MQTT-сообщений.

Следующий код подписывается на топик pico/led и управляет светодиодом в соответствии с полученным сообщением.

# Rui Santos & Sara Santos - Random Nerd Tutorials
# Complete project details at https://RandomNerdTutorials.com/raspberry-pi-pico-w-mqtt-micropython/

from machine import Pin
from time import sleep
import network
from umqtt.simple import MQTTClient
import config

# Define LED
led = Pin('LED', Pin.OUT)

# Constants for MQTT Topics
MQTT_TOPIC_LED = 'pico/led'

# MQTT Parameters
MQTT_SERVER = config.mqtt_server
MQTT_PORT = 0
MQTT_USER = config.mqtt_username
MQTT_PASSWORD = config.mqtt_password
MQTT_CLIENT_ID = b'raspberrypi_picow'
MQTT_KEEPALIVE = 7200
MQTT_SSL = True   # set to False if using local Mosquitto MQTT broker
MQTT_SSL_PARAMS = {'server_hostname': MQTT_SERVER}

# Init Wi-Fi Interface
def initialize_wifi(ssid, password):
    wlan = network.WLAN(network.STA_IF)
    wlan.active(True)

    # Connect to the network
    wlan.connect(ssid, password)

    # Wait for Wi-Fi connection
    connection_timeout = 10
    while connection_timeout > 0:
        if wlan.status() >= 3:
            break
        connection_timeout -= 1
        print('Waiting for Wi-Fi connection...')
        sleep(1)

    # Check if connection is successful
    if wlan.status() != 3:
        return False
    else:
        print('Connection successful!')
        network_info = wlan.ifconfig()
        print('IP address:', network_info[0])
        return True

# Connect to MQTT Broker
def connect_mqtt():
    try:
        client = MQTTClient(client_id=MQTT_CLIENT_ID,
                            server=MQTT_SERVER,
                            port=MQTT_PORT,
                            user=MQTT_USER,
                            password=MQTT_PASSWORD,
                            keepalive=MQTT_KEEPALIVE,
                            ssl=MQTT_SSL,
                            ssl_params=MQTT_SSL_PARAMS)
        client.connect()
        return client
    except Exception as e:
        print('Error connecting to MQTT:', e)

# Subcribe to MQTT topics
def subscribe(client, topic):
    client.subscribe(topic)
    print('Subscribe to topic:', topic)

# Callback function that runs when you receive a message on subscribed topic
def my_callback(topic, message):
    # Perform desired actions based on the subscribed topic and response
    print('Received message on topic:', topic)
    print('Response:', message)
    # Check the content of the received message
    if message == b'ON':
        print('Turning LED ON')
        led.value(1)  # Turn LED ON
    elif message == b'OFF':
        print('Turning LED OFF')
        led.value(0)  # Turn LED OFF
    else:
        print('Unknown command')

try:
    # Initialize Wi-Fi
    if not initialize_wifi(config.wifi_ssid, config.wifi_password):
        print('Error connecting to the network... exiting program')
    else:
        # Connect to MQTT broker, start MQTT client
        client = connect_mqtt()
        client.set_callback(my_callback)
        subscribe(client, MQTT_TOPIC_LED)

        # Continuously checking for messages
        while True:
            sleep(5)
            client.check_msg()
            print('Loop running')
except Exception as e:
    print('Error:', e)

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

Если вы используете локальный MQTT-брокер Mosquitto, вам следует установить параметр MQTT_SSL в False.

MQTT_SSL = False

Подписка на MQTT-топики

Мы создаём функцию subscribe(), которая принимает в качестве аргументов MQTT-клиент и топик, на который мы хотим подписаться.

def subscribe(client, topic):
    client.subscribe(topic)
    print('Subscribe to topic:', topic)

Функция обратного вызова (Callback)

Нам нужно создать функцию обратного вызова (callback), которая будет выполняться при получении нового сообщения в топике, на который мы подписаны. Аргументы topic и message автоматически передаются в функцию при её вызове.

def my_callback(topic, message):
    # Perform desired actions based on the subscribed topic and response
    print('Received message on topic:', topic)
    print('Response:', message)
    # Check the content of the received message
    if message == b'ON':
        print('Turning LED ON')
        led.value(1)  # Turn LED ON
    elif message == b'OFF':
        print('Turning LED OFF')
        led.value(0)  # Turn LED OFF
    else:
        print('Unknown command')

В этой функции мы проверяем содержимое сообщения. Если это ON, мы включим встроенный светодиод Raspberry Pi Pico.

if message == b'ON':
    print('Turning LED ON')
    led.value(1)  # Turn LED ON

Если сообщение — OFF, светодиод будет выключен.

elif message == b'OFF':
    print('Turning LED OFF')
    led.value(0)  # Turn LED OFF

Если мы получим какие-либо другие сообщения, мы выведем «Unknown command».

else:
    print('Unknown command')

Подключение к Wi-Fi, MQTT-брокеру и проверка сообщений

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

Следующие строки пытаются подключиться к интернету:

try:
    # Initialize Wi-Fi
    if not initialize_wifi(config.wifi_ssid, config.wifi_password):
        print('Error connecting to the network... exiting program')

Если подключение прошло успешно, мы также подключаемся к MQTT-брокеру.

client = connect_mqtt()

Затем назначаем функцию обратного вызова с помощью set_callback() и передаём в качестве аргумента созданную ранее функцию обратного вызова my_callback.

client.set_callback(my_callback)

Наконец, мы можем подписаться на MQTT-топики. В данном случае мы подписываемся только на MQTT_TOPIC_LED, но вы можете подписаться на несколько топиков.

subscribe(client, MQTT_TOPIC_LED)

Затем создаём цикл while для постоянной проверки входящих сообщений с помощью метода check_msg().

while True:
    sleep(5)
    client.check_msg()
    print('Loop running')

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

Тестирование кода

Запустите предыдущий код на вашем Raspberry Pi Pico. Он подключится к интернету и подпишется на MQTT-топик.

RPi Pico - подписка на MQTT-топики в Thonny IDE

Теперь давайте опубликуем несколько сообщений в топик pico/led для управления светодиодом с помощью веб-клиента HiveMQ.

Перейдите в веб-клиент вашего кластера HiveMQ. Введите имя пользователя и пароль вашего MQTT-брокера и нажмите Connect Client.

Веб-клиент HiveMQ

Прокрутите страницу вниз до раздела Publish Message.

Введите pico/led в поле Topic Name и введите ON в поле Message.

HiveMQ - публикация MQTT-сообщения через веб-клиент

Наконец, нажмите кнопку Publish.

Ваш Raspberry Pi Pico получит сообщение и включит встроенный светодиод.

RPi Pico - получение сообщений через MQTT в Thonny IDE Raspberry Pi Pico W - встроенный светодиод включён

Вернитесь в клиент HiveMQ и отправьте сообщение OFF.

HiveMQ - публикация MQTT-сообщения через веб-клиент

Raspberry Pi Pico получит сообщение и выключит светодиод.

Raspberry Pi Pico W - встроенный светодиод выключён

Вот и всё. Вы успешно подписались на MQTT-топик и выполнили различные задачи в соответствии с полученным сообщением. Теперь вместо веб-клиента HiveMQ вы можете использовать любую платформу домашней автоматизации или IoT с поддержкой MQTT, которая позволяет создать веб-интерфейс с кнопками или переключателями для отправки MQTT-сообщений.

Заключение

В этом руководстве вы изучили все основные концепции для начала использования MQTT с Raspberry Pi Pico. Мы настроили MQTT-брокер и опубликовали и подписались на MQTT-топики с помощью Pico.

Надеемся, что это руководство оказалось для вас полезным.