🎧

[ライフハック]ヘッドフォンの利用とスピーカのオンオフを連動させる

2022/01/08に公開

ラズベリーパイを使って、↓を作りました。個人的な悩みを解決したという話です。

https://twitter.com/tw_kotatu/status/1478280851612106759

😟困ったこと

私のPCには、ヘッドフォンとスピーカの両方が接続されています。
通常時は、スピーカしか使っていないのですが、夜や集中したいときはヘッドフォンで音楽を聴きます。

Image from Gyazo

そのため、以下を毎回行っています。

  • ヘッドフォンを使用する場合、スピーカの電源を切る
  • ヘッドフォンを使用しない場合、スピーカの電源を入れる
    • 使用しているスピーカの関係で、ボリュームの調整も必要

↑スピーカの電源のオンオフ操作が面倒なので、これを自動化できないか?というのが悩みでした。

💡やったこと

Image from Gyazo

  1. 利用者は、ヘッドフォンを片付ける(または、取り出す)
  • 片付ける(/取り出す)と、スイッチの状態が変わる
  • ※スイッチの概要は後述
  1. ラズベリーパイは、スイッチ状態を監視している
  • スイッチの変化に応じて、IFTTTへリクエストを送信する
  1. IFTTTを経由し、スピーカに接続されたスマートプラグを制御する
  • 片付ける -> スピーカ電源をオン
  • 取り出す -> スピーカ電源をオフ

今回は、ラズベリーパイとスイッチを使って作ることにしました。

🔧パーツ一覧

no 部品名 個数 備考
1 ラズベリーパイ 1 今回は、4Bを使用
2 スイッチ 1 Amazon 12x12のもの
3 ジャンパー線 適量 -
4 ばね 1 -
5 スイッチ用ケース 1 3Dプリンタで自作(後述)
6 スマートプラグ 1 Amazon IFTTT対応のモノを使用
7 PCスピーカ 1 Amazon 電源オンオフとボリューム共通

接続図

スイッチの監視のため、回路自体はシンプルです。

Image from Gyazo

💻環境

開発環境

  • ラズベリーパイ
    • Linux rpi 5.10.17-v7l+ #1403 SMP Mon Feb 22 11:33:35 GMT 2021 armv7l GNU/Linux
  • Python
    • Python 3.7.3 (default, Jan 22 2021, 20:04:44)

ラズベリーパイの設定

特になし

モジュールのインストール

apt

pigpioライブラリは、Raspberry PiのGPIOを制御するためのライブラリです。
以下のコマンドは、はじめてインストールする場合のみ必要です。

$ sudo apt install pigpio
$ sudo service pigpiod start
$ sudo systemctl enable pigpiod.service

pip

Pythonに関するモジュールをインストールします。

$ python3 -m venv env
$ source env/bin/activate
(env) $ pip install pigpio
(env) $ pip install gpiozero
(env) $ pip install requests

📝手順

以下を記載します。

  • 3Dプリンタによるスイッチの作成
  • スマートプラグの制御準備(IFTTTの準備)
  • スイッチの監視

3Dプリンタのスイッチの作成

スイッチは↓の感じです。

Image from Gyazo

スイッチの3Dデータの作成は、SolidPythonを使用しました。

SolidPythonとは

PythonプログラムからOpenSCADプログラムを生成し、3Dモデリングを実施します。

インストール方法や使い方は、以前の記事にまとめておきましたので、参照願います。

https://zenn.dev/kotaproj/articles/a70464d8cd3540

コード

下記の4つの部品をコードで作成します。

Image from Gyazo

sw_all.py
from solid import *
from solid.utils import *

def add_hole(cu, cu_x=24, cu_y=24, cu_z=10, cy_h=5, cy_r=1.3, ana_w=8):
    """ネジ穴の追加
    """
    cy = cylinder(h=cy_h, r=cy_r)
    cy = rotate([0, 90, 0])(cy)
    cy = translate([-1*cy_h/2, 0, 0])(cy)

    offs = [ (cu_x/2, ana_w, cu_z/2),
            (cu_x/2, -ana_w, cu_z/2),
            ((-1)*cu_x/2, ana_w, cu_z/2),
            ((-1)*cu_x/2, -ana_w, cu_z/2),
            ]
    for off in offs:
        cu -= translate(off)(cy)

    return cu


def make_tutu(xw, yw, zw, t=2.0):
    """筒の作成
    """
    c_base = cube([xw, yw, zw], center=False)
    c_cut = cube([xw-t, yw-t, zw-t+0.01], center=False)
    c_cut = translate([t/2, t/2, -t-0.01])(c_cut)
    c = c_base - c_cut
    c = translate([-xw/2, -yw/2, 0])(c)
    return c


def make_tutu_ana(xw, yw, zw, t=2.0, r=3.1):
    """筒に穴あけ
    """
    c = make_tutu(xw, yw, zw, t)
    cy = cylinder(h=zw*4, r=r)
    return (c - cy)


def make_btm(cu_x=24, cu_y=24, cu_z=10, cut_x=12, cut_y=12, cut_z=5, cut_mizo=3):
    """btm部品の作成
    """
    cu_base = cube([cu_x, cu_y, cu_z])
    cu_cut_naka = translate([(cu_x-cut_x)/2, (cu_y-cut_y)/2, cu_z-(cut_z-0.1)])(cube([cut_x, cut_y, (cut_z+0.1)]))
    cu_cut_t1 = translate([(0-0.1), (cu_y-cut_y)-cut_mizo/2 - 2.5, cu_z-(cut_z-0.01)])(cube([cu_x+1.0, cut_mizo, (cut_z+0.01)]))
    cu_cut_t2 = translate([(0-0.1), (cu_y-cut_y)-cut_mizo/2 + 2.5, cu_z-(cut_z-0.01)])(cube([cu_x+1.0, cut_mizo, (cut_z+0.01)]))

    wa = cu_base - (cu_cut_naka + cu_cut_t1 + cu_cut_t2)
    wa = translate([(-1)*cu_x/2, (-1)*cu_y/2, 0])(wa)
    return wa


def make_btm_cover(cu_x=24, cu_y=24, cu_z=10, cut_x=12, cut_y=12, cut_z=5):
    """cover部品の作成
    """
    cu_base = cube([cu_x, cu_y, cu_z])
    cu_cut_naka = translate([(cu_x-cut_x)/2, (cu_y-cut_y)/2, cu_z-(cut_z-0.1)])(cube([cut_x, cut_y, (cut_z+0.1)]))

    wa = cu_base - (cu_cut_naka)
    wa = translate([(-1)*cu_x/2, (-1)*cu_y/2, 0])(wa)
    return wa


def make_totu(cu_x=20, cu_y=20, cu_z=2, cy_h=10, cy_r=3):
    """totu部品の作成
    """
    cu = cube([cu_x, cu_y, cu_z], center=False)
    cu = translate([-1*cu_x/2, -1*cu_y/2, 0])(cu)
    cy = cylinder(h=cy_h, r=cy_r)
    sp = sphere(r=cy_r)
    sp = translate([0, 0, cy_h])(sp)
    return cu + cy + sp


if __name__ == "__main__":
    # tutu
    wa_tutu = make_tutu_ana(xw=26, yw=26, zw=28, t=1.6, r=9.0)
    wa_tutu = add_hole(wa_tutu, cu_x=24, cu_y=24, cu_z=10, cy_h=5, cy_r=1.7, ana_w=8)
    wa_tutu = add_hole(wa_tutu, cu_x=24, cu_y=24, cu_z=12, cy_h=20, cy_r=2, ana_w=2.5)
    for cz in range(12):
        wa_tutu = add_hole(wa_tutu, cu_x=24, cu_y=24, cu_z=cz, cy_h=20, cy_r=2, ana_w=2.5)
        wa_tutu = add_hole(wa_tutu, cu_x=24, cu_y=24, cu_z=cz, cy_h=20, cy_r=2, ana_w=1.5)
    wa_tutu = color("blue")(wa_tutu)
    scad_render_to_file(wa_tutu, "sw_tutu.scad", include_orig_code=False)

    # btm
    wa_btm = make_btm(cu_x=24, cu_y=24, cu_z=10, cut_x=12.2, cut_y=12.1, cut_z=5, cut_mizo=3)
    wa_btm = add_hole(wa_btm, cu_x=24, cu_y=24, cu_z=10, cy_h=5, cy_r=1.7, ana_w=8)
    wa_btm = color("red")(wa_btm)
    scad_render_to_file(wa_btm, "sw_btm.scad", include_orig_code=False)

    # cover
    wa_cover = make_btm_cover(cu_x=24, cu_y=24, cu_z=4, cut_x=7.1, cut_y=7.0, cut_z=10)
    wa_cover = up(10)(wa_cover)
    wa_cover = color("cyan")(wa_cover)
    scad_render_to_file(wa_cover, "sw_cover.scad", include_orig_code=False)

    # totu
    wa_totu = make_totu(cu_x=22, cu_y=22, cu_z=2, cy_h=25, cy_r=8.5)
    wa_totu = wa_totu - translate([0, 0, -1])(cylinder(h=6.0, r=4))
    wa_totu = up(10)(wa_totu)
    wa_totu = color("green")(wa_totu)
    scad_render_to_file(wa_totu, "sw_totu.scad", include_orig_code=False)

    # all
    wa = wa_tutu + wa_btm + wa_cover + wa_totu
    scad_render_to_file(wa, "sw.scad", include_orig_code=False)

ファイルの作成

印刷するためのファイルを作成します。

$ python -m venv env
$ env\Scripts\activate
$ pip install solidpython numpy
$ python sw_all.py
# =>同じフォルダ内に scadファイルが作成されます。

各scadファイルをOpenSCADで開き、STLファイルを作成します。

あとは、各部品を印刷します。

組み立て

基本的には組み立てるだけですが、btm部品のところを補足します。

Image from Gyazo

あとは組み立てて、ねじ止めします↓。

Image from Gyazo

スマートプラグの制御準備(IFTTTの準備)

電源のオンオフ制御をするためのIFTTTの設定について記載します。

https://zenn.dev/kotaproj/articles/b3e8766c192ce5

事前準備(IFTTT)

Appletの登録

IFTTTの設定手順について説明します。

  • https://ifttt.com/home へアクセスしてアカウントを作成 or ログイン
  • Createにて、以下の2つを作成
    • 電源をオフする
      • This(Trigger)
        • "Webhooks" - "Receive a web request"
          • "Event Name" : meross_speaker_poweroff
      • That(Action)
        • Meross - Turn off
    • 電源をオンする
      • This(Trigger)
        • "Webhooks" - "Receive a web request"
          • "Event Name" : meross_speaker_poweron
      • That(Action)
        • Meross - Turn on

Image from Gyazo

tokenの確認

webhooksをキックするためにtokenを確認します。

  • Webhooks Settingsのページより確認する
  • 下記画像のマスクされている箇所に記載されている

スイッチの監視

ラズベリーパイからスイッチの監視を行います。

コード

head_onoff.py
from gpiozero import Button
from gpiozero.pins.pigpio import PiGPIOFactory
from signal import pause
import requests

# SWのピン設定
PIN_BTN1 = 24

# IFTTT
IFTTT_TOKEN = "xxxxxxxxxxxxxxxxxxxxxxxxxx"

def post_ifttt(eventid, values=None):
    print("post_ifttt - run", eventid)
    if values is None:
        # {"value1":"1", "value2":"2", "value3":"3"}
        payload = {"value"+str(x):str(x) for x in range(1, 4)}
    else:
        # {"value1":values[0], "value2":values[1], "value3":values[2]}
        payload = {"value"+str(cnt+1):x for cnt, x in enumerate(values)}
    url = "https://maker.ifttt.com/trigger/" + eventid + "/with/key/" + IFTTT_TOKEN
    print(url)
    response = requests.post(url, data=payload)
    return

def main():
    # SWピンを入力に設定(プルアップ設定)
    factory = PiGPIOFactory()
    btn = Button(PIN_BTN1, pull_up=True, bounce_time=0.3, hold_time=1.0, pin_factory=factory)

    def press_btn():
        print("pressed button!")
        post_ifttt("meross_speaker_poweron")

    def release_btn():
        print("released button!")
        post_ifttt("meross_speaker_poweroff")

    # 起動時の初期化
    if btn.is_pressed:
        press_btn()
    else:
        release_btn()

    # callback
    btn.when_pressed = press_btn
    btn.when_released = release_btn
    pause()
    return

if __name__ == "__main__":
    main()

デモ(記事最初の動画)の結果になります。

🔎ポイント

簡単にプログラムのポイントを記載します。

スイッチ - プルアップ設定 - pull_up

スイッチが押されていない状態では、PINが浮いている状態(不安定な状態)になってしまいます。
ノイズが入ってきた場合に誤作動につながる可能性があります。
そのため、"pull_up=True"を指定し、マイコンの電圧(3.3V)が入力される状態にします。
回路図上に、プルアップ抵抗を使用する場合は、コード上で、pullupを指定する必要がありません。

スイッチ - 検出設定 - bounce_time, hold_time

スイッチにはチャダリング(不定な期間)が存在します。
不定期間を無視すために、bounce_timeを300msecに設定しています。
最初の変化点(押す⇔離す)から、300msec無視することで不要なイベントを上げないようにしています。
 また、今回のアプリケーションは、即時の動作が不要のため1秒安定したらイベント上げるよう
hold_timeを1秒に設定しています。

さいごに

無事に作りたかったものが作れました。
また、ヘッドフォンも定位置に片付ける習慣がつき、よかったです。

この記事も含め、ラズパイの活用方法を

https://zenn.dev/kotaproj/books/raspberrypi-tips

としてまとめ中です。

GitHubで編集を提案

Discussion