➡️ Background
In this section of the lab you will construct a demonstration of how an IoT setup can be used to detect the potential equipment failures based on a Temperature anomaly and create an Service notification to trigger the maintenance activities in SAP.
This project contains a set of AWS CDK deployable stacks that contains a common set of resources for testing devices with AWS IoT Core. The purpose of this project is to provide a quick pathway for creating AWS resources commonly used for evaluating and gaining visibility into IoT device behavior.
Technical Considerations:
For this workshop you can use any IDE of choice also on your local machine e.g. Visual Code.
Install CDK in your local environment. Follow the steps outlined in lab preparation section.
You will validate the following services in AWS account after CDK deployment is complete.
Resources created in these CDK stacks include:
This project is designed for use with devices with cryptographic coprocessors such as the ATECC608. If you are using a device with a protected private key or wish to use your own private key, place the CSR in the certs/ directory with the filename <thing_name>.csr.pem where <thing_name> is the X509 Certificate Subject’s CommonName (e.g. certs/my_device_1.csr.pem). If you do not have a private key and CSR you care to use, they will be created for you on when the stack is deployed.
UNLESS OTHERWISE NOTED, ENSURE REGION SET TO US-EAST-1 (N. Virginia)
git clone https://github.com/aws-samples/aws-iot-sap-condition-monitoring-demo.git
python3 -m venv .venv
source .venv/bin/activate
cd cdk-iot-analytics/
pip install -r requirements.txt
cd cdk-iot-analytics
Ensure your run npm install -g npm from you local terminal to have the latest version of cdk
npm install -g npm
cdk bootstrap aws://<account>/<region>
cdk deploy iot -O=iot-outputs.json
pip install \
requests \
xmltodict \
-t ./cdk_sap_blog/sap/lambda_assets/layer/python/
cdk deploy sap
cdk deploy analytics -O=analytics-outputs.json
IoT Core : Log into your AWS console and make sure you can access the AWS IoT dashboard by selecting AWS IoT Core in AWS Services search window. Click Manage ->Things->Click on your thing name to validate the Thing attributes.
From the IoT Core Console, click ‘Act’/Rules and select the rule created as part of this cdk.
Validate the following values in create a rule screen:
SELECT parse_time("yyyy-MM-dd'T'HH:mm:ssZ", timestamp() ) AS timestamp, topic(2) AS thingname, e.t AS temperature_degC, e.h AS humidity_percent, e.d AS dewpoint_degC, e.i AS heatIndex_degC, l AS location FROM 'trackercdk'
On the AWS IoT Analytics console landing page, Click Channels->To view your channel and validate:
Validate the Pipeline: Pipeline activities enrich or transform message attributes, or filter entire messages out of your pipeline. Chaining activities together enables you to process and prepare messages before storing them.
Validate the three Activties.
Note the Lambda referenced by the IoT Analytics is created as part of the the CDK to get the product range from the Dynamo DB to identify the temperature range and determine if there is a temperature fluctuation in the data sent from the IoT simulator.
Open the Functions page on the Lambda console. To verify the code.
In addtion Dynamo DB, contain the SAP attributes mapped against the device name (Compressor). When an anomaly is detected the SAP product information is pulled to create a service notification. Login toDynamoDB console to validate the mapping.
Validate the Datastore: On the AWS IoT Analytics console landing page, Click Channels->To view your channel and validate:
Data store receives and stores your messages. It is not a database but a scalable and queryable repository of your messages. Processed data store messages are stored in an Amazon S3 bucket managed by AWS IoT Analytics or in one managed by you.
Validate the SQL query. This query selects the most recent location information, but averages the temperature reading over the period. Also note that the range from Dynamo was previously injected into the message and the most recent value is selected here.
SELECT max(timestamp) AS endTime, (to_unixtime(from_iso8601_timestamp(max(timestamp))) - to_unixtime(from_iso8601_timestamp(min(timestamp)))) AS durS, max_by(thingname, timestamp) as thingname, avg(temperature_degC) AS temperature_degC_mean, avg(humidity_percent) AS humidity_percent_mean, max_by(range.temperature.max, timestamp) AS maxTemp_degC, max_by(range.temperature.min, timestamp) AS minTemp_degC, max_by(location, timestamp) AS location, max_by(registry.attributes.Equipment, timestamp) AS equipment, max_by(registry.attributes.FunctLoc, timestamp) AS functloc, max_by(registry.attributes.Type, timestamp) AS type FROM trackerdatastore
NOTE: This rule inserts an ISO8601 timestamp, the thingname (extracted from the topic), and expands the “shorthand” references to environmental conditions into more descriptive property names.
{
"detectorModelDefinition": {
"states": [
{
"stateName": "OverTemp",
"onInput": {
"events": [],
"transitionEvents": [
{
"eventName": "overTempTooLong",
"condition": "timeout(\"hiTempDur\")",
"actions": [],
"nextState": "OverTempAlarm"
},
{
"eventName": "tempBelowMax",
"condition": "convert(Decimal,$input.CDKSAPBlogEventsInput.temperature_degC_mean) < convert(Decimal,$input.CDKSAPBlogEventsInput.maxTemp_degC)",
"actions": [],
"nextState": "TempInRange"
}
]
},
"onEnter": {
"events": [
{
"eventName": "startTimer",
"condition": "true",
"actions": [
{
"setTimer": {
"timerName": "hiTempDur",
"seconds": 300,
"durationExpression": null
}
}
]
}
]
},
"onExit": {
"events": [
{
"eventName": "deleteTimer",
"condition": "true",
"actions": [
{
"clearTimer": {
"timerName": "hiTempDur"
}
}
]
}
]
}
},
{
"stateName": "TempInRange",
"onInput": {
"events": [
{
"eventName": "readTemp",
"condition": "true",
"actions": [
{
"setVariable": {
"variableName": "temperature_degC",
"value": "convert(Decimal, $input.CDKSAPBlogEventsInput.temperature_degC_mean)"
}
}
]
}
],
"transitionEvents": [
{
"eventName": "tempAboveMax",
"condition": "convert(Decimal, $input.CDKSAPBlogEventsInput.temperature_degC_mean) > convert(Decimal, $input.CDKSAPBlogEventsInput.maxTemp_degC)",
"actions": [],
"nextState": "OverTemp"
},
{
"eventName": "tempBelowMin",
"condition": "convert(Decimal,$input.CDKSAPBlogEventsInput.temperature_degC_mean) < convert(Decimal,$input.CDKSAPBlogEventsInput.minTemp_degC)",
"actions": [],
"nextState": "UnderTemp"
}
]
},
"onEnter": {
"events": [
{
"eventName": "inRange",
"condition": "true",
"actions": [
{
"lambda": {
"functionArn": "arn:aws:lambda:us-east-1:<aws-account number>:function:CDK-SAP-Lab-OData-Function",
"payload": {
"contentExpression": "'{\n \"d\": {\n \"Equipment\": \"${$input.CDKSAPBlogEventsInput.Equipment}\",\n \"FunctLoc\": \"${$input.CDKSAPBlogEventsInput.FunctLoc}\",\n \"ShortText\": \"Temperature Alarm[TempInRange:inRange]\",\n \"LongText\": \"Temperature Alarm in ${$input.CDKSAPBlogEventsInput.Type} Motor\"\n }\n}'",
"type": "JSON"
}
}
}
]
},
{
"eventName": "sendAlert",
"condition": "true",
"actions": [
{
"lambda": {
"functionArn": "arn:aws:lambda:us-east-1:<aws-account number>:function:CDK-SAP-Lab-OData-Function",
"payload": {
"contentExpression": "'{\n \"d\": {\n \"Equipment\": \"${$input.CDKSAPBlogEventsInput.Equipment}\",\n \"FunctLoc\": \"${$input.CDKSAPBlogEventsInput.FunctLoc}\",\n \"ShortText\": \"Temperature Alarm[TempInRange.sendAlert]\",\n \"LongText\": \"Temperature Alarm in ${$input.CDKSAPBlogEventsInput.Type} Motor\"\n }\n}'",
"type": "JSON"
}
}
}
]
}
]
},
"onExit": {
"events": []
}
},
{
"stateName": "UnderTemp",
"onInput": {
"events": [],
"transitionEvents": [
{
"eventName": "underTempTooLong",
"condition": "timeout(\"loTempDur\")",
"actions": [],
"nextState": "UnderTempAlarm"
},
{
"eventName": "tempAboveMin",
"condition": "convert(Decimal,$input.CDKSAPBlogEventsInput.temperature_degC_mean) > convert(Decimal,$input.CDKSAPBlogEventsInput.minTemp_degC)",
"actions": [],
"nextState": "TempInRange"
}
]
},
"onEnter": {
"events": [
{
"eventName": "startTimer",
"condition": "true",
"actions": [
{
"setTimer": {
"timerName": "loTempDur",
"seconds": 300,
"durationExpression": null
}
}
]
}
]
},
"onExit": {
"events": [
{
"eventName": "deleteTimer",
"condition": null,
"actions": [
{
"clearTimer": {
"timerName": "loTempDur"
}
}
]
}
]
}
},
{
"stateName": "OverTempAlarm",
"onInput": {
"events": [],
"transitionEvents": [
{
"eventName": "tempBelowMax",
"condition": "convert(Decimal,$input.CDKSAPBlogEventsInput.temperature_degC_mean) < convert(Decimal,$input.CDKSAPBlogEventsInput.maxTemp_degC)",
"actions": [],
"nextState": "TempInRange"
}
]
},
"onEnter": {
"events": [
{
"eventName": "sendAlert",
"condition": "true",
"actions": [
{
"lambda": {
"functionArn": "arn:aws:lambda:us-east-1:<aws-account number>:function:CDK-SAP-Lab-OData-Function",
"payload": {
"contentExpression": "'{\n \"d\": {\n \"Equipment\": \"${$input.CDKSAPBlogEventsInput.Equipment}\",\n \"FunctLoc\": \"${$input.CDKSAPBlogEventsInput.FunctLoc}\",\n \"ShortText\": \"Temperature Alarm[OverTempAlarm.sendAlert]\",\n \"LongText\": \"Temperature Alarm in ${$input.CDKSAPBlogEventsInput.Type} Motor\"\n }\n}'",
"type": "JSON"
}
}
}
]
}
]
},
"onExit": {
"events": []
}
},
{
"stateName": "UnderTempAlarm",
"onInput": {
"events": [],
"transitionEvents": [
{
"eventName": "tempAboveMin",
"condition": "convert(Decimal,$input.CDKSAPBlogEventsInput.temperature_degC_mean) > convert(Decimal,$input.CDKSAPBlogEventsInput.minTemp_degC)",
"actions": [],
"nextState": "TempInRange"
}
]
},
"onEnter": {
"events": [
{
"eventName": "sendAlert",
"condition": "true",
"actions": [
{
"lambda": {
"functionArn": "arn:aws:lambda:us-east-1:<aws-account number>:function:CDK-SAP-Lab-OData-Function",
"payload": {
"contentExpression": "'{\n \"d\": {\n \"Equipment\": \"${$input.CDKSAPBlogEventsInput.Equipment}\",\n \"FunctLoc\": \"${$input.CDKSAPBlogEventsInput.FunctLoc}\",\n \"ShortText\": \"Temperature Alarm[UnderTempAlarm.sendAlert]\",\n \"LongText\": \"Temperature Alarm in ${$input.CDKSAPBlogEventsInput.Type} Motor\"\n }\n}'",
"type": "JSON"
}
}
}
]
}
]
},
"onExit": {
"events": []
}
}
],
"initialStateName": "TempInRange"
},
"detectorModelDescription": "Detect temperature in a given range.",
"detectorModelName": "trackerEventDetectorModel",
"evaluationMethod": "BATCH",
"key": "thingname",
"roleArn": "arn:aws:iam::<aws-account number>:role/trackerEventDetectorRole"
}
From the IoT Events Console, click on the Detector Model (hamburger menu/Detector models.
Click ‘Import detector model’ and upload the model JSON (trackerEventDetectorModel.json. Validate the events.
The simulator uses the temperature_min/temperature_max variables you defined in cdk.json to report temperatures uniformly to be a few degrees hotter than the maximum (see simulator.py:L50).
See the minimum temperature and maximum temperature in your Dynamo DB table maintained as part of your CDK deploy.
Validate the service notification in SAP. Go to SAP Transaction IW23
Congratulations, you have completed the integration between SAP and AWS AWS IoT services to enable Maintenance with AWS CDK.