Greengrass V2でClassic Shadowを使う方法があまり情報無かったので書いておきます。
test-device-001
というThingを登録した前提で話を進めます。
Componentの設定
AWSの以下のドキュメントを頼りに設定します。
いかにaws.greengrass.ShadowManager
のコンポーネントの設定を記載しています。
{
"aws.greengrass.Cli": {
"componentVersion": "2.9.1"
},
...省略
"aws.greengrass.ShadowManager": {
"componentVersion": "2.3.0",
"configurationUpdate": {
"merge": "{\"rateLimits\":{\"maxLocalRequestsPerSecondPerThing\":20,\"maxOutboundSyncUpdatesPerSecond\":100,\"maxTotalLocalRequestsRate\":200},\"shadowDocumentSizeLimitBytes\":8192,\"shadowDocuments\":[{\"classic\":true,\"thingName\":\"test-device-001\"}],\"strategy\":{\"type\":\"realTime\"},\"synchronize\":{\"coreThing\":{\"classic\":true},\"direction\":\"betweenDeviceAndCloud\"}}"
}
},
...省略
}
Classic Shadowを使う上で重要な部分は merge
のところに入っています。 以下のような内容になります。デプロイ後に確認したYAMLから見ると以下の内容になります。
synchronize.coreThing.classic = true
とshadowDocuments.shadowDocuments
のthingName
ごとの設定に classic = true
を入れているのがポイントです。
---
componentType: "PLUGIN"
configuration:
rateLimits:
maxLocalRequestsPerSecondPerThing: 20
maxOutboundSyncUpdatesPerSecond: 100
maxTotalLocalRequestsRate: 200
shadowDocuments:
- classic: true
thingName: "test-device-001"
shadowDocumentSizeLimitBytes: 8192
strategy:
type: "realTime"
synchronize:
coreThing:
classic: true
direction: "betweenDeviceAndCloud"
dependencies:
- "aws.greengrass.Nucleus:SOFT"
lifecycle: {}
version: "2.3.0"
Pythonスクリプトの例
起動時に一度だけClassic Shadowを更新して、そのShadowの内容を読み込む簡単なスクリプトを作ってみました。
ポイントとなるのは shadow_name=""
の部分です。空文字がClassic Shadowを表すようです。
import json
from typing import Optional, Union
from traceback import format_exc
import awsiot.greengrasscoreipc
from awsiot.greengrasscoreipc.model import (
GetThingShadowRequest,
UpdateThingShadowRequest,
)
THING_NAME = "test-device-001"
def update_default_shadow(
thing_name: str, payload: Optional[Union[bytes, str]]
) -> Optional[dict]:
try:
ipc_client = awsiot.greengrasscoreipc.connect()
op = ipc_client.new_update_thing_shadow()
op.activate(
UpdateThingShadowRequest(
thing_name=thing_name,
shadow_name="", # Classic Shadow
payload=payload,
)
)
future_response = op.get_response()
result = future_response.result(timeout=10)
if result.payload:
return json.loads(result.payload)
return None
except Exception as e:
print(format_exc())
print("Error update shadow", type(e), e)
raise e
def read_default_shadow(thing_name: str) -> Optional[dict]:
try:
ipc_client = awsiot.greengrasscoreipc.connect()
op = ipc_client.new_get_thing_shadow()
op.activate(
request=GetThingShadowRequest(
thing_name=thing_name,
shadow_name="", # Classic Shadow
)
)
future_resp = op.get_response()
result = future_resp.result(timeout=10)
if result.payload:
return json.loads(result.payload)
return None
except Exception as e:
print(format_exc())
print("Error read shadow", type(e), e)
raise e
update_res = update_default_shadow(
thing_name=THING_NAME,
payload=bytes(json.dumps({"state": {"reported": {"status": "OK"}}}), "utf-8"),
)
update_message = f"UPDATE -> {update_res}"
print(update_message)
read_res = read_default_shadow(thing_name=THING_NAME)
message = f"READ -> {read_res}"
print(message)
Recipeの例
後は以下のようなRecipeでデプロイすればOKです。 com.hello.shadow
というComponent名で登録してみました。
{
"RecipeFormatVersion": "2020-01-25",
"ComponentName": "com.hello.shadow",
"ComponentVersion": "1.0.0",
"ComponentDescription": "Hello Shadow",
"ComponentPublisher": "Me",
"ComponentConfiguration": {
"DefaultConfiguration": {
"accessControl": {
"aws.greengrass.ShadowManager": {
"com.hello.shadow:shadow:1": {
"policyDescription": "Allows access to shadows",
"operations": [
"aws.greengrass#GetThingShadow",
"aws.greengrass#UpdateThingShadow",
"aws.greengrass#DeleteThingShadow"
],
"resources": [
"$aws/things/test-device-001/shadow"
]
}
}
}
}
},
"Manifests": [
{
"Platform": {
"os": "linux"
},
"Lifecycle": {
"Install": {
"RequiresPrivilege": true,
"script": "python3 -m pip install --user awsiotsdk"
},
"Run": {
"RequiresPrivilege": true,
"script": "python3 -u {artifacts:path}/main.py "
}
}
}
]
}
実行してみる
Greengrass CLIから以下のように実行してみます。
sudo /greengrass/v2/bin/greengrass-cli component restart -n "com.hello.shadow"
ログを見るとShadowを更新して、読み込んでいるところが確認できます。
2023-03-13T11:20:15.997Z [INFO] (pool-2-thread-48) com.hello.shadow: shell-runner-start. {scriptName=services.com.hello.shadow.lifecycle.Run.script, serviceName=com.hello.shadow, currentState=STARTING, command=["python3 -u /greengrass/v2/packages/artifacts/com.hello.shadow/1.0.0/main.py "]}
2023-03-13T11:20:16.176Z [INFO] (Copier) com.hello.shadow: stdout. UPDATE -> b'{"version":1,"timestamp":1678706416,"state":{"reported":{"status":"OK"}},"metadata":{"reported":{"status":{"timestamp":1678706416}}}}'. {scriptName=services.com.hello.shadow.lifecycle.Run.script, serviceName=com.hello.shadow, currentState=RUNNING}
2023-03-13T11:20:16.194Z [INFO] (Copier) com.hello.shadow: stdout. READ -> {'state': {'reported': {'status': 'OK'}}, 'metadata': {'reported': {'status': {'timestamp': 1678706416}}}, 'version': 1, 'timestamp': 1678706416}. {scriptName=services.com.hello.shadow.lifecycle.Run.script, serviceName=com.hello.shadow, currentState=RUNNING}
2023-03-13T11:20:16.229Z [INFO] (Copier) com.hello.shadow: Run script exited. {exitCode=0, serviceName=com.hello.shadow, currentState=RUNNING}
AWS側を見るとClassic Shadowが登録されています。
Shadowの中身も更新されてそうです。