The Internet of Things on AWS – Official Blog
Converting industrial protocols with AWS IoT Greengrass
Gaining access to sensor data or telemetry of industrial machines is a key requirement for implementing high-value use cases around smart manufacturing or Industry 4.0. For instance, predictive maintenance or automated quality control is not possible without having such data at a high temporal resolution.
Given the heterogeneous environment of modern industrial production lines with many different machine types, it’s difficult to access machine data because the machines’ interfaces may use different protocols.
This post provides a detailed look at the conversion of industrial protocols and extracting data from industrial machines. The solution uses the AWS IoT platform and a fully automated setup using AWS CloudFormation with a virtual drill. You can derive a general design pattern for implementing protocol conversions using AWS IoT Greengrass.
Prerequisites
To follow the content of this post, you need an AWS account and access rights to deploy a CloudFormation template that creates a VPC, a subnet and two EC2 instances. Code samples are provided in Python as part of AWS Lambdas deployed by the CloudFormation script.
Obtaining near real-time data from an industrial drill
This post provides a real-world example that allows you to explore and experiment with Industrial IoT technology. In this scenario, you want to connect an automated drill to AWS and retrieve its telemetry data every second. You can use the telemetry data to monitor production output or quality, detect anomalies, or do predictive maintenance.
To limit the scope of this post, the scenario focuses on connecting this drill. For more information, see Using AWS IoT for Predictive Maintenance.
The automated drill repeatedly executes the following operation:
- It starts in an idle state.
- Based on a trigger not covered here, the drill starts a drilling procedure that lasts up to 15 seconds.
- During this work period, the drill spins up, and the motor speed (red line), as well as the spindle speed (blue line), increase to values between 1300 and 1600 rpm.
- After the drilling finishes, the drill spins down and returns to idle, waiting for the next trigger.
The following diagram shows six periods of this procedure. In some cases, the procedures deviate from the usual behavior. For example, in periods one, four, and six, the spindle speed and the motor speed are not in sync.
To allow hands-on experimenting and learning, this post uses an AWS CloudFormation template that deploys resources that simulate industrial assets, as well as the necessary structures in the AWS backend: Though both devices are simulated on Amazon EC2 instances, the setup is similar to that in a manufacturing plant. The gateway, as well as the drill, are in a subnet in which they can communicate with each other. The gateway has a connection to the internet and sends the data to AWS IoT Core. The gateway uses AWS IoT Greengrass with Lambdas to extract telemetry data from the drill.
As this post focuses on the conversion of industrial protocols, the drill has three interfaces that provide the same data, but use three different protocols:
- OPC-UA – A modern industrial protocol standard that includes security mechanisms as well as meta-data. Only the most recent versions of industrial assets typically have such an interface.
- Modbus TCP – An industrial protocol invented in 1979. This protocol is an example for many other dated protocols used in existing machines, which lack security mechanisms as well as structure to the data they deliver.
- TCP – This scenario includes a Transmission Control Protocol (TCP) to show that you can apply the protocol extraction pattern demonstrated here to custom or proprietary protocols. This also occurs with industrial assets that provide any binary payloads, such as images.
The drill has four main output variables that update every 500ms:
- Status – This indicates whether the drill is currently lifted (idle), drilling (working), or in a transition between idle and working.
- Spindle Speed – The drill’s spindle speed in RPM.
- Motor Speed – The speed at which the motor of the drill rotates.
- Pressure – The pressure on the bit at the top of the drill.
You can turn the data about drilling operations obtained from the three preceding interfaces into JSON and push via MQTT over TLS 1.2 to AWS IoT Core.
Deploying the AWS CloudFormation template
To deploy the AWS CloudFormation template which provides the resources shown in the aforementioned architecture diagram, execute the following steps:
Step 1: log in to your AWS account
Step 2: click on one of the following six links, depending on which AWS Region you would like to use:
- us-east-1 (North Virginia)
- us-west-2 (Oregon)
- eu-west-1 (Ireland)
- eu-central-1 (Frankfurt)
- ap-northeast-1 (Tokyo)
- ap-southeast-2 (Sydney)
You can check which Region you are deploying to by looking at the top right of the AWS Management Console. This scenario uses eu-west-1
, but any AWS Region from the preceding list produces similar results.
Step 3: Any of the preceding links result in a browser window showing AWS CloudFormation with the following fields:
- For Stack name, you can optionally change the name of the stack.
- For Parameters, you have the following User Options:
- For EC2 Instance Type to be used, enter a different instance type. The pre-configured type
t2.micro
is sufficient for this post. - For Name tag to be appended to resources, enter a name tag that is easy to identify, such as
Drill-Blog
.
- For EC2 Instance Type to be used, enter a different instance type. The pre-configured type
The stack deployment fails if you change the stack name or the name tag to values that are too long.
Step 4: There are two check boxes at the bottom of the screen: I acknowledge that AWS CloudFormation might create IAM resources with custom names, and I acknowledge that AWS CloudFormation might require the following capability: CAPABILITY_AUTO_EXPAND. Select both boxes
Step 5: Start the deployment process by clicking Next. The deployment of the stack takes up to 10 minutes. The stack uses nested stacks to deploy some basic resources.
Step 6: When all stacks show the status CREATE COMPLETE
, open the AWS IoT Core console.
Step 7: On the left navigation pane, choose Greengrass Groups. Choosing Groups opens a page showing a deployed and configured AWS IoT Greengrass group.
Step 8: Select the deployed group.
Step 9: Click Actions at the top right and select Deploy.
Step 10: If a corresponding pop-up appears, use Automatic detection. The AWS IoT Greengrass deployment should finish in under two minutes. It indicates the finish at the top left of the screen with a green icon and the message “Successfully completed” (instead of “Not deployed”).
The deployment starts the data extraction using Lambda functions that the AWS CloudFormation template created. This post goes through the different deployment steps and the corresponding Lambda functions.
The two EC2 instances, as well as the traffic, create charges to your AWS account. During a test run in eu-west-1
, deploying and running the preceding architecture with 3 msg/s (1 msg/s from each conversion Lambda function) results in costs less than $0.15 per hour. To avoid unnecessary costs to your AWS account, make sure that you delete the stack when you are done.
Converting protocols on AWS IoT Greengrass
The conversion of protocols with AWS IoT Greengrass relies on the ability to deploy Lambda functions to the AWS IoT Greengrass Core. For the conversion of protocols using AWS Lambda—even beyond those discussed here—adhere to the following four steps:
- Load a protocol-specific library.
- Connect to the industrial asset.
- Read or retrieve data from the industrial asset.
- Optionally, convert the retrieved data into a usable format.
- Publish the read result to an MQTT topic.
Following these four steps extracts the data from the drill and publishes the result of the conversion to a protocol-specific topic on AWS IoT Core. The code of the Lambda functions deployed using the AWS CloudFormation template contains comments that mark these steps.
Accessing OPC-UA interfaces
All of the following sections use openly available libraries to access the drill. For more information, see the Python library on GitHub. All code examples—those in this post as well as those deployed by AWS CloudFormation—are optimized for readability at the cost of efficiency, error resiliency, or monetary cost.
To review the full code of the OPC-UA extraction function, open Lambda on the AWS Management Console. You can see a list of Lambda functions. The entries for the (1) OPC-UA extraction Lambda function as well as the extraction functions for (2) Modbus, and (3) raw TCP contain the code that executes on the AWS IoT Greengrass Core. To see the executed code, select the function whose name contains OPCUA
.
Because it is a long-running function, it only has an entry point that calls a function poll_opcua_server
. This function has code similar to the following, which polls data from the OPC-UA interface of the drill every second:
from opcua import Client as OPCUAClient """ Step 1 """
""" Step 2 """
client = OPCUAClient("opc.tcp://" + os.environ["OPCUA_SERVER_HOSTNAME"] + ":4840")
client.connect() # connect to the data source
while True:
""" STEP 3 - Read/Retrieve data from the industrial asset """
rootNode = client.get_root_node() # get the root node of the OPCUA tree
results = {}
for name, path in OPCUA_VARIABLES.items(): # payload defines names -> path relations
results[name] = rootNode.get_child(path).get_data_value() # get the value from the node identified by path
""" STEP 4 - Publish the read result to MQTT / AWS IOT Core. """
extractResult = convertResultsToDict(results)
iotclient.publish(topic=generateRawTopicName(), payload=json.dumps(extractResult))
time.sleep(1) # get new value every second
The preceding Lambda code first creates a URL and an OPC-UA client instance. It reads the hostname from a system variable and constructs a URL from it. The actual extraction starts at the root node of the OPC-UA and retrieves data from its child nodes.
A dictionary called OPCUA_VARIABLES
maps names like SpindleSpeed
to node path objects conforming to the OPC-UA standard, such as ns=2; s=Objects.PLC1.MAIN.Drill.SpindleSpeed
.
After the Lambda function retrieves these results, it converts them into JSON that follows the format described previously, and publishes the JSON as an MQTT.
To review the JSON sent by the OPC-UA, open the AWS IoT console. On the left navigation pane, choose Test. You can use the test function of AWS IoT Core to see incoming messages. For Subscription topic, enter opcua/#
. This page shows all messages in the MQTT topics starting with opcua/
. Choose Subscribe to topic.
The following is an example of a message delivered to AWS IoT Core. It contains a timestamp, the state of the drill, as well as the telemetry data for motor speed, spindle speed, and pressure in JSON format:
{
"timestamp": 1568012615.4760735,
"MotorSpeed": 16.707550552488215,
"State": "DrillState.COOLDOWN",
"SpindleSpeed": 21.89205389937024,
"Pressure": 2.4648222246416243
}
Accessing Modbus TCP interfaces
Accessing Modbus assets with Python requires the library pymodbus
. For more information, see the Python modbus project on GitHub.
Compared with the well-typed and structured format of OPC-UA, Modbus has a much lower abstraction level, but the general approach remains the same. To review the code, select the function from the Lambda console whose name contains Modbus
(2).
As with the previous OPC-UA, the long-running Lambda function calls a function poll_measurements
, which retrieves data from the Modbus master every second. The following is a condensed version of the code in this function:
""" Step 1 """
from pymodbus.client.sync import ModbusTcpClient as ModbusClient
""" Step 2 """
mbClient = ModbusClient(os.environ["MODBUS_MASTER_HOSTNAME"], port=5020)
mbClient.connect()
while True
""" Step 3 Read/Retrieve data from the industrial asset """
readResult = mbClient.read_holding_registers(address=0x0, count=32, unit=1)
""" Step 4 Publish the read result to MQTT / AWS IOT Core. """
# transform the binary result into a dictionary
extractResult = decodePayloadToDictionary(readResult=readResult)
# publish the result to MQTT topics
iotclient.publish(topic=generateRawTopicName(), payload=json.dumps(extractResult))
time.sleep(frequency)
The first two steps of the Lambda function are similar to the one for OPC-UA: it imports the TCP client class of the library pymodbus
. The Modbus TCP client connects to the given hostname/IP and a port, which it uses to read holding registers.
Given the low abstraction level of Modbus, you must provide correct memory addresses and the size of the memory area to read. The implementation of the client depends on the way you write the data, such as its order. The result of this read process is an unstructured binary object, which is then decoded.
A call to a customized method decodePayloadToDictionary
, which reads the various variables of the drill in order, simplifies the method of decoding this unstructured binary object. You can examine the code of this method in the Lambda function deployed through the AWS CloudFormation template. The result of this decoding procedure is published to a topic whose name starts with modbus/
.
To view the messages published by this function, use the same steps described previously to subscribe to the topic modbus/#
. The following is an example of such a message:
{
"timestamp": 1568012917.0377738,
"MotorSpeed": 16.11205287977468,
"SpindleSpeed": 21.170123345258368,
"Pressure": 2.314612395615421,
"State": "DrillState.IDLE"
}
Configuring the Modbus serial and RTU connectors
This post uses TCP as its underlying transport mechanism. Modbus also provides a protocol variant using serial connections and User Datagram Protocol (UDP). You can use the function described previously for this serial protocol, with the following minor changes:
- Depending on whether you use a UDP or serial connection, load a different client library.
- In the case of a serial connection, the address of the endpoint from which the function has to retrieve data is not an IP address.
For example, connecting to a serial Modbus master needs the following code for steps 1 and 2:
from pymodbus.client.sync import ModbusSerialClient as ModbusClient # Step 1
client = ModbusClient(method='rtu', port='/dev/ttyp0') # Step 2
AWS IoT Greengrass needs hardware access to use a serial connection, therefore you need to configure it properly by adding the serial port as a local resource. Additionally, AWS IoT Greengrass offers a Modbus RTU Connector to retrieve Modbus data over a serial connection.
Accessing raw TCP interfaces
While many industrial assets use some application protocols like Modbus or OPC-UA, there are use cases that need to extract data from assets using fully proprietary protocols. Furthermore, there are use cases where industrial assets provide binary results, such as images or sound recordings.
In many of these cases, the interface to these assets is a TCP interface without any openly available application protocol. To review the Lambda code that extracts raw TCP from the drill, select from the Lambda console the function that contains Lambda (3).
The long-running Lambda function calls a function poll_tcp_server
, which retrieves data from the drill every second. The following code example shows how to use the socket library of Python to connect to such a machine and retrieve a binary payload:
import socket """ Step 1 """
tcpSocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
""" Step 2 """
tcpSocket.connect((os.environ["TCP_SERVER_HOSTNAME"], os.environ["TCP_SERVER_PORT"]))
while True:
""" Step 3 - Read/Retrieve data from the industrial asset """
data = tcpSocket.recv(1024)
""" Step 4 - Publish the read result to MQTT / AWS IOT Core. """
result = json.loads(decodeBinaryPayload(payload=data))
iotclient.publish(topic=generateRawTopicName(), payload=json.dumps(result))
time.sleep(1) # wait for 1 sec and then read again
Steps 1 to 3 are similar to the previous examples, but they use the socket library for Python. As with Modbus, step 4 starts with decoding the binary message into a format that you can convert to JSON. You can review the details of this method in the Lambda function deployed by AWS CloudFormation.
For other applications, you need different conversion methods, for example, if the binary payload contained an image. Because the payload for this drill example already contains a string, you can parse this string using the json.loads
function.
As in the previous examples, the function pushes the JSON content to a topic starting with tcp/
. You can see these messages by subscribing to tcp/#
in the test function of AWS IoT Core:
{
"state": "DrillState.WORKING",
"motor": 1397.3864579172175,
"spindle": 1358.69176246319,
"pressure": 10.208308057019503,
"timestamp": 1568013211.0736094
}
Instead of publishing the telemetry data to AWS IoT Core, you can store a file directly to Amazon S3 using temporary access credentials associated with the AWS IoT Greengrass group. For more information, see Configure the Group Role. You need a direct file upload to S3 (or other services) if the industrial asset generates binary data, such as images or sound files.
Conclusion
This post provides a design approach to converting industrial protocols using AWS IoT Greengrass and Lambda. Based on the AWS CloudFormation template that deploys AWS IoT Greengrass along with a virtual industrial drill, you can explore and experiment with industrial protocol conversion in a full end-to-end scenario.
Protocol conversion is the foundation of many high-value use cases in the Industry 4.0 space, such as predictive or prescriptive maintenance or automated quality control. While this post addresses three popular protocols, the four-step approach presented here applies to a broader range of protocols you can encounter in today’s heterogeneous industrial environments. Additional details on how to integrate the protocol extraction in this post into a predictive maintenance use case are provided in this workshop.