柏の葉 IoTハッカソンで技術賞を受賞しました!

2018/02/19

IT/IoT お知らせ

私の提案(プログラムの実装)

アイディアを踏まえて、どのようなプログラムを実装すればいいのかを考えました。まずは概要図を見て下さい!

車両情報更新の図
車両情報更新の図

車両呼出、JSON送信の図
車両呼出、JSON送信の図

サーバーには、安いVPSを利用

色々考えた結果、まずはサーバーを用意しよう!ということに。とりあえずデモ段階、ということで、DTI serversmanのエントリープランをレンタルしました。とても安いのにスペックもよく、コスパとってもいいです!

ただ、CentOS7と相性が悪い(yumでアップデートができない)ので、CentOS6.9をインストールしました。

CentOS6系をインストールしたので、Apacheは2.2、phpは5系、など色々制限が出ました。phpは7系を使いたかったので、全部ソースビルドで最新のを入れました。

Apache2.4、php7.1、mysql5.7、Ruby2.4.3をインストールしました。

データベースがシステムの要

今の状態をMotionBoardで見たり、呼出を随時かけたりすることを考えて、データベースをシステムの主役としました。

車両状態に関するテーブルと、タクシープール、つまり駐車場情報に関するテーブルの2つを作成しました。

車両状態に関するテーブル

車両状態に関するテーブルは、次の項目を作りました。

  • IC番号
  • ナンバープレート
  • 会社名
  • 車両ステータス
  • タクシープールのID
  • サブプール入庫時刻
  • 最終信号送信時刻

IC番号はICカード等の番号、ナンバープレート・会社名はそのままです。タクシープールのIDは駐車場情報に登録しておいたIDを使用します(詳しくは駐車場情報に関するテーブルの項目で)。サブプール入庫時刻は、呼出順を決定するのに使用します。場所に関係なく「入庫した順番」でリストを作るためです。最終信号送信時刻は、何らかの原因でLoRaWANデバイスから情報が二重に送信された時に誤作動させないために使用します(一度信号を受け付けたら20秒は受け付けない、など)。

ポイントになるのが「車両ステータス」で、0~7の数字で車両の状態を表しています。

車両ステータス
0 タクシープールと関係ない状態
1 サブプールに入庫した
2 サブプールにいて、呼出がかかった
3 サブプールにいて、呼出がかかり、出庫した
4 メインプールに入庫した
5 メインプールで呼出がかかった
6 メインプールで呼出がかかり、出庫した
7 駅前ロータリーに入った

ステータス7のあとは「お客さんを乗せて出発」しますが、その時にステータス0に戻します。これなら適切に車両の状態を管理・把握でき、違反車両がいたらすぐにわかります。

駐車場情報に関するテーブル

駐車場情報に関するテーブルは、次の項目を作りました。

  • 駐車場名
  • 駐車場ID
  • devEUI
  • 駐車場カテゴリID
  • 最大収容台数

駐車場名はそのままです。IDは、データベースへ駐車場情報を登録する時に順番に割り振ります。車両情報のテーブルに登録する「タクシープールのID」はこれです。devEUIは、設置したLoRaWANデバイスのdevEUIです。複数台設置したなら、複数の値が登録されます。駐車場カテゴリは、「サブプール」メインプール」「駅前」のどれなのかがわかるように番号を割り振ります。最大収容台数は、そのままで、何台まで停められるのかという意味です。

phpでデータベースを読み書きする

phpは3ファイル用意しました。

phpファイル
car_status_update.php LoRaWANのデータをJSONで受け取り、車両状態に関するテーブルを適切に更新する
car_call.php 車両状態に関するテーブルを確認して、新たに車両を呼び出す
send_json.php 現在の状態をJSONにして、MQTTパブリッシュする
car_status_update.php:車両情報を更新

car_status_update.phpは、受け取ったJSONを配列にデコードし、「IC番号」「時刻」「devEUI」を取り出して、次のような処理をします。

IC番号で車両情報テーブルを検索し、車両情報を取り出す。同時に、devEUIで駐車場情報テーブルを検索し、駐車場情報を取り出す。
→現在の車両ステータスに対して、どこの駐車場で反応があったのかで処理を分岐する(ステータス0でサブプールに反応があれば「入庫した」とみなして、ステータスを1に更新、この時の時刻を記録、駐車場IDも記録、など)

この時に違反車両かどうかもすぐわかります。また、呼び出されていないのにプールを出た車両はステータス0へ戻します。これにより、「もういない車両なのに呼び出してしまう」こともありません。

car_call.php:車両の呼出

car_call.phpは、車両を適切に呼び出します。

今の状態を確認して、「どこに何台追加する必要があるか」を判断します。
次に、まだ呼び出されていない待機車両を「サブプールに入庫した順」で呼び出します。

駅前に車両が不足しないよう、メインプールから駅前への呼び出しは余分に行ないます。駅前に最大10台入るなら、混雑時は16台呼び出すなど、自動で必要台数を計算して呼び出しています。

send_json.php:現在の情報をJSONでMQTT Publish

現在の情報をまとめてJSONにまとめて、MQTTパブリッシュします。MotionBoardで受け取って、表示を更新するのを想定しています。

常駐プロセスをRubyで

LoRaWANデバイスからのJSONはMQTTで来ますので、用意したサーバーは、常にMQTTを受け取れる状態でなくてはいけません。また、車両呼出とJSONの送信は、理想を言えば常に行ないたい処理ですので、負荷も考えて3秒間隔で実行を繰り返すものとしました。

これらはプロセスを常駐させることで実現できます。常駐させるプロセスは、Rubyで実装しました。

一応phpでも実装できるんですが、php.ini(phpの設定ファイル)の設定次第で、30秒でプロセスが強制的に落とされたり、意図しない動作をする恐れがあります。元々Webページを動的に生成するための言語ですので、「長時間実行されっぱなし」というのは想定してないんですね、php。

sub.rb:RubyでJSONを受け取ってphpに渡す

RubyでMQTTサブスクライバーを常駐させるのは簡単でした。問題は、受け取ったMQTTメッセージ、つまりJSONをどうやってphpに渡すか、という点でした。

何か特別な関数とか無いか調べていくうちに、「コマンドライン引数にJSONを入れればいいんだ」と閃いて、実装することができました。ただ、コマンドライン引数はダブルクォートが消えるので、ダブルクォートをエスケープしておく必要があります。エスケープしないと、php側でjsonをデコードできません。

daemon.rb:3秒に1度実行されるデーモンっぽいRuby

車両呼出、JSONの送信は3秒に1度実行したいところです。なので、Rubyで無限ループするプログラムを作りました。

ただ単に無限ループすると尋常じゃない回数実行されるので、ループの最後に「3秒間停止」という処理を入れました。これにより、3秒感覚で実行、というのが実現できます。

実行するphpファイルは、コマンドラインで実行する形にしました。

MotionBoardで「見える化」

2ページ目にも書きましたが、システムの運用状態をチェックするために、ウイングアーク1stさんのMotionBoard(モーションボード)を使わせていただきました!MotionBoardにデータを送信するには、MQTT経由かWebAPI経由の2通りの方法があります。

MotionBoard

MotionBoardには、次の情報を表示しています。

  • メインプールの現在の状態を表したリスト
  • サブプールの現在の状態を表したリスト
  • サブプールの空車情報(リストと、地図表示の2つを表示)
  • 現在の乗客混雑度(※)

MotionBoard画像
MotionBoard画像

市役所職員さん用と、タクシー会社職員さん用の2種類の表示を用意しました。違いは、市役所職員用は「タクシー会社ごとに絞込み検索して、表示する情報をある程度操作することができる」という点と、タクシー会社職員用は「自社の車両が、リストで強調されて表示される」という点です。

※乗客の混雑度に関するシステムは実装していませんが、センサー等を利用して大まかな混雑度を把握できるようなシステムがあったらいいなと思い、載せています。