- tworzenie automatyzacji w tym także automatyzacji opartych o pętle czasowe czy opóźnienia,
- tłumaczenie wiadomości MQTT np. pobieranie informacji o temperaturze i wilgotności i wysyłanie informacji o temperaturze punktu rosy,
- publikowanie wiadomości typu „discovery” dla Home Assistant (HA),
- agregowanie danych z czujników i sensorów i publikowanie ich w postaci jednego komunikatu – rozwiązanie idealne do prezentacji wszystkich danych na własnej stronie internetowej,
- możliwość pobierania danych z innych źródeł (np. z pliku) i publikowanie ich jako komunikatu MQTT,
- logowanie komunikatów,
- możliwość uruchomienia wielu automatyzacji równocześnie.
Wstęp
Tworząc inteligentny dom musimy wybrać jakąś technologię komunikacji. Można zdecydować się na rozwiązania przewodowe, które jednak są drogie i wymagają wcześniejszego zaprojektowania wszystkiego, lub o rozwiązania bezprzewodowe, które gdy są dobrze zaprojektowane zapewniają dużą niezawodność, a przede wszystkim elastyczność. Do takich popularnych rozwiązań bezprzewodowych możemy zaliczyć WiFi. Na dłuższą metę nie jest to jednak rozwiązanie przyszłościowe. Routery mają zazwyczaj ograniczenia na liczbę równoczesnych połączeń, a chcąc odciąć się od zewnętrznej chmury musimy takie urządzenia stworzyć lub zmieniać im programy, a to często wiąże się z demontażem urządzeń, podłączaniem przewodów, programowaniem itd. Czy jest sposób, aby kupić gotowe urządzenie, którego nie musimy przeprogramowywać, a zarazem bez problemu uwolnimy go od chmury producenta? Otóż są takie rozwiązania – najpopularniejsze z nich to Zigbee. Urządzenia pracujące w sieci Zigbee łączą się z koordynatorem (bezpośrednio lub za pomocą routerów), a taki koordynator umożliwia nam uwolnienie się od chmury. Nic by nam jednak nie przyszło z takich urządzeń Zigbee jeśli nie potrafimy ich wysterować. Tu pomocą jest niesamowity projekt Zigbee2Mqtt, który w chwili obecnej obsługuje ponad 3000 urządzeń pochodzących od niemal 400 dostawców. Podstawowym zadaniem tego oprogramowania jest utworzenie „mostu” pomiędzy urządzeniami Zigbee a MQTT. Niniejszy artykuł nie opisuje jednak podstaw i założeń Zigbee oraz MQTT. Jeśli te pojęcia są Ci obce, to najpierw trochę o nich poczytaj.
Moje urządzenia IoT używają głównie protokołu http. Był on dla mnie naturalnym wyborem, gdyż w rozwiązaniach używałem html/javascript (po stronie klienta) oraz php (po stronie serwera). O MQTT wiedziałem jedynie, że oparty jest o komunikaty, które możemy publikować i subskrybować. Ten drugi przypadek oznaczał aktywne czekanie na informację, co niezbyt pasuje do architektury opartej o Apache i PHP. Popełniłem jednak błąd myślowy – subskrypcja nie oznacza, że router MQTT nawiąże z nami połączenie. Niezależnie od tego czy publikujemy, czy subskrybujemy to klient zawsze nawiązuje połączenie z routerem. W przypadku publikacji może to połączenie nawiązać, przesłać komunikat i zakończyć. W drugim przypadku połączenie jest także nawiązywane przez klienta, który aktywnie czeka na zasubskrybowane tematy. Temu czekaniu towarzyszą pingi, co umożliwia utrzymania połączenia nawet za routerem sieci Ethernet. Możliwa jest także weryfikacja, czy połączenie jest aktywne.
PHP, w którym wykonałem opisywany tu projekt, posiada niskopoziomowe rozwiązania typu socket, co umożliwia bezproblemowe utworzenie i utrzymanie połączenia.
MQTT ma też jedną ciekawą cechę – komunikaty mogą być publikowane z flagą „retain”. Taki komunikat jest udostępniany nowemu subskrybentowi natychmiast po nawiązaniu połączenia. To bardzo ciekawa właściwość, gdyż umożliwia błyskawiczne pobrania wiadomości (zawsze ostatniej). Ta funkcjonalność została wykorzystana w moim projekcie.
Muszę przyznać, że jestem „oczarowany” technologią MQTT i postanowił używać jej w nowych projektach.
Początki
Aby zapoznać się z Zigbee i MQTT zakupiłem koordynator Zigbee (Sonoff) oraz kilka urządzeń – termometr, przycisk i gniazdko które pełni także funkcję routera. Router MQTT (Mosquitto) oraz Zigbee2Mqtt zainstalowałem na RPi3. Byłem też bardzo ciekawy Home Assistanta – nie zamierzam na niego przechodzić, ale chciałem się z nim zapoznać. HA zainstalowałem dla testów jako maszynę wirtualną na moim komputerze Windows. HA zawiera dodatki w postaci Mosquitto i Zigbee2Mqtt, ja jednak sugeruję instalację na innej maszynie. Powoduje to, że sam MQTT jest zupełnie niezależny od HA – nie przestanie działać w przypadku konieczności restartu HA i umożliwia nam zrobienie automatyzacji w zupełnie różnych technologiach - choćby w opisywanym tu PHP. Idealnym rozwiązaniem wydaje się rozdzielenie wszystkich trzech elementów (Zigbee2Mqtt, Mosquitto oraz HA), na trzy maszyny, co w przypadku użycia wirtualizacji nie wydaje się przesadą. Artykuł ma dotyczyć MQTT w PHP dlatego pominę opis instalacji Zigbee2Mqtt i Mosquitto – w sieci jest sporo informacji na ten temat. Dodam tylko, że początkowo zainstalowałem te programy na świeżo zainstalowanym Pi OS i wszystko poszło bez problemów. Ostateczna instalacja poszła na starszym Pi i zaczęły się kłopoty. Musiałem nawet pobierać nowszego Pythona i go kompilować co trwało chyba z godzinę. W każdym razie jeśli tylko możecie zróbcie czystą instalację nowego Pi OS.
Moduł obsługi MQTT w PHP
Tworzenie automatyzacji opartej o PHP zacząłem od poszukiwania jakiegoś modułu obsługi MQTT w PHP. Zdecydowałem się na moduł phpMQTT Link Moduł ten wydaje się nie być rozwijanym, ale wprowadziłem w nim kilka poprawek sugerowanych przez innych użytkowników i wydaje się działać bardzo dobrze. Wykonałem trochę zmian w module, w tym dostosowanie do PHP w wersji 5.4 (ale na PHP 8 też działa). Całość kodu oraz zmieniony moduł znajduje się na końcu artykułu.
Moduł został użyty w mojej aplikacji subscribe.php, która zawiaduje całością. Do działania wymagane są dodatkowe pliki php: mqtt_param.php i konfiguracja.php.
mqtt_param.php zawiera podstawowe parametry konieczne do połączenia z brokerem MQTT:
Kod: PHP
Plik konfiguracja.php zawiera podstawowe parametry konfigurujące oprogramowanie:
Kod: PHP
Warto zwrócić uwagę na to, że nazwa pliku php zawierającego automatyzację i sterowanie zawarta jest w konfiguracji. Chodzi o zmienną $proc_msg. Takie rozwiązanie umożliwia uruchomienie kilku automatyzacji całkowicie równolegle. Jeśli program subscribe.php zostanie uruchomiony bez parametrów, to użyje pliku konfiguracja.php. Jeśli jednak będzie parametr, to jest on interpretowany jako nazwa pliku konfiguracyjnego.
Kluczowym plikiem podlegającym edycji u każdego użytkownika jest plik z automatyzacją – domyślnie proc_msg.php. Poniżej dokładnie opiszę jego zawartość:
Kod: PHP
Na początku mamy definicję tablicy $topics. Jest to tablica asocjacyjna, gdzie indeksami tablicy są subskrybowane tematy. Można subskrybować tematy ze znakami wieloznacznymi np. do subskrypcji wszystkiego wystarczy użyć $topics['#']. Wartościami tablicy są różne parametry, z których kluczowe są dwa: ‘log’ – załączenie logowania komunikatów (zapisywane są do pliku logów), oraz ‘function’ – nazwa funkcji, która ma być uruchomiona w przypadku otrzymania komunikatu. Funkcja ta powinna mieć dwa lub trzy parametry zależnie od występowania klucza ‘wait’. Jeśli klucz ‘wait’ nie występuje funkcja powinna mieć trzy parametry:
$key – klucz w tabeli $topics,
$msg – wiadomość
$topic – temat wiadomości
Wartości $key i $topic są równe w przypadku, gdy subskrybowany temat nie zawiera znaków wieloznacznych. W przeciwnym przypadku $key zawiera indeks tablicy tematów, a $topics zawiera nazwę tematu, który rzeczywiście przyszedł.
W przykładowym pliku proc_msg.php mamy funkcję „procPrzycisk” obsługującą przycisk Zigbee. Przycisk ma 3 funkcje: kliknięcie, podwójne kliknięcie i przytrzymanie. Każda z akcji przycisku jest rozpoznawana:
- kliknięcie przełącza stan gniazdka – publikowany jest komunikat rozpoznawany przez gniazdko,
- podwójne kliknięcie uruchamia timer (czas liczony w ms), który po wskazanym czasie (5s) uruchomi funkcję „tprocPrzycisk”, co w przykładzie spowoduje wyłączenie gniazdka.
- długie przytrzymanie uruchomi lub wyłączy (zapamiętywany jest stan) cykliczne (co 5s) wywołanie funkcji „tprocPrzycisk2”. O cykliczności decyduje ujemna wartość czasu w funkcji „ms_delay”. Ta funkcja jest dosyć istotna i często używana. Jej parametrami są:
- czas w ms (jeśli ujemny to oznacza cykliczne wywołanie),
- nazwa funkcji do wywołania
- parametr przekazywany do wywoływanej funkcji – zawsze jeden, ale jeśli chcemy przekazać więcej wartości, to po prostu tworzymy tablicę jak w przykładzie.
Rezultatem funkcji „ms_delay” jest uchwyt, który możemy użyć w funkcji „ms_delay_del” w celu skasowania naszego timera. Może to być wygodne np. w celu utworzenia wyłącznika gaszącego światło po upływie określonego czasu. Ponowne naciśniecie przełącznika może skasować poprzedni timer i uruchomić nowy – otrzymamy efekt wydłużenia czasu na wyłączenie. Funkcja do wywołania powinna mieć dwa parametry – pierwszym jest indeks timera (przydatny, gdy chcemy usunąć timer cykliczny), a drugim parametrem jest wartość, którą przekazaliśmy do „ms_delay”.
Ogólnie polecam tworzenie pętli czasowych – przykładowo jeśli chcemy sterować ogrzewaniem to możemy każdy czujnik temperatury połączyć z zaworem i tak tym sterować. Możemy też zachowywać wartości z sensorów, a w pętli czasowej (np. co minutę) kontrolować parametry i podejmować odpowiednie decyzje np. jeśli w pokoju mamy 2 termometry, to możemy uśrednić ich wartość temperatury. Możemy też sterować ogrzewaniem z uwzględnieniem temperatury zewnętrznej. Mając jedno centralne miejsce wygodniej też sterować obniżaniem temperatury w całym domu, gdy np. z niego wychodzimy.
Jak już wspomniałem funkcja obsługi komunikatu może mieć dwa parametry w przypadku, gdy występuje „wait”. Aby zrozumieć sens tego rozwiązania należy zapoznać się ze sposobem wysyłania komunikatów z czujników, które rejestrują więcej parametrów np. temperaturę, wilgotność i poziom baterii. Komunikaty te są wysyłane w postaci JSON – raz do temperatury, raz dla wilgotności i kolejny raz ze stanem baterii. Ponieważ HA nie akceptuje sytuacji, w której czujnik przesyła tylko jedną z wartości dlatego Zigbee2Mqtt wysyła zawsze wszystkie (można to jedna wyłączyć w konfiguracji Zigbee2Mqtt). Robi to w następujący sposób: gdy przyjdzie wartość temperatury wysyłany jest komunikat z aktualną temperaturą i poprzednim stanem wilgotności oraz stanu baterii. Gdy otrzyma wilgotność wysyła kolejny komunikat tym razem z poprawną wilgotnością itd. Komunikaty są wysyłane bardzo szybko po sobie. Aby rozwiązać ten problem wprowadziłem opóźnienie „wait” w ms. Gdy przyjdzie komunikat nie jest od razu uruchamiana nasza procedura obsługi, lecz poszczególne komunikaty są kolekcjonowane w tablicy. Każdy element tablicy jest z kolei dwuelementową tablicą. Pod indeksem „0” jest temat, a pod indeksem „1” zawartość. Zatem zapis „$msg[count($msg)-1][1]” oznacza po prostu ostatnią wartość tablicy.
Procedura „procTerm” realizuje kilka zadań. Zapisuje wartości temperatury i wilgotności w globalnej tablicy $pub. Tablica ta gromadzi wszystkie wartości, które chcemy opublikować w postaci jednego tematu. Ułatwia to np. tworzenie stron WWW prezentujących „na raz” wszystkie wartości. Publikacja wartości następuje w momencie wywołania „pub_var()” oraz okresowo według harmonogramu określonego w konfiguracji w zmiennej „$pub_var_time”. Publikowane są dwa tematy:
$my_topic.'/data'
oraz
$my_topic.'/data1'
Dla domyślnej wartości zmiennej $my_topis będzie to odpowiednio: mqttPHP/data i mqttPHP/data1. Różnica pomiędzy data i data1 polega na tym, że „data” ma ustawioną flagę „retain”, a data1 nie ma.
Kolejne zadanie w „procTerm” to obliczanie temperatury punktu rosy i wilgotności bezwzględnej. Wartości te są następnie publikowane jako temat „soft_dev/rosa”. W funkcji „procStart()” uruchamianej jednorazowo przy starcie programu subscribe.php, wysyłane są komunikaty umożliwiające automatyczną konfigurację naszego softwarowego czujnika rosy w aplikacji HA. Zaprezentowane rozwiązanie umożliwia zatem tłumaczenie jednych komunikatów w zupełnie inne, a następnie ich publikację.
Na koniec jeszcze trochę informacji o przechowywaniu wartości pomiędzy wywołaniami funkcji obsługi komunikatów. Jeśli należy przechować wartości używane wyłącznie przez daną funkcję należy to zrobić poprzez „$topics[$key]” – przykładowo zapamiętanie uchwytu pętli czasowej: $topics[$key]['indh']=ms_delay(…
Taką globalną strukturą jest opisywana też tablica $pub. Dodatkowo mamy też tablicę „$var” do zapamiętywania innych potrzebnych nam wartości. Należy tylko pamiętać o dodaniu odpowiedniej tablicy w „global”
Uruchomienie programu
Program bardzo łatwo uruchomić. Piszemy po prostu „php subscribe.php”. Na maszynie Windows piszemy pełną ścieżkę np. „c:\Apache24_64\php7\php.exe”. Oczywiście takie uruchomienie jest tylko testowe. Aby nasza obsługa działała cały czas i do tego w tle przygotowałem skrypt w bash, w którym należy poprawić ścieżki:
Kod: Bash
Skrypt uruchamia program, a gdy ten się zakończy czeka 10s i uruchamia go ponownie i tak w nieskończoność. Warto zauważyć, że w skrypcie jest przekierowanie wyjść do pliku logu, co umożliwia nam śledzenie poprawności działania. Wywołanie takiego skryptu musimy dodać do jakiegoś autostartu. Ja polecam cron i @reboot. Programem, który tu przedstawiam sterować można przy użyciu MQTT. Program subskrybuje temat $my_topic.'/control' czyli w domyślnej konfiguracji „mqttPHP/control” program rozpoznaje 3 polecenia: start, stop, restart. „Stop” powoduje wstrzymanie wszelkich akcji, „start” przywraca normalne działanie, a restart kończy program. Ponieważ uruchamianie programu jest w skrypcie bash jago zakończenie spowoduje odczekanie 10s i ponowne uruchomienie. Funkcja „restart” jest stosowana przy zmianach w automatyzacji – najpierw poprawiamy skrypt automatyzacji (proc_msg.php), a później wykonujemy restart i nowy skrypt zaczyna działać. Aby ułatwić czynność restartu przygotowałem skrypt „restart.php” do uruchomienia w przeglądarce.
Integracja ze stroną WWW
Aby ułatwić integrację MQTT z stronami WWW utworzyłem dodatkowy moduł „mqttf.php”, w którym znajduje się jedna klasa służąca do obsługi żądań publikacji i subskrypcji. Jest tam też funkcja „pub_sub”, która może być użyta do komunikacji z urządzeniami, które w wyniku zapytania MQTT od razu odpowiadają. Ciekawym przykładem jest zestaw plików test1.htm i test1.php, który demonstruje aktywne wyświetlanie treści publikowanej przez program (publikacja tablicy „$pub”). Najlepiej do wykonania takiego połączenia użyć WebSocket, zaproponowałem jednak rozwiązanie bazujące na zwykłych zapytaniach. Technologia opiera się o „długie” połączenia. Działa to w następujący sposób:
1) test1.htm odpytuje się w technologii AJAX o nowe dane z MQTT – zapytanie jest kierowane do test1.php, a parametrem zapytania jest obecny stan.
2) Skrypt PHP subskrybuje kanał z danymi, a po ich odbiorze sprawdza, czy dane się różnią od tych, które otrzymał w zapytaniu. Jeśli się różnią natychmiast kończy działanie wysyłając nowy stan. Skrypt czeka na nowe komunikaty przez 30s, a jeśli nic nowego nie przyszło zwraca tekst „false”
3) test1.htm odbiera dane i jeśli są różne od „false” wywołuje procedurę prezentacji danych, a następnie natychmiast ponownie odpytuje – powrót do punktu 1.
To rozwiązanie jest znacznie lepsze niż ciągłe odpytywanie (pooling) gdyż nie generuje ciągłego ruchu.
Podsumowanie
Mam nadzieję, że projekt okaże się przydatny i zachęci innych do stosowania MQTT. Jeśli coś nie było jasne, to proszę o pytania na forum. Możliwości zaprezentowanego rozwiązania są naprawdę duże, gdyż nic nie zastąpi „normalnego” języka programowania szczególnie przy złożonych akcjach.
Fajne? Ranking DIY