スマートスピーカー、流行のトリガーは「オフィス」? 「Alexa for Business」の可能性 (1/2)

買おうにも、アマゾンから招待が届かずいつ買えるか分からない、音声AIアシスタント「Alexa」搭載デバイスの「Amazon Echo」。実は、これがビジネスの場面に進出しつつあることをご存じだろうか。 2017月11月に開催されたアマゾンの年次イベント「AWS re:Invent 2017」で、エンタープライズ向けの音声AIアシスタント「Alexa … 続きを読む

アレクサに仮想通貨の総資産を答えさせる

どうせならAIスピーカーに現在の資産を答えさせたい

仮想通貨は値動きが激しいのでこまめに値段をチェックしたくなると思います
レートは「仮想通貨チャート」というスキルですぐ確認できるのですが、
(僕が作ったスキルなのでインストールお願いします!笑)
総資産は見れません(APIキーが必要なので公開スキルでは無理そう)
仮想通貨の所持数をいちいち手動で登録して総資産を算出するものもありますが登録が面倒ですよね…
だけど、どうせAIスピーカー×仮想通貨という近未来感満載の環境があるなら総資産もスマートに聞きたい!
というわけでアレクサに取引所(今回はzaif)にある総資産を答えさせるスキルを作ってみました

開発環境

AWS lambda (Python3.6)
Alexa Skills kit

ZaifAPIキーの利用

このスキルでは残高情報取得のためにウォレットのAPIキーを使用します
昨今APIの不正使用で勝手に出金されたなどのニュースがあるのでAPIキーの管理はしっかりと行いましょう
今回のスキルでは取引所の口座内の仮想通貨の量さえわかればいいので,
APIキーには残高を確認できる権限のみ付与します
特にAPIキーは暗号化せずlambdaにベタ打ちします
あとこのスキルの公開の申請は絶対にやめましょう(テストが通った時点で自分のアカウントではスキルを使うことができます)
(スキルを公開したらあなたの総資産が他の人にわかってしまいますw)

ZaifでAPIキーを取得

Zaif開発者向けAPI
まずここからAPIキーを発行します
写真のようにinfoとpersonal_infoのみチェックを付けました.
zaif_api.png
発行できたらkeyとsecretをコピーしておきます.

APIキーをpythonで使う

ZaifのAPIを扱えるzaifapiというpythonパッケージがあります.今回はそれを使うこととします.
lambdaでソースコードはzipアップロードするため,そのディレクトリで以下のコマンドでパッケージをインストールします

pip install zaifapi -t.

総資産を取得するのは以下のようなものを書いてみました(asset_info.py)
これ単体を実行するだけでもコマンドライン上で総資産を確認することは可能です

asset_info.py

import zaifapi
class AssetInfo(object):
    def __init__(self, key_zaif, secret_zaif):
        self.key_zaif = key_zaif
        self.secret_zaif = secret_zaif
    def get_zaif_asset(self):
        zaif_trade = zaifapi.ZaifTradeApi(key=self.key_zaif, secret=self.secret_zaif)
        zaif_public = zaifapi.ZaifPublicApi()
        deposits = zaif_trade.get_info2()["deposit"] #所有通貨とその量を取得
        asset_zaif = 0 #初期値
        for currency, amount in deposits.items():
            if currency == "jpy": #jpyのみレートはいらない
                asset_zaif += amount
            else:
                asset_zaif += amount * zaif_public.last_price(currency.lower() + "_jpy")["last_price"]
        return round(asset_zaif) #小数点以下省略

Alexa Skills Kitでスキルの設定

「総資産」というスキルを作ります
スキルを呼び出すときに総資産を言ってくれればいいのでカスタムインテントは本来不要ですが,
カスタムインテントなしで先に進めなかったので形だけ作りました
(slotsは「ザイフ」のみ登録しましたが,将来的に他の取引所ごとに総資産を取得することもできます)
github

lambdaの記述

一から記述するのは面倒なので,
僕はalexa-skills-kit-color-expert-pythonを参考にいつも書いています
lambdaのソースコードはgithubに上げましたので参考まで
lambda_function.pyの初めの部分のkeyとsecretは各々のAPIキーをコピペしてください
ただしzaifのAPIのget_info2()のところでかなり時間がかかるため,lambdaの実行時間(「基本設定」の「タイムアウト」)を20秒にします
時間がかかりすぎるとタイムアウトになってしまうためです

実際に使ってみる

pythonコードをlambdaにアップロードできたら準備完了です
以上で設定はできました
あとはアレクサに向かって
「アレクサ、総資産を開いて」と聞くだけ!
アレクサ「現在の総資産は〜円です」
と答えてくれたらOKです
実際に使うと情報の取得に時間がかかりすぎるのでどうにかしたいですが…
ここは諦めています笑

さいごに

zaifの総資産を教えてくれるスキルを作りました
AIスピーカーで総資産を聞けるなんてとってもスマート!(笑)
あと、くれぐれもAPIキーを公開しないよう最新の注意を払ってくださいね
今回のスキル開発は全て自己責任でお願いします
スキルに必要なソースは全てgithubにまとめましたのでよければそちらを参考にして下さい!

仮想通貨のレートを答えてくれるスキル(仮想通貨チャート)も開発したので、ぜひインストールしてください!
今回の記事が参考になった方、投げ銭お願いします!

XEM : NB5TFT3OELRHD7VEAF3OW3CTL2IL4UI6GBDBGZLV
MONA : Zc5T7wTJfFowmhSu7Tv21vbYD4dXsfLCJs
Counterpartyトークン : 1FsKWsuarQa57HXYvrSQegKNk21K46B8hX

続きを読む

Alexa からの smart home skill イベントを SQS でデバイスに送信して結果を受信 【Python】

Alexa のスマートホームスキルで呼ばれる lambda で取得したイベントを SQS を使ってデバイスに送る方法を紹介します。目的は AWS の勉強ですので正直プロダクトじゃない場合はこの辺は自作せず買った方が良い気がします。
これは以前の記事にも書きましたが AWS greengrass でもっときれいに実現できる(と思います)。一応 python でスマートホームスキル書いたのでそれの軽い解説もしておきます。

前提

  • python 3.6
  • Alexa での smart home skill の作り方は知っている
  • 私は初めてのホームスキル作成

参考サイト

Alexa のスキルに対応するコード

Alexa のメッセージが送られて来る Lambda の部分です。namespacenameに合わせて処理内容を書きます。今回は基本的に Discovery のイベント以外はラズパイに投げてしまい、ラズパイで色々な作業をします。

smartHomeSkill.py
def my_handler(event, context):
    logging("DEBUG", "Request", event)
    header = event['directive']['header']
    namespace = header['namespace']
    name = header['name']

    # Discovery request だったら Lambda で処理
    if namespace == ALEXA_DISCOVERY and name == DISCOVER:
        return handleDiscovery(event, context)

    # 今回は Lambda 側ではあまり作業はせずにイベントをデバイスに渡してしまう
    # configファイルの読み込み
    ini = configparser.ConfigParser()
    ini.read("./config.ini")

    # 現在(2018/01/14)日本語の Alexa スキルはオレゴンでしか実行できないので
    # 東京リージョンのSQSを使うためにリージョンを指定
    sqs = boto3.client('sqs', region_name='ap-northeast-1')
    requestQueUrl = ini['sqs']['requestQueUrl']
    responseQueUrl = ini['sqs']['responseQueUrl']

    # イベントを送信
    result = sendQueBody(sqs, requestQueUrl, event)
    if result is False:
        return createErrorResponse(event, 'INTERNAL_ERROR', "Failed to send request to the device")
    # 結果を受信
    response = receiveQueBody(sqs, responseQueUrl)
    if response is None:
        return createErrorResponse(event, 'INTERNAL_ERROR', "Failed to receive a device's response")

    logging("DEBUG", "Response", response)
    return response

requestQueUrlresponseQueUrlには SQS の URL を設定ファイルから読み取っています。https://sqs.ap-northeast-1.amazonaws.com/XXXXXXXXXX/YYYYYYYYYってかんじのやつです。
SQS クライアントは作った場所のリージョンを指定する必要があるので気をつけてください。
スキルの作成時にどこで実行するのかを選べるのでおそらくエッジロケーションで lambda は実行されていると思います。Far-east と指定ができるのでおそらく日本付近で実行されているのではないかと。なので SQS も日本に作っておくのが良いと思います。

まずは本題の SQS の部分の説明、次におまけでhandleDiscovery(event, context)を紹介します。

SQS の送受信

sendQueBody()receiveQueBody()の実装です。

utils.py
# -*- coding: utf-8 -*-
import json
import hashlib

# url で指定した SQS に body を json で送信
def sendQueBody(sqs, url, body):
    jsonBody = json.dumps(body)
    response = sqs.send_message(
        QueueUrl=url,
        DelaySeconds=0,
        MessageBody=(
            jsonBody
        )
    )

    # 送信に成功したか確認
    if response['MD5OfMessageBody'] != hashlib.md5(jsonBody.encode('utf-8')).hexdigest():
        logging("ERROR", "sendQueBody", "Failed to send sqs")
        return False

    return True

# SQS からメッセージを受け取る
# returnType: dict
def receiveQueBody(sqs, url):
    response = sqs.receive_message(
        QueueUrl=url,
        AttributeNames=[
            'SentTimestamp'
        ],
        MaxNumberOfMessages=1,
        VisibilityTimeout=0,
        WaitTimeSeconds=20
    )

    # キューになにもない場合
    if 'Messages' not in response:
        return None

    message = response['Messages'][0]
    body = json.loads(message['Body'])

    # メッセージを削除するための情報を取得
    receipt_handle = message['ReceiptHandle']

    # 場合によっては処理が完了してからメッセージを削除するが
    # 今回は受信した時点で削除する
    sqs.delete_message(
        QueueUrl=url,
        ReceiptHandle=receipt_handle
    )

    return body

SQS からデータを削除するタイミングは場合によると思いますが、今回は Alexa に命令したものが一回失敗したら次も失敗する想定だったのでリトライとか考えずに受信したら削除してしまいます。sqs.send_message()MD5OfMessageBodyによって送信ができているかが確認できます。

handleDiscovery(event, context)

Alexa にスマートホーム家電を見つけてなどと指示をしたときの処理をする部分です。これで「電気をつけて」などに対応できることを Alexa に伝えられます。対応する部分は別に実装する必要があります。

smartHomeSkill.py
# Alexa の Discovery request に対応するためのコード
# ここでこのスキルで使えるデバイスの内容を全て返してあげる。
def handleDiscovery(event):
    payload = {
        "endpoints": [
            {
                # 電気
                "endpointId": RIGHT_00["endpointId"],
                "manufacturerName": COMPANY_NAME,
                "friendlyName": RIGHT_00["friendlyName"],
                "description": "This is smart device",
                "displayCategories": [RIGHT_00["displayCategories"]],
                "capabilities": [{
                    "type": "AlexaInterface",
                    "interface": "Alexa",
                    "version": "3"
                },
                    {
                        "interface": "Alexa.PowerController",
                        "version": "3",
                        "type": "AlexaInterface",
                        "properties": {
                            "supported": [{
                                "name": "powerState"
                            }],
                            "retrievable": True
                        }
                    }
                ]
            },
            {
                # TV
                "endpointId": TV_00["endpointId"],
                "manufacturerName": COMPANY_NAME,
                "friendlyName": TV_00["friendlyName"],
                "description": "This is smart device",
                "displayCategories": [TV_00["displayCategories"]],
                "capabilities": [{
                    "type": "AlexaInterface",
                    "interface": "Alexa",
                    "version": "3"
                },
                    {
                        "interface": "Alexa.PowerController",
                        "version": "3",
                        "type": "AlexaInterface",
                        "properties": {
                            "supported": [{
                                "name": "powerState"
                            }],
                            "retrievable": True
                        }
                    }
                ]
            },
            {
                # エアコン
                "endpointId": THERMOSTAT_00["endpointId"],
                "manufacturerName": COMPANY_NAME,
                "friendlyName": THERMOSTAT_00["friendlyName"],
                "description": "This is smart device",
                "displayCategories": [THERMOSTAT_00["displayCategories"]],
                "capabilities": [{
                    "type": "AlexaInterface",
                    "interface": "Alexa",
                    "version": "3"
                },
                    {
                        "interface": "Alexa.PowerController",
                        "version": "3",
                        "type": "AlexaInterface",
                        "properties": {
                            "supported": [{
                                "name": "powerState"
                            }],
                            "retrievable": True
                        }
                    }
                ]
            }
        ]
    }
    header = event['directive']['header']
    header['name'] = "Discover.Response"
    logging("DEBUG", 'Discovery Response: ', ({'header': header, 'payload': payload}))
    return {
        'event': {'header': header, 'payload': payload}
    }

所感

Alexa スキルが Lambda で実装されていることをしり、面白そうだなと Alexa 予約してスキルを作っています。JSONでの送受信フォーマットが違うと Alexa は毎回「XXの応答がありません」としか返してくれないのでデバックがやりにくいです。またいざ自分で作ってみると日本語に対応していない API が多いようでがっかりしています。早く対応してほしい。これからさかんになる IoT の実装を体験できたのは良い勉強だったのではないでしょうか。

まとめ

今回は Lambda から SQS の送受信を説明しました。また Alexa に Discovery 部分も軽く紹介しました。今度はラズパイでの家電を実際に動かす実装を紹介します。

続きを読む

[日本語Alexa] 私の部屋の蛍光灯のリモコンが壊れたので・・・Echoで操作できるようにした

1 私の部屋の蛍光灯のリモコンが壊れました・・・ 私の部屋の蛍光灯は、壁とかにスイッチが無く、リモコンでしか操作できなかったのですが、その大事なリモコンが壊れてしまいました。 幸い、2階の部屋の蛍光灯が同じリモコンだった […] 続きを読む

AWSとAzureとGCPを比較してみる – FaaS編

FaaSについてAWSとAzureとGCPを比較してみました。

注)

1. FaaS比較表

AWS Azure GCP
Lambda Functions Cloud Functions***
言語 Python,
node.js,
java,
C#,
go
ランタイムバージョン1.X
C#,
JavaScript,
F#,
Python*,
PHP*,
TypeScript*,
バッチ (.cmd、.bat)*,
Bash*,
PowerShell*

ランタイムバージョン2.X
C#**,
JavaScript**,
Java**
node.js***
最大実行時間 5分 10分 (従量課金プラン)
無制限 (App Serviceプラン)
9分
直接HTTPアクセスを受け付けるか 受け付けない(API Gatewayと連携必要) 受け付ける 受け付ける
トリガー Amazon S3,
Amazon DynamoDB,
Amazon Kinesis Data Streams,
Amazon Simple Notification Service,
Amazon Simple Email Service,
Amazon Cognito,
AWS CloudFormation,
Amazon CloudWatch Logs,
Amazon CloudWatch Events,
AWS CodeCommit,
スケジュールされたイベント (Amazon CloudWatch Events を使用),
AWS Config,
Amazon Alexa,
Amazon Lex,
Amazon API Gateway,
AWS IoT ボタン,
Amazon CloudFront,
Amazon Kinesis Data
Blob Storage,
Cosmos DB,
Event Hubs,
HTTP,
Microsoft Graph Events(2.Xのみ),
Queue storage,
Service Bus,
Timer,
Webhooks(1.Xのみ)
HTTP,
Cloud Storage,
Cloud Pub/Sub

*試験段階
**プレビュー
***ベータ

2. 対応言語の比較

言語の種類は試験段階とプレビューを含めればAzureが一番多いのですが、正式リリースされたものに限定すればAWSの方が種類が多いです。
一方GCPは機能自体がベータリリースなので、まだこれからといった感じでしょうか。

AzureはBashにも対応しているのが特徴です。運用系のシェルスクリプトをFaaS化すれば、スクリプト用のサーバが不要になりますね。

3. 最大実行時間

最大実行時間はAzureの10分(要host.jsonのfunctionTimeoutプロパティ変更)、GCPの9分に対しAWS Lamdbaは5分と約半分です。実際にAWS Lambdaを利用していると5分の壁を結構感じます。この点は他クラウドが羨ましいですね。
2017年のRe:InventでAWSはFargateというコンテナのサービスをリリースしましたが、このサービスがlambdaが5分以上実行できないことに対するAWSからの回答のように感じます。

4. 直接HTTPアクセスを受け付けるか

AWS lambdaだけ直接HTTPアクセスを受け付けることができません。HTTPアクセスを受け付けるには、API Gatewayと連携する必要がありますが、多機能な分やや設定が面倒な印象です。(但しAPI経由でLambdaを起動することは可能)

まとめ

AWS Lambdaのリリース後、Azure・GCP・Bluemix(現IBM Cloud)は超特急で追従しました。AWS LambdaがIT業界に与えたインパクトはとても大きかったと思います。
現在は「FaaS無ければばクラウドにあらず」といったところでしょうか。

また、AWS GreengrassやAzure IoT Edge**というエッジにデプロイするサービスも出てきています。
将来AWS LambdaがiPhoneやApple Watchにデプロイできるようにならないかなーと妄想中です。

**プレビュー

続きを読む

Alexa Home Skill では Payload Version v3 を利用する

Alexa Home Skill を使ってみようとした時に詰まったのでメモ.

Payload Version v2 のサイトは古い

いくつか説明サイトがあるがレスポンスの
Payload Versionv2にしているものは古いので公式説明ページを参照することをおすすめします.

現在(2018/01/12)は,Lambdaに用意されているalexa-smart-home-skill-adapterテンプレートも古いままで,これをそのまま使うと versionがv2なので Alexa スキルのデフォルト設定の v3 にしているとデバイスを見つけてくれないので注意が必要.公式説明ページにLambdaの書き換えるコードも書いてあります.

追記(実際にアレクサでスキルを試す場合)

Alexa Skill を作ってみる分には公式説明ページで十分でしたが,実際にコードをアレクサで試す場合には公式ページではレスポンスをうまく返せていないのでAlexaは「○○は応答していません」とエラーを返します.そこでLambdaの関数を書き換えました.基本は公式ページのコードを引用しています.

index.js
exports.handler = function(request, context) {
    if (request.directive.header.namespace === 'Alexa.Discovery' && request.directive.header.name === 'Discover') {
        log("DEGUG:", "Discover request", JSON.stringify(request));
        handleDiscovery(request, context, "");
    }
    else if (request.directive.header.namespace === 'Alexa.PowerController') {
        if (request.directive.header.name === 'TurnOn' || request.directive.header.name === 'TurnOff') {
            log("DEBUG:", "TurnOn or TurnOff Request", JSON.stringify(request));
            handlePowerControl(request, context);
        }
    }

    function handleDiscovery(request, context) {
        var payload = {
            "endpoints": [{
                "endpointId": "demo_id",
                "manufacturerName": "Smart Device Company",
                "friendlyName": "電気",
                "description": "smart switch",
                "displayCategories": ["SWITCH"],
                "cookie": {
                    "key1": "arbitrary key/value pairs for skill to reference this endpoint.",
                    "key2": "There can be multiple entries",
                    "key3": "but they should only be used for reference purposes.",
                    "key4": "This is not a suitable place to maintain current endpoint state."
                },
                "capabilities": [{
                        "type": "AlexaInterface",
                        "interface": "Alexa",
                        "version": "3"
                    },
                    {
                        "interface": "Alexa.PowerController",
                        "version": "3",
                        "type": "AlexaInterface",
                        "properties": {
                            "supported": [{
                                "name": "powerState"
                            }],
                            "retrievable": true
                        }
                    }
                ]
            }]
        };
        var header = request.directive.header;
        header.name = "Discover.Response";
        log("DEBUG", "Discovery Response: ", JSON.stringify({ header: header, payload: payload }));
        context.succeed({ event: { header: header, payload: payload } });
    }

    function log(message, message1, message2) {
        console.log(message + message1 + message2);
    }

    function handlePowerControl(request, context) {
        // get device ID passed in during discovery
        var requestMethod = request.directive.header.name;
        // get user token pass in request
        var requestToken = request.directive.endpoint.scope.token;
        var powerResult;

        if (requestMethod === "TurnOn") {

            // Make the call to your device cloud for control 
            // powerResult = stubControlFunctionToYourCloud(endpointId, token, request);
            powerResult = "ON";
        }
        else if (requestMethod === "TurnOff") {
            // Make the call to your device cloud for control and check for success 
            // powerResult = stubControlFunctionToYourCloud(endpointId, token, request);
            powerResult = "OFF";
        }

        var response = {
            "context": {
                "properties": [{
                    "namespace": "Alexa.PowerController",
                    "name": "powerState",
                    "value": powerResult,
                    "timeOfSample": "2017-02-03T16:20:50.52Z",
                    "uncertaintyInMilliseconds": 500
                }]
            },
            "event": {
                "header": {
                    "namespace": "Alexa",
                    "name": "Response",
                    "payloadVersion": "3",
                    "messageId": "5f8a426e-01e4-4cc9-8b79-65f8bd0fd8a4",
                    "correlationToken": "dFMb0z+PgpgdDmluhJ1LddFvSqZ/jCc8ptlAKulUj90jSqg=="
                },
                "payload": {}
            }
        };


        log("DEBUG", "Alexa.PowerController ", JSON.stringify(response));
        context.succeed(response);
    }
};

これで「電気をつけて」とAlexaにお願いすると「はい」と返答してくれます.

続きを読む

Raspberry pi model b+ では AWS greengrass できなかった

ずっと眠っていたラズパイを活用しようとAWS Greengrass利用しようとしたら条件満たせず使えませんでした.
(できるかわからないけど)Alexa スキルの Lambda をラズパイで実行するつもりでしたが作戦を変える必要がありそうです.

CPUアーキテクチャ条件

よし,使うぞ!とアーキテクチャ調べたら即条件を満たせず.GreengrassがサポートしているアーキテクチャはARMv7lだそうです.

pi@raspberrypi:~ $ arch
armv6l

続きを読む

【神戸2/11】Alexa Day 2018にて当社の大石、坂本が登壇いたします

【Alexa Day 2018とは】 Alexa Day 2018は、米国をはじめとして世界中で爆発的な人気となっており、今後国内のAI技術に対して大きなインパクトを与えることが予想されるAIアシスタントの「Alexa」やこれを支えるServerlessアーキテクチャ・LEX・AIやMLなどの技術や最新情報の共有を目的として、AWSの日本のユーザー … 続きを読む

AWS SDK で SQS を Python で操作する

今回はPythonのAWS SDKでSQSメッセージの送受信を行います.

前提

  • aws cli の configure を済ませている

ソースコード

設定はconfigparserモジュールを使用します.

config.ini
[sqs]
url : https://sqs.ap-northeast-1.amazonaws.com/YYYYYYY/XXX

それではソースコードです.

sqs.py
# -*- coding: utf-8 -*-

import configparser
import boto3
import json

# configファイルの読み込み
ini = configparser.SafeConfigParser()
ini.read("./config.ini")

sqs = boto3.client('sqs')
url = ini.get("sqs", "url")

# 送信するJSON
body = {"type": "Right", "Action": "On"}

# SQSへJSONの送信
response = sqs.send_message(
    QueueUrl=url,
    DelaySeconds=0,
    MessageBody=(
        json.dumps(body)
    )
)

# SQSからJSONを受信
response = sqs.receive_message(
    QueueUrl=url,
    AttributeNames=[
        'SentTimestamp'
    ],
    MaxNumberOfMessages=1,
    VisibilityTimeout=0,
    WaitTimeSeconds=0
)

message = response['Messages'][0]
body = json.loads(message['Body'])
print(body)

# メッセージを削除するための情報を取得
receipt_handle = message['ReceiptHandle']

# メッセージを削除
sqs.delete_message(
    QueueUrl=url,
    ReceiptHandle=receipt_handle
)

実行します.

$python3 sqs.py
{'Action': 'On', 'type': 'Right'}

無事JSONを受信できました.
Alexaを使って遊ぼうと考えているので今日はその準備にSQSを使用してみました.

参考文献

続きを読む

【レポート】Alexa賞の裏側にある科学: AIチャレンジ #reinvent #ALX320

Alexa賞を獲得するために このセッションは、Alexaは何なのか、どこを目指しているのか、どのような仕組みで動いているのか解説した後、コンペティションであるAlexa賞のグランプリ獲得に向けた最終候補者の紹介を行って […] 続きを読む