目次

閲覧機能を追加

ウェブブラウザを使って、取得したデータを閲覧できるようにします。
最新10件を表形式で表示する画面を作ったあと、Aloneのグラフモジュールを使ってトレンドグラフを表示する画面も作ってみます。

最新データを表形式で表示

送信されてきた最新データ10件を、表形式でブラウザに表示してみます。
今回、サンプルコード hello world をコピーして使います。

cd alone/controllers
cp -Rp 00_hello logger
cd logger
rm README

アクションの実装

アクション名は、view_table としましょう。併せて、コントローラ名をそれらしい名前に変更しましょう。

main.rb
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

この行を追加して、アクションを実行してみましょう。
http://192.168.1.11/cgi-bin/index.rb?ctrl=logger&action=view_table

テンプレートファイルの準備

テンプレートファイルを準備します。

cp index.rhtml view_table.rhtml

テンプレートファイルでは、コントローラで用意した @data の内容を、html table タグを使って表示すれば良いので、以下のようになります。

view_table.rhtml
<%= header_section %>
<title>最新データ10件</title>
<meta http-equiv="refresh" content="60" >
 
<%= body_section %>
<p>本日の気圧・気温 最新データ10件</p> 
 
<table class="al-sheet-table">
  <tr>
    <th>日時
    <th>気圧(hPa)
    <th>気温(℃)
  </tr>
  <% @data.each do |d| %>
  <tr>
    <td><%=h d[0].strftime("%Y/%m/%d %H:%M") %>
    <td style="text-align: right;"><%=h sprintf("%.2f", d[1]) %>
    <td style="text-align: right;"><%=h sprintf("%.1f", d[2]) %>
  </tr>
  <% end %>
</table>
 
<%= 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

実行結果

任意日のデータをグラフで表示

今度は、任意の日付を指定して、その日の気圧、気温の推移をグラフで表示します。

テンプレートファイルの実装

フォーム入力があるので、テンプレートから作っていきましょう。
参考になるサンプルは、フォームサンプル2 (controllers/??_form2/index.rhtml) です。これをベースにします。

cd alone/controllers
cp ??_form2/index.rhtml logger/view_graph.rhtml

また、グラフは html埋め込み で行います。
入力項目は日付のみ1項目なので、date という識別名にして、以下のようなテンプレートを書きます。

view_graph.rhtml
<%= header_section %>
<title>トレンドグラフ</title>
 
<%= body_section %>
<p>気圧・気温 トレンドグラフ</p>
 
<form method="<%=h @form.method %>" action="<%=h @form.action %>">
  日付指定:<%= @form.make_tag( :date )  %>
  <input type="submit" value="OK">
</from>
 
<p>
  気圧推移<br>
  <%= @trend_p %>
</p>
 
<p>
  気温推移<br>
  <%= @trend_t %>
</p>
 
<%= footer_section %>

アクションの実装

対応するアクションの作成です。 アクション名は、view_graphとしました。 まず、フォームが意図通り表示されるか確認するために、テンプレートを適用するだけのアクションを作ってみます。また、html5で追加された date タグを使って、日付入力に向いた入力欄を作ってみましょう。

main.rb
  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

実行結果

意図通りフォームが表示されたので、アクションの続きを作ります。

    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

実行結果

見た目の改善

上のグラフは、ちょうど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

実行結果

意図通りにグラフが表示されました。

今回はデータ量が最大1440個で少し多い程度でしたが、もっと大量にデータがある場合はグラフ描画に使われるSVGのサイズが必要以上に大きくなるので、プログラムでデータを間引いたほうが良いでしょう。

インデクスページの作成

インデクスページを作って、表形式、グラフ両方のページにワンクリックで飛べるようにしましょう。

index.rhtml
<%= header_section %>
<%= body_section %>
<h1>ラズベリーパイによる気圧・気温データロガー</h1>
 
<ul>
  <li>
    <a href="<%=h Alone.make_uri(action:"view_table") %>">最新10件のデータ表示</a>
  <li>
    <a href="<%=h Alone.make_uri(action:"view_graph") %>">トレンドグラフ表示</a>
</ul>
 
<%= footer_section %>

これで完成となります。

プログラム全体

コントローラ全体を再掲しておきます。

main.rb
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