YAMAGUCHI::weblog

海水パンツとゴーグルで、巨万の富を築きました。カリブの怪物、フリーアルバイター瞳です。

Raspberry Pi Zero WとStackdriver Monitoringで部屋の空気のモニタリングをする

はじめに

こんにちは、Stackdriver担当者です。同僚の @proppy と Pimoroni 見てたら、便利そうなHATとセンサーがあったので買って遊んでいます。

TL;DR

BME680で取得したデータをStackdriver Monitoringでダッシュボード作って見てみると楽しい。

BME680

BME680とは

BME680は、BOSCH製の空気質センサーで、この3mm四方の小さなセンサーだけで気温、湿度、大気圧、空気質が測定できます。

BME680

さらにこのセンサーのブレイクアウトがPimoroniから販売していて、I2Cを使って簡単に数値を読み取れるというわけです。

セットアップ

RaspberryPi Zero W は Raspbian (Stretch) が動いている前提です。

$ sudo apt-get install python3-venv i2c-tools read-edid libi2c-dev python3-smbus
$ python3 -m venv --system-site-packages .venv
$ .venv/bin/pip install bme680
$ wget https://raw.githubusercontent.com/pimoroni/bme680-python/master/examples/read-all.py
$ .venv/bin/python3 read-all.py

これでちゃんと数値が表示されたらBME680を無事に認識しているので準備完了。 これを使って部屋の気温、湿度、気圧、空気室などを記録してみます!

Stackdriver Monitoring へのデータの送信

Stackdriver Monitoringとは

Stackdriver Monitoringは、通常Monitoring Agentと呼ばれるエージェントをインスタンス上で実行し、システムやアプリケーションのメトリクスをサンプリングすることを想定しています。特によく利用されるデータベースやミドルウェアに関しては事前定義のメトリクスを簡単にStackdriver Monitoringに送信できます。

カスタム指標(カスタムメトリクス)

そして、もちろん自分のアプリケーションの状況を伝えるためのカスタム指標をエージェント経由で送信することもできますが、今回は別の方法としてStackdriver Monitoring API v3 を使って送信してみます。

cloud.google.com

今回は上のようにBME680のデータをPythonで取ってきているので、そのままPythonクライアントを使って送信しようと思います。

ライブラリセットアップ

上記に加えて google-cloud-monitoring のパッケージをインストールする必要がありますが、ここで一つ嵌りどころとして依存先の grpcio パッケージのインストールがあります。Raspberry Pi はSDカード上でOSを走らせているので、なるべくSDカードの寿命を延命させるために /tmp/var/tmp を tmpfs 上でマウントしたりします。自分も何も考えずにそうしていたのですが、これが grpcio パッケージのインストール時にネックとなります。

google-cloud-monitoring パッケージが依存している grpcio パッケージの最新バージョンは本記事執筆時当時は 1.19.0 です。執筆時の Raspbian Stretch でのデフォルトのPython 3のバージョンは 3.5 なのですが、上のPyPIのページを見るわかるように 1.19.0 には CPython 3.5 の ARMv6ターゲットの wheel がありません。 また piwheels を見ても 1.18.0 までは CPython 3.5 の ARMv6 ターゲットの wheel があるのですが、1.19.0 はありません。

そうなると手っ取り早い方法としては選択肢は2つなります。*1

  1. constraints.txt などで grpcio パッケージは 1.18.0 以前に固定(依存的には 1.8.0 以上であればよい)し、 --extra-index-url=https://www.piwheels.org/simple のオプションを pip install に加える
  2. grpcio 1.19.0 をローカルでビルドすることに決めて、 pip--build--cache-dir のオプションを /tmp 以外の場所に設定する

自分は新しいライブラリで問題ないかも確認したかったので grpcio 1.19.0 をローカルでビルドすることにして、何も設定せずにそのまま google-cloud-monitoring パッケージをインストールしました。(結構時間がかかる)

$ TMPDIR=$HOME/tmp .venv/bin/pip install --cache-dir=$TMPDIR/cache --build=$TMPDIR/build google-cloud-monitoring

1. メトリクスの作成

先に挙げたドキュメントのとおりなのですが、カスタムメトリクスを記録するためには、まずカスタムメトリクス自体をStackdriver Monitoringに登録する必要があります。 流れとしては

  1. Stackdriver Monitoringのクライアントのインスタンスを作成
  2. MetricDescriptorインスタンスを作成し、カスタムメトリクス名、メトリクスのタイプ、メトリクスの値の型、メトリクスの説明書き、メトリクスのラベルを設定
  3. クライアントに今作成した MetricDescriptorインスタンスを渡して、メトリクスを作成

のようになります。次は temperature というカスタムラベルを作成しています。実際にはセンサーが取得できる presssurehumiditygas_resistance についても同様に作成しています。

client = monitoring_v3.MetricServiceClient()
project_name = client.project_path(project_id)

descriptor = monitoring_v3.types.MetricDescriptor()
descriptor.type = "custom.googleapis.com/temperature"
descriptor.metric_kind = (
    monitoring_v3.enums.MetricDescriptor.MetricKind.GAUGE)
descriptor.value_type = (
    monitoring_v3.enums.MetricDescriptor.ValueType.DOUBLE)
rainfall_label = descriptor.labels.add()
rainfall_label.key = "rainfall"
rainfall_label.value_type = (
    monitoring_v3.enums.LabelDescriptor.ValueType.INT64)
rainfall_label.description = "rainfall observation report from weather forecast service"
descriptor.description = "room temperature"

descriptor = client.create_metric_descriptor(project_name, descriptor)

ここでメトリクスのラベルを設定していますが、これは取得するメトリクスに紐づいて取得時ごとに変化する付加情報を入れるためのものです。今回のサンプルでは温度や湿度を取るわけですが、その際に外の天気との関係性を知りたくなるでしょう。そのため、天気予報サイトから雨量の情報を取得し、 rainfall ラベルに入れることにしました。

実際のユースケースでは、例えばウェブアプリケーションの場合、「レスポンスデータのサイズ」をメトリクスにしていた場合、ステータスコードが400番台のものだけのグラフを作りたい場合に、「ステータスコード」をラベルとして設定しておきます。

またこの create_metric_descriptor はまったく同盟のメトリクスが存在する場合は特にエラーが起きず上書きされ、エラーになることはありません。(メトリクスが存在するかどうか事前に確認しなくても大丈夫です。)

これをプログラムの開始時にだけ1度実行します。

2. メトリクスデータの書き込み

こちらもドキュメントのとおりです。ある瞬間のセンサーデータを登録する場合の流れは

  1. TimeSeries インスタンスを作成
  2. カスタムメトリクス名、メトリクスのラベルの値を設定
  3. リソースのラベルを設定
  4. TimeSeries にメトリクスデータが入った Point を追加
  5. クライアント経由で TimeSeries をStackdriver Monitoringに送信
# 1. TimeSeriesの作成
series = monitoring_v3.types.TimeSeries()
# 2. カスタムメトリクス名、メトリクスのラベルの値を設定
series.metric.type = "custom.googleapis.com/temperature"
series.metric.labels['rainfall'] = fetch_rainfall()
# 3. リソースのラベルを設定
# https://cloud.google.com/monitoring/custom-metrics/creating-metrics#which-resource
series.resource.type = 'generic_node'
series.resource.labels['location'] = 'asia-northeast1-a'
series.resource.labels['namespace'] = RESOURCE_NAMESPACE
series.resource.labels['node_id'] = socket.gethostname()
# 4. `TimeSeries` にメトリクスデータが入った `Point` を追加
sensor = bme680.BME680() # 実際はプログラム起動時にインスタンス作成
sensor.get_sensor_data()
point = series.points.add()
point.value.double_value = value
now = time.time()
point.interval.end_time.seconds = int(now)
point.interval.end_time.nano = int(
    (now - point.interval.end_time.seconds) * 10**9)
# 5. クライアント経由で `TimeSeries` をStackdriver Monitoringに送信
client.create_time_series(project_name, [series])

リソースのラベルを設定する部分では generic_node を設定しています。(本来は global を設定すべきかも)リソースラベルはメトリクスラベルと違い、時間が経過してもそのデータを取得する上において変化しない属性を記録します。たとえば、マシンのホスト名などです。

どのリソースタイプでどのリソースラベルを設定するかは次のページに記載されています。

Point の追加で注意しなければいけないことは、TimeSeries を送信する際には Point が2つ以上含まれていてはいけないということ。公式ドキュメントを引用します。

When creating a time series, this field must contain exactly one point and the point's type must be the same as the value type of the associated metric. If the associated metric's descriptor must be auto-created, then the value type of the descriptor is determined by the point's type, which must be BOOL, INT64, DOUBLE, or DISTRIBUTION.

したがって、上のように Point を1つだけ追加しておしまいです。 *2

逆に TimeSeriesインスタンスはクライアント経由でまとめて送信できます。今回はセンサーで取れる値が温度、湿度、気圧、空気質の4つあるので、上と同じ流れで4つ TimeSeries をつくって、まとめて送信します。

これを10秒おきなど、定期的に実行します。

Stackdriver Monitoringの設定

以上のようなコードでデータをStackdriver Monitoringに送信し続けると、ダッシュボードでメトリクスとして選択できるようになります。

f:id:ymotongpoo:20190318084145p:plain

この自分で作成した custom/temperature のメトリクスを選択すると、送信されたデータを元に作成されたグラフが表示され、さらにFilterの項目ではリソースラベルとメトリクスラベルでの絞り込みができるようになっています。

f:id:ymotongpoo:20190318084514p:plain

それ以下、Aggregationの項目ではグラフの書き方などについての一般的な設定を行っていき、SAVEボタンでチャートを保存します。そのようにして以下、湿度、気圧、空気質に関しても同様に行っていくとダッシュボードが完成します。

f:id:ymotongpoo:20190318084736p:plain

部屋の温度が日の出とともにぐっと上昇する様子や、湿度が人間の生活時間に合わせて上昇する様子、今日は高気圧であるなとわかるなど、記録を取るだけでかなり面白かったです。次はこれを複数台に増やしてチャートに重ねてみようと思います。

参照

BME680

Stackdriver Monitoring V3

*1:他にも自分でQEMUを使ってビルドする方法などもあります

*2:Pointのintervalでend_timeしか設定していないのは、start_timeがoptionalだからです。詳細はproroファイルを参照。