超音波距離センサを用いて,灯油の残量を確認でき,少なくなったらアラートするシステムを作ります.
用いるもの
- Raspberry Pi 3B: 始めた時は Raspberry Pi zero WH がまだ発売されていなかったため,3Bです.
- 超音波距離センサ HC-SR04: 今回のメインのセンサ.超音波の送信から受信までの時間差から距離を求めるもの.
- 温度センサ ADT7410: 音速は温度によって変わるため,距離センサの計測精度向上のために用いる.
- LED, タクトスイッチ, 自励式ブザー: 完成度を上げるため.
- ユニバーサル基板,ジャンパー線,抵抗,トランジスタなど: 必要に応じて.
金額は税込,購入当時のもの.
Raspberry Pi 3B
適当にセットアップします.
もしよろしければ参考にしてください: Qiita: y_k: 【ヘッドレス】Raspbery Pi 3 セットアップ for macOS
追加のセットアップ項目としては,python3のインストールです.apt
でインストールしてください.
超音波距離センサ (HC-SR04)
仕様
- 電源: DC 5.0V, 15mA
- 測距範囲: 2cm〜400cm (分解能: 0.3cm), センサー基板正面を中心とした15度の範囲
- 動作周波数: 40kHz
入出力
- 入力: トリガー
- High 10μsで計測を開始
- 出力: エコー
- 反射(往復)時間
仕組み
- トリガーに10μs以上信号入力
- センサーからパルス波を8回送信 -> センサー受信
- パルス波の往復時間をエコーから出力
- $\frac{\text{往復時間}}{2}$を音速で割ることで距離を求めることができる
音速
- 海面: 340.29 m/s (Google)
- 一次のテイラー展開近似式: $v = 331.5 + 0.6 t$ ($t$: 摂氏温度)
- 気温 0℃: 331.5 m/s
- 気温 30℃: 349.5 m/s
回路
実際に距離を測る
Pythonでpigpioを用いてプログラムを作成します.
$ sudo apt install pigpio python3-pigpio
$ sudo pigpiod
#!/usr/bin/env python3
import pigpio
import time
HC_SR04_trig = 17
HC_SR04_echo = 27
pi = pigpio.pi()
pi.set_mode(HC_SR04_trig, pigpio.OUTPUT)
pi.set_mode(HC_SR04_echo, pigpio.INPUT)
try:
while True:
# Trig
# 10μs信号出力
pi.gpio_trigger(HC_SR04_trig, 10, 1)
# Echo
while pi.read(HC_SR04_echo) == 0:
signaloff = time.time()
while pi.read(HC_SR04_echo) == 1:
signalon = time.time()
# 往復にかかった時間
timepassed = signalon - signaloff
# 距離を計算 (cm)
# 音速は 340m/s とする
distance = timepassed * 340 * 100 / 2
print(distance)
time.sleep(1)
except KeyboardInterrupt:
pass
pi.stop()
$ python3 meas_dist.py
12.68625259399414
10.793447494506836
10.761022567749023
13.444185256958008
13.51308822631836
13.59415054321289
13.48876953125
13.50092887878418
^C%
ctrl+c
で止めれます.
Ambientを用いて可視化
Ambientに測定値をアップロードして,可視化を行います.
無料で利用できますので,右上の「ユーザー登録」から,ユーザ登録してください.
ログイン後は「Myチャネル」から,チャネルを作成します.
「チャネルを作る」をクリックするとチャネルが作成されます.
次に,Ambient: Pythonライブラリーを参考にライブラリをインストールし,スクリプトを作成します.
$ pip install git+https://github.com/AmbientDataInc/ambient-python-lib.git
実際に,ライブラリを使い,データを送信するときは以下のようにします.
import ambient
am = ambient.Ambient(チャネルId, ライトキー[, リードキー[, ユーザーキー]])
r = am.send({'d1': 数値, 'd2': 数値})
組み合わせると以下のようになります.
#!/usr/bin/env python3
import pigpio
import time
import ambient
HC_SR04_trig = 17
HC_SR04_echo = 27
ambient_ch_id = チャネルId
ambient_write_key = "ライトキー"
pi = pigpio.pi()
pi.set_mode(HC_SR04_trig, pigpio.OUTPUT)
pi.set_mode(HC_SR04_echo, pigpio.INPUT)
am = ambient.Ambient(ambient_ch_id, )
try:
while True:
# Trig
# 10μs信号出力
pi.gpio_trigger(HC_SR04_trig, 10, 1)
# Echo
while pi.read(HC_SR04_echo) == 0:
signaloff = time.time()
while pi.read(HC_SR04_echo) == 1:
signalon = time.time()
# 往復にかかった時間
timepassed = signalon - signaloff
# 距離を計算 (cm)
# 音速は 340m/s とする
distance = timepassed * 340 * 100 / 2
r = am.send({'d1': distance})
print(r.status_code, distance)
time.sleep(10)
except KeyboardInterrupt:
pass
pi.stop()
温度センサ (ADT7410)
せっかくなら,温度も取得してより正確な音速を用いて,距離を求めることで精度を高めたいと思います.
仕様
- 電圧: 2.7V〜5.5V
- 電源消費電流:
- 3.3V時: 210μA
- パワーセービングモード時: 46μA (1サンプル/s)
- シャットダウンモード時: 2μA
- 3.3V時: 210μA
- 動作・測定範囲: -55℃〜+150℃ (分解能: 0.0078℃)
- 精度:
- ±0.5℃ (-40℃〜+105℃; 2.7V〜3.6V)
- ±0.4℃ (-40℃〜+105℃; 3.0V)
- 入出力: VDD, GND, SCL, SDA
- I2C 互換インターフェース
回路
実際に温度を測る
I2Cの設定
Raspberry PiのI2Cを有効にします.
# 設定画面を起動
$ sudo raspi-config
設定画面が出てきたら以下の通り操作します:
- 「5 Interfacing Options」を選択
- 「P5 I2C」を選択
- 「Would you like the ARM I2C interface to be enabled?」と出てくるので,「はい」を選択
- 「The ARM I2C interface is enabled」と出てくるので,「了解」を選択
- メニューに戻るので,「Finish」を選択
そうすると,I2Cが有効になるので,必要なライブラリをインストールします:
$ sudo apt install i2c-tools
スクリプトの作成
#!/usr/bin/env python3
import pigpio
import time
address_adt7410 = 0x48
register_adt7410 = 0x00
pi = pigpio.pi()
sensor = pi.i2c_open(1, address_adt7410)
try:
while True:
b, d = pi.i2c_read_i2c_block_data(sensor, register_adt7410, 2)
d = ((d[0]<<8) + d[1])
if d & 0x8000:
print((d-65536)/128) #negative
else:
print(d/128) #positive
time.sleep(1)
except KeyboardInterrupt:
pass
pi.i2c_close(temp_sensor)
pi.stop()
温度で補正して距離を算出
実際に超音波距離センサ (HC-SR04)と組み合わせ,測った温度で音速を求め,距離を算出してみます.
#!/usr/bin/env python3
import pigpio
import time
HC_SR04_trig = 17
HC_SR04_echo = 27
address_adt7410 = 0x48
register_adt7410 = 0x00
pi = pigpio.pi()
pi.set_mode(HC_SR04_trig, pigpio.OUTPUT)
pi.set_mode(HC_SR04_echo, pigpio.INPUT)
temp_sensor = pi.i2c_open(1, address_adt7410)
def temp():
b, d = pi.i2c_read_i2c_block_data(temp_sensor, register_adt7410, 2)
d = ((d[0]<<8) + d[1])
if d & 0x8000:
d = d - 65536 #negative
return d/128
def sonic_speed(t=temp()):
return 331.5 + 0.6 * t
try:
while True:
# Trig
# 10μs信号出力
pi.gpio_trigger(HC_SR04_trig, 10, 1)
# Echo
while pi.read(HC_SR04_echo) == 0:
signaloff = time.time()
while pi.read(HC_SR04_echo) == 1:
signalon = time.time()
# 往復にかかった時間
timepassed = signalon - signaloff
# 距離を計算 (cm)
distance = timepassed * sonic_speed() * 100 / 2
print(distance)
time.sleep(1)
except KeyboardInterrupt:
pass
pi.i2c_close(temp_sensor)
pi.stop()
タクトスイッチ,LED,ブザー
灯油給油中は,測定を中止できるように,タクトスイッチを取り付けます.
また,残量測定プログラムが動作中はLEDが点灯,休止中は点滅するように,休止が一定時間続けば注意を促すためのブザーを取り付けます.
回路
タクトスイッチとLED
#!/usr/bin/env python3
import pigpio
import time
flag = False
LED_PIN = 10
SWI_PIN = 22
pi = pigpio.pi()
pi.set_mode(SWI_PIN, pigpio.INPUT)
pi.set_pull_up_down(SWI_PIN, pigpio.PUD_DOWN)
pi = pigpio.pi()
pi.set_mode(LED_PIN, pigpio.OUTPUT)
def cb_interrupt(gpio, level, tick):
global flag
print (gpio, level, tick)
flag = not flag
cb = pi.callback(SWI_PIN, pigpio.FALLING_EDGE, cb_interrupt)
try:
while True:
if flag:
pi.write(LED_PIN, 1)
else:
pi.write(LED_PIN, 0)
time.sleep(0.3)
except KeyboardInterrupt:
pass
pi.set_mode(LED_PIN, pigpio.INPUT)
pi.stop()
折角なので,もう一種類:
#!/usr/bin/env python3
import pigpio
import time
LED_PIN = 10
SWI_PIN = 22
pi = pigpio.pi()
pi.set_mode(SWI_PIN, pigpio.INPUT)
pi.set_pull_up_down(SWI_PIN, pigpio.PUD_DOWN)
pi.set_mode(LED_PIN, pigpio.OUTPUT)
def cb_interrupt(gpio, level, tick):
global flag
global pi
print (gpio, level, tick)
flag = (pi.read(LED_PIN) == 1)
pi.write(LED_PIN, 0 if flag else 1)
cb = pi.callback(SWI_PIN, pigpio.FALLING_EDGE, cb_interrupt)
try:
while True:
time.sleep(0.3)
except KeyboardInterrupt:
pass
pi.set_mode(LED_PIN, pigpio.INPUT)
pi.stop()
ブザーの動作確認
#!/usr/bin/env python3
import pigpio
import time
PIN = 26
pi = pigpio.pi()
pi.set_mode(PIN, pigpio.OUTPUT)
for _ in range(3):
pi.write(PIN, 1)
time.sleep(0.3)
pi.write(PIN, 0)
time.sleep(0.3)
pi.set_mode(PIN, pigpio.INPUT)
pi.stop()
メイン
今までは,各パーツの使い方など,要素ごとの話でした.
ここからは,要素を組み合わせて実際にメインのスクリプトを書きます.
その前に,まずはタンクの中が空のときの値を取得します:
# 測定値を empty.dat に書き込む
# ある程度したら,`ctrl-c`でストップ
$ python3 meas_dist_temp.py > empty.dat
^C%
# データ数を確かめる
$ wc -l empty.dat
264 empty.dat
# 中央値を求める
$ sort -n empty.dat | awk ' { a[i++]=$1; } END { print a[int(i/2)]; }'
70.43320387601852
次に18L灯油を入れてから,値を取得します:
$ python3 meas_dist_temp.py > 18l.dat
^C%
$ wc -l 18l.dat
462 18l.dat
$ sort -n 18l.dat | awk ' { a[i++]=$1; } END { print a[int(i/2)]; }'
55.835982263088226
ということで,0Lは70.433cm,1Lで0.811cm変わると設定します1.
#!/usr/bin/env python3
import pigpio
import time
import ambient
from statistics import mean
tank_empty = 70.433
tank_1l = 0.811
HC_SR04_trig = 17
HC_SR04_echo = 27
LED_PIN = 10
SWI_PIN = 22
BUZZER_PIN = 26
I2C_BUS = 1
address_adt7410 = 0x48
register_adt7410 = 0x00
ambient_ch_id = チャネルID
ambient_write_key = "ライトキー"
working = True
switch_stop_datetime = time.time()
am = ambient.Ambient(ambient_ch_id, ambient_write_key)
pi = pigpio.pi()
pi.set_mode(HC_SR04_trig, pigpio.OUTPUT)
pi.set_mode(HC_SR04_echo, pigpio.INPUT)
temp_sensor = pi.i2c_open(I2C_BUS, address_adt7410)
pi.set_mode(SWI_PIN, pigpio.INPUT)
pi.set_pull_up_down(SWI_PIN, pigpio.PUD_DOWN)
pi.set_mode(LED_PIN, pigpio.OUTPUT)
pi.set_mode(BUZZER_PIN, pigpio.OUTPUT)
def temp():
b, d = pi.i2c_read_i2c_block_data(temp_sensor, register_adt7410, 2)
d = ((d[0]<<8) + d[1])
if d & 0x8000:
d = d - 65536 #negative
return d/128
def sonic_speed(t=temp()):
return 331.5 + 0.6 * t
def to_buzz():
for _ in range(3):
pi.write(BUZZER_PIN, 1)
time.sleep(0.3)
pi.write(BUZZER_PIN, 0)
time.sleep(0.3)
def meas():
# Trig
# 10μs信号出力
pi.gpio_trigger(HC_SR04_trig, 10, 1)
# Echo
while pi.read(HC_SR04_echo) == 0:
signaloff = time.time()
while pi.read(HC_SR04_echo) == 1:
signalon = time.time()
# 往復にかかった時間
timepassed = signalon - signaloff
# 距離を計算 (cm)
return timepassed * sonic_speed() * 100 / 2
def calc_cm2l(cm):
return (tank_empty - cm)/tank_1l
def cb_interrupt(gpio, level, tick):
global working
global pi
print (gpio, level, tick)
working = not working
pi.write(LED_PIN, 1 if working else 0)
if not working:
switch_stop_datetime = time.time()
cb = pi.callback(SWI_PIN, pigpio.FALLING_EDGE, cb_interrupt)
try:
while True:
if working:
distances = [d for d in [meas() for _ in range(10)] if 2 <= d <= 400]
if len(distances) == 0:
print("計測エラー")
else:
distance = mean(distances)
temperature = temp()
print(distance, temperature)
now = time.localtime()
r = None
while not r or not r.status_code == 200:
r = am.send({
'created': time.strftime('%Y-%m-%d %H:%M:%S', now),
'd1': distance,
'd2': temperature
})
else:
if time.time() - switch_stop_datetime >= 300:
to_buzz()
time.sleep(10)
except KeyboardInterrupt:
pass
pi.i2c_close(temp_sensor)
pi.set_mode(LED_PIN, pigpio.INPUT)
pi.set_mode(BUZZER_PIN, pigpio.INPUT)
pi.stop()
まとめ
完成したプログラム
#!/bin/bash
sleep 30
screen -UmdS kerosene_tank_oil_gauge python3 /home/yk/kerosene_tank_oil_gauge/main.py
#!/usr/bin/env python3
import pigpio
import time
import ambient
from statistics import mean
tank_empty = 70.433
tank_1l = 0.811
HC_SR04_trig = 17
HC_SR04_echo = 27
LED_PIN = 10
SWI_PIN = 22
BUZZER_PIN = 26
I2C_BUS = 1
address_adt7410 = 0x48
register_adt7410 = 0x00
ambient_ch_id = チャネルId
ambient_write_key = "ライトキー"
working = True
switch_stop_datetime = time.time()
am = ambient.Ambient(ambient_ch_id, ambient_write_key)
pi = pigpio.pi()
pi.set_mode(HC_SR04_trig, pigpio.OUTPUT)
pi.set_mode(HC_SR04_echo, pigpio.INPUT)
temp_sensor = pi.i2c_open(I2C_BUS, address_adt7410)
pi.set_mode(SWI_PIN, pigpio.INPUT)
pi.set_pull_up_down(SWI_PIN, pigpio.PUD_DOWN)
pi.set_mode(LED_PIN, pigpio.OUTPUT)
pi.set_mode(BUZZER_PIN, pigpio.OUTPUT)
def temp():
b, d = pi.i2c_read_i2c_block_data(temp_sensor, register_adt7410, 2)
d = ((d[0]<<8) + d[1])
if d & 0x8000:
d = d - 65536 #negative
return d/128
def sonic_speed(t=temp()):
return 331.5 + 0.6 * t
def to_buzz():
for _ in range(3):
pi.write(BUZZER_PIN, 1)
time.sleep(0.3)
pi.write(BUZZER_PIN, 0)
time.sleep(0.3)
def meas():
signalon = time.time()
signaloff = time.time()
time.sleep(1)
# Trig
# 10μs信号出力
pi.gpio_trigger(HC_SR04_trig, 10, 1)
# Echo
while pi.read(HC_SR04_echo) == 0:
signaloff = time.time()
while pi.read(HC_SR04_echo) == 1:
signalon = time.time()
# 往復にかかった時間
timepassed = signalon - signaloff
# 距離を計算 (cm)
return timepassed * sonic_speed() * 100 / 2
def calc_cm2l(cm):
return (tank_empty - cm)/tank_1l
def cb_interrupt(gpio, level, tick):
global working
global pi
print (gpio, level, tick)
working = not working
pi.write(LED_PIN, 1 if working else 0)
if not working:
switch_stop_datetime = time.time()
if __name__ == '__main__':
print("起動")
cb = pi.callback(SWI_PIN, pigpio.FALLING_EDGE, cb_interrupt)
print("setup finished callbacks")
try:
print("実行開始")
while True:
if working:
distances = [d for d in [meas() for _ in range(1)] if 2 <= d <= 400]
if len(distances) == 0:
print("計測エラー")
else:
distance = mean(distances)
temperature = temp()
print(calc_cm2l(distance), distance, temperature)
now = time.localtime()
r = None
while not r or not r.status_code == 200:
r = am.send({
'created': time.strftime('%Y-%m-%d %H:%M:%S', now),
'd1': calc_cm2l(distance),
'd2': distance,
'd3': temperature
})
else:
if time.time() - switch_stop_datetime >= 300:
to_buzz()
time.sleep(30)
except KeyboardInterrupt:
pass
pi.i2c_close(temp_sensor)
pi.set_mode(LED_PIN, pigpio.INPUT)
pi.set_mode(BUZZER_PIN, pigpio.INPUT)
pi.stop()
$ crontab -e
@reboot /home/pi/kerosene_tank_oil_gauge/start.sh
値段
Raspberry Pi 3B + ACアダプタ | ¥5,292 | 1 |
Raspberry Pi 3B ケース | ¥1069 | 1 |
超音波距離センサ HC-SR04 | ¥400 | 1 |
温度センサ ADT7410 | ¥500 | 1 |
自励式ブザー BUZ-1.5-9.5 | ¥108 | 1 |
タクトスイッチ TVDP01-6.5 | ¥39 | 1 |
LED L3G2530-12 | ¥54 | 1 |
LED キャップ | ¥54 | 1 |
トランジスタ 2SC1815 | ¥16.2/個 | 1 |
参考
- @mygod877, @big2teacher: IoTの先輩.色々教えてもらった.
- 灯油の残量を監視するものが欲しかったが,元々は距離センサで残量を測るという考えはなかった.アイデアは次から:
- Yahoo!ニュース: 灯油配送を効率化 IoT活用「見守り」も 来月から地域実験 北海道・JA新しのつなど(日本農業新聞): 北海道・新篠津村で屋外ホームタンクのキャップにセンサを取り付け,残量を計測・送信.
- 日本経済新聞: IoTで灯油配送を効率化 北海道新篠津村が実証実験: 1日4回測定.LPWAまたはLTEを使ってJAのPCに送信.(LTE: sakura.io, LPWA: Sigfox.)
- 各プレスリリースなど:
- Raspberry Pi GPIO Pinout
- pigpio library
- Make.: 超音波距離センサ(HC-SR04)を使う
- 高精度計算サイト: 抵抗の分圧計算
- Ambient: IoTデーターの可視化サービス.サンプルや実例も取り上げられている.合わせてFacebook: Ambientも.
- SlideShare: Takehiko Shimojima: IoTデーター可視化サービス「Ambient」+Python+pandas: “IoTデーター可視化サービス「Ambient」( https://ambidata.io )のご紹介とPython+pandasと組み合わせるととても便利だという話。”
- あること・ないこと日記: Raspberry Pi 3(ラズベリーパイ3)で超音波距離センサー(HC-SR04)を使う: 温・湿度センサ(DHT11)で測定した温度を用いて音速を算出した上で,超音波距離センサ(HC-SR04)で距離を測定.精度の検証も行っている.
- Qiita: @py_iK: pigpioを用いてI2C通信 GP2Y0E03編: pigpioを用いたI2Cの書き方
- Life with IT: [Raspbian] Raspberry Pi 3でタクトスイッチを使ったLED制御(Fritzingのインストール手順付き): タクトスイッチ, LED関連
- untitled: トランジスタ
- こた電 - こたつぁーの電子工作: トランジスタを使ってみよう
- CODE Q&A [日本語]: [Bash] awkの列の中央値 sed
- GitHub: examples/ADT7410.fzz at master · MozOpenHard/examples
-
いまいち,この距離センサの分解能の考え方がわからない… ↩