Introduction
This article explores a method for monitoring the environment using sensor data rules and issuing alerts for abnormal readings. To that end, we will setup a continuous MQTT communication for passing sensor data using the UP Squared* board and Grove* shield, the AWS Greengrass* group and device-to-cloud communication. The Grove sensors used in this article are the loudness sensor, the barometer sensor, and the IR distance interrupter. The environment’s loudness, ambient temperature, barometric pressure, and altitude will be the data collected from the sensors. The rules are set according to the sensor readings to determine the abnormal values and to issue alerts. First, we will configure the Greengrass devices via the AWS* console, and then we will configure them on both the UP Squared* board and another Linux Ubuntu* 16.04 machine. On the UP Squared board, we will run a Python* script to collect the sensor data, and filter it out based on rules. Then, the publisher device will send the sensor data to the subscriber device (the Linux Ubuntu machine), as well as send the alerts for abnormal readings to the AWS IoT cloud via MQTT topics.
Learn more about the AWS Greengrass
Learn more about the UP Squared board
Prerequisites
Hardware:
- UP Squared board
- Linux Ubuntu* Server for UP Squared 16.04
- Grove interface board, part of the UP Squared board kit
- Grove loudness sensor
- Grove barometer sensor
- Grove IR distance interrupter sensor
- 4-pin Grove cables
- Machine with Linux Ubuntu 16.04 OS
AWS Greengrass:
- AWS account; free trial was used for this article
Grove* Sensors
On the UP Squared board, install the MRAA and UPM libraries to interface with Grove sensors:
sudo add-apt-repository ppa:mraa/mraa sudo apt-get update sudo apt-get install libmraa1 libmraa-dev mraa-tools python-mraa python3-mraa sudo apt-get install libupm-dev libupm-java python-upm python3-upm node-upm upm-example
Code 1. Commands to install Grove dependencies
To enable non-privileged access to the Grove sensors, run the following commands:
sudo add-apt-repository ppa:ubilinux/up sudo apt update sudo apt install upboard-extras sudo usermod -a -G i2c ggc_user sudo usermod -a -G grio ggc_user sudo reboot
Code 2. Commands to enable non-privileged access to Grove sensors
AWS Greengrass* Setup
To install AWS Greengrass on the UP Squared board, follow these instructions
Check that you have installed all the needed dependencies:
sudo apt update git clone https://github.com/aws-samples/aws-greengrass-samples.git cd aws-greengrass-samples cd greengrass-dependency-checker-GGCv1.3.0 sudo ./check_ggc_dependencies
Code 3. Commands to check AWS dependencies
Go to the AWS IoT console. Choose Greengrass from leftside menu, select Groups underneath it, and select your group from main window:
Figure 1. AWS Greengrass Groups view
Select Devices from the left-side menu. Click Add Device button, on the top right corner:
Figure 2. Greengrass devices view
Choose Create New Device:
Figure 3. Creating a new device view
Enter the name, pub, in the field and clickNext:
Figure 4. Creating a Registry entry for a device
Click on Use Defaults button:
Figure 5. Set up security view
Download the security credentials, we will use them in the next module. Click Finish:
Figure 6. Download security credentials
You should see the new device on the screen:
Figure 7. Greengrass Devices view
Add another new device and name it sub. When you’re done, you should see the following screen, with two new devices:
Figure 8. Updated Greengrass Devices view
On the left-side menu, select Subscriptions. Click on Add Subscription:
Figure 9. Greengrass Subscriptions view
For the source, go to Devices tab and select pub. For the target, go to Devices tab and select sub. Click Next:
Figure 10. Creating subscription: selecting source and target view
Add topic, sensors/data/pubsub:
Figure 11. Creating subscription: adding topic view
Review the subscription and click Next:
Figure 12. Confirm and save Subscription view
You can see the subscription:
Figure 13. Subscriptions view
Create another subscription by following the steps below. Choose pub as a source and IoT Cloud as a target. For topic, enter sensors/data/alerts. After you done, you should see a similar screen:
Figure 14. Updated Subscriptions view
On the group header, click Actions, select Deploy and wait until it is successfully completed:
Figure 15. Deploying the Greengrass Group view
Publisher Setup
In this module, we will configure the Greengrass device to be a MQTT publisher. In this case, we are using the UP Squared board both as a Greengrass core, and as a publisher device, so we can get the sensor data from a device. The other Linux machine will be used as a subscriber device and configured in the next module.
Copy pub’s tar file, which was saved in a previous module, and untar it. Save the files in the publisher device (UP Squared board) and rename them for readability:
tar –xzvf <pub-credentials-id>-setup.tar.gz mv <pub-credentials-id>.cert.pem pub.cert.pem mv <pub-credentials-id>.private.pem pub.private.pem mv <pub-credentials-id>.public.pem pub.public.pem
Code 4. Commands to Save Publisher’s Credentials
In the publisher folder, get a root certificate and save it as root-ca-cert.pem:
wget https://www.symantec.com/content/en/us/enterprise/verisign/roots/VeriSign-Class%203-Public-Primary-Certification-Authority-G5.pem –O root-ca-cert.pem
Code 5. Commands to get a root certificate
On Up Squared board, install AWS IoT SDK for Python:
python >>> import ssl >>> ssl.OPENSSL_VERSION # output should be version of OpenSSL 1.0.1+:‘OpenSSL 1.0.2g 1 Mar 2016’ >>> exit() cd ~ git clone https://github.com/aws/aws-iot-device-sdk-python.git cd aws-iot-device-sdk-python python setup.py install
Code 6. Commands to install AWS IoT SDK for Python
The following rules will allow us to filter the sensor data values, and capture messages if the values are abnormal. We will determine the range of normal readings. For temperature, we define the normal range to be below 25 and above 20 degrees Celsius:
class TemperatureOver25(Rule): def predicate(self, sensorValue): return sensorValue > 25 def action(self, sensorValue): message = “Temperature Over 25 Rule activated on “ + self.sensorId + “ with sensor value “ + str(sensorValue) return message class TemperatureUnder20(Rule): def predicate(self, sensorValue): return sensorValue < 20 def action(self, sensorValue): message = “Temperature Under 20 Rule activated on “ + self.sensorId + “ with sensor value “ + str(sensorValue) return message
Code 7. Code snippet with temperature classes
We will filter the rules by sensor:
def filterBySensorId(sensorId, rules): “Filter a list of rules by sensorId” return [rule for rule in rules if rule.sensorId == sensorId]
Code 8. Code snippet for filtering rules by sensor
The rules will be imported and instantiated in the next script, greengrassCommunication.py. Save the following Python script as sensor_rules.py to the publisher folder:
class Rule: “”” A Base Class for defining IoT automation rules. “”” def __init__(self, sensorId): “”” Constructor function that takes an id that uniquely identifies a sensor. “”” self.sensorId = sensorId def predicate(self, sensorValue): “In the base Rule class, the predicate always returns False” return False def action(self, sensorValue): message = “Generic Rule activiation on “ + self.sensorId + “ with senor value “ + str(sensorValue) return message class TemperatureOver25(Rule): def predicate(self, sensorValue): return sensorValue > 25 def action(self, sensorValue): message = “Temperature Over 25 Rule activated on “ + self.sensorId + “ with sensor value “ + str(sensorValue) return message class TemperatureUnder20(Rule): def predicate(self, sensorValue): return sensorValue < 20 def action(self, sensorValue): message = “Temperature Under 20 Rule activated on “ + self.sensorId + “ with sensor value “ + str(sensorValue) return message class PressureOver96540(Rule): def predicate(self, sensorValue): return sensorValue > 96540 def action(self, sensorValue): message = “Pressure Over 96540 Rule activated on “ + self.sensorId + “ with sensor value “ + str(sensorValue) return message class PressureUnder96534(Rule): def predicate(self, sensorValue): return sensorValue < 96534 def action(self, sensorValue): message = “Pressure Under 96534 Rule activated on “ + self.sensorId + “ with sensor value “ + str(sensorValue) return message class AltitudeOver1214(Rule): def predicate(self, sensorValue): return sensorValue > 1214 def action(self, sensorValue): message = “Altitude Over 1214 Rule activated on “ + self.sensorId + “ with sensor value “ + str(sensorValue) return message class AltitudeUnder1214(Rule): def predicate(self, sensorValue): return sensorValue < 1214 def action(self, sensorValue): message = “Altitude Under 1214 Rule activated on “ + self.sensorId + “ with sensor value “ + str(sensorValue) return message class ObjectDetected(Rule): def predicate(self, sensorValue): return sensorValue == True def action(self, sensorValue): message = “Object Detected Rule activated on “ + self.sensorId + “ with sensor value “ + str(sensorValue) return message class LoudnessOver3(Rule): def predicate(self, sensorValue): return sensorValue > 3 def action(self, sensorValue): message = “Loudness Over 3 Rule activated on “ + self.sensorId + “ with sensor value “ + str(sensorValue) return message class LoudnessUnder05(Rule): def predicate(self, sensorValue): return sensorValue < 0.5 def action(self, sensorValue): message = “Loudness Under 0.5 Rule activated on “ + self.sensorId + “ with sensor value “ + str(sensorValue) return message def filterBySensorId(sensorId, rules): “Filter a list of rules by sensorId” return [rule for rule in rules if rule.sensorId == sensorId]
Code 9. sensor_rules.py, Python script to create rules
We will need the following imports to get Grove sensor data:
from upm import pyupm_bmp280 as bmp280 from upm import pyupm_rfr359f as rfr359f from upm import pyupm_loudness as loudness import mraa
Code 10. Import statements for Grove sensors
We will interface with Grove shield and instantiate the sensors. Loudness sensor is connected to pin A2 which needs to be offset by 512. The IR distance interrupter, which will be used for object detection, is connected to pin D2 and needs to be offset by 512:
mraa.addSubplatform(mraa.GROVEPI, “0”) # bmp is a barometer sensor bmp = bmp280.BMP280(0, 0x76) loudness_sensor = loudness.Loudness(514, 5.0) # IR distance interruptor object_detector = rfr359f.RFR359F(514)
Code 11. Interfacing with Grove shield and instantiating its sensors
Rules will be instantiated for each one class and will be saved in rules list:
r1 = sensor_rules.Rule(“generic_Sensor”) # Generic Rule assigned to a sensor name “generic_Sensor” r2 = sensor_rules.TemperatureOver25(“temperature”) # Derived rule assigned to a sensor name “temperature” r3 = sensor_rules.TemperatureUnder20(“temperature”) # Derived rule assigned to a sensor name “temperature” r4 = sensor_rules.PressureOver96540(“pressure”) # PressureOver96534 assigned to a sensor name “pressure” r5 = sensor_rules.PressureUnder96534(“pressure”) # PressureUnder96534 assigned to a sensor name “pressure” r6 = sensor_rules.AltitudeOver1214(“altitude”) # AltitudeOver1214 assigned to a sensor name “altitude” r7 = sensor_rules.AltitudeUnder1214(“altitude”) # AltitudeUnder1214 assigned to a sensor name “altitude” r8 = sensor_rules.ObjectDetected(“object detection”) # ObjectDetected is assigned to a sensor “object detection” r9 = sensor_rules.LoudnessOver3(“loudness”) # LoudnessOver3 is assigned to a sensor “loudness” r10 = sensor_rules.LoudnessUnder05(“loudness”) # LoudnessUnder05 is assigned to a sensor “loudness” rules = [r1, r2, r3, r4, r5, r6, r7, r8, r9, r10]
Code 12. Instantiating rules
The following code snippet reads the sensor data and saves them in a JSON format:
def get_sensor_data(): # Getting new readings from barometer sensor bmp.update() pressure_value = bmp.getPressurePa() temperature_value = bmp.getTemperature() # Translating altitude value from meters to feet altitude_value = int(bmp.getAltitude() * 3.2808) # Get IR object detection data # returns True or False object_detection_value = object_detector.objectDetected() loudness_value = loudness_sensor.loudness() timestamp = time.time() sensor_data = {“values”:[ {“sensor_id”: “pressure”, “value”: pressure_value, “timestamp”: timestamp}, {“sensor_id”: “temperature”, “value”: temperature_value, “timestamp”: timestamp}, {“sensor_id”: “altitude”, “value”: altitude_value, “timestamp”: timestamp}, {“sensor_id”: “object detection”, “value”: object_detection_value, “timestamp”: timestamp}, {“sensor_id”: “loudness”, “value”: loudness_value, “timestamp”: timestamp} ] } sensor_data_json = json.loads(json.dumps(sensor_data[“values”])) return sensor_data_json
Code 13. Code snippet to get sensor data
This code snippet filters the JSON object with sensor data and creates alert messages for abnormal readings:
def apply_rules(sensor_data_json): rules_message = [] for item in sensor_data_json: sensor_id = item[‘sensor_id’] sensor_value = item[‘value’] filteredRules = sensor_rules.filterBySensorId(sensor_id, rules) for r in filteredRules: if r.predicate(sensor_value) == True: rules_message.append(r.action(sensor_value)) return rules_message
Code 14. Code snippet to filter rules by sensor
The while loop will run continuously to publish the sensor data in the sensors/data/pubsub topic and alerts in sensors/data/alerts topic:
while True: if args.mode == ‘both’ or args.mode == ‘publish’: message = {} sensor_data_json = get_sensor_data() # message[‘message’] = args.message message[‘message’] = get_message(sensor_data_json) message[‘alerts’] = apply_rules(sensor_data_json) message[‘sequence’] = loopCount messageJson = json.dumps(message) myAWSIoTMQTTClient.publish(topic, messageJson, 0) if args.mode == ‘publish’: print(‘Published topic %s: %s\n’ % (topic, messageJson)) cloud_topic = “sensors/data/alerts” alerts_json = json.dumps(message[‘alerts’]) myAWSIoTMQTTClient.publish(cloud_topic, alerts_json, 0) loopCount += 1 time.sleep(1)
Code 15. Continuous while loop for publishing sensor data messages and alerts
The Code 16 Python script will create the sensor rules, collect the sensor data, and send MQTT messages with the sensor data values to the Greengrass subscriber device as well as send alerts with abnormal data to the IoT cloud. Save this Python script as greengrassCommunication.py to the same folder:
from __future__ import print_function import os import sys import time import uuid import json import argparse from AWSIoTPythonSDK.core.greengrass.discovery.providers import DiscoveryInfoProvider from AWSIoTPythonSDK.core.protocol.connection.cores import ProgressiveBackOffCore from AWSIoTPythonSDK.MQTTLib import AWSIoTMQTTClient from AWSIoTPythonSDK.exception.AWSIoTExceptions import DiscoveryInvalidRequestException import signal, atexit from upm import pyupm_bmp280 as bmp280 from upm import pyupm_rfr359f as rfr359f from upm import pyupm_loudness as loudness import mraa import sensor_rules mraa.addSubplatform(mraa.GROVEPI, “0”) # bmp is a barometer sensor bmp = bmp280.BMP280(0, 0x76) loudness_sensor = loudness.Loudness(514, 5.0) # IR distance interruptor object_detector = rfr359f.RFR359F(514) r1 = sensor_rules.Rule(“generic_Sensor”) # Generic Rule assigned to a sensor name “generic_Sensor” r2 = sensor_rules.TemperatureOver25(“temperature”) # Derived rule assigned to a sensor name “temperature” r3 = sensor_rules.TemperatureUnder20(“temperature”) # Derived rule assigned to a sensor name “temperature” r4 = sensor_rules.PressureOver96540(“pressure”) # PressureOver96534 assigned to a sensor name “pressure” r5 = sensor_rules.PressureUnder96534(“pressure”) # PressureUnder96534 assigned to a sensor name “pressure” r6 = sensor_rules.AltitudeOver1214(“altitude”) # AltitudeOver1214 assigned to a sensor name “altitude” r7 = sensor_rules.AltitudeUnder1214(“altitude”) # AltitudeUnder1214 assigned to a sensor name “altitude” r8 = sensor_rules.ObjectDetected(“object detection”) # ObjectDetected is assigned to a sensor “object detection” r9 = sensor_rules.LoudnessOver3(“loudness”) # LoudnessOver3 is assigned to a sensor “loudness” r10 = sensor_rules.LoudnessUnder05(“loudness”) # LoudnessUnder05 is assigned to a sensor “loudness” rules = [r1, r2, r3, r4, r5, r6, r7, r8, r9, r10] def get_sensor_data(): # Getting new readings from barometer sensor bmp.update() pressure_value = bmp.getPressurePa() temperature_value = bmp.getTemperature() # Translating altitude value from meters to feet altitude_value = int(bmp.getAltitude() * 3.2808) # Get IR object detection data # returns True or False object_detection_value = object_detector.objectDetected() loudness_value = loudness_sensor.loudness() timestamp = time.time() sensor_data = {“values”:[ {“sensor_id”: “pressure”, “value”: pressure_value, “timestamp”: timestamp}, {“sensor_id”: “temperature”, “value”: temperature_value, “timestamp”: timestamp}, {“sensor_id”: “altitude”, “value”: altitude_value, “timestamp”: timestamp}, {“sensor_id”: “object detection”, “value”: object_detection_value, “timestamp”: timestamp}, {“sensor_id”: “loudness”, “value”: loudness_value, “timestamp”: timestamp} ] } sensor_data_json = json.loads(json.dumps(sensor_data[“values”])) return sensor_data_json def get_message(sensor_data_json): sensor_data_message = [] for item in sensor_data_json: sensor_id = item[‘sensor_id’] sensor_value = item[‘value’] sensor_data_message.append(item) return sensor_data_message def apply_rules(sensor_data_json): rules_message = [] for item in sensor_data_json: sensor_id = item[‘sensor_id’] sensor_value = item[‘value’] filteredRules = sensor_rules.filterBySensorId(sensor_id, rules) for r in filteredRules: if r.predicate(sensor_value) == True: rules_message.append(r.action(sensor_value)) return rules_message AllowedActions = [‘both’, ‘publish’, ‘subscribe’] # General message notification callback def customOnMessage(message): print(‘Received message on topic %s: %s\n’ % (message.topic, message.payload)) MAX_DISCOVERY_RETRIES = 10 GROUP_CA_PATH = “./groupCA/” # Read in command-line parameters parser = argparse.ArgumentParser() parser.add_argument(“-e”, “–endpoint”, action=”store”, required=True, dest=”host”, help=”Your AWS IoT custom endpoint”) parser.add_argument(“-r”, “–rootCA”, action=”store”, required=True, dest=”rootCAPath”, help=”Root CA file path”) parser.add_argument(“-c”, “–cert”, action=”store”, dest=”certificatePath”, help=”Certificate file path”) parser.add_argument(“-k”, “–key”, action=”store”, dest=”privateKeyPath”, help=”Private key file path”) parser.add_argument(“-n”, “–thingName”, action=”store”, dest=”thingName”, default=”Bot”, help=”Targeted thing name”) parser.add_argument(“-m”, “–mode”, action=”store”, dest=”mode”, default=”both”, help=”Operation modes: %s”%str(AllowedActions)) args = parser.parse_args() host = args.host rootCAPath = args.rootCAPath certificatePath = args.certificatePath privateKeyPath = args.privateKeyPath clientId = args.thingName thingName = args.thingName topic = “sensors/data/pubsub” if args.mode not in AllowedActions: parser.error(“Unknown –mode option %s. Must be one of %s” % (args.mode, str(AllowedActions))) exit(2) if not args.certificatePath or not args.privateKeyPath: parser.error(“Missing credentials for authentication.”) exit(2) # Progressive back off core backOffCore = ProgressiveBackOffCore() # Discover GGCs discoveryInfoProvider = DiscoveryInfoProvider() discoveryInfoProvider.configureEndpoint(host) discoveryInfoProvider.configureCredentials(rootCAPath, certificatePath, privateKeyPath) discoveryInfoProvider.configureTimeout(10) # 10 sec retryCount = MAX_DISCOVERY_RETRIES discovered = False groupCA = None coreInfo = None while retryCount != 0: try: discoveryInfo = discoveryInfoProvider.discover(thingName) caList = discoveryInfo.getAllCas() coreList = discoveryInfo.getAllCores() # We only pick the first ca and core info groupId, ca = caList[0] coreInfo = coreList[0] print(“Discovered GGC: %s from Group: %s” % (coreInfo.coreThingArn, groupId)) print(“Now we persist the connectivity/identity information…”) groupCA = GROUP_CA_PATH + groupId + “_CA_” + str(uuid.uuid4()) + “.crt” if not os.path.exists(GROUP_CA_PATH): os.makedirs(GROUP_CA_PATH) groupCAFile = open(groupCA, “w”) groupCAFile.write(ca) groupCAFile.close() discovered = True print(“Now proceed to the connecting flow…”) break except DiscoveryInvalidRequestException as e: print(“Invalid discovery request detected!”) print(“Type: %s” % str(type(e))) print(“Error message: %s” % e.message) print(“Stopping…”) break except BaseException as e: print(“Error in discovery!”) print(“Type: %s” % str(type(e))) print(“Error message: %s” % e.message) retryCount -= 1 print(“\n%d/%d retries left\n” % (retryCount, MAX_DISCOVERY_RETRIES)) print(“Backing off…\n”) backOffCore.backOff() if not discovered: print(“Discovery failed after %d retries. Exiting…\n” % (MAX_DISCOVERY_RETRIES)) sys.exit(-1) # Iterate through all connection options for the core and use the first successful one myAWSIoTMQTTClient = AWSIoTMQTTClient(clientId) myAWSIoTMQTTClient.configureCredentials(groupCA, privateKeyPath, certificatePath) myAWSIoTMQTTClient.onMessage = customOnMessage connected = False for connectivityInfo in coreInfo.connectivityInfoList: currentHost = connectivityInfo.host currentPort = connectivityInfo.port print(“Trying to connect to core at %s:%d” % (currentHost, currentPort)) myAWSIoTMQTTClient.configureEndpoint(currentHost, currentPort) try: myAWSIoTMQTTClient.connect() connected = True break except BaseException as e: print(“Error in connect!”) print(“Type: %s” % str(type(e))) print(“Error message: %s” % e.message) if not connected: print(“Cannot connect to core %s. Exiting…” % coreInfo.coreThingArn) sys.exit(-2) # Successfully connected to the core if args.mode == ‘both’ or args.mode == ‘subscribe’: myAWSIoTMQTTClient.subscribe(topic, 0, None) time.sleep(2) loopCount = 0 while True: if args.mode == ‘both’ or args.mode == ‘publish’: message = {} sensor_data_json = get_sensor_data() # message[‘message’] = args.message message[‘message’] = get_message(sensor_data_json) message[‘alerts’] = apply_rules(sensor_data_json) message[‘sequence’] = loopCount messageJson = json.dumps(message) myAWSIoTMQTTClient.publish(topic, messageJson, 0) if args.mode == ‘publish’: print(‘Published topic %s: %s\n’ % (topic, messageJson)) cloud_topic = “sensors/data/alerts” alerts_json = json.dumps(message[‘alerts’]) myAWSIoTMQTTClient.publish(cloud_topic, alerts_json, 0) loopCount += 1 time.sleep(1)
Code 16. greengrassCommunication.py, Python script to get Sensor Data and Publish the MQTT Messages
Subscriber Setup
In this module, we will configure the Greengrass device to be a MQTT subscriber. On the subscriber device, the non-UP Squared Linux machine, do the same: copy sub’s tar file which was saved in a previous module and untar it, save the files in the publisher device, and rename them for readability:
tar –xzvf <sub-credentials-id>-setup.tar.gz mv <sub-credentials-id>.cert.pem sub.cert.pem mv <sub-credentials-id>.private.pem sub.private.pem mv <sub-credentials-id>.public.pem sub.public.pem
Code 17. Commands to save subscriber credentials
In the subscriber folder, get a root certificate and save it as root-ca-cert.pem:
wget https://www.symantec.com/content/en/us/enterprise/verisign/roots/VeriSign-Class%203-Public-Primary-Certification-Authority-G5.pem –O root-ca-cert.pem
Code 18. Commands to get a root certificate
On the subscriber device, install AWS IoT SDK for Python:
python >>> import ssl >>> ssl.OPENSSL_VERSION # output should be version of OpenSSL 1.0.1+:‘OpenSSL 1.0.2g 1 Mar 2016’ >>> exit() cd ~ git clone https://github.com/aws/aws-iot-device-sdk-python.git cd aws-iot-device-sdk-python python setup.py install
Code 19. Commands to install AWS IoT SDK for Python
The subscriber device will listen to sensors/data/pubsub topic continuously, printing “Waiting for the message.” every 10 seconds to confirm the script is still running:
while True: print(“Waiting for the message.”) time.sleep(10)
Code 20. Code snippet to continuously wait for the message
Copy the following Python script into the publisher device’s folder where publisher keys are stored, and save it as subscription.py:
from __future__ import print_function import os import sys import time import uuid import json import argparse from AWSIoTPythonSDK.core.greengrass.discovery.providers import DiscoveryInfoProvider from AWSIoTPythonSDK.core.protocol.connection.cores import ProgressiveBackOffCore from AWSIoTPythonSDK.MQTTLib import AWSIoTMQTTClient from AWSIoTPythonSDK.exception.AWSIoTExceptions import DiscoveryInvalidRequestException import signal, atexit AllowedActions = [‘both’, ‘publish’, ‘subscribe’] # General message notification callback def customOnMessage(message): print(‘Received message on topic %s: %s\n’ % (message.topic, message.payload)) MAX_DISCOVERY_RETRIES = 10 GROUP_CA_PATH = “./groupCA/” # Read in command-line parameters parser = argparse.ArgumentParser() parser.add_argument(“-e”, “–endpoint”, action=”store”, required=True, dest=”host”, help=”Your AWS IoT custom endpoint”) parser.add_argument(“-r”, “–rootCA”, action=”store”, required=True, dest=”rootCAPath”, help=”Root CA file path”) parser.add_argument(“-c”, “–cert”, action=”store”, dest=”certificatePath”, help=”Certificate file path”) parser.add_argument(“-k”, “–key”, action=”store”, dest=”privateKeyPath”, help=”Private key file path”) parser.add_argument(“-n”, “–thingName”, action=”store”, dest=”thingName”, default=”Bot”, help=”Targeted thing name”) parser.add_argument(“-m”, “–mode”, action=”store”, dest=”mode”, default=”both”, help=”Operation modes: %s”%str(AllowedActions)) args = parser.parse_args() host = args.host rootCAPath = args.rootCAPath certificatePath = args.certificatePath privateKeyPath = args.privateKeyPath clientId = args.thingName thingName = args.thingName topic = “sensors/data/pubsub” if args.mode not in AllowedActions: parser.error(“Unknown –mode option %s. Must be one of %s” % (args.mode, str(AllowedActions))) exit(2) if not args.certificatePath or not args.privateKeyPath: parser.error(“Missing credentials for authentication.”) exit(2) # Progressive back off core backOffCore = ProgressiveBackOffCore() # Discover GGCs discoveryInfoProvider = DiscoveryInfoProvider() discoveryInfoProvider.configureEndpoint(host) discoveryInfoProvider.configureCredentials(rootCAPath, certificatePath, privateKeyPath) discoveryInfoProvider.configureTimeout(10) # 10 sec retryCount = MAX_DISCOVERY_RETRIES discovered = False groupCA = None coreInfo = None while retryCount != 0: try: discoveryInfo = discoveryInfoProvider.discover(thingName) caList = discoveryInfo.getAllCas() coreList = discoveryInfo.getAllCores() # We only pick the first ca and core info groupId, ca = caList[0] coreInfo = coreList[0] print(“Discovered GGC: %s from Group: %s” % (coreInfo.coreThingArn, groupId)) print(“Now we persist the connectivity/identity information…”) groupCA = GROUP_CA_PATH + groupId + “_CA_” + str(uuid.uuid4()) + “.crt” if not os.path.exists(GROUP_CA_PATH): os.makedirs(GROUP_CA_PATH) groupCAFile = open(groupCA, “w”) groupCAFile.write(ca) groupCAFile.close() discovered = True print(“Now proceed to the connecting flow…”) break except DiscoveryInvalidRequestException as e: print(“Invalid discovery request detected!”) print(“Type: %s” % str(type(e))) print(“Error message: %s” % e.message) print(“Stopping…”) break except BaseException as e: print(“Error in discovery!”) print(“Type: %s” % str(type(e))) print(“Error message: %s” % e.message) retryCount -= 1 print(“\n%d/%d retries left\n” % (retryCount, MAX_DISCOVERY_RETRIES)) print(“Backing off…\n”) backOffCore.backOff() if not discovered: print(“Discovery failed after %d retries. Exiting…\n” % (MAX_DISCOVERY_RETRIES)) sys.exit(-1) # Iterate through all connection options for the core and use the first successful one myAWSIoTMQTTClient = AWSIoTMQTTClient(clientId) myAWSIoTMQTTClient.configureCredentials(groupCA, privateKeyPath, certificatePath) myAWSIoTMQTTClient.onMessage = customOnMessage connected = False for connectivityInfo in coreInfo.connectivityInfoList: currentHost = connectivityInfo.host currentPort = connectivityInfo.port print(“Trying to connect to core at %s:%d” % (currentHost, currentPort)) myAWSIoTMQTTClient.configureEndpoint(currentHost, currentPort) try: myAWSIoTMQTTClient.connect() connected = True break except BaseException as e: print(“Error in connect!”) print(“Type: %s” % str(type(e))) print(“Error message: %s” % e.message) if not connected: print(“Cannot connect to core %s. Exiting…” % coreInfo.coreThingArn) sys.exit(-2) # Successfully connected to the core if args.mode == ‘both’ or args.mode == ‘subscribe’: myAWSIoTMQTTClient.subscribe(topic, 0, None) print(“after subscription”) time.sleep(2) loopCount = 0 while True: print(“Waiting for the message.”) time.sleep(10)
Code 21. subscription.py, Python script to subscribe to MQTT messages
Run the Scripts
In this module, we will run the Python scripts and view the MQTT messages with sensor data.
On the UP Squared board, start the Greengrass service:
cd <path-to-greengrass>/greengrass/ggc/core sudo ./greengrassd start
Code 22. Commands to start Greengrass service
Go to the publisher folder:
cd <path-to-publisher-folder>
Code 23. Command to navigate to publisher folder
Get your AWS IoT endpoint ID by going to the AWS console, then to IoT Core page. On the bottom left-side menu, select Settings. Copy your endpoint value:
Figure 16. Settings view
Substitute your AWS IoT endpoint ID and run the following command:
python greengrassCommunication.py --endpoint <your-aws-iot-endpoint-id>.iot.us-west-2.amazonaws.com --rootCA root-ca-cert.pem --cert pub.cert.pem --key pub.private.key --thingName pub --mode publish
Code 24. Command to run greengrassCommunication.py
You should see the following screen:
Figure 17. Command to run greengrassCommunication.py and MQTT messages
On the subscriber device, go to the subscriber folder:
cd <path-to-subscriber-folder>
Code 25. Command to navigate to subscriber folder
Substitute your AWS IoT endpoint ID and run the following command:
python subscription.py --endpoint <your-aws-iot-endpoint-id>.iot.us-west-2.amazonaws.com --rootCA root-ca-cert.pem --cert sub.cert.pem --key sub.private.key --thingName sub --mode subscribe
Code 26. Command to run subscription.py
You should see the following screen:
Figure 18. Command to run subscription.py and received MQTT messages
Go to the AWS IoT console. Select Test from the left-side menu. Type sensors/data/alerts in the topic field, change MQTT payload display to display it as strings, and click Subscribe to topic:
Figure 19. MQTT subscription view
After some time, messages should display on the bottom of the screen:
Figure 20. MQTT messages view
As you can see, the rules were activated for some abnormal sensor readings. You may wish to create some action logic to return the values back to normal. This setup will allow you to monitor your environment and ensure the data readings are in the normal range.
About the Author
Rozaliya Everstova is a software engineer at Intel in the Core and Visual Computing Group working on scale enabling projects for Internet of Things.