真夏のデザインフェスタへの出展が決まって何をメインに据えようかと悩んでいたら、YouTubeでスマートミラーのビデオを見かけて『スマートホームのハブにはスマートミラーが最適では!』っと思い立ち、作ってみることにしました。
で、作ってみたのがこれです!
AlexaやGoogle Assistantでスマートホームを実現した時と同じように、家電のコントロールはWiFi対応赤外線学習リモコンのBlack BeanことBroadlink RM Mini 3を使いました。
今後、どのように作ったかを更新していくので、興味のある人は時々見に来てくださいね!
使ったもの(ハードウェア)
マジックミラー部
- IKEAで買ったガラス額縁 VIRSERUM 40 x 50cm
- コーナンで買ったカーフィルム ミラリード FM-21 透過率16% 2m
- コーナンで買ったL字取付金具2枚組 30 x 10cm
- コーナンで買った木ねじ12本組 3 x 10mm
- ダイソーで買った工作用ボール紙 黒色厚紙B4サイズ3枚入り
- ダイソーで買った戸当たりの防音テープ2.5m
マジックミラーを調達しようとGoogle先生にうっかり聞いたら、例の車のビデオがたくさんヒットしてしまいました。しかも、お目当てのマジックミラーって普通に売っていなくて、オーダーメードぐらいしか見つからなかったんです。
仕方がないので、ガラスにフィルムを貼って代用することにしたのですが、それだったら額を買ってそれにフィルムを貼ればいいやと思ってIKEAに行きました。一応事前にGoogle先生に確認して、”アクリル板だと像が歪むのでガラス製の方がいい”という情報を得ていたので、IKEAの額売り場でサンプルの額を片っ端からコツコツと叩いてガラス製の物を探しました。意外にガラス製の額って少ないんですよ。
次に問題になったのがフィルムです。どんなものがいいのか全然わからなかったので、ネットで評判の良かったフィルムを買ったのですが、可視光の透過率が5%のものだったため、ディスプレイの文字が殆ど見えなかったんです。ガーン!
Google先生に改めて聞いたら、”デジタルサイネージなどには透過率10%が最適”などと教えてくれたので、よく行くホームセンターのコーナンに行って車用の透過率が16%のフィルムを買って貼ったら丁度いい塩梅でした。
ディスプレイに引っ掛ける金具やディスプレイからはみ出る部分の覆いを付けた裏の姿がこれです。
PC部(電子機器)
- Raspberry Pi 3 Model B
- TOSHIBA EXCERIA™ microSDHCメモリカード 16GB
- 24インチディスプレイ DELL U2412M
- USBマイク SANWA MM-MCU03BK
- 赤外線学習リモコン Broadlink RM Mini 3 (Free shippingだと1ヵ月くらいかかります...)
- 赤外線対応 Minger LED RGBW 電球
- スピーカー Anker SoundCore mini
SANWAのUSBマイクはAIY Projectの記事でも使用していたもので、Raspberry Piにドライバーなど気にせず繋ぐだけで使えます。指向性タイプなので、外部ノイズに強い特徴があります。
赤外線学習リモコンのBroadlink RM Mini 3もAlexaの記事などGoogle Assistantでの家電コントロールにも使用していておなじみな感じになっています。
LED電球は赤外線コントロールができて色も変えられるのにかなりお安く日本でも入手できます。これ、結構おもしろいです。これを家電コントロールのデモ用に使用してます。
肝心のディスプレイには長年使用しているDELL U2412Mを使用します。これが使えなくなるとソフト開発に支障をきたしてしまうので、スマートミラーとして使う時だけ縦回転させてマジックミラー額を引っ掛けて載せるようにしたんです。
スピーカーは最初100均の物を買ったのですがあまりにも音が小さすぎたので、これを機会にモバイルスピーカーを新調してしまいました。これ、小さいのに迫力のある音がでます。スマートミラーにはオーバースペックなんですけどね。
使ったもの(ソフトウェア)
スマートミラー(プラットフォーム)
スマートミラー用として公開されているさまざまなソフトウェアの中から今回選んだのが、たぶん最もポピュラーだと思われるこれです。
- MagicMirror²のWebサイト(紹介ビデオとかがあります)
MagicMirror²を選んだ理由は、ドキュメントが整理されていて頑張って読めばなんとかなるかなぁと思ったのと、機能がモジュール化されていてサードパーティーのモジュールがたくさんあるだけでなく自分でもモジュールを作ることができるようになっていたからです。
標準モジュール
- Clock --- 日付と時刻を表示
- Current Weather --- 温度・湿度・風速・天候・日の出の時刻を表示
- Weather Forecast --- 一週間の天気・最高気温・最低気温を表示
- News Feed --- RSSで取得したニュースを表示
- Compliments --- お世辞を表示
- Alert --- 各モジュールのアラートを表示
サードパーティーモジュール
- voicecontrol by alexyak --- snowboyベースの音声検出機能
自作モジュール
- ircontrol --- テキスト読み上げ(TTS)と赤外線リモコン機能
Snowboy - ホットワード検出エンジン
Google Assistantの初期設定として「OK, Google」と3回言ったことを覚えていますか?その「OK, Google」がGoogle Assistantのホットワードです。「Alexa」や「Hey Siri」もホットワードで、ホットワードを検出するとその後の音声をクラウド音声AIに渡す仕組みになっています。
Snowboyはホットワード検出エンジンで、音が合っているかだけをローカルで判断するので軽くて速いレスポンスが得られます。その代りに音が似ていたり途中まで一緒だったりする「言葉」の場合は、なかなか正しく識別してくれなかったりします。その辺りが言語を理解して文脈からも何を話したかを推測する音声AIとの違いですね。
今回は簡単な家電のコントロールを目的としていたので、手軽なSnowboyを使うことにしました。ビジネス用途でなければお金がかからないのもポイントです。
運用的に独特なのが「ホットワードの作成の仕方」ですね。Snoyboyのサイトにログインすると「Create Hotword」というボタンがあって、そこからホットワードを3回言って登録しダウンロードするという方法を取っています。他の人が登録したホットワードに自分の声の情報も加えられるようになっていて、幅広い検出が可能なホットワードを作ることができる仕組みが用意されています。
「voicecontrol by alexyak」モジュールにはSnowboyのバイナリとPythonラッパーが含まれていて、Snowboyのサイトから別途インストールしなくても使えるようになっています。
Open JTalk - テキスト読み上げ(TTS)エンジン
Open JTalkは名古屋工業大学などで開発されたテキスト読み上げ(Text-to-Speach)エンジンです。公式サイトのリンクを貼りましたが、お世辞にもWebサイトの構成や説明が良いとは言えないので「クラゲのIoTテクノロジー」さんの「RaspberryPiを音声合成でしゃべらせよう」を紹介しておきます。
ぼくもクラゲさんのページを見てメイちゃんに喋ってもらうようにしました。
python-broadlink - Broadlink製品の制御
ぼくのブログではお馴染みになった感のあるプログラムです。これでBroadlink RM mini 3を制御して、LEDを点けたり消したりします。使い方はぼくのAlexaの記事を見てくださいね。
MagicMirror²の設定
MagicMirror²本体とモジュールの設定はconfigフォルダにあるconfig.jsファイルで行います。
使用したモジュールの設定はこんな感じです。
module: "clock",
position: "top_left"
Clockモジュールはデフォルト設定で写真のようにいい感じに表示されます。
Current Weatherモジュール
module: "currentweather",
position: "top_right",
config: {
location: "Tokyo",
locationID: "1850147",
appid: "OpenWeatherMapで取得したAPI keyを設定",
roundTemp: true,
degreeLabel: true,
showWindDirection: true,
showWindDirectionAsArrow: false,
showHumidity: true
}
locationIDはhttp://www.openweathermap.org/help/city_list.txtから都市名を探して先頭の数値を設定します。「Tokyo」の場合は次のように登録されています。
1850147 Tokyo 35.689499 139.691711 JP
appidはhttp://www.openweathermap.org/に登録して手に入れます。個人利用であれば無料プランで十分だと思います。
他の設定の意味
roundTemp:小数点以下を四捨五入するか
degreeLabel:「C」の表示
showWindDirection:風向の表示
showWindDirectionAsArrow:風向を略語の代わりに矢印で表示
showHumidity:湿度の表示
Weather Forecastモジュール
module: "weatherforecast",
position: "top_right",
header: "Weather Forecast",
config: {
location: "Tokyo",
locationID: "1850147",
appid: "OpenWeatherMapで取得したAPI keyを設定",
roundTemp: true,
showRainAmount: false
}
設定はCurrent Weatherモジュールとほぼ一緒です。
positionの場所が被った場合はconfig.jsで先に書いた方が上に表示されます。
showRainAmountは雨量で日本でよく使う降水確率ではなかったので表示していません。
News Feedモジュール
module: "newsfeed",
position: "top_center",
config: {
feeds: [
{
title: "Yahoo Japan News",
url: "https://news.yahoo.co.jp/pickup/rss.xml"
}
],
showSourceTitle: true,
showPublishDate: true
}
写真撮影時には出していませんでしたが、デザインフェスタで展示していた時に表示していたモジュールです。
RSSフィードを表示できるようになっているのでYahooニュースを出すようにしました。
他の設定の意味
showSourceTitle:titleで設定した文字列を表示
showPublishDate:ヘッドラインの配信日を表示
Complimentsモジュール
module: "compliments",
position: "lower_third"
Complimentsモジュールはデフォルト設定で使いました。表示する内容も変更できるのですが、お世辞って日本語で出されると直接的過ぎるのでそのままにして置きました。
Voice Controlモジュール
module: 'voicecontrol',
position: 'top_left',
config: {
models: [
{
keyword: "LED On",
description: "「あかるく、して」と言うと点灯します",
file: "akaruku.pmdl",
message: "LED_ON"
},
{
keyword: "LED Off",
description: "「くらく、して」と言うと消灯します",
file: "kuraku.pmdl",
message: "LED_OFF"
},
{
keyword: "LED RGB",
description: "シークレットワード",
file: "tanoshiku.pmdl",
message: "LED_RGB"
},
{
keyword: "LED Red",
description: "「レッド」と言うと赤くなります",
file: "red.pmdl",
message: "LED_RED"
},
{
keyword: "LED Green",
description: "「グリーン」と言うと緑になります",
file: "green.pmdl",
message: "LED_GREEN"
},
{
keyword: "LED Blue",
description: "「ブルー」と言うと青くなります",
file: "blue.pmdl",
message: "LED_BLUE"
},
]
}
keyword:識別用(自分にわかりやすい名前を付けましょう)
description:表示する説明文
file:snowboyサイトで作成したHotwordモデルファイル
message:ホットワード検出時に通知するメッセージ(MagicMirror²のモジュール間通信用)
ircontrol自作モジュール
module: 'ircontrol',
position: 'lower_third',
config: {
models: [
{
keyword: "LED On",
dialogue: "明るくしますね",
file: "LED.white",
message: "LED_ON"
},
{
keyword: "LED Off",
dialogue: "暗くしますね",
file: "LED.off",
message: "LED_OFF"
},
{
keyword: "LED RGB",
dialogue: "レッツダンス!",
file: "LED.rgb",
message: "LED_RGB"
},
{
keyword: "LED Red",
dialogue: "あかくなーれ!",
file: "LED.red",
message: "LED_RED"
},
{
keyword: "LED Green",
dialogue: "みどりになーれ!",
file: "LED.green",
message: "LED_GREEN"
},
{
keyword: "LED Blue",
dialogue: "あおくなーれ!",
file: "LED.blue",
message: "LED_BLUE"
},
]
}
keyword:識別用(自分にわかりやすい名前を付けましょう)
dialogue:メッセージ受信時に読み上げと表示をする文字列
file:Broadlink RM mini 3で学習させた赤外線信号ファイル
message:通知を受けるメッセージ(MagicMirror²のモジュール間通信用)
Voice Controlモジュールでホットワードを検出するとホットワードに対応したメッセージが通知されます。このモジュールは通知されるメッセージに応じてdialogueの読み上げと表示をして、対応した赤外線信号ファイルをBroadlink RM mini 3に送ります。
ircontrol自作モジュール
modulesフォルダにircontrolフォルダを作って、voicecontrolモジュールを参考にしてircontrol.jsとnode_helper.jsの2つのプログラムを作成しました。
2つのファイルの役割はこんな感じです。
- ircontrol.js --- モジュール間通信と画面制御(検出した音声の表示)
- node_helper.js --- Open JTalkとpython-broadlinkの呼び出し
ircontrol.jsとnode_helper.jsはsocket間通信でメッセージを送ります。
ircontrol.js
/* Magic Mirror
* Module: ircontrol
*
* By Hiroyuki ITO
* MIT Licensed.
*/
Module.register("ircontrol",{
// Default module config.
defaults: {
models: [
{
keyword: "Light On",
dialogue: "I'll trun on the light",
file: "light.on",
message: "LIGHT_ON"
},
]
},
start: function() {
this.wrapper = document.createElement("div");
this.wrapper.innerHTML = "";
},
notificationReceived: function(notification, payload, sender) {
var models = this.config.models;
for (var i=0; i<models.length; i++) {
if (models[i].message == notification) {
this.wrapper.innerHTML = models[i].dialogue;
this.updateDom();
this.sendSocketNotification("DETECTED", models[i]);
break;
}
}
},
socketNotificationReceived: function(notification, payload){
var self = this;
if (notification == "DATA_SENT"){
setTimeout(function() {self.wrapper.innerHTML = "";}, 3000);
}
},
// Override dom generator.
getDom: function() {
return this.wrapper;
},
});
モジュール間通信でメッセージを受け取ると、メッセージに応じた読み上げテキストの表示とヘルパーへのメッセージ送信を行います。
ヘルパーからメッセージを受け取ると、表示していたテキストを3秒後に消します。
node_helper.js
'use strict';
/* Magic Mirror
* Module: ircontrol
*
* By Hiroyuki ITO
* MIT Licensed.
*/
const NodeHelper = require('node_helper');
const exec = require('child_process').execFile;
module.exports = NodeHelper.create({
socketNotificationReceived: function(notification, payload) {
if (notification === "DETECTED") {
this.speechVoice(payload.dialogue);
this.sendIRData(payload.file);
this.sendSocketNotification("DATA_SENT", payload);
return;
}
},
speechVoice : function(dialogue) {
console.log("speechVoice by helper: " + dialogue);
var command = '/home/pi/MagicMirror/jtalk';
var params = [];
params.push(dialogue);
var sidProcess = exec(command, params);
},
sendIRData : function(file) {
console.log("sendIRDataa by helper: " + file);
var command = '/home/pi/python-broadlink/cli/rm';
var params = [];
params.push(file);
var sidProcess = exec(command, params);
},
});
メッセージを受け取ると、メッセージに応じたテキストをパラメーターにしてOpen JTalkコマンドを実行するシェルスクリプト呼び出します。
続いて、メッセージに応じた赤外線情報ファイルをパラメーターにしてpython-broadlinkを実行するシェルスクリプトを呼び出します。
最後にircontrolにメッセージを送信して終了します。
おわり