[Alexa] DynamoDBによるiPhoneと連携(暗証番号の取得)

2017.12.03

この記事は公開されてから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 [GitHub] https://github.com/furuya02/CooperationByDynamoDB

6 参考資料

Alexa Skills Kitによるスキルの作成

alexa/alexa-skills-kit-sdk-for-nodejs

Amazon Cognito

Amazon DynamoDB Object Mapper API