강좌 목차 보기

  • 현재 강좌 ==> [사물 인터넷 네트워크와 서비스 구축 강좌] #4-4 센서장치에 웹 소켓 활용하기

이전 실습 예제에서는 HTTP client 가 주기적으로 서버에 HTTP 요청을 보내서 데이터를 가져왔습니다. 물론 이 방식이 일반적인 웹 사용법이긴 하지만 특정 서비스에는 적합하지 않은 측면이 있습니다. 예를들어 특정 웹 페이지에 채팅 기능을 넣는다고 생각해보세요. 실시간으로 채팅 메시지가 보여지도록 하려면 브라우는 매우 빠르게 그리고 끊임없이 HTTP request 를 보내야 할 것입니다. 채팅 같은 기능에는 항상 서버-클라이언트가 백그라운드로 연결된 상태를 유지하면서 실시간-양방향으로 자유롭게 데이터를 주고 받는 방식이 필요합니다.

Web Socket 이 이런 자유로운 통신 방식을 제공해줍니다. HTML5에 포함된 Web Socket은 웹 서버와 클라이언트(브라우저의 JavaScript 등) 간 소켓 연결을 유지하게 해 줌으로써, 지속적인 양방향 데이터 전송을 할 수 있습니다. 다만, 이 기능을 사용하기 위해서는 비교적 최신의 브라우저를 사용해야 하는 제약이 있습니다. 그리고 서버에는 WebSocket 통신을 위한 준비가 되어 있어야 합니다. 보통 웹 소켓은 브라우저에서 많이 사용하지만 웹 소켓이 특정 플랫폼에 종속된 것은 아닙니다.

이번 파트에서는 ESP32 모듈을 이용해서 웹 소켓 통신을 구현하는 방법을 다룹니다.

웹 소켓 서버 구현

ESP32 모듈을 웹 소켓 서버로 구현해 보도록 하겠습니다. 그리고 라즈베리파에에 파이썬으로 웹 소켓 클라이언트를 구현할 것입니다. 웹 소켓이 연결되면 라즈베리파이의 웹 소켓 클라이언트에서 메시지를 보냅니다. 웹 소켓 서버는 받은 메시지를 다시 클라이언트에게 되돌려 줄 것입니다.

ESP32 모듈에 웹 소켓 기능을 넣기 위해 라이브러리 설치가 필요합니다. 아래 링크에서 라이브러리 파일을 받으세요.

다운받은 소스를 아두이노 라이브러리 폴더에 복사합니다. 아두이노 라이브러리 폴더의 경로는 다음과 같습니다. 라이브러리 파일들이 담긴 폴더를 붙여넣기 할 때 라이브러리 폴더의 이름을 WebSocketServer로 만드세요.

  • C:\사용자\사용자명\문서\Arduino\libraries

이 라이브러리를 그냥 사용할 경우  기존의 MD5 관련 함수와 충돌이 발생합니다. WebSocketServer 라이브러리 폴더 내 MD5.c 와 MD5.h 파일에서 다음 문자열을 찾아서 모두 치환해 주면됩니다.

  • MD5Init →MD5Init_XXX
  • MD5Update →MD5Update_XXX
  • MD5Final →MD5Final_XXX

_XXX 는 원하는대로 지정하면 됩니다.

이제 ESP32 용 웹 소켓 서버 예제를 올려보겠습니다. 아래 링크에서 스케치를 받을 수 있습니다.

ESP32 서버용 코드를 컴파일 후 업로드하면 ESP32 가 서버로 동작하기 시작합니다. ESP32 모듈의 IP 주소를 꼭 기억해두세요!!

ESP32 모듈에 웹 소켓 서버 구현은 끝났으니 라즈베리파이에 웹 소켓 클라이언트를 구현하겠습니다. 파이썬과 websocket-client 모듈을 이용합니다.

라즈베리파이에 접속 후 PIP 를 이용해 websocket-client 를 설치합니다.

  • sudo pip3 install websocket-client

아래 링크에 웹 소켓 클라이언트를 구현한 파이썬 코드가 있습니다.

소스코드에서 ESP32 모듈의 IP 주소를 넣어주세요.

import websocket
import time
 
ws = websocket.WebSocket()
ws.connect("ws://192.168.219.117/")
 
i = 0
nrOfMessages = 200
 
while i < nrOfMessages:
    ws.send("message nr: " + str(i))
    result = ws.recv()
    print(result)
    i=i+1
    time.sleep(1)
 
ws.close()

파이썬으로 작선된 웹 소켓 클라이언트 코드는 단순합니다. 특정 IP 로 웹 소켓 접속을 한 후, 1초 간격으로 메시지를 200번 보냅니다. 메시지 전송할 때는 ws.send() 를 이용합니다. 메시지 수신은 ws.recv() 를 사용하면 됩니다.

ESP32 웹소켓 서버가 구동되고 있는 상태에서 파이썬 코드를 실행하면 메세지가 오고가는 것을 확인할 수 있습니다.

ESP32 모듈에 업로드 한 소스코드를 살펴보겠습니다. 먼저 setup() 함수를 살펴보면…

// 서버 생성시 연결될 포트 지정
WiFiServer server(80);
WebSocketServer webSocketServer;

void setup() {
    Serial.begin(115200);
    delay(10);
    
    Serial.println();
    Serial.println();
    Serial.print("Connecting to ");
    Serial.println(ssid);

    WiFi.begin(ssid, password);

    // 와이파이망에 연결
    while (WiFi.status() != WL_CONNECTED) {
        delay(500);
        Serial.print(".");
    }

    Serial.println("");
    Serial.println("WiFi connected");
    Serial.println("IP address: ");
    Serial.println(WiFi.localIP());  
    
    startServer();
}

// 서버 시작
void startServer() {
    Serial.println("Server start");
    server.begin();
}

공유기에 연결하고, 웹 서버를 시작했습니다.

웹 서버로 HTTP request 요청이 오면 그 안에 웹 소켓 연결을 요청하는 정보가 있습니다. 이때 웹 소켓 연결을 위한 handshake 과정을 거치면 연결됩니다. loop() 안에서 순차적으로 이 작업을 처리해줍니다.

void loop() {
    // 클라이언트 연결 대기
    WiFiClient client = server.available();

    // 클라이언트가 연결되면 파일 전송 시작
    if(client.connected() && webSocketServer.handshake(client)) {
        String data;
        while(client.connected()) {
            data = webSocketServer.getData();
            if(data.length() > 0) {
                Serial.println("received: "+data);
                webSocketServer.sendData("send back - "+data); 
            }
            delay(10);
        }
        Serial.println("The client is disconnected");
        delay(100);
    }
    delay(100);
}

이 코드는 간단하게 웹 소켓 연결을 구현해주고 있지만 제약사항이 하나 있습니다. 웹 소켓 연결이 되면, 연결이 종료될 때 까지 다른 웹 소켓 연결요청을 처리할 수가 없습니다. 만약 여러개의 웹 소켓 연결을 동시에 처리하고 싶다면 몇 가지 수정을 해줘야 합니다.

기존에 사용했던 WebSocketServer 라이브러리를 삭제하고 대신 다중 웹 소켓 접속을 지원하는 라이브러리를 설치하세요.

그리고 아래 코드를 사용하면 다중 웹 소켓 서버를 만들 수 있습니다. 라즈베리파이에 여러개의 터미널로 접속 한 뒤, 파이썬 웹 소켓 클라이언트 코드를 여러개 실행시켜 확인해보세요.

웹 소켓 클라이언트 구현

이번에는 ESP32 모듈을 웹 소켓 클라이언트로 사용해 보겠습니다. 외부에 우리가 다룰 수 있는 웹 서버가 존재한다면 이렇게 구현하는 것이 더 효율적이겠죠.

먼저 라즈베리파이에 웹 소켓 서버를 구동시키겠습니다. 라즈베리파이에 접속해서 websockets 파이썬 모듈을 설치해야 합니다.

  • sudo pip3 install websockets

설치가 완료되면 아래 코드를 구동시켜보세요.

import socket
import fcntl
import struct
import asyncio
import websockets

def get_ipaddress(network):
    s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    return socket.inet_ntoa(fcntl.ioctl(
        s.fileno(),
        0x8915,  # SIOCGIFADDR
        struct.pack('256s', network[:15].encode('utf-8'))
    )[20:24])


async def echo(websocket, path):
    async for message in websocket:
        print(message)
        await websocket.send(message)

print("Server start : "+get_ipaddress('eth0'))

port = 80
asyncio.get_event_loop().run_until_complete(
    websockets.serve(echo, get_ipaddress('eth0'), port))
asyncio.get_event_loop().run_forever()

websocket.server() 함수 호출해서 웹 소켓 서버를 구동시킵니다. 이때 사용할 웹 서버 포트번호와 IP address, 메시지를 받았을 때 실행할 콜백 함수를 함께 전달합니다.

파이썬 코드를 실행시키면 라즈베리파이는 웹 소켓 접속이 있을때까지 대기합니다.

  • sudo python webSocketServer.py

이때 표시되는 서버 주소를 기억해주세요.

이제 ESP32 모듈에 웹 소켓 클라이언트를 구현하면 됩니다. 아래 링크의 스케치를 사용하세요.

소스코드 상단의 설정 값을 자신의 환경에 맞게 바꿔줘야 합니다.

const char* ssid     = "your_ssid";
const char* password = "your_pass";

char path[] = "/";
char host[] = "192.168.1.9";    // 웹소켓 서버 주소

공유기 SSID와 비번을 지정해주고, 웹 소켓 서버 주소를 넣어주세요.

이제 ESP32 에 업로드하고 시리얼 모니터를 실행해서 통신 상태를 확인하면 됩니다.

두 장치가 메시지 카운트를 주고 받으면 성공입니다!!

ESP32 모듈에 올린 소스코드를 확인해 보겠습니다. setup() 함수에서 중요한 부분만 추려보면 아래와 같습니다.

void setup()
{
    ......
    // 서버에 연결
    if (client.connect(host, 80)) {
        Serial.println("Connected");
    } else {
        Serial.println("Connection failed.");
    }
    delay(1000); 

    webSocketClient.path = path;
    webSocketClient.host = host;
 
    if (webSocketClient.handshake(client)) {
        Serial.println("Handshake successful");
    } else {
        Serial.println("Handshake failed.");
    }
}

일단 공유기에 연결이 되면 client.connect() 를 통해 라즈베리파이 서버에 연결합니다. 그리고 웹 소켓 연결을 위한 handshake 를 실행합니다. 이 과정이 성공하면 ESP32 와 라즈베리파이는 서로 실시간-양방향 통신이 가능해집니다.

void loop() {
  delay(1000);
  String data;

  if (client.connected()) {
    // 데이터 전송
    webSocketClient.sendData("msg count : "+String(count++));
    // 데이터 수신
    webSocketClient.getData(data);
    if (data.length() > 0) {
      Serial.println(data);
    }
  } else {
    Serial.println("Client disconnected.");
  }
 
  delay(3000);
}

loop() 함수에는 데이터 전송과 수신 코드만 있습니다.

활용

ESP32 모듈을 이용해서 센서장치를 만들 때, 웹 소켓을 활용하면 서버와 실시간으로 데이터를 주고 받을 수 있습니다. 그럼 서버에서는 실시간 센서 모니터링이 가능하겠죠.

만약 굉장히 많은 수의 센서가 데이터를 전송해야 하는 상황이라면 이런 웹 소켓 방식은 서버측에 버거울 수도 있습니다. 하지만 비교적 적은 수의 센서가 실시간 모니터링이 필요한 데이터를 전송하는 환경이라면 웹 소켓은 한번 고려해 볼만 한 옵션입니다.

참고자료

주의!!! [사물 인터넷 네트워크와 서비스 구축 강좌] 시리즈 관련 문서들은 무단으로 내용의 일부 또는 전체를 게시하여서는 안됩니다. 계속 내용이 업데이트 되는 문서이며, 문서에 인용된 자료의 경우 원작자의 라이센스 문제가 있을 수 있습니다.

강좌 목차 보기

  • 현재 강좌 ==> [사물 인터넷 네트워크와 서비스 구축 강좌] #4-4 센서장치에 웹 소켓 활용하기