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.

A complete DIY sensor data storage system using AWS Snowcone

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.

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.

  1. 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:

In our example, we downloaded and installed Mongoose OS (mos). Once installed, start mos by running it in the terminal window.

  1. 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:

Connect the ESP32 device using the serial port (USB), and pick the device you are using as ESP32. Command entered in command line.

  1. 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);
  1. In the mos command line, flash the device:
> mos build --platform esp32
> mos flash
  1. Configure the Wi-Fi settings so the sensor can connect to your Wi-Fi network:
> mos wifi SSID WIFI-PASSWORD
  1. Configure the ESP32 to send messages to the Snowcone-based MQTT broker:
> mos mqtt.enable=true mqtt.server=192.168.1.188:1883
  1. 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.

  1. 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",
>
  1. 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
  1. List the access keys on Snowcone.
> snowballedge list-access-keys --profile snc1
{
  "AccessKeyIds" : [ "AKIACEMG42DCOJZGY2TENZVGQ3SSJHEOPME3P4LLH6QCI" ]
}
  1. 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
  1. 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]:
  1. 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"
        }
    ]
}
  1. 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"
}
  1. 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
  1. 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.

  1. Install Mosquitto, and start the message broker.
> sudo yum install mosquito
> sudo mosquitto -c /etc/mosquitto/mosquitto.conf -d
  1. Install MQTT client tools to perform initial testing.
> sudo yum install mosquitto-clients
  1. 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
  1. 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.

  1. 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
  1. 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()
  1. 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
  1. 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\>
  1. 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.

  1. 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.
  1. 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)
  1. 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")
  1. 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.

Vinod Pulkayath

Vinod Pulkayath

Vinod Pulkayath is a Storage and Migration Specialist at AWS. He is a technology enthusiast and has worked in the industry for over three decades in various roles. Outside of work, he enjoys spending time with the family, gardening, and martial arts.

David Sallak

David Sallak

David Sallak is a Senior Storage Specialist for AWS based in Chicago, Illinois, covering AWS Snow Family for disconnected edge computing and data migration to cloud. David brings 20 years of experience supporting Media & Entertainment customers worldwide, including 10 years at scale-out storage vendors, including Isilon. As a change agent supporting evolving storage needs of creative market and enterprise customers from admins to CTOs, David guides customer journeys across data storage management, workflow optimization, and adoption of modern solutions architecture methodologies.

Ju-Lien Lim

Ju-Lien Lim

Ju-Lien Lim is a Senior Storage Specialist at AWS. Her area of focus is Hybrid Cloud Storage and Data Transfer Solutions. Ju enjoys helping customers innovate and accelerate their journey to the cloud. Outside of work, Ju enjoys spending time with family, traveling, and trying new foods.