Raspberry Pi のCPU温度をfirebaseの Cloud Firestore に記録してVue.jsでグラフ表示する
自宅に設置したRaspberry PiからCPU温度を定期発信し、firebaseの Cloud Firestore に記録します。記録した温度の情報を、Vue.jsコンポーネントにグラフで表示します。
完成図
注意
Firestoreがアーキテクチャ選択的に微妙だったので、こちらでやり直しています。
kojimainjp.hatenablog.com
(それでも読む場合↓↓↓)
本記事ではFirebaseの無料プランを利用しています。現時点ではクレジットカード登録してアップグレードしない限りにおいて料金が発生することは無いはずですが、実践する場合は自己責任でお願いします。
動機
自身のポートフォリオサイトに載せるネタの一つとして始めました。(※)
そのポートフォリオがこちらです。
https://github.com/kojimain/kojimain-portfolio
技術を追っかけるために一人遊んでいる分には楽しいのですが、中身の薄ーい、希釈しすぎた粉ジュースのようなサイトになっています。
何か見栄えのする題材が無いかなと思い、部屋に転がっていた Raspberry Pi を利用することにし、タイトルの題材に決めました。
目次
- firebaseプロジェクト立ち上げ、DB作成
- Firebase Admin SDK で温度送信用スクリプトを書く
- Vue.js でグラフ表示する
- Raspberry Pi にデータ送信スクリプトを配置する
注意
本記事は個人の趣味の範囲で書かれています。誰かの参考になれば幸いではありますが、実践は自己責任にてお願いします。
また、本記事ではFirebaseの無料プランを利用しています。現時点ではクレジットカード登録してアップグレードしない限りにおいて料金が発生することは無いはずですが、こちらも自己責任でお願いします。
firebaseプロジェクト立ち上げ、DB作成
- firebaseプロジェクト立ち上げ、DB作成
- Firebase Admin SDK で温度送信用スクリプトを書く
- Vue.js でグラフ表示する
- Raspberry Pi にデータ送信スクリプトを配置する
まずはfirebaseプロジェクトを作成します。
firebaseコンソールにアクセスします。
https://console.firebase.google.com/?hl=ja
こんな感じでプロジェクト追加します。
(黒塗りしていますが、最終的にフロントのソースに出る値なので秘匿性は無いです。)
続いて、Raspberry Pi から送信する温度データを格納するためのDBを、Cloud Firestore で作成します。
左のメニュー>開発>databaseへ
Firestoreの作成ボタンがあるのでクリックし、「ロックモードで開始」のまま作成します。
このような画面になるので、「コレクションを追加」します。
"rasp_temperatures"コレクションを追加します。
すると、ドキュメント第一号を登録するよう促されます。
以下のように登録します。
- ドキュメントID: ユニークなID(自動生成を押す)
- value: 温度値(number型 単位は℃)
- sentAt: 送信日時
更にもう1ドキュメントしておきます。
「ドキュメントを追加」から、以下の内容で追加します。
これで、温度のプロット値を格納する入れ物となるDBが完成しました。
Firebase Admin SDK で温度送信用スクリプトを書く
- firebaseプロジェクト立ち上げ、DB作成
- Firebase Admin SDK で温度送信用スクリプトを書く
- Vue.js でグラフ表示する
- Raspberry Pi にデータ送信スクリプトを配置する
Raspberry Pi から Cloud Firestore に温度データを書き込むためのスクリプトを、ローカルで作成します。
初めに、Firebase Admin SDK を操作するためのサービスアカウントを作成します。
左のメニュー>設定アイコン>プロジェクトの設定>サービスアカウントタブへ
「新しい秘密鍵の生成」からキーを生成すると、credential情報のjsonファイルがDLされます。
この後の手順で使用するので保管しておきます。
続いて、温度送信用スクリプトをNode.jsで開発していきます。
まずローカルの任意の場所にプロジェクトフォルダを作成し、yarn init します。
$ cd path/to/workdir $ mkdir send_temperature $ cd send_temperature $ yarn init (全部エンター)
今回のスクリプトに必要なパッケージを追加します。
$ yarn add firebase-admin
スクリプトを書いていきます。
index.jsを以下の内容で作成します。
60秒おきにCPU温度をfirestoreに送信する、という内容になっています。
CPU温度取得の処理はダミーです。後でラズパイに配置するときに実際の処理に書き換えます。
// setup const admin = require("firebase-admin") const serviceAccount = require("./credentials/serviceAccount.json") admin.initializeApp({ credential: admin.credential.cert(serviceAccount), databaseURL: `https://${serviceAccount.project_id}.firebaseio.com` }) const firestore = admin.firestore() firestore.settings({timestampsInSnapshots: true}) // functions // ダミーのCPU温度取得処理 const fetchTemperature = function() { const array = [41.0, 41.5, 42.0] return array[Math.floor(Math.random() * array.length)] } // CPU温度送信処理 const sendTemperature = function() { const value = fetchTemperature() const sentAt = new Date() console.log(`${sentAt}: ${value}`) firestore.collection('rasp_temperatures') .add({ value: value, sentAt: sentAt }) } // main sendTemperature() setInterval(function() { sendTemperature() }, 60000)
加えて、credentials/serviceAccount.json
に、先ほどDLしたcredential情報のjsonファイルを保存します。
以上が整ったら、ローカルで実行します。
コマンド直後に一度温度が送信され、以後1分間隔で送信を続けます。
$ node index.js Tue Dec 25 2018 09:59:57 GMT+0900 (GMT+09:00): 41 Tue Dec 25 2018 10:00:57 GMT+0900 (GMT+09:00): 41.5 ... (終了はctrl-c)
Cloud Firestore のデータを確認すると、データが追加されています。
以上で温度送信の方法が確立できました。
今回作成したスクリプトは、Raspberry Pi でcronの定期実行を設定するときに再登場します。
Vue.js でグラフ表示する
- firebaseプロジェクト立ち上げ、DB作成
- Firebase Admin SDK で温度送信用スクリプトを書く
- Vue.js でグラフ表示する
- Raspberry Pi にデータ送信スクリプトを配置する
Cloud Firestore に登録された温度データを表示するグラフのVue.jsコンポーネントを作成していきます。
まずローカルの任意の場所にVue.jsコンポーネント開発環境を整えます。
vue-cli v3が必要になりますので、グローバルにインストールします。
$ yarn global add @vue/cli @vue/cli-service-global $ cd path/to/workdir $ vue create temperature_graph ? Please pick a preset: (Use arrow keys) ❯ default (babel, eslint) Manually select features ...(完了) $ cd temperature_graph $ tree -L 1 . ├── README.md ├── babel.config.js ├── node_modules ├── package.json ├── public ├── src └── yarn.lock
必要なパッケージを追加します。
yarn add firebase c3 vue-c3
グラフ描画に「c3」というライブラリを使用します。
(こちらの記事を参考にさせて頂きました。)
開発環境が整ったので、グラフ描画用コンポーネントを作成していきます。
src/components/Graph.vueを以下の内容で作成します。
(追記:2018/12/29)
以下コードの.limit(12)
の箇所を、当初.limit(288)
で掲載していましたが、「読み取りオペレーション数」の指標が無料枠に収まらない速度で上昇したため、一度に読み取る数を減らしました。
(読み取り回数はcollection単位でカウントされると思い込んでいましたが、doc.data()
でdocumentを取得する度にカウントされるようです。)
<template> <div> <vue-c3 :handler="handler"/> </div> </template> <script> import Vue from 'vue' import firebase from 'firebase/app' import 'firebase/firestore' import VueC3 from 'vue-c3' import 'c3/c3.min.css' // firebase firebase.initializeApp({ projectId: 'portfolio-xxx' }) const firestore = firebase.firestore() firestore.settings({timestampsInSnapshots: true}) export default { components: { VueC3 }, data() { return { temperatures: [], handler: new Vue() } }, methods: { async getTemperatures() { await firestore.collection('rasp_temperatures') .orderBy('sentAt', 'asc') .limit(12) .get() .then(querySnapshot => { this.temperatures = querySnapshot.docs.map(doc => { const data = doc.data() return { value: data.value, sentAt: data.sentAt.toDate() } }) }) }, async initGraph() { await this.getTemperatures() const options = { data: { x: 'x', columns: [ ['x'].concat(this.temperatures.map(t => { return t.sentAt })), ['温度(℃)'].concat(this.temperatures.map(t => { return t.value })) ] }, axis: { x: { type: 'timeseries', tick: { format: '%Y-%m-%d %H:%M' } } } } this.handler.$emit('init', options) } }, mounted() { this.initGraph() } } </script>
作成後、以下で起動します。
$ vue serve src/components/Graph.vue
http://localhost:8080/で確認すると、エラーが出てグラフが描画されません。
Firestoreを「ロックモードで開始」で作成しているので、フロントエンドから参照できない状態になっています。
フロントエンドから参照できるよう設定していきます。
左のメニュー>開発>database>ルールタブを選択します。
このようになっているので、以下に書き換えて登録します。
rasp_temperaturesのlist表示のみを許可するよう設定しました。
service cloud.firestore { match /databases/{database}/documents { match /rasp_temperatures/{rasp_temperature} { allow list; } } }
改めて、http://localhost:8080/で確認します。
まだプロットが少ないので少々いびつですが、欲しいグラフが得られました。
Raspberry Pi にデータ送信スクリプトを配置する
- firebaseプロジェクト立ち上げ、DB作成
- Firebase Admin SDK で温度送信用スクリプトを書く
- Vue.js でグラフ表示する
- Raspberry Pi にデータ送信スクリプトを配置する
最後に、自宅に設置したRaspberry Pi にCPU温度送信スクリプトを配置し常駐稼働させます。
まず、先に作成した温度送信スクリプトの温度取得がダミー処理なので、これを実処理にしていきます。
CPU温度は以下の方法で取得できます。
単位は摂氏の1000倍なので、以下の例では「46.1℃」となります。
(ラズパイで実行) $ cat /sys/class/thermal/thermal_zone0/temp 46160
これを利用し、index.jsを以下のように編集します。
CPU温度を/sys/class/thermal/thermal_zone0/tempから取得するようにしました。
併せて、送信間隔を300000ミリ秒(5分)と伸ばしました。
(追記:2018/12/29)
上記変更に加え、main処理でsetInterval前に初回のsendTemperature()を実行していたのを削除しました。
// setup const admin = require("firebase-admin") const serviceAccount = require("./credentials/serviceAccount.json") admin.initializeApp({ credential: admin.credential.cert(serviceAccount), databaseURL: `https://${serviceAccount.project_id}.firebaseio.com` }) const firestore = admin.firestore() firestore.settings({timestampsInSnapshots: true}) const execSync = require('child_process').execSync // functions // CPU温度取得処理 const fetchTemperature = function() { const tempOrg = execSync('cat /sys/class/thermal/thermal_zone0/temp').toString() return parseFloat(`${tempOrg.slice(0,2)}.${tempOrg.slice(3,4)}`) } // CPU温度送信処理 const sendTemperature = function() { const value = fetchTemperature() const sentAt = new Date() console.log(`${sentAt}: ${value}`) firestore.collection('rasp_temperatures') .add({ value: value, sentAt: sentAt }) } // main // sendTemperature() ←削除 setInterval(function() { sendTemperature() }, 300000)
以上で温度送信スクリプトの実処理が完成しました。
ここから、実際にRaspberry Pi にスクリプトを配置して稼働させます。
3記事目にしてようやく主役のRaspberry Pi が登場します。
OSはRaspbianを入れてあります。
宅内LANに接続してあり、同じく宅内LANに接続した別PC端末からssh接続できるようになっています。
(Raspberry Pi セットアップ方法はメインでないため割愛します。)
Raspberry Pi にyarnを入れます。
公式のインストール手順に従います。
(ラズパイにssh接続) $ curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | sudo apt-key add - $ echo "deb https://dl.yarnpkg.com/debian/ stable main" | sudo tee /etc/apt/sources.list.d/yarn.list $ sudo apt-get update && sudo apt-get install yarn
続いて、先の記事で作成したデータ送信スクリプトを配置します。
ラズパイ上に持ってくる手段は何でも良いんですが、筆者はgithubに公開リポジトリを建てたのでそこから持ってきます。
追記:2019/1/6
削除しました。
rootユーザで作業します。
$ sudo su - # cd /usr/local/src # git clone https://github.com/kojimain/rasp_send_temperature.git # cd rasp_send_temperature # vi credentials/serviceAccount.json (ローカルからjsonの中身をコピペして:wq)
npmパッケージをインストールします。
# yarn install
ここで一度、送信してみます。
# node index.js Wed Dec 26 2018 11:09:02 GMT+0900 (JST): 41.5 (終了はctrl-c)
Raspberry Pi からCPU温度送信が行えました。
グラフにデータが追加され、無事にクリスマスを横断していきました。
最後に、起動サービスに登録して常駐化させます。
# vi /etc/systemd/system/rasp_send_temperature.service
以下の内容で保存します。
(「/usr/bin/node」は# which node
の値)
[Unit] Description = rasp_send_temperature [Service] ExecStart = /usr/bin/node /usr/local/src/rasp_send_temperature/index.js Restart = always Type = simple [Install] WantedBy = multi-user.target
自動起動ON、起動、確認します。
これで、OS起動時に自動で立ち上がり、5分おきに温度が送信されるようになります。
# systemctl enable rasp_send_temperature # systemctl start rasp_send_temperature # systemctl status rasp_send_temperature ● rasp_send_temperature.service - rasp_send_temperature Loaded: loaded (/etc/systemd/system/rasp_send_temperature.service; enabled; vendor preset: enabled) Active: active (running) since Wed 2018-12-26 14:39:52 JST; 5s ago Main PID: 13385 (node) CGroup: /system.slice/rasp_send_temperature.service └─13385 /usr/bin/node /usr/local/src/rasp_send_temperature/index.js 12月 26 14:39:52 raspberrypi systemd[1]: Started rasp_send_temperature. 12月 26 14:39:55 raspberrypi node[13385]: Wed Dec 26 2018 14:39:55 GMT+0900 (JST): 49.8
数分経過した図です。
(注) データ自動削除の仕組みがまだないので、確認が終わったら停止して無効化します。
# systemctl stop rasp_send_temperature # systemctl disable rasp_send_temperature
まとめ
以上で、表題の内容としては全て達成しました。
まだ詰まり切っていない部分として、以下のような課題があります。
これらはいずれ着手しようと思います。
- グラフのx軸の文字が重なっている
読めないので、見た目の修正が必要です。 - Firestoreの各制限
read(50,000/日)やwrite(20,000/日)のAPI制限は十分ですが、保存容量(1 GiB)はこのままだといずれ超えてしまいます。Cloud Functions で Firestore 書き込みのイベントを拾って、古い日付から自動消去するような仕組みを考えています。
冒頭の繰り返しになりますが、本記事は個人の趣味の範囲で書かれています。
誰かの参考になれば幸いですが、間違いがありましたらご指摘頂けますと大変助かります!
追記:2018/12/27
続編を書きました。
古い日付から自動消去する仕組みを構築しました。
kojimainjp.hatenablog.com
また、Vueコンポーネントを少々加工して、ポートフォリオサイトに組み込みました。(※)
だいたい当初の思い通りに仕上がったので満足です。
https://github.com/kojimain/kojimain-portfolio
追記:2018/12/29
本記事は無料枠で十分収まる範囲という想定ですが、Firestoreの指標の中に、趣味に収まらない速度で上昇する指標がありました。
一番ネックになっていた実装を修正しましたが、依然としてDoS攻撃を受けると一発でアウトな状況です。
そもそもFirebaseの使い方として、ログイン制アプリなどでリクエスト数が膨らまない、もしくは膨らんでも採算が取れるシチュエーションで使うのが正解なんだろうなという感想です。
以上の理由で、筆者のように公開サイトに搭載することはオススメしません。
現在Firestore以外の保管場所に移すことを検討中です。
(問題の指標↓)