前回の記事ではリモートに設置したリモートCPU ボードからXBee 経由で、温度センサ、赤外線センサ、照度センサの各サンプリングデータを取得して、クラウドサービスにデータを保存してグラフ表示する例について紹介しました。
今回は、前回と同じリモートCPU ボードとセンサを使用して、センサーデータの集計を行う例を紹介したいと思います。センサーデータを手元の表計算ソフト(エクセル)にロードしてグラフ作成などで活用できるようになります。クラウドサービスに送信したデータもネットワーク経由でダウンロードすることも可能ですが、今回の記事で使用するセンサデータは DeviceServer を設置したPC にインストールされた統計用データベース(Firebird RDMS )を利用します。
前回の記事でクラウド側にセンサデータを送信するときに使用したスクリプトでは、Xively にHTTP プロトコルでデータ送信する部分で、同時にサーバーPC 内の統計用データベースにもセンサーデータを保存するように記述していました。 たとえば XBee-ZB を利用したリモートCPU ボードから定期的に送信されたセンサーデータを処理するスクリプト ZB_SENSOR_DATA_STORE では以下の様に記述されています。
file_id = "ZB_SENSOR_DATA_STORE" --[[ ●機能概要 TDCP デバイスから送信されたサンプリングデータを DeviceServerに登録 するスクリプトの例です。このスクリプトはユーザー環境に合わせて カストマイズして使用してください。 パラメータで指定した XBeeデバイスに接続している TDCPボード上の センサーデータを取得してデータベースに登録します。 このスクリプトは、リモートデバイスから定期的に送信されてくる SAMPLINGイベントのイベントハンドラ中からコールされて使用できるように しています。 リモートデバイスに接続した各種センサーから、I2C、SPI、A/D変換入力、 デジタルポート、カウンタ入力ポート経由で現在の測定値を取得します。 測定した値は、センサーデータとして扱いやすい値に変換した後 データベースに登録します。 リモートデバイスごとに、接続しているセンサーが異なる場合には、 スクリプト内でデバイスの NodeIdentifier を元に処理を分けることができます。 データベースに登録するときには、キー名に NodeIdentifier と センサー種別 を示す文字列を含めると、後のデータ処理が行い易くなります。 ●リクエストパラメータ --------------------------------------------------------------------------------- キー値 値 値の例 --------------------------------------------------------------------------------- TDCP_1 TDCPイベント文字列第1カラム(Prefix文字列) "$$$" TDCP_2 TDCPイベント文字列第2カラム(イベント種別) "SAMPLING TDCP_3 TDCPイベント文字列第3カラム(app_mode) "32" TDCP_4 TDCPイベント文字列第4カラム(イベントデータ#1) "0F" TDCP_5 TDCPイベント文字列第5カラム(イベントデータ#2) "0" .. .. TDCP_N TDCPイベント文字列第4カラム(イベントデータ#n) "123" ( ZB_TDCP_DATA イベントハンドラに渡された全てのパラメータを、このスクリプトを コールする時にも指定する) NodeIdentifier XBee-ZB デバイスのNodeIdentifier "Node1" ●リターンパラメータ --------------------------------------------------------------------------------- キー値 値 値の例 --------------------------------------------------------------------------------- ●備考 ●変更履歴 ]] ------------------------- -- パラメータチェック ------------------------- if not (g_params["NodeIdentifier"]) then log_msg("parameter error",file_id) error() end local dev = g_params["NodeIdentifier"] local param = {} --------------------------------------------------------------------------------- -- NodeIdentifierが Node1 の名前をもつ XBee-ZBデバイスに接続された TDCPZB リモート -- CPUボードに、以下のセンサーデバイスが接続されている場合の例です -- * TMP102 温度センサ (I2Cバスに接続) -- * CDS照度センサ (A/D#0に接続) -- * IR(人感)センサ (DIO#3/COUNTER入力接続) --------------------------------------------------------------------------------- -------------------------------------------------------------------------------- -- NodeIdentifier, TDCP イベント種別、TDCP app_modeが一致するものだけを選択して -- サンプリングデータをデータベースに格納する。 -------------------------------------------------------------------------------- if (dev == "Node1") and (g_params["TDCP_2"] == "SAMPLING") and (g_params["TDCP_3"] == "32") then --------------------------------------------- -- Xively サービスに登録するためのパラメータ --------------------------------------------- local xively_ch_data = {} ----------------------------------------------------------- -- I2Cバスに接続された TMP102 センサーから温度データを取得 ----------------------------------------------------------- param["device"] = dev stat,result = script_exec2("ZB/DEVICE/TMP102_READ",param) if not stat then error() end ----------------------------------------------------------------- -- 統計データベースに測定値を保存、共有データには最新の値を保存 -- キー名 "SENSOR_TP_<NodeIdentifier>" ----------------------------------------------------------------- if not add_stat_data("SENSOR_TP_" .. dev,result["temperature"]) then error() end if not set_shared_data("SENSOR_TP_" .. dev,result["temperature"]) then error() end xively_ch_data["SENSOR_TP_" .. dev] = result["temperature"] -- for Xively ----------------------------------------------------------------------------- -- SAMPLING イベント発生時の A/D#0 (TDCP_7) は光センサーの値が格納されている -- -- 統計データベースに測定値を保存、共有データには最新の値を保存 -- キー名 "SENSOR_LUMI_<NodeIdentifier>" ----------------------------------------------------------------------------- if not add_stat_data("SENSOR_LUMI_" .. dev,g_params["TDCP_7"]) then error() end if not set_shared_data("SENSOR_LUMI_" .. dev,g_params["TDCP_7"]) then error() end xively_ch_data["SENSOR_LUMI_" .. dev] = g_params["TDCP_7"] -- for Xively ----------------------------------------------------------------------------- -- SAMPLING イベント発生時の COUNTER (TDCP_6) はIRセンサーのカウンタ値が格納されている -- -- 統計データベースに測定値を保存、共有データには最新の値を保存 -- キー名 "SENSOR_IR_<NodeIdentifier>" ----------------------------------------------------------------------------- if not add_stat_data("SENSOR_IR_" .. dev,g_params["TDCP_6"]) then error() end if not set_shared_data("SENSOR_IR_" .. dev,g_params["TDCP_6"]) then error() end xively_ch_data["SENSOR_IR_" .. dev] = g_params["TDCP_6"] -- for Xively ----------------------------------------------------------------------------- -- Xively サービスにセンサーデータを登録 ----------------------------------------------------------------------------- if not script_exec("XIVELY_DATA_STORE",xively_ch_data) then error() end end
xively_ch_data[] 連想配列はクラウドにデータ送信するためのスクリプトに渡すセンサーデータが格納されたパラメータになります。この配列にセンサデータを代入すると同時に、 add_stat_data() ライブラリ関数を使用してセンサーデータを統計用データベースにも登録しています。(このスクリプトの詳しい説明は前回の記事を参照していください)
統計データベースには、キー名とデータ登録時のタイムスタンプ、センサデータの値を含んだレコードが保存されていきます。これらのレコードは、いつでも集計したい日付時刻とデータ登録時のキー名を指定して集計することができます。集計計算は DeviceServer のスクリプトやイベントハンドラ中からコールできる専用のライブラリ関数 summary_stat_data() を使用します。このライブラリ関数は DeviceServer ユーザーマニュアル中で以下の様に定義されています。
このライブラリ関数では、datetime_start パラメータに集計対象となる期間の開始日付時刻を指定します。interval パラメータには集計する間隔を指定します。count パラメータには集計間隔をどれだけ繰り返すかの回数を指定します。ここで指定した回数だけ、 datetime_start パラメータに指定した時刻を interval 分だけ進めて集計計算を繰り返します。これらの結果得られた複数の集計データ(対象データ個数、合計値、平均値、最大値、最小値)を配列の形で取り出せます。
このライブラリ関数を使用して、センサーデータを集計するスクリプトファイル(SENSOR_DATA_CSV.lua ) を作成します。スクリプトファイルは DeviceServer をインストールしたフォルダにある “Script” フォルダの中に “SUMMARY” フォルダを作成してその中に格納しています。(もちろん別のフォルダに設置しても構いません)
file_id = "SENSOR_DATA_CSV" --[[ ●機能概要 統計データベースに保存されているセンサーデータを集計して、 結果を CSV ファイルに出力する。 統計データベースに登録したときのキー名毎に1日分の集計結果をファイルに出力する。 ●リクエストパラメータ --------------------------------------------------------------------------------- キー値 値 値の例 --------------------------------------------------------------------------------- TargetDate 集計対象日付(YYYY/MM/DD) "2010/01/31" パラメータ省略時はスクリプトが起動された日付が 集計対象日になる ●リターンパラメータ --------------------------------------------------------------------------------- キー値 値 値の例 --------------------------------------------------------------------------------- ●備考 出力ファイルは、C:\WORK フォルダの中に <Key名>.csv のファイル名で格納される。 出力先フォルダを変更したい場合には、outfolder 変数の初期値を変更する。 ●変更履歴 2014/07/2 初版作成 ABS-9000 DeviceServer copyright(c) All Blue System ]] local outfolder = "c:/work" log_msg("start..",file_id) ---------------------------------------------------------------------- -- TargetDate パラメータが指定されていない場合にはスクリプトが起動 -- された日を集計対象にする ---------------------------------------------------------------------- local timestamp if g_params["TargetDate"] then timestamp = g_params["TargetDate"] .. " 0:0:0" else local now = os.date "*t" timestamp = string.format("%4.4d/%2.2d/%2.2d 0:0:0",now["year"],now["month"],now["day"]) end ---------------------------------------------------------------------- -- 統計用データベースに保管された全てのセンサーデータを集計対象とする ---------------------------------------------------------------------- local file; local stat,dev_list = key_list_stat_data("SENSOR_") -- キー名リストを取得 for k,v in ipairs(dev_list) do log_msg("calculating: " .. v,file_id) ------------------------------------------------------------------------- -- 集計対象日の 00:00 から 24:00 までを集計する ------------------------------------------------------------------------- local stat,datetime,sample,total,mean,max,min = summary_stat_data(v,timestamp,600,144) -- 10分単位の集計 -- local stat,datetime,sample,total,mean,max,min = summary_stat_data(v,timestamp,1800,48) -- 30分単位の集計 -- local stat,datetime,sample,total,mean,max,min = summary_stat_data(v,timestamp,3600,24) -- 1時間単位の集計 if not stat then error() end ---------------------------------------------------------------------- -- ファイルオープン ---------------------------------------------------------------------- file = io.open(outfolder .. "/" .. v .. ".csv","w+"); if (file == nil) then error() end; ---------------------------------------------------------------------- -- CSV形式でタイトルと集計データ出力 ---------------------------------------------------------------------- file:write("DataKey,DateTime,SampleCount,MeanValue,MaxValue,MinValue,Total\n") for key,val in ipairs(datetime) do file:write(string.format("%s,%s,%d,%2.3g,%2.3g,%2.3g,%2.3g\n",v,datetime[key],sample[key],mean[key],max[key],min[key],total[key])) end ---------------------------------------------------------------------- -- ファイルクローズ ---------------------------------------------------------------------- file:close(); end
このスクリプトでは統計データベースに登録された1日分のセンサデータを10分単位で集計して CSV ファイルの形で出力します。センサーデータ登録時に指定したキー名ごとに別々のファイルを “C:\WORK” フォルダ内に書き出しています。ファイル出力には Lua 標準ライブラリ io を使用しています。ちなみに、DeviceServer で実行するスクリプトでは標準io 出力や標準 io 入力を使用することはできません。これは DeviceServer がサービスプログラムとして Windows のバックグランドで動作しているためです。ただし、ファイルに出力することは今回の様に可能です。
スクリプトパラメータ “TargetDate” に日付を指定すると、指定した日付のセンサーデータを集計します。パラメータ省略時にはスクリプトを起動した日付が集計対象日になります。
DeviceServer のクライアントプログラムにログインして、”スクリプト” ツールボタンを押してスクリプト実行画面を出します。プルダウンメニューから上記のスクリプトを選択して実行します。
ここでは、TargetDate スクリプトパラメータを指定しています。集計対象日付をパラメータの値に設定して、”実行” ボタンを押します。
今回の集計では合計5個のセンサーに対するファイルを計算していますが、集計計算は2秒程度で直ぐに完了します。もし、大量のセンサーデータを集計する場合に時間が掛かることが予想される場合には、”別スレッド実行” ボタンを使用してバックグランドで実行させることもできます。
集計が完了すると “C:\WORK” フォルダの中にセンサーデータ登録時に指定したキー名ごとに CSV ファイルが作成されます。
この CSV ファイルはエクセル等の表計算ソフトで簡単に扱うことができます。エクセルから集計ファイルを読み込んだ様子は以下になります。
CSV ファイル中の各カラムがセルに変換されてロードされます。集計データ中にある SampleCount は集計期間(10分毎)内に見つかったデータ個数を表します。もしこの値が 0 の場合にはリモートからセンサーデータが届いていないことを意味します。
温度センサから取得したデータの場合には集計期間中に見つかったデータの平均値を使用できますので “MeanValue” カラムの値を使用します。赤外線センサの場合には合計値を意味する “Total” カラムの値を使用します。今回はリモートCPU ボードから定期的に送信するサンプリング間隔と集計計算時に指定した時間間隔が等しく 10 分なのでどのカラムの値も同じになります。
集計間隔(Interval) を 1時間単位など変更した場合には、集計結果を利用するときに上記のセンサーデータ種別に応じて使用する値を選択してください。
全ての CSV ファイルをエクセルで同時に開いて、セルのコピー&ペーストを行って温度と赤外線センサの値をまとめてグラフを作成した様子が以下になります。
CSV ファイルの形でセンサーデータを集計しておくと、表計算ソフトやWindowsアプリケーションからセンサデータを簡単に利用できます。また、エクセルのマクロを併用すると毎日の集計作業を自動化することもできると思います。
自動化を行うときには、DeviceServer 側では集計用のスクリプトを毎日定時に起動するように設定しておきます。このときには Windows のタスクスケジューラ(schtasks) や DeviceServer のスクリプト実行プログラム・コマンドライン版 (ScriptExecCmd.exe) が使用できます。詳しくは DeviceServer ユーザーマニュアル中の “39. Windowsタスクスケジューラを使用してスクリプトを実行” の章をご覧ください。
それではまた。