MQTT protokol je jeden z najpoužívanejších v IoT. Využitie našiel aj v priemyselnej sfére. Tento protokol disponuje ľahkým spôsobom použitia,
jeho prednosťami je M2M komunikácia --> stoj - stroj v spojitosti s nízkou dátovou náročnosťou a minimálnou odozvou. Protokol sa radí do triedy TCP protokolov. MQTT protokol pozná dve sieťové entity. Medzi entity sa radí Broker a klient. Klienty využívajú hierachiu správ - topicov.
Topic -
správa (informácia), ktorú
odoberá (Subscribe), alebo poskytuje (Publish) klient. Topic je najčastejšie viazaný na konkrétne dáta, ktoré klient odoberá.
Napríklad: teplota, vlhkost. Nakoľko je topic hierachický, je možné topic prispôsobiť aj na konkrétne miesto.
Napríklad: byt/teplota, byt/stav_termostatu, byt/vlhkost. Topic je možné hierarchicky zapuzdriť hlbšie,
napríklad: byt/obyvacia_izba/okno_server_teplota. Topic je case, ale aj znakovo senzitívny (pozor na medzery, opačné lomítka). Iné (alebo aj rovnaké) zariadenie môže topic odoberať a na základe načítanej informácie (hodnoty) môže vykonať akciu. Topic je možné využiť na radu akcií, od zaznamenávania dát (teplota, vlhkosť) je ho možné použiť aj na záznam a distribúciu aktuálnych stavov (stav svetiel, stav termostatu [ZAP / VYP]). Subscribe klienti majú vždy aktuálnu informáciu o tomto topicu, keď dôjde k jeho zmene od Publish-era.
Broker - serverové zariadenie (na spôsob routra), ktoré
získava správy od klientov a distribuuje ich iným klientom (ktorí o to požiadajú). Zabezpečuje komunikáciu, stará sa o spoľahlivé doručenie správ s ich aktálnou verziou (najnovšie správy). Klienti medzi sebou nikdy priamo nekomunikujú.
Najčastejším MQTT Brokerom pre vlastné použitie (v LAN sieti, v inteligentnej domácnosti) je Mosquitto. Tento Broker je jeden z najobľúbenejších, využívaný hlavne v systémoch pre domácu automatizáciu - Hass.io, či Domoticz. Broker má vždy prehľad o počte klientov. V prípade, že topic nikto neodoberá, zahodí ho. V prípade, že správa od Publishera obsahuje retain. Je archivovaná. Klientom, ktorí odoberajú (Subscriberom) poskytuje túto správu v pamäti, i keď nie je aktualizovaná od Publishera.
Nevýhodou Brokera je, že sa jedná o fyzické zariadenie (alebo softvérovo emulované) a tak väčšina vývojárov a bastliarov siahne po cloudovom riešení, aspoň na testovacie účely. Medzi najznámenšie patrí Google IoT Cloud, IBM Watson, Azure IoT, IoT Guru Live. Tieto cloudové MQTT servery sú viazané na konkrétnu platformu, častokrát vyžadujú prihlásenie, prepojenie účtu a sú limitované na počet zariadení, počet topicov.
Klient - koncové zariadenie - častokrát
mikrokontróler s jednoduchým sieťovým stackom, ktorý zariadeniu umožňuje pripojiť sa k Brokeru. Zariadenie odosiela (Publish) dáta do siete na príslušný topic. Zariadenie môže taktiež dáta z Brokeru získavať - prihlasením odberu na Topic - Subscribe. V našom prípade budú klientov predstavovať dve platformy z Espressifu - ESP8266 na vývojovej doske NodeMCU v3 Lolin a ESP32 DevKit V1.
Softvérová implementácia pre spojenie s MQTT serverom využíva socketové spojenie z pôvodnej knižnice WiFi, ktorá sa používa na pripojenie k WiFi sieti a predovšetkým na HTTP spojenie. Využitím PubSubClient knižnice je možné
spojiť sa s MQTT Brokerom na porte 1883 - nezabezpečenom porte (s použitím knižnice WiFiClientSecure je možné pripojiť sa na zabezpečenom MQTT porte 8883) a následne na Broker odosielať topic, alebo sa k nejakému prihlásiť na odber.
Klienta môže predstavovať aj iné používateľské zariadenie, napríklad počítač, ktorý sa môžu k odberu (Subscribe) prihlásiť pomocou webovej, či systémovej (Windows) aplikácie. Takéto zariadenie môže do siete taktiež odosielať stavy, dáta (napríklad: počítač je zapnutý, konektivita do internetu).
Zaujímavým cloudovým riešením - zatiaľ v testovacej prevádzke je slovenský MQTT Broker, ktorý prevádzkuje IoT Industries Slovakia. Broker funguje (zatiaľ) bez autentizácie a je voľne - anonýmne využiteľný. Broker funguje 24 hodín denne, 7 dní v týždni s 99.9% garanciou funkčnosti. Každý používateľ tak môže odosielať (Publishovať) topicy, ktoré chce a taktiež odoberať topicy.
Nakoľko tu nefunguje autentizácia a Broker je zdieľaný všetkými používateľmi, je tak možné pripojiť sa k odberu akéhokoľvek dostupného topicu iného zariadenia (s dátami iného používateľa). Broker sa teda momentálne nehodí na bezpečné implementácie. Je vhodný predovšetkým na testovacie a vývojárske účely pre osvojenie si funkcionality komunikácie s MQTT Brokerom, typov správ.
Rozhodol som sa teda tento MQTT Broker vyskúšať, i keď nemám s MQTT takmer žiadne skúsenosti. Na stránke
https://mqtt.iotindustries.sk/index.html je možné nájsť informácie o nastavení pripojenia pre spojenie s MQTT Brokerom. Nakoľko som používal nešifrované spojenie, využil som štandardný MQTT protokol 1883 a adresu MQTT Brokera:
mqtt.iotindustries.sk. Zdrojový kód pre ESP8266 a ESP32 som jednoducho upravil z ukážkových kódov.
Do kódov som doplnil direktívy pre kompilátor (respektíve pre preprocessor), ktorý v závislosti od zvolenej vývojovej dosky dynamicky priradí linkovanú knižnicu, ktorú využíva daný mikrokontróler.
Zdrojové kódy sú tak pre ESP8266 a ESP32 identické a plne prenositeľné medzi týmito platformami. Program obsahuje počítadlo, ktoré sa inkrementuje (aby bola viditelne zmenená odosielaná hodnota). Dáta sa odosielajú (Publishujú) do topicu:
esp32/pocitadlo každých 5 sekúnd. Na rovnaký topic si mikrokontróler prihlási aj odber. Takto je možné kontrolovať, či sa hodnota v danom topicu mení. Subscribe je možné využívať aj z iných mikrokontrolérov - klientov.
Výstup odoberaného topicu je vypísaný do UART monitoru:K tomuto topicu má následne prístup akékoľvek zariadenie, ktoré je na daný Broker pripojené a prihlási si odber na
esp32/pocitadlo. V prípade, že chce klient odoberať všetky topicy, ktoré sú na MQTT Brokeri dostupné, zvolí topic #. Znak mriežky # je obdobný ako používaný * v dopytovacích jazykoch (všetko). Výstup môže vyzerať následovne:
Z testovania musím potvrdiť, že Broker je svižný, odozva je malá, na úrovni od 30 do 50 ms so spracovaním prijatých dát z odoberaného topicu (čas odmeraný pred zavolaním a po ukončení funkcie). Program neobsahuje výpis odosielania topicu (Publish) a tak som nevedel určiť, ako dlho sa vykonáva Publish týchto dát, avšak odozva mi príde taktiež minimálna.
V budúcom článku (v závislosti od čítanosti a ohlasov) si predstavíme pripojenie na MQTT Broker s využitím šifrovaného MQTT protokolu, pričom využijeme socket pre šifrované spojenie z knižnice WiFiClientSecure. Predstavíme si niektoré špeciálne operátory, možnosti čítania (Subscribingu).
Zdrojové kódy pre platformu ESP8266, ESP32 s pripojením na MQTT Broker IoT Industries Slovakia je možné nájsť na Github-e: https://github.com/martinius96/MQTT-Broker-IoTIndustriesESP32 bolo testované pod verziou Arduino Core 1.0.4, ESP8266 pod Arduino Core 2.5.2.
Mnou realizované projekty môžete nájsť na adrese: https://arduino.php5.sk/Zdrojový kód pre pripojenie k MQTT brokeru (Arduino + Ethernet W5100 / W5500, ESP8266, ESP32 comatible sketch):Kód:
#if defined(__AVR_ATmega168__) || defined(__AVR_ATmega328P__) || defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__)
#include <SPI.h>
#include <Ethernet.h>
//#include <Ethernet2.h> //pre Wiznet W5500 Ethernet shield
#elif defined(ESP32)
#include <WiFi.h>
#elif defined(ESP8266)
#include <ESP8266WiFi.h>
#endif
#include <PubSubClient.h>
#if defined(ESP32) || defined(ESP8266)
WiFiClient espClient;
const char* ssid = "WIFI_NAME";
const char* password = "WIFI_PASSWORD";
#elif defined(__AVR_ATmega168__) || defined(__AVR_ATmega328P__) || defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__)
EthernetClient espClient;
byte mac[] = { 0xAA, 0xBB, 0xCC, 0x81, 0x7B, 0x4A };
//IPAddress ip(192, 168, 0, 2);
//IPAddress dnServer(192, 168, 0, 1);
//IPAddress gateway(192, 168, 0, 1);
//IPAddress subnet(255, 255, 255, 0);
#endif
const char* mqtt_server = "mqtt.iotindustries.sk";
PubSubClient client(espClient);
unsigned long lastMsg = 0;
char msg[50];
int pocitadlo = 0;
void callback(char* topic, byte* message, unsigned int length) {
Serial.print("Message arrived on topic: ");
Serial.print(topic);
Serial.print(". Message: ");
String messageTemp;
for (int i = 0; i < length; i++) {
Serial.print((char)message[i]);
messageTemp += (char)message[i];
}
Serial.println();
}
void reconnect() {
// Loop until we're reconnected
while (!client.connected()) {
Serial.print("Attempting MQTT connection...");
// Attempt to connect
if (client.connect("ESP8266Client")) {
Serial.println("connected");
// Subscribe
client.subscribe("esp32/output");
} else {
Serial.print("failed, rc=");
Serial.print(client.state());
Serial.println(" try again in 5 seconds");
// Wait 5 seconds before retrying
delay(5000);
}
}
}
void setup() {
Serial.begin(115200);
Serial.println("UART ready");
#if defined(ESP32) || defined(ESP8266)
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println("WiFi connected");
Serial.println("IP address: ");
Serial.println(WiFi.localIP());
#elif defined(__AVR_ATmega168__) || defined(__AVR_ATmega328P__) || defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__)
if (Ethernet.begin(mac) == 0) {
Ethernet.begin(mac);
//Ethernet.begin(mac, ip);
//Ethernet.begin(mac, ip, dns);
//Ethernet.begin(mac, ip, dns, gateway);
//Ethernet.begin(mac, ip, dns, gateway, subnet);
}
Serial.print(" DHCP assigned IP ");
Serial.println(Ethernet.localIP());
#endif
client.setServer(mqtt_server, 1883);
client.setCallback(callback);
}
void loop() {
if (!client.connected()) {
reconnect();
}
#if defined(ESP32) || defined(ESP8266)
if (WiFi.status() != WL_CONNECTED) {
WiFi.begin(ssid, password);
}
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
#elif defined(__AVR_ATmega168__) || defined(__AVR_ATmega328P__) || defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__)
if (Ethernet.begin(mac) == 0) {
Ethernet.begin(mac);
//Ethernet.begin(mac, ip);
//Ethernet.begin(mac, ip, dns);
//Ethernet.begin(mac, ip, dns, gateway);
//Ethernet.begin(mac, ip, dns, gateway, subnet);
}
#endif
client.loop();
if (millis() - lastMsg > 10000) {
char pocitadlo_pole[8];
pocitadlo = pocitadlo + 1;
dtostrf(pocitadlo, 1, 2, pocitadlo_pole);
client.publish("esp32/pocitadlo", pocitadlo_pole);
client.subscribe("esp32/pocitadlo");
lastMsg = millis();
}
}