●概要
リモートセンサーノードからセンサーデータを収集して集計するアプリケーション例を前回までの2回の記事(クラウドサービスにセンサーデータを保管する例、ローカルにデータを保管してバッチジョブで作成したCSV ファイルをエクセルで利用する例)に分けて紹介してきました。今回は サーバーPC に設置したWeb アプリケーションでセンサーデータをグラフ表示する例を紹介したいと思います。今回のシステム構成も前回までの記事と全く同様に、複数のリモートセンサーノードからセンサーデータを収集するシステムで動作させます。
リモートに設置した2つのセンサーノードから送信されてくる、温度・明るさ・赤外線の各センサデータをサーバーPC に保存して、任意のタイミングでWebブラウザからグラフを表示することができます。全体のシステム構成については、こちらの記事を参照してください。
クラウドサービスを利用したアプリケーションでも、センサデータのグラフ表示をすることが出来ましたが、今回は全てローカルPC(DeviceServer を設置したサーバーPC) のみで実現しています。クラウド等の外部のサービスを一切利用しませんので、セキュリティなどの要因で LAN 内のみでシステムを構築したい病院や工場などで利用する場合にも、Webアプリ版で作成する今回のアプリケーション例が参考になると思います。
●動作例
以下が今回作成する Webアプリケーションでグラフ表示した様子です。DeviceServer を設置したPC にアクセスできる端末であれば、OS の種類を問わず Webブラウザさえあれば何時でも直近のセンサーデータの傾向をグラフを表示したり、月次や週次のグラフを集計して表示することができます。
上の画面はWindows7(64bit)で動作している Firefox Web ブラウザでWebアプリを起動している様子です。
今回のアプリで紹介しているリモートセンサーからのセンサーデータ以外のあらゆるデータをグラフ化できるようにWebアプリを設計しています。DeviceServer の統計データベースに集計したいデータを登録しておくだけで、後から簡単にグラフを表示できるようになります。
●WebAPI で実行するサーバー側スクリプト(Lua)を作成する
今回作成する Webアプリは DeviceServer の Webサーバー機能を利用して配信する HTML ファイルと JavaScriptファイル、CSS ファイル等で作成されています。これらのファイルは Webブラウザ側でダウンロードされて実行されますが、これとは別に、JavaScript 中から WebAPI 経由で実行する DeviceServer 側(サーバーPC) のスクリプト(Lua)もいくつか準備しておきます。
ログイン認証やログアウト、スクリプト(Lua)実行などの基本的な WebAPI 機能はDeviceServerに予め用意されています。このスクリプト実行 WebAPI で指定する DeviceServer 側の Lua スクリプトを幾つか用意することで、サーバー側で実行する機能とブラウザ側で実行する機能を分離させることができます。
今回の Web アプリでは画面に表示する GUI コンポーネントや画面遷移、グラフ化の処理などは JavaScript で記述して Webブラウザ側で実行しています。統計データベースのキー名列挙、統計データベースの集計処理などは DeviceServer 側に用意する Lua スクリプトで実行します。Lua スクリプトでは Lua のライブラリ関数をコールして、処理の殆どが DeviceServer 内に記述しているネイティブコードで実行します。このような形式をとることで、処理スピードが向上して、エラー発生時のリカバリ処理なども確実に行えるようになります。
今回の Web アプリ用に新規に作成DeviceServer 側に用意する Lua スクリプトは以下の2種類あります。
(1) 統計データベースに格納しているキー名リストを取得するときの WebAPI で実行する Lua スクリプト
(2) 任意の期間のセンサーデータを集計して、結果を配列で取得するときの WebAPI で実行する Lua スクリプト
最初に(1) の WebAPI を実現するための Lua スクリプト(LIST_JSON.lua)を作成します。ファイルの内容は以下になります。このファイルは最新のDeviceServer インストールキットを使用すると、C:\Program Files (x86)\AllBlueSystem\Scripts\SUMMARY\LIST_JSON.lua に配置されます。
file_id = "SUMMARY/LIST_JSON" --[[ ●機能概要 統計データベースで使用中のキー名リストを取得する。 ●リクエストパラメータ --------------------------------------------------------------------------------- キー値 値 値の例 --------------------------------------------------------------------------------- prefix キー名の部分文字列を指定する。前方一致でマッチしたキー名 のみがリストに格納される。パラメータを省略した場合には全て のキー名が対象になる "SENSOR_" ●リターンパラメータ --------------------------------------------------------------------------------- キー値 値 値の例 --------------------------------------------------------------------------------- KeyList 統計データベースで使用中のキー名リストがJSON フォーマット 文字列で格納される [ {"Key":"<統計データベース登録時のキー#1>"}, {"Key":"<統計データベース登録時のキー#2>"}, .. .. {"Key":"<統計データベース登録時のキー#n>"} ] 値の例 [ {"Key":"SENSOR_IR_Device4"}, {"Key":"SENSOR_IR_Node1"}, {"Key":"SENSOR_LUMI_Node1"}, {"Key":"SENSOR_TP_Device4"}, {"Key":"SENSOR_TP_Node1"} ] ●備考 Web API 経由でこのスクリプトを実行するときに、URL パラメータに noquote=1 を指定 して、リターンパラメータの値を直接 JSON オブジェクトとしてアクセスします。 JavaScriptから以下のURL をコールしてJSON リプライデータを受信します。 http://<hostname>:<port>/command/json/script?session=<session_token>&name=SUMMARY%2FLIST_JSON&noquote=1 取得した JSON データからデバイスリスト中の各データ項目にアクセスするときには下記の様な JavaScript を記述します。このとき、data 変数には WebAPI で取得した JSON オブジェクトが格納されている ものとします。 for(i in data.ResultParams.KeyList){ .. .. data.ResultParams.KeyList[i].Key .. .. } ●変更履歴 2014/07/13 初版作成 ABS-9000 DeviceServer copyright(c) All Blue System ]] ----------------------------------------------------- -- 統計データベースで使用中のキー名リストを取得 ----------------------------------------------------- local stat,keys if g_params["prefix"] then stat,keys = key_list_stat_data(g_params["prefix"]) else stat,keys = key_list_stat_data() end if not stat then error() end ----------------------------------------------------------------- -- キー名リストを JSON 文字列に変換 ----------------------------------------------------------------- local key_list = "[" local cnt = 0 for key,val in ipairs(keys) do if cnt ~= 0 then key_list = key_list .. "," end key_list = key_list .. '{"Key":"' .. val .. '"}' cnt = cnt + 1 end key_list = key_list .. "]" --------------------------------------------- -- デバイスリストをリターンパラメータに格納 --------------------------------------------- script_result(g_taskid,"KeyList",key_list)
このスクリプトでは、センサーデータを格納している統計データベースに存在する全てのキー名リストを JSON 文字列で返します。リクエストパラメータにキー名の一部分(前部)を指定すると一致するキー名のみを選択することもできます。
Web アプリで、グラフ表示の対象とするキー名をチェックボックスで選択するときに、このスクリプトを WebAPI で実行して選択肢のチェックボックスリストを表示します。
次に (2) の集計用のスクリプト(SUMMARY_DATA_JSON.lua) を作成します。ファイルの内容は以下になります。このファイルは最新のDeviceServer インストールキットを使用すると、C:\Program Files (x86)\AllBlueSystem\Scripts\SUMMARY\SUMMARY_DATA_JSON.lua に配置されます。
file_id = "SUMMARY_DATA_JSON" --[[ ●機能概要 統計データベースに保存されているデータを集計して JSON配列で取得する。 集計パラメータには日、週、月の範囲を指定できる。 ●リクエストパラメータ --------------------------------------------------------------------------------- キー値 値 値の例 --------------------------------------------------------------------------------- KeyList 集計対象とする統計データベース中のキー名リスト 複数指定するときにはカンマで区切る "SENSOR_IR_Device4,SENSOR_IR_Node1" TargetDate 集計対象開始日付(YYYY/MM/DD) "2010/01/31" パラメータ省略時には Range パラメータの指定によってデフォルト 日付が設定される。 Range パラメータを省略または "day" または "month" 指定時には、 現在日が集計対象開始日になる。 Rangeパラメターが "week","2d","month" の場合には現在日から その日数期間前の日付が集計対象開始日になる。 TargetTime 集計対象開始時刻(HH:MM:SS) "13:25:0" TargetDate, TargetTime 両方のパラメータ省略時には、 現在の日付・時刻(秒部分は切り捨てて xx:xx:00 になる)から集計期間分前の 日付時刻が設定される。 TargetDate を指定して、TargetTime パラメータのみを省略した場合には "0:0:0" が設定される Range パラメータに 1日以上の期間を指定した場合には、このパラメータ の指定は無視されて常に "0:0:0" からの集計期間になる Range 集計期間を指定する。下記の値が指定可能でパラメータ省略時には "day" が選択される "hour" TargetDate, TargetTime に指定した日付時刻から 1時間を 30秒単位 に集計する "6h" TargetDate, TargetTime に指定した日付時刻から 6時間を 1分単位 に集計する "day" TagetDate に指定した日付の 0:0:0 から 24:00:00 までの期間を 5 分単位に集計する。 "2d" TagetDate に指定した日付の 0:0:0 から次の日の 24:00:00 までの期間を 10 分単位に集計する "week" TagetDate に指定した日付の 0:0:0 から 7日間を 1時間単位に集計する "month" TagetDate に指定した日付の同月 1日の 0:0:0 から月の最終日の 24:00:00 までの期間を 4時間単位に集計する Summary 集計単位ごとの期間で集計計算したときに使用する値を指定する パラメター省略時には "mean" が選択される "mean" "mean" 平均値 "total" 合計値 Interval Range パラメータの指定によって予め決められた集計単位時間を変更して このパラメータで指定された秒を使用する。 秒数で指定する。 "3600" ●リターンパラメータ --------------------------------------------------------------------------------- キー値 値 値の例 --------------------------------------------------------------------------------- SeriesList 集計結果を jqplot の データ配列パラメータで指定できるような フォーマットに変換した JSON 文字列。 KeyList に指定したキーの数だけ集計結果が格納される。 集計単位期間の開始時刻と集計値のペアからなる集計データ <item> が作成される 複数の集計データを合わせて集計配列 <series> を構成する。 KeyList に指定したキーごとに 集計配列 <series> が作成されて、それらを配列で まとめたものが SeriesList になる。 LabelList: KeyList で指定された文字列を jqplot の series ラベル・オプションパラメータで 指定できるようなフォーマットに変換した JSON 文字列。 ●リターンパラメータフォーマット SeriesList := [ [<series#1>], [<series#2>], .. [<series#n>] ] series := [ [<item#1], [<item#2], .. [<item#n] ] item := ["<集計単位の開始時刻", <集計値>] SeriesList例:(2つのキー文字列を KeyList に指定した場合) [ [ ["2014/07/02 00:00:00",10], ["2014/07/02 00:10:00",13], ["2014/07/02 00:20:00",13.5], .. ["2014/07/02 23:50:00",20] ], [ ["2014/07/02 00:00:00",12], ["2014/07/02 00:10:00",12], ["2014/07/02 00:20:00",12], .. ["2014/07/02 23:50:00",12.4] ] ] LabelList := [ {label:"<key#1>"}, {label:"<key#2>"}, .. {label:"<key#n>"} ] ●備考 集計単位ごとの期間内に統計データレコードが見つからなかった場合には、 その単位期間の集計結果レコードは SeriesList 中に入りません。 Web API 経由でこのスクリプトを実行するときに、URL パラメータに noquote=1 を指定 して、リターンパラメータの値を直接 JSON オブジェクトとしてアクセスします。 JavaScriptから以下のURL をコールしてJSON リプライデータを受信します。 http://<hostname>:<port>/command/json/script?session=<session_token>&name=SUMMARY%2FSUMMARY_DATA_JSON&noquote=1 ●変更履歴 2014/08/01 初版作成 ABS-9000 DeviceServer copyright(c) All Blue System ]] ---------------------------------------------------------------------- -- KeyList パラメータからキー文字列配列 keys 作成 ---------------------------------------------------------------------- local keys if g_params["KeyList"] then keys = csv_to_tbl(g_params["KeyList"]) else log_msg("parameter error",file_id) error() end ---------------------------------------------------------------------- -- TargetDate パラメータが指定されていない場合にはスクリプトが起動 -- された日を集計対象開始日にして timestamp 変数に設定する。 -- Range パラメータが "day","2d","week","month" の場合に有効で、それ以外 -- の場合には timestamp は後で上書きされる ---------------------------------------------------------------------- local timestamp -- 一日以上の集計期間を指定する場合の開始日付時刻 local now = os.date "*t" if g_params["TargetDate"] then timestamp = g_params["TargetDate"] .. " 0:0:0" else timestamp = string.format("%4.4d/%2.2d/%2.2d 0:0:0",now["year"],now["month"],now["day"]) end local interval,count,days if not g_params["Range"] then g_params["Range"] = "day" end -- デフォルトは1日間の集計を行う ---------------------------------------------------------------------- -- Range, Interval パラメータから集計間隔と集計データ数を決定 ---------------------------------------------------------------------- --------------- -- 1日間集計 --------------- if g_params["Range"] == "day" then days = 1 if not g_params["Interval"] then interval = 300 else interval = tonumber(g_params["Interval"]) end count = math.floor((days * 24 * 3600)/interval) end --------------- -- 2日間集計 --------------- if g_params["Range"] == "2d" then if not g_params["TargetDate"] then -- 集計対象日が未指定の場合には現在日から 1日前に設定する local stat,y,m,d,h,min,s = str_to_datetime(timestamp) if not stat then error() end local stat,y,m,d = inc_day(-1,y,m,d) if not stat then error() end timestamp = string.format("%4.4d/%2.2d/%2.2d 0:0:0",y,m,d) end days = 2 if not g_params["Interval"] then interval = 600 else interval = tonumber(g_params["Interval"]) end count = math.floor((days * 24 * 3600)/interval) end --------------- -- 1週間集計 --------------- if g_params["Range"] == "week" then if not g_params["TargetDate"] then -- 集計対象日が未指定の場合には現在日から 6日前に設定する local stat,y,m,d,h,min,s = str_to_datetime(timestamp) if not stat then error() end local stat,y,m,d = inc_day(-6,y,m,d) if not stat then error() end timestamp = string.format("%4.4d/%2.2d/%2.2d 0:0:0",y,m,d) end days = 7 if not g_params["Interval"] then interval = 3600 else interval = tonumber(g_params["Interval"]) end count = math.floor((days * 24 * 3600)/interval) end --------------- -- 1月間集計 --------------- if g_params["Range"] == "month" then local stat,y,m,d,h,min,s = str_to_datetime(timestamp) if not stat then error() end timestamp = string.format("%4.4d/%2.2d/%2.2d 0:0:0",y,m,1) -- 検索対象日を月初め1日に変更 stat,days = days_in_month(y,m) -- 集計対象日数を対象月に含まれる日数に設定 if not stat then error() end if not g_params["Interval"] then interval = 14400 else interval = tonumber(g_params["Interval"]) end count = math.floor((days * 24 * 3600)/interval) end --------------- -- 1時間集計 --------------- if g_params["Range"] == "hour" then if (not g_params["TargetDate"]) and (not g_params["TargetTime"]) then -- 集計対象日と時間の両方が未指定の場合は1時間前の時刻に設定 local stat,y,m,d,h,min,s = inc_second(-3600,now["year"],now["month"],now["day"],now["hour"],now["min"],0) if not stat then error() end timestamp = string.format("%4.4d/%2.2d/%2.2d %2.2d:%2.2d:%2.2d",y,m,d,h,min,s) end if (not g_params["TargetDate"]) and g_params["TargetTime"] then -- 集計対象時間のみ指定の場合は現在日に設定 timestamp = string.format("%4.4d/%2.2d/%2.2d ",now["year"],now["month"],now["day"]) .. g_params["TargetTime"] end if g_params["TargetDate"] and (not g_params["TargetTime"]) then -- 集計対象日のみ指定の場合は 0:0:0 に設定 timestamp = g_params["TargetDate"] .. " 0:0:0" end if g_params["TargetDate"] and g_params["TargetTime"] then -- 集計対象日と時間の両方を指定の場合 timestamp = g_params["TargetDate"] .. " " .. g_params["TargetTime"] end if not g_params["Interval"] then interval = 30 else interval = tonumber(g_params["Interval"]) end count = math.floor(3600/interval) end --------------- -- 6時間集計 --------------- if g_params["Range"] == "6h" then if (not g_params["TargetDate"]) and (not g_params["TargetTime"]) then -- 集計対象日と時間の両方が未指定の場合は6時間前の時刻に設定 local stat,y,m,d,h,min,s = inc_second(-21600,now["year"],now["month"],now["day"],now["hour"],now["min"],0) if not stat then error() end timestamp = string.format("%4.4d/%2.2d/%2.2d %2.2d:%2.2d:%2.2d",y,m,d,h,min,s) end if (not g_params["TargetDate"]) and g_params["TargetTime"] then -- 集計対象時間のみ指定の場合は現在日に設定 timestamp = string.format("%4.4d/%2.2d/%2.2d ",now["year"],now["month"],now["day"]) .. g_params["TargetTime"] end if g_params["TargetDate"] and (not g_params["TargetTime"]) then -- 集計対象日のみ指定の場合は 0:0:0 に設定 timestamp = g_params["TargetDate"] .. " 0:0:0" end if g_params["TargetDate"] and g_params["TargetTime"] then -- 集計対象日と時間の両方を指定の場合 timestamp = g_params["TargetDate"] .. " " .. g_params["TargetTime"] end if not g_params["Interval"] then interval = 60 else interval = tonumber(g_params["Interval"]) end count = math.floor(21600/interval) end if count > 10000 then -- 集計計算に時間がかかりすぎるため、エラーにする log_msg("too many records",file_id) error() end ---------------------------------------------------------------------- -- 集計に使用する計算方法のデフォルト設定 ---------------------------------------------------------------------- if not g_params["Summary"] then g_params["Summary"] = "mean" end ---------------------------------------------------------------------- -- データ集計 ---------------------------------------------------------------------- log_msg(string.format("start_datetime: %s interval: %d count: %d summary: %s", timestamp,interval,count,g_params["Summary"]),file_id) local sum local series_json = '[' local label_json = '[' local first_series = true for k,v in ipairs(keys) do log_msg("calculating: " .. v,file_id) if first_series then first_series = false else series_json = series_json .. "," label_json = label_json .. "," end --------------------------- -- キー(シリーズ)毎の集計 --------------------------- local stat,datetime,sample,total,mean,max,min = summary_stat_data(v,timestamp,interval,count) if not stat then error() end series_json = series_json .. '[' label_json = label_json .. '{label:"' .. v .. '"}' local first_item = true for key,val in ipairs(datetime) do if g_params["Summary"] == "mean" then sum = mean[key] -- 集計単位期間内の平均データを使用 else sum = total[key] -- 集計単位期間内の合計データを使用 end if sample[key] > 0 then -- 集計単位期間内にデータが存在しない場合にはレコードを出力しない if first_item then first_item = false else series_json = series_json .. "," end series_json = series_json .. string.format('["%s",%g]',val,sum) end end series_json = series_json .. ']' end series_json = series_json .. ']' label_json = label_json .. ']' -------------------------------------------- -- リターンパラメータに JSON 文字列を設定 -------------------------------------------- script_result(g_taskid,"SeriesList",series_json) script_result(g_taskid,"LabelList",label_json)
このスクリプトでは、Web API 実行時に指定された統計データベースのキー名(複数指定可)のデータを集計して JSON 配列を返します。Web API の URL パラメータには集計期間の長さや日付、時刻、集計間隔、集計に使用する値(合計値または平均値)を指定することができます。
Web アプリの集計パラメータ設定画面で、ユーザーからラジオボタンやチェックボックスなどのGUI で指定された条件を、このスクリプトに渡すことで様々な条件で集計することができます。
集計結果はシリーズ配列として JSON 文字列で返します。1シリーズはキー名1つに対応する時系列の集計データを表します。このシリーズ配列のフォーマットはそのまま、グラフ表示を行うための JavaScript ライブラリ( jqplot )のプロットデータとして使用します。詳しいフォーマットは上記スクリプト中のコメントをご覧ください。
●WebAPIの動作確認
Lua スクリプトの準備ができたら Web API を実行して動作確認を行います。Web アプリの雛形がある程度できている場合には、Webアプリを動作させている Webブラウザのデバッグ機能を使用してネットワーク中にやり取りされるデータを直接モニタしてデバッグすることができます。また URL パラメータの数やリターンデータが少ない場合には Web ブラウザの URI 欄に直接 Web API のパスを入力して確かめる方法も使えます。
今回は Web API 動作を確認するために linux コマンド(curl) を使用しています。curl コマンドは HTTP プロトコルでデータの送受信を簡単にコンソールから実行できます。linux マシンは手元にあった Raspberry Pi マシン(Raspbian OS) から実行しています。
Raspberry Pi にログインして、”curl -X GET xxxxxx” コマンドを実行して HTTP GET コマンドを DeviceServer に送信しています。URL のIP アドレス部分で指定している 192.168.100.45 は DeviceServer が動作している サーバーPC のアドレスです。
最初のコマンド実行で SUMMARY フォルダに格納したキー名リスト取得(LIST_JSON) を実行しています。スクリプト名は URL パラメータの name= 部分で指定します。noquote=1 パラメータは実行結果の JSON 文字列中でリターンパラメータに指定した値をダブルコートで囲まない指定です。これを指定すると JavaScript 側でJSON.parse() を使用しなくても直接リターン値を JSON オブジェクトとして利用できるようになります。session=1234 は WebAPI の動作試験をするときにログイン認証を省略して、予めサーバー側に作成したセッショントークン文字列 “1234″ を指定しています。
(Web API で指定可能な URL パラメータの種類については DeviceServer ユーザーマニュアル中の “36.2 /command/json/script” WebAPI コマンドの項を参照してください。また試験用のセッショントークン作成方法については “36.11 セッショントークン作成方法” の項をご覧ください)
WebAPI コマンドの実行結果は コンソールに出力されます。JSON 配列形式でキー名リストが返っているのが確認できます。また、日本語などのマルチバイト文字列は Unicode (UCS-2) の “\uxxxx” エンコード形式で出力しています。これによってWebブラウザやOSの種類に影響されずに日本語を正しく表示できます。
次に、集計スクリプトも同様に動作確認します。URL パラメータ name= 部分をSUMMARY/SUMMARY_DATA_JSON に変更して curl コマンドを実行します。KeyList パラメータには2つのリモートセンサーデバイスから送信された温度センサデータが格納されている SENSOR_TP_Device4 と SENSOR_TP_Node1 をカンマ区切りで指定しています。
集計パラメータの指定によってはかなりのデータ量が JSON 配列で返されますので、curl コマンドの出力は一旦リダイレクトして out.txt ファイルに保存してから more コマンドで内容を確認しています。
これでWeb API 経由でキー名リスト取得と集計機能の動作確認ができました。
●Webアプリ作成
次にWebアプリ本体を作成します。アプリの基本的な動作は jquery と jquery mobile のフレームワークを使用して作成しますので、主に作成するファイルは HTML ファイルと JavaScript ファイルのみで非常にシンプルです。もちろん、これ以外の Web アプリフレームワークを使用して Web アプリを作成しても構いません。
最初に、アプリの各ページで表示する画面とダイアログなどを記述した HTML ファイルを作成します。ファイルの内容は以下になります。このファイルは最新のDeviceServer インストールキットを使用すると、C:\Program Files (x86)\AllBlueSystem\WebRoot\web_api_sample\summary\chart\index.html に配置されます。
<!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1" /> <title>集計グラフ</title> <link rel="stylesheet" href="libs/css/themes/default/jquery.mobile-1.2.1.css" /> <link rel="stylesheet" href="my.css" /> <link rel="stylesheet" href="libs/css/jquery.jqplot.css" /> <link rel="stylesheet" href="libs/css/jquery.mobile.datepicker.css" /> <link rel="stylesheet" href="libs/css/jquery.mobile.datepicker.theme.css" /> <script src="libs/js/jquery.js"></script> <script src="libs/js/jquery.mobile-1.2.1.js"></script> <script src="libs/js/socket.io.min.js"></script> <script src="libs/js/jquery.jqplot.js"></script> <script src="libs/js/jqplot.cursor.min.js"></script> <script src="libs/js/jqplot.dateAxisRenderer.js"></script> <script src="libs/js/jqplot.barRenderer.js"></script> <script src="libs/js/jqplot.highlighter.js"></script> <script src="libs/js/datepicker.js"></script> <script src="libs/js/jquery.mobile.datepicker.js"></script> <script src="libs/device_server/webapi.js"></script> </head> <body> <div data-role="page" id="login" data-theme="a"> <div data-theme="a" data-role="header" data-position="inline"> <h3>ユーザー認証</h3> </div> <ul data-role="listview"> <li data-role="fieldcontain"> <fieldset data-role="controlgroup"> <div><h1> </h1></div> </fieldset> <fieldset data-role="controlgroup"> <label for="login_name">Name:</label> <input id="login_name" placeholder="" value="" type="text" /> </fieldset> <fieldset data-role="controlgroup"> <label for="login_password">Password:</label> <input id="login_password" placeholder="" value="" type="password" /> </fieldset> <fieldset data-role="controlgroup"> <div><h1> </h1></div> </fieldset> <fieldset data-role="controlgroup"> <div class="ui-grid-b"> <div class="ui-block-a"></div> <div class="ui-block-b"> <a data-role="button" data-inline="true" data-theme="a" data-icon="check" data-iconpos="left" id="login_btn" >Login</a> </div> <div class="ui-block-c"></div> </div> </fieldset> </li> </ul> <div data-theme="a" data-role="footer"> <h3>ABS-9000 DeviceServer</h3> </div> </div> <div data-role="page" id="select_keys_page" data-theme="a"> <div data-theme="a" data-role="header" data-position="inline"> <a data-icon="home" id="logout_btn" href="#logout_caution" data-rel="dialog" data-transition="pop">Logout</a> <h3>集計対象とするキー選択</h3> <a data-icon="arrow-r" id="key_list_next_btn">(次に進む) 集計パラメータ</a> </div> <a id="key_list_reload_btn" data-role="button" data-icon="refresh" data-iconpos="left">最新の状態に更新</a> <fieldset id="key_list" data-role="controlgroup"> </fieldset> <div data-theme="a" data-role="footer"> <h3>ABS-9000 DeviceServer</h3> </div> </div> <div data-role="page" id="summary_params_page" data-theme="a"> <div data-theme="a" data-role="header" data-position="inline"> <a data-icon="arrow-l" id="summary_params_prev_btn">(戻る) キー選択</a> <h3>集計パラメータ</h3> <a data-icon="arrow-r" id="summary_params_next_btn">(次に進む) グラフ作成</a> </div> <div data-role="fieldcontain"> <label for="target_keys">集計対象キー(カンマ区切り):</label> <input type="text" id="target_keys"> </div> <div data-role="fieldcontain"> <label for="target_date">集計対象とする最初の日付:</label> <input type="text" id="target_date" placeholder="YYYY/MM/DD または NULL(自動設定)" class="date_input"> </div> <div data-role="fieldcontain"> <label for="target_time">集計対象とする最初の時刻:</label> <input type="text" id="target_time" placeholder="HH:MM:SS または NULL(自動設定)"> </div> <fieldset data-role="controlgroup"> <legend>期間 (集計単位):</legend> <input type="radio" name="summary_range" id="range-5" value="hour"/> <label for="range-5">1時間 (30秒)</label> <input type="radio" name="summary_range" id="range-6" value="6h"/> <label for="range-6">6時間 (1分)</label> <input type="radio" name="summary_range" id="range-1" value="day" checked="checked" /> <label for="range-1">1日 (5分)</label> <input type="radio" name="summary_range" id="range-2" value="2d"/> <label for="range-2">2日 (10分)</label> <input type="radio" name="summary_range" id="range-3" value="week"/> <label for="range-3">1週 (1時間)</label> <input type="radio" name="summary_range" id="range-4" value="month"/> <label for="range-4">1月 (4時間)</label> </fieldset> <fieldset data-role="controlgroup"> <legend>グラフのタイプ:</legend> <input type="radio" name="plot_type" id="plot-type-1" value="bar" checked="checked" /> <label for="plot-type-1">棒グラフ (bar)</label> <input type="radio" name="plot_type" id="plot-type-2" value="line"/> <label for="plot-type-2">折れ線グラフ (line)</label> </fieldset> <fieldset data-role="controlgroup"> <legend>プロットする集計値:</legend> <input type="radio" name="summary_method" id="method-1" value="mean" checked="checked" /> <label for="method-1">平均値 (mean)</label> <input type="radio" name="summary_method" id="method-2" value="total"/> <label for="method-2">合計値 (total)</label> </fieldset> <div data-role="fieldcontain"> <label for="summary_interval">集計単位を変更:</label> <input type="number" id="summary_interval" placeholder="秒数 または NULL(自動設定)" value="" /> </div> <div data-theme="a" data-role="footer"> <h3>ABS-9000 DeviceServer</h3> </div> </div> <div data-role="page" id="chart_disp_page" data-theme="a"> <div data-theme="a" data-role="header" data-position="inline"> <a data-icon="arrow-l" id="chart_disp_prev_btn">(戻る) 集計パラメータ</a> <h3>集計グラフ</h3> <a data-icon="gear" id="chart_disp_redraw_btn">再描画</a> </div> <div id="chartdiv" style="height:400px;width:95%; "></div> <div data-theme="a" data-role="footer"> <h3>ABS-9000 DeviceServer</h3> </div> </div> <div data-role="page" id="login_error_dialog"> <div data-role="header" data-theme="e"> <h3>*ERROR*</h3> </div> <div data-role="content" data-theme="e"> <h2>ログインに失敗しました</h2> <p>ユーザー名またはパスワードが間違っています。システムのログイン制限により失敗している場合があります</p> <p><a href="#login" data-role="button" data-inline="true" data-icon="check" data-theme="c">戻る</a></p> </div> </div> <div data-role="page" id="logout_caution"> <div data-role="header" data-theme="e"> <h3>*WARNING*</h3> </div> <div data-role="content" data-theme="e"> <h2>ログアウトしますか?</h2> <p>ログアウト操作を行う場合には "OK" を押してください。"キャンセル" で元の画面に戻ります</p> <p><a data-role="button" data-inline="true" data-icon="back" data-rel="back" data-theme="c">キャンセル</a> <a data-role="button" data-inline="true" data-icon="check" id="logout_ok_btn" data-theme="c">OK</a></p> </div> </div> <div data-role="page" id="error_quit_dialog"> <div data-role="header" data-theme="e"> <h3>*ERROR*</h3> </div> <div data-role="content" data-theme="e"> <h2>エラーが発生しました</h2> <p>サーバー処理中にエラーが発生しました。現在のセッションが無効になっている場合があります。再ログイン操作を行ってください</p> <p><a data-role="button" data-inline="true" data-icon="check" id="server_error_ok_btn" data-theme="c">OK</a></p> </div> </div> <div data-role="page" id="error_back_dialog"> <div data-role="header" data-theme="e"> <h3>script error</h3> </div> <div data-role="content" data-theme="e"> <h3>エラーが発生しました</h3> <p>サーバー処理中にエラーが発生しました。スクリプト実行中にエラーが発生した可能性がありますのでサーバー側のログを確認して下さい</p> <p><a data-role="button" data-inline="true" data-icon="check" data-rel="back" data-theme="e">OK</a></p> </div><!-- /content --> </div> <div data-role="page" id="error_prev_dialog"> <div data-role="header" data-theme="e"> <h3>script error</h3> </div> <div data-role="content" data-theme="e"> <h3>エラーが発生しました</h3> <p>サーバー処理中にエラーが発生しました。スクリプト実行中にエラーが発生した可能性がありますのでサーバー側のログを確認して下さい</p> <p><a data-role="button" data-inline="true" data-icon="check" id="error_prev_ok_btn" data-theme="e">OK</a></p> </div><!-- /content --> </div> <div data-role="page" id="key_select_error_dialog"> <div data-role="header" data-theme="e"> <h3>script error</h3> </div> <div data-role="content" data-theme="e"> <h3>エラーが発生しました</h3> <p>1つ以上の統計デーベースキーを選択してください</p> <p><a data-role="button" data-inline="true" data-icon="check" data-rel="back" data-theme="e">OK</a></p> </div><!-- /content --> </div> <script src="main.js"></script> </body> </html>
今回作成したWeb アプリには、ログイン認証画面、グラフ作成対象のキー名選択画面、集計パラメータ設定画面とグラフ表示画面のページがあります。これらの画面レイアウトを index.html ファイル中に記述しています。また、エラー発生時やログアウト動作時に表示されるダイアログメッセージも同様に index.html ファイル中に記述しています。
index.html ファイル中には画面レイアウトのみが記述されていて、チェックボックス操作時やボタンを押したときの動作、WebAPI のコール、画面遷移などのアプリケーションロジックは除いた形になっています。これらのアプリケーションロジック部分は 別途用意する JavaScript ファイル(main.js) 中に全て記述しています。
次に JavaScript ファイルを作成します。ファイルの内容は以下になります。このファイルは最新のDeviceServer インストールキットを使用すると、C:\Program Files (x86)\AllBlueSystem\WebRoot\web_api_sample\summary\chart\main.js に配置されます。
// // ABS-9000 DeviceServer 統計データ集計アプリケーション // // 2014/8/7 ver1.00 初版作成 // // copyright(c) all rights reserved 2014 All Blue System // // スクリプト実行結果ステータスのみをチェック function script_exec_callback(data){ if (data.Result != "Success"){ if(data.ErrorText.match(/CertifyUpdateSession failed/i)) { $.mobile.changePage( "#error_quit_dialog", {transition: "pop",role:"dialog"}); } else { $.mobile.changePage( "#error_back_dialog", {transition: "pop",role:"dialog"}); } } } // UI コンポーネントの xml 属性値を検索取得 function getAttrVal(node,name){ var val = ""; var attr = node.attributes; for (var i=0; i<attr.length; i++){ if (attr[i].nodeName == name){ val = attr[i].nodeValue; } } return val; } // Line チャート表示 function plot_chart_line(data){ $.mobile.loading( 'hide'); if (data.Result != "Success"){ if(data.ErrorText.match(/CertifyUpdateSession failed/i)) { $.mobile.changePage( "#error_quit_dialog", {transition: "pop",role:"dialog"}); } else { $.mobile.changePage( "#error_prev_dialog", {transition: "pop",role:"dialog"}); } return; } // jqplot ライブラリを使用してチャート表示 // プロットデータとラベルはスクリプトリターンパラメータから取得する var plot1 = $.jqplot('chartdiv',data.ResultParams.SeriesList,{ series: data.ResultParams.LabelList, legend:{ show: true }, highlighter: { show: true, sizeAdjust: 7.5 }, axes: { xaxis: { renderer: $.jqplot.DateAxisRenderer, tickOptions: { formatString: "%m/%d %H:%M", angle: -30, textColor: '#dddddd' }, drawMajorGridlines: true }, yaxis: { rendererOptions: { minorTicks: 1 }, label:" ", // 左マージンの為にダミーを配置 min: 0, tickOptions: { formatString: "%'d", showMark: false, textColor: '#dddddd' } } }, cursor:{show: true,zoom:true} }); } // Bar チャート表示 function plot_chart_bar(data){ $.mobile.loading( 'hide'); if (data.Result != "Success"){ if(data.ErrorText.match(/CertifyUpdateSession failed/i)) { $.mobile.changePage( "#error_quit_dialog", {transition: "pop",role:"dialog"}); } else { $.mobile.changePage( "#error_prev_dialog", {transition: "pop",role:"dialog"}); } return; } // jqplot ライブラリを使用してチャート表示 // プロットデータとラベルはスクリプトリターンパラメータから取得する var plot1 = $.jqplot('chartdiv',data.ResultParams.SeriesList,{ seriesDefaults: { renderer:$.jqplot.BarRenderer, rendererOptions: { barWidth: 4 }, pointLabels: { show: true } }, series: data.ResultParams.LabelList, legend:{ show: true }, highlighter: { show: true// , }, axes: { xaxis: { renderer: $.jqplot.DateAxisRenderer, tickOptions: { formatString: "%m/%d %H:%M", angle: -30, textColor: '#dddddd' }, drawMajorGridlines: true }, yaxis: { rendererOptions: { minorTicks: 1 }, min: 0, label:" ", // 左マージンの為にダミーを配置 tickOptions: { formatString: "%'d", showMark: false, textColor: '#dddddd' } } }, cursor:{show: true,zoom:true} }); } // DeviceServer の集計スクリプトを起動して、集計パラメータで指定された // シリーズデータを取得する。スクリプト完了時のイベントハンドラでグラフを描画する function plot_chart(){ $('#chartdiv').empty(); $.mobile.loading( 'show'); // DeviceServer の集計スクリプトを起動する var params = {}; params["noquote"] = "1"; // スクリプトリターンパラメータを JSON オブジェクトとして受信する params["KeyList"] = selected_keys; var target_date = $("#target_date").val(); if (target_date != ""){ params["TargetDate"] = target_date; } var target_time = $("#target_time").val(); if (target_time != ""){ params["TargetTime"] = target_time; } params["Range"] = $('input[name=summary_range]:checked').val(); params["Summary"] = $('input[name=summary_method]:checked').val(); var summary_interval = $("#summary_interval").val(); if (summary_interval != ""){ params["Interval"] = summary_interval; } var plot_type = $('input[name=plot_type]:checked').val(); switch(plot_type){ case "bar": script_exec("SUMMARY/SUMMARY_DATA_JSON",params,"plot_chart_bar"); break; case "line": script_exec("SUMMARY/SUMMARY_DATA_JSON",params,"plot_chart_line"); break; } } // グラフ画面が表示された $( "#chart_disp_page" ).live( "pageshow",function(event){ plot_chart(); }); // グラフ画面の Redrawボタンが操作された $( "#chart_disp_redraw_btn" ).bind( "click", function(event, ui){ plot_chart(); }); // グラフ画面の Prevボタンが操作された $( "#chart_disp_prev_btn" ).bind( "click", function(event, ui){ $.mobile.changePage( "#summary_params_page", { transition: "slide"}); }); // 集計パラメータ設定画面の Nextボタンが操作された $( "#summary_params_next_btn" ).bind( "click", function(event, ui){ $.mobile.changePage( "#chart_disp_page", { transition: "slide"}); }); // 集計パラメータ設定画面の Prevボタンが操作された $( "#summary_params_prev_btn" ).bind( "click", function(event, ui){ $.mobile.changePage( "#select_keys_page", { transition: "slide"}); }); // 集計パラメータ設定画面が表示された $( "#summary_params_page" ).live( "pageshow",function(event){ $("#target_date" ).datepicker({ dateFormat: "yy/mm/dd" }); $("#target_keys").val(selected_keys); }); // 集計対象キーのテキスト入力コンポーネントの内容が変更された $( "#target_keys" ).bind( "change", function(event, ui){ selected_keys = $("#target_keys").val(); }); // サーバー側でログイン操作が成功したらデバイス選択画面に移動する function login_callback(data){ if (data.Result == "Success"){ session_token = data.SessionToken; // dump_login_result(data); $.mobile.changePage( "#select_keys_page", { transition: "none"}); } else { $.mobile.changePage( "#login_error_dialog", {transition: "pop",role:"dialog"}); } } // サーバー側でログアウト操作が完了したらログイン画面に戻る function logout_callback(data){ session_token = ""; $("#login_password").val(""); $.mobile.changePage( "#login", { transition: "pop"}); } // ログインボタンを押した $( "#login_btn" ).bind( "click", function(event, ui){ var user = $("#login_name").val(); var pass = $("#login_password").val(); login(user,pass,"login_callback"); }); // ログアウトボタンを押した $( "#logout_ok_btn" ).bind( "click", function(event, ui){ logout("logout_callback"); }); // サーバーエラーのダイアログから復帰する場合はログイン画面に戻る $( "#server_error_ok_btn" ).bind( "click", function(event, ui){ session_token = ""; $.mobile.changePage( "#login", { transition: "pop"}); }); // ログインページが表示された $( "#login" ).live( "pageshow",function(event){ // セッショントークンが指定されている場合にはユーザー認証を省略して // デバイス選択画面に移動する if (session_token != ""){ $.mobile.changePage( "#select_keys_page", { transition: "slide"}); } }); // 集計対象の統計データベースキー名リスト(カンマ区切り) // チェックボックスを操作する時に、update_selected_keys() 関数によって // 常に最新のチェック状態を反映している var selected_keys = ""; // チェックボックスで選択されているキーのリストを selected_keys に反映させる function update_selected_keys(){ var first = true; selected_keys = ""; // キーが少なくとも1つ以上選択されているかを調べる $(".key_select:checked").each(function(index, checkbox){ var key_name = checkbox.name; if(first) { first = false; } else { selected_keys = selected_keys + ","; } selected_keys = selected_keys + key_name; }); }; // 集計対象キーのチェックボックスが操作された $( ".key_select" ).live( "change", function(event, ui){ update_selected_keys(); }); // 集計対象キー選択画面の Nextボタンが操作された $( "#key_list_next_btn" ).bind( "click", function(event, ui){ if (selected_keys == ""){ // キーが未指定の場合にはエラー $.mobile.changePage( "#key_select_error_dialog", {transition: "pop",role:"dialog"}); return; } $.mobile.changePage( "#summary_params_page", { transition: "slide"}); }); // 統計データベースで使用中のキー名リストを取得する function get_key_list(){ var params = {}; params["noquote"] = "1"; // スクリプトリターンパラメータを JSON オブジェクトとして受信する //params["prefix"] = "SENSOR_"; // SENSOR_ で始まるキー名のみを対象にする script_exec("SUMMARY/LIST_JSON",params,"get_key_list_handler"); } // SUMMARY/LIST_JSONスクリプト実行結果のイベントハンドラ。 function get_key_list_handler(data){ if (data.Result != "Success"){ if(data.ErrorText.match(/CertifyUpdateSession failed/i)) { $.mobile.changePage( "#error_quit_dialog", {transition: "pop",role:"dialog"}); } else { $.mobile.changePage( "#error_back_dialog", {transition: "pop",role:"dialog"}); } return; } // 選択済みのキーがある場合にはチェックボックスをチェック済みにする var selected_arr = selected_keys.split(","); // 統計データベースで使用中のキー名をチェックボックスリストに表示する $('#key_list').empty(); for(i in data.ResultParams.KeyList){ var item = data.ResultParams.KeyList[i].Key; if ($.inArray(item,selected_arr) >= 0) { // 既に選択済み? $('#key_list').append('<input type="checkbox" name="' + item + '" id="' + item + '"checked="checked" class="key_select" /><label for="' + item + '">' + item + '</label>'); } else { $('#key_list').append('<input type="checkbox" name="' + item + '" id="' + item + '" class="key_select" /><label for="' + item + '">' + item + '</label>'); } } $("#key_list").trigger('create'); } // 集計対象キー選択画面が表示された $( "#select_keys_page" ).live( "pageshow",function(event){ get_key_list(); }); // 集計対象キー選択画面の Reloadボタンが操作された $( "#key_list_reload_btn" ).bind( "click", function(event, ui){ get_key_list(); }); // 集計スクリプト実行中エラーのダイアログから復帰する場合は集計パラメータ設定画面に戻る $( "#error_prev_ok_btn" ).bind( "click", function(event, ui){ $.mobile.changePage( "#summary_params_page", { transition: "slide"}); });
main.js ファイルは、Webアプリケーションの以下の機能を実現しています。
(1) ログイン認証
(2) ログアウト
(3) 統計データベースキー名リストをチェックボックスリストに表示
(4) 集計パラメータの操作
(5) グラフ表示
これらの機能はWeb アプリ画面に表示されている GUI コンポーネントを操作したときのイベントハンドラとして記述しています。jquery API 関数の $(xxxx).live(xxxx) や $(xxxx).bind(xxxx) を使用して index.html ファイル中の id や class 属性に指定したコンポーネントとその GUI を操作したときに動作する内容を結び付けています。
ログイン直後に表示されるキー名選択画面では、get_key_list() 関数がコールされて、その関数内から Web API 用に作成したLIST_JSON.lua スクリプトをコールしています。script_exec() 関数は予めオールブルーシステム側で用意している JavaScript 関数で C:\Program Files (x86)\AllBlueSystem\WebRoot\web_api_sample\summary\chart\libs\device_server\webapi.js 内で定義されていて、下記のようになっています。
///////////////////////////////////////////////////////////////////////////////////////////////// // // DeviceServer のスクリプトを実行する。 // 第2パラメータにスクリプトパラメータを指定する // callback パラメータを省略するとリターンパラメータを受け取らない // ///////////////////////////////////////////////////////////////////////////////////////////////// function script_exec(name,params,callback){ if (callback == undefined){ var callback = "default_callback"; var url = server_host_url + "/command/json/script" + "?session=" + encodeURIComponent(session_token) + "&resultrecords=0" + "&name=" + encodeURIComponent(name); } else { var url = server_host_url + "/command/json/script" + "?session=" + encodeURIComponent(session_token) + "&name=" + encodeURIComponent(name); } for(key in params){ url = url + "&" + encodeURIComponent(key) + "=" + encodeURIComponent(params[key]); } $.ajax({"url" : url, "dataType" : "jsonp", "jsonpCallback" : callback }); }
Web API をコールするための URL パラメータを作成した後、jquery の $.ajax() 関数をコールしています。
集計パラメータ設定画面では、集計対象期間やグラフプロット値に使用する計算方法(平均または合計)の選択が行えます。これらの集計パラメータ設定値は、グラフ表示画面へ移動するボタンを押したときにplot_chart() 関数がコールされて、SUMMARY_DATA_JSON スクリプトを実行する WebAPI コール時の URL パラメータに指定されます。
グラフの表示には jqplot ライブラリを使用しています。WebAPI の実行完了時にコールされるハンドラ( plot_chart_bar() または plot_chart_line() )関数で JSON 配列で返された集計データ(シリーズ配列)をグラフに表示します。
jqplot ライブラリを使用すると、軸ラベルの表示やスケーリングを全て自動計算しますので、集計データのみをパラメータで渡すだけで最適なグラフを表示してくれます。また、マウスクリックによるズーム機能がありますので、集計パラメータで集計単位間隔を短い時間に変更した場合でも拡大して見易く表示することができます。
●Webアプリケーション動作例
早速Webアプリを起動してみます。その前に、センサーデータが統計データベースに定期的に登録されていないとなにも表示できませんので、この記事 と この記事を参考にしてデータを格納してください。また、簡単に試験するだけでしたら、DeviceServer のライブラリ関数 add_stat_data() にタイムスタンプパラメータ(第3パラメータを省略すると登録時の時刻に設定されます)も指定して、
add_stat_data(“試験用キー”, 10,”2014/1/1 10:10:0″)
add_stat_data(“試験用キー”, 20,”2014/1/1 10:20:0″)
add_stat_data(“試験用キー”,30,”2014/1/1 10:30:0″)
の様な感じでテストデータを登録するだけでもグラフ表示できます。
Web ブラウザを起動して、http://localhost/web_api_sample/summary/chart/index.htmlにアクセスします。WebブラウザをサーバーPC とは別の PC で起動する場合には、localhost 部分を DeviceServer の動作する PC のIP アドレスに変更してブラウザの URI 欄に指定します。
最初にログイン画面が表示されます。DeviceServer に作成したユーザー名とパスワードを指定してログインします。DeviceServer のアカウントはインストール時に作成した一般ユーザーアカウントや管理者アカウントを指定できます。このとき、ユーザーアカウントのアプリケーションフラグ”WebLogin” にチェックが付いていないと一切のWebAPI 経由での操作(ログイン, スクリプト実行等)ができませんので注意してください。
ログインに成功すると、統計データベース中に登録済みの全てのキー名がチェックボックスリストで表示されます。グラフ表示したいキーをここで複数選択できます。
グラフ表示するときには、ここで指定したデータが合わせて1つのグラフに表示されます。このため、例えば温度センサーのグラフならば同じデータ単位(℃) をもったものだけを複数選択するようにします。赤外線センサと明るさ(CDS)のデータを同時に表示するような指定をすると、グラフの Y 軸の単位(意味)が不明になりますので避けましょう。
集計パラメータボタンを押すと下記の画面が表示されます。
ここではグラフ表示したい期間を指定します。集計対象とする最初の日付や時刻を空のままにしておくと、現在の日付と時刻を基に最新のデータを表示するように自動計算されます。
グラフのタイプは棒グラフまたは折れ線グラフを選択できます。リモート側からのデータが届いていない欠損部分を見やすくするには棒グラフの方が見やすいです。折れ線グラフでは欠損部分を飛ばして線を描画しますので変化傾向を見やすくなります。
グラフ作成ボタンを押すとグラフを表示します。
グラフのX軸には登録データのタイムスタンプを元に自動スケーリングされた目盛りが表示されます。Y 軸も同様にデータの値を元に自動スケーリングされて表示されます。
ヘッダ部分の集計パラメータボタンを押すと集計パラメータ設定画面に戻って、集計期間や集計間隔の指定をやり直すことができます。
ここでは、集計対象日付を2014/7/1 に設定したのち、月次グラフを表示するように変更しています。月次集計はデフォルトでは 4 時間毎に集計値をプロットしますが、今回はもっと細かく表示するために 1 時間(3600秒)単位で計算するように集計単位パラメータを変更しています。
グラフ作成ボタンを押して再び集計計算を行ってグラフを表示してみます。
かなりグラフが込み入っていますが日々のデータの動きがよくわかるようになりました。細かいデータを確認するために、マウスで拡大表示したい部分を囲んでみます(上記の明るくなっている部分) 。
グラフが拡大表示されると同時に、X, Y 軸のラベルも更新されて、細かいデータまで確認できるようになります。
このように jqplot ライブラリを利用すると簡単にグラフを作成したりズーム機能を利用することができます。また、Y 軸に別の単位のデータを重ねて表示する機能も jqplot に用意されていますので、温度と赤外線 、明るさの複合グラフを表示するように変更するのも簡単にできると思います。
ここで紹介した Web アプリのソースファイルは全てインストールキットに含まれています。商用・個人利用を問わずに自由に改造や流用をしていただいて構いません。此方から最新のキットをダウンロードして、直ぐに使用することができます。(デモライセンスが添付されていますので直ちに使用可能です)。
それではまた。