The Internet of Things on AWS – Official Blog

Perform Protocol Conversion at the Edge with AWS Lambda and AWS Greengrass

In industrial automation, you will find dozens of protocols used by devices and PLC vendors: OPC-UA, Modbus TCP, serial, and more. In fact, often in just one facility, it is common to find two similar processes performing the same function with different equipment.

AWS Greengrass reduces the operational overhead for businesses to change protocols as needed through remote deployments. AWS Greengrass uses AWS Lambda, a service that makes it possible to program devices in the cloud. For example, a business can deploy AWS Lambda functions to field devices to add additional sensors or change vendor equipment. The ability to roll out changes from the cloud to the field reduces the development and maintenance cycle for IoT applications.

In this blog post, we will show an example of how you can convert data from a common industrial protocol, such as Modbus TCP. You might be able to use the same approach with other protocols.

Example: Resistance temperature detector

AWS Greengrass supports OPC-UA, an information exchange standard for industrial communication. Like Modbus TCP, OPC-UA allows you to ingest and process messages from industrial equipment and deliver them to devices in your AWS Greengrass group or to the cloud based on rules you define.

In the example used in this post, we collect simulated temperature readings from a resistance temperature detector (RTD) attached to the bearing housing of a motor. (Although it’s beyond the scope of this blog post, you can use AWS IoT Analytics to analyze this temperature information. It can help you provide predictive maintenance for bearing lubrication or detect imminent failure especially for motors, generators, and other high-voltage equipment)

The following diagram shows how AWS Greengrass runs a Lambda function that connects natively to devices that use Modbus TCP, such as a programmable logic controller (PLC). The same Lambda function publishes the values read from the Modbus Slave and registers to MQTT topics in AWS IoT Core.

Lambda functions can be triggered by events detected locally or from AWS IoT Core, which can invoke writes back to the Modbus Slave device.

Prerequisites

To follow the steps in this post, you need a Raspberry Pi with python-dev, python-pip, pyModbus, pycrypto, pyasn1 and version 1.3 of Greengrass Core software or higher installed.

Set up AWS Greengrass and driver libraries

Follow these steps to set up AWS Greengrass on your Raspberry Pi. For more information, see the Greengrass Developer Guide.

Create a Greengrass core

Create a Greengrass group. If you already have a group, you can skip this step.

  1. In the left pane of the AWS IoT console, choose Greengrass, and then choose Groups.
  2. Choose the Create Group button.
  3. Choose Use easy creation.
  4. Type a name for your group.
  5. Type a name for the Greengrass core associated with the group or use the default.
  6. Choose Create Group and Core.
  7. Download the core security resources, which are provided as a tar.gz.

Follow the steps in the Developer Guide to load keys to your device and start Greengrass.

Deploy Greengrass core

  1. In the AWS IoT console, from the Actions menu, choose Deploy.
  2. Choose Automatic Detection or manually set up the endpoint information for the core.
  3. After your core is deployed, you should see a Successfully completed message:

Install PyModbus libraries

Now you need to install pyModbus, a full Modbus protocol implementation.

To install pyModbus, execute the following commands on the AWS Greengrass device:

sudo apt-get update
sudo apt-get install python-dev
sudo apt-get install python-pip
sudo pip install pymodbus
sudo pip install pycrypto
sudo pip install pyasn1

Create Lambda functions

In this section, you create three Lambda functions. You can skip the first two if you are connecting to a real Modbus device.

  • Modbus Slave (Server) – This function simulates the extruder machine PLC device.
  • Modbus Simulator – This function periodically reads the CPU temperature of your Raspberry Pi and writes it to the Modbus Slave registers to simulate the motor bearing temperature.
  • Modbus To AWS IoT – This function periodically reads the Modbus register for the bearing temperature and publishes the value to an MQTT topic.

Create a Modbus Slave Lambda function

    1. Sign in to the AWS Management Console on your computer and open the AWS Lambda console.
    2. Choose Author from scratch.
    3. For Name, type ModbusSlave. For Runtime, choose Python 2.7.
    4. When you choose an IAM role for the function, you must provide CloudWatch log permissions. The Greengrass-Modbus-Role has the following policies attached. Be sure to apply the appropriate permissions for the role.
    5. The following script starts a Modbus TCP Server (Slave) on the Raspberry Pi. It allocates in memory some Modbus registers that can be used to read/write data to. Insert the following script and publish a new version to be used in AWS Greengrass.Tip: Create an alias for your AWS Lambda function and assign the version published to that alias. This makes it easier to deploy updates to your function later.

 

#
# Copyright 2010-2017 Amazon.com, Inc. or its affiliates. All Rights Reserved.
#

# ModbusSlaveServer.py
# Runs a modbus slave device in memory for testing purposes.
# This AWS Lambda function allocates registers for the modbus tcp slave and starts
# the server. If an exception occurs, it will wait 5 seconds and try again.
# Since the function is long-lived it will run forever when deployed to a 
# Greengrass core.  The handler will NOT be invoked in our example since 
# the we are executing an infinite loop.

import time
import logging
import sys
#import pymodbus libraries for the server
from pymodbus.server.sync import StartTcpServer
from pymodbus.datastore import ModbusSequentialDataBlock
from pymodbus.datastore import ModbusSlaveContext, ModbusServerContext

# instantiate logger which will log any exceptions to Cloudwatch or Greengrass 
# local logs
logger = logging.getLogger(__name__)
logging.basicConfig(stream=sys.stdout, level=logging.DEBUG)

while True:
    try:
        #create registy for modbus server
        store = ModbusSlaveContext(
            di = ModbusSequentialDataBlock(0, [17]*100),
            co = ModbusSequentialDataBlock(0, [17]*100),
            hr = ModbusSequentialDataBlock(0, [17]*100),
            ir = ModbusSequentialDataBlock(0, [17]*100))
        context = ModbusServerContext(slaves=store, single=True)
        
        # change default port of modbus from 502 to 5020 as it requires 
        # root permissions below 1024
        StartTcpServer(context, address=("localhost", 5020))
    except Exception, e:
        logging.info("Error: {0}".format(str(e)))
    time.sleep(5)

# this function is never called as it will be configured as long-lived.
def lambda_handler(event, context):
    return 'Hello'

 

Create a Modbus Slave Simulator Lambda function

  1. Repeat the steps 1-3 above to create a function named ModbusSlaveSim.
  2. The following script creates a Modbus Client (Master) that connects to the Modbus TCP Server (Slave) from the previous step and stores the bearing temperature captured from an RTD device every 5 seconds. (The RTD device in this case is just the CPU temperature of the Raspberry Pi.) Insert the following script and publish a new version to be used in AWS Greengrass.

 

#
# Copyright 2010-2017 Amazon.com, Inc. or its affiliates. All Rights Reserved.
#

# ModbusSlaveSim.py
# This is a simulator script that connects to a modbus slave device and  
# writes the CPU temperature for the raspberry pi device to a modbus register.
# If an exception occurs, it will wait 5 seconds and try again.
# Since the function is long-lived it will run forever when deployed to a 
# Greengrass core.  The handler will NOT be invoked in our example since 
# we are executing an infinite loop.

import time
import logging
import sys
import os
# import pymodbus libraries for the simulator client
from pymodbus.client.sync import ModbusTcpClient as ModbusClient
from pymodbus.payload import BinaryPayloadDecoder
from pymodbus.payload import BinaryPayloadBuilder
from pymodbus.constants import Endian
from pymodbus.compat import iteritems

# Default port for modbus slave is typically 502. Using 5020 for simulation to 
# avoid root permissions.
client = ModbusClient('127.0.0.1', port=5020)

# instantiate logger which will log any exceptions to Cloudwatch or Greengrass 
# local logs
logger = logging.getLogger(__name__)
logging.basicConfig(stream=sys.stdout, level=logging.DEBUG)

# returns the cpu temperature from the raspberry pi device
def get_temp():
    f = open("/sys/class/thermal/thermal_zone0/temp")
    CPUTemp = f.read()
    f.close()
    return float(CPUTemp)/1000.0

# in an infinite loop, this procedure will poll the cpu temperature and write
# it to a local modbus slave device.
def poll_temp():
    while True:
        try:
            client.connect()
            builder = BinaryPayloadBuilder(endian=Endian.Big)
            builder.add_32bit_float(get_temp())
            payload = builder.build()
            address = 0
            rq = client.write_registers(address, payload, skip_encode=True, unit=1)
    
        except Exception, e:
            logging.info("Error: {0}".format(str(3)))
            time.sleep(5)

# executing polling on startup.
poll_temp()

# this function is never called as it will be configured as long-lived.
def lambda_handler(event, context):
    return 'Hello'

Create a Modbus to AWS IoT function

  1. To create the final function, in the AWS Lambda console, select Blueprints. Under Blueprints, search for greengrass. Choose the Python blueprint runtime and name it ModbusToAWSIoT.
  2. The following script creates a Modbus Client (Master) that connects to the Modbus Server (Slave) and retrieves the bearing temperature stored in the Modbus Server register. The value is then published to a topic on AWS IoT. Insert the following script and publish a new version to be used in AWS Greengrass.

 

#
# Copyright 2010-2017 Amazon.com, Inc. or its affiliates. All Rights Reserved.
#

# ModbusToAWSIoT.py
# This is an example script that connects to a modbus slave device to read  
# a temperature value and publish to an MQTT Topic in AWS IoT every 5 seconds.
# If an exception occurs, it will wait 5 seconds and try again.
# Since the function is long-lived it will run forever when deployed to a 
# Greengrass core.  The handler will NOT be invoked in our example since 
# we are executing an infinite loop.

import greengrasssdk
import platform
from threading import Timer
import time
import logging
import sys
import json
# import pymodbus libraries for the modbus client
from pymodbus.client.sync import ModbusTcpClient as ModbusClient
from pymodbus.payload import BinaryPayloadDecoder
from pymodbus.payload import BinaryPayloadBuilder
from pymodbus.constants import Endian
from pymodbus.compat import iteritems

# Instantiate the client for your modbus slave device. In this example we are
# using the local IP address where a simulator exists. Change this to your 
# desired IP. In addition, the typical default port for Modbus TCP is 502. For
# this example, 5020 was used.
mbclient = ModbusClient('127.0.0.1', port=5020)

# Default port for modbus slave is typically 502. Using 5020 for simulation to 
# avoid root permissions.
logger = logging.getLogger(__name__)
logging.basicConfig(stream=sys.stdout, level=logging.DEBUG)

# Creating a greengrass core sdk client
client = greengrasssdk.client('iot-data')

# in an infinite loop, this procedure will poll the bearing temperature from a
# modbus slave device (simulator) and publish the value to AWS IoT via MQTT.
def poll_temp():
    while True:
        try:
            # connect to modbus slave device
            mbclient.connect()
            # set the address and number of bytes that will be read on the modbus device
            address = 0x00
            count = 8
            # read the holding register value for the temperature
            rr = mbclient.read_holding_registers(address, count, unit=1)
            # decode results as a 32 bit float
            decoder = BinaryPayloadDecoder.fromRegisters(rr.registers, endian=Endian.Big)
            decoded = {
                'float': decoder.decode_32bit_float()
            }
            # publish results to topic in AWS IoT
            for name, value in iteritems(decoded):
                client.publish(topic='dt/controller/extruder/plc1/rtd', payload=json.dumps({ 'Temp': value}))
        except Exception, e:
            logging.info("Error: {0}".format(str(e)))

        time.sleep(5)

poll_temp()

# This is a dummy handler and will not be invoked
# Instead the code above will be executed in an infinite loop for our example
def function_handler(event, context):
    return

Deploy the AWS Lambda functions

Now you are ready to deploy our protocol conversion script. Sign in to the AWS Management Console and then open the AWS IoT console.

Add AWS Lambda functions to the Greengrass group

  1. Go to the group you deployed earlier. In the left pane, choose Lambdas and then choose Add Lambda.
  2. Choose Use existing Lambda
  3. Choose one of the Lambda functions you created earlier and then choose Next
  4. Select the alias that was created when you defined the function. If an alias was not created, select the version that was published.Tip: We recommend that you create an alias so that you will not need to perform this step again if a new version of your Lambda function needs to be published.
  5. In the list of AWS Lambda functions, choose the Lambda function that was added, and then choose Edit. On this page, you can enable access to resources on the device. This could be serial or USB ports, depending on the protocol driver.
  6. For Lambda lifecycle, select Make this function long-lived and keep it running indefinitely. For Read access to /sys directory, select Enable
  7. Repeat these steps for the other Lambda functions that were created

Assign subscription

  1. Go to the group you deployed earlier. In the left pane, choose Subscriptions
  2. Choose Add Subscription
  3. Under Subscriptions, for Source, choose ModbusToAWSIoT. For Target, choose IoT Cloud. Do not provide a filter for the topic

This creates a subscription in AWS IoT to capture any published data coming from the Lambda function on the AWS Greengrass device.

Deploy changes

  1. From the Actions menu on your group configuration page, choose Deploy. From AWS IoT, you can subscribe to dt/controller/extruder/plc1/rtd to view data that is converted from Modbus TCP to MQTT

 

Conclusion

With AWS Greengrass, it is possible to acquire data from many field devices even if they are using different local communication protocols. The example in this post has shown how you can communicate with protocols such as Modbus TCP and securely transfer data to the cloud in real time.

We hope you found this blog post helpful. Feel free to leave questions and other feedback in the comments.