するめごはんのIT日記

主にITネタを書いていくのさ

Alexaで位置情報を取得するReal-Time Location Services for Alexa Skillsの設定と流れるJSON

ごきげんよう

この記事は
ADVENTARの「するめごはんのVUI・スマートスピーカー Advent Calendar 2018」
の24日目の記事です。

AlexaDevSummitの1日目で会場をふらふらしてたら、岡本さんから
「showさんやばいっす!これみてくださいよ!!」
と、ノートPCを開きながらめっちゃ興奮されてたので、そのPCをみたらAlexaが位置情報に対応してた画面でした。

岡本さんありがとうございました。

僕もその日の夜に「おおおー」となってました。

で、既に試されてる方々がブログに上がっているわけですが、僕は僕で改めて試して、流れるJSONがたしかに変わったなーと思ったのでここに記載します。

Real-Time Location Services for Alexa Skills

これについては公式のサイトをみてください。

Enhance Your Customer Experience with Real-Time Location Services for Alexa Skills : Alexa Blogs

先に確認できた記事類

これらをみればわかると思います。

■情報提供元、岡本さんの記事
・Alexaから位置情報を取得可能になったので試してみた

alexa.wp-kyoto.net

■さっそくためした @MYamate_jp さんの記事
・Alexa Real Time Location Serviceを使う

qiita.com

■ @MYamate_jp さんの他の記事
・Alexa Skill開発でよく使いそうな関数をまとめたnpmモジュールを作った

qiita.com

僕も試してみた

まず、APL対応した画面を、スマートスピーカーを遊びたおす会で録画したかったがためのスキル
「ヒロインのテスト」
で、試します。

が、別に何でもいいです。SDK v2であるだけです。

1.Alexa Developer Consoleのアクセス権限を押す

いつもの左下箇所です。

f:id:surumegohan:20181224203450p:plain
アクセス権限を押す

2.位置情報のトグルをONにする

いつの間にか、しれっとでてきていた、位置情報サービスのトグルをONにします。

f:id:surumegohan:20181224203514p:plain
位置情報サービスのトグルをON

3.LambdaでJSONを確認してみる

単純にJSONの中身を出力します。

console.log(JSON.stringify(handlerInput));

LaunchRequestのHandlerにでも入れてみてください。

4.スマホからスキルを起動する

スマホからスキルを起動してみたら "alexa::devices:all:geolocation:read" が追記されていました。
が、アクセス権を許可していないのでDENIEDになっています。

    "requestEnvelope": {
        "version": "1.0",
        "session": {
            "new": true,
            "sessionId": "amzn1.echo-api.session.fad41c95-9292-XXXXXXXXXXXXXXX",
            "application": {
                "applicationId": "amzn1.ask.skill.fd6f4522-518c-XXXXXXXXXXXXXX"
            },
            "user": {
                "userId": "amzn1.ask.account.XXXXXXXXXXXXXXXX",
                "permissions": {
                    "scopes": {
                        "alexa::devices:all:geolocation:read": {
                            "status": "DENIED"
                        }
                    }
                }
            }
        },
        "context": {
            "System": {
                "application": {
                    "applicationId": "amzn1.ask.skill.fd6f4522-518c-40b3-8508-71e6d205ed1e"
                },
                "user": {
                    "userId": "amzn1.ask.account.XXXXXXXXXXXXXX",
                    "permissions": {
                        "scopes": {
                            "alexa::devices:all:geolocation:read": {
                                "status": "DENIED"
                            }
                        }
                    }
                },
                "device": {
                    "deviceId": "amzn1.ask.device.XXXXXXXXXXXXXXXXXX",
                    "supportedInterfaces": {
                        "Geolocation": {}
                    }
                },
                "apiEndpoint": "https://api.fe.amazonalexa.com",
                "apiAccessToken": "XXXXXXXXXXXXXXXXXXXXXXXXX"
            }
        },

5.スマホのAlexaで位置情報を許可してみる

スマホ(僕はAndroid)のAlexaスキルアプリを呼び出して、スキルへの位置情報提供を許可します。

一応、スクリーンショットをとってみました。
まずは起動してメニューを表示しましょう。

f:id:surumegohan:20181224203616p:plain
いつも通りまずはメニューを開く

上記で「スキル・ゲーム」を選びます。

f:id:surumegohan:20181224203645p:plain
お馴染みのスキルストア

はい。いつもの画面です。
ここでいつも通り、有効なスキルを選びましょう。

今回の「ヒロインのテスト」は開発中のスキルなので
開発スキル を選びます。

f:id:surumegohan:20181224203715p:plain
開発中のスキルを選択

そして、画面をスクロールして目的のスキルを選びましょう。

f:id:surumegohan:20181224203742p:plain
自分の開発中のスキルを選ぶ

目的のスキルを選んだら、グレーになっている「設定」をタップ。
※個人的に選べなさそうな色にみえる。

f:id:surumegohan:20181224203804p:plain
「設定」をタップ

Alexa Developer Consoleで位置情報サービスのトグルをONにしているので、権限を聞かれます。
ここで位置情報サービスの権限を許可して保存します。

f:id:surumegohan:20181224203826p:plain
位置情報サービスの権限を許可する

スマホで位置情報が取得できているなら、位置情報サービスのアクセス権に許可がされたことが確認できます。

f:id:surumegohan:20181224203849p:plain
有効にされた画面

6.もう1回、スマホからスキルを呼び出してみる

再度スマホから、「ヒロインのテスト」を起動します。

するとJSONが変化しています。

"alexa::devices:all:geolocation:read"がGRANTEDに変化して、位置情報のJSONとしてGeolocationが追記されています。

※位置情報の有効桁数はそのままで、適当な数字をいれてます。

    "requestEnvelope": {
        "version": "1.0",
        "session": {
            "new": true,
            "sessionId": "amzn1.echo-api.session.0c9XXXXXXXXXXXXXXXX",
            "application": {
                "applicationId": "amzn1.ask.skill.XXXXXXXXXXX"
            },
            "user": {
                "userId": "amzn1.ask.accounXXXXXXXXXXXXXXXXXXXXXX",
                "permissions": {
                    "consentToken": "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
                    "scopes": {
                        "alexa::devices:all:geolocation:read": {
                            "status": "GRANTED"
                        }
                    }
                }
            }
        },
        "context": {
            "System": {
                "application": {
                    "applicationId": "amzn1.asXXXXXXXXXXXXXXXXXXXXXXXX"
                },
                "user": {
                    "userId": "amzn1.ask.account.XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
                    "permissions": {
                        "consentToken": "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
                        "scopes": {
                            "alexa::devices:all:geolocation:read": {
                                "status": "GRANTED"
                            }
                        }
                    }
                },
                "device": {
                    "deviceId": "amzn1.ask.device.AXXXXXXXXXXXXXXXXXXXXXXXXX",
                    "supportedInterfaces": {
                        "Geolocation": {}
                    }
                },
                "apiEndpoint": "https://api.fe.amazonalexa.com",
                "apiAccessToken": "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
            },
            "Geolocation": {
                "timestamp": "2018-12-24T10:12:06Z",
                "coordinate": {
                    "latitudeInDegrees": 79.1235586,
                    "longitudeInDegrees": 283.1111355,
                    "accuracyInMeters": 33.1111104196167
                },
                "altitude": {
                    "altitudeInMeters": 40.111111525878906,
                    "accuracyInMeters": 29.23455004196167
                },
                "heading": {
                    "directionInDegrees": 0
                },
                "speed": {
                    "speedInMetersPerSecond": 0
                }
            }
        },
        "request": {
            "type": "LaunchRequest",
            "requestId": "amzn1.echo-api.request.XXXXXXXXXXXXXXXXXXX",
            "timestamp": "2018-12-24T10:13:44Z",
            "locale": "ja-JP",
            "shouldLinkResultBeReturned": false
        }
    },

位置情報っぽいような数値がたくさんでてきましたが、個人的にアツいと思っているのが
speedInMetersPerSecond です。

まだドキュメントを読んでいないので正確なことはわからないのですが、そのまま解釈すると1秒当たりの移動速度がメートル単位ででてくる・・・??マジで?

となると、例えば車などの乗り物に搭載されたAlexaデバイスとかも想定してるのだろうかと個人的には考えています。

というわけで

位置情報を使ったスキルを組み込むことが可能になったと思われます。
もちろん日本語版で。

となるとエラーハンドリングとかも大変そうですが、IoTやりたい勢としては実世界との融和性が高まるので・・オラ、わくわくしてきたぞ!

以上です。

技術書典5で買ったスマートスピーカー系の本を読み直した

ごきげんよう

この記事は ADVENTERの「するめごはんのVUI・スマートスピーカー Advent Calendar 2018」 の23日目の記事です。

技術書典6に向けて、次にやるならアレをやろうみたいな話がそろそろ僕の周囲でも具体的な話がでてきています。

技術書典5では、「スマートスピーカーを遊びたおす本」を出させて頂きましたが、他にもスマスピ・VUI系の本は技術書典5にて購入していました。

もちろん、1度読みましたが、今回改めて読み直したので、それぞれの内容等にざっくり触れたいと思います。

書評するような立場ではないですし、100%個人の感想です。

そもそも技術書典って何?

今の時期ならコミケが着目される時期で、要するに同人誌即売会です。 それの技術書のみが集まるイベントです。

技術書典

僕が参加した技術書典5は1万人の来場者がいらっしゃいました。

以下では、その場で買ったスマートスピーカー・VUI本に触れていきます。

1.GoogleHomeプチハック

f:id:surumegohan:20181224180308p:plain
GoogleHomeプチハック

入ってすぐのサイボウズさんのブースでまさかの200円。

サイボウズさんなので、kintoneの話なのですが、Google Homeアプリのインストール設定からActions on GoogleやDialogflowの設定までがフルカラーでめっちゃ丁寧に書いてあります。

VUI LT7で会場がサイボウズ社であったこともあり、著者のミケさんの登壇をその場で観させていただきましたし、サイボウズ社の森みたいな会場もすごかったわけです。

が、この本の内容、これ絶対200円じゃない。

プチハックと言いつつも、設定類はできますし、kintoneとの連携はもちろん記載されてます。

Google Homeの設定を知りたい、kintoneと連携したい等のモチベーションがあればイチオシです。

2.Raspberry PiではじめるDIYスマートホーム

f:id:surumegohan:20181224180336p:plain
Rasberry PiではじめるDIYスマートホーム

表紙これ、怒られないのかと思うんですけども、それはさておいて。

この本はAlexaを使ったスマートホームについて触れています。 そして、タイトル通り、Raspberry Piを使ったスマートホームの話です。

Alexaについても記載がされてますが、スキル作成はNode-REDです。 プログラミングに関する記述はむしろ bash しかないです。

Node-REDの使い方や、Raspberry Piで動かすコード、物理的な環境について語っている本です。

Node-REDでAlexaスキルを扱い方、Raspberry Piなどを使ってセンサー類を扱いたい方にはオススメです。

3.スマートスピーカーアプリのお品書き

f:id:surumegohan:20181224180415p:plain
スマートスピーカーアプリのお品書き

サインもらってますが、温泉BBAの方々の本です。

内容は大きく分けてVUIデザインの話と、Clovaのスキルの作り方の二部構成です。

VUIのデザイナーをなさっている元木さん記述部分はさすがデザイナーです。 音声アプリの企画の話から対話設計の話まで、大変濃密です。

特筆すべき点は 音声アシスタント3プラットフォームの機能別比較表 だと思います。 これの何が良いかというと、本当にこれからVUIをやろうとしたときに、どのプラットフォームで何ができるの?という壁に最初に当たるからです。 それが一目でわかります。

執筆時点での日本とアメリカの比較と、Clovaで何ができるかがまとまっています。

スキルの履歴書という発想も僕にはまったくなかったので大変参考になります。

第二部のClovaスキルの作成方法は画面キャプチャが丁寧なのですが、npmのエラー時の対応方法まで記載されているVUI本は他に観たことがないです。

Clovaの画面は変化が激しい状態にありますので、現状とは若干ことなりますが、この本を読んでおけば、スキル作成時に気を付けることが濃縮されている上に、本当に初心者向けに書いてありますので、これからVUIを始めたい方にはオススメです。

巻末の用語集も嬉しい記載です。

4.俺たちが知りたかったAlexaの話

f:id:surumegohan:20181224180444p:plain
俺たちが知りたかったAlexaの話

この本は、とんでもないです。 なんで2000円なんだ。安すぎる。

技術書典5で、この本だけは絶対に手に入れるつもりでいました。

Alexaチャンピオンの方々が関わっているというのもありますが、書いた人うんぬんよりも 内容がガチだから です。

例えば、一般的なVUIのデザインや設計だと「ハッピーパスを作りましょう」という感じですが、そんなレベルではないです。

代替パス、シーンの前提、ヘルプなどのパスについても書いてあります。 Experience flowの話は大変勉強になる上に、他にもダメな例、良い例など非常に具体的です。

デザイン面だけでも濃厚ですが、開発面はより一層深いところまで踏み込んでいます。

・ASK CLI
・SSMLの効果的な使い方
・DynamoDBを扱う場合にask-sdkaws-sdkのメリデメの比較
・開発手法
・粒度ごとのテスト
・ツール類の紹介

などなど極めて実践的です。
そして、最後にコミュニティの話と、アウトプットの話になっています。

Alexaでスキルを複数個リリースしてきて、市販本等に物足りなくなってきたら、この本は必読だと思います。

VUIのデザイン部分は他のプラットフォームでも考え方は同様になると思います。
極めて良書です。

読んでみて思ったことは

技術書典5は、そもそも僕自身の人生経験として「同人誌即売会」が初でした。
そしていきなり執筆&編集をやらせてもらい、リアルお仕事にもつながりました。

技術者にとって、本を書くというのは一つの目標だと僕はずっと思っていたわけですが、上記4冊のように様々な人たちが様々な視点で本を書いています。

アウトプットというと、ブログやイベント登壇などが個人的には思いがちなのですが、技術書典に1回本をだしてみると得られることがありますし、自分が経験したからこそ上記の本の「すごさ」がわかる面もあると思います。

というわけで、Qiitaなどのブログ類以外にも、本という形でアウトプットしてみてはいかがでしょうか。

以上。

もしビブリオバトルするなら選ぶ予定のAlexaスキル

ごきげんよう

この記事は ADVENTARの「するめごはんのVUI・スマートスピーカー Advent Calendar 2018」 の22日目の記事です。

来年の某イベントで、自分のオススメのスキルを選んで、それを集まったみんなで試すというビブリオバトル なイベントをやる流れなのですが、そこで紹介しようと思っているスキルをここに記載しておきます。

今年1年、いろんなイベントで、いろんなVUIのスキル・アプリを観てきました。
イベントで見たのもあれば、人から勧められたのもありますが、僕が知る中で、個人単位で、 色んな意味でもっともがんばってる と勝手に思ってるAlexaスキルのうちの1つは

強欲なうさぎの迷路

だと思ってます。

Amazonのスキルストアの結果

以下は、Amazonのサイトで「強欲なうさぎの迷路」の検索結果です。

f:id:surumegohan:20181223220646p:plain
強欲なうさぎの迷路

これ、審査通るのかよ!w という内容です。

詳細説明を読んでみると凄すぎる

以下の文言で掲載されてるのが凄すぎる。

・Wi***dry彷彿とさせる
・画面がなくても難易度は高いですが遊べます
・方眼紙でマップ作製
・アイテムの名称はランダムで65536通り

知った機会

VUI LT7で作者がこのスキルについて登壇なさってました。

■VUI LT7

iotlt.connpass.com

何がすごいかは見ればわかる

Echo Showでのプレイ動画をあげました

www.youtube.com

あのゲームを再現しているのがわかります。
これをEcho Dotなどの音声のみでやらせるという流れがすごい。

僕は画面があってもわかんないですからね。。。

画像作成にこだわりまくっている

上記connpassのスライドにあるのですが、

・通勤電車内でドット絵を描いた
・迷路画像256パターン全部作った
・迷路生成ロジックがガチだし、0.5msで生成される

VUIのイベントで、「ビット演算」とかこのセッション以外聞いたことないです。

APL対応したらどうなるのか

このスキルをAPL対応させるとどうなるのか、ものすごい興味があります。
たぶん、現時点での描画の方があえていいんでしょうけども。

がんばってる感は半端ない

VUIでWi***dryをやるという発想から、ビット演算とかドット絵とか考え抜いて、審査通過して公開されてるわけです。

埋もれてしまうには、あまりにも残念なので、ご関心ある方はプレイしてみてください。

まったく関係ないけど、PS Vita版もあったんですね

懐かしくてググってたら、PS Vita版があったらしいです。

www.youtube.com

けど、A社なので、きっとものすごい難易度なんだろうなぁと勝手に思います。

以上。

深夜テンションで創ってAmazonさんの審査員さんに協議させてしまった「ノリノリのサンタ」の話

ごきげんよう

この記事は ADVENTARの「するめごはんのVUI・スマートスピーカー Advent Calendar 2018」 の21日目の記事です。

Echo Showのために4つのスキルを作成した方々、お疲れ様です。

さて、今回は僕が完全に深夜テンションで作成したAlexaスキル「ノリノリのサンタ」について記載します。

相変わらず、AmazonJapanの方を大至急協議させてしまいましたので、それについても触れていきます。

Alexaスキル「ノリノリのサンタ」とは

f:id:surumegohan:20181221221530p:plain
ノリノリのサンタ

「なんかクリスマスのスキルを創るかー」
と思って、いらすとや ではない素材を探していたら、テンションが高そうなサンタクロースの画像素材を見つけました。

なので、サンタクロースとかクリスマスとかについての雑学をググって、まとめて、僕が深夜テンションで自分で声を収録しました

もちろん画面対応

Echo Showが来る前にリリースしたので、Echo Spotでの画面表示に対応しています。

テンション高めなサンタクロースの画像と、ノリノリな声がぞれぞれランダムに再生されます。

ソースコードの一部

めっちゃ一部ですが、たいしたことはしてないです。
ユーザーが初回起動か否かの判定と、操作説明をオーディオファイルで流しているだけです。
画像とオーディオファイルは別々にランダムで表示させています。

'use strict';

const Alexa = require('ask-sdk');
const AWS = require("aws-sdk");
const docClient = new AWS.DynamoDB.DocumentClient({region: 'ap-northeast-1'});


//音声を定義

//起動時
const smartmacchiato = '<audio src=\"https://s3XXXXXXX.mp3\" />';


//2回目以降、わしがのりのりサンタじゃあ
const washiganorinori = '<audio src=\"https://s3-XXXXXX.mp3\" />';

//初回ユーザー用説明
const first_user = '<audio src=\"https://s3-XXXXXXXXXXX.mp3\" />';

:
:
:

//トリビアの数
const norinori_01 = '<audio src=\"https://s3-XXXXXXXXXXX/norinori1.mp3\" />';
const norinori_02 = '<audio src=\"https://s3-XXXXXXXXXXX/norinori2.mp3\" />';
const norinori_03 = '<audio src=\"https://s3-XXXXXXXXXXX/norinori3.mp3\" />';

:
:
:

//トリビアのの音声配列
var norinori_speak_array = [
    norinori_01,
    norinori_02,
    norinori_03,
        :
        :
        :
];



//画像
const DisplayImg1 = {
      title: 'のりのりサンタ1',
      url: 'https://s3-XXXXXXXXXXXX/img/santa1.png'
    };
    
const DisplayImg2 = {
      title: 'のりのりサンタ2',
      url: 'https://s3-XXXXXXXXXXXX/img/santa2.png'
};

const DisplayImg3 = {
      title: 'のりのりサンタ3',
      url: 'https://s3-XXXXXXXXXXXX/img/santa3.png'
};

:
:
:


//サンタ画像の配列
var norinori_img_array = [
    DisplayImg1,
    DisplayImg2,
    DisplayImg3,
        :
        :
];


//////////////////////////////////////////////////////////////////////////


const LaunchRequestHandler = {
  canHandle(handlerInput) {
    return handlerInput.requestEnvelope.request.type === 'LaunchRequest';
  },
  async handle(handlerInput) {


    //サンタ話のうち1つをランダムで選ぶ
    var factSpeakArr = norinori_speak_array;
    var factSpearkIndex = Math.floor(Math.random() * factSpeakArr.length);
    var randomSpearkFact = factSpeakArr[factSpearkIndex];


    //サンタ画像のうち1つをランダムで選ぶ
    var factImgArr = norinori_img_array;
    var factImgIndex = Math.floor(Math.random() * factImgArr.length);
    var randomImgFact = factImgArr[factImgIndex];


    // Template 6
    if (supportsDisplay(handlerInput)){
      const myImage1 = new Alexa.ImageHelper()
        .addImageInstance(randomImgFact.url)
        .getImage();

      const myImage2 = new Alexa.ImageHelper()
        .addImageInstance(randomImgFact.url)
        .getImage();

      const primaryText = new Alexa.RichTextContentHelper()
        .withPrimaryText('')
        .getTextContent();

        handlerInput.responseBuilder.addRenderTemplateDirective({
        type: 'BodyTemplate6',
        token: 'string',
        backButton: 'HIDDEN',
        backgroundImage: myImage2,
        image: myImage1,
        title: "",
        textContent: primaryText
      });
    }
     


    //JSONを扱う関連      
    let handlerInput_json = await JSON.stringify(handlerInput, null, 2);
          
    (略)

    let norinori_start = washiganorinori + randomSpearkFact;

    try{
        
        const queryItems = await docClient.query({
          TableName: "norinoriSantaTable", 
          KeyConditionExpression: "#userId = :userId",
          ExpressionAttributeNames: {"#userId": "userId"},
          ExpressionAttributeValues: {":userId": JSONのユーザーID}
        }).promise();
        
        try{
            
            console.log("queryItems.Items[0].userId: " + queryItems.Items[0].userId);
        
        } catch (err){ //よろしくない実装
            
            //初回ユーザー用のオーディオファイルにする
            norinori_start = first_user;
            
            console.log("user Nothing");
        }
        
    } catch(err){
        console.error(`[query Error]: ${JSON.stringify(err)}`);
    }
    

    //DynamoDBにputする情報
    var item = {
           userId: JSONからのユーザーID,
    };

    var params = {
        TableName: 'テーブル名',
        Item: item
    };

    //DynamoDBにPut
    await putDynamo(params);

    //sessionAttributeを、起動後であることを示すように格納
    var sessionAttribute = '';
    
        sessionAttribute = {
        "SHA_state": "after_start"
        };
    
    handlerInput.attributesManager.setSessionAttributes(sessionAttribute); 

    //しゃべる音声スキルと、間に0.7秒の待機を挟み、ノリノリの話とユーザーへの操作説明をする
    let speechText = smartmacchiato + '<break time="0.7s"/>' + norinori_start + ask_next;

    return handlerInput.responseBuilder
      .speak('<speak>' + speechText + '</speak>')
      .reprompt('<speak>' + speechText + '</speak>')
      .withShouldEndSession(false)
      .getResponse();
  }
};

    

//起動直後、本アプリの継続に「はい」「次」「もっと」「のりのり」「きかせて」と答えた場合の処理
const continueHandler = {
    canHandle(handlerInput) {
      
        return handlerInput.requestEnvelope.request.type === 'IntentRequest'
            && ((handlerInput.requestEnvelope.request.intent.name === 'AMAZON.YesIntent')
                || (handlerInput.requestEnvelope.request.intent.name === 'AMAZON.NextIntent')
                || (handlerInput.requestEnvelope.request.intent.name === 'AMAZON.MoreIntent')
                || (handlerInput.requestEnvelope.request.intent.name === 'norinoriIntent')
                || (handlerInput.requestEnvelope.request.intent.name === 'kikitaiIntent')
                )
            && handlerInput.attributesManager.getSessionAttributes().SHA_state == 'after_start';
    },
    async handle(handlerInput,event) {


    //サンタ話のうち1つをランダムで選ぶ
    var factSpeakArr = norinori_speak_array;
    var factSpearkIndex = Math.floor(Math.random() * factSpeakArr.length);
    var randomSpearkFact = factSpeakArr[factSpearkIndex];


    //サンタ画像のうち1つをランダムで選ぶ
    var factImgArr = norinori_img_array;
    var factImgIndex = Math.floor(Math.random() * factImgArr.length);
    var randomImgFact = factImgArr[factImgIndex];
    
    
    // Template 6
    if (supportsDisplay(handlerInput)){
      const myImage1 = new Alexa.ImageHelper()
        .addImageInstance(randomImgFact.url)
        .getImage();

      const myImage2 = new Alexa.ImageHelper()
        .addImageInstance(randomImgFact.url)
        .getImage();

      const primaryText = new Alexa.RichTextContentHelper()
        .withPrimaryText('')
        .getTextContent();

        handlerInput.responseBuilder.addRenderTemplateDirective({
        type: 'BodyTemplate6',
        token: 'string',
        backButton: 'HIDDEN',
        backgroundImage: myImage2,
        image: myImage1,
        title: "",
        textContent: primaryText
      });
    }
    
        //ランダムに話して、ユーザー操作を促す
        const speechText = randomSpearkFact + ask_next;
        
        return handlerInput.responseBuilder
              .speak('<speak>' + speechText + '</speak>')
              .reprompt('<speak>' + speechText + '</speak>')
              .withShouldEndSession(false)
              .getResponse();
    
    }
};




//DynamoDBにputする関数
function putDynamo(params) {
    
    console.log("=== putDynamo function ===" + params);
    
    docClient.put(params, function (err, data) {
    
        console.log("=== put ===");
    
        if (err) {
            console.log(err);
        } else {
            console.log(data);
        }
    });

}


// returns true if the skill is running on a device with a display (show|spot)
function supportsDisplay(handlerInput) {
  var hasDisplay =
    handlerInput.requestEnvelope.context &&
    handlerInput.requestEnvelope.context.System &&
    handlerInput.requestEnvelope.context.System.device &&
    handlerInput.requestEnvelope.context.System.device.supportedInterfaces &&
    handlerInput.requestEnvelope.context.System.device.supportedInterfaces.Display;

  console.log("Supported Interfaces are" + JSON.stringify(handlerInput.requestEnvelope.context.System.device.supportedInterfaces));
  return hasDisplay;
}

リジェクト内容で協議させてしまうパターン

僕にとっては、もう慣れているので構わないのですが、リジェクト理由に納得がいかずに、進言したら協議の上、承認されました。

リジェクト理由は 「ノリノリ」は名詞ではないから。

日本語でスキルの呼び出し名を決める際には、原則として名詞を2つにする必要があります。

ヒロイン告白」みたいな。 「の」は無くてもOKの場合も多いです。

で、納得いかないので以下のように伝えたら承認されました。

1.「ノリノリ」は名詞でも使われる

goo辞書で検索すると、以下のように記載されてます。

[名・形動]《動詞「乗る」の連用形を重ねた語。「ノリノリ」と書くことも多い》調子がよくて気分が高揚していること。乗りがよくて、リズミカルであること。また、そのさま。いけいけ。「乗り乗りな曲で踊る」「乗り乗りムードで一気に勝ち進む」

[名・形動]ってあるじゃん。

2.テンションが高い場合はどう表現するのか

テンションが高い状態を示す場合、具体的にAmazonさんはどういう表現なら良いのか求めました。

そしてその際に、

仮に「ハイテンションなサンタ」にした場合、【ハイテンション】こそ日本語ではないと私は解釈します。 日本語のみで状況・状態を説明するための具体的なガイドラインをください。

と、伝えました。

そしたら通った

いつも通り?、「大至急協議します」のメールが飛んでくるので、しばし待ちます。 Amazonさんから「大至急協議します」のメールが来た場合、たいていその日のうちに返答がきます。

今回は 「協議の上、認められることになりましたので、再申請をお願いします。」

とのことで、再申請したら通りました。

その後のAlexaDevSummitでつかまる

AlexaDevSummitで会場をウロウロしてたら、中の人からお声がけがありました。

A「showさんですよね!この間の件ですが・・・」
僕「(やらかしまくっているので)どの件でしょうかごめんなさi・・・」
A「ノリノリの件です。ご指摘ありがとうございました。あれはたしかに認められないと表現できないですよね。フィードバックありがとうございました!」
僕「こちらこそ、ご対応ありがとうございましたぁぁぁ!!!」

全力で土下座する体制にしようとしましたが、むしろ感謝されました。

何が言いたいかというと、最近、スキル審査結果に対してフィードバックが画面ポチポチで選べるようになったんですけども、審査員側だって、ユーザーからのご意見が欲しいわけです。

明らかに開発者側のミスは置いておいて、リジェクトされたから黙って従うだと、AmazonさんのAlexaスキル審査員さんが独りよがりの神になってしまうわけです。

なので、ちゃんと根拠を示して、自分の意見を伝えると、AmazonのAlexa担当の方々はちゃんと対応してくれますよ。

リジェクトを頂いても、認定されても、フィードバックは送りましょう。 その方がお互いにハッピーだと思います。

以上。

Alexaデザインガイドの本は他のプラットフォームでも応用できる

ごきげんよう

この記事は ADVENTARの「するめごはんのVUI・スマートスピーカー Advent Calendar 2018」20日目の記事です。

先日のAlexaDevSummitで以下の本が配られました。

f:id:surumegohan:20181221113408p:plain
デザインガイドβ

中身は基本的に以下のWebページと同様です。

紙の物理本ですので、オンラインの方が最新であることは確かでしょう。

■Alexaデザインガイド
developer.amazon.com

もう1回振り返るのに最適

Alexaでスキルを作成している方々はすでに上記サイトは確認済みとは思いますが、画面が付いたりマルチモーダルになってきたAlexaについてのデザインガイドは再度読んでおくと、新しい発見があると思います。

もちろん、これからスキルを作成する方々にも極めて有用です。

他にも公式のこのドキュメントは目を通すべき

上記のデザインガイドはもちろん有用ですが、以下も併せて確認することをオススメします。

■音声デザインガイド
Amazon Alexa Voice Design Guide

特にチェックリストが載っているのが良いと思われます。

Alexa以外のプラットフォームでも共通部分は多い

もちろんこれらのドキュメントはAmazonのAlexa用のドキュメント類です。

とはいえ、音声デザインに関しては他社プラットフォームに共通している部分も多く、上記のドキュメントをしっかり読んでいれば他社プラットフォームでも応用が十分聞くと思います。

特にLINE系の場合はClovaに限らず、Messaging APIやLIFFなどに置き換えることもある程度できるのではないでしょうか。

以上

IT系イベント実施の会場設営チェックリストを作った

ごきげんよう

この記事は ADVENTARの「するめごはんのVUI・スマートスピーカー Advent Calendar 2018」 の19日目の記事です。

風邪ひいて遅れました。。。

さて、もともとアドベントカレンダーである程度の人が見てくれるようになってきたら投稿したいと思っていた記事を今回は投稿します。

直接的にはVUIも技術も関係ないですが、技術イベントを開催する際に会場設営で必要なチェックリスト をまとめました。

今年、様々なVUI・スマートスピーカー系のイベントの運営スタッフや主催をやらせてもらいまして、そこで会場設営時のノウハウがたまってきたのでまとめました次第です。

VUI系なので音を気にすることも多いですが、他のイベントでも応用できるリストであると考えておりますので、ガンガン共有します。

技術系イベントを開催時の AWS Well-Architected Framework みたいな感じのチェックリスト として、よしなに参考にしてください。

https://github.com/surumegohan/event_know-how

様々なIT系のイベントでは運営している人たちは上記のようなことを気を配って実施しています。
なので慢性的にスタッフが足りてないと思いますので、この記事を読んだ方が、イベント実施者に「こんなリストがあるよ」とか「ここらへんは手伝える」とか、そういう流れになってくれると嬉しいです。

また、他にも自分はこんなことを気を付けてるぜ!とか、こんなことがあったぜ!とかありましたら、コメントをくれるかプルをリクってください。

以上

【深堀版】スマートスピーカーを遊びたおす会で話した「ヒロインとデート」のVUI設計思想

ごきげんよう showです。

この記事は ADVENTERの「するめごはんのVUI・スマートスピーカー Advent Calendar 2018」 の18日目の記事です。

昨日、「スマートスピーカーを遊びたおす会」のVol.4を開催させて頂きました。

■イベントのconnpass

connpass.com

■デモ動画

www.youtube.com

そして、そこで流したデモ動画についての記事を先ほど「スマートスピーカー 2 Advent Calendar 2018」に投稿しました。

スマートスピーカー 2 Advent Calendar 2018

qiita.com

僕が考えているVUIデザインについて記載しています。

スマートスピーカーを遊びたおす会で話した「ヒロインとデート」のVUI設計思想

qiita.com

で、今この画面で開いているこの記事は僕のアドベントカレンダーなので、上記記事をもっと深堀します。

VUIデザインについて再掲

上記記事でも触れましたが、VUIのデザインはユーザーの想定外の対話や、1ショット起動のため、フラット型をベースにする方がいいと僕は考えています。

f:id:surumegohan:20181218122855p:plain
VUIデザインはワンショットも対応するべき

「ヒロインとデート」についてもう少し語ると

僕がヒロインシリーズを創っているのは、もっとも人間に近い対話設計にチャレンジできると考えているからです。

前作の「ヒロインの告白」は、告白なので基本的には「はい」「いいえ」で進むことがわかりやすくできます。

この次の対話設計としては、人間と想定する 結城琴葉ちゃん と、どれだけ自然に会話できるかを考えています。 そうなると「はい」「いいえ」だけで会話するのは無理です。 というか、それは会話や対話ではないです。

駅名と施設名を指定できる

デートをするシチュエーションを想定すると、現実には事前に行先を決めていると思われるのと、位置情報とリンクした際は、その位置情報に合わせた設計が必要になります。

とはいえ、僕がまだそこまで実装しきれていないので、デモ動画では駅名から聞かれます。

どこかで待ち合わせをしたとして、「じゃあどこ行こう」となった際に、例えばアメリカに行くとしたら 「グランドキャニオンに行こう」 などの観光スポットになると思います。 これは、グランドキャニオンの最寄り駅を指定するよりは、グランドキャニオンそのものを指定するはずです。

なので、声優さんに渡した脚本として、はじめから「動物園」「水族館」などの場所・施設名を話しかけた場合の動き も収録してもらっています。

動物園に入ったら、水族館には突然ワープさせない

そして、例えば動物園に行った場合、そこからいきなり水族館にワープすることは現実的には(たぶん)ないので、そこへのワンショットはさせないようにデザインしています。

その変わり、動物園内で「パンダ」「フラミンゴ」「カバ」などの動物をめぐるようにしており、それぞれ 「●●(動物)を見に行こう」とユーザーが話しかける場合と、結城琴葉ちゃんが「パンダが観たい」と話しかけてくるような設計にしています。

動物園内ではワンショットで対象の動物を選択し、また、「他の動物」や「次の動物」だとランダムで他の動物を観に行くことを想定しています。

LIFFで思い出をエモく

位置情報が使えるなら実世界とリンク、自宅にいながらでもClovaの操作によって、LINEさんのLIFFを使って、過去に廻った観光スポットとかをパーソナルに表示して、思い出としてエモく残すこともできると考えています。

応用すると

例えば、アメリカ人でバットマンが好きな人が日本にきたとして、そこでモバイルでVUIを活用して日本観光をするとなると、英語でバットマンが日本観光案内をしてくれることもできなくはないと思っています。 しかも場合により課金もできる。

というわけで

やりたいことが山ほどあります。 もうこうなると個人でやるのは限界なので「オラに元気をわけてくれ」状態です。

ともあれ、やりたいことがあるのにやらないという選択肢はとりたくないので、コツコツ頑張っていきます。

以上