[책상 보조등 제어하기] 1. 프로토콜

이 글은 4부작입니다.

이전 글 [책상 보조등 제어하기] 0. 구성 에서 이어지는 글입니다.

틀린 서술이 많을 것 같습니다.
혹시나 발견하신다면 댓글 남겨주시거나 potados99@gmail.com으로 피드백 보내주신 주신다면 정말 감사하겠습니다 :)

프로토콜

제어는 휴대전화 또는 PC로 한다. 애플의 HomeKit도 지원하고 싶다. 휴대전화를 못 쓸 때에는 해당 장비와 통신할 수 있는 다른 장비로 접근하게 하고 싶다. 같은 네트워크에 연결되어 있으면 누구든 제어할 수 있게 하고 싶다. 물론 네트워크 보안에 더 신경쓴다.

HomeKit은 Raspberry Pi와 HAP-NodeJS로 지원한다 치고, 다른 장비에서 접근하기 위해서는 적절한 범용성 있는 프로토콜을 선택해야 한다.

선택

저전력 임베디드 IoT 장비를 위한 프로토콜은 간단하고 빠르고 전력 소비가 적어야 한다. 먼저 상용화된 프로토콜 몇 개를 알아보자.

  • HTTP
    HTTP는 주로 웹을 구성하는 데에 사용되지만 서버와 클라이언트 통신 측면에서 보면 IoT용 프로토콜로도 훌륭하다. http://host:port/power으로 GET을 날리면 응답으로 ON이 날아오고 OFF를 인자로 POSTPUT을 날리면 불이 꺼지고 OK 응답이 오는 식이다. 조금 더 복잡한 페이로드를 실을 수도 있다. 아니 그냥 웹페이지를 보내도 된다!

  • MQTT (Message Queue Telemetry Transport Protocol)
    대표적인 게시/구독형 프로토콜이다. 아주아주 널리 쓰이고 있다. MQTT에서는 브로커의 존재가 필요한데, 메시지를 전달해주기 위함이다. 클라이언트와 서버(센서같은 노드)와 직접 통신하지 않고 브로커를 거친다. TCP 위에서 작동하며 큐잉을 지원한다.

  • AMQP (Advanced Message Queuing Protocol)
    MQTT와 비슷하다(?). 브로커가 존재하고 큐잉도 지원한다.

  • DDS (Data Distribution Service)
    DDS 또한 게시/구독형 모델을 사용하는 프로토콜이다. 확장이 용이하고, 실시간이며 신뢰도가 높고 성능이 좋아 군용으로도 사용된다고 한다.

위에 나열된 것들 모두 참 좋은 기술이지만, 아주 간단하고 작은 가정용 기기에 적용하기에는 조금 무리인 것 같다. 가벼운 프로토콜도 있다.

  • CoAP (Constrained Application Protocol)
    HTTP와 비슷하다. UDP를 사용하며, 가볍고 빠르다. 1:1 통신을 기본으로 한다.

CoAP를 쓰기로 했다. 다른 이유는 없고, 그냥 마음에 들어서이다. 레퍼런스도 매우 적고 잘 알려져있지도 않지만 그냥 써보기로 했다.

구현

C로 구현된 libCoAP가 있다. 그런데 Arduino 환경에서 바로 쓰지는 못한다. Unix/Linux OS 기능에 많이 의존하고 있기도 하고, Arduino 스타일에 맞지도 않는다(?).

불행히도 Arduino만을 위해 만들어진, 모든 CoAP 기능을 지원하는 구현체는 없다. 더 찾아보니 Hirotaka Niisato 라는 사람이 작성한 CoAP-simple-library가 있었는데 별로 마음에 들지 않아 새로 작성하기로 했다.

CoAP 표준은 이렇게 잘 정리되어 공개되어 있지만 저걸 다 읽지는 못하겠고, 위에서 찾은 라이브러리를 바탕으로 작성하기로 했다.

CoAP는 메소드로 GET, POST, PUT, DELETE를 지원하는데, 이중 GETPUT만 구현하기로 했다. 부가적인 기능으로는 observe나 proxy 등도 있는데 이들은 필요하지 않아서 포기했다.

PUT은 이미 존재하는 URI에 대해 데이터를 갱신하며, POST는 주어진 URI와 관련된 새로운 데이터를 추가한다. 출처

아무튼 구현된 물건은 여기에 있다. 사실 만들다 만 것이라 간신히 돌아가는 수준이다. 완성도는 계획한 것의 80% 정도? 직접 만들었다는 데에 의미를 두려고 한다… :)

적용

사용법은 아래와 같다.

#include <ESP8266WiFi.h>
#include <WiFiUdp.h>
#include <CoapServer.h>

WiFiUDP udp;
CoapServer server(udp);

// Callback defines actions when data is recieved.
callback onPower = [](int coap_method, const char *payload, char *reply) {
    if (reply == NULL) return;

    switch (coap_method) {

        case COAP_GET: {
            sprintf(reply, apricot.device() -> getPower() ? "ON" : "OFF");
            break;
        }

        case COAP_PUT: {
            if (payload == NULL) return;

            String msg(payload);
            msg.toUpperCase();

            if (msg == "ON") {
                // Turn on.
                sprintf(reply, "OK");
            }
            else if (msg == "OFF") {
                // Turn off.
                sprintf(reply, "OK");
            }
            else {
                // Error.
                sprintf(reply, "FAIL");
            }

            break;
        }

    } /* end of switch */
};

void setup() {
  WiFi.begin("SSID", "PASSWORD");

  // Add resource with callback and resource name.
  server.addResource(onPower, "power");
  server.start();
}

void loop() {
  server.loop();
}

loop가 반복되다 보면 언젠가 콜백 onPower가 실행되는데, 이때 이 콜백은 메소드 종류와 페이로드, 그리고 답장을 쓸 빈 종이를 받는다.

메모리는 콜백 내에서 신경쓰지 않아도 된다. 일종의 컨테이너처럼 움직인다. 객체지향에서는 IoC라고 하는, 제어의 역전을 흉내냈다.

다음 글에서 이어집니다.

Reference

댓글