• Skip to primary navigation
  • Skip to main content

IoTmaker

사물인터넷에 대한 모든 것 여기서 해결하셔요!

  • 홈
  • 책:마이크로파이썬을 활용한 사물인터넷
  • 책:따라 하면서 배우는 사물인터넷
  • 온라인 교육 코스
  • 새로운 소식
  • 의견보내기
  • 내 수강정보
  • 로그인
  • 회원가입

와이파이와 MQTT기본 라이브러리 사용법

책에서 사용하는 부품 구입하기

아두이노에서 MQTT 브로커에 접속하기 위한 클라이언트 라이브러리는 PubSubClient.h입니다.

PubSubClient.h 바로가기 다음 프로그램은 제12장의 프로그램 12-1 mqtt-led-control을 AimMqtt.h의 도움없이 직접 PubSubClient.h를 이용하여 구현한 것입니다.

프로그램 mqtt-led-control-primitive
// topic
#define TOPIC_LED   "led"   // 1:ON, 0:OFF
#define TOPIC_LED_R "led-r" // LED 상태 응답
#define TOPIC_MSG   "msg"   // 진행 상태 보고

// 무선 공유기(AP,엑세스 포인트)의 이름과 비밀번호
#define AP_SSID1  "my-ssid1"
#define AP_PASS1  "my-pass1"

#define AP_SSID2  "my-ssid2"  // 없으면 ""
#define AP_PASS2  "my-pass2"  // 없으면 ""

#define AP_SSID3  "my-ssid3"  // 없으면 ""
#define AP_PASS3  "my-pass3"  // 없으면 ""

// MQTT 설정 
#define SERVER     "192.168.XXX.XXX"
#define PORT       1883

#define USER       "user"  
#define PASS       "yourPass"

#include <ESP8266WiFi.h>
#include <ESP8266WiFiMulti.h>
#include <PubSubClient.h>

WiFiClient espClient;
ESP8266WiFiMulti wifiMulti;
PubSubClient client;


// 라이브러리
#include <AimOutput.h>
#include <AimSerial.h>
#include <AimTimer.h>

// 오브젝트 생성
AimOutput led(LED_BUILTIN,LOW);
AimSerial mySerial;
AimTimer  timerMsg(10);

void setup_wifi() {  
  randomSeed(micros());
  delay(100);
  
  // WiFi station mode
  WiFi.mode(WIFI_STA);  
  wifiMulti.addAP(AP_SSID1,AP_PASS1);
  wifiMulti.addAP(AP_SSID2,AP_PASS2);
  wifiMulti.addAP(AP_SSID3,AP_PASS3);

  Serial.printf("\nConnecting to [%s],[%s],[%s]\n",
                AP_SSID1,AP_SSID2,AP_SSID3);  
  while (wifiMulti.run() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }

  Serial.print("\nConnected to [");
  Serial.print(WiFi.SSID());
  Serial.print("],IP=");
  Serial.println(WiFi.localIP());
}

void reconnect() {
  // Loop until we're reconnected
  while (!client.connected()) {
    Serial.println("Attempting MQTT connection...");
    String clientId = "ESP8266-";
    clientId += String(random(0xffff), HEX); 
    Serial.printf("SERVER=[%s],PORT=%d\n",SERVER,PORT);
    Serial.printf("device=[%s],USER=[%s],PASS=[***]\n",clientId.c_str(),USER);
    // Attempt to connect
    if (client.connect(clientId.c_str(),USER,PASS)) {
      Serial.println("MQTT connected\n-----");
      int QoS = 1;
      client.subscribe(TOPIC_LED,QoS);
      Serial.printf("SUB: [%s],[%d]\n",TOPIC_LED,QoS);
    } 
    else {
      Serial.print("failed, rc=");
      Serial.print(client.state());
      Serial.println(" try again in 5 seconds");
      delay(5000);
    }
  }
}

void callback(char* topic, byte* payload, unsigned int length) {
  char newPayload[32];
  int newLength = length;
  if (newLength > 31 ) {
    newLength = 31;
  }

  Serial.print("RCV: [");
  Serial.print(topic);
  Serial.print("]");
  for (int i = 0; i < newLength; i++) {
    newPayload[i] = (char)payload[i];
  }
  newPayload[newLength] = '\0';
  Serial.print(",[");
  Serial.print(newPayload);
  Serial.println("]");

  String topicS = String(topic);
  String payloadS = String(newPayload);

  if (topicS == TOPIC_LED) {
    led.set(payloadS);
    client.publish(TOPIC_LED_R,payloadS.c_str(),true);
    Serial.printf("PUB: [%s],[%s]\n",topicS.c_str(),payloadS.c_str());
  }
}

void setup() {
  led.off();

  Serial.begin(115200);
  Serial.println();

  mySerial.begin(mySerialCallback);

  ////mqtt.begin(myMqttSub,myMqttCallback);  
  setup_wifi();
  client.setClient(espClient);
  client.setServer(SERVER,PORT);
  client.setCallback(callback);
}

void loop() {
  mySerial.run();

  if (!client.connected()) {
    reconnect();
  }
  client.loop();

  if (timerMsg.isOn()) {
    static unsigned long value = 0;
    String valueS = String(++value);
    client.publish(TOPIC_MSG,valueS.c_str());
    Serial.printf("PUB: [%s],[%s]\n",TOPIC_MSG,valueS.c_str());
  }
}

//-----
// 시리얼 모니터에서 key,val이 입력될 때마다 수행
//-----
void mySerialCallback(const String& key,const String& val) {
  if (key == TOPIC_LED) {
    client.publish(TOPIC_LED,val.c_str(),true);
    Serial.printf("PUB: [%s],[%s]\n",TOPIC_MSG,val.c_str());
  }
}
AP(공유기)에 접속하기: AP를 하나만 지정
AP를 한 개만 지정할 때 WiFi 접속 코드의 일부
// 무선 공유기(AP,엑세스 포인트)의 이름과 비밀번호
#define AP_SSID  "my-ssid"
#define AP_PASS  "my-pass"
#include <ESP8266WiFi.h>

WiFiClient espClient;

void setup_wifi() { 
  delay(100);
  
  // WiFi station mode
  WiFi.mode(WIFI_STA);  
  WiFi.begin(AP_SSID,AP_PASS);   

  Serial.printf("\nConnecting to [%s]\n",AP_SSID);  
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }

  Serial.print("\nConnected to [");
  Serial.print(WiFi.SSID());
  Serial.print("],IP=");
  Serial.println(WiFi.localIP());
}
WiFi.begin(AP_SSID,AP_PASS) 메소드로 WiFi에 접속합니다. WiFi.status()의 값이 WL_CONNECTED가 되면 WiFi 접속이 완료됩니다. WiFi.status()의 코드 값은 다음과 같이 정의되어 있습니다. 따라서 WiFi가 접속이 되면 WiFi.status()의 값은 3이 됩니다.

WiFi.status()의 값
typedef enum {
  WL_NO_SHIELD = 255,
  WL_IDLE_STATUS = 0,
  WL_NO_SSID_AVAIL = 1,
  WL_SCAN_COMPLETED = 2,
  WL_CONNECTED = 3,
  WL_CONNECT_FAILED = 4,
  WL_CONNECTION_LOST = 5,
  WL_DISCONNECTED = 6
} wl_status_t;
AP(공유기)에 접속하기: AP를 여러 개 지정
AP를 여러개 지정할 때 WiFi 접속 코드의 일부
// 무선 공유기(AP,엑세스 포인트)의 이름과 비밀번호
#define AP_SSID1  "my-ssid1"
#define AP_PASS1  "my-pass1"

#define AP_SSID2  "my-ssid2"  // 없으면 ""
#define AP_PASS2  "my-pass2"  // 없으면 ""

#define AP_SSID3  "my-ssid3"  // 없으면 ""
#define AP_PASS3  "my-pass3"  // 없으면 ""

#include <ESP8266WiFi.h>
#include <ESP8266WiFiMulti.h>

WiFiClient espClient;
ESP8266WiFiMulti wifiMulti;

void setup_wifi() { 
  delay(100);
  
  // WiFi station mode
  WiFi.mode(WIFI_STA);  
  wifiMulti.addAP(AP_SSID1,AP_PASS1);
  wifiMulti.addAP(AP_SSID2,AP_PASS2);
  wifiMulti.addAP(AP_SSID3,AP_PASS3);

  Serial.printf("\nConnecting to [%s],[%s],[%s]\n",
                AP_SSID1,AP_SSID2,AP_SSID3);  
  while (wifiMulti.run() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }

  Serial.print("\nConnected to [");
  Serial.print(WiFi.SSID());
  Serial.print("],IP=");
  Serial.println(WiFi.localIP());
}
wifiMulti.addAP(AP_SSIDn,AP_PASSn) 메소드로 WiFi를 여러 개 지정한후 wifiMulti.run()으로 접속을 시도합니다. 이 때 가장 신호가 강한 AP(공유기)에 접속이 됩니다. wifiMulti.run()은 실행 결과를 숫자로 돌려주는데 그 값은 위의 WiFi.status()와 동일합니다.

WiFi에 접속이 되었다가 접속이 끊어진 경우 별도 명령문을 실행하지 않아도 WiFi에 자동으로 접속을 시도합니다. 따라서 WiFi에 다시 접속하기 위한 코드를 추가로 작성할 필요는 없습니다.
MQTT 브로커에 접속하기
MQTT 브로커에 접속하는 코드의 일부
// MQTT 설정 
#define SERVER     "192.168.XXX.XXX"
#define PORT       1883

#define USER       "user"  
#define PASS       "yourPass"

#include <ESP8266WiFi.h>
#include <ESP8266WiFiMulti.h>
#include <PubSubClient.h>

WiFiClient espClient;
ESP8266WiFiMulti wifiMulti;
PubSubClient client;

void reconnect() {
  // Loop until we're reconnected
  while (!client.connected()) {
    Serial.println("Attempting MQTT connection...");
    String clientId = "ESP8266-";
    clientId += String(random(0xffff), HEX); 
    Serial.printf("SERVER=[%s],PORT=%d\n",SERVER,PORT);
    Serial.printf("device=[%s],USER=[%s],PASS=[***]\n",clientId.c_str(),USER);
    // Attempt to connect
    if (client.connect(clientId.c_str(),USER,PASS)) {
      Serial.println("MQTT connected\n-----");
      int QoS = 1;
      client.subscribe(TOPIC_LED,QoS);
      Serial.printf("SUB: [%s],[%d]\n",TOPIC_LED,QoS);
    } 
    else {
      Serial.print("failed, rc=");
      Serial.print(client.state());
      Serial.println(" try again in 5 seconds");
      delay(5000);
    }
  }
}
client.connect(디바이스,사용자,비밀번호) 메소드를 사용하여 MQTT 브로커에 접속을 시도하고 성공 여부를 돌려 받습니다. MQTT에 접속되어 있는지는 client.connected() 메소드로 확인할 수 있습니다. 접속과 관련된 자세한 내용은 client.state()로 알 수 있습니다. 그 값은 다음과 같습니다.

client.state() 값
// Possible values for client.state()
#define MQTT_CONNECTION_TIMEOUT     -4
#define MQTT_CONNECTION_LOST        -3
#define MQTT_CONNECT_FAILED         -2
#define MQTT_DISCONNECTED           -1
#define MQTT_CONNECTED               0
#define MQTT_CONNECT_BAD_PROTOCOL    1
#define MQTT_CONNECT_BAD_CLIENT_ID   2
#define MQTT_CONNECT_UNAVAILABLE     3
#define MQTT_CONNECT_BAD_CREDENTIALS 4
#define MQTT_CONNECT_UNAUTHORIZED    5
MQTT 토픽이 수신될 때 callback 함수 사용하기
callback함수
void callback(char* topic, byte* payload, unsigned int length) {
  char newPayload[32];
  int newLength = length;
  if (newLength > 31 ) {
    newLength = 31;
  }

  Serial.print("RCV: [");
  Serial.print(topic);
  Serial.print("]");
  for (int i = 0; i < newLength; i++) {
    newPayload[i] = (char)payload[i];
  }
  newPayload[newLength] = '\0';
  Serial.print(",[");
  Serial.print(newPayload);
  Serial.println("]");

  String topicS = String(topic);
  String payloadS = String(newPayload);

  if (topicS == TOPIC_LED) {
    led.set(payloadS);
    client.publish(TOPIC_LED_R,payloadS.c_str(),true);
    Serial.printf("PUB: [%s],[%s]\n",topicS.c_str(),payloadS.c_str());
  }
}
client.subscribe(토픽,QoS)로 구독한 토픽의 내용이 수신될 때마다 실행되는 함수입니다. 이 함수 프로토타입은 다음과 같습니다.

void callback(char* topic, byte* payload, unsigned int length);

첫 번째 매개변수는 topic이며 null로 끝나는 문자열입니다. 문제는 두 번째 매개변수인 payload의 속성이 byte* 이라는 점입니다. 그래서 null로 끝나는 문자열로 처리할 수 없기 때문에 세 번째 매개변수로 length를 지정하고 있습니다. 결국 payload를 null로 끝나는 문자열로 바꾸는 루틴이 추가로 필요합니다. 그 방법은 byte를 char로 형변환하여 복사하고 값의 끝에 \0를 삽입하여 null로 끝나는 문자열로 바꾸는 것입니다.

for (int i = 0; i < newLength; i++) {
  newPayload[i] = (char)payload[i];
}
newPayload[newLength] = '\0';
null로 끝나는 문자열인 topic과 newPayload을 String 속성으로 바꾼 후, 각 토픽에 맞는 후속처리를 하면 편합니다.

함수 setup() 안에 꼭 두어야 할 코드
setup() 코드의 일부
void setup() {
  setup_wifi();
  client.setClient(espClient);
  client.setServer(SERVER,PORT);
  client.setCallback(callback);
}
함수 loop() 안에 꼭 두어야 할 코드
loop() 코드의 일부
void loop() {
  if (!client.connected()) {
    reconnect();
  }
  client.loop();
}

Copyright © 2025 ·로그인