概要
ラズパイオーディオ筐体の操作プログラムを作成し、完成させた。
背景と目的
前回、筐体の組み立てを行った。今回は、ボリュームなどの操作プログラムを作成し、完成させる。
詳細
0. 準備
今回作成する操作系は、
- ①ロータリーエンコーダによるボリューム
- 3つのタクトスイッチによる再生停止、トラック戻し、トラック送り
だ。①については、JustBoom Amp HAT基板のドキュメント
を見ると、ロータリーエンコーダを読み取るプログラムが用意されていたようなのだが、残念ながらすでに利用不可能になっている。そのため、自前で用意する。
GPIOを読み取る必要があるので、
sudo apt-get install python3-rpi.gpio
としてPythonライブラリをインストールしておく。
1. volume操作部
ロータリーエンコーダの操作を検出処理を一から作成するのは面倒なのでネット上で使えるものを探す。
上記サイトを参考に
- Rpi.GPIOを使う
- 読み取りを周期的に行うようにスレッド化する
- 読み取り時に任意の処理を呼び出すためのコールバックの仕組みを入れる
- チャタリング対策
といった修正を行った。
import RPi.GPIO as GPIO import sys import time import threading import subprocess import traceback GPIO.setmode(GPIO.BCM) class RotaryEncoder(threading.Thread): """ ロータリーエンコーダ """ def __init__(self, pin_a, pin_b, on_change=None): super().__init__(target=self.run, daemon=True) self.pin_a = pin_a self.pin_b = pin_b self.position = 0 self.on_change = on_change self._stop_request = False self.min_interval = 0.1 self.last_time = time.time() GPIO.setup(self.pin_a, GPIO.IN, pull_up_down=GPIO.PUD_UP) GPIO.setup(self.pin_b, GPIO.IN, pull_up_down=GPIO.PUD_UP) self.last_pin_a = GPIO.input(self.pin_a) def run(self): try: while True: self.read() time.sleep(0.01) if self._stop_request: print("Rotary encoder thread recieved stop request") self._stop_request = False break except Exception: print(traceback.format_exc()) print("Error in rotary encoder thread") finally: GPIO.cleanup() print("Rotary encoder thread terminated") def read(self): current_pin_a = GPIO.input(self.pin_a) current_pin_b = GPIO.input(self.pin_b) direction = None if current_pin_a != self.last_pin_a: if current_pin_b != current_pin_a: position = self.position + 1 direction = "CW" else: position = self.position - 1 direction = "CCW" if ( self.on_change is not None and direction != self.last_direction # チャタリング対策 and time.time() - self.last_time > self.min_interval # チャタリング対策 ): self.on_change[0](direction, self.position, self.on_change[1]) # 任意処理コールバック self.last_time = time.time() self.position = position self.last_pin_a = current_pin_a self.last_direction = direction return (direction, self.position) def stop(self): """ 終了 """ self._stop_request = True
2. 再生/停止、トラック操作部
こちらも、上記とほぼ同様の構成でクラス化。冒頭のインポート部は共通なので省略。
class SwitchBorad(threading.Thread): """ スイッチ基板を読み取る """ def __init__( self, prev_pin, play_pin, next_pin, on_next_pushed=None, on_play_pushed=None, on_prev_pushed=None, ): super().__init__(target=self.run, daemon=True) self.prev_pin = prev_pin self.play_pin = play_pin self.next_pin = next_pin GPIO.setup(self.prev_pin, GPIO.IN, pull_up_down=GPIO.PUD_UP) GPIO.setup(self.play_pin, GPIO.IN, pull_up_down=GPIO.PUD_UP) GPIO.setup(self.next_pin, GPIO.IN, pull_up_down=GPIO.PUD_UP) self.on_play_pushed = on_play_pushed self.on_next_pushed = on_next_pushed self.on_prev_pushed = on_prev_pushed def run(self): try: while True: self.read() time.sleep(0.01) except Exception: print(traceback.format_exc()) print("Error in switch borad thread") finally: GPIO.cleanup() print("Switch borad thread terminated") def read(self): """ 読み取る """ self.read_basic(self.prev_pin, self.on_prev_pushed) self.read_basic(self.play_pin, self.on_play_pushed) self.read_basic(self.next_pin, self.on_next_pushed) def read_basic(self, target_pin, on_pushed=None): if GPIO.input(target_pin) == 0: while True: if GPIO.input(target_pin) == 1: time.sleep(0.01) break if on_pushed is not None: on_pushed[0](on_pushed[1])
3. スクリプト全体
上記2クラスを用いて、読み取り処理スクリプトを作成。
volumioの操作は、Command-Line Clientを使用した。
def on_play_pushed(user_data): print("Play pushed") subprocess.run("volumio toggle", shell=True) def on_next_pushed(user_data): print("Next pushed") subprocess.run("volumio next", shell=True) def on_prev_pushed(user_data): print("Prev pushed") subprocess.run("volumio previous", shell=True) def on_volume_change(direction, position, user_data): print(f"on_volume_change Direction: {direction}") if direction == "CW": param = "plus" else: param = "minus" subprocess.run(f"volumio volume {param}", shell=True) if __name__ == "__main__": PIN_A = 23 PIN_B = 24 PIN_PREV = 13 PIN_PLAY = 6 PIN_NEXT = 5 re0 = RotaryEncoder(PIN_A, PIN_B, (on_volume_change, None)) re0.start() swb = SwitchBorad( PIN_PREV, PIN_PLAY, PIN_NEXT, on_prev_pushed=(on_prev_pushed, None), on_play_pushed=(on_play_pushed, None), on_next_pushed=(on_next_pushed, None), ) swb.start() try: while True: time.sleep(1) except KeyboardInterrupt: pass finally: re0.stop() swb.stop() re0.join() swb.join()
4. systemdサービス化
ラズパイ起動時に自動的にスクリプトが起動するように、サービス化する。
まず、my-volumio-controller.serviceというファイルを作成。
[Unit] Description=My Volumio Controller Service After=network.target [Service] ExecStart=/usr/bin/python3 /home/volumio/volume.py Restart=on-failure User=volumio WorkingDirectory=/home/volumio [Install] WantedBy=multi-user.target
/etc/systemd/system/にコピー。
sudo cp ./my-volumio-controller.service /etc/systemd/system/ sudo chmod 755 /etc/systemd/system/my-volumio-controller.service sudo chown root:root /etc/systemd/system/my-volumio-controller.service
サービス有効化。
sudo systemctl enable my-volumio-controller.service
ラズパイを再起動して、有効化されているか操作して動作確認→OK。
5. 完成
機能はシンプルだが、操作が楽になり、ぐちゃぐちゃだったケーブルも整理され、ホコリも入りづらくなったので、完成とする。
まとめと今後の課題
ラズパイオーディオ筐体の操作プログラムを作成し、完成させた。操作が快適になり、オーディオライフが充実しそうだ。
