チュートリアル: MQTT 経由でローカル IoT デバイスとやり取りする - AWS IoT Greengrass

チュートリアル: MQTT 経由でローカル IoT デバイスとやり取りする

このチュートリアルを完了すると、コアデバイスでローカル IoT デバイスとやり取りできるように設定できます。ローカル IoT デバイスはクライアントデバイスとよばれ、MQTT 経由でコアデバイスに接続されます。このチュートリアルでは、AWS IoT モノがクライアントデバイスとしてコアデバイスに接続するために、Cloud Discovery を使用するように設定します。Cloud Discovery を設定すると、クライアントデバイスはリクエストを AWS IoT Greengrass クラウドサービスに送信してコアデバイスを検出することができます。AWS IoT Greengrass からのレスポンスには、クライアントデバイスが検出するように設定したコアデバイスの接続情報と証明書が含まれています。その後、クライアントデバイスはこの情報を使用して利用可能なコアデバイスに接続することができ、そこから MQTT を介して操作できるようになります。

このチュートリアルでは、以下の作業を行います。

  1. 必要に応じて、コアデバイスの権限を確認して更新します。

  2. クライアントデバイスをコアデバイスに関連付けて、Cloud Discovery を使用してコアデバイスを検出できるようにします。

  3. Greengrass コンポーネントをコアデバイスにデプロイして、クライアントデバイスのサポートを有効にします。

  4. クライアントデバイスをコアデバイスに接続し、AWS IoT Core クラウドサービスとの通信をテストします。

  5. クライアントデバイスと通信する、カスタムの Greengrass コンポーネントを開発します。

  6. クライアントデバイスの AWS IoT デバイスシャドウを操作する、カスタムの Greengrass コンポーネントを開発します。

このチュートリアルでは、単一のコアデバイスと単一のクライアントデバイスを使用します。チュートリアルに従って、複数のクライアントデバイスを接続してテストすることもできます。

このチュートリアルは 30~60 分を要します。

前提条件

このチュートリアルを完了するには、以下が必要です。

  • AWS アカウント。アカウントをお持ちでない場合は、「AWS アカウント のセットアップ」を参照してください。

  • 管理者権限を持つ AWS Identity and Access Management (IAM) ユーザー。

  • Greengrass コアデバイス。コアデバイスの設定方法の詳細については、「AWS IoT Greengrass コアデバイスをセットアップする」を参照してください。

    • コアデバイスで、Greengrass nucleus v2.6.0 以降が実行されている必要があります このバージョンには、ローカルの公開/サブスクライブ通信でのワイルドカードのサポートと、クライアントでのデバイスシャドウに関するサポートが含まれています。

      注記

      クライアントデバイスのサポートには、Greengrass nucleus v2.2.0 以降が必要です。このチュートリアルでは、ローカルの公開/サブスクライブでの MQTT ワイルドカードのサポートや、クライアントでのデバイスシャドウのサポートなど、新しい機能について説明しています。これらの機能を使用するには、Greengrass nucleus v2.6.0 以降が必要です。

    • コアデバイスは、接続するクライアントデバイスと同じネットワーク上にある必要があります。

    • (オプション) カスタムの Greengrass コンポーネントを開発しているモジュールを完了するには、コアデバイスで Greengrass CLI を実行している必要があります。詳細については、「Greengrass CLI のインストール」を参照してください。

  • このチュートリアルで、クライアントデバイスとして接続する AWS IoT モノ。詳細については、「AWS IoT Core デベロッパーガイド」の「AWS IoT リソースを作成する」を参照してください。

    • クライアントデバイスの AWS IoT ポリシーでは、greengrass:Discover 権限を許可する必要があります。詳細については、「クライアントデバイス向けの最低限の AWS IoT ポリシー」を参照してください。

    • クライアントデバイスは、コアデバイスと同じネットワーク上にある必要があります。

    • クライアントデバイスは Python 3 を実行する必要があります。

    • クライアントデバイスは Git を実行する必要があります。

ステップ 1: コアデバイスの AWS IoT ポリシーを確認および更新する

クライアントデバイスをサポートするには、コアデバイスの AWS IoT ポリシーで、次のアクセス権限が付与されている必要があります。

  • greengrass:PutCertificateAuthorities

  • greengrass:VerifyClientDeviceIdentity

  • greengrass:VerifyClientDeviceIoTCertificateAssociation

  • greengrass:GetConnectivityInfo

  • greengrass:UpdateConnectivityInfo - (オプション) このアクセス許可は、コアデバイスのネットワーク接続に関する情報を AWS IoT Greengrass クラウドサービスに報告する IP 検出コンポーネントを使用するために必要です。

コアデバイスのための、これらのアクセス許可と AWS IoT ポリシーの詳細については、「データプレーンオペレーションの AWS IoT ポリシー」および「クライアントデバイスをサポートするための最低限の AWS IoT ポリシー」を参照してください。

このセクションでは、コアデバイスの AWS IoT ポリシーを確認し、必要なアクセス許可が不足している場合には追加します。AWS IoT Greengrass Core ソフトウェアインストーラを使用してリソースをプロビジョニングした場合、コアデバイスには、すべての AWS IoT Greengrass アクション (greengrass:*) へのアクセスを許可する AWS IoT ポリシーが存在します。この状態で、シャドウマネージャーコンポーネントを設定してデバイスシャドウを AWS IoT Core と同期させる場合には、AWS IoT ポリシーのみを更新する必要があります。それ以外の場合、このセクションはスキップできます。

コアデバイスの AWS IoT ポリシーを確認および更新するには
  1. AWS IoT Greengrass コンソールのナビゲーションメニューで、[Core devices] (コアデバイス) を選択します。

  2. [Core devices] (コアデバイス) ページで、更新するコアデバイスを選択します。

  3. コアデバイスの詳細ページで、コアデバイスの [Thing] (モノ) へのリンクを選択します。このリンクをクリックすると、AWS IoT コンソールの [thing details] (モノ詳細) ページが開きます。

  4. [thing details] (モノ詳細) ページで、[Certificates] (証明書) を選択します。

  5. [Certificates] (証明書) タブで、モノのアクティブな証明書を選択します。

  6. [certificate details] (証明書詳細) ページで、[Policies] (ポリシー) を選択します。

  7. [Policies] (ポリシー) タブで、確認して更新する AWS IoT ポリシーを選択します。コアデバイスのアクティブな証明書にアタッチされている任意のポリシーに、必要なアクセス許可を追加できます。

    注記

    リソースのプロビジョニングに AWS IoT Greengrass Core ソフトウェアインストーラを使用している場合、2 つの AWS IoT ポリシーが存在します。存在する場合は、GreengrassV2IoTThingPolicy という名前のポリシーを選択することをお勧めします。クイックインストーラで作成するコアデバイスは、デフォルトでこのポリシー名を使用します。このポリシーにアクセス許可を追加すると、このポリシーを使用する他のコアデバイスにもこれらのアクセス許可が付与されます。

  8. [policy overview] (ポリシーの概要) で、[Edit active version] (アクティブなバージョンの編集) を選択します。

  9. 必要なアクセス許可についてポリシーを確認し、不足していれば必要なアクセス許可を追加します。

  10. 新しいポリシーバージョンをアクティブなバージョンとして設定するには、[Policy version status] (ポリシーバージョンのステータス) で、[Set the edited version as the active version for this policy] (編集したバージョンをこのポリシーのアクティブバージョンとして設定) を選択します。

  11. [Save as new version] (新しいバージョンとして保存) を選択します。

ステップ 2: クライアントデバイスのサポートを有効にする

クライアントデバイスが Cloud Discovery を使用してコアデバイスに接続するには、デバイスを関連付ける必要があります。クライアントデバイスをコアデバイスに関連付けると、そのクライアントデバイスが、接続に使用するコアデバイスの IP アドレスと証明書を取得できるようになります。

クライアントデバイスがコアデバイスに安全に接続し、Greengrass コンポーネントと AWS IoT Core と通信できるようにするには、コアデバイスに次の Greengrass コンポーネントをデプロイします。

  • クライアントデバイス認証 (aws.greengrass.clientdevices.Auth)

    クライアントデバイス認証コンポーネントをデプロイして、クライアントデバイスを認証し、クライアントデバイスのアクションを認可します。このコンポーネントは、AWS IoT モノがコアデバイスに接続できるようにします。

    このコンポーネントを使用するには、いくつかの設定が必要です。クライアントデバイスのグループを指定して、MQTT を介した接続や通信などの各グループが実行することができる操作を指定する必要があります。詳細については、「クライアントデバイス認証コンポーネントの設定」を参照してください。

  • MQTT 3.1.1 ブローカー (モケット) (aws.greengrass.clientdevices.mqtt.Moquette)

    軽量な MQTT ブローカーを実行するため、Moquette MQTT ブローカーコンポーネントをデプロイします。Moquette MQTT ブローカーは MQTT 3.1.1 に準拠しており、QoS 0、QoS 1、QoS 2、保持されたメッセージ、Last Will メッセージ、および永続サブスクリプションに対するローカルサポートが含まれます。

    使用するにあたり、このコンポーネントを設定する必要はありません。ただし、このコンポーネントが MQTT ブローカを操作するポートを設定することができます。デフォルトではポート 8883 が使用されます。

  • MQTT ブリッジ (aws.greengrass.clientdevices.mqtt.Bridge)

    (オプション) MQTT ブリッジコンポーネントをデプロイして、クライアントデバイス (ローカル MQTT)、ローカルパブリッシュ/サブスクライブ、および AWS IoT Core MQTT 間でメッセージをリレーします。クライアントデバイスを AWS IoT Core と同期するようにこのコンポーネントを設定し、Greengrass コンポーネントからクライアントデバイスとやり取りします。

    このコンポーネントを使用するには、設定する必要があります。このコンポーネントがメッセージをリレーするトピックマッピングを指定する必要があります。詳細については、「MQTT ブリッジコンポーネントの設定」を参照してください。

  • IP ディテクター (aws.greengrass.clientdevices.IPDetector)

    (オプション) IP 検出コンポーネントをデプロイして、コアデバイスの MQTT ブローカエンドポイントを AWS IoT Greengrass クラウドサービスに自動的に報告します。ルータがコアデバイスに MQTT ブローカポートを転送する場合など、複雑なネットワーク設定がある場合には、このコンポーネントを使用することはできません。

    使用するにあたり、このコンポーネントを設定する必要はありません。

このセクションでは、AWS IoT Greengrass コンソールを使用して、クライアントデバイスを関連付けて、クライアントデバイスコンポーネントをコアデバイスにデプロイします。

クライアントデバイスのサポートを有効にするには
  1. 左のナビゲーションメニューで、[Core devices] (コアデバイス) を選択します。

  2. [Core devices] (コアデバイス) ページで、クライアントデバイスのサポートを有効にするコアデバイスを選択します。

  3. [core device details] (コアデバイスの詳細) ページで、[Client devices] (クライアントデバイス) タブを選択します。

  4. [Client devices] (クライアントデバイス) タブで、[Configure cloud discovery] (Cloud Discoveryを設定する) を選択します。

    [Configure core device discovery] (コアデバイスディスカバリを設定する) ページが開きます。このページでは、クライアントデバイスをコアデバイスに関連付けて、クライアントデバイスコンポーネントをデプロイすることができます。このページでは、[Step 1: Select target core devices] (ステップ 1: ターゲットコアデバイスを選択する) でコアデバイスを選択します。

    注記

    このページを使用して、モノグループのコアデバイスディスカバリを設定することもできます。このオプションを選択すると、モノグループ内のすべてのコアデバイスにクライアントデバイスコンポーネントをデプロイできます。ただし、このオプションを選択した場合は、デプロイを作成した後に、クライアントデバイスを各コアデバイスに手動で関連付ける必要があります。このチュートリアルでは、単一のコアデバイスを設定します。

  5. [Step 2: Associate client devices] (ステップ 2: クライアントデバイスを関連付ける) では、クライアントデバイスの AWS IoT モノをコアデバイスに関連付けます。こうすることで、クライアントデバイスが Cloud Discovery を使用して、コアデバイスの接続情報と証明書を取得できるようになります。以下の操作を実行します。

    1. [Associate client devices] (クライアントデバイスを関連付ける) を選択します。

    2. [Associate client devices with core device] (クライアントデバイスをコアデバイスに関連付ける) モーダルに関連付ける AWS IoT モノの名前を入力します。

    3. [追加] を選択します。

    4. [関連付ける] を選択します。

  6. [Step 3: Configure and deploy Greengrass components] (ステップ 3: Greengrass コンポーネントを設定およびデプロイする) では、コンポーネントをデプロイして、クライアントデバイスのサポートを有効にします。ターゲットコアデバイスに以前のデプロイが存在する場合、このページでそのデプロイが修正されます。そうでない場合は、このページがコアデバイスの新しいデプロイを作成します。クライアントデバイスコンポーネントを設定およびデプロイするには、次の手順を実行します。

    1. このチュートリアルを完了するには、コアデバイスが Greengrass nucleus v2.6.0 以降を実行している必要があります。これより前のバージョンがコアデバイスでを実行されている場合には、以下を実行します。

      1. ボックスを選択して aws.greengrass.Nucleus コンポーネントをデプロイします。

      2. aws.greengrass.Nucleus コンポーネントで [Edit configuration] (設定を編集する) を選択します。

      3. [Component version] (コンポーネントバージョン) で、バージョン 2.6.0 以降を選択します。

      4. [確認] を選択します。

      注記

      Greengrass nucleus を以前のマイナーバージョンからアップグレードする場合で、この nucleus に依存する AWS 提供のコンポーネントをコアデバイスが実行している際には、AWS 提供のコンポーネントの方も、新しいバージョンに更新する必要があります。このチュートリアルの後半でデプロイを確認するときに、これらのコンポーネントのバージョンを設定できます。詳細については、「AWS IoT Greengrass Core ソフトウェア (OTA) の更新」を参照してください。

    2. aws.greengrass.clientdevices.Auth コンポーネントで [Edit configuration] (設定を編集する) を選択します。

    3. クライアントデバイス認可コンポーネントの [設定を編集する] モーダルで、クライアントデバイスがコアデバイス上の MQTT ブローカをパブリッシュしサブスクライブできるようにする承認ポリシーを設定します。以下の操作を実行します。

      1. [設定] にある [マージする設定] コードブロックに次の設定を入力します。この設定にはクライアントデバイス承認ポリシーが含まれます。各デバイスグループの承認ポリシーは、クライアントデバイスがこれらのアクションを実行できるアクションセットとリソースを指定しています。

        • このポリシーでは、名前が MyClientDevice で始まるクライアントデバイスが、すべての MQTT トピックに接続し通信することを許可しています。MyClientDevice* をクライアントデバイスとして接続する AWS IoT モノの名前に置き換えます。クライアントデバイスの名前と一致する * ワイルドカードを使って、名前を指定することもできます。* ワイルドカードは、名前の末尾にくる必要があります。

          接続する 2 台目のクライアントデバイスがある場合は、MyotherClientDevice* をそのクライアントデバイスの名前、またはそのクライアントデバイスの名前と一致するワイルドカードパターンに置き換えます。ない場合は、削除するか、この選択ルールにある MyOtherClientDevice* と一致する名前のクライアントデバイスの接続と通信を許可するセクションをそのまま残しておくことができます。

        • このポリシーでは OR 演算子を使用して、名前が MyOtherClientDevice で始まるクライアントデバイスが、すべての MQTT トピックに接続し通信できるように許可しています。この部分は選択ルールから削除するか、接続するクライアントデバイスに合わせて修正することができます。

        • このポリシーは、クライアントデバイスがすべての MQTT トピックでパブリッシュしサブスクライブすることを許可しています。セキュリティのベストプラクティスに従うため、mqtt:publish および mqtt:subscribe 操作はクライアントデバイスが通信に使用するトピックの最小セットになるように制限してください。

        { "deviceGroups": { "formatVersion": "2021-03-05", "definitions": { "MyDeviceGroup": { "selectionRule": "thingName: MyClientDevice* OR thingName: MyOtherClientDevice*", "policyName": "MyClientDevicePolicy" } }, "policies": { "MyClientDevicePolicy": { "AllowConnect": { "statementDescription": "Allow client devices to connect.", "operations": [ "mqtt:connect" ], "resources": [ "*" ] }, "AllowPublish": { "statementDescription": "Allow client devices to publish to all topics.", "operations": [ "mqtt:publish" ], "resources": [ "*" ] }, "AllowSubscribe": { "statementDescription": "Allow client devices to subscribe to all topics.", "operations": [ "mqtt:subscribe" ], "resources": [ "*" ] } } } } }

        詳細については、「クライアントデバイス認証コンポーネントの設定」を参照してください。

      2. [確認] を選択します。

    4. aws.greengrass.clientdevices.mqtt.Bridge コンポーネントで [Edit configuration] (設定を編集する) を選択します。

    5. MQTT ブリッジコンポーネントの [Edit configuration] (設定を編集する) モーダルで、クライアントデバイスから AWS IoT Core に MQTT メッセージをリレーするトピックマッピングを設定します。以下の操作を実行します。

      1. [Configuration to merge] (マージする設定) コードブロック内にある [Configuration] (設定) に、次の設定を入力します。この設定では、クライアントデバイスから AWS IoT Core クラウドサービスに、clients/+/hello/world トピックフィルターの MQTT メッセージをリレーするように指定しています。例えば、このトピックフィルターは、clients/MyClientDevice1/hello/world トピックに一致します。

        { "mqttTopicMapping": { "HelloWorldIotCoreMapping": { "topic": "clients/+/hello/world", "source": "LocalMqtt", "target": "IotCore" } } }

        詳細については、「MQTT ブリッジコンポーネントの設定」を参照してください。

      2. [確認] を選択します。

  7. [Review and deploy] (レビューとデプロイ) を選択して、このページで作成されるデプロイを確認します。

  8. このリージョンの Greengrass サービスロールをこれまで設定したことがない場合、コンソールにモーダルが表示され、サービスロールが設定されます。クライアントデバイス認証コンポーネントは、このサービスロールを使用してクライアントデバイスの ID を検証し、IP 検出コンポーネントはこのサービスロールを使用してコアデバイスの接続情報を管理します。[Grant permissions] (アクセス許可の付与) を選択します。

  9. [Review] (レビュー) ページで、[Deploy] (デプロイ) を選択してコアデバイスへのデプロイを開始します。

  10. デプロイが成功したかどうかを確認するため、デプロイのステータスと、コアデバイスのログを確認します。コアデバイスでのデプロイのステータスを確認するには、デプロイ [Overview] (概要) にある [Target] (ターゲット) を選択できます。詳細については、次を参照してください:

ステップ 3: クライアントデバイスに接続する

クライアントデバイスでは AWS IoT Device SDK を使用して、コアデバイスを検出して接続し、通信することができます。クライアントデバイスは AWS IoT モノである必要があります。詳細については、「AWS IoT Core デベロッパーガイド」の「モノのオブジェクトを作成する」を参照してください。

このセクションでは、AWS IoT Device SDK v2 for Python をインストールして、AWS IoT Device SDK から Greengrass 検出サンプルアプリケーションを実行します。

注記

AWS IoT Device SDK は、他のプログラミング言語でも利用できます。このチュートリアルでは AWS IoT Device SDK v2 for Python を使用していますが、ユースケースに合わせて他の SDK を試すことができます。詳細については、「AWS IoT Core デベロッパーガイド」の「AWS IoT デバイス SDK」を参照してください。

クライアントデバイスをコアデバイスに接続するには
  1. AWS IoT Device SDK v2 for Python をクライアントデバイスとして接続する AWS IoT モノにダウンロードしてインストールします。

    クライアントデバイスで、次の操作を行います。

    1. AWS IoT Device SDK v2 for Python のリポジトリをクローンしてダウンロードします。

      git clone https://github.com/aws/aws-iot-device-sdk-python-v2.git
    2. AWS IoT Device SDK v2 for Python をインストールします。

      python3 -m pip install --user ./aws-iot-device-sdk-python-v2
  2. AWS IoT Device SDK v2 for Python のサンプルフォルダに移動します。

    cd aws-iot-device-sdk-python-v2/samples
  3. サンプルの Greengrass 検出アプリケーションを実行します。このアプリケーションでは、クライアントデバイスのモノの名前、使用する MQTT トピックとメッセージ、および接続を認証して保護する証明書を指定する引数が必要です。次の例では、clients/MyClientDevice1/hello/world トピックに「Hello World」メッセージを送信します。

    注記

    このトピックは、この前に、AWS IoT Core にメッセージをリレーするために MQTT ブリッジを設定した際のものと同じです。

    • MyClientDevice1 は、クライアントデバイスでのモノの名前に置き換えます。

    • ~/certs/AmazonRootCA1.pem をクライアントデバイスの Amazon ルート CA 証明書へのパスに置き換えます。

    • ~/certs/device.pem.crt をクライアントデバイスのデバイス証明書へのパスに置き換えます。

    • ~/certs/private.pem.key をクライアントデバイスのプライベートキーファイルへのパスに置き換えます。

    • us-east-1 をクライアントデバイスとコアデバイスが動作する AWS リージョンに置き換えます。

    python3 basic_discovery.py \\ --thing_name MyClientDevice1 \\ --topic 'clients/MyClientDevice1/hello/world' \\ --message 'Hello World!' \\ --ca_file ~/certs/AmazonRootCA1.pem \\ --cert ~/certs/device.pem.crt \\ --key ~/certs/private.pem.key \\ --region us-east-1 \\ --verbosity Warn

    検出サンプルアプリケーションはメッセージを 10 回送信し、その後切断します。また、メッセージの公開先と同じトピックにサブスクライブします。出力にアプリケーションがトピックに関する MQTT メッセージを受信したことが示される場合は、クライアントデバイスはコアデバイスと正常に通信できています。

    Performing greengrass discovery... awsiot.greengrass_discovery.DiscoverResponse(gg_groups=[awsiot.greengrass_discovery.GGGroup(gg_group_id='greengrassV2-coreDevice-MyGreengrassCore', cores=[awsiot.greengrass_discovery.GGCore(thing_arn='arn:aws:iot:us-east-1:123456789012:thing/MyGreengrassCore', connectivity=[awsiot.greengrass_discovery.ConnectivityInfo(id='203.0.113.0', host_address='203.0.113.0', metadata='', port=8883)])], certificate_authorities=['-----BEGIN CERTIFICATE-----\ MIICiT...EXAMPLE=\ -----END CERTIFICATE-----\ '])]) Trying core arn:aws:iot:us-east-1:123456789012:thing/MyGreengrassCore at host 203.0.113.0 port 8883 Connected! Published topic clients/MyClientDevice1/hello/world: {"message": "Hello World!", "sequence": 0} Publish received on topic clients/MyClientDevice1/hello/world b'{"message": "Hello World!", "sequence": 0}' Published topic clients/MyClientDevice1/hello/world: {"message": "Hello World!", "sequence": 1} Publish received on topic clients/MyClientDevice1/hello/world b'{"message": "Hello World!", "sequence": 1}' ... Published topic clients/MyClientDevice1/hello/world: {"message": "Hello World!", "sequence": 9} Publish received on topic clients/MyClientDevice1/hello/world b'{"message": "Hello World!", "sequence": 9}'

    アプリケーションが代わりにエラーを出力する場合は、「Greengrass 検出で生じる問題のトラブルシューティング」を参照してください。

    コアデバイスの Greengrass ログを表示して、クライアントデバイスが正常に接続してメッセージを送信しているかどうかを確認することもできます。詳細については、「AWS IoT Greengrass ログのモニタリング」を参照してください。

  4. MQTT ブリッジがクライアントデバイスからのメッセージをに AWS IoT Core リレーしていることを確認します。AWS IoT Core コンソールの MQTT テストクライアントを使用して、MQTT トピックフィルターにサブスクライブすることができます。以下の操作を実行します。

    1. AWS IoT コンソールに移動します。

    2. 左側のナビゲーションメニューの [Test] (テスト) で、[MQTT test client] (MQTT テストクライアント) を選択します。

    3. [Subscribe to a topic] (トピックへのサブスクライブ) タブの [Topic filter] (トピックのフィルター) に clients/+/hello/world と入力して、コアデバイスからクライアントデバイスメッセージをサブスクライブします。

    4. [サブスクライブ] を選択します。

    5. クライアントデバイスでパブリッシュ/サブスクライブアプリケーションを再度実行します。

      MQTT テストクライアントに、クライアントデバイスから送信したこのトピックフィルターに一致するトピックについてのメッセージが表示されます。

ステップ 4: クライアントデバイスと通信するコンポーネントを開発する

クライアントデバイスと通信する Greengrass コンポーネントを開発することができます。コンポーネントはプロセス間通信 (IPC)ローカルパブリッシュ/サブスクライブインターフェイスを使用して、コアデバイス上で通信します。クライアントデバイスとやり取りするには、クライアントデバイスとローカルの公開/サブスクライブインターフェース間でメッセージをリレーするように、MQTT ブリッジコンポーネントを設定します。

このセクションでは、クライアントデバイスからローカルのパブリッシュ/サブスクライブインターフェイスにメッセージをリレーするように、MQTT ブリッジコンポーネントを更新します。その後、これらのメッセージにサブスクライブし、メッセージを受信したときにメッセージを表示するコンポーネントを開発します。

クライアントデバイスと通信するコンポーネントを開発するには
  1. コアデバイスへのデプロイを修正し、クライアントデバイスからローカルパブリッシュ/サブスクライブにメッセージをリレーするように MQTT ブリッジコンポーネントを設定します。以下の操作を実行します。

    1. 左のナビゲーションメニューで、[Core devices] (コアデバイス) を選択します。

    2. [Core devices] (コアデバイス) ページで、このチュートリアルで使用するコアデバイスを選択します。

    3. [core device details] (コアデバイスの詳細) ページで、[Client devices] (クライアントデバイス) タブを選択します。

    4. [Client devices] (クライアントデバイス) タブで、[Configure cloud discovery] (Cloud Discoveryを設定する) を選択します。

      [Configure core device discovery] (コアデバイスディスカバリを設定する) ページが開きます。このページでは、コアデバイスにデプロイするクライアントデバイスコンポーネントを変更または設定できます。

    5. [Step 3] (ステップ 3) の aws.greengrass.clientdevices.mqtt.Bridge コンポーネントでは、[Edit configuration] (設定を編集する) を選択します。

    6. MQTT ブリッジコンポーネントの [Edit configuration] (設定を編集する) モーダルで、クライアントデバイスからローカルのパブリッシュ/サブスクライブインターフェースに MQTT メッセージをリレーするトピックマッピングを設定します。以下の操作を実行します。

      1. [Configuration to merge] (マージする設定) コードブロック内にある [Configuration] (設定) に、次の設定を入力します。この設定では、クライアントデバイスから AWS IoT Core クラウドサービスおよびローカル Greengrass のパブリッシュ/サブスクライブブローカーに、clients/+/hello/world トピックフィルターと一致するトピックに関する MQTT メッセージをリレーするように指定しています。

        { "mqttTopicMapping": { "HelloWorldIotCoreMapping": { "topic": "clients/+/hello/world", "source": "LocalMqtt", "target": "IotCore" }, "HelloWorldPubsubMapping": { "topic": "clients/+/hello/world", "source": "LocalMqtt", "target": "Pubsub" } } }

        詳細については、「MQTT ブリッジコンポーネントの設定」を参照してください。

      2. [確認] を選択します。

    7. [Review and deploy] (レビューとデプロイ) を選択して、このページで作成されるデプロイを確認します。

    8. [Review] (レビュー) ページで、[Deploy] (デプロイ) を選択してコアデバイスへのデプロイを開始します。

    9. デプロイが成功したかどうかを確認するため、デプロイのステータスと、コアデバイスのログを確認します。コアデバイスでのデプロイのステータスを確認するには、デプロイ [Overview] (概要) にある [Target] (ターゲット) を選択できます。詳細については、次を参照してください:

  2. クライアントデバイスからの Hello World メッセージをサブスクライブする Greengrass コンポーネントを開発してデプロイします。以下の操作を実行します。

    1. コアデバイスに、recipe とアーティファクト用のフォルダを作成します。

      Linux or Unix
      mkdir recipes mkdir -p artifacts/com.example.clientdevices.MyHelloWorldSubscriber/1.0.0
      Windows Command Prompt (CMD)
      mkdir recipes mkdir artifacts\com.example.clientdevices.MyHelloWorldSubscriber\1.0.0
      PowerShell
      mkdir recipes mkdir artifacts\com.example.clientdevices.MyHelloWorldSubscriber\1.0.0
      重要

      アーティファクトフォルダのパスには、次のフォーマットを使用する必要があります。recipe で指定したコンポーネント名とバージョンを含めてください。

      artifacts/componentName/componentVersion/
    2. テキストエディタを使用し、次の内容でコンポーネント recipe を作成します。この recipe では、AWS IoT Device SDK v2 for Python をインストールし、トピックにサブスクライブしてメッセージを出力するスクリプトを実行するように指定しています。

      例えば、Linux ベースのシステムでは、次のコマンドを実行し、GNU nano を使用してファイルを作成できます。

      nano recipes/com.example.clientdevices.MyHelloWorldSubscriber-1.0.0.json

      ファイル内には、次の recipe をコピーします。

      { "RecipeFormatVersion": "2020-01-25", "ComponentName": "com.example.clientdevices.MyHelloWorldSubscriber", "ComponentVersion": "1.0.0", "ComponentDescription": "A component that subscribes to Hello World messages from client devices.", "ComponentPublisher": "Amazon", "ComponentConfiguration": { "DefaultConfiguration": { "accessControl": { "aws.greengrass.ipc.pubsub": { "com.example.clientdevices.MyHelloWorldSubscriber:pubsub:1": { "policyDescription": "Allows access to subscribe to all topics.", "operations": [ "aws.greengrass#SubscribeToTopic" ], "resources": [ "*" ] } } } } }, "Manifests": [ { "Platform": { "os": "linux" }, "Lifecycle": { "install": "python3 -m pip install --user awsiotsdk", "run": "python3 -u {artifacts:path}/hello_world_subscriber.py" } }, { "Platform": { "os": "windows" }, "Lifecycle": { "install": "py -3 -m pip install --user awsiotsdk", "run": "py -3 -u {artifacts:path}/hello_world_subscriber.py" } } ] }
    3. テキストエディタを使用し、hello_world_subscriber.py という名前の Python スクリプトアーティファクトを、次の内容で作成します。このアプリケーションは、パブリッシュ/サブスクライブ IPC サービスを使用して、clients/+/hello/world トピックにサブスクライブし、受信したメッセージを出力します。

      例えば、Linux ベースのシステムでは、次のコマンドを実行し、GNU nano を使用してファイルを作成できます。

      nano artifacts/com.example.clientdevices.MyHelloWorldSubscriber/1.0.0/hello_world_subscriber.py

      ファイルに次の Python コードをコピーします。

      import sys import time import traceback from awsiot.greengrasscoreipc.clientv2 import GreengrassCoreIPCClientV2 CLIENT_DEVICE_HELLO_WORLD_TOPIC = 'clients/+/hello/world' TIMEOUT = 10 def on_hello_world_message(event): try: message = str(event.binary_message.message, 'utf-8') print('Received new message: %s' % message) except: traceback.print_exc() try: ipc_client = GreengrassCoreIPCClientV2() # SubscribeToTopic returns a tuple with the response and the operation. _, operation = ipc_client.subscribe_to_topic( topic=CLIENT_DEVICE_HELLO_WORLD_TOPIC, on_stream_event=on_hello_world_message) print('Successfully subscribed to topic: %s' % CLIENT_DEVICE_HELLO_WORLD_TOPIC) # Keep the main thread alive, or the process will exit. try: while True: time.sleep(10) except InterruptedError: print('Subscribe interrupted.') operation.close() except Exception: print('Exception occurred when using IPC.', file=sys.stderr) traceback.print_exc() exit(1)
      注記

      このコンポーネントでは、AWS IoT Device SDK v2 for Python の IPC クライアント V2 を使用して、AWS IoT Greengrass Core ソフトウェアと通信しています。オリジナルの IPC クライアントと比較して、IPC クライアント V2で は、カスタムコンポーネントで IPC を使用するために記述する必要があるコードの量が減っています。

    4. Greengrass CLI を使用してコンポーネントをデプロイします。

      Linux or Unix
      sudo /greengrass/v2/bin/greengrass-cli deployment create \ --recipeDir recipes \ --artifactDir artifacts \ --merge "com.example.clientdevices.MyHelloWorldSubscriber=1.0.0"
      Windows Command Prompt (CMD)
      C:\greengrass\v2/bin/greengrass-cli deployment create ^ --recipeDir recipes ^ --artifactDir artifacts ^ --merge "com.example.clientdevices.MyHelloWorldSubscriber=1.0.0"
      PowerShell
      C:\greengrass\v2/bin/greengrass-cli deployment create ` --recipeDir recipes ` --artifactDir artifacts ` --merge "com.example.clientdevices.MyHelloWorldSubscriber=1.0.0"
  3. コンポーネントログを表示して、コンポーネントが正常にインストールされ、トピックにサブスクライブされていることを確認します。

    Linux or Unix
    sudo tail -f /greengrass/v2/logs/com.example.clientdevices.MyHelloWorldSubscriber.log
    PowerShell
    gc C:\greengrass\v2/logs/com.example.clientdevices.MyHelloWorldSubscriber.log -Tail 10 -Wait

    コアデバイスがメッセージを受信しているかどうかを確認できるように、ログフィードは開いたままにしておくことができます。

  4. クライアントデバイスで、サンプルの Greengrass 検出アプリケーションを再度実行して、コアデバイスにメッセージを送信します。

    python3 basic_discovery.py \\ --thing_name MyClientDevice1 \\ --topic 'clients/MyClientDevice1/hello/world' \\ --message 'Hello World!' \\ --ca_file ~/certs/AmazonRootCA1.pem \\ --cert ~/certs/device.pem.crt \\ --key ~/certs/private.pem.key \\ --region us-east-1 \\ --verbosity Warn
  5. コンポーネントログをもう一度表示して、コンポーネントがクライアントデバイスからのメッセージを受信して出力していることを確認します。

    Linux or Unix
    sudo tail -f /greengrass/v2/logs/com.example.clientdevices.MyHelloWorldSubscriber.log
    PowerShell
    gc C:\greengrass\v2/logs/com.example.clientdevices.MyHelloWorldSubscriber.log -Tail 10 -Wait

ステップ 5: クライアントのデバイスシャドウを操作するコンポーネントを開発する

クライアントデバイスの AWS IoT デバイスシャドウとやり取りする、Greengrass コンポーネントを開発することができます。シャドウは、AWS IoT でのモノ (クライアントデバイスなど) に関する、現在または所望の状態についての情報を保存する JSON ドキュメントです。カスタムコンポーネントは、クライアントデバイスが AWS IoT に接続されていない場合でも、クライアントデバイスのシャドウにアクセスしてその状態を管理できます。AWS IoT のモノには、それぞれに名前のないシャドウがあり、また、モノごとに、名前付きシャドウを複数作成することができます。

このセクションでは、シャドウマネージャーコンポーネントをデプロイし、コアデバイスのシャドウを管理します。また、クライアントデバイスとシャドウマネージャーコンポーネント間でシャドウメッセージをリレーするように、MQTT ブリッジコンポーネントを更新します。その後、クライアントデバイスのシャドウを更新するコンポーネントを開発し、コンポーネントからのシャドウの更新に応答するサンプルアプリケーションを、クライアントデバイスで実行します。このコンポーネントは、コアデバイスにクライアントデバイスとして接続された、スマートライトの色の状態を管理する、スマートライト管理アプリケーションのものです。

クライアントのデバイスシャドウを操作するコンポーネントを開発するには
  1. シャドウマネージャーコンポーネントをデプロイするようにコアデバイスのデプロイを修正し、クライアントデバイスからローカルの公開/サブスクライブにシャドウメッセージをリレーするように、MQTT ブリッジコンポーネントを設定します。以下の操作を実行します。

    1. 左のナビゲーションメニューで、[Core devices] (コアデバイス) を選択します。

    2. [Core devices] (コアデバイス) ページで、このチュートリアルで使用するコアデバイスを選択します。

    3. [core device details] (コアデバイスの詳細) ページで、[Client devices] (クライアントデバイス) タブを選択します。

    4. [Client devices] (クライアントデバイス) タブで、[Configure cloud discovery] (Cloud Discoveryを設定する) を選択します。

      [Configure core device discovery] (コアデバイスディスカバリを設定する) ページが開きます。このページでは、コアデバイスにデプロイするクライアントデバイスコンポーネントを変更または設定できます。

    5. [Step 3] (ステップ 3) の aws.greengrass.clientdevices.mqtt.Bridge コンポーネントでは、[Edit configuration] (設定を編集する) を選択します。

    6. MQTT ブリッジコンポーネントの [Edit configuration] (設定を編集) モーダルで、クライアントデバイスとローカルの公開/サブスクライブインターフェース間で、デバイスシャドウトピックの MQTT メッセージを伝達するための、トピックマッピングを設定します。同時に、互換性のある MQTT ブリッジバージョンを、デプロイで指定されていることも確認します。クライアントでのデバイスシャドウのサポートには、MQTT ブリッジ v2.2.0 以降が必要です。以下の操作を実行します。

      1. [Component version] (コンポーネントバージョン) で、バージョン 2.2.0 以降を選択します。

      2. [Configuration to merge] (マージする設定) コードブロック内にある [Configuration] (設定) に、次の設定を入力します。この設定では、シャドウトピックで MQTT メッセージを伝達するように指定しています。

        { "mqttTopicMapping": { "HelloWorldIotCoreMapping": { "topic": "clients/+/hello/world", "source": "LocalMqtt", "target": "IotCore" }, "HelloWorldPubsubMapping": { "topic": "clients/+/hello/world", "source": "LocalMqtt", "target": "Pubsub" }, "ShadowsLocalMqttToPubsub": { "topic": "$aws/things/+/shadow/#", "source": "LocalMqtt", "target": "Pubsub" }, "ShadowsPubsubToLocalMqtt": { "topic": "$aws/things/+/shadow/#", "source": "Pubsub", "target": "LocalMqtt" } } }

        詳細については、「MQTT ブリッジコンポーネントの設定」を参照してください。

      3. [確認] を選択します。

    7. [Step 3] (ステップ 3) で、デプロイする aws.greengrass.ShadowManager コンポーネントを選択します。

    8. [Review and deploy] (レビューとデプロイ) を選択して、このページで作成されるデプロイを確認します。

    9. [Review] (レビュー) ページで、[Deploy] (デプロイ) を選択してコアデバイスへのデプロイを開始します。

    10. デプロイが成功したかどうかを確認するため、デプロイのステータスと、コアデバイスのログを確認します。コアデバイスでのデプロイのステータスを確認するには、デプロイ [Overview] (概要) にある [Target] (ターゲット) を選択できます。詳細については、次を参照してください:

  2. スマートライトのクライアントデバイスを管理する、Greengrass コンポーネントの開発とデプロイを行います。以下の操作を実行します。

    1. コンポーネントのアーティファクト用のフォルダーを、コアデバイスに作成します。

      Linux or Unix
      mkdir -p artifacts/com.example.clientdevices.MySmartLightManager/1.0.0
      Windows Command Prompt (CMD)
      mkdir artifacts\com.example.clientdevices.MySmartLightManager\1.0.0
      PowerShell
      mkdir artifacts\com.example.clientdevices.MySmartLightManager\1.0.0
      重要

      アーティファクトフォルダのパスには、次のフォーマットを使用する必要があります。recipe で指定したコンポーネント名とバージョンを含めてください。

      artifacts/componentName/componentVersion/
    2. テキストエディタを使用し、次の内容でコンポーネント recipe を作成します。この recipe では、AWS IoT Device SDK v2 for Python をインストールし、スマートライトのクライアントデバイスのシャドウを操作して色を管理するための、スクリプトを実行するように指定しています

      例えば、Linux ベースのシステムでは、次のコマンドを実行し、GNU nano を使用してファイルを作成できます。

      nano recipes/com.example.clientdevices.MySmartLightManager-1.0.0.json

      ファイル内には、次の recipe をコピーします。

      { "RecipeFormatVersion": "2020-01-25", "ComponentName": "com.example.clientdevices.MySmartLightManager", "ComponentVersion": "1.0.0", "ComponentDescription": "A component that interacts with smart light client devices.", "ComponentPublisher": "Amazon", "ComponentDependencies": { "aws.greengrass.Nucleus": { "VersionRequirement": "^2.6.0" }, "aws.greengrass.ShadowManager": { "VersionRequirement": "^2.2.0" }, "aws.greengrass.clientdevices.mqtt.Bridge": { "VersionRequirement": "^2.2.0" } }, "ComponentConfiguration": { "DefaultConfiguration": { "smartLightDeviceNames": [], "accessControl": { "aws.greengrass.ShadowManager": { "com.example.clientdevices.MySmartLightManager:shadow:1": { "policyDescription": "Allows access to client devices' unnamed shadows", "operations": [ "aws.greengrass#GetThingShadow", "aws.greengrass#UpdateThingShadow" ], "resources": [ "$aws/things/MyClientDevice*/shadow" ] } }, "aws.greengrass.ipc.pubsub": { "com.example.clientdevices.MySmartLightManager:pubsub:1": { "policyDescription": "Allows access to client devices' unnamed shadow updates", "operations": [ "aws.greengrass#SubscribeToTopic" ], "resources": [ "$aws/things/+/shadow/update/accepted" ] } } } } }, "Manifests": [ { "Platform": { "os": "linux" }, "Lifecycle": { "install": "python3 -m pip install --user awsiotsdk", "run": "python3 -u {artifacts:path}/smart_light_manager.py" } }, { "Platform": { "os": "windows" }, "Lifecycle": { "install": "py -3 -m pip install --user awsiotsdk", "run": "py -3 -u {artifacts:path}/smart_light_manager.py" } } ] }
    3. テキストエディタを使用し、smart_light_manager.py という名前の Python スクリプトアーティファクトを、次の内容で作成します。このアプリケーションは、シャドウ IPC サービスを使用してクライアントデバイスのシャドウを取得および更新します。また、ローカルの公開/サブスクライブ用 IPC サービスを使用して、報告されたシャドウアップデートの受け取りも行います。

      例えば、Linux ベースのシステムでは、次のコマンドを実行し、GNU nano を使用してファイルを作成できます。

      nano artifacts/com.example.clientdevices.MySmartLightManager/1.0.0/smart_light_manager.py

      ファイルに次の Python コードをコピーします。

      import json import random import sys import time import traceback from uuid import uuid4 from awsiot.greengrasscoreipc.clientv2 import GreengrassCoreIPCClientV2 from awsiot.greengrasscoreipc.model import ResourceNotFoundError SHADOW_COLOR_PROPERTY = 'color' CONFIGURATION_CLIENT_DEVICE_NAMES = 'smartLightDeviceNames' COLORS = ['red', 'orange', 'yellow', 'green', 'blue', 'purple'] SHADOW_UPDATE_TOPIC = '$aws/things/+/shadow/update/accepted' SET_COLOR_INTERVAL = 15 class SmartLightDevice(): def __init__(self, client_device_name: str, reported_color: str = None): self.name = client_device_name self.reported_color = reported_color self.desired_color = None class SmartLightDeviceManager(): def __init__(self, ipc_client: GreengrassCoreIPCClientV2): self.ipc_client = ipc_client self.devices = {} self.client_tokens = set() self.shadow_update_accepted_subscription_operation = None self.client_device_names_configuration_subscription_operation = None self.update_smart_light_device_list() def update_smart_light_device_list(self): # Update the device list from the component configuration. response = self.ipc_client.get_configuration( key_path=[CONFIGURATION_CLIENT_DEVICE_NAMES]) # Identify the difference between the configuration and the currently tracked devices. current_device_names = self.devices.keys() updated_device_names = response.value[CONFIGURATION_CLIENT_DEVICE_NAMES] added_device_names = set(updated_device_names) - set(current_device_names) removed_device_names = set(current_device_names) - set(updated_device_names) # Stop tracking any smart light devices that are no longer in the configuration. for name in removed_device_names: print('Removing %s from smart light device manager' % name) self.devices.pop(name) # Start tracking any new smart light devices that are in the configuration. for name in added_device_names: print('Adding %s to smart light device manager' % name) device = SmartLightDevice(name) device.reported_color = self.get_device_reported_color(device) self.devices[name] = device print('Current color for %s is %s' % (name, device.reported_color)) def get_device_reported_color(self, smart_light_device): try: response = self.ipc_client.get_thing_shadow( thing_name=smart_light_device.name, shadow_name='') shadow = json.loads(str(response.payload, 'utf-8')) if 'reported' in shadow['state']: return shadow['state']['reported'].get(SHADOW_COLOR_PROPERTY) return None except ResourceNotFoundError: return None def request_device_color_change(self, smart_light_device, color): # Generate and track a client token for the request. client_token = str(uuid4()) self.client_tokens.add(client_token) # Create a shadow payload, which must be a blob. payload_json = { 'state': { 'desired': { SHADOW_COLOR_PROPERTY: color } }, 'clientToken': client_token } payload = bytes(json.dumps(payload_json), 'utf-8') self.ipc_client.update_thing_shadow( thing_name=smart_light_device.name, shadow_name='', payload=payload) smart_light_device.desired_color = color def subscribe_to_shadow_update_accepted_events(self): if self.shadow_update_accepted_subscription_operation == None: # SubscribeToTopic returns a tuple with the response and the operation. _, self.shadow_update_accepted_subscription_operation = self.ipc_client.subscribe_to_topic( topic=SHADOW_UPDATE_TOPIC, on_stream_event=self.on_shadow_update_accepted_event) print('Successfully subscribed to shadow update accepted topic') def close_shadow_update_accepted_subscription(self): if self.shadow_update_accepted_subscription_operation is not None: self.shadow_update_accepted_subscription_operation.close() def on_shadow_update_accepted_event(self, event): try: message = str(event.binary_message.message, 'utf-8') accepted_payload = json.loads(message) # Check for reported states from smart light devices and ignore desired states from components. if 'reported' in accepted_payload['state']: # Process this update only if it uses a client token created by this component. client_token = accepted_payload.get('clientToken') if client_token is not None and client_token in self.client_tokens: self.client_tokens.remove(client_token) shadow_state = accepted_payload['state']['reported'] if SHADOW_COLOR_PROPERTY in shadow_state: reported_color = shadow_state[SHADOW_COLOR_PROPERTY] topic = event.binary_message.context.topic client_device_name = topic.split('/')[2] if client_device_name in self.devices: # Set the reported color for the smart light device. self.devices[client_device_name].reported_color = reported_color print( 'Received shadow update confirmation from client device: %s' % client_device_name) else: print("Shadow update doesn't specify color") except: traceback.print_exc() def subscribe_to_client_device_name_configuration_updates(self): if self.client_device_names_configuration_subscription_operation == None: # SubscribeToConfigurationUpdate returns a tuple with the response and the operation. _, self.client_device_names_configuration_subscription_operation = self.ipc_client.subscribe_to_configuration_update( key_path=[CONFIGURATION_CLIENT_DEVICE_NAMES], on_stream_event=self.on_client_device_names_configuration_update_event) print( 'Successfully subscribed to configuration updates for smart light device names') def close_client_device_names_configuration_subscription(self): if self.client_device_names_configuration_subscription_operation is not None: self.client_device_names_configuration_subscription_operation.close() def on_client_device_names_configuration_update_event(self, event): try: if CONFIGURATION_CLIENT_DEVICE_NAMES in event.configuration_update_event.key_path: print('Received configuration update for list of client devices') self.update_smart_light_device_list() except: traceback.print_exc() def choose_random_color(): return random.choice(COLORS) def main(): try: # Create an IPC client and a smart light device manager. ipc_client = GreengrassCoreIPCClientV2() smart_light_manager = SmartLightDeviceManager(ipc_client) smart_light_manager.subscribe_to_shadow_update_accepted_events() smart_light_manager.subscribe_to_client_device_name_configuration_updates() try: # Keep the main thread alive, or the process will exit. while True: # Set each smart light device to a random color at a regular interval. for device_name in smart_light_manager.devices: device = smart_light_manager.devices[device_name] desired_color = choose_random_color() print('Chose random color (%s) for %s' % (desired_color, device_name)) if desired_color == device.desired_color: print('Desired color for %s is already %s' % (device_name, desired_color)) elif desired_color == device.reported_color: print('Reported color for %s is already %s' % (device_name, desired_color)) else: smart_light_manager.request_device_color_change( device, desired_color) print('Requested color change for %s to %s' % (device_name, desired_color)) time.sleep(SET_COLOR_INTERVAL) except InterruptedError: print('Application interrupted') smart_light_manager.close_shadow_update_accepted_subscription() smart_light_manager.close_client_device_names_configuration_subscription() except Exception: print('Exception occurred', file=sys.stderr) traceback.print_exc() exit(1) if __name__ == '__main__': main()

      この Python アプリケーションは次を実行します。

      • コンポーネントの設定を読み取って、管理するスマートライトクライアントデバイスのリストを取得します。

      • SubscribeToConfigurationUpdate IPC オペレーションを使用して、設定の更新通知にサブスクライブします AWS IoT Greengrass Core ソフトウェアは、コンポーネントの設定が変更されるたびに通知を送信します。コンポーネントは、設定の更新通知を受信すると、管理しているスマートライトでのクライアントデバイスのリストを更新します。

      • 各スマートライトの、クライアントデバイスのシャドウを取得して、初期のカラー状態を取得します。

      • 各スマートライトのクライアントデバイスの色を 15 秒ごとにランダムに変更します。コンポーネントでは、色を変更するために、クライアントデバイスのモノのシャドウを更新してします。この操作は、MQTT 経由で、クライアントデバイスにシャドウの差分イベントを送信します。

      • SubscribeToTopic IPC オペレーションを使用して、ローカルの公開/サブスクライブインターフェイスでの、シャドウアップデート承認済みメッセージにサブスクライブします このコンポーネントは、各スマートライトのクライアントデバイスの色を追跡するために、これらのメッセージを受信します。スマートライトのクライアントデバイスがシャドウの更新を受信すると、MQTT メッセージを送信して、その更新の受信を確認します。MQTT ブリッジは、このメッセージをローカルの公開/サブスクライブインターフェイスに伝達します。

    4. Greengrass CLI を使用してコンポーネントをデプロイします。このコンポーネントをデプロイする際は、クライアントデバイス、smartLightDeviceNames、シャドウを管理する対象のリストを指定します。MyClientDevice1 は、クライアントデバイスでのモノの名前に置き換えます。

      Linux or Unix
      sudo /greengrass/v2/bin/greengrass-cli deployment create \ --recipeDir recipes \ --artifactDir artifacts \ --merge "com.example.clientdevices.MySmartLightManager=1.0.0" \ --update-config '{ "com.example.clientdevices.MySmartLightManager": { "MERGE": { "smartLightDeviceNames": [ "MyClientDevice1" ] } } }'
      Windows Command Prompt (CMD)
      C:\greengrass\v2/bin/greengrass-cli deployment create ^ --recipeDir recipes ^ --artifactDir artifacts ^ --merge "com.example.clientdevices.MySmartLightManager=1.0.0" ^ --update-config '{"com.example.clientdevices.MySmartLightManager":{"MERGE":{"smartLightDeviceNames":["MyClientDevice1"]}}}'
      PowerShell
      C:\greengrass\v2/bin/greengrass-cli deployment create ` --recipeDir recipes ` --artifactDir artifacts ` --merge "com.example.clientdevices.MySmartLightManager=1.0.0" ` --update-config '{ "com.example.clientdevices.MySmartLightManager": { "MERGE": { "smartLightDeviceNames": [ "MyClientDevice1" ] } } }'
  3. コンポーネントログを表示して、コンポーネントのインストールと実行が正常に行われていることを確認します。

    Linux or Unix
    sudo tail -f /greengrass/v2/logs/com.example.clientdevices.MySmartLightManager.log
    PowerShell
    gc C:\greengrass\v2/logs/com.example.clientdevices.MySmartLightManager.log -Tail 10 -Wait

    このコンポーネントは、スマートライトのクライアントデバイスの色を変更するリクエストを送信します。シャドウマネージャーがこのリクエストを受け取り、シャドウの desired ステートをセットします。しかし、スマートライトのクライアントデバイスはまだ動作していないので、シャドウの reported 状態は変更されません。このコンポーネントのログには、次のメッセージが含まれています。

    2022-07-07T03:49:24.908Z [INFO] (Copier) com.example.clientdevices.MySmartLightManager: stdout. Chose random color (blue) for MyClientDevice1. {scriptName=services.com.example.clientdevices.MySmartLightManager.lifecycle.Run, serviceName=com.example.clientdevices.MySmartLightManager, currentState=RUNNING} 2022-07-07T03:49:24.912Z [INFO] (Copier) com.example.clientdevices.MySmartLightManager: stdout. Requested color change for MyClientDevice1 to blue. {scriptName=services.com.example.clientdevices.MySmartLightManager.lifecycle.Run, serviceName=com.example.clientdevices.MySmartLightManager, currentState=RUNNING}

    コンポーネントがメッセージを出力した時点で確認ができるように、このログフィードは開いたままにしておくことができます。

  4. Greengrass Discovery を使用しデバイスシャドウの更新をサブスクライブする、サンプルアプリケーションをダウンロードして実行します。クライアントデバイスで、次の操作を行います。

    1. AWS IoT Device SDK v2 for Python のサンプルフォルダに移動します。このサンプルアプリケーションは、サンプルフォルダーにあるコマンドライン解析モジュールを使用します。

      cd aws-iot-device-sdk-python-v2/samples
    2. テキストエディタを使用して、次の内容を含む Python スクリプトを、basic_discovery_shadow.py という名前で作成します。このアプリケーションは、Greengrass Discovery とシャドウを使用して、クライアントデバイスとコアデバイス間でのプロパティの同期を維持します。

      例えば、Linux ベースのシステムでは、次のコマンドを実行し、GNU nano を使用してファイルを作成できます。

      nano basic_discovery_shadow.py

      ファイルに次の Python コードをコピーします。

      # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. # SPDX-License-Identifier: Apache-2.0. from awscrt import io from awscrt import mqtt from awsiot import iotshadow from awsiot.greengrass_discovery import DiscoveryClient from awsiot import mqtt_connection_builder from concurrent.futures import Future import sys import threading import traceback from uuid import uuid4 # Parse arguments import utils.command_line_utils; cmdUtils = utils.command_line_utils.CommandLineUtils("Basic Discovery - Greengrass discovery example with device shadows.") cmdUtils.add_common_mqtt_commands() cmdUtils.add_common_topic_message_commands() cmdUtils.add_common_logging_commands() cmdUtils.register_command("key", "<path>", "Path to your key in PEM format.", True, str) cmdUtils.register_command("cert", "<path>", "Path to your client certificate in PEM format.", True, str) cmdUtils.remove_command("endpoint") cmdUtils.register_command("thing_name", "<str>", "The name assigned to your IoT Thing", required=True) cmdUtils.register_command("region", "<str>", "The region to connect through.", required=True) cmdUtils.register_command("shadow_property", "<str>", "The name of the shadow property you want to change (optional, default='color'", default="color") # Needs to be called so the command utils parse the commands cmdUtils.get_args() # Using globals to simplify sample code is_sample_done = threading.Event() mqtt_connection = None shadow_thing_name = cmdUtils.get_command_required("thing_name") shadow_property = cmdUtils.get_command("shadow_property") SHADOW_VALUE_DEFAULT = "off" class LockedData: def __init__(self): self.lock = threading.Lock() self.shadow_value = None self.disconnect_called = False self.request_tokens = set() locked_data = LockedData() def on_connection_interupted(connection, error, **kwargs): print('connection interrupted with error {}'.format(error)) def on_connection_resumed(connection, return_code, session_present, **kwargs): print('connection resumed with return code {}, session present {}'.format(return_code, session_present)) # Try IoT endpoints until we find one that works def try_iot_endpoints(): for gg_group in discover_response.gg_groups: for gg_core in gg_group.cores: for connectivity_info in gg_core.connectivity: try: print('Trying core {} at host {} port {}'.format(gg_core.thing_arn, connectivity_info.host_address, connectivity_info.port)) mqtt_connection = mqtt_connection_builder.mtls_from_path( endpoint=connectivity_info.host_address, port=connectivity_info.port, cert_filepath=cmdUtils.get_command_required("cert"), pri_key_filepath=cmdUtils.get_command_required("key"), ca_bytes=gg_group.certificate_authorities[0].encode('utf-8'), on_connection_interrupted=on_connection_interupted, on_connection_resumed=on_connection_resumed, client_id=cmdUtils.get_command_required("thing_name"), clean_session=False, keep_alive_secs=30) connect_future = mqtt_connection.connect() connect_future.result() print('Connected!') return mqtt_connection except Exception as e: print('Connection failed with exception {}'.format(e)) continue exit('All connection attempts failed') # Function for gracefully quitting this sample def exit(msg_or_exception): if isinstance(msg_or_exception, Exception): print("Exiting sample due to exception.") traceback.print_exception(msg_or_exception.__class__, msg_or_exception, sys.exc_info()[2]) else: print("Exiting sample:", msg_or_exception) with locked_data.lock: if not locked_data.disconnect_called: print("Disconnecting...") locked_data.disconnect_called = True future = mqtt_connection.disconnect() future.add_done_callback(on_disconnected) def on_disconnected(disconnect_future): # type: (Future) -> None print("Disconnected.") # Signal that sample is finished is_sample_done.set() def on_get_shadow_accepted(response): # type: (iotshadow.GetShadowResponse) -> None try: with locked_data.lock: # check that this is a response to a request from this session try: locked_data.request_tokens.remove(response.client_token) except KeyError: return print("Finished getting initial shadow state.") if locked_data.shadow_value is not None: print(" Ignoring initial query because a delta event has already been received.") return if response.state: if response.state.delta: value = response.state.delta.get(shadow_property) if value: print(" Shadow contains delta value '{}'.".format(value)) change_shadow_value(value) return if response.state.reported: value = response.state.reported.get(shadow_property) if value: print(" Shadow contains reported value '{}'.".format(value)) set_local_value_due_to_initial_query(response.state.reported[shadow_property]) return print(" Shadow document lacks '{}' property. Setting defaults...".format(shadow_property)) change_shadow_value(SHADOW_VALUE_DEFAULT) return except Exception as e: exit(e) def on_get_shadow_rejected(error): # type: (iotshadow.ErrorResponse) -> None try: # check that this is a response to a request from this session with locked_data.lock: try: locked_data.request_tokens.remove(error.client_token) except KeyError: return if error.code == 404: print("Thing has no shadow document. Creating with defaults...") change_shadow_value(SHADOW_VALUE_DEFAULT) else: exit("Get request was rejected. code:{} message:'{}'".format( error.code, error.message)) except Exception as e: exit(e) def on_shadow_delta_updated(delta): # type: (iotshadow.ShadowDeltaUpdatedEvent) -> None try: print("Received shadow delta event.") if delta.state and (shadow_property in delta.state): value = delta.state[shadow_property] if value is None: print(" Delta reports that '{}' was deleted. Resetting defaults...".format(shadow_property)) change_shadow_value(SHADOW_VALUE_DEFAULT) return else: print(" Delta reports that desired value is '{}'. Changing local value...".format(value)) if (delta.client_token is not None): print (" ClientToken is: " + delta.client_token) change_shadow_value(value, delta.client_token) else: print(" Delta did not report a change in '{}'".format(shadow_property)) except Exception as e: exit(e) def on_publish_update_shadow(future): #type: (Future) -> None try: future.result() print("Update request published.") except Exception as e: print("Failed to publish update request.") exit(e) def on_update_shadow_accepted(response): # type: (iotshadow.UpdateShadowResponse) -> None try: # check that this is a response to a request from this session with locked_data.lock: try: locked_data.request_tokens.remove(response.client_token) except KeyError: return try: if response.state.reported != None: if shadow_property in response.state.reported: print("Finished updating reported shadow value to '{}'.".format(response.state.reported[shadow_property])) # type: ignore else: print ("Could not find shadow property with name: '{}'.".format(shadow_property)) # type: ignore else: print("Shadow states cleared.") # when the shadow states are cleared, reported and desired are set to None except: exit("Updated shadow is missing the target property") except Exception as e: exit(e) def on_update_shadow_rejected(error): # type: (iotshadow.ErrorResponse) -> None try: # check that this is a response to a request from this session with locked_data.lock: try: locked_data.request_tokens.remove(error.client_token) except KeyError: return exit("Update request was rejected. code:{} message:'{}'".format( error.code, error.message)) except Exception as e: exit(e) def set_local_value_due_to_initial_query(reported_value): with locked_data.lock: locked_data.shadow_value = reported_value def change_shadow_value(value, token=None): with locked_data.lock: if locked_data.shadow_value == value: print("Local value is already '{}'.".format(value)) return print("Changed local shadow value to '{}'.".format(value)) locked_data.shadow_value = value print("Updating reported shadow value to '{}'...".format(value)) reuse_token = token is not None # use a unique token so we can correlate this "request" message to # any "response" messages received on the /accepted and /rejected topics if not reuse_token: token = str(uuid4()) # if the value is "clear shadow" then send a UpdateShadowRequest with None # for both reported and desired to clear the shadow document completely. if value == "clear_shadow": tmp_state = iotshadow.ShadowState(reported=None, desired=None, reported_is_nullable=True, desired_is_nullable=True) request = iotshadow.UpdateShadowRequest( thing_name=shadow_thing_name, state=tmp_state, client_token=token, ) # Otherwise, send a normal update request else: # if the value is "none" then set it to a Python none object to # clear the individual shadow property if value == "none": value = None request = iotshadow.UpdateShadowRequest( thing_name=shadow_thing_name, state=iotshadow.ShadowState( reported={ shadow_property: value } ), client_token=token, ) future = shadow_client.publish_update_shadow(request, mqtt.QoS.AT_LEAST_ONCE) if not reuse_token: locked_data.request_tokens.add(token) future.add_done_callback(on_publish_update_shadow) if __name__ == '__main__': tls_options = io.TlsContextOptions.create_client_with_mtls_from_path(cmdUtils.get_command_required("cert"), cmdUtils.get_command_required("key")) if cmdUtils.get_command(cmdUtils.m_cmd_ca_file): tls_options.override_default_trust_store_from_path(None, cmdUtils.get_command(cmdUtils.m_cmd_ca_file)) tls_context = io.ClientTlsContext(tls_options) socket_options = io.SocketOptions() print('Performing greengrass discovery...') discovery_client = DiscoveryClient(io.ClientBootstrap.get_or_create_static_default(), socket_options, tls_context, cmdUtils.get_command_required("region")) resp_future = discovery_client.discover(cmdUtils.get_command_required("thing_name")) discover_response = resp_future.result() print(discover_response) if cmdUtils.get_command("print_discover_resp_only"): exit(0) mqtt_connection = try_iot_endpoints() shadow_client = iotshadow.IotShadowClient(mqtt_connection) try: # Subscribe to necessary topics. # Note that is **is** important to wait for "accepted/rejected" subscriptions # to succeed before publishing the corresponding "request". print("Subscribing to Update responses...") update_accepted_subscribed_future, _ = shadow_client.subscribe_to_update_shadow_accepted( request=iotshadow.UpdateShadowSubscriptionRequest(thing_name=shadow_thing_name), qos=mqtt.QoS.AT_LEAST_ONCE, callback=on_update_shadow_accepted) update_rejected_subscribed_future, _ = shadow_client.subscribe_to_update_shadow_rejected( request=iotshadow.UpdateShadowSubscriptionRequest(thing_name=shadow_thing_name), qos=mqtt.QoS.AT_LEAST_ONCE, callback=on_update_shadow_rejected) # Wait for subscriptions to succeed update_accepted_subscribed_future.result() update_rejected_subscribed_future.result() print("Subscribing to Get responses...") get_accepted_subscribed_future, _ = shadow_client.subscribe_to_get_shadow_accepted( request=iotshadow.GetShadowSubscriptionRequest(thing_name=shadow_thing_name), qos=mqtt.QoS.AT_LEAST_ONCE, callback=on_get_shadow_accepted) get_rejected_subscribed_future, _ = shadow_client.subscribe_to_get_shadow_rejected( request=iotshadow.GetShadowSubscriptionRequest(thing_name=shadow_thing_name), qos=mqtt.QoS.AT_LEAST_ONCE, callback=on_get_shadow_rejected) # Wait for subscriptions to succeed get_accepted_subscribed_future.result() get_rejected_subscribed_future.result() print("Subscribing to Delta events...") delta_subscribed_future, _ = shadow_client.subscribe_to_shadow_delta_updated_events( request=iotshadow.ShadowDeltaUpdatedSubscriptionRequest(thing_name=shadow_thing_name), qos=mqtt.QoS.AT_LEAST_ONCE, callback=on_shadow_delta_updated) # Wait for subscription to succeed delta_subscribed_future.result() # The rest of the sample runs asynchronously. # Issue request for shadow's current state. # The response will be received by the on_get_accepted() callback print("Requesting current shadow state...") with locked_data.lock: # use a unique token so we can correlate this "request" message to # any "response" messages received on the /accepted and /rejected topics token = str(uuid4()) publish_get_future = shadow_client.publish_get_shadow( request=iotshadow.GetShadowRequest(thing_name=shadow_thing_name, client_token=token), qos=mqtt.QoS.AT_LEAST_ONCE) locked_data.request_tokens.add(token) # Ensure that publish succeeds publish_get_future.result() except Exception as e: exit(e) # Wait for the sample to finish (user types 'quit', or an error occurs) is_sample_done.wait()

      この Python アプリケーションは次を実行します。

      • Greengrass Discovery を使用して、コアデバイスを検出し、それに接続します。

      • プロパティの初期状態を取得するために、コアデバイスに対しシャドウドキュメントをリクエストします。

      • シャドウの差分イベントにサブスクライブします。このイベントは、プロパティの desired 値が reported 値と異なった場合に、コアデバイスにより送信されます。シャドウの差分イベントを受信したアプリケーションは、プロパティの値を変更し、reported 値に新しい値を設定するためにコアデバイスに更新を送信します。

      このアプリケーションでは、AWS IoT Device SDK v2 の Greengrass Discovery とシャドウのサンプルを組み合わせて使用しています。

    3. サンプルアプリケーションを実行します。このアプリケーションでは、クライアントデバイスのモノの名前、使用するシャドウプロパティ、および接続の認証および保護のための証明書を、引数で指定する必要があります。

      • MyClientDevice1 は、クライアントデバイスでのモノの名前に置き換えます。

      • ~/certs/AmazonRootCA1.pem をクライアントデバイスの Amazon ルート CA 証明書へのパスに置き換えます。

      • ~/certs/device.pem.crt をクライアントデバイスのデバイス証明書へのパスに置き換えます。

      • ~/certs/private.pem.key をクライアントデバイスのプライベートキーファイルへのパスに置き換えます。

      • us-east-1 をクライアントデバイスとコアデバイスが動作する AWS リージョンに置き換えます。

      python3 basic_discovery_shadow.py \ --thing_name MyClientDevice1 \ --shadow_property color \ --ca_file ~/certs/AmazonRootCA1.pem \ --cert ~/certs/device.pem.crt \ --key ~/certs/private.pem.key \ --region us-east-1 \ --verbosity Warn

      このサンプルアプリケーションは、シャドウトピックをサブスクライブし、コアデバイスからシャドウの差分イベントを受信するまで待機します。アプリケーションがシャドウの差分イベントを受信して応答したことが、出力に表示されている場合は、クライアントデバイスがコアデバイスのシャドウと正常に対話をできています。

      Performing greengrass discovery... awsiot.greengrass_discovery.DiscoverResponse(gg_groups=[awsiot.greengrass_discovery.GGGroup(gg_group_id='greengrassV2-coreDevice-MyGreengrassCore', cores=[awsiot.greengrass_discovery.GGCore(thing_arn='arn:aws:iot:us-east-1:123456789012:thing/MyGreengrassCore', connectivity=[awsiot.greengrass_discovery.ConnectivityInfo(id='203.0.113.0', host_address='203.0.113.0', metadata='', port=8883)])], certificate_authorities=['-----BEGIN CERTIFICATE-----\nMIICiT...EXAMPLE=\n-----END CERTIFICATE-----\n'])]) Trying core arn:aws:iot:us-east-1:123456789012:thing/MyGreengrassCore at host 203.0.113.0 port 8883 Connected! Subscribing to Update responses... Subscribing to Get responses... Subscribing to Delta events... Requesting current shadow state... Received shadow delta event. Delta reports that desired value is 'purple'. Changing local value... ClientToken is: 3dce4d3f-e336-41ac-aa4f-7882725f0033 Changed local shadow value to 'purple'. Updating reported shadow value to 'purple'... Update request published.

      アプリケーションが代わりにエラーを出力する場合は、「Greengrass 検出で生じる問題のトラブルシューティング」を参照してください。

      コアデバイスの Greengrass ログを表示して、クライアントデバイスが正常に接続してメッセージを送信しているかどうかを確認することもできます。詳細については、「AWS IoT Greengrass ログのモニタリング」を参照してください。

  5. コンポーネントログをもう一度表示して、スマートライトのクライアントデバイスからのシャドウ更新確認を、そのコンポーネントが受信していることを検証します。

    Linux or Unix
    sudo tail -f /greengrass/v2/logs/com.example.clientdevices.MySmartLightManager.log
    PowerShell
    gc C:\greengrass\v2/logs/com.example.clientdevices.MySmartLightManager.log -Tail 10 -Wait

    コンポーネントは、スマートライトのクライアントデバイスが色を変更したことを、確認するメッセージをログに記録します。

    2022-07-07T03:49:24.908Z [INFO] (Copier) com.example.clientdevices.MySmartLightManager: stdout. Chose random color (blue) for MyClientDevice1. {scriptName=services.com.example.clientdevices.MySmartLightManager.lifecycle.Run, serviceName=com.example.clientdevices.MySmartLightManager, currentState=RUNNING} 2022-07-07T03:49:24.912Z [INFO] (Copier) com.example.clientdevices.MySmartLightManager: stdout. Requested color change for MyClientDevice1 to blue. {scriptName=services.com.example.clientdevices.MySmartLightManager.lifecycle.Run, serviceName=com.example.clientdevices.MySmartLightManager, currentState=RUNNING} 2022-07-07T03:49:24.959Z [INFO] (Copier) com.example.clientdevices.MySmartLightManager: stdout. Received shadow update confirmation from client device: MyClientDevice1. {scriptName=services.com.example.clientdevices.MySmartLightManager.lifecycle.Run, serviceName=com.example.clientdevices.MySmartLightManager, currentState=RUNNING}
注記

クライアントデバイスのシャドウは、コアデバイスとクライアントデバイスの間で同期を維持します。ただしコアデバイスは、AWS IoT Core を使用しているクライアントデバイスのシャドウとは同期しません。フリート内のすべてのデバイスの状態を表示または変更するためには、このシャドウを AWS IoT Core と同期させる必要が生じます。シャドウが AWS IoT Core と同期するようにシャドウマネージャーコンポーネントを設定する方法については、「ローカルデバイスシャドウを AWS IoT Core と同期する」を参照してください。

これで、このチュートリアルは終了です。クライアントデバイスはコアデバイスに接続され、MQTT メッセージを AWS IoT Core と Greengrass コンポーネントに送信し、コアデバイスからシャドウ更新を受信しています。このチュートリアルで説明しているトピックの詳細については、以下を参照してください。