雲中樹莓派(5):利用 AWS IoT Greengrass 進行 IoT 邊緣計算

雲中樹莓派(5):利用 AWS IoT Greengrass 進行 IoT 邊緣計算

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

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

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

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

雲中樹莓派(5):利用 AWS IoT Greengrass 進行 IoT 邊緣計算git


  IoT 的諸多場景中,邊緣計算有不少需求。好比,不是每一個物聯網設備都能鏈接到互聯網,從而鏈接雲上物聯網服務。還好比有一些數據安全考慮,不容許將某些數據發到雲上。所以,AWS 發佈了 Greengrass 服務,用於支持物聯網場景中的邊緣計算。 github

1. AWS IoT Greengrass 服務概述

 

  AWS Greengrass 是一種軟件,用於將 AWS 雲功能擴展到本地設備,使得本地設備能夠更靠近信息源來收集和分析數據,同時在本地網絡上安全地相互通訊。更具體來講,使用 AWS Greengrass 的開發人員能夠在雲中編寫無服務器代碼 (AWS Lambda 函數),而後方便地將其部署到設備以在本地執行應用程序。在 AWS Greengrass 中,設備可在本地網絡上安全地通訊並互相交換消息而沒必要鏈接到雲。
 
安裝:
  • Greengrass 是一個軟件,能夠安裝在多種設備上,好比樹莓派、AWS EC2 實例、Ubuntu 虛擬機,以及多種專用設備。 具體參加AWS 官網。
功能:
  • Lambda 運行時:能夠將雲上建立的 Lambda 函數部署到 Greengrass Core 上並使其運行。Lambda 函數能夠和邊緣物聯網設備,以及雲服務進行交互。
  • 影子設備:爲邊緣物聯網設備提供 Device Shadow 服務,相似雲上 Device Shadow 服務。能夠經過更新和查詢設備的影子,來獲取和修改設備的狀態。
  • 消息管理器:支持 Greengrass 組中的物聯網設備之間的通訊,以及與 Lambda 函數、設備影子服務之間通訊。影子數據能夠只保存在本地(Local Shadow),也能夠同步到雲上。
  • 組管理: 管理 Greengrass Group,一個 group 爲一個獨立的邊緣物聯網環境。
  • 發現服務:物聯網設備能夠經過鏈接到IoT雲服務,而後經過 Discovery 功能來發現 Greengrass Core,從而與之通訊。
  • 無線更新代理:能夠遠程更新 Greengrass Core 軟件。
  • 本地資源訪問:支持 Greengrass Core 上的 Lambda 函數訪問本地資源,好比樹莓派的GPIO,本地視頻攝像頭等。
  • 機器學習推理:支持將雲上 ML 機器學習推理功能部署到Greengrass Core。

架構:json

關於架構的部分說明:ubuntu

    • 若干本地設備和一個Greengrass Core (GGC)組成一個 Greengrass 組。與 AWS Greengrass 核心通訊的全部設備都必須是 Greengrass 組的成員。每一個組都必須包含 AWS Greengrass 核心(彷佛一個組只能有一個 GGC)。Discovery API 使設備可以檢索鏈接到 AWS Greengrass 核心 (與設備位於同一 Greengrass 組中) 所需的信息。
    • 本地設備和 Greengrass Core 經過本地網絡通訊,沒法訪問雲(有看到 Discovery Service 須要設備在啓動時鏈接到雲上獲取到 GG Core 的鏈接信息)。設備上必須安裝 AWS IoT Device SDK。
    • Greengrass 能夠和雲通訊,須要有互聯網訪問能力。
    • 能夠在 Greengrass Core 上運行 Lambda 函數,這些函數能夠和設備之間通訊,也能夠和雲通訊。
    • 雲上的配置、Lambda 函數以及機器學習模版經過 『Deploy』 被安裝到 Greengrass Core 上。Greengrass Core 上有一個部署代理,它在接到通知後,從雲上獲取待部署材料,而後在 Greengrass Core 上進行部署。
    • 組中設備鏈接到GGC 的過程:
      • AWS IoT 設備使用其設備證書、私有密鑰和 AWS IoT 根 CA 鏈接到 Greengrass 雲服務。
      • 鏈接後,AWS IoT 設備將使用 Greengrass Discovery Service 查找其 AWS Greengrass 核心設備的 IP 地址。該設備還可下載組的根 CA 證書,該證書可用於對 Greengrass 核心設備進行身份驗證。
      • AWS IoT 設備嘗試鏈接到 AWS Greengrass 核心,並傳遞其設備證書和客戶端 ID。若是客戶端 ID 與設備的事物名稱匹配而且證書有效,則將進行鏈接。不然,將終止鏈接。

2. 部署 AWS IoT Greengrass Core

2.1 操做系統準備

我在本地建立了一臺 ubuntu 16.04 虛機,用於 Greengrass Core 部署。根據 Greengrass 文檔作一些操做系統配置,具體參見Greengrass文檔。在配置完成後,可運行檢查工具來驗證環境是否可用:安全

cd /home/pi/Downloads
git clone https://github.com/aws-samples/aws-greengrass-samples.git
cd aws-greengrass-samples
cd greengrass-dependency-checker-GGCv1.5.0
sudo modprobe configs
sudo ./check_ggc_dependencies | more

遇到兩個小問題,提示未發現 java8 和 nodejs610。此時,須要建立三個軟連接:服務器

ln -s /usr/bin/node /usr/bin/nodejs6.10
ln -s /usr/bin/node /usr/bin/nodejs
ln -s /usr/bin/java /usr/bin/java8

2.2 在 AWS IoT 上配置 Greengrass 服務

目前全球只有5個region 提供了 Greengrass 服務。在選擇使用哪一個region時候,必定要注意本地到這個region的網絡狀況。一開始,我想固然地認爲國內到亞洲好比東京或者悉尼由於地理距離較近所以網絡會較好,但實際上卻發現到美國弗吉尼亞的網絡比到東京的網絡要好得多。

(1)建立 Greengrass Group

建立後,需下載兩個壓縮包:

  • 一個是證書包:

  • 一個是 Greengrass Core 軟件安裝包:根據系統平臺選擇。

一個 Greengrass Group 包含的資源以下圖所示,具體有:

  • 部署(Deployments)
  • 訂閱表(Subscriptions)
  • 核心(Cores)
  • 本地設備(Devices)
  • Lambda函數
  • 本地資源(Resources)

 

(2)在設備上啓動 Greengrass Core

  1. 將上面兩個 zip 文件傳到待安裝 Greengrass Core 的環境中
  2. 將軟件安裝包解壓到 /greengrass 中
  3. 將證書zip文件解壓到 /greengrass/certs
  4. 下載 AWS IoT ROOT 證書到  /greengrass/certs 中
  5. 修改 config/config.json 文件以下所示
  6. 運行 /greengrass/ggc/core/greengrassd start 啓動 Greengrass Core 服務
複製代碼
{
    "coreThing": {
        "caPath": "root.ca.pem",
        "certPath": "022829d5c4.cert.pem",
        "keyPath": "022829d5c4.private.key",
        "thingArn": "arn:aws:iot:us-east-1:*******:thing/homepi_Core",
        "iotHost": "*********.iot.us-east-1.amazonaws.com",
        "ggHost": "greengrass.iot.us-east-1.amazonaws.com",
        "keepAlive": 2000
    },
    "runtime": {
        "cgroup": {
            "useSystemd": "yes"
        }
    },
    "managedRespawn": false
}
複製代碼

(3)問題排查

能夠在 /greengrass/ggc/var/log/system 中查看 Greengrass Core 的日誌文件。若是有錯誤,則定向排查。

2.3 測試

2.3.1 建立第一個 Lambda 函數

運行在 GGC 中的 Lambda 函數須要把 Greengrass SDK 打包進去。它的SDK 中提供了 HelloWorld 示例函數代碼。函數代碼以下,很簡單,它每隔5秒鐘向 hello/world MQTT 主題發送『Hello World』消息。

複製代碼
import greengrasssdk
import platform
from threading import Timer
import time

# Creating a greengrass core sdk client
client = greengrasssdk.client('iot-data')

# Retrieving platform information to send from Greengrass Core
my_platform = platform.platform()

def greengrass_hello_world_run():
    if not my_platform:
        client.publish(topic='hello/world', payload='Hello world! Sent from Greengrass Core.')
    else:
        client.publish(topic='hello/world', payload='Hello world! Sent from Greengrass Core running on platform: {}'.format(my_platform))

    # Asynchronously schedule this function to be run again in 5 seconds
    Timer(5, greengrass_hello_world_run).start()

# Start executing the function above
greengrass_hello_world_run()

def function_handler(event, context):
    return
複製代碼

參考GG文檔,完成所需步驟後,完成該函數的建立。發佈它的的一個版本,並建立別名 GG_HelloWorld。

2.3.2 將該函數添加到 Greengrass Group 中

在 Greengrass 服務中添加上面建立的函數:

2.3.3 建立訂閱 (subscription)

訂閱表用於定義 Greengrass 組內 (AWS Greengrass 核心設備、AWS IoT 設備和 Lambda 函數之間) 如何交換消息。訂閱表中的每一個條目指定源、目標和發送/接收消息時使用的 MQTT 主題。僅當訂閱表中存在指定源 (消息發件人)、目標 (消息收件人) 和 MQTT 主題的條目時才能交換消息。訂閱表條目指定從源到目標的單向消息傳遞。若是您須要雙向消息傳遞,請建立兩個訂閱表條目,每一個條目針對一個方向。

爲了測試該函數是否按設計發出了消息,建立一個從該函數到 IoT Service 的訂閱,這樣從 IoT 服務上就能夠收到它發出的消息了。

2.3.4 部署(Deploy)

雲上的全部操做都必須經過『部署』應用到 Greengrass Core 上。所以,對 Greengroup 作了任何變化後,都必須經過部署操做將其應用到Core 上。

點擊 Actions -> Deploy,開始部署。能夠從 Core 的 runtime.log 文件中看到其大體過程:

  1. 在Group 上有部署請求後,部署代理收到消息,消息中有 deploymentId
  2. 部署代理從雲上獲取待部署的素材
  3. 部署代理在Core上進行實際部署
[2018-08-13T15:56:55.622+08:00][INFO]-Received deploymentId 613dc2ec-8877-41b2-a217-8f939a7782fc of type NewDeployment for group f7a0bc8f-4527-481d-9301-1545b86fcf68
[2018-08-13T15:56:55.622+08:00][INFO]-Updating status to InProgress of deployment 613dc2ec-8877-41b2-a217-8f939a7782fc for group f7a0bc8f-4527-481d-9301-1545b86fcf68
[2018-08-13T15:56:56.611+08:00][INFO]-Executing the group Downloading step

而後查詢其狀態:

2.3.5 測試消息接收

在界面上的Test 功能中,能夠收到 Lambda 函數發出的消息:

2.3.6 Core 上的變化

在 Lambda 函數被部署到 Core 上以後,在 Core 上起了一個新的進程:

ggc_user 21106  0.3  0.7 189616 15148 ?        Ssl  16:33   0:12 python2.7 -u /runtime/python2.7/lambda_runtime.py --handler=greengrassHelloWorld.function_handler

該進程利用了 cgroup 來限制資源:

複製代碼
root@greengrass:/home/ubuntu# cat /proc/21106/cgroup 
11:freezer:/system.slice/83db5d89-d650-4f65-542a-f6669153d4e6
10:blkio:/user.slice/system.slice/83db5d89-d650-4f65-542a-f6669153d4e6
9:memory:/user.slice/system.slice/83db5d89-d650-4f65-542a-f6669153d4e6
8:net_cls,net_prio:/system.slice/83db5d89-d650-4f65-542a-f6669153d4e6
7:pids:/user.slice/user-1000.slice/system.slice/83db5d89-d650-4f65-542a-f6669153d4e6
6:hugetlb:/system.slice/83db5d89-d650-4f65-542a-f6669153d4e6
5:perf_event:/system.slice/83db5d89-d650-4f65-542a-f6669153d4e6
4:devices:/user.slice/system.slice/83db5d89-d650-4f65-542a-f6669153d4e6
3:cpuset:/system.slice/83db5d89-d650-4f65-542a-f6669153d4e6
2:cpu,cpuacct:/user.slice/system.slice/83db5d89-d650-4f65-542a-f6669153d4e6
1:name=systemd:/user.slice/user-1000.slice/session-63.scope/system.slice/83db5d89-d650-4f65-542a-f6669153d4e6
複製代碼

 目前只支持指定函數的內存限制:

 

3. 邊緣物聯網設備經過 Greengrass Core 進行消息交互

示意圖:

3.1 雲上配置

(1)在 IoT Greengrass 服務中建立兩個設備,分別是 HelloWorld_Publisher (模擬上圖中的設備 #1)和 HelloWorld_Subscriber(模擬上圖中的設備 #2)。將獲取到各自的證書文件。

建立設備:

建立結果:

 

(2)配置訂閱,從 Publisher 到 Subscriber:

(3)經過 部署,把應用同步到 Greengrass Core 上。

 

3.2 樹莓派中的配置和操做

以樹莓派爲平臺,在上面運行兩個程序,來模擬上面的兩個物聯網設備。

(1)首先須要在樹莓派上安裝 AWS IoT Device SDK

git clone https://github.com/aws/aws-iot-device-sdk-python.git
cd aws-iot-device-sdk-python
python setup.py install

(2)SDK 中有個示例文件 /aws-iot-device-sdk-python/samples/greengrass/basicDiscovery.py 可用於本測試

(3)運行腳本模擬 publlisher:

python basicDiscovery.py  -e *****.iot.us-east-1.amazonaws.com -r pubcerts/root-ca.pem  -c pubcerts/3ed88f606a.cert.pem -k pubcerts/3ed88f606a.private.key  -n HelloWorld_Publisher -m publish -t hello/world/pubsub -M "Hellow, I am Publisher"

它會不停地向 hello/world/pubsub 發送消息:

2018-08-14 16:44:14,143 - AWSIoTPythonSDK.core.protocol.internal.workers - DEBUG - Produced [puback] event
2018-08-14 16:44:14,145 - AWSIoTPythonSDK.core.protocol.internal.workers - DEBUG - Dispatching [puback] event
2018-08-14 16:44:15,144 - AWSIoTPythonSDK.core.protocol.mqtt_core - INFO - Performing sync publish...
Published topic hello/world/pubsub: {"message": "Hellow, I am Publisher", "sequence"

(4)運行另外一個腳本模擬 subscriber:

python basicDiscovery.py  -e *******.iot.us-east-1.amazonaws.com -r subcerts/root-ca.pem  -k subcerts/7d8fefa9d3.private.key  -c subcerts/7d8fefa9d3.cert.pem -n HelloWorld_Subscriber -t hello/world/pubsub -m subscribe

它會不斷收到消息:

2018-08-14 16:44:15,194 - AWSIoTPythonSDK.core.protocol.internal.workers - DEBUG - Produced [message] event
2018-08-14 16:44:15,196 - AWSIoTPythonSDK.core.protocol.internal.workers - DEBUG - Dispatching [message] event
2018-08-14 16:44:15,197 - AWSIoTPythonSDK.core.protocol.internal.clients - DEBUG - Invoking custom event callback...
Received message on topic hello/world/pubsub: {"message": "Hellow, I am Publisher", "sequence": 6}

3.3 過程說明

(1)訂閱者一開始,會向 IoT Service Endpoint 發送一個 Discovery 消息:

 Sending discover request: GET /greengrass/discover/thing/HelloWorld_Subscriber HTTP/1.1
Host: a1upjpa864lewg.iot.us-east-1.amazonaws.com:8443

說明:這裏說明邊緣的物聯網設備仍是須要鏈接到雲上的IoT端點,這說明它們仍然須要互聯網訪問能力。

(2)它收到返回消息

Receiving discover response body...
Discovered GGC: arn:aws:iot:us-east-1:*******:thing/homepi_Core from Group: 669d91fc-0690-48ab-a36d-90816b2332b4
Now we persist the connectivity/identity information...

(3)它鏈接到 Greengrass Core

Trying to connect to core at 192.168.1.12:8883

(4)它訂閱到指定 topic

Adding a new subscription record: hello/world/pubsub qos: 0

(5)它開始接收消息

2018-08-14 16:44:09,381 - AWSIoTPythonSDK.core.protocol.internal.workers - DEBUG - Produced [message] event
2018-08-14 16:44:09,382 - AWSIoTPythonSDK.core.protocol.internal.workers - DEBUG - Dispatching [message] event
2018-08-14 16:44:09,384 - AWSIoTPythonSDK.core.protocol.internal.clients - DEBUG - Invoking custom event callback...
Received message on topic hello/world/pubsub: {"message": "Hellow, I am Publisher", "sequence": 0}

可見這過程裏面,處於邊緣的物聯網設備仍是須要鏈接到雲上IoT 服務一次,去獲取Core 的信息。Core 的 Connectivity 信息能夠收入輸入,也能夠由Core 自動推送到雲上。

4. 與本地設備影子進行交互

示意圖:

4.1 雲上配置

(1)在IoT 服務中,在 Greengrass 組內,建立兩個設備,GG_Switch 和 GG_TrafficLight。

(2)建立訂閱

(3)部署

4.2 樹莓派上的配置和操做

 https://github.com/aws-samples/aws-greengrass-samples/tree/master/traffic-light-example-python 下載 lightController.py 和 trafficLight.py 文件。前者模擬一個Led 燈的控制器,後者模擬Led 燈。

(1)運行Controller

python lightController.py -e ****.iot.us-east-1.amazonaws.com -r switchcerts/root-ca.pem -c switchcerts/8bb0278c01.cert.pem -k switchcerts/8bb0278c01.private.key -n GG_TrafficLight --clientId GG_Switch

它會定時向設備影子發出更新請求:

{"state":{"desired":{"property":"Y"}}}
2018-08-14 17:00:28,915 - AWSIoTPythonSDK.core.protocol.mqtt_core - INFO - Performing sync publish...
~~~~~~~~~~Shadow Update Accepted~~~~~~~~~~~~~
Update request with token: 1827378e-9b0b-4b03-a7df-2c1af119510f accepted!
property: Y

(2)運行 Light

python trafficLight.py -e ****.iot.us-east-1.amazonaws.com -r lightcerts/root-ca.pem  -c lightcerts/eae63a2ee2.cert.pem -k lightcerts/eae63a2ee2.private.key  -n GG_TrafficLight --clientId GG_TrafficLight

它會收到 Delta 請求,變動Led 的狀態:

複製代碼
Light changed to: Y
{"state":{"reported":{"property":"Y"}}}
2018-08-14 17:02:29,111 - AWSIoTPythonSDK.core.protocol.mqtt_core - INFO - Performing sync subscribe...
2018-08-14 17:02:29,120 - AWSIoTPythonSDK.core.protocol.mqtt_core - INFO - Performing sync subscribe...
2018-08-14 17:02:31,132 - AWSIoTPythonSDK.core.shadow.deviceShadow - INFO - Subscribed to update accepted/rejected topics for deviceShadow: GG_TrafficLight
2018-08-14 17:02:31,133 - AWSIoTPythonSDK.core.protocol.mqtt_core - INFO - Performing sync publish...
~~~~~~~~~~ Shadow Update Accepted ~~~~~~~~~~~~~
Update request with token: ec5f0cdb-0558-44b7-a685-02d2df8a31cb accepted!
property: Y
複製代碼

5. 從 Lambda 函數中訪問雲服務

示意圖:

5.1 雲上配置

(1)建立 IAM Role Greengrass_DynamoDB_Role,將其賦予給 Greengrass,用於訪問 DynamoDB。

(2)建立 IAM Role Lambda_DynamoDB_Role,它會被賦予給 Lambda 函數,用於訪問 DynamoDB。

(3)從 https://github.com/aws-samples/aws-greengrass-samples/tree/master/traffic-light-example-python 下載 carAggregator.py,打包成 Lambda 函數包,建立 Lambda 函數。函數名爲 GG_Car_Aggregator。看下它的代碼:

複製代碼
import logging
import boto3
from datetime import datetime
from random import *
from botocore.exceptions import ClientError

dynamodb = boto3.resource('dynamodb', region_name='us-east-1')
tableName = "CarStats"

# Create the dynamo db table if needed
try:
    table = dynamodb.create_table(
        TableName=tableName,
        KeySchema=[
            {
                'AttributeName': 'Time', 
                'KeyType': 'HASH'  #Partition key
            }
        ],
        AttributeDefinitions=[
            {
                'AttributeName': 'Time',
                'AttributeType': 'S'
            }
        ],
        ProvisionedThroughput={
            'ReadCapacityUnits': 5,
            'WriteCapacityUnits': 5
        }
    )

    # Wait until the table exists.
    table.meta.client.get_waiter('table_exists').wait(TableName=tableName)
except ClientError as e:
    if e.response['Error']['Code'] == 'ResourceInUseException':
        print("Table already created")
    else:
        raise e

# initialize the logger
logger = logging.getLogger()
logger.setLevel(logging.INFO)

# This is a long lived lambda so we can keep state as below
totalTraffic = 0
totalGreenlights = 0
minCars = -1
maxCars = -1

def function_handler(event, context):
    global totalTraffic
    global totalGreenlights
    global minCars
    global maxCars
    
    # grab the light status from the event
    # Shadow JSON schema:
    # { "state": { "desired": { "property":<R,G,Y> } } }
    logger.info(event)
    lightValue = event["current"]["state"]["reported"]["property"]
    logger.info("reported light state: " + lightValue)
    if lightValue == 'G':
        logger.info("Green light")

        # generate a random number of cars passing during this green light
        cars = randint(1, 20)

        # update stats
        totalTraffic += cars
        totalGreenlights+=1
        if cars < minCars or minCars == -1:
            minCars = cars
        if cars > maxCars:
            maxCars = cars

        logger.info("Cars passed during green light: " + str(cars))
        logger.info("Total Traffic: " + str(totalTraffic))
        logger.info("Total Greenlights: " + str(totalGreenlights))
        logger.info("Minimum Cars passing: " + str(minCars))
        logger.info("Maximum Cars passing: " + str(maxCars))

        # update car stats to dynamodb every 3 green lights
        if totalGreenlights % 3 == 0:
            global tableName
            table = dynamodb.Table(tableName)
            table.put_item(
                Item={
                    'Time':str(datetime.utcnow()),
                    'TotalTraffic':totalTraffic,
                    'TotalGreenlights':totalGreenlights,
                    'MinCarsPassing':minCars,
                    'MaxCarsPassing':maxCars,
                }
            )
    return
複製代碼

代碼也很簡單。它首先會嘗試建立一個 Dynamo table。而後在每次收到 documents 後,檢查 reported 狀態。若是爲 「G」,表示爲綠燈,它會向Dynamo 表中寫入一條數據。

(4)將該函數添加到 Greengrass 組中。

(5)配置訂閱。本地影子服務會將設備的 documents 發給 Aggregator Lambda 函數。

5.2 樹莓派上的配置

保持 4.2 中的 Controller 和 Light 持續運行。幾分鐘後,Dynamo 中將會有數據產生:

6. 一點感覺

感受AWS IoT Greengrass 服務還有一些不太完善,主要有如下幾個緣由:

  • 目前全球只有5個區域內可使用 Greengrass 服務
  • 彷佛沒法作到邊緣物聯網設備徹底不需訪問雲而只須要能訪問 Greengrass Core,由於至少 Discovery Serivce 須要訪問 IoT Service Endpoint來獲取 Core 的鏈接信息。
  • 利用訂閱來控制消息的發送很繁瑣。若是有不少的設備,不少的topic,那這個配置將成爲一個苦力活。
  • Greengrass 服務應該須要高可用,可是沒看到相關的文檔和方案。

 

參考連接:

相關文章
相關標籤/搜索