AWS | AWS IoT Core 와 ESP8266 간단 연동해 보기

AWS 를 이용한 IoT 를 제안하기 위해, AWS IoT Core 를 테스트 해 봤습니다. 테스트 진행하면서, 사용 편리성, 필요한 제반 지식, 기본적인 사항이 어떤 것들이 있는지를 알아 봤습니다.

예전에 작성한, IoT 를 쉽게 사용할 수 있는 플랫폼인 ThingSpeak 활용사례와 비교하면서 사용해 보면 좋겠습니다.

* Software | ThingSpeak 등록하여 IoT 데이터 펼처보기




1. IoT on Cloud

우선 IoT 는 지금까지 어떻게 활용되어 왔는지를 알아야 합니다. 기존의 공장 등에서도 각 장비의 데이터를 취합하여 제어하거나 현황을 파악하는 것에 활용되어 왔습니다.

다만, 지금의 클라우드 기반의 인프라가 아니었던 기존 공장들은 Silo 형태로 운영되어 왔습니다. 보통 Cloud 와 반대되는 개념으로 On-Premise 형태라고 부릅니다.

아래 그림처럼, 각 장비의 데이터를 내부에서 받아서, HMI / SCADA 를 통해 정보를 취합 관리했습니다. HMI (Human Interface Machine... 와 언제쩍) 과 SCADA (Supervisory Control and Data Acquisition) 이라는 어플리케이션 들을 사용해서 정보취합 / 관리 / 컨트롤 합니다.

주로 공장 등의 제조업에서 사용하다 보니, 이런 데이터를 최종적으로 활용하는 MES 등도 빠질 수 없습니다. 이렇게 하는 것은 좋지만, 관련된 H/W 를 직접 구매 / 관리 해야 하고, 관련된 application 등도 직접 수급 / 설치 / 설정 / 유지보수 등을 해야 하는 버거움이 있습니다.

이러다 보니, 본사에 있는 ERP 에서 통합하여 data 를 확인하고 싶을 때, Silo 화 되어 있는 환경이다 보니, 실시간 데이터 확인은 고사하고, bulk data 를 가지고 온다 하더래도 쉽지 않습니다.


Cloud native 는, 애초에 모든 data 가 real-time 으로 Cloud 로 직접 쏠 수 있는 방법 입니다. 이게 우리가 지향하는 모양 입니다. 다만, 이렇게 하기 위해서는 기존 장비를 바꾸거나 최신으로 업그레이드를 해야지만 통신이 가능할 것 입니다.

기존 공장을 Cloud native 로 만들고 싶으면, 각 component 에 Cloud 와 연결할 수 있도록 module 을 새로 심거나, upgrade 를 해야 합니다. 또한, 이런 통신이 가능하게끔 On-Premise 의 network 환경도 모두 바꿔야 합니다. 상상을 넘어서는 비용과 시간이 걸립니다. 아예 처음부터 Cloud native 한 공장을 새로 짖는것이 시간과 비용이 절약될 수 있습니다.

그래서, 한꺼번에 여기까지 간다는건 말처럼 그리 쉬운게 아닙니다.


그 중간 단계인 Hybrid 그림 입니다. 어느정도 silo 에서 벗어난 구도를 보여주고 있습니다. HMI / SCADA 는 그대로 유지하지만, 모아진 데이터를 VPN 이나 전용망을 이용하여 Cloud 로 옮기게 됩니다. 실시간 까지는 아니어도, 준 실시간 대응이 가능하게 됩니다.

이를 가능하게 하려면, On-Premise 와 Cloud 간을 연결해 주는 부분이 필요합니다. 주로 network interface 가 되겠지만, 그냥 data 를 보내는 것 뿐만 아니라 data 를 정제하여 보낼 경우에는 효율이 좋게 됩니다.

좀더 고도화를 할 수 있으면, HMI / SCADA 뿐만 아니라, 말단의 PLC 에서 바로 data 를 보낼 수 있게 하는 것 입니다. 이 부분이 지금 우리에게 처한 상황입니다. 기존 On-Premise 의 silo 에서 network interface 가 생겼으니, Cloud 로 연결되는 통로 등, 환경은 구비 되었고, 나머지는 구식의 data 전달을 어떻게 하면 신형 Cloud 을 바로 꽂을 수 있는지 입니다.


예전 공장 등에서 센서, PLC, HMI, SCADA 에서는 그 시절 사용되었던, OPC-UA/DA, Modbus 등이 있습니다. 문제는 현대화된 Cloud IoT 시스템과 연결하기 위해서는, 과거 protocol 을 이용해서 접근하기는 쉽지 않습니다. 이를 위해서는 양단의 통신장비도 이를 지원해야 하며, Internet 을 이용한 통신에 적합하지 않습니다. 그래서 나온 protocol 이 MQTT 입니다. MQTT 는 TCP/IP 기반의 protocol 이며, 인터넷 망을 통한 통신 시설을 그대로 사용할 수 있습니다.




2. Arduino with AWS IoT Core

Protocol 의 기본을 이야기 했으니, 한번 AWS IoT 를 사용해 봅니다. 보통 sensor 들은 network 기능까지 넣으려면 비용이 많이 드니, sensor 와 연결하여 데이터를 보내주는 매개체가 필요합니다. Raspberry Pi 도 좋고, Arduino 도 좋습니다. 많은 데이터 정제 작업이 필요 없으면, arduino 도 충분합니다.

아래 사진은 조도 센서를 Arduino 와 연결한 예시 사진입니다. 실제로는 Wifi 연결이 가능한 ESP32 나, Arduion + ESP8266 등을 연결하여 통신을 처리하면 되겠습니다.


AWS 에서는 Protocol 통합을 위해 onsite 에 AWS IoT Greengrass 라는 VM 을 설치하여, legacy 공장에서 사용되는 OPC-UA/OPC-DA 등의 프로토콜을 MQTT 로 변환해 주는 역할을 하게 됩니다.


Raspberry Pi 나 ESP32, 혹은 Arduino + ESP8266 등은, 최신 MQTT 프로토콜을 사용할 수 있으니, 프로토콜 변경 용 proxy 를 거칠 필요 없이 직접 통신하게 하면 됩니다.


이번에 구성해서 확인해 본 architecture 는 위와 같습니다.



3. IoT Core - Basic Configuration

MQTT 로 들어오는 데이터에 대해, AWS 내에서 캐치하고 활용할 수 있도록 하기 위해, AWS IoT Core 설정을 진행 합니다. 일단 region 을 설정합니다.


그리고, 사용할 AWS IoT Core 메뉴로 들어 갑니다.


AWS IoT 에 대한 여러 메뉴를 확인할 수 있습니다.


외부 device 와 연동하기 위해, Things 를 설정 합니다. 외부 device 에 대한 AWS 내의 피사체라고 생각하면 되겠습니다.


한방에 여러개의 Things 를 만들 수도 있고, 간단하게 하나씩 만들 수도 있습니다. 이번에는 하나씩 하는 것이라, "Create single thing" 을 선택 하였습니다.


Things 정의를 위해 필요한 옵션들을 설정 합니다. 우선 이름을 정하구요.




4. IoT Core - Certificates

IoT 운영할 때, 중요한 것 중에 하나가, 인증서 - Certificate 을 각 device 에 심는 것 입니다. 인증/등록된 device 와 통신하고, 그렇지 않은 장비가 보내는 데이터는 받아들이지 않아야 하므로, 그 기준을 각 device 에 설치하는 이 Certificate 으로 구분되게 됩니다. MQTT = HTTPS 통신시 443 port 를 통한 SSL payload 를 가지게끔 해야 합니다.


따로 인증서를 가지고 있지 않으면, AWS IoT 에서 발급하는 인증서를 발급받아, IoT device 에서 통신할 때, 이 인증서를 사용하게끔 하면 됩니다.



5. IoT Core - Policies

인증서 외에, Policies 를 설정하여, 인증서와 pair 가 되는 action 에 대해 정의해 줍니다.


아래는 어떤 action 이든 허용해 주는 방식으로 설정 하였습니다. 자잘한 조정이 필요한 경우, 여기에서 조정하면 됩니다.


방금 설정한 Policy 를 Things 에 attach 합니다.


이제 모든 설정이 완료 되었습니다.




6. IoT Core - Download Certificates

Things 설정이 끝나면, 해당 Thing = Device 에 설치할 인증서를 다운로드 받을 수 있게 됩니다.


Device certificate + Private key + RSA 2048 bit key: Amazon Root CA 1 을 다운로드 받아 하나의 파일로 만들기를 합니다.


위의 세 개의 인증서를 아래처럼 하나의 인증서로 만듭니다.


저는 Device > Private > CA 순으로 분이기를 했습니다.




7. IoT Core - Attach Policy and Certificates

Things, Certificate, Policy 가 준비 되었으니, 이제 서로 연결을 해 줍니다.


Device 에 Certificate 을 attach 합니다.


Device + Certificate 은 완료 했으니, Certificate + Policy 를 진행 합니다.


이제 준비가 되었으니, 완료된 Things 의 Endpoint 를 확인해 봅니다. 이 Endpoint 를 통해서 데이터가 들어오게 됩니다.


Interact > View Settings 하면, Endpoint 정보를 확인할 수 있습니다.




8. Source Code

ESP32 관련 소스는 꽤 있는데, ESP8266 상에서 동작하는 소스는 찾기 힘들다고 하네요. 이번에 사용한 기본 소스는 아래 링크에서 따왔습니다.

* Connecting ESP8266 to AWS IoT Core using Arduino Mqtt [두원공과대학교 AI융합과 김동일교수] 6.1.1

* kdi6033 / cloud


// Example of the different modes of the X.509 validation options
// in the WiFiClientBearSSL object
//
// Jul 2019 by Taejun Kim at www.kist.ac.kr

#include "ESP8266WiFi.h"
#include "PubSubClient.h"
#include "time.h"

const char *ssid = "i2r";  // 와이파이 이름
const char *pass = "00000000";      // 와이파이 비밀번호
const char *thingId = "abc";          // 사물 이름 (thing ID) 
const char *host = "**********-ats.iot.us-west-2.amazonaws.com"; // AWS IoT Core 주소

// 사물 인증서 (파일 이름: xxxxxxxxxx-certificate.pem.crt)
const char cert_str[] PROGMEM = R"EOF(
-----BEGIN CERTIFICATE-----
MIIDWjCCAkKgAwIBAgIVAJ21gkV7uYRyYloKhP3J9LSvMviLMA0GCSqGSIb3DQEB
CwUAME0xSzBJBgNVBAsMQkFtYXpvbiBXZWIgU2VydmljZXMgTz1BbWF6b24uY29t
IEluYy4gTD1TZWF0dGxlIFNUPVdhc2hpbmd0b24gQz1VUzAeFw0xOTA3MDEwODQw
MjFaFw00OTEyMzEyMzU5NTlaMB4xHDAaBgNVBAMME0FXUyBJb1QgQ2VydGlmaWNh
dGUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDMXtLCDHsSJMGj0YW7
AYkLmiSiy/c04zsYiRKiugAOWdm9ePnwBAB2uBWLuze1NmftB6g+7G/vDog0ed6p
5J+flXB+C57FXepImn992AJgy2+zlNmEz4XmG8SnnUhI8J7v2ufMhYWMr1m65RJj
mR8RtAoxjmcD7f6PBzNjDgsO9D3hRYwD1YmWk3VuJ8wKI8Z0OEvZxLZlcNUHsv/5
+fIpsDY/l10kyNNhf3DsyS3csYwk9GHTfgerMJCYT+31QeI1nTTjMH/V7QMHGygv
9KahzDXkNnpxW0spQclewDj0k5v511YtMOZRkFaofwr3i3kTw+IIA/F3uP/UnEfI
EOU/AgMBAAGjYDBeMB8GA1UdIwQYMBaAFJ4ZUgC+5dDTg1hV5tXXPolPwq7qMB0G
A1UdDgQWBBS7B0IfwzMIV2WsEDjb6lpLGVC74zAMBgNVHRMBAf8EAjAAMA4GA1Ud
DwEB/wQEAwIHgDANBgkqhkiG9w0BAQsFAAOCAQEApPL5ZHJyufhZOcvT8wcwSzMg
MasR3yJBjFfBjrOq/TvFBccoke7CEo6jDT5ruXiQFKXiR4TlvWJsOzjF/RHyfPz1
QPMoVWtmKrIZP255ji9J9WIcm5oRREg7MHEod9ml+KNXC+Ro0+6RZ9ABa4zTj6mF
kj+3iMB5tr3b45ClhREh3o0g8qGYJyrz/rUksfMwNI6ZWsrHGkb2uRbHJ6Pzj+ni
-----END CERTIFICATE-----
)EOF";
// 사물 인증서 프라이빗 키 (파일 이름: xxxxxxxxxx-private.pem.key)
const char key_str[] PROGMEM = R"EOF(
-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEAzF7Swgx7EiTBo9GFuwGJC5okosv3NOM7GIkSoroADlnZvXj5
8AQAdrgVi7s3tTZn7QeoPuxv7w6INHneqeSfn5VwfguexV3qSJp/fdgCYMtvs5TZ
hM+F5hvEp51ISPCe79rnzIWFjK9ZuuUSY5kfEbQKMY5nA+3+jwczYw4LDvQ94UWM
A9WJlpN1bifMCiPGdDhL2cS2ZXDVB7L/+fnyKbA2P5ddJMjTYX9w7Mkt3LGMJPRh
034HqzCQmE/t9UHiNZ004zB/1e0DBxsoL/Smocw15DZ6cVtLKUHJXsA49JOb+ddW
LTDmUZBWqH8K94t5E8PiCAPxd7j/1JxHyBDlPwIDAQABAoIBAQCxd84wr8HXeY+l
f/ZO9ABb0NjrfY8HoCLbJXzHThlqXN/Vxs3TfMYiUax0EHlJpRsOG84gBhUVVFs2
pnWStnNektiCu/h9jxY5QeBgGUnHYCF5olJZIBQ4Q/i7TLtOi5SY2FDdqzzTdBnJ
T85uKrNuHheT/QK7yNl6vlgDNlin/CaJ3CuMDzjRekk6N2cZGETSyugmZieFIcfE
Ex7NI84eqWis8u+TC+uPo5Q7dhgpxv6jNIMf5ciW1IOYhkZAkJWk8kG/BCep7uMr
9+trqf04fzL7SUipXWLrX2V+rb8012ilItM2vuYxMJxs8hZyPk/Cud7UzaHBxovO
PkBY+RtxAoGBAO3UClMUpD64InyFptYw8Arpcudq+v3QDnPtFuFkl0pBNNWr4Q8n
mbEQphaoLWSyk9sig4Eja3E+pAvRLsGWxuerBn9bTOGjkgpdW4YWJKjbFKdKiTBv
HeAEEUXqWjQKZ/WS3CEPWOkrAeOyIC11XoHB9NUxdAQTv5FspxPuogkTAoGBANv8
V2cRdRZcesQ4tMZzqNxI/2hU2WcPGnAeufCAYBz4eJv4qliBlM12Fw+wo5Qe6z7L
x/ImAXlH9Fswy8O3U+DUvVi+FeEWI6xCc/cMlwwmlWPa3+CnqUvVXqT8InIfX740
efW6YEi43+EsaUSlDWNGW5IstAKpStEkEZoy6ESlAoGAcd1QKCC81i5wjG+sxeXe
N0s3sSZeDsDa+pOrnbP8XxfDBP3qncfW5JhU/In+WbTJ52Op1F0x5qEYB3RaT2Mx
zd7rGHSM5Ybbt8ykshjN6m4hgErGTgMVKZio8HFYSIwm48MxUz620cO+ftZcY4dK
/RLwdlXb9svBrw13HKrmtzMCgYB4Xdso/wlU4ecehHSNfW1wktSFLqAB3ua1YGqW
6HcugtnjZa03XjegMDQwFpN6kWxgYLoXiaoWxUMzvkBP53iStXShIOjxzt5X+8hd
7dqcAGSPTYxf7P0aDDaMCZWDYh1OAoKU7JSQVe/R0i4LLFBl+HJUSfd42U6hnp2v
BJnILQKBgCE1HGiEUC5RQ9O0e+se1z47M3D2S7T87v2tdlnXLSuLXVmBNydwER68
-----END RSA PRIVATE KEY-----
)EOF";
// Amazon Trust Services(ATS) 엔드포인트 CA 인증서 (서버인증 > "RSA 2048비트 키: Amazon Root CA 1" 다운로드)
const char ca_str[] PROGMEM = R"EOF(
-----BEGIN CERTIFICATE-----
MIIDQTCCAimgAwIBAgITBmyfz5m/jAo54vB4ikPmljZbyjANBgkqhkiG9w0BAQsF
ADA5MQswCQYDVQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6
b24gUm9vdCBDQSAxMB4XDTE1MDUyNjAwMDAwMFoXDTM4MDExNzAwMDAwMFowOTEL
MAkGA1UEBhMCVVMxDzANBgNVBAoTBkFtYXpvbjEZMBcGA1UEAxMQQW1hem9uIFJv
b3QgQ0EgMTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALJ4gHHKeNXj
ca9HgFB0fW7Y14h29Jlo91ghYPl0hAEvrAIthtOgQ3pOsqTQNroBvo3bSMgHFzZM
9O6II8c+6zf1tRn4SWiw3te5djgdYZ6k/oI2peVKVuRF4fn9tBb6dNqcmzU5L/qw
IFAGbHrQgLKm+a/sRxmPUDgH3KKHOVj4utWp+UhnMJbulHheb4mjUcAwhmahRWa6
VOujw5H5SNz/0egwLX0tdHA114gk957EWW67c4cX8jJGKLhD+rcdqsq08p8kDi1L
93FcXmn/6pUCyziKrlA4b9v7LWIbxcceVOF34GfID5yHI9Y/QCB/IIDEgEw+OyQm
jgSubJrIqg0CAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMC
AYYwHQYDVR0OBBYEFIQYzIU07LwMlJQuCFmcx7IQTgoIMA0GCSqGSIb3DQEBCwUA
A4IBAQCY8jdaQZChGsV2USggNiMOruYou6r4lK5IpDB/G/wkjUu0yKGX9rbxenDI
U5PMCCjjmCXPI6T53iHTfIUJrU6adTrCC2qJeHZERxhlbI1Bjjt/msv0tadQ1wUs
N+gDS63pYaACbvXy8MWy7Vu33PqUXHeeE6V/Uq2V8viTO96LXFvKWlJbYK8U90vv
o/ufQJVtMVT8QtPHRh8jrdkPSHCa2XV4cdFyQzR1bldZwgJcJmApzyMZFo6IQ6XU
-----END CERTIFICATE-----
)EOF";

void callback(char* topic, byte* payload, unsigned int length) {
  Serial.print("Message arrived [");
  Serial.print(topic);
  Serial.print("] ");
  for (int i = 0; i < length; i++) {
    Serial.print((char)payload[i]);
  }
  Serial.println();

}

X509List ca(ca_str);
X509List cert(cert_str);
PrivateKey key(key_str);
WiFiClientSecure wifiClient;
PubSubClient client(host, 8883, callback, wifiClient); //set  MQTT port number to 8883 as per //standard

void reconnect() {
  // Loop until we're reconnected
  while (!client.connected()) {
    Serial.print("Attempting MQTT connection...");
    // Attempt to connect
    if (client.connect(thingId)) {
      Serial.println("connected");
      // Once connected, publish an announcement...
      client.publish("outTopic", "hello world");
      // ... and resubscribe
      client.subscribe("inTopic");
    } else {
      Serial.print("failed, rc=");
      Serial.print(client.state());
      Serial.println(" try again in 5 seconds");

      char buf[256];
      wifiClient.getLastSSLError(buf,256);
      Serial.print("WiFiClientSecure SSL error: ");
      Serial.println(buf);

      // Wait 5 seconds before retrying
      delay(5000);
    }
  }
}

// Set time via NTP, as required for x.509 validation
void setClock() {
  configTime(3 * 3600, 0, "pool.ntp.org", "time.nist.gov");

  Serial.print("Waiting for NTP time sync: ");
  time_t now = time(nullptr);
  while (now < 8 * 3600 * 2) {
    delay(500);
    Serial.print(".");
    now = time(nullptr);
  }
  Serial.println("");
  struct tm timeinfo;
  gmtime_r(&now, &timeinfo);
  Serial.print("Current time: ");
  Serial.print(asctime(&timeinfo));
}

void setup() {
  Serial.begin(115200);
  Serial.setDebugOutput(true);
  Serial.println();
  Serial.println();

  // We start by connecting to a WiFi network
  Serial.print("Connecting to ");
  Serial.println(ssid);
  WiFi.mode(WIFI_STA);
  WiFi.begin(ssid, pass);

  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.println("");

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

  wifiClient.setTrustAnchors(&ca);
  wifiClient.setClientRSACert(&cert, &key);
  Serial.println("Certifications and key are set");

  setClock();
  //client.setServer(host, 8883);
  client.setCallback(callback);
}

long lastMsg = 0;
char msg[50];
int value = 0;

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

  long now = millis();
  if (now - lastMsg > 5000) {
    lastMsg = now;
    ++value;
    snprintf (msg, 75, "hello world #%ld", value);
    Serial.print("Publish message: ");
    Serial.println(msg);
    client.publish("outTopic", msg);
    Serial.print("Heap: "); Serial.println(ESP.getFreeHeap()); //Low heap can cause problems
  }
}



9. ESP8266

AWS Infra. 와 source 준비는 끝났습니다. 이제 Endpoint 를 향해 쏴주는 device 쪽에 소스를 넣어 주면 되겠죠. Source 에서 Internet 접속을 위한 Wifi 설정과 AWS 에 구축한 Endpoint 정보를 넣어 줍니다. 그리고 설정을 통해 받은 인증서도 붙여 줍니다. 인증서가 좀 커지므로, 용량이 좀 나가게 됩니다. 그 말인 즉, device 메모리 용량이 작은 device 들은 AWS IoT Core 에 연관된 Certificate 처리가 되지 않아, 사용할 수 없다는 결론이 됩니다.


인터넷에서 받은 소스를 그대로 활용하려 하니, PubSubClient 라이브러리가 필요 했었네요. Arduino IDE 의 Library Manager 에서 검색하여 설치해 줍니다.


정상적으로 소스파일이 컴파일 되고, arduino 에 설치가 되었습니다.


저는 Arduino + ESP8266 을 이용했고, 아래와 같이 정상적으로 통신을 시작하는 것을 알 수 있습니다.




10. Check on AWS Console

AWS Console 에서 정상적으로 data 가 들어 오는지 확인해 봅니다. AWS IoT > MQTT test client 항목에서 Subscribe to a topicoutTopic 입력하고 Subscribe 를 클릭 합니다.


Arduino IDE 의 Monitor 화면과 비교하면, 데이터가 잘 들어 오는 것을 확인할 수 있습니다.


Publish to a topic 탭에서 inTopic 이라고 명명하고, 작성한 메시지를 Publish 하면, 반대로 Arduino IDE Monitor 에서, AWS 부터 들어온 메시지가 표시 됩니다. 이를 통해 양방향 데이터 통신이 가능하다는 것을 확인할 수 있습니다.


AWS IoT Core 와 IoT device 들과 어떻게 연결이 가능한지를 알아 보았습니다. 단순한 test 에서 벗어나 실제 데이터를 받고, 이를 Amazon QuickSight 와 같은 그래프를 만들어주는 서비스까지 활용 한다면, AWS 를 이용한 토탈 IoT 서비스 구축이 가능하게 됩니다. 많이들 활용해 보시죠.


FIN

댓글