皆さんこんにちは。
顧客対応や開発のサポート業務を行っておりますGMOアドマーケティングのR.Aです。
先日より一般販売が開始されているAmazon Echo(Alexa)ですが、皆さんは持っておりますでしょうか。
筆者の家では、リビングとキッチンにあり、テレビや照明の制御・3分タイマーだったりと活躍しています。
そんなAmazon Echoですが、スキルを作ってもっと出来る事を増やしたいなぁーと思い今回スキルを作成してみることにいたしました。
どんなAlexaスキルを作ろうかなと考えた結果、普段使用している東急バスの接近情報を教えてもらうスキルを作成する事にいたしました。
東横線アプリで毎朝バスの運行状況を確認していますが、毎回開いて確認するのがとても面倒です。
そこで以下のバスの運行状況を情報を取得し、教えてもらうようにいたします。
また、野沢龍雲寺のバス停からは渋谷行きと目黒行きのバスがあるので、どちらのバスを調べるか指定できるようにします。
野沢龍雲寺からの東急バス運行状況(渋谷駅行き)
野沢龍雲寺からの東急バス運行状況(目黒駅行き)
1、スキル開発に必要なもの
・AWSまたはHTTPS環境
・プログラミング環境(今回はRuby(2.3.1p112)で開発)
・Amazon Developerアカウント
今回スキルを公開する予定は無いためお試しを兼ねてAWSでは無く、HTTPS環境で行う事にしました。
ngrokを使用するとHTTPS環境を無理やり作成できるので、行ってみたいかと思います。
全体の大まかな流れは以下のようになります。
2、Alexaスキルの作成・設定
まず、Amazon Developerアカウントを登録し、ログインを行います。
alexa-skills-kitページへ行き、「スキル開発を行う。」をクリックします。
遷移先の「スキルの作成」をクリック
画面に従い、スキル名等を入力し「スキルを作成」をクリック。
するとスキルの管理画面が作成されます。
画面右側にスキルビルダーのチェックリストという綱目がありますが、こちらを全て埋めないとスキルを作成する事が出来ません。
続いてこちらの設定を行っていきます。
①呼び出し名
スキルの呼び出しを行う際の、文字列を設定いたします。
「Alexa、〇〇〇を開いて」の 〇〇〇にあたる部分を設定いたします。
今回は【東急バス運行状況】と設定します。
②インテント、サンプル発話(スロット)を設定
インテント、サンプル発話を必ず設定する必要があります。
まずそれぞれの用語を説明いたします。
インテントとはAlexaが実行するメソッドの様な物になります。
このインテントを判別して、指定の処理を実行したりします。
今回は【TokyuBusIntent】と設定します。
次に、サンプル発話を設定します。
サンプル発話とはユーザーが発言する可能性のある文字列を設定します。
公開を行う際はスキルを使用するユーザーが特定多数の為、出来るだけ多くのサンプル発話は入れた方がいいかと思います。ですが、今回は公開をいたしませんので、3つほどのみ設定いたしました。
またスロットを設定する場合は、サンプル発話画面で設定を行います。
スロットというのは、インテントのリクエストとともに送信される変数のようなものです。
今回のスキルは各駅に向かうバスを調べたいので、名前を city とし、スロットタイプを AMAZON.City に設定します。
③モデルをビルド
1・2の設定が完了したら、ビルドを実行します。
新しい変更を加えた場合は、再度ビルドを行い、変更を反映させる必要があります。
④エンドポイント
エンドポイントとは、Alexaから送られて来るリクエストに従い、処理を行う場所になります。
冒頭説明した通り、今回はHTTPSで設定を行います。
詳細な設定については、後ほど説明いたします。
以上で、大まかなAlexa側の設定は完了になります。
3、インテントの処理
続いてエンドポイント(Ruby)側の設定を行います。
今回Rubyで開発を行いますが、【ralyxa】というRubyのフレームワークがあるのでこちらを使用していきます。
・まず、ralyxaをローカルにダウンロードします。
1 |
git clone git@github.com:sjmog/ralyxa.git |
その後、Gemfileを開きgemを以下にします。
1 2 3 4 5 |
source 'https://rubygems.org' gem 'ralyxa' gem 'sinatra' gem 'nokogiri' |
その後、bundle installを行い、インストールを行います。
1 |
bundle install --path vendor/bundle |
インストールが完了したらディレクトリに以下の2つのファイルを作成いたします。
①sinatraからralyxaへ実行するための処理
②intentsディレクトリの作成とインテントの処理(図を参照)
各ファイルの中身は以下のように記載いたします。
①sinatraからralyxaへ実行するための処理(receiving.rb)
1 2 3 4 5 6 |
require 'sinatra' require 'ralyxa' post '/' do Ralyxa::Skill.handle(request) end |
②intentsディレクトリの作成とインテントの処理(intents/response.rb)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 |
require 'open-uri' require 'nokogiri' intent "LaunchRequest" do ask("はい、野沢龍雲寺からの東急バスを調べます。") end intent "TokyuBusIntent" do class StringOutPuter #運行状況の情報を取得する def gethtml(url,searchword,transformword) charset = nil html = open(url) do |f| charset = f.charset f.read end doc = Nokogiri::HTML.parse(html, nil, charset) status = [] doc.xpath('//table').each do |node| status << node.css('td').inner_text end replyshaping(status[0],searchword,transformword) end #情報の整形 def replyshaping(status,searchword,transformword) if (status.match(/(#{searchword}\xc2\xa0)+/)) status.gsub!(/(#{searchword}\xc2\xa0)+/, "#{transformword}") end status.gsub!(/(\xc2\xa0|\s)+/, ",") if (!status.match("#{transformword}")) ask = "#{transformword}のバスは、野沢龍雲寺近辺には来ていないようです。" return ask end operation_status = status.split(",") query = [] operation_status.each do |i| result = {} if (i.match("#{transformword}")) i.gsub!("【", ",") i.delete!("】") result["destination"] = i.split(",")[0] s = i.split(",")[1] if (s.match(/(1[0-9])+/)) result["time"] = s else s.slice!(0) result["time"] = s end query << result end end ask = "#{query[0]["destination"]}きは、#{query[0]["time"]}です。" if (query.length >= 2) ask << "その次のバスは、#{query[1]["time"]}です。" end return ask end end #発話によって参照元変更 if(request.slot_value("city") == "目黒" ) url = 'http://tokyu.bus-location.jp/blsys/navi?VID=rsi&EID=nt&CID=rsi&FID=rtl&SID=&PRM=&SCT=1&DSMK=0&DSN=null&ASMK=0&ASN=null&FDSN=0&FASN=0&RMK=0&RAMK=121&DOF=22&SKF=2463&DOS=1&SKS=2534&SSC=2&DIF=&ARC=20&ART=0' searchword = "黒09・中目01" transformword = "黒09" else url = 'http://tokyu.bus-location.jp/blsys/navi?VID=rsi&EID=nt&CID=rsi&FID=rtl&SID=&PRM=&SCT=1&DSMK=0&DSN=null&ASMK=0&ASN=null&FDSN=0&FASN=0&RMK=0&RAMK=7&DOF=20&SKF=2463&DOS=1&SKS=2336&SSC=2&DIF=&ARC=20&ART=0' searchword = "渋32" transformword = "渋32" end ans = StringOutPuter.new tell(ans.gethtml(url,searchword,transformword)) end intent "SessionEndedRequest" do tell("ご利用ありがとうございました。") end intent "AMAZON.HelpIntent" do ask("このスキルは野沢龍雲寺への東急バスの到着時刻を通知するスキルです。") end |
intents/response.rbで行っている処理はスロットの値を見て、参照するURL選び、運行状況をスクレイピングし返却しているだけですので、説明は省きます。
返却の際askやtellで返していますが、こちら違いはセッションを維持するかしないかの違いになります。
それでは残りの設定を行っていきます。
まず、①で作成したプログラムを立ち上げ、localhostにアクセス出来るようにします。
1 |
bundle exec ruby receiving.rb -p 8080 -o 0.0.0.0 |
続いて、ngrokを別ウィンドウで立ち上げ、外部からアクセス出来るようにいたします。
1 |
ngrok http 127.0.0.1:8080 |
立ち上げると以下の様な画面が表示されますので、選択している箇所をAlexaの設定画面に入力します。
これでチェックリストが全て設定出来ましたので、テストをして確認してみます。
ビルドタブの横にテストタグがあるので、テストを行います。
うまく情報が戻ってきました。
Amazon Echo でも喋らせてみた所問題なく処理してくれました。
いかがでしたでしょうか。今回はAlexa開発の初歩的な部分をまとめてみましたが、
今後はAWSなどを使って公開出来るようなスキルを作れればなと思っております。