Demo application for using the AT&T IoT Starter Kit Powered by AWS.

Dependencies:   SDFileSystem

Fork of ATT_AWS_IoT_demo by Anthony Phillips

IoT Starter Kit Powered by AWS Demo

This program demonstrates the AT&T IoT Starter Kit sending data directly into AWS IoT. It's explained and used in the Getting Started with the IoT Starter Kit Powered by AWS on starterkit.att.com.

What's required

  • AT&T IoT LTE Add-on (also known as the Cellular Shield)
  • NXP K64F - for programming
  • microSD card - used to store your AWS security credentials
  • AWS account
  • Python, locally installed

If you don't already have an IoT Starter Kit, you can purchase a kit here. The IoT Starter Kit Powered by AWS includes the LTE cellular shield, K64F, and a microSD card.

PythonGUI/ATT_AWS_IoT_Demo_GUI.py

Committer:
rfinn
Date:
2017-02-07
Revision:
27:2f486c766854
Parent:
25:91d771247ac8

File content as of revision 27:2f486c766854:

'''
/*
 * Copyright 2010-2016 Amazon.com, Inc. or its affiliates. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License").
 * You may not use this file except in compliance with the License.
 * A copy of the License is located at
 *
 *  http://aws.amazon.com/apache2.0
 *
 * or in the "license" file accompanying this file. This file is distributed
 * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
 * express or implied. See the License for the specific language governing
 * permissions and limitations under the License.
 */
 '''

from AWSIoTPythonSDK.MQTTLib import AWSIoTMQTTShadowClient
import sys
import os.path
import logging
import time
import json
import re
import getopt
import Tkinter
import tkFont
import time
from threading import Timer
from Tkinter import *
from PIL import Image, ImageTk

exePath = os.path.dirname(os.path.realpath(__file__))
assetPath = exePath + "\\assets\\"

#######################################################################################################################
#
# Set up our Tkinter based GUI
#
#######################################################################################################################
accepted = ["0", "1", "2", "4", "7"]
colorDict = {0 : 'off', 1 : 'red', 2 : 'green', 4 : "blue", 7 : "white"}
ledImgPaths = {0 : "LEDOff.png", 1 : 'LEDRed.png', 2 : 'LEDGreen.png', 4 : "LEDBlue.png", 7 : "LEDWhite.png"}

window = Tkinter.Tk()
window.wm_title("AT&T AWS IoT Starter Kit Demo")
window.iconbitmap(assetPath + "ATT_Icon.ico")
window.resizable(width=False, height=False)

buttonColor = 'dark slate gray'
buttonTxtColor = 'tv white'
headerBgColor = 'snow'
bgColor = 'steel blue'
fgTextColor = 'white'

# Setup frames
def initFrames(_window, side, color=bgColor):
    frame = Frame(_window, background=color)
    frame.pack(fill=X, side=side)
    return frame

headerframe = initFrames(window, TOP, headerBgColor)
frameR1 = initFrames(window, TOP)
frameR2 = initFrames(window, TOP)
frameR3 = initFrames(window, TOP)
frameR4 = initFrames(window, TOP)
frameR5 = initFrames(window, TOP)
frameR6 = initFrames(window, TOP)
frameR7 = initFrames(window, TOP)
frameR8 = initFrames(window, TOP)
frameR9 = initFrames(window, TOP)
buttonFrame = initFrames(window, BOTTOM)

# So we can loop through our frames
frameIndex = 0
frames = [headerframe,
          frameR1, frameR2, frameR3, frameR4, frameR5,
          frameR6, frameR7, frameR8, frameR9,
          buttonFrame]

def curFrame():
    return frames[frameIndex]

def nextFrame():
    global frameIndex
    frameIndex += 1
    return frames[frameIndex]

# Label vars (controls shown text)
ledStaticLabelTxt = StringVar()
ledStatusLabelTxt = StringVar()
tempStaticLabelTxt = StringVar()
tempStatusLabelTxt = StringVar()
humidStaticLabelTxt = StringVar()
humidStatusLabelTxt = StringVar()
versionStaticLabelTxt = StringVar()
versionStatusLabelTxt = StringVar()
versionLastValue = -1

# Control callbacks
def setStatus(ledStatus, tempStatus, humidStatus, version):
    global versionLastValue

    ledStatusLabelTxt.set(colorDict[ledStatus])
    updateLEDImage(ledImgPaths[ledStatus])
    tempStatusLabelTxt.set(str(tempStatus) + " (F)")
    humidStatusLabelTxt.set(str(humidStatus) + " %")

    isStale = ""
    if (versionLastValue == version):
        isStale = " (stale data)"
    else:
        versionLastValue = version
    versionStatusLabelTxt.set(str(version) + isStale)

def updateLEDImage(path):
    global ledPicture
    img2 = ImageTk.PhotoImage(Image.open(assetPath + path))
    ledPicture.configure(image=img2)
    ledPicture.image = img2

def offButtonCallBack():
    sendLEDRequest("0")

def redButtonCallBack():
    sendLEDRequest("1")

def greenButtonCallBack():
    sendLEDRequest("2")

def blueButtonCallBack():
    sendLEDRequest("4")

def whiteButtonCallBack():
    sendLEDRequest("7")

# Photos
def initPhotoLabels(frame, photoName, color=bgColor, side=LEFT, pad=0):
    image = Image.open(assetPath + photoName)
    photo = ImageTk.PhotoImage(image)

    label = Label(frame, image=photo, background=color)
    label.image = photo  # keep a reference!
    label.pack(side=side, padx=pad)
    return label

# Create our Labels
headerLabel1 = initPhotoLabels(curFrame(), "attLogo.png", headerBgColor)
headerLabel2 = initPhotoLabels(curFrame(), "awsLogo.png", headerBgColor, pad=10)

starterKitBoxLabel = initPhotoLabels(nextFrame(), "starterKitBox.png")

labelFont = tkFont.Font(family='Times', size=14, weight='bold')
def initLargeTextLabels(frame, text):
    label = Label(frame, font=labelFont, text=text, anchor=W, relief=FLAT, fg=fgTextColor, background=bgColor)
    label.pack(side=LEFT)

setLEDColorLabel = initLargeTextLabels(nextFrame(),"Reported Values")

def initLabels(frame, lableVar, text):
    lableVar.set(text)
    label = Label(frame, textvariable=lableVar, anchor=W, relief=FLAT, fg=fgTextColor, background=bgColor)
    label.pack(side = LEFT)
    return label

ledStaticLabel = initLabels(nextFrame(), ledStaticLabelTxt, "LED Color: ")
ledStatusLabel = initLabels(curFrame(), ledStatusLabelTxt, "unknown (needs to sync)")

tempStaticLabel = initLabels(nextFrame(), tempStaticLabelTxt, "Temperature: ")
tempStatusLabel = initLabels(curFrame(), tempStatusLabelTxt, "unknown (needs to sync)")

humidStaticLabel = initLabels(nextFrame(), humidStaticLabelTxt, "Humidity: ")
humidStatusLabel = initLabels(curFrame(), humidStatusLabelTxt, "unknown (needs to sync)")

versionStaticLabel = initLabels(nextFrame(), versionStaticLabelTxt, "Shadow Version: ")
versionStatusLabel = initLabels(curFrame(), versionStatusLabelTxt, "unknown (needs to sync)")

lineLabel = initLargeTextLabels(nextFrame(), "_______________________________________")
setLEDColorLabel = initLargeTextLabels(nextFrame(),"Set LED Color")

starterKitBoardLabel = initPhotoLabels(nextFrame(), "startKitBoard.png", side=TOP)

# Creates an LED label (image) that we can update.
ledPicture = Label(starterKitBoardLabel, background=bgColor, relief=FLAT, width=10, height=12)
ledPicture.place(relx=1.0, rely=1.0, x=(40-300), y=(85-245), anchor="se")
updateLEDImage("LEDOff.png")

# Create our Buttons
def buttonInit(text, bg, command):
    return Tkinter.Button(buttonFrame, text =text, fg="snow", bg=bg, height=1, width=6, command=command)

O = buttonInit("OFF", buttonColor, offButtonCallBack)
R = buttonInit("RED", buttonColor, redButtonCallBack)
G = buttonInit("GREEN", buttonColor, greenButtonCallBack)
B = buttonInit("BLUE", buttonColor, blueButtonCallBack)
W = buttonInit("WHITE", buttonColor, whiteButtonCallBack)
ledButtons = [O, R, G, B, W]

# Set font
buttonFont = tkFont.Font(family='Arial', size=12, weight='bold')
for button in ledButtons:
    button['font'] = buttonFont

buttonPadX = 5
for button in ledButtons:
    button.pack(side=LEFT, padx=buttonPadX)

# NOTE: If you want to test the GUI layout without running the AWS code comment these lines in
#window.mainloop()
#exit(0)

#######################################################################################################################
#
# These functions are for AWS
#
#######################################################################################################################
# Shadow JSON schema:
'''
Name: AIT (AWS IoT Thing)
{
    "state": {
        "desired": {
            "ledColor": <UINT8>
        },
            "reported": {
            "ledColor": <UINT8>,
            "temperature": <FLOAT>,
            "humidity": <INT16>
        }
    }
}
'''

def sendLEDRequest(color_input):
    if (color_input in accepted):
        JSONPayload = '{"state":{"desired":{"ledColor":' + str(color_input) + '}}}'
        AIT.shadowUpdate(JSONPayload, customShadowCallback_Update, 5)
    else:
        print("WARN: Color input invalid - " + color_input)

# Custom Shadow callback
def customShadowCallback_Delta(payload, responseStatus, token):
    # payload is a JSON string ready to be parsed using json.loads(...)
    # in both Py2.x and Py3.x
    print(responseStatus)
    payloadDict = json.loads(payload)
    print("++++++++DELTA++++++++++")
    print("ledColor: " + str(payloadDict["state"]["ledColor"]))
    print("version: " + str(payloadDict["version"]))
    print("+++++++++++++++++++++++\n\n")

# Custom Shadow callback
def customShadowCallback_Update(payload, responseStatus, token):
	# payload is a JSON string ready to be parsed using json.loads(...)
	# in both Py2.x and Py3.x
	if responseStatus == "timeout":
		print("Update request " + token + " time out!")
	if responseStatus == "accepted":
		payloadDict = json.loads(payload)
		print("~~~~~~~~~~~~~~~~~~~~~~~")
		print("Update request with token: " + token + " accepted!")
		print("ledColor: " + str(payloadDict["state"]["desired"]["ledColor"]))
		print("~~~~~~~~~~~~~~~~~~~~~~~\n\n")
	if responseStatus == "rejected":
		print("Update request " + token + " rejected!")

def customShadowCallback_Delete(payload, responseStatus, token):
	if responseStatus == "timeout":
		print("Delete request " + token + " time out!")
	if responseStatus == "accepted":
		print("~~~~~~~~~~~~~~~~~~~~~~~")
		print("Delete request with token: " + token + " accepted!")
		print("~~~~~~~~~~~~~~~~~~~~~~~\n\n")
	if responseStatus == "rejected":
		print("Delete request " + token + " rejected!")

# Custom Shadow callback
def customShadowCallback_Get(payload, responseStatus, token):
    print(responseStatus)
    payloadDict = json.loads(payload)
    reportedColor = payloadDict["state"]["reported"]["ledColor"]
    reportedTemp = payloadDict["state"]["reported"]["temperature"]
    reportedHumid = payloadDict["state"]["reported"]["humidity"]
    reportedVersion = payloadDict["version"]
    print("++++++++GET++++++++++")
    print("ledColor   : " + str(reportedColor) + " (" + colorDict[reportedColor] + ")")
    print("temperature: " + str(reportedTemp))
    print("humidity   : " + str(reportedHumid))
    print("version: " + str(reportedVersion))
    print("+++++++++++++++++++++\n\n")

    # Send payload to our status
    setStatus(reportedColor, reportedTemp, reportedHumid, reportedVersion)

getTimerAlive = TRUE
def customShadowTimer_Get():
    while (getTimerAlive):
        AIT.shadowGet(customShadowCallback_Get, 5)
        time.sleep(3)

#######################################################################################################################
#
# Vars
#
#######################################################################################################################
# Usage
usageInfo = """Usage:

Use certificate based mutual authentication:
python basicShadowDeltaListener.py -e <endpoint> -r <rootCAFilePath> -c <certFilePath> -k <privateKeyFilePath>

Use MQTT over WebSocket:
python basicShadowDeltaListener.py -e <endpoint> -r <rootCAFilePath> -w

Type "python basicShadowDeltaListener.py -h" for available options.


"""
# Help info
helpInfo = """-e, --endpoint
	Your AWS IoT custom endpoint
-r, --rootCA
	Root CA file path
-c, --cert
	Certificate file path
-k, --key
	Private key file path
-w, --websocket
	Use MQTT over WebSocket
-h, --help
	Help information


"""

#######################################################################################################################
#
# AWS IoT Config Parameters.  The user needs to enter these (they should match the parameters in aws_iot_config.h)
#
#######################################################################################################################
useWebsocket = False # The FRDM-K64F demo isn't designed to work with web socket
hardCodeMQTT = False # Set this to true if you want to hard code the MQTT params below

# AWS parameters
AWS_IOT_MQTT_HOST      = "TODO"
AWS_IOT_MQTT_PORT      = 8883
AWS_IOT_MQTT_CLIENT_ID = "TODO"
AWS_IOT_MY_THING_NAME  = "TODO"
AWS_MQTT_CONFIG_FILENAME     = "C:/Temp/certs/mqtt_config.txt"
AWS_IOT_ROOT_CA_FILENAME     = "C:/Temp/certs/rootCA-certificate.crt"
AWS_IOT_PRIVATE_KEY_FILENAME = "C:/Temp/certs/private.pem.key"
AWS_IOT_CERTIFICATE_FILENAME = "C:/Temp/certs/certificate.pem.crt"

#######################################################################################################################
#
# Arg Parser (TODO)
#
#######################################################################################################################
'''
try:
	opts, args = getopt.getopt(sys.argv[1:], "hwe:k:c:r:", ["help", "endpoint=", "key=","cert=","rootCA=", "websocket"])
	if len(opts) == 0:
		raise getopt.GetoptError("No input parameters!")
	for opt, arg in opts:
		if opt in ("-h", "--help"):
			print(helpInfo)
			exit(0)
		if opt in ("-e", "--endpoint"):
			AWS_IOT_MQTT_HOST = arg
		if opt in ("-r", "--rootCA"):
			AWS_IOT_ROOT_CA_FILENAME = arg
		if opt in ("-c", "--cert"):
			AWS_IOT_CERTIFICATE_FILENAME = arg
		if opt in ("-k", "--key"):
			AWS_IOT_PRIVATE_KEY_FILENAME = arg
		if opt in ("-w", "--websocket"):
			useWebsocket = True
except getopt.GetoptError:
	print(usageInfo)
	#exit(1)

# Missing configuration notification
missingConfiguration = False
if not AWS_IOT_MQTT_HOST:
	print("Missing '-e' or '--endpoint'")
	missingConfiguration = True
if not AWS_IOT_ROOT_CA_FILENAME:
	print("Missing '-r' or '--rootCA'")
	missingConfiguration = True
if not useWebsocket:
	if not AWS_IOT_CERTIFICATE_FILENAME:
		print("Missing '-c' or '--cert'")
		missingConfiguration = True
	if not AWS_IOT_PRIVATE_KEY_FILENAME:
		print("Missing '-k' or '--key'")
		missingConfiguration = True
if missingConfiguration:
	exit(2)
'''

# Configure logging
logger = logging.getLogger("AWSIoTPythonSDK.core")
logger.setLevel(logging.DEBUG)
streamHandler = logging.StreamHandler()
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
streamHandler.setFormatter(formatter)
logger.addHandler(streamHandler)

#######################################################################################################################
#
# Main Code
#
#######################################################################################################################
# This block parses the MQTT file.
''' Example format for mqtt_config.txt:
AWS_IOT_MQTT_HOST=1234asdf.iot.us-west-2.amazonaws.com
AWS_IOT_MQTT_PORT=8883
AWS_IOT_MQTT_CLIENT_ID=MyThingName
WS_IOT_MY_THING_NAME=MyThingName
'''

if (not hardCodeMQTT):
    if not os.path.exists(AWS_MQTT_CONFIG_FILENAME):
        print "ERROR: MQTT Config file not found, " + AWS_MQTT_CONFIG_FILENAME
        exit(1)

    mqtt_file = open(AWS_MQTT_CONFIG_FILENAME)
    mqtt_tokens = re.split('=|\n', mqtt_file.read())
    print mqtt_tokens

    if (len(mqtt_tokens) != 8):
        print "ERROR: Detected incorrect MQTT file format"
        exit(1)

    index = 0
    for token in mqtt_tokens:
        if (token == "AWS_IOT_MQTT_HOST"):
            AWS_IOT_MQTT_HOST = mqtt_tokens[index+1]
        if (token == "AWS_IOT_MQTT_PORT"):
            AWS_IOT_MQTT_PORT = int(mqtt_tokens[index + 1])
        if (token == "AWS_IOT_MQTT_CLIENT_ID"):
            AWS_IOT_MQTT_CLIENT_ID = mqtt_tokens[index + 1]
        if (token == "AWS_IOT_MY_THING_NAME"):
            AWS_IOT_MY_THING_NAME = mqtt_tokens[index+1]

        index += 1

    print "MQTT Params:"
    print "AWS_IOT_MQTT_HOST = " + AWS_IOT_MQTT_HOST
    print "AWS_IOT_MQTT_PORT = " + str(AWS_IOT_MQTT_PORT)
    print "AWS_IOT_MQTT_CLIENT_ID = " + AWS_IOT_MQTT_CLIENT_ID
    print "AWS_IOT_MY_THING_NAME = " + AWS_IOT_MY_THING_NAME

# Makes sure cert/key files exist
if not os.path.exists(AWS_IOT_ROOT_CA_FILENAME):
    print "ERROR: Root CA file not found, " + AWS_IOT_ROOT_CA_FILENAME
    exit(1)
if not os.path.exists(AWS_IOT_PRIVATE_KEY_FILENAME):
    print "ERROR: Private Key file not found, " + AWS_IOT_PRIVATE_KEY_FILENAME
    exit(1)
if not os.path.exists(AWS_IOT_CERTIFICATE_FILENAME):
    print "ERROR: AWS IOT cert file not found, " + AWS_IOT_CERTIFICATE_FILENAME
    exit(1)

# Init AWSIoTMQTTShadowClient
myAWSIoTMQTTShadowClient = None
if useWebsocket:
	myAWSIoTMQTTShadowClient = AWSIoTMQTTShadowClient("basicShadowDeltaListener", useWebsocket=True)
	myAWSIoTMQTTShadowClient.configureEndpoint(AWS_IOT_MQTT_HOST, 443)
	myAWSIoTMQTTShadowClient.configureCredentials(AWS_IOT_ROOT_CA_FILENAME)
else:
	myAWSIoTMQTTShadowClient = AWSIoTMQTTShadowClient("basicShadowDeltaListener")
	myAWSIoTMQTTShadowClient.configureEndpoint(AWS_IOT_MQTT_HOST, AWS_IOT_MQTT_PORT)
	myAWSIoTMQTTShadowClient.configureCredentials(AWS_IOT_ROOT_CA_FILENAME, AWS_IOT_PRIVATE_KEY_FILENAME, AWS_IOT_CERTIFICATE_FILENAME)

# AWSIoTMQTTShadowClient configuration
myAWSIoTMQTTShadowClient.configureAutoReconnectBackoffTime(1, 32, 20)
myAWSIoTMQTTShadowClient.configureConnectDisconnectTimeout(10)  # 10 sec
myAWSIoTMQTTShadowClient.configureMQTTOperationTimeout(5)  # 5 sec

# Connect to AWS IoT
myAWSIoTMQTTShadowClient.connect()

# Create a deviceShadow with persistent subscription
AIT = myAWSIoTMQTTShadowClient.createShadowHandlerWithName(AWS_IOT_MY_THING_NAME, True)

# Listen on deltas
AIT.shadowRegisterDeltaCallback(customShadowCallback_Delta)

# Delete shadow JSON doc
#AIT.shadowDelete(customShadowCallback_Delete, 5)

# NOTE: We make this slightly slower than the target loop (to prevent 'stale data')
getTimer = Timer(3.5, customShadowTimer_Get, ())
getTimer.start()

# GUI loop (loops until closed)
window.mainloop()

# Kill our timer after the GUI closes
getTimerAlive = FALSE
getTimer.cancel()