AWS Storage Blog
Building an IoT solution at the edge with AWS Snowcone
UPDATE: The second blog post in this two-post series was published on January 5, 2020.
Internet of Things (IoT) applications, like other applications, require edge solutions to operate in austere conditions with limited network connectivity or limited infrastructure. IoT applications at the edge can span numerous uses, like automation, optimization, and intelligent manufacturing to name a few. One real world example is a mining customer collecting toxic gas measurements from mining sensors in underground mines, and then using those measurements to monitor and analyze airflow. In many cases, IoT solutions at the edge help to keep workers safe, in this case by ensuring optimal ventilation and air quality.
In this blog, we show how you can build an edge compute solution to collect sensor data in a disconnected environment. We also cover storing the sensor data, so you can use sensor data for additional post-processing work in any IoT data pipeline. This blog, which is the first of two blogs in a series, focuses on building an IoT sensor workflow at the edge using non-cloud-native tools.
The second blog (coming soon) covers how you can simplify and manage this workflow using AWS IoT Greengrass. AWS IoT Greengrass is an IoT service that provides complete management, security, and monitoring for your sensor network. In the second blog in the series, we will demonstrate how to trigger email notifications using the Amazon Simple Notification Service (Amazon SNS) as well as how to deploy AWS Lambda code to run inference at the Edge.
What is AWS Snowcone?
AWS Snowcone, the smallest member of the AWS Snow Family, extends data transfer and cloud services to the edge. Snowcone is small, portable, ruggedized, and can be powered with a portable battery pack for mobility. Snowcone offers up to 8 TB of disk storage that is secure and capable of running Amazon EC2 instance types: snc1.micro, snc1.small, and snc1.medium. You can order a Snowcone by following the Snowcone ordering process and you can learn even more about Snowcone by reading this blog.
A complete DIY sensor data storage system using AWS Snowcone
In this blog, we build a solution using AWS Snowcone and an ESP32-based sensor to simulate a disconnected factory floor. The following diagram depicts a workflow that collects sensor data and stores it on a Snowcone-based EC2 instance.
We use MQTT, which is a messaging protocol, for publishing and subscribing messages between devices and servers. The EC2 instance runs an MQTT broker to process and store incoming requests. Sensor data is then stored as flat files or in a database.
IoT prototype
The following is a prototype of the home-built sensor based on the ESP32 platform.
A repurposed push-button is connected to PIN 18 and GND of the ESP32. No pullup resistors are needed when you use these two pins. The ESP32 device is programmed and powered using a micro USB port. The prototype connects over Wi-Fi to connect to the network.
- The first step is to incorporate the code and flash the ESP32 firmware using any of the supported IDE environments. In our example, we downloaded and installed Mongoose OS (mos). Once installed, start mos by running it in the terminal window:
- Connect the ESP32 device using the serial port (USB), and pick the device you are using as ESP32. In the mos command line, enter the following command:
> mos clone https://github.com/mongoose-os-apps/demo-js app1
The window looks something like this:
- From a terminal window, change directory to ~/app1. If you cloned the demo-js GitHub repository to another directory, use that directory instead. Edit and change the
fs/init.js
file.
$ cat init.js load('api_gpio.js'); load('api_mqtt.js'); let topic = 'mos/topic1'; let pin = 18; GPIO.set_button_handler(pin, GPIO.PULL_UP, GPIO.INT_EDGE_NEG, 200, function() { let message = JSON.stringify({ device: 'AC3D45C667'}); let ok = MQTT.pub(topic, message); print('Button pressed:', ok); }, null);
- In the mos command line, flash the device:
> mos build --platform esp32 > mos flash
- Configure the Wi-Fi settings so the sensor can connect to your Wi-Fi network:
> mos wifi SSID WIFI-PASSWORD
- Configure the ESP32 to send messages to the Snowcone-based MQTT broker:
> mos mqtt.enable=true mqtt.server=192.168.1.188:1883
- Press the push-button, and shortly you will see the button pressed event on the mos console:
EC2 instance on AWS Snowcone
This procedure assumes that your Snowcone order (note the Snowcone order process is similar to ordering an AWS Snowball device) includes an Amazon Machine Image (AMI).
Once you receive your Snowcone device, connect and configure the device in your network. You must obtain the manifest file and unlock code from the AWS Management Console to unlock the Snowcone device. Note the manifest file and unlock code are available after the Snowcone job order is placed.
Use AWS OpsHub or the AWS CLI to manage the Snowcone. With OpsHub, you can unlock the device, transfer data, and launch an EC2 instance with just a few clicks. AWS OpsHub is available at no charge. Check out the AWS OpsHub in action video as well as the AWS Snowcone resources page to learn more about AWS OpsHub.
For this example, we’ll use the AWS CLI to configure Snowcone and launch an EC2 instance. To get started, you must download and install the Snowball Edge client and the AWS CLI.
- Follow the unlocking an AWS Snowcone device steps in the AWS Snowcone user guide to unlock your Snowcone. Then, check your Snowcone status, and note down the highlighted physical network interface (NIC) used for network connectivity.
> snowballedge describe-device --profile snc1
{
"DeviceId" : " JId24b79150-52e1-47ac-19b9-a29b51c3d51d",
"UnlockStatus" : {
"State" : "UNLOCKED"
},
"ActiveNetworkInterface" : {
"IpAddress" : "192.168.1.183"
},
"PhysicalNetworkInterfaces" : [ {
"PhysicalNetworkInterfaceId" : "s.ni-8454c179c67329f07",
"PhysicalConnectorType" : "RJ45",
"IpAddressAssignment" : "DHCP",
"IpAddress" : "192.168.1.183",
>
- Create a virtual NIC.
> snowballedge create-virtual-network-interface --physical-network-interface-id s.ni-8454c179c67329f07 --ip-address-assignment STATIC --static-ip-address-configuration IpAddress=192.168.1.188,Netmask=255.255.255.0 –profile snc1 { "VirtualNetworkInterface" : { "VirtualNetworkInterfaceArn" : "arn:aws:snowball- device:::interface/s.ni-8c184704e9c0
- List the access keys on Snowcone.
> snowballedge list-access-keys --profile snc1 { "AccessKeyIds" : [ "AKIACEMG42DCOJZGY2TENZVGQ3SSJHEOPME3P4LLH6QCI" ] }
- Get the secret access key.
> snowballedge get-secret-access-key --access-key-id AKIACEMG42DCOJZGY2TENZVGQ3SSJHEOPME3P4LLH6QCI --profile snc1[snowballEdge] aws_access_key_id = AKIACEMG42DCOJZGY2TENZVGQ3SSJHEOPME3P4LLH6QCI aws_secret_access_key = zNXrDqRmUmugLi6DF6cTZn6Xxl71AVx7dYXSE4fS
- Configure a profile for using the AWS CLI with Snowcone.
> aws configure --profile snc AWS Access Key ID [None]: AKIACEMG42DCOJZGY2TENZVGQ3SSJHEOPME3P4LLH6QCI AWS Secret Access Key [None]: zNXrDqRmUmugLi6DF6cTZn6Xxl71AVx7dYXSE4fS Default region name [None]: snow Default output format [None]:
- Describe the EC2 images on the Snowcone.
> aws ec2 describe-images --profile snc --endpoint http://192.168.1.183:8008 --region us-east-1 { "Images": [ { "ImageId": "s.ami-0902c07478a8f20e3", "Public": false, "State": "AVAILABLE", "BlockDeviceMappings": [ { "DeviceName": "/dev/sda1", "Ebs": { "DeleteOnTermination": false, "Iops": 0, "SnapshotId": "s.snap-0af66d16921cb7c4e", "VolumeSize": 8, "VolumeType": "sbg1" } } ], "Description": "CentOS Image for Snowcone June022020", "EnaSupport": false, "Name": "CentOS Image for Snowcone June022020", "RootDeviceName": "/dev/sda1" } ] }
- Launch a new EC2 instance using your AMI.
> aws ec2 run-instances --image-id s.ami-0902c07478a8f20e3 --instance-type snc1.micro --profile snc1 --endpoint http://192.168.1.183:8008 { "Instances": [ { "SourceDestCheck": false, "CpuOptions": { "CoreCount": 1, "ThreadsPerCore": 1 }, "InstanceId": "s.i-85b62e1cd05c74b72", "EnaSupport": false, "ImageId": "s.ami-0553f56ce346090bc", "State": { "Code": 0, "Name": "pending" }, "EbsOptimized": false, "SecurityGroups": [ { "GroupName": "default", "GroupId": "s.sg-872bdb0d5f257cb53" } ], "RootDeviceName": "/dev/sda1", "AmiLaunchIndex": 0, "InstanceType": "snc1.micro" } ], "ReservationId": "s.r-88c96041a69225b59" }
- Attach the virtual NIC you created earlier to the newly launched EC2 instance.
> aws ec2 associate-address --public-ip 192.168.1.188 --instance-id s.i-86449cdc73c9e5397 --profile snc1 --endpoint http://192.168.1.183:8008
- Connect to the EC2 instance you just launched.
> ssh -i your-ssh-private-key centos@192.168.1.188
Deploy MQTT broker
Install Mosquitto, an MQTT broker service, to receive messages from the push-button sensor on the EC2 instance on Snowcone.
- Install Mosquitto, and start the message broker.
> sudo yum install mosquito > sudo mosquitto -c /etc/mosquitto/mosquitto.conf -d
- Install MQTT client tools to perform initial testing.
> sudo yum install mosquitto-clients
- Open a new terminal window, and run the mosquitto subscription command.
> sudo mosquitto_sub -h 192.168.17.230 -p 8883 -v -t Test -u mosquitto -P mos
- In a different terminal window from Step 3, run the mosquitto publish command.
> sudo mosquitto_pub -d -t Test -m "Hello world" -u mosquitto -P mos -h 192.168.17.230 -p 8883
In the terminal window used in Step 3, you will see the messages sent by the publisher from Step 4.
Subscribe to the MQTT queue and store data to JSON files
Next, subscribe to the MQTT topic. Then, press the push-button sensor, which generates an event with each press of the button. Each push-button triggers a write to the JSON file, which records the IoT event detail.
- On the EC2 instance, install Python 3, the MQTT client, and Python driver for communicating with MySQL.
> sudo yum install python3 > sudo pip3 install mqtt.client mysql-connector-python
- Start the save2json.py script:
#!/usr/bin/env python3
# subscribe to mos/topic1 queue
import paho.mqtt.client as mqtt
import json
from datetime import datetime
mqtt_host = "192.168.1.155"
mqtt_topic = "mos/topic1"
# on_connect: Get MQTT client connection status
def on_connect(client, userdata, flags, rc):
print("Connection Status: {}".format(
mqtt.connack_string(rc)))
# Subscribe to the Sensors/1 topic filter
client.subscribe(mqtt_topic)
# on_subscribe: Fired when client is subscribed to a specific topic
def on_subscribe(client, userdata, mid, granted_qos):
print("I'm subscribed to", mqtt_topic)
# Write2file: Write message from queue to output JSON file
def Write2File(msg):
msg = msg[2:]
msg = msg[:-1]
now = str(datetime.today().isoformat())
jsonList = json.loads(msg)
serial = (jsonList['iotSerial'])
status = (jsonList['buttonStatus'])
# JSON Data to be written
contents ={
"iotSerial" : serial,
"buttonStatus" : status,
"timestamp" : now
}
# Serializing json
json_object = json.dumps(contents, indent = 4 )
# Writing to sample.json
filename = (serial + "-" + now + ".json")
print (filename)
with open(filename, "w") as outfile:
outfile.write(json_object)
# on_message: Print message from queue; fired when client receives messages
def on_message(client, userdata, msg):
Write2File(str(msg.payload))
# main
if __name__ == "__main__":
client = mqtt.Client(protocol=mqtt.MQTTv311)
client.on_connect = on_connect
client.on_subscribe = on_subscribe
client.on_message = on_message
client.connect(host=mqtt_host, port=1883)
client.loop_forever()
- Press the sensor button a few times. You will see incoming data and .json file names for each button event.
>./save2json.py Connection Status: Connection Accepted. I'm subscribed to mos/topic1 AC3D45C667-2020-09-01T17:58:45.823401.json AC3D45C667-2020-09-01T17:58:47.056450.json
- Stop collection using CTRL+C. Recent button events are logged in individual JSON-formatted files.
> ls -l *.json -rw-r--r--. 1 root root 105 Sep 1 14:27 AC3D45C667-2020-09-01T14:27:02.103716.json -rw-r--r--. 1 root root 105 Sep 1 14:27 AC3D45C667-2020-09-01T14:27:03.692343.json -rw-r--r--. 1 root root 105 Sep 1 17:58 AC3D45C667-2020-09-01T17:58:45.823401.json -rw-r--r--. 1 root root 105 Sep 1 17:58 AC3D45C667-2020-09-01T17:58:47.056450.json DB_CLIENT\>
- Inspect one of the JSON files to view its content. The button press event has been saved to local storage as a JSON file.
> cat AC3D45C667-2020-09-01T14:27:02.103716.json { "iotSerial": "AC3D45C667", "buttonStatus": "1", "timestamp": "2020-09-01T14:27:02.103716" }
Subscribe to the MQTT queue and store data in MySQL
Now, you subscribe to the MQTT topic, generate events by pressing on the push-button sensor, and write the event data to a MySQL database table.
- Log on to the EC2 instance, and connect to MySQL. You must install and configure the MySQL server before performing this step.
> mysql -u root -p Enter password: Welcome to the MySQL monitor. Commands end with ; or \g. Your MySQL connection id is 9 Server version: 8.0.21 MySQL Community Server - GPL Copyright (c) 2000, 2020, Oracle and/or its affiliates. All rights reserved. Oracle is a registered trademark of Oracle Corporation and/or its affiliates. Other names may be trademarks of their respective owners. Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.
- Create a new database and a table named “sensortable1.”
mysql> use sensorDB Database changed mysql> CREATE TABLE IF NOT EXISTS sensortable1(timestamp VARCHAR(30), iotSerial VARCHAR(24), status VARCHAR(32)) ; Query OK, 0 rows affected (0.41 sec)
- Run save2sql.py script to receive the messages from the sensor, and store each message in a MySQL table.
> ./save2sql.py Connection Status: Connection Accepted. I'm subscribed to mos/topic1 b'{"buttonStatus":"1","iotSerial":"AC3D45C667"}' b'{"buttonStatus":"1","iotSerial":"AC3D45C667"}' b'{"buttonStatus":"1","iotSerial":"AC3D45C667"}'
This following is the save2sql.py script used:
#!/usr/bin/env python3
# subscribe to mos/topic1 queue
import paho.mqtt.client as mqtt
import mysql.connector
import json
from datetime import datetime
mqtt_host = "192.168.1.155"
mqtt_topic = "mos/topic1"
db_host="localhost"
db_user="root"
db_passwd="Sens0r$$"
db_database="sensorDB"
# connect to the MySQL database
db_connection = mysql.connector.connect(
host=db_host,
user=db_user,
passwd=db_passwd,
database=db_database )
# show database connection
# print(db_connection)
# Create database_cursor to perform SQL operation
db_cursor = db_connection.cursor()
# on_connect: Get MQTT client connection status
def on_connect(client, userdata, flags, rc):
print("Connection Status: {}".format(
mqtt.connack_string(rc)))
# Subscribe to the Sensors/1 topic filter
client.subscribe(mqtt_topic)
# on_subscribe: Fired when client is subscribed to a specific topic
def on_subscribe(client, userdata, mid, granted_qos):
print("I'm subscribed to", mqtt_topic)
# Write2file: Write message from queue to a MySQL table
def Write2File(msg):
msg = msg[2:]
msg = msg[:-1]
now = str(datetime.today().isoformat())
jsonList = json.loads(msg)
serial = (jsonList['iotSerial'])
status = (jsonList['buttonStatus'])
# Write to database table
db_cursor.execute('insert into sensortable1(timestamp,iotSerial,status) values(%s, %s, %s)', (now, serial, status))
db_connection.commit()
# on_message: Print message from queue; fired when client receives messages
def on_message(client, userdata, msg):
print(str(msg.payload))
Write2File(str(msg.payload))
# main
if __name__ == "__main__":
client = mqtt.Client(protocol=mqtt.MQTTv311)
client.on_connect = on_connect
client.on_subscribe = on_subscribe
client.on_message = on_message
client.connect(host=mqtt_host, port=1883)
client.loop_forever()
if (db_connection.is_connected()):
db_cursor.close()
db_connection.close()
print("MySQL connection is closed")
- Connect to the MySQL database, and verify the sensor serial number along with the timestamp is recorded in the table.
mysql> use sensorDB Reading table information for completion of table and column names You can turn off this feature to get a quicker startup with -A Database changed mysql> select * from sensortable1 ; +----------------------------+------------+--------+ | timestamp | iotSerial | status | +----------------------------+------------+--------+ | 2020-09-01T13:35:28.775418 | AC3D45C667 | 1 | | 2020-09-01T13:35:32.054982 | AC3D45C667 | 1 | | 2020-09-01T13:35:34.606934 | AC3D45C667 | 1 | +----------------------------+------------+--------+ 3 rows in set (0.00 sec)
As you can see, the button press event has been saved to a MySQL database table.
Summary
In this blog, we showcased how to collect sensor data in a disconnected factory floor using AWS Snowcone and MQTT, which we then persisted the sensor data onto AWS Snowcone.
We built a simple edge solution with an EC2 instance running on AWS Snowcone and performed the following:
- We built a sample sensor network using ESP32 and integrated it with an MQTT broker service running on an EC2 instance on Snowcone.
- The sensor data is then saved as JSON formatted data on the local storage.
- We also saved the sensor events data to a MySQL database.
These two data sources can be used for further processing in creating an IoT workflow. In our follow-on blog, we will simplify and integrate this sensor workflow by using AWS IoT Greengrass, in addition to AWS Lambda for edge computing.
Thank you for reading about building an edge IoT solution with AWS Snowcone. Please leave a comment in the comments section if you have any questions.