[ライフハック]ヘッドフォンの利用とスピーカのオンオフを連動させる
ラズベリーパイを使って、↓を作りました。個人的な悩みを解決したという話です。
😟困ったこと
私のPCには、ヘッドフォンとスピーカの両方が接続されています。
通常時は、スピーカしか使っていないのですが、夜や集中したいときはヘッドフォンで音楽を聴きます。
そのため、以下を毎回行っています。
- ヘッドフォンを使用する場合、スピーカの電源を切る
- ヘッドフォンを使用しない場合、スピーカの電源を入れる
- 使用しているスピーカの関係で、ボリュームの調整も必要
↑スピーカの電源のオンオフ操作が面倒なので、これを自動化できないか?というのが悩みでした。
💡やったこと
- 利用者は、ヘッドフォンを片付ける(または、取り出す)
- 片付ける(/取り出す)と、スイッチの状態が変わる
- ※スイッチの概要は後述
- ラズベリーパイは、スイッチ状態を監視している
- スイッチの変化に応じて、IFTTTへリクエストを送信する
- IFTTTを経由し、スピーカに接続されたスマートプラグを制御する
- 片付ける -> スピーカ電源をオン
- 取り出す -> スピーカ電源をオフ
今回は、ラズベリーパイとスイッチを使って作ることにしました。
🔧パーツ一覧
no | 部品名 | 個数 | 備考 |
---|---|---|---|
1 | ラズベリーパイ | 1 | 今回は、4Bを使用 |
2 | スイッチ | 1 | Amazon 12x12のもの |
3 | ジャンパー線 | 適量 | - |
4 | ばね | 1 | - |
5 | スイッチ用ケース | 1 | 3Dプリンタで自作(後述) |
6 | スマートプラグ | 1 | Amazon IFTTT対応のモノを使用 |
7 | PCスピーカ | 1 | Amazon 電源オンオフとボリューム共通 |
接続図
スイッチの監視のため、回路自体はシンプルです。
💻環境
開発環境
- ラズベリーパイ
- 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プリンタのスイッチの作成
スイッチは↓の感じです。
スイッチの3Dデータの作成は、SolidPythonを使用しました。
SolidPythonとは
PythonプログラムからOpenSCADプログラムを生成し、3Dモデリングを実施します。
インストール方法や使い方は、以前の記事にまとめておきましたので、参照願います。
コード
下記の4つの部品をコードで作成します。
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部品のところを補足します。
あとは組み立てて、ねじ止めします↓。
スマートプラグの制御準備(IFTTTの準備)
電源のオンオフ制御をするためのIFTTTの設定について記載します。
事前準備(IFTTT)
Appletの登録
IFTTTの設定手順について説明します。
- https://ifttt.com/home へアクセスしてアカウントを作成 or ログイン
- Createにて、以下の2つを作成
- 電源をオフする
- This(Trigger)
- "Webhooks" - "Receive a web request"
- "Event Name" : meross_speaker_poweroff
- "Webhooks" - "Receive a web request"
- That(Action)
- Meross - Turn off
- This(Trigger)
- 電源をオンする
- This(Trigger)
- "Webhooks" - "Receive a web request"
- "Event Name" : meross_speaker_poweron
- "Webhooks" - "Receive a web request"
- That(Action)
- Meross - Turn on
- This(Trigger)
- 電源をオフする
tokenの確認
webhooksをキックするためにtokenを確認します。
- Webhooks Settingsのページより確認する
- 下記画像のマスクされている箇所に記載されている
スイッチの監視
ラズベリーパイからスイッチの監視を行います。
コード
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秒に設定しています。
さいごに
無事に作りたかったものが作れました。
また、ヘッドフォンも定位置に片付ける習慣がつき、よかったです。
この記事も含め、ラズパイの活用方法を
としてまとめ中です。
Discussion