MQTT 개념과 구현 예제를 다룹니다.
이름만 들어봤던 것을 마침 개념을 정확히 알아둘 필요가 생겨서 정리해봤습니다.
아직 예제 코드 및 MQTT 예제는 실행하여 확인해보지는 않았습니다.
다음 링크의 문서를 번역한 내용을 바탕으로 이해한 것을 보충했습니다.
https://learn.sparkfun.com/tutorials/introduction-to-mqtt/all
추가적으로 아래 링크를 참고했습니다.
https://khj93.tistory.com/entry/MQTT-MQTT의-개념
2022. 2. 23 최초작성
MQTT는 네트워크에 있는 IoT 디바이스간에 메시지를 주고 받기 위한 프로토콜입니다.
주요 개념
몇가지 개념을 알아둘 필요가 있습니다.
Broker - 브로커는 서버에 연결된 구독자 클라이언트와 발행자 클라이언트 간에 메시지를 전송하는 역할을 합니다.
Client - 브로커에 연결하여 정보를 보내거나 받는 장치입니다. 메시지를 전송하는 발행자 클라이언트와 메시지를 수신하는 구독자 클라이언트가 될 수 있습니다. 발행자 클라이언트는 토픽을 통해 브로커에게 메시지를 전송하고 브로커는 특정 토픽을 구독한 구독자 클라이언트에게 메시지를 전송하게 됩니다. 구독자 클라이언트는 주기적으로 체크하는 폴링 방식으로 브로커에서 구독한 토픽에 해당되는 메시지를 수신하게 됩니다.
Publish - 토픽을 구독한 클라이언트에게 메시지를 송신하기 위해 브로커에 메시지를 송신하는 클라이언트입니다.
Subscribe - 클라이언트는 브로커에게 관심 있는 토픽을 구독한다고 알려줍니다.
클라이언트가 토픽을 구독하면 브로커에 배포된 해당 토픽의 모든 메시지가 해당 토픽 구독자(subscribers)에게 송신됩니다.
클라이언트는 해당 토픽에 대한 브로커로부터의 메시지 수신을 중지하기 위해 구독을 취소할 수도 있습니다.
Topic - 메시지의 이름입니다. 클라이언트는 토픽을 게시(publish), 구독(subsctibe) 또는 둘을 동시에 수행할 수 있습니다.
QoS - 서비스 품질(Quality of Service)
각 연결(connection)은 0 ~ 2 사이의 정수 값을 지정하여 브로커에 대한 서비스 품질을 지정할 수 있습니다.
QoS는 TCP 데이터 전송 처리에 영향을 주지 않고 MQTT 클라이언트 사이에서만 영향을 미칩니다.
0 으로 지정하면 최대 한번 송신 하며, 정상적으로 보내졌는지 확인하지 않습니다.
1 으로 지정하면 적어도 한번 송신하며 정상적으로 보내졌는지 확인될 때가지 여러 번 전송합니다.
같은 메시지를 중복 송신할 가능성이 있습니다
2 를 지정하면 메시지를 한 번만 송신합니다. 발신자 및 수신자 클라이언트가 2단계 핸드쉐이크를 사용하기 때문에 송신 클라이언트는 이미 메시지를 보낸 것을 알고 있기 때문에 수신자 클라이언트가 메시지를 못받았다고 중복 송신을 하지는 않습니다.
MQTT 동작
MQTT는 발행/구독 메시징 프로토콜(publish/subcribe messaging protocol)입니다.
MQTT가 동작하려면 클라이언트가 토픽을 구독하거나 발행할 수 있는 네트워크에 연결되어야 합니다. 클라이언트가 토픽을 게시하면 메시지가 브로커로 전송되기 시작하고 브로커는 해당 주제를 구독한 모든 클라이언트에게 해당 메시지를 전송합니다.
토픽은 디렉토리와 같은 구조로 상위 토픽과 하위 토픽으로 구성되어 배치됩니다. 상위 토픽 내에 여러 클라이언트가 있는 경우 토픽은 "LivingRoom" 또는 "LivingRoom/Light"일 수 있습니다.
아래 그림은 상위 토픽과 하위 토픽으로 구성된 예입니다.
출처 - https://khj93.tistory.com/entry/MQTT-MQTT의-개념
구독자 클라이언트는 구독된 주제에서 들어오는 메시지를 수신하고 "켜기" 또는 "끄기"와 같이 해당 토픽에 게시된 내용에 반응하여 전등을 제어하도록 할 수 있습니다.
클라이언트는 하나의 토픽을 구독한 상태에서 다른 토픽을 게시할 수 있습니다. 클라이언트가 조명을 제어하는 “LivingRoom/Light"을 구독하는 경우 다른 클라이언트가 해당 조명의 상태를 모니터링할 수 있도록 조명의 상태를 배포하기 위해 "LivingRoom/Light/State"와 같은 다른 토픽에 게시할 수 있습니다.
이미지 출처 - https://underflow101.tistory.com/22
이제 MQTT가 작동하는 방식에 대한 이론을 이해했으므로 Raspberry Pi 및 ESP32 Thing 보드를 사용하여 예제를 빌드하여 작동하는 모습을 확인하겠습니다. 브로커를 설정하고 테스트를 실행하여 올바르게 작동하는지 확인합니다.
브로커 세팅
본 문서에서 사용된 예제에서는 로컬 네트워크에 연결된 Raspberry Pi에서 Mosquitto라는 무료 오픈 소스 브로커를 실행합니다.
Mosquitto를 다음처럼 설치합니다.
sudo apt-get install mosquitto -y
mosquitto client를 설치합니다.
sudo apt-get install mosquitto mosquitto-clients -y
이제 다음처럼 "test_topic" 토픽을 구독합니다.
mosquitto_sub -t "test_topic"
현재 명령을 실행한 터미널에서 브로커에서 수신된 메시지를 출력하기 때문에 메시지를 게시하려면 두 번째 터미널 창을 열어야 합니다. 열리면 다음 명령을 사용하여 토픽 test_topic에 메시지를 게시합니다.
“test_topic” 토픽에 “HELLO WORLD!” 메시지를 게시합니다.
mosquitto_pub -t "test_topic" -m "HELLO WORLD!"
클라이언트 세팅
이제 브로커가 실행 중인 상태이니 클라이언트를 추가할 차례입니다.
우리는 두 개의 클라이언트를 만들 것입니다.
첫 번째 클라이언트(Publish Client - Switch)는 버튼을 누를 때마다 "on" 또는 "off" 메시지를 "room/light" 주제에 게시됩니다.
두 번째 클라이언트(Subscribe Client - Light)는 "room/light" 토픽을 구독하고 수신된 메시지에 따라 LED를 켜거나 끄고 현재 LED 상태를 "room/light/state" 토픽에 게시합니다.
Publish Client - Switch
스위치 눌러진 상태를 메시지로 전송하는 역할을 하는 클라이언트 생성하기 위해 ESP32을 사용합니다. ESP32에서 MQTT가 작동하도록 하려면 아래 링크에서 다운로드할 수 있는 PubSubClient라는 라이브러리를 설치해야 합니다.
공유기의 WiFi 인증 정보과 Raspberry Pi 브로커의 IP 주소를 코드에서 수정해야 합니다.
ESP32가 네트워크에 연결된 후, 버튼이 눌러지면 ESP32는 "room/light" 토픽에 “on” 메시지를 게시하고 버튼에서 손을 떼면 “off” 메시지를 게시합니다.
/****************************************************************************** MQTT_Switch_Example.ino Example for controlling a light using an MQTT switch by: Alex Wende, SparkFun Electronics This sketch connects the ESP32 to a MQTT broker and subcribes to the topic room/light. When the button is pressed, the client will toggle between publishing "on" and "off". ******************************************************************************/ #include <WiFi.h> #include <PubSubClient.h> const char *ssid = "-----"; // name of your WiFi network const char *password = "-----"; // password of the WiFi network const byte SWITCH_PIN = 0; // Pin to control the light with const char *ID = "Example_Switch"; // Name of our device, must be unique const char *TOPIC = "room/light"; // LED 제어 명령 메시지를 게시할 토픽 이름 IPAddress broker(192,168,1,-); // IP address of your MQTT broker eg. 192.168.1.50 WiFiClient wclient; PubSubClient client(wclient); // Setup MQTT client bool state=0; // Connect to WiFi network void setup_wifi() { Serial.print("\nConnecting to "); Serial.println(ssid); WiFi.begin(ssid, password); // Connect to network while (WiFi.status() != WL_CONNECTED) { // Wait for connection delay(500); Serial.print("."); } Serial.println(); Serial.println("WiFi connected"); Serial.print("IP address: "); Serial.println(WiFi.localIP()); } // Reconnect to client void reconnect() { // Loop until we're reconnected while (!client.connected()) { Serial.print("Attempting MQTT connection..."); // Attempt to connect if (client.connect(ID)) { Serial.println("connected"); Serial.print("Publishing to: "); Serial.println(TOPIC); // 게시할 토픽을 등록하는 부분이 없네요. Serial.println('\n'); } else { Serial.println(" try again in 5 seconds"); // Wait 5 seconds before retrying delay(5000); } } } void setup() { Serial.begin(115200); // Start serial communication at 115200 baud pinMode(SWITCH_PIN,INPUT); // Configure SWITCH_Pin as an input digitalWrite(SWITCH_PIN,HIGH); // enable pull-up resistor (active low) delay(100); setup_wifi(); // Connect to network client.setServer(broker, 1883); // 브로커를 설정합니다 } void loop() { if (!client.connected()) // Reconnect if connection is lost { reconnect(); } client.loop(); // 스위치 상태를 읽음 if(digitalRead(SWITCH_PIN) == 0) { state = !state; //toggle state if(state == 1) // 버튼이 눌러진 경우 { client.publish(TOPIC, "on"); // on 메시지를 토픽에 게시합니다. Serial.println((String)TOPIC + " => on"); } else // 버튼이 안눌러진 경우 { client.publish(TOPIC, "off"); // off 메시지를 토픽에 게시합니다. Serial.println((String)TOPIC + " => off"); } while(digitalRead(SWITCH_PIN) == 0) // Wait for switch to be released { // Let the ESP handle some behind the scenes stuff if it needs to yield(); delay(20); } } } |
코드를 업로드 후, ESP32를 네트워크에 연결합니다. 이제 브로커가 설치되어 있는 라즈베리파이의 터미널 창에서 다음 명령을 사용하여 "room/light" 토픽을 구독합니다.
mosquitto_sub -t "room/light"
이제 GPIO 핀 0에 연결된 ESP32의 버튼을 누르면 아래와 같이 켜짐/꺼짐 명령이 표시됩니다.
Subscribe Client - Light
이제 스위치가 브로커에 연결되었으므로 구독한 토픽의 메시지를 수신하여 LED를 동작하게 만들 장치를 준비합니다.
또 하나의 ESP32를 준비하여 GPIO 핀 5에 연결된 LED를 제어하도록 합니다.
공유기의 WiFi 인증 정보과 Raspberry Pi 브로커의 IP 주소를 코드에서 수정해야 합니다.
/****************************************************************************** MQTT_Light_Example.ino Example for controlling a light using MQTT by: Alex Wende, SparkFun Electronics This sketch connects the ESP8266 to a MQTT broker and subcribes to the topic room/light. When "on" is recieved, the pin LIGHT_PIN is toggled HIGH. When "off" is recieved, the pin LIGHT_PIN is toggled LOW. ******************************************************************************/ #include <WiFi.h> #include <PubSubClient.h> const char *ssid = "-----"; // name of your WiFi network const char *password = "-----"; // password of the WiFi network const byte LIGHT_PIN = 5; // LED가 연결된 핀 const char *ID = "Example_Light"; // Name of our device, must be unique const char *TOPIC = "room/light"; // LED 명령을 구독할 토픽 const char *STATE_TOPIC = "room/light/state"; // LED 상태를 게시할 토픽 IPAddress broker(192,168,1,-); // IP address of your MQTT broker eg. 192.168.1.50 WiFiClient wclient; PubSubClient client(wclient); // Setup MQTT client // 브로커에서 메시지가 수신되면 호출됩니다. void callback(char* topic, byte* payload, unsigned int length) { String response; for (int i = 0; i < length; i++) { response += (char)payload[i]; // 브로커에서 수신된 메시지에서 LED 제어 명령을 꺼냅니다. } Serial.print("Message arrived ["); Serial.print(topic); Serial.print("] "); Serial.println(response); if(response == "on") // 수신된 메시지가 on인 경우 { digitalWrite(LIGHT_PIN, HIGH); // LED를 켜진 상태로 만듭니다. client.publish(STATE_TOPIC,"on"); // LED 상태를 on으로 게시합니다. } else if(response == "off") // 수신된 메시지가 off인 경우 { digitalWrite(LIGHT_PIN, LOW); // LED를 꺼진 상태로 만듭니다. client.publish(STATE_TOPIC,"off"); // LED 상태를 off로 게시합니다. } } // Connect to WiFi network void setup_wifi() { Serial.print("\nConnecting to "); Serial.println(ssid); WiFi.begin(ssid, password); // Connect to network while (WiFi.status() != WL_CONNECTED) { // Wait for connection delay(500); Serial.print("."); } Serial.println(); Serial.println("WiFi connected"); Serial.print("IP address: "); Serial.println(WiFi.localIP()); } // Reconnect to client void reconnect() { // Loop until we're reconnected while (!client.connected()) { Serial.print("Attempting MQTT connection..."); // Attempt to connect if(client.connect(ID)) { client.subscribe(TOPIC); // 토픽을 구독합니다. Serial.println("connected"); Serial.print("Subcribed to: "); Serial.println(TOPIC); Serial.println('\n'); } else { Serial.println(" try again in 5 seconds"); // Wait 5 seconds before retrying delay(5000); } } } void setup() { Serial.begin(115200); // Start serial communication at 115200 baud pinMode(LIGHT_PIN, OUTPUT); // Configure LIGHT_PIN as an output delay(100); setup_wifi(); // Connect to network client.setServer(broker, 1883); // 브로커를 설정합니다. client.setCallback(callback);// Initialize the callback routine } void loop() { if (!client.connected()) // Reconnect if connection is lost { reconnect(); } client.loop(); } |
두 번째 ESP32에서 GPIO 핀 5에 연결된 LED 위치를 확인합니다.
두 번째 ESP32가 네트워크에 연결되면 자동으로 "room/light" 토픽에 구독되고 첫 번째 ESP32의 버튼을 누르면 두 번째 ESP32의 GPIO 핀 5에 연결된 LED가 켜져야 합니다.