Raspberry PiとAWS IoT Device Clientを使用して、リモートアクション(ジョブ)をデモする

Raspberry PiとAWS IoT Device Clientを使用して、リモートアクション(ジョブ)をデモする

以下のページでRaspberry PiにAWS IoT Device Clientをインストールする方法をご紹介しました。

あわせて読みたい
Systems Managerに登録したRaspberry Piに、AWS IoT Device Clientをインストールする 【Systems Managerに登録したRaspberry Piに、AWS IoT Device Clientをインストールする】 本ページでは、AWS IoT Device Clientを取り上げます。 The AWS IoT Device C...

また以下のページでAWS IoT Device Clientを使用してMQTTメッセージを送受信する方法を取り上げました。

あわせて読みたい
Systems Managerに登録したRaspberry Piを使用して、AWS IoT Device ClientのMQTTメッセージ通信をデモする 【Systems Managerに登録したRaspberry Piを使用して、AWS IoT Device ClientのMQTTメッセージ通信をデモする】 以下のページでRaspberry PiにAWS IoT Device Clientを...

今回もその続きということで、以下の公式チュートリアルに従い、AWS IoT Device Clientを使用して、リモートアクション(ジョブ)を体験します。

あわせて読みたい
ステップ 2: AWS IoT でジョブを作成して実行する - AWS IoT Core このセクションの手順では、ジョブドキュメントと AWS IoT ジョブリソースを作成します。ジョブリソースを作成した後、AWS IoT はジョブドキュメントを指定されたジョブタ...

構築する環境

Diagram of demonstrating remote actions (jobs) using Raspberry Pi and AWS IoT Device Client

基本的な構成は冒頭でご紹介したページと同様です。

変更点は4つです。

1点目はIoTポリシーです。
これはRasbperry Piに対応するAWS IoT Thing(モノ)に適用されるポリシーです。
上記のチュートリアルの内容に従い修正を加えます。

2点目はS3バケットに保存するファイルです。
冒頭のページでは、証明書と鍵を配置していました。
今回はこれらに加えて、ジョブドキュメントも作成して配置します。

3点目はSSMドキュメントで実行するコマンドです。
SSMドキュメントはRaspberry Piに対して実行し、AWS IoT Device Clientインストール等のセットアップを行います。
こちらもチュートリアルに沿って修正して、ジョブが実行できるようにします。

4点目はジョブの作成です。
新たにLambda関数を作成し、ジョブを作成します。
そしてこの関数をCloudFormationカスタムリソースに紐付けて、CloudFormationスタック作成時に、自動的に実行されるように設定します。

なお今回作成するLambda関数のランタイム環境はPython3.12とします。

CloudFormationテンプレートファイル

上記の構成をCloudFormationで構築します。
以下のURLにCloudFormationテンプレートを配置しています。

GitHub
awstut-fa/156 at main · awstut-an-r/awstut-fa Contribute to awstut-an-r/awstut-fa development by creating an account on GitHub.

テンプレートファイルのポイント解説

先述の通り、今回の構成は冒頭でご紹介した内容と大部分が同一ですから、変更点を中心に取り上げます。

IoTポリシー

Resources:
  Policy:
    Type: AWS::IoT::Policy
    Properties:
      PolicyDocument:
        Version: 2012-10-17
        Statement:
          - Effect: Allow
            Action:
              - iot:Connect
            Resource:
              - !Sub "arn:aws:iot:${AWS::Region}:${AWS::AccountId}:client/${Thing}"
          - Effect: Allow
            Action:
              - iot:Publish
            Resource:
              - !Sub "arn:aws:iot:${AWS::Region}:${AWS::AccountId}:topic/${PublishTopicName}"
              - !Sub "arn:aws:iot:${AWS::Region}:${AWS::AccountId}:topic/$aws/events/job/*"
              - !Sub "arn:aws:iot:${AWS::Region}:${AWS::AccountId}:topic/$aws/events/jobExecution/*"
              - !Sub "arn:aws:iot:${AWS::Region}:${AWS::AccountId}:topic/$aws/things/${Thing}/jobs/*"
          - Effect: Allow
            Action:
              - iot:Subscribe
            Resource:
              - !Sub "arn:aws:iot:${AWS::Region}:${AWS::AccountId}:topicfilter/${SubscribeTopicName}"
              - !Sub "arn:aws:iot:${AWS::Region}:${AWS::AccountId}:topic/$aws/events/jobExecution/*"
              - !Sub "arn:aws:iot:${AWS::Region}:${AWS::AccountId}:topicfilter/$aws/things/${Thing}/jobs/*"
          - Effect: Allow
            Action:
              - iot:Receive
            Resource:
              - !Sub "arn:aws:iot:${AWS::Region}:${AWS::AccountId}:topic/${SubscribeTopicName}"
              - !Sub "arn:aws:iot:${AWS::Region}:${AWS::AccountId}:topic/$aws/things/${Thing}/jobs/*"
          - Effect: Allow
            Action:
              - iot:DescribeJobExecution
              - iot:GetPendingJobExecutions
              - iot:StartNextPendingJobExecution
              - iot:UpdateJobExecution
            Resource:
              - !Sub "arn:aws:iot:${AWS::Region}:${AWS::AccountId}:topic/$aws/things/${Thing}"
      PolicyName: !Sub "${Prefix}-policy"
Code language: YAML (yaml)

IoTデバイスがジョブを実行するために必要なIoTポリシーを定義します。

以下のページを参考にしてポリシーを作成しました。

あわせて読みたい
ステップ 1: ジョブを実行するために Raspberry Pi を準備する - AWS IoT Core このセクションの手順では、AWS IoT Device Client を使用してジョブを実行するために Raspberry Pi を準備する方法について説明します。

ジョブドキュメントを作成するLambda関数

Resources:
  Function2:
    Type: AWS::Lambda::Function
    Properties:
      Architectures:
        - !Ref Architecture
      Environment:
        Variables:
          ACTIVATION_ID: !Ref ActivationId
          BUCKET_NAME: !Ref BucketName
          CERTIFICATE_NAME: !Ref CertificateName
          JOB_DOCUMENT_NAME: !Ref JobDocumentName
          PRIVATE_KEY_NAME: !Ref PrivateKeyName
          PUBLIC_KEY_NAME: !Ref PublicKeyName
          REGION: !Ref AWS::Region
          THING: !Ref Thing
      Code:
        ZipFile: |
          import boto3
          import cfnresponse
          import os
          
          activation_id = os.environ['ACTIVATION_ID']
          bucket_name = os.environ['BUCKET_NAME']
          certificate_name = os.environ['CERTIFICATE_NAME']
          job_document_name = os.environ['JOB_DOCUMENT_NAME']
          private_key_name = os.environ['PRIVATE_KEY_NAME']
          public_key_name = os.environ['PUBLIC_KEY_NAME']
          region = os.environ['REGION']
          thing = os.environ['THING']
          
          s3_key = '{folder}/{object}'
          
          CREATE = 'Create'
          response_data = {}
          
          iot_client = boto3.client('iot', region_name=region)
          s3_client = boto3.client('s3', region_name=region)
          ssm_client = boto3.client('ssm', region_name=region)
        
          def lambda_handler(event, context):
            try:
              if event['RequestType'] == 'Create':
                iot_response = iot_client.create_keys_and_certificate(
                  setAsActive=True
                )
                print(iot_response)
                
                # certificate
                s3_client.put_object(
                  Body=iot_response['certificatePem'],
                  Bucket=bucket_name,
                  Key=s3_key.format(
                    folder=thing,
                    object=certificate_name
                  )
                )
                
                # public key
                s3_client.put_object(
                  Body=iot_response['keyPair']['PublicKey'],
                  Bucket=bucket_name,
                  Key=s3_key.format(
                    folder=thing,
                    object=public_key_name
                  )
                )
                
                # private key
                s3_client.put_object(
                  Body=iot_response['keyPair']['PrivateKey'],
                  Bucket=bucket_name,
                  Key=s3_key.format(
                    folder=thing,
                    object=private_key_name
                  )
                )

                # job document
                s3_client.put_object(
                  Body='{"operation": "echo","args": ["Hello world!"]}',
                  Bucket=bucket_name,
                  Key=s3_key.format(
                    folder='jobs',
                    object=job_document_name
                  )
                )
                response_data['CertificateArn'] = iot_response['certificateArn']
                certificate_id = iot_response['certificateId']
                
                iot_endpoint_response = iot_client.describe_endpoint(
                  endpointType='iot:Data-ATS'
                )
                print(iot_endpoint_response)
                
                response_data['IoTEndpoint'] = iot_endpoint_response['endpointAddress']
                
                describe_instance_response = ssm_client.describe_instance_information(
                  Filters=[
                    {
                      'Key': 'ActivationIds',
                      'Values': [
                        activation_id
                      ]
                    }
                  ]
                )
                print(describe_instance_response)
                
                response_data['InstanceId'] = describe_instance_response['InstanceInformationList'][0]['InstanceId']
                
              elif event['RequestType'] == 'Delete':
                certificate_id = event['PhysicalResourceId']
              
                # delete objects in s3 bucket
                list_response = s3_client.list_objects_v2(
                  Bucket=bucket_name
                )
                
                if 'Contents' in list_response and len(list_response['Contents']):
                  for obj in list_response['Contents']:
                    delete_response = s3_client.delete_object(
                      Bucket=bucket_name,
                      Key=obj['Key']
                    )
                    print(delete_response)
                  
                # inactive and delete iot cert
                iot_client.update_certificate(
                  certificateId=certificate_id,
                  newStatus='INACTIVE'
                )
                iot_client.delete_certificate(
                  certificateId=certificate_id,
                  forceDelete=True
                )
                
              cfnresponse.send(
                event=event,
                context=context,
                responseStatus=cfnresponse.SUCCESS,
                responseData=response_data,
                physicalResourceId=certificate_id
                )
            except Exception as e:
              print(e)

              cfnresponse.send(
                event=event,
                context=context,
                responseStatus=cfnresponse.FAILED,
                responseData=response_data
                )
      FunctionName: !Sub "${Prefix}-function-02"
      Handler: !Ref Handler
      Runtime: !Ref Runtime
      Role: !GetAtt FunctionRole2.Arn
      Timeout: !Ref Timeout
Code language: YAML (yaml)

本来、この関数の主な役割は、クライアント証明書と鍵を作成し、S3バケットにアップロードすることでした。
これに加えて、今回はジョブドキュメントを作成し、S3バケットにアップロードします。

以下のページを参考にして、ジョブドキュメントを作成しました。

あわせて読みたい
ステップ 2: AWS IoT でジョブを作成して実行する - AWS IoT Core このセクションの手順では、ジョブドキュメントと AWS IoT ジョブリソースを作成します。ジョブリソースを作成した後、AWS IoT はジョブドキュメントを指定されたジョブタ...

この関数をCloudFormationカスタムリソースに関連付けます。
これによって、CloudFormationスタック作成時に、自動的に本関数が実行されるようになります。

SSMドキュメント

Resources:
  RunShellScriptAssociation:
    Type: AWS::SSM::Association
    Properties:
      AssociationName: !Sub "${Prefix}-shellscript-association"
      Name: AWS-RunShellScript
      OutputLocation:
        S3Location:
          OutputS3BucketName: !Ref BucketName
          OutputS3KeyPrefix: shellscript-association-log
      Parameters:
        commands:
          - "apt-get -y install build-essential libssl-dev cmake unzip git python3-pip"
          - !Sub 'su - ${UserName} -c "export PATH=$PATH:~/.local/bin"'
          - !Sub 'su - ${UserName} -c "git clone https://github.com/aws/aws-cli.git"'
          - !Sub 'su - ${UserName} -c "cd aws-cli && git checkout v2 && sudo pip3 install --break-system-packages -r requirements.txt"'
          - !Sub 'su - ${UserName} -c "cd aws-cli && git checkout v2 && sudo pip3 install --break-system-packages ."'
        
          - !Sub 'su - ${UserName} -c "mkdir ~/certs"'
          - !Sub 'su - ${UserName} -c "curl -o ~/certs/AmazonRootCA1.pem https://www.amazontrust.com/repository/AmazonRootCA1.pem"'
          - !Sub 'su - ${UserName} -c "chmod 745 ~"'
          - !Sub 'su - ${UserName} -c "chmod 700 ~/certs"'
          - !Sub 'su - ${UserName} -c "chmod 644 ~/certs/AmazonRootCA1.pem"'
          
          - !Sub 'su - ${UserName} -c "git clone https://github.com/awslabs/aws-iot-device-client aws-iot-device-client"'
          - !Sub 'su - ${UserName} -c "mkdir ~/aws-iot-device-client/build"'
          - !Sub 'su - ${UserName} -c "cd ~/aws-iot-device-client/build && cmake ../"'
          - !Sub 'su - ${UserName} -c "cd ~/aws-iot-device-client/build && cmake --build . --target aws-iot-device-client"'
          
          - !Sub 'su - ${UserName} -c "mkdir ~/dc-configs"'
          - !Sub 'su - ${UserName} -c "mkdir ~/certs/jobs"'
          - !Sub 'su - ${UserName} -c "chmod 745 ~"'
          - !Sub 'su - ${UserName} -c "chmod 700 ~/certs/jobs"'

          - !Sub 'su - ${UserName} -c "sudo aws s3 cp s3://${BucketName}/${Thing}/${CertificateName} ~/certs/jobs/"'
          - !Sub 'su - ${UserName} -c "sudo aws s3 cp s3://${BucketName}/${Thing}/${PrivateKeyName} ~/certs/jobs/"'
          - !Sub 'su - ${UserName} -c "sudo aws s3 cp s3://${BucketName}/${Thing}/${PublicKeyName} ~/certs/jobs/"'
          - !Sub 'su - ${UserName} -c "sudo chown ${UserName}:${UserName} ~/certs/jobs/*"'
          - !Sub 'su - ${UserName} -c "sudo chmod 644 ~/certs/jobs/*"'
          - !Sub 'su - ${UserName} -c "sudo chmod 600 ~/certs/jobs/${PrivateKeyName}"'
          
          - !Sub 'su - ${UserName} -c "chmod 745 ~/dc-configs"'
          - !Sub |
            cat << EOF > /home/${UserName}/dc-configs/dc-jobs-config.json
            {
              "endpoint": "${Endpoint}",
              "cert": "~/certs/jobs/${CertificateName}",
              "key": "~/certs/jobs/${PrivateKeyName}",
              "root-ca": "~/certs/AmazonRootCA1.pem",
              "thing-name": "${Thing}",
              "logging": {
                "enable-sdk-logging": true,
                "level": "DEBUG",
                "type": "STDOUT",
                "file": ""
              },
              "jobs": {
                "enabled": true,
                "handler-directory": ""
              },
              "tunneling": {
                "enabled": false
              },
              "device-defender": {
                "enabled": false,
                "interval": 300
              },
              "fleet-provisioning": {
                "enabled": false,
                "template-name": "",
                "template-parameters": "",
                "csr-file": "",
                "device-key": ""
              },
              "samples": {
                "pub-sub": {
                  "enabled": false,
                  "publish-topic": "",
                  "publish-file": "",
                  "subscribe-topic": "",
                  "subscribe-file": ""
                }
              },
              "config-shadow": {
                "enabled": false
              },
              "sample-shadow": {
                "enabled": false,
                "shadow-name": "",
                "shadow-input-file": "",
                "shadow-output-file": ""
              }
            }
            EOF
          - !Sub 'su - ${UserName} -c "sudo chown ${UserName}:${UserName} ~/dc-configs/dc-jobs-config.json"'
          - !Sub 'su - ${UserName} -c "chmod 644 ~/dc-configs/dc-jobs-config.json"'
          
          - !Sub 'su - ${UserName} -c "sudo mkdir /var/log/aws-iot-device-client"'
          - !Sub 'su - ${UserName} -c "sudo chmod 745 /var/log/aws-iot-device-client"'
          - !Sub 'su - ${UserName} -c "sudo chown ${UserName}:${UserName} /var/log/aws-iot-device-client"'
      Targets:
        - Key: InstanceIds
          Values:
            - !Ref InstanceId
      WaitForSuccessTimeoutSeconds: !Ref WaitForSuccessTimeoutSeconds
Code language: YAML (yaml)

冒頭でご紹介したページからの主な変更点はdc-jobs-config.jsonの内容です。
以前はdc-pubsub-custom-config.jsonという名前で設定ファイルを作成していましたが、内容を一部変更します。
具体的には、jobsのenabledの値をtrueに変更します。

ジョブを作成するLambda関数

Resources:
  Function3:
    Type: AWS::Lambda::Function
    Properties:
      Architectures:
        - !Ref Architecture
      Environment:
        Variables:
          JOB_DOCUMENT_URL: !Sub "https://${BucketName}.s3.${AWS::Region}.amazonaws.com/jobs/${JobDocumentName}"
          REGION: !Ref AWS::Region
          THING_ARN: !Sub "arn:aws:iot:${AWS::Region}:${AWS::AccountId}:thing/${Thing}"
      Code:
        ZipFile: |
          import boto3
          import cfnresponse
          import os
          
          job_document_url = os.environ['JOB_DOCUMENT_URL']
          region = os.environ['REGION']
          thing_arn = os.environ['THING_ARN']
          
          CREATE = 'Create'
          response_data = {}
          
          iot_client = boto3.client('iot', region_name=region)
          
          def lambda_handler(event, context):
            try:
              if event['RequestType'] == 'Create':
                response = iot_client.create_job(
                  jobId='hello-world-job-1',
                  targets=[
                    thing_arn
                  ],
                  documentSource=job_document_url,
                  targetSelection='SNAPSHOT'
                )
                print(response)
                
                job_id = response['jobId']
                
              elif event['RequestType'] == 'Delete':
                job_id = event['PhysicalResourceId']
                
                response = iot_client.delete_job(
                  jobId=job_id,
                  force=True
                )
                print(response)
              
              cfnresponse.send(
                event=event,
                context=context,
                responseStatus=cfnresponse.SUCCESS,
                responseData=response_data,
                physicalResourceId=job_id
                )
            except Exception as e:
              print(e)
              
              job_id = event['PhysicalResourceId']

              cfnresponse.send(
                event=event,
                context=context,
                responseStatus=cfnresponse.FAILED,
                responseData=response_data,
                physicalResourceId=job_id
                )
      FunctionName: !Sub "${Prefix}-function-03"
      Handler: !Ref Handler
      Runtime: !Ref Runtime
      Role: !GetAtt FunctionRole3.Arn
Code language: YAML (yaml)

この関数の主な目的はジョブを作成することです。

Boto3のIoT用クライアントオブジェクトを作成後、create_jobメソッドを実行してジョブを作成します。
作成するジョブの名前は「hello-world-job-1」とします。
ジョブの対象には、Raspberry Piに関連付けているIoT Thing(モノ)を指定します。
作成するジョブの内容には、先述のS3バケットにアップロードしてジョブドキュメントを指定します。

この関数もCloudFormationカスタムリソースに関連付けて、スタック作成時に、本関数が自動的に実行されるようにします。

本関数用のIAMロールは以下です。

Resources:
  FunctionRole3:
    Type: AWS::IAM::Role
    DeletionPolicy: Delete
    Properties:
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          - Effect: Allow
            Action: sts:AssumeRole
            Principal:
              Service:
                - lambda.amazonaws.com
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
      Policies:
        - PolicyName: FunctionPolicy3
          PolicyDocument:
            Version: 2012-10-17
            Statement:
              - Effect: Allow
                Action:
                  - iot:CreateJob
                  - iot:DeleteJob
                Resource:
                  - "*"
              - Effect: Allow
                Action:
                  - s3:GetObject
                Resource:
                  - !Sub "arn:aws:s3:::${BucketName}/jobs/${JobDocumentName}"
Code language: YAML (yaml)

ジョブの作成・削除のための権限(iot:CreateJob, iot:DeleteJob)に加えて、S3バケットに配置されているジョブドキュメントを取得するための権限(s3:GetObject)が必要です。

環境構築

Raspberry PiをSSMに登録する

1つ目のCloudFormationスタックを作成します。

AWS CLIを使用して、CloudFormationスタックを作成します。
上記のテンプレートファイルを任意のS3バケットに設置した上で、以下のコマンドを実行します。

aws cloudformation create-stack \
--stack-name fa-156-01 \
--template-url https://[bucket-name].s3.[region].amazonaws.com/fa-156/fa-156-01.yaml \
--capabilities CAPABILITY_IAM
Code language: Bash (bash)

スタック作成の詳細については、以下のページをご確認ください。

あわせて読みたい
CloudFormationのネストされたスタックで環境を構築する 【CloudFormationのネストされたスタックで環境を構築する方法】 CloudFormationにおけるネストされたスタックを検証します。 CloudFormationでは、スタックをネストす...

このスタックによって、主に以下のリソースが作成されます。

  • クライアント証明書や鍵を配置するS3バケット
  • SSMハイブリッドアクティベーション
  • アクティベーション用のIAMロール

スタックの作成が完了すると、以下の通りハイブリッドアクティベーション用のコードとIDが取得できます。

Detail of SSM 01.

Raspberry PiにSSM Agentをインストールする

Raspberry Piで以下のコマンドを実行してSSM Agentをインストールします。

sudo apt-get -y update
sudo apt-get -y upgrade
sudo apt-get -y install libc6:armhf
mkdir /tmp/ssm
curl https://amazon-ssm-ap-northeast-1.s3.ap-northeast-1.amazonaws.com/latest/debian_arm/ssm-setup-cli -o /tmp/ssm/ssm-setup-cli
sudo chmod +x /tmp/ssm/ssm-setup-cli
sudo /tmp/ssm/ssm-setup-cli -register -activation-code "[activation-code]" -activation-id "[activation-id]" -region "ap-northeast-1"
Code language: Bash (bash)

最後のコマンドに先ほど確認したアクティベーション用のIDとコードを埋め込みます。

手順の詳細については以下のページをご確認ください。

あわせて読みたい
Raspberry Pi 5をセットアップ後、Systems Managerに登録して、AWSからコマンドを実行する 【Raspberry Pi 5をセットアップ後、Systems Managerに登録して、AWSからコマンドを実行する】 AWS Systems ManagerはEC2インスタンスだけでなく、ハイブリッドおよびマ...

SSM Fleet Managerを確認すると、確かにRaspberry PiにSSMエージェントがインストールされて管理対象になっていることがわかります。

Detail of SSM 02.

SSMドキュメントを使用して、Raspberry PiをAWS IoTに登録後、AWS IoT Device Clientをセットアップする

2つ目のCloudFormationスタックを作成します。

先ほどと同様にAWS CLIを使用してスタックを作成します。

$ aws cloudformation create-stack \
--stack-name fa-156-02 \
--template-url https://[bucket-name].s3.[region].amazonaws.com/fa-156/fa-156-02.yaml \
--capabilities CAPABILITY_IAM
Code language: Bash (bash)

このスタックによって、主に以下のリソースが作成されます。

  • IoT Thing(モノ)
  • IoTポリシー
  • クライアント証明書と鍵
  • クライアント証明書をモノおよびIoTポリシーにアタッチ

加えてSSMドキュメントによって、Raspberry Piにクライアント証明書や鍵がダウンロードされて、Raspberry Piをモノとして扱うことができるようになります。
そしてAWS IoT Device Clientを使用するために必要な手続きが実行されます。

SSMドキュメントの実行後、モノに対してジョブドキュメントに基づいたジョブが作成されます。

マネージメントコンソールから作成されたリソースを確認します。

S3バケットを確認します。

Detail of S3 01.

バケットにジョブドキュメントが保存されています。
これを使ってジョブが作成されることになります。

AWS IoTに関係するリソースを確認します。

Diagram of IoT 01.
Diagram of IoT 02.
Diagram of IoT 03.

モノ、証明書、ポリシーが正常に作成されています。

モノに関連づいているジョブを確認します。

Diagram of IoT 04.

確かにジョブが作成されてモノに関連づいています。

Diagram of IoT 05.

ジョブの内容を見ると、確かにジョブドキュメントで指定したものです。

動作確認

AWS IoT Device Clientのインストール状況確認

準備が整いましたので、Raspberry Piにアクセスします。
アクセスはローカルマシンからSSHします。

証明書と鍵の有無を確認します。

awstut@raspberrypi:~ $ ls -l ~/certs/jobs/
total 12
-rw-r--r-- 1 awstut awstut 1220 Apr  5 21:41 device.pem.crt
-rw------- 1 awstut awstut 1675 Apr  5 21:41 private.pem.key
-rw-r--r-- 1 awstut awstut  451 Apr  5 21:41 public.pem.key
Code language: Bash (bash)

確かにS3バケットから証明書および鍵がダウンロードされています。
つまりこのRaspberry PiはAWS IoTデバイスとして動作できます。

AWS IoT Device Clientのインストール状況を確認します。

awstut@raspberrypi:~ $ cd ~/aws-iot-device-client/build/
awstut@raspberrypi:~/aws-iot-device-client/build $ ./aws-iot-device-client --version
v1.9.2-cf76107
Code language: Bash (bash)

確かにAWS IoT Device Clientがインストールされて、使用できる状態であることがわかります。

AWS IoT Device Clientを使用してジョブ実行

以下のページに従い、Raspberry Piでジョブを実行します。

あわせて読みたい
ステップ 2: AWS IoT でジョブを作成して実行する - AWS IoT Core このセクションの手順では、ジョブドキュメントと AWS IoT ジョブリソースを作成します。ジョブリソースを作成した後、AWS IoT はジョブドキュメントを指定されたジョブタ...
awstut@raspberrypi:~/aws-iot-device-client/build $ cd ~/aws-iot-device-client/build
awstut@raspberrypi:~/aws-iot-device-client/build $ ./aws-iot-device-client --config-file ~/dc-configs/dc-jobs-config.json
2024-04-05T22:23:54.453Z [WARN]  {FileUtils.cpp}: Permissions to given file/dir path '/home/awstut/dc-configs/dc-jobs-config.json' is not set to recommended value... {Permissions: {desired: 640, actual: 644}}
2024-04-05T22:23:54.453Z [INFO]  {Config.cpp}: Successfully fetched JSON config file: {
  "endpoint": "a2oxckhng7gmur-ats.iot.ap-northeast-1.amazonaws.com",
  "cert": "~/certs/jobs/device.pem.crt",
  "key": "~/certs/jobs/private.pem.key",
  "root-ca": "~/certs/AmazonRootCA1.pem",
  "thing-name": "fa-156-thing",
  "logging": {
    "enable-sdk-logging": true,
    "level": "DEBUG",
    "type": "STDOUT",
    "file": ""
  },
  "jobs": {
    "enabled": true,
    "handler-directory": ""
  },
  "tunneling": {
    "enabled": false
  },
  "device-defender": {
    "enabled": false,
    "interval": 300
  },
  "fleet-provisioning": {
    "enabled": false,
    "template-name": "",
    "template-parameters": "",
    "csr-file": "",
    "device-key": ""
  },
  "samples": {
    "pub-sub": {
      "enabled": false,
      "publish-topic": "",
      "publish-file": "",
      "subscribe-topic": "",
      "subscribe-file": ""
    }
  },
  "config-shadow": {
    "enabled": false
  },
  "sample-shadow": {
    "enabled": false,
    "shadow-name": "",
    "shadow-input-file": "",
    "shadow-output-file": ""
  }
}

2024-04-05T22:23:54.453Z [INFO]  {FileUtils.cpp}: Successfully create directory /home/awstut/.aws-iot-device-client/sample-shadow/ with required permissions 700
2024-04-05T22:23:54.453Z [INFO]  {Config.cpp}: ~/.aws-iot-device-client/sample-shadow/default-sample-shadow-document
2024-04-05T22:23:54.453Z [INFO]  {Config.cpp}: Succesfully create default file: /home/awstut/.aws-iot-device-client/sample-shadow/default-sample-shadow-document required for storage of shadow document
2024-04-05T22:23:54.453Z [DEBUG] {Config.cpp}: Did not find a runtime configuration file, assuming Fleet Provisioning has not run for this device
2024-04-05T22:23:54.453Z [DEBUG] {Config.cpp}: Did not find a http proxy config file /home/awstut/.aws-iot-device-client/http-proxy.conf, assuming HTTP proxy is disabled on this device
2024-04-05T22:23:54.454Z [DEBUG] {EnvUtils.cpp}: Updated PATH environment variable to: /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/local/games:/usr/games:/home/awstut/.aws-iot-device-client:/home/awstut/.aws-iot-device-client/jobs:/home/awstut/aws-iot-device-client/build:/home/awstut/aws-iot-device-client/build/jobs
2024-04-05T22:23:54.454Z [DEBUG] {LockFile.cpp}: creating lockfile
2024-04-05T22:23:54.454Z [INFO]  {Main.cpp}: Now running AWS IoT Device Client version v1.9.2-cf76107
2024-04-05T22:23:54.454Z [INFO]  {SharedCrtResourceManager.cpp}: SDK logging is enabled. Check /var/log/aws-iot-device-client/sdk.log for SDK logs.
2024-04-05T22:23:54.454Z [DEBUG] {Retry.cpp}: Retryable function starting, it will retry until success
2024-04-05T22:23:54.496Z [INFO]  {SharedCrtResourceManager.cpp}: Establishing MQTT connection with client id fa-156-thing...
2024-04-05T22:23:54.761Z [INFO]  {SharedCrtResourceManager.cpp}: MQTT connection established with return code: 0
2024-04-05T22:23:54.761Z [INFO]  {SharedCrtResourceManager.cpp}: Shared MQTT connection is ready!
2024-04-05T22:23:54.761Z [INFO]  {Main.cpp}: Provisioning with Secure Elements is disabled
2024-04-05T22:23:54.761Z [INFO]  {Main.cpp}: Config shadow is disabled
2024-04-05T22:23:54.761Z [INFO]  {Main.cpp}: Jobs is enabled
2024-04-05T22:23:54.761Z [INFO]  {Main.cpp}: Secure Tunneling is disabled
2024-04-05T22:23:54.761Z [INFO]  {Main.cpp}: Device Defender is disabled
2024-04-05T22:23:54.761Z [INFO]  {Main.cpp}: Sample shadow is disabled
2024-04-05T22:23:54.761Z [INFO]  {Main.cpp}: Pub Sub is disabled
2024-04-05T22:23:54.761Z [INFO]  {Main.cpp}: Sensor Publish is disabled
2024-04-05T22:23:54.761Z [INFO]  {SharedCrtResourceManager.cpp}: Starting Device Client features.
2024-04-05T22:23:54.761Z [DEBUG] {FeatureRegistry.cpp}: Attempting to start Jobs
2024-04-05T22:23:54.761Z [INFO]  {Main.cpp}: Client base has been notified that Jobs has started
2024-04-05T22:23:54.761Z [INFO]  {JobsFeature.cpp}: Running Jobs!
2024-04-05T22:23:54.761Z [DEBUG] {JobsFeature.cpp}: Attempting to subscribe to startNextPendingJobExecution accepted and rejected
2024-04-05T22:23:54.761Z [DEBUG] {JobsFeature.cpp}: Attempting to subscribe to nextJobChanged events
2024-04-05T22:23:54.761Z [DEBUG] {JobsFeature.cpp}: Attempting to subscribe to updateJobExecutionStatusAccepted for jobId +
2024-04-05T22:23:54.823Z [DEBUG] {JobsFeature.cpp}: Ack received for SubscribeToStartNextJobAccepted with code {0}
2024-04-05T22:23:54.823Z [DEBUG] {JobsFeature.cpp}: Ack received for SubscribeToUpdateJobExecutionAccepted with code {0}
2024-04-05T22:23:54.823Z [DEBUG] {JobsFeature.cpp}: Attempting to subscribe to updateJobExecutionStatusRejected for jobId +
2024-04-05T22:23:54.828Z [DEBUG] {JobsFeature.cpp}: Ack received for SubscribeToStartNextJobRejected with code {0}
2024-04-05T22:23:54.830Z [DEBUG] {JobsFeature.cpp}: Ack received for SubscribeToNextJobChanged with code {0}
2024-04-05T22:23:54.888Z [DEBUG] {JobsFeature.cpp}: Ack received for SubscribeToUpdateJobExecutionRejected with code {0}
2024-04-05T22:23:54.889Z [DEBUG] {JobsFeature.cpp}: Publishing startNextPendingJobExecutionRequest
2024-04-05T22:23:54.925Z [DEBUG] {JobsFeature.cpp}: Ack received for StartNextPendingJobPub with code {0}
2024-04-05T22:23:54.926Z [DEBUG] {JobsFeature.cpp}: We have not seen a job yet, this is not a duplicate job notification
2024-04-05T22:23:54.926Z [DEBUG] {JobsFeature.cpp}: Attempting to update job execution status!
2024-04-05T22:23:54.926Z [INFO]  {JobsFeature.cpp}: Executing job: hello-world-job-1
2024-04-05T22:23:54.926Z [DEBUG] {Retry.cpp}: Retryable function starting, it will retry until success
2024-04-05T22:23:54.927Z [DEBUG] {JobsFeature.cpp}: Created EphemeralPromise for ClientToken NETuKkPetx in the updateJobExecution promises map
2024-04-05T22:23:54.927Z [INFO]  {JobEngine.cpp}: About to execute step with name: echo
2024-04-05T22:23:54.927Z [DEBUG] {JobEngine.cpp}: Assuming executable is in PATH
2024-04-05T22:23:54.927Z [INFO]  {JobEngine.cpp}: About to execute: echo  Hello world! 
2024-04-05T22:23:54.927Z [DEBUG] {JobEngine.cpp}: Child process now running
2024-04-05T22:23:54.927Z [DEBUG] {JobEngine.cpp}: Child process about to call execvp
2024-04-05T22:23:54.927Z [DEBUG] {JobEngine.cpp}: Parent process now running, child PID is 19340
2024-04-05T22:23:54.927Z [DEBUG] {19340}:  Hello world!
2024-04-05T22:23:54.927Z [DEBUG] {JobEngine.cpp}: JobEngine finished waiting for child process, returning 0
2024-04-05T22:23:54.927Z [INFO]  {JobsFeature.cpp}: Job exited with status: 0
2024-04-05T22:23:54.927Z [INFO]  {JobsFeature.cpp}: Job executed successfully!
2024-04-05T22:23:54.927Z [DEBUG] {JobsFeature.cpp}: Attempting to update job execution status!
2024-04-05T22:23:54.927Z [DEBUG] {Retry.cpp}: Retryable function starting, it will retry until success
2024-04-05T22:23:54.927Z [DEBUG] {JobsFeature.cpp}: Created EphemeralPromise for ClientToken ucviLUeZRe in the updateJobExecution promises map
2024-04-05T22:23:54.954Z [DEBUG] {JobsFeature.cpp}: Ack received for PublishUpdateJobExecutionStatus with code {0}
2024-04-05T22:23:54.954Z [DEBUG] {JobsFeature.cpp}: Removing ClientToken NETuKkPetx from the updateJobExecution promises map
2024-04-05T22:23:54.954Z [DEBUG] {JobsFeature.cpp}: Success response after UpdateJobExecution for job hello-world-job-1
2024-04-05T22:23:54.979Z [DEBUG] {JobsFeature.cpp}: Ack received for PublishUpdateJobExecutionStatus with code {0}
2024-04-05T22:23:54.979Z [DEBUG] {JobsFeature.cpp}: Removing ClientToken ucviLUeZRe from the updateJobExecution promises map
2024-04-05T22:23:54.979Z [DEBUG] {JobsFeature.cpp}: Success response after UpdateJobExecution for job hello-world-job-1
2024-04-05T22:23:55.230Z [INFO]  {JobsFeature.cpp}: No pending jobs are scheduled, waiting for the next incoming job
Code language: Bash (bash)

ログを見ると、確かにジョブが実行されたことがわかります。

改めてマネージメントコンソールにアクセスして、ジョブのステータスを確認します。

Diagram of IoT 06.

こちらでも「Completed」に変化しました。

このようにAWS IoT Device Clientを使用して、リモートアクション(ジョブ)を実行することができました。

まとめ

Raspberry Piにおいて、AWS IoT Device Clientを使用して、リモートアクション(ジョブ)を実行する方法を確認しました。