블로그 이미지
평범하게 살고 싶은 월급쟁이 기술적인 토론 환영합니다.같이 이야기 하고 싶으시면 부담 말고 연락주세요:이메일-bwcho75골뱅이지메일 닷컴. 조대협


Archive»


 
 

ESP01 (ESP8266)을 이용한 HTTP 통신


조대협 (http://bcho.tistory.com)


하드웨어 시리얼과 소프트웨어 시리얼

앞의 글에서 ESP01을 연결해봤는데, ESP01 연결시에 포트를 2,3번 포트를 사용하고, 코드는 SoftSerial 라이브러리를 이용하였다. 아두이노 우노의 0,1 번포트는 시리얼 통신을 위한 RX,TX 포트이다. 이를 Hardware Serial 이라고 한다. 그러면 하드웨어 시리얼 포트를 사용하지 않고 2,3번 포트를 사용한 후에 소프트웨어 시리얼 처리를 한 이유는 무엇을까?


하드웨어 시리얼 포트는 PC와 연결되어 있을때 PC와 통신을 목적으로 사용된다. 그래서 하드웨어 시리얼을 사용하지 않은것인데, ESP8266 관련 라이브러리들을 살펴보니, 대부분 하드웨어 시리얼을 사용하는 것으로 보인다. 그러면 이 문제를 어떻게 해결할것일까?


아두이오 우노 기판을 보면 TX와 RX라고 써있는 LED 가 있는데, 하드웨어 시리얼 통신을 할때만 깜빡 거린다.




PC에 연결이 되어 있을 때, AT 명령을 실행할때는 이 LED가 빛나지 않고, 코드를 업로드 할때만 빛이 난다. 즉 코드 업로드 시에만 하드웨어 시리얼을 PC가 사용한다는 것이기 때문에, 코드를 업로드한후에 ESP01 보드를 TX,RX 단자에 연결하면 하드웨어 시리얼을 사용할 수 있다.

아두이노를 이용한 HTTP 통신

아두이노에 ESP01 모듈을 연결했으면, HTTP 통신을 해보도록 한다. ESP01 (ESP8266 계열)은 TCP/UDP 스택이 이미 들어가 있기 때문에, TCP 통신은 가능하다. HTTP 통신을 하려면 TCP 프로토콜을 이용하여 HTTP 메세지를 보내주면 되는데, AT 명령어를 사용하면 된다.

AT 명령어 레퍼런스는 https://www.electrodragon.com/w/ESP8266_AT-Command_firmware 를 참고하기 바란다. AT명령을 이용하여 WIFI 네트워크에 연결하는 방법은 http://bcho.tistory.com/1283 에 설명되어 있다. WIFI 네트워크에 연결되어 있다고 가정하고 AT명령을 이용해서 HTTP 명령을 보내는 방법을 보자


HTTP  서버 연결

HTTP 요청을 보내기 위해서 먼저 웹서버에 TCP 커넥션을 연결해야 한다. TCP 커넥션 연결 명령어는 AT+CIPSTART로 다음과 같다.


AT+CIPSTART={TCP 연결 번호},”TCP”,”웹서버IP”,{포트번호}


이 명령은 해당 웹서버 IP의 포트로 연결을 하고 연결 번호(이름)를  “0”으로 지정한것이다.

210.118.92.89 서버의 80 포트로 연결하려면 Serial 콘솔에서 AT+CIPSTART=0,”TCP”,”210.118.92.89”,80 명령어를 실행하면 된다. 이 연결의 연결 번호는 0 번이 된다.

명령어 전달

서버로 연결이 되었으면, HTTP 명령어를 보내야 하는데, 명령어 전달은 AT+CIPSEND 명령어를 사용하면 된다. 명령어의 형식은 다음과 같다.


AT+CPISEND={TCP 연결번호},{보내고자 하는 메세지 길이}


예를 들어 “GET /index.html” 명령을 0번 TCP 연결로 보내고자 하면, 메세지의 길이가 15자이기 때문에,

AT+CPISEND=0,15 로 실행하고, 그 다음에

> 가 나오면 GET /index.html 을 전송하면 된다.

예제

실제 개발에서는 콘솔에서 AT 명령을 직접 입력해서 사용하는 것이 아니기 때문에, 이를 코드화 해야 하는데, 아래는 코드화 한 예제이다. (실제 개발에서는 사용하지 않을것이기 때문에 테스트는 하지 않았다)

#include <SoftwareSerial.h>


SoftwareSerial esp(2,3); //TX,RX

String SSID="{Wifi SSID}";

String PASSWORD="{Wifi password}";


void connectWifi(){

 String cmd = "AT+CWMODE=1";

 esp.println(cmd);

 cmd ="AT+CWJAP=\""+SSID+"\",\""+PASSWORD+"\"";

 esp.println(cmd);

 delay(1000);

 if(esp.find("OK")){

   Serial.println("Wifi connected");

 }else{

   Serial.println("Cannot connect to Wifi"+esp);

 }

}


void httpGet(String server,String uri){

 String connect_server_cmd = "AI+CIPSTART=4,\"TCP\",\""+server+"\",80";

 esp.println(connect_server_cmd);

 String httpCmd = "GET "+uri;

 String cmd = "AT+CIPSEND=4,"+httpCmd.length();

 esp.println(cmd);

 esp.println(httpCmd);


}

void setup() {

 // put your setup code here, to run once:

 esp.begin(9600);

 Serial.begin(9600);

 connectWifi();

 httpGet("210.192.111.122","/index.html");

}


void loop() {

 // put your main code here, to run repeatedly:


}


위의 붉은 색으로 표시된 부분을 보면 번호 4번으로 TCP연결을 열고, 4번 채널에 AT+CIPSEND 명령을 이용하여 GET /index.html 을 실행하였다.

SDK의 활용

HTTP 통신의 기본 원리를 이해하였으면, 이제 실제로 HTTP 호출을 해보자. AT 명령을 이용해도 되지만,  HTTP 명령어 종류가 많고, 연결, 메세지 전송/받기등 다양한 명령을 AT 명령으로 직접 코딩하기에는 코딩양이 많고 복잡해진다. 그래서 이런 명령을 잘 추상화하여 단순화 해놓은 SDK를 사용하는 것이 좋은데, 아두이노가 오픈 소스인 만큼, 오픈소스 기반의 SDK가 많다. 장점이기도 하지만 반대로 단점도 되는데, 아두이노나 ESP8266 펌웨어의 버전이 낮으면 동작하지 않는 경우도 있고, 또한 많은 라이브러리들이 아두이노 WIFI 실드를 기준으로 개발되었거나 또는 아두이노 → ESP8266으로 통신하는 용도가 아닌 ESP8266 기반의 아두이노 보드를 기준으로 개발된 라이브러리가 많다.

아두이노 라이브러리 추가하기

우리가 사용할 라이브러리는 WiFiESP라는 라이브러리 (https://github.com/bportaluri/WiFiEsp) 로 소프트웨어 시리얼과 하드웨어 시리얼 통신 양쪽을 모두 지원하며, HTTP 형태로 라이브러리가 잘 패키징 되어 있다. 또한 예제가 비교적 잘 정리되어 있어서 사용하기 좋다.

이런 오픈소스 라이브러리를 사용하려면 라이브러리를 설치해야 하는데, 설치 방법은 다음과 같다.

github에 배포된 코드의 경우, 해당 코드를 아래 그림과 같이 download 버튼을 이용하여 zip 파일로 다운로드 받는다.




다음 Sketch에서 Sketch > Include Library > Add .Zip Library 를 선택해서 앞에서 다운 받은 ZIP 파일을 선택하면 라이브러리로 등록되어 사용이 가능하다.




SDK를 이용하여 HTTP 호출


아래코드는 라이브러리를 추가한후에, 예제에 나온 코드를 그대로 사용한 예이다. 아두이노 2,3번 핀을 ESP01의 TX,RX 핀에 연결해서 소프트웨어 시리얼로 통신하였다. 그래서 코드 부분을 보면 SoftwareSerial Serial1(2,3)으로 소프트웨어 시리얼로 선언되어 있는 것을 확인할 수 있다.


#include "WiFiEsp.h"


// Emulate Serial1 on pins 6/7 if not present

#ifndef HAVE_HWSERIAL1

#endif

#include "SoftwareSerial.h"

SoftwareSerial Serial1(2, 3); // RX, TX


char ssid[] = "{WIFI SSID}";            // your network SSID (name)

char pass[] = "{WIFI Password}";        // your network password

int status = WL_IDLE_STATUS;     // the Wifi radio's status


char server[] = "arduino.cc";


// Initialize the Ethernet client object

WiFiEspClient client;


void setup()

{

 // initialize serial for debugging

 Serial.begin(9600);

 // initialize serial for ESP module

 Serial1.begin(9600);

 // initialize ESP module

 WiFi.init(&Serial1);


 // check for the presence of the shield

 if (WiFi.status() == WL_NO_SHIELD) {

   Serial.println("WiFi shield not present");

   // don't continue

   while (true);

 }


 // attempt to connect to WiFi network

 while ( status != WL_CONNECTED) {

   Serial.print("Attempting to connect to WPA SSID: ");

   Serial.println(ssid);

   // Connect to WPA/WPA2 network

   status = WiFi.begin(ssid, pass);

 }


 // you're connected now, so print out the data

 Serial.println("You're connected to the network");

 

 printWifiStatus();


 Serial.println();

 Serial.println("Starting connection to server...");

 // if you get a connection, report back via serial

 if (client.connect(server, 80)) {

   Serial.println("Connected to server");

   // Make a HTTP request

   client.println("GET /asciilogo.txt HTTP/1.1");

   client.println("Host: arduino.cc");

   client.println("Connection: close");

   client.println();

 }

}


void loop()

{

 // if there are incoming bytes available

 // from the server, read them and print them

 while (client.available()) {

   char c = client.read();

   Serial.write(c);

 }


 // if the server's disconnected, stop the client

 if (!client.connected()) {

   Serial.println();

   Serial.println("Disconnecting from server...");

   client.stop();


   // do nothing forevermore

   while (true);

 }

}



void printWifiStatus()

{

 // print the SSID of the network you're attached to

 Serial.print("SSID: ");

 Serial.println(WiFi.SSID());


 // print your WiFi shield's IP address

 IPAddress ip = WiFi.localIP();

 Serial.print("IP Address: ");

 Serial.println(ip);


 // print the received signal strength

 long rssi = WiFi.RSSI();

 Serial.print("Signal strength (RSSI):");

 Serial.print(rssi);

 Serial.println(" dBm");

}


접속하고자하는 서버의 주소는

char server[] = "arduino.cc";

에 arduino.cc 로 정의되어 있고


   client.println("GET /asciilogo.txt HTTP/1.1");

   client.println("Host: arduino.cc");

   client.println("Connection: close");

   client.println();


명령어를 이용해서 arduino.cc/asciilogo.txt 파일을 HTTP GET으로 읽어오게 되어있다.

읽어온 응답값을

 while (client.available()) {

   char c = client.read();

   Serial.write(c);

 }


코드를 이용해서 client.read를 통해서 한글자씩 읽어온후 화면에 출력하기 위해서 Seirial.write(c)를 이용하여 한글자씩 콘솔에 출력하였다.

실행 결과는 다음과 같다.



결론

테스트를 하면서 가장 어려웠던 점이 아두이노 우노에서 ESP01 로 소프트웨어 시리얼 통신을 하는 경우 HTTP나 WIFI SDK를 찾기가 어려웠다. 대부분 ESP8266을 MCU로 하는 보드 (예를 들어 nodeMCU, ESP12,Wemo D1/D1 mini)를 베이스로 하는 경우가 많았는데, 그도 그럴것이 개발이나 테스트 용도가 아니라 실제 디바이스로 만들어서 사용하려면 우노 + ESP01 조합은 크기도 크고 연결도 복잡해서 효용성이 없다. 네트워크 통신을 하는 시나리오등은 IOT와 같이 센서 데이타를 얻어서 서버에 전송하는 케이스이기 때문에,  하나의 소형 디바이스로 처리가 가능하다면 하나의 디바이스를 사용하는 것이 훨씬 효율적이다. (가격이나 배터리 용량면등).

다음 글에서는 실제로 센서에서 데이타를 읽어서 HTTP를 이용해서 서버에 전송하는 부분을 고민해보도록 하겠다.

참고자료



빠르게 훝어보는 node.js

#10 - Socket.IO (2/4)

조대협 (http://bcho.tistory.com)


Socket.IO APIs

Socket.IO 이밖에도 다양한 이벤트를 전달할 있는 API 제공하는데, 이에 대해서 살펴보자.

여기서 사용하는 socket이라는 객체는

io.sockets.on('connection',function(socket){

의해서 callback function 의해서 전달된 인자임을 미리 명시해둔다.


1. 이벤트 보내기 받기

먼저 소켓으로 또는부터 이벤트를 보내고 받는 방법부터 알아보자.앞에 예제에서도 봤지만 가장 간단한 방법은

Ÿ  * 이벤트 보내기 socket.emit('이벤트명',{메세지});

현재 연결되어 있는 클라이언트 소켓에 “이벤트명”으로 “{메시지}” 데이터로 이벤트를 보낸다.

Ÿ  *  이벤트 받기 socket.on('이벤트명',function(data){ });

현재 연결되어 있는 클라이언트 소켓으로부터 들어오는 이벤트명이벤트에 대해서 두번째 인자로 정의된 callback function 의해서 이벤트에 대한 처리를 한다. 이때 이벤트 메시지는 callback function 인자인 “data” 통해서 전달된다.

하나의 클라이언트가 아니라 다수의 다른 클라이언트나 또는 다른 클라이언트에 이벤트를 어떻게 보내는지 알아보자

Ÿ  * 나를 제외한 다른 클라이언트들에게 이벤트 보내기 socket.broadcast.emit('이벤트명',{메세지});

socket 대해서 broadcast 하면, 나를 제외한 다른 소켓 클라이언트들에게 이벤트를 보낼 있다.

Ÿ 나를 포함한 모든 클라이언트들에게 이벤트 보내기io.sockets.emit('이벤트명',function(data){ });

개별 클라이언트 소켓을 대표하는 객체가 socket이라면, 전체 연결된 socket들을 대표하는 객체는io.sockets이다. 여기서는 io.sockets.emit 사용했는데, 이는 전체 연결된 클라이언트 소켓에 대해서 이벤트를 보내도록 것이다.

소켓이 아닌 다른 특정 소켓에게 이벤트를 보내는 방법이 있는데,

Ÿ  * io.sockets(socket_id).emit('이벤트명',function(data){ });

사용하면 된다. 이때 socket_id socket.id 값으로 클라이언트 소켓은 id라는 property 가지고 있고, 이는 소켓을 구별해주는 식별자가 된다. 그나중에 예제에서 설명하겠지만, 채팅 귓속말과 같이 특정 소켓으로 메시지를 보내려면, 메시지를 전달하는 대상이 되는 소켓의 id값을 알아서 위의 io.socket(socket_id) 이벤트를 전달해야 한다.


2.  소켓에 데이터 바인딩

소켓에는 소켓에 연관된 데이터를 set 메서드를 이용해서 binding하고, get 이용해서 binding 데이터를 꺼낼 있다.

Ÿ   * socket.set(‘key’, ‘value’,function() {});

Ÿ   socket.get(‘key’, function(err,value) {});

Ÿ   socket.del(‘key’, function(err,value) {});

값으 저장할때는 set을 이용하여 socket key값을 키로 사용하여 value라는 데이터를 저장한다. 값을 읽어올 때는 get을 이용하여 socket에 저장된 key이름으로 저장된 값을 value를 통해서 리턴한다.그리고 해당값을 삭제하고자 할때는 del을 이용하여 socket key이름으로 저장된 값을 삭제한다.

socket도 객체이기 때문에 Object property를 사용해도 되는데, 예를 들어 socket.key = value 왜 굳이 set/get을 사용할까? socket.ioset/get 내부 구현을 뜯어보면 실제로는 Object property를 사용한다. socket.iostore Redis로 지정하게 되면, set/get은 값을 내부 Object property에 저장하지 않고, Redis에 저장하게 되서, 이 값들은 클러스터 노드 (다른 노드간)에서도 접근이 가능해진다.

3.  Room 처리 (그룹)

socket.io 소켓들을 그룹핑하는 채널과 같은 개념인 ‘room’이라는 개념을 지원한다. 채팅 프로그램의 대화방과 같은 개념이다. Room 사용하게 되면 broadcast 하더라도 같은 room안에 있는 클라이언트에게만 이벤트가 전송된다.

Ÿ   socket.join(‘roon name’)

Ÿ   socket.leave(‘roon name’)

소켓을 특정 room binding하는 방법은 join 이용하면 해당 소켓은 room binding 되고, leave 하면 room에서부터 나오게 된다.

특정 room 있는 socket에게 이벤트를 보내는 것은 앞에 설명한 똑같이 socket.emit 사용하면 되는데, broadcast 하거나 room안에 있는 전체 클라이언트 소켓에게 이벤트를 보낼때는 아래와 같이 room 명시해주면 된다

Ÿ   io.sockets.in(‘roon name’).emit(‘event’,message)

‘room name’ room안에 있는 모든 클라이언트들에게 이벤트 보내기

Ÿ   socket.broadcast.to(‘roon name’).emit(‘event’,message)

 ‘room name’ room 안에 있는 나를 제외한 다른 클라이언트들에게 이벤트 보내기

또한 현재 생성되어 있는 room 대한 정보를 읽어오는 방법이 몇가지가 있는데,

Ÿ  io.sockets.manager.rooms

현재 생성되어 있는 모든 room 목록을 리턴한다.

Ÿ  io.sockets.clients(‘roon name’)

 ‘room name’ room 안에 있는 모든 클라이언트 소켓 목록을 리턴한다.

몇가지 주요 메서드들을 성명하였지만, 여기서 설명한 것은 일부에 불과하다. 상세한 내용들은 아래 내용을 참고하기 바란다.

상세한 Configuration 처리 https://github.com/LearnBoost/Socket.IO/wiki/Configuring-Socket.IO

이벤트 종류(disconnect ) https://github.com/LearnBoost/socket.io/wiki/Exposed-events

자아 그러면 소개된 메서드들을 가지고, 앞에서 만든 채팅 프로그램의 기능을 더해보도록 하자