知的好奇心 for IoT

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

乙女ゲーキャラで温度や湿度を囁いてくれるWiFi温湿度計(ESP8266 + Blynk)のGoogle Assistantアプリを作ってみた

AmazonはAIアシスタントにAlexaという名前を与えて、Alexaを調教するに教える(スキルを追加)ことでAlexaができることを拡張するアプローチを取っていて、ぼくは分かりやすいし親しみやすいと思っていますが、Googleは全然違うアプローチを取っています。

GoogleはAIアシスタントに名前を与えず、機能を拡張をしたい場合はその機能を持っている相手と”チャットする”というアプローチを取っています。

この思想の違いは重要なんです。

Alexaの場合は話し相手はいつもAlexaで、Alexaに部屋の温度やピザの注文をお願いするという、色々と世話を焼いてくれる家族の一員のような役割を与えていて、まさに家庭にAlexaというAIを迎えるという演出をしているんです。今後、家庭に入って来るお掃除ロボット白物家電、更には家自体(ドアや窓、カーテンのコントロールも)、車もAlexaというAIのコントロール下に置かれて、Alexaと話すことでなんでもできるようになることでしょう。

Amazonの究極の目標はAlexaをAIの代名詞にすること、言い換えると、Alexaを機械やソフトウェアと人間との標準インターフェースにすることなのかも知れません。

っと、SF好きなぼくの妄想はさておいて、今回の趣旨に話を戻しましょう。

 

構成

前回のAlexaでの記事と比べると、AWS LambdaからBlynkにアクセスする部分以外が違っています。

GoogleにもFirebaseというクラウドアプリ開発環境(Cloud Functionsの仲間)が用意されているので、本来AWS Lamdbaを使う必要が無いのですが、なんとGoogle以外にアクセスするには有料プランの契約が必要になっているのです!

ぼくのアプリではBlynkのクラウドサーバーにアクセスする必要があるため、有料プランに変更しないと駄目だったんです。

これに丸1日ぐらい気付かず、なんでうまく動かないのか散々調べました...。

そんな理由であっさりFirebaseを捨ててAWS Lamdaを使うことにしたんです。

f:id:IntellectualCuriosity:20180118185649p:plain

 

デモビデオ

同じシェアハウスに住んでいる茜ちゃんに協力してもらいました。

Google Assistant、むっちゃ反応いいです!ピポッという音が終わる前に喋ってもキチンと認識しています!この辺りはAlexaより良く出来ているように思います。 

 

Actions on Google

アプリを公開せずに自分だけで楽しむのであれば、やることはあまりありません。

最初にプロジェクトを作ってActionを設定し、アプリ名を決めることぐらいです。

プロジェクトの作成

f:id:IntellectualCuriosity:20180120201310p:plain

プロジェクト名はとりあえず適当に付けてしまいましょう。

Actionの設定

正直インターフェースというか作りが良くありません。Alexaに対抗するためよっぽど急いだのでしょうか。

プロジェクトを作るとこの画面が表示されますが、これはActionの設定のようです。

Dialogflowの「BUILD」をクリックします。

f:id:IntellectualCuriosity:20180120202502p:plain

一度作った後はこのように「①Actions」の所に表示されます。この画面を最初から出して設定するようにした方が、何をやっているのかがわかると思うのですが...。

f:id:IntellectualCuriosity:20180120203803p:plain

「BUILD」をクリックするか「Dialogflow actions」をクリックするとこの画面が表示されます。「CREATE ACTIONS ON DIALOGFLOW」をクリックするとDialogflowに飛びます。

Dialogflowは旧Api.aiという会社をGoogleが買収して名前が変わったようです。Dialogflowは機能的に非常に優れていると思いますが、Google Assistantに限らず汎用に出来ているいるために一手間かかる状態となっていて、設定が終わった後に「Integrations」を行わないと設定がGoogle Assistantに反映されません。苦肉の策としてこの画面を挟んでアピールするというなんともスマートじゃない状態となっています。

f:id:IntellectualCuriosity:20180120204822p:plain

Dialogflowの設定が終わったら、App infomationの「Assistant app name」と「Pronounciation」を設定すれば、「ブリンクにつないで」とGoogle Assistantに話しかければブリンクアプリにつないでくれます。

アプリの声はGoogle Assistantの比較的ナチュラルな声ではなく、Assistant app voice項目で設定できるたどたどしい声になります。Male 1、Male 2、Female 1、Female 2の4つから選べますが、この声を変更して「SAVE」しただけでは反映されません。DialogflowでIntegrationを行うか、Overviewに戻って「TEST DRAFT」をクリックする必要があるんです!んー、なんだろう、この作り...。

f:id:IntellectualCuriosity:20180120215744p:plain

 

Dialogflow

Dialogflowでは基本「Entities」「Fulfillment」「Intents」の3つの設定を行います。

Entities

会話の変数となるような言葉のバリエーションを定義するっといった感じです。

今回の場合は「部屋の温度を教えてください」や「ベランダの湿度を教えてください」という会話を想定しているので、部屋やベランダ用のLocationと温度や湿度用のMeasurementを登録します。

f:id:IntellectualCuriosity:20180121165038p:plain

f:id:IntellectualCuriosity:20180121165745p:plain

Fulfillment

外部プログラムとの連携を設定するといった感じです。

Firebaseを使う場合はInline Editorでコードを直接書くことができて、最初はこれでやっていましたが、外部アクセス問題のためにAWS Lambdaに切り替えたためWebhookを使うようにしました。

この段階ではまだAWS Lambdaの設定が終わっていないので、適当なURLを記入しておいて後で変更します。

f:id:IntellectualCuriosity:20180121174358p:plain

Intents

初めから次の2つのIntentが登録されています。

Default Welcome Intent:アプリにつないだときに言う言葉

f:id:IntellectualCuriosity:20180121190106p:plain

Default Fallback Intent:登録したIntentに合致しなかったとき用の言い訳集

f:id:IntellectualCuriosity:20180121185621p:plain

これらのIntentにResponseを追加することもできたのですが、それだと世界観が崩れてしまうので、このように変えました。

Default Welcome Intent:変更後

f:id:IntellectualCuriosity:20180121190553p:plain

Default Fallback Intent:変更後

f:id:IntellectualCuriosity:20180121190828p:plain

これらの文言は「12分●罰ゲーム用台詞集【男性用】」からお借りしました。七菜 かずはさんに感謝!

Intentの追加

追加するIntentは会話と変数となる言葉を定義するみたいな感じです。

少し独特のインターフェースで、User Saysにまず「locationのmeasurementを教えて」というように入力して、Entityに対応させる部分をマウスでドラッグするなどで選択して(青く反転します)表示されるEntity一覧から選択します。

f:id:IntellectualCuriosity:20180121180033p:plain

User saysでEntityの対応付けを行うとActionにも自動で追加されます。

事前にFulfillmentでWebhookを「ENABLE」にしておくと、この画面にもFulfillment項目が表示されるのでUse webhookにチェックを付けます。

f:id:IntellectualCuriosity:20180121171650p:plain

 

AWS Lambda

AWS Lambda関数を作成するには、まず、リージョンを「東京」に変えて、AWSサービスの「最近アクセスしたサービス」か「すべてのサービス」から「Lambda」をクリックします。

f:id:IntellectualCuriosity:20180123002531p:plain

ロールは最初「カスタムロールの作成」を選んで、新たに開いた画面で「新しいIAMロールの作成」を選んで「許可」をクリックした後、「既存のロールを選択」に選び直します。

f:id:IntellectualCuriosity:20180123003134p:plain

関数コード (Node.js 6.10)

index.jsを以下のものと置き換えます。コードはAlexaの時のreadTemperatureInSession()とほぼ一緒です。違いはDialogflowからのEntityの受け取り方ぐらいです。

Google Assistant

  • var location = JSON.parse(event.body).result.parameters.Location;
  • var measurement = JSON.parse(event.body).result.parameters.Measurement;

Alexa

  • var location = LocationSlot.value;
  • var measurement = MeasurementSlot.value;
var http = require('http');

exports.handler = (event, context, callback) => {
    // TODO implement
    var repromptText = '';
    var speechOutput = '';
	var body = '';
	var blynkAuthToken;
	var blynkPin;

    var location = JSON.parse(event.body).result.parameters.Location;
    if (location === '') {
        location = 'ベランダ';
    }
    switch (location) {
        case '部屋':
            blynkAuthToken = '**********'; // 部屋の温湿度計のtoken
            break;
        case 'ベランダ':
        case '外':
            blynkAuthToken = '**********'; // ベランダの温湿度・気圧計のtoken
            break;
    }

    var measurement = JSON.parse(event.body).result.parameters.Measurement;
    if (measurement === '') {
        measurement = '温度';
    }
    switch (measurement) {
        case '気温':
        case '温度':
            blynkPin = 'V1';
            break;
        case '湿度':
            blynkPin = 'V2';
            break;
        case '気圧':
            blynkPin = 'V3';
            break;
    }

    var httpPromise = new Promise( function(resolve,reject){
		http.get({
			host: 'blynk-cloud.com',
			path: '/' + blynkAuthToken + '/get/' + blynkPin,
			port: '80'
		}, function(response) {
			// Continuously update stream with data
			response.on('data', function(d) {
				body += d;
			});
			response.on('end', function() {
				// Data reception is done, do whatever with it!
				console.log(body);
				resolve('Done Sending');
			});
		});
	});
	httpPromise.then(
		function(data) {
			console.log('Function called succesfully:', data);
			var info = parseFloat(JSON.parse(body));
			speechOutput = 'よしよし、いい子だね。ご褒美をあげるよ。' + location + 'の' + measurement + 'は';
			switch (measurement) {
                default:
                case '気温':
                case '温度':
		        	repromptText = speechOutput + info.toFixed(1) + '℃だ。';
        			speechOutput = speechOutput + info.toFixed(1) + '度だ。';
                    break;
                case '湿度':
		        	repromptText = speechOutput + info.toFixed(1) + '%だ。';
        			speechOutput = speechOutput + info.toFixed(1) + 'パーセントだ。';
                    break;
                case '気圧':
		        	repromptText = speechOutput + info.toFixed(0) + 'hPaだ。';
        			speechOutput = speechOutput + info.toFixed(0) + 'ヘクトパスカルだ。';
                    break;
			}
			console.log(speechOutput);
		    callback(null, {
                "statusCode": 200, 
                "body": JSON.stringify({
		            speech: speechOutput,
		            displayText: repromptText,
		            source: "AWS API Gateway"
	            })
            });
		},
		function(err) {
			console.log('An error occurred:', err);
		}
	);
};

コードを置き換えたら保存します。

f:id:IntellectualCuriosity:20180123005616p:plain

トリガーの部分は、次の「Amazon API Gateway」で設定すると表示されるようになります。

 

Amazon API Gateway

DialogflowのwebhookからAWS Lambdaを利用する場合には、LambdaのトリガーとしてAPI Gatewayを作成します。

API Gatewayを作成するには、まず、リージョンを「東京」に変えて、AWSサービスの「最近アクセスしたサービス」か「すべてのサービス」から「API Gateway」をクリックします。

f:id:IntellectualCuriosity:20180123001840p:plain

API名は設定画面でしか使われないので適当に付けてしまいましょう。

f:id:IntellectualCuriosity:20180122171049p:plain

作成の手順は若干わかり難いです。

まず、リソースのアクションから「メソッドの作成」を選びます。

f:id:IntellectualCuriosity:20180122171927p:plain

プルダウンリストから「POST」選びます。

f:id:IntellectualCuriosity:20180122172327p:plain

チェックマークをクリックします。

f:id:IntellectualCuriosity:20180122172746p:plain

するとセットアップ画面になります。

LambdaリージョンはLambda関数を作成したときのリージョンを選択します。「ap-northeast-1」が「東京」です。実際に存在しているLambda関数を指定しないとエラーになるので、先にLambda関数を作成しておきましょう。

f:id:IntellectualCuriosity:20180122175217p:plain

 

まだまだ、つづく...