前回の記事で、PC上でセンサーの値が取得できることが確認できたので、今回はクラウドのAWS上にデータ連携していきます。前回の記事は以下からご覧ください。
4. AWSとの連携
4-1. AWS IoT Coreの設定
前回作成したクライアントプログラムからのデータをAWS IoT Coreで受け取れるように、AWSコンソールにログインしてAWS IoT Coreの設定します。
4-1-1. 証明書の作成
AWS IoT Coreの左のメニューから『安全性』→『証明書』をクリックし、画面右上の『作成』ボタンを押下します。
『1-Click証明証作成(推奨)』にある『証明書の作成』ボタンを押下します。
『1-Click証明書作成(推奨)』にある『証明書の作成』ボタンを押下します。『証明書が作成されました!』という画面が表示されるので画面内の「このモノの証明書」、「パブリックキー」、「プライベートキー」それぞれの『ダウンロード』ボタンを押下して各キーをダウンロードします。
また、AWS IoT のルートCAのダウンロードが必要となるので、AWS IoT のルートCA『ダウンロード』リンクに行き、ページ内の「RSA 2048 ビットキー: Amazon ルート CA 1」のリンクよりルートCAのキーも併せてダウンロードしておきます。
上記が完了したら、『証明書が作成されました!』の画面の左下にある『有効化』ボタンを押下します。
全部で4つのキーがダウンロードされているので、上記のプログラムが格納されているフォルダ配下にcertsというフォルダを作成し、その中に配置しておきます。
4.1.2 ポリシーの作成
次に、AWS IoT Coreと接続するためのポリシーを作成と上記で作成した証明書とのアタッチを行います。
AWS IoT Coreの左のメニューから『安全性』→『ポリシー』をクリックし、画面右上の『作成』ボタンを押下します。
入力項目があるのでそれぞれ以下のように入力して画面右下の『作成』ボタンを押下します。
項目 | 設定値 |
名前 | sat-iot-aws-demo (任意の文字列でOK) |
アクション | iot:* |
リソースARN | * (本来はリソースを絞る必要があるが今回は*とした) |
効果 | 『許可』をチェック |
再度、AWS IoT Coreの画面に戻り、左メニューの『安全性』→『証明書』をクリックし、上記で作成した証明書の行の左端のチェックをONにします。
画面、右上の『アクション』より『ポリシーのアタッチ』を押下して、上記で作成したポリシー(ここでは、sat-iot-aws-demo)を選択して、『アタッチ』を押下します。
4.1.3 アクションルールの作成
ここまでで、センサーとIoT Core側の接続が可能になったので、次に、AWS側でデータ受信した際のアクションルールの設定を行います。アクションルールを設定することで、IoT Coreでデバイスから受信した後の動作を定義することが可能になります。
今回は、AWS IoT Coreが受信した後に、Lambdaを呼び出して、CloudWatchのメトリクスに登録していくシナリオを立ててみました。
まずは、AWS IoT Coreが受けた際に呼び出されるLambdaを作成していきます。Lambda内ではCloudWatchのメトリクスの登録を行うプログラムを以下のように作成してあり、名前を『sat-IoTCoreActtionRuleLambda』として作成しました。
(Lambdaの作成に必要なIMAロールやLambda自体の作成手順は省略してありますのでご了承ください)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 |
var aws = require('aws-sdk'); // AWS SDKモジュール var momentTz = require('moment-timezone'); // moment-timezone.js用ライブラリ var moment = require('moment'); // moment.js用ライブラリ /** * エントリーポイント * → リクエストが送られてきたときのイベントハンドラー */ exports.handler = async (event) => { console.log("start."); console.log("event : [" + JSON.stringify(event) + "]"); // センサーからの値設定 const metriscNamespace = 'SATIoTMetrics'; const metriscName = 'SATIotMetrics-Prism' + event['proofid_u12bit']; const unixtime = event['unixtime']; const ts = moment.unix(event['unixtime']); const ts_tz = momentTz.unix(event['unixtime'], 'X').tz('Asia/Tokyo').format('YYYY-MM-DD HH:mm:ssZ'); const temperature_value = event['temperature_value']; const temperature_unit = event['temperature_unit']; const humidity_value = event['humidity_value']; const humidity_unit = event['humidity_unit']; // debug console.log("metrics : namespace [" + metriscNamespace + "] name : [" + metriscName + "]"); console.log("timestamp : ISO8601 [" + ts.toISOString() + "] unixtime [" + unixtime + "] tz [" + ts_tz + "]"); console.log("temperature : val - " + temperature_value + ", unit - " + temperature_unit + ""); console.log("humidity : val - " + humidity_value + ", unit - " + humidity_unit + ""); // cloudwatchメトリクスのパラメータ設定 const metricsParam = { MetricData: [ { MetricName: metriscName, // メトリクス名 Dimensions:[{ Name: "DataType", Value: "temperature" }], // DataType:温度 Timestamp: ts.toISOString(), // タイムスタンプ(ISO8601形式) Unit: 'None', // 単位(℃がないのでNone) Value: Number(temperature_value) // センサーの値(温度) }, { MetricName: metriscName, // メトリクス名 Dimensions:[{ Name:"DataType", Value:"humidity" }], // DataType:湿度 Timestamp: ts.toISOString(), // タイムスタンプ(ISO8601形式) Unit: 'None', // 単位(%はあるけど、温度に合わせてNone) Value: Number(humidity_value) // センサーの値(湿度) } ], Namespace: metriscNamespace // カスタム名前空間 }; // cloudwatchメトリクスの登録 const cloudwatch = new aws.CloudWatch({ region: 'ap-northeast-1' }); let responseBody = []; // レスポンスの body(cloudwatch.putMetricsData 結果保持) let metricsData = {}; try { metricsData = await cloudwatch.putMetricData( metricsParam ).promise(); responseBody.push({ 'name' : metriscName, 'data' : metricsData }); console.log("cloudwatch.putMetricData success ! [" + JSON.stringify(metricsData) + "]"); } catch (e) { console.log("cloudwatch.putMetricData error ! [" + e.message + "]"); throw new Error(e); } // 正常終了 console.log("end."); const response = { // status : 200(Ok) / 500(Error) statusCode: 200, body: responseBody, }; return response; }; |
なお、Lambda側のインベントではAWS IoT Coreから(元はセンサー側)以下のようなデータが引数で送信される前提で作成してありますので、ご注意ください。
1 2 3 4 5 6 7 8 |
{ "unixtime": 1568883351, // unixtime "proofid_u12bit": "EF8E", // ProofIDの下12bit "temperature_value": "27.12", // センサーの温度 "temperature_unit": "℃", // 温度の単位 "humidity_value": "54.23", // センサーの湿度 "humidity_unit": "%" // 湿度の単位 } |
AWS IoT Coreの左メニューの『ACT』で表示される画面の右上の『作成』ボタンを押下します。
入力項目は、それぞれ以下のように入力して画面右下の『ルールの作成』ボタンを押下します。
項目 | 設定値 |
名前 | sat_iot_aws_rule (任意の文字列でOK) |
説明 | AWS IoT Core – Test (任意の文字列でOK) |
SQLバージョンの使用 | 2016-03-23 (デフォルト) |
ルールクエリステートメント | SELECT * FROM sat/test |
1つ以上のアクションを設定する | 下記の手順を参照 |
エラーアクション | 今回は未設定 |
タグ | 今回は未設定 |
また、アクションの追加の手順は、『1つ以上のアクションを設定する』にある『アクションの追加』ボタンを押下し、次の画面で『メッセージデータを渡すLambda関数を呼び出す』を選択し、下にスクロールさせ『アクションの設定』ボタンを押下します。
次に、アクションの設定画面となるので、上記で作成したLambda関数(この例では、sat-IoTCoreActtionRuleLambda)を選択し、画面右下の『更新」ボタンを押下します。
すべての項目の入力が完了すると、以下のように画面右下の『ルールの作成』ボタンが押下可能になるので、ボタンを押下して作成します。
アクションルールの作成がされていることを確認してください。
4.2 クライアントプログラムの修正
ここまで、AWS側の設定が完了したので、上部で作成したクライアントプログラムを改修し、取得したセンサーのデータをAWS IoT Coreに送信するように修正します。
AWS IoT Coreに送信するためには、上記AWS IoT Coreの設定でダウンロードした4つの証明書(このモノの証明書:xxxxx.pem.crt、パブリックキー:xxxxx.public.pem.key、プライベートキー:xxxxx.private.pem.key、AWS IoTのルートCA:AmazonRootCA1.pem)が格納されたcertsフォルダをプログラム直下に配置していただく必要があります。
改修したクライアントプログラムは以下のとおりです。MQTTで通信する際のトピック名には、上記でのAWS IoT Coreのルールの作成でルールクエリステートメントに指定した sat/test にし、送信内容もLambda関数作成時に想定している内容で送信するように変更しました。
なお、AWS IoTのオブジェクト( var device = awsIoT.device({ ... }); )の初期化で、keyPath、certPath、caPathで <SET YOUR ... KEY> としている箇所は、それぞれダウンロードした、プライベートキー(拡張子 .private.pem.key)、このモノの証明書(拡張子 .crt)、AWS IoTのルートCA(拡張子.pem)のファイル名を指定してください。
また、host の <SET YOUR AWS IOT ENDPOINT> には、AWSコンソールのAWS IoT Coreの左メニュー『設定』で表示されるAWS IoTのエンドポイントを指定してください。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 |
// websocket var WebSocketClient = require('websocket').client; var client = new WebSocketClient(); // awsIoTCore var awsIot = require('aws-iot-device-sdk'); var device = awsIot.device({ keyPath : '.\\certs\\<SET YOUR PRIVATE KEY>', certPath : '.\\certs\\<SET YOUR CERT KEY>', caPath : '.\\certs\\<SET YOUR ROOT CA KEY>', clientId : 'nouser' + (Math.floor((Math.random() * 100000) + 1)), host: '<SET YOUR AWS IOT ENDPOINT>' }); // 10進 → 16進変換(2byte、16bit) function decimalToHexString(number) { if (number < 0) { number = 0xFFFF + number + 1; } return number.toString(16).toUpperCase(); } // リトルエンディアン16進 → 10進変換(2byte、16bit) function littleEndianHexToDec(hexstring) { if (hexstring == '0') return parseInt(hexstring); var number = parseInt('0x'+ hexstring.match(/../g).reverse().join('')); return number; // decimal } // リトルエンディアン16進 → 16進変換(2byte、16bit) function littleEndianHexToHex(hexstring) { if (hexstring == '0') return parseInt(hexstring).toString(16).toUpperCase(); var number = parseInt('0x'+ hexstring.match(/../g).reverse().join('')); return number.toString(16).toUpperCase(); // Hex } // Q15フォーマットの10進を通常の10進数の固定長少数(digits=2)に変換 function q15DecToFixed(number) { // Q15フォーマット、LSB=100 を通常の10進の固定長少数に変換 var num = number * 100 / 32768; return num.toFixed(2); } // AWS IoT Core接続時 device.on('connect', function() { console.log('AWS-IoTCore connected !'); // AWS IoTCore接続エラー時 device.on('error', function(error) { console.log("connection error(aws) : " + error.toString()); }); }); // socket接続時 client.on('connect', function(connection) { // 接続成功! console.log('websocket connected !'); // socket接続エラー時 connection.on('error', function(error) { console.log("connection error(websocket) : " + error.toString()); }); // socketデータ受信時(Nodeの場合は、Bufferとなる模様) connection.on('message', function(e) { var buf = e.binaryData; console.log("received -------------------------"); // console.log(buf); // console.log(buf.byteLength); // buffer → ArrayBuffer var ab = new ArrayBuffer(buf.byteLength); var view = new Int8Array(ab); for ( var i=0; i < buf.byteLength; i++) { view[i] = buf[i]; } var dv = new DataView(ab); var offset = 0; // BLE通信データ(Blob)の解析 offset = 0; // 経過時間(16進) console.log('dataAge(hex) - 0x' + littleEndianHexToHex( decimalToHexString(dv.getInt16(offset)))); offset += 2; // major、minor(16進、プルーフID:DB Address下12bit)、2byteずらす var proofid_u12bit = littleEndianHexToHex( decimalToHexString(dv.getInt16(offset))); console.log('proofid(hex) - 0x' + proofid_u12bit); offset += 2; // 温度 value[0](10進)、2byteずらす var tempQ15Hex = littleEndianHexToHex( decimalToHexString(dv.getInt16(offset)) ); var tempQ15Dec = littleEndianHexToDec( decimalToHexString(dv.getInt16(offset)) ); var temperature = q15DecToFixed(tempQ15Dec); console.log('temperature - 0x' + tempQ15Hex + ' -> dec-q15 : [' + tempQ15Dec + '] -> dec : [' + temperature + ']'); offset += 2; // 湿度 value[1](10進)、2byteずらす var humidityQ15Hex = littleEndianHexToHex( decimalToHexString(dv.getInt16(offset)) ); var humidityQ15Dec = littleEndianHexToDec( decimalToHexString(dv.getInt16(offset)) ); var humidity = q15DecToFixed(humidityQ15Dec); console.log('humidity - 0x' + humidityQ15Hex + ' -> dec-q15 : [' + humidityQ15Dec + '] -> dec : [' + humidity + ']'); // AWSのIoTCoreに送信 // MQTT送信情報 const mqttTopicName = 'sat/test' const mqttMessage = { 'unixtime': Math.floor( new Date().getTime() / 1000), 'proofid_u12bit': proofid_u12bit, 'temperature_value': temperature, 'temperature_unit': "℃", 'humidity_value' : humidity, 'humidity_nit': "%" } console.log('publish : [' + JSON.stringify(mqttMessage) + ']'); device.publish(mqttTopicName, JSON.stringify(mqttMessage)); console.log('aws iot published !!!'); }); // WebSocket message.on // socket接続切断時 connection.on('close', function() { console.log('WebSocket Client Closed'); }); }); // モニタリングボックスへwebsocket接続 var con = 'ws://172.28.0.3/vreg?readBlocks=0x0300&interval=9000'; client.connect(con); |
上記、プログラムを実行( node client.js )すると、以下のようにセンサーから取得したデータをAWS IoT Coreに転送が開始されます。
4.3 クライアントとAWSの連携確認
これまでの設定した内容を元に、AWS IoT CoreのトップページにあるモニタリングでクライアントとAWSとの連携がされていることを確認します。
AWSコンソールから『AWS IoT Core』→『モニタリング』で接続成功のグラフが表示されます。ここに接続成功のデータが表示されていれば、センサーからデータが接続されていることが確認できます。
AWS IoT Coreで設定したアクション(Lambdaを経由してColudWatchのメトリクス登録)もされていることが確認できます。
5. センサーデータを可視化
上記でセンサーの情報がCloudWatchまで登録されていることが確認できたので、最後にセンサーデータの可視化を行います。
今回は、AWS IoT Coreでデータを受けた際に、アクションの起動されるLambdaでCloudWatchメトリクスの登録を行っているので、CloudWatchのダッシュボードを使用してメトリクスを可視化してみました。
まとめ
今回、センサーはμPRISMを使用しましたが、モニタリングボックスと繋がるセンサーであれば、基本的には今回の構成で様々なセンサーデータと接続することができ、IoTとしてクラウドと連携して可視化やその他を行うことが可能になる仕組みを構築することができました。
また、AWS側で受けたデータをLambdaで処理する際に、温度、湿度から暑さ指数(WBGT)を算出したり、一定の閾値を超えた場合に、SNSでメールや携帯に通知やConnectで電話をかけるなどといった、他のサービスとの連携も可能になるなど、IoTを使用した運用の仕組みなどにも応用ができるといえそうです。
最後に、IoTデータをクラウド上にアップして処理を行ってますが、会社のポリシーでクラウドにデータを上げることができない場合でも、モニタリングボックスから社内ネットワークに繋ぐことで工場内の機器やデバイスの値を可視化するなど汎用的な仕組みとなっております。もし、今回の記事で、センサーデータの可視化にご興味を持たれましたら、ぜひ当社お問い合わせフォームからご連絡いただければと思います。
執筆者プロフィール
- 社内の開発プロジェクトの技術支援、クラウド開発支援を担当。まずは手を動かす!をモットーになんでも屋さんとして奮闘中。最近はコンテナ技術やマイクロサービスの現場活用に向け日々精進してます。
この執筆者の最新記事
- Pick UP!2020.03.27汎用的な仕組みでセンサーデータを見える化してみた――後編
- Pick UP!2020.03.27汎用的な仕組みでセンサーデータを見える化してみた――前編
- AWS・クラウド2019.04.01AWSでIoTデータを見える化してみた その2
- AWS・クラウド2019.01.22AWSでIoTデータを見える化してみた