この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。
0 Amazon Alexa Advent Calendar
こちらは、Amazon Alexa Advent Calendar 2017への投稿記事です。
1 はじめに
Alexaでスキルを作成する場合、名前や電話番号、又は、暗証番号などの情報が必要になることがあります。しかし、これらを音声で聞き取るのは、ちょっと厄介であり、少し助長になりがちです。
例えば、電話番号や暗証番号などは、正確に聞き取れているか、ユーザーへの再確認が必要でしょうし、英数字などで構成された長い識別子を正確に聞き取るのは、かなり難しかったりします。また、そもそも、暗証番号などを音声で話すのは、ちょっと違和感があるようにも思います。
そこで、今回は、そのような比較的、音声入力に適さない情報を、iPhoneのアプリ経由で取得する方法を考えてみました。
最初に動作している様子を見てみて下さい。
専用のiPhoneアプリを起動して、Alexaスキルから提示されたシークレット・キーを入力すると、名前及び、暗証番号の入力画面になります。 ここで情報を入力してOKボタンを押すと、それから30秒以内にスキルを呼び出した場合に、その情報が利用されてスキルが動作します。
2 動作の流れ
スキルの動作は、概ね以下のとおりです。
- iPhoneと連携していない状態でスキルを起動すると、シークレット・キーが返ってきます。
- 専用アプリに、シークレット・キーを入力すると、情報入力の画面となります。
- 入力を終えて「OK」ボタンを押すと、シークレット・キーを使用してDBに情報が書き込まれます。
- DBに情報が書き込まれた状態でスキルを起動すると、そのデータを取得し、TTLが有効範囲であれば、その情報を使用します。
2 シークレット・キー
シークレット・キーは、UserIDの一部を使用して生成されています。
UserIdは、同一ユーザーが、同一端末から、同一のスキルを起動した場合、常に同じです。 このため、一旦シークレット・キーを聞いて、iPhoneアプリを操作して、再び、スキルを呼び出しても、スキルとiPhoneの間で、共通の情報として使用できます。
参考:Amazon AlexaのSkillにおける、applicationId,userId,deviceIdの一意性の調査
{
"version": "1.0",
"session": {
// 略
},
"context": {
"AudioPlayer": {
// 略
},
"System": {
"application": {
// 略
},
"user": {
"userId": "amzn1.ask.account.XXXXXXXXXXXXXXXXXXXXXXXXX"
},
"device": {
// 略
},
"apiEndpoint": "https://api.amazonalexa.com"
}
},
"request": {
// 略
}
}
iPhoneアプリでは、最後に入力したシークレット・キーが保存されるようになっていますので、次回からは、いきなり情報入力画面に進んで「OK」ボタンを押すところから始めることができます。
3 IOS側の実装
DynamoDBに書き込むためにCognitoとDynamoDBの使用していますが、Cocoapodsで組み込む事が可能です。
pod 'AWSCognito'
pod 'AWSDynamoDB'
CognitoのプールIDを使用した初期化は以下のとおりです。
let poolId = "us-east-1:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
let region = AWSRegionType.USEast1
let credentialsProvider = AWSCognitoCredentialsProvider(regionType: region, identityPoolId: poolId)
let dynamoDbConfiguration = AWSServiceConfiguration(region: region, credentialsProvider: credentialsProvider)
AWSServiceManager.default().defaultServiceConfiguration = dynamoDbConfiguration
シークレット・キーをKeyとして、データを書き込むコードは、以下のとおりです。
func saveDb(name: String, pin: String) {
let dbItem = DbItem()
dbItem?.SecretKey = secretKey
dbItem?.Name = name
dbItem?.Pin = pin
dbItem?.Ttl = NSNumber(integerLiteral: Int(NSDate().timeIntervalSince1970 + expired))
let dynamoDBObjectMapper = AWSDynamoDBObjectMapper.default()
dynamoDBObjectMapper.save(dbItem!).continueWith(block: { (task:AWSTask!) -> Any? in
if let error = task.error as NSError? {
print("The request failed. Error: \(error)")
} else {
print("Success")
}
return nil
})
}
DBに書き込むためのオブジェクトモデルです。swift4以降では、プロパティに@objcを着けないと、Type不整合でエラーとなるので注意が必要です。
class DbItem : AWSDynamoDBObjectModel, AWSDynamoDBModeling {
@objc var SecretKey : String?
@objc var Name : String?
@objc var Pin : String?
@objc var Ttl: NSNumber = 0
static func dynamoDBTableName() -> String {
return "CooperationByDynamoDBTable"
}
class func hashKeyAttribute() -> String {
return "SecretKey"
}
}
4 スキル側の実装
スキルは、最初にUserIdを元にしてシークレッ・キーを生成します。そして、それをKeyにしてDyanmoDBをスキャンします。
TTL(有効期限)も確認し、有効なデータが取得できた場合と、取得できなかった場合で処理を分けています。
Lambdaの主要な部分のコードは下記のとおりです。
const AWS = require('aws-sdk');
const docClient = new AWS.DynamoDB.DocumentClient()
const handlers = {
'HelloIntent': function () {
// UserIdからシークレット・キーの生成
let userId = this.event.context.System.user.userId;
// 取り合えずUserIdの一部(10文字)にしてみる
let secretKey = userId.split('.')[3].slice(0,10).toLowerCase();
let self = this;
// シークレット・キーをKeyとしてDynamoDBを検索する
let params = {
TableName: "CooperationByDynamoDBTable",
Key:{
"SecretKey": secretKey,
}
};
docClient.get(params, function(err, data) {
let name = '';
let pin = '';
if (err) {
console.error("Unable to read item. Error JSON:", JSON.stringify(err, null, 2));
} else {
if (typeof data.Item === "undefined") {
console.log('? Not found')
} else {
// TTLの期限確認
let date = new Date();
let now = Math.floor( date.getTime() / 1000 )
let ttl = data.Item.Ttl;
if( (now - ttl) < 0) { // TTLが期限を過ぎていない場合、これを利用する
pin = data.Item.Pin
name = data.Item.Name
}
}
}
if (pin != '') { // 情報が取得出来た場合のの処理
self.emit(':tell',name + 'さんの暗証番号を確認しました。暗証番号は' + SLEEP + pin + SLEEP + 'です');
} else { // 情報が取得できなかった場合の処理
self.emit(':tell', 'アプリを起動して、シークレット・キーを入力して下さい。' + SLEEP + 'シークレット・キーは、' + SLEEP + readSecretKey(secretKey) + SLEEP + 'です。');
}
});
},
// 略
};
5 最後に
今回試したみたものは、iPhoneとの連携で、決してベストプラクティスと言う訳ではありません。
情報の媒介も、今回は、DynamoDを使用していますが、S3、SMS、CognitoのPoolDataなど、色々考えられる思います。また、入力のタイミングを、スキルの起動時ではなく、セッション中に行うなどの組み立ても可能かもしれません。
本記事は、音声入力が比較的不得意とするところを補うために、他のUIを使用してみるという一例でした。参考になれば幸いです。
コードは、下記に置きました。参考になれば幸いです。
[GitHub] https://github.com/furuya02/CooperationByDynamoDB