Skip to content

トイレの音姫をラズパイで自作する

人感センサーのHC-SR501をラズパイ(Raspberry Pi 3A)に接続して、トイレに座ると川のせせらぎを再生するようにします。

TOTOの製品で音姫がありますが、自作することで好きな音源をカスタマイズして再生することができます。

必要なもの

  • Raspberry Pi:ステレオジャック端子を使うためZeroシリーズ以外にしてください。今回は3Aを使用しました。
  • USBスピーカー:USB電源で作動するスピーカーでないと音量が足りないので注意してください。100円均一ショップではBluetoothスピーカーが主流で、USBスピーカーを見つけるのは難しいかもしれません。下記の商品をリサイクルショップで見つけました。

少し高いですがエレコム等の商品でも可能です。

  • 人感センサー:HC-SR501

導入手順

ラズパイの初期設定

Raspberry Pi Imagerを使ってRaspberry Pi OSをインストールします。32bitのデスクトップにしておくと他の用途でも使いやすいためおすすめです。

配線

人感センサーを以下のように接続します。

7dee27f0 otohime kairozu

スピーカー出力の確認

接続したUSBスピーカーが認識されているか確認するために、ターミナルを開いて以下のコマンドを実行します。

aplay -l

以下のようにスピーカーが認識されていればOKです。(例ではcard 1に認識しています)

**** List of PLAYBACK Hardware Devices ****
card 0: vc4hdmi [vc4-hdmi], device 0: MAI PCM i2s-hifi-0 [MAI PCM i2s-hifi-0]
Subdevices: 1/1
Subdevice #0: subdevice #0
card 1: Headphones [bcm2835 Headphones], device 0: bcm2835 Headphones [bcm2835 Headphones]
Subdevices: 8/8
Subdevice #0: subdevice #0
Subdevice #1: subdevice #1
Subdevice #2: subdevice #2
Subdevice #3: subdevice #3
Subdevice #4: subdevice #4
Subdevice #5: subdevice #5
Subdevice #6: subdevice #6
Subdevice #7: subdevice #7

raspi-configを用いて音声出力先をスピーカーに設定します。ターミナルで以下のコマンドを実行します。

amixer cset numid=3 1

numid=3 11の値は以下の意味があります。

  • 0: オーディオ出力を自動選択
  • 1: 3.5mmジャック
  • 2: USBオーディオ(USBスピーカー)

Pythonプログラムを作成する

適当な場所(私はホームフォルダにしました。)にディレクトリを作成して、Pythonプログラムを作成します。ついでに音源を格納するディレクトリも作成します。

cd
mkdir otohime
cd otohime
mkdir sounds
vim otohime.py

以下のPythonスクリプトを作成します。

import RPi.GPIO as GPIO
import time
import os
import random
import subprocess
import datetime
import logging

# ログ設定
LOG_FILE = "/home/[ユーザー名]/otohime/otohime.log"
logging.basicConfig(
    filename=LOG_FILE,
    level=logging.INFO,
    format="%(asctime)s - %(levelname)s - %(message)s",
    datefmt="%Y-%m-%d %H:%M:%S"
)

# GPIOピン設定
SENSOR_PIN = 26
GPIO.setmode(GPIO.BCM)
GPIO.setup(SENSOR_PIN, GPIO.IN)

# 音声ディレクトリ
SOUND_DIR = "sounds/"

# 音声停止の条件
STOP_DELAY = 30  # 30秒間動きがなければ停止
FADE_TIME = 5  # フェードイン・フェードアウト時間(秒)
MAX_VOLUME = 80   # 最大音量 50%

# 音声プロセス管理
current_process = None
current_quarter = None  # 15分単位の現在の時間
current_sound = None  # 現在の再生音声ファイル

# 音量を設定
def set_volume(volume):
    subprocess.run(["amixer", "set", "Master", f"{volume}%"], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)

# フェードイン処理(10% → MAX_VOLUME%)
def fade_in():
    for volume in range(10, MAX_VOLUME + 1, 5):
        set_volume(volume)
        time.sleep(0.2)

# フェードアウト処理(MAX_VOLUME% → 10%)
def fade_out():
    for volume in range(MAX_VOLUME, 10 - 1, -5):
        set_volume(volume)
        time.sleep(0.2)

# **15分ごとに異なる音声を選択**
def get_quarterly_sound():
    global current_quarter, current_sound
    sound_files = [f for f in os.listdir(SOUND_DIR) if f.endswith((".mp3", ".wav", ".m4a"))]
    if not sound_files:
        logging.warning("音声ファイルが見つかりません")
        return None

    now = datetime.datetime.now()
    quarter = now.minute // 15

    if current_quarter != quarter:
        current_quarter = quarter
        random.seed(now.hour * 4 + quarter)
        selected_file = random.choice(sound_files)
        current_sound = os.path.join(SOUND_DIR, selected_file)
        logging.info(f"{now.hour}:{quarter * 15} の音声: {selected_file}")

    return current_sound

# 音声をループ再生
def play_sound():
    global current_process

    selected_file = get_quarterly_sound()
    if not selected_file:
        return

    logging.info(f"再生中: {selected_file}")
    set_volume(10)
    current_process = subprocess.Popen(
        ["mpg123", "-q", "--loop", "-1", selected_file],
        stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL
    )
    fade_in()

# 音を停止
def stop_sound():
    global current_process
    if current_process:
        logging.info("フェードアウト中...")
        fade_out()
        current_process.terminate()
        current_process = None
        logging.info("音声を停止しました")

# メインループ
try:
    last_motion_time = 0
    sound_playing = False

    while True:
        sensor_state = GPIO.input(SENSOR_PIN)

        if sensor_state == 1:
            last_motion_time = time.time()
            if not sound_playing:
                logging.info("動きを検知しました。音声を再生します。")
                play_sound()
                sound_playing = True

        elif sound_playing and (time.time() - last_motion_time >= STOP_DELAY):
            logging.info(f"{STOP_DELAY}秒間動きがないため、音声を停止します。")
            stop_sound()
            sound_playing = False

        time.sleep(1)
except KeyboardInterrupt:
    logging.info("プログラム終了")
finally:
    GPIO.cleanup()

ポイント

  • 動きがあるとsoundsディレクトリ内のmp3ファイルを再生します。(フェードイン再生します)
  • 30秒間動きがないと音声を停止します。(音量がフェードアウトして停止します)
  • 15分おきにランダムに音声が選択されます。
  • ログ設定のLOG_FILE = “/home/[ユーザー名]/otohime/otohime.log”のユーザー名を環境に応じて変更してください。
  • 停止時間やフェード時間、最大音量の値は適宜調整してください。

サービス化して自動起動させる

ラズパイが起動した際に自動で音姫プログラムを起動するようにサービス化します。

sudo vim /etc/systemd/system/otohime.service
[Unit]
Description=Otohime Auto Start
After=network.target sound.target

[Service]
ExecStart=/usr/bin/python3 /home/[ユーザー名]/otohime/otohime.py
Restart=always
User=[ユーザー名]
WorkingDirectory=/home/[ユーザー名]/otohime
StandardOutput=append:/home/[ユーザー名]/otohime/otohime.log
StandardError=append:/home/[ユーザー名]/otohime/otohime.log
Environment="XDG_RUNTIME_DIR=/run/user/1000"
Environment="DISPLAY=:0"
Environment="PULSE_SERVER=unix:/run/user/1000/pulse/native"

[Install]
WantedBy=multi-user.target

ユーザー名を環境に合わせて変更してください。

以下のコマンドで実行権限を付与してください。

chmod +x /home/[ユーザー名]/otohime/otohime.py

以下のコマンドでログファイルの権限を付与してください。

sudo touch /home/[ユーザー名]/otohime/otohime.log
sudo chmod 664 /home/[ユーザー名]/otohime/otohime.log

サービスを有効化します。

sudo systemctl daemon-reload
sudo systemctl enable otohime.service
sudo systemctl start otohime.service

サービスのステータスを確認して正常に動作しているか確認してください。

sudo systemctl status otohime.service

エラーが発生した場合は、ログファイルを確認してください。

cat /home/[ユーザー名]/otohime/otohime.log

設置してみる

正常にサービス化できたらトイレに設置してみましょう。

ef0093a8 image

e2721803 image

トイレの横にタオルバーがあるので、そこにスピーカーを置いています。また、吊り下げる収納フックにラズパイを設置しています。配線がごちゃっとしているので、箱にきれいに収めたほうがいいのですが、このまま運用しています。

カテゴリRaspberry Pi

Be First to Comment

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

コメントは日本語で入力してください。(スパム対策)

CAPTCHA