ホームアシスタント、Raspberry Pi、GridDB Cloudを用いたカリフォルニア州の大気質モニタリング

人類の(精彩を欠いた)努力にもかかわらず、気候変動は今や至る所で否定できないほどの勢いとなっています。地球の平均気温の上昇に伴う多くの副作用があるとはいえ、今日は山火事とそれが周辺の空気の質に与える影響に焦点を当てたいと思います。100エーカーの火災がカリフォルニアの森林を焼き尽くすとき、その残骸や粒子状物質はすべて大気中に巻き上げられ、潜在的に危険で吸入可能な物質となります。

もちろん、私たちが呼吸する空気の質に影響を与える要因は他にもさまざまあります。私にとって、これはたとえ近くで山火事が発生しておらず、AQI(大気質指数)が急上昇していなくても、その時々の大気質について情報を入手し、認識しておくべき理由があることを意味します。

プロジェクト

この記事では、私たちは自宅内の大気質を測定することに関心がありました。具体的には、大気質データのライブ測定値を取得し、永続的なストレージに保存し、大気質が一定の閾値を超えた場合に居住者に通知したいと考えました。

この目標を達成するために、私たちはGridDB CloudをHome Assistantとして知られるオープンソースのスマートホームソリューションと統合することを試みました。Home Assistantは「モノのインターネット(IoT)のエコシステムに依存しない統合プラットフォームであり、スマートホームデバイス用の中央制御システムです。通常、エンドユーザーはインターネットに接続された物理デバイスの制御を行うために、このソフトウェアをホームサーバーにインストールします。例えば、Home Assistantをスマートハブとしてインストールし、スマート電球やロボット掃除機などを制御することができます。

Home Assistantの優れた点は、その多機能性と柔軟性です。例えば、完全にオープンソースで、いじり回したり開発したりする人向けに構築されているため、ユーザーは独自のソリューションを開発し、独自の自動化を行うことができます。私たちのケースでは、フィルターを通さない生のセンサーデータをすべて、1秒あたり1回の解像度でGridDB Cloudに保存し、Home Assistantを使用して、私たちが望むことを行うために必要な値を照会し、最終的には、センサーと同じ空間に住む人々に、大気質が危険なレベルに近づいていることを何らかの具体的な方法で通知できるようにすることに興味がありました。

プロジェクトの詳細とPM1粒子

アイデアはこうです。生の空気質データを取得するセンサーに接続するために、シングルボードコンピューターを使用します。次に、1粒子/秒の解像度で、その生データをGridDBクラウドに送信します。すると、Home Assistantがそのデータを照会し、目的を達成するために必要な情報をダウンサンプルします。

まず、空気品質センサー(Adafruit PMSA003I Air Quality Breakout)をラズベリーパイに接続しました。次に、Pythonスクリプトを使用してセンサーの読み取り値を読み取り、HTTPリクエストにより1秒ごとにGridDB Cloudに送信します。GridDB Cloudの永続ストレージにデータを保存することで、HTTPリクエストで「SQL Select文」を使用してデータセットを照会することができます。このケースでは、Home Assistantを使用してデータセットを照会し、私たちのいる場所の大気中の粒子状物質が通常より高いことを通知します。また、Home Assistantダッシュボードに読み取りやすいセンサー読み取り値を移動平均とともに表示することもできます。

補足として:この特殊なセンサーに関する興味深い点は、1ミクロン(PM1と表示)という微小な物質を検出できることです。これらの粒子は非常に小さいため、肺組織を突き抜け、血流に直接入り込む可能性があり、またこの粒子が非常に小さいことから、上記のセンサーを含む特殊な機器でしか検出できません。PM1の測定値を入手できるソースが容易にないため、この粒子は、そのレベルが上昇していることを在宅者に通知するための問い合わせの中心となります。

プロジェクト要件

このプロジェクトに参加したい場合は、以下のものが必要です。

  1. GridDB Cloud の無料アカウント a. GridDB Cloud の無料アカウントの登録方法は、こちらからご覧いただけます:GridDB Free Plan。 関連ブログ:GridDB Cloud Quick Start
  2. 大気質センサー
  3. センサーをコンピュータに接続する方法(シングルボードまたはその他)

必要なハードウェアをセットアップし、GridDB Cloudデータベースに接続できるようになったら、センサーの読み取り値を送信するシンプルなPythonスクリプトを実行するためのソースコードを、こちらから入手できます:GitHub

ハードウェアの接続

私の場合は、STEMMAコネクタ付きの大気質センサーを購入しました。Raspberry Pi 4に接続するために、STEMMA HatとSTEMMAワイヤーを購入しました。STEMMAハットを購入したくない場合は、ピンをセンサーに半田付けし、ブレッドボードを使用してRaspberry PiのGPIOピンに接続することもできます。この方法を取る場合は、ソースコードに付属のPythonスクリプトを変更する必要があるかもしれません。この大気質センサーの物理的な接続方法については、同社のドキュメントページ(Docs)で詳しく説明されています。

ソフトウェア

次に、このプロジェクトを動かすソフトウェアに焦点を当てます。

コンテナの作成とデータのプッシュ用Pythonスクリプト

まず、センサーの読み取り値をHTTPリクエストとして送信するスクリプトについて説明します。これはada fruitのドキュメントで直接提供されているサンプルスクリプトを修正したものです。受信データのデータ構造はすでに便利な辞書形式でレイアウトされているため、単にそれを繰り返し処理して、コンテナ作成時にスキーマをレイアウトした方法に合わせて値を一致させる必要があります。まず、コンテナ作成用のスクリプトは次のとおりです。

import http.client
import json

conn = http.client.HTTPSConnection("cloud5197.griddb.com")
payload = json.dumps({
  "container_name": "aqdata",
  "container_type": "TIME_SERIES",
  "rowkey": True,
  "columns": [
    {
      "name": "ts",
      "type": "TIMESTAMP"
    },
    {
      "name": "pm1",
      "type": "DOUBLE"
    },
    {
      "name": "pm25",
      "type": "DOUBLE"
    },
    {
      "name": "pm10",
      "type": "DOUBLE"
    },
    {
      "name": "pm1e",
      "type": "DOUBLE"
    },
    {
      "name": "pm25e",
      "type": "DOUBLE"
    },
    {
      "name": "pm10e",
      "type": "DOUBLE"
    },
    {
      "name": "particles03",
      "type": "DOUBLE"
    },
    {
      "name": "particles05",
      "type": "DOUBLE"
    },
    {
      "name": "particles10",
      "type": "DOUBLE"
    },
    {
      "name": "particles25",
      "type": "DOUBLE"
    },
    {
      "name": "particles50",
      "type": "DOUBLE"
    },
    {
      "name": "particles100",
      "type": "DOUBLE"
    }
  ]
})
headers = {
  'Content-Type': 'application/json',
  'Authorization': 'Basic <redacted>'
}
conn.request("POST", "/griddb/v2/gs_clustermfcloud5197/dbs/B2xcGQJy/containers", payload, headers)
res = conn.getresponse()
data = res.read()
print(data.decode("utf-8"))</redacted>

スキーマについては、センサーの読み取り値によって返される aqdata 辞書に 1:1 のマッピングを適用するだけです。 これらのデータポイントをすべて使用するつもりがない場合でも、データ出力は非常に小さいので、そのままにしておきます。

次に、センサーの読み取り値をGridDB Cloudインスタンスにプッシュするスクリプトを見てみましょう。このスクリプトは1秒ごとにセンサーデータを読み取り、そのデータをクラウドにプッシュするために1秒ごとにHTTPリクエストを送信します。

# SPDX-FileCopyrightText: 2021 ladyada for Adafruit Industries
# SPDX-License-Identifier: MIT

"""
Example sketch to connect to PM2.5 sensor with either I2C or UART.
"""

# pylint: disable=unused-import
import time
import datetime
import board
import busio
from digitalio import DigitalInOut, Direction, Pull
from adafruit_pm25.i2c import PM25_I2C


import http.client
import json

conn = http.client.HTTPSConnection("cloud5197.griddb.com")
headers = {
  'Content-Type': 'application/json',
  'Authorization': 'Basic <redacted>'
}

reset_pin = None
i2c = busio.I2C(board.SCL, board.SDA, frequency=100000)
# Connect to a PM2.5 sensor over I2C
pm25 = PM25_I2C(i2c, reset_pin)

print("Found PM2.5 sensor, reading data...")

while True:
    time.sleep(1)

    try:
        aqdata = pm25.read()
        # print(aqdata)
        current_time = datetime.datetime.utcnow().replace(microsecond=0)
        now = current_time.strftime('%Y-%m-%dT%H:%M:%S.%fZ')
       # print(now)
        temp = []
        temp.append(now)
    except RuntimeError:
        print("Unable to read from sensor, retrying...")
        continue
    
    for data in aqdata.values():
        temp.append(data)
    payload = json.dumps([temp])
    print(payload)
    conn.request("PUT", "/griddb/v2/gs_clustermfcloud5197/dbs/B2xcGQJy/containers/aqdata/rows", payload, headers)
    res = conn.getresponse()
    data = res.read()
    print(data.decode("utf-8"))</redacted>

前述の通り、このスクリプトには特別な機能や目新しい機能はありません。単にセンサーデータを読み取り、タイムスタンプを添付して即座にクラウドにプッシュするだけです。ただ、注意すべき点として、GridDB CloudはデフォルトでUTCタイムであるため、一貫性を保つために、データ結果に添付するタイムスタンプをUTCに合わせて変更しました。

クラウド上でデータが利用可能になったので、他のテクノロジーとの統合に進むことができます。

Home Assistant

ホームオートメーションソフトウェアには多くの種類があり、多くの企業が独自のハブを自社製品とともに販売しています。Home Assistantは、1.完全にオープンソースであり、2.あらゆる物理的および仮想デバイスと統合できるように作られているという点でユニークです。例えば、私の個人的な家庭環境では、Raspberry Pi上でHome Assistantを実行しています。他の物理デバイスと通信できるようにするために、Zigbee/Z-Wave USBスティックをインストールしました。ご存じない方のために説明すると、ZigbeeとZ-Waveは、スマートホームデバイスがハブと通信するために使用するプロトコルです。私の場合、スマート電球のほとんどがZigbeeで通信しています。

いずれにしても、Home Assistant を使用すれば、さまざまなスクリプトや自動化を作成して、さまざまなことを実行することができます。例えば、起床時に寝室の照明を1%点灯させたり、毎日昼食時に Spotify のプレイリストを再生したり、などです。 空気品質センサーの場合、センサーデータに対して直接 HTTP クエリを送信し、測定値が特定の閾値よりも高い場合は「何か」を行うことができます。このブログでは、過去1時間において「pm1」粒子が一定の閾値を超えた場合、自宅のリビングルームの電球を「オン」にし、「赤」に変えるように設定しました。しかし、Home Assistantは柔軟性があるので、もちろん、電子メール、電話のプッシュ通知、あるいはロボット掃除機の電源を入れるなど、お好みの通知方法を設定することができます。

それでは、引き続き、まずセンサーの読み取り値に関するクエリを作成し、Home Assistant 経由で HTTP リクエストを作成する方法と、クエリによって返されたデータに対してアクションを起こす方法を学びましょう。センサーの読み取り値の平均が高い場合の通知システムを設定した後、Home Assistant のダッシュボードにすべてのセンサーの読み取り値を表示したいと思います。

クエリの作成

まず、クエリを確定させましょう。私のざっとした調査では、pm1粒子は、非常に小さいため肺から直接吸収される可能性があり、健康への脅威となる可能性が最も高いと考えられます。

始める前に、cURL または Postman を使用して、探している結果が得られるまで HTTP クエリをテストしてみましょう。私の場合は、結果に満足するまで Postman を使用しました。私が最終的に決めたクエリは次のとおりです。SELECT AVG(pm1) FROM aqdata WHERE ts > TIMESTAMP_ADD(HOUR, NOW(), -1) AND ts < NOW() AND pm1 > 10 "。このクエリは過去1時間のデータを調べ、平均値が10を超える場合はデータを返します。ただし、pm1粒子の不健康な量について厳密な調査は行っていないのでご注意ください。これは単にデモ目的です。しかし、クエリが得られたので、Home Assistant 経由で HTTP リクエスト を送信する方法を理解できます。

Home Assistant による HTTP リクエストの作成

Home Assistant の /config ディレクトリ内には、ソフトウェア自体の設定や自動化の変更に使用される yaml ファイルが多数あります。非常に柔軟でカスタマイズが可能です。 今回は、設定用 yaml 内に rest_command と呼ばれるものを追加します。また、rest_command から返されたデータに基づいて実行するアクションの自動化も追加したいと考えています。これは scripts.yaml ファイルで実行されます。 すべての yaml ファイルは、上記の GitHub ページのソースコードに含まれています。

それでは、HTTP リクエストを実行するために必要な configuration.yaml ファイルの内容を以下に示します。

#configuration.yaml
rest_command:
  griddb_cloud_get_aqdata:
    url: https://cloud5197.griddb.com/griddb/v2/gs_clustermfcloud5197/dbs/B2xcGQJy/sql
    method: post
    content_type: "application/json"
    headers:
      authorization: "Basic <redacted>"
    payload: '[{"type" : "sql-select", "stmt" : "SELECT AVG(pm1) FROM aqdata WHERE ts > TIMESTAMP_ADD(HOUR, NOW(), -1) AND ts < NOW() AND pm1 > 10 "}]'</redacted>

ご覧の通り、HTTP Sql Select リクエストをクラウドインスタンスに送信するために必要なものはすべて含まれています。ここでは、rest_commandgriddb_cloud_get_aqdata と名付けています。これにより、scripts ファイルでこの service を直接呼び出すことができます。

#scripts.yaml
get_griddb_data:
  sequence:
    - service: rest_command.griddb_cloud_get_aqdata
      response_variable: aqdata
    - if: "{{ aqdata['status'] == 200 }}"
      then:
        - alias: Parse data
          variables:
            results: "{ {aqdata['content']['results'] }}"
        - if: "{{ results != 'null'}}"
          then:
            service: light.turn_on
            target:
              entity_id: light.living_room
            data:
              rgb_color:
                - 240
                - 0
                - 0

ご覧の通り、rest_command.griddb_cloud_get_aqdata をサービスとして呼び出しています。これにより、HTTPリクエストが実行され、その結果のデータが解析されます。結果がnull(閾値以上のデータがないことを意味する)の場合は何も起こりませんが、データが取得された場合は何らかのアクションを起こすことができます。この場合、リビングルームの照明を「オン」に切り替え、RGBを完全に赤色に変更します。これにより、家の中の誰もが空気の質が何らかの形で悪化していることを知ることができます。

そこで、yamlファイルの動作についてもう少し説明すると、構成yamlでは「サービス」を作成することができます。私たちのケースでは、HTTPリクエストです。スクリプトファイルは、さまざまな場所で何度も実行される可能性があるアクション用です。ソフトウェアの関数と少し似ています。最後に、automations.yaml を使用して、スクリプトを実行すべきタイミングのトリガーを設定します。 今回は、10分ごとに実行したいと思います。つまり、10分ごとにHome Assistantが過去1時間の平均 pm1 レベルを調べ、高すぎる場合は対応します。

#automations.yaml
- id: '1713475588605'
  alias: Get GridDB Data
  description: get the griddb data
  trigger:
  - platform: time_pattern
    minutes: /10
  condition: []
  action:
  - service: script.get_griddb_data
  mode: single

ここから、自動化がGridDBのデータを取得するために、当社のスクリプトを呼び出していることがわかります。また、トリガーが10分間隔であることもわかります。

センサーデータをホームアシスタントダッシュボードに表示

最後に、センサーの読み取り値をホームアシスタントダッシュボードに直接表示します。これにより、すべてのホームユーザーが常に読み取り値を認識できるようになります。これを行うには、sensors.yamlファイルを使用し、センサー平均センサーデータを読み取るHTTPリクエストの新しい仮想「センサー」を確立する必要があります。これを行うには、新しいSQLクエリを作成する必要があり、今回は過去24時間の平均値で対応できると思います。そのクエリは次のようになります。SELECT ROUND(AVG(pm1)),ROUND(AVG(pm25)),ROUND(AVG(pm10)),ROUND(AVG(particles03)),ROUND(AVG(particles05)),ROUND(AVG(particles10)),ROUND(AVG(particles25)),ROUND(AVG(particles50)),ROUND(AVG(particles100)) FROM aqdata WHERE ts > TIMESTAMP_ADD(DAY, NOW(), -1) AND ts < NOW()。 過去1日間の丸められた平均をすべて取得し、その情報をダッシュボードに表示します。

#sensors.yaml
- platform: rest
  resource: https://cloud5197.griddb.com/griddb/v2/gs_clustermfcloud5197/dbs/B2xcGQJy/sql
  method: POST
  headers:
    authorization: "Basic <redacted>"
    Content-Type: application/json
  payload: '[{"type" : "sql-select", "stmt" : "SELECT ROUND(AVG(pm1)),ROUND(AVG(pm25)),ROUND(AVG(pm10)),ROUND(AVG(particles03)),ROUND(AVG(particles05)),ROUND(AVG(particles10)),ROUND(AVG(particles25)),ROUND(AVG(particles50)),ROUND(AVG(particles100)) FROM aqdata WHERE ts > TIMESTAMP_ADD(DAY, NOW(), -1) AND ts < NOW() "}]'
  value_template: "{{ value_json[0].results[0][0] }}"
  name: "Particle Matter 1"
  scan_interval: 3600</redacted>

ここでは、このセンサーをエンティティとして選択した際に表示される結果として value_template を使用します。残念ながら、複数の値を持つ単一のHTTPリクエストの使用方法が分からなかったため、結果のインデックス配列の位置を変更するだけで、各値を独自のHTTPリクエストとして作成する必要がありました。例えば、上記の例はコンテナスキーマにより結果配列のインデックスが0である pm1 です。

したがって、センサーの設定が完了したので、ダッシュボードに移動して表示するように追加することができます。編集(右上隅の鉛筆)をクリックし、カードを追加し、次にグランスカードを追加し、そして手動でセンサーをすべて追加します。私の場合、テキストエディタは次のようになります。

show_name: true
show_icon: true
show_state: true
type: glance
entities:
  - entity: sensor.particle_matter_1
  - entity: sensor.particle_matter_2_5
  - entity: sensor.particle_matter_10
  - entity: sensor.particles_03
  - entity: sensor.particles_05
  - entity: sensor.particles_10
  - entity: sensor.particles_25
  - entity: sensor.particles_50
  - entity: sensor.particles_100
title: Daily Air Quality Averages
columns: 3
state_color: false

そして今、私たちは日々の平均値を一目で確認することができます。もちろん、このように簡単に利用できる情報からできることはこれだけではありません。これは、オープンソースのHome Assistantがホームオートメーションを動かすことの素晴らしさであり、GridDB Cloudがすべてのデータをホスティングしていることの素晴らしさです。インターネットに接続できれば、どこからでもデータにアクセスでき、どこからでもデータをアップロードできます。もちろん、遠隔地や離れた場所にいる愛する人の空気の質を監視し、必要に応じて対応することも可能です。

結論

本記事では、物理的なハードウェアセンサーのデータを接続する方法と、その素晴らしいデータをすべてGridDB Cloudにプッシュする方法を学びました。そして、Home Assistantを使用して、そのデータに対して直接対応できることを学びました。

ブログの内容について疑問や質問がある場合は Q&A サイトである Stack Overflow に質問を投稿しましょう。 GridDB 開発者やエンジニアから速やかな回答が得られるようにするためにも "griddb" タグをつけることをお忘れなく。 https://stackoverflow.com/questions/ask?tags=griddb

Leave a Reply

Your email address will not be published. Required fields are marked *