[Alexa] DynamoDBによるiPhoneと連携(暗証番号の取得)
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