Raspberry Pi 用「HAL」で、カップラーメン・タイマーを作ってみよう!~ ラズベリーパイ研究室

このコーナーでは、新しくなった Raspberry Pi 用サーバー・アプリケーション・フレームワーク「HAL」を使って、カップラーメン・タイマーを作ってみようと思います。

洗濯物を畳んでいたり YouTube で動画を見ていたら時間を大幅に過ぎてしまい、カップラーメンが伸びてしまった、といった悲しい出来事から解放されるかもしれません。

おススメ!記事
Raspberry Pi 用「HAL」で、
カップラーメン・タイマーを作ってみよう!
ラズパイDIYの決定版! ソケットサーバー「HAL」をご紹介します。

Raspberry Pi 用「HAL」で、カップラーメン・タイマーを作ってみよう!

このコーナーでは、新しくなった Raspberry Pi 用サーバー・アプリケーション・フレームワーク「HAL」を使って、カップラーメン・タイマーを作ってみようと思います。

洗濯物を畳んでいたり YouTube で動画を見ていたら時間を大幅に過ぎてしまい、カップラーメンが伸びてしまった、といった悲しい出来事から解放されるかもしれません。

参考)ソケットサーバー「HAL」の概要

参考)Raspberry Pi「HAL」を楽しむためのインストール・レジュメ

このコーナーの最終目標

以下の様な機能を実装します。

※途中で音楽を再生していますが、間が持たなくなっただけなので、曲を再生する必要はありません。また、白とグレーの円筒はただのスピーカーです。

Julius の認識文法ファイルの作成(version 1.4.1以降)

2016/08/30 追記

HAL version 1.4.1 からは、認識文法ファイルの作成がとても簡単になりました。まず、/HAL/userdata/julius/ ディレクトリ内に、次のような認識文法定義 YAML ファイルを配置します。

例)instruct.yml

## instruct YAML.
---
rules:
- "HAL SUBJECT DO"
alias:
words:
HAL:
-
word: "ハル"
kana: "ハル"
SUBJECT:
-
word: "ラーメン"
kana: "ラーメン"
-
word: "どん兵衛"
kana: "ドンベエ"
DO:
-
word: "1分"
kana: "イップン"
-
word: "2分"
kana: "ニフン"
-
word: "3分"
kana: "サンプン"
-
word: "4分"
kana: "ヨンプン"
-
word: "5分"
kana: "ゴフン"
-
word: "6分"
kana: "ロップン"
-
word: "7分"
kana: "ナナフン"
-
word: "8分"
kana: "ハップン"
-
word: "9分"
kana: "キュウフン"
-
word: "10分"
kana: "ジュップン"
-
word: "キャンセル"
kana: "キャンセル"

あとは -j オプションを使って bakepi コマンドを使うだけでコンパイルされ、自動的に認識文法ファイルが同ディレクトリ内に作成されます。

sudo bakepi -j instruct

また、/HAL/userdata/julius/ 内の全ての YAML ファイルをコンパイルする場合は all を指定します。

sudo bakepi -j all

なお、bakepi コマンドをまだ作成していない場合は 「DBアクセスライブラリ「DataMapper Lite」」のbakepi コマンドの作成 を参照して下さい。

Julius の認識文法ファイルの作成(手動で認識文法ファイルを作成する場合)

bakepi コマンドでなく、手動で認識文法ファイルを作成する場合は以下のように行って下さい。

Julius はとても簡単に音声認識処理ができます。ただし、Raspberry Pi は省電力設計で CPU の演算速度が遅いため、あらゆる言葉に対応させると、音声認識に随分時間がかかってしまいます。

そこで、通常の利用では使える語句と文法を限定した認識文法ファイルを作成し、それを利用する事で、音声認識にかかる時間を短縮するのが便利です。

/HAL/userdata/julius/ 内に、既にいくつかの音素列ファイル (.voca) と構文制約ファイル (.grammar) が収録されています(version 1.4 以前。1.4.1以降には含まれません)。

そのうちの、instruct.voca と instruct.grammar ファイルを見てみましょう。

instruct.voca
% HAL
ハル、 h a r u
% SUBJECT
ラーメン r a: m e n
% DO
1分 i p p u n
2分 n i h u n
3分 s a n p u n
4分 y o n p u n
5分 g o h u n
6分 r o p p u n
7分 n a n a h u n
8分 h a p p u n
9分 ky u: h u n
10分 j u p p u n
キャンセル ky a n s e r u
% NS_B
<s> silB
% NS_E
<s> silE

% で始まる行はカテゴリ定義で、このカテゴリを使って、次に紹介する .grammar ファイルで文法を定義します。

上記のうち、NS_B は認識前の無音、NS_E は認識後の無音を指します。この2つは全ての .voca ファイルで共通の定義を行っておくと分かりやすいでしょう。

instruct.grammar
S      : NS_B HAL SUBJECT DO NS_E

こちらが構文制約の定義です。認識前の無音→HALカテゴリ→SUBJECTカテゴリ→DOカテゴリ→認識後の無音の順番での構文制約を S で始まる行で定義しています。

なお、これらファイルの詳しい説明は、以下を参照して下さい。

参考)第7章 言語モデル|julius.rsdn.jp

認識文法 のコンパイル

※bakepi コマンドを使った場合はコンパイルも自動で行われます。

.voca ファイルと .grammar ファイルを作成したら、これを Julius に同梱されている mkdfa.pl を使ってコンパイルします。

Julius の導入にあたっては、Julius の導入 に簡潔に纏めてありますので、これを参照して下さい。こちらの通りにインストールを行うと、pi ユーザーのホームディレクトリに Julius のディレクトリがあり、以下にコンパイルを行うための mkdfa.pl が配置されています。

/home/pi/julius-4.3.1/gramtools/mkdfa/mkdfa.pl

(相対パスでは ~/julius-4.3.1/gramtools/mkdfa/mkdfa.pl)

この mkdfa.pl を使って、先ほどの instruct.voca と instruct.grammar をコンパイルするには、

sudo ~/julius-4.3.1/gramtools/mkdfa/mkdfa.pl /var/www/HAL/userdata/julius/instruct

を実行します。(このコマンドは頻繁に使うことになるのでメモ帳などで保存しておきましょう。最後の instruct の部分を書き換えるだけで、別の構文制約をコンパイルできます)

コンパイルが正常終了すると、

pi@raspberrypi:~ $ sudo ~/julius-4.3.1/gramtools/mkdfa/mkdfa.pl /var/www/HAL/userdata/julius/instruct
/var/www/HAL/userdata/julius/instruct.grammar has 1 rules
/var/www/HAL/userdata/julius/instruct.voca has 5 categories and 15 words
---
Now parsing grammar file
Now modifying grammar to minimize states[-1]
Now parsing vocabulary file
Now making nondeterministic finite automaton[6/6]
Now making deterministic finite automaton[6/6]
Now making triplet list[6/6]
5 categories, 6 nodes, 5 arcs
-> minimized: 6 nodes, 5 arcs
---
generated: /var/www/HAL/userdata/julius/instruct.dfa /var/www/HAL/userdata/julius/instruct.term /var/www/HAL/userdata/julius/instruct.dict
pi@raspberrypi:~ $

のようにメッセージが表示され、プロンプトが戻ります。/var/www/HAL/userdata/julius/ 内に、以下 3ファイルが作成されているはずです。

/var/www/HAL/userdata/julius/instruct.dfa
/var/www/HAL/userdata/julius/instruct.term
/var/www/HAL/userdata/julius/instruct.dict

もし、最後にエラーが報告された場合は、.voca ファイルと .grammar ファイルを見なおしてみましょう。特に .voca ファイルの読みにあたるローマ字が間違っている場合が多いかと思います。(ヘボン式で指定するようです)

参考)ヘボン式ローマ字

これで、構文制約ファイルの準備は整いました。

juliusActions.yml での action 定義

HAL version 1.4 からは、action ファイルの抽象化が行われました。Julius からの音声認識結果に対する処理の定義は juliusActions.yml で行います。/HAL/actions/juliusActions.yml を見てみましょう。

/HAL/actions/juliusActions.yml
## Julius Action YAML.
---
(前略)

-
type: "\\Aハル、ラーメン(\\d)分\\z"
do: "Noodle::measure"
-
type: "\\Aハル、ラーメンキャンセル\\z"
do: "Noodle::cancel"

(後略)

これは、YAML(ヤムル)というデータ記述形式です。特徴は、インデントでデータ構造を定義することです。- で始めているブロックは配列の表現で、上記の場合、PHP で表すと、

array(
array(
"type" => "\\Aハル、ラーメン(\\d)分\\z", "do" => "Noodle::measure"
),
array(
"type" => "\\Aハル、ラーメンキャンセル\\z", "do" => "Noodle::cancel"
)
);

と同等になりますし、実際にこの juliusActions.yml を PHP で読み込むと上記のような配列が作成されます。

type は正規表現で記述されています。この type の定義がそのまま、PHP の preg_match_all() の pattern として利用されます。この pattern にマッチした場合、do で定義されているクラスメソッドが実行されます。このため、\A(文頭) といったメタ表記については \\A のようにエスケイプが必要なことに注意して下さい。(このバックスラッシュは日本語 Windows 環境では半角の¥です)

参考)preg_match_all

なお、type の正規表現で ( ) によるグルーピングを利用した場合、適合した文字列の順番に配列に格納されて do で定義されたクラスメソッドの第三引数 $args に渡されます。上記の "\\Aハル、ラーメン(\\d)分\\z" では「ハル、ラーメン3分」という音声命令を認識した場合、$args として array("3") が渡されます。

上記一連の処理が具体的にどのような処理を行っているかは /HAL ディレクトリの第一階層にある juliusActions.php を参照して下さい。

Julius からの音声認識によってこうしてパターンマッチが行われた場合の do に記述されているクラスとメソッドは、/HAL/actions/julius/ 内に格納してください。このディレクトリはオートロードの対象となっているため、ファイルが配置してあるだけで自動で読み込まれます。ルールとしては、クラス名とファイル名を同じにして下さい。

では、上記で定義している Noodle クラスを見てみましょう。/HAL/actions/julius/Noodle.php です。

/HAL/actions/julius/Noodle.php
<?php
namespace Feijoa\HAL;

class Noodle
{
public static function measure(HALDto $dto, $read_sock, $args)
{
if(isset($dto->ignore_voice_timer) && $dto->ignore_voice_timer > time()){
LOG::output("ignore noodle timer.");
return;
}
unset($dto->ignore_voice_timer);

$dto->noodle_timer = time() + (60 * (int)$args[0]);
Voice::say($dto, "noodle_{$args[0]}", "カップラーメンタイマーを{$args[0]}分に設定しました");
}

public static function cancel(HALDto $dto, $read_sock)
{
unset($dto->noodle_timer);
Voice::say($dto, "noodle_cansel", "カップラーメンタイマーをキャンセルしました");
}
}

measure() メソッドとcancel() メソッドが定義されているのが分かるかと思います。measure() メソッド では、渡された $args を元に、$dto->noodle_timer を設定しています。

両メソッド内で利用している Voice クラスは、/HAL/system/class 内に格納されており、暗号化されています。この Voice クラスの say() メソッド内では Open JTalk を利用して音声合成を行っています。第一引数は HALDto オブジェクト、第二引数が作成する音声ファイル名で、第三引数が音声合成する文章です。このファイル名と文章の組み合わせが既に作成されている場合は新たに合成を行わず、既存のデータで発声を行います。

Open JTalk のインストールについては、Open JTalkの導入 を参照して下さい。paco を利用する場合は、アプリケーション管理ソフト paco の導入 で、予め paco をインストールしておく必要があります。(paco を利用しない場合は、make の後 make install でインストールして下さい)

version 1.4.1 で Open JTalk をインストールしない場合は、/HAL/setting/HALSetting.php の

// 読上げライブラリ Open JTalk の利用 インストールしていない、または利用しない場合は false
const SPEECH_ENGINE = "OpenJTalk|eSpeak";

について、const SPEECH_ENGINE = ””; とする事で、発声を行わないようにする事ができます。

version 1.4 で Open JTalk をインストールしない場合は、/HAL/setting/HALSetting.php の

// 日本語読上げライブラリ Open JTalk の利用 インストールしていない、または利用しない場合は false
const OPEN_JTALK = true;

について false にする事で、この機能を無効にできます。Open JTalk の利用に不都合がある場合はこちらを設定して、何らかの別の告知手段を別途、実装して下さい。

$dto->ignore_voice_timer は、マイクとスピーカーが近いと、カップラーメンが出来上がった事を報告する HAL 自身の音声で、HAL が音声を誤認して再度カップラーメンタイマーをセットしてしまうため、カップラーメン出来上がりの音声発声時に拒否タイマーとして設定しています。

これはあまり良い解決策ではなく、このサンプルプログラムの実装の後で Julius の音声認識の解析そのものをロックするフラグファイル機能を追加しました。ですが、こちらもファイルが頻繁に書きだされるため、できればメモリキャッシュシステム memcached の利用に切り替えたいと思っています。ですから、今後、この仕様は変わると思われます。

Sensor.php の編集

さて、上記のように、音声認識結果によって設定されたタイマー $dto->noodle_timer ですが、この時刻が来たことを検知して告知する必要があります。

PHP ではデフォルトではスレッド機能が存在しないため、JavaScript の setTimeout() のような処理を行うわけには行きません。

そこで、HAL の第一階層にある Sensor.php を利用します。HAL のライフサイクル (version 1.4) をご覧いただくとわかりますが、この Sensor.php で定義される Sensor クラスの exec() メソッドは、HALSetting.php に定義されている HAL::SYSTEM_INTERVAL マイクロ秒毎に実行されます(CPU の負荷により、多少遅延します)。

ですから、この Sensor::exec() 内で $dto->noodle_timer に設定された時間が過ぎた事を検査し、告知を行えば良いことになります。実際のプログラムを見てみましょう。

Sensor.php
<?php
/* *
*
* socket server HAL v1.4
*
* (c) 2016 Katsuhiko Miki
*
* */
namespace Feijoa\HAL;

class Sensor
{
public static function exec(HALDto $dto)
{
// 以下のコードを参考に、あなたのアプリケーションを記述して下さい。
// (前略)

// カップヌードルタイマー
if(isset($dto->noodle_timer) && $dto->noodle_timer < time()){
$dto->ignore_voice_timer = time() + 10;
Voice::say($dto, "noodle_is_cooked", "カップラーメンが出来上がりました。お召し上がり下さい");
unset($dto->noodle_timer);
}
}

// (後略)
}

これで、指定の時間が過ぎたら Raspberry Pi がラーメンの完成を知らせてくれます。

Julius の起動オプション

以上の準備が整ったら、HAL、Julius、voice_client を起動します。

参考)HALの起動方法

Julius の起動オプションには、先ほど作成した構文制約をカンマ区切りで追加してください。例えば以下のようになります(kaden と instruct の構文制約を適用した場合)。

julius -C /home/pi/julius-kits/grammar-kit-v4.1/hmm_mono.jconf -input mic -gram /var/www/HAL/userdata/julius/kaden,/var/www/HAL/userdata/julius/instruct -nostrip -nolog -module

機能を拡張してみよう!

カップラーメンタイマーが無事に動作することを確認したら、もっとカップラーメン・タイマーを高機能にしてみましょう。例えば、

instruct.voca
% HAL
ハル、 h a r u
% SUBJECT
ラーメン r a: m e n
カップラーメン k a p p u r a: m e n
どん兵衛 d o N b e i
% DO
1分 i p p u n
2分 n i h u n
3分 s a n p u n
4分 y o n p u n
5分 g o h u n
6分 r o p p u n
7分 n a n a h u n
8分 h a p p u n
9分 ky u: h u n
10分 j u p p u n
タイマー t a i m a:
キャンセル ky a n s e r u
% NS_B
<s> silB
% NS_E
<s> silE

のように、「ラーメン」だけなく「カップラーメン」にも対応させることが出来ます。また、「どん兵衛」のように商品に対応したタイマーを作るのも良いでしょう。

YAML ファイルはこんな感じになります。

## Julius Action YAML.
---
(前略)

-
type: "\\Aハル、(カップ)?ラーメン(\\d)分\\z"
do: "Noodle::measure"
-
type: "\\Aハル、(カップ)?ラーメンキャンセル\\z"
do: "Noodle::cancel"
-
type: "\\Aハル、どん兵衛タイマー\\z"
do: "Noodle::donbei"

(後略)

? は、直前の文字またはグループが存在するか、存在しない場合にマッチします。例えば "雨が?降る" は、"雨降る" と "雨が降る" にはマッチしますが、"雨は降る" や "雨も降る" にはマッチしません。

上記の変更で、measure メソッドに渡される $args の配列には 2 つの要素が含まれるようになります。ですから、Noodle::measure() メソッド内で参照している $args の添字を変更する必要があります。

どん兵衛については時間が 5分 と決まっているので、「どん兵衛5分」ではなく、「どん兵衛タイマー」で donbei() メソッドが発動するようにしてみました。(なお、個人的には7分どん兵衛が好みです。加えて「どん兵衛そば」は 3 分です)

実際の Noodle クラスの変更はここには記しません。ぜひご自身で実装してみてください。あるいは、もっと色々な変更を加えて、本当にあなただけの HAL を育て上げてくれる事を期待しています。

参考)ソケットサーバー「HAL」の概要

参考)Raspberry Pi「HAL」を楽しむためのインストール・レジュメ

全く関係ないけれど、どん兵衛拡散委員会

余談ですが、どん兵衛は東と西で味が変わるのが有名ですね。私は京都に行った時に西のどん兵衛を初めて食べてファンになりました。東のスープは醤油味が濃いのですが、西のスープは出汁を前面に押し出した、とてもお上品な味です。Amazon のレビューを見ても、明らかに西の味の方がファンが多いようで、もう、こっちがレギュラーでも良いんじゃないかと思ったりもします。

最近ではどん兵衛を 10 分待つ新しい食べ方なども紹介されました。どん兵衛がいかに多くの人に愛されてきたのかがよく分かります。

どん兵衛「10分待ち」の衝撃、日清“おわび”SNSが崩した40年の常識|産経新聞記事

※最近 TV を殆ど見ていないのですが、少々混乱して武田鉄矢さんが「どん兵衛」の CM タレントに鞍替えしたような錯覚をして数分ほど武田さんの名に泥を塗るような事を書いてしまいました。実際には武田さんはライバル製品一筋みたいですね。武田さん、申し訳ありませんでした。

この記事へのコメント

※現在コメントはMarkdown記法が強制です。>>Markdown の書き方


この記事に返信

このコメントに返信