require "alone"
class LoggerController < AlController
def action_view_table()
end
end
画面に本日の最新データ10件を表示する事が目的です。まずは、本日のデータファイルからデータを10件取り出す部分を実装します。
**CSVファイルの読み込み**
require "csv"
FILENAME = "/var/log/logger/data-"
filename = FILENAME + Time.now.strftime("%Y%m%d") + ".csv"
data = CSV.read(filename, :converters=>[:date_time, :float, :float]) rescue []
CSVのファイルを扱う場合は、Rubyは標準でCSVライブラリを持っているので、それを使います。CSVライブラリでは、カラムごとにデータタイプを指定して変換させることができます。今回は、日時、気圧、気温の順で並んでいるので、それぞれ、:date_time, :float, :float と指示してデータを変換します。また、ファイルが存在しなかった場合にそなえて、空配列を返すことでエラー停止しないように配慮します。
**最新の10件を抽出**
@data = data.pop(10)
変数 data には、ファイルに記述された順番のままで配列に変換されるので、後ろ10件を取り出せば最新10件となります。
この値は htmlテンプレートに渡したいので、@をつけてインスタンス変数としておきます。
ここで、@dataを表示すれば、最新10件が取り出されている様子が確認できます。
p @data
この行を追加して、アクションを実行してみましょう。\\
<%= header_section %>
最新データ10件
<%= body_section %>
本日の気圧・気温 最新データ10件
日時
気圧(hPa)
気温(℃)
<% @data.each do |d| %>
<%=h d[0].strftime("%Y/%m/%d %H:%M") %>
<%=h sprintf("%.2f", d[1]) %>
<%=h sprintf("%.1f", d[2]) %>
<% end %>
<%= footer_section %>
見栄えの調整のため、styleタグを使って少し工夫しています。また、metaタグで60秒毎にリロードさせて、常に最新の情報を表示するようにしています。
では、コントローラにこのテンプレートを適用するように指示を追加しましょう。
AlTemplate.run("view_table.rhtml")
完成したアクションは次のようになります。
def action_view_table()
filename = FILENAME + Time.now.strftime("%Y%m%d") + ".csv"
data = CSV.read(filename, :converters=>[:date_time, :float, :float]) rescue []
@data = data.pop(10)
AlTemplate.run("view_table.rhtml")
end
==== 実行結果 ====
{{:raspi_iot:view_table.png?nolink|}}
===== 任意日のデータをグラフで表示 =====
今度は、任意の日付を指定して、その日の気圧、気温の推移をグラフで表示します。
==== テンプレートファイルの実装 ====
フォーム入力があるので、テンプレートから作っていきましょう。\\
参考になるサンプルは、フォームサンプル2 (controllers/??_form2/index.rhtml) です。これをベースにします。
cd alone/controllers
cp ??_form2/index.rhtml logger/view_graph.rhtml
また、グラフは [[prog_cgi:グラフ_チャート_の描画#html埋め込みを使う方法|html埋め込み]] で行います。\\
入力項目は日付のみ1項目なので、date という識別名にして、以下のようなテンプレートを書きます。
<%= header_section %>
トレンドグラフ
<%= body_section %>
気圧・気温 トレンドグラフ
==== アクションの実装 ====
対応するアクションの作成です。
アクション名は、view_graphとしました。
まず、フォームが意図通り表示されるか確認するために、テンプレートを適用するだけのアクションを作ってみます。また、html5で追加された date タグを使って、日付入力に向いた入力欄を作ってみましょう。
def action_view_graph()
@form = AlForm.new(
AlDate.new("date", :label=>"日付", :value=>Time.now, :required=>true, :tag_type=>"date")
)
AlTemplate.run("view_graph.rhtml")
end
**実行結果**
{{:raspi_iot:view_graph_1.png?nolink|}}
意図通りフォームが表示されたので、アクションの続きを作ります。
if @form.validate
target_date = @form[:date]
else
target_date = Time.now
end
filename = FILENAME + target_date.strftime("%Y%m%d") + ".csv"
data = CSV.read(filename, :converters=>[:date_time, :float, :float]) rescue []
フォームはバリデーションして、正しくバリデーションできればその値を採用し、そうでなければ本日の日付でデータファイルを選択します。データファイルのCSVパースは、先ほどの表形式の場合と同じです。
次に、グラフを作ります。Aloneのグラフモジュールは、データを1次元配列で渡す仕様なので、CSVをパースした結果(2次元配列)を再加工して必要なデータ(気圧データの1次元配列)を作ることにします。取り出した結果が空でなければ、グラフを生成して @trend_p 変数に入れておきます。
data_p = data.collect {|d| d[1] } # dataから気圧だけを取り出して配列に入れ直す
if !data_p.empty?
graph = AlGraph.new( 700, 200 )
graph.add_data_line( data_p )
@trend_p = graph.draw_buffer()
end
気温の方も、全く同じです。
data_t = data.collect {|d| d[2] } # dataから気温だけを取り出して配列に入れ直す
if !data_t.empty?
graph = AlGraph.new( 700, 200 )
graph.add_data_line( data_t )
@trend_t = graph.draw_buffer()
end
==== 実行結果 ====
{{:raspi_iot:view_graph_2.png?nolink|}}
==== 見た目の改善 ====
上のグラフは、ちょうど60分、つまり60個のデータによるグラフで、とりあえず表示はできました。
このように、グラフを表示するだけなら非常に簡単に試すことができます。
しかし、最終的に1日分は 60×24=1440個のデータになるので、かなり見苦しくなることが予想されます。
いくつかの点で改良が必要のようです。
* 測定点が●で表示されており、見にくい。
* 縦軸の値の小数点以下がバラバラで、一部の値ははみ出して見えない。
* 横軸は時刻のはずだが、何時のデータなのかわからない。
* 横軸ラベルが測定点すべてに表示されており、重なってしまっている。
では、一つづつ改善していきます。
=== マーカーの非表示 ===
測定点を表す●はマーカーと呼び、デフォルトで表示されています。以下の指示をすることで消すことができます。
line = graph.add_data_line( data_p ) # add_data_lineメソッドの戻り値 (ContainerLine) に対して、
line.clear_marker() # マーカー消去を指示
=== 縦軸の調整 ===
縦軸は、graphオブジェクトが保持している y_axisオブジェクトプロパティーを変更することで調整します。
graph.y_axis.max = 50 # 最大値を指定
graph.y_axis.min = -10 # 最小値を指定
graph.y_axis.interval = 2 # ラベル値の表示間隔を指定
graph.y_axis.at_labels[:format] = "%.0f" # ラベル値の表示フォーマットを sprintf のフォーマットで指定
=== 横軸の調整 ===
横軸は、全体で1日分なので、左端が0時、右端が24時に固定したほうが見やすそうです。しかし、データファイルは電源を入れた時刻が1行目で最新(≒現在時刻)が最終行になっています。
つまり、ただデータを取り出すだけではだめで、測定されていない時刻を考慮した配列を作らなければならないということになります。
とは言うものの、実際はそれほど難しくはありません。全体が1440個のデータであることがわかっているので、最初からそのサイズの配列を作っておき、CSVファイルから読み込んだデータをそこへ割り当てていけば良いだけです。
# 24時間分の気圧・気温データを再構築する
data_p = Array.new(1440)
data_t = Array.new(1440)
data.each {|tm, p, t|
idx = tm.hour * 60 + tm.minute
data_p[idx] = p
data_t[idx] = t
}
軸ラベルについて、これは「何時」のデータであるかを知りたいので、ラベル用配列に「時」だけを適切な間隔で入れておき labels アトリビュートに指定します。
# X軸ラベルを1時間毎で生成する
labels = []
24.times {|i| labels[i * 60] = i }
graph.x_axis.interval = 60
graph.x_axis.labels = labels
==== 実行結果 ====
{{:raspi_iot:view_graph_3.png?nolink|}}
意図通りにグラフが表示されました。
今回はデータ量が最大1440個で少し多い程度でしたが、もっと大量にデータがある場合はグラフ描画に使われるSVGのサイズが必要以上に大きくなるので、プログラムでデータを間引いたほうが良いでしょう。
===== インデクスページの作成 =====
インデクスページを作って、表形式、グラフ両方のページにワンクリックで飛べるようにしましょう。
<%= header_section %>
<%= body_section %>
ラズベリーパイによる気圧・気温データロガー
<%= footer_section %>
これで完成となります。
===== プログラム全体 =====
コントローラ全体を再掲しておきます。
require "alone"
require "csv"
require "al_graph"
class LoggerController < AlController
FILENAME = "/var/log/logger/data-"
def action_index()
AlTemplate.run( 'index.rhtml' )
end
def action_view_table()
filename = FILENAME + Time.now.strftime("%Y%m%d") + ".csv"
data = CSV.read(filename, :converters=>[:date_time, :float, :float]) rescue []
@data = data.pop(10)
AlTemplate.run("view_table.rhtml")
end
def action_view_graph()
@form = AlForm.new(
AlDate.new("date", :label=>"日付", :value=>Time.now, :required=>true, :tag_type=>"date")
)
if @form.validate
target_date = @form[:date]
else
target_date = Time.now
end
filename = FILENAME + target_date.strftime("%Y%m%d") + ".csv"
data = CSV.read(filename, :converters=>[:date_time, :float, :float]) rescue []
# X軸ラベルを1時間毎で生成する
labels = []
24.times {|i| labels[i * 60] = i }
# 24時間分の気圧・気温データを再構築する
data_p = Array.new(1440)
data_t = Array.new(1440)
data.each {|tm, p, t|
idx = tm.hour * 60 + tm.minute
data_p[idx] = p
data_t[idx] = t
}
if !data_p.empty?
graph = AlGraph.new( 700, 200 )
line = graph.add_data_line( data_p )
line.clear_marker()
graph.y_axis.interval = 2
graph.y_axis.at_labels[:format] = "%.0f"
graph.x_axis.interval = 60
graph.x_axis.labels = labels
@trend_p = graph.draw_buffer()
end
if !data_t.empty?
graph = AlGraph.new( 700, 200 )
line = graph.add_data_line( data_t )
line.clear_marker()
graph.y_axis.max = 50
graph.y_axis.min = -10
graph.y_axis.at_labels[:format] = "%.0f"
graph.x_axis.interval = 60
graph.x_axis.labels = labels
@trend_t = graph.draw_buffer()
end
AlTemplate.run("view_graph.rhtml")
end
end