AWS IoT, AWS IoT Device Shadow

AWS

この記事ではAWS IoT、AWS IoT Device Shadowについて解説します。AWS IoT、AWS IoT Device Shadowを使用することで、簡単にJetson Nanoなどのエッジデバイスをクラウドと連携させることが可能となります。エッジデバイスとクラウドを連携させることで、例えば動的にYOLOv5のモデルをクラウドからエッジデバイスに更新することが可能になります。

    以下の操作はAWSアカウントが必要です。

    AWS CLIのインストールとセットアップも必要です。

    AWS IoT

    AWS IoTを使用することで、MQTTという通信プロトコルを使用し、エッジデバイスとクラウドを簡単に接続することが可能となります。

    AWS IoTの準備として・モノ、・証明書、・ポリシーを作成し、・モノ証明書をアタッチし・証明書ポリシーをアタッチします。

    モノを作成

    以下でモノを作成します。以下ではtest-thingという名前のモノを作成します。

    aws iot create-thing --thing-name test-thingCode language: Bash (bash)

    証明書を作成し、モノにアタッチ

    以下で証明書を作成し、上で作成したモノtest-thingにアタッチします。

    $ aws iot create-keys-and-certificate --set-as-active --certificate-pem-outfile certificate.pem --public-key-outfile public.key --private-key-outfile private.key
    {
        "certificateArn": "arn:aws:iot:us-east-1:xxxxxxxxxxxx:cert/xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
        ...
    $ aws iot attach-thing-principal --principal arn:aws:iot:us-east-1:xxxxxxxxxxxx:cert/xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx --thing-name test-thingCode language: Bash (bash)

    aws iot attach-thing-principal--principalで指定するARN(Amazon Resource Name)は、aws iot create-keys-and-certificateで返されたcertificateArnを指定します。

    証明書は--certificate-pem-outfileで指定したcertificate.pem、秘密鍵は--private-key-outfileで指定したprivate.keyに作成されます。

    ポリシーを作成し、証明書にアタッチ

    ポリシーはモノ(正確にはモノにアタッチされた証明書)の通信を制限するための機構で、具体的にはMQTTを使用してデータをパブリッシュ・サブスクライブする際の通信を制限することができます。

    cat << EOS > thing-policy.json
    {
       "Version": "2012-10-17",
       "Statement": [{
             "Effect": "Allow",
             "Action": "iot:Connect",
             "Resource": "arn:aws:iot:us-east-1:xxxxxxxxxxxx:client/\${iot:Connection.Thing.ThingName}"
          },
          {
             "Effect": "Allow",
             "Action": "iot:Publish",
             "Resource": [
                "arn:aws:iot:us-east-1:xxxxxxxxxxxx:topic/\$aws/things/\${iot:Connection.Thing.ThingName}/shadow/update",
                "arn:aws:iot:us-east-1:xxxxxxxxxxxx:topic/\$aws/things/\${iot:Connection.Thing.ThingName}/shadow/delete",
                "arn:aws:iot:us-east-1:xxxxxxxxxxxx:topic/\$aws/things/\${iot:Connection.Thing.ThingName}/shadow/get"
    
             ]
          },
          {
             "Effect": "Allow",
             "Action": "iot:Receive",
             "Resource": [
                "arn:aws:iot:us-east-1:xxxxxxxxxxxx:topic/\$aws/things/\${iot:Connection.Thing.ThingName}/shadow/get/*",
                "arn:aws:iot:us-east-1:xxxxxxxxxxxx:topic/\$aws/things/\${iot:Connection.Thing.ThingName}/shadow/update/*",
                "arn:aws:iot:us-east-1:xxxxxxxxxxxx:topic/\$aws/things/\${iot:Connection.Thing.ThingName}/shadow/delete/*"
             ]
          },
          {
             "Effect": "Allow",
             "Action": "iot:Subscribe",
             "Resource": [
                "arn:aws:iot:us-east-1:xxxxxxxxxxxx:topicfilter/\$aws/things/\${iot:Connection.Thing.ThingName}/shadow/get/*",
                "arn:aws:iot:us-east-1:xxxxxxxxxxxx:topicfilter/\$aws/things/\${iot:Connection.Thing.ThingName}/shadow/update/*",
                "arn:aws:iot:us-east-1:xxxxxxxxxxxx:topicfilter/\$aws/things/\${iot:Connection.Thing.ThingName}/shadow/delete/*"
    
             ]
          },
          {
             "Effect": "Allow",
             "Action": [
                "iot:GetThingShadow",
                "iot:UpdateThingShadow",
                "iot:DeleteThingShadow"
             ],
             "Resource": "arn:aws:iot:us-east-1:xxxxxxxxxxxx:thing/\${iot:Connection.Thing.ThingName}"
          }
       ]
    }
    EOS
    aws iot create-policy --policy-name thing-policy --policy-document file://thing-policy.json
    aws iot attach-policy --policy-name thing-policy --target arn:aws:iot:us-east-1:xxxxxxxxxxxx:cert/xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxCode language: Bash (bash)

    ${iot:Connection.Thing.ThingName}はポリシー変数です。このポリシー変数を使用するとモノの名前として評価されます。

    arn:aws:iot:us-east-1:xxxxxxxxxxxxの部分は自身のアカウントIDに置き換えて下さい。アカウントIDは以下で取得可能です。

    aws sts get-caller-identity|jq -r .AccountCode language: Bash (bash)

    またaws iot attach-policy --policy-name thing-policy --targetで指定する、arn:aws:iot:us-east-1:xxxxxxx...aws iot attach-thing-principal--principalで使用したcertificateArnを指定します。

    注意点として以下のようなポリシーを使用してしまうと、このポリシーがアタッチされたモノはすべてのリソース(MQTTトピック)に対してパブリッシュ・サブスクライブすることが可能になります。これは別のモノへトピックをパブリッシュ(送信)することや、別のモノへのトピックをサブスクライブ(受信)することが可能になりますので一般的には危険で設定してはいけません。

    {
      "Version": "2012-10-17",
      "Statement": [
        {
          "Effect": "Allow",
          "Action": [
             "iot:Publish",
             "iot:Subscribe",
             "iot:Receive"
          ],
          "Resource": ["*"],
        },
      ]
    }Code language: JSON / JSON with Comments (json)

    AWS IoT Device Shadow

    AWS IoT Device SDK v2 for Pythonを使用してAWS IoT Device Shadowの動作を確認します。

    $ python3 -m pip install awsiotsdk
    $ git clone https://github.com/aws/aws-iot-device-sdk-python-v2.git
    $ curl -o AmazonRootCA1.pem https://www.amazontrust.com/repository/AmazonRootCA1.pemCode language: Bash (bash)

    aws-iot-device-sdk-python-v2/samples/shadow.pyを実行します。aws-iot-device-sdk-python-v2/samples/shadow.pyで指定するendpointaws iot describe-endpoint --endpoint-type iot:Data-ATSにより取得します。

    $ aws iot describe-endpoint --endpoint-type iot:Data-ATS
    {
        "endpointAddress": "xxxxxxxxxxxxxx-ats.iot.us-east-1.amazonaws.com"
    }
    $ python3 aws-iot-device-sdk-python-v2/samples/shadow.py --endpoint xxxxxxxxxxxxxx-ats.iot.us-east-1.amazonaws.com --key private.key --cert certificate.pem --client_id test-thing --ca_file AmazonRootCA1.pem --thing_name test-thing
    Connecting to xxxxxxxxxxxxxx-ats.iot.us-east-1.amazonaws.com with client ID 'test-thing-jetson'...
    Connected!
    Subscribing to Update responses...
    Subscribing to Get responses...
    Subscribing to Delta events...
    Requesting current shadow state...
    Launching thread to read user input...
    Thing has no shadow document. Creating with defaults...
    Changed local shadow value to 'off'.
    Updating reported shadow value to 'off'...
    Update request published.
    Finished updating reported shadow value to 'off'.
    Enter desired value: 
    Code language: Bash (bash)

    shadow.pyは以下のような機能を実現するためのサンプルです。

    1. 操作端末(ウェブアプリやスマートフォンアプリなど)で照明の色の変更を指示
    2. 変更の指示を受けた照明デバイスは、指定された色に従って照明の色を変更

    shadow.pyで使用されるAWS IoT Device Shadowはクラウドとエッジデバイスで共有される以下のJSON形式のデータを持ちます。

    {
      "state": {
        "desired": {
          "color": "red",
        },
        "reported": {
          "color": "blue"
        },
        "delta": {
          "color": "red",
        }
      },
      "version": 1,
      "timestamp": 1669898853
    }Code language: JSON / JSON with Comments (json)

    サンプルshadow.pyを実際の製品として実装する際、照明の色の更新は以下の流れで行われます。

    • 操作端末は
      1. state.desiredに更新したい照明の色を設定する
    • 照明デバイスは
      1. state.desiredstate.reportedの差分を受信し
      2. state.desiredに書かれた値に照明の色を変更し
        • サンプルに照明の色を変更するコードはありません
      3. state.reportedに変更した照明の色を報告する

    IoT Device Shadowで重要なポイントは以下の通りです。

    • 操作端末側はstate.desiredのみ変更します。
    • 照明デバイスはstate.reportedのみ変更します。
    • state.desiredstate.reportedに差分が発生した場合、state.deltaに差分情報が自動的に作成されます。
      • 差分情報をサブスクライブしている場合は、サブスクライブ設定時に指定したコールバック関数が呼び出されます。
    • versionは古いリクエストを捨てるために使用します。もしstate.desiredの更新が複数回実施され、state.deltaをサブスクライブするコールバックが複数回呼び出された場合、state.desiredの更新の順番にstate.deltaのコールバックが呼び出されるとは限らないため、サブスクライブしたversionより古いversionが届いた場合は破棄します。
      下記の図で照明デバイスは最新のversionを保存しておき、最新のversionより値の低い、後から届いた"version": 1のメッセージを単に破棄します。

    エッジデバイスとクラウドとの接続は別の記事で解説します。

    以上です。

    参照

    AWS IoT Device Shadow サービス - AWS IoT Core
    のシャドウ AWS IoT、状態情報の保存と取得に使用されるJSONドキュメント、およびこれらのドキュメントが保存されている Device Shadow サービスについて説明します。
    GitHub - aws/aws-iot-device-sdk-python-v2: Next generation AWS IoT Client SDK for Python using the AWS Common Runtime
    Next generation AWS IoT Client SDK for Python using the AWS Common Runtime - aws/aws-iot-device-sdk-python-v2
    [AWS IoT] ポリシー変数で複数のモノをセキュアに管理してみた | DevelopersIO