知的好奇心 for IoT

IoT関連の知的好奇心を探求するブログです

音声で家電もコントロールできるスマートミラーを作ってみた

真夏のデザインフェスタへの出展が決まって何をメインに据えようかと悩んでいたら、YouTubeでスマートミラーのビデオを見かけて『スマートホームのハブにはスマートミラーが最適では!』っと思い立ち、作ってみることにしました。

で、作ってみたのがこれです!

f:id:IntellectualCuriosity:20170720134541j:plain

AlexaやGoogle Assistantでスマートホームを実現した時と同じように、家電のコントロールWiFi対応赤外線学習リモコンのBlack BeanことBroadlink RM Mini 3を使いました。

今後、どのように作ったかを更新していくので、興味のある人は時々見に来てくださいね!

 

使ったもの(ハードウェア)

マジックミラー部

マジックミラーを調達しようとGoogle先生にうっかり聞いたら、例の車のビデオがたくさんヒットしてしまいました。しかも、お目当てのマジックミラーって普通に売っていなくて、オーダーメードぐらいしか見つからなかったんです。

仕方がないので、ガラスにフィルムを貼って代用することにしたのですが、それだったら額を買ってそれにフィルムを貼ればいいやと思ってIKEAに行きました。一応事前にGoogle先生に確認して、”アクリル板だと像が歪むのでガラス製の方がいい”という情報を得ていたので、IKEAの額売り場でサンプルの額を片っ端からコツコツと叩いてガラス製の物を探しました。意外にガラス製の額って少ないんですよ。

次に問題になったのがフィルムです。どんなものがいいのか全然わからなかったので、ネットで評判の良かったフィルムを買ったのですが、可視光の透過率が5%のものだったため、ディスプレイの文字が殆ど見えなかったんです。ガーン!

Google先生に改めて聞いたら、”デジタルサイネージなどには透過率10%が最適”などと教えてくれたので、よく行くホームセンターのコーナンに行って車用の透過率が16%のフィルムを買って貼ったら丁度いい塩梅でした。

ディスプレイに引っ掛ける金具やディスプレイからはみ出る部分の覆いを付けた裏の姿がこれです。

f:id:IntellectualCuriosity:20170723025235j:plain

 

PC部(電子機器)

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                         --- 各モジュールのアラートを表示 

サードパーティーモジュール

自作モジュール

  • 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ファイルで行います。

使用したモジュールの設定はこんな感じです。

 

Clockモジュール

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にメッセージを送信して終了します。

 

おわり