Demonstrate remote actions (jobs) using Raspberry Pi and AWS IoT Device Client

TOC

Demonstrate remote actions (jobs) using Raspberry Pi and AWS IoT Device Client

The following page shows how to install the AWS IoT Device Client on a Raspberry Pi.

あわせて読みたい
Install the AWS IoT Device Client on the Raspberry Pi registered in Systems Manager 【Install the AWS IoT Device Client on the Raspberry Pi registered in Systems Manager】 This page describes the AWS IoT Device Client. The AWS IoT Device Cli...

We also covered how to send and receive MQTT messages using the AWS IoT Device Client in the following pages.

あわせて読みたい
Demonstrate AWS IoT Device Client MQTT message communication using a Raspberry Pi registered with... 【Demonstrate AWS IoT Device Client MQTT message communication using a Raspberry Pi registered with Systems Manager】 The following page shows how to install...

As a continuation, we will experience remote actions (jobs) using AWS IoT Device Client, following the official tutorial below.

あわせて読みたい
Create and run the job in AWS IoT with AWS IoT Device Client - AWS IoT Core AWS IoT Device Client tutorial to create the job document and run the IoT job on a single device.

Environment

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

The basic structure is the same as the page introduced at the beginning of this document.

There are four changes.

The first point is the IoT Policy.
This is the policy that applies to the AWS IoT Thing that correspond to the Rasbperry Pi.
Make modifications according to the above tutorial.

The second point is the file to be stored in the S3 bucket.
In the opening page, we placed the certificate and key.
This time, in addition to these, we will also create and place a job document.

The third point is the command to be executed in the SSM document.
The SSM document runs against the Raspberry Pi and sets up the AWS IoT Device Client installation, etc.
This one will also be modified according to the tutorial so that the job can be executed.

The fourth point is to create a job.
Create a new Lambda function and create a job.
Then, attach this function to a CloudFormation custom resource and configure it to run automatically when the CloudFormation stack is created.

The runtime environment for the Lambda functions to be created this time is Python 3.12.

CloudFormation template files

The above configuration is built with CloudFormation.
The CloudFormation template is placed at the following URL

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.

Explanation of key points of template files

As mentioned earlier, the structure of this issue is largely the same as that introduced at the beginning of this report, so we will focus on the changes.

IoT Policy

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)

Define the IoT policies required for IoT devices to perform jobs.

The following pages were used to create the policy.

あわせて読みたい
Prepare the Raspberry Pi to run jobs - AWS IoT Core Tutorial demo that shows how to provision your Raspberry Pi and configure Device Client for jobs.

Lambda function to create job documents

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)

Originally, the primary role of this function was to create the client certificate and key and upload them to the S3 bucket.
In addition to this, this time the job document is created and uploaded to the S3 bucket.

The following pages were used to create the job documentation.

あわせて読みたい
Create and run the job in AWS IoT with AWS IoT Device Client - AWS IoT Core AWS IoT Device Client tutorial to create the job document and run the IoT job on a single device.

Associate this function with your CloudFormation custom resource.
This will cause this function to be automatically executed when the CloudFormation stack is created.

SSM Document

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)

The main change from the page mentioned at the beginning of this document is the contents of dc-jobs-config.json.
Previously, the configuration file was named dc-pubsub-custom-config.json, but some of the contents will be changed.
Specifically, change the value of enabled for jobs to true.

Lambda function to create a job

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)

The main purpose of this function is to create a job.

After creating a client object for IoT in Boto3, run the create_job method to create a job.
The name of the job to be created is “hello-world-job-1”.
For the target of the job, specify an IoT Thing associated with the Raspberry Pi.
For the content of the job to be created, specify the job document by uploading it to the S3 bucket mentioned above.

This function should also be associated with the CloudFormation custom resource so that this function is automatically executed when the stack is created.

The IAM role for this function is as follows

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)

In addition to the permissions for creating and deleting jobs (iot:CreateJob, iot:DeleteJob), you must have permission to retrieve job documents placed in S3 buckets (s3:GetObject).

Architecting

Registering the Raspberry Pi with SSM

Create the first CloudFormation stack.

Create a CloudFormation stack using the AWS CLI.
After placing the above template file in any S3 bucket, execute the following command

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)

For more information on creating stacks, please see the following page.

あわせて読みたい
CloudFormation’s nested stack 【How to build an environment with a nested CloudFormation stack】 Examine nested stacks in CloudFormation. CloudFormation allows you to nest stacks. Nested ...

This stack primarily creates the following resources

  • S3 bucket to place client certificates and keys
  • SSM Hybrid Activation
  • IAM role for activation

Once the stack has been created, you will receive a hybrid activation code and ID as shown below.

Detail of SSM 01.

Install SSM Agent on Raspberry Pi

Run the following command on the Raspberry Pi to install 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)

Embed the activation ID and code you identified earlier in the last command.

Please refer to the following page for details on the procedure.

あわせて読みたい
After setting up the Raspberry Pi, register it in Systems Manager and run commands from AWS 【After setting up the Raspberry Pi, register it in Systems Manager and run commands from AWS】 AWS Systems Manager can manage not only EC2 instances, but al...

Checking SSM Fleet Manager, we see that the SSM agent is indeed installed and managed on the Raspberry Pi.

Detail of SSM 02.

Set up AWS IoT Device Client after registering Raspberry Pi to AWS IoT using SSM documentation

Create a second CloudFormation stack.

Create a stack using the AWS CLI as before.

$ 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)

This stack primarily creates the following resources

  • IoT Thing
  • IoT Policy
  • Client Certificates and Keys
  • Attach client certificates to Thing and IoT policies

In addition, the SSM document will download the client certificate and key to the Raspberry Pi, allowing the Raspberry Pi to be treated as a thing.
The necessary steps to use the AWS IoT Device Client are then performed.

After the SSM document is executed, a job based on the job document is created for Thing.

Check the resources created from the management console.

Check the S3 bucket.

Detail of S3 01.

The job document is stored in the bucket.
This will be used to create the job.

Identify resources related to AWS IoT.

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

Mono, certificate, and policy have been successfully created.

Check the jobs associated with Thing.

Diagram of IoT 04.

Indeed, jobs are created and associated with things.

Diagram of IoT 05.

If you look at the job content, it is indeed what was specified in the job document.

Action Check

Check the installation status of AWS IoT Device Client

Now that you are ready, access the Raspberry Pi.
Access is done via SSH from the local machine.

Check the availability of certificates and keys.

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)

Indeed, the certificate and key are downloaded from the S3 bucket.
This means that this Raspberry Pi can act as an AWS IoT device.

Check the installation status of 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)

You can see that the AWS IoT Device Client is indeed installed and ready to use.

Job execution using AWS IoT Device Client

Follow the page below to run the job on the Raspberry Pi.

あわせて読みたい
Create and run the job in AWS IoT with AWS IoT Device Client - AWS IoT Core AWS IoT Device Client tutorial to create the job document and run the IoT job on a single device.
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)

The log shows that the job was indeed executed.

Access the management console again to check the status of the job.

Diagram of IoT 06.

Here, too, it has changed to “Completed”.

Thus, we were able to execute a remote action (job) using the AWS IoT Device Client.

Summary

We have identified how to execute a remote action (job) on a Raspberry Pi using the AWS IoT Device Client.

TOC