LoginSignup
6
4

More than 5 years have passed since last update.

AWS Lambda~(IoT)~ESP32の双方向通信

Last updated at Posted at 2018-07-08

前説

  • 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上のトピックにメッセージを投げています。

lambda_function.py
# 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上のコード

AWS_iot.ino
#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/

シャドウステータス

「管理」->「モノ」->「シャドウ」で確認できます。

shadow_status
{
  "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データは送られてこない。

$aws/things/ESP32/shadow/update/delta
{
  "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はされなくなった。

$aws/things/ESP32/shadow/update
{
  "state": {
    "desired": {
      "led": "on"
    }
  }
}

なんだかモヤモヤする・・・

次にやること

  • どうにかして制御
  • ルールの設定がやっぱり難しい・・・

参考文献

6
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
4