GoogleのAIY ProjectのVoice Kitを使用すれば、自分でGoogle Homeのようなものを作ることができます。今回はRaspberry Pi 3 Model BにVoice Kitを入れて、音声でSwitchBotをオン・オフする音声コマンドデバイスを開発してみたいと思います。
手作り感あふれる完成品の動画
目次
序章
私はMacbook Proを使用しているのですが、毎回充電が100%になる度に充電器のスイッチをオフにして、空になったらまたオンにするのが面倒でした。デスクの奥に電源タップがあって、わざわざ席を立ってポチっとしに行かなきゃ届かない位置だったのです。これがすごく面倒臭くて。なんだ、しょうもない話だなとお思いかもしれませんが、私はそれ以上にしょうもない人間なんです。
なんとか楽な方法はないかなーと苦しみに苦しみぬいた上、カレント・ビッグウェーブに乗ってここはひとつボイスで操ることにしました。買ってからしばらく、ヌルヌルのまま放置プレイしていたVoice Kitがありましたからね。あと、何かに使えるんじゃないかと思って買っていたSwitchBot。Bluetooth経由で操作できるアナログなスマートスイッチです。
Amazonで日本語版も売ってるみたいです。
もはや死んだ感のある往年のバズワード「マッシュアップ」によって、様々な技術を組み合わせて作りますよ。
環境
私の環境は次の通りです。なおSnowboyというのはホットワード検出のライブラリ(+ボイスモデル作成クラウドサービス)です。
- Raspberry pi 3 Model B
- Voice Kit V1
- Snowboy
- SwitchBot ファームウェアバージョン 4.2
※2018-05-01現在、Voice KitがV2になったせいか、Voice KitのページからはSDカードイメージのファイルがダウンロードできなくなっているようです。Voice Kit V2を使っている人はバンドルされているSDカードのままでOKだと思います(持ってないから確証はないけど)。V1を使用している方で、再度イメージ焼きから開始したい方は、以下のURLのページからイメージがダウンロードできます。
Pythonセットアップ
ではまず最初にPythonのセットアップから始めます。Pythonのバージョンはもちろん3です。python-venvは最初から入っているはずなのでvenvを作成するところから始めます。
📄cloud_speech_test.py
$ cd $ python3 -m venv env/yoshiko
ここでは仮想環境名をyoshiko
にしました。「よしこちゃん」と話しかけると反応するようにしたかったので。まあここは「まりこちゃん」でも「さちこちゃん」でも構いません。適宜読み替えてください。言わずもがな、思い出の女子の名前にしておくと、グッと盛り上がりますよ!
続いて、pipとsetuptoolsのインストール。
$ env/yoshiko/bin/python -m pip install --upgrade pip setuptools
これで仮想環境を使用することができます。
$ source env/yoshiko/bin/activate
ターミナルに(yoshiko)
という表示が付き、python --version
の結果がPython 3系になっていればOKです。
Google Assistant SDKのセットアップ
今回は直接このGoogle Assistant SDKを使用することはないんですが、どうせ必要になってくると思うのセットアップしておきます。いわゆるGoogle Home的なことができるAPIです。pipでインストールします。
(yoshiko)$ python -m pip install --upgrade google-assistant-sdk[samples]
それから認証用ライブラリも同様に。
(yoshiko)$ python -m pip install --upgrade google-auth-oauthlib[tool]
クライアントから使用するのにはOAuth認証が必要です。WebでクライアントIDを取得する必要があるので、Google Cloud Consoleを訪れてOAuthクライアントIDを取得しましょう。まずはGoogle Cloud Consoleへアクセスして、新規プロジェクトを作成します。
プロジェクト名もyoshikoにしました。しばらく待っていると、プロジェクトの作成が完了するはずです。左側のメニューから「APIとサービス」-「ライブラリ」を選択します。
検索ボックスが表示されるので、「google assistant sdk」と入力すると、該当するAPIに絞り込まれますので、これをクリックします。
さっそくAPIを有効にして、完了を待ちます。
完了したら左側のメニューから「認証情報」をし、続いてOAuthクライントIDを選択します。
同意画面が必要だそうで、「同意画面を設定」へと進みましょう。
同意画面の設定で入力が必要なのは「ユーザーに表示するサービス名」のみですので、ここに「Yoshiko」と入力しました。そして保存。
すると先程の画面に戻ってきて「アプリケーションの種類」の選択を迫られます。なんでもいいと思うのですが、私は「その他」を選択し、名前を「yoshiko」にしておきました。
認証情報の作成が完了したので、クライアントに配置するJSONファイルをダウンロードできます。
ラズパイ上でGUIで作業していた方はそのままダウンロードすればよいですし、別の端末からscpで送ってもよいです。ラズパイ上の/home/pi
にこのJSONファイルを配置してください。
でのこのクライアント(ラズパイ)からGoogle Assistant APIを利用できるよう認証します。次のコマンドを実行します。
(yoshiko)$ google-oauthlib-tool --scope https://www.googleapis.com/auth/assistant-sdk-prototype --save --headless --client-secrets /home/pi/client_secret_XXXXX.json
client_secret_XXXXX.json
というのは、先程ダウンロードしたJSONファイル名です。ご自分の環境に合わせて適宜変更して下さい。
コマンドを実行すると「Please visit this URL to authorize this application: https://…」というメッセージが表示されますので、そのURLにブラウザでアクセスします。Googleアカウントを選択し、アクセスを許可すると認証コードが表示されるので、ターミナルにコピペしてEnterを押します。「credentials saved: …」と表示されていれば認証は完了です。
さらにデバイスモデルというのを登録しないといけないです。
(yoshiko)$ googlesamples-assistant-devicetool register-model --manufacturer 製造者 --product-name 製品名 --type SWITCH --model モデル名
このコマンドのオプション等はググってみてください。大事なのはモデル名ですが、ここではモデル名をyoshiko
にしました。
なお、作成したモデルの一覧は次のコマンドで確認できます。
(yoshiko)$ googlesamples-assistant-devicetool list --model
では最後にちゃんと動作するか確認しておきましょう。Googleが用意していくれているPush to talkのサンプルを実行してみます。
(yoshiko)$ googlesamples-assistant-pushtotalk --project-id プロジェクトID --device-model-id モデル名 --lang ja_jp INFO:root:Connecting to embeddedassistant.googleapis.com INFO:root:Using device model yoshiko and device id xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx Press Enter to send a new request...
Press Enterとか言われるので、指示通りエンターキーを押します。それから何か日本語で話しかけてみて下さい。Google Homeと同じように応答してくれるはずです。上手く動いているようなら、これでGoogle Assistant SDKのセットアップは完了です。
Google Cloud Speech APIのセットアップ
Google Cloud Speech APIは音声→テキストに変換する、いわゆるディクテーション(書き起こし)APIです。ホットワードを検出した後に聞き取りモードに入り、マイクから拾った音声はこのAPIにてテキストに変換します。
cloud_speechも含めたaiyライブラリの依存関係は~/AIY-voice-kit-python/requirements.txt
の中に書いてありますので、すべて最新版を入れておきましょう。
(yoshiko)$ pip install --upgrade google-assistant-grpc (yoshiko)$ pip install --upgrade google-cloud-speech (yoshiko)$ pip install --upgrade google-auth-oauthlib (yoshiko)$ pip install --upgrade pyasn1 (yoshiko)$ pip install --upgrade grpcio
またGoogle Cloud Consoleにいって、認証情報を作成します。今度はサービスアカウントキーを作成します。
続いて必要情報を入力してキーを作成。
勝手にJSONファイルがダウンロードされたら、それを/home/pi
に配置します。その際、ファイル名をcloud_speech.jsonに変更するのを忘れないでください。
次にCloud Speech APIを有効にしておかなければなりません。再びAssistant APIのときと同様に有効にします。
APIを有効にしたらCloud Speechのセットアップは完了です。動作を確認しておきましょう。
確認用スクリプトを入れるディレクトリを作成して、そこで作業します。aiyにリンクを貼れば、pythonパッケージとして認識してくれます。
(yoshiko)$ cd (yoshiko)$ mkdir cloud_speech (yoshiko)$ ln -s ~/AIY-voice-kit-python/src/aiy aiy (yoshiko)$ vi cloud_speech_test.py
import os import aiy.audio import aiy.cloudspeech import aiy.voicehat import aiy.i18n aiy.i18n.set_language_code('ja-JP') recognizer = aiy.cloudspeech.get_recognizer() recognizer.expect_phrase('さようなら') button = aiy.voicehat.get_button() led = aiy.voicehat.get_led() aiy.audio.get_recorder().start() while True: print('ボタンを押して話しかけてください') button.wait_for_press() led.set_state(aiy.voicehat.LED.BLINK) text = recognizer.recognize() led.set_state(aiy.voicehat.LED.OFF) if text is None: print('え?今、なんつった?') else: print('今、「' + text + '」って言いましたよね?') if 'さようなら' in text: print('さよならー') os._exit(0)
スクリプト実行するとVoice Kit上面のボタンを押すまで待ちます。ボタンが押されるとボタンが点滅し、音声入力を待ちます。そこで何か話しかけると、その音声がテキストとして出力されます。「さようなら」と発話するとスクリプトの終了です。なお次の2行の設定で、Cloud Speechを日本語化しています。
📄snowboydecorder.py
import aiy.i18n aiy.i18n.set_language_code('ja-JP')
上手く日本語の書き起こしができていればセットアップは正常です。
Snowboyのセットアップ
次にSnowboyをセットアップします。Snowboyはhotword detectionライブラリです。ホットワードというのは「Ok, Google」とか「Hey, Siri」とかいうアレでして、要するにGoogle Homeのような音声認識デバイスを起動するトリガーとなるワードのことです。よしこちゃんと私の架け橋です。
実はGoogle Assistant SDKだけでも音声待ち受けループ→ディクテーション(音声をテキストに書き起こすこと)という流れでホットワード待ち受けを作成することはできるのですが、待ち受け中もAPI使用の課金対象となるので、無料で遊ぶには敷居が高いです。Snowboyは(個人用途では)無料で利用することができ、しかもホットワードをWeb上で録音・学習させ、モデルデータとしてダウンロードして使用することができます。今回はこのホットワードにSnowboyを使用します。
ライブラリはPythonから使用できます。インストール手順がgithubに書いてありますが、そのままでは動かなかったので、手順を順に説明します。
READMEにはprecompiledバイナリがあると書いてますが、そのままでは動かなかったので、手動でビルドした方がいいと思います。まずは依存関係の解決から。
(yoshiko)$ sudo apt-get install swig3.0 python-pyaudio python3-pyaudio sox (yoshiko)$ pip install pyaudio (yoshiko)$ sudo apt-get install libatlas-base-dev
READMEにも記述のある通り、一度マイクからの録音と再生を試してみます。
(yoshiko)$ rec test.wav --- 何かしゃべってCTRL-C --- (yoshiko)$ play test.wav
録音した音声が再生できればOK。
ではビルドするためにソース類一式をダウンロードしましょう。ホームディレクトリ直下にgit cloneしてます。
(yoshiko)$ git clone https://github.com/Kitt-AI/snowboy.git
ホットワード検出ライブラリをビルドします。
(yoshiko)$ cd snowboy (yoshiko)$ cd swig/Python3 (yoshiko)$ make
READMEの通りmakeしてもmake: swig: Command not found
とエラーになってしまいます。aptでインストールしたswigはswig3.0
というコマンド名になっているからですね。ここはMakefileを編集しましょう。viなりnanoなりで開いて、次のように編集して保存します。
# SWIG := swig <-- コメントアウトして SWIG := swig3.0 <-- この行を追加
再びmakeすると成功するはずです。直下に_snowboydetect.so
というファイルができていれば成功です。
ではホットワード検出のデモを試してみます。
(yoshiko)$ cd ~/snowboy/examples/Python3/ (yoshiko)$ python demo.py resources/models/snowboy.umdl
2018-05-01時点ではここでエラーが出ました。pythonで相対importしている点に問題があります。一時的にimport文を直接編集して問題を回避することにします。snowboydecorder.py
を編集して次のようにします。(テストが終わったら元に戻しておきましょう)
5| #from . import snowboydetect <-- コメントアウトして 6| import snowboydetect <-- この行を追加
では再びデモを実行しましょう。
📄switchbot_bluepy.py
(yoshiko)$ python demo.py resources/models/snowboy.umdl Listening... Press Ctrl+C to exit connect(2) call to /tmp/jack-1000/default/jack_0 failed (err=No such file or directory) attempt to connect to server failed
ホットワードの待ち受けに入ります。なお先程のdemo.pyで引数として渡したsnowboy.umdlが音声モデルであり、この音声モデルは「Snowboy」というホットワードに対するモデルです。というわけで、マイクに向かって「Snowboy」と話してみましょう。INFO:snowboy:Keyword 1 detected at time: ...
とターミナルに表示され「ピコーン」というサウンドが聞こえてくれば成功です。
それでは先程の「Snowboy」という音声モデルを独自のものに差し替えます。Snowboyにログインすれば音声モデルの作成ができるようになります。録音もブラウザ上でできますので、お好みの音声モデルが登録されていなければ自分で作成してみてください。なお、私は「よしこちゃん」という音声モデルを作成し使用しています。よしこちゃんで問題ないなら、この音声モデルをそのまま使用しても構いません。
https://snowboy.kitt.ai/hotword/20463
ダウンロードした音声モデルファイルを次の位置に配置します。ここでは音声モデルファイルをyoshiko-chan.pmdl
とします。
(yoshiko)$ mv yoshiko-chan.pmdl snowboy/resources/models/
でも先程のデモを独自の音声モデルで実行してみましょう。
(yoshiko)$ python demo.py resources/models/yoshiko-chan.pmdl
今度は自分で選んだホットワードで話しかけて下さい。私の場合はもちろん「よしこちゃん」。前回同様に「ピコーン」というサウンドが再生されれば成功です!Snowboyの導入はこれにて完了です。なお、サンプリング数が少ない音声モデルは検出精度がそんなに高くないのかも?私の「よしこちゃん」は、ラズパイのかなり近くで大きな声で話し掛けないと検出してくれませんでした。もっとみんなによしこちゃんを育ててもらわねばならないのですね。まあ今回はとりあえずこれで良しとします。
SwitchBotをあやつる
SwitchBotをpythonからあやつるにあたって、とりあえずオン・オフがpythonから切り替えることができるかを試してみましょう。switchbotがpython-hostというpythonからSwitchBotを操作するスクリプトを提供してくれています。
OpenWonderLabs/python-host [github]
ただこのスクリプト、root権限が必要だったり、環境によっては動かなかったりと、あまりイケてません。gistにもっと簡単な例が上がっているので、こいつをベースにスクリプトを書きます。
先に必要なライブラリをインストールしておきます。
(yoshiko)$ pip install bluepy
ではテスト用のスクリプトを作成します。
(yoshiko)$ cd ~ (yoshiko)$ mkdir switchbot (yoshiko)$ vi switchbot_bluepy.py
# Activate switchbot by python with bluepy on Raspberry Pi # https://github.com/IanHarvey/bluepy # # Switchbot's API is taken from this # https://github.com/OpenWonderLabs/python-host import binascii from bluepy.btle import Peripheral # find your switchbot address by # pi@raspberry:~ $ sudo hcitool lescan # replace "ff:..:ff" p = Peripheral("ff:ff:ff:ff:ff:ff", "random") hand_service = p.getServiceByUUID("cba20d00-224d-11e6-9fb8-0002a5d5c51b") hand = hand_service.getCharacteristics("cba20002-224d-11e6-9fb8-0002a5d5c51b")[0] hand.write(binascii.a2b_hex("570100")) # press #hand.write(binascii.a2b_hex("570101")) # on #hand.write(binascii.a2b_hex("570102")) # off p.disconnect()
このまま実行しても動きません。SwitchBotのMACアドレスを指定する必要があるのです。コメントに記載してある通り、次のコマンドでSwitchBotのMACアドレスを確認します。
📄switchbot/switchbot_handler.py
(yoshiko)$ sudo hcitool lescan LE Scan ... FX:XX:XX:XX:XX:01 (unknown) FX:XX:XX:XX:XX:01 (unknown) ...
ここの表示されるMACアドレスでSwitchBotのものをコピーして、先程のソースのff:ff:ff…の箇所に貼り付けます。そして実行すると・・・
(yoshiko)$ python switchbot_bluepy.py
SwitchBotのPressが走るはず!壁スイッチモード(オン・オフ切り替え式)にしている場合は、先程のソースの以下の箇所のコメントを調整して試してみて下さい。
hand.write(binascii.a2b_hex("570100")) # press #hand.write(binascii.a2b_hex("570101")) # on #hand.write(binascii.a2b_hex("570102")) # off
一旦疎通が確認できればここはOKです。
ようやくお目当てのものを開発
下準備が完了したので、ようやくお目当てのものを開発していきます。仕様としては、
- ホットワード「よしこちゃん」(あるいは独自のもの)を待ち受ける
- 検出したらVoice Kit上面ボタンを点灯して、音声入力待ちに
- 「充電つけて」「充電消して」音声コマンドでSwitchBotをオン・オフに
- 何か違うことを言ったら、最初のホットワード待ち受けモードに戻る
というシンプルなものにします。
まずはディレクトリの作成や、必要なライブラリのコピーやリンクを貼っておきます。/home/pi/yoshikoをプロジェクトディレクトリにしましょう。
(yoshiko)$ cd (yoshiko)$ mkdir yoshiko (yoshiko)$ cd yoshiko # snowboyパッケージを作成する (yoshiko)$ mkdir snowboy (yoshiko)$ touch snowboy/__init.py__ (yoshiko)$ ln -s ~/snowboy/resources snowboy/resources (yoshiko)$ cp ~/snowboy/examples/Python3/snowboydecoder.py snowboy/ (yoshiko)$ ln -s ~/snowboy/swig/Python3/snowboydetect.py snowboy/snowboydetect.py (yoshiko)$ ln -s ~/snowboy/swig/Python3/_snowboydetect.so snowboy/_snowboydetect.so
次に、switchbotパッケージを作成します。
(yoshiko)$ mkdir switchbot (yoshiko)$ touch swithbot/__init.py__ (yoshiko)$ vi switchbot/switchbot_handler.py
import sys import binascii from bluepy.btle import Peripheral def send_command(cmd): p = Peripheral("★SwitchBotのMACアドレス★", "random") hand_service = p.getServiceByUUID("cba20d00-224d-11e6-9fb8-0002a5d5c51b") hand = hand_service.getCharacteristics("cba20002-224d-11e6-9fb8-0002a5d5c51b")[0] if cmd == 'press': hand.write(binascii.a2b_hex("570100")) elif cmd == 'on': hand.write(binascii.a2b_hex("570101")) elif cmd == 'off': hand.write(binascii.a2b_hex("570102")) else: print('Unsupported command!') p.disconnect() if __name__ == '__main__': send_command(sys.argv[1])
(SwitchBotのMACアドレスを置換するのを忘れないようにしてください)
これは単独で動作を確認しておきましょう。
📄main.py
(yoshiko)$ python switchbot/switchbot_handler.py on
コマンドライン引数で press|on|off を渡すようにしました。ちゃんと動きますでしょうか?
aiyライブラリも使用できるようにシンボリックリンクを貼っておきます。
(yoshiko)$ ln -s ~/AIY-voice-kit-python/src/aiy aiy
最後にメインのスクリプトファイルを書きます。
#!/usr/bin/env python import re from snowboy import snowboydecoder import aiy.voicehat import aiy.cloudspeech import aiy.i18n from switchbot import switchbot_handler aiy.i18n.set_language_code('ja-JP') status_ui = aiy.voicehat.get_status_ui() status_ui.status('ready') aiy.audio.get_recorder().start() def detected_callback(): status_ui.status('listening') print('よしこちゃんが聞き入っています...') text = recognizer.recognize() status_ui.status('ready') if text is None: return print('text=' + text) if re.search(r'充電.*つけて', text): print('オッケー、付けるね') switchbot_handler.send_command('on') elif re.search(r'充電.*消して', text): print('オッケー、消すね') switchbot_handler.send_command('off') recognizer = aiy.cloudspeech.get_recognizer() recognizer.expect_phrase('充電つけて') recognizer.expect_phrase('充電消して') # 音声モデルファイルへのパスを適宜変更してください detector = snowboydecoder.HotwordDetector('snowboy/resources/models/yoshiko-chan.pmdl', sensitivity=0.5, audio_gain=10) print('「よしこちゃん」と話しかけて下さい') detector.start(detected_callback=detected_callback)
さあ、スクリプトに実行権限を付与して実行しましょう!
(yoshiko)$ chmod +x main.py (yoshiko)$ ./main.py
「よしこちゃん」と話し掛けて「充電つけて」「充電けして」コマンドを試すと・・・
よ、よしこ!健気なよしこ!ありがとう、よしこちゃん!
なお、ホットワード検出の精度が微妙な人はdetector = snowboydecoder.HotwordDetector('snowboy/resources/models/yoshiko-chan.pmdl', sensitivity=0.5, audio_gain=10)
のsensitivityとaudio_gainを調整してみてください。ささやかなヘルプはsnowboyのgithubに書いてあります。
一応、ソース置いておきます。(と言っても、シンボリックリンクばっかりなんですけど。。)
あとがき
とりあえず動くところまでということで、かなりpoorな実装なのでそこはあしからず。しかし、思ったほどsnowboyのホットワード検出の感度がよろしくない・・・。これなら、素直にJulius使った方がいいのかもしれない。
また、aiy.audio.say
でよしこちゃんに日本語を話してもらおうと思ったのですが、内部で使用しているpico2waveが日本語に対応していないので無理みたいでした・・・。ここも素直にOpenJtalkに置き換えた方が手っ取り早そう・・・。
結局のところ、Google Assistantの機能とVoice Kitのボタン操作にaiyライブラリ使用するぐらいでいいのかな。次はRaspberry pi + Voice Kit + Julius(書き起こし)+ OpenJTalk(発話)という構成でよしこちゃんをいぢることにします。
関連する記事
- ラズベリーパイに日本語で喋らせる[その2]Amazon Polly
- Google AIY Voice Kit V2の基本操作サンプル集
- ラズベリーパイに日本語で喋らせる[その1]Google Cloud Text-To-Speech
- Google AIY Voice Kit V2をキーボード・モニター無しでセットアップして日本語会話まで
- ラズベリーパイに日本語で喋らせる[その3]Open JTalk