Node-RedとMQTTでスマホの位置情報をリアルタイムでモニタする

# はじめに
スマホの位置情報を簡単に利用する方法がないか調べていたところ、[OwnTracks](http://owntracks.org/)というスマホアプリが便利そうだったので試してみた。

OwnTracksはMQTTでスマホの位置情報をMQTT Brokerに定期的にPublishしてくれるアプリであり、アプリをインストールしている友達同士で位置情報を共有したり、特定の領域内にスマホが入ったことを検知するGeo-Fenceといった機能を使うことが出来る。

このOwntracksを使って、「**自分のスマホの位置情報をリアルタイムにずーっとトラッキングし続けるWebページ**」をBluemixのNode-Redで作ってみた。


# どんな構成か

OwntracksからMQTTでスマホの位置情報を、クラウドサービスを利用して作成したMQTT Brokerに定期的にPublishし、その位置情報をNode-Red上に作成したSubscriberが読み出して、Google Map上に表示するという簡単なもの。

| 役割 | ツール  | コメント |
|:-----------|:------------|:------------|
| ** Publisher**       | [OwnTracks](http://owntracks.org/) | スマホの位置情報を、設定されたMQTT BrokerにPublishする |
| **Broker**     | [Cloud MQTT](http://www.cloudmqtt.com/) | MQTT Brokerを一瞬でクラウド上に作成できるサービス。    |
| **Subscriber**  | [Bluemix](https://console.ng.bluemix.net/) / [Node-Red](http://nodered.org/) | Node-Redで提供されているMQTTのデータをSubscibeするNodeを使用 |

# MQTT Brokerのセットアップ ( MQTT Cloud )
MQTTでの通信を行うためには、MQTT Brokerが必要であり、[Mosquitto](http://mosquitto.org/), [Rabbite MQ](https://www.rabbitmq.com/mqtt.html)といったサーバー製品をLinuxマシンやRaspberry Piといったボードコンピュータにインストールして構成するのが一般的なようだ。

ただ、調べてみると、下記のようなMQTT Brokerをクラウド上で一瞬で構築できるサービスがあるようで、今回はMQTT Cloudというサービスを使ってみた。
* [Cloud MQTT](http://www.cloudmqtt.com/)
* [Sango](https://sango.shiguredo.jp/) ( 株式会社時雨堂という日本の会社が提供するMQTT as a Service )

#### 1. MQTT Cloudのアカウント取得
Topページから、上部ナビゲーションバーの「Plan」を選択し、次の画面でFree Accountを選択し、Sing up。
![2015-08-09_23h33_20.png](https://qiita-image-store.s3.amazonaws.com/0/86169/23cac351-91de-bc76-3c48-4b2815b2b35f.png)


#### 2. MQTT Broker Instanceの作成
Sing up後、「Control Panel」より、「+ Create」ボタンを押して、MQTT Broker Instanceを作成。インスタンス名の入力だけで、他は特に変更せずにOK。
インスタンスの詳細を表示すると下記のような情報が表示される。下記の情報をPublisher/Subscriber双方で使用するので、メモをしておく。
* server
* User
* Password
* Port

![2015-08-10_09h21_58.png](https://qiita-image-store.s3.amazonaws.com/0/86169/3b0ad513-0820-678e-9021-d0c2c1ffdebf.png)


# MQTT Publisherの設定（ Owntracks ）
アプリケーションをダウンロードして、Publisher側の設定をする。これから設定する内容の概要を説明すると、「MQTT Brokerへのログイン情報を設定し、位置情報をPublishするTopicを設定する」といった作業をすることになる。

アプリを起動し、「Settings」にて、Modeを「Private」にし、下記の4項目の設定を行う。
*** Host - 作成したMQTT Broker インスタンスの「Server」のアドレス**
* **Port - 同じくインスタンスの「Port」** (今回はSSLを使用しないので、SSL Portではなく、ただのPort）
* **UserID - 同じくインスタンスの「User」**
* **Password - 同じくインスタンスの「Password」**
* **Device ID - 任意のデバイス名を指定**

![2015-08-10_10h02_39.png](https://qiita-image-store.s3.amazonaws.com/0/86169/12c939d3-e276-34b2-fcf0-3422b19134ce.png)


OwnTracksでは、UserIDとDeviceIDを元に、位置情報をPublishするTopic名が決められる。Topicは特定のデータの投稿先で、PublisherがTopicにデータを送信し、SubscriberはTopicを購読するようにBrokerにSubscribeしておき、BrokerはSubscribeしているSubscriberにTopicに届いた情報を配信する。Topic情報は、Subscriberを設定する際に必要になるので、メモしておく。

 **owntracks/[UserID]/[DeviceID]**

設定後、アプリ画面上部の「Check」をタップし、「Connnected」となれば、MQTT Brokerへの接続が成功である。

# MQTT Broker上での接続確認
OwnTracksから位置情報が正しくMQTT BrokerにPublishされているかどうかを確認する。
MQTT Cloudでは、Brokerに届いたメッセージを**WebSocketを用いてリアルタイムで確認可能なツール**があるので、これを使って動作確認をする。MQTT Cloudのインスタンスの詳細情報画面（ServerアドレスやUser名、Passwordが確認出来る画面）の上部に、「**WebSocket UI**」というボタンあるので、コレをクリックすると確認可能だ。

正しくPublishされていると下記のように、OwnTracksがPublishした位置情報が表示される。

![2015-08-10_10h21_07.png](https://qiita-image-store.s3.amazonaws.com/0/86169/1e1d3f71-aad8-6c11-2482-7a75f74839eb.png)


#### 注意点
* スマートフォンにて、OwnTracksがGPS等の位置情報にアクセス可能なように設定すること。
* テストが終わったら、位置情報の配信は止めること（止めた方が良いです。。）

# Subscriberの設定（ Node-Red )
MQTT BrokerのTopic（**owntracks/[UserID]/[DeviceID]**）を購読するようにSubscribeする。（Node-Redの使用方法は他のQiitaの記事等を参照してください。）

Subscriberとして使用するのは、下記の「MQTT In」Node。
![2015-08-10_10h49_48.png](https://qiita-image-store.s3.amazonaws.com/0/86169/7334904f-1d18-59cc-d51b-c67cab20560c.png)


下記のように設定する。設定内容としては、OwnTracksとほぼ同じ内容である。
![2015-08-10_10h48_53.png](https://qiita-image-store.s3.amazonaws.com/0/86169/dc61713f-76f8-43e4-a17c-3261ddc13b5f.png)



Topicには、owntracks/[UserID]/[DeviceID]を指定する。
![2015-08-10_10h53_21.png](https://qiita-image-store.s3.amazonaws.com/0/86169/37c74d79-7006-2804-9f70-d81521485ea4.png)


うまく接続されれば、下記のようにNodeの下に「Connected」と表示され、Debug Nodeにつなぎ、OwnTracks側から「Publish Now」を押して、位置情報をPublishすると、Topicが受信でき、結果がNode-RedのDebug Windowに表示される。

![2015-08-10_10h54_26.png](https://qiita-image-store.s3.amazonaws.com/0/86169/1f3c8f52-c4fe-cc36-5cf8-4fc2a8cea000.png)
![2015-08-10_10h57_13.png](https://qiita-image-store.s3.amazonaws.com/0/86169/95fb1e43-03a6-3ec3-7249-7b4b2ef30e79.png)



# 位置情報のマッピング
Node-Redでの位置情報のマッピングは[こちらのQiitaの投稿](http://qiita.com/916/items/621d44615b48ae0dfe9d)が参考になる。ほとんど同じ実装で、WebsocketでMQTTから得られた位置情報をリアルタイムでGoogle Map上に表示させている。

下記の設定だと、Bluemixで割り当てられたApplicationのBase URLに、/mapを加えたURLにブラウザからアクセスすると、スマホの位置がGoogle Map上に表示される。

![2015-08-10_11h13_53.png](https://qiita-image-store.s3.amazonaws.com/0/86169/4ab092dc-a1e1-b967-accc-8039bf0e59b2.png)

上記のFlowは下記の情報をNode-RedでImportしてください。

>[{"id":"2cd7f590.29844a","type":"websocket-listener","path":"/ws/location","wholemsg":"false"},{"id":"bdaa2a8.f98e958","type":"mqtt-broker","broker":"m11.cloudmqtt.com","port":" \t10212","clientid":""},{"id":"3261c734.ff3838","type":"mqtt in","name":"","topic":"owntracks/ealsglxm/iphone","broker":"bdaa2a8.f98e958","x":209.33331298828125,"y":70.33334350585938,"z":"35bec190.918f46","wires":[["b58b0a8e.4d8d58","c7b9da8b.7929f8"]]},{"id":"b58b0a8e.4d8d58","type":"debug","name":"","active":true,"console":"false","complete":"false","x":428.33331298828125,"y":71.33332824707031,"z":"35bec190.918f46","wires":[]},{"id":"b4b761b2.d40b8","type":"websocket out","name":"","server":"2cd7f590.29844a","x":813.3333129882812,"y":226.33334350585938,"z":"35bec190.918f46","wires":[]},{"id":"92142786.1ec2","type":"websocket in","name":"","server":"2cd7f590.29844a","client":"","x":391.33331298828125,"y":258.3333435058594,"z":"35bec190.918f46","wires":[["44852596.df2134"]]},{"id":"a35c3ced.3c69d","type":"http in","name":"","url":"/map","method":"get","swaggerDoc":"","x":280.33331298828125,"y":328.3333435058594,"z":"35bec190.918f46","wires":[["236a7bcb.035bb4"]]},{"id":"dbbda48e.7bacc","type":"http response","name":"","x":728.3333129882812,"y":328.3333435058594,"z":"35bec190.918f46","wires":[]},{"id":"236a7bcb.035bb4","type":"template","name":"","field":"","template":"<!DOCTYPE html>\n<html>\n<head>\n  <title>Owntracks & The ThingBox Live Map</title>\n  <script type=\"text/javascript\" src=\"http://ajax.googleapis.com/ajax/libs/jquery/1.6.4/jquery.min.js\"></script>\n  <script type=\"text/javascript\" src=\"http://maps.google.com/maps/api/js?sensor=true\"></script>\n  <script type=\"text/javascript\" src=\"http://yourjavascript.com/4594301102/gmaps.js\"></script>\n \n  <style type=\"text/css\" media=\"screen\">\n    #map {\n      position:absolute;\n      top: 0; bottom: 0; left: 0; right: 0;\n    }\n  </style>\n</head>\n<body>\n \n  <div id=\"map\"></div>\n  <script type=\"text/javascript\">\n  var socketaddy = \"ws://phone-location.eu-gb.mybluemix.net/ws/location\";\n    var map;\n    var sock;\n    $(document).ready(function(){\n      \n      map = new GMaps({\n        div: '#map',\n        lat: -12.043333,\n        lng: -77.028333\n      });\n      \n      \n      sock = new WebSocket(socketaddy);\n      sock.onopen = function(){ console.log(\"Connected websocket\");\n\t      console.log(\"Sending ping..\");\n\t      sock.send(\"Ping!\");\n\t      console.log(\"Ping sent..\");\n      };\n      sock.onerror = function(){ console.log(\"Websocket error\"); };\n      sock.onmessage = function(evt){\n        var latlng = JSON.parse(evt.data);\n        var array = $.map(latlng, function(el) {\n  \t\t\treturn [[el.lat, el.lng]];\n\t\t\t});\n        \n        map.removeMarkers();\n        map.removePolylines();\n       \tconsole.log(\"Got marker at \" + latlng[0].lat + \", \" + latlng[0].lng, latlng);\n        map.setZoom(17);\n       \tmap.setCenter(latlng[0].lat, latlng[0].lng);\n        map.addMarkers(latlng);\n      \tmap.drawPolyline({\n\t\t  path: array,\n\t\t  strokeColor: '#131540',\n\t\t  strokeOpacity: 0.6,\n\t\t  strokeWeight: 6\n\t\t});\n      }\n    });\n  </script>\n</body>\n</html>","x":500.33331298828125,"y":328.3333435058594,"z":"35bec190.918f46","wires":[["dbbda48e.7bacc"]]},{"id":"44852596.df2134","type":"function","name":"function 2","func":"// The received message is stored in 'msg'\n// It will have at least a 'payload' property:\n//   console.log(msg.payload);\n// The 'context' object is available to store state\n// between invocations of the function\n//   context = {};\n\nmsg.payload = context.global.location;\n\nreturn msg;","outputs":1,"noerr":0,"x":621.3333129882812,"y":258.3333435058594,"z":"35bec190.918f46","wires":[["b4b761b2.d40b8"]]},{"id":"a02ee9d2.dd6b88","type":"function","name":"function 1","func":"// The received message is stored in 'msg'\n// It will have at least a 'payload' property:\n//   console.log(msg.payload);\n// The 'context' object is available to store state\n// between invocations of the function\n//   context = {};\ncontext.global.location = msg.payload;\n\nreturn msg;","outputs":1,"noerr":0,"x":618.3333282470703,"y":197.33334350585938,"z":"35bec190.918f46","wires":[["b4b761b2.d40b8"]]},{"id":"65ff2d00.eea4dc","type":"function","name":"Adaptation","func":"var latitude = msg.payload.lat;\nvar longitude = msg.payload.lon;\nmsg.payload = '[{\"lat\":\"' + latitude + '\",\"lng\":\"' + longitude + '\"}]'; \nreturn msg;","outputs":1,"noerr":0,"x":454.33331298828125,"y":198.33334350585938,"z":"35bec190.918f46","wires":[["a02ee9d2.dd6b88","8e4087bf.d5131"]]},{"id":"8e4087bf.d5131","type":"debug","name":"","active":true,"console":"false","complete":"false","x":629.3333129882812,"y":124.33331298828125,"z":"35bec190.918f46","wires":[]},{"id":"c7b9da8b.7929f8","type":"json","name":"","x":413.33331298828125,"y":127.33334350585938,"z":"35bec190.918f46","wires":[["65ff2d00.eea4dc"]]}]