RaspberryPi3からGoogle Cloud IoTへGrovePi+のセンサデータを送信
RaspberryPi3につないだGrovePi+のセンサデータをGCP Cloud IoTへ送信した。
以下、Cloud IoTのQuickStart(https://cloud.google.com/iot/docs/quickstart?hl=ja)に従って進める。
※Rasberry Pi3はModel B(+ではない)。OSはraspbian。
cat /etc/debian_version =>8.0
事前準備
プロジェクト作成(既存でもOK)、課金を有効、Cloud IoT Core and Cloud Pub/Sub API(複数)を有効にする。
デバイス側の設定
Google Cloud SDKインストール
手順:https://cloud.google.com/sdk/docs/?hl=ja#deb
「Debian、Ubuntu」タブの内容に従いインストールする。
※毎回exportするのは面倒なので、今回は、~/.profileに追記した。また、追加コンポーネントのインストールはなし。
echo "export CLOUD_SDK_REPO=\"cloud-sdk-$(lsb_release -c -s)\"" >> ~/.profile exec bash -l echo "deb http://packages.cloud.google.com/apt $CLOUD_SDK_REPO main" | sudo tee -a /etc/apt/sources.list.d/google-cloud-sdk.list curl https://packages.cloud.google.com/apt/doc/apt-key.gpg | sudo apt-key add - sudo apt-get update && sudo apt-get install google-cloud-sdk gcloud init
gcloud initを実行すると、ブラウザが開いてGoogleへのログインを求められる。
事前準備で作成したプロジェクトのアカウントでログインすると、Google Cloud SDKが許可を求める画面に遷移するので、「許可」ボタンを押すと認証完了。
コンソールに戻ると、対話形式で、使用するプロジェクト、リージョン/ゾーンを聞かれる。
今回は、事前準備で作成したプロジェクト、asia-northeast1-b(東京リージョンのbゾーン)、を選択した。
※リージョン/ゾーンによって、マシンのスペックも変わるので注意(選択可能なリージョン/ゾーン一覧:https://cloud.google.com/compute/docs/regions-zones/regions-zones#available)。
Node.jsインストール
インストールは下記記事を参照。2018/03/22現在、最新のLTSはv8.10.0 kmth23.hatenablog.com
node -v =>v8.10.0 npm -v =>5.6.0
デバイスの登録
Cloud IoTのコンソール(https://console.cloud.google.com/iot?hl=ja)で作業。
まず「レジストリ」を作成し、「レジストリ」にデバイスを追加する。
「Create device registry.」ボタンを押して下記のように設定。
- レジストリ ID: 任意の名前
- Region: asia-east1
- プロトコル: MQTTとHTTP
- デフォルトのテレメトリーのトピック: トピックを作成⇒任意の名前
- デバイス状態のトピック: なし
- CA 証明書: 設定しない
「作成」ボタンを押す。(これでレジストリの作成が完了)
遷移したページで「端末を追加」ボタンを押して下記のように設定。
「追加」ボタンを押す。(これでデバイスの登録が完了) コンソールはこのまま開いておく。
デバイスに公開鍵を追加
opensslがない場合は、事前にインストールする。 下記のコマンドで、rsa_cert.pem(公開鍵)、rsa_private.pem(秘密鍵)を作成する。
cd /tmp openssl req -x509 -newkey rsa:2048 -keyout rsa_private.pem -nodes -out rsa_cert.pem -subj "/CN=unused"
開いたままにしておいたコンソールで「公開鍵を追加」ボタンを押し、rsa_cert.pemの内容をコピーして張り付ける。
- 入力方法: 手動で入力
- 公開鍵の形式: RS256_X509
- 公開鍵の有効期限: 設定しない
「追加」ボタンを押す。
サンプルで動作確認
サンプルをgit cloneで取得する。gitがない場合は、事前にインストールする。
下記コマンドについては、これまでに作成した情報に従い、<<PROJECT_ID>>をプロジェクトID、<<TOPIC_NAME>>をトピック名、<<REGISTRY_ID>>をレジストリID、<<DEVICE_ID>>をデバイスIDに置き換えること。
<<任意のサブスクリプション名>>は、任意のサブスクリプション名に置き換えること。
また、jsファイルの実行時には、--cloudRegionの指定を忘れないこと(デフォルトはus-central1。今回はasia-east1にしたので指定が必要)。
--numMessagesで送信するデータ数を指定できる。今回はテストのため1メッセージだけ送信する。
cd 任意の作業dir git clone https://github.com/GoogleCloudPlatform/nodejs-docs-samples cd nodejs-docs-samples/iot/mqtt_example cp /tmp/rsa_private.pem . npm install gcloud pubsub subscriptions create \ projects/<<PROJECT_ID>>/subscriptions/<<任意のサブスクリプション名>> \ --topic=projects/<<PROJECT_ID>>/topics/<<TOPIC_NAME>> node cloudiot_mqtt_example_nodejs.js \ --projectId=<<PROJECT_ID>> \ --registryId=<<REGISTRY_ID>> \ --deviceId=<<DEVICE_ID>> \ --privateKeyFile=rsa_private.pem \ --numMessages=1 \ --algorithm=RS256 \ --cloudRegion=asia-east1 gcloud pubsub subscriptions pull --auto-ack \ projects/<<PROJECT_ID>>/subscriptions/<<任意のサブスクリプション名>>
jsファイルの実行で、1つのデータがpublishされる。gcloud pubsub subscriptions pullコマンドでsubscribeできれば成功。
センサー情報の取得
AWS IoTにデータ送信したときと同じ仕組みを使う。 kmth23.hatenablog.com
まず、pythonでGrovePi+につないだセンサー情報を取得する。
次に、動作確認で使ったJavaScriptコードを参考にして、mqttでCloud IoTへデータを送信するコードを書く。
pythonコードの実行は、AWS IoTの時と同様、child_processのexecファンクションを使用する。
まずは、package.jsonを作成し、必要なライブラリをインストール。
{ "name": "grovepi-test", "version": "0.0.1", "description": "", "main": "index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "author": "", "license": "ISC", "dependencies": { "jsonwebtoken": "7.4.1", "mqtt": "2.15.0", "yargs": "8.0.2" }, "devDependencies": {} }
npm install
次に、実行ファイルを、index.jsとして作成。
5分間隔でセンサーデータを取得し、Cloud IoTへ送信する。
※pythonコードは、/path/to/python/script.py。
// This software includes the work that is distributed in the Apache License 2.0 'use strict'; const fs = require('fs'); const jwt = require('jsonwebtoken'); const mqtt = require('mqtt'); const exec = require('child_process').exec; var argv = require(`yargs`) .options({ projectId: { default: process.env.GCLOUD_PROJECT || process.env.GOOGLE_CLOUD_PROJECT, description: 'The Project ID to use. Defaults to the value of the GCLOUD_PROJECT or GOOGLE_CLOUD_PROJECT environment variables.', requiresArg: true, type: 'string' }, cloudRegion: { default: 'us-central1', description: 'GCP cloud region.', requiresArg: true, type: 'string' }, registryId: { description: 'Cloud IoT registry ID.', requiresArg: true, demandOption: true, type: 'string' }, deviceId: { description: 'Cloud IoT device ID.', requiresArg: true, demandOption: true, type: 'string' }, privateKeyFile: { description: 'Path to private key file.', requiresArg: true, demandOption: true, type: 'string' }, algorithm: { description: 'Encryption algorithm to generate the JWT.', requiresArg: true, demandOption: true, choices: ['RS256', 'ES256'], type: 'string' }, mqttBridgeHostname: { default: 'mqtt.googleapis.com', description: 'MQTT bridge hostname.', requiresArg: true, type: 'string' }, mqttBridgePort: { default: 8883, description: 'MQTT bridge port.', requiresArg: true, type: 'number' }, messageType: { default: 'events', description: 'Message type to publish.', requiresArg: true, choices: ['events', 'state'], type: 'string' } }) .example(`node $0 cloudiot_mqtt_example_nodejs.js --projectId=blue-jet-123 \\\n\t--registryId=my-registry --deviceId=my-node-device \\\n\t--privateKeyFile=../rsa_private.pem --algorithm=RS256 \\\n\t --cloudRegion=us-central1`) .wrap(120) .recommendCommands() .epilogue(`For more information, see https://cloud.google.com/iot-core/docs`) .help() .strict() .argv; function createJwt (projectId, privateKeyFile, algorithm) { const token = { 'iat': parseInt(Date.now() / 1000), 'exp': parseInt(Date.now() / 1000) + 20 * 60, // 20 minutes 'aud': projectId }; const privateKey = fs.readFileSync(privateKeyFile); return jwt.sign(token, privateKey, { algorithm: algorithm }); } const mqttClientId = `projects/${argv.projectId}/locations/${argv.cloudRegion}/registries/${argv.registryId}/devices/${argv.deviceId}`; const mqttTopic = `/devices/${argv.deviceId}/${argv.messageType}`; let connectionArgs = { host: argv.mqttBridgeHostname, port: argv.mqttBridgePort, clientId: mqttClientId, username: 'unused', password: createJwt(argv.projectId, argv.privateKeyFile, argv.algorithm), protocol: 'mqtts', secureProtocol: 'TLSv1_2_method' }; let client = mqtt.connect(connectionArgs); client.subscribe(`/devices/${argv.deviceId}/config`); client.on('connect', (success) => { console.log('connect'); if (!success) { console.log('Client not connected...'); } else { setInterval(() => { exec('python /path/to/python/script.py', (error, stdout, stderr) => { if (error !== null) { console.log('exec error: ' + error); return } var data = stdout.replace(/\r?\n/g,""); var datas = data.split(",") var record = { registryid: argv.registryId, deviceid: argv.deviceId, timestamp: datas[0], temperature: Number(datas[1]), humidity: Number(datas[2]), moisture: Number(datas[3]), light: Number(datas[4]), location: datas[5] + "," + datas[6] }; const payload = JSON.stringify(record); console.log("Publish: " + payload); client.publish(mqttTopic, payload, { qos: 1 }); }); return; }, 300000); } }); client.on('close', () => { console.log('close'); }); client.on('error', (err) => { console.log('error', err); }); client.on('message', (topic, message, packet) => { console.log('message received: ', Buffer.from(message, 'base64').toString('ascii')); }); client.on('packetsend', () => { // Note: logging packet send is very verbose });
実行コマンドは下記。rsa_private.pemはあらかじめコピーしておくこと。
cp /tmp/rsa_private.pem . npm start \ --projectId=<<PROJECT_ID>> \ --registryId=<<REGISTRY_ID>> \ --deviceId=<<DEVICE_ID>> \ --privateKeyFile=rsa_private.pem \ --algorithm=RS256 \ --cloudRegion=asia-east1
subscribeすると、下記のようなデータが取得できるはず。
gcloud pubsub subscriptions pull --auto-ack \ projects/<<PROJECT_ID>>/subscriptions/<<任意のサブスクリプション名>> =>{ "registryid":"<<REGISTRY_ID>>", "deviceid":"<<DEVICE_ID>>", "timestamp":"2018-03-23T16:19:48+09:00", "temperature":25.2, "humidity":30.4, "moisture":0, "light":206 }