【ラズパイ】Voice KitにSnowboyを入れてホットワードでSwithBotを操る【よしこちゃん】


GoogleのAIY ProjectVoice Kitを使用すれば、自分でGoogle Homeのようなものを作ることができます。今回はRaspberry Pi 3 Model BにVoice Kitを入れて、音声でSwitchBotをオン・オフする音声コマンドデバイスを開発してみたいと思います。

手作り感あふれる完成品の動画

序章

私はMacbook Proを使用しているのですが、毎回充電が100%になる度に充電器のスイッチをオフにして、空になったらまたオンにするのが面倒でした。デスクの奥に電源タップがあって、わざわざ席を立ってポチっとしに行かなきゃ届かない位置だったのです。これがすごく面倒臭くて。なんだ、しょうもない話だなとお思いかもしれませんが、私はそれ以上にしょうもない人間なんです。

なんとか楽な方法はないかなーと苦しみに苦しみぬいた上、カレント・ビッグウェーブに乗ってここはひとつボイスで操ることにしました。買ってからしばらく、ヌルヌルのまま放置プレイしていたVoice Kitがありましたからね。あと、何かに使えるんじゃないかと思って買っていたSwitchBot。Bluetooth経由で操作できるアナログなスマートスイッチです。

Voice Kit

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のページからイメージがダウンロードできます。

Voice Kit V1

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のような音声認識デバイスを起動するトリガーとなるワードのことです。よしこちゃんと私の架け橋です。

snowboy.kitt.ai

実はGoogle Assistant SDKだけでも音声待ち受けループ→ディクテーション(音声をテキストに書き起こすこと)という流れでホットワード待ち受けを作成することはできるのですが、待ち受け中もAPI使用の課金対象となるので、無料で遊ぶには敷居が高いです。Snowboyは(個人用途では)無料で利用することができ、しかもホットワードをWeb上で録音・学習させ、モデルデータとしてダウンロードして使用することができます。今回はこのホットワードにSnowboyを使用します。

ライブラリはPythonから使用できます。インストール手順がgithubに書いてありますが、そのままでは動かなかったので、手順を順に説明します。

Kitt-AI/snowboy [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に書いてあります。

一応、ソース置いておきます。(と言っても、シンボリックリンクばっかりなんですけど。。)

itmammoth/yoshiko-01 [github]

あとがき

とりあえず動くところまでということで、かなりpoorな実装なのでそこはあしからず。しかし、思ったほどsnowboyのホットワード検出の感度がよろしくない・・・。これなら、素直にJulius使った方がいいのかもしれない。

また、aiy.audio.sayでよしこちゃんに日本語を話してもらおうと思ったのですが、内部で使用しているpico2waveが日本語に対応していないので無理みたいでした・・・。ここも素直にOpenJtalkに置き換えた方が手っ取り早そう・・・。

結局のところ、Google Assistantの機能とVoice Kitのボタン操作にaiyライブラリ使用するぐらいでいいのかな。次はRaspberry pi + Voice Kit + Julius(書き起こし)+ OpenJTalk(発話)という構成でよしこちゃんをいぢることにします。



関連する記事


コメントする

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

CAPTCHA


このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください