テクノロジの無駄づかい

日々の「ステュディオス」を求めて遠回りしがちなエンジニアの記録

Alexaスキルをリリースしてみた

ここしばらく、仕事でAlexaスキルを作っていたのだけど、 スキルアワードもあるし、自分でもなんか作って公開してみようと思っていた。

ただ、ネタがない。

アワードに応募するなら、なにか思いついてももう作っている時間がないなぁと思っていたところ 思いついて、構想30分、実装数時間で作って公開したのがこれ。

発想の扉

発想の扉

「アレクサ、発想の扉を開いて」でEchoに話しかけると オズボーンのチェックリストをベースにした、視点を変え発想を促す(と思う)問いかけしてくるだけのスキル。

「アレクサ、発想の扉を開いて」で始めると、連続問いかけモードで「ほかにもいりますか?」などと次を促してくるので、「はい」とか「次の頂戴」とか言うと連続して問いかけさせることができる。

「アレクサ、発想の扉で問いかけて」というと、単発問いかけモードとなり、一つだけ問いかけてすぐに終了する。

一応、連続問いかけモードモードの場合には、同じことを何度も言わないようにしている。 工夫といえばそれくらいで、対話しているようでしてなくて一方的にしゃべるだけのスキルなので作りもシンプル、簡単。

でも、アイコンが...

ただ、困ったのは、スキルのアイコン。
スキルの公開には必須だのだけど、絵心のない私にはコードを書くよりハードル高かった1

Pixabayで見つけた CC0の画像をちょっといじって使わせてもらった。

リジェクト喰らった

サクッと作って、サクッと申請して、サクッと公開!と思いきや、一度リジェクトされてしまった。

Amazonによるスキル審査でリジェクトされた場合、理由と再現手順が記載された丁寧なメールが届く。 どこをどう直せばよいのかがわかりやすかった。

Alexaの審査チームからの指摘は3点、うち2点は指摘の箇所が違うだけで同じ理由。

サンプル発話が少なすぎる

カスタムインテントの中で、サンプル発話を2つだけしか設定していないものがあった。 想定はしていたが、直接定義していなかった発話も拡張して拾ってくれていたので 実機テストをして問題なしと思っていたが、だめらしい。

ユーザーによる発話 | Amazon Alexa Voice Design Guideには「1インテント当たりのサンプル発話数は30個以上を目安としてください。」と書かれているが、18個に増やしたら審査をパスできた2

セッションオープンなら必ず次の発話を促すこと

これは2箇所で指摘された。一つはヘルプインテントを呼ばれた場合にスキルを終了せずに続けるようにしていたのだが、 次を促す台詞で終わっていなかった。これは単純にリファクタした際に落としてしまっており私のミス。

もう一箇所が、連続問いかけモードで問いかけの後で、次を促す台詞がないという指摘。
連続問いかけモードでは問いかけの後に「ほかにもいりますか?」と次を促す。 ただ、疑問文2連続となり不自然でうざいと思ったので 最初の3回は使い方を知ってもらうために「ほかにもいりますか?」と話すが、4回目以降は言わないようにしていた。

申請時に記載するスキルのテスト方法にもその旨記載していたのだけど、セッションをクローズしないなら、次の発話を促す台詞を入れることというのは絶対らしい。

審査は速かった

修正自体はちょっとした変更で済んだので、修正して再申請。今度はサクッと通過して公開となった。 スキルアワードの締切間近だったので、混んでいるかと思ったが、最初の申請から2営業日目でリジェクト、 その日のうちに修正して再申請して翌日の公開となった。

Amazonの審査チームの皆さんの迅速で丁寧な審査に感謝。

最後に

スキルの呼び出しフレーズが「〜を開いて」であることから、思いつきで作ったたわいのないスキルなのだが、 ブレストなどで煮詰まったときの気分転換程度に使ってみてほしい。

その結果、このスキルの問いかけが役立っても立たなくても、「発想の扉」が開いてアイデアが出れば幸いである。


  1. Amazon側もそこがつまずきポイントになっていると思っているのか最近になって謹製のアイコン作成ツールAlexa Icon Builderを出してきた。

  2. Alexaハンズオンセミナーでは「1インテントに対して最低6つ、理想は30」と教わった。2個ではやっぱ少なすぎた。

GAS を Webpack + Babel でやってみた

久々に、Google Sheets の Add-on で欲しいものができたので作った。 良い機会なので、Webpack + Babel でビルドする構成を試してみた。 また、Google Apps Script (GAS)周りでもいくつか変化があったので取り入れてみた。

その記録。

作ったもの

会社で開発に使った時間を資産管理の関係で申請する必要があるのだが、Redmineのチケットに時間を記録すればOKとなっている。 一方、私は Togglで日々の時間管理をしている。

チケット終了ごとにRedmineに登録したり、締め日にTogglを見ながら手作業でRedmineに登録したりしてみたのだけど、どちらも面倒でやってられなかった。 やはり、普段の時間管理はTogglだけにしたい。 ということで、TogglからRedmineにデータ移行できるツールを作ることにした1

コマンドラインツールでも良かったのだけど、Redmineに登録する前にちょっと確認したり、調整したりできたほうが便利かと考えた。 となると Google Sheetsに一旦書き出して、それを登録できるものがよかろう、それならばAdd-onとして実装するのが最も便利に使えそうだと考えた。

それで作ったのは toggl2rm 。 次の機能を備えている。

以下に、toggl2rmを作る過程でキーとなった部分や新たに調べたことを記述する。

Webpack + Babelの設定

ざっと、TogglとRedmineAPIを叩く部分を GASのWebエディタで検証がてら実装した後、gappsコマンド(node-google-apps-script)を使って、ローカルにソースを持ってきて、Webkit + Babelでビルドする設定を行った。

webpack.config.babel.jsは次の通り設定した。

import GasPlugin from 'gas-webpack-plugin';
import HtmlWebpackPlugin from 'html-webpack-plugin';
import path from 'path';

export default {
  context: path.resolve(__dirname, 'src'),
  entry: {
    code: './code',
    props: './props',
    utils: './utils',
  },
  output: {
    path: path.resolve(__dirname, 'dest'),
    filename: '[name].js',
  },
  module: {
    rules: [
      {
        test: /\.js$/,
        exclude: /node_modules/,
        use: 'babel-loader',
      },
      {
        test: /\.json$/,
        exclude: /node_modules/,
        use: {
          loader: 'file-loader',
          options: {
            name: './[name].[ext]',
          },
        },
      },
    ],
  },
  plugins: [
    new GasPlugin(),
    new HtmlWebpackPlugin({
      filename: 'sidebar.html',
      template: 'sidebar.html',
      inject: false,
    }),
    new HtmlWebpackPlugin({
      filename: 'setting_dialog.html',
      template: 'setting_dialog.html',
      inject: false,
    }),
  ],
};

.babelrcは次の通り。

{
  "presets": ["env", "gas"]
}

この設定では次のプラグインを利用している。

gas-webpack-pluginbabel-preset-gasでES6なJSをいい感じのGAS向けコードにトランスパイルしてくれる。

html-webpack-pluginは結局 srcディレクトリからdestディレクトリにHTMLファイルをコピーするのにしか使っていない。 本当はHTMLに含まれるCSSとJSを別ファイルで実装し、ビルドによって一つのHTMLにまとめたかった。 html-webpack-pluginプラグインを更にインストールしていろいろ試したが、どうもうまくいかなかった。 最初からdestディレクトリにHTMLだけ置いたり、Webpack使わずコピーしても良かったのだけど、ソースとビルド成果物をはっきり分けたい+設定はwebpack.config.babel.jsに集約したいのでこうした。

また、JSを一つのファイルにビルドしてまとめても良かったのだけど、なんとなく大きな1ファイルを作りたくないなぁと思って、3つに分けた。 しかし、一つにしたほうが、グローバルに晒したくない関数を隠せるので今後見直すかもしれない。

ESLint

ESLintも導入してみた。 ベースの設定は eslint-config-airbnb-baseを利用した。 これだけだとGAS特有のオブジェクトの参照でエラーが出るので、それはeslint-plugin-googleappsscriptで解決した。

自分で実装したグローバルオブジェクトとconsole.error()等のログ出力系(後述のStackdriver Loggingのため)を個別に許可設定した。

私はAtomエディタを使っているので、エディタのLinterが実装時にリアルタイムでチェックしてくれ、便利だった。

上記を設定したのものはこんな感じ。

extends: airbnb-base

plugins:
  - googleappsscript

env:
  googleappsscript/googleappsscript: true

globals:
  Props: false
  Utils: false

rules:
  no-console: ["error", { allow: ["info", "warn", "error"] }]

clasp

最初は gappsコマンド(node-google-apps-script)を使っていた。 しかし、複数のGASのプロジェクトがある場合、個別にGoogle Drive APIの認証が必要なのにその認証情報の管理は~/.gapps 1ファイルで行われ、切り替えるのが不便だった。

汎用的な不満だと思うので認証情報ファイルをコマンド実行時に指定する方法があるのではと、リポジトリを久々に見るとディスコンしていた。 代わりにGoogle謹製の google/claspを使えと書かれていた。

claspを調べてみてgappsと比べ以下が優れていると思ったので乗り換えた。

  • GASプロジェクト毎の認証設定が不要(1回のログインですべてのGASプロジェクトの管理が可能)
  • GASスクリプトの作成も可能(gappsは先にGASスクリプトを作っておく必要がある)
  • ドキュメントに紐づくスクリプトの管理も可能(gappsでは不可)
  • Google謹製という安心感

使い方はこちらを参照 => GAS のGoogle謹製CLIツール clasp - Qiita

Stackdriver Logging

確か以前は console.log()はGASでは使えなかったと思うのだが、今は使える。 GASはサーバサイドで実行されるスクリプトなので、ブラウザのDev Toolには何も出力されない。 どこに出力されるかというと、Stackdriver というGoogle Cloud上のログサービスに出力される。

どうやら、去年の夏頃こうなったようだ3。 以前からGASにちゃんとしたロギングがほしいと思っていた。 Logger.log()では最後の実行分しか見れないので、どうしても必要な場合には スプレッドシートに書いたりしたものだ。 しかし、これからはStackdriver Loggingが使える。 DEBUG、INFO、WARN、ERRORという一般的なログレベルを設定して出力できるのも嬉しい。

Stackdriverは無料のサービスではないが、毎月無料枠があり、個人ユースではそれで足りるのではないかと思う。 今回はエラーログの記録に利用している。

ざっと調べてみた結果はこちら => GAS でStackdriver Loggingを使う - Qiita

最後に

久々に GASでの開発をやってみて、自分なりのモダンな開発構成を確立できたと思う。 gas-webpack-pluginがWebpack4に対応できていないのかどうもうまくトランスパイルできなかったので、実は今回はWebpack3を使っている。 そのうち対応されればアップデートしようと思う。

しばらく見ないうちにGASを取り巻く環境も色々変わっていた。 以前は自分にだけ自作Add-onをインストールする方法があったのだが、それがなくっている。代わりにテスト設定を作成してそこから起動すれば動かせる様に変更されようだ。少々めんどくさくなった。 その一方で、謹製CLIツールが用意されたり、ログ周りが強化されていた。
更に、GAS専用のマネジメントコンソールもできた。Google Driveでは他のドキュメントとごちゃまぜの管理だけど、こちらだとドキュメントに埋め込まれたGASも含め、すべてのGASのみを管理できる。 まだ、あまり調べていないが、バージョン管理と公開周りも開発版と公開版を別々に動かせるようにするためにちょっと変わったようだ。 このあたりの変更はChromeアプリのプラットフォームとしてGASを使っていくためであろうか?

何れにせよ、以前より開発しやすくなったと思う。 便利なサーバレス環境なので今後も活用していこうと思う。


  1. 個人的には全員でTogglに移行したほうが効率的なのではと思うものの、今、回っている仕組みを私の我儘トリガーで変更するほどでもないし、Togglも有料プランが必要になりそうで費用もいくらか発生してしまうので、個人的に解決する道を選択した。

  2. CSVでダウンロードする機能をTogglは提供している。しかし有料プランの機能。同等のデータはAPIを使えば取得でき、それを利用している。無料で有料機能相当を提供していることに気を使ってライセンスは CC-BY-NC-4.0 としている。

  3. G Suite Developers Blog: Stackdriver Logging for Google Apps Script is now available

Google Homeでリモコン操作をやってみた。その2

hero.hatenablog.jp

上に示したポストで Google Home -> Node-RED までを書いたけど、これはその続き。 Node-RED でMQTTのメッセージを受けた後、赤外線を発するまでを書く。

irMagicianの準備

Node-REDのフローのフローを作成する前に、その要素として必要になる赤外線リモコン irMagicianの準備をしておく。

f:id:HeRo:20180108140216p:plain

irMagician は大宮技研製の赤外線リモコンモジュール。 Raspiとの組み合わせでよく使われるものらしく、Amazonでも購入できる。 USB接続なので、電子工作苦手な私にはありがたい。

最初はirmagicianを使ってリモコン信号を記録しようとMacBookに接続して、動かしてみた。 その時点では、少々不安定なものの動いており、それらしいデータも取れていた。

しかし、そのデータを書き込んで動作確認するも、ターゲットのシーリングライトは全く反応しない。 キャプチャしたリモコン信号データが、Macではうまく書き出せないのかなと思い、 Raspi上で試すと今度は書き出したデータを書き込もうとするとエラーが発生する。 こりゃダメだと思い、仕方なくJSのライブラリの利用は諦めた。

ググると、pythonを使っている人が多そうだった。 で、netbuffalo/irmcli: irMagician command line utilityを試してみた。

irmcliのインストール

インストールと言っても irmcli自体は単なる puthon のスクリプトなので、github から cloneするだけ。

ただ、素のraspiだと、git や pip がないのと、pyserial に依存しているのでそれらをインストールする必要がある。 手順は次の通り。

sudo apt-get update
sudo apt-get install git python-pip
sudo pip install pyserial
git clone https://github.com/netbuffalo/irmcli.git

リモコンデータの作成

リモコン信号のキャプチャするには次のコマンドを実行し、リモコン受光部に実際のリモコンの赤外線を浴びせる。

$ python irmcli/irmcli.py -c

キャプチャした信号は irMagician に保持されているので、次のコマンドで、発信することができる。 irMagicianを操作したい機器に向ければそれで操作できる。

$ python irmcli/irmcli.py -p

リモコン信号をファイルに書き出すには次のコマンド。

$ python irmcli/irmcli.py -s -f [書き出し先ファイルパス]

書き出したファイルを読ませてリモコンを発信するには次のコマンドを使う。

$ python irmcli/irmcli.py -p -f [読み込ませるファイルパス]

一通りのやりたい操作をRaspi上で試して問題なく動作したので、 ターゲットのシーリングライトのリモコンのON/OFFの信号をキャプチャしてファイルに保存しておく。 irMagicianが保持できるリモコン信号は1つで、しかも揮発性で電源落とせば消えてしまう。 したがって、onとoffのそれぞれの信号をデータとして保存しておいて、発信するたびに読み込ませる必要があるのだ。

事前準備はここまで。

Node-REDのフローの登録

いよいよNode-RED上のフローを登録する。 次のようなフローにした。

f:id:HeRo:20180108100726p:plain

mqtt in ノードは その1 に書いたとおりの設定。 それ以降の設定を順に説明する。

JSON ノード

文字通り、受けたメッセージをJSONに変換する。 その1で書いたように、IFTTTからBeebotteには次のようなJSONを送信している。

{ "data": {"room": "bedroom","device": "light","action":"{{TextField}}"} }

これが、次の様にmag.payloadstringで入れられて送られて来る。

"{"data":{"room":"bedroom","device":"light","action":"オン"},"ispublic":true,"ts":1515375632470}"

これを後の処理で使いやすいようにJSONをオブジェクトに変換する。

{"data":{"room":"bedroom","device":"light","action":"オフ"},"ispublic":true,"ts":1515375632470}

Change ノード

Changeノードを制すものがNode-REDを制すほど使い勝手のあるノードらしい。

ここでは次の3つの設定で値を書き換えてある。

f:id:HeRo:20180108104648p:plain

1つ目と2つ目は オンオフをそれぞれ、onoffに書き換えている。 3つ目で、msg.payload.datamsg.payloadにセットして必要な値のみ後続の処理に送るようにしている。

Switchノード

Switchノードは条件分岐を行うノード。 ここでは、msg.payload.actionの値 on|offにより、点灯するのか消灯するのかを制御する。 次の様に設定した。

f:id:HeRo:20180108105715p:plain

Exec ノード

前述の様に node様のJSのライブラリはうまく動かなかったので、それを内部で利用する node-red-contrib-irmagician を使うことはできなさそうということで、pythonCLIツールirmcliを Execノードで実行することにした。 まあ、Exec ノードがあればどんな言語で実装されたものでも、コマンドラインで実行できればなんとかなる。

次の様に設定した。 次のキャプチャは 点灯用だけど、同様に消灯用も登録する。違うのは読み込ませるjsonファイルのみ。 f:id:HeRo:20180108110841p:plain

ついにシーリングライトのON/OFF

これですべての設定が完了した。

今回、利用したRaspiは 昔に買って、使い道なく放置していた RASPBERRY PI 1 MODEL B+。 Raspiの中でスペックは最も低いが、代わりに消費電力はPi3より小さいし、Node-REDを動かすだけなら問題ない。 ただ、起動して動作するまでは少々時間がかかるが、ずっと稼働させておくものなので気にならない。

f:id:HeRo:20180108141028p:plain

実際にやってみると、irMagicianの赤外線が方向にシビアできちんと対象の方向に向けていなければ赤外線が届かない。 それで、irMagicianを繋いでいるUSBケーブルに針金を巻き付け方向を保っている。

Google Homeに「OK Google、寝室ライトオン」と話しかければシーリングライトが点灯するし、 「OK Google、寝室ライトオフ」と話しかければ消灯する。 これまで、夜明かりをつけるときには暗闇でリモコンを探していたので思った以上に便利だ。

本当は、寝室のエアコンも操作したかったところだが、irMagicianの指向性が思った以上に強く、一つでいろんな家電を操作するには使いづらい。 LED拡散キャップも試してみたが、今度が距離的に厳しくなりすぎる。 ラズベリー・パイ専用 学習リモコン基板 だと部屋に1個で事足りるようになるだろうか。

まあ、しばらくは寝室の明かりだけかな。

あ、部屋を片付けてたら、大昔に買った PC-OP-RS1が出てきた。これまだ動くかなぁ。

Google Homeでリモコン操作をやってみた。その1

Amazon Echoの招待がなかなか届かないので*1Google Home を買ってみた。 使ってみて、ちょっとしたことを声だけで操作できるのは思った以上に便利だと感じる今日このごろ。

音楽を流したり、ちょっとした質問に答えてくれるのも便利だし、なぞなぞとか早口言葉をやってくれるのも面白いのだが、 やはり家電機器の操作もやってみたい。

とりあえず、最初は定番、電灯のON/OFF。我が家のシーリングライトは赤外線リモコン操作なので、リモコン信号を操作できれば実現できるだろう。

最初から連携するデバイスを購入してしまえばもっと簡単なのかもしれない。 しかしデバイスメーカーのサーバサイドサービスに依存するとサービスが終了した場合に機器ごと使えなくなってしまいかねないので、なるべく自作できるものの組み合わせで実現したい。

で、以下の組み合わせでやってみた。

うーん、どうもやりたいことのシンプルさの割に実現する仕掛けが複雑すぎるように思う。 しかし、2018年正月の時点ではインターネットに晒す自前サーバの運用なし構成ではこれでもシンプルな方かと思う。

以下にそれぞれの連携方法について書く。

Google Assistant -> IFTTT -> beebotte

IFTTTにGoogle Assistant のトリガーが用意されている。 そのトリガーとWebhookを利用して beebotte にメッセージを送る。 ここまでで、Google Assistantへの音声入力をプログラムから扱える形のメッセージに変換することになる。 そこまでを順に説明する。

Beebotte の設定

IFTTTのアプレット登録に、REST APIのエンドポイントが必要になるので先にBeebotteの設定を行う。

Beebotte はIoT向けのクラウドサービス。REST API や MQTTをサポートして、接続された機器にリアルタイムにメッセージを通知することができる。今回はこのサービスをMQTTブローカーとして利用する1。 50,000メッセージ/日までならフリープランで利用できる。個人ユースにはそれで十分であろう。

まずはアカウントを登録する。フリープランならユーザ名、メアドとパスワードだけで登録できる。 アカウントを作成したら、チャネルとリソースを登録するよう促される。 特に迷うところはないと思う。 登録したらこんな感じ。

f:id:HeRo:20180103182319p:plain

Channel名とresource名で REST API や MQTTのエンドポイントが決まる。 また Channel Token が発行されるが、これは後にIFTTTのアプレット登録やNode-REDから接続する場合に必要となる。

IFTTTでのアプレット登録

Google Assistant トリガーは4種類ある。用途に応じて使えば良いが、今回は Say a phrase with a text ingredient を利用する。これは音声入力したテキストをアウトプットに渡せる。今回は電灯の操作なので、オンオフを渡して制御する。

入力項目は次の通り。

設定項目 設定値
What do you want to say? 寝室ライト $
What's another way to say it? (オプションなのでよしなに)
And another way? (これもオプションなのでよしなに)
What do you want the Assistant to say in response? 寝室ライト $ しました。
Language Japanese を選択

What do you want to say? の設定が肝となる。 寝室ライト $と設定したが、「寝室ライト」の部分が 「OK Google,」の後に続くコマンドワードで何をしたいのかを示すキーワードとなる。 「$」はコマンドワードのパラメータとして設定できる音声入力のプレースホルダーで、ここでは「オン」あるいは「オフ」を想定している。 つまり、「OK Google, 寝室ライト オン」とGoogle Homeに話しかければ、このアプレットが起動して、$=オン がセットされるということとなる。

What do you want the Assistant to say in response? は音声コマンドを復唱させることにより、入力が正しいかどうか確認するために設定する。

続いて、アプレットの後半、アウトプットの設定。 このアプレットの目的は beebotte にメッセージをパブリッシュすること。パブリッシュの手段としてはbeebotteのREST APIを利用する。 そのため、アウトプットには Webhookを利用する。

設定は次の通り。

設定項目 設定値
URL http://api.beebotte.com/v1/data/publish/<Channel名>/<resource名>?token=
Method POST を選択
Content Type application/json を選択
Body {"data": [{"room": "bedroom","device": "light","action":"{{TextField}}"}]}

URL にはbeebotteのメッセージパブリッシュのRESR APIを指定する。先に登録した チャネルとリソースをパスにはめこんで、認証のためクエリパラメータtokenChennel token を設定する。 前述の スクリーンキャプチャに合わせると http://api.beebotte.com/v1/data/publish/MyIoT/light_at_bedroomというのがAPIのURLとなる。

Body には、APIに送信するデータを設定する。ここにはJSON形式で、後にNode-REDで処理する時に必要になりそうなパラメータを送信できるようにする。{{TextField}}には $ にセットされたテキスト、すなわち「オン」または「オフ」が入ることになる。

beebotte に Node−REDで接続

前節までで、Google Homeへの音声コマンドが、beebotte 上のメッセージに変換されるところまでができている。 あとは、Node-REDでそのメッセージを受け、処理するだけ。

Raspberry Pi やその上で動くNode-REDの準備は次に書いてあるとおり。

Node-REDではデフォルトで MQTTがサポートされている。 beebotte と連携するには mqtt in ノードを利用する。

mqtt in ノードの設定はこんな感じ。 トピックには [Channel名]/[resource名]を設定する

f:id:HeRo:20180107191427p:plain

mqtt in ノードのサーバ設定は別フォームが開くので次のように設定する。 セキュリティタブで、ユーザ名に token:[Channel Tokem] を設定するのが肝。

f:id:HeRo:20180107190740p:plain

設定項目 設定値
サーバ mqtt.beebotte.com
ポート 8883
SSL/TLS接続を使用 チェックする。TLSの設定は不要
ユーザ名 token:[Channel Tokem]

設定が正しく、beebotte に接続できる場合にはフローのデプロイ後、次のようにノードに 接続済 と表示される。

f:id:HeRo:20180107192117p:plain

動作確認

ここまでの作業を確認すために次のようなストリームを作って、Google Home => Node-REDに届くメッセージを確認する。

f:id:HeRo:20180108000451p:plain

OK Google, 寝室ライト オン」とGoogle Homeに話しかけて、 Debugノードのデバッグ出力に送ったメッセージが出力されれば成功。

さて、

この先は Node−REDでフローを作って、赤外線を発するまでだけど、ここまでで結構長くなったので、続きはまたあとで。

その2に続く。

hero.hatenablog.jp


  1. IFTTTはまあ大丈夫だろうけど、デバイスメーカーのディスコンを嫌う割にbeebotteなどというイマイチ聞いたことないサービスを使って大丈夫か?とも思ったが、いざとなればHerokuでCloudMQTTを動かすという代替策があるので採用した。ただし、CloudMQTTは無料では同時接続デバイスが10と少々少ない。

*1:今はやっと手元に届いたところ。

Severlessをやってみた

AWSでちょっとした処理を実行するのに Lambdaはとても便利。

これまで、ちょっとしたことをLambdaで実装したことはあったのだが、 ちょっとしたこと以上のコードをAWSコンソール上で書くのはなかなかつらい。

そこで、Serverless Frameworkを使ってみようと思った。

当初、Serverlessを使うメリットとして考えていたのは次のとおり。

  • ローカルの使い慣れたエディタでLambdaを実装できる。
  • Webpack + Babel プラグインを使えば、モダンなJavascriptの機能を使える。
  • デプロイも楽になりそう

試しに簡単なアプリケーションを書いてみた。

サンプルアプリのシナリオ

試してみる課題シナリオは次のとおりとした。

  1. Githubにプルリクを作ったり、プルリクにプッシュするとCodeBuildでビルドを実行する。
  2. プルリクには Github の Status APIで状態を通知する
  3. ビルドの開始をSlackにも通知する
  4. CodeBuild の処理終了をCloudWatch Eventで受け、その結果をGithub の Status APIで更新する
  5. 結果をSlackにも通知する。

作ってみたコードはこちら => HeRoMo/ServerlessSample: My First Serverless Application Sample

やってみて

やってみてどうだったのか?感想を以下に述べる。

関連リソースも一緒に定義可能

予備知識少なめで取り掛かったのでドキュメントを読みながら進めた。 最初はトリガーとなるAmazon SNS等は別に登録して用意しないといけないのかな?と思っていたのだが、 SNSを始め、API Gatewayなど、Lambdaをトリガーできるリソースは serverless.ymlの関数定義で一緒に定義できる。 これはとても便利。 また、Lambdaから他のAWSのリソース・サービスにアクセスするのに必要な権限もserverless.ymlでLambdaの実行ロールに追加できる。 とにかく、Lambdaの実行に必要なものがserverless.ymlで一元管理できるのが管理しやすくてよかった。 最初にデプロイした時、裏でCloud Formationのスタックが作成、実行されてちょっと驚いたが、こういうことなら納得。 Cloud Formationの定義記法で、Lambdaとは直接やり取りしない設定も定義できそうなのでCloud Formationも覚えねばと思った。

やはり async/await は便利

Webpack プラグインとbabelを追加して、ES2015 からNode6.10相当にトランスパイルするように設定した。 Lambdaを使う動機としてはAWSのサービス間の連携を開発するためのグルーコードとして便利というのもあると思う。 実際、AWSSDKは組み込みで動作するので適切な権限がLambdaの実行ロールに付与されていれば、別サービスにアクセスするのは それほど難しくない。 しかし、AWSの実態はHTTPベースのAPIなので、呼び出しはどうしても非同期となる。非同期を多用する必要がある場合には もう、async/awaitが使えないと辛い体になってしまっているので、これは助かった。 AWS SDKの関数は現状、コールバック式なので、早くPromiseを返すようになればと思う。

全般的な感想

前述したメリットはすべて享受できたと思う。 私はJSを書くときにはAtomエディタを使うことが多いが、やはり使い慣れたエディタだと実装しやすい。 webpackでビルドできると、babelでトラスアイルできるだけでなく、自由にnodeモジュールを使うこともできるので、 Github APIやSlack APIのライブラリを利用することができ、その点でも効率的に実装することができた。 デプロイもsls deployワン・コマンドで済むのはとても楽だった。

テンプレートも作った

今後別のアプリケーションを実装するのに便利なように、webpack+babelを設定したアプリケーションの雛形を作った。 ServerlessにはServerlessアプリをgithubから取ってきてそれをひな形に別のアプリを作成する機能が備わっている。 そこもよく考えられているなぁと感心した。

テンプレートはこちら。=> HeRoMo/sls-template: Serverless Framework application template

サイトをチェックするツールを作ってみた

ボランティアで防災・減災関連のwebサイトの情報を収集しているが、そのようなサイトは災害時にわっと作られるが、その後、閉鎖されたり、メンテナンスされなくなってしまうサイトも少なくない。同様な地方自治体のホームページも少なからずある。

時々そのようなサイトの状況をチェックするのに便利なツールがなかったので自分で作ることにした。

最初はHTTPでアクセスしてレスポンスをチェックするだけと思っていたのでcurl等を使ってちょっとしたスクリプトで済ませようと思った。だた実際に確認してみると個人の作ったWeb サイトは思ったより続けられていないことが分かった。 同じドメインが全く違うサイトに変わっていたり、閉鎖されてNot Found ページが返ってくるのに レスポンスコードは200だったり。ドメインも管理者も変わってなさそうだけど、内容が全く変わっているものもあった。

また、文章ではなく画像に情報が書いてあったり、地図等の外部コンテンツを埋め込んでいるサイトも多く、レスポンスのテキストにキーワードが含まれるかどうかだけではどうも判別が難しい。ということで、チェックは目視ということになるが、いちいちURLにアクセスしていチェックしなくても良いようにスクリーンショットをとる機能を実装することにした。

その他、必要な機能を検討し、以下のようなツールを作ることとした。

  • URLが有効かどうか実際にアクセスして確認する
  • その際、リダイレクトにも追従できる
  • レスポンスのステータスコードが200ならスクリーンショットを取る
  • 単一のURLでなく、URLのリストを読ませて複数のサイトを一度にチェックできる
  • 結果を再利用しやすい形で出力する

最初は Phantom.js を使って作った

HTTPのリクエスト/レスポンスだけなら色々方法はあるが、スクリーンショットを取るとなると使えるツールが限られてくる。 実行中にブラウザがぱたぱた動くのも鬱陶しいし、できればGUIを持たないサーバで動かせるようにしたい。 となると、Phantom.js を使うのが最も手軽かと思い、最初のプロトタイプを作った。 実際には、Phantom.js を使いやすくラップしてくれる CasperJS を利用した。

最低限、動くものを作って、さあ、使いやすくブラッシュアップしていこうかなと思った矢先、Chrome 59にて ヘッドレスブラウジング がサポートされ、それに伴い、Phantomのメンテナがやる気を失ったらしい(参考:Phantom.jsのメンテナー、プロジェクトの将来に疑問を呈し、その座を降りる

ということで、PhantomJSをインストールしなくても使えるような実装のほうが良いし、いまやシェアトップとなった Chromeを使うほうが、スクリーンショットの結果も普段使っているブラウザとを乖離せず良かろうと、この実装は捨てることにした。

ヘッドレスChromeへの変更

Chromeをプログラムから操るには、DevToolのリモートプロトコルを使えば良いようで、そのためのライブラリchrome-remote-interface を利用して実装を変更した。 このライブラリ、Chrome DevTools Protocolの機能をほぼほぼカバーしているけれど少々プリミティブ過ぎて、使いづらかった。

例えば、通信が一つ一つハンドリングできるので、ページのレスポンスコードを取るのに、ページからリンクされる画像やCSSその他へのリクエストの中からドキュメントへのリクエストを探して得る必要があるとか、それもリクエストIDが取れるのでそのIDで再度結果を取得する必要があるとか、とにかく煩雑で、やりたいことに対して実装しないといけないことが多かった。おまけに、Chromeそのものを起動する機能は持っていないので、GoogleChrome/lighthouseを使ってChromeの起動を実装する必要があった。

このまま実装を続けても、この先の機能追加とか辛そうと思っていた矢先、GoogleChrome/puppeteer を知った。

puppeteer に切り替え

puppeteer だと、次のようにほんの数行書くだけで、Chromeをヘッドレスで起動して、URLにアクセスしてスクリーンショットを撮ってブラウザを終了してくれる。

// puppeteer でスクリーンショットのサンプル
const puppeteer = require('puppeteer');

(async () => {
  const browser = await puppeteer.launch();
  const page = await browser.newPage();
  const response = await page.goto('https://example.com');
  await page.screenshot({path: 'example.png'});
  console.log(response.status)

  browser.close();
})();

同じことを chrome-remote-interface を利用すると次のようになる。スクリーンショットの保存も自前で、しかもこれにはChromeの起動/終了が含まれていない。

// chrome-remote-interfaceでのスクリーンショットのサンプル。
// Chromeの起動/終了は別途必要。
const CDP = require('chrome-remote-interface');
const fs = require('fs');

CDP(async (client) => {
    const {Page} = client;
    try {
        await Page.enable();
        await Page.navigate({url: 'https://example.com'});
        await Page.loadEventFired();
        const {data} = await Page.captureScreenshot();
        fs.writeFileSync('scrot.png', Buffer.from(data, 'base64'));
    } catch (err) {
        console.error(err);
    } finally {
        await client.close();
    }
}).on('error', (err) => {
    console.error(err);
});

また、puppeteer だと、レスポンスコードも簡単に取れるが、chrome-remote-interface だと更に Networkクラスを使って、Page.navigateのレスポンスコードを探して取り出す必要がある。

これだけ比べても、puppeteer に乗り換えるメリットを感じたし、Chromeチーム謹製というのも安心感があった。 それで、再度実装を変更した。

site-checker として公開

で、作ったツールを この度、公開した。

我ながら、センスのないベタなネーミングに嫌気がさすが、これというのが思いつかなかったし、npmライブラリとしても使われていない名前だったので、この名前にした。

次のコマンドでインストールできる。

$ npm install -g site-checker

インストールしたら、次のコマンドで、スクリーンショットが取れる。

$ site-checker -u http://hero.hatenablog.jp

できることは次の通り。

特に、フルページとエミュレーションは puppeteer に切り替えてとても簡単に実装できた。

詳しい使い方はこちらを参照 => 使い方 · HeRoMo/site-checker Wiki

災害に関わる「言い伝え」をマッピングしてみた

今日は大晦日。2016年も残りあと数時間。

振り返ると熊本地震、北海道の台風に鳥取地震、そして先日の糸魚川の大規模火災に、茨城での震度6弱と大きな災害が多かった一年だったように思う。

起こってしまった災害をなかったことにはできないが、そこから教訓や今後発生する災害の被害を減らすための知恵を得ることはできるはずと思う。

そのようなことは私でなくても考えていて、公開から少し時間が経ってしまっているようだが、消防庁が収集・整理した防災に関わる「言い伝え」 なる資料が 全国災害伝承情報:総務省消防庁 に公開されている。

そこには資料に添えて次のように記されている。

有史以来、全国で発生した災害は各地に多大な被害をもたらし、それらの災害の教訓は各地域において記録としてあるもの、図画として残されているもの、あるいは物語、ことわざとして伝承されているものなどがあります。  そのような災害にまつわる資料や情報は、これまで国として整理されず今日にいたっており、その多くが各地域に埋もれたままとなっています。  全国災害伝承情報は、そうした各地域に残る貴重な資料を、国として整理集約し、インターネットを活用し広く一般に公開することを目的としたもので、平成16年度から平成18年度にかけて都道府県や市町村などの協力をいただき、調査を通じて収集した情報を整理集約しました。  この情報を通じて、身近な地域に残されている災害に対する教訓を個々人に認識していただき、防災意識高揚に役立てていただくとともに、防災教育用の教材としての活用が図られることを期待しています。

防災に関わる「言い伝え」

防災に関わる「言い伝え」はなかなか興味深い資料で、全国の災害・防災に関する800弱もの言い伝えが一覧でまとめられている。 各言い伝えごとにそれが伝わる自治体名が記されているので地図上にマッピングしてみた。なお、統廃合により自治体が消滅しているものは引き継がれた現在の自治体にマッピングして旧名を併記している。

地図上の色の付いたエリアをクリックすると、その土地に伝わる言い伝えを右ペインに表示する。

防災に関わる「言い伝え」MAP

f:id:HeRo:20161231164740p:plain

地図化したデータを見てみると北海道、東北の日本海側と近畿地方で言い伝えが少ないのがわかる。これらの地域ではもともと言い伝えが少なかったのだろうかそれとも、伝承が途絶え収集できなかったのだろうか。近畿は長らく都があり、大勢暮らしていたはずなのに、どうして少ないのだろうか。 逆に伝承の多い地域は、古くから多くの災害に見舞われてきた結果、地域の知恵として言い伝えが受け継がれてきたということなのであろう。

個々の地域を見ていくと、言い伝えの内容からどのような災害に苦しんできたのかがわかる。水害に関する言い伝えが多い地域は水害に、地震に関する言い伝えが多ければ地震に苦しめられることが多かったのであろう。

栃木県には地名に関する言い伝えが多い。古い地名を見るとその土地でよく起こった災害がわかるようだ。災害の記憶をいまに伝える日本全国「あぶない地名」(週刊現代) | 現代ビジネス | 講談社(1/6)にも同じような記事がある。この記事にあるように安易に地名を変えてしまうのは良くないのではと思ってしまう。

言い伝えの類似性に目を向けると「地震が来たら竹やぶに逃げろ」に類する言い伝えは、全国的に多いのだなぁと気が付く。竹の根が地盤を強固にし地割れを防ぐことができると知られていたのであろう。

また、次のような言い伝えもある。

  • 地震のとき「マンダラッコ、マンダラッコ」と唱えるとよい。(神奈川県平塚市)
  • 地震のとき「マンザイロク、マンザイロク」といって、竹やぶに逃げる。(新潟県新潟市
  • 地震のときは「まんぜえろく」と唱える。(埼玉県毛呂山町

一説によると「まんぜいろく」とは「万歳禄」と書き、末永く神の恩恵を受けられますように、という祈願らしい。 同じような言葉が地域を超えて言い伝えられているのはなぜだろうか? 一見、つながりなさそうな地域で同じような言葉が言い伝えられているのも興味深い。昔は互いに交流があったのだろうか?

雑雑とした印象・感想を綴ったが、眺めていると色々発見がありそうな資料だ。