[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の一意性の調査
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | { "version" : "1.0" , "session" : { // 略 }, "context" : { "AudioPlayer" : { // 略 }, "System" : { "application" : { // 略 }, "user" : { "userId" : "amzn1.ask.account.XXXXXXXXXXXXXXXXXXXXXXXXX" }, "device" : { // 略 }, } }, "request" : { // 略 } } |
iPhoneアプリでは、最後に入力したシークレット・キーが保存されるようになっていますので、次回からは、いきなり情報入力画面に進んで「OK」ボタンを押すところから始めることができます。
3 IOS側の実装
DynamoDBに書き込むためにCognitoとDynamoDBの使用していますが、Cocoapodsで組み込む事が可能です。
1 2 | pod 'AWSCognito' pod 'AWSDynamoDB' |
CognitoのプールIDを使用した初期化は以下のとおりです。
1 2 3 4 5 | 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として、データを書き込むコードは、以下のとおりです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | 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<AnyObject>!) -> Any? in if let error = task.error as NSError? { print( "The request failed. Error: \(error)" ) } else { print( "Success" ) } return nil }) } |
DBに書き込むためのオブジェクトモデルです。swift4以降では、プロパティに@objcを着けないと、Type不整合でエラーとなるので注意が必要です。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | 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の主要な部分のコードは下記のとおりです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 | 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( '<img draggable="false" class="emoji" alt=" ) } 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
6 参考資料
Alexa Skills Kitによるスキルの作成
alexa/alexa-skills-kit-sdk-for-nodejs
Amazon Cognito
Amazon DynamoDB Object Mapper API