This article is part of the series "Generative-AI-driven Chatbot for the Factory Plant" implementing a P.O.C. about an environmental pollution monitoring (PM2.5/PM10) to safeguard workers in a production plant.
This a techie article, if you are interested only on the high level use case aspects you can skip it.
Disclaimer
The information provided in this document is for informational purposes only and is subject to change. While efforts have been made to ensure the accuracy and reliability of the content, no guarantees are made regarding the completeness, reliability, or suitability of the information.
The author shall not be liable for any losses, injuries, or damages arising from the use or reliance on the information contained herein.
The AWS resources provisioned following this article can generate costs. Make sure to delete all the resources created at the end of the exercise.
Overview
In this section you focus on the IoT part configuring the gateway to:
- directly read data from the PM sensor
- authenticate to AWS IoT Core
- periodically push PM values to AWS IoT Core
- verify availability of the data
1. Setup certificates and IoT cloud resources
The following steps assume you have:
- AWS CLI installed and configured
- OpenSSL installed
- NodeJS (v18 or v20) installed
- Optional - Terraform (>= v.1.9.5) if you prefer to work with IaC approach instead of running AWS CLI commands
1.1 Prepare the working location
Open the Terminal to start with the following commands:
# set required env variables for AWS CLI
export AWS_DEFAULT_REGION=<your-region>
export AWS_ACCOUNT_ID=<your-account-id>
# move to user home directory
cd ~/
# copy source code of the project to you directory
git clone git@bitbucket.org:fabiobeoni/bedrock-studio-chatbot.git
Note: If you don't use Git, download the code from the same web address, and unpack it:
# move into the project directory
cd bedrock-studio-chatbot/pm-monitor-service
# install nodejs dependencies
npm install --production
1.2 Create authentication certificate & key
Make the gateway device controller to connect to AWS IoT Core by authenticating it with certificate and private key.
You also want to associate the certificate to a virtual representation of the gateway known as IoT Things.
Give a name to the gateway device:
# set a variable to host the name
thing_name="gateway_pm_monitor"
# create certificate and key pairs,
# assign the $thing_name as subject
openssl req -x509 -newkey rsa:2048 -sha256 -days 365 -nodes \
-keyout gateway.private_key.pem \
-out gateway.certificate.pem \
-subj "/CN=$thing_name"
Download the Amazon Root CA1 certificate:
curl -O https://www.amazontrust.com/repository/AmazonRootCA1.pem
Perform a ls -l
in the local folder, to make sure it includes the following files:
AmazonRootCA1.pem
gateway.certificate.pem
gateway.private_key.pem
1.3 Provision IoT resources
From now on, you can go ahead in two ways:
- provisioning the cloud resources by running AWS CLI commands (easy to follow step-by-step, requires manual deletion of the resources when done), or
- provisioning by IaC approach using the Terraform template (much faster and preconfigured, requires basic knowledge of Terraform - File included in the repo you just downloaded)
The article follows the provisioning by the AWS CLI, to make it easier to understand the process step-by-step.
From the terminal, provision a IoT Thing:
aws iot create-thing --thing-name $thing_name
Then register the certificate into AWS IoT Core, to enable the device authentication:
# register certificate
certificatedata=$(aws iot register-certificate-without-ca \
--certificate-pem file://gateway.certificate.pem \
--status ACTIVE)
# keep track of the certificate Arn
# and certificate ID in variables
certificate_arn=$(echo $certificatedata | jq -r '.certificateArn')
certificate_id=$(echo $certificatedata | jq -r '.certificateId')
At this point you can find the certificate in the AWS Web Console to:
# open the following address with a browser, make sure to replace
# <ASW_DEFAULT_REGION> with your region
https://<ASW_DEFAULT_REGION>.console.aws.amazon.com/iot/home/#/certificatehub
Get the AWS IoT Core endpoint URL in your region, to push data from device to it:
# get the iot endpoint and save on a variable
endpoint_address=$(aws iot describe-endpoint --endpoint-type iot:Data-ATS --query 'endpointAddress' --output text)
Create a client ID, a unique identifier for the gateway client:
# create a client ID
client_id=$(uuidgen)
As always, in AWS everything must be authorized to perform a given task. The gateway device must connect and publish message data, so you want to provision a Policy to request the following permissions:
- iot:Connect
- iot:Publish
# define the name and the "topic" you push data to
policy_name="${thing_name}_iot_policy"
monitor_topic="pm_monitor"
topic_arn="arn:aws:iot:$AWS_DEFAULT_REGION:$AWS_ACCOUNT_ID:*"
# actually create the policy with required permissions
aws iot create-policy \
--policy-name "$policy_name" \
--policy-document '{
"Version": "2012-10-17",
"Statement": [
{"Effect": "Allow", "Action": ["iot:Connect"], "Resource": ["*"]},
{"Effect": "Allow", "Action": ["iot:Publish"], "Resource": ["'"$topic_arn"'"]}
]
}'
Now you attach the policy to the certificate, so that the gateway device authenticating with the certificate inherits the permissions listed in the policy, then attach the certificate to the thing:
aws iot attach-policy \
--policy-name $policy_name \
--target $certificate_arn
aws iot attach-thing-principal \
--principal $certificate_arn \
--thing-name $thing_name
1.4 Provision database resources
Time for storing data. Right now the messages don't get persisted in any database yet. To save all incoming data from the gateway device you create a DynamoDB table (nosql database), and an IoT Rule (forwarding rule) to forward all incoming device data to it.
The items stored in the table have this JSON shape:
{
// name of the company's plant where the gateway device and sensor reside and measure
// (dynamodb partition key)
"plant_name": "plant-abc",
// timestamp of the measure, when it happened
// (dynamodb sort key)
"date_time": 1633036800,
// PM 2.5 value
"pm25": 12.5,
// PM 10 value
"pm10": 20.3,
//metadata about the machine generating the PM
"machine": {
"name" : "machine-one",
"type" : "type-1",
"code" : "mot1"
}
}
Create the DynamoDB table with:
dbtable_name=pm_values
aws dynamodb create-table \
--table-name $dbtable_name \
--attribute-definitions \
AttributeName=plant_name,AttributeType=S \
AttributeName=date_time,AttributeType=N \
--key-schema \
AttributeName=plant_name,KeyType=HASH \
AttributeName=date_time,KeyType=RANGE \
--provisioned-throughput \
ReadCapacityUnits=5,WriteCapacityUnits=5
You may want to feed the table with some synthetic data to test it out, before getting data from the gateway and IoT Core. If so, use the following script:
# current timestamp
current_time=$(date +%s)
# insert 20 items in a time range with different pm2.5 and pm10 values, they all refer to "plant-abc"
for i in {0..19}; do
# Calculate the date_time for each item (1 hour apart)
date_time=$((current_time - (i * 3600))) # 3600 seconds in an hour
pm25=$((RANDOM % 61 + 10)) # Random value between 10 and 70
pm10=$((RANDOM % 61 + 10)) # Random value between 10 and 70
aws dynamodb put-item \
--table-name $dbtable_name \
--item '{
"plant_name": {"S": "plant-abc"},
"date_time": {"N": "'"$date_time"'"},
"pm25": {"N": "'"$pm25"'"},
"pm10": {"N": "'"$pm10"'"},
"machine": {
"M": {
"name": {"S": "machine-one"},
"type": {"S": "type-1"},
"code": {"S": "mot1"}
}
}
}'
done
Run a query to read the data you just pushed:
# test the data inserted by querying dynamodb
aws dynamodb query \
--table-name $dbtable_name \
--key-condition-expression "plant_name = :plant_name" \
--expression-attribute-values '{":plant_name": {"S": "plant-abc"}}'
1.5 Provision the IoT Rule to store IoT data into the database
First, create role and policies to allow the data forwarding from IoT Core to the DynamoDB:
role_name=pm_monitor_iot_push_to_dynamo_role
# Trust policy – specifies the trusted accounts (iot.amazonaws.com) that are allowed to assume the role.
aws iam create-role \
--role-name $role_name \
--assume-role-policy-document '{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Service": "iot.amazonaws.com"
},
"Action": "sts:AssumeRole"
}
]
}'
# Permissions policy – grants the user of the role the required permissions to put items into the table.
policy_name=pm_monitor_iot_push_to_dynamo_policy
aws iam put-role-policy \
--role-name $role_name \
--policy-name $policy_name \
--policy-document "{
\"Version\": \"2012-10-17\",
\"Statement\": [
{
\"Effect\": \"Allow\",
\"Action\": \"dynamodb:PutItem\",
\"Resource\": \"arn:aws:dynamodb:${AWS_DEFAULT_REGION}:${AWS_ACCOUNT_ID}:table/${dbtable_name}\"
}
]
}"
Create an IoT Rule to direct messages from the topic to the table.
aws iot create-topic-rule \
--region ${AWS_DEFAULT_REGION} \
--rule-name "${monitor_topic}_to_dynamodb" \
--topic-rule-payload "{
\"sql\": \"SELECT * FROM '${monitor_topic}'\",
\"description\": \"Rule to send messages from ${monitor_topic} topic to dynamodb ${dbtable_name} table\",
\"actions\": [
{
\"dynamoDBv2\": {
\"putItem\": { \"tableName\": \"${dbtable_name}\" },
\"roleArn\": \"arn:aws:iam::${AWS_ACCOUNT_ID}:role/${role_name}\"
}
}
],
\"ruleDisabled\": false,
\"awsIotSqlVersion\": \"2016-03-23\"
}"
1.6 Test the provisioned resources
To test the provisioned IoT resources, push a message to AWS IoT Core. To see messages coming from the test, subrscribe to the topic where the message is pushed to. This time you go ahead using the AWS Web Console.
First open the web browser to:
https://<AWS_DEFAULT_REGION>.console.aws.amazon.com/iot/home/#/test
Second, in the web console create a subscription to the "pm_monitor" topic:
Now push a message to IoT Core:
# install the mqtt client
curl -L -o mqtt-cli-4.31.0.deb https://github.com/hivemq/mqtt-cli/releases/download/v4.31.0/mqtt-cli-4.31.0.deb
sudo apt install ./mqtt-cli-4.31.0.deb
# push a message to the topic "pm_monitor"
mqtt pub \
-h $endpoint_address \
-p 8883 \
--cafile AmazonRootCA1.pem \
--cert gateway.certificate.pem \
--key gateway.private_key.pem \
-t $monitor_topic \
-m "{ \"plant_name\": \"plant-abc\", \"date_time\": 1633036800, \"pm25\": 12.5, \"pm10\": 20.3, \"machine\": { \"name\" : \"machine-one\", \"type\" : \"type-1\", \"code\" : \"mot1\" } }" \
-d -V 5 -q 0
The message appears in the web console (bottom of the page):
https://<AWS_DEFAULT_REGION>.console.aws.amazon.com/iot/home/#/test
Check that the message is also stored in the DynamoDB table:
# test the data inserted by querying dynamodb
aws dynamodb query \
--table-name $dbtable_name \
--key-condition-expression "plant_name = :plant_name" \
--expression-attribute-values '{":plant_name": {"S": "plant-abc"}}'
Or navigate the web console, then select the table:
https://<AWS_DEFAULT_REGION>.console.aws.amazon.com/dynamodbv2/home/#tables
2. Setup the gateway device and sensor
2.1 The "hard" part ;)
Start by connecting the Nova PM sensor...
...to the RaspberryPi4 gateway device via USB:
2.2 Device configuration
2.2.1 Installing configuration, user and service software module
Create a JSON configuration file (to configure the IoT client instance later on):
device_config=$(cat <<EOF
{
"host": "$endpoint_address",
"port":8883,
"clientId": "$client_id",
"thingName": "$thing_name",
"cert": "./gateway.certificate.pem",
"key": "./gateway.private_key.pem",
"ca": "./AmazonRootCA1.pem",
"topic":"$monitor_topic",
"mockSensor":false,
"sensorPath":"/dev/ttyUSB0",
"pushFrequency":3000,
"pmMultiplier": 10
}
EOF
)
# save the configuration to a file
echo $device_config > device_config.json
Perform a cat device_config.json
and cross-check that all fields have values.
From the terminal, connect to the Raspberry gateway device (assuming the gateway device already connected to your network) to copy the service and the certificate files:
cd ~/bedrock-studio-chatbot
scp -r pm-monitor-service pi@raspberrypi.local:~/
Connect to the Raspberry:
ssh pi@raspberrypi.local
Make the service user, config location:
# create the service user "pmmonitor" and provide a password
sudo adduser pmmonitor
Move the files and assign the permissions:
# move the project file to the user home
sudo mv ~/pm-monitor-service /home/pmmonitor
# assign permissions
sudo chown pmmonitor /home/pmmonitor/pm-monitor-service -R
# assing permissions to read the input data from the sensor
sudo chown pmmonitor /dev/ttyUSB0 # or any USB port you have set in the device_config.json ("sensorPath":"/dev/ttyUSB0")
2.2.2 Installing NodeJS runtime
# install nodejs 18
curl -fsSL https://deb.nodesource.com/setup_18.x | sudo -E bash -
sudo apt install -y nodejs
# login as the service user
sudo su pmmonitor
cd ~/pm-monitor-service
2.2 Run the pm-monitor-service
# start the service
nodejs main.mjs
Going back to web console, access the DynamoDB table "pm_values" (link), click on button "Scan" to see the data.
https://<AWS_DEFAULT_REGION>.console.aws.amazon.com/dynamodbv2/home/#table?name=pm_values
You can stop the service at any time by:
ctrl+c
Note: If you want the service to run automatically at boot time, you may want to create an .service
file for "systemd".
Next
Part 2 - Deploy a Chat App, Bedrock Agent and Actions to Ask Questions about Live Data
Top comments (0)