Edited at

AWS IoTでM2Mことはじめ(iOS, Node.js)

More than 1 year has passed since last update.

AWS IoTでMQTTプロトコルを使用したNode.js, iOS(Swift)の疎通サンプルがあります。

2016年11月現在動作確認済み。AWS IoT自体は昨年12月にリリースされたものです。備忘録として残しておきます。


Amazon IoTってなに?

ハードウェアをサポートするバックエンドの仕組みをAWSに素早く、簡単に構築できるクラウドサービス。インターネットに接続されたデバイスとデバイスをつなぎ、安全な双方向性通信を提供。


プロトコルはMQTT


  • TCP/IP 上で動作するpublish/subscribeモデルに基づく軽量なメッセージプロトコル。

  • 軽量メッセージ配信に特化しており、センサーデータなどに使用される(M2M)、

  • Facebook MessengerもMQTT

  • 256メガバイトが最大

  • 固定長ヘッダーが最小2バイトとオーバーヘッドが少なく、またプロトコルも単純です。そのため、HTTPに比べるとネットワーク帯域および処理速度に優れています。また、処理が少ないということで、消費電力も少なくなっており、モバイル機器にも向いている


キーワード

Topic、QoS、Device gateway, Registry, Thing Shadow, Rules


Getting Started

まずはコンソール上でpub/sub


OSS,MQTT実装のmosquittoクライアントをインストール

npm install mqtt


AWS CLI のアップグレード

IoT Message Broker を操作するには AWS CLI 1.8.12 以上が必要

sudo pip install awscli --upgrade


認証関連

こちらに沿って

http://docs.aws.amazon.com/iot/latest/developerguide/secure-communication.html

cert.json というファイル名で保存

aws iot create-keys-and-certificate --set-as-active > cert.json

jqコマンドがなければ

brew install jq

各情報を保存

cat cert.json | jq .keyPair.PublicKey -r > thing-public-key.pem

cat cert.json | jq .keyPair.PrivateKey -r > private-key.pem

cat cert.json | jq .certificatePem -r > cert.pem

ルートCAをシマンテックサイトから取得

curl -o rootCA.pem https://www.symantec.com/content/en/us/enterprise/verisign/roots/VeriSign-Class%203-Public-Primary-Certification-Authority-G5.pem


IAM Roleの設定

MQTT ブローカーに pub/sub するための IAM Role を Certification に設定

iot サービスを操作できる "PubSubToAnyTopic" という名前のポリシーを作成

以下jsonファイルを作成


policy.json

{

"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Action":["iot:*"],
"Resource": ["*"]
}]
}


ポリシーをプリンシパルにひも付けます。 プリンシパルとなるのは aws iot create-keys-and-certificateコマンドを実行した時の certificateArn です

aws iot attach-principal-policy --principal "arn:aws:iot:ap-northeast-1:1111111:cert/SNIP" --policy-name "PubSubToAnyTopic"


準備完了

pub/sub通信が出来る状態となりました。


MQTT エンドポイントの確認

aws iot describe-endpointでAWSアカウントごとに異なるエンドポイントの確認

$ aws iot describe-endpoint

{
"endpointAddress": "HOGE.iot. northeast-1.amazonaws.com"
}


Subscribe

mosquitto_sub --cafile rootCA.pem --cert cert.pem --key private-key.pem -h "HOGE.iot.ap-northeast-1.amazonaws.com" -p 8883 -q 1 -d -t topic/test -i clientid1

Client clientid1 sending CONNECT
Client clientid1 received CONNACK
Client clientid1 sending SUBSCRIBE (Mid: 1, Topic: topic/test, QoS: 1)
Client clientid1 received SUBACK
Subscribed (mid: 1): 1


Publish

mosquitto_pub --cafile rootCA.pem --cert cert.pem --key private-key.pem -h "HOGE.iot.ap-northeast-1.amazonaws.com" -p 8883 -q 1 -d -t topic/test -i clientid2 -m "Hello, World"

Client clientid2 sending CONNECT
Client clientid2 received CONNACK
Client clientid2 sending PUBLISH (d0, q1, r0, m1, 'topic/test', ... (12 bytes))
Client clientid2 received PUBACK (Mid: 1)
Client clientid2 sending DISCONNECT


確認

Subscribe側でHello, Worldの確認

Client clientid1 received PUBLISH (d0, q1, r0, m1, 'topic/test', ... (12 bytes))

Client clientid1 sending PUBACK (Mid: 1)
Hello, World

とpublishした後、メッセージがHello, Worldと表示されていればOKです。


Node.jsでやってみる

AWS IoT SDK for JavaScriptのインストール

npm install aws-iot-device-sdk

先ほどのmqtt.jsのラッパーでクライアントインスタンスを経由して、

デバイスとAWS IoTをセキュアに接続します。mqttを意識することなく使えます。

deviceクラスとthingShadowクラスがあります。

詳細はこちら

https://github.com/aws/aws-iot-device-sdk-js


Subscribe


deviceSub.js


var awsIot = require('aws-iot-device-sdk');
var fs = require('fs');

// 先ほど生成した認証ファイルを指定
var KEY = __dirname + '/private-key.pem';
var CERT = __dirname + '/cert.pem';
var TRUSTED_CA = __dirname + '/rootCA.pem';

var device = awsIot.device(
{
keyPath: KEY,
certPath: CERT,
caPath: TRUSTED_CA,
clientId: 'clientid2',
region: 'ap-northeast-1'
});

device
.on('connect', function() {
console.log('connect');
device.subscribe('topic_1', {'qos': 1});
});

device
.on('message', function(topic, payload) {
console.log('message', topic, payload.toString());
});



Publish


devicePub.js


var awsIot = require('aws-iot-device-sdk');
var fs = require('fs');

// 先ほど生成した認証ファイルを指定
var KEY = __dirname + '/private-key.pem';
var CERT = __dirname + '/cert.pem';
var TRUSTED_CA = __dirname + '/rootCA.pem';

var device = awsIot.device(
{
keyPath: KEY,
certPath: CERT,
caPath: TRUSTED_CA,
clientId: 'clientid2',
region: 'ap-northeast-1'
});

device
.on('connect', function() {
console.log('connect');
device.publish('topic_1', 'Hello mqtt\n\n', {'qos': 1});
});


node deviceSub.jsを立ち上げて、

別画面で

node devicePub.jsを打つと、

Sub側で

connect

message topic_1 Hello mqtt

とmessegeが表示されていればOK


iOSでやってみる

流れとしては、先ほど作成した証明書3つのプロジェクト内にコピーし、

クライアントを利用して指定したトピックにpublishするものです。

今回の例は、GPS情報(json)を送信と、ボタンアクションで文字列の送信を行います。


iOS向けMQTTクライアント

Swiftで書かれたMQTTクライアントMoscapsule

https://github.com/flightonary/Moscapsule

こちらを使うと X.509 形式の証明書を利用した MQTT 通信が行えます。OpenSSL-Universalも依存しているので、こちらも一緒にインポート。

use_frameworks!

target 'MQTTSample' do
pod 'Moscapsule', :git => 'https://github.com/flightonary/Moscapsule.git'
pod 'OpenSSL-Universal', '~> 1.0.1.l'
pod 'SwiftyJSON'
end


証明書のインポート

Xcodeプロジェクトに先ほど用意した証明書、Privateキーファイル、ルートCAファイルをインポートします。


iOSからTopicに向けてPublish

位置情報を利用するため、Info.plistNSLocationWhenInUseUsageDescriptionを追加することを忘れずに。

Swift3.0にて


ViewController.swift

import UIKit

import Moscapsule
import SwiftyJSON
import CoreLocation

class ViewController: UIViewController, CLLocationManagerDelegate {

var manager: CLLocationManager?
var mqttClient: MQTTClient?
var timer:Timer?

@IBOutlet weak var latLb: UILabel!
@IBOutlet weak var lngLb: UILabel!

override func viewDidLoad() {
super.viewDidLoad()
initMQTTClient()

manager = CLLocationManager()
manager?.delegate = self
// 最高精度
manager?.desiredAccuracy = kCLLocationAccuracyBestForNavigation
manager?.pausesLocationUpdatesAutomatically = false
manager?.allowsBackgroundLocationUpdates = true

// 「アプリ使用時のみ許可」でなかったら、ダイアログを出す。
if CLLocationManager.authorizationStatus() != CLAuthorizationStatus.authorizedAlways {
manager?.requestAlwaysAuthorization()
}

manager?.startUpdatingLocation()
}

func getGpsData() {

let location = manager?.location
let coordinate = location!.coordinate
let lat = coordinate.latitude.description
let long = coordinate.longitude.description

print("緯度:\(lat) 経度:\(long)")

let json: JSON = [
"state": [
"reported": [
"location": [
"latitude": coordinate.latitude,
"longitude": coordinate.longitude
]
]
]
]

publishTopic(json)
}

func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
if status == .authorizedWhenInUse {
manager.startUpdatingLocation()
}
}

// 位置情報の更新で MQTT のトピックに送信
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {

let location = locations.first!
let json: JSON = [
"state": [
"reported": [
"location": [
"latitude": location.coordinate.latitude,
"longitude": location.coordinate.longitude
]
]
]
]

latLb.text = location.coordinate.latitude.description
lngLb.text = location.coordinate.longitude.description

print("緯度:\(location.coordinate.latitude.description) 経度:\(location.coordinate.longitude.description)")
publishTopic(json)
}

// MQTT クライアントの初期設定
func initMQTTClient() {
moscapsule_init()
let mqttConfig = MQTTConfig(clientId: "server_cert_test",
host: "HOGE.iot.ap-northeast-1.amazonaws.com", port: 8883, keepAlive: 60)
let certFile = Bundle.main.path(forResource: "cert.pem", ofType: "crt")
let keyFile = Bundle.main.path(forResource: "private-key.pem", ofType: "key")
let caFile = Bundle.main.path(forResource: "rootCA", ofType: "pem")
mqttConfig.mqttServerCert = MQTTServerCert(cafile: caFile, capath: nil)
mqttConfig.mqttClientCert = MQTTClientCert(certfile: certFile!, keyfile: keyFile!, keyfile_passwd: nil)
mqttClient = MQTT.newConnection(mqttConfig)
}

// Device Shadow トピックに向けた Publish
func publishTopic(_ json: JSON) {
let data = try! json.rawData()
guard let mqttClient = mqttClient else { return }
mqttClient.publish(data, topic: "topic_1", qos: 1, retain: false)
// device shadowの場合、別途後述
// mqttClient.publish(data, topic: "$aws/things/iPhone-6/shadow/update", qos: 1, retain: false)
}

@IBAction func pushPublish(_ sender: AnyObject) {
guard let mqttClient = mqttClient else { return }
mqttClient.publish(string: "iOSからのpublish!!!", topic: "topic_1", qos: 1, retain: false)
}

func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
print("Failure")
}

}



Subscribe

こちらはさきほどの

node deviceSub.js

で待ち受けていればOK


結果


iOSアプリから位置情報が更新されたら

message topic_1 

{
"state": {
"reported": {
"location": {
"longitude": 135.7848895,
"latitude": 35.0115832
}
}
}
}

pushPublishボタンアクションで

message topic_1 iOSからのpublish!!!

上記それぞれ表示されればOK


ここまではただのPub/Sub

ここまではただのMQTTブローカーです。


ここからが真骨頂 Thing Shadows

デバイスのことをThingと呼びます。

Thing Shadow(デバイスの影像)はThingの状態がAWS上に存在するもの

デバイスはオフラインになったり、状態が変更されるケースがあるので、アプリから管理するためにThing Shadowが存在する。永続的な仮想バージョンを作成できる。

retainが不要なのは、Thing Shadowがあるから。

retainというのは最後にPublishされたメッセージをMQTTサーバーが保持しておき、新しいSubscriberにそのメッセージを渡す機能

ThingとThing Shadowは一対一で。Thingに変化があれば、Thing Shadowにも変更が通知される。

reportedがThings、desiredがThing Shadowsの状態を示す


Thing作成

AWS CLIからも可能ですが、

①AWSコンソールからAWS IoTを開きます

②Resources横の Create a Resource を選択

③パネルが展開されてCreate Thingを選択、Nameを入力後、Create

④下のthings一覧から先ほど作成したthingを選択,右サイドパネルが展開されて、

Connect a deviceを選択

⑤Connect a deviceの画面でサポートするSDKの一覧から NodeJS を選択

Generate certificate and policy を選択


  • Download public key

  • Download private key

  • Download certificate

上記3つのファイルをダウンロード

Confirm & Start Connecting を選択

⑧以下のようなJSONが表示されるので。コピー

{

"host": "HOGE.iot.ap-northeast-1.amazonaws.com",
"port": 8883,
"clientId": "hoge",
"thingName": "hoge",
"caCert": "root-CA.crt",
"clientCert": "dca2683d89-certificate.pem.crt",
"privateKey": "dca2683d89-private.pem.key"
}

⑨root-CA.crtというファイルをこちらのSymantecのページから取得

https://www.symantec.com/content/en/us/enterprise/verisign/roots/VeriSign-Class%203-Public-Primary-Certification-Authority-G5.pem

⑩ダウンロードした3つのファイル、JSON、root-CA.crtを同階層に置きます。

これで準備で完了


AWS CLIでThing Shadow


更新

デバイス iPhone-6 の状態を初期登録

aws iot-data update-thing-shadowコマンドで状態の更新

ルート階層のstateは固定で、その下のreported以下に自由に要素を定義することができる。

$ aws iot-data update-thing-shadow --thing-name iPhone-6  --payload '{"state": {"reported" : {"power" : "off"}}}'   outfile.json

レスポンスにはstateに加えてリクエストのバージョンとリクエストのタイムスタンプなどが付与されます。


outputfile.json

{

"state": {
"reported": {
"power": "off"
}
},
"metadata": {
"reported": {
"power": {
"timestamp": 1450529567
}
}
},
"version": 1,
"timestamp": 1450529567
}

パワーオンで更新

$ aws iot-data update-thing-shadow --thing-name iPhone-6  --payload '{"state": {"desired" : {"power" : "on"}}}'   outfile2.json


outputfile2.json

{

"state": {
"desired": {
"power": "on"
}
},
"metadata": {
"desired": {
"power": {
"timestamp": 1450529706
}
}
},
"version": 2,
"timestamp": 1450529706
}


desiredがpower onになっています。

状態情報の確認 aws iot-data get-thing-shadowコマンドにて

$ aws iot-data get-thing-shadow --thing-name iPhone-6 outfile3.json


outputfile3.json

{

"state": {
"desired": {
"power": "on"
},
"reported": {
"power": "off"
},
"delta": {
"power": "on"
}
},
"metadata": {
"desired": {
"power": {
"timestamp": 1450529706
}
},
"reported": {
"power": {
"timestamp": 1450529567
}
}
},
"version": 2,
"timestamp": 1450529787
}

desired(thing shadow), reported(device)の状態で、

deltaはstate.deltaにはデバイスの状態と望ましい状態の差分が抽出される。

デバイス側でのパワーオン処理をしたと仮定して、パワーオンにしてみる。

$ aws iot-data update-thing-shadow --thing-name iPhone-6 --payload '{"state": {"reported" : {"power" : "on"}}}' outfile3.json

再度状態情報を確認

$ aws iot-data get-thing-shadow --thing-name iPhone-6 outfile4.json


outfile4.json

{

"state": {
"desired": {
"power": "on"
},
"reported": {
"power": "on"
}
},
"metadata": {
"desired": {
"power": {
"timestamp": 1450529706
}
},
"reported": {
"power": {
"timestamp": 1450529951
}
}
},
"version": 3,
"timestamp": 1450529989
}

deltaが無くなり、差分が無くなっていることがわかる。

versionは3となっているが、更新リクエストによってAWS側で自動でインクリメントされる。

これは古いバージョンへ先祖還りを防いだり、他のpublisherによる更新を検知する手段となりうる。

$ aws iot-data update-thing-shadow --thing-name iPhone-6 --payload '{"state": {"reported" : {"power" : "on"}},"version" : 2}' outfile.json

A client error (ConflictException) occurred when calling the UpdateThingShadow operation: Version conflict

更新リクエストに"version" : 2の指定を入れたところ、既にバージョン3になっているため更新が拒否される

現在のバージョンの確認 jqで抜き出し

$ aws iot-data get-thing-shadow --thing-name iPhone-6 outfile4.json | cat outfile4.json | jq .version 

3が出力される。


ルールの作成

①Create a rule、ルールの登録

Rules Engineを通して必要なデータのみをフィルタリングしてデータのやり取りが可能

queryをこのように

SELECT * FROM 'topic_1'

記述して



topic_1にトピックを送信→S3にストアという流れを作ります。

今回はS3にデータを送りたいので「Store the message in a file and store in the cloud (S3)」を選択して、Bucket等の設定を行います。

iphone6-location-${timestamp()}

で保存して、順次GPSデータが保存されていきます。

${timestamp()}なしであれば、上書き保存します。

正しくデータが送信されれば「iphone6」というファイルができているはず

AWS IoTを接続してS3にデータを送る一連の流れの説明


おわりに

AWS IoTでM2Mことはじめはこれで終わりです。

実装のポイントとしては


・ひとまず疎通確認のためにはS3への登録を選んでおいて、疎通確認後に本当に渡したいサービスを登録する

・AWS IoTはQoS(Quality of Service)レベルの2が選択できないので複数投げられてもデータが重複しないような冪等性を確保した設計にするとよい

・連続したデータはTopicにMQTTデータを投げる形で実装し、単発データや選択データ(ランプがつく/消える、や電話をかける、等)はSHADOWSで管理したほうがよい

となります。入力部、出力部のデバイスの実装はなるべくシンプルにし、難しい処理はAWS内で片付けたほうが変更に強い実装ができます。例えばデータの丸め等はデバイス部では行わず、AWS IoTからLambda等を繋いでそこで行ったほうがよいです

(http://dev.classmethod.jp/cloud/aws/cm-advent-calendar-2015-getting-started-again-iot/)


things shadowの状態を監視して、他のデバイスを動かすという展開が考えられます。

http://dev.classmethod.jp/cloud/update-device-shadow-by-lambda/


参考

MQTTについてのまとめ

http://tdoc.info/blog/2014/01/27/mqtt.html

AWS IoT Message BrokerのMQTTでpub/subをやってみた #reinvent

http://dev.classmethod.jp/cloud/aws/pub-sub-with-aws-iot-over-mqtt/

AWS IoTとRuby製MQTTクライアントでPub/Subしてみた

http://qiita.com/hiroeorz@github/items/f933ad1158a08506922a

Message Broker for AWS IoT (Beta)

http://docs.aws.amazon.com/iot/latest/developerguide/iot-message-broker.html

AWS IoT SDK for JavaScript

https://github.com/aws/aws-iot-device-sdk-js

The MQTT client for Node.js and the browser

https://github.com/mqttjs/MQTT.js

AWS IoT の Device Shadow を iOS アプリから MQTT で使ってみた #reinvent

http://dev.classmethod.jp/cloud/aws/aws-iot-mqtt/

AWS IoTおよびThing Shadowsに関する雑感

http://tdoc.info/blog/2015/10/09/thing_shadows.html

AWS IoTのThing Shadowsを図と実行例で理解する #reinvent

http://dev.classmethod.jp/cloud/aws-iot-things-shadow/

これからAWSを使ってIoTをやってみたい人が抑えておくべき10のキーサービス & 7つのキーワード #reinvent

http://dev.classmethod.jp/cloud/aws/aws-key-service-people-wanted-to-do-iot-should-study/

AWS IoTのいろいろなルールを見てみる&ちょっと試してみる #reinvent

http://dev.classmethod.jp/cloud/aws-iot-rules/

【新機能】AWS IoT のRules EngineがAmazon machine Learningをサポート。IoTと機械学習が一体に

http://dev.classmethod.jp/cloud/aws/aws-iot-supports-integration-with-amazon-machine-learning/