イラスト:(@fujiirohasu)
はじめに
はじめまして、京都大学バーチャル YouTuber 同好会のふじ333です。普段はのんびりとお絵描きをしています。
今回は、Raspberry Pi を用いて名取さなさんといくつかのやり取りをできるアラーム機能付きスマートスピーカーと、それに連動して毎朝予定と天気をテキストチャンネルに投稿する Slackbot を作成しました。使用させて頂いた音声については最後のリンク集に記載しています。
- はじめに
- 機能紹介
- 今後の展望
- 開発記録
- おわりに
- リンク集
機能紹介
動画
スマートスピーカー
時報
22:00と23:00に時報が鳴ります。
アラーム
設定された時刻にボイスが再生されます。「おはよう」と話しかけると鳴り止みます。もし、設定時刻に起きていなければ、5分後にスヌーズが再生されます。
あいさつ
「おはよう」「おやすみ」「行ってきます」「ただいま」に返事をします。なお、11:00以降に「おはよう」と言うと「こんにちは」と言われます。
Slackbot
予定・天気の貼り付け
「おはよう」と話しかけたタイミングで、Slack に一日の予定と天気をまとめたメッセージが送信されます。
アラームの設定
"/set-huji333's-alarm (時刻)" というコマンドをslackで実行すると、入力した時刻にアラームの設定時刻が変更されます。
睡眠・外出のログ
上記のあいさつに応じて、Slack にログが流れます。
今後の展望
IoT化
外出に応じてエアコンを ON/OFF できるようにしたいと思っています。
会話パターンを増やす
余力があれば、音声認識の辞書の単語数を増やし、より多くのやりとりができるようにしたいと思っています。時間と発想力の勝負になってきます。
開発記録
この開発に関連することがらをほぼすべて書き連ねているので、とても長くなっています。適宜読み飛ばしてあげてください。少しでも参考になるところがあれば嬉しいです。
稚拙なコードですが、一応 GitHub に今回のアプリケーションを投稿しています。
きっかけ
7月の頭に頼んだ覚えのない謎の荷物が自分の家に届きました。中身を開けてみると、なんと Raspberry Pi 4 が入っていました。大学の同級生からの誕生日プレゼントだったのです。ラズパイを頂いたからには何かを作らないといけないなと筆者は思いました。
7月某日、4限が空きコマだったので京大から四条まで自転車でごはんを食べに行きました。ところが、道中で豪雨が降ってきました。傘もかっぱも持っていなかった筆者は雨宿りする前に全身がずぶ濡れになってしまいました。ある程度乾くまで商店街を徘徊した後、とんかつを美味しく頂きました(ふじ333 on Twitter: "#京都大学ぼっち飯の会")。
このときに、天気予報を見ていればよかったと後悔しました(折りたたみ傘を持とうよ自分)。しかし、毎朝とても眠いときにわざわざ天気を確認するのはとても煩わしく、自分には到底できることではありません。
そこで毎朝確認するのが面倒な天気や時間割などを、Slack にまとめて表示すればいいのではないかという発想に至り、Slackbot を夏休みに作ろうとを決意しました。
そして夏休みになり、Raspberry Pi にマイクとスピーカーを繋げば実質スマートスピーカーではないかという天啓が下り、音声を通して Bot を操作をするということもをしようと思いました。
スマートスピーカーなら会話ができるじゃんとワクワクしながら、具体的に案を練りました。ボイスは名取さなさんのを使うことにしました。名取さな関連の開発は先駆者様がたくさんいらっしゃり気軽にでき、さなボタン(2)のおかげで素材もとても探しやすいなどといった理由もあったりします。
ソフトの大まかな構造
一応画像で一通りそれぞれのファイルの役割を書きました。
環境
- 開発: WSL Ubuntu 24.04.3LTS
- 言語: Python 3.8.10
- 使用ソフト:Julius
- 動作: Raspberry Pi 4 Model B, Raspberry Pi OS 32bit
WSL の方がシンプルで開発しやすいような気がした(知らんけど)ので、とりあえず Ubuntu を導入しました。言語は、とりあえず動けばいいと思ったのでライブラリが充実している Python にしました。
ラズパイはとりあえず一番普通な感じの OS にしました。開発はほぼメイン PC でして、ラズパイは本当にテスト&本番環境という感じになっています。
開発環境構築
WSLのインストール
上に書いてある通りに WSL をインストールすれば大丈夫なはずです。
筆者はなぜかパソコンに Ubuntu が入っていましたが、WSL のバージョンが1のままだったので、上のサイトを読んで一応アップデートしておきました。
VSCodeの導入
Visual Studio Code - Code Editing. Redefined
上記リンクから VSCode をダウンロードし、インストールします。
拡張機能は、Remote WSL と Vim を使用しています。設定は、オートセーブを有効にして、フォントを Ricty Diminished にしています。もっと便利な設定や拡張機能はたくさんあると思います。
Vimはラズパイの方でちょっとコードを改変するときに使うことになるので、VSCode 拡張機能である程度操作に慣れました。(筆者はたまに矢印キーに指が伸びてしまい Vimmer に殺されてしまいそうです)
Gitのセットアップ
なんかよくわからんけどコード書いてる人がみんな使ってる Git を入れてみます。筆者は Git 未経験だったので、大学同期を30分ほど捕まえて教えてもらいました。
ネット上にたくさん解説記事が転がっているので詳しくは割愛します。今回はあくまで個人の小規模開発なので、ブランチとかイシューとかのうんぬんかんぬんは全く学習しておりません。
GitHubでssh接続する手順~公開鍵・秘密鍵の生成から~ - Qiitaに書いてあるように ssh接続をして、WSL のターミナルからコミットすることができるようにしました。
また、GitHub をコマンドラインで認証する際に、トークンが必要になるようになったため、個人アクセストークンを使用する - GitHub Docsを見て設定しました。
環境変数の設定
GitHubにAPIのトークンやパスワードなどの機密情報をソースコードを載せるのは避けたいです。そのために環境変数としてパスワードなどを保管します。下記のコマンドで環境変数を設定することができます。
export (変数名) (値)
PATHを通すときと同様にこのコマンドを /.profile や /.bashrc に記入して、起動するたびに自動で実行されるようにすると便利です。
Slackbotの開発
Google Calendar API から予定の取得
上記リンクの通りにすれば、9割完了したようなものです。とはいえ、初めてだったので割と時間がかかりました。
とても雑にやったことを言うと、Google Cloud Platform でプロジェクトを作成して API を追加します。そして API の Oauth 同意画面を作成し、認証情報から .json をダウンロードします。ダウンロードした .json ファイルを "credentials.json" に名前変更します。そしてこのファイルと同じディレクトリにサンプルコードをコピペしたものを作り、実行します。
実際に自分でやったことは、サンプルコードをちょっと変更して1日の予定を取得するとうにしただけです。
もとのコードでは直近10件のイベントを出力していますが、イベント取得の条件を変更して現在時刻から18時間後までの間の予定を取得するようにします。
そして取得した予定の開始時刻とタイトルをひとつの文字列にまとめます。予定の開始時刻は年月日も含まれるので、部分文字列を取るようにします。ほかにも改行コードや、Slackの書式を変える記号などを適宜入力して見た目を整えます。
気象庁公式サイトから天気の取得
天気APIはたくさんありますが、今回は気象庁公式サイトのAPIを使います。京都の場合、https://www.jma.go.jp/bosai/forecast/data/forecast/260000.json から.jsonを取得できます。この .json ファイルから天気や降水確率といった要素を取り出して文字列にすれば天気の取得は完了です。
しかし、.json ファイルから取得とはいっても、入れ子構造がとても複雑で人間に読めるものではありません。そこで、JSON Viewer - Chrome ウェブストアという拡張機能を使いました。
PandAから課題の取得
京大の学習支援システムをハックするを参考に PandA のベースとなっている Sakai の、Keitai API (PDF) を使用しました。
ログインは京大独自のものだったので、API に書いてあるやり方ではログインできませんでした。なので、実際に PandA のログインページのソースを表示して認証方法を確認しました。
すると、ユーザー名とパスワードの他に "lt" ,"execution" ,"_eventid" をPOSTしなければいけないとわかりました。それらの値は Beautiful Soup 4 などでスクレイピングすれば得る事ができます。
そしてログインできたら、https://panda.ecs.kyoto-u.ac.jp/direct/assignment/my.json から課題のリストを取得します。ログイン時と同じセッションを保たないと、空のデータが送られてきてしまうので、PythonでHTTPリクエストしてJSONで受け取る方法 | mktia's note を参考に requests の get メソッドで .json を取得しました。
天気の .json 取得の部分を urllib 使わずに requests を使えば、もう少し短く書くことができ、一時ファイルもなくなるとわかりました。しかし、筆者はすでに動いているしいいやというゆるい思想を持っているので何も変えませんでした。
Slackbotの作成
Slack API | Slackリンク先の右上のところから、新たなアプリを作成します。今回使用する機能は、Socket Mode と Slash Commands です。権限はチャンネルにメッセージを書き込むのだけで十分です。
アイコンや名前もここで設定しておきます。
そしてこのページから見ることができる、Bot OAuth トークン (xoxb-……) と App トークン (xapp-……) を環境変数に追加しておきます。
Slackbotからメッセージを送信
上記リンクのサンプルコードをほぼそのまま使い、chat.postMessage でさきほどまとめた天気と予定と課題を送信します。他にも、「おやすみ」「行ってきます」「ただいま」と言ったときのログも送信するようにしておきます。
python のモジュール機能を用い、さきほど作成した予定を取得するプログラムなどを import します。そして、追加したモジュールの関数の戻り値を足せば、メッセージが完成します。
メッセージを送信するコードもいずれはモジュールとして使用することになるので、朝のメッセージを送る部分は適当に名付けて関数にしておきます。
Slackのコマンドでアラーム設定
Socket Mode を用いて Slack Bolt でスラッシュコマンドの実装をしました。Socket Mode が有効でないと、色々とごちゃごちゃしないといけなくなります。筆者はここで詰まりました。
上のチュートリアルの通りにコマンドを実装し、コマンドで取得したアラームの時刻を .json で保存します。また、スヌーズとして設定時刻の5分後の時刻も保存しておきます。そして、「hh:mm にアラームを設定しました」というメッセージを返すようにしておきます。
動作環境構築
Raspberry Pi のセットアップ
カバーやファンなどを取り付けて組み立てます。OS は Raspberry Pi Imager を用いてインストールし、microSD カードに焼きます。ラズパイにマウスとキーボードとディスプレイと USB マイクとスピーカーと、お好みで LAN ケーブルを接続して起動します。
ターミナルで、Ubuntu でやったことと同様に Github の ssh接続や環境変数の設定をします。また、Python のライブラリもインストールしておきます。
ラズパイでは日本語入力ができないので、必要があれば日本語入力できるようにしておいても良いと思います(筆者は面倒でやっていません)。
サウンドデバイスの設定
raspberryPiとjulius(音声認識)を使用する。①マイク編 - Qiita に書いてある通りのことを行って、USB マイクから音声の入力ができるようにします。また、デフォルトの出力デバイスはスピーカーでない場合があるので、右上の音量調節のところから出力デバイスをイヤホンジャックの方に切り替えておきます。
Juliusの設定
インストール
git clone https://github.com/julius-speech/julius
git clone https://github.com/julius-speech/dictation-kit
git clone https://github.com/julius-speech/grammar-kit
まずは Julius のレポジトリをクローンします。zip ファイルを取得して解凍するといった方法でも大丈夫です。
cd julius
./configure --with-mictype=alsa
make
sudo make install
そしてさきほどクローンした Julius 本体のディレクトリに移動して、インストールをします。
独自辞書の作成
Julius にもともと入ってる辞書は、今回使わない語彙がたくさん登録されていて、このままでは認識の精度はあまり良いものと言えません。なので、独自で辞書を作成し、使用することにします。
Juliusの独自辞書を使って音声を認識させる - Qiita を参考に辞書を作成します。「こんにちは」の"は"など、ひらがなの表記と発音が違う部分には注意しておきます。
エラーと戦う
julius -C (dictation-kit のあるところ)/dictation-kit/am-gmm.jconf -nostrip -gram /natori-bot/dict/test -input mic
ネットの記事に書いている上のようなコマンドを実行すると、「 .jconf ファイルを読み込めませんでした」的なエラーメッセージが出ました。そこで僕は、他の .jconf ファイルを読みこむようにして解決しました。
そしたら今度は、「音声を入力できませんでした」的なエラーメッセージが出ました。下のコマンドを入力してから julius を実行するとちゃんとできました。
sudo modprobe snd-pcm-oss
次に「入力が長すぎます」的なことを言われました。これは、無音状態でも喋り続けている判定がされていることが原因になっています。それに対しては "-lv 10000" というオプションを使い入力のしきい値を上げました。
結局実行するコマンドは、
julius -C (grammar-kitのある場所)/grammar-kit/hmm_ptf.jconf -nostrip -gram /natori-bot/dict/test -input mic -lv 10000 -module
になりました。"-module" を消すと普通に実行され、ちゃんと認識できているかをテストすることができます。
Julius 音声認識ソフト part1 フリーソフトだがセッティングでエラーの嵐!│60爺の手習いでその他エラーやその対処法などが書いてあります。
スマートスピーカーの開発
ボイスの取得
さなボタン(2)で Ctrl+F してセリフを探しました。音声をダウンロードをしたら、 Audacity で切り取り、コンプレッサーを適用して、ファイルごとの音の大きさのばらつきを軽減しました。
音声認識の結果から動作の実装
Julius の認識結果をソケット通信で受け取り、認識した単語に応じて動作をするプログラムを書きます。さきほど作成した slack にメッセージを送るプログラムなどをモジュールとして使用します。
Raspberry Pi×JuliusとPythonでスマートスピーカー風にカメラを操作 | パソコン工房 NEXMAGやjuliusをmoduleモードで起動して、pythonで話した言葉を取得する - Raspberry Pi & Python 開発ブログ ☆彡などを参考にしました。
アラームの実装
指定した時刻になっているかを一分ごとに確認し、その時刻になっていたら、音が鳴るようにします。
起きているときはアラームが鳴らないように、朝の天気や予定などを貼った長い文が連投されないように、また寝ているときに夜の時報が鳴らないようにしておきたいです。そのために筆者が起きているかどうかの bool 変数を作り、「おはよう」「おやすみ」で値を切り替えるようにしておきます。
しかし、すでに音声認識の結果を受け取る部分で while ループを回しており、このままではアラームの部分を動かすことができません。アラームを別のコードにすると、筆者の起床状態に連動した処理をするのがとても難しくなってしまいます。
そこで、並列処理をすることにしました。Pythonの並行処理を理解したい [マルチスレッド編]を参考にし、ThreadPoolExecutor を使うことにしました。これで、音声入力に応じて止めることができるスヌーズ機能付きアラームができました。
コンセプトヌォンタートを描く
Slackbot のアイコンや、この記事のサムネなどに使う画像が欲しかったので、自分で描くことにしました。神絵師になりたい。
動画を編集する
一応ちゃんと動いてるという証拠的なものを作っておいたほうがいいかなと思い、機能紹介動画を作ることにしました。スマホで直撮りして Aviutl で動画を編集しました。BGM はよく聞くものをなんとなく使っています。
ブログを書く
基本的にはてなブログの見たまま編集機能で十分なのですが、linux コマンドを書くのと、リンクの仕様を変更するために html を編集しました。
おわりに
いかがでしたしょうか。とても長い記事になりましたが、最後まで読んでいただきありがとうございます。
筆者はプログラミングとか開発の経験が全然ありませんでした。しかし、明確な作りたいもののイメージができていると、頑張って色々調べれば自分で作ることができるのだとわかりました。やはり、自分にとってプログラミングは手段であるのかもしれないなあと思いました。
リンク集
ボイス引用元
さなボタン(2)様からボイスを探しました。
【Minecraft】沼地の魔女こと名取さなですけども。 - YouTube
【Papers, Please】さなちゃんねる王国は誰でもウェルカム - YouTube
【ゲリラ雑談】なんでもないはなしをしよう - YouTube
【雑談】2020年ももう終わりかあ…1年お疲れ様でした! - YouTube
【FallGuys】競争社会に負けない名取さなちゃん奮闘記 - YouTube
関連リンク集
記事内では出てこなかった、この記事に関連する内容のサイトのリンクを、書いてある順番で貼っています。
VSCodeのRemote WSLでWSLを快適に使う - Qiita
vimを使うとき十字キーで移動して、vimmerに殺されるその前に - Qiita
君には1時間でGitについて知ってもらう(with VSCode) - Qiita
ゼロからはじめるPython(75) Twitterで話題になった気象庁の天気予報APIをPythonで使ってみよう | TECH+
urllib.request — Extensible library for opening URLs — Python 3.9.7 documentation
Beautiful Soup Documentation — Beautiful Soup 4.9.0 documentation
GitHub - julius-speech/julius: Open-Source Large Vocabulary Continuous Speech Recognition Engine
Raspberry Pi+Juliusで音声を認識する - Qiita
pygame.mixer.music — pygame v2.0.1.dev1 documentation
GitHub - kmc-jp/css-study-seminar: 「CSS完全に理解した」で学ぶCSS
Github
(記事作成 ふじ333)