LoginSignup
62
79

More than 3 years have passed since last update.

部屋のCO2濃度を、Raspberry Pi / AWS IoT / Kibanaで可視化/通知する

Last updated at Posted at 2020-04-21

動機

部屋に長くこもっているとだんだん眠くなって集中力が落ちてくるのは酸素が薄くなっている(≒二酸化炭素濃度が上がっている)からなのでは?と思い調べたらやはりそういう研究結果があるらしい。
参考: https://www.excite.co.jp/news/article/Rocketnews24_259718/
室内では1000ppmを超えない方が良いらしいけど自宅の家電で測れないので換気のタイミングがわからない。
=> Raspberry PiにCO2センサーくっつけてデータをクラウドに送ったら色々出来るだろうし、AWS IoTとAWS IoT Greengrassのハンズオンがてらやってみよう。

目標

以下のような構成で、Raspberry Pi上のCO2センサーデータ取得し、Kibanaでデータを可視化します。
さらにCO2濃度が閾値を超えたらSlackに通知するようにします。
image.png
AWS IoT Greengrassを使いLambda関数をRaspberry Pi上で稼働させてCO2センサーの値を読み取り、AWS IoT Coreに送信します。そこから、IoT Rule Actionを使いAmazon Elasticsearch Serviceにデータを連携するという構成です。単にデータを送信して可視化するだけにしては大げさですが、Greengrassの導入やAWS IoTへのデータ送信が出来ていれば、このあとAWSの様々なサービスを組み合わせることで色々な応用が効きます。(AWS IoT Analyticsでデータ処理/蓄積してQuickSightで可視化したり、SageMakerで機械学習モデルを作成してGreengrassにモデル配布、エッジで画像認識、異常値検出など。。。)

Disclaimer

  • 自宅向けの日曜大工のため手順やコードはプロダクションレベルの品質、セキュリティレベルではありません。
  • センサーや配線を扱う際はショートなどに十分気をつけてください。
  • AWS IoTやAmazon Elasticsearch ServiceのAWS利用料金が発生します。

    • AWS IoT Core
      • 本記事の構成(1台、5秒間隔Publish、メッセージサイズ5KB未満、Rule Action x 1)の場合は約1USD/月(東京リージョン) 料金表
      • サインアップ後12ヶ月の無料枠が適用される場合はさらに低くなります。
    • AWS IoT Greengrass
      • 今回の構成(1台)の場合は0.18USD/月 料金表
      • サインアップ後12ヶ月の無料枠(3台まで)が適用される場合は0USD/月。
      • Greengrass上で動作するLambdaには追加料金はかかりません。
    • Amazon Elasticsearch Service
      • 今回の構成(t2.small.elasticsearch/10GB EBS)の場合、約40USD/月 料金表
      • サインアップ後12ヶ月の無料枠(t2.small.elasticsearch/10GB EBS x 750h)が適用される場合は0USD/月

準備するもの

  • Raspberry Pi(本記事では3 Model B+ : 6000円前後) + SDカード/キーボード/HDMIケーブル等の起動/設定に必須なもの
  • MH-Z19 (CO2センサー) : Amazonだと4000円くらいですがもう少し安く買えるところもありそうです。
  • メス-メスのジャンパーワイヤー x 4本 : リンク先(Amazon)の120本セットで約900円
  • ピンヘッダ : リンク先(Amazon)のセットで約550円
  • AWSアカウント

Raspberry Piやセンサー周りで準備必要なものについては、こちらの記事:Raspberry Pi 3 で CO2濃度を測る (Raspbian OS)がとても参考になります。

前提条件

  • 作業環境 : Mac OS or Linux
  • Node.js :10.3.0以上
  • AWS CDK version : 1.33.0以上
  • Raspberry Pi Model : Raspberry Pi 3 Model B+
  • Rasbpian OS version : Buster
  • AWS IoT Greengrass version : 1.10.0

構築手順

AWS CLIのセットアップ

手元の作業環境にAWS CLIをインストールし、リソースを構築するAWSアカウントのクレデンシャルとリージョンの設定を実施しておきます。(手順省略)

Greengrass Core用 クライアント証明書の作成

Raspberry PiにインストールするGreengrass Core用のクライアント署名書を作成します。

1 Click証明書の場合 (ファイル名は例)

aws iot create-keys-and-certificate \
    --certificate-pem-outfile "raspi01.cert.pem" \
    --public-key-outfile "raspi01.public.key" \
    --private-key-outfile "raspi01.private.key" \
    --set-as-active

上記コマンドの出力結果から、"certificateArn"の値を控えておく。

(参考) 手元でキーペアとCSRを作成し、CSRから証明書を作成する場合 (ファイル名は例)

openssl genrsa -out client.key 2048
openssl req -new -key client.key -out client.csr 
aws iot create-certificate-from-csr \
     --certificate-signing-request=file://client.csr
     --set-as-active

上記コマンド結果から、"certificateArn"を控えておく。

クラウド環境の構築

AWS CDKのインストール

AWS CDKをインストールします。AWS CDKと使い方についてはBlackbelt参照。

npm install -g aws-cdk

本構成のサンプルコードのダウンロード

CDKをTypescript、Lambda関数をPython3で記述した本構成のコードをGitHubに置いてあります。

git clone https://github.com/f-daiki-86/mh-z19-aws-greengrass-cdk-sample.git
cd mh-z19-aws-greengrass-cdk-sample
ファイル構成(一部省略)
├── README.md
├── bin
│   └── app.ts  # CDK App本体
├── cdk.context.json
├── cdk.json    # CDK設定ファイル。今回はコンテキスト(パラメータ)を追記します
├── handlers    # Lambda関数コード用のフォルダ
│   └── co2-sensor-reader
│       ├── handler.py    # Lambda関数コード
│       └── requirments.txt
├── lib
│   ├── elasticsearch-stack.ts     # Elasticsearch用Stack定義
│   ├── greengrass-lambda-stack.ts # Lambda用Stack定義
│   ├── greengrass-stack.ts        # Greengrass用Stack定義
│   └── iot-rule-stack.ts          # IoT Rule用 Stack定義
├── package.json

依存パッケージのインストール

CDKのTypeScriptコードとLambda関数のPythonコードの依存パッケージをインストールします。

# CDKの依存パッケージをインストール
npm install

# Lamdda関数の依存パッケージをインストール
cd handlers/co2-sensor-reader/
mkdir packages
pip3 install -r requirments.txt -t packages/
cd ../../

CDKコンテキスト(パラメータ)の編集

コンテキストはSSM Parameterストアなどから取得する方法もありますが今回はcdk.jsonファイルに記載します。
cdk.json内のcontextの各項目を更新します。

  • "greengrassCoreThingName": Greengrass Coreに紐づくThingの名前です。iot-thing01など任意の名前でOK。
  • "grenngrassGroupName": Greengrass Groupの名前です。gg-group01など任意の名前でOK。
  • "greengrassCoreCertArn :"AWS IoT Core用クライアント明書の作成"で作成した証明書のARN。
  • "esSourceIp": 簡単のためKibanaのアクセス制限をCognitoによる認証ではなくIPアドレスでの制限としているため、自身のグローバルIPアドレスをCMANなどで調べて入力。
cdk.json
{
  "app": "npx ts-node bin/app.ts",
  "context": {
    "greengrassCoreThingName": "[Greengrass CoreのThingに付与するName(任意)]", 
    "grenngrassGroupName": "[Greengrass Group名(任意)]",
    "greengrassCoreCertArn": "[Greengrass Core用 クライアント証明書のARN]",
    "esSourceIp": "[Kibanaにアクセスを許可するIPアドレスレンジ]"
  }
}

スタックのデプロイ

aws cdkのコマンドを使用してスタックをデプロイします。


# ビルド
npm run build

# デプロイのためのS3バケット作成
cdk bootstrap

# Elasticsearchスタックのデプロイ
cdk deploy ElasticSearchStack

# Lambdaスタックのデプロイ
cdk deploy GreengrassLambdaStack

# Greengrassスタックのデプロイ
cdk deploy GreengrassStack

# IoT Ruleスタックのデプロイ
cdk deploy IoTRuleStack

全てエラーなく終了すればOKです。

MH-Z19(CO2)センサーを接続するためのRaspberry Piセットアップ

参考

UART有効化とシリアルコンソールの無効化

(Raspberry PiへのRaspian OSのインストール、Wifiのセットアップなどの準備は他の記事に譲ります。)
MH-Z19はUARTという仕組みでシリアル通信するため、Raspberri Piで有効にします。また、デフォルトではUARTで使うデバイスがシリアルコンソールに使われてしまっているため、無効化します。

SSHでRaspberry Piに接続します。

ssh pi@[Raspberry PiのIP Address]

UARTを有効化します。

sudo vi /boot/config.txt
/boot/config.txt
以下の行を追加する。
enable_uart=1

シリアルコンソールを無効化します。

sudo vi /boot/cmdline.txt
/boot/cmdline.txt
以下の記述を削除する。
console=serial0,115200

シリアルサービスを無効化します。

sudo systemctl stop serial-getty@ttyS0.service 
sudo systemctl disable serial-getty@ttyS0.servic

Raspberry Piを再起動します。

sudo reboot

確認

ls -ld /dev/ttyS0 
crw-rw---- 1 root dialout 4, 64 Apr  5 22:54 /dev/ttyS0

=> 再起動後にグループがttyではなくdialoutになっていれば恐らくOK.

Raspberry PiとCO2センサーの配線

こちらの記事:Raspberry Pi 3 で CO2濃度を測る (Raspbian OS)を参考に、Raspberry PiとMH-Z19を接続し、センサーの値が取得できることを確認します。

AWS IoT Greengrassのインストールと初期設定

参考

インストールに必要な依存関係の設定

SSHでRaspberry Piに接続します。

ssh pi@[Raspberry PiのIP Address]

Greengrass Core用User/Groupを作成します。

sudo adduser --system ggc_user
sudo addgroup --system ggc_group

ハードリンクとソフトリンクの保護を有効化します。

sudo vi /etc/sysctl.d/98-rpi.conf
/etc/sysctl.d/98-rpi.conf
以下を追加
fs.protected_hardlinks = 1
fs.protected_symlinks = 1

リブートします。

sudo reboot

メモリcgroup有効化します。

sudo vi /boot/cmdline.txt
/boot/cmdline.txt
以下を既存の行の末尾に追加
cgroup_enable=memory cgroup_memory=1

リブートします。

sudo reboot

Greengrass 依存関係チェッカーを実行し、要件が満たされていることを確認します。

cd /home/pi/Downloads
mkdir greengrass-dependency-checker-GGCv1.10.x
cd greengrass-dependency-checker-GGCv1.10.x
wget https://github.com/aws-samples/aws-greengrass-samples/raw/master/greengrass-dependency-checker-GGCv1.10.x.zip
unzip greengrass-dependency-checker-GGCv1.10.x.zip
cd greengrass-dependency-checker-GGCv1.10.x
sudo modprobe configs
sudo ./check_ggc_dependencies | more

実行結果に問題が表示されてないか確認します。
以下のResultではResultにNode.jsとJava8に関するNoteが出ていますが、LambdaのRuntimeとして使用しない場合は修正必須ではありません。

実行結果(抜粋)
<...snip...>

Result
------------------------------------Results-----------------------------------------
Note:
1. It looks like the kernel uses 'systemd' as the init process. Be sure to set the
'useSystemd' field in the file 'config.json' to 'yes' when configuring Greengrass core.

Missing optional dependencies:
1. Could not find the binary 'nodejs12.x'.

If NodeJS 12.x or later is installed on the device, name the binary 'nodejs12.x' and
add its parent directory to the PATH environment variable. NodeJS 12.x or later is
required to execute NodeJS lambdas on Greengrass core.

2. Could not find the binary 'java8'.

If Java 8 or later is installed on the device name the binary 'java8' and add its
parent directory to the PATH environment variable. Java 8 or later is required to
execute Java lambdas as well as stream management features on Greengrass core.

Supported lambda isolation modes:
No Container: Supported
Greengrass Container: Supported

Greengrass Coreのインストール

Greengrass Coreをダウンロード、展開します。
(アーキテクチャごとにダウンロードするファイルが違うので注意。RaspiはArmv7l)

wget https://d1onfpft10uf5o.cloudfront.net/greengrass-core/downloads/1.10.0/greengrass-linux-armv7l-1.10.0.tar.gz
sudo tar -xzvf greengrass-linux-armv7l-1.10.0.tar.gz -C /

AWS IoT Core クライアント証明書の作成で生成したクライアント証明書と秘密鍵を、SCP等で"/greengrass/certs"にコピーしておきます。
また以下の様にAmazon root CAのCA証明書を"/greengrass/certs/"にダウンロードします。

cd /greengrass/certs/
sudo wget -O root.ca.pem https://www.amazontrust.com/repository/AmazonRootCA1.pem

Greengrass Coreのコンフィグを編集します。[XXX]の箇所を適切な値に変更します。

sudo vi /greengrass/config/config.json
/greengrass/config/config.json

```json
# 変更前
  {
    "coreThing": {
        "caPath": "[ROOT_CA_PEM_HERE]",
        "certPath": "[CLOUD_PEM_CRT_HERE]",
        "keyPath": "[CLOUD_PEM_KEY_HERE]",
        "thingArn": "[THING_ARN_HERE]",
        "iotHost": "[HOST_PREFIX_HERE]-ats.iot.[AWS_REGION_HERE].amazonaws.com",
        "ggHost": "greengrass-ats.iot.[AWS_REGION_HERE].amazonaws.com"
    },
    "runtime": {
        "cgroup": {
            "useSystemd": "[yes|no]"
        }
    },
    "managedRespawn": false,
    "crypto": {
        "caPath" : "file://certs/[ROOT_CA_PEM_HERE]",
        "principals": {
            "IoTCertificate": {
                "privateKeyPath": "file://certs/[CLOUD_PEM_KEY_HERE]",
                "certificatePath": "file://certs/[CLOUD_PEM_CRT_HERE]"
            },
            "SecretsManager": {
                "privateKeyPath": "file://certs/[CLOUD_PEM_KEY_HERE]"
            }
        }
    }
  }             

 # 変更後(例)
  {
    "coreThing": {
        "caPath": "AmazonRootCA1.pem",
        "certPath": "raspi01.cert.pem",
        "keyPath": "raspi01.private.key",
        "thingArn": "arn:aws:iot:ap-northeast-1:[自アカウントのAccount Id]:thing/raspi01",
        "iotHost": "[自アカウントのIoT Endpoint Prifix]-ats.iot.ap-northeast-1.amazonaws.com",
        "ggHost": "greengrass-ats.iot.ap-northeast-1.amazonaws.com"
    },
    "runtime": {
        "cgroup": {
            "useSystemd": "yes"
        }
    },
    "managedRespawn": false,
    "crypto": {
        "caPath" : "file://certs/AmazonRootCA1.pem",
        "principals": {
            "IoTCertificate": {
                "privateKeyPath": "file://certs/raspi01.private.key",
                "certificatePath": "file://certs/raspi01.cert.pem"
            },
            "SecretsManager": {
                "privateKeyPath": "file://certs/raspi01.private.key"
            }
        }
    }
  }

Greengrassを起動します。

sudo /greengrass/ggc/core/greengrassd start

OS起動時にGreengrassが起動するようにします。

sudo vi /etc/systemd/system/greengrass.service
/etc/systemd/system/greengrass.service
[Unit]
Description=Greengrass Daemon

[Service]
Type=forking
PIDFile=/var/run/greengrassd.pid
Restart=on-failure
ExecStart=/greengrass/ggc/core/greengrassd start
ExecReload=/greengrass/ggc/core/greengrassd restart
ExecStop=/greengrass/ggc/core/greengrassd stop

[Install]
WantedBy=multi-user.target
sudo systemctl enable greengrass

GreengrassへのLambdaのデプロイ

GreengrassへのLambdaデプロイ

全てのCDKスタックのデプロイが完了したら、クラウドからRaspberry Pi上のGreengrass CoreにLambda Functionをデプロイします。

GG_NAME="[Greengrass Group名]"
GG_ID=$(aws greengrass list-groups --query "Groups[?Name==\`${GG_NAME}\`].Id" --output text)
GG_VERSION=$(aws greengrass list-groups --query "Groups[?Name==\`${GG_NAME}\`].LatestVersion" --output text)

GG_DEPLOYMENT_ID=$(aws greengrass create-deployment --deployment-type NewDeployment --group-id $GG_ID --group-version-id $GG_VERSION --query "DeploymentId" --output text)

aws greengrass get-deployment-status --group-id ${GG_ID} --deployment-id ${GG_DEPLOYMENT_ID}

上記コマンドの結果が以下のように"DeploymentStatus": "Success"になればデプロイ成功です。
(In Progressの場合は少し待ってもう一度実行する。)


{
    "DeploymentStatus": "Success",
    "DeploymentType": "NewDeployment",
    "UpdatedAt": "2020-04-19T12:30:25.841Z"
}

動作確認

Raspberry Piにログインし、Lambdaのログを確認します。

# /greengrass/ggc/var/log/user/[Region]/[Account ID] 以下にLambdaのログが出力されるはずです。
sudo tail -f /greengrass/ggc/var/log/user/[Region]/[Account ID]/GreengrassLambdaStack-GreengrassLambdaHandlerxxxxx-xxxxxxxxxxxx.log

問題なければ以下のようなログが出力されます。

[2020-04-19T19:50:20.522+09:00][DEBUG]-Lambda.py:96,Invoking Lambda function "arn:aws:lambda:::function:GGRouter" with Greengrass Message "{"device_name": "raspi01", "co2": 559, "timestamp": "2020/04/19 10:50:20"}"
[2020-04-19T19:50:20.522+09:00][INFO]-ipc_client.py:167,Posting work for function [arn:aws:lambda:::function:GGRouter] to http://localhost:8000/2016-11-01/functions/arn:aws:lambda:::function:GGRouter
[2020-04-19T19:50:20.533+09:00][INFO]-ipc_client.py:177,Work posted with invocation id [7f8cca07-301d-4438-5601-4cdd834f77bb]
[2020-04-19T19:50:25.564+09:00][INFO]-ipc_client.py:167,Posting work for function [arn:aws:lambda:::function:GGRouter] to http://localhost:8000/2016-11-01/functions/arn:aws:lambda:::function:GGRouter
[2020-04-19T19:50:25.564+09:00][DEBUG]-handler.py:49,CO2:560 ppm <= 取得されたCo2濃度
[2020-04-19T19:50:25.564+09:00][DEBUG]-IoTDataPlane.py:126,Publishing message on topic "data/raspi01/sensor/co2" with Payload "{"device_name": "raspi01", "co2": 560, "timestamp": "2020/04/19 10:50:25"}"

Kibanaの設定

Elasticsearchにデータが届いているはずなので、あとは好きなようにKibanaを設定します。
データは以下のようなJSON形式で5秒間隔でElasticsearchに届きます。

{
  "device_name": "[IoT Thing名]"
  "co2": "[CO2濃度(ppm)]"
  "timestamp": "[センサーから値を読んだ時間(UTC)]"
{

インデックスパターンを作成します。
Index nameを"sensor-data*"、Time Filter field nameを"timestamp"としてインデックスパターンを作成します。
image.png

グラフやダッシュボードを作成します。

  • グラフ(Visualization)の設定例 image.png

必要に応じてSlackへの閾値アラートを設定します。
以下の例では、CO2濃度の過去1分間の平均値が800ppmを超えたらSlack Channelにメッセージを飛ばします。
(800は比較的低めの設定で、料理したり運動してるとすぐ超えます。)
直接Slackに通知する以外にも、DestinationにAmazon SNS Topicを設定することでE-mail/SMS/Mobile Push/他AWSサービスへの連携も簡単にできます。

  • Destination(アラート通知先)設定例
    (事前にSlackでWebhook URLを取得しておきます)
    image.png

  • Monitor設定例
    image.png

  • Trigger設定例
    image.png
    (続き)
    image.png

  • Slack通知例
    image.png

以上です。

62
79
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
62
79