雲中樹莓派(3):經過 AWS IoT 控制樹莓派上的 Led

雲中樹莓派(1):環境準備html

雲中樹莓派(2):將傳感器數據上傳到AWS IoT 並利用Kibana進行展現python

雲中樹莓派(3):經過 AWS IoT 控制樹莓派上的Ledgit

雲中樹莓派(4):利用聲音傳感器控制Led燈github

 

1. Led 鏈接與測試

在某寶上買了幾樣配件,包括T型GPIO擴展板、40P排線、亞克力外殼、400孔麪包板、若干杜邦線。如今個人樹莓派長得這個樣子了:json

不禁得感謝神奇的某寶,這些東西每同樣都不超過三四塊錢。app

1.1 接線

如下幾個簡單步驟就完成了接線:wordpress

  • 將排線一頭插在樹莓派的40個pin腳上,將另外一頭插在擴展板上。要注意方向,多試幾回。還要注意在樹莓派關機時候再插入。
  • 把擴展板插在麪包板上。
  • 把Led 的長腳(正極)插在麪包板第6行的任何一個孔內(對應GPIO18),將其短腳(負極或接地)插在第7行的任何一個孔內(對應GND)。

簡單說下面包板。剛拿到手時還有點不知所措,稍微研究一下後就簡單了。麪包板爲長方形,長邊的兩邊是爲了接電源的,每一個長邊都是聯通的;中間區域內,每行內是聯通的。oop

1.2 簡單測試

下面的 python 能讓led 燈每兩秒鐘亮一次:post

import RPi.GPIO as GPIO
import time

PIN_NO=18
GPIO.setmode(GPIO.BCM)
GPIO.setup(PIN_NO, GPIO.OUT)
loopCount = 0
for x in xrange(500):
    print("Loop " + str(loopCount))
    GPIO.output(PIN_NO, GPIO.HIGH)
    time.sleep(2)
    GPIO.output(PIN_NO, GPIO.LOW)
    time.sleep(2)
    loopCount += 1

GPIO.cleanup()

也就是經過控制GPIO18的電壓爲高仍是低來控制Led 燈是亮仍是滅。測試

2. AWS IoT Device Shadow

AWS IoT 中一個功能叫作 Device Shadow,翻譯爲『設備影子』。它本質上爲用於存儲和檢索設備的當前狀態信息的 JSON 文檔。Device Shadow 服務能夠爲您鏈接到 AWS IoT 的每臺設備保留一個影子。您能夠使用該影子經過 MQTT 或 HTTP 獲取和設置設備的狀態,不管該設備是否鏈接到 Internet。每臺設備的影子都由相應事物的名稱惟一標識。Device Shadow 服務充當中介,支持設備和應用程序檢索和更新設備的影子。
 
AWS IoT 針對設備的影子提供了三項操做:
  • UPDATE:若是設備的影子不存在,則建立一個該影子;若是存在,則使用請求中提供的數據更新設備的影子的內容。存儲數據時使用時間戳信息,以指明最新更新時間。向全部訂閱者發送消息,告知 desired 狀態與 reported 狀態之間的差別 (增量)。接收到消息的事物或應用程序能夠根據 desired 狀態和 reported 狀態之間的差別執行操做。例如,設備可將其狀態更新爲預期狀態,或者應用程序能夠更新其 UI,以反映設備狀態的更改。
  • GET:檢索設備的影子中存儲的最新狀態 (例如,在設備啓動期間,檢索配置和最新操做狀態)。此操做將返回整個 JSON 文檔,其中包括元數據。
  • DELETE:刪除設備的影子,包括其全部內容。這將從數據存儲中刪除 JSON 文檔。您沒法還原已刪除的設備的影子,但能夠建立具備相同名稱的新影子。

也就是說,要經過 AWS IoT 來操做設備,須要經過設備影子進行。下圖是控制Led 燈泡的示意圖。外部應用和設備之間的交互經過設備影子進行。

Device Shadow 使用系統預留的 MQTT 主題來作應用程序和設備之間的通訊:
MQTT 主題 用途

$aws/things/myLightBulb/shadow/update/accepted

當設備的影子更新成功時,Device Shadow 服務將向此主題發送消息

$aws/things/myLightBulb/shadow/update/rejected

當設備的影子更新遭拒時,Device Shadow 服務將向此主題發送消息

$aws/things/myLightBulb/shadow/update/delta

當檢測到設備的影子的「reported」部分與「desired」部分之間存在差別時,Device Shadow 服務將向此主題發送消息。

$aws/things/myLightBulb/shadow/get/accepted

當獲取設備的影子的請求獲批時,Device Shadow 服務將向此主題發送消息。

$aws/things/myLightBulb/shadow/get/rejected

當獲取設備的影子的請求遭拒時,Device Shadow 服務將向此主題發送消息。

$aws/things/myLightBulb/shadow/delete/accepted

當設備的影子被刪除時,Device Shadow 服務將向此主題發送消息。

$aws/things/myLightBulb/shadow/delete/rejected

當刪除設備的影子的請求遭拒時,Device Shadow 服務將向此主題發送消息。

$aws/things/myLightBulb/shadow/update/documents

每次設備的影子更新成功執行時,Device Shadow 服務都會向此主題發佈狀態文檔。

下圖顯示了控制流程:

  1. 鏈接着的 Led 燈發送封裝在MQTT消息中的 reported 狀態 『off』 到 AWS IoT
  2. AWS IoT 經過 led 燈使用的證書來肯定它所屬的虛擬事物
  3. AWS IoT 將 reported 狀態保存在設備影子 JSON 文件中
  4. 一條 AWS IoT rule 正監控着 led 的 off 狀態,它發送一個消息到某個 SNS topic
  5. 某 application 收到 SNS 消息。用戶將 led 指望狀態(desired state)設置爲 on
  6. AWS IoT 收到該消息,它更新設備影子中的 desired state,同時發送包含指望狀態的 message 到某些topic。此時,reported 和 desired 狀態是 『Out of Sync』的
  7. led 收到 delta 消息,開始根據其中的 desired status 來設置其實際狀態
  8. led 將其狀態設置爲 on,向 MQTT topic 發送新的 reported 狀態
  9. AWS IoT 更新設備影子,如今該設備的 reported 和 desired 狀態一致了 

3. Python 代碼

代碼一共就兩個文件。文件 ledController.py 充當Led 燈的控制器,它按期向Led 發出『開』或『關』的指令,並按期獲取其狀態;文件 ledSwitch.py 充當 Led 等的操縱器,它經過調用樹莓派的 GPIO 接口來設置和獲取 led 燈的狀態,以及將其狀態上報給 IoT 服務。

AWS IoT  提供了 Device Shadow python SDK,所以不須要直接操做各類 MQTT 主題,而能夠使用 get,update 和 delete 這種API。其地址在 https://github.com/aws/aws-iot-device-sdk-python/tree/master/AWSIoTPythonSDK/core/shadow

3.1 ledController.py

from AWSIoTPythonSDK.MQTTLib import AWSIoTMQTTShadowClient
import logging
import time
import json
import threading

# Led shadow JSON Schema
#
# 
# Name: Led
# {
#     "state: {
#               "desired": {
#                          "light": <on|off>
#                }
#      }
#}

deviceShadowHandler = None

def getDeviceStatus():
    while True:
        print("Getting device status...\n")
        deviceShadowHandler.shadowGet(customShadowCallback_get, 50)
        time.sleep(60)    

def customShadowCallback_get(payload, responseStatus, token):
    if responseStatus == "timeout":
        print("Get request with token " + token + " time out!")
    if responseStatus == "accepted":
        print("========== Printing Device Current Status =========")
        print(payload)
        payloadDict = json.loads(payload)
        #{"state":{"desired":{"light":0},"reported":{"light":100}
        try:
            desired = payloadDict["state"]["desired"]["light"]
            desiredTime = payloadDict["metadata"]["desired"]["light"]["timestamp"]
        except Exception:
            print("Failed to get desired state and timestamp.")
        else:
            print("Desired status: " + str(desired) + " @ " + time.ctime(int(desiredTime)))

        try:
            reported = payloadDict["state"]["reported"]["light"]
            #"metadata":{"desired":{"light":{"timestamp":1533893848}},"reported":{"light":{"timestamp":1533893853}}}
            reportedTime = payloadDict["metadata"]["reported"]["light"]["timestamp"]
        except Exception:
            print("Failed to get reported time or timestamp")
        else:
            print("Reported status: " + str(reported) + " @ " + time.ctime(int(reportedTime)))
        
        print("=======================================\n\n")
    if responseStatus == "rejected":
        print("Get request with token " + token + " rejected!")

def customShadowCallback_upate(payload, responseStatus, token):
    # payload is a JSON string which will be parsed by jason lib
    if responseStatus == "timeout":
        print("Update request with " + token + " time out!")
    if responseStatus == "accepted":
        playloadDict = json.loads(payload)
        print("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~")
        print("Update request with token: " + token + " accepted!")
        print("light: " + str(playloadDict["state"]["desired"]["light"]))
        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("Delete request with token " + token + " accepted!")
    if responseStatus == "rejected":
        print("Delete request with token " + token + " rejected!")

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

# AWS IoT Core endpoint. Need change some values to yours.
awsiotHost = "**********.iot.*********.amazonaws.com"
awsiotPort = 8883;
# AWS IoT Root Certificate. Needn't change.
rootCAPath = "/home/pi/awsiot/VeriSign-Class3-Public-Primary-Certification-Authority-G5.pem"
# Device private key. Need change to yours.
privateKeyPath = "/home/pi/awsiot/aec2731afd-private.pem.key"
# Device certificate. Need change to yours.
certificatePath = "/home/pi/awsiot/aec2731afd-certificate.pem.crt"
myAWSIoTMQTTShadowClient = None;
myAWSIoTMQTTShadowClient = AWSIoTMQTTShadowClient("RaspberryLedController")
myAWSIoTMQTTShadowClient.configureEndpoint(awsiotHost, awsiotPort)
myAWSIoTMQTTShadowClient.configureCredentials(rootCAPath, privateKeyPath, certificatePath)

myAWSIoTMQTTShadowClient.configureAutoReconnectBackoffTime(1, 32, 20)
myAWSIoTMQTTShadowClient.configureConnectDisconnectTimeout(60) # 10sec
myAWSIoTMQTTShadowClient.configureMQTTOperationTimeout(50) #5sec

#connect to AWS IoT
myAWSIoTMQTTShadowClient.connect()

#create a devcie Shadow with persistent subscription
thingName = "homepi"
deviceShadowHandler = myAWSIoTMQTTShadowClient.createShadowHandlerWithName(thingName, True)

#Delete shadow JSON doc
deviceShadowHandler.shadowDelete(customShadowCallback_delete, 50)

#start a thread to get device status every 5 seconds
statusLoopThread = threading.Thread(target=getDeviceStatus)
statusLoopThread.start()

#update shadow in a loop
loopCount = 0
while True:
    desiredState = "off"
    if (loopCount % 2 == 0):
        desiredState = "on"
    print("To change Led desired status to \"" + desiredState + "\" ...\n")
    jsonPayload = '{"state":{"desired":{"light":"' + desiredState + '"}}}'
    print("payload is: " + jsonPayload + "\n")
    deviceShadowHandler.shadowUpdate(jsonPayload, customShadowCallback_upate, 60)
    loopCount += 1
    time.sleep(60)

 

3.2 ledSwitch.py

import RPi.GPIO as GPIO
from AWSIoTPythonSDK.MQTTLib import AWSIoTMQTTShadowClient
import logging
import time
import json

# Led shadow JSON Schema
#
# 
# Name: Led
# {
#     "state: {
#               "desired": {
#                          "light": <on|off>
#                }
#      }
#}

LED_PIN_NUM = 18 # GPIO Number of Led long pin. Change to yours.
deviceShadowHandler = None
#initialize GOPI
GPIO.setmode(GPIO.BCM)
GPIO.setup(LED_PIN_NUM, GPIO.OUT)

def customShadowCallback_Delta(payload, responseStatus, token):
    # payload is a JSON string which will be parsed by jason lib
    print(responseStatus)
    print(payload)
    payloadDict = json.loads(payload)
    print("++++++++ Get DELTA data +++++++++++")
    desiredStatus = str(payloadDict["state"]["light"])
    print("desired status: " + desiredStatus)
    print("version: " + str(payloadDict["version"]))
    
    #get device current status
    currentStatus = getDeviceStatus()
    print("Device current status is " + currentStatus)
    #udpate device current status
    if (currentStatus != desiredStatus):
        # update device status as desired
        updateDeviceStatus(desiredStatus)
        # send current status to IoT service
        sendCurrentState2AWSIoT()
    print("+++++++++++++++++++++++++++\n")
    
def updateDeviceStatus(status):
    print("=============================")
    print("Set device status to " + status)
    if (status == "on"):
        turnLedOn(LED_PIN_NUM)
    else:
        turnLedOff(LED_PIN_NUM)
    print("=============================\n")

def getDeviceStatus():
    return getLedStatus(LED_PIN_NUM)

def turnLedOn(gpionum):
    GPIO.output(gpionum, GPIO.HIGH)

def turnLedOff(gpionum):
    GPIO.output(gpionum, GPIO.LOW)

def getLedStatus(gpionum):
    outputFlag = GPIO.input(gpionum)
    print("outputFlag is " + str(outputFlag))
    if outputFlag:
        return "on"
    else:
        return "off"    

def sendCurrentState2AWSIoT():
    #check current status of device
    currentStatus = getDeviceStatus()
    print("Device current status is " + currentStatus)
    print("Sending reported status to MQTT...")
    jsonPayload = '{"state":{"reported":{"light":"' + currentStatus + '"}}}'
    print("Payload is: " + jsonPayload + "\n")
    deviceShadowHandler.shadowUpdate(jsonPayload, customShadowCallback_upate, 50)
 
def customShadowCallback_upate(payload, responseStatus, token):
    # payload is a JSON string which will be parsed by jason lib
    if responseStatus == "timeout":
        print("Update request with " + token + " time out!")
    if responseStatus == "accepted":
        playloadDict = json.loads(payload)
        print("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~")
        print(payload)
        print("Update request with token: " + token + " accepted!")
        print("light: " + str(playloadDict["state"]["reported"]["light"]))
        print("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\n")
    if responseStatus == "rejected":
        print("Update request " + token + " rejected!")

def customShadowCallback_Get(payload, responseStatus, token):
    print("responseStatus: " + responseStatus)
    print("payload: " + payload)
    payloadDict = json.loads(payload)
    # {"state":{"desired":{"light":37},"delta":{"light":37}},"metadata":{"desired":{"light":{"timestamp":1533888405}}},"version":54
    stateStr = "" 
    try:
        stateStr = stateStr + "Desired: " + str(payloadDict["state"]["desired"]["light"]) + ", "
    except Exception:
        print("No desired state")

    try:
        stateStr = stateStr + "Delta: " + str(payloadDict["state"]["delta"]["light"]) + ", "
    except Exception:
        print("No delta state")

    try:
        stateStr = stateStr + "Reported: " + str(payloadDict["state"]["reported"]["light"]) + ", "
    except Exception:
        print("No reported state") 
    
    print(stateStr + ", Version: " + str(payloadDict["version"]))

def printDeviceStatus():
    print("=========================")
    status = getDeviceStatus()
    print(" Current status: " + str(status))
    print("=========================\n\n")

# Cofigure 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)

awsiotHost = "***********.iot.********.amazonaws.com"
awsiotPort = 8883;
rootCAPath = "/home/pi/awsiot/VeriSign-Class3-Public-Primary-Certification-Authority-G5.pem"
privateKeyPath = "/home/pi/awsiot/aec2731afd-private.pem.key"
certificatePath = "/home/pi/awsiot/aec2731afd-certificate.pem.crt"
myAWSIoTMQTTShadowClient = None;
myAWSIoTMQTTShadowClient = AWSIoTMQTTShadowClient("RaspberryLedSwitch")
myAWSIoTMQTTShadowClient.configureEndpoint(awsiotHost, awsiotPort)
myAWSIoTMQTTShadowClient.configureCredentials(rootCAPath, privateKeyPath, certificatePath)

myAWSIoTMQTTShadowClient.configureAutoReconnectBackoffTime(1, 32, 20)
myAWSIoTMQTTShadowClient.configureConnectDisconnectTimeout(60) # 10sec
myAWSIoTMQTTShadowClient.configureMQTTOperationTimeout(30) #5sec

#connect to AWS IoT
myAWSIoTMQTTShadowClient.connect()

#create a devcie Shadow with persistent subscription
thingName = "homepi"
deviceShadowHandler = myAWSIoTMQTTShadowClient.createShadowHandlerWithName(thingName, True)

#listen on deleta
deviceShadowHandler.shadowRegisterDeltaCallback(customShadowCallback_Delta)

#print the intital status
printDeviceStatus()

#send initial status to IoT service
sendCurrentState2AWSIoT()

#get the shadow after started
deviceShadowHandler.shadowGet(customShadowCallback_Get, 60)

#update shadow in a loop
loopCount = 0
while True:
    time.sleep(1)

3.3 主要過程

(1)ledSwitch.py 在運行後,獲取led 的初始狀態,並將其發給 AWS IoT 服務:

=========================
 outputFlag is 0
 Current status: off
=========================
outputFlag is 0
Device current status is off
Sending reported status to MQTT...
Payload is: {"state":{"reported":{"light":"off"}}}

(2)ledController.py 開始運行後,它首先獲取led 的當前狀態,爲 『off』

(3)Controller 將其 desired 狀態設置爲 on

To change Led desired status to "on" ...
payload is: {"state":{"desired":{"light":"on"}}}

(4)Switch 收到 DELTA 消息,調用 GPIO 接口設置其狀態,並向 IOT 服務報告其狀態

{"version":513,"timestamp":1533983956,"state":{"light":"on"},"metadata":{"light":{"timestamp":1533983956}},"clientToken":"93dfc84c-c9f9-49fb-b844-d55203991208"}
++++++++ Get DELTA data +++++++++++
desired status: on
version: 513
outputFlag is 0
Device current status is off
=============================
Set device status to on
=============================
outputFlag is 1
Device current status is on
Sending reported status to MQTT...
Payload is: {"state":{"reported":{"light":"on"}}}

(5)Controller 獲取最新狀態

{"state":{"desired":{"light":"on"},"reported":{"light":"on"}},"metadata":{"desired":{"light":{"timestamp":1533983956}},"reported":{"light":{"timestamp":1533983957}}},"version":514,"timestamp":1533983959,"clientToken":"f24bcbbb-4b24-4354-b1df-349afdf23422"}
Desired status: on @ Sat Aug 11 18:39:16 2018
Reported status: on @ Sat Aug 11 18:39:17 2018

(6)循環往復

 

歡迎你們關注個人我的公衆號:

 

參考連接:

相關文章
相關標籤/搜索