本文為英文版的機器翻譯版本,如內容有任何歧義或不一致之處,概以英文版為準。
您可以完成此教學課程,以設定核心裝置與透過 連接至核心裝置的本機 IoT 裝置互動,稱為用戶端裝置MQTT。在本教學課程中,您會將 AWS IoT 物件設定為使用雲端探索,以用戶端裝置的形式連接至核心裝置。當您設定雲端探索時,用戶端裝置可以將請求傳送至 AWS IoT Greengrass 雲端服務,以探索核心裝置。的回應 AWS IoT Greengrass 包含您設定用戶端裝置探索的核心裝置的連線資訊和憑證。然後,用戶端裝置可以使用此資訊連線到可用的核心裝置,以便透過 進行通訊MQTT。
在此教學課程中,您將執行下列操作:
-
視需要檢閱並更新核心裝置的許可。
-
將用戶端裝置與核心裝置建立關聯,以便他們可以使用雲端探索來探索核心裝置。
-
將 Greengrass 元件部署至核心裝置,以啟用用戶端裝置支援。
-
將用戶端裝置連接至核心裝置,並測試與雲端服務的 AWS IoT Core 通訊。
-
開發與用戶端裝置通訊的自訂 Greengrass 元件。
-
開發與用戶端裝置AWS IoT 影子互動的自訂元件。
本教學課程使用單一核心裝置和單一用戶端裝置。您也可以遵循教學課程來連接和測試多個用戶端裝置。
您可以預期在本教學課程中花費 30-60 分鐘。
必要條件
為了完成本教學,您需要以下項目:
-
AWS 帳戶:如果您沒有帳戶,請參閱 設定 AWS 帳戶。
-
具有管理員許可的 AWS Identity and Access Management (IAM) 使用者。
-
Greengrass 核心裝置。如需如何設定核心裝置的詳細資訊,請參閱設定 AWS IoT Greengrass 核心裝置。
-
核心裝置必須執行 Greengrass nucleus v2.6.0 或更新版本。此版本包含支援本機發佈/訂閱通訊中的萬用字元,以及支援用戶端裝置影子。
注意
用戶端裝置支援需要 Greengrass nucleus 2.2.0 版或更新版本。不過,本教學課程會探索較新的功能,例如支援本機發佈/訂閱中的MQTT萬用字元,以及支援用戶端裝置影子。這些功能需要 Greengrass nucleus 2.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 偵測器元件,該元件會將核心裝置的網路連線資訊報告給 AWS IoT Greengrass 雲端服務。
如需核心裝置的這些許可和 AWS IoT 政策的詳細資訊,請參閱 資料平面操作的AWS IoT 政策和 支援用戶端裝置的最低AWS IoT原則。
在本節中,您可以檢閱核心裝置的 AWS IoT 政策,並新增任何缺少的必要許可。如果您使用 AWS IoT Greengrass Core 軟體安裝程式佈建資源,您的核心裝置具有允許存取所有 AWS IoT Greengrass 動作 () AWS IoT 的政策greengrass:*
。在此情況下,只有在您計劃設定影子管理員元件以與裝置影子同步時,才必須更新 AWS IoT 政策 AWS IoT Core。否則,您可以略過本節。
檢閱和更新核心裝置的 AWS IoT 政策
-
在AWS IoT Greengrass 主控台
導覽功能表中,選擇核心裝置。 -
在核心裝置頁面上,選擇要更新的核心裝置。
-
在核心裝置詳細資訊頁面上,選擇核心裝置物件的連結。此連結會在 主控台中 AWS IoT 開啟物件詳細資訊頁面。
-
在物件詳細資訊頁面上,選擇憑證。
-
在憑證索引標籤中,選擇物件的作用中憑證。
-
在憑證詳細資訊頁面上,選擇政策。
-
在政策索引標籤中,選擇要檢閱和更新 AWS IoT 的政策。您可以將所需的許可新增至連接到核心裝置作用中憑證的任何政策。
注意
如果您使用 AWS IoT Greengrass Core 軟體安裝程式佈建資源,則有兩個 AWS IoT 政策。我們建議您選擇名為 的政策 GreengrassV2IoTThingPolicy,如果存在的話。您使用快速安裝程式建立的核心裝置預設會使用此政策名稱。如果您將許可新增至此政策,則也會將這些許可授予使用此政策的其他核心裝置。
-
在政策概觀中,選擇編輯作用中版本。
-
檢閱必要許可的政策,並新增任何缺少的必要許可。
-
若要將新的政策版本設定為作用中版本,請在政策版本狀態下,選取將編輯版本設定為此政策的作用中版本。
-
選擇儲存為新版本。
步驟 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)、本機發佈/訂閱 和 之間轉送訊息 AWS IoT Core MQTT。設定此元件,以從 Greengrass 元件同步用戶端裝置與用戶端裝置, AWS IoT Core 以及與用戶端裝置互動。
此元件需要使用組態。您必須指定此元件轉送訊息的主題映射。如需詳細資訊,請參閱MQTT橋接元件組態。
-
IP 偵測器 (
aws.greengrass.clientdevices.IPDetector
)(選用) 部署 IP 偵測器元件,以自動將核心裝置的MQTT代理程式端點報告給 AWS IoT Greengrass 雲端服務。如果您有複雜的網路設定,例如路由器將MQTT代理程式連接埠轉送至核心裝置的網路設定,則無法使用此元件。
您不需要設定此元件即可使用它。
在本節中,您可以使用 AWS IoT Greengrass 主控台來建立用戶端裝置的關聯,並將用戶端裝置元件部署至核心裝置。
啟用用戶端裝置支援
-
在左側導覽功能表中,選擇核心裝置。
-
在核心裝置頁面上,選擇您要啟用用戶端裝置支援的核心裝置。
-
在核心裝置詳細資訊頁面上,選擇用戶端裝置索引標籤。
-
在用戶端裝置索引標籤上,選擇設定雲端探索。
設定核心裝置探索頁面隨即開啟。在此頁面上,您可以將用戶端裝置與核心裝置建立關聯,並部署用戶端裝置元件。此頁面會在步驟 1:選取目標核心裝置中為您選取核心裝置。
注意
您也可以使用此頁面來設定物件群組的核心裝置探索。如果您選擇此選項,您可以將用戶端裝置元件部署到物件群組中的所有核心裝置。不過,如果您選擇此選項,則必須在建立部署之後,手動將用戶端裝置與每個核心裝置建立關聯。在本教學課程中,您會設定單一核心裝置。
-
在步驟 2:關聯用戶端裝置,將用戶端裝置的 AWS IoT 物件與核心裝置建立關聯。這可讓用戶端裝置使用雲端探索來擷取核心裝置的連線資訊和憑證。請執行下列操作:
-
選擇關聯用戶端裝置。
-
在將用戶端裝置與核心裝置模型建立關聯中,輸入要建立關聯的 AWS IoT 物件名稱。
-
選擇新增。
-
選擇關聯。
-
-
在步驟 3:設定和部署 Greengrass 元件,部署元件以啟用用戶端裝置支援。如果目標核心裝置有先前的部署,此頁面會修訂該部署。否則,此頁面會為核心裝置建立新的部署。執行下列動作來設定和部署用戶端裝置元件:
-
核心裝置必須執行 Greengrass nucleus 2.6.0 版或更新版本,才能完成本教學課程。如果核心裝置執行舊版,請執行下列動作:
-
選取方塊以部署 aws.greengrass.Nucleus 元件。
-
對於 aws.greengrass.Nucleus 元件,選擇編輯組態。
-
針對元件版本,選擇 2.6.0 版或更新版本。
-
選擇確認。
注意
如果您從較早的次要版本升級 Greengrass 核,且核心裝置執行 提供的元件,而這些AWS元件依賴於核,則您也必須將 AWS提供的元件更新為較新的版本。您可以在本教學稍後檢閱部署時設定這些元件的版本。如需詳細資訊,請參閱更新AWS IoT Greengrass核心軟件(OTA)。
-
-
對於 aws.greengrass.clientdevices.Auth 元件,選擇編輯組態。
-
在編輯用戶端裝置驗證元件的組態模式中,設定授權政策,允許用戶端裝置在核心裝置上發佈和訂閱MQTT代理程式。請執行下列操作:
-
在組態下,在要合併程式碼區塊的組態中,輸入下列組態,其中包含用戶端裝置授權政策。每個裝置群組授權政策都會指定一組動作,以及用戶端裝置可以對其執行這些動作的資源。
-
此政策允許名稱開頭為 的用戶端裝置
MyClientDevice
在所有MQTT主題上進行連線和通訊。MyClientDevice*
將 取代為做為用戶端裝置連線的 AWS IoT 物件名稱。您也可以使用符合用戶端裝置名稱的*
萬用字元指定名稱。*
萬用字元必須位於名稱的結尾。如果您有第二個用戶端裝置要連線,請將 取代
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": [ "*" ] } } } } }如需詳細資訊,請參閱用戶端裝置驗證元件組態。
-
-
選擇確認。
-
-
對於 aws.greengrass.clientdevices.mqtt.Bridge 元件,選擇編輯組態。
-
在編輯橋接元件的組態模態中,設定主題映射,將MQTT訊息從用戶端裝置轉送到 AWS IoT Core。 MQTT請執行下列操作:
-
在組態下,在要合併程式碼區塊的組態中,輸入下列組態。此組態指定 將
clients/+/hello/world
主題篩選條件上的MQTT訊息從用戶端裝置轉送至 AWS IoT Core 雲端服務。例如,此主題篩選條件符合clients/MyClientDevice1/hello/world
主題。{ "mqttTopicMapping": { "HelloWorldIotCoreMapping": { "topic": "clients/+/hello/world", "source": "LocalMqtt", "target": "IotCore" } } }
如需詳細資訊,請參閱MQTT橋接元件組態。
-
選擇確認。
-
-
-
選擇檢閱並部署,以檢閱此頁面為您建立的部署。
-
如果您先前尚未在此區域中設定 Greengrass 服務角色,主控台會開啟模型來為您設定服務角色。用戶端裝置驗證元件會使用此服務角色來驗證用戶端裝置的身分,而 IP 偵測器元件會使用此服務角色來管理核心裝置連線資訊。選擇 授予許可。
-
在檢閱頁面上,選擇部署以開始部署至核心裝置。
-
若要驗證部署是否成功,請檢查部署的狀態,並檢查核心裝置上的日誌。若要檢查核心裝置上的部署狀態,您可以在部署概觀中選擇目標。如需詳細資訊,請參閱下列內容:
步驟 3:連接用戶端裝置
用戶端裝置可以使用 AWS IoT Device SDK 來探索、連線核心裝置,以及與核心裝置通訊。用戶端裝置必須是 AWS IoT 物件。如需詳細資訊,請參閱《 AWS IoT Core 開發人員指南》中的建立物件物件。
在本節中,您會安裝 AWS IoT Device SDK v2 for Python
注意
AWS IoT Device SDK 也提供其他程式設計語言的 。本教學課程使用 AWS IoT Device SDK v2 for Python,但您可以SDKs針對使用案例探索另一個。如需詳細資訊,請參閱《 AWS IoT Core 開發人員指南》中的 AWS IoT 裝置SDKs。
將用戶端裝置連接到核心裝置
-
將 AWS IoT Device SDK v2 for Python
下載並安裝到 AWS IoT 物件,以做為用戶端裝置進行連線。 在用戶端裝置上,執行下列動作:
-
複製適用於 Python 儲存庫的 AWS IoT Device SDK v2 以進行下載。
git clone https://github.com/aws/aws-iot-device-sdk-python-v2.git
-
安裝 AWS IoT Device SDK v2 for Python。
python3 -m pip install --user ./aws-iot-device-sdk-python-v2
-
-
變更為 AWS IoT Device SDK v2 for Python 中的範例資料夾。
cd aws-iot-device-sdk-python-v2/samples
-
執行範例 Greengrass 探索應用程式。此應用程式預期引數會指定用戶端裝置物件名稱、要使用MQTT的主題和訊息,以及驗證和保護連線的憑證。下列範例會將 Hello World 訊息傳送至
clients/
主題。MyClientDevice1
/hello/world注意
本主題與您設定MQTT橋接器將訊息轉送至 AWS IoT Core 較早版本的主題相符。
-
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
\\ --regionus-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日誌。
-
-
確認MQTT橋接器將訊息從用戶端裝置轉送至 AWS IoT Core。您可以使用 AWS IoT Core 主控台中的MQTT測試用戶端來訂閱MQTT主題篩選條件。請執行下列操作:
-
導覽至 AWS IoT 主控台
。 -
在左側導覽選單的測試下,選擇MQTT測試用戶端。
-
在訂閱主題索引標籤上,針對主題篩選條件,輸入
clients/+/hello/world
以從核心裝置訂閱用戶端裝置訊息。 -
選擇 Subscribe (訂閱)。
-
在用戶端裝置上再次執行發佈/訂閱應用程式。
MQTT 測試用戶端會在符合此主題篩選條件的主題上顯示您從用戶端裝置傳送的訊息。
-
步驟 4:開發與用戶端裝置通訊的元件
您可以開發與用戶端裝置通訊的 Greengrass 元件。元件使用程序間通訊 (IPC) 和本機發佈/訂閱界面,在核心裝置上進行通訊。若要與用戶端裝置互動,請設定MQTT橋接元件在用戶端裝置與本機發佈/訂閱介面之間轉送訊息。
在本節中,您會更新MQTT橋接元件,將訊息從用戶端裝置轉送至本機發佈/訂閱介面。然後,您會開發訂閱這些訊息的元件,並在收到訊息時列印訊息。
開發與用戶端裝置通訊的元件
-
修訂核心裝置的部署,並設定MQTT橋接元件,將訊息從用戶端裝置轉送至本機發佈/訂閱。請執行下列操作:
-
在左側導覽功能表中,選擇核心裝置。
-
在核心裝置頁面上,選擇您用於本教學課程的核心裝置。
-
在核心裝置詳細資訊頁面上,選擇用戶端裝置索引標籤。
-
在用戶端裝置索引標籤上,選擇設定雲端探索。
設定核心裝置探索頁面隨即開啟。在此頁面上,您可以變更或設定部署到核心裝置的用戶端裝置元件。
-
在步驟 3 中,針對 aws.greengrass.clientdevices.mqtt.Bridge 元件,選擇編輯組態。
-
在MQTT橋接元件的編輯組態模態中,設定主題映射,將MQTT訊息從用戶端裝置轉送到本機發佈/訂閱界面。請執行下列操作:
-
在組態下,在要合併程式碼區塊的組態中,輸入下列組態。此組態指定 將符合用戶端裝置
clients/+/hello/world
主題篩選條件的主題MQTT訊息,轉送至 AWS IoT Core 雲端服務和本機 Greengrass 發佈/訂閱代理程式。{ "mqttTopicMapping": { "HelloWorldIotCoreMapping": { "topic": "clients/+/hello/world", "source": "LocalMqtt", "target": "IotCore" }, "HelloWorldPubsubMapping": { "topic": "clients/+/hello/world", "source": "LocalMqtt", "target": "Pubsub" } } }
如需詳細資訊,請參閱MQTT橋接元件組態。
-
選擇確認。
-
-
選擇檢閱並部署,以檢閱此頁面為您建立的部署。
-
在檢閱頁面上,選擇部署以開始部署至核心裝置。
-
若要驗證部署是否成功,請檢查部署的狀態,並檢查核心裝置上的日誌。若要檢查核心裝置上的部署狀態,您可以在部署概觀中選擇目標。如需詳細資訊,請參閱下列內容:
-
-
開發和部署從用戶端裝置訂閱 Hello World 訊息的 Greengrass 元件。請執行下列操作:
-
在核心裝置上建立配方和成品的資料夾。
mkdir recipes mkdir -p artifacts/com.example.clientdevices.MyHelloWorldSubscriber/1.0.0
重要
您必須針對成品資料夾路徑使用下列格式。包含您在配方中指定的元件名稱和版本。
artifacts/
componentName
/componentVersion
/ -
使用文字編輯器建立具有下列內容的元件配方。此配方指定安裝 AWS IoT Device SDK v2 for Python,並執行訂閱主題並列印訊息的指令碼。
例如,在以 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" } } ] }
-
使用文字編輯器建立名為
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)
注意
此元件使用適用於 Python 的 V2 中的IPC用戶端 V2 與 AWS IoT Greengrass Core 軟體通訊。 AWS IoT Device SDK
與原始IPC用戶端相比,IPC用戶端 V2 可減少您需要寫入以在自訂元件IPC中使用的程式碼量。 -
使用 Greengrass CLI 部署元件。
sudo
/bin/greengrass-cli deployment create \ --recipeDir recipes \ --artifactDir artifacts \ --merge "com.example.clientdevices.MyHelloWorldSubscriber=1.0.0"/greengrass/v2
-
-
檢視元件日誌,確認元件安裝成功並訂閱主題。
sudo tail -f
/logs/com.example.clientdevices.MyHelloWorldSubscriber.log/greengrass/v2
您可以保持日誌饋送開啟,以驗證核心裝置是否接收訊息。
-
在用戶端裝置上,再次執行範例 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
\\ --regionus-east-1
\\ --verbosity Warn -
再次檢視元件日誌,以確認元件從用戶端裝置接收和列印訊息。
sudo tail -f
/logs/com.example.clientdevices.MyHelloWorldSubscriber.log/greengrass/v2
步驟 5:開發與用戶端裝置影子互動的元件
您可以開發與用戶端裝置AWS IoT 影子互動的 Greengrass 元件。影子是一種JSON文件,可存放 AWS IoT 物件的目前或所需狀態資訊,例如用戶端裝置。自訂元件可以存取用戶端裝置的影子來管理其狀態,即使用戶端裝置未連線也一樣 AWS IoT。每個 AWS IoT 物件都有一個未命名的陰影,您也可以為每個物件建立多個命名的陰影。
在本節中,您會部署影子管理員元件來管理核心裝置上的影子。您也可以更新MQTT橋接器元件,在用戶端裝置和影子管理員元件之間轉送影子訊息。然後,您會開發更新用戶端裝置影子的元件,並在用戶端裝置上執行範例應用程式,以回應元件的影子更新。此元件代表智慧光線管理應用程式,其中核心裝置會管理以用戶端裝置身分連接到智慧光線的顏色狀態。
開發與用戶端裝置影子互動的元件
-
修訂核心裝置的部署,以部署影子管理員元件,並設定MQTT橋接元件,以在用戶端裝置與本機發佈/訂閱之間轉送影子訊息,影子管理員會在此通訊。請執行下列操作:
-
在左側導覽功能表中,選擇核心裝置。
-
在核心裝置頁面上,選擇您用於本教學課程的核心裝置。
-
在核心裝置詳細資訊頁面上,選擇用戶端裝置索引標籤。
-
在用戶端裝置索引標籤上,選擇設定雲端探索。
設定核心裝置探索頁面隨即開啟。在此頁面上,您可以變更或設定部署到核心裝置的用戶端裝置元件。
-
在步驟 3 中,針對 aws.greengrass.clientdevices.mqtt.Bridge 元件,選擇編輯組態。
-
在MQTT橋接元件的編輯組態模態中,設定主題映射,在用戶端裝置與本機發佈/訂閱介面之間轉送裝置影子主題MQTT的訊息。您也可以確認部署指定相容的MQTT橋接器版本。用戶端裝置影子支援需要MQTT橋接器 v2.2.0 或更新版本。請執行下列操作:
-
針對元件版本,選擇 2.2.0 版或更新版本。
-
在組態下,於要合併程式碼區塊的組態中,輸入下列組態。此組態指定 轉送陰影主題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 中,選取 aws.greengrass.ShadowManager 元件進行部署。
-
選擇檢閱並部署,以檢閱此頁面為您建立的部署。
-
在檢閱頁面上,選擇部署以開始部署至核心裝置。
-
若要驗證部署是否成功,請檢查部署的狀態,並檢查核心裝置上的日誌。若要檢查核心裝置上的部署狀態,您可以在部署概觀中選擇目標。如需詳細資訊,請參閱下列內容:
-
-
開發和部署 Greengrass 元件,以管理智慧光線用戶端裝置。請執行下列操作:
-
在核心裝置上建立元件成品的資料夾。
mkdir -p artifacts/com.example.clientdevices.MySmartLightManager/1.0.0
重要
您必須針對成品資料夾路徑使用下列格式。包含您在配方中指定的元件名稱和版本。
artifacts/
componentName
/componentVersion
/ -
使用文字編輯器建立具有下列內容的元件配方。此配方會指定安裝 AWS IoT Device SDK v2 for Python,並執行指令碼,以與智慧光線用戶端裝置的陰影互動來管理顏色。
例如,在以 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" } } ] }
-
使用文字編輯器建立名為
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 應用程式會執行下列動作:
-
讀取元件的組態,以取得要管理的智慧光用戶端裝置清單。
-
使用 SubscribeToConfigurationUpdateIPC操作訂閱組態更新通知。 AWS IoT Greengrass 核心軟體會在每次元件的組態變更時傳送通知。當元件收到組態更新通知時,它會更新其管理的智慧光源用戶端裝置清單。
-
取得每個智慧光用戶端裝置的陰影,以取得其初始顏色狀態。
-
將每個智慧光用戶端裝置的顏色設定為每 15 秒隨機的顏色。元件會更新用戶端裝置的物件陰影,以變更其顏色。此操作會透過 將影子差異事件傳送至用戶端裝置MQTT。
-
使用 SubscribeToTopicIPC操作,在本機發佈/訂閱界面上訂閱陰影更新接受的訊息。此元件會收到這些訊息,以追蹤每個智慧光用戶端裝置的顏色。當智慧光用戶端裝置收到陰影更新時,會傳送訊息MQTT以確認其已收到更新。MQTT 橋接器會將此訊息轉送至本機發佈/訂閱介面。
-
-
使用 Greengrass CLI 部署元件。當您部署此元件時,您可以指定用戶端裝置的清單
smartLightDeviceNames
,其會管理其影子。MyClientDevice1
將 取代為用戶端裝置的物件名稱。sudo
/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": [ "/greengrass/v2
MyClientDevice1
" ] } } }'
-
-
檢視元件日誌,確認元件安裝和執行成功。
sudo tail -f
/logs/com.example.clientdevices.MySmartLightManager.log/greengrass/v2
元件會傳送變更智慧光用戶端裝置顏色的請求。影子管理員會收到請求並設定影子
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}
您可以保持日誌饋送開啟,以查看元件何時列印訊息。
-
下載並執行使用 Greengrass 探索並訂閱裝置陰影更新的範例應用程式。在用戶端裝置上,執行下列動作:
-
變更為 AWS IoT Device SDK v2 for Python 中的範例資料夾。此範例應用程式使用範例資料夾中的命令列剖析模組。
cd aws-iot-device-sdk-python-v2/samples
-
使用文字編輯器建立名為
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 探索來探索和連線至核心裝置。
-
從核心裝置請求影子文件,以取得屬性的初始狀態。
-
訂閱至影子差異事件,當屬性
desired
的值與其reported
值不同時,核心裝置會傳送該事件。當應用程式收到陰影差異事件時,它會變更 屬性的值,並將更新傳送到核心裝置,以將新值設定為其reported
值。
此應用程式結合了來自 v2 的 AWS IoT Device SDK Greengrass 探索和陰影範例。
-
-
執行範例應用程式。此應用程式預期引數會指定用戶端裝置物件名稱、要使用的影子屬性,以及驗證和保護連線的憑證。
-
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
\ --regionus-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-----\n
MIICiT...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日誌。
-
-
-
再次檢視元件日誌,以確認元件從智慧光用戶端裝置接收陰影更新確認。
sudo tail -f
/logs/com.example.clientdevices.MySmartLightManager.log/greengrass/v2
元件會記錄訊息,以確認智慧光用戶端裝置已變更其顏色。
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 元件,並從核心裝置接收陰影更新。如需本教學課程涵蓋主題的詳細資訊,請參閱以下內容: