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 Client is free, open-source, modular software written in C++ that you can compile and install on your Embedded Linux based IoT devices to access AWS IoT Core, AWS IoT Device Management, and AWS IoT Device Defender features by default. It serves as a reference implementation for your IoT devices to work with AWS IoT services, with operational best practices baked in – using it is the easiest way to create a proof-of-concept (PoC) for your IoT project.
aws-iot-device-client
This time, we will follow the official AWS tutorial and install the AWS IoT Device Client on a Raspberry Pi.
The Raspberry Pi to be used will be registered with Systems Manager.
This is to send commands through SSM to install the AWS IoT Device Client.
Environment
The composition is divided into two main parts.
The first is for registering the Raspberry Pi with Systems Manager.
Run the Lambda function associated with the CloudFormation custom resource to create a hybrid activation for registration.
The second is related to AWS IoT.
The Lambda function is used to create the resources needed to register the Raspberry Pi as an IoT device.
This function is also associated with a custom resource.
The generated resources will include client certificates and keys, which will be placed in an S3 bucket.
This is to allow the Raspberry Pi to download these resources.
From the AWS side, send commands to the Raspbbery Pi to set up the AWS IoT Device Client.
Specifically, this takes the form of creating an SSM association to the Raspberry Pi and running a shell script.
The runtime environment for Lambda functions is Python 3.12.
CloudFormation template files
In the above configuration, CloudFormation is used to build the AWS-side configuration.
The CloudFormation templates are placed at the following URL
Registering the Raspberry Pi with SSM
Changes to IAM Roles for SSM
Register your Rraspberry Pi with SSM.
Registration is a two-step process.
- Create a hybrid activation for Systems Manager
- Install SSM agent on Raspberry Pi while using activation code
Please refer to the following page for specific procedures.
What differs from the above page is the IAM role for SSM.
Resources:
SSMServiceRole:
Type: AWS::IAM::Role
DeletionPolicy: Delete
Properties:
AssumeRolePolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Action: sts:AssumeRole
Principal:
Service:
- ssm.amazonaws.com
Condition:
StringEquals:
aws:SourceAccount: !Ref AWS::AccountId
ArnEquals:
aws:SourceArn: !Sub "arn:aws:ssm:${AWS::Region}:${AWS::AccountId}:*"
ManagedPolicyArns:
- arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore
Policies:
- PolicyName: SSMServiceRolePolicy
PolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Action:
- s3:GetObject
- s3:PutObject
Resource:
- !Sub "arn:aws:s3:::${BucketName}/*"
Code language: YAML (yaml)
This IAM role defines the AWS actions that the Raspberry Pi can perform.
In this case, we will allow access to the S3 bucket.
This bucket has two uses.
The first is used to install certificates and keys for AWS IoT.
These objects will be downloaded and used by the Raspberry Pi, so allow s3:GetObject.
The second is used to place the execution log for the SSM association.
Since the logs will be placed from the Raspberry Pi, s3:PutObject is allowed.
CloudFormation Stacks Creation
Create a CloudFormation stack using the AWS CLI.
Place the above template files in any S3 bucket and then execute the following command
$ aws cloudformation create-stack \
--stack-name fa-153-01 \
--template-url https://[bucket-name].s3.[region].amazonaws.com/fa-153/fa-153-01.yaml \
--capabilities CAPABILITY_IAM
Code language: Bash (bash)
For more information on creating stacks, please see the following page.
Check the relevant stack on the CloudFormation page of the Management Console.
We have indeed obtained a code and ID for hybrid activation.
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.
After the SSM Agent installation is successfully completed, the Agent will automatically start.
awstut@raspberrypi:~ $ sudo systemctl status amazon-ssm-agent
● amazon-ssm-agent.service - amazon-ssm-agent
Loaded: loaded (/lib/systemd/system/amazon-ssm-agent.service; enabled; preset: enabled)
Active: active (running) since Thu 2024-03-21 22:10:02 JST; 2 days ago
Main PID: 2978 (amazon-ssm-agen)
Tasks: 24 (limit: 9253)
CPU: 6min 11.989s
CGroup: /system.slice/amazon-ssm-agent.service
├─2978 /usr/bin/amazon-ssm-agent
└─2996 /usr/bin/ssm-agent-worker
Code language: Bash (bash)
Check SSM Fleet Manager.
Indeed, the Raspberry Pi is registered as a Managed Node.
Register your Raspberry Pi with AWS IoT
Obtain the instance ID assigned to the Raspberry Pi
Registering a Raspberry Pi as an IoT device with AWS IoT involves the following two processes
- Create an AWS IoT resource.
- Upload client certificate and key for AWS IoT from S3 bucket.
To register your Raspberry Pi with AWS IoT, create the following resources
- Thing
- IoT Policy
- Client certificate
- Private and Public Keys
- Attachment of objects and certificates
- Attach IoT policies and certificates
Please refer to the following page for specific procedures.
The difference from the above page is the Lambda function that creates client certificates and keys.
Resources:
Function2:
Type: AWS::Lambda::Function
Properties:
Architectures:
- !Ref Architecture
Environment:
Variables:
ACTIVATION_ID: !Ref ActivationId
BUCKET_NAME: !Ref BucketName
CERTIFICATE_NAME: !Ref CertificateName
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']
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
)
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
)
)
response_data['CertificateArn'] = iot_response['certificateArn']
certificate_id = iot_response['certificateId']
iot_endpoint_response = iot_client.describe_endpoint(
endpointType='iot:Data-ATS'
)
response_data['IoTEndpoint'] = iot_endpoint_response['endpointAddress']
describe_instance_response = ssm_client.describe_instance_information(
Filters=[
{
'Key': 'ActivationIds',
'Values': [
activation_id
]
}
]
)
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)
certificate_id = event['PhysicalResourceId']
cfnresponse.send(
event=event,
context=context,
responseStatus=cfnresponse.FAILED,
responseData=response_data,
physicalResourceId=certificate_id
)
FunctionName: !Sub "${Prefix}-function-02"
Handler: !Ref Handler
Runtime: !Ref Runtime
Role: !GetAtt FunctionRole2.Arn
Timeout: !Ref Timeout
Code language: YAML (yaml)
The change is to get the instance ID assigned to the Raspberry Pi.
Execute the describe_instance_information method of the client object for SSM in boto3 to get the ID.
Search based on the ID of the hybrid activation.
The retrieved instance ID is used to create the SSM association described below.
Set up AWS IoT Device Client using SSM
The procedure for installing AWS IoT Device Client on a Raspberry Pi is basically the same as the procedure described on the official AWS page below.
This time, however, instead of manually tracing the installation procedure, we will send commands from the AWS side to proceed with the setup automatically.
Specifically, we will create an SSM association and run the command through SSM Run Commands.
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 ~/policies"'
- !Sub 'su - ${UserName} -c "mkdir ~/messages"'
- !Sub 'su - ${UserName} -c "mkdir ~/certs/testconn"'
- !Sub 'su - ${UserName} -c "mkdir ~/certs/pubsub"'
- !Sub 'su - ${UserName} -c "mkdir ~/certs/jobs"'
- !Sub 'su - ${UserName} -c "chmod 745 ~"'
- !Sub 'su - ${UserName} -c "chmod 700 ~/certs/testconn"'
- !Sub 'su - ${UserName} -c "chmod 700 ~/certs/pubsub"'
- !Sub 'su - ${UserName} -c "chmod 700 ~/certs/jobs"'
- !Sub 'su - ${UserName} -c "sudo aws s3 cp s3://${BucketName}/${Thing}/${CertificateName} ~/certs/testconn/"'
- !Sub 'su - ${UserName} -c "sudo aws s3 cp s3://${BucketName}/${Thing}/${PrivateKeyName} ~/certs/testconn/"'
- !Sub 'su - ${UserName} -c "sudo aws s3 cp s3://${BucketName}/${Thing}/${PublicKeyName} ~/certs/testconn"'
- !Sub 'su - ${UserName} -c "sudo chown ${UserName}:${UserName} ~/certs/testconn/*"'
- !Sub 'su - ${UserName} -c "sudo chmod 644 ~/certs/testconn/*"'
- !Sub 'su - ${UserName} -c "sudo chmod 600 ~/certs/testconn/${PrivateKeyName}"'
- !Sub 'su - ${UserName} -c "chmod 745 ~/dc-configs"'
- !Sub |
cat << EOF > /home/${UserName}/dc-configs/dc-testconn-config.json
{
"endpoint": "${Endpoint}",
"cert": "~/certs/testconn/${CertificateName}",
"key": "~/certs/testconn/${PrivateKeyName}",
"root-ca": "~/certs/AmazonRootCA1.pem",
"thing-name": "${Thing}",
"logging": {
"enable-sdk-logging": true,
"level": "DEBUG",
"type": "STDOUT",
"file": ""
},
"jobs": {
"enabled": false,
"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": true,
"publish-topic": "${PublishTopicName}",
"publish-file": "",
"subscribe-topic": "${SubscribeTopicName}",
"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-testconn-config.json"'
- !Sub 'su - ${UserName} -c "chmod 644 ~/dc-configs/dc-testconn-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 SSM association executes the SSM document AWS-RunShellScript.
This document allows the specified command to be executed on the target instance.
The command we will be executing is the one discussed on the above page.
But with the following two changes
The first point uses the su command to specify the user who will execute each command.
As covered in detail on the following page, the user who executes the commands run by AWS-RunShellScript is root.
This time, the command will be executed against the Raspberry Pi, but this time as the user (awstut) specified during the Raspberry Pi OS installation.
The second point is about handling AWS IoT resources.
In the page introduced at the beginning of this chapter, the flow is to create things and IoT policies within the Raspberry Pi.
But in this configuration, they will be created using CloudFormation, so we will not execute any commands related to this.
The certificate and key are created by the Lambda function mentioned above and placed in the S3 bucket.
So we download them from the bucket using the AWS CLI.
CloudFormation Stacks Creation
Create a CloudFormation stack using the AWS CLI.
Place the above template file in any S3 bucket and then execute the following command
aws cloudformation create-stack \
--stack-name fa-153-02 \
--template-url https://[bucket-name].s3.[region].amazonaws.com/fa-153/fa-153-02.yaml \
--capabilities CAPABILITY_IAM
Code language: Bash (bash)
Resource Acknowledgement
Access the Management Console to see the resources that have been created.
Identify resources related to AWS IoT.
Thing, IoT policy, and certificate have been created.
And you can also see that the certificate is attached to the thing and policy.
Check the S3 bucket.
Indeed, the certificate and key are in place.
This means that the Lambda functions associated with the CloudFormation custom resources were automatically run when the stack was created and uploaded to this bucket after they were created.
Check Systems Manager.
Looking at SSM State Manager, the SSM association is indeed created.
Next, check SSM Run Command.
You can see that the command has indeed been executed against the Raspberry Pi.
From the above, we can see that the SSM document AWS-RunShellScript was used to send the commands related to the AWS IoT Device Client installation to the Raspbbery Pi and successfully. It means that it was completed successfully.
Operation Check
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/testconn
total 12
-rw-r--r-- 1 awstut awstut 1220 Mar 21 22:11 device.pem.crt
-rw------- 1 awstut awstut 1679 Mar 21 22:11 private.pem.key
-rw-r--r-- 1 awstut awstut 451 Mar 21 22:11 public.pem.key
Code language: Bash (bash)
Indeed, the certificate and key are downloaded from the S3 bucket.
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.
Finally, use the AWS IoT Device Client to send MQTT messages to the AWS IoT Core.
For sending the message, proceed as per the following page.
awstut@raspberrypi:~/aws-iot-device-client/build $ ./aws-iot-device-client --config-file ~/dc-configs/dc-testconn-config.json
2024-03-24T09:04:23.545Z [WARN] {FileUtils.cpp}: Permissions to given file/dir path '/home/awstut/dc-configs/dc-testconn-config.json' is not set to recommended value... {Permissions: {desired: 640, actual: 644}}
2024-03-24T09:04:23.545Z [WARN] {Config.cpp}: Key {publish-file} was provided in the JSON configuration file with an empty value
2024-03-24T09:04:23.545Z [WARN] {Config.cpp}: Key {subscribe-file} was provided in the JSON configuration file with an empty value
2024-03-24T09:04:23.545Z [INFO] {Config.cpp}: Successfully fetched JSON config file: {
"endpoint": "a2oxckhng7gmur-ats.iot.ap-northeast-1.amazonaws.com",
"cert": "~/certs/testconn/device.pem.crt",
"key": "~/certs/testconn/private.pem.key",
"root-ca": "~/certs/AmazonRootCA1.pem",
"thing-name": "fa-153-thing",
"logging": {
"enable-sdk-logging": true,
"level": "DEBUG",
"type": "STDOUT",
"file": ""
},
"jobs": {
"enabled": false,
"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": true,
"publish-topic": "test/dc/pubtopic",
"publish-file": "",
"subscribe-topic": "test/dc/subtopic",
"subscribe-file": ""
}
},
"config-shadow": {
"enabled": false
},
"sample-shadow": {
"enabled": false,
"shadow-name": "",
"shadow-input-file": "",
"shadow-output-file": ""
}
}
2024-03-24T09:04:23.545Z [INFO] {FileUtils.cpp}: Successfully create directory /home/awstut/.aws-iot-device-client/sample-shadow/ with required permissions 700
2024-03-24T09:04:23.545Z [INFO] {Config.cpp}: ~/.aws-iot-device-client/sample-shadow/default-sample-shadow-document
2024-03-24T09:04:23.545Z [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-03-24T09:04:23.545Z [DEBUG] {Config.cpp}: Did not find a runtime configuration file, assuming Fleet Provisioning has not run for this device
2024-03-24T09:04:23.545Z [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-03-24T09:04:23.545Z [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-03-24T09:04:23.545Z [DEBUG] {LockFile.cpp}: creating lockfile
2024-03-24T09:04:23.545Z [INFO] {Main.cpp}: Now running AWS IoT Device Client version v1.9.2-cf76107
2024-03-24T09:04:23.545Z [INFO] {SharedCrtResourceManager.cpp}: SDK logging is enabled. Check /var/log/aws-iot-device-client/sdk.log for SDK logs.
2024-03-24T09:04:23.546Z [DEBUG] {Retry.cpp}: Retryable function starting, it will retry until success
2024-03-24T09:04:23.588Z [INFO] {SharedCrtResourceManager.cpp}: Establishing MQTT connection with client id fa-153-thing...
2024-03-24T09:04:23.946Z [INFO] {SharedCrtResourceManager.cpp}: MQTT connection established with return code: 0
2024-03-24T09:04:23.946Z [INFO] {SharedCrtResourceManager.cpp}: Shared MQTT connection is ready!
2024-03-24T09:04:23.946Z [INFO] {Main.cpp}: Provisioning with Secure Elements is disabled
2024-03-24T09:04:23.946Z [INFO] {Main.cpp}: Config shadow is disabled
2024-03-24T09:04:23.946Z [INFO] {Main.cpp}: Jobs is disabled
2024-03-24T09:04:23.946Z [INFO] {Main.cpp}: Secure Tunneling is disabled
2024-03-24T09:04:23.946Z [INFO] {Main.cpp}: Device Defender is disabled
2024-03-24T09:04:23.946Z [INFO] {Main.cpp}: Sample shadow is disabled
2024-03-24T09:04:23.946Z [INFO] {Main.cpp}: PubSub is enabled
2024-03-24T09:04:23.946Z [INFO] {samples/PubSubFeature.cpp}: Creating Pub/Sub file: /home/awstut/.aws-iot-device-client/pubsub/publish-file.txt
2024-03-24T09:04:23.946Z [INFO] {samples/PubSubFeature.cpp}: Creating Pub/Sub file: /home/awstut/.aws-iot-device-client/pubsub/subscribe-file.txt
2024-03-24T09:04:23.946Z [INFO] {Main.cpp}: Sensor Publish is disabled
2024-03-24T09:04:23.946Z [INFO] {SharedCrtResourceManager.cpp}: Starting Device Client features.
2024-03-24T09:04:23.946Z [DEBUG] {FeatureRegistry.cpp}: Attempting to start Pub Sub Sample
2024-03-24T09:04:23.946Z [INFO] {samples/PubSubFeature.cpp}: Starting Pub Sub Sample
2024-03-24T09:04:23.947Z [INFO] {Main.cpp}: Client base has been notified that Pub Sub Sample has started
2024-03-24T09:04:23.975Z [DEBUG] {samples/PubSubFeature.cpp}: PublishCompAck: PacketId:(Pub Sub Sample), ErrorCode:0
2024-03-24T09:04:23.997Z [DEBUG] {samples/PubSubFeature.cpp}: SubAck: PacketId:(Pub Sub Sample), ErrorCode:0
Code language: Bash (bash)
The log shows that MQTT messages were successfully sent.
Then check the MQTT test client page of the Managed Console.
After waiting with test/dc/pubtopic subscribed, the message was indeed sent.
This was sent by the AWS IoT Device Client on the Raspberry Pi.
Following the tutorial, I was indeed able to send a message using the AWS IoT Device Client.
Summary
We have installed the AWS IoT Device Client on the Raspberry Pi and have identified how to send test messages to the AWS IoT Core.
We were also able to set up the AWS IoT Device Client by registering the Raspberry Pi with Systems Manager and sending commands from SSM.