Volumio で Pirate Audio 備忘録です.
はじめに
2025年のお正月の暇つぶしに Raspberry pi zero 2W + Volumio OS + Pirate Audio を使ったオーディオプレーヤーを作成してみました.Volumio OS のプラグインで Pirate Audio を有効化した場合と比べて,
- 曲名,アーチスト名等がきちんと日本語に対応している
- 物理ボタンがきちんと動く
- 曲の未再生時は時計としての動作となる
ところが良いところでしょうか.
逆に欠点は,Volumio OS のアップデート時には,リセットと全面的な再設定が必要になるところと,相性の良くないプラグイン等があるところですが,これは Volumio OS の標準状態を書き換える形で実現していることから仕方のないところだと思います.
プレイヤーの概要
外観
外観は以下の写真の通りです.オーディオプレーヤー本体を鉄製の小型ブックエンド上部にシート状の磁石を用いて固定する形としました.また,これだけだとブックエンド表面が少し寂しいので,磁石で固定できるフックを取り付けています.
動作の概要
Volumio で音楽を再生していないときは日付と時刻のみが表示され,音楽再生時のみ,曲名,アルバム名,アーティスト名,ジャケット写真,Audio Visualizer,CPU温度,音声フォーマットが表示されます.ただし,Airplay や DLNA で再生する場合,もしくはプラグイン等を用いて Youtube 等を再生する場合の表示は,どのようなプレイヤーを用いているかや,どのようなプラグインを使っているかで違います.
操作方法
全面にある4つのボタン(A, B, X, Y)を使って以下の操作が可能です.
- A: 再生・停止,長押しで Volumio OS の終了
- B: 次の曲へ
- X: ボリュームを上げる(5メモリ)
- Y: ボリュームを下げる(5メモリ)
もちろん Volumio OS の Web Interface やスマホアプリで操作も可能です.ただし,ボリュームを実際に操作するには,Volumio OS の Web メニューの「再生設定」の「音量オプション」の「ミキサータイプ」を「ソフトウェア」に変更する必要があります.
レシピ(ハードウェア)
今回使用した主な材料は以下の通りです(電源供給用の microUSB ケーブル等ももちろん必要ですが,これらは主な材料ではないので詳細は省きます).
- Raspberry pi zero 2W
- ケース
- 16GByte SD カード
- ピンヘッダ
- Primoni Pirate Audio
- ブックエンド(DAISO で購入)
- 磁石シート(DAISO で購入)
- 磁石フック(DAISO で購入)
Raspberry pi zero W でも大丈夫だと思いますが,以下,必要なアプリの作成を他のPCで行わないといけなくなると思います(いわゆるクロスコンパイルですね).それから, Raspberry pi zero 2W にはピンヘッダを取り付けないといけませんが,これについては,以前の記事,
をご覧ください.
レシピ(基本ソフトウェア)
今回は基本ソフトウェアとして,Volumio OS を使います.
私が使ったのは,Volumio-3.785-2024-12-16-pi.zip です.通常通りインストール作業を行ったあとに,http://volumio.local/dev にアクセスし,SSH を ENABLE 状態にしてください.以下の作業は,Volumio OS に SSH でアクセス(ログイン)した状態で行う内容となります.
Pirate Audio 制御ソフトウェアの導入
Pirate Audio の有効化
まず,Pirate Audio の I2C 接続部分を有効化するために,/etc/config.txt の最後に以下の項目を付け加えて再起動してください.
$ sudo vi /etc/config.txt .... gpio=25=op,dh dtparam=spi=on
Rust ビルド環境の準備
以下,液晶とボタンの制御を行うプログラムを使います.このプログラムは,rust のソースコードの形で配布されている(する)ので,これを実行できる形にするにはコンパイルしなければなりません.したがって,まず,rust のビルド環境を準備します.
私の場合は,Volumio をインストールした Raspberry pi zero 2W 上に合わせてこのビルド環境を準備し利用していますし,以下の手順もこの前提で行いますが,より非力な Raspberry pi zero W の場合は,さすがによりパワフルな別環境でこれを行う(いわゆるクロスコンパイルする)必要があると思います.
$ sudo apt install build-essential libssl-dev $ curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh # いくつかの選択肢でENTERを入力 $ reboot # 正常にインストールできているかの確認 $ cargo -V cargo 1.83.0 (5ffbef321 2024-10-29)
なお,上記以外にも cargo-binstall や cargo-watch を入れるとコンパイル時間の大幅な節約になるようなので,お好みで導入されてください.以下の記事が参考になります.
液晶の制御
次に液晶の制御プログラムを追加します.このために Volumio で ST7789 の液晶を制御する以下 blog のものを利用します.
実際のプログラムは以下の github ページにあります.
ただし,このソースコードそのままだと,Pirate Audio の場合は画面が上下逆になるので,以下にオリジナルと書き換えたソースコードの相違部分を示しておきます(アルバムアートの枠線の削除とフォントインストール位置の変更も行なっています).
ソースコードの準備と変更
まず,ソースコードを github からクローンします.
$ cd src/ $ git clone https://github.com/Trunkene/st7789volumio.git
ソースコードを書き換えた部分は以下の通りです.
$ diff -up main.rs.orig main.rs --- main.rs.orig 2025-01-07 09:03:04.953435699 +0900 +++ main.rs 2024-12-14 19:28:05.000000000 +0900 @@ -35,8 +35,8 @@ use std::{ /// Constants /// -const INFO_FONT: &str = "/home/volumio/.local/share/fonts/TakaoPGothic.ttf"; -const NUM_FONT: &str = "/home/volumio/.local/share/fonts/led_digital_7.ttf"; +const INFO_FONT: &str = "/usr/local/share/fonts/TakaoPGothic.ttf"; +const NUM_FONT: &str = "/usr/local/share/fonts/led_digital_7.ttf"; const INFO_INTERVAL_SEC: u64 = 2; const DISP_INTERVAL_MSEC: u64 = 20; @@ -567,7 +567,7 @@ impl State<'_> { draw_hollow_rect_mut( baseimg, Rect::at(THUMB_X, THUMB_Y).of_size(THUMB_WIDTH, THUMB_HEIGHT), - COLOR_WHITE, + COLOR_BLACK, ); } // SampleRate/BitDepth/Channels @@ -877,7 +877,7 @@ fn main() -> Result<(), Box<dyn std::err Some(blk_pin), DISP_WIDTH, DISP_HEIGHT, - ROTATION::Rot180, + ROTATION::Rot0, ); let mut st7789img = St7789Img::new(DISP_WIDTH, DISP_HEIGHT); // Display
コンパイルとインストール
コンパイルの手順は以下の通りです.結構時間がかかります.
$ cd st7789volumio $ cargo b --release $ sudo cp target/release/st7789volumio /usr/local/bin/.
また,実際にこのプログラムを利用するには,Takao Fonts と LED DIGITAL 7 Font のインストールが必要です.Takao Fonts については,
から,LED DIGIGAL 7 Font については,
からダウンロードして,それぞれの中に含まれている TakaoPGothic.ttf と led_digital_7.ttf を /usr/local/share/fonts に配置しておいてください.
Audio Visualizer の有効化
Audio Visualizer を有効化したい場合は,さらに以下の設定を行なってください(無効で良い場合は無視してください).
$ sudo vi /volumio/app/plugins/music_service/mpd/mpd.conf.tmpl audio_output { type "fifo" <途中略> enabled "yes" # この部分の "no" を "yes" に変更 path "/tmp/snapfifo" format "44100:16:1" }
ただし,Audio Visualizer を有効化した場合は st7789volumio.service の起動をかなり遅延させなければならなくなることにご注意ください.
動作チェック
プログラムの実行テストは以下のようにすれば行えます.
$ st7789volumio -s0 -c1 -d9 -b13 -r17 # Audio Visualizer OFF $ st7789volumio -s0 -c1 -d9 -b13 -r17 -x1 # Audio Visualizer ON
自動起動の設定
Volumio 起動と合わせて本プログラムも自動起動させるための systemd への登録手順は以下の通りです.
$ sudo vi /etc/systemd/system/st7789volumio.service [Unit] Description=ST7789 LED Display for Volumio After=volumio-time-update.service Requires=volumio.service [Service] ExecStart=/usr/local/bin/st7789volumio -s0 -c1 -d9 -b13 -r17 # ExecStartPre=/bin/sleep 30s # Audio Visualizer ON # ExecStart=/usr/local/bin/st7789volumio -s0 -c1 -d9 -b13 -r17 -x1 # Audio Visualizer ON Type=simple [Install] WantedBy=multi-user.target $ sudo systemctl enable st7789volumio.service $ sudo systemctl start st7789volumio.service
後片付け
プログラムのコンパイル時にできる一時ファイルがかなりの量になるので,正常に動作することを確認できたら掃除をしておいた方が良いと思います.
$ cd src/st7789volumio $ cargo clean
ボタンの制御
次はボタンの制御を行うプログラムを追加します.このプログラムも rust のソースコードの形で以下示します.
なお,このプログラム,私が自作したものなのですが,面倒なので,github 等にソースコードを公開はしていません.また,ボタンの状態の判定は割り込みではなく,単なるループで行なっています.これはループのままでも Raspberry pi zero 2W で使う限りは負荷を気にする必要がなさそうだったからです(割り込み版のサンプルコードも以下示しておきます.興味のある方はこちらで書き換えてみてください).
その他にもいろいろツッコミどころのあるプログラムだとは思いますし,実際動作が少し怪しいところもありますが,動けば良いやという日曜プログラムなので,ご容赦ください.なお,プログラムは勝手に改変,配布していただいて構いません.
ソースコードの準備
新しい rust プロジェクト pabvolumio を準備します.
$ cd src $ cargo new pabvolumio
プロジェクトのファイルの内容を以下のように書き換えます.まず,Cargo.toml ファイルは以下の通りです.
$ cd pabvolumio $ vi Cargo.toml [package] name = "pabvolumio" version = "0.1.0" edition = "2021" [dependencies] regex = "1.11.1" reqwest = { version = "0.12.9", features =["json", "blocking", "rustls-tls"] } rppal = "0.22.1"
次に,src/main.rs の内容は以下の通りです.
$ vi src/main.rs use rppal::gpio::{Gpio, Level}; use reqwest::blocking; use regex::Regex; use std::process; fn main() -> Result<(), Box<dyn std::error::Error>> { // GPIO を初期化 let gpio = Gpio::new()?; // GPIO20 を入力モードで初期化し、プルアップ抵抗を有効にする let ab = gpio.get(5)?.into_input_pullup(); let bb = gpio.get(6)?.into_input_pullup(); let xb = gpio.get(16)?.into_input_pullup(); let yb = gpio.get(20)?.into_input_pullup(); // 読み取り loop { let av = ab.read(); let bv = bb.read(); let xv = xb.read(); let yv = yb.read(); let mut n = 0; if av == Level::Low { while n < 9 { if n < 8 { std::thread::sleep(std::time::Duration::from_millis(1000)); if ab.read() == Level::High { blocking::get("http://127.0.0.1:3000/api/v1/commands/?cmd=toggle")?; break; } } else { println!("Power off!"); process::exit(0); } n += 1; } } if bv == Level::Low { blocking::get("http://127.0.0.1:3000/api/v1/commands/?cmd=next")?; } if xv == Level::Low { let res_vs = blocking::get("http://127.0.0.1:3000/api/v1/getState")?.text()?; let re = Regex::new(r#""volume":"*(\d+)"#).unwrap(); let cv = re.captures(&res_vs).unwrap().get(1).unwrap().as_str().parse::<u32>().unwrap(); if cv > 95 { blocking::get("http://127.0.0.1:3000/api/v1/commands/?cmd=volume&volume=100")?; } else { blocking::get(format!("http://127.0.0.1:3000/api/v1/commands/?cmd=volume&volume={}", cv+5))?; } } if yv == Level::Low { let res_vs = blocking::get("http://127.0.0.1:3000/api/v1/getState")?.text()?; let re = Regex::new(r#""volume":"*(\d+)"#).unwrap(); let cv = re.captures(&res_vs).unwrap().get(1).unwrap().as_str().parse::<u32>().unwrap(); if cv < 5 { blocking::get("http://127.0.0.1:3000/api/v1/commands/?cmd=volume&volume=0")?; } else { blocking::get(format!("http://127.0.0.1:3000/api/v1/commands/?cmd=volume&volume={}", cv-5))?; } } std::thread::sleep(std::time::Duration::from_millis(200)); } }
コンパイルとインストール
コンパイルの手順は液晶表示部分と同様です.結構時間がかかります.なお,動作確認を終えたら後の一時ファイルの削除の手順も液晶表示部分と同じなので省略します.
$ cargo b --release $ sudo cp target/release/pabvolumio /usr/local/bin/.
動作チェック
動作チェックは単に /usr/local/bin にインストールしたファイルを実行するだけです.
$ pabvolumio
なお,この段階だと,A ボタンの長押しは単にこのボタン制御プログラムを終了させるだけになっていることにご注意ください.ボタン長押しで Volumio のシャットダウンの設定は systemd の機能を用います.
自動起動の設定と終了時動作の設定
Volumio 起動と合わせて本プログラムも自動起動させるための systemd への登録手順は以下の通りです.サービス終了後の処理を ExecStop で定義している(この場合は Volumio OS の Poweroff)ことにご注意ください.
$ sudo vi /etc/systemd/system/pabvolumio.service [Unit] Description=pirate audio pb service After=volumio-time-update.service Requires=st7789volumio.servic [Service] ExecStart=/usr/local/bin/pabvolumio ExecStop=/bin/systemctl poweroff Type=simple [Install] WantedBy=multi-user.target $ sudo systemctl enable pabvolumio.service $ sudo systemctl start pabvolumio.service
割り込みサンプルプログラム
上で示したプログラムはボタンの状態監視を loop で行っていますが,これを割り込みで行う場合は以下のような処理になるのではないかと思います.ただし,本サンプルは x ボタンを押すとメッセージを表示するだけの例となっています.上の loop での処理をこれに置き換えるものにはなっていないことにご注意ください.
use rppal::gpio::{Gpio, Trigger, InputPin, Event}; use std::sync::mpsc::channel; use std::thread; use std::time::Duration; fn main() -> Result<(), Box<dyn std::error::Error>> { // GPIO を初期化 let gpio = Gpio::new()?; // GPIO20 を入力モードで初期化し、プルアップ抵抗を有効にする let mut pin20: InputPin = gpio.get(20)?.into_input_pullup(); // チャンネルを作成してスレッド間通信を確立 let (tx, rx) = channel(); // 非同期割り込みを設定 pin20.set_async_interrupt(Trigger::Both, None, move |event| { // 割り込み時にイベントを送信 if let Err(err) = tx.send(event) { eprintln!("Failed to send GPIO event: {}", err); } })?; // メインスレッドで割り込みイベントを処理 thread::spawn(move || { loop { match rx.recv() { Ok(event) => match event.trigger { Trigger::Disabled => println!("GPIO20 Disabled"), Trigger::RisingEdge => println!("GPIO20 Rising Edge detected"), Trigger::FallingEdge => println!("GPIO20 Falling Edge detected"), Trigger::Both => println!("GPIO20 Both detected"), }, Err(err) => { eprintln!("Error receiving GPIO event: {}", err); break; } } } }); // メインスレッドが終了しないように適当な時間待機 thread::sleep(Duration::from_secs(60)); Ok(()) }
おわりに
如何でしょうか? Volumio OS 標準と比べると,日本語にきちんと対応している分使いやすいのではないかなと思います.また,いろいろ動作の怪しいところや,ボタン操作の内容をもっと他のものに入れ替えたいなどもあると思いますが,その場合は,直接ソースコードを書き換えて実現していただければと思います.
と言うか,今回はじめて Rust でプログラムを組みましたが.いろいろ文法あやふやなままの理解でやりましたので,なんでこんな変なことしてるんだ!というところが多々あるんじゃないかなと思いますが,この辺りについては生暖かい目で見ていただければと思います.
以上!