AWS GreengrassでLチカ:クラウドとエッジについて考える(2)

前回記事(AWS GreengrassでLチカ:クラウドとエッジについて考える(1))の続きになります.
取り組む課題や環境,クラウドタイプのやり方についてはそちらを参照ください.

2. AWS Greengrassにてネットワーク型信号機を実現する

本記事で取り組む課題は”ネットワーク型信号機の設計”ですが,クラウドの考え方を素直に適用した場合,いろいろと問題が発生しそうなことについては前回記事にて述べました.今回はそれらの問題を解消すべく,エッジの考え方を導入したネットワーク型信号機をAWS Greengrassを使って実現します.

今回もできるだけ単純なモデルとするため,構成は以下としました.

p2.png

大きな違いはメッセージの発行元です.

  • クラウドタイプ : インターネットをはさんだサーバー上で稼働するLambda関数がメッセージ発行元
  • エッジタイプ : RasPi3自身(正確にはRasPi3上で動作するLambda関数)がメッセージ発行元

構成を見る限り自立して稼働しそうな気がします…なんだかエッジぽいです.
早速作成してみます.

信号機側にデプロイされるLambda関数

まずはこれを作成することにします.作成はAWS Lambdaのコンソール上で実施しました.AWS Lambdaのコンソールから [関数の作成]選択,”Greengrass”で検索すると現状では2件ヒットします.うちPython版の”greengrass-hello-world”をテンプレートにして作業を進めます.
今回は,テンプレートを少しだけ変更して以下としました.

import greengrasssdk
import platform
from threading import Timer
import time

client = greengrasssdk.client('iot-data')

my_platform = platform.platform()

def greengrass_hello_world_run():
    # (追加部分)緑点灯のためのメッセージ発行,後5秒待ち
    client.publish(topic='io/led', payload='{"red":0,"green":1}')
    time.sleep(5)
    # (追加部分)赤点灯のためのメッセージ発行,後5秒待ち
    client.publish(topic='io/led', payload='{"red":1,"green":0}')
    # 関数自身を呼ぶことで無限ループ
    Timer(5, greengrass_hello_world_run).start()

# 実行開始
greengrass_hello_world_run()

def function_handler(event, context):
    return

※本来はデバイスシャドウを利用すべきかもしれませんが,簡素化のために直にメッセージを発行する形にしています

AWS Greengrass を通じてこのLambda関数がエッジ側(RasPi3)にデプロイされると,(無限ループのおかげで)動き続けることになります.エッジ側で動くLambda関数は通常のLambda関数と異なり,動作時間上限がありません.デプロイの過程やセキュリティの担保はGreengrassが面倒をみてくれるので,いろいろ考えなくてすみます.

信号機側S/W

前回記事にて作成したS/Wを改良する形で進めます.AWS Greengrassのサンプルを参考にしました.

主な変更点はMQTTクライアントの接続先を,サーバー側ではなく,RasPi3上で動作するGreengrassコア相手にする事です.接続先情報はサーバー側に問い合わせる機構となっているようです.

# -*- coding: utf-8 -*-

# AWS Greengrass を取り扱うためのモジュール
from AWSIoTPythonSDK.core.greengrass.discovery.providers import DiscoveryInfoProvider
from AWSIoTPythonSDK.core.protocol.connection.cores import ProgressiveBackOffCore
from AWSIoTPythonSDK.exception.AWSIoTExceptions import DiscoveryInvalidRequestException

from AWSIoTPythonSDK.MQTTLib import AWSIoTMQTTClient
import RPi.GPIO as GPIO
import time
import json
import sys
import uuid
import os

# gpio pins
LED_R = 14
LED_G = 15
LEVEL = [GPIO.LOW, GPIO.HIGH]

# host
host = "a364yta89jsfw8.iot.ap-northeast-1.amazonaws.com"
port = 8883

# certs
rootca_path = "./certs/root-ca.pem"
certificate_path = "./certs/thing-certificate.pem.crt"
privatekey_path = "./certs/thing-private.pem.key"
thing_name = "RasPi3"

def onMessage(message):    # メッセージを受け取った際に呼ばれる
    print(message.payload)
    payload = json.loads(message.payload.decode('utf-8'))
    print(" > Message : {}".format(payload))
    print("--------------n")   

    try:
        red_lv = int(payload['red'])    
        green_lv = int(payload['green'])
        GPIO.output(LED_R, LEVEL[red_lv])
        GPIO.output(LED_G, LEVEL[green_lv])
    except:
        print(" ! Invalid message payload")     

def getGgCoreInfo():    # エッジ上Greengrass Coreの情報取得
    GROUP_CA_PATH = "./groupCA/"

    # Greengrass coreの検索
    discovery_info_provider = DiscoveryInfoProvider()
    discovery_info_provider.configureEndpoint(host)
    discovery_info_provider.configureCredentials(rootca_path, certificate_path, privatekey_path)
    discovery_info_provider.configureTimeout(10)  # 10 sec  

    discovery_info = discovery_info_provider.discover(thing_name)
    ca_list = discovery_info.getAllCas()
    core_list = discovery_info.getAllCores()

    # 見つかった1つめのCoreの情報を取得
    group_id, ca = ca_list[0]
    core_info = core_list[0]
    print("Discovered GGC: %s from Group: %s" % (core_info.coreThingArn, group_id))

    print("Now we persist the connectivity/identity information...")
    group_ca = GROUP_CA_PATH + group_id + "_CA_" + str(uuid.uuid4()) + ".crt"
        # GroupCAの取得(...よく理解していない)
    if not os.path.exists(GROUP_CA_PATH):
        os.makedirs(GROUP_CA_PATH)
    group_ca_file = open(group_ca, "w")
    group_ca_file.write(ca)
    group_ca_file.close()

    return (group_ca,core_info)

if __name__ == "__main__":

    # gpio setup
    GPIO.setwarnings(False) 
    GPIO.setmode(GPIO.BCM)
    GPIO.setup(LED_R, GPIO.OUT)
    GPIO.setup(LED_G, GPIO.OUT)

    # mqtt setup
    mqtt_client = AWSIoTMQTTClient(thing_name)
    #mqtt_client.configureEndpoint(host,port)
    #mqtt_client.configureCredentials(rootca_path, privatekey_path, certificate_path)

    mqtt_client.configureOfflinePublishQueueing(0)
    mqtt_client.configureDrainingFrequency(2)
    mqtt_client.configureConnectDisconnectTimeout(120)
    mqtt_client.configureMQTTOperationTimeout(5)

    # Register an onMessage callback
    mqtt_client.onMessage = onMessage

    # 取得した接続先に対して順に接続を試みる
    connected = False
    (rootca_path, core_info) = getGgCoreInfo()
    for connectivity_info in core_info.connectivityInfoList:
        current_host = connectivity_info.host
        current_port = connectivity_info.port
        print(" * Trying to connect to core at %s:%d" % (current_host, current_port))
        mqtt_client.configureEndpoint(current_host, current_port)
        mqtt_client.configureCredentials(rootca_path, privatekey_path, certificate_path)
        try:
            mqtt_client.connect()
            connected = True
            break
        except BaseException as e:
            print(" * Error in connect!")
            print(" * Type: %s" % str(type(e)))
            print(" * Error message: %s" % e.message)

    if not connected:
        print(" * Cannot connect to core %s. Exiting..." % core_info.coreThingArn)
        sys.exit(-2)    

    mqtt_client.connect()
    time.sleep(2)
    print(" * connection success")  
    mqtt_client.subscribe("io/led", 1, None)
    print(" * start subscribing...")

    while True:
        time.sleep(3)

公式のサンプルではGreengrass Coreの検索自体をリトライする構成になっています.これは “RasPi3側にLambda関数をデプロイした後でしか接続先を発見できない”ためです.今回はコード簡略化のため”Lambda関数はすでにデプロイされている前提”で進めます.

信号機側H/W

前回記事と同じなので省略します.

動かしてみる

(本来の動きとは逆になりますが)まずはRasPi3側にLambda関数をデプロイします.注意点としてデプロイ前に

  • RasPi3上でGreengrass Coreを起動しておく
  • Lambda関数を作成後,[アクション]→[新しいバージョンを発行]を実行しておく
  • サブスクリプション(メッセージが配送される経路)を適切に設定しておく

が必要です.Greengrass Coreは以下で起動できます.起動に失敗する場合はGreengrass Coreの設定が間違っている可能性が高いです.

pi@raspi3-nobu_e753:/greengrass/ggc/core $ pwd
/greengrass/ggc/core
pi@raspi3-nobu_e753:/greengrass/ggc/core $ sudo ./greengrassd start
Setting up greengrass daemon
Validating execution environment
Found cgroup subsystem: cpu
...
Starting greengrass daemon
Greengrass successfully started with PID: 2856

サブスクリプションの経路は以下としました(自信なし).ターゲットに”IoT Cloud”を設けておくことで,AWS IoTのコンソール側([テスト]→[トピックへサブスクライブする])でも発行メッセージを確認することができます.

aws03s.png

設定が完了したらデプロイを実行します.あらかじめ[Lambda]タブで指定された関数がエッジ側へロードされます.ランプが緑に変わったら完了です.

aws04s.png

次にRasPi3側のプログラムを動かします.

pi@raspi3-nobu_e753:~/Workspace/aws $ ls
certs/  led_blink_awsgg.py  led_blink_awsiot.py
pi@raspi3-nobu_e753:~/Workspace/aws $ python led_blink_awsgg.py 
Discovered GGC: arn:aws:iot:ap-northeast-1:xxxxxxxxxxxx:thing/GgcGroup0_Core from Group: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
Now we persist the connectivity/identity information...
 * Trying to connect to core at xxxx::xxx:xxxx:xxxx:xxxx:8883 # 接続先1トライ
 * Error in connect!
 * Type: <class 'socket.error'>
 * Error message: 
 * Trying to connect to core at 127.0.0.1:8883 # 接続先2トライ
 * connection success
 * start subscribing...    # エッジ側のLambda関数が発行するメッセージを受け,以下が出力される
{"red":1,"green":0}
 > Message : {u'green': 0, u'red': 1}
--------------

{"red":0,"green":1}
 > Message : {u'green': 1, u'red': 0}
--------------

{"red":1,"green":0}
 > Message : {u'green': 0, u'red': 1}
--------------

この状態で,RasPi3に接続されたLEDの色が5秒ごとに赤→緑→赤…と変わるのが確認できると思います.

最後に

エッジ側だけの情報で制御がなされていることを確認するためRasPi3のネットワーク接続を切断してみます(RasPi3のWifiを切断,ルーターの電源をおとす..などどのような方法でもよいです).その後もLEDの色が切り替わり続けるはずです.

このエッジタイプ信号機であれば,クラウドタイプ信号機の問題点を解決できそうですね.

わからなかった箇所

エッジ側へデプロイするLambda関数内で利用可能なコード
例えば,時刻の取得 datetime.now() やプラットフォーム情報の取得
platform.platform() は実行可能でしたが,ディレクトリ構成の取得 os.listdir() は実行できませんでした.何が実行できて何が実行できるのでしょう…

まとめ

AWS Greengrassを使い,信号機に見立てたLEDの制御を行うシステムを2通り作成しました.これを通じて,クラウドとエッジの違いを感覚的につかむことができました.

続きを読む

AWS GreengrassでLチカ:クラウドとエッジについて考える(1)

できたもの

LED blink by AWS Greengrass controll pic.twitter.com/sdBzLaifdf

— nobu_e753 (@nobu_e753) 2017年10月7日

  • AWS Greengrassのコンソール上からLambda関数をRasPi3へデプロイすると,RasPi3につないであるLEDが点滅する(5秒毎に赤→緑→赤…)
  • その後,RasPi3のネットワークを切断してもLEDは既定のパターンで点滅し続ける

はじめに

先日,JAWS-UG主催のAWS Greengrassハンズオンに参加したのですが,機材トラブルや難度の問題もあり,半端な状態で終わってしまいました.あまり理解できず終わってしまったことが悔しかったので,自宅にて自分なりの課題を立て進めてみることにしました.本記事はそのまとめです.

※「自分なりに理解すること」を優先したので,公式のやり方に沿わないもしくは語弊がある部分もあるかもしれません

下準備

機材は以下で進めました.こちらにGreengrassに対応している機材一覧があります.

  • RaspberryPi 3
  • PC

またとりくみ前に以下のチュートリアルを実施しました.内容そのものの理解というより”なんとなくオペレーションを理解しておく”ためです.

言語はPythonですすめました.あと,あえて述べておくと私のAWSに関する知識は「EC2やDynamoDBを仕事でつかうものの利用は基本的な範囲にとどまる」レベルです.

とりくみ内容

AWSを利用して以下のような「ネットワーク型信号機」を設計する,ことをお題にしました(実際は信号機にみたてたRasPi3+LEDを制御・点灯させます).

problem.png

この課題に取り組むにあたり

  1. AWS IoT にて実現(クラウドの考え方)
  2. AWS Greengrass にて実現(エッジの考え方)

の双方を試すことにより,それらの違い,そしてクラウドとエッジの考え方を感覚的に理解することを目標にしました.本記事では1つめ,続編で2つめに取り組みます.

1. AWS IoTにてネットワーク型信号機を実現する

まずは構成検討です.できるだけ単純なモデルにするため以下にしました.

p1.png

  • 制御はAWS側から相当するMQTTメッセージを送信
  • RasPi3側でメッセージを受信,信号機に見立てたLEDを制御(赤/緑の2色)

赤/緑だけの2色信号機をネットワーク経由で制御します(最初に「赤を点灯」に相当するメッセージを発信,60秒後に「緑を点灯」に相当するメッセージを発信…).信号の切り替え間隔を変える場合も,サーバー(AWS)側の制御プログラムを変更するだけで済みます.なんかクラウドっぽいです…
早速作成してみます.

信号機側S/W

  • MQTTメッセージを受信
  • メッセージに合わせGPIO経由でLED制御

機能としてはこれだけです.コードは以下としました.AWS IoTのサンプルに毛の生えた程度です.各種アドレスやファイルパスはすべて埋め込みとしました.AWS IoTに接続するための証明書はあらかじめ作成し,./certs/ フォルダ以下に格納しておくものとします.
GPIO経由でLEDを点灯させることについてはこちらの記事などが参考になります.

led_brink_iot.py
# -*- coding: utf-8 -*-
from AWSIoTPythonSDK.MQTTLib import AWSIoTMQTTClient
import RPi.GPIO as GPIO
import time
import json

# gpio pins
LED_R = 14
LED_G = 15
LEVEL = [GPIO.LOW, GPIO.HIGH]

# host
host = "xxxxxxxxxxxxxx.iot.ap-northeast-1.amazonaws.com"
port = 8883

# certs
rootca_path = "./certs/root-ca.pem"
certificate_path = "./certs/thing-certificate.pem.crt"
privatekey_path = "./certs/thing-private.pem.key"
thing_name = "RasPi3"

def onMessage(message):    # メッセージを受け取った際に呼ばれる
    payload = json.loads(message.payload.decode('utf-8'))
    print(" > Message : {}".format(payload))
    print("--------------n")   

    try:    # GPIOの制御
        red_lv = int(payload['red'])    
        green_lv = int(payload['green'])
        GPIO.output(LED_R, LEVEL[red_lv])
        GPIO.output(LED_G, LEVEL[green_lv])
    except:
        print(" ! Invalid message")     

if __name__ == "__main__":

    # gpio setup
    GPIO.setwarnings(False) 
    GPIO.setmode(GPIO.BCM)
    GPIO.setup(LED_R, GPIO.OUT)
    GPIO.setup(LED_G, GPIO.OUT)

    # mqtt setup
    mqtt_client = AWSIoTMQTTClient(thing_name)
    mqtt_client.configureEndpoint(host,port)    # MQTTクライアントの接続先を上で指定したhost/portにする
    mqtt_client.configureCredentials(rootca_path, privatekey_path, certificate_path)

    mqtt_client.configureOfflinePublishQueueing(0)
    mqtt_client.configureDrainingFrequency(2)
    mqtt_client.configureConnectDisconnectTimeout(120)
    mqtt_client.configureMQTTOperationTimeout(5)

    # Register an onMessage callback
    mqtt_client.onMessage = onMessage    # メッセージを受信したときのコールバック関数設定

    # start subscribe
    mqtt_client.connect()    # 接続
    time.sleep(2)
    print(" * Connection successful")   

    mqtt_client.subscribe("io/led", 1, None)
    print(" * Start subscribe..")

    while True:
        time.sleep(3)    # 無限ループでメッセージ受信を待つ

サーバー側からのメッセージは以下のMQTTトピック&JSON形式としました.0なら消灯,1なら点灯です.

# MQTT topic
io/led
# Message format
{
    "red":0,
    "green":1
}

信号機側H/W

GPIO-LED間の接続は以下としました(抵抗には手元にあった330Ωを使用).

20170929_krs_qiita.png

動かしてみる

まずはRasPi3側のプログラムを動かします.

pi@raspi3-nobu_e753:~/Workspace/aws $ ls
certs/  led_blink_awsgg.py  led_blink_awsiot.py
pi@raspi3-nobu_e753:~/Workspace/aws $ python3 led_blink_awsiot.py 
 * connection success
 * start subscribing...    # AWS側からメッセージを発行すると以下が出力される
 > Message : {'green': 0, 'red': 1}
--------------

 > Message : {'green': 1, 'red': 0}
--------------

 > Message : {'green': 0, 'red': 1}
--------------

待ち受け状態になったらAWS IoTのコンソールからMQTTメッセージを発行してみます.

aws01s.png

aws02.png

メッセージを発行すると,コンソールにその内容が表示されると当時にRasPi3に接続されているLEDの点灯状況が変わるはずです.このメッセージを発行手順を,手動ではなく,AWS Lambdaで記述し,これを定期実行するようにすれば,「サーバーからのメッセージによって切り替えを行うクラウドタイプ信号機」が実現できます(AWS Lambdaによる定期発行のコードは省略します).

クラウドタイプ信号機の問題点

さて,もし世の中の信号機が上記の構成で実現されていたらかなり危ないことに気づくと思います.

1. ネットワークが切れたら終わり
まずこれです.信号機の制御はサーバーから送信されにはてくるメッセージによっているので,ネットワークが切れた瞬間に制御できなくなります.事故が多発するに違いありません.

2. 通信費がバカにならない
日本にある信号機は全部で約20万基だそうです(ちなみに,コンビニ5万件,歯医者7万件,美容院23万件!だそうです).これらをすべて制御する場合,一体どれだけの費用がかかるか想像もしたくありません.

どうやらこの構成だと問題が大きそうです

ではどうしたら?

上記問題を解決するには

  • 信号機側である程度自立して動くようにする
  • ただしネットワークを通じて制御内容をアップデートできるようにする

としたいです.すると信号機側にアップデーを受け入れる機構や,新設定にしたがって動き出す機構,アップデートが失敗した場合の手当てを行う機構などなど,本題ではないところでいろいろと面倒なことが発生します.

ううむ面倒だ,誰かやってもらえないだろうか…

→ AWS Greengrassに面倒をみてもらおう!(という意図のサービスだと私は理解しました

記事2に続きます.

続きを読む

AWS Greengrassを利用する際の「センサー制御部」と「新しいLambda関数のデプロイ」について

9/26のJAWS-UG IoT専門支部主催「AWS Greengrass Handson」を受講し終わったばかりの松下です

受講目的

  1. AWS Greengrass Coreからセンサー制御ってどうするの?
  2. Lambda関数の更新時の手順は?

この2点が前から気になっていました。運用するのに必須だからさ。

結論

AWS Greengrass についてわかった事 (たぶん)

AWS Greengrass Core (エッジデバイス上で動くコンテナプロセス、以下ggc) は、AWS IoT に対するMQTT Proxyサーバ的役割と、Lambda実行環境を持っている。

そのため

  • ggcを起動すると 8883 ポートでMQTT接続を待ち受ける
  • オフラインとなったとしても、センサー制御部プログラム側から見ると、AWS IoTに直接接続しているかの如く動作し続けることができる

AWS Greengrass Coreとセンサー制御部の関係

ggcやその上で動くLambdaからOSが持っているローカルリソース、例えば /dev/ttyUSB0 をRead/Writeすることはできない。(と推測される)

  • そのため、「センサーからReadする→AWS Greengrass SDKを使って)ggcにデータを投げる」というプログラムが必要。また、このプログラムが動くようにする環境が必須
  • また、上記プログラムの活動制御はggcは行ってくれないので、systemdのUnitを作る※、そのプログラム自体の更新の仕組みを考える、等が必須
    • ※ggc上のLambdaを更新する際、ggcのMQTT接続が切れる模様。そうなると、センサー制御部のプログラムは再接続の仕組みを持たせるか、supervisor的なところから再起動してもらう必要がある

Lambda関数の更新時の手順

ggc上で動くLambdaのイメージ

ハンズオンでの構成を再現すると

ThingShadowSensor.py
  (publishするように書く)
     ↓
(8883:sensing/data/Sensor-01)
     ↑
  (subscribeするように設定)
Lambda:cpuUsageChecker-01:VERSION
  (publishするように書く)
     ↓
(8883:$aws/things/Alert-01/shadow/#)
     ↑
  (subscribeするように書く)
ThingAlertSensor.py

わかりづらくてゴメン。何が言いたいかというと、Lambda関数だ~とか言わず、結局のところMQTTのトピックを介してパイプ処理をしている。そして、ggc内はスクリプトじゃなくてLambda関数を実行できるよ、という解釈で大丈夫。(たぶん)

そのため、この行先(=サブスクリプション)に、たとえば Lambda:cpuUsageChecker-01:NEW-VERSION が含まれるエントリー(トピックの組み合わせ)を作ってあげれば NEW-VERSION な Lambda関数が起動する。

手順は以下の通り;

  1. AWS Lambda関数を更新、発行する (新しいバージョンができる)
  2. クラウド側AWS Greengrassの「Lambda」メニューで、新しいバージョンのLambda関数を「追加」する (複数のバージョンが存在することになる)
  3. クラウド側AWS Greengrassの「サブスクリプション」メニューで、新しいバージョンのLambda関数を「ソース」や「ターゲット」とするエントリーを作成し、古いバージョンのLambda関数を利用しているエントリーを消す
    • 消さなくても良い。その代わり、トピックを調整する必要がある
  4. クラウド側AWS Greengrassの「グループ」メニューから、デプロイする

まだわからないこと

  • ggcに対してすっぴんのMQTTクライアント接続が可能なのか? (AWS Greengrass SDKはデカすぎる。センサー制御プログラムは極力小さくしたいはずだ!)

あとがき

市川さんはじめ、全国のJAWS-UG IoT 専門支部の方々、お疲れ様でしたー!!!

続きを読む

Raspi(デバイス)とAWSIoT(クラウド)間でMQTTを使ってメッセージを送受信する

はじめに

AWSのIoTのサービスといえばAWSIoTですよね!
つい先日大阪で行われてた「AWS Cloud Roadshow 2017」の中で、AWS Greengrassが東京リージョンに上陸したことが発表されました!
ということでですね、デバイスとクラウド間のやり取りの基本的な部分をAWSIoTを利用して実装していこうと思います

※前半部分にはsakura.io ✕ AWS IoTハンズオン [AWS編]の内容を投稿者の方の許可をいただいた上で、使わせていただいております

[0] 主な流れ

AWSIoTとラズパイ間の通信をMQTTでつなぎ、送受信する手順を紹介します
おおまかな流れはこんな感じです

  1. AWSIoTで証明書を発行する
  2. 発行した証明書を用いてデバイス(Raspi)とAWSIoTを接続する
  3. メッセージを送受信する
  4. (おまけ)Lambda経由でDynamoDBにデータをPUTする

[1] AWS IoTの設定(Thing)

AWS IoTは概念が少々複雑ですが、ある程度把握しておかないとこれ以降の設定内容が理解できないと思います。AWS Black Belt Online Seminar 2016 AWS IoTで学習すると良いでしょう。

  1. 証明書Thingポリシーを紐付ける
  2. 証明書をデバイスにインストールする
  3. 流れてきたトピックに対するアクション(連携先等)をルールで定義する

ポリシーの作成

AWS IoTコンソールを開く

スクリーンショット 2017-09-18 00.46.54.png

リージョンが「Asia Pacific (Tokyo)」でない場合は変更する

4bd6b0f2-3593-3b10-1839-62f3c2f13173.png

[Get started]をクリックする(下図は表示されない場合もある)

スクリーンショット 2017-09-11 17.23.40.png

ポリシーを作成する

スクリーンショット 2017-09-18 01.26.53.png

下表の通り入力して[Create]をクリックする

項目 入力内容
Name AllPubSub
Action iot:*
Resource ARN *
Effect Allow

スクリーンショット 2017-09-18 01.12.18.png

Thingと証明書の作成

Thingを作成する

スクリーンショット 2017-09-18 00.49.51.png

device01を入力して[Create thing]をクリックする

awsiot_test_6.PNG

作成したThingをクリックする

awsiot_test_7.PNG

証明書を作成する

スクリーンショット 2017-09-18 00.55.14.png

クライアント証明書/クライアント秘密鍵/ルート証明書の3種類をダウンロードする(ルート証明書のみ、右クリック -> リンク先を別名で保存)

[Activate](押し忘れ注意!) -> [Attach a policy]の順にクリックする

スクリーンショット 2017-09-18 00.57.16.png

[AllPubSub]を選択して[Done]をクリックする

スクリーンショット 2017-09-18 01.18.49.png

戻る

スクリーンショット 2017-09-18 01.24.11.png

エンドポイントを確認する

歯車マークをクリックするとエンドポイントが表示されるので控えておく

スクリーンショット 2017-09-18 01.38.48.png

[2] RaspiとAWSIoTを接続する

前章で発行した証明書を使って、RaspiとAWSIoTを接続します

使用したRaspiの情報は以下のとおり

  • Raspbian GNU/Linux 8.0 (jessie)
  • Linux raspberrypi 4.4.34

MQTTの実装

MQTTの実装には「mosquitto」を利用します。下記コマンドを実行することでAWSIoTとの通信を行います

  • Pub mosquitto_pubコマンドで光量データを送信
  • Sub mosquitto_subコマンドで光量データを受信

mosquittoのインストール

Raspberry Piのデフォルトリポジトリのmosquitto-clientsはバージョンが古く、TLSのエラーが発生するため最新のmosquitto-clientsをインストールします

$ wget  http://repo.mosquitto.org/debian/mosquitto-repo.gpg.key
$ sudo apt-key add mosquitto-repo.gpg.key
$ wget http://repo.mosquitto.org/debian/mosquitto-wheezy.list
$ sudo cp mosquitto-wheezy.list /etc/apt/sources.list.d/

$ sudo apt-get update
$ sudo reboot

$ sudo apt-get install  mosquitto-clients
$ mosquitto_sub --help |grep version
 mosquitto_sub version 1.4.14 running on libmosquitto 1.4.14.

2017/09/25時点で最新のバージョンは1.4.14です

先ほど発行した証明書(クライアント証明書/クライアント秘密鍵/ルート証明書の3種類)をRasberry Piにコピーします
※この際コピーミスを防ぐために、SFTPなどで確実にコピーするようにしてください。scpでホームディレクトリにファイルを置く例

$ scp file-name pi@IP_ADDRESS:~

AWSIoT MQTTクライアントを開く

[Test]を選択する

awsiot_menu.PNG

[Subscription topic]に「$aws/things/device01/test」と入力する

awsiot_test_1.PNG

[Subscribe to topic]をクリックする

awsiot_test_2.PNG

下記のような画面になると思います

awsiot_test_3.PNG

ラズパイからAWSIoTへのメッセージ送信(publish)

例)トピック「$aws/things/device01/test」に対して文字列「test message」を送信します

$ mosquitto_pub --cafile [ルート証明書] --cert [クライアント証明書] --key [クライアント秘密鍵] -h [AWSIoTのエンドポイント] -p 8883 -q 1 -t '$aws/things/device01/test' -i device01 --tls-version tlsv1.2 -m '{"test": "message"}' -d

例)トピック「$aws/things/device01/test」に対してJSONファイル「data_sample.json」を送信します

data_sample.json
{
  "data": [
    {
      "d1": "0000",
      "d2": "9999",
      "d3": "0003",
      "d4": "0004",
      "d5": "0005",
      "d6": "0006",
      "d7": "0007",
      "d8": "0008",
      "d9": "0009",
      "d10": "0010",
      "serial": "00000001",
      "time": "201706010100"
    }
  ]
}
$ mosquitto_pub --cafile [ルート証明書] --cert [クライアント証明書] --key [クライアント秘密鍵] -h [AWSIoTのエンドポイント] -p 8883 -q 1 -t '$aws/things/device01/test' -i device01 --tls-version tlsv1.2 -f data_sample.json -d

問題なく送信が完了すれば、先ほど開いたテスト画面にデバイスからのメッセージが表示されます
↓こんな感じ(data_sample.jsonを送信した際の結果)

awsiot_test_5.PNG

AWSIoTからのメッセージ受信(subscribe)

例)トピック「$aws/things/device01/test」に対するメッセージを待ち受けます

$ mosquitto_sub --cafile [ルート証明書] --cert [クライアント証明書] --key [クライアント秘密鍵] -h [AWSIoTのエンドポイント] -p 8883 -q 1 -t '$aws/things/device01/test' -i device01 --tls-version tlsv1.2

上記コマンドでsubscribeしている状態で、テスト画面からメッセージをデバイスへ送信します

awsiot_test_4.PNG

問題なければデバイス側のターミナルに、こんなメッセージが表示されます

{
  "message": "Hello from AWS IoT console"
}

Device shadowを更新する

AWSIoTにはすでに予約されているトピックがあり「$aws/things/device01/shadow/update」に対してメッセージを送信するとshadowを更新することができます

shadow_sample.json
{
  "state":
    {
      "desired":
        {
          "status_data": [
            {
              "status1": "1101",
              "status2": "1202",
              "status3": "1303",
              "status4": "1404",
              "status5": "1005",
              "status6": "1006"
            }
          ]
        }
    }
}
$ mosquitto_pub --cafile [ルート証明書] --cert [クライアント証明書] --key [クライアント秘密鍵] -h [AWSIoTのエンドポイント] -p 8883 -q 1 -t '$aws/things/device01/shadow/update' -i device01 --tls-version tlsv1.2 -f shadow_sample.json -d

[3]Lambda経由でDynamoDBにデータをPUTする(おまけ)

前章でデバイスからAWSIoTにメッセージを送信することができるようになりましたので、デバイスからJSONのデータを送信し、それをDynamoDBに格納してみます

簡単な流れとしては
1. AWSIoTのルールを設定し、特定のトピックにメッセージが送信されたらLambdaをキックする
2. Lambdaが受け取ったデータを加工しDynamoDBにPUTする

送信するデータは前章で作成した「data_sample.json」を利用します

Lambdaの作成

関数名「IoT-Lambda-DynamoDB」というLambda関数を作成していきます
今回はRuntimeをNode.js6.10でLambdaを作成しています

Policy

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "logs:CreateLogGroup",
                "logs:CreateLogStream",
                "logs:PutLogEvents"
            ],
            "Resource": "arn:aws:logs:*:*:*"
        },
        {
            "Effect": "Allow",
            "Action": "dynamodb:PutItem",
            "Resource": "arn:aws:dynamodb:ap-northeast-1:xxxxxxxxxx:table/iot-data-lambda"
        }
    ]
}

※DynamoDBにPUTする部分のARNはご利用されているAWSのアカウントIDに書き換えてください

Code(Node.js)

const AWS = require("aws-sdk");
const dynamoDB = new AWS.DynamoDB.DocumentClient({
    region: "ap-northeast-1"
});

exports.handler = (event, context, callback) => {
    const item = event["data"][0];
    item["deviceID"] = event["deviceID"];
    const nowTime = Date.now();
    item["time"] = String(nowTime);

    const params = {
        TableName: "iot-data-lambda",
        Item: item
    };

    dynamoDB.put(params).promise();
};

DynamoDBの作成

LambdaがPUTするDynamoDBを作成します

DynamoDBの情報

項目 入力内容
TableName iot-data-lambda
Primary partition key deviceID (String)
Primary sort key time (String)

AWSIoTルールの作成

  1. AWSIoTコンソール画面左メニューからルール画面に移動し、画面右上の「create」をクリックしてルール作成画面に移動する
  2. 「Name」に任意の文字列を入力する
  3. 「Attribute」「Topic filter」に下記の内容を入力する
項目 入力内容
Attribute *,topic(3) AS deviceID
Topic filter $aws/things/+/data

「topic(3) AS deviceID」と書いておくことで、トピックの3番目をdeviceIDとしてデータの中に含ませて次のActionに渡します
今回の場合はトピックの「+」に入る文字列が、Lambdaに渡されるeventの中にdeviceIDが含まれるようになります

awsiot_test_8.PNG

4. [Set one or more actions]の「Add action」をクリックし、Lambdaを選択、画面下部の[Configure action]をクリック

awsiot_test_9.PNG

awsiot_test_10.PNG

5. 先ほど作成したLambda関数を選択し[Add action]をクリック

awsiot_test_11.PNG

6. 最後に[Create rule]をクリック

これで「IoT_Lambda_DynamoDB」というルールが作成されました
デバイスからトピック「$aws/things/+/data」(+にはdeviceIDとなる文字列を入れる)にメッセージが送信されたときに、Lambdaにデータが渡され、加工してDynamoDBに格納します

実際に実行してみる

デバイスからデータを送信する

$ mosquitto_pub --cafile [ルート証明書] --cert [クライアント証明書] --key [クライアント秘密鍵] -h [AWSIoTのエンドポイント] -p 8883 -q 1 -t '$aws/things/device01/data' -i device01 --tls-version tlsv1.2 -f data_sample.json -d

問題なく処理が実行されればDynamoDBにデータがPUTされる

awsiot_test_12.PNG

さいごに

AWSIoTで発行した証明書ベースでメッセージの送受信ができるようになりました
今回は証明書の発行、デバイスへの証明書のコピーなどすべて手作業で行いましたが、デバイスが複数台になってくるとこんなことしれいられないので、このあたりを自動化するのが必要になってくるなと実感しましたね
そこらへんの自動化を実現してくれたすばらしいお方から資料をいただいたので、試していこうと思います
もし許可をいただいたら記事にまとめようと思いますー

ではまた!

続きを読む