์ด๋ฒ ๊ธ์ ๐ Workshop:ESP32 with AWS IoT ์ ๋ด์ฉ์ ์ง์ ์ค์ตํ ๋ค ์ฌ ํฌ์คํ ํ ๋ด์ฉ์ ๋๋ค.
์ด๋ฒ ์ค์ต์์๋ Web์ ํตํด ESP32๋ฅผ ์ ์ดํ๋ ์ค์ต์ ๋ํด ์งํํ ์์ ์ด๋ค. ์ด๋ฒ ์ค์ต์์๋ Relay Module์ ์ด์ฉํ Lamp๋ฅผ ๋ง๋ค์์ ์ด๋ค. ์ด์ 4๋ฐ ์ค์์น๋ฅผ ์ด์ฉํด ๋ฐ์ดํฐ๋ฅผ ์ ์กํ๋ ๐ Create an AWS IoT Button ์ค์ต๊ณผ ํฐ ์ฐจ์ด์ ์ด ๋ ๊ฐ์ง๊ฐ ์๋ค.
์ฃผ ๋ฐ์ดํฐ์ ํ๋ฆ
AWS IoT Button ์ค์ต๋์ ์ฃผ๋ ํ๋ฆ์ ESP32 ์์ ๋ฐ์ํ ๋ฐ์ดํฐ(๋ฒํผ ๋๋ ธ๋ค๋ ๋ฉ์์ง + ๋๋ฆฐ ์)๊ฐ AWS์ MQTT Broker์๊ฒ ์ ๋ฌ๋ Lambda๋ฅผ ํธ๋ฆฌ๊ฑฐ๋ง ํ๋ ๋ฐฉ์์ผ๋ก์จ ๊ฐ๋จํ ์ค๋ช ํด ESP32 to AWS ๋ผ๊ณ ํ๋ค๋ฉด ์ค๋์ ์ค์ต์ AWS to ESP32 ์ด๋ค.
์์คํ ์ปดํฌ๋ํธ์ ์
์ ๋ฒ ์ค์ต์๋ ์์คํ ์ AWS Lambda ๋ฅผ ์ด์ฉํด ๊ฐ๋จํ ๋ก๊ทธ๋ฅผ ์ถ๋ ฅํ๋ค๋ฉด, ์ด๋ฒ์๋ AWS Lambda, AWS API Gatyeway, AWS 3S(Simple Stroage Service) ์ AWS Device Shadow ๊ฐ ์์คํ ์ ์ปดํฌ๋ํธ๋ก ๋ค์ด๊ฐ๋ค.
์์คํ ๊ตฌ์ฑ์ ์ ์ ๊ฐ๋ค. ์ฌ์ฉ์๋ API Gateway๋ฅผ ํตํด ์๋ฒ์ ์ ์ํ๋ค, ์ ์๋ ์๋ฒ์์ ๋ฒํผ์ ๋๋ฌ Lamp๋ฅผ ์ ์ดํ๋ ์์คํ ์ด๋ค.
์ ๊ธฐ Lamp๋ฅผ ๊ตฌ์ฑํ๋ ์์ ์ค ๊ฐ์ฅ ์ค์ํ ์์๋ ์ ๊ตฌ ํน์ LED ์ผ ๊ฒ ์ด๋ค. ๋ง์ฝ ์ด๋ฌํ ์ ๊ตฌ๊ฐ 220V 60W์ ์ ์์ ์๊ตฌํ๋ค๋ฉด ESP32๋ก ์ด๋ป๊ฒ ์ ์ดํ ์ ์์๊น? ๊ฐ์ฅ ์์ฌ์ด ๋ฐฉ๋ฒ์ Relay Module์ ์ด์ฉํ๋ ๊ฒ ์ด๋ค.
Relay Module์ ์ ์ด์ ํธ(SIG)๋ฅผ ์ด์ฉํด ์ผ๋ฐ ๊ฐ์ ์์ ์ฌ์ฉํ๋ ๊ฐ๋จํ 220V ๊ธฐ๊ธฐ๋ฅผ On/Off ํ ์ ์๋ค. Relay Module์ VCC์ GND๋ฅผ ๊ฐ๊ฐ 5V(3.3V) ์ GND๋ฅผ ์ฐ๊ฒฐํ๊ณ SIG๊ฐ LOW์ผ ๋, COM ๋จ์์ NC(Normal Close)๊ฐ ์ ์ง์ธ ์ํ ์ฆ ์ฐ๊ฒฐ์ด ๋์ด ์๋ค. ์ด๋ ์ ์ด ์ ํธ๊ฐ HIGH ์ํ๋ก ๋ฐ๋๋ฉด COM๊ณผ NO(Normal Open)์ด ์ฐ๊ฒฐ๋๋ค.
๋ง์ฝ ์ง์ง ๊ฐ์ ์ฉ ์ฝ์ผํธ๋ฅผ ์ฌ์ฉํ๋ ์ ๊ตฌ๋ก ๋ฌด๋๋ฑ์ ๋ง๋ค๊ณ ์ถ๋ค๋ฉด 220V ์ ๊ตฌ ์์ผ์ ํผ๋ณต์ ๋ฒ๊ธด๋ค COM๊ณผ NO์ ์ฐ๊ฒฐ์ ํ๋ฉด ๋๋ค. ์ด๋ฒ ์ค์ต์์๋ LED๋ฅผ ์ ๊ตฌ ๋์ ์ฌ์ฉํ๋๊ฒ์ผ๋ก ์งํํ๋ค.
Relay์ ๋์ํ ์คํธ๋ฅผ ์ํด ์๋ ์ฝ๋๋ฅผ ์ ๋ก๋ํด Relay๊ฐ ์ ์๋ ๋๋์ง ํ์ธํด ๋ณด์.
int signalPin = 16;
void setup() {
Serial.begin(115200);
delay(2000);
Serial.println("Lelay Module Test");
pinMode(signalPin, OUTPUT);
}
void loop() {
Serial.println("On");
digitalWrite(signalPin, HIGH);
delay(1000);
Serial.println("Off");
digitalWrite(signalPin, LOW);
delay(1000);
}
AWS IoT์๋ Device Shadow๋ผ๋ ๊ธฐ๋ฅ์ด ์๋ค. AWS IoT Core์ ๊ฐ๋ฐ์ ์๋ด์์ Device Shadow Service์ ๋จ๋ฝ์ ์ฝ์ด๋ณด๋ฉด ๋ค์๊ณผ ๊ฐ์ด ์จ์๋ค.
The AWS IoT Device Shadow service adds shadows to AWS IoT thing objects. Shadows can make a deviceโs state available to apps and other services whether the device is connected to AWS IoT or not. AWS IoT thing objects can have multiple named shadows so that your IoT solution has more options for connecting your devices to other apps and services.
์ถ์ฒ: AWS IoT Core Developer - Guide Deivce Shadow Service
๋๋ฐ์ด์ค์ online/offline์ ๊ด๊ณ์์ด ๋๋ฐ์ด์ค์ ์ํ๋ฅผ ์ฌ์ฉํ ์ ์๊ฒ ํด์ฃผ๋ ์๋น์ค๋ผ๊ณ ์ ํ์๋ค. ๋ง์น ๋๋ฐ์ด์ค๊ฐ ์ธํฐ๋ท์ ์ฐ๊ฒฐ์ ๋ฌด์ ์๊ด์์ด ์ ์ด๋ฅผ ํ ์์๋ ๊ฒ ์ฒ๋ผ ์คํดํ ์๋ ์์ง๋ง ์ ์ฝ์ด๋ณด๋ฉด ๋๋ฐ์ด์ค๊ฐ ์จ๋ผ์ธ์ด๋ ์คํ๋ผ์ธ์ด๋ ์ํ๋ฅผ ์ฌ์ฉํ ์ ์๊ฒ ํด์ฃผ๋ ์๋น์ค๋ผ๊ณ ์ ํ์๋ค.
Device Shadow Service๋ฅผ ์ด์ฉํ๋ฉด ๋ด๊ฐ ์ก์ ํ ๋ฉ์์ง๋ ์ค์ ์ฌ๋ฌผ์ ์ ๋ฌ๋์ง ์๊ณ Device Shadow์ ์ ๋ฌ์ด ๋๋ค. ์ด๋ ๊ฒ ์ ๋ฌ ๋ ๋ฉ์์ง๋ ํ์ฌ ์ฌ๋ฌผ์ ์ํ์ ๋น๊ตํ์ฌ ์ฐจ์ด(๋ธํ)๋ฅผ ๊ณ์ฐํ ํ ์ฌ๋ฌผ์๊ฒ ์ ๋ฌ๋์ด ์ง๋ค.
Device Shadow ์ JSON์ผ๋ก ์ด๋ฃจ์ด์ง ๋ฌธ์์ด๋ฉฐ, ์ด JSON ๋ฌธ์์ ์ฌ๋ฌผ์ ํ์ฌ ์ํ ๋ฐ๋์ํ ๋ ์ํ๊ฐ์ ์ฐจ์ด๋ฅผ ๊ธฐ์ ํ๋ค. ์ด๋ ๊ฒ ์ธ ์น์ ์ ๊ฐ๊ฐ ์๋์ ๊ฐ๋ค.
Sample Flow๋ฅผ ํตํด ๊ฐ๋จํ ์์๋ฅผ ๋ค์ด๋ณด์. ์ฒ์ Device Shadow์ JSON ๋ฌธ์๊ฐ ์๋์ ๊ฐ์ด ์ ํ ์๋ค.
{
"desired": {
"status": "on"
},
"reported": {
"status": "on"
}
}
์ด์ ์ฌ์ฉ์๊ฐ Web ํน์ ๊ธฐํ ์ธํฐํ์ด์ค๋ฅผ ํตํด ์ฌ๋ฌผ์ ์ํ๋ฅผ ๋ฐ๊พธ๋ คํ๋ค. ์ด๋ฒ ์ค์ต์์๋ Web์ ์๋ ๋ฒํผ์ ๋๋ฌ Lamp๋ฅผ ํค๋ ค๊ณ ํ๋ ๊ฒ๊ณผ ๊ฐ๋ค. ์ด๋ฌํ desired ์ํ๊ฐ AWS IoT๋ก ์ ์ก์ด ๋ ๊ฒ ์ด๋ค.
{
"state": {
"desired": {
"status": "off"
}
}
}
desired ์ reported์ ์ํ ๋ถ์ผ์น๋ ๋ ์ํ์ ์ฐจ์ด๊ฐ ๋ฐ์ํ์ฌ delta๊ฐ ๊ณ์ฐ๋๋ค. delat ์ํ๊ฐ๋๋ฉด ๊ทธ ์ฆ์ ์์ ์ ์ฌ๋ฌผ์๊ฒ ๋ค์๊ณผ ๊ฐ์ ํ ํฝ์ผ๋ก $aws/things/{THING_NAME}/shadow/update/delta
(ํน์ $aws/things/{THING_NAME}/shadow/{SHADOW_NAME}/update/delta
) ์๋ ๋ฉ์์ง๋ฅผ ๋ฐํํ๋ค.
{
"version": 463,
"timestamp": 1571747274,
"state": {
"status": "off"
},
"metadata": {
"status": {
"timestamp": 1571747274
}
}
}
์ด ๋ฉ์์ง๋ฅผ ์์ ํ ์ฌ๋ฌผ์ ๊ทธ ์ฆ์ desired์ ์๋ status๋ก ์ค์ ์ํ(์ฌ๊ธฐ์๋ Lamp์ ์๋ฑ)๋ฅผ ๋ฐ์ํ ๋ค $aws/things/{THING_NAME}/shadow/update
(ํน์ $aws/things/{THING_NAME}/shadow/{SHADOW_NAME}/update
)๋ก reported๋ฅผ ์ ์กํ๋ค.
{
"state": {
"reported": {
"status": "off"
}
}
}
reported๋ฅผ ๋ฐ์ AWS IoT๋ ์ด๋ฅผ ๋ฐ์ํด Device Shadow์ Document์ ๋ฐ์ํ๋ค.
{
"desired": {
"status": "off"
},
"reported": {
"status": "off"
}
}
์ด์ ์ค์ Device Shadow๋ฅผ ์์ฑํด ๋ณด์. AWS IoT Core๋ก ์ ์ ํ ๋ค ์ข์ธก ํจ๋์์ ๊ด๋ฆฌ
โ ์ฌ๋ฌผ
์์ ์์ ์ด ๋ง๋ค์๋ ์ฌ๋ฌผ(์ฌ๊ธฐ์๋ ESP32) ์ ํด๋ฆญํด๋ณด์.
์๋์ฐ ์์ฑ ์ ํด๋ฆญํ ๋ค, ์ด๋ฆ ์๋(ํด๋์) ์๋์ฐ๋ฅผ ์์ฑํด ๋ณด์. ๐ Workshop:ESP32 with AWS IoT ์์๋ ํด๋์๊ณผ ๋ค์๋ ์๋์ฐ์ ๊ตฌ๋ถ์ ๋ฐ๋ก ์ง์ ํ์ง ์์๋๋ฐ ์ด๊ฒ ๋ฒ์ ์ด ์ฌ๋ผ๊ฐ์ ์๊ธด ์ฐจ์ด์ธ์ง ํน์ ์ ์ ์๊ฐ ์ผ๋ถ๋ฌ ์ธ๊ธ์ ์ํ๊ฑด์ง ์ ์๋ ์๋ค. ๋ค์๋ ์๋์ฐ๋ฅผ ์์ฑํด ์ดํด๋ณด๋ฉด์บก
๋ค์๊ณผ ๊ฐ์ ํ ํฝ์ฐจ์ด๊ฐ ๋ฐ์ํ๋ค. ์ ์ ์๊ฐ $aws/things/ESP32/shadow
๋ผ๋ ํ ํฝ ์ ๋์ฌ๋ฅผ ๊ณ์ ์ฌ์ฉํ๊ฒ์ผ๋ก๋ณด์ ํด๋์ ์๋์ฐ๋ฅผ ์์ฑํ๋ฉด ๋๋ค.
์ฐธ๊ณ ๋ก $aws
๋ ์์คํ
ํน์ ํ๋ ํ ํฝ์ด๋ผ๊ณ ๋ถ๋ฅธ๋ค(MQTT์์ ์ฌ์ฉํ๋ ์์ผ๋ ์นด๋๋ ์๋).
Device Shadow๋ฅผ ์์ฑํ๋ฉด ๋๋ฐ์ด์ค ์๋์ฐ ๋ฌธ์ ํญ์ ๋ค์ด๊ฐ๋ฉด ๋ค์๊ณผ ๊ฐ์ ์ด๊ธฐ ์ํ๋ฅผ ๋ณผ ์ ์๋ค.
๋๋ฐ์ด์ค ์๋์ฐ ์ํ
{
"state": {
"desired": {
"welcome": "aws-iot"
},
"reported": {
"welcome": "aws-iot"
}
}
}
๋๋ฐ์ด์ค ์๋์ฐ ๋ฉํ๋ฐ์ดํฐ
{
"metadata": {
"desired": {
"welcome": {
"timestamp": 1638861952
}
},
"reported": {
"welcome": {
"timestamp": 1638861952
}
}
}
}
์ด์ ์ฌ๋ฌผ์ ์ฝ๋๋ฅผ ์ ๋ก๋ํ๋ค Device Shadow์ ํ ํฝ ์ฌ์ฉ์ ์ํด Policy๋ฅผ ์ ๋ฐ์ดํธ ํ์. ์ฐ์ ์๋์ ์ฝ๋๋ฅผ ESP32์ ์ ๋ก๋ ํ๋ค.
/*
Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
Permission is hereby granted, free of charge, to any person obtaining a copy of this
software and associated documentation files (the "Software"), to deal in the Software
without restriction, including without limitation the rights to use, copy, modify,
merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#include "secrets.h"
#include <WiFiClientSecure.h>
#include <MQTTClient.h>
#include <ArduinoJson.h>
#include "WiFi.h"
// The MQTT topics that this device should publish/subscribe
#define AWS_IOT_PUBLISH_TOPIC "$aws/things/ESP32/shadow/update"
#define AWS_IOT_SUBSCRIBE_TOPIC "$aws/things/ESP32/shadow/update/delta"
int msgReceived = 0;
int signalPin = 16;
String rcvdPayload;
char sndPayloadOff[512];
char sndPayloadOn[512];
WiFiClientSecure net = WiFiClientSecure();
MQTTClient client = MQTTClient(256);
void connectAWS()
{
WiFi.mode(WIFI_STA);
WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
Serial.println("");
Serial.println("###################### Starting Execution ########################");
Serial.println("Connecting to Wi-Fi");
while (WiFi.status() != WL_CONNECTED){
delay(500);
Serial.print(".");
}
// Configure WiFiClientSecure to use the AWS IoT device credentials
net.setCACert(AWS_CERT_CA);
net.setCertificate(AWS_CERT_CRT);
net.setPrivateKey(AWS_CERT_PRIVATE);
// Connect to the MQTT broker on the AWS endpoint we defined earlier
client.begin(AWS_IOT_ENDPOINT, 8883, net);
// Create a message handler
client.onMessage(messageHandler);
Serial.println("Connecting to AWS IOT");
while (!client.connect(THINGNAME)) {
Serial.print(".");
delay(100);
}
if(!client.connected()){
Serial.println("AWS IoT Timeout!");
return;
}
// Subscribe to a topic
client.subscribe(AWS_IOT_SUBSCRIBE_TOPIC);
Serial.println("AWS IoT Connected!");
}
void messageHandler(String &topic, String &payload) {
msgReceived = 1;
rcvdPayload = payload;
}
void setup() {
Serial.begin(115200);
pinMode(signalPin, OUTPUT);
digitalWrite(signalPin, LOW);
sprintf(sndPayloadOn,"{\"state\": { \"reported\": { \"status\": \"on\" } }}");
sprintf(sndPayloadOff,"{\"state\": { \"reported\": { \"status\": \"off\" } }}");
connectAWS();
Serial.println("Setting Lamp Status to Off");
client.publish(AWS_IOT_PUBLISH_TOPIC, sndPayloadOff);
Serial.println("##############################################");
}
void loop() {
if(msgReceived == 1)
{
// This code will run whenever a message is received on the SUBSCRIBE_TOPIC_NAME Topic
delay(100);
msgReceived = 0;
Serial.print("Received Message:");
Serial.println(rcvdPayload);
StaticJsonDocument<200> sensor_doc;
DeserializationError error_sensor = deserializeJson(sensor_doc, rcvdPayload);
const char *sensor = sensor_doc["state"]["status"];
Serial.print("AWS Says:");
Serial.println(sensor);
if(strcmp(sensor, "on") == 0)
{
Serial.println("IF CONDITION");
Serial.println("Turning Lamp On");
digitalWrite(signalPin, HIGH);
client.publish(AWS_IOT_PUBLISH_TOPIC, sndPayloadOn);
}
else
{
Serial.println("ELSE CONDITION");
Serial.println("Turning Lamp Off");
digitalWrite(signalPin, LOW);
client.publish(AWS_IOT_PUBLISH_TOPIC, sndPayloadOff);
}
Serial.println("##############################################");
}
client.loop();
}
์ด์ AWS IoT Core์์ ์ข์ธก ํจ๋์ ๋ณด์
โ ์ ์ฑ
์ง๋ ์ค์ต ๐ Connecting AWS with ESP32: Tutorial ์์ ์์ฑํ๋ ESP32 Policy ๋ฅผ ์๋ก ์
๋ฐ์ดํธํ๋ค.
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "iot:Connect",
"Resource": "arn:aws:iot:REGION:ACCOUNT_ID:client/${iot:Connection.Thing.ThingName}"
},
{
"Effect": "Allow",
"Action": "iot:Publish",
"Resource": [
"arn:aws:iot:REGION:ACCOUNT_ID:topic/esp32/pub",
"arn:aws:iot:REGION:ACCOUNT_ID:topic/$aws/things/${iot:Connection.Thing.ThingName}/shadow/update"
]
},
{
"Effect": "Allow",
"Action": "iot:Subscribe",
"Resource": [
"arn:aws:iot:REGION:ACCOUNT_ID:topicfilter/esp32/sub",
"arn:aws:iot:REGION:ACCOUNT_ID:topicfilter/$aws/things/${iot:Connection.Thing.ThingName}/shadow/update/delta"
]
},
{
"Effect": "Allow",
"Action": "iot:Receive",
"Resource": [
"arn:aws:iot:REGION:ACCOUNT_ID:topic/esp32/sub",
"arn:aws:iot:REGION:ACCOUNT_ID:topic/$aws/things/${iot:Connection.Thing.ThingName}/shadow/update/delta"
]
}
]
}
REGION ๊ณผ ACCOUNT_ID ๋ฅผ ๋ชจ๋ฅด๊ฒ ์ผ๋ฉด ์ ์ฑ ARN ๋์ ์๋ ARN์์ ํ์ธํ์.
arn:aws:iot:REGION:ACCOUNT_ID:policy/...
${iot:Connection.Thing.ThingName} ์ฐ๋ฆฌ๊ฐ ์ฐ๊ฒฐํ๋ ์ฌ๋ฌผ์ ์ด๋ฆ์ด๋ค. ์ฐ๋ฆฌ๊ฐ ๋ง๋ ์ฌ๋ฌผ์ ์ด๋ฆ์ ESP32
์ด๋ฏ๋ก ESP32๋ฅผ ๋์ ๋ฃ์ด์ฃผ๋ฉด ๋๋ค.
์ด์ 2 ์ข ๋ฅ์ Lambda Function์ ๋ง๋ค์ด์ผ ํ๋ค. ํ๋๋ Web์์ ๋ฒํผ์ ํด๋ฆญํ์๋ API Gateway๋ฅผ ํตํด Desired ์ํ๋ฅผ ์ ๋ฌํด์ฃผ๋ Lambda ์ EPS32์ ์ํ๋ฅผ ์ฒดํฌํด์ฃผ๋ Lambda ์ด๋ค. ์ฌ๊ธฐ์ boto3 ๋ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ import ํ๋๋ฐ boto3๋ python ์ฉ AWS SDK์ด๋ค.
Shadow Status Check
import json
import boto3
import logging
logger = logging.getLogger()
logger.setLevel(logging.INFO)
client = boto3.client('iot-data')
def lambda_handler(event, context):
# print(json.dumps(event))
print("Thing Name: " + json.dumps(event['queryStringParameters']['thingname']))
logger.info("Attempting to fetch Shadow State")
THINGNAME=event['queryStringParameters']['thingname']
if (THINGNAME == ""):
print("No Thing Name found. Setting Thing Name = ESP32")
THINGNAME="ESP32"
try:
response = client.get_thing_shadow(thingName=THINGNAME)
logger.info("Shadow State Received")
res = response['payload'].read()
res_json = json.loads(res)
print(json.dumps(res_json))
status = res_json['state']['reported']
logger.info("Received From IoT: " + json.dumps(status))
logger.info("\nChanging for website\n")
value = status['status']
if (value == "on"):
status['status'] = "It's On"
else:
status['status'] = "It's Off"
logger.info("Sending to Website: " + json.dumps(status) + "\n")
return {
'statusCode': 200,
"headers": {
'Access-Control-Allow-Origin':'*',
'Access-Control-Allow-Headers':'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token',
'Access-Control-Allow-Methods':'GET,OPTIONS'
},
'body': json.dumps(status)
}
except:
status = {"status": "Device Shadow State Error"}
return {
'statusCode': 200,
"headers": {
'Access-Control-Allow-Origin':'*',
'Access-Control-Allow-Headers':'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token',
'Access-Control-Allow-Methods':'GET,OPTIONS'
},
'body': json.dumps(status)
}
Shadow Update
import json
import boto3
import logging
logger = logging.getLogger()
logger.setLevel(logging.INFO)
client = boto3.client('iot-data')
def lambda_handler(event, context):
print(json.dumps(event['body']))
body = event['body']
body = json.loads(body)
THINGNAME=body['thingname']
if (THINGNAME == ""):
print("No Thing Name found. Setting Thing Name = ESP2")
THINGNAME="ESP32"
if body['action'] == "on":
payload = json.dumps({'state': { 'desired': { 'status': 'on' } }})
logger.info("Attempting to Update Shadow State to ON")
response = client.update_thing_shadow(
thingName=THINGNAME,
payload=payload
)
logger.info("IOT Shadow Updated")
else:
payload = json.dumps({'state': { 'desired': { 'status': 'off' } }})
logger.info("Attempting to Update Shadow State to OFF")
response = client.update_thing_shadow(
thingName=THINGNAME,
payload=payload
)
logger.info("IOT Shadow Updated")
return {
'statusCode': 200,
"headers": {
'Access-Control-Allow-Origin':'*',
'Access-Control-Allow-Headers':'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token',
'Access-Control-Allow-Methods':'GET,OPTIONS'
},
'body': json.dumps('Shadow Updated!')
}
Lambda ์์ฑ ๋ฐฉ๋ฒ์ ๐ Create an AWS IoT Button ์ค์ต ํธ์ ์ฐธ๊ณ ํ์. ์ด์ ์ฐ๋ฆฌ์ Lambda Function์ด AWS IoT Data์ ์ ๊ทผํ๊ธฐ ์ํด
๐ AWSIoTDataAccess ์์ ์ ์ฑ
์ฌ์ฉ์ Lambda๋ฅผ ๋ฃ์ด ์ค์ผํ๋ค. ๋งํฌ๋ฅผ ํ๊ณ ๋ค์ด๊ฐ๋ ๋๋ฉฐ, AWS ๊ด๋ฆฌ ์ฝ์์ ๋ก๊ทธ์ธํ ๋ค, AWS ์๋น์ค
โ ๋ชจ๋ ์๋น์ค
โ IAM
์ ์ ์ํ๋ค(IAM์ ๋ชจ๋ ์๋น์ค ๋์ ๋ณด์, ์๊ฒฉ ์ฆ๋ช
๋ฐ ๊ท์ ์ค์ ์์ ์ฐพ์ ์ ์๋ค). ์ด์ ์ข์ธก ํจ๋์ ์ก์ธ์ค ๊ด๋ฆฌ
โ ์ ์ฑ
์ผ๋ก ๋ค์ด๊ฐ์ AWSIoTDataAcces ์ ์ฑ
์ ๊ฒ์ํ๋ค ์ ์ฑ
์ฌ์ฉํญ์์ ์ฐ๊ฒฐ์ ํด๋ฆญํด ๋ฐฉ๊ธ ๋ง๋ Lambda Function ์ ๋ฑ๋กํ๋ค.
API Gateway๋ API ์๋ฒ ์๋จ์์ ๋ชจ๋ API ์๋ฒ๋ค์ ์๋ํฌ์ธํธ๋ฅผ ๋จ์ผํ ํด์ฃผ๋ ๋ ๋ค๋ฅธ ์๋ฒ์ ๋๋ค. API์ ๋ํ ์ธ์ฆ๊ณผ ์ธ๊ฐ ๊ธฐ๋ฅ์ ๊ฐ์ง๊ณ ์์ผ๋ฉฐ, ๋ฉ์ธ์ง์ ๋ด์ฉ์ ๋ฐ๋ผ ์ดํ๋ฆฌ์ผ์ด์ ๋ด๋ถ์ ์๋ ๋ง์ดํฌ๋ก์๋น์ค๋ก ๋ผ์ฐํ ํ๋ ์ญํ ์ ๋ด๋นํฉ๋๋ค.
์ถ์ฒ: ์ด๋ฐ์ํ ์๋ฃจ์
API Gateway๋ ์ฌ์ฉ์๊ฐ API๋ฅผ ํธ์ถํ๋ ์ ๋ฌธ์ด๋ค. AWS์์๋ ์ด๋ฌํ API๋ฅผ ์ฝ๊ฒ ์์ฑ ๋ฐ ๋ฐฐํฌ๋ฅผ ํ ์ ์์ผ๋ฉฐ ์ด๋ฌํ API๋ค์ AWS API Gateway Service์์ ๊ด๋ฆฌํ ์ ์๋ค. ๋ํ API Gateway Service๋ฅผ ์ด์ฉํด Lambda๋ฅผ ํธ์ถํ๊ฑฐ๋ ๋ฐ์ดํฐ๋ฒ ์ด์ค์์ ์ฐ๋ ๋ฑ AWS ์ฌ๋ฌ ๋ค๋ฅธ ์๋น์ค์ ์ฐ๋ํ ์ ์๋ค.
API Gateway ์์ API๋ฅผ ์์ฑํ๋ค ์์ฑ๋ API์ Lambda๋ฅผ ์ฐ๊ฒฐํด API๋ฅผ ํธ์ถํ ๋ ๋ง๋ค Lambda๋ฅผ ํธ๋ฆฌ๊ฑฐ๋งํ๋๋ก ์ฐ๋์์ผ ๋ณด์
AWS ์ฝ์ ์๋น์ค์์ API Gateway๋ก ์ ์ํ ๋ค ์ข์ธก ํจ๋ API๋ฅผ ํด๋ฆญํ ๋ค API๋ฅผ ์์ฑํ์. ์์ฑํ ์ ์๋ ์๋ 4๊ฐ์ง ์ ํ์ด ์๋ค.
์ฌ๊ธฐ์ HTTP API ํน์ REST API ๋ API๋ ๊ฑฐ์ ๋น์ทํ ์ผ์ ํ ์ ์๋๋ฐ HTTP API๋ ๋จ์ํ ์์
๋ฐ์ ๋ชปํ์ง๋ง ๊ฐ๊ฒฉ๋ฉด์์ ์ ๋ ดํ๋ค. ๋ฐ๋ฉด REST API๋ ๋ณต์กํ ์์
๊ณผ ์ฐ๋์ด ๋์ง๋ง ๊ฐ๊ฒฉ์ด ๋น์ธ๋ค. ๋ API ๋ชจ๋ Lambda์ ์ฐ๋์ด ๋์ง๋ง ๋์ค์๋ GET
์ ํตํด S3์ ์๋ ์ ์ HTML์ ๋ถ๋ฌ์์ผ ํ๋ฏ๋ก REST API๋ก ์์ฑํ๋ค.
๋ฐฉ๊ธ ๋ง๋ IoT_Lamp_API ์ข์ธก ํญ์์ ๋ฆฌ์์ค์์ ์๋ก์ด ๋ฆฌ์์ค๋ฅผ ์์ฑํด์ผํ๋ค. ๋ฆฌ์์ค ์ด๋ฆ์ shadow-state๋ก ์ค์ ํ ๋ค API Gateway CORS ํ์ฑํ ์ฌ๋ถ์ ์ฒดํฌ๋ฅผ ํด์ค๋ค.
์๋ก ์์ฑํ ๋ฆฌ์์ค์ธ shdow-state ์๋์ ์๋ก์ด GET
๊ณผ POST
๋ฉ์๋๋ฅผ ๋ง๋ค์ด Lambda์ ๋งคํ์ํค๋๋ก ํ๋ค.
์ด์ ์์ ๋ฒํผ์ ํด๋ฆญํด ๋ฐฐํฌํ๋ค. ๋ง์ฝ ๋ฐฐํฌ์คํ ์ด์ง๊ฐ ์๋ค๋ฉด ์ ์คํ ์ด์ง์์ Dev ๋ฐฐํฌ ์คํ ์ด์ง๋ฅผ ๋ง๋ค์ด ๋ฐฐํฌํ๋ค.
AWS S3๋ Simple Stroage Service ์ ์ฝ์๋ก AWS ์์ ์ ๊ณตํ๋ Cloud Stroage ์ด๋ค. AWS S3์ ๋ฐ์ดํฐ๋ฅผ ์ ์ฅํ๊ธฐ ์ํด์๋ bucket์ ์์ฑํด์ผํ๋๋ฐ AWS ์์ bucket์ด๋ ๋ค์์ ์ง์นญํ๋ค.
Amazon S3์ ๋ฐ์ดํฐ๋ฅผ ์ ์ฅํ๋ ค๋ฉด ๋ฒํท ๋ฐ ๊ฐ์ฒด๋ผ๋ ๋ฆฌ์์ค๋ฅผ ์ฌ์ฉํฉ๋๋ค. ๋ฒํท์ ๊ฐ์ฒด์ ๋ํ ์ปจํ ์ด๋์ ๋๋ค. ๊ฐ์ฒด๋ ํ์ผ๊ณผ ํด๋น ํ์ผ์ ์ค๋ช ํ๋ ๋ชจ๋ ๋ฉํ๋ฐ์ดํฐ์ ๋๋ค.
Amazon S3์ ๊ฐ์ฒด๋ฅผ ์ ์ฅํ๋ ค๋ฉด ๋ฒํท์ ์์ฑํ ๋ค์ ๋ฒํท์ ๊ฐ์ฒด๋ฅผ ์ ๋ก๋ํฉ๋๋ค. ๊ฐ์ฒด๊ฐ ๋ฒํท์ ์์ผ๋ฉด ๊ฐ์ฒด๋ฅผ ์ด๊ณ ๋ค์ด๋ก๋ํ๊ณ ์ด๋ํ ์ ์์ต๋๋ค. ๊ฐ์ฒด๋ ๋ฒํท์ด ๋ ์ด์ ํ์ํ์ง ์์ ๊ฒฝ์ฐ ๋ฆฌ์์ค๋ฅผ ์ ๋ฆฌํ ์ ์์ต๋๋ค.
AWS ๊ด๋ฆฌ ์ฝ์์์ S3์ ์ ์ ํ ๋ฒํท ํญ์ผ๋ก ๋ค์ด๊ฐ ์๋ก์ด bucket์ ๋ง๋ค์ด ๋ณด์. bucket์ ์ด๋ฆ์ ์ ํ ๋๋ ๋ช ๊ฐ์ง ๊ท์น์ด ์กด์ฌํ๋๋ฐ ์๋ฌธ์๋ง ๊ฐ๋ฅํ๊ณ ํน์๋ฌธ์๋ dot(.) ๊ณผ hyphens(-) ๋ง ์ง์ํ๊ณ ๋ URL ํ์์ ๊ถ์ฅํ์ง ์๋๋ค. ์์ธํ ๋ด์ฉ์ด ๊ถ๊ธํ๋ค๋ฉด ๐ AWS S3 Bucket naming rules ์ ์ ์ํด ํ์ธํด ๋ณด์.
์ด๋ฆ์ ์ ํ๋ค๋ฉด ๊ทธ ์ธ ๋ชจ๋ ์ต์ ์ ๋ํดํธ๋ก ์ ํํ ๋ค ์์ฑํ๋ฉด ๋๋ค. bucket์ ์์ฑํ์ผ๋ ์๋ ์ฝ๋๋ก HTMLํ์ผ์ ํ๋ ๋ง๋ ๋ค.
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>IoT Lamp</title>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bulma/0.7.5/css/bulma.min.css">
<script defer src="https://use.fontawesome.com/releases/v5.3.1/js/all.js"></script>
<script type = "text/javascript"
src = "https://ajax.googleapis.com/ajax/libs/jquery/2.1.3/jquery.min.js">
</script>
<script type="text/javascript">
$(window).load(function() {
});
$(document).ready(function() {
$("#onoff").click(function(event){
currentvalue = document.getElementById('onoff').innerHTML;
if(currentvalue == "It's Off"){
// trying to add the value from text box
thingName = document.getElementById("txtField").value;
console.log(thingName);
var mydata={"action":"on", "thingname":thingName};
console.log(mydata);
document.getElementById("onoff").innerHTML="It's On";
document.getElementById("onoff").setAttribute("class","button is-rounded is-large is-fullwidth is-loading");
$.ajax( {
type: 'POST',
url:'',
contentType: 'application/json',
crossDomain: true,
processData: false,
dataType: "json",
data: JSON.stringify(mydata),
success:function(data) {
console.log(" on success");
document.getElementById("onoff").setAttribute("class","button is-success is-rounded is-large is-fullwidth");
},
error:function(data) {
console.log(" on fail;");
document.getElementById("onoff").setAttribute("class","button is-success is-rounded is-large is-fullwidth");
}
});
}
else {
document.getElementById("onoff").innerHTML="It's Off";
// trying to add the value from text box
thingName = document.getElementById("txtField").value;
console.log(thingName);
var mydata={"action":"off", "thingname":thingName};
console.log(mydata);
document.getElementById("onoff").innerHTML="It's Off";
document.getElementById("onoff").setAttribute("class","button is-rounded is-large is-fullwidth is-loading");
$.ajax( {
type: 'POST',
url:'',
contentType: 'application/json',
crossDomain: true,
processData: false,
dataType: "json",
data: JSON.stringify(mydata),
success:function(data) {
console.log(" off success");
document.getElementById("onoff").setAttribute("class","button is-danger is-rounded is-large is-fullwidth");
},
error:function(data) {
console.log(" off fail");
document.getElementById("onoff").setAttribute("class","button is-danger is-rounded is-large is-fullwidth");
}
});
}
});
$("#fndbtn").click(function(event){
console.log("testing the button");
//check the status of Shadow
// thingName = "ESP32"
thingName = document.getElementById("txtField").value;
console.log(`thingname=${thingName}`);
$.ajax( {
type: 'GET',
url:'',
contentType: 'application/json',
crossDomain: true,
processData: false,
dataType: "json",
data: `thingname=${thingName}`,
success:function(data) {
console.log("success");
console.log(data.status);
document.getElementById("onoff").innerHTML=data.status;
if (data.status == "It's On" )
{
document.getElementById("onoff").setAttribute("class","button is-success is-rounded is-large is-fullwidth");
}
else{
document.getElementById("onoff").setAttribute("class","button is-danger is-rounded is-large is-fullwidth");
}
},
error:function(data) {
console.log("error");
}
});
});
});
</script>
</head>
<body>
<div class="columns is-mobile">
<div class="column is-2">
<!-- First Column-->
</div>
<div class="column is-8">
<br><br><br><br><br>
<h1 class="title is-1" style="text-align: center;">IoT Lamp</h1>
<br><br>
<center>
<p>Enter Thing Name: </p>
 
</center>
<center>
<input name="txtField" type="text" maxlength="512" id="txtField" class="searchField" style="padding: 8px;" />
 
<!-- <input type = "submit" id = "fndbtn" value = "Submit"/> -->
<a class="button" id = "fndbtn">Check</a>
</center>
<br><br>
<a class="button is-info is-rounded is-large is-fullwidth" id="onoff" value="on">Pending Check</a>
</div>
<div class="column is-2">
<!-- Third Column-->
</div>
</div>
</body>
</html>
์ HTML ์ฝ๋์ 31, 58, 84๋ฒ ๋ผ์ธ์ url
๋ถ๋ถ์ด ๊ณต๋์ธ ๊ฒ์ ํ์ธํ ์ ์๋๋ฐ ์ฌ๊ธฐ์ ์๊น ์ฐ๋ฆฌ๊ฐ ๋ง๋ค์๋ POST
์ GET
์ url์ ๋ฃ์ด์ฃผ๋ฉด ๋๋ค. url ํ์ธ์ API Gateway ์ฝ์์์ IoT_Lamp_API ํญ์ผ๋ก ๋ค์ด๊ฐ ๋ค, ์คํ
์ด์ง ์์ญ์ ํด๋ฆญํ๋ฉด ํ์ธํ ์ ์๋ค.
์ด์ AWS API Gateway์ S3๋ฅผ ์ฐ๋ํด๋ณด์. ์ด๋ฒ ์น์ ์ด ๋๋๋ฉด ๋ธ๋ผ์ฐ์ ์ฐฝ์ AWS Gateway์์ ๋ง๋ AWS IoT Lamp API์ API๋ฅผ ํตํด ์ค์ Lamp๋ฅผ ์ ์ดํ๋ Web์ผ๋ก ์ ์ํ ์ ์๋ค.
์ฐ์ API Gateway๊ฐ S3์ ์ ๊ทผํ ์ ์๋ Role์ ๋ง๋ค์ด ๋ณด์ IAM ์ฝ์์ ์ ์ํ ๋ค ์ข์ธก ํจ๋ ์ก์ธ์ค ๊ด๋ฆฌ
โ ์ญํ
์ ์ ์ํด ์ญํ ๋ง๋ค๊ธฐ ๋ฅผ ํด๋ฆญํด ์๋ก์ด ์ญํ ์ ๋ง๋ค์.
์ ๋ขฐํ ์ ์๋ ์ ํ์ ๊ฐ์ฒด ์ ํ ๋์์ AWS ์๋น์ค ์ ๋๋ ์๋น์ค๋ฅผ ์ ํํ์ฌ ํด๋น ์๋น์ค์ ์ฌ์ฉ ์ฌ๋ก ํ์ธ ๋์์ API Gateway๋ฅผ ์ ํํ์ ๋ชจ๋ ์คํ ์ด์ง๋ฅผ ๋ํดํธ๋ก ์ค์ ํ ๋ค ์ญํ ์ด๋ฆ์ ์ ํ๋ค. ์ญํ ์ด๋ฆ์ APIGatewayAccessS3๋ก ์ค์ ํ๋ค.
์ญํ ์ ๋ง๋ค์์ผ๋ ์ด ์ญํ ๊ณผ ์ฐ๊ฒฐ๋ ์ ์ฑ
์ ์์ฑํ๋ค. ์ก์ธ์ค ๊ด๋ฆฌ
โ ์ ์ฑ
์ ์ ์ํด ์ ์ฑ
์์ฑ์ ํด๋ฆญํ๋ค JSON ํญ์ ๋ค์ด๊ฐ ์๋ JSON ๋ฌธ์๋ฅผ ๋ถ์ฌ ๋ฃ๊ธฐํ๋ค.
{
"Version":"2012-10-17",
"Statement":[
{
"Effect":"Allow",
"Action":[
"s3:GetObject"
],
"Resource":"arn:aws:s3:::<bucket_name>/<object_name>"
}
]
}
<bucket_name>/<object_name>
์ ๊ฐ์์ bucket๊ณผ bucket์์ HTML ํ์ผ์ ๋ฃ์ด์ฃผ๋ฉด๋๋ค. ์ด๋ฒ ์ค์ต์ ์ด๋ฆ๊น์ง ๋๊ฐ์ด ๋ฐ๋ผํ๋ค๋ฉด iot-lamp-bucket/IoT_Lamp.html
์ ์ ์ด์ฃผ๋ฉด ๋๋ค. ์ ์ฑ
์ด๋ฆ์ IoTLampBucketAccessPolicy ๋ก ์ ํ๋ค.
๋ค์ ์ญํ ํญ์ผ๋ก ๋ค์ด๊ฐ APIGatewayAccessS3์ ๊ถํํญ์์ ๋ฐฉ๊ธ๋ง๋ IoTLampBucketAccessPolicy ์ ์ฑ ์ ์ฐ๊ฒฐํ๋ค.
์ด์ API Gateway์ IoT_Lamp_API์ ๋ฃจํธ ๋ฆฌ์์ค์์ ์๋ก์ด GET
๋ฉ์๋๋ฅผ ์์ฑํ์.
๋ฉ์๋๋ฅผ ์์ฑํ ๋ค ์ข์ธก ํญ์์ ์ค์ ๋์ผ๋ก ๋ค์ด๊ฐ ์ด์ง ๋ฏธ๋์ด ํ์ ์ ์ด์ง ๋ฏธ๋์ด ํ์ ์ถ๊ฐ ๋ฒํผ์ ๋๋ฅด๊ณ ๋ค์๊ณผ ๊ฐ์ด ์์ฑํ๋ค.
์ค์ ์ ๋ง์น๋ฉด ๋ค์ ๋ฐฐํฌ๋ฅผ ๋๋ฌ API๋ฅผ ๋ฐฐํฌํ๋ค ์น ๋ธ๋ผ์ฐ์ ธ์์ ๋ฃจํธ ๋ฆฌ์์ค๋ก ์ ์์ ์๋ํด๋ณด์.
Enter Thing Name์ ์ฐ๋ฆฌ๊ฐ ๋ง๋ ์ฌ๋ฌผ ์ด๋ฆ์ธ ESP32๋ฅผ ์ ๊ณ Pending Check ๋ฒํผ์ ๋๋ฅธ ํ on/off ๋ฅผ ํจ์๋ฐ๋ผ Lamp ์ญ์ on/off๊ฐ ๋๋ ๊ฒ์ ํ์ธํ ์ ์๋ค.
๋๋ฐ์ด์ค ์๋์ฐ ์ํ ๋ฌธ์๋ฅผ ํ์ธํ๋ฉด desired ์ reported์ status ๋ผ๋ ์์ฑ์ด ์ถ๊ฐ๋์์์ ํ์ธํ ์์๋ค.
์ด๋ฐ์ CloudWatchlog ๋ Mqtt Test๋ฅผ ์ด์ฉํด Lambda๋ฅผ ํธ๋ฆฌ๊ฑฐ๋ง์ด ์ ๋๋๊ฒ ์ญ์ ํ์ธ ๊ฐ๋ฅํ๋ค.