前回の『ドアの動き検知システムの構築2』ではAWS IoTのルールを作成しドアの動き情報を時系列データとしてS3に保存しAmazon Athenaから検索できるようになった。
今回はこれに加えドアの動きをSlackへ通知する機能を追加する。
今回の目的
今回の目的は2つ。
- Slackへの通知機能追加
- サーバサイドClojureScriptでFigwheelを試す
Slackへの通知機能追加
AWS IoTのルールアクションを追加、ドアの動きと連動してSlack通知用AWS Lambdaが呼び出されるようにする。
一回のドア開閉ではドアの細かい動きに連動し数回の加速度情報が発生する。
これを全てSlackへ通知してもあまり意味が無いのでドアの動きを検知した際に前回の検知日時から3分以上経っていた場合のみSlackへの通知を行うようにする。
サーバサイドClojureScriptでFigwheelを試す
『ClojureScriptでLambd@Edge』や『打刻情報をKinesis FirehoseでS3へ保存』でもClojureScriptでLambdaを書いたが、REPL駆動開発時におけるコードのリロード方法について課題が残っていた。
今回課題だったのがREPL環境。 REPL自体はcljs.repl/repl*を直接使いNode.js環境のREPLを問題無く利用する事ができたのだが、 肝心のREPL駆動開発時におけるコードのリロード方法のセオリーが分からなかった。 (tools.namespaceやfigwheelのような)
ということで今回は致し方なく素朴にrequireをreloadフラグ付きで利用した。
Figwheelといえばフロントエンド開発で利用するイメージしか無かったが最近ではサーバサイド開発でも利用できる。
今回はFigwheelを使用してどのような塩梅なのかを確認する。
Slackの設定
LambdaからSlackのAPIを叩く際に使用するBotユーザを登録する。
https://my.slack.com/services/new/bot
- Username
- Botユーザ名
Add Bot Integeration で登録。
API Token
はSlackのAPI呼び出し時に使用するのでメモしておく。
Save Integration で保存。
Slackのフリープランには最大10個のアプリとインテグレーションまでという制約がある。 また特に機能に応じて個別のBotを払い出す必要も無いためインテグレーション数の節約のため[『TwitterのリストタイムラインをSlackへ連携する』](/post/slack-bot-listening-to-twitter-lists/)で作成したボット[chabonze](https://github.com/nijohando/chabonze)のAPI Tokenを利用することにした。
Lambda実行環境の準備
ドアの動き情報をSlackへ通知するLambda関数 naruko-slack-notifier
の実行環境を整えるため以下を行う。
- ロールの作成
- S3バケットの作成
ロールの作成
Lambda関数 naruko-slack-notifier
を実行する為のロールを定義する。
AWS マネージメントコンソールから IAM を開き ロール から 新しいロールの作成 AWSサービス から Lambda を選択。
次のステップ:アクセス権限
ポリシー AWSLambdaBasicExecutionRole
を検索し選択。
次のステップ:確認
- ロール名
naruko-slack-notifier
ロールの作成 で作成完了。
S3バケットの作成
Lambdaのデプロイパッケージを配置するためのバケットを作成する。
AWS マネージメントコンソールから S3 を開き バケットを作成する
- バケット名
naruko.nijohando.jp
- リージョン
Tokyo
次へ 、プロパティ設定、アクセス許可設定はスキップし バケットを作成 を実行。
Lambdaの実装
ClojureScriptでLambda naruko-slack-notifier を作成。
Figwheelを利用するための準備
figwheelを利用するため、project.clj
に lein-figwheel
プラグインを追加。
:plugins [[lein-figwheel "0.5.14"]]
dev dependenciesに figwheel-sidecar
を追加。
:profiles {:dev {:dependencies [[figwheel-sidecar "0.5.14"]
今回はlein-cljsbuildを使わずにcljs.build.api を直接使いビルドしているためFigwheelについてもScriptingで実行している。
src/dev/clj/tools.clj
の repl
関数からfigwheelを起動するようにした。
(defn repl
[]
(ra/start-figwheel!
{:figwheel-options {} ;; <-- figwheel server config goes here
:build-ids ["dev"] ;; <-- a vector of build ids to start autobuilding
:all-builds ;; <-- supply your build configs here
[{:id "dev"
:figwheel true
:source-paths [cljs-src-dir]
:compiler cljs-dev-compiler-opts}]})
(ra/cljs-repl))
またサーバサイドClojureScriptでFigwheelを利用する場合、npmパッケージ ws が必要となる。
上記Figwheelの :compiler
オプションで指定した cljs-dev-compiler-opts
には以下のように npm-deps で ws を指定するようにしている。
(def cljs-npm-deps {})
(def cljs-dev-npm-deps {:ws "4.0.0"})
(def cljs-compiler-opts {:output-dir cljs-out-dir
:output-to cljs-output-to
:optimizations :simple
:source-map cljs-source-map
:npm-deps cljs-npm-deps
:install-deps true
:target :nodejs
:verbose true})
(def cljs-dev-compiler-opts (merge cljs-compiler-opts {
:main cljs-main-ns
:npm-deps cljs-dev-npm-deps
:optimizations :none
:source-map true}))
project.clj
にタスク エイリアス fwrepl
を追加し、上記 repl
関数を呼び出すようにする。
:aliases {"fwrepl" ["run" "-m" "clojure.main" "src/dev/clj/tools/repl.clj"]
以上でFigwheelを利用する準備が整った。
Figwheelを利用する
Leiningenからfigwheelを起動する。
$ lein fwrepl
Figwheel: Starting server at http://0.0.0.0:3449
Figwheel: Watching build - dev
Compiling "target/out/index.js" from ["src/main/cljs"]...
Installing Node.js dependencies
・・・
Successfully compiled "target/out/index.js" in 9.989 seconds.
Launching ClojureScript REPL for build: dev
Figwheel Controls:
(stop-autobuild) ;; stops Figwheel autobuilder
(start-autobuild [id ...]) ;; starts autobuilder focused on optional ids
(switch-to-build id ...) ;; switches autobuilder to different build
(reset-autobuild) ;; stops, cleans, and starts autobuilder
(reload-config) ;; reloads build config and resets autobuild
(build-once [id ...]) ;; builds source one time
(clean-builds [id ..]) ;; deletes compiled cljs target files
(print-config [id ...]) ;; prints out build configurations
(fig-status) ;; displays current state of system
(figwheel.client/set-autoload false) ;; will turn autoloading off
(figwheel.client/set-repl-pprint false) ;; will turn pretty printing off
Switch REPL build focus:
:cljs/quit ;; allows you to switch REPL to another build
Docs: (doc function-name-here)
Exit: Control+C or :cljs/quit
Results: Stored in vars *1, *2, *3, *e holds last exception object
Prompt will show when Figwheel connects to your application
ClojureScriptからJavaScriptがコンパイルされFigwheelサーバが立ち上がる。
別のコンソールからコンパイルで生成されたindex.js
をNode.jsで実行。
$ node target/out/index.js
Figwheel: trying to open cljs reload socket
Figwheel: socket connection established
Figwheelサーバへの接続が確立した旨のメッセージが表示され、先ほどFigwheelサーバを起動したコンソール側でREPLが利用できるようになる。
Prompt will show when Figwheel connects to your application
To quit, type: :cljs/quit
dev:cljs.user=>
Figwheelサーバをあげアプリケーション側が接続しREPLが利用可能になるというフロントエンド開発時のブラウザREPLと同じ要領。
ClojureScriptを修正して保存する度にJavaScriptへリコンパイルされNode.js側へ反映、REPLから変更後のコードを操作できる。
Lambdaのデプロイ
パッケージの作成
デプロイパッケージの作成処理についてもsrc/dev/clj/tools.cljの中で自前で行っている。
(defn package
[]
(clean-node-modules)
(build)
(do*
(let [zip (ZipOutputStream. (jio/output-stream (str target-dir "/" archive-file-name)))
_ (defer (.close zip))
main-js (jio/file cljs-output-to)
source-map (jio/file cljs-source-map)
append (fn [^String entry-name ^File f]
(.putNextEntry zip (ZipEntry. entry-name))
(jio/copy f zip)
(.closeEntry zip))]
(append (.getName main-js) main-js)
(append (.getName source-map) source-map)
(doseq [f (file-seq (jio/file "node_modules")) :when (.isFile f)]
(append (.getPath f) f)))))
開発時はFigwheelが必要とする ws が node_modules 配下へインストールされている。
パッケージングの際はこのような dev dependencies なnpmパッケージを含めないようにするため clean-node-modules
で一度 node_modules を削除し本番用の :npm-deps
で node_modules を再構築しパッケージングしている。
もう少し良き方法がないものかと思っているが一旦はこの方法で。。。
fwreplの時と同様にproject.clj
にタスク エイリアス package
を追加し、上記 package
関数を呼び出すようにする。
:aliases { ;;他のタスク エイリアス・・・
"package" ["run" "-m" "clojure.main" "src/dev/clj/tools/package.clj"]}
パッケージの配備
Lambda関数 naruko-slack-notifier
のデプロイパッケージをAWS上へ配備する。
今回はCIによる自動デプロイは行わず手動によるデプロイとする。
またデプロイについてはnervous-systems/cljs-lambdaを利用する方法もあるが、今回は食わず嫌いで利用を見送りとした。
デプロイパッケージの作成
lein package
S3に配置
aws s3 cp target/naruko-slack-notifier.zip s3://naruko.nijohando.jp
lambdaの登録
AWS マネージメントコンソールから Lambda を開き 関数の作成
一から作成 からを選択。
- 名前
- naruko-slack-notifier
- ランタイム
- Node.js 6.10
- ロール
- 既存のロールを選択
- 既存のロールを選択
naruko-slack-notifier
関数の作成
次にトリガーを設定する。
トリガーの追加 から Aws IoT を選択。
トリガーの設定を行う。
- IoTタイプ
- カスタムIoTルール
- ルール
- NarukoDoor1FirehoseS3
- トリガーの有効化
- ✓
追加 でトリガを追加し 一旦 保存 で関数を保存する。
再度 関数 naruko-slack-notifier
を選択し残りの設定を行う。
関数コード から
- コードエントリタイプ
- Amazon S3からのファイルのアップロード
- ランタイム
- Node.js 6.10
- ハンドラ
index.handler
- S3リンクのURL
https://s3-ap-northeast-1.amazonaws.com/naruko.nijohando.jp/naruko-slack-notifier.zip
環境変数 から naruko-slack-notifier
の実行に必要な環境変数を定義する。
- SLACK_API_TOKEN
- SlackのAPIトークン
- SLACK_CHANNEL
- 通知したいチャネル名 (例:
#home
) - SLACK_USERNAME
- Slack上の表示されるメッセージ送信者名 (例:
door1
) - DETECTION_THRESHOLD
- Slackへ通知する条件の閾値(秒)
ドアが動いた時間から前回ドアが動いた時間の差(秒)がこの値以上であればSlackへ通知を行う。
(例:180
)
テスト して 保存
以上でドア開閉のタイミングでSlackへ通知が来るようになり、元々やりたかったことが全て出来る様になった。
現在は玄関ドアを対象にして運用しているが郵便受けや宅配ボックスも対象にしたら色々捗りそうだ。
構成的にもRaspberry Pi3とMONOSTICKは今のままで
TWELITE 2525Aを買い足すだけで対応できるので折を見て他へ展開してみようと思う。