ラズパイ4と超音波センサーを使って部屋から出るタイミングで出撃音を鳴らす

初投稿です。
淫夢要素はありません。

![1.001.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1793346/dd713959-b38e-a027-857d-9a173e404f47.png)


#WHY

扉の開閉と同時に効果音を流せるおもちゃ［[Amazonリンク](https://www.amazon.co.jp/エフェクトーン-一番くじ-モンスターハンター-ワールド-生ける大地と共に/dp/B08P9DH32D)］を見つけてテンションが上ったのですが、時既に遅し、とっくの昔に1番くじは終了していました。
仕方ないので自分で作ることにします。
> ねだるな、勝ち取れ、さすれば与えられん
> ーアドロック・サーストン

ついでと言ってはなんですが、こういうおもちゃ特有のチープな音を「高音質化」＆「大音量化」し、臨場感マシマシで行きたいと思います。


#概要

センサーの前を人が通ると音が鳴るおもちゃを作ります。

少し真面目な言い方をするならば、ラズパイ4に超音波センサーを付けて0.5秒置きに距離を測定し、その取得した距離が一定以下(センサーの前を人が通過)になった時に音を出すような電子回路とプログラムを作ります。


#準備したもの

- Raspberry Pi
- ブレッドボード
- オーディオケーブル(ステレオミニプラグ〜フォンプラグ)
- ジャンパ線
- HC-SR04(超音波距離センサー)
- センサー用の抵抗(510Ω×1、1kΩ×1)
- LED
- LED用の抵抗(510Ω×1)

※LEDとLED用の抵抗は動確用なので別にあってもなくても良いです


#回路図

![qiita用.001.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1793346/2b678e85-6585-7971-527a-abfca1b3027e.png)


GPIOとはラズパイ本体にたくさんついているピンのことです。

本回路図はラズパイ4Bのピン配置で記載しています。
過去世代のラズパイなど別ボードで動かす場合は、各テクニカルシートに書いてあるピン配置に従ってください(後述のプログラムもそれに合わせて修正してください)。

※LEDとLED用の抵抗の回路は動確用なので(以下略

<font color="Gray">本当はFritzingとか使ってイケてる回路図を作りたかったですが、Macちゃんが不機嫌で動きませんでした。Keynoteでしこしこ作りました。悲しみ。</font>


#ラズパイの事前準備
大前提として、ラズパイのOSインストールや開発環境(Python)の準備は整っていることとします。

まず、GPIOのコントロールにRPI.GPIOを使用するため事前にインストールが必要です。

```
$ sudo apt-get install python-rpi.gpio
```

インストールしているかどうかは、pipの一覧で確認できます(一部抜粋)。

```
$ pip list
Package           Version    
----------------- -----------
~
responses         0.9.0      
RPi.GPIO          0.7.0      
RTIMULib          7.2.1      
~
```

その他のパッケージはプリインストールされているので個別にインストールしなくても大丈夫です。

#プログラムの一部解説

事前準備と物理配線が出来ていればもうコピペで動くと思うのですが、情緒がないので一部解説します。


**●　距離を測定する関数**

```python
def read_distance():
```

HC-SR04を使って距離測定するプログラムはネット上にたくさん転がっており私も大変リスペクトさせていただいた次第なんですが、今回は測定した距離の結果に応じて分岐するため、サンプルプログラムで頻繁に見かける「センサーのブレによる外れ値の除外」を行っておりません。

具体的には下記のような分岐を無くしています。

```python
# 500cm 以上の場合はノイズと判断する
if distance <= 500:
    return distance
else:
    return None
```

Noneを返すとintとの比較で型が不一致のエラーが出て止まります。

```python
   return distance
```

そのため、常に数値のみ返すようにします。

**●　距離測定のキック、音源再生**

```python
while True:
```

音源の再生処理に手間取ったため、特記しておきます。

ラズパイないしpythonから音を出す方法はいくつかあるのですが、ググってよく出てくるのは以下あたりだと思います。

- PySound
- pygame
- aplay

これらの違いと各サンプルプログラムは[Raspberry Piで音楽 wav/mp3ファイルを再生する方法 python編](https://qiita.com/Nyanpy/items/cb4ea8dc4dc01fe56918)に分かりやすくまとめられているので気になる方は読んでみてください。

閑話休題、記事が多く最もポピュラーだと思われるPySoundから試してみたんですが、音は出るんですが下記の表示が出て止まっちゃうんですよ。

```
ALSA lib pcm.c:2565:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.front
ALSA lib pcm.c:2565:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.rear
ALSA lib pcm.c:2565:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.center_lfe
ALSA lib pcm.c:2565:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.side
ALSA lib pcm.c:2565:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.surround21
ALSA lib pcm.c:2565:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.surround21
ALSA lib pcm.c:2565:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.surround40
ALSA lib pcm.c:2565:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.surround41
ALSA lib pcm.c:2565:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.surround50
ALSA lib pcm.c:2565:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.surround51
ALSA lib pcm.c:2565:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.surround71
ALSA lib pcm.c:2565:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.iec958
ALSA lib pcm.c:2565:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.iec958
ALSA lib pcm.c:2565:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.iec958
ALSA lib pcm.c:2565:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.hdmi
ALSA lib pcm.c:2565:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.hdmi
ALSA lib pcm.c:2565:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.modem
ALSA lib pcm.c:2565:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.modem
ALSA lib pcm.c:2565:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.phoneline
ALSA lib pcm.c:2565:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.phoneline
Cannot connect to server socket err = No such file or directory
Cannot connect to server request channel
jack server is not running or cannot be started
JackShmReadWritePtr::~JackShmReadWritePtr - Init not done for -1, skipping unlock
JackShmReadWritePtr::~JackShmReadWritePtr - Init not done for -1, skipping unlock
Cannot connect to server socket err = No such file or directory
Cannot connect to server request channel
jack server is not running or cannot be started
JackShmReadWritePtr::~JackShmReadWritePtr - Init not done for -1, skipping unlock
JackShmReadWritePtr::~JackShmReadWritePtr - Init not done for -1, skipping unlock
ALSA lib pcm_oss.c:377:(_snd_pcm_oss_open) Unknown field port
ALSA lib pcm_oss.c:377:(_snd_pcm_oss_open) Unknown field port
ALSA lib pcm_a52.c:823:(_snd_pcm_a52_open) a52 is only for playback
```

JackServerをスキップ(ダミー化)する処理を入れれば回避できるという記事もあるのですが、私の環境では上手く動作せず。。。
ネット上に公開されているいろんなPySoundのサンプルアプリを実行してみても同じ結果だったので、諦めてpygameに変更しました。

pygameに変更後は音源再生後も問題なく動作し、ループ処理もご機嫌に動いていたためそのまま使っています。



#プログラム(Python)

```python
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import time
import RPi.GPIO as GPIO
import pygame.mixer

#距離を計測する関数
def read_distance():
    # 使用するピンの設定
    GPIO.setmode(GPIO.BOARD)
    # ボード上の11番ピン(GPIO17)
    TRIG = 11 
    # ボード上の13番ピン(GPIO27)
    ECHO = 13

    # ピンのモードをそれぞれ出力用と入力用に設定
    GPIO.setup(TRIG,GPIO.OUT)
    GPIO.setup(ECHO,GPIO.IN)
    GPIO.output(TRIG, GPIO.LOW)

    # TRIGに短いパルスを送る
    GPIO.output(TRIG, GPIO.HIGH)
    time.sleep(0.00001)
    GPIO.output(TRIG, GPIO.LOW)

    # ECHOピンがHIGHになるのを待つ
    signaloff = time.time()
    while GPIO.input(ECHO) == GPIO.LOW:
        signaloff = time.time()

    # ECHOピンがLOWになるのを待つ
    signalon = signaloff
    while time.time() < signaloff + 0.1:
        if GPIO.input(ECHO) == GPIO.LOW:
            signalon = time.time()
            break

    # GPIOの初期化
    GPIO.cleanup()

    # 時刻の差から、物体までの往復の時間を求め、距離を計算する
    timepassed = signalon - signaloff
    distance = timepassed * 17000

    return distance

while True:
    start_time = time.time()
    distance = read_distance()
    if distance:
        print ("distance: %.1f cm" % (distance))

        # センサーからの距離が100cm未満の場合LEDを点灯する
        if distance < 100:                          # LED無しの場合不要
            GPIO.setmode(GPIO.BOARD)                # LED無しの場合不要
            # ボード上の15番ピン(GPIO22)
            LED = 15                                # LED無しの場合不要
            GPIO.setup(LED, GPIO.OUT)               # LED無しの場合不要
            # LED点灯
            GPIO.output(LED, GPIO.HIGH)             # LED無しの場合不要

    # 0.5秒間に１回実行するためのウェイトを入れる
    wait = start_time + 0.5 - start_time
    if wait > 0:
       time.sleep(wait)

    #　センサーからの距離が100cmの場合LEDをを消灯し音源再生する
    if distance < 100:
        # LED消灯
        GPIO.output(LED, GPIO.LOW)                  # LED無しの場合不要
        # GPIOの初期化
        GPIO.cleanup()                              # LED無しの場合不要

        print('出撃！「ポ〜パ〜（迫真）」')

        # mixerモジュールの初期化
        pygame.mixer.init()
        # 音源ファイルの読み込み
        pygame.mixer.music.load("/home/pi/Desktop/XXXX.mp3")
        # 音源再生、および再生回数の設定
        pygame.mixer.music.play(1)

        # 音源再生が終了するまでのウェイトを入れる
        time.sleep(7)

        # 音源再生の終了
        pygame.mixer.music.stop()
```

#いざ出撃

以上のプログラムを使って、実際に出撃してみます。

冒頭にも述べましたように、「高音質」かつ「大音量」で行きます。

![2.001.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1793346/3ccc5180-b9cc-f1ec-86b9-aad43e88b217.png)


（ご近所迷惑にならない程度に）**モニタースピーカー4基フル稼働で鳴らします**。

---
<iframe width="560" height="315" src="https://www.youtube.com/embed/OmLq9GRL8MY" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
---
やったぜ。

#最後に

楽しかったです。また何か作ったら記事化しようと思います。