AWS IoTでMQTTプロトコルを使用したNode.js, iOS(Swift)の疎通サンプルがあります。
2016年11月現在動作確認済み。AWS IoT自体は昨年12月にリリースされたものです。備忘録として残しておきます。
##Amazon IoTってなに?
ハードウェアをサポートするバックエンドの仕組みをAWSに素早く、簡単に構築できるクラウドサービス。インターネットに接続されたデバイスとデバイスをつなぎ、安全な双方向性通信を提供。
- MQTT v3.1.1
- QoS 0, 1に対応(2には対応していない)
- Will, Retainに対応していない
→Thing Shadowという状態を検知する仕組みがあるので不要ということ。 - Payloadサイズ128KB
- TLS対応
- WebSocket、WebSecureSocket対応あり(2016/1/28のアップデートにて)
https://aws.amazon.com/jp/about-aws/whats-new/2016/01/aws-iot-now-supports-websockets-custom-keepalive-intervals-and-enhanced-console/ - 認証, 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ファイルを作成
{
"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
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
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.plist
にNSLocationWhenInUseUsageDescription
を追加することを忘れずに。
Swift3.0にて
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に加えてリクエストのバージョンとリクエストのタイムスタンプなどが付与されます。
{
"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
{
"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
{
"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
{
"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/