logo elektroda
logo elektroda
X
logo elektroda
REKLAMA
REKLAMA
Adblock/uBlockOrigin/AdGuard mogą powodować znikanie niektórych postów z powodu nowej reguły.

Malutki broker/serwer MQTT hostowany bezpośrednio na urządzeniu OBK/Beken/ESP32

p.kaczmarek2 26 Lut 2026 12:27 1149 2

TL;DR

  • Na OBK/OpenBeken działa malutki broker MQTT uruchomiony bezpośrednio na BK7231, ESP32 i Windows, zamiast na Raspberry Pi czy w chmurze.
  • Serwer używa nieblokujących socketów, polling w QuickTick oraz kolejek klientów i subskrypcji, więc nie wymaga wątków ani stałej, dużej tablicy.
  • Testy wykazały, że pakiety z Tasmoty potrafią przychodzić fragmentami i sklejone, więc bufor recv trzeba było przenieść do struktury klienta.
  • Działający sterownik MQTTServer obsługuje logowanie, subskrypcje, publikacje, ping i disconnect, ale wspiera tylko jednego użytkownika i zwiększa RAM przez osobne bufory.
Wygenerowane przez model językowy.
REKLAMA
📢 Słuchaj (AI):
  • Zielona świecąca lampka oraz smartfon z panelem TasmotaRoom i przyciskami sterowania
    Z reguły przyjęło się, że brokerem MQTT musi być wydajny serwer, usługa chmurowa, albo co najmniej mikrokomputer w stylu Raspberry Pi. Pytanie jednak brzmi - czy na pewno? Czy nie dałoby się zrealizować prostego serwera MQTT na układzie pokroju ESP32 lub BK7231, przy ograniczonym rozmiarze pamięci RAM, powiedzmy, do kilkunastu kilobajtów? W tym temacie postaram się pokazać, że jest to możliwe i MQTT wcale nie jest tak zasobożernym protokołem, jakby się to mogło wydawać. Przedstawię tu moje rozważania implementacyjne, napotkane problemy i zabiegi, jakie zastosowałem, by zmniejszyć użycie pamięci, a potem przedstawię efekty mojej pracy.

    Serwerek opracuję w moim środowisku OBK, co oznacza, że będzie on działać na 32 platformach (w tym na BK7231, ESP32 i Windows):
    Wieloplatformowy firmware IoT wspierający aż 32 platformy - podsumowanie OBK 2025

    Czym jest MQTT?
    MQTT (Message Queuing Telemetry Transport) to lekki protokół komunikacyjny warstwy aplikacji, zaprojektowany do wymiany komunikatów w modelu opartym o subskrypcje (subscription). Komunikacja odbywa się za pośrednictwem brokera, który pośredniczy między klientami publikującymi dane a klientami subskrybującymi określone tematy, eliminując konieczność bezpośrednich połączeń między nimi i przekazując klientom tylko te dane, które ich dotyczą. Dodatkowo subskrypcje mogą zawierać znaki specjalne wildcard, co daje większą kontrolę nad tym, jakie informacje są przekazywane.

    Architektura prostego serwera MQTT
    Jeden serwer MQTT obsługuje wiele klientów, a wiele klientów to wiele wątków, czy to nie jest logiczna implikacja? Otóż nie - swój serwer zrealizowałem w oparciu o nieblokujące sockety (gniazda). Pozwala to znacznie zredukować wymóg zasobów po stronie systemu operacyjnego i nie wymaga tworzenia dodatkowych wątków. Najpierw tworzę jeden nieblokujący socket, który oczekuje na nowe połączenia:
    Kod: C / C++
    Zaloguj się, aby zobaczyć kod

    Potem w funkcji "QuickTick" (taki odpowiednik loop - odświeżenie) wykonuję z głównego wątku OBK tzw. polling, by odbierać nowe połączenia.
    Kod: C / C++
    Zaloguj się, aby zobaczyć kod

    Dla nowych połączeń dopiero alokuję strukturę klienta. Struktury klienta trzymam w kolejce, a nie w liście - dzięki temu nie muszę z góry znać jej rozmiaru. Aby zrobić tablicę, musiałbym albo określić jej rozmiar (np. MAX_CLIENTS) albo dynamicznie ją poszerzać przez realloc, a kolejka zrealizowana jest tak, że każdy klient ma po prostu wskaźnik na następnego, co zmniejsza narzut pamięci,
    Analogicznie, tak wygląda obsługa klientów i iteracja kolejki:
    Kod: C / C++
    Zaloguj się, aby zobaczyć kod

    Reszta sprowadza się tylko do przetwarzania pakietów zgodnie ze standardem MQTT, sprawdzania hasła, itd, ale można jeszcze przytoczyć użyte struktury danych. Kolejki używam też do listy subskrypcji:
    Kod: C / C++
    Zaloguj się, aby zobaczyć kod

    Jak widać powyżej, nie ma tu wcale dużo zmiennych ani złożonych mechanizmów. Same przetwarzanie pakietów też sprowadza się zasadniczo do kilku typów zapytań, takich jak:
    - logowanie (z odpowiedzią)
    - subskrypcja tematu (z odpowiedzią)
    - usunięcie subskrypcji (z odpowiedzią)
    - publikacje danych (z potwierdzeniem)
    - ping tudzież heartbeat - utrzymywanie stanu online, notyfikacja w stylu "urządzenie wciąż żyje"
    - odłączenie od serwera ("pożegnanie")
    Poniżej enumeracja pakietów:
    Kod: C / C++
    Zaloguj się, aby zobaczyć kod

    Resztę jednak możecie zobaczyć już w moim projekcie na GitHub, nie chciałem w tym temacie jednak robić pełnej dokumentacji protokołu MQTT, bo to można znaleźć w sieci.


    Problemy z Tasmotą
    Początkowo testy pokazały, że klient MQTT z OpenBeken działa, natomiast ten z Tasmoty nie pokazuje subskrybowanych tematów. OpenBeken korzysta z MQTT z LWIP, a Tasmota PubSubClient. Zacząłem diagnozować krok po kroku, gdzie jest różnica i gdzie psuje się komunikacja.
    
    Info:GEN:MQTTS: parse offset=0 lenBytes=2 remLen=138 pktTotal=141 raw=[10 8A 01 00]
    Info:GEN:MQTTS: pkt type=1 flags=0x0 remLen=138 from ''
    Info:GEN:MQTTS: client 'DVES_476739' connected
    Info:GEN:MQTTS: recv 27 bytes from 'DVES_476739' [31 1F 00 17 74 ...]
    Info:GEN:MQTTS: recv 139 bytes from 'DVES_476739' [4F 6E 6C 69 6E ...]
    Info:GEN:MQTTS: parse offset=0 lenBytes=1 remLen=110 pktTotal=112 raw=[4F 6E 6C 69]
    Info:GEN:MQTTS: pkt type=4 flags=0xf remLen=110 from 'DVES_476739'
    Info:GEN:MQTTS: recv 724 bytes from 'DVES_476739' [31 FA 04 00 25 ...]
    Info:GEN:MQTTS: parse offset=0 lenBytes=2 remLen=634 pktTotal=637 raw=[31 FA 04 00]
    Info:GEN:MQTTS: pkt type=3 flags=0x1 remLen=634 from 'DVES_476739'
    Info:GEN:MQTTS: parse offset=637 lenBytes=1 remLen=85 pktTotal=87 raw=[31 55 00 26]
    Info:GEN:MQTTS: pkt type=3 flags=0x1 remLen=85 from 'DVES_476739'
    

    Szybko okazało się, że pakiety z Tasmoty są łączone w jeden ciąg bajtów, TCP pewnie dodatkowo je fragmentuje. TCP nie gwarantuje tego, że całość dojdzie w jednej ramce, recv może odczytać część pakietu, a część pojawić się później. Na skutek tego musiałem zrezygnować z globalnego bufora dla recv i przenieść ten bufor do struktury klienta:
    Kod: C / C++
    Zaloguj się, aby zobaczyć kod

    Bufor alokuję i poszerzam na bieżąco wedle potrzeb. Z reguły po kilku realokacjach ten bufor się ustatkowuje. Trochę szkoda, że jest taka potrzeba, bo zwiększa to użycie RAM, ale trudno.



    Prezentacja praktyczna
    Kompilujemy (można online) OBK z włączonym sterownikiem MQTTServer w obk_config.h:
    
    #define ENABLE_DRIVER_MQTTSERVER				1
    

    Polecam też kompilować z Berry, bo Berry w OBK ma już integracje tego serwera.
    Uruchamiany sterownik - startDriver MQTTServer. Najlepiej w autoexec.bat:



    Można też go skonfigurować:
    - ms_publish Topic Value
    - ms_user UserName
    - ms_pass Pass
    - ms_port 123
    Wspierany jest tylko jeden użytkownik. Ustawione dane wpisujemy na innych urządzeniach - testowałem z OBK i Tasmota - powinny się one pojawić u nas na panelu, choć ta ich lista jest tu tylko w ramach testów i do debugowania:
    Zrzut ekranu panelu MQTT Server z listą 3 podłączonych urządzeń i przyciskami „Send Toggle” oraz kolorów.
    Na liście są przyciski wysyłające komendy Tasmoty (też zgodne z OBK), ale to również jest tylko dla testów.
    Większe możliwości otwiera dla nas język skryptowy Berry. Poniżej przykładowy skrypt autoexec.be nasłuchujący wybranych tematów od podłączonych urządzeń (komenda ms_subscribe):
    
    autoexec = module('autoexec')
    
    autoexec.init = def()
            autoexec.power_sub = ms_subscribe("cmnd/+/POWER", def (topic, payload)
                    print("Berry POWER: " + topic + " = " + payload)
            end)
            autoexec.get_sub = ms_subscribe("+/+/get", def (topic, payload)
                    print("Berry GET: " + topic + " = " + payload)
            end)
             autoexec.ip_sub = ms_subscribe("+/ip", def (topic, payload)
                     print("Berry Got IP Report: " + topic + " = " + payload)
    		end) 
    		autoexec.uptime_sub = ms_subscribe("+/uptime", def (topic, payload)
    			print("Berry Got UpTime Report: " + topic + " = " + payload)
    		end)
            autoexec.ha_sub = ms_subscribe("homeassistant/+", def (topic,payload) 
    			print("Berry HA Discovery: " + topic + " = " + payload)
    		end)
            autoexec.con_sub = ms_subscribe("+/connected", def (topic, payload)
    			print("Berry Connected: " + topic + " = " + payload)
    		end)
            autoexec.sensor_sub = ms_subscribe("stat/+/SENSOR", def (topic, payload)
                    print("Berry SENSOR: " + topic + " = " + payload)
            end)
            autoexec.stat_sub = ms_subscribe("stat/+/+", def (topic,payload)
    			print("Berry STAT: " + topic + " = " + payload)
    		end)
    end
    
    return autoexec
    

    Rezultaty:
    Zrzut ekranu z logami: połączenie MQTT, komunikaty BERRY i raporty uptime urządzenia obk174083A4
    Zrzut logów konsoli z wpisami „Info:BERRY:Berry POWER STATE” i tematami MQTT.
    Berry to jednak nie tylko nasłuchiwanie. Możemy też publikować dane (komenda ms_publish). W ten sposób można robić różne automatyzacje. Przykładowo, poniższy skrypt przekazuje stan drugiego przekaźnika na włączniku światła do lampki LED. Warto tu wspomnieć, że ten włącznik, mam zamontowany w puszce z przewodami do jednej lampy, a mimo to ma on aż trzy przyciski - jeden z nich steruje lampą, dwa pozostałe są do automatyzacji.
    
    autoexec = module('autoexec')
    
    autoexec.init = def()
    	autoexec.power_state_sub = ms_subscribe("stat/tasmota_476739/POWER2", def (topic, payload)
    		ms_publish("cmnd/obk174083A4/POWER",payload);
    		print("Berry POWER STATE: " + topic + " = " + payload)
    	end)
    end
    
    return autoexec
    
    

    Filmik:



    W ten sposób można również zrobić bardziej złożone mechanizmy, przykładowo:
    - reakcje na przyciski (OBK wspiera zdarzenia przycisków, pojedyncze kliknięcie, podwójne, itd, wciśnięcie)
    - zdarzenia rozłożone w czasie (Berry w OBK ma funkcję oczekiwania)
    - zdarzenia przypisane do danych godzin (OBK ma system kalendarza - addClockEvent)

    Podsumowanie
    Nie ma problemu z tym, by uruchomić prosty serwer MQTT bezpośrednio na układzie takim jak BK7231, a w razie potrzeby można przejść na ESP32 z dodatkowym PSRAM (z 100 kB RAM zyskujemy wtedy kilka megabajtów). Pamięć Flash zasadniczo wcale tu nie ogranicza, a z RAM też nie jest źle, choć wymóg trzymania osobnych buforów dla pakietów dla każdego klienta nieco zwiększa zużycie. Oprócz tego same listy klientów i subskrypcji są dość lekkie i nie zajmują dużo.
    Opracowany serwer MQTT działa i wcale nie jest taki wymagający - wszystko zrealizowałem bez wątków dzięki użyciu nieblokujących socketów, a listy klientów i subskrypcji są oparte na kolejkach, więc nie alokują nadmiernych ilości pamięci na starcie a jednocześnie nie są ograniczone rozmiarowo.
    Ciekawą obserwacją może być to, że standard MQTT z OBK, gdzie każda wartość to osobny "publish" jest znacznie lżejszy pod kątem RAM niż standard Tasmoty, gdzie całość jest publikowana jako jeden JSON, który trzeba chwilę trzymać w pamięci na czas przetwarzania.
    Dodatkowa integracja z Berry sprawia, że możliwości robią się naprawdę duże. Można zaprogramować sobie dowolną logikę i odzwierciedlać działanie Home Assistant na malutkim mikrokontrolerze.
    Dalsze plany:
    - dodać więcej przykładów języka skryptowego Berry dla obsługi MQTT
    - dodać osobny sterownik, który niczym Home Assistant odbiera HASS Discovery i pokazuje samodzielnie urządzenia na panelu OBK, bez żadnego skryptowania i bez pisania kodu
    - sprawdzić sterownik serwera na różnych platformach oraz zbadać, ile urządzeń można wspierać na BK7231 mającym z reguły wolne około 100 kB RAM, a ile na ESP32 z PSRAM (8 MB RAM lub więcej)
    Czy widzicie jakieś zastosowania dla takiego mniejszego serwera MQTT? Wydaje mi się, że to może być dobra alternatywa dla osób, które nie chcą stawiać pełnego Home Assistant.

    Powiązane i dokumentacja:
    https://github.com/openshwprojects/OpenBK7231T_App
    https://openbekeniot.github.io/webapp/devicesList.html
    Berry w OpenBeken
    Online Builds (kompilacja) w OBK
    Wieloplatformowy firmware IoT wspierający aż 32 platformy - podsumowanie OBK 2025

    Fajne? Ranking DIY
    Pomogłem? Kup mi kawę.
    O autorze
    p.kaczmarek2
    Moderator Smart Home
    Offline 
    Inżynier programista z wieloletnim doświadczeniem embedded i full stack developer.
    Specjalizuje się w: embedded, Full-Stack Developer
    p.kaczmarek2 napisał 14604 postów o ocenie 12617, pomógł 654 razy. Jest z nami od 2014 roku.
  • REKLAMA
  • #2 21851339
    dbell37
    Poziom 1  
    Posty: 1
    >>21849274 Bardzo fajnie! Otwiera to kilka interesujących możliwości. Jestem również ciekawy, ile urządzeń i wiadomości na sekundę mogą obsłużyć różne płytki, np. 8266, ESP32. Dzięki za opublikowanie tego fajnego projektu.
  • #3 21853746
    Pomarańczowy_Piotruś
    Poziom 10  
    Posty: 10
    Pewnie, że się znajdzie zastosowanie. Nie ma nawet małej cząstki twojej wiedzy. Mi różne czaty, bo wielu mękach zrobiły z esp32-c3 broker z AP. Jest podpięte kilka esp8266. Śmiga. Ściąga ceny rce z giełdy, rządzi. Ludzie często jeżdżą Lamborghini po bułki do sklepu, zamiast się przejść. Powodzenia w projekcie.
📢 Słuchaj (AI):
REKLAMA