Adways Advent Calendar 4日目の記事です。
http://blog.engineer.adways.net/entry/advent_calendar/archive
こんにちは。久保田です。
さて、突然ですが、我が社アドウェイズは大きな問題を抱えています。
それは、 「トイレ」 です。
人数に対して若干トイレが少ないのか、よく辛そうに並んでいる方々を見受けます。
この問題はただ心や体が辛いだけでなく、待ち時間があることで業務も滞ります。
チリも詰まれば、なので、最終的に会社にとって大きな損害であるわけです。
問題があったら解決するのがエンジニアの仕事。
というわけで、今回は試験的に僕が作った「トイレセンサー」と周辺技術のお話をご紹介します。
概要
今回この状況を改善するため、 「自席からでもトイレの空き状況がわかるシステム」 を作ることにしました。
そのため、こちらの記事を参考にさせていただき、トイレセンサーを作りました。
このセンサーを中心にシステムを作り上げて行きます。
以下のような設計になります。
トイレのドアにセンサーを仕掛けておき、開閉状況を受信機であるラズベリーパイに送信し、ラズベリーパイは信号を受け取ったら保存しておきます。
そしてラズベリーパイをAPIサーバーとしても稼働させておき、SlackBotやwebページからトイレの状況を確認できるようにします。
トイレセンサー
上でも述べたように、
こちらの記事を参考にさせていただき、ほぼそのまま行いました。
道具などもほぼ同じものを使っています。
電子工作初心者の僕でもわかりやすく、非常に助かりました!
僕はTWE-Lite Rの設定をMacで行ったので、少しメモしておきます。
ファームウェアの書き込み
まず、SDKをインストールします。
http://mono-wireless.com/jp/products/TWE-NET/TWESDK.html
そして、こちらの公式のページを参考にファームウェアの書き込みを行いました。
http://mono-wireless.com/jp/tech/misc/jenprog/index.html
usbでtwe-liteとPCをつなぎ、デバイスの確認をします。
$ ls /dev/tty.usbserial* #=> /dev/tty.usbserial-xxx
次に、SDKの中に入っているjenprogを使ってファームウェアを書き込みます。
書き込むファイルはこちらのページからインストールします。
http://mono-wireless.com/jp/products/Software_download/index.html
jenprog -t /dev/tty.usbserial-xxx App_TweLite\Master\Build\App_TweLite_Master_JN5164_1_7_1.bin
ハマったところは、書き込みがうまく行かず、何度か試したらうまくいったことです。
マイコンてそんなものかな。。と思い進みました。
TWE-Lite Rの設定
次はUSB経由でTEW-LiteをPCと接続し、設定を行います。
sudo cu -s 115200 -l /dev/tty.usbserial-***
+を三回押すと、こんな画面に切り替わるので、
センサー側、受信機側の設定を行います。 ここの設定でデータを送信する間隔などが設定できます。
回路の作成
こちらも参考にさせていただき、真似させていただきました。 ドアが閉じた瞬間の検出にするように組んであります。
受信側の設定
受信側に設定したTWE-Liteをラズベリーパイに接続します。
まずは、ちゃんとセンサーに変化があった時にデータが送られることを確認します。
ラズパイにcuを入れて
sudo apt-get install cu
出力します。
cu -s 115200 -l /dev/ttyUSB0
こんな感じで送られてきたら成功です。
35桁目が0だったらセンサー同士が離れている(空いている)、1だったら近づいている(閉まっている)
という状況です。
これで動作確認ができたので、このデータを好きなプログラミング言語で処理するプログラムを作って、プロセスを動かしておけばOKです!
僕はRubyで処理しました。
今回はセンサーごとにファイルを作り、ファイルに状態を保存することにしました。
require 'serialport' sp = SerialPort.new('/dev/ttyUSB0', 115200) Process.deamon loop do line = sp.gets if line.length > 5 puts line File.open("/home/pi/ngx_mruby/build/nginx/mruby/#{line[1]}#{line[2]}", "w") do |f| f.puts("#{line[33]}#{line[34]}") end end end
このプログラムを動かしておくと、センサーごとにファイルが作られ、
01のファイルに00
02のファイルに01
といった形式でデータが保存されます。
(状態が2連続で続くと一桁目が8になります。)
APIサーバーの構築
次はラズベリーパイをAPIサーバー化します。
ラズベリーパイにあまり負担をかけたくないので、比較的軽量で動かすため、Railsなどは使わず、
nginxにmrubyを組み込んだ、ngx_mruby(https://github.com/matsumotory/ngx_mruby)を使います。
少し話が横道にそれますが、ラズベリーパイでこちらのngx_mrubyがうまく動かない問題が発生しました。
僕の力ではどうにもならなかったので、プルリクを作らせていただいたところ、解決まで導いていただきました。
OSSってすごい、、と思いました。
ありがとうございました。
https://github.com/matsumotory/ngx_mruby/issues/229
ngx_mrubyからmrubyを使い、保存したデータをjsonに直して、ホストするようにしました。
#files = ['01', '02', '03'] files = ['01'] #(※今はテスト段階なので、01のセンサーのみ) base_path = '/home/pi/ngx_mruby/build/nginx/mruby/' h = {} files.each do |file| File.open(base_path + file) do |f| h[file] = f.read end end r = Nginx::Request.new r.content_type = "application/json" Nginx.echo h.to_json
こうして、nginx.confに上のプログラムが動くように設定し、リクエストを投げると、以下のようなレスポンスが返ってきます。
pi@raspberrypi:~/ngx_mruby/build/nginx $ curl 127.0.0.1 -i -s HTTP/1.1 200 OK Server: nginx/1.11.6 Date: Sun, 04 Dec 2016 16:58:08 GMT Content-Type: application/json Content-Length: 14 Connection: keep-alive {"01":"80\n"}
これでAPIサーバーは完成です。
Botの作成
最後にクライアントである、Botを完成させます。
なんでも作りたがりの僕は、hubotなどは使わずに構築しました。
SlackのRTM API(https://api.slack.com/rtm)を使ってwebソケットで常に繋いでおくようにしたのですが、
意外と簡単に接続が切れてBotが落ちてしまうため、
lxcを使ってコンテナ化し、監視用のプログラムを作り、落ちたらコンテナごと再起動しまたbotを動かす、という風な設計にしました。
bot本体
bot.rb
require 'websocket-client-simple' require 'open-uri' require 'faraday' require 'json' class RestRoom module Util SDGRESTROOMS = (1..3) class << self def sdg?(no) SDGRESTROOMS.include?(no.to_i) end def available?(s) s.to_i == 0 end end end def initialize(state) @state = JSON.parse(state) end def to_message "to_message" @state.map do |no, s| "room #{no}[#{Util.sdg?(no) ? 'SDG' : 'BDG'}] is #{Util.available?(s) ? 'available' : 'close now...'} " end.join("\n") end end pid = fork do conn = Faraday.new(:url => 'https://slack.com') do |faraday| faraday.request :url_encoded faraday.adapter Faraday.default_adapter end res = conn.post "/api/rtm.start", :token => "token" url = JSON.parse(res.body).to_h['url'] wss = WebSocket::Client::Simple.connect url id = 1 wss.on :message do |msg| data = JSON.parse(msg.data) if data['text'] && (data['text'] == '<> tell me') # どのような形式で話しかけてきたら答えるようにするかの条件 type = { 'id': id, "type": "typing", "channel": ch }.to_json wss.send type id += 1 rr = nil open('ラズパイのエンドポイント') do |f| rr = RestRoom.new(f.read) end j = { "id": id, "type": "message", "channel": ch, "text": rr.to_message }.to_json wss.send j end id += 1 end loop do end end
監視用のプログラム
monitoring.rb
require 'faraday' require 'json' def bot_run `sudo lxc-stop -r -n slack-bot-poo-001` # lxcによるコンテナの再起動 end token = "slackのtoken" user = 'botのユーザーID' while true conn = Faraday.new(:url => 'https://slack.com') do |faraday| faraday.request :url_encoded faraday.adapter Faraday.default_adapter end res = conn.post "/api/users.getPresence", {:token => token, :user => user} # 10秒に一度botの状態をチェックする。 JSON.parse(res.body)['presence'] == 'active' ? true : bot_run sleep 10 end
lxcでコンテナを作り、bot.rbを動かし、
monitoring.rbをホストOSで動かして監視しておけばOKです!
まとめ
さて、これで全ての用意が整いましたので、今週中にトイレに設置しテストを始めたいと思います。
これで社員の皆様のトイレの悩みがなくなり、皆様の業務が捗ることを期待しております。
ではまたどこかで。。。
次は大曲さんの記事です。