0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

AWS Greengrass V2のShadowManagerでClassic Shadowと同期する方法

Last updated at Posted at 2023-03-13

Greengrass V2でClassic Shadowを使う方法があまり情報無かったので書いておきます。

test-device-001 というThingを登録した前提で話を進めます。

Componentの設定

AWSの以下のドキュメントを頼りに設定します。

いかにaws.greengrass.ShadowManager のコンポーネントの設定を記載しています。

component.json
{
  "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 = trueshadowDocuments.shadowDocumentsthingName ごとの設定に 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を表すようです。

参考: https://github.com/aws-greengrass/aws-greengrass-shadow-manager/blob/v2.3.0/src/integrationtests/java/com/aws/greengrass/integrationtests/SyncDirectionalityTest.java#L59

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名で登録してみました。

recipe.json
{
  "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が登録されています。

image.png

Shadowの中身も更新されてそうです。

image.png

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?