スイッチでAlexa定型アクションを起動~スキルテスト編~

有効にしたAlexaスマートホームスキルをテストしてみします。

Step1:リフレッシュコードの取得

スキルの有効時にアクセス権限を許可され、リフレッシュトークンを取得したはずですので確認します。AWSのページでCloudWatchサービスを選択します。
CloudWatchのメニューっから「ロググループ」を選択します。

作成した関数のロググループができているので、クリックします。

ログストリームにスキルを有効にした時刻のタイムスタンプでログができているので、選択します。

上から2番目のログでINFO StatusCode:200と表示されアクセス権限の取得に成功しているのがわかります。その下のログはINFO AcceptGrant:token_=となっており取得したトークンを記録しています。ログ左側の▲をクリックしてログを展開します。

コピーを押下してログをコピーし、リフレッシュトークンをメモしておきます。AcceptGrant:token=より先がリフレッシュトークンとなります。

Step2:イベント送信プログラムの完成

イベント送信プログラムは「あんスマ」ではphpで書かれていましたが、phpのインストール手間が面倒なのでpythonで書き直しました。動作上の違いは「あんスマ」のものは検出イベント送信後、直ぐに非検出を送信していたのですが、問題なさそうなので検出だけにしました。

client_ID = 、client_secret = にはスキルビルド画面のアクセス権限メニューから取得したAlexaクライアントIDとAlexaクライアントシークレットを貼り付けます。(index.jsと同じもの)

refresh_token=にはStep1で取得したリフレッシュトークンを貼り付けます。 ファイル名はalexa_trigger.pyとしました。

import sys
import os
import json
import datetime
from trace import Trace
import requests

client_ID = ''
client_secret = ''
refresh_token=''
sensor_name = 'dummy_sensor_' + format(sys.argv[1], '0>3')
token_file = './token.txt'
today = datetime.datetime.today()

# アクセストークンが有効かどうかチェックする
access_token = ''
access_token_valid = False
if os.path.exists(token_file):
  #ファイル有、データ読み込み
  f = open(token_file)
  data = f.read()
  f.close()
  jdata = json.loads(data)
  expires = jdata['expires_in']
  f_time = os.path.getmtime(token_file)
  file_time = datetime.datetime.fromtimestamp(f_time)
  expiring_date = datetime.datetime.fromtimestamp(f_time + expires)
  if today < expiring_date:
    # アクセストークン有効、取得
    access_token_valid = True
    access_token = jdata['access_token']

# アクセストークンが無効の場合はoa2サーバーから取得
if not access_token_valid:
  header_str = {
    'Content-Type': 'application/x-www-form-urlencoded;Accept-Charset=UTF-8'
  }
  body_str = {
    "grant_type": "refresh_token",
    "refresh_token": refresh_token,
    "client_id": client_ID,
    "client_secret": client_secret
  }
  response = requests.post('https://api.amazon.com/auth/o2/token', data = body_str, headers = header_str)
  if response.status_code != 200 :
    print(response.status_code)
    exit()
  jdata = json.loads(response.text)
  access_token = jdata['access_token']
  if len(access_token) > 0:
    access_token_valid = True
    # ファイルに書き込み
    f = open(token_file, 'w')
    f.write(response.text)
    f.close()

if not access_token_valid:
  print('can not get access_token')
  exit()

# イベントゲートウェイにイベントを送信する
message_id = sensor_name + '_' + str(today)
timeOfSample = today.strftime('%Y-%m-%dT%H:%M:%S.00Z')
header_str = {
    'Authorization': 'Bearer ' + access_token,
    'Content-Type': 'application/json'
}
json_str = {
  "event":{
    "header":{
      "namespace":"Alexa",
      "name":"ChangeReport",
      "messageId":message_id,
      "payloadVersion":"3"
    },
    "endpoint":{
      "scope":{
        "type":"BearerToken",
        "token":access_token
       },
       "endpointId":sensor_name
    },
    "payload":{
      "change":{
        "cause":{
          "type":"PHYSICAL_INTERACTION"
        },
        "properties":[
          {
            "namespace":"Alexa.MotionSensor",
            "name":"detectionState",
            "value":"DETECTED",
            "timeOfSample":timeOfSample,
            "uncertaintyInMilliseconds":0
          }
        ]
      }
    }
  },
  "context":{
    "properties": [
      {
        "namespace": "Alexa.EndpointHealth",
        "name": "connectivity",
        "value": {
          "value": "OK"
        },
        "timeOfSample": timeOfSample,
        "uncertaintyInMilliseconds": 0
      }
    ]
  }
}

response = requests.post('https://api.fe.amazonalexa.com/v3/events', json = json_str, headers = header_str)
if response.status_code == 202:
  print('success')
else:
  print(response)

起動方法は引数にセンサーNo.を渡します。
例えばダミーセンサー0を検出状態にするにはコマンドラインで以下のように実行します。

>python alexa_trigger.py 0

このプログラムの注意点はカレントディレクトリにtoken.txtいうファイルを生成することです。取得したアクセストークンには有効期限があるので、有効期限内は保存したファイルからアクセストークンを取り出します。初回や有効期限が切れた場合は新たにアクセストークンを通信により取得します。有効期限はアクセストークンが含まれたメッセージ中に存在しますが、3600秒=1時間となっています。アクセストークン取得時はレスポンスがやや遅くなります。
アクセストークンを取得後、イベントゲートウェイにイベントを送信します。

Step3:定型アクションの作成とテスト

テスト用の定型アクションを作成します。
実行条件に「スマートホーム」-「ダミーセンサー000」を指定し、モーションの状態を検出にします。
アクションは「Alexaのおしゃべり」-「カスタム」で「ダミーセンサーゼロ!」とテキトーに設定し、保存します。

>python alexa_trigger.py 0

をWindowsのコマンドラインで実行すると”success”と出力され、echoが「ダミーセンサーゼロ」と喋るはずです。
成功しなかった場合は401などHTTPステータスコードを出力します。
成功した場合は202が帰ります。これは受理したが処理はまだ、という意味で、表面的なチェックは成功したということです。エンドポイントの文字列を間違っている場合は202が帰ってきますが、イベントは発生しません。

ここまでうまくいきましたでしょうか?ここまでできれば終わったも同然です。後は不要と言う方も多い事でしょう。
以上でテスト編は終わりです。
次はラズベリーパイセットアップ編です。