Study & Project ✏️/node.js 🐣

[socket.io] 프로젝트 준비2 - 아두이노 Socket.io

JM 2022. 2. 9. 21:47
반응형

앞의 프로젝트 준비 글에서는

라즈베리파이에서 돌릴 node.js socket.io 서버를 구현했다.

이번 글에서는 그 서버에 접속할 아두이노 Client 부분을 완성해보겠다.


ㄷㄷ 목표

2. 아두이노(Client)에서 socket에서 접근

 

아두이노 보드는 ESP8266이 기본 탑재되어 있는

Wemos D1R1을 사용하고 있다.

5000원 미만에 pin도 넉넉하고

여러 프로젝트에서 사용하기에 편하다.

 

webSocket을 기반으로 Socket.io가 만들어졌고

TCP를 기반으로 webSocket이 만들어졌다는데 사실 잘 모른다

이번 프로젝트에서 사용할 라이브러리는 바로 이 녀석!

Markus Sattler 형님의 라이브러리인데 웹소켓 관련 별점이 제일 높다.

찾기 힘들다면 여기로 해서 zip 라이브러리를 추가하면 된다.

https://github.com/Links2004/arduinoWebSockets

 

GitHub - Links2004/arduinoWebSockets: arduinoWebSockets

arduinoWebSockets. Contribute to Links2004/arduinoWebSockets development by creating an account on GitHub.

github.com

그럼 좌측 상단의 파일 -> 예제에서

해당 예제를 불러오면 된다.

주석을 제외하고 나머지를 다 연습용 프로젝트로 붙여 넣기 하면!!

#include <Arduino.h>

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

#include <ArduinoJson.h>

#include <WebSocketsClient.h>
#include <SocketIOclient.h>

#include <Hash.h>

ESP8266WiFiMulti WiFiMulti;
SocketIOclient socketIO;

#define USE_SERIAL Serial1

void socketIOEvent(socketIOmessageType_t type, uint8_t * payload, size_t length) {
    switch(type) {
        case sIOtype_DISCONNECT:
            USE_SERIAL.printf("[IOc] Disconnected!\n");
            break;
        case sIOtype_CONNECT:
            USE_SERIAL.printf("[IOc] Connected to url: %s\n", payload);

            // join default namespace (no auto join in Socket.IO V3)
            socketIO.send(sIOtype_CONNECT, "/");
            break;
        case sIOtype_EVENT:
            USE_SERIAL.printf("[IOc] get event: %s\n", payload);
            break;
        case sIOtype_ACK:
            USE_SERIAL.printf("[IOc] get ack: %u\n", length);
            hexdump(payload, length);
            break;
        case sIOtype_ERROR:
            USE_SERIAL.printf("[IOc] get error: %u\n", length);
            hexdump(payload, length);
            break;
        case sIOtype_BINARY_EVENT:
            USE_SERIAL.printf("[IOc] get binary: %u\n", length);
            hexdump(payload, length);
            break;
        case sIOtype_BINARY_ACK:
            USE_SERIAL.printf("[IOc] get binary ack: %u\n", length);
            hexdump(payload, length);
            break;
    }
}

void setup() {
    // USE_SERIAL.begin(921600);
    USE_SERIAL.begin(115200);

    //Serial.setDebugOutput(true);
    USE_SERIAL.setDebugOutput(true);

    USE_SERIAL.println();
    USE_SERIAL.println();
    USE_SERIAL.println();

      for(uint8_t t = 4; t > 0; t--) {
          USE_SERIAL.printf("[SETUP] BOOT WAIT %d...\n", t);
          USE_SERIAL.flush();
          delay(1000);
      }

    // disable AP
    if(WiFi.getMode() & WIFI_AP) {
        WiFi.softAPdisconnect(true);
    }

    WiFiMulti.addAP("SSID", "passpasspass");

    //WiFi.disconnect();
    while(WiFiMulti.run() != WL_CONNECTED) {
        delay(100);
    }

    String ip = WiFi.localIP().toString();
    USE_SERIAL.printf("[SETUP] WiFi Connected %s\n", ip.c_str());

    // server address, port and URL
    socketIO.begin("10.11.100.100", 8880, "/socket.io/?EIO=4");

    // event handler
    socketIO.onEvent(socketIOEvent);
}

unsigned long messageTimestamp = 0;
void loop() {
    socketIO.loop();

    uint64_t now = millis();

    if(now - messageTimestamp > 2000) {
        messageTimestamp = now;

        // creat JSON message for Socket.IO (event)
        DynamicJsonDocument doc(1024);
        JsonArray array = doc.to<JsonArray>();

        // add evnet name
        // Hint: socket.on('event_name', ....
        array.add("event_name");

        // add payload (parameters) for the event
        JsonObject param1 = array.createNestedObject();
        param1["now"] = (uint32_t) now;

        // JSON to String (serializion)
        String output;
        serializeJson(doc, output);

        // Send event
        socketIO.sendEVENT(output);

        // Print JSON for debugging
        USE_SERIAL.println(output);
    }
}

해당 파일이 된다.

그냥 이걸 복붙해도 될 것 같다.

 

이제 해당 내용들을 조금씩 수정하고 필요 없는 부분들을 바꿔보겠다.

#include <Arduino.h>

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

#include <ArduinoJson.h>

#include <WebSocketsClient.h>
#include <SocketIOclient.h>

#include <Hash.h>

ESP8266WiFiMulti WiFiMulti;
SocketIOclient socketIO;

// 수정했음
#define USE_SERIAL Serial

void socketIOEvent(socketIOmessageType_t type, uint8_t * payload, size_t length) {
    switch(type) {
        case sIOtype_DISCONNECT:
            USE_SERIAL.printf("[IOc] Disconnected!\n");
            break;
        case sIOtype_CONNECT:
            USE_SERIAL.printf("[IOc] Connected to url: %s\n", payload);

            // join default namespace (no auto join in Socket.IO V3)
            socketIO.send(sIOtype_CONNECT, "/");
            break;
        case sIOtype_EVENT:
            USE_SERIAL.printf("[IOc] get event: %s\n", payload);
            break;
        case sIOtype_ACK:
            USE_SERIAL.printf("[IOc] get ack: %u\n", length);
            hexdump(payload, length);
            break;
        case sIOtype_ERROR:
            USE_SERIAL.printf("[IOc] get error: %u\n", length);
            hexdump(payload, length);
            break;
        case sIOtype_BINARY_EVENT:
            USE_SERIAL.printf("[IOc] get binary: %u\n", length);
            hexdump(payload, length);
            break;
        case sIOtype_BINARY_ACK:
            USE_SERIAL.printf("[IOc] get binary ack: %u\n", length);
            hexdump(payload, length);
            break;
    }
}

void setup() {
    // 시리얼 모니터 사용 시 115200 사용
    USE_SERIAL.begin(115200);

    //Serial.setDebugOutput(true);
    USE_SERIAL.setDebugOutput(true);

    USE_SERIAL.println();
    USE_SERIAL.println();
    USE_SERIAL.println();

      for(uint8_t t = 4; t > 0; t--) {
          USE_SERIAL.printf("[SETUP] BOOT WAIT %d...\n", t);
          USE_SERIAL.flush();
          delay(1000);
      }

    // disable AP
    if(WiFi.getMode() & WIFI_AP) {
        WiFi.softAPdisconnect(true);
    }

    WiFiMulti.addAP("공유기 이름", "공유기 비밀번호");

    //WiFi.disconnect();
    while(WiFiMulti.run() != WL_CONNECTED) {
        delay(100);
    }

    String ip = WiFi.localIP().toString();
    USE_SERIAL.printf("[SETUP] WiFi Connected %s\n", ip.c_str());

    // server address, port and URL
    // 서버의 ip,port,그대로
    socketIO.begin("172.30.1.44", 3000, "/socket.io/?EIO=4");

    // event handler
    socketIO.onEvent(socketIOEvent);
}

unsigned long messageTimestamp = 0;
void loop() {
    socketIO.loop();
    // 아두이노 시리얼에서 입력 후 테스트 할 부분
}

이렇게 하고 보드에 업로드를 하게 되면

node.js에서 connect를 감지해야 하는데

io.on('connection', (socket) => {
    console.log('connect!');
    socket.on('chat message', (msg) => {
        io.emit('chat message', msg);
        console.log('message: ' + msg);
      });
    socket.on('disconnect', () => {
        console.log('user disconnected');
      });
  });

해당 내용으로 Client가 서버에 접속했는지 감지해주자.

내 보드가 잘 접속한게 맞는지 궁금하다면

전원을 껐다가 다시 켜보면 잘 connect가 뜨는 걸 볼 수 있다.

그리고 전 시간에 테스트했던

브라우저에서 메세지를 보내면

Sever가 다시 모든 Client에게 메세지를 보내주는 것으로 테스트해보자

브라우저에서 message를 보내기
server에서 받은 message
아두이노에서 받은 message

이제 아두이노가 webSocket에 잘 연결된 것을 알 수 있다!!!

 

해당 메세지를 가지고

아두이노에서 컨트롤을 하거나

컨트롤을 할 조건에 대해 제시하거나

명령을 내릴 수 있다.

 

웹(모바일)에서 접속 후

-> 서버에 명령 전달 or 서버에서 데이터 받아오기

-> 아두이노 동작

-> 서버에서 받고

-> 웹(모바일)에 표시

 

내가 말하고도 복잡하다 ㅠㅠ

결국 모바일에서 할 일은 로그인 기능 + 해당 웹페이지로 router 만 하게 되면

좀 더 인터렉티브 한 아두이노 컨트롤 페이지를 만들어 줄 수 있을 것 같다.

 

이제 3번 예제를 직접 눈으로 볼 수 있게

웹페이지를 허접하게나마 꾸며놓고

아두이노에서 LED 제어 정도는 해 봐야겠다.


사실 이 정도 프로젝트는 노드레드에서도 쉽게 사용이 가능하다.

 

하지만 노드레드는 리소스가 많이 사용되고

내가 원하는 웹페이지를 만들기가 까다로워서

이 방식으로 접근해보려고 한다.

 

아두이노의 Status에 따른 message가 바뀐다던지

모바일 앱에서 webview를 사용해서

인터렉티브 웹앱을 만든다던지

아주 흥미로운 프로젝트가 될 것 같다.

 

특히 마지막 DB는 어떻게 구현해야 할지.... 고민 좀 해봐야겠다.