気の向くままに辿るIT/ICT/IoT
IoT・電子工作

PyGTK/Gladeでラズパイ自作スマートスピーカー用操作パネル作成

ホーム前へ次へ
Raspberry Piって?

PyGTK/Gladeでラズパイ自作スマートスピーカー用操作パネル作成

PyGTK/Gladeでラズパイ自作スマートスピーカー用操作パネル作成

2019/10/30

 以前、作って運用しつつもブラッシュアップ中のRaspberry Pi 3 Model B+とJuliusOpen JTalkベースの自作スマートスピーカーがあります。

 主な機能は、

 尚、ラズパイ用ACアダプタを挿したスイッチ付きコンセントでのON/OFFとは別にラズパイ用boot/reboot/shutdown物理ボタン付き。

 音声認識にJuliusを使った自作スマートスピーカーに伝言とメモの機能を実装するにあたり、マイクとスピーカーを専有してしまうOSSやALSAからPulseAudioに移行しました。

 ちなみに便利なのでラズパイだけでなく、PC/Debianにも自作スマートスピーカー機能を搭載しています。

 自ずとモニタ付きとなるPC版スマートスピーカーには、PC及びラズパイ双方のスマートスピーカー機能のデスクトップアプリとしてPyQt5/Qt Designerによる操作パネルも作成しました。

GTK・GUIツールキットで操作パネルを作成

 今回は、自作スマートスピーカー機能においてマウス操作もできるようにすべく、GUIツールキットであるPyGTK/Gladeでスマートスピーカー用の操作パネルを作ってみることにしました。

 と言っても自身の場合、ラズパイスマートスピーカーには、今のところディスプレイを搭載していないので、とりあえず、PC版Julius+Open JTalkスマートスピーカー用やパソコンから遠隔操作でラズパイスマートスピーカーを操作することにしました。

 Linuxで(というか、今どきは、たいていクロスプラットフォームも)デスクトップアプリを作るにあたっては、GTK/GTK+やQtなどを使うことは知っていましたが、自身としてはこれらで作るのは、初めてです。

 当初、全てブラウザで実装...とも思いましたが、いろいろ考えるとブラウザ起動-ページ表示も含めアプリを作る方が賢明かと思うに至りました。

環境

GTK/GTK+用GUIデザイナの1つGlade

 最初、Gladeが目に付いたのですが、インストールしてはみたものの、URLから辿れるドキュメントも概要プラスアルファっぽく、他をあたろうと検索、The Python GTK+ 3 Tutorialを発見、これを使ってみることにしました。

 読み進めていくとGlade(やGtk.Builder)についても書いてあり、これは、どうやらレイアウト・デザインのみ行なうことができる(結果XMLファイル≒GtkBuilderファイルとして出力してくれる)ソフトウェアで後からスクリプトを実装できるとのこと。

 また、Gladeの(メニューからポップアップされる)開発者向けマニュアルからGTKについてたどってみると複雑なユーザーインタフェースを作る場合には、GtkBuilderとGTK固有の説明マークアップ言語の利用が推奨され、Gladeのようなビジュアルユーザーインタフェースエディタを使うこともできるよとのこと。

試作

 今回は、試作的に9. Button Widgetsのサンプルをほぼそのまま使わせて頂き、いくつかボタンを配置・機能させるところまで。

 これに伴い、ボタン押下時に既存のスクリプトを実行できるよう追記、また、起動用の.desktopファイルの作成・デスクトップへの配置をしてみました。

Pythonで作ったGtk/Gtk+自作スマートスピーカー用操作パネル

 結果から言うと、こんな画面(文字は日本語も可)を表示させてみることにしました。

 これは、先週作った自作スマートスピーカーによるYoutubeの音楽再生、スキップ、停止とOpenはさておき、Closeは、当該画面を閉じるボタンで動作確認済みです。

 要は、スマートスピーカー機能をデスクトップアプリとしても使うことができるということです。

$ chmod +x ~/tmp/sp_ctrl_panel.py
$ cat ~/tmp/sp_ctrl_panel.py
#!/usr/bin/python3
 
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk
import subprocess
import sys
 
class ButtonWindow(Gtk.Window):
 
  def __init__(self):
    Gtk.Window.__init__(self, title="SmartSpeaker Ctrl Panel")
    self.set_border_width(10)
 
    hbox = Gtk.Box(spacing=6)
    self.add(hbox)
 
    button = Gtk.Button.new_with_label("JPOP")
    button.connect("clicked", self.on_jpop_clicked)
    hbox.pack_start(button, True, True, 0)
 
    button = Gtk.Button.new_with_label("Skip Playlist")
    button.connect("clicked", self.on_skip_playlist_clicked)
    hbox.pack_start(button, True, True, 0)
 
    button = Gtk.Button.new_with_label("Stop Music")
    button.connect("clicked", self.on_stop_music_clicked)
    hbox.pack_start(button, True, True, 0)
 
    button = Gtk.Button.new_with_mnemonic("_Open")
    button.connect("clicked", self.on_open_clicked)
    hbox.pack_start(button, True, True, 0)
 
    button = Gtk.Button.new_with_mnemonic("_Close")
    button.connect("clicked", self.on_close_clicked)
    hbox.pack_start(button, True, True, 0)
 
  def on_jpop_clicked(self, button):
    print("\"JPOP\" button was clicked")
    subprocess.call("/home/xxx/tmp/sound/script/jpop_play.sh &", shell=True)
 
  def on_skip_playlist_clicked(self, button):
    print("\"Skip Playlist\" button was clicked")
    subprocess.run(['pkill', 'youtube-dl'])
 
  def on_stop_music_clicked(self, button):
    print("\"Stop Music\" button was clicked")
    subprocess.call("/home/xxx/tmp/sound/script/stop_radio.sh", shell=True)
 
  def on_open_clicked(self, button):
    print("\"Open\" button was clicked")
 
  def on_close_clicked(self, button):
    print("Closing application")
    Gtk.main_quit()
 
win = ButtonWindow()
win.connect("destroy", Gtk.main_quit)
win.show_all()
Gtk.main()
$ ./home/xxx/tmp/sp_ctrl_panel.py
$

 pythonスクリプトはこんな感じです。

 一応、実行確認も。

$ cat ~/tmp/panel_for_speaker.desktop
[Desktop Entry]
Type=Application
Encoding=UTF-8
Name=Speaker Panel
Comment=Panel for Smart Speaker
Exec=/home/xxx/tmp/sp_ctrl_panel.py
Terminal=false
$ cp ~/tmp/panel_for_speaker.desktop ~/Desktop
$

 デスクトップファイルは、適当なものを/usr/share/applicationsから選んでコピー・編集してこんな感じに。

 コマンドとしては、/usr/share/applicationsや~/.local/share/applicationsなどに入れて(~/Desktopにはln -s ...して)おくのでしょうが、メニューやタスクバーにおきたいわけでもない為、~/Desktopに置いておくだけに留めました。

 あとは、他のデスクトップでも同様の設定を要すると思いますが、デスクトップがCinnamonの場合には、アイコンのプロパティを開いて[パーミッション]で[プログラムとして実行可能 (E)]にチェックを入れておきます。

 そうしない場合、デスクトップに配置されたアイコンをクリックすると[安全性が確認されていないアプリケーションランチャ]と言う名の警告ポップアップが出力されますが、[警告を無視して起動(L)]なり、[安全性が確認されたものとする(T)]なりをクリックすれば、冒頭の操作画面が表示されます。

リモートホストの操作

 パソコンからラズパイ上のスマートスピーカー機能を操作するにあたっては、sshコマンドで接続先のサーバでコマンドを実行するのようにsshを使用します。

 コマンド部の(シングル・ダブル問わず)クォートの有無で結果が返るのがローカルかリモートか決まるというのは、興味深いですね。

 ともあれ、これでローカルからリモート側のコマンドやスクリプトを実行できます。

 ただ、リモートホスト(ラズパイ)へのアクセスにパスワードを求められると自動化できない為、パスワード無しでログインできるようにしておく必要があります。

 具体的には、3 Steps to Perform SSH Login Without Password Using ssh-keygen & ssh-copy-idにあるようにローカル側においてssh-keygenで何も設定せず(全てEnterでやりすごして)認証鍵を作り、"ssh-copy-id -i ..."を使ってリモートホスト側に公開鍵をコピーします。

 こうすることでパスワード・パスフレーズなしでローカルからリモートホストにssh接続できるようになります。

 よって[ssh user@remotehost]を接頭辞として入れる以外は、当該ホスト上でコマンドやスクリプトを実行するのと同様にローカルからリモートホストを操作することができるようになりますし、それらをローカルでスクリプトに書くこともできるようになります。

$ cat /path/to/remote_play.sh
#!/bin/sh
 
ping -c1 speaker.local
if [ $? -ge 1 ]; then
  :
else
  ssh user@remotehost "/remote/path/to/jpop_play.sh"
fi
$

 ただ、今回は、sp_ctrl_panel.pyで、この[ssh]によるコマンドを直でコールしようにもうまくいかなかった為、shellスクリプトに書いてこれを指定する恰好としました。

 これは再生ボタン押下時の例ですが、スキップや停止ボタン全てラズパイ用にスクリプトを作り、同様にしました。

 尚、自身の場合、ラズベリーパイのスマートスピーカーは、使う時以外は、電源を落としている為、疎通チェックして通った場合のみ、コマンドを実行するようにしました。

 ただし、遠隔操作の場合、リモートホスト側にもプロセスはあるものの、ラッパスクリプト側でプレイリストをランダムに並べ替えてループ内でsshコマンドを投げる等ごにょごにょした場合、ローカル(今回はノートPC)側にもラッパスクリプトに加え、その中で実行したsshのプロセスがあります。

 よって単にsshコマンドを投げただけなら何事もなくとも、ごにょごにょした上に立つsshコマンドの場合には、ごにょごにょ含めてなんぼなのでローカルホストのPCの電源を落とすとリモートホスト側にも影響があり、プレイリストなどでは、再生が途中で停止してしまったりするので要注意。

変更の反映

 今回の方法だと、デスクトップアプリである操作パネルについては、操作パネルを起動し直せば十分、せいぜいデスクトップをクリックしてアクティブにした状態で[F5]で良さ気ですが、先のようなパスに置いてメニューに登録する場合には、[Alt]+[F2]後、[r]と入力、[Enter]すると反映されるでしょう。

ホーム前へ次へ