Programowanie w środowisku MicroPython (10). Chmura i MQTT

Programowanie w środowisku MicroPython (10). Chmura i MQTT

Protokół MQTT powstał w 1999 roku. Został opracowany z myślą o prostych urządzeniach, które przesyłają małe pakiety danych. Umożliwia bardzo łatwą komunikację z serwerem w celu wysyłania i pobierania niewielkich ilości informacji.

Trochę teorii

Urządzenia wykorzystujące protokół MQTT nie przesyłają informacji sobie bezpośrednio, lecz wykorzystują tzw. brokera, który jest pośrednikiem w komunikacji. Stanowi on coś w rodzaju serwera, który nadzoruje wszystkie urządzenia i zarządza komunikacją pomiędzy nimi. Odbiera wiadomości nadesłane przez urządzenia, a następnie przekazuje dalej te wiadomości urządzeniom, które powinny je otrzymać.

Ważnym pojęciem jest temat (ang. topic). Każda wiadomość musi być wysłana do jakiegoś tematu. Wewnątrz tematu mogą być zdefiniowane podtematy, a wewnątrz nich mogą być jeszcze podtematy podrzędne. W ten sposób tworzy się hierarchiczna struktura danych.

Aby urządzenie mogło odczytać wiadomość, najpierw musi zasubskrybować chociaż jeden temat. Kiedy broker MQTT otrzyma jakąś wiadomość związaną z tym tematem, wówczas automatycznie roześle ją do wszystkich urządzeń, które dany temat zasubskrybowały.

Załóżmy, że w systemie automatyki domowej mamy prostą sieć czujników, które mierzą parametry pogodowe. Mamy także sterownik bramy garażowej, która może być otwarta lub zamknięta. Ponadto mamy jeden wspólny kanał, z którego odczytywać mogą wszystkie urządzenia. Drzewo tematów mogłoby wyglądać następująco:

  • sensor1
    • temperature = 25
    • humidity = 80
  • sensor2
    • temperature = 20
    • humidity = 75
  • sensor3
    • temperature = 22
    • humidity = 50
    • pressure = 1015
  • garage = closed
  • all = {„firmware_file”: „http://www.github.com/jakis_adres/nowy_firmware.bin”}

Sensory 1 i 2 są identyczne, zatem ich podtematy również są takie same. Sensor 3 mierzy dodatkowo ciśnienie powietrza, zatem ma dodatkowy podtemat z tym związany. Sterownik garażu nie ma żadnych podtematów i ma tylko przypisany stan drzwi. Temat o nazwie all odczytują wszystkie urządzenia. W tym przykładzie mamy pakiet w formacie JSON, który mówi, skąd urządzenia mają pobrać plik z nowym firmwarem – mogą go ściągnąć w taki sposób, jak przedstawiliśmy to w 7 odcinku kursu, opublikowanym w EP 11/2025.

MQTT Explorer

Aby ułatwić sobie podglądanie co i jak trafia do brokera, posłużymy się programem MQTT Explorer, który za darmo można pobrać spod adresu [2]. Polecam pobrać wersję portable, ponieważ działa ona bez instalacji i od razu jest skonfigurowana jak należy. Po uruchomieniu program pyta nas, z jakim serwerem chcemy nawiązać połączenie. Domyślnie skonfigurowane są dwa serwery – mqtt.eclipse.org oraz test.mosquitto.org. Wybieramy ten drugi (rysunek 1), po czym klikamy CONNECT.

Rysunek 1. Konfiguracja serwera test.mosquitto.org

Po połączeniu z brokerem powinna pokazać się długa lista tematów po lewej stronie okna. Różni ludzie i różne urządzenia ciągle przysyłają tam jakieś wiadomości, więc co chwilę pojawia się coś nowego. Wszystkie wiadomości są publicznie dostępne i nie należy tu udostępniać żadnych poufnych informacji.

Wyślijmy coś do brokera. W polu topic wpisz 0000000_Elektronika_Praktyczna/test. Jest to nazwa tematu, a po znaku / mamy nazwę podtematu. W razie potrzeby możemy tworzyć bardziej zagnieżdżoną strukturę danych. Tyle zer na początku jest tylko po to, aby nasz testowy temat pojawił się na górze listy, ponieważ jest ona sortowana alfabetycznie.

Poniżej mamy do wyboru jedną z trzech opcji formatu danych. Raw, czyli zwykły tekst bez formatowania oraz XML i JSON. Wybieramy raw, a następnie w polu tekstowym poniżej napisz jakiś dowolny tekst i kliknij przycisk PUBLISH. Temat 0000000_Elektronika_Praktyczna powinien od razu pojawić się na górze listy. Należy go rozwinąć i będzie w nim widoczny podtemat test, do którego przypisana jest wartość, jaką podałeś. Efekt kilku takich operacji zapisu przedstawiono na rysunku 2.

Rysunek 2. Podgląd wybranego tematu

Program ma możliwość śledzenia historii oraz wyświetlania zmian, jakie zachodziły dla publikowanych wartości. Jeżeli są to wartości liczbowe, to program może nawet narysować wykres. Ponadto wykresy najbardziej interesujących nas kanałów z danymi liczbowymi możemy przypiąć do dolnej części okna. W tym celu należy kliknąć przycisk zaznaczony czerwoną strzałką na rysunku 3.

Rysunek 3. Podgląd wykresów

MQTT w praktyce

Napiszemy prosty program, który łączy się z darmowym brokerem Mosquitto. Program będzie mierzył temperaturę procesora co 5 sekund. Następnie będzie przesyłał ją na dedykowany kanał, którego nazwa jest utworzona na podstawie ID mikrokontrolera ESP32-S3. W ten sposób, demonstracyjny program będziemy mogli uruchomić jednocześnie na kilku płytkach z ESP32-S3. Poznamy kilka metod subskrybowania kanałów, aby wysyłać wiadomość z chmury do jednej wybranej płytki lub do wszystkich jednocześnie. Kod tego programu przedstawiono na listingu 1.

# Plik mqtt_demo.py

import _thread
import esp32
import machine
import network
import time
import wifi_config
import sys
from umqtt.robust import MQTTClient # 1

client_id = machine.unique_id().hex().upper() # 2
print(f"Client ID: {client_id}")

def wifi_connect(): # 3
station = network.WLAN(network.STA_IF)
station.active(True)
if not station.isconnected():
print("Łączenie z siecią", end="")
station.connect(wifi_config.ssid, wifi_config.password)
while not station.isconnected():
print(".", end="")
time.sleep_ms(250)
print()

print(f"Adres IP: {station.ifconfig()[0]}")

def mqtt_publish(topic, payload): # 4
client.publish(
f"0000000_Elektronika_Praktyczna/{topic}", payload # 5
)

def mqtt_callback(topic, payload): # 6
print(f"{topic} > {payload}")

def mqtt_listener_task(delay_ms): # 7
while True: # 8
time.sleep_ms(delay_ms) # 9
try:
client.check_msg() # 10
except Exception as e:
sys.print_exception(e)

def temperature_task(delay_ms): # 11
while True:
client.publish(
f"0000000_Elektronika_Praktyczna/sensor_{client_id}", # 12
str(esp32.mcu_temperature())
)
time.sleep_ms(delay_ms)

wifi_connect() # 13
client = MQTTClient(client_id, "test.mosquitto.org") # 14
client.set_callback(mqtt_callback) # 15
client.connect() # 16

# client.subscribe("0000000_Elektronika_Praktyczna") # 17
client.subscribe("0000000_Elektronika_Praktyczna/test")
# client.subscribe("0000000_Elektronika_Praktyczna/#")

print("Oczekiwanie na wiadomości z MQTT")
_thread.start_new_thread(mqtt_listener_task, [1000]) # 18
_thread.start_new_thread(temperature_task, [5000]) # 19

Listing 1. Kod pliku mqtt_demo.py

Program, jak zwykle w Pythonie, zaczynamy od zaimportowania wszystkich używanych modułów. Jest ich dość sporo, ale jedyną nowością jest umqtt.robust (linia 1). MicroPython wyposażony jest w dwóch klientów MQTT. Klient umqtt.simple jest zoptymalizowany pod kątem zapotrzebowania na pamięć i szybkość działania. Drugi klient o nazwie umqtt.robust jest zbudowany na bazie tego pierwszego i wzbogacony o różne procedury zwiększające niezawodność komunikacji – między innymi automatyczne łączenie po przerwaniu połączenia internetowego czy wykrywanie i naprawianie różnych błędów.

Każde urządzenie podłączone do brokera MQTT musi posiadać jakiś unikalny identyfikator. W przypadku mikrokontrolerów ESP32 przyjęło się, że tym identyfikatorem jest adres MAC wbudowanej karty sieciowej. Aby go uzyskać, posłużymy się funkcją unique_id z modułu machine (linia 2). Funkcja ta zwraca identyfikator w postaci obiektu typu bytes, który składa się z sześciu bajtów, np.: b’\xdc\xda\x0c\x1eN\xe0’. Aby taka informacja była użyteczna jako identyfikator urządzenia w MQTT, musimy ją przekonwertować na string. Robimy to przy pomocy metody hex, którą ma każdy obiekt typu bytes, a w rezultacie dostajemy ‘dcda0c1e4ee0’. Zmieńmy małe litery na wielkie. Nie jest to obowiązkowe, ale warto tego dokonać dla celów demonstracji. W tym celu wykorzystujemy metodę upper, którą ma każdy obiekt str i w ten sposób dostajemy ‘DCDA0C1E4EE0’, co w dalszej części programu będzie wykorzystywane jako identyfikator urządzenia.

Linia 2 jest dość skondensowanym zapisem. Równie dobrze moglibyśmy to samo zapisać w następujący sposób:

client_id = machine.unique_id()
client_id = client_id.hex()
client_id = client_id.upper()

Pomińmy na razie wszystkie definicje funkcji i przeskoczmy teraz do linii 13, gdzie zaczyna się wykonywać nasz program. Zaczynamy od wywołania funkcji wifi_connect, którą utworzyliśmy w linii 3. Tę funkcję znamy już dobrze z kilku poprzednich odcinków kursu.
W linii 14 tworzymy instancję klienta MQTT. Do konstruktora klasy MQTTClient podajemy dwa argumenty. Pierwszym z nich jest uzyskany wcześniej identyfikator urządzenia client_id, a drugim – adres serwera, z którym chcemy się połączyć.

Kolejnym krokiem jest podanie callbacka (linia 15), czyli wskaźnika do funkcji, która ma się wywołać, kiedy zostanie otrzymana jakaś wiadomość od brokera MQTT. W naszym przypadku jest to funkcja o nazwie mqtt_callback, którą definiujemy w linii 6. Funkcja ta przyjmuje tylko dwa argumenty: nazwę tematu, w którym pojawiła się nowa wiadomość oraz treść tej wiadomości. Wewnątrz callbacka powinniśmy interpretować otrzymane dane i podejmować jakieś działania, jakie z tych danych wynikają, np. coś włączyć lub zmienić jakieś ustawienia.

W linii 16 łączymy się z brokerem MQTT.

Jeżeli chcemy otrzymywać wiadomości, najpierw musimy zasubskrybować tematy. W tym celu wywołujemy metodę subscribe, do której podajemy nazwę tematu. Możemy zasubskrybować tyle tematów, ile jest nam potrzebne, a także posłużyć się symbolami wieloznacznymi, aby móc zasubskrybować wiele tematów jednocześnie. Załóżmy, że mamy takie drzewo tematów:

  • dom/pokoj/temperatura
  • dom/pokoj/ciśnienie
  • dom/kuchnia/temperatura
  • dom/kuchnia/ciśnienie
  • piwnica/kotlownia/temperatura
  • piwnica/kotlownia/cisnienie

Możemy zasubskrybować wszystkie tematy związane z temperaturą, posługując się znakiem +. Należy rozumieć, że ten znak zastępuje dowolne słowo w nazwie tematu:

  • dom/+/temperatura – wszystkie czujniki temperatury z domu,
  • +/+/temperatura – wszystkie czujniki temperatury w ogóle.

Możemy także zasubskrybować wszystkie tematy i podtematy, korzystając ze znaku #, który zastępuje dowolną kombinację znaków leżących na prawo od niego.

  • dom/kuchnia/# – wszystko, co znajduje się w kuchni,
  • dom/# – wszystko, co znajduje się w domu,
  • # – wszystko bez wyjątku.

Aby odbierać wiadomości od brokera MQTT musimy zrobić jeszcze jedno – wymyślić jakiś sposób, aby cyklicznie wywoływać funkcję check_msg. W tym celu możemy wykorzystać task z modułu _thread, co czynimy w linii 18. Przypomnijmy, że funkcja start_new_thread przyjmuje dwa argumenty – pierwszy to nazwa tasku, czyli mqtt_listener_task, a drugi to lista dowolnych obiektów, jakie zostaną przekazane do tasku przy jego wywołaniu. W naszym przypadku jest to tylko czas oczekiwania pomiędzy wywołaniami tasku, czyli 1000 ms.

Task o nazwie mqtt_listener_task, który cyklicznie odpytuje brokera MQTT, tworzymy w linii 7. Jest to zwyczajna funkcja, która zawiera pętlę nieskończoną while True (linia 8). Wewnątrz pętli mamy tylko opóźnienie funkcją sleep_ms (linia 9) oraz blok try-except, w którym wywołujemy funkcję check_msg (linia 10). Jeżeli zostanie pobrana jakaś wiadomość, to wtedy zostanie automatycznie wywołana zdefiniowana wcześniej funkcja callback. Wywołanie funkcji check_msg umieściliśmy wewnątrz try-except, ponieważ w razie jakichś problemów może ona zgłaszać wyjątki.

W linii 19 dodajemy kolejny task. Jego zadaniem będzie pomiar temperatury co 5000 ms i wysyłanie wyniku do brokera. Task ten definiujemy w linii 11 i również ma on pętlę nieskończoną while True. Wewnątrz niej wywołujemy funkcję publish (linia 12), do której podajemy dwa argumenty. Pierwszym jest nazwa tematu, na który zostanie wysłany wynik pomiaru temperatury. Tworzymy ją używając zmiennej client_id, którą łączymy ze stałą częścią nazwy tematu używając f-string. Drugim argumentem jest to, co zamierzamy wysłać, czyli temperatura procesora, uzyskana poprzez funkcję mcu_temperature z modułu esp32. Pamiętaj, że dane wysyłane na serwer muszą mieć format tekstowy. Aby przekonwertować wartość temperatury na string, musimy posłużyć się funkcją str.

Gotowe! Uruchom program, wciskając klawisz F5. Program połączy się z siecią Wi-Fi i wyświetli komunikaty podobne do tych z rysunku 4. Oczywiście zobaczysz inne wartości Client ID oraz adresu IP.

Rysunek 4. Komunikaty na konsoli po uruchomieniu programu

Task do pomiaru temperatury jest aktywny i po kilkunastu sekundach powinieneś zobaczyć, że w programie MQTT Explorer pojawił się nowy temat, zaczynający się od 0000000_Elektronika_Praktyczna. Jeżeli mimo dłuższego czasu oczekiwania ten temat się nie pojawił, spróbuj wpisać jego nazwę do pasku wyszukiwania w górnej części ekranu. Powinieneś zobaczyć efekt jak na rysunku 5. Podczas testów uruchomiłem ten sam program na trzech płytkach z ESP32-S3. Jak widać, każda z nich utworzyła sobie swój indywidualny kanał, w którym raportuje temperaturę procesora co 5 sekund.

Rysunek 5. Wykresy wygenerowane na podstawie danych z dwóch modułów ESP32-S3

Przetestujmy komunikację od brokera do mikrokontrolera.

W polu Topic po prawej stronie okna wpisz 0000000_Elektronika_Praktyczna/test. Jest to temat, który zasubskrybowaliśmy, zatem pojawienie się jakiejś nowej wiadomości w tym temacie powinno spowodować wyświetlenie jej w konsoli MicroPythona. Wybierz format danych raw, wpisz dowolny komunikat i kliknij PUBLISH. Po chwili powinien się on pojawić w konsoli – patrz rysunek 6.

Rysunek 6. Wiadomości odebrane od brokera MQTT

W następnym odcinku poznamy interfejs ESP-NOW. Jest to ciekawa forma komunikacji, opracowana przez firmę Espressif. Można ją wykorzystać do bezprzewodowego przesyłania danych pomiędzy modułami ESP32, bez pośrednictwa żadnych brokerów, routerów ani czegokolwiek innego. Ponadto zobaczymy inną niż _thread metodę tworzenia tasków wykonujących się równolegle, czyli asyncio.

Dominik Bieczyński
leonow32@gmail.com

Elektronika Praktyczna Plus lipiec - grudzień 2012

Elektronika Praktyczna Plus

Monograficzne wydania specjalne

Elektronik czerwiec 2026

Elektronik

Magazyn elektroniki profesjonalnej

Raspberry Pi 2015

Raspberry Pi

Wykorzystaj wszystkie możliwości wyjątkowego minikomputera

Świat Radio maj - czerwiec 2026

Świat Radio

Magazyn krótkofalowców i amatorów CB

Automatyka, Podzespoły, Aplikacje maj 2026

Automatyka, Podzespoły, Aplikacje

Technika i rynek systemów automatyki

Elektronika Praktyczna czerwiec 2026

Elektronika Praktyczna

Międzynarodowy magazyn elektroników konstruktorów

Elektronika dla Wszystkich czerwiec 2026

Elektronika dla Wszystkich

Interesująca elektronika dla pasjonatów