#前説
- AWS IoTとAWS Lambdaの連携
- AWS IoTとESP32の連携
#目的
AWS Lambda->ESP32へメッセージを送るために、AWS IoTのShadow機能を用いて実装してみました。
Lambdaのプログラムを実行すると、LEDが点灯するだけのプログラム。
#環境
- Arduino IDE
- ESP-WROOM-32
- AWS IoT
- AWS Lambda
- MQTT
- Pubsubclient
#相互通信
デバイス -> AWS IoT -> AWS他サービス
この流れならルールを用いたりして、結構充実している感じがした。しかし、その逆=アプリケーション->デバイス方面の実装にAWS IoT初心者としては少し戸惑った。
ネット情報も思っていたより、少なかったのでドキュメントを読み漁ったところ・・・
#Shadowとは
AWS IoTからデバイスに向けて、デバイスの設定を指示できる機能。アプリケーションとデバイス間の相互通信が可能であり、デバイスの現在の設定とアプリケーションが期待する設定の差分をとって、デバイスに設定変更を指示してくれる。
ただし、一度に送信できるJsonのデータサイズは非常に小さい。
今回の実装
デバイス「LED OFFだよ!」
Lambda「ONにしてこい」
IoT Shadow「あいよ」
デバイス「ONになった!」
#AWS Lambdaで使用したコード
AWS IoT上のトピックにメッセージを投げています。
# coding: utf-8
import json
import boto3
iot = boto3.client('iot-data')
#関数を定義
def lambda_handler(event, context):
#トピックを指定
topic = '$aws/things/ESP32/shadow/update'
#メッセージの内容
#形式通りのJsonでお願いします
payload = {"state": {"desired": {"led": "on"}}}
try:
#メッセージをPublish
iot.publish(
topic=topic,
qos=0,
payload=json.dumps(payload, ensure_ascii=False)
)
return "Succeeeded."
except Exception:
return "Failed."
#Arduino上のコード
#include <WiFiClientSecure.h>
#include <PubSubClient.h>
#include "ArduinoJson.h"
#define ID "ESP32"
#define OUT_TOPIC "$aws/things/[device]/shadow/update"
#define IN_TOPIC "$aws/things/[device]/shadow/update/delta"
#OUT_TOIC => Publishする先のトピック
#IN_TOIC => Publishする先のトピック
const char* ssid = "[ssid]";
const char* password = "[password]";
const char* server = "[end point].amazonaws.com";
const char* json = "{\"state\":{\"reported\":{\"led\":\"off\"}}}";
StaticJsonDocument<1024> doc;
const int port = 8883;
const int LED_pin = 15;
const char* Root_CA = \
"-----BEGIN CERTIFICATE-----\n" \
"-----END CERTIFICATE-----\n";
const char* Client_cert = \
"-----BEGIN CERTIFICATE-----\n" \
"-----END CERTIFICATE-----\n";
const char* Client_private = \
"-----BEGIN RSA PRIVATE KEY-----\n" \
"-----END RSA PRIVATE KEY-----\n";
WiFiClientSecure client;
PubSubClient mqttClient(client);
#AWS IoTとの接続
void connectAWSIoT() {
while (!mqttClient.connected()) {
if (mqttClient.connect("ESP32")) {
Serial.println("Connected.");
} else {
Serial.print("Failed. Error state=");
Serial.print(mqttClient.state());
//5秒待ち
delay(5000);
}
}
}
void setup()
{
Serial.begin(115200);
pinMode(LED_pin, OUTPUT);
delay(10);
Serial.print("Connecting to ");
Serial.println(ssid);
WiFi.begin(ssid, password);
delay(1000);
Serial.println(Root_CA);
Serial.println(Client_cert);
Serial.println(Client_private);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.println(".");
}
Serial.println("");
Serial.println("WiFi connected");
Serial.println("IP address: ");
Serial.println(WiFi.localIP());
client.setCACert(Root_CA);
client.setCertificate(Client_cert);
client.setPrivateKey(Client_private);
mqttClient.setServer(server, port);
mqttClient.setCallback(callback);
connectAWSIoT();
int qos = 0;
//通信できるデータの最大サイズ
Serial.println(MQTT_MAX_PACKET_SIZE);
if(mqttClient.subscribe(IN_TOPIC, qos)){
Serial.println("Subscribed.");
Serial.println("Success!!");
}
deserializeJson(doc, json);
JsonObject& obj = doc.as<JsonObject>();
if(mqttClient.publish(OUT_TOPIC, json)){
Serial.println("Published!!");
}
}
#subscribeした時に呼び出すコールバック関数
void callback (char* topic, byte* payload, unsigned int length) {
Serial.println("Received. topic=");
Serial.println(topic);
char subsc[length];
#文字の出力
for (int i = 0; i < length; i++) {
subsc[i] = (char)payload[i];
subsc[length]='\0';
Serial.print(subsc);
}
Serial.print("\n");
#Subscribeした際にLチカ
digitalWrite(LED_pin, HIGH);
}
void mqttLoop() {
#PubsubClientのmainLoop->データの取得を待っている
mqttClient.loop();
delay(100);
digitalWrite(LED_pin, LOW);
Serial.print(".");
}
void loop() {
mqttLoop();
delay(1000);
}
arduinoJson変換サイト -> https://arduinojson.org/v5/assistant/
#シャドウステータス
「管理」->「モノ」->「シャドウ」で確認できます。
{
"desired": {
"led": "on"
},
"reported": {
"led": "off"
},
"delta": {
"led": "on"
}
}
#大事なこと
qos -> 0か1。2はAWS IoTでは使えません!
Jsonデータは必ず形式通りに!
#ここまではスムーズに行った
しかし・・・Arduino上でJsonがSubscibeされないではないか!
Rejectされてた。
よってLチカもされない!
##原因
Jsonのbyte数が小さい時は、問題なく送受信できる。
しかし、/update/deltaで受信するJsonデータは送られてこない。
{
"version": 212,
"timestamp": 1531017344,
"state": {
"led": "on"
},
"metadata": {
"led": {
"timestamp": 1531017344
}
}
}
##考えてた案
- 実験として$aws/things/ESP32/shadow/update/deltaに送信される中身からmetadataを削除すると送れる -> 設定できない。
- ルールから送信するデータのカラムを設定する -> やり方がよくわかんない
- なんとかしてJsonデータを小さくしたい。
良い改善方法があればご教授お願いいたします。
#妥協案
とりあえず、IN_TOPICを$aws/things/ESP32/shadow/update/deltaとした差分を発見した時に、データを受信するようにしていた。
これを変更して$aws/things/ESP32/shadow/updateにして、トピックにLambdaからメッセージが来た時に、LEDが点灯するようにした。この場合、metadataが含まれず、送受信データ量も比較的小さく収まるので、データ量の制約によりRejectはされなくなった。
{
"state": {
"desired": {
"led": "on"
}
}
}
なんだかモヤモヤする・・・
#次にやること
- どうにかして制御
- ルールの設定がやっぱり難しい・・・
#参考文献
- https://pubsubclient.knolleary.net/api.html#subscribe
- https://docs.aws.amazon.com/ja_jp/iot/latest/developerguide/pub-sub-policy.html
- https://recipe.kc-cloud.jp/archives/10747
- https://techtutorialsx.com/2017/04/24/esp32-subscribing-to-mqtt-topic/
- https://blog.maripo.org/2017/07/esp32-aws-iot-troubleshooting/