AWS Edge computing example with Lambda and IoT Greengrass (version 2)

Rostyslav Myronenko
14 min readOct 8, 2021

--

Table of contents

Introduction

In this blog post, I will describe the setup of the example used for the previous post about the Greengrass version 1.

The test scenario is still the same and is represented in the picture given below.

The device has some YAML files. It reads them and publishes the content to the Greengrass topic via MQTT. The Core device uses the Lambda function deployed on it to subscribe to the topic, consume the messages with file content and convert it into JSON. The converted message is sent to the SQS queue for further processing. For both devices, AWS EC2 instances will be used to make the setup available for anyone.

Implementation

The new Greengrass version (v2) will be used for implementation.

For both the Core device and the Publisher device the same EC2 instance will be used to simplify the setup.

The application code and scripts can be found in the GitHub repository: https://github.com/rimironenko/aws-iot-greengrass-project

1. Ensure that AWS Greengrass has an IAM service role

  1. Go to the AWS console.
  2. Type in “Greengrass” in the search bar and click on “Greengrass”.

3. Click on the “Settings” menu item in the left nav menu.

4. Check the “Greengrass service role” section on the bottom.

For the test scenario, the Greengrass service role requires “AmazonSQSFullAccess” and “AWSGreengrassResourceAccessRolePolicy” AWS-managed policies.

If your service role is missing, create the new one according to the AWS guideline: https://docs.aws.amazon.com/greengrass/v1/developerguide/service-role.html

2. Create a Publisher device

  1. Open the Greengrass service in the AWS console.
  2. Go to the “Manage -> Things” left menu item and click “Create things”.

3. Choose “Create single thing” and click “Next”.

4. Fill in the thing name (e.g., “PublisherDevice”), leave all the other settings as default, and click “Next”.

5. Select “Auto-generate a new certificate” and click “Next”.

6. Do not attach policies right now, just click the “Create thing” button.

7. Download the device certificate and key files, and click “Done”.

8. Upload the certificate and key files to an S3 bucket.

3. Setup an EC2 instance as the Core and the Publisher device

To get more about the setup given below, see https://docs.aws.amazon.com/greengrass/v2/developerguide/quick-installation.html.

  1. Go to the EC2 service in the AWS console and click “Launch instance”.

2. Select the “Amazon Linux 2” AMI (marked with “Free tier eligible”) by clicking “Select” near it.

3. Select the “t2.micro” instance type (marked with “Free tier eligible”) and click “Next: Configure Instance Details” on the bottom right.

4. Leave all the settings as default and provide the user data script to configure the instance for AWS Greengrass. Replace “INSERT_ACCESS_KEY” and “INSERT_SECRET_ACCCESS_KEY” with valid API keys for your AWS user. Replace “INSERT_S3_DEVICE_CERTIFICATE_URI” and “INSERT_S3_DEVICE_PRIVATE_KEY_URI” with the S3 URI of the certificate and key of the publisher device that was created above. Replace the names in the java command that creates the Core device, if needed.

#!/bin/bash

# Uninstall AWS CLI version 1
yum update -y
pip3 install --upgrade --user awscli
pip3 uninstall awscli -y

# Install AWS CLI version 2
curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip"
unzip awscliv2.zip
./aws/install -i /usr/local/aws-cli -b /usr/local/bin
source ~/.bash_profile

# Install Java 11
yum install -y java-11-amazon-corretto

# Install Greengrass Core software
curl -s https://d2s8p88vqu9w66.cloudfront.net/releases/greengrass-nucleus-latest.zip > greengrass-nucleus-latest.zip
unzip greengrass-nucleus-latest.zip -d GreengrassInstaller && rm greengrass-nucleus-latest.zip

# Provide AWS API credentials
export AWS_ACCESS_KEY_ID=INSERT_ACCESS_KEY
export AWS_SECRET_ACCESS_KEY=INSERT_SECRET_ACCCESS_KEY

# Create a Greengrass Core device
# Specify your own names
java -Droot="/greengrass/v2" -Dlog.store=FILE \
-jar ./GreengrassInstaller/lib/Greengrass.jar \
--aws-region us-east-1 \
--thing-name GreengrassV2Core \
--thing-group-name GreengrassV2Group \
--thing-policy-name GreengrassV2IoTThingPolicy \
--tes-role-name GreengrassV2TokenExchangeRole \
--tes-role-alias-name GreengrassV2TokenExchangeRoleAlias \
--component-default-user ggc_user:ggc_group \
--provision true \
--setup-system-service true \
--deploy-dev-tools true


# Create a folder for the device data
cd /home/ec2-user
mkdir publisher_device
cd publisher_device

# Install the Greengrass v2 Python SDK
yum -y install git
git clone https://github.com/aws/aws-iot-device-sdk-python-v2.git
python3 -m pip install ./aws-iot-device-sdk-python-v2

# Upload the device certificates
aws s3 cp INSERT_S3_DEVICE_CERTIFICATE_URI device.pem.crt
aws s3 cp INSERT_S3_DEVICE_PRIVATE_KEY_URI private.pem.key
wget -O root.ca.pem https://www.amazontrust.com/repository/AmazonRootCA1.pem

Click “Next: Add Storage”.

5. Leave the default settings and click “Next: Add Tags”.

6. Add a tag (e.g. “app: greengrassV2”) and click “Next: Configure Security Group”.

7. Choose the “greengrass” group that was created before. If you do not have it, just create a new group with the inbound rules that allow communication at 22 and 8883 ports.

Click “Review and Launch”.

8. Click “launch” and ensure that you have the key pair to access the instance by SSH.

Click “Launch instances”.

9. Wait until the instance gets running. Wait additional time (5–10 minutes) until the Greengrass Core device is created.

10. Go to the Greengrass Service in the AWS console.

11. Select the “Greengrass -> Core devices” in the left nav menu and ensure that the Core device is present there.

12. Go to the “Manage -> Things” left nav menu item, click on the publisher device, select the “Certificates” tab and click on the certificate link.

13. Click on “Policies” and click “Actions -> Attach policy” on the top right.

14. Select “GreengrassV2IoTThingPolicy” and click “Attach”.

4. Configure and deploy baseline communication

To get more information about the configurations performed below, see:

  1. Go to the Greengrass Service in the AWS console.
  2. Select the “Greengrass -> Core devices” in the left nav menu and click on the Core device that was created before.

3. Click the “Client devices” tab and click “Configure cloud discovery”.

4. Select “Core device” as the target type (selected by default). Click “Associate client devices”, type in “PublisherDevice” and click “Add”.

5. Click “Associate”.

6. Click “Edit configuration” near the “aws.greengrass.clientdevices.Auth” component.

7. In the “Configuration to merge” text area, add the following policy that allows the publisher device to send MQTT messages.

{
"deviceGroups": {
"formatVersion": "2021-03-05",
"definitions": {
"PublisherGroup": {
"selectionRule": "thingName: PublisherDevice*",
"policyName": "MqttPolicy"
}
},
"policies": {
"MqttPolicy": {
"AllowConnect": {
"statementDescription": "Allow client devices to connect.",
"operations": [
"mqtt:connect"
],
"resources": [
"*"
]
},
"AllowPublish": {
"statementDescription": "Allow client devices to publish on all topics.",
"operations": [
"mqtt:publish"
],
"resources": [
"mqtt:topic:*"
]
}
}
}
}
}

8. Click “Confirm”.

9. Click “Edit configuration” near the “aws.greengrass.clientdevices.mqtt.Bridge” component.

10. In the “Configuration to merge” text area, add the following configuration that sets up a local communication between the Core and the Publisher devices. Feel free to provide your topic name.

11. Click “Confirm”.

12. Click “Review and deploy”.

13. Click “Deploy” on the bottom right.

14. Go to the “Greengrass -> Deployments” left menu item and ensure that the deployment has the “Completed” status.

5. Register and deploy a Lambda Greengrass component

For the code, the AWS SAM application is used. See my GitHub repo: https://github.com/rimironenko/aws-iot-greengrass-project. To build and deploy it, use sam build and sam deploy --guided commands.

To get more about the Lambda configuration, see https://docs.aws.amazon.com/greengrass/v2/developerguide/import-lambda-function-console.html.

  1. Go to the Greengrass Service in the AWS console.
  2. Select the “Greengrass -> Components” left menu item and click “Create component”.

3. Choose “Import lambda function” and select the Lambda function created from the AWS SAM application.

4. For the event source, use the topic that was specified above for the “aws.greengrass.clientdevices.mqtt.Bridge” component. The type should be “Local publish/subscribe”. Increase the timeout to 15 seconds.

5. In the additional parameters section, configure the environment variable with the queue URL to be used by the function for sending messages.

6. For the container parameters, increase the memory size to 128 MB.

7. Leave all the other options as default and click the “Create component” button (the second button in the row on the top). The component definition will appear.

8. Click “Deploy” on the top right.

9. Select “Add to existing deployment”, select the Core device deployment that is used for the previous deployment, and click “Next”.

10. Leave all the default settings and click “Next”.

11. Leave all the components in the list and click “Next”.

12. Select the “aws.greengrass.Nucleus” component and click “Configure component”.

13. In the “Configuration to merge” text area, specify the following configuration to allocate more memory to the JVM. This step is required to avoid “OutOfMemory” errors.

{
"jvmOptions": "-Xmx256m -XX:+UseSerialGC -XX:TieredStopAtLevel=1"
}

14. Click “Confirm” and click “Next”.

15. Leave “Deployment policies” by default and click “Next”.

16. Click “Deploy” and wait until the deployment gets the “Completed” status.

17. Restart the Greengrass service to apply the configuration changes by sudo service greengrass restart command.

7. Test the application

  1. Connect to the EC2 instance by SSH.
  2. Navigate to the “/home/ec2-user/publisher_device” folder.
  3. Create inside the folder the “test.yaml” file with the content given below:
id: 1
message: Hello from Greengrass version 2

4. Create inside the folder the python script to publish messages to an MQTT topic.

import argparse
import time
import uuid
import json
from concurrent.futures import Future
from awscrt import io
from awscrt.io import LogLevel
from awscrt.mqtt import Connection, Client, QoS
from awsiot.greengrass_discovery import DiscoveryClient, DiscoverResponse
from awsiot import mqtt_connection_builder

allowed_actions = ['both', 'publish', 'subscribe']

parser = argparse.ArgumentParser()
parser.add_argument('-r', '--root-ca', action='store', dest='root_ca_path', help='Root CA file path')
parser.add_argument('-c', '--cert', action='store', required=True, dest='certificate_path', help='Certificate file path')
parser.add_argument('-k', '--key', action='store', required=True, dest='private_key_path', help='Private key file path')
parser.add_argument('-n', '--thing-name', action='store', required=True, dest='thing_name', help='Targeted thing name')
parser.add_argument('-t', '--topic', action='store', dest='topic', default='test/topic', help='Targeted topic')
parser.add_argument('--region', action='store', dest='region', default='us-east-1')
parser.add_argument('--max-pub-ops', action='store', dest='max_pub_ops', default=10)
parser.add_argument('--print-discover-resp-only', action='store_true', dest='print_discover_resp_only', default=False)
parser.add_argument('-v', '--verbosity', choices=[x.name for x in LogLevel], default=LogLevel.NoLogs.name,
help='Logging level')
parser.add_argument("-f", "--file", action="store", dest="file", default="test.yaml", help="File to send content to topic")

args = parser.parse_args()

io.init_logging(getattr(LogLevel, args.verbosity), 'stderr')

event_loop_group = io.EventLoopGroup(1)
host_resolver = io.DefaultHostResolver(event_loop_group)
client_bootstrap = io.ClientBootstrap(event_loop_group, host_resolver)

tls_options = io.TlsContextOptions.create_client_with_mtls_from_path(args.certificate_path, args.private_key_path)
if args.root_ca_path:
tls_options.override_default_trust_store_from_path(None, args.root_ca_path)
tls_context = io.ClientTlsContext(tls_options)

socket_options = io.SocketOptions()

print('Performing greengrass discovery...')
discovery_client = DiscoveryClient(client_bootstrap, socket_options, tls_context, args.region)
resp_future = discovery_client.discover(args.thing_name)
discover_response = resp_future.result()

print(discover_response)
if args.print_discover_resp_only:
exit(0)


def on_connection_interupted(connection, error, **kwargs):
print('connection interrupted with error {}'.format(error))


def on_connection_resumed(connection, return_code, session_present, **kwargs):
print('connection resumed with return code {}, session present {}'.format(return_code, session_present))


# Try IoT endpoints until we find one that works
def try_iot_endpoints():
for gg_group in discover_response.gg_groups:
for gg_core in gg_group.cores:
for connectivity_info in gg_core.connectivity:
try:
print('Trying core {} at host {} port {}'.format(gg_core.thing_arn, connectivity_info.host_address, connectivity_info.port))
mqtt_connection = mqtt_connection_builder.mtls_from_path(
endpoint=connectivity_info.host_address,
port=connectivity_info.port,
cert_filepath=args.certificate_path,
pri_key_filepath=args.private_key_path,
client_bootstrap=client_bootstrap,
ca_bytes=gg_group.certificate_authorities[0].encode('utf-8'),
on_connection_interrupted=on_connection_interupted,
on_connection_resumed=on_connection_resumed,
client_id=args.thing_name,
clean_session=False,
keep_alive_secs=30)

connect_future = mqtt_connection.connect()
connect_future.result()
print('Connected!')
return mqtt_connection

except Exception as e:
print('Connection failed with exception {}'.format(e))
continue

exit('All connection attempts failed')

mqtt_connection = try_iot_endpoints()


message = {}
file = open(args.file)
fileContent = file.read()
file.close()
message['message'] = fileContent
messageJson = json.dumps(message)
pub_future, _ = mqtt_connection.publish(args.topic, messageJson, QoS.AT_LEAST_ONCE)
pub_future.result()
print('Published topic {}: {}\n'.format(args.topic, messageJson))
time.sleep(5)

For the successful test, the folder should contain the files as on the screenshot given below.

5. Send the test message by the following command:

python3 sendMessage.py \
--thing-name PublisherDevice \
--topic greengrass/v2/message \
--file test.yaml \
--root-ca root.ca.pem \
--cert device.pem.crt \
--key private.pem.key \
--region us-east-1 \
--verbosity Warn

Ensure that you are using the correct AWS Region and naming of your device, topic, and files.

6. If the message is sent successfully, you should see the log message about the successful publishing to the topic.

7. Go to the SQS queue in the AWS console and see that there is a new message.

8. Select the queue, click “Send and receive messages”.

9. Click “Poll for messages” and observe the message in the queue.

10. Click on the message and observe the JSON message that was sent by the Lambda function from the Greengrass Core device.

Cleanup

  1. Go to the Greengrass Service in the AWS console.
  2. Select the “Greengrass -> Deployments” left nav menu item.
  3. Select the two deployments that were created and click “Cancel”.

4. Ensure that both deployments are canceled.

5. Select the “Greengrass -> Core devices” left nav menu item, click on the Core device, and click “Delete” on the top right.

6. Select the “Greengrass -> Components” left nav menu item, click on the Lambda function and click “Delete version”.

7. Select the “Manage -> Things” left menu item, select both the Core and the Publisher device, and click “Delete”.

8. Select the “Secure -> Role Aliases” left menu item, select the Alias and click “Actions -> Delete”.

9. Select the “Secure -> Policies” left menu item, select the Alias and click “Actions -> Delete”.

10. Select the “Secure -> Certificates” left menu item, select the Alias and click “Actions -> Delete”.

11. Terminate the EC2 instance.

12. Undeploy the AWS SAM application by sam delete command.

13. (Optional) remove the IAM roles and policies except the “Greengrass_ServiceRole” that can be re-used for further experiments with AWS Greengrass.

Troubleshooting

  1. If the Greengrass service role was removed from IAM, you may face difficulties attaching a new one in the AWS console. Use CLI commands to attach a new role in this case, as described in https://docs.aws.amazon.com/greengrass/v1/developerguide/service-role.html#manage-service-role-cli.
  2. The Lambda function uses System.out to write messages to the log file under the “/greengrass/v2/logs” folder, the file name is the same as the component name.

Use this log file and the “greengrass.log” file to troubleshoot any issues.

3. If you have received the 403 status code during an attempt to send a message, ensure that the publisher device has a policy assigned to its certificate, and the SQS queue has a policy that allows sending messages to it.

Conclusion

Advantages of AWS IoT Greengrass (version 2)

  1. The total configuration is simpler than for the previous version.
  2. Comparing to the previous version, Greengrass does not struggle with Java anymore. The only configuration that is required is specifying JVM options for the Nucleus component.

Disadvantages of AWS IoT Greengrass (version 2)

  1. Lambda subscriptions are coupled to the Lambda Component configuration. To update event sources for a Lambda component, the new version should be introduced with repetitive manual input of all the settings that are not consumed automatically from the previous version.

Constraints of AWS IoT Greengrass (version 2)

  1. The setup is very different comparing to version 1 and requires a different flow.
  2. Lambda functions for Greengrass still require the language-specific SDK to be packaged with the Lambda function (but only if custom code uses it).
  3. Java, Python, and Node.js are the supported runtimes for Greengrass version 2.

--

--