この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。
1 はじめに
AIソリューション部の平内(SIN)です。
今回は、Amazon Lex(以下、Lex)を、AWS IoT のMQTTで使用してみました。
最初に、実際に利用しているようすをご確認ください。時間が空いて、Lambdaのインスタンスが無い場合、一回目のレスポンスだけ一呼吸遅れますが、それ以外は、殆ど違和感なく使えそうです。
2 構成
構成は、以下のようになっています。
- クライアントへは、CognitoのPoolIDでAWSIoTの特定のトピックへのPublishとSubscribeの権限を渡しています
- 送信メッセージは、MQTTへのPublishの形で行います
- AWS IoTのルールでヒットしたメッセージは、Lambdaに送られます
- Lambdaは、送られてきたメッセージを入力としてLexとのやり取りします
- Lexからのレスポンスは、MQTTにPublishします
- トピックをSubscribeしているクライアントは、メッセージが到着すると、それを表示しています
3 Cognito
Cognitoで発行されるPoolIdには、特定のトピック(Sample_Topic)でのSubscribeとPublishのパーミッションが付与されています。(ここでは、clientを省略していますが、名前で絞ると、より厳格になります)
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"iot:Connect",
"iot:Receive",
"iot:Subscribe",
"iot:Publish"
],
"Resource": [
"arn:aws:iot:ap-northeast-1:xxxxxxxxxxxx:client/*",
"arn:aws:iot:ap-northeast-1:xxxxxxxxxxxx:topic/Sample_Topic",
"arn:aws:iot:ap-northeast-1:xxxxxxxxxxxx:topicfilter/Sample_Topic"
]
}
]
}
4 クライアントの実装
(1) メッセージ
やり取りするメッセージの形式を下記のように定めました。送信も受信も同じトピックで行っているので、自分が送信したメッセージも受信してしまいます。
そこで、typeタグで、送信・受信を表現しています。
const param = {
type: 'req', // req or res
body: 'message',
userId: 'xxxxxx'
}
(2) 実装
クライアントは、とりあえず、html(JavaScript)で作成しています。
- 最初に、トピックをSubscribeしています。(Mqttクラスのinit()で実装)
- メッセージの入力があった場合、MQTTへpublishしています。(sendMessage())
- メッセージが到着した場合は、その内容を、表示しています。(iot.onRecive())
- 表示する際の送信・受信メッセージの区別は、typeタグで行われています。(appendLog(param.body, param.type))
<script>
$('#message').focus();
const userId = 'id-' + Date.now(); // ブラウザの更新時に初期化する
const PoolId = 'ap-northeast-1:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx';
const region = 'ap-northeast-1';
const endpoint = 'xxxxxxxxxxx-ats.iot.ap-northeast-1.amazonaws.com';
const topic = "Sample_Topic";
const clientId = "sample_id";
const iot = new Mqtt();
iot.init(PoolId, region, endpoint, topic, clientId);
iot.onRecive = async data => {
const param = JSON.parse(data);
appendLog(param.body, param.type);
}
function sendMessage() {
const message = $('#message').val().trim();
if(message.length > 0) {
$('#message').val('');
iot.Send({ type: 'req', userId: userId, body: message });
}
return false;
}
function appendLog(message, className) {
$('<log>', { class:className, text:message }).appendTo('#logView');
$('#logView').scrollTop(self.innerHeight);
}
</script>
すべてのコードは、下記に置きました。
furuya02/MQTT_LexClient_index.html
furuya02/MQTT_LexClient_mqtt.js
(3) テスト
AWSIoTのコンソール(テスト)で動作確認している様子です。
送信
受信
5 ルール
AWS IoTに到着したMQTTのメッセージは、ルールによりLambdaへ送られます。ここでは、トピック名(Sample_Toipc)のメッセージを、すべて送っています。
6 Lambda
AWS IoTからキックされるLambdaでは、そのメッセージをLexに送信し、そのレスポンスをMQTTでPublishしています。
const aws = require('aws-sdk');
exports.handler = async (event) => {
if(event.type != 'req') {
return;
}
const message = await lexClient(event.body, event.userId);
await sendMqtt(message);
};
async function lexClient(message, userId) {
// 下記で紹介します(7 Lex CLient)
}
async function sendMqtt(message) {
// 下記で紹介します(8 Publish)
}
すべてのコードは、下記に置きました。
furuya02/MQTT_LexClient_index.js
7 Lex CLient
LambdaからpostText()を使用してLexへアクセスするコードは、以下のとおりです。 Lexにアクセスするためのポリシーを追加する必要があります。
async function lexClient(message, userId) {
const botAlias = '$LATEST';
const botName = 'OrderFlowers';
const sessionAttributes = {};
const lexruntime = new aws.LexRuntime( {region: 'us-east-1'});
const params = {
botAlias: botAlias,
botName: botName,
inputText: message,
userId: userId,
sessionAttributes: sessionAttributes
};
const data = await lexruntime.postText(params).promise();
console.log(JSON.stringify(data));
return data.message;
}
8 Publish
LambdaからMQTTへPublishするコードは、以下のとおりです。 AWS IoTへPublishできるポリシーを追加する必要があります。
async function sendMqtt(message) {
const endpoint = 'xxxxxxxxxxxx-ats.iot.ap-northeast-1.amazonaws.com';
const topic = "Sample_Topic"
const region = 'ap-northeast-1';
var iotdata = new aws.IotData({
endpoint: endpoint,
region: region
});
const param = {
type: 'res',
body: message
}
var iotParams = {
topic: topic,
payload: JSON.stringify(param),
qos: 0
};
let result = await iotdata.publish(iotParams).promise();
}
9 セッション
MQTTでは、1つ1つが独立したメッセージですが、Lexでは、セッションの管理が必要です。
Lexでは、userIdでセッションを区別しているため、今回は、クライアントのJavaScriptでブラウザを 更新した際に、新たにuserIdを採番するように実装しました。
const userId = 'id-' + Date.now(); // ブラウザの更新時に初期化する
10 最後に
最初に、紹介したとおり、時間が空いて、Lambdaのインスタンスがいない場合、一回目のレスポンスだけ一呼吸遅れますが、それ以外は、殆ど違和感なく使えそうです。
これで、クライアントにLexへのパーミッションを付与しなくても、Lambda側の制御で実装が可能になります。
11 参考リンク
[Amazon Lex] HTML+JavaScriptでLexクライアントを作ってみました
[AWS IoT] MQTTを使用して、Lambdaからブラウザを更新する方法〜aws-iot-device-sdk(aws-iot-sdk-browser-bundle.js)を使用する場合〜