raspi

ラズパイに温度湿度センサーを付けてGrafanaで可視化する

はじめに

ちょっと前 (1年前) にラズパイで温度湿度可視化してみたときのメモ.
最終的にPrometheus・Grafanaで可視化できることを目指します.

環境

使用するラズパイの環境は以下.

  • raspi: 3B
  • OS: ubuntu 20.04

温度湿度センサー (AM2320)

温度センサーには,AM230を使用します.

https://amzn.asia/d/hOp48C1

また,ラズパイと接続するためのジャンパーワイヤーとブレッドボードも必要になります.

https://amzn.asia/d/09KagBJ

ラズパイとセンサーの接続

ラズパイとセンサーを接続していきます.

I2C

ラズパイとセンサーはI2C(Inter-Integrated Circuit)で通信します.I2Cは低速な周辺機器を接続するシリアル通信の規格です.

I2Cでは,マスタスレーブの方式で通信します.マスタは複数のスレーブに呈して,データの読み取り等が可能です.
情報通信では以下2つのバスラインを使用します.

  • SDA (Serial Data line): データ通信
  • SCL (Serial Clock line): クロック供給

ラズパイとの接続

ラズパイと温度センサーの接続は,SDA/SCLの接続と,電源・GNDの接続が必要です.
今回は以下の様に接続します.

センサーの値の読み取り

接続後,I2C Toolsのi2cdetectコマンドで接続できていることを確かめます.また,センサーがどのアドレスであるのかもメモしておきます.

shu1r0@raspi3node1:~/workspace$ i2cdetect -l
i2c-1   i2c             bcm2835 (i2c@7e804000)                  I2C adapter
shu1r0@raspi3node1:~/workspace$ sudo i2cdetect -y 1
     0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f
00:                         -- -- -- -- -- -- -- -- 
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
50: -- -- -- -- -- -- -- -- -- -- -- -- 5c -- -- -- 
60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
70: -- -- -- -- -- -- -- --       

センサーを読み取るコードは以下となります.ファイル名はcapture_exporter.pyです.

import sys
import time
import smbus


SLAVE_ADDR = 0x5c  # i2cdetectで確認したアドレス

FUNCTION_READ = 0x03


def get_trh(i2c):
    read_register = 0x00  # 読み取るレジスタアドレス
    data_num = 0x04  # データ数
    # スリープの解除
    try:
        i2c.write_i2c_block_data(SLAVE_ADDR, 0x00, [])
    except:
        pass

    # 読み取り命令 (800μsec以上,3msec以下で要求を送信)
    time.sleep(0.002)
    i2c.write_i2c_block_data(SLAVE_ADDR, FUNCTION_READ, [read_register, data_num])

    # データ取得
    time.sleep(0.015)
    block = i2c.read_i2c_block_data(SLAVE_ADDR, read_register, data_num+4)
    humidity = float(block[2] << 8 | block[3])/10
    temperature = float(block[4] << 8 | block[5])/10
    
    return temperature, humidity


def main ():
    i2c = smbus.SMBus(1)
    try:
        while True:
            temperature, humidity = get_trh(i2c)
            print('温度={}℃  湿度={}%'.format(temperature, humidity))
            time.sleep(1)

    except KeyboardInterrupt:
            sys.exit(0)


if __name__ == '__main__':
    main()

読み取り部分では,Read命令である0x03で値を読み取ってます.このFunctionCodeは,データシートに記載されているものです.

    # 読み取り命令 (800μsec以上,3msec以下で要求を送信)
    time.sleep(0.002)
    i2c.write_i2c_block_data(SLAVE_ADDR, FUNCTION_READ, [read_register, data_num])

    # データ取得
    time.sleep(0.015)
    block = i2c.read_i2c_block_data(SLAVE_ADDR, read_register, data_num+4)
    humidity = float(block[2] << 8 | block[3])/10
    temperature = float(block[4] << 8 | block[5])/10

実行

センサー部分だけをローカルでの実行していきます.

pip3 install smbus
python3 capture_exporter.py &

ここで正しくセンサーの値を読み取れているのであれば,温度と湿度がプリントされていくはずです.

Grafanaでの可視化

Exporter側は作成しますが,Prometheus ServerとGrafanaの環境はすでにあることを前提にしてます.

Exporterの作成

読み取った値をGrafanaで可視化するためにExporterを作成します.
最終的なファイル構成は以下となります.

$ tree --dirsfirst -L 3
.
├── flask
│ └── Dockerfile
├── nginx
│ └── nginx.conf
├── trh_exporter
│ ├── capture_exporter.py
│ ├── capture_trh.py
│ ├── __init__.py
│ └── __main__.py
├── docker-compose.yml
├── README.md
├── requirements.txt
└── uwsgi.ini

まずは,APIを定義するcapture_exporter.pyです.ExporterのAPI実現のためにFlaskも利用しています.メトリクスの定義として,温湿度はリアルタイムの数値なのでGaugeで定義します.また,Labelは好みですが,deviceでraspi3を指定しています.

import smbus
from flask import Flask, Response
from prometheus_client import Gauge, Counter, generate_latest

from .capture_trh import get_trh

app = Flask(__name__)

CONTENT_TYPE_LATEST = str("text/plain; version=0.0.4; charset=utf-8")

number_of_requests = Counter("number_of_requests", "The number of requests")
temperature = Gauge("current_temperature", "temperature", ["device"])
humidity = Gauge("current_humidity", "humidity", ["device"])


i2c = smbus.SMBus(1)


@app.route("/metrics", methods=["GET"])
def return_trh():
    number_of_requests.inc()
    try:
        t, h = get_trh(i2c)
        print("温度={}℃  湿度={}%".format(t, h))
        temperature.labels(device="raspi3").set(t)
        humidity.labels(device="raspi3").set(h)
    except Exception as e:
        print("ERROR: " + str(e))
    return Response(generate_latest(), mimetype=CONTENT_TYPE_LATEST)

app.runする部分は__main__.pyに定義します.

from .capture_exporter import app


if __name__ == "__main__":
    app.run(debug=True, host="0.0.0.0", port=9999)

Docker

次にExporterを実行するための環境を作ります.

docker-compose以下の構成になってます.

version: '3'

services:

  flask:
    container_name: flask
    build: 
      context: .
      dockerfile: ./flask/Dockerfile
    restart: always
    privileged: true
    volumes:
    - ./:/usr/src/app/
    environment:
      TZ: Asia/Tokyo
    command: uwsgi --ini /usr/src/app/uwsgi.ini

  nginx:
    container_name: nginx
    image: nginx:latest
    restart: always
    volumes:
    - "./nginx/nginx.conf:/etc/nginx/nginx.conf"
    ports:
      - "9999:9999"
    environment:
      TZ: Asia/Tokyo

また必要なファイルを構成していきます.
Dockerfileは以下のように定義します.

FROM python:3.10

RUN apt-get update && apt install -y i2c-tools python3-smbus

COPY ../requirements.txt /usr/src/app/
RUN pip3 install --no-cache-dir -r /usr/src/app/requirements.txt

WORKDIR /usr/src/app/

requirements.txt

smbus
flask
prometheus_flask_exporter
prometheus_client
uwsgi

uwsgi.ini

[uwsgi]
module = trh_exporter.capture_exporter
callable = app
master = true
processes = 1

socket = :9999

# 実行するpythonファイル
chdir = /usr/src/app/
wsgi-file = /usr/src/app/trh_exporter/capture_exporter.py

logto = /usr/src/app/capture_exporter.log

また,nginx.confは以下のように記述します.

# 実行ユーザー
user nginx;
# 使用可能process数
worker_processes 1;
# プロセスID
pid /var/run/nginx.pid;

# イベント処理モジュール
events {
  worker_connections  1024;
}

# http関連のモジュール
http {
  upstream uwsgi {
    server flask:9999;
  }

  server {
    listen 9999;
    charset utf-8;

    location / {
      include uwsgi_params;
      uwsgi_pass uwsgi;
    }
  }
}

あとは実行するだけです.

docker compoose up -d

Grafanaでの可視化

Prometheus ServerとGrafanaの環境は構築済みです.
まず,Prometheusにデータを食わせるためにprometheus.ymlに以下を追記します.

  - job_name: Get Temperature and Humidity
    static_configs:
      - targets: ['raspi3node1.shu1r0.net:9999']

あとはGrafanaで可視化するだけ!! おわり!

コメント

タイトルとURLをコピーしました