Help us understand the problem. What is going on with this article?

Clova + Clova Extension Kit + enebular で京急の運行情報確認ができるClovaスキルを作る

More than 1 year has passed since last update.

こんにちは、ポキオです。

IMG_20180825_220406324-01.jpg

スキルの処理を簡単に書きたい

以前、簡単なClovaスキルを作ったんですが、意外と簡単で、しかも無料で、そしてすぐに実機で試せて、最高でした。

ただし、一つだけ問題があって、LINEではPaaSは提供していない点です。

名称未設定.png

Dialogflowと比較をすると一目瞭然ですが、Clovaスキルのメインの処理の部分をLINE以外のサービスで立ち上げて設定する必要がありました。前回はIBM Cloud Foundryを無料で使うということをしました。ただ、そこにデプロイするのはコード(Node.js)で、バリバリのコードです。

なんとかして、ノンプログラミングな方法でスキルの中身を書けないかと模索していたところ、出会ったのがenebularでした。

enebularとは

enebular(エネブラー)は、IoT製品・サービスづくりを包括的に支援する、開発・運用サービスです。エッジとクラウドにまたがるIoTアプリケーションを開発し、様々なデバイスへ迅速にデプロイ、さらに膨大な量のデータを可視化やAIにより利活用することで、IoTアプリケーションの最適な運用を支援します。
enebularの名前は、星の数(nebular)ほどあるデバイスを、アップデートにより賢くし(enable)、分散しながら協調するアプリケーションの開発から由来しています。

無料でも使えるツールで、Node−REDベースのツールで、グラフィカルに、大半をノンプログラミングで処理を設計できます。

今回、enebularを使うきっかけになったのは、こちらの記事です。

要は「Clovaで発話を受け取り、Clova Extension Kit経由でenebularでリクエストを受けて、enebular上でレスポンスを作成し、Clovaに発話させる」ということが簡単にできてしまうということらしいです。Node-REDでグラフィカルに設計できるのであれば、かなりClovaスキル開発のハードルが下がるなと思い、ためしに開発してみました。

せっかくなので、大好きな京急にちなみ、京急の運行情報を教えてくれるClovaスキルを作ってみようと思います。

Clovaスキルを作ってみる

とりあえずClovaスキルを立ち上げる

何はともあれ、LINEの開発者向けのページで、新しくClovaスキルのプロジェクトを立ち上げます。よしなにスキル名を名付けていきます。

Screenshot_2018-08-25_16-19-10-01.jpg

そして、このスキルに対して対話モデルを設計していきます。細かいやり方については、上記記事と私のブログを御覧いただきつつ、ここでの説明は簡単なものとさせていただきます。

Screenshot_2018-08-25_16-27-01-01.jpg

ポイントは大きく2点。

  • インテントは2つ
    • 京急の運行情報を確認するインテント
    • 会話を終えるインテント
  • 前者のインテントで「京急」という表現にバリエーションがありそう
    • カスタムスロットで、京急の言い回しをある程度吸収するように設計

こんな感じでスキルの設定をしました。

enebularでもプロジェクトを立ち上げる

enebular側でもプロジェクトを新規作成。

スクリーンショット 2018-08-25 午後8.36.32-01.jpg

デフォルトのままだと、プロジェクト内にアセットが何もない状態ですが、このままでOKです。

スクリーンショット 2018-08-25 午後8.45.58-01.jpg

OKというは、先程のClovaとenebular連携の記事で、Node−REDのフローが公開されているので、それをForkして使ってみようと思います。

スクリーンショット 2018-08-25 午後8.49.48-01.jpg

上記サイトに飛ぶと、Clova向けにサンプルのフローが公開されているので、それをForkします。

スクリーンショット 2018-08-25 午後8.51.06-01.jpg

Forkボタンを押すと、自分のプロジェクトが選べるようになっているので、しれっと選択してForkします。

スクリーンショット 2018-08-25 午後8.51.48-01.jpg

すると、自分のプロジェクトに先程のフローがコピーされています。これをいじりながら京急スキルを組んでいきます。

フローを実装する

Forkしてきたフローを開き、Edit Flowからフローの編集画面を開きます。

スクリーンショット 2018-08-25 午後8.55.26-01.jpg

このフローを以下のように編集していきます。

  • LaunchRequest時のセリフを変える
  • IntentRequest時は、Intentの種類で分岐させ処理を変える
    • 運行情報確認のIntentの場合、京急のHPから運行情報をパースし、レスポンスとする
    • 会話終了のIntentの場合、終了っぽいセリフをレスポンスとしつつ、レスポンスを返したあとはユーザーの発話待受をやめる
    • 上記以外のIntentは、未知のIntentとして、再度発話を促すようなセリフをレスポンスとする

で、出来上がったものがこちらです(笑)

スクリーンショット 2018-08-25 午後10.03.02-01.jpg

フローのデータはこちら。

[{"id":"2c4d10e5.3fadf","type":"http in","z":"de769471.1b1e68","name":"","url":"/clova","method":"post","swaggerDoc":"","x":90,"y":160,"wires":[["57574f82.b0b49","898d36c2.da7418"]]},{"id":"c233d368.fd81","type":"function","z":"de769471.1b1e68","name":"LaunchRequestセリフ","func":"msg.payload =\n{\n    \"version\": \"1.0\",\n    \"sessionAttributes\": {},\n    \"response\": {\n        \"outputSpeech\": {\n            \"type\": \"SimpleSpeech\",\n            \"values\": {\n                \"type\": \"PlainText\",\n                \"lang\": \"ja\",\n                \"value\": \"こんにちは!赤い電車です!\"\n            }\n        },\n        \"card\": {},\n        \"directives\": [],\n        \"shouldEndSession\": false\n    }\n}\nreturn msg;","outputs":1,"noerr":0,"x":520,"y":120,"wires":[["474b4507.0e327c"]]},{"id":"57574f82.b0b49","type":"switch","z":"de769471.1b1e68","name":"request.type 判定","property":"payload.request.type","propertyType":"msg","rules":[{"t":"eq","v":"LaunchRequest","vt":"str"},{"t":"eq","v":"IntentRequest","vt":"str"}],"checkall":"true","outputs":2,"x":290,"y":160,"wires":[["c233d368.fd81"],["b761c32c.0f634"]]},{"id":"bfd2a44e.ad6b08","type":"comment","z":"de769471.1b1e68","name":"LaunchRequest","info":"","x":500,"y":80,"wires":[]},{"id":"898d36c2.da7418","type":"debug","z":"de769471.1b1e68","name":"","active":true,"console":"false","complete":"false","x":270,"y":200,"wires":[]},{"id":"474b4507.0e327c","type":"http response","z":"de769471.1b1e68","name":"","x":710,"y":120,"wires":[]},{"id":"1e537e25.5a5d32","type":"comment","z":"de769471.1b1e68","name":"IntentRequest","info":"","x":490,"y":280,"wires":[]},{"id":"c8e079d1.fec438","type":"comment","z":"de769471.1b1e68","name":"Intentの種類を判定","info":"","x":510,"y":320,"wires":[]},{"id":"12bed258.8d1a7e","type":"comment","z":"de769471.1b1e68","name":"CHECK_KEIKYU","info":"","x":760,"y":160,"wires":[]},{"id":"2c6bb4c4.a10aec","type":"comment","z":"de769471.1b1e68","name":"起動時の応答","info":"","x":730,"y":80,"wires":[]},{"id":"75d7f9c6.966138","type":"comment","z":"de769471.1b1e68","name":"Clovaからの受け取る","info":"","x":120,"y":120,"wires":[]},{"id":"b761c32c.0f634","type":"switch","z":"de769471.1b1e68","name":"request.intent.name 判定","property":"payload.request.intent.name","propertyType":"msg","rules":[{"t":"eq","v":"CHECK_KEIKYU","vt":"str"},{"t":"eq","v":"END_KEIKYU","vt":"str"},{"t":"else"}],"checkall":"true","outputs":3,"x":530,"y":360,"wires":[["3c4833ab.ae84cc"],["f9928a98.a148b8"],["42aad12d.8b656"]]},{"id":"6fcc8005.7365c","type":"http response","z":"de769471.1b1e68","name":"","x":1050,"y":360,"wires":[]},{"id":"f9928a98.a148b8","type":"function","z":"de769471.1b1e68","name":"ご乗車ありがとうございました!","func":"msg.payload =\n{\n    \"version\": \"1.0\",\n    \"sessionAttributes\": {},\n    \"response\": {\n        \"outputSpeech\": {\n            \"type\": \"SimpleSpeech\",\n            \"values\": {\n                \"type\": \"PlainText\",\n                \"lang\": \"ja\",\n                \"value\": \"ご乗車ありがとうございました!\"\n            }\n        },\n        \"card\": {},\n        \"directives\": [],\n        \"shouldEndSession\": true\n    }\n}\nreturn msg;","outputs":1,"noerr":0,"x":820,"y":360,"wires":[["6fcc8005.7365c"]]},{"id":"b003fb9a.8c29e8","type":"comment","z":"de769471.1b1e68","name":"END_KEIKYU","info":"","x":750,"y":320,"wires":[]},{"id":"42aad12d.8b656","type":"function","z":"de769471.1b1e68","name":"もう一度言ってください!","func":"msg.payload =\n{\n    \"version\": \"1.0\",\n    \"sessionAttributes\": {},\n    \"response\": {\n        \"outputSpeech\": {\n            \"type\": \"SimpleSpeech\",\n            \"values\": {\n                \"type\": \"PlainText\",\n                \"lang\": \"ja\",\n                \"value\": \"もう一度言ってください!\"\n            }\n        },\n        \"card\": {},\n        \"directives\": [],\n        \"shouldEndSession\": false\n    }\n}\nreturn msg;","outputs":1,"noerr":0,"x":800,"y":460,"wires":[["6fcc8005.7365c"]]},{"id":"965df001.3de56","type":"comment","z":"de769471.1b1e68","name":"UNKNOWN","info":"","x":750,"y":420,"wires":[]},{"id":"50925403.9d586c","type":"comment","z":"de769471.1b1e68","name":"発話時の応答","info":"","x":1070,"y":400,"wires":[]},{"id":"3c4833ab.ae84cc","type":"http request","z":"de769471.1b1e68","name":"京急運行情報取得","method":"GET","ret":"txt","url":"http://unkou.keikyu.co.jp","tls":"","x":770,"y":200,"wires":[["449159af.958908"]]},{"id":"449159af.958908","type":"function","z":"de769471.1b1e68","name":"運行情報パース","func":"msg.payload = msg.payload.split(\"<div class=unko-panel>\")[1];\nmsg.payload = msg.payload.split(\"</div>\")[0];\nmsg.payload = msg.payload.replace(/\\\\r?\\\\n/g,\"\");\nmsg.keikyu = msg.payload;\nmsg.payload = null;\nreturn msg;","outputs":1,"noerr":0,"x":760,"y":240,"wires":[["8c8bd701.c2cb88"]]},{"id":"8c8bd701.c2cb88","type":"function","z":"de769471.1b1e68","name":"運行情報反映","func":"msg.payload =\n{\n    \"version\": \"1.0\",\n    \"sessionAttributes\": {},\n    \"response\": {\n        \"outputSpeech\": {\n            \"type\": \"SimpleSpeech\",\n            \"values\": {\n                \"type\": \"PlainText\",\n                \"lang\": \"ja\",\n                \"value\": \"\"\n            }\n        },\n        \"card\": {},\n        \"directives\": [],\n        \"shouldEndSession\": false\n    }\n}\nmsg.payload.response.outputSpeech.values.value = msg.keikyu;\nreturn msg;","outputs":1,"noerr":0,"x":760,"y":280,"wires":[["6fcc8005.7365c"]]}]

入力に対するパースの仕方や、レスポンスのボディーのサンプルが、Forkしたサンプルに丁寧に記述されているため、用意に理解でき、自分のやりたいことを実現するために、どこをどう直せばいいか一目瞭然でした。

主にどこを変えたかというと、

  • IntentRequestだったときの処理を変更
    • Intent名でSwitch
    • Intent名はpayload.request.intent.nameで取得可能
    • 運行情報取得Intentときは、HTMLをパースしレスポンスに突っ込む
    • 会話終了Intentのときは、レスポンスのshouldEndtrueにする

こんな感じです。HTMLパースの部分はゴリゴリ書いていますが、それ以外はほぼパラメタ変更程度で、スキルの処理が記述できてしまいます。

スクリーンショット 2018-08-25 午後9.25.22-01.jpg

最後に、フロー編集画面の右上のiボタンから、このフローへアクセスするためのURLをコピーして、ClovaスキルのExtensionサーバー設定のところに貼り付けます。ただし、実際にはこのURLの後ろにclova/をつけたURLで待ち受けているので、Clovaスキルの設定にはclova/の文字列を末尾に追加して登録します。

実際に動かしてみる

Clovaスキルのダッシュボードから、ビルドを行い完了すると、ダッシュボードや実機上で動作確認ができるようになります。実機上で試すためには、スマホのClovaアプリから、今回作ったスキルがスキルストア上に表示されているので、Clovaで使えるように設定してあげると、準備完了です。

実際に実機でスキルを試してみました。起動ワードはJessicaになっています。
諸事情で暗闇でコーディングしていますが、気にしないでください・・・(笑)

pokiiio
どうも、ポキオです。
https://pokiiio.github.io/
iotlt
IoT縛りの勉強会です。 毎月イベントを実施しているので是非遊びに来てください! 登壇者を中心にQiitaでも情報発信していきます。 https://iotlt.connpass.com
https://iotlt.connpass.com/
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした