Tweet

2018年12月10日月曜日

【AmazonAlexaスキル開発】Echo Spotなどのディスプレイ対応Alexa対応スキルの作り方 ask-sdk V1での作り方

こういう人向け

  • AmazonAlexaでEcho Spot(Echo Show)対応のディスプレイ表示スキル作成したい
  • 音声情報だけではなくディスプレイに表示させたい
  • AmazonAlexaキャンペーンでEcho Show無料クーポンを狙っている開発者
  • AmazonAlexaディスプレイ表示でドハマリした人
当記事を読破後、通常のAmazonAlexaスキルを開発されている開発者様全員が
ディスプレイ付き開発が出来るような、そんな記事を目指して書きました。

謝辞

ディスプレイ対応にあたり、
Twitterで相談を投げた所、すますぴ様がご丁寧にご教示いただきました
頂いたアドバイスを全てノートに転記し、チャレンジした所、問題なく動作しました。
当記事は皆様のご協力無しには成立しなかった事であることを明記してきます。

<宣伝>
すますぴ!~初心者の為のスマートスピーカーサイト~ | GoogleHomeなどの単体型だけでなく、AppleのSiriやGoogleアシスタントなどのスマホ型の話題も・・・・ https://sumasupi.net/


ディスプレイ対応は難しい?いえいえそんなことない。


ディスプレイ自体そんなに難しい事ではありませんでした。
ただ、僕はそもそもディスプレイ対応が初めてでかつネットに文献が少ないので、
かなりどはまりしてしまいました。

また、開発環境としては ask-sdkになります。いわゆるV1です。
本当はV2でやらないといけないと思うんだけど調査不足でまだ移行出来ていません。
ただ、V1開発者もまだまだ多いと思うのでしっかり書いていきます!

手順の確認

  1. AmazonAlexaスキル内でインターフェースの切り替え(Displayの有効化)
  2. Amazonシミュレータ(ビルド)上で、Displayの有効化
  3. lambda関数のコーディング書き換え・追記
ひとつひとつ順を追って説明していきますね。
結構やっている事自体はシンプルです。

Alexaスキルでディスプレイ使うタイプのスキルですと宣言して、
デバック用にDisplayを表示させて、
コードでディスプレイ対応のコーディングするだけです。

インターフェースの切り替え


    カスタム、対話モデル、インテントなどの左タブの下側に
    「インターフェース」という項目がございます。

    「インターフェース」>「Displayインターフェース」をチェックつけて、
    「インターフェースを保存」して「モデルをビルド」してください。

    ビルトインインサイトにディスプレイに関するものが追加されていると思います。
    現時点ではこちらでOKです。

    ※補足:読み飛ばしてもOK
    AmazonAlexaスキルシミュレータ内で、ディスプレイありなしを判別をどうやって切り替えるのか調べた結果、こちらのインターフェースを切り替える事でシミュレータ内部処理的にディスプレイを持っているかどうかオンオフになる事がわかりました。
    最終的なデバックでディスプレイが無い機種の場合の挙動を調べたい時はこちらをオフにすれば、OKそうです。

    Alexaシミュレータ上でDisplayを有効化する。


    これは普段からご実施の方もいらっしゃるかもしれません。

    ページ右上にある ☑Device Displayにチェックを入れるだけです。
    そうすればAlexaシミュレータ上でディスプレイを出す事が出来ます。
    基本的にはいつもONでも良いと思います、非ディスプレイスキルだとしても。

    ※上記にも説明があるように
    ☑Device Displayはあくまで「ディスプレイをここの画面に表示する?しない?」の機能になります。
    内部的にディスプレイを所持しているかどうかをいじりたい場合はインターフェースを調整してください。


    Lambda関数側でディスプレイ対応のコーディングする


    こちらのURLにコード公開しました。
    URLを押すだけで閲覧可能だと思います。(一応JSONエディターも公開してます)

    実装必須(?)のutils関連のconst宣言をしておく



    'use strict';

    const Alexa = require('alexa-sdk');


    const makePlainText = Alexa.utils.TextUtils.makePlainText;

    const makeRichText = Alexa.utils.TextUtils.makeRichText;

    const makeImage = Alexa.utils.ImageUtils.makeImage;
    こんな感じですね。
    const Alexa = require('alexa-sdk'); 直下に書いてください。
    変数名も多分重複しないと思うのでこちらでOKです。
    代入なども行わないので、constで大丈夫です。

    ヘルパー関数を実装する


    先程のconst宣言の直下に関数(function)を置きました。


    function supportsDisplay() {

       var hasDisplay =

           this.event.context &&

           this.event.context.System &&

           this.event.context.System.device &&

           this.event.context.System.device.supportedInterfaces &&
           this.event.context.System.device.supportedInterfaces.Display;
       return hasDisplay;
    }

    僕ならオブジェクトの有無判定式は、もっとスマートに書きますが、
    一応公式サポートの書き方なので引用しておきます。

    ■読み飛ばしてもOK:AmazonAlexaでディスプレイありなし機種の判別方法
    こちらの関数の意味は、「ディスプレイありなしか識別しております」
    AmazonAlexaでは、ディスプレイありなし判定は入力JSONで識別可能です。


    機種によって入力JSONが変化するので、こちらの情報を取得しています。
    外部にヘルパー関数を出す意味としては、毎回記述していたら長くなってしまうので、
    外に出しましょうということです。

    こちらのヘルパー関数はそういう記述になります。

    ディスプレイを表示させるためのコーディング


    先にコーディング記載します。
    こちらは当スキル唯一のインテント「complaints」になります。
    その中にディスプレイを表示させるコードを書いてます。


       //愚痴愚痴いうもん 唯一のインテント


       'complaints': function () {

         //Alexa対応デバイスのディスプレイの有無を判定している。

         if (supportsDisplay.call(this)) {

             const builder = new Alexa.templateBuilders.BodyTemplate1Builder();

             // ディスプレイディレクティブの作成
             let template = builder.setTitle('愚痴愚痴いうもん')
       .setBackgroundImage(makeImage('https://2.bp.blogspot.com(省略).jpg'))
               .setTextContent(makePlainText('あなたは何も悪くないから必要以上に悲しむのはやめるのよ。'))
               .build();

               this.response.renderTemplate(template);
         }else{
           //ディスプレイが無いので特に何もしない。 //本来はelse書く必要ないけど理解のために書きます。
         }

    //ディスプレイの有無問わず通る場所。
    //ディスプレイの有無に問わず、音声は一緒で同じという理屈。
    this.response.speak(message_result).listen(message_result + ' ' + HELP_REPROMPT);
           this.emit(':responseReady');
       },

    if (supportsDisplay.call(this)){


    ヘルパー関数で返ってきた値で条件分岐しています。
    ヘルパー関数めっちゃ便利。正分岐がディスプレイある場合、偽分岐が無しの場合です。

    ディスプレイディレクティブを作成する。

    (1)const builder = new Alexa.templateBuilders.BodyTemplate1Builder();


    こちらで使用するテンプレートを定義しています。
    AmazonAlexaではさまざまなテンプレートが用意されているので、
    ステップアップの際は挑戦してみてください。
    こちらのURLでのテンプレート紹介が非常に分かりやすかったです。

    (2)let template = builder.setTitle('愚痴愚痴いうもん')
       .setBackgroundImage(makeImage('https://2.bp.blogspot.com(省略).jpg'))
               .setTextContent(makePlainText('あなたは何も悪くないから必要以上に悲しむのはやめるのよ。'))
               .build();
               this.response.renderTemplate(template);


    これが要ですね。
    ひとつひとつ定義しています。
    • タイトル:愚痴愚痴いうもん
    • 背景画像:キドニークックのアイコン
    • テキスト:あなたは何も悪くないから必要以上に悲しむのはやめるのよ。
    と設定して表示しています。
    先程のconst宣言の「malePlainText」などはここで使用するので必要だったわけです。

    ここを固定値ではなく変数名にすることで毎回違う処理結果を表示する事が出来ますね。
    それこそ音声で返している内容を当てはめて上げてもいいと思います。

    this.response.renderTemplate(template);
    this.emit(':responseReady');


    これが僕の最大ハマりポイントでした。
    特大のハマりポイント
    一応上記公開コードだとしっかり離して書いているので注意。元コード見てくださいね。
    (1行も開けず書くなら セミコロンなしで.で書く)

    this,emit(':responseReady');を返してあげないと、
    ディスプレイディレクティブを生成しても反映されませんでした。
    この1行が大事です。

    上記補足(Responseオブジェクトについて)


    【AmazonAlexaスキル開発】Responseオブジェクトについて考えてみる(ask,tellなど) http://iga34engineer.blogspot.com/2018/12/amazonalexaresponseasktell_10.html


    記事を書きましたのでtell,ask,responseが分からない場合はご参照ください。

    上記内容をビルドして反映させる。


    背景白くなっているので分かりづらいですが、
    しっかり画像も表示されております。

    結論


    トラブルシューティング

    • ディスプレイ表示に「タイトル」だけ上手く行って「テキストが出てこない」
      • setTextContent(makePlainText('あなたは何も悪くないから必要以上に悲しむのはやめるのよ。'))
      • "string"ではなく、'string'という書き方にしないと正しく認識しません。私も一度ありました。
    • Responseオブジェクトの書き方が分からない。
    • 使用できる画像の条件(要項)を教えてください。
      • JPEGもしくはPNG
      • 2MB以下
      • HTTPSでアクセス出来る場所にホストされ、一般に公開されていること。 噛み砕くと、 ネット上で右クリックして「画像アドレスをコピー」して、 httpsで始まっていて.jpgもしくは.pngで終わっていればOK ※Amazon S3などにアップロードする必要性は無し。 例)https://www.google.co.jp/images/branding/googlelogo/2x/googlelogo_color_272x92dp.png
    • 条件分岐と「this.emit(':responseReady');」が頭の中でこんがらがる。
      • うーん、シンプルに考えてみよう。
      • ヘルパー関数内のif構文は正(ディスプレイ有)表現だけ
        • その中にthis.response.renderTemplate(template);が1個
      • あとは、インテント関数の最後に下記2つ書けばいいよ。それでlet宣言したmessage_resultの中を条件分岐で書き換えてあげるの。
        • this.response.speak(message_result).listen(message_result + ' ' + HELP_REPROMPT);
        • this.emit(':responseReady');



      0 件のコメント:

      コメントを投稿