AWS IoT TwinMaker によるデジタルツインアプリケーションを AWS CDK で構築する 〜その2 シーン作成編〜
こんにちは、CX 事業本部 Delivery 部の若槻です。
前回の下記エントリでは、AWS IoT TwinMaker でデジタルツインアプリケーションを構築する際に最上位のコンテナとなるリソースであるワークスペースを AWS CDK で作成しました。
そして、そのワークスペース内で実際にデジタルツインを編集して構成する場となるリソースが、下記図の⑤に該当するシーン (Scene) です。 What is AWS IoT TwinMaker? - AWS IoT TwinMaker より引用
今回は、AWS IoT TwinMaker によるデジタルツインアプリケーションを AWS CDK で作成する上で、このシーン作成する方法と、作成時に遭遇したいくつかのハマりポイントを合わせてご紹介します。
最終的に動作したもの
最終的には以下の CDK コードで、動作するシーンを構築することができました。
import { aws_iam, aws_s3, aws_iottwinmaker, aws_s3_deployment, Stack, RemovalPolicy, } from 'aws-cdk-lib'; import { Construct } from 'constructs'; export class CdkSampleStack extends Stack { constructor(scope: Construct, id: string) { super(scope, id); const accounId = this.account; const region = this.region; const workspaceId = 'CdkDemoWorkspace'; // ワークスペース用リソース保管バケット const workspaceResourceBucket = new aws_s3.Bucket( this, 'WorkspaceResourceBucket', { removalPolicy: RemovalPolicy.DESTROY, autoDeleteObjects: true, } ); // IoT TwinMaker ワークスペース実行ロール用プリンシパル const principal = new aws_iam.ServicePrincipal( 'iottwinmaker.amazonaws.com' ).withConditions({ StringEquals: { 'aws:SourceAccount': accounId, }, StringLike: { 'aws:SourceArn': `arn:aws:iottwinmaker:${region}:${accounId}:workspace/${workspaceId}`, }, }); // ワークスペース実行ロール const workspaceExecutionRole = new aws_iam.Role( this, 'WorkspaceExecutionRole', { assumedBy: principal, inlinePolicies: { readWorkspaceResourceBucket: aws_iam.PolicyDocument.fromJson({ Version: '2012-10-17', Statement: [ { Effect: 'Allow', Action: [ 's3:GetBucket', 's3:GetObject', 's3:ListBucket', 's3:PutObject', 's3:ListObjects', 's3:ListObjectsV2', 's3:GetBucketLocation', ], Resource: [ workspaceResourceBucket.bucketArn, workspaceResourceBucket.arnForObjects('*'), ], }, { Effect: 'Allow', Action: ['s3:DeleteObject'], Resource: [ workspaceResourceBucket.arnForObjects( `DO_NOT_DELETE_WORKSPACE_${workspaceId}` ), ], }, ], }), }, } ); // ワークスペース const cdkDemoWorkspace = new aws_iottwinmaker.CfnWorkspace( this, 'CdkDemoWorkspace', { workspaceId, s3Location: workspaceResourceBucket.bucketArn, role: workspaceExecutionRole.roleArn, } ); // シーン設定ファイルのアップロード new aws_s3_deployment.BucketDeployment(this, 'DeploySampleSceneConfig', { sources: [ aws_s3_deployment.Source.asset('./src/iottwinmaker/cdk-demo-workspace'), ], destinationBucket: workspaceResourceBucket, destinationKeyPrefix: 'SceneConfig/', }); // シーン const sampleScene = new aws_iottwinmaker.CfnScene(this, 'SampleScene', { sceneId: 'SampleScene', contentLocation: workspaceResourceBucket.s3UrlForObject( '/SceneConfig/SampleScene.json' ), workspaceId: cdkDemoWorkspace.workspaceId, }); // ワークスペースとシーンの依存関係の設定 sampleScene.addDependency(cdkDemoWorkspace); } }
- ワークスペースと同様に、シーンの作成でも L1 Construct の
CfnScene
を使用します。contentLocation
では、シーン設定ファイルの S3 バケット内のパスを指定します。- L1 Construct 同士の依存となるので、
addDependency
でcdkDemoWorkspace
に対する依存関係を追加しています。 - 依存関係を設定しない場合は、スタック削除時などにワークスペースの削除が先に試行されてしまい、
Workspace has scenes. Delete all resources in the workspace.
というエラーが発生します。
- シーン設定ファイルを S3 バケットにアップロードするために
aws_s3_deployment.BucketDeployment
を使用しています。 - 下記がシーン設定ファイルの内容です。マネジメントコンソールからシーン作成時にデフォルトで作成される設定ファイルを参考に、必須およびあると便利な項目を記述しています。
{ "specVersion": "1.0", "version": "1", "unit": "meters", "nodes": [], "rootNodeIndexes": [], "cameras": [], "properties": { "environmentPreset": "neutral" } }
上記を CDK デプロイ後に、AWS CLI で ID SampleScene
のシーンを取得すると、シーンが作成されていることが確認できました。
$ aws iottwinmaker get-scene --workspace-id CdkDemoWorkspace --scene-id SampleScene --region us-east-1 { "workspaceId": "CdkDemoWorkspace", "sceneId": "SampleScene", "contentLocation": "s3://cdksamplestack-workspaceresourcebucketeecc84e2-xxxxxxxxxx/SceneConfig/SampleScene.json", "arn": "arn:aws:iottwinmaker:us-east-1:xxxxxxxxxxxx:workspace/CdkDemoWorkspace/scene/SampleScene", "creationDateTime": 1695566570.944, "updateDateTime": 1695571709.3, "capabilities": [], "sceneMetadata": {}, "generatedSceneMetadata": {} }
マネジメントコンソールからワークスペースにアクセスすると、こちらでもシーンが作成されていることが確認できます。
シーンをクリックして開くと、シーンコンポーザーにも問題なくアクセスすることができるようになっています。
CfnSceneProps 型定義
参考までに、CfnScene
のプロパティの型定義一覧は次のようになります。
/** * Properties for defining a `CfnScene` * * @struct * @stability external * @see http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iottwinmaker-scene.html */ export interface CfnSceneProps { /** * A list of capabilities that the scene uses to render. * * @see http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iottwinmaker-scene.html#cfn-iottwinmaker-scene-capabilities */ readonly capabilities?: Array<string>; /** * The relative path that specifies the location of the content definition file. * * @see http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iottwinmaker-scene.html#cfn-iottwinmaker-scene-contentlocation */ readonly contentLocation: string; /** * The description of this scene. * * @see http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iottwinmaker-scene.html#cfn-iottwinmaker-scene-description */ readonly description?: string; /** * The scene ID. * * @see http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iottwinmaker-scene.html#cfn-iottwinmaker-scene-sceneid */ readonly sceneId: string; /** * The scene metadata. * * @see http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iottwinmaker-scene.html#cfn-iottwinmaker-scene-scenemetadata */ readonly sceneMetadata?: cdk.IResolvable | Record<string, string>; /** * The ComponentType tags. * * @see http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iottwinmaker-scene.html#cfn-iottwinmaker-scene-tags */ readonly tags?: Record<string, string>; /** * The ID of the workspace. * * @see http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iottwinmaker-scene.html#cfn-iottwinmaker-scene-workspaceid */ readonly workspaceId: string; }
試行錯誤
以降は、シーンを CDK で作成する際にハマって試行錯誤したポイントを書き残しておきます。
その1
事象
以前のエントリでのワークスペース作成時のコードを以下のように変更してシーンを作成しようとしました。
$ git diff diff --git a/lib/cdk-sample-stack.ts b/lib/cdk-sample-stack.ts index bc5371c..5a17cf9 100644 --- a/lib/cdk-sample-stack.ts +++ b/lib/cdk-sample-stack.ts @@ -62,10 +62,21 @@ export class CdkSampleStack extends Stack { ); // ワークスペース - new aws_iottwinmaker.CfnWorkspace(this, 'IotTwinmakerWorkspace', { - workspaceId: 'CdkDemoWorkspace', - s3Location: workspaceResourceBucket.bucketArn, - role: workspaceExecutionRole.roleArn, + const cdkDemoWorkspace = new aws_iottwinmaker.CfnWorkspace( + this, + 'CdkDemoWorkspace', + { + workspaceId: 'CdkDemoWorkspace', + s3Location: workspaceResourceBucket.bucketArn, + role: workspaceExecutionRole.roleArn, + } + ); + + // シーン + new aws_iottwinmaker.CfnScene(this, 'SampleScene', { + sceneId: 'SampleScene', + contentLocation: `${workspaceResourceBucket.bucketArn}/SampleScene.json`, + workspaceId: cdkDemoWorkspace.workspaceId, }); } }
CDK デプロイすると次のようなエラーが発生しました。
$ cdk deploy ✨ Synthesis time: 3.16s CdkSampleStack: start: Building d772b71253b31ce822e7c9a83209cc1a3dba29d2bc2dd65e58ba58a760cd22b4:current_account-us-east-1 CdkSampleStack: success: Built d772b71253b31ce822e7c9a83209cc1a3dba29d2bc2dd65e58ba58a760cd22b4:current_account-us-east-1 CdkSampleStack: start: Publishing d772b71253b31ce822e7c9a83209cc1a3dba29d2bc2dd65e58ba58a760cd22b4:current_account-us-east-1 CdkSampleStack: success: Published d772b71253b31ce822e7c9a83209cc1a3dba29d2bc2dd65e58ba58a760cd22b4:current_account-us-east-1 CdkSampleStack: deploying... [1/1] CdkSampleStack: creating CloudFormation changeset... 11:17:28 PM | CREATE_FAILED | AWS::IoTTwinMaker::Scene | SampleScene Properties validation failed for resource SampleScene with message: #/ContentLocation: failed validation constraint for keyword [pattern] ❌ CdkSampleStack failed: Error: The stack named CdkSampleStack failed to deploy: UPDATE_ROLLBACK_COMPLETE: Properties validation failed for resource SampleScene with message: #/ContentLocation: failed validation constraint for keyword [pattern] at FullCloudFormationDeployment.monitorDeployment (/Users/wakatsuki.ryuta/.nvm/versions/node/v18.17.0/lib/node_modules/aws-cdk/lib/index.js:454:10232) at process.processTicksAndRejections (node:internal/process/task_queues:95:5) at async Object.deployStack2 [as deployStack] (/Users/wakatsuki.ryuta/.nvm/versions/node/v18.17.0/lib/node_modules/aws-cdk/lib/index.js:457:179910) at async /Users/wakatsuki.ryuta/.nvm/versions/node/v18.17.0/lib/node_modules/aws-cdk/lib/index.js:457:163158 ❌ Deployment failed: Error: The stack named CdkSampleStack failed to deploy: UPDATE_ROLLBACK_COMPLETE: Properties validation failed for resource SampleScene with message: #/ContentLocation: failed validation constraint for keyword [pattern] at FullCloudFormationDeployment.monitorDeployment (/Users/wakatsuki.ryuta/.nvm/versions/node/v18.17.0/lib/node_modules/aws-cdk/lib/index.js:454:10232) at process.processTicksAndRejections (node:internal/process/task_queues:95:5) at async Object.deployStack2 [as deployStack] (/Users/wakatsuki.ryuta/.nvm/versions/node/v18.17.0/lib/node_modules/aws-cdk/lib/index.js:457:179910) at async /Users/wakatsuki.ryuta/.nvm/versions/node/v18.17.0/lib/node_modules/aws-cdk/lib/index.js:457:163158 The stack named CdkSampleStack failed to deploy: UPDATE_ROLLBACK_COMPLETE: Properties validation failed for resource SampleScene with message: #/ContentLocation: failed validation constraint for keyword [pattern]
解決
CfnScene
でのcontentLocation
の指定方法が間違っていました。値として Construct を指定すれば良い L2 Construct とは異なり、L1 Construct は生の文字列の指定となるので、こういう所に気をつけないといけないですね。
$ git diff diff --git a/lib/cdk-sample-stack.ts b/lib/cdk-sample-stack.ts index 5a17cf9..cfd5519 100644 --- a/lib/cdk-sample-stack.ts +++ b/lib/cdk-sample-stack.ts @@ -75,7 +75,7 @@ export class CdkSampleStack extends Stack { // シーン new aws_iottwinmaker.CfnScene(this, 'SampleScene', { sceneId: 'SampleScene', - contentLocation: `${workspaceResourceBucket.bucketArn}/SampleScene.json`, + contentLocation: `s3://${workspaceResourceBucket.bucketName}/SampleScene.json`, workspaceId: cdkDemoWorkspace.workspaceId, }); }
その2
事象
その 1 で設定ファイルをデプロイした後にマネジメントコンソールからシーンにアクセスしてみました。
すると次のようなエラーが発生し、シーンコンポーザーを操作することができませんでした。
調査
コンソールから手動でワークスペースとシーンを作成してみます。
この場合はシーンコンポーザーを正常に開くことができます。
そして S3 バケットの内容を見ると、次のように <シーン名>.json
という JSON ファイルが作成されていました。
$ aws s3 ls s3://twinmaker-workspace-test-<accountId>-iad 2023-09-25 00:22:05 0 DO_NOT_DELETE_WORKSPACE_test 2023-09-25 00:22:42 1103 test_scene.json
JSON ファイルの内容は次のようになっています。シーンのデフォルトの設定や、サンプルルールが記述がコンフィグとして記述されています。
{ "specVersion": "1.0", "version": "1", "unit": "meters", "nodes": [], "rootNodeIndexes": [], "cameras": [], "rules": { "sampleAlarmIconRule": { "statements": [ { "expression": "alarm_status == 'ACTIVE'", "target": "iottwinmaker.common.icon:Error" }, { "expression": "alarm_status == 'ACKNOWLEDGED'", "target": "iottwinmaker.common.icon:Warning" }, { "expression": "alarm_status == 'SNOOZE_DISABLED'", "target": "iottwinmaker.common.icon:Warning" }, { "expression": "alarm_status == 'NORMAL'", "target": "iottwinmaker.common.icon:Info" } ] }, "sampleTimeSeriesIconRule": { "statements": [ { "expression": "temperature >= 40", "target": "iottwinmaker.common.icon:Error" }, { "expression": "temperature >= 20", "target": "iottwinmaker.common.icon:Warning" }, { "expression": "temperature < 20", "target": "iottwinmaker.common.icon:Info" } ] }, "sampleTimeSeriesColorRule": { "statements": [ { "expression": "temperature >= 40", "target": "iottwinmaker.common.color:#FF0000" }, { "expression": "temperature >= 20", "target": "iottwinmaker.common.color:#FFFF00" }, { "expression": "temperature < 20", "target": "iottwinmaker.common.color:#00FF00" } ] } }, "properties": { "environmentPreset": "neutral" } }
この JSON ファイルを CDK デプロイ時にも作成すると良さそうです。
解決
まず、試しに空の JSON ファイルを使用してみます。
{}
aws_s3_deployment.BucketDeployment
を使用して、S3 バケットに JSON ファイルをアップロードするようにします。この時、プレフィクスSceneConfig
を指定することにより、他のパスのオブジェクトが意図せず削除されないようにします。
$ git diff diff --git a/lib/cdk-sample-stack.ts b/lib/cdk-sample-stack.ts index 5a17cf9..f436f45 100644 --- a/lib/cdk-sample-stack.ts +++ b/lib/cdk-sample-stack.ts @@ -2,6 +2,7 @@ import { aws_iam, aws_s3, aws_iottwinmaker, + aws_s3_deployment, Stack, StackProps, RemovalPolicy, @@ -75,8 +76,17 @@ export class CdkSampleStack extends Stack { // シーン new aws_iottwinmaker.CfnScene(this, 'SampleScene', { sceneId: 'SampleScene', - contentLocation: `${workspaceResourceBucket.bucketArn}/SampleScene.json`, + contentLocation: `s3://${workspaceResourceBucket.bucketName}/SceneConfig/SampleScene.json`, workspaceId: cdkDemoWorkspace.workspaceId, }); + + // シーン設定ファイルのアップロード + new aws_s3_deployment.BucketDeployment(this, 'DeploySampleSceneConfig', { + sources: [ + aws_s3_deployment.Source.asset('./src/iottwinmaker/cdk-demo-workspace'), + ], + destinationBucket: workspaceResourceBucket, + destinationKeyPrefix: 'SceneConfig/', + retainOnDelete: false, + }); } }
上記をデプロイすると、下記の通り S3 バケットに JSON ファイルが作成されました。
$ aws s3 ls s3://${workspaceResourceBucket} --recursive 2023-09-25 00:53:49 0 DO_NOT_DELETE_WORKSPACE_CdkDemoWorkspace 2023-09-25 01:14:24 3 SceneConfig/SampleScene.json
再度シーンコンポーザーにアクセスすると、先ほどとエラーが変わりました。
Error - unable to render the scene
Invalid scene document. Cause - unable to find specVersion
specVersion
など必要なプロパティを追加すれば良さそうです。
{ "specVersion": "1.0", "version": "1", "unit": "meters", "nodes": [], "rootNodeIndexes": [], "cameras": [], "properties": { "environmentPreset": "neutral" } }
再度デプロイした後にシーンコンポーザーを開くと、今度は正常にアクセスすることができました。
おわりに
AWS IoT TwinMaker によるデジタルツインアプリケーションを AWS CDK で作成する上で、シーン作成する方法と、作成時に遭遇したいくつかのハマりポイントを合わせてご紹介しました。
次回はエンティティを作成し、3D モデルのアップロードおよびバインディングまで挑戦してみたいと思います。
以上