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

この記事は公開されてから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<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不整合でエラーとなるので注意が必要です。

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