Edited at

Slackに入力した文字をAmazon Pollyを使ってしゃべらせる

More than 1 year has passed since last update.

さて、2016年12月にAWSにText-to-Speachのサービスが追加されました。数あるText-to-Speachのユースケースのの中で、やっぱりIoTと絡めたく、色々検討した結果、Slackに入力した文字列をAWS IoT経由でラズパイにメッセージを飛ばして、文字列から発話データをPollyから取得する流れを考えました。

以下が構成図です。


ざっくりとした流れ


  1. Lambdaのコーディング

  2. API GWの設定

  3. SlackのOutgoing WebHooksの設定

  4. ラズパイの設定

  5. ラズパイ上で動作するアプリの実装


Lambdaのコーディング

流れ的には、Lambdaから設定する流れになります。

Lambdaでは、Slackで入力された文字列からSlackのWebHookトリガーの文字列を除いて、Base64エンコードしたデータをAWS IoTの特定トピックにPublishするものです。

トリガー文字列は"speak"にしてます。

抽出した文字列の中身チェックなどのバリデーションは、省略しております。

Lambda Function


index.py

import boto3

import json
import logging
import os
import base64
from urlparse import parse_qs

iot = boto3.client('iot-data')

logger = logging.getLogger()
logger.setLevel(logging.DEBUG)

def respond(err, res=None):
return {
'statusCode': '400' if err else '200',
'body': err.message if err else json.dumps(res),
'headers': {
'Content-Type': 'application/json',
},
}

def mqtt_message(text, res=None):
return {
'message': text
}

def lambda_handler(event, context):
logger.info(event)
user = event['user_name']
channel = event['channel_name']
command_text = event['text']
logger.info(command_text)
text = command_text.split("speak ")
logger.info(text[1])
iot.publish(
topic='/rasp/test',
qos=0,
payload=json.dumps(mqtt_message(base64.b64encode(text[1])))
)

return respond(None, "%s invoked %s in %s" % (user, command_text, channel ))



API GWの設定

API GWの詳細設定は、SlackのOutgoing WebHooksがPOSTのようですので、POSTでアクセスできるリソースとメソッドを用意しました。

今回は、slackというリソースを作り、そこにPOSTメソッドを設定してます。

統合リクエストについては、先程作成したLambdaを選択します。

設定が終わったら、忘れずに、デプロイをしましょう。

デプロイしたステージでのエンドポイントをメモしておきます。


SlackのOutgoing WebHooksの設定

Botを作る上で、とても簡単なOutgoing WehHooksを使いました。

Channel、Trigger Word、URLなどを設定します。

今後も、コマンドを増やすかもしれないので、Trigger Wordをいれました。

URLには、先程のAPI GWのエンドポイントを入力しておきます。

基本的にはこれだけですね。


ラズパイの設定

こちらは、過去の記事を参考にしてください。

太陽光パネルの発電量をAWS IoTとAmazon Elasticsearch Serviceを使って可視化してみる

やることは、AWS IoTにThing作成、証明書発行、証明書にThingとポリシをアタッチする作業になります。

一番手間がかかったのは、Node.jsのバージョンです。過去の記事では、Pythonを使ってましたが、今回は、Node.jsにしてみました。

Node.jsでAWS IoTが求める、TLS1.2を利用するには、デフォルトに入っているNode.jsのバージョンでは実行できません。なので、最新のNode.jsを、ポーティングするのに時間がかかりました。


ラズパイ上で動作するアプリの実装

ここまで、終えると、Slackで文字列をいれるとラズパイ側でMQTTにてメッセージを受信できるところまできました。

いやー、簡単ですね。

あとは、受信したメッセージをAmazon Pollyにいれるだけです。

Amazon Pollyには、レキシコンを用いた発音のカスタマイズができる仕組みがありますが、今回は、シンプルにSynthesizeSpeech APIだけ使います。

SynthesizeSpeech APIでは、音声フォーマットをmp3, ogg_vorbis, pcmから選択できます。今回は、mp3を使いました。

ラズパイ、かつ、Node.jsで、MP3を再生したかったので、色々探したのですが、Playerというモジュールが使いやすそうだったので、そちらをnpmでいれました。

あまり検証せずにやったので、もっと、きれいな実装ができたと思いますが、ひとまず、ファイルに書いて再生する形にしてます。スマートでないですが。。

あとは、AWS SDK、AWS IoT Device SDKをいれて、実装するだけです。


client.js


var awsIot = require('aws-iot-device-sdk');
var AWS = require('aws-sdk');
AWS.config.update({region: 'us-east-1'});
var ep = new AWS.Endpoint('polly.us-east-1.amazonaws.com');
var polly = new AWS.Polly({endpoint: ep});
var fs = require('fs');
var Player = require('player');
var path = require('path');
var debug = require('debug');

// AWS IoT Device初期化
var device = awsIot.device({
region: 'us-east-1',
clientId: 'raspi3',
privateKey: 'certs/pri.pem',
clientCert: 'certs/cert.pem',
caCert : 'certs/root-CA.crt'
});

// ConnectしたらSubscribe
device.on('connect', function() {
console.log('connect');
device.subscribe('/rasp/test');
});

// Messageを受け取ったときの処理
device.on('message', function(topic, payload) {
var messObj = JSON.parse(payload);
var decode = new Buffer(messObj.message, 'base64').toString();
console.log('message', topic, decode);

// Amazon Pollyのパラメータ
var params = {
OutputFormat: 'mp3',
Text: decode,
VoiceId: 'Mizuki',
};

// SynthesizeSpeech API
polly.synthesizeSpeech(params, function(err, data) {
if (err){
console.log(err, err.stack);
} else {
console.log(data);

// 結果が、AudioStreamにBlobで返ってくるので、ファイルに同期で書き出し
fs.writeFileSync('test.mp3',data.AudioStream);

// Playerで再生
var songs = [
path.join(__dirname, './test.mp3')
]
new Player(songs)
.on('playing', function(song) {
debug(song);
})
.on('playend', function(song) {
debug('Play done');
})
.on('error', function(err) {
debug(err);
})
.play()
}
});
});

process.stdin.resume();
process.on('SIGINT', function() {
console.log('receive SIGINT signal');
process.exit();
});



最後に

一番時間がかかったNode.jsのバージョンアップをいれても半日くらいで、Slackに入力した文字列を発話までできるようになりました。

色々荒削りですが、試すには充分かなと。

便利な世の中になりました。


免責

こちらは個人の意見で、所属する企業や団体は関係ありません。