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.
We also covered how to send and receive MQTT messages using the AWS IoT Device Client in the following pages.
As a continuation, we will experience remote actions (jobs) using AWS IoT Device Client, following the official tutorial below.
Environment
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
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.
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.
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.
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.
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.
Checking SSM Fleet Manager, we see that the SSM agent is indeed installed and managed on the Raspberry Pi.
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.
The job document is stored in the bucket.
This will be used to create the job.
Identify resources related to AWS IoT.
Mono, certificate, and policy have been successfully created.
Check the jobs associated with Thing.
Indeed, jobs are created and associated with things.
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.
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.
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.