Node.js
AWS
RaspberryPi
awsIoT

初めてのAWS IoTでDeviceSDK(Node.js)を使ってラズパイのLEDチカチカ

More than 1 year has passed since last update.

AWS IoTを使ったことがなかったので使ってみたときのメモ。

AWS IoT DeviceSDK(Node.js)を使って、MQTTでクライアント(Mac)からAWS IoT経由でThing(ラズパイ)に接続されたLEDをチカチカさせてみます。


参考


環境


  • クライアント(Mac OSX 10.11.4)

  • ラズパイ3

  • AWS IoT DeviceSDK for Node.js

ラズパイのLEDでのチカチカは以前書いたものを参考にしてください。

Node.jsでラズパイに接続したLEDを点灯、消灯させる(Lチカ)


今回はAWS IoTのどの部分を使うのか

AWS IoTは様々な構成要素があります。

AWS IoT プラットフォームの仕組み

今回は以下を使ってみます。


  • AWS IoTデバイスSDK->AWS IoTで用意するデバイスゲートウェイとの暗号化通信を行う際に利用。MQTT,HTTP,WebSocketプロトコルが使えるようですが、今回はMQTTプロトコルを使います。SDKはC,JavaScript及びArduinoがサポートされており、JavScript(Node.js)を利用

  • デバイスゲートウェイ->IoTやデバイスのエンドポイントとして利用。MQTTを使っているのでMQTTブローカーとして利用。10億以上のデバイスにも対応できるように自動的にスケールするらしい。すごい

  • 認証と認可->IoTとデバイスゲートウェイ間の通信はインターネット経由となります。上記より、多くの場合で通信の暗号化が必要。また、相互認証(偽者のデバイスからのデータではない)の機能も必要です。そこでAWS IoTでは暗号化にTLSを利用し、認証ではAWSの認証メソッド(SigV4)を使うことで実施するものです。今回の例でもIoTとデバイスゲートウェイ間の認証と認可をしていますが、AWSデバイスSDKでラップしているのでコード自体が複雑になることはありませんでした。また、証明書や公開鍵・秘密鍵の生成や管理をAWS IoTで実施してくれます。

他にもデバイスゲートウェイに送信されたデータのフィルタリングや他のAWSリソースとの連携を行う「ルールエンジン」機能や、デバイスがオフラインの状態からオンライン状態に復帰した際にデータの同期を行う事が出来る「デバイスシャドウ」という機能もありますが、今回は利用しませんでした。


AWS IoTの設定をする

まず初めにAWS IoTの設定をマネージメントコンソールを使って行います。

こちらについては以下を参考にやってみました。

Getting Started with AWS IoT on Raspberry Pi and the AWS IoT Device SDK for Node.js


thing(デバイス)の登録

最初にthing(デバイス)の登録を行います。

thingとは具体的にはラズベリーパイであったり、AWS IoTと接続する 何らかのデバイス という意味だと思われます。

とりあえずマネージメントコンソール画面を開きます。

Screen Shot 2016-04-03 at 7.32.35 AM.png

「Get started」を選択します。

次の画面でthinsを任意の名称で登録します。

Screen Shot 2016-04-03 at 7.35.50 AM.png

名称を設定する必要があります。

とりあえず「MyNewThing」ということで登録しましょう。

入力後、「Create」を選択します。

これでthingが登録されました。

次の画面で「View Thing」を選択することでthingごとのREST APIのエンドポイントなどが確認できます。

Screen Shot 2016-04-03 at 7.41.08 AM.png

thing.png


キーペア(公開鍵、秘密鍵)のダウンロード及び証明書のダウンロード

AWS IoTでは通信を行う際にTLSによる暗号化を行って通信を行います。

TLS通信の際に必要な証明書やキーペアのダウンロードを行います。

先ほど登録したMyNewThingの詳細を表示している画面の右下の「Connect device」というボタンを押下します。

thing.png

次の画面ではthingとAWS IoTとの通信は何を使って行うかの確認画面が表示されます。

今回はNode.jsのDeviceSDKを使うので「Node.js」をチェックします。

Screen Shot 2016-04-03 at 7.50.56 AM.png

選択後、上記画面のように「Generate certificate and policy」というボタンが表示されるので選択して証明書とポリシーを作成します。

選択後、少し待つと以下のような画面が表示されます。

Screen Shot 2016-04-03 at 7.56.06 AM.png


  • 公開鍵のダウンロード

  • 秘密鍵のダウンロード

  • 証明書のダウンロード

ができます。

証明書はいつでも取得できるようですが、公開鍵、秘密鍵はこのページのみしかダウンロードできないようなので忘れずにダウンロードしておきます。

各リンクをクリックし、Macにダウンロードしておきます。

自分の場合、それぞれ以下のようなファイル名となっておりました。


  • xxxxx-certificate.pem.crt->証明書

  • xxxxx-private.pem.key->秘密鍵

  • xxxxx-public.pem.key->公開鍵

opensslコマンドを使ってデコードでして詳細も確認できます。

# X.509証明書をデコード

$openssl x509 -text < xxxx-certificate.pem.crt
Certificate:
Data:
Version: 3 (0x2)
Serial Number:
xxxxxx
Signature Algorithm: sha256WithRSAEncryption
Issuer: OU=Amazon Web Services O=Amazon.com Inc. L=Seattle ST=Washington C=US
Validity
Not Before: Apr 2 22:53:28 2016 GMT
Not After : Dec 31 23:59:59 2049 GMT
Subject: CN=AWS IoT Certificate
Subject Public Key Info:
Public Key Algorithm: rsaEncryption
Public-Key: (2048 bit)
(以下略)

上記より以下となっているようですね。


  • 公開鍵暗号方式に2048bitのRSA

  • ハッシュ関数にSHA-256


ルート証明書のダウンロード

ルート証明書

SSL/TLS通信ではサーバーから取得したデジタル証明書が正当なものであるか確認する必要があります。

デジタル証明書が正しいかについては「正当な認証局から発行されたものであれば正しい」ということで証明されます。

具体的にamazon.co.jpを例に確認してみます。

ブラウザから証明書情報を確認すると以下の階層構造となっているのが分かります。

Screen Shot 2016-04-03 at 8.45.44 AM.png

ということが分かります。

じゃあ最後にベリサインの証明書は何を持って正当であると判断するかというとここで登場するのがルート証明書になります。

つまり


  • ベリサインの証明書はルート証明書から発行された(なので最終的にwww.amazon.co.jpは正当なもの)

ということになります。

幾つかの認証局はルート証明書としてブラウザなどに最初から登録されているため、我々が通常見るようなサイトを閲覧する際にわざわざルート証明書をダウンロードする必要はありません。

ただし、今回は通信をするのがブラウザではないため、対象のルート証明書だけ予めダウンロードする必要があります。

ということで話が逸れましたが、ルート証明書をダウンロードしておきます。

# root-CA.crtというファイル名でルート証明書を取得

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


Mac側でPubulishの実装をする

最初にMac側でAWS IoTで用意したMQTTブローカーへLEDのON/OFFの設定を定期的に送信するコードを書きます。

作成にはAWS DeviceSDKのNode.jsを利用します。

aws/aws-iot-device-sdk-js

まずは上記をインストールします。

$npm install aws-iot-device-sdk

上記ディレクトリに以下のようなコードを配置します。


publish.js

// AWS IoT DeviceSDKの利用

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

// 秘密鍵、証明書などの設定
var device = awsIot.device({
region: 'ap-northeast-1',
clientId: 'MyNewThing-client',
privateKey: 'certs/private.pem.key',
clientCert: 'certs/certificate.pem.crt',
caCert: 'certs/root-CA.crt',
});

var count = 0;

// 通信確立した際に呼び出されるイベント
device.on('connect', function() {
console.log('connect');
setInterval( function() {
count++;
var led = count % 2;
console.log(led.toString());
device.publish('topic_1', led.toString());
}, 1000);
});


setInterval関数を使って周期的にtopic_1というトピックに対して0もしくは1を送信します。

なお、awsIot.deviceクラスのコンストラクタで設定するリージョンや証明書、秘密鍵のパスは適宜変更ください。


ラズパイ側でsubscribeの実装をする

ラズパイ側でのNode.js環境の構築やどうやってラズパイからLEDのON/OFFをするかについては以下を参照ください。

Node.jsでラズパイに接続したLEDを点灯、消灯させる(Lチカ)

ラズパイ側でも同じようにAWS IoT DeviceSDKを使ってpublishされたタイミングでLEDをON/OFFにするコードを書きます。

なお、秘密鍵、証明書、ルート証明書はMacからscpなどでコピーするか別途他のものを用意しておきます。

同じようにnpmで必要なライブラリをインストールします。

$npm install aws-iot-device-sdk

$npm install sleep

コードを書きます。


subscribe.js

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

var fs = require('fs');
var sleep = require('sleep');
var pin = 4;

var device = awsIot.device({
region: 'ap-northeast-1',
clientId: 'MyNewThing',
privateKey: 'certs/private.pem.key',
clientCert: 'certs/certificate.pem.crt',
caCert: 'certs/root-CA.crt',
});

// 通信確立時
device.on('connect', function() {
console.log('connect');
device.subscribe('topic_1');
fs.writeFileSync('/sys/class/gpio/export', pin);

// すぐに書き込もうとすると権限エラーのためsleepさせる
sleep.sleep(1);
fs.writeFileSync('/sys/class/gpio/gpio' + pin + '/direction', 'out');
});

// subscribeしたトピックにpublishされた時のイベント
device.on('message', function(topic, payload) {
console.log('message', topic, payload.toString());

// 1の時はLEDをON.0の時はLEDをOFF
fs.writeFileSync('/sys/class/gpio/gpio' + pin + '/value', payload.toString());
});

process.stdin.resume();

// Ctrl+Cによって終了する場合の処理
process.on('SIGINT', function() {
console.log('receive SIGINT signal');
fs.writeFileSync('/sys/class/gpio/unexport', pin);
process.exit();
});


なお、clientIdについてはMac側と違うので注意してください。(clientIdは重複不可のため、どちらかで接続が確立されるともう一方の接続が切れるため)


LEDのチカチカ(Lチカ)をやってみる

ではやってみましょう。

先にラズパイ側の実行をして、topic_1というトピックのsubscribeをします。

$node subscribe.js

connect

次にMac側のコードを動かし、対象トピックに対して0と1の情報を定期的に送信する(publish)コードを実行します。

$node publish.js

node publish.js
connect
1
0
1
・・・

うまくいけば上記のように1,0,1,0...と標準出力に表示されます。

また、ラズパイ側も確認するとメッセージ受信時に標準出力をしています。

message topic_1 1

message topic_1 0
message topic_1 1
message topic_1 0
・・・

また、0の受信タイミングでは消灯、1の受信タイミングではLEDが点灯することも確認できるかと思います。

どちらも終了はCtrl+Cによって終了します。


AWS IoTの削除

最後に作成したAWSのリソースを削除します。

とその前に少し現在の状態を整理します。

現在は以下の3つのリソースがあります。


  • Thing(デバイス)

  • Policy(ポリシー)

  • Certicficate(証明書)

そしてそれぞれ以下の関連があります。


  • ThingにCertificateが紐づいている

  • PolicyにCertificateが紐づいている

なのでそれぞれをいきなり削除しようとするとエラーとなってしまうので、それぞれの紐づいた内容を解消しつつ、削除する必要があります。

マネージメントコンソールでのやり方が不明だったのでAWS CLIを使ってやってみます。

まずはそれぞれの情報(IDやARNなど)を確認します。

# thing一覧

$aws iot list-things
{
"things": [
{
"attributes": {},
"thingName": "MyNewThing"
}
]
}

# Policy一覧
$ aws iot list-policies
{
"policies": [
{
"policyName": "MyNewThing-Policy",
"policyArn": "arn:aws:iot:ap-northeast-1:xxxx:policy/MyNewThing-Policy"
}
]
}

# Certificate一覧
$aws iot list-certificates
{
"certificates": [
{
"certificateArn": "arn:aws:iot:ap-northeast-1:xxxx:cert/xxxxxx",
"status": "ACTIVE",
"creationDate": 1459740701.304,
"certificateId": "xxxxxx"
}
]
}

最初にポリシーを削除します。

# ポリシーから証明書をデタッチする

$aws iot detach-principal-policy --policy-name MyNewThing-Policy --principal arn:aws:iot:ap-northeast-1:xxxxxx:cert/xxxxxx

# 削除
$aws iot delete-policy --policy-name MyNewThing-Policy

次に証明書を削除します。

ステータスの変更も行っています。

# thingとprincipalの関連を削除する.principalにはcertificateのARNを指定

$aws iot detach-thing-principal --thing-name MyNewThing --principal arn:aws:iot:ap-northeast-1:xxxxx:cert/xxxxx

# 証明書の状態をACTIVEからINACTIVEに更新
$aws iot update-certificate --certificate-id xxxxxxxx--new-status INACTIVE

# 証明書の削除
$aws iot delete-certificate --certificate-id xxxxxxxxxxxxxxxx

最後にthingを削除します。

# thingの削除

$ aws iot delete-thing --thing-name MyNewThing

これでOKです!

お疲れ様でしたー!