はじめに
今回は弊社製品のメタルケースセット(ADPi Pro + slee-Pi 3)を使用し、アナログ出力型の測距モジュール GP2Y0A21YK で計測した距離を、ラズパイ上のWebサーバーにChart.jsを使用し、リアルタイムで表示する方法をご紹介します。
メタルケースセット(ADPi Pro + slee-Pi 3)には、Raspberry Pi 4 Model Bと、高精度AD変換モジュールのADPi Proと、電源管理や時刻の保持が可能なslee-Pi 3が搭載されています。さらに、環境構築済みSDカードやACアダプタ等も付属しており、すぐにお使いいただくことが可能です。
使うもの
- 測距モジュール GP2Y0A21YK
- ADPi Proへの取り付けのために、数cm程度の配線(オス-オスのジャンパ線等でも構いません)と、データシートを参考に10μFのコンデンサもご用意ください。
- データシートとアプリケーションノートはそれぞれこちらを参照してください。
- 開発用のPC、ラズパイへのログイン用のUSB-シリアル変換ケーブルなど
ハードウェアの準備
今回はADPi ProのCH1に測距モジュールを接続します。メタルケースセットのカバーを外し、CH1のターミナルを外します。
図の向きのとき、上から12V、CH1+、CH1-、GNDとなっています。測距モジュールのデータシートを参考に接続してください。また、CH1-とGNDは用意した配線で短絡し、12VとGNDの間には10μFのコンデンサを接続します。
図を参考にADPi ProのCH1にターミナルを取り付け、付属の「DCジャック - XHP-2コネクタ変換ハーネス」をslee-pi 3のCN1に取り付け、付属のACアダプタから電源を投入します。
ソフトウェアの準備
電源を投入し、ラズパイが起動したらPCからログインしてください。初期ログイン情報はこちらを参照してください。ラズパイへのログイン方法についてはこちらなどを参照してください。
ログインができたら、PCと同一のLAN内にラズパイを接続してください。
任意のディレクトリに移動し、以下の2つのスクリプト(get_adpi.py と web_server.py)を作成します。今回は、/home/mtx/adpi-web-monitor
に作成しました。ディレクトリを変更する場合はそれぞれのスクリプトのDIR=
も変更してください。
import spidev
import smbus
import adpi
import sys
import time
import csv
import os
from datetime import datetime
from time import sleep
DIR = "/home/mtx/adpi-web-monitor"
INTERVAL_SEC = 1 # 計測のインターバル(秒)
ADPI_CH = "1" # 使用するADPiのチャンネル
RAW_OFFSET = (1 << 23) # AD7794の24bit ADC出力(2の補数形式)で0Vに相当する値(=8388608)
RAW_SCALE = (
0.000596040,
0.000298020,
0.000149010,
0.000074500,
0.000037250,
0.000018620,
0.000009310,
0.000004650,
) # AD7794のゲインごとの1bitあたりの電圧換算係数(V/LSB)
TEMP_VREF = 1.17 # AD7794内部温度センサ用の基準電圧(V)
def v2k(rate, val):
for k, v in rate.items():
if v == val:
return k
def set_calib(dev):
g = dev.adc.gain['1']
r = dev.adc.rate['470']
bias = dev.load_bias(g)
scale = dev.load_scale(g)
_, r = dev.read_mode()
for i in range(dev.channels):
dev.write_configuration(g, i)
dev.write_mode(dev.adc.mode['idle'], r)
dev.write_offset(bias[i])
dev.write_mode(dev.adc.mode['idle'], r)
dev.write_fullscale(scale[i])
def single_conversion(dev, ch):
c = dev.adc.channel[ch]
g, _ = dev.read_configuration()
dev.write_configuration(g, c)
_, r = dev.read_mode()
dev.write_mode(dev.adc.mode['single'], r)
rate = v2k(dev.adc.rate, r)
while True:
sleep(2 * 1.0 / float(rate))
if not dev.read_status() & 0x80:
break
raw = dev.read_data()
return raw, g
def get_voltage(dev, ch):
raw, g = single_conversion(dev, ch)
vol = RAW_SCALE[g] * (raw - RAW_OFFSET)
return vol
def calc_distance(v):
d = 22 / (v - 0.13) * 1000
return d
def ctrl_output(dev, ch, state):
c = dev.adc.channel[ch]
dev.set_output(c, state)
if __name__ == "__main__":
spibus = 0
spics = 0
eeprombus = 1
eepromaddr = 0x57
gpiobus = 1
gpioaddr = 0x27
spi = spidev.SpiDev()
i2c = smbus.SMBus(eeprombus)
timestamp = datetime.now().strftime("%Y%m%d_%H%M")
csv_path = f"{DIR}/sensor_data_{timestamp}.csv"
try:
spi.open(spibus, spics)
spi.mode = 0b11
spi.max_speed_hz = 1000000
ad = adpi.ADPiPro(spi, i2c, eepromaddr, gpioaddr)
ctrl_output(ad, ADPI_CH, True)
set_calib(ad)
sleep(0.5)
with open(csv_path, "w", newline="") as f:
writer = csv.writer(f)
writer.writerow(["timestamp", "distance[cm]"])
link_path = f"{DIR}/sensor_data_latest.csv"
if os.path.islink(link_path) or os.path.exists(link_path):
os.remove(link_path)
os.symlink(csv_path, link_path)
while True:
voltage = get_voltage(ad, ADPI_CH)
distance = calc_distance(voltage)
with open(csv_path, "a", newline="") as f:
writer = csv.writer(f)
writer.writerow([
datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
"{:.9f}".format(distance)
])
sleep(INTERVAL_SEC)
except (IndexError, ValueError):
sys.exit(2)
finally:
try:
ctrl_output(ad, ADPI_CH, False)
except:
pass
spi.close()
from flask import Flask, jsonify, render_template_string
import csv
import os
app = Flask(__name__)
DIR = "/home/mtx/adpi-web-monitor"
CSV_LATEST_PATH = f"{DIR}/sensor_data_latest.csv"
TITLE = "ADPi Monitor"
DATA_NUM = 10 # 表示するデータの件数
REFRESH_INTERVAL = 500 # 画面の更新(ミリ秒)
@app.route("/api/latest")
def api_latest():
with open(CSV_LATEST_PATH) as f:
reader = list(csv.DictReader(f))
rows = reader[-DATA_NUM:]
return jsonify(rows)
@app.route("/")
def index():
return render_template_string(f"""
<html>
<head>
<meta charset="UTF-8">
<title>{TITLE}</title>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
</head>
<body>
<h1>{TITLE}</h1>
<canvas id="chart" width="960" height="540"></canvas>
<script>
const REFRESH_INTERVAL = {REFRESH_INTERVAL};
async function fetchData() {{
const res = await fetch("/api/latest");
const data = await res.json();
const labels = data.map(d => d["timestamp"]);
const distances = data.map(d => parseFloat(d["distance[cm]"]));
const ctx = document.getElementById('chart').getContext('2d');
if (window.myChart) {{
window.myChart.data.labels = labels;
window.myChart.data.datasets[0].data = distances;
window.myChart.update();
}} else {{
window.myChart = new Chart(ctx, {{
type: 'line',
data: {{
labels: labels,
datasets: [{{
label: 'Distance (cm)',
data: distances,
borderWidth: 2
}}]
}},
options: {{
responsive: true,
scales: {{
y: {{
min: 0,
max: 80,
beginAtZero: true
}}
}}
}}
}});
}}
}}
fetchData();
setInterval(fetchData, REFRESH_INTERVAL);
</script>
</body>
</html>
""")
if __name__ == '__main__':
app.run(host="0.0.0.0", port=8000)
Webサーバーのセキュリティ対策について
本記事のWebサーバーはセキュリティ対策を考慮していません。
インターネット越しのアクセスは想定していないため、同一のLAN内での利用に限定してください。
外部公開を行う場合は各種セキュリティ対策の実施をお願いいたします。
次に、Pythonモジュールを実行する仮想環境を構築します。/home/mtx/adpi-web-monitor
で次のコマンドを実行してください。
python3 -m venv venv
source venv/bin/activate
sudo apt install python3-dev
pip install flask spidev smbus
cp -r /usr/lib/python3/dist-packages/adpi ~/adpi-web-monitor/venv/lib/python3.11/site-packages/
仮想環境の構築が完了したら下記のコマンドで仮想環境を終了します。
deactivate
ラズパイ起動時に作成したスクリプトを自動実行する
続いて下記の2つのサービスファイル( get_adpi.service と web_server.service )を作成します。ここでは、/home/mtx/adpi-web-monitor
に作成しています。
[Unit]
After=network.target
[Service]
WorkingDirectory=/home/mtx/adpi-web-monitor
ExecStart=/home/mtx/adpi-web-monitor/venv/bin/python3 get_adpi.py
Restart=on-failure
RestartSec=10
[Install]
WantedBy=multi-user.target
[Unit]
After=network.target get_adpi.service
Requires=get_adpi.service
[Service]
WorkingDirectory=/home/mtx/adpi-web-monitor
ExecStart=/home/mtx/adpi-web-monitor/venv/bin/python3 web_server.py
Restart=on-failure
RestartSec=10
[Install]
WantedBy=multi-user.target
下記のコマンドを実行し、ラズパイの起動時に自動実行されるようにします。
sudo cp /home/mtx/adpi-web-monitor/get_adpi.service /etc/systemd/system/
sudo cp /home/mtx/adpi-web-monitor/web_server.service /etc/systemd/system/
sudo systemctl daemon-reload
sudo systemctl enable get_adpi.service web_server.service
サービスファイルの設定が完了したらシャットダウンします。
sudo shutdown -h now
起動の確認
メタルケースセットのカバーを閉じます。ADPi ProのCH1のターミナルを一度外し、カバーを閉じてから再度取り付けてください。
今回は、測距モジュールをカバーを留めるねじと共締めしてケースに固定しました。
ラズパイを起動し、下記のようにIPアドレス(192.168.YY.ZZZ)を確認します。続いてWEBブラウザから確認したIPアドレスを使用し、192.168.YY.ZZZ:8000
にアクセスしてください。
$ ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
...
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP group default qlen 1000
link/ether XX:XX:XX:XX:XX:XX brd ff:ff:ff:ff:ff:ff
inet 192.168.YY.ZZZ/24 brd 192.168.XX.255 scope global dynamic noprefixroute eth0
valid_lft 12671sec preferred_lft 12671sec
inet6 XXXX:XXXX:XXXX:XXXX:XXXX:XXXX:XXXX:XXXX/64 scope global dynamic noprefixroute
valid_lft 14305sec preferred_lft 14305sec
inet6 fe80::XXXX:XXXX:XXXX:XXXX/64 scope link noprefixroute
valid_lft forever preferred_lft forever
...
下記のように計測距離をWEBブラウザに表示し、測距モジュールの前にものを置くと値が変化する様子を確認できました。
注意事項
-
作成した2つのスクリプト( get_adpi.py と web_server.py )は、仮想環境で実行してください。仮想環境を有効にするには
source venv/bin/activate
を実行します。仮想環境を終了するにはdeactivate
を実行します。 -
サービスを使わずに動作確認を行いたい場合は、
python3 get_adpi.py &
のようにバックグラウンドで get_adpi.py を実行してからpython3 web_server.py
を実行してください。バックグラウンドで動作中の get_adpi.py を停止するにはpkill -f get_adpi.py
を実行する方法などがあります。 -
get_adpi.py の実行中に強制終了や異常終了した場合などに、ADPi Proからセンサへの電源供給が継続することがあります。電源供給を止めるには
adpictl set output 1 off
を実行してください。adpictl
コマンドの詳細はこちらを参照してください