教學課程:透過 MQTT 與本地 IoT 裝置互動 - AWS IoT Greengrass

本文為英文版的機器翻譯版本,如內容有任何歧義或不一致之處,概以英文版為準。

教學課程:透過 MQTT 與本地 IoT 裝置互動

您可以完成此教學課程,將核心裝置設定為與透過 MQTT 連線至核心裝置的本機 IoT 裝置 (稱為用戶端裝置) 互動。在本教學課程中,您會設定AWS IoT要使用雲端探索連線至核心裝置做為用戶端裝置的項目。當您設定雲端探索時,用戶端裝置可以傳送要求至AWS IoT Greengrass雲端服務以探索核心裝置。來源的回應AWS IoT Greengrass包括您設定要探查的用戶端裝置之核心裝置的連線資訊和憑證。然後,用戶端裝置可以使用此資訊連接到可用的核心裝置,在此裝置可透過 MQTT 進行通訊。

在此教學課程中,您將執行下列操作:

  1. 如有需要,請檢閱並更新核心裝置的權限。

  2. 將用戶端裝置與核心裝置建立關聯,以便他們可以使用雲端探索來探索核心裝置。

  3. 將 Greengrass 元件部署到核心裝置,以啟用用戶端裝置支援。

  4. 將用戶端裝置 Connect 至核心裝置,並測試與AWS IoT Core雲端服務的通訊。

  5. 開發與用戶端裝置通訊的自訂 Greengrass 元件。

  6. 開發與用戶端AWS IoT裝置裝置陰影互動的自訂元件。

本教學課程使用單一核心裝置和單一用戶端裝置。您也可以按照教學課程連接和測試多個客戶端設備。

您可以預期在本教程中花費 30-60 分鐘。

必要條件

為了完成本教學,您需要以下項目:

  • AWS 帳戶。如果您沒有帳戶,請參閱 設定 AWS 帳戶

  • 具有管理員權限的 AWS Identity and Access Management (IAM) 使用者。

  • 一個 Greengrass 核心設備。如需如何設定核心裝置的詳細資訊,請參閱設定 AWS IoT Greengrass 核心裝置

    • 核心裝置必須執行 Greengrass 核 v2.6.0 或更新版本。此版本包括本地發布/訂閱通信中的通配符支持以及客戶端設備陰影的支持。

      注意

      用戶端裝置支援需要 Greengrass 核 v2.2.0 或更新版本。不過,本教學課程將探討較新的功能,例如支援本機發佈/訂閱中的 MQTT 萬用字元,以及用戶端裝置陰影的支援。這些功能需 Greengrass 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— (選用) IP 偵測器元件需要此權限,IP 偵測器元件會向AWS IoT Greengrass雲端服務報告核心裝置的網路連線資訊。

如需有關核心裝置的這些權限和AWS IoT原則的詳細資訊,請參閱資料平面操作的AWS IoT 政策支援用戶端裝置的最低AWS IoT原則

在本節中,您可以檢閱核心裝置的AWS IoT原則,並新增任何遺失的必要權限。如果您使用 AWS IoT GreengrassCore 軟體安裝程式佈建資源,您的核心裝置會有允許存取所有AWS IoT Greengrass動作的AWS IoT原則 (greengrass:*)。在此情況下,只有當您計劃將陰影管理員元件設定為同步處理裝置陰影時,才必須更新AWS IoT原則AWS IoT Core。否則,您可以跳過此部分。

檢閱和更新核心裝置的AWS IoT政策
  1. AWS IoT Greengrass主控台瀏覽功能表中,選擇 [核心裝置]。

  2. 在 [核心裝置] 頁面上,選擇要更新的核心裝置。

  3. 在核心裝置詳細資料頁面上,選擇核心裝置物件的連結。此連結會在AWS IoT主控台中開啟物件詳細資訊頁面。

  4. 在物件詳細資訊頁面上,選擇 [憑證]。

  5. 在「憑證」索引標籤中,選擇物件的使用中憑證。

  6. 在憑證詳細資料頁面上,選擇 [原則]。

  7. 在「略」索引標籤中,選擇要檢閱和更新的AWS IoT策略。您可以將必要的權限新增至任何附加至核心裝置作用中憑證的原則。

    注意

    如果您使用 AWS IoT GreengrassCore 軟體安裝程式佈建資源,則有兩個AWS IoT原則。建議您選擇名為GreengrassV2IoTThingPolicy的策略 (如果存在的話)。依預設,您使用快速安裝程式建立的核心裝置會使用此原則名稱。如果您將權限新增至此原則,也會將這些權限授與使用此原則的其他核心裝置。

  8. 在原則概觀中,選擇 [編輯作用中版本]。

  9. 檢閱所需權限的原則,並新增任何遺失的必要權限。

  10. 若要將新的原則版本設定為作用中版本,請在 [原則版本狀態] 下選取 [將編輯的版本設定為此原則的作用中版本]。

  11. 選擇「另存為新版本」。

步驟 2:啟用用戶端裝置支援

若要讓用戶端裝置使用雲端探索連線至核心裝置,您必須關聯這些裝置。當您將用戶端裝置與核心裝置建立關聯時,您可以讓該用戶端裝置擷取核心裝置的 IP 位址和憑證以用於連線。

若要讓用戶端裝置安全地連線至核心裝置並與 Greengrass 元件通訊AWS IoT Core,請將下列 Greengrass 元件部署到核心裝置:

  • 用戶端裝置驗證 (aws.greengrass.clientdevices.Auth)

    部署用戶端裝置驗證元件以驗證用戶端裝置並授權用戶端裝置動作。這個組件允許你的AWS IoT東西連接到一個核心設備。

    此組件需要一些配置才能使用它。您必須指定用戶端裝置群組,以及每個群組獲授權執行的作業,例如透過 MQTT 進行連線和通訊。如需詳細資訊,請參閱用戶端裝置驗證元件組態

  • MQTT 3.1.1 經紀商 (平均) (aws.greengrass.clientdevices.mqtt.Moquette)

    部署 Moquette MQTT 代理程式元件以執行輕量型 MQTT 代理程式。Moquette MQTT 代理程式符合 MQTT 3.1.1 規範,並包含對 QoS 0、QoS 1、QoS 2、保留訊息、最後將訊息和持續訂閱的本機支援。

    您不需要配置此組件即可使用它。不過,您可以設定此元件操作 MQTT 代理程式的連接埠。依預設,它會使用連接埠 8883。

  • MQTT 大橋 (aws.greengrass.clientdevices.mqtt.Bridge)

    (選擇性) 部署 MQTT 橋接器元件,以在用戶端裝置 (本機 MQTT)、本機發佈/訂閱和 MQTT 之間轉送訊息。AWS IoT Core設定此元件,以便與 Greengrass 元件的用戶端裝置同步處理用戶端裝置,AWS IoT Core並與其互動。

    此元件需要組態才能使用。您必須指定此元件轉送訊息的主題對映。如需詳細資訊,請參閱 MQTT 橋接器元件組態

  • IP 偵測器 (aws.greengrass.clientdevices.IPDetector)

    (選擇性) 部署 IP 偵測器元件,以自動向AWS IoT Greengrass雲端服務報告核心裝置的 MQTT 代理程式端點。如果您有複雜的網路設定,例如路由器將 MQTT 代理程式連接埠轉送至核心裝置的網路設定,則無法使用此元件。

    您不需要配置此組件即可使用它。

在本節中,您可以使用AWS IoT Greengrass主控台來關聯用戶端裝置,並將用戶端裝置元件部署到核心裝置。

啟用用戶端裝置支援
  1. 在左側導覽選單中,選擇 [核心裝置]。

  2. 在 [核心裝置] 頁面上,選擇要啟用用戶端裝置支援的核心裝置。

  3. 在核心裝置詳細資料頁面上,選擇 [用戶端裝置] 索引標籤。

  4. 用戶端裝置索引標籤上,選擇 [設定雲端探索]。

    [設定核心裝置探索] 頁面隨即開啟。您可以在此頁面上建立用戶端裝置與核心裝置的關聯,並部署用戶端裝置元件。此頁面會在 「步驟 1:選取目標核心裝置」中為您選取核心裝置

    注意

    您也可以使用此頁面來設定物件群組的核心裝置探索。如果您選擇此選項,您可以將用戶端裝置元件部署到物件群組中的所有核心裝置。但是,如果您選擇此選項,則必須在稍後建立部署後,手動將用戶端裝置與每個核心裝置相關聯。在本教學課程中,您會設定單一核心裝置。

  5. 步驟 2:關聯用戶端裝置中,將用戶端裝置的AWS IoT物件與核心裝置建立關聯。這可讓用戶端裝置使用雲端探索擷取核心裝置的連線資訊和憑證。請執行下列操作:

    1. 選擇關聯用戶端裝置

    2. 在「關聯用戶端裝置與核心裝置」強制回應中,輸入要關聯之AWS IoT物件的名稱。

    3. 選擇新增

    4. 選擇 Associate (關聯)。

  6. 步驟 3:設定和部署 Greengrass 元件中,部署元件以啟用用戶端裝置支援。如果目標核心裝置具有先前的部署,則此頁面會修訂該部署。否則,此頁面會為核心裝置建立新部署。執行下列動作來設定和部署用戶端裝置元件:

    1. 核心裝置必須執行 Greengrass 核 v2.6.0 或更新版本才能完成本教學課程。如果核心裝置執行舊版,請執行下列動作:

      1. 選取方塊以部署aws.greengrass.Nucleus元件。

      2. 對於aws.greengrass.Nucleus零組件,請選擇編輯模型組態

      3. 對於「元件」版本,請選擇版本 2.6.0 或更新版本。

      4. 選擇確認

      注意

      如果您從較早的次要版本升級 Greengrass 核心核心,且核心裝置執行依賴於核心的元件,您也必須將AWS提供的元件更新為較新版本。AWS稍後在本自學課程中檢閱部署時,您可以規劃這些元件的版本。如需詳細資訊,請參閱 更新AWS IoT Greengrass核心軟件(OTA)

    2. 對於aws.greengrass.clientdevices.Auth零組件,請選擇編輯模型組態

    3. 在用戶端裝置驗證元件的編輯組態模式中,設定允許用戶端裝置在核心裝置上發佈和訂閱 MQTT 代理程式的授權原則。請執行下列操作:

      1. 在 [設] 下的 [合併程式碼區塊的組態] 中,輸入下列組態,其中包含用戶端裝置授權原則。每個裝置群組授權原則都會指定一組處理行動以及用戶端裝置可以在其上執行這些處理行動的資源。

        • 此原則允許名稱開頭MyClientDevice為的用戶端裝置在所有 MQTT 主題上連線並進行通訊。將 MyClientDevice* 取代為要連接為用戶端裝置的AWS IoT物件名稱。您也可以使用與用戶端裝置名稱相符的*萬用字元來指定名稱。*萬用字元必須位於名稱的末尾。

          如果您有第二個用戶端裝置要連線,請將 MyOtherClientDevice* 取代為該用戶端裝置的名稱,或符合該用戶端裝置名稱的萬用字元模式。否則,您可以移除或保留選取規則的這個區段,以允許名稱相符的用戶端裝置MyOtherClientDevice*進行連線和通訊。

        • 此原則使用OR操作員也允許名稱開頭為的用戶端裝置在所有 MQTT 主題上進行連線和通訊。MyOtherClientDevice您可以在選取規則中移除此子句,或修改它以符合要連線的用戶端裝置。

        • 此原則允許用戶端裝置針對所有 MQTT 主題發佈和訂閱。若要遵循最佳安全性做法,請將mqtt:publishmqtt: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零組件,請選擇編輯模型組態

    5. 在 MQTT 橋接器元件的編輯組態模式中,設定將 MQTT 訊息從用戶端裝置轉送至的主題對應。AWS IoT Core請執行下列操作:

      1. 在「組態」下,在「要合併程式碼的組態」區塊中,輸入下列設定。此組態指定將clients/+/hello/world主題篩選器的 MQTT 訊息從用戶端裝置轉送至AWS IoT Core雲端服務。例如,此主題篩選符合clients/MyClientDevice1/hello/world主題。

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

        如需詳細資訊,請參閱 MQTT 橋接器元件組態

      2. 選擇確認

  7. 選擇複查並建置,以複查此頁面為您建立的部署。

  8. 如果您先前尚未在此區域中設定 Greengrass 服務角色,主控台會開啟強制回應來為您設定服務角色。用戶端裝置驗證元件使用此服務角色來驗證用戶端裝置的身分識別,而 IP 偵測器元件則使用此服務角色來管理核心裝置連線資訊。選擇 授予許可

  9. 在「複查」頁面上,選擇「部」以開始部署至核心裝置。

  10. 若要驗證部署是否成功,請檢查部署狀態,然後檢查核心裝置上的記錄檔。若要檢查核心裝置上的部署狀態,您可以在部署概觀中選擇目標。如需詳細資訊,請參閱下列內容:

步驟 3:Connect 用戶端裝置

用戶端裝置可以使用AWS IoT Device SDK來探索、連線和與核心裝置進行通訊。客戶端設備必須是一AWS IoT件事。如需詳細資訊,請參閱AWS IoT Core開發人員指南中的建立物件物件

在本節中,您將安裝適用於 Python Greengrass AWS IoT Device SDK v2,並從. AWS IoT Device SDK

注意

也提供其他程式設計語言版本AWS IoT Device SDK。本教學課程將 AWS IoT Device SDK v2 用於 Python,但您可以針對您的使用案例探索其他軟體開發套件。如需詳細資訊,請參閱AWS IoT Core開發人員指南中的AWS IoT裝置 SDK

將用戶端裝置連線到核心裝置
  1. 下載並安裝適用於 Python 的 AWS IoT Device SDK v2 到作為客戶端設備連接的AWS IoT東西。

    在用戶端裝置上,執行下列動作:

    1. 克隆 AWS IoT Device SDK v2 的 Python 存儲庫下載它。

      git clone https://github.com/aws/aws-iot-device-sdk-python-v2.git
    2. 安裝適用於 Python 的 AWS IoT Device SDK V2。

      python3 -m pip install --user ./aws-iot-device-sdk-python-v2
  2. 切換到示例文件夾在 AWS IoT Device SDK v2 中的 Python。

    cd aws-iot-device-sdk-python-v2/samples
  3. 執行範例 Greengrass 探索應用程式。此應用程式需要引數,這些引數會指定用戶端裝置物件名稱、要使用的 MQTT 主題和訊息,以及驗證和保護連線的憑證。下列範例會將 Hello World 訊息傳送至clients/MyClientDevice1/hello/world主題。

    注意

    本主題與您將 MQTT 橋接器設定為將郵件轉送至較早版本的主題相符。AWS IoT Core

    • MyClientDevice1 取代為用戶端裝置的物件名稱。

    • ~/證書/ AmazonRoot CA1.pem 取代為用戶端裝置上 Amazon 根 CA 憑證的路徑。

    • 〜/證書/設備 .pem.crt 替換為客戶端設備上的設備證書的路徑。

    • 〜/證書/私有 .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您可以使用主控台中的 MQTT 測試用戶端來訂閱 MQTT AWS IoT Core 主題篩選器。請執行下列操作:

    1. 導覽至 AWS IoT主控台

    2. 在左側導覽功能表的 [測試] 下,選擇 [MQTT 測試用戶端]。

    3. 在 [訂閱主題] 索引標籤上,對於 [主題] 篩選器,輸入clients/+/hello/world以訂閱來自核心裝置的用戶端裝置訊息。

    4. 選擇 Subscribe (訂閱)

    5. 再次在用戶端裝置上執行發佈/訂閱應用程式。

      MQTT 測試用戶端會針對符合此主題篩選器的主題,顯示您從用戶端裝置傳送的訊息。

步驟 4:開發與用戶端裝置通訊的元件

您可以開發與用戶端裝置通訊的 Greengrass 元件。元件使用處理序間通訊 (IPC)本機發佈/訂閱介面,在核心裝置上進行通訊。若要與用戶端裝置互動,請將 MQTT 橋接器元件設定為在用戶端裝置和本機發佈/訂閱介面之間轉送訊息。

在本節中,您將更新 MQTT 橋接器元件,以將訊息從用戶端裝置轉送至本機發佈/訂閱介面。然後,您開發一個組件,該組件可以訂閱這些消息,並在收到消息時打印消息。

開發與用戶端裝置通訊的元件
  1. 修訂核心裝置的部署,並將 MQTT 橋接器元件設定為將訊息從用戶端裝置轉送至本機發佈/訂閱。請執行下列操作:

    1. 在左側導覽選單中,選擇 [核心裝置]。

    2. 在 [核心裝置] 頁面上,選擇您在本教學課程中使用的核心裝置。

    3. 在核心裝置詳細資料頁面上,選擇 [用戶端裝置] 索引標籤。

    4. 用戶端裝置索引標籤上,選擇 [設定雲端探索]。

      [設定核心裝置探索] 頁面隨即開啟。在此頁面上,您可以變更或設定要部署到核心裝置的用戶端裝置元件。

    5. 步驟 3 中,為零組aws.greengrass.clientdevices.mqtt.Bridge件選擇編輯模型組態

    6. 在 MQTT 橋接器元件的編輯組態模式中,設定將 MQTT 訊息從用戶端裝置轉送至本機發佈/訂閱介面的主題對應。請執行下列操作:

      1. 在「組態」下,在「要合併程式碼的組態」區塊中,輸入下列設定。此組態指定將符合主題篩選器的主題從用戶端裝置轉送至AWS IoT Core雲端服務和本機 Greengrass 發佈/訂閱代理程式的 MQTT 訊息。clients/+/hello/world

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

        如需詳細資訊,請參閱 MQTT 橋接器元件組態

      2. 選擇確認

    7. 選擇複查並建置,以複查此頁面為您建立的部署。

    8. 在「複查」頁面上,選擇「部」以開始部署至核心裝置。

    9. 若要驗證部署是否成功,請檢查部署狀態,然後檢查核心裝置上的記錄檔。若要檢查核心裝置上的部署狀態,您可以在部署概觀中選擇目標。如需詳細資訊,請參閱下列內容:

  2. 開發並部署 Greengrass 元件,該元件會從用戶端裝置訂閱 Hello World 訊息。請執行下列操作:

    1. 在核心裝置上建立配方和成品的資料夾。

      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
      重要

      您必須針對人工因素資料夾路徑使用下列格式。包括您在方案中指定的元件名稱和版本。

      artifacts/componentName/componentVersion/
    2. 使用文字編輯器建立包含下列內容的元件配方。此配方指定為 Python 安裝 AWS IoT Device SDK v2,並執行訂閱主題並列印訊息的指令碼。

      例如,在 Linux 系統上,您可以執行下列命令來使用 GNU nano 來建立檔案。

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

      將下列配方複製到檔案中。

      { "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)
      注意

      這個組件使用的 IPC 客戶端 V2 中的 Python 與AWS IoT Greengrass核心軟件進行通信 AWS IoT Device SDK V 2。與原始 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. 在左側導覽選單中,選擇 [核心裝置]。

    2. 在 [核心裝置] 頁面上,選擇您在本教學課程中使用的核心裝置。

    3. 在核心裝置詳細資料頁面上,選擇 [用戶端裝置] 索引標籤。

    4. 用戶端裝置索引標籤上,選擇 [設定雲端探索]。

      [設定核心裝置探索] 頁面隨即開啟。在此頁面上,您可以變更或設定要部署到核心裝置的用戶端裝置元件。

    5. 步驟 3 中,為零組aws.greengrass.clientdevices.mqtt.Bridge件選擇編輯模型組態

    6. 在 MQTT 橋接器元件的編輯組態模式中,設定主題對應,以便在用戶端裝置與本機發佈/訂閱介面之間轉送裝置陰影主題的 MQTT 訊息。您也會確認部署指定了相容的 MQTT 橋接器版本。用戶端裝置陰影支援需要 MQTT 橋接器 v2.2.0 或更新版本。請執行下列操作:

      1. 對於「元件」版本,請選擇 2.2.0 或更新版本。

      2. 在「組態」下,在「要合併程式碼的組態」區塊中,輸入下列設定。此配置指定轉送有關陰影主題的 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. 步驟 3 中,選取要部署的aws.greengrass.ShadowManager元件。

    8. 選擇複查並建置,以複查此頁面為您建立的部署。

    9. 在「複查」頁面上,選擇「部」以開始部署至核心裝置。

    10. 若要驗證部署是否成功,請檢查部署狀態,然後檢查核心裝置上的記錄檔。若要檢查核心裝置上的部署狀態,您可以在部署概觀中選擇目標。如需詳細資訊,請參閱下列內容:

  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
      重要

      您必須針對人工因素資料夾路徑使用下列格式。包括您在方案中指定的元件名稱和版本。

      artifacts/componentName/componentVersion/
    2. 使用文字編輯器建立包含下列內容的元件配方。此配方指定為 Python 安裝 AWS IoT Device SDK v2,並執行與智慧燈用戶端裝置的陰影互動的指令碼,以管理其顏色。

      例如,在 Linux 系統上,您可以執行下列命令來使用 GNU nano 來建立檔案。

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

      將下列配方複製到檔案中。

      { "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 指令碼加工品。此應用程式使用 shadow 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 GreengrassCore 軟體都會傳送通知。當元件收到組態設定更新通知時,會更新其管理的智慧型照明用戶端裝置清單。

      • 獲取每個智能燈客戶端設備的陰影以獲取其初始顏色狀態。

      • 每 15 秒將每個智慧燈用戶端裝置的顏色設定為隨機顏色。元件會更新用戶端裝置的物件陰影,以變更其顏色。此作業會透過 MQTT 傳送陰影增量事件至用戶端裝置。

      • 使用 IPC 作業,在本機發佈/訂閱介面上訂閱陰影更新接受的SubscribeToTopic訊息。此元件會接收這些訊息,以追蹤每個智慧型照明用戶端裝置的色彩。當智慧型照明用戶端裝置收到陰影更新時,會傳送 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 探索並訂閱裝置陰影更新的範例應用程式。在用戶端裝置上,執行下列動作:

    1. 切換到示例文件夾在 AWS IoT Device SDK v2 中的 Python。此範例應用程式使用 sample 資料夾中的命令列剖析模組。

      cd aws-iot-device-sdk-python-v2/samples
    2. 使用文字編輯器建立以下列內容命名basic_discovery_shadow.py的 Python 指令碼。此應用程序使用 Greengrass 發現和陰影來保持客戶端設備和核心設備之間的屬性同步。

      例如,在 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 發現來發現並連接到核心設備。

      • 從核心裝置要求陰影文件以取得屬性的初始狀態。

      • 訂閱陰影增量事件,當內容的值與其desiredreported值不同時,核心裝置會傳送此事件。當應用程式收到 shadow delta 事件時,會變更內容的值,並將更新傳送至核心裝置,以將新值設定為其reported值。

      此應用程式結合了來自 v2 的 Greengrass 探索和陰影樣本。AWS IoT Device SDK

    3. 執行範例應用程式。此應用程式需要指定用戶端裝置物件名稱、要使用的 shadow 屬性,以及驗證和保護連線的憑證的引數。

      • MyClientDevice1 取代為用戶端裝置的物件名稱。

      • ~/證書/ AmazonRoot CA1.pem 取代為用戶端裝置上 Amazon 根 CA 憑證的路徑。

      • 〜/證書/設備 .pem.crt 替換為客戶端設備上的設備證書的路徑。

      • 〜/證書/私有 .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. 再次檢視元件記錄檔,以確認元件是否接收來自 Smart Light 用戶端裝置的陰影更新確認。

    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

    元件會記錄訊息,以確認 Smart Light 用戶端裝置已變更其顏色。

    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 訊息傳送至 Greengrass 元件,AWS IoT Core並接收來自核心裝置的陰影更新。如需本自學課程所涵蓋主題的詳細資訊,請參閱下列內容: