ESP-WROOM-02で二次元温度センサ(ROBOBA041, Grid-EYE)を使ってみる 後編
しばらく間が空いてしまいました、申し訳ありません。育児と趣味(工作)の両立はなかなかやはり、難しいもので。基本的に子供が寝ている時間しか趣味の時間がとれないという。そして、その貴重な時間が、この一ヶ月間、全部ゼルダに持っていかれているというこの有様。
。。。いや、だって、めちゃめちゃ面白いんですもの。。。数年ぶりに寝る間を惜しんでゲームしてしまっています。日々、攻略サイトを見るか見ないかの葛藤を続けていますが、とりあえず、何も見ずに、四体の神獣を解放し、マスターソードを手に入れるところまでは進みました。
ということで、ようやく一区切り(?)ついたので、前回やり残していたことを片付けてしまいたいと思います。二次元温度センサ Grid-EYEで取得し、クラウド(ThingSpeak)に蓄積していたデータを二次元平面で可視化してみます。
可視化については、Grid-EYEのArduinoライブラリを作成されている方が、Processingを使って取得データをリアルタイムに可視化するものを既に作られています。なので、それをそのまま流用させてもらった方が話が早いのかもしれませんが、自分がProcessingにあまり馴染みがないため、Pythonでイチから作ってみることにしました。
はじめは、本業の方で使い慣れている、Pythonのデータ分析界隈ではおなじみの可視化ツール・matplotlibを使って何とかならないかなあと思ってやってみました。
試しにデータを読み込んで、一回分の取得データをヒートマップで可視化してみると、こんな感じになります。
import csv import numpy as np ######## データの読み込み ######## in_file_name = 'feeds.csv' pixel_frame = [] with open(in_file_name,'r') as in_file: reader = csv.reader(in_file) header = next(reader) for row in reader: time_utc = row[0] id = row[1] temperature = row[2] temp_grid_1 = row[3].split(':') temp_grid_2 = row[4].split(':') pixels = [[0 for i in range(8)] for j in range(8)] for i in range(4): for j in range(8): pixels[i][j] = float(temp_grid_1[i*7+j]) pixels[i+4][j] = float(temp_grid_2[i*7+j]) pixel_frame.append(pixels) # 読み込んだ配列をnumpy配列に変換 np_pixel_frame = np.array(pixel_frame) ######## matplotlibで可視化 ######## import matplotlib.pyplot as plt %matplotlib inline data = np_pixel_frame[0] # 描画する fig, ax = plt.subplots() heatmap = ax.pcolor(data, cmap=plt.cm.OrRd) ax.invert_yaxis() # y軸の0を上に ax.xaxis.tick_top() # x軸の値を上に plt.show()
画像はこちら。
うん、とりあえず表示されました。ちなみにSeabornを場合は、上のソースの「matplotlibで可視化」の部分を以下に差し替えてみてください。
import seaborn as sns data = np_pixel_frame[0] ax = sns.heatmap(data, cmap='OrRd', square=True) sns.plt.axis("off") sns.plt.show()
Seabornを使ったときの画像はこちら。
横にバーがついて見やすくなりました。
さて、取得したデータを一つずつ可視化するならこれでOKなのですが、できればやっぱり、時間経過と共にどう変わっていくかを見えるようにしたいところです。それで、matplotlibのアニメーション機能で何とかならないかと試行錯誤してみたのですが、結局うまくいきませんでした。
というわけで、素直にGUIアプリケーションを作ってみることにしました。PythonでGUIアプリケーションを作るのは何気に初めてでしたので、色々調べてみたところ、何だかTkinterというものが使いやすそうでしたので、これをベースに作ってみることにしました。
とりあえず機能を最小限に絞って、こんな感じで作ってみました。
“Start”ボタンで、右に書いてある速さ(秒)で1分ごとの取得データを表示していきます。速さは0.1s〜1.0sまで、0.1s刻みで自由に変更できるようにしています。”Start”ボタンはアニメーション開始後に”Stop”ボタンに変わります。Stop中は、”Back”ボタンで1分前のデータに遡って表示、”Next”ボタンで1分後のデータを表示するようになります。”Reset”ボタンで、今回読み込んだ最初のデータの表示に戻ります。
実際にはこんな感じで動きます。
ソースコードは以下です。
import tkinter as tk import csv import time import threading from pytz import timezone from dateutil import parser # データの読み込み---------------------------------- in_file_name = 'feeds.csv' time_frame = [] pixel_frame = [] with open(in_file_name,'r') as in_file: reader = csv.reader(in_file) header = next(reader) for row in reader: time_utc = row[0] id = row[1] temperature = row[2] temp_grid_1 = row[3].split(':') temp_grid_2 = row[4].split(':') # 時刻情報を日本時間に変換して保持 time_jst = parser.parse(time_utc).astimezone(timezone('Asia/Tokyo')) time_frame.append(time_jst) # 8x8の二次元配列を定義 pixels = [[0 for i in range(8)] for j in range(8)] for i in range(4): for j in range(8): pixels[i][j] = float(temp_grid_1[i*7+j]) pixels[i+4][j] = float(temp_grid_2[i*7+j]) pixel_frame.append(pixels) ######################################################## # GUI作成 ######################################################## frame_index = 0 animation_on = False TEMPERATURE_MAX = 35 TEMPERATURE_MIN = 15 TEMPERATURE_RANGE = TEMPERATURE_MAX - TEMPERATURE_MIN root = tk.Tk() root.title("GridEye Viewer") root.geometry("225x350") # 時刻テキスト定義 lb_time = tk.Label(text='Time') # グリッド定義 color_grids = [] for i in range(0,8): color_grid_row = [] for j in range(0,8): color_grid_row.append(tk.Label(text=' ', bg='#f0f0f0')) color_grids.append(color_grid_row) # アニメーション定義 class AnimationThread(threading.Thread): def __init__(self): threading.Thread.__init__(self) def run(self): global frame_index global animation_on global speed_var while animation_on: frame = pixel_frame[frame_index] for i in range(0,8): for j in range(0,8): temp = frame[i][j] temp_diff = 0 if temp < TEMPERATURE_MIN: temp_diff = 0 elif temp > TEMPERATURE_MAX: temp_diff = TEMPERATURE_RANGE else: temp_diff = temp - TEMPERATURE_MIN r = 255 g = 255 - int(temp_diff/TEMPERATURE_RANGE*255) b = 255 - int(temp_diff/TEMPERATURE_RANGE*255) color = '#%02x%02x%02x' % (r,g,b) color_grids[i][j].configure(bg = color) lb_time.configure(text=time_frame[frame_index]) frame_index += 1 time.sleep(float(speed_var.get()[:-2])) # 単位を削除して変換 # アニメーション開始/停止イベントハンドラ def animation(): global animation_on animation_on = not animation_on if animation_on: # アニメーションスレッド生成・開始 th_anim = AnimationThread() th_anim.start() bt_anim.configure(text="Stop") else: bt_anim.configure(text="Start") # ステップ実行関数 def update_grids(step): global frame_index frame_index += step if frame_index < 0: frame_index = 0 frame = pixel_frame[frame_index] for i in range(0,8): for j in range(0,8): temp = frame[i][j] temp_diff = 0 if temp < TEMPERATURE_MIN: temp_diff = 0 elif temp > TEMPERATURE_MAX: temp_diff = TEMPERATURE_RANGE else: temp_diff = temp - TEMPERATURE_MIN r = 255 g = 255 - int(temp_diff/TEMPERATURE_RANGE*255) b = 255 - int(temp_diff/TEMPERATURE_RANGE*255) color = '#%02x%02x%02x' % (r,g,b) color_grids[i][j].configure(bg = color) lb_time.configure(text=time_frame[frame_index]) # ステップ実行(戻る)ハンドラ def step_back(): update_grids(-1) # ステップ実行(進む)ハンドラ def step_next(): update_grids(1) # リセットハンドラ def reset(): global frame_index frame_index = -1 update_grids(1) # アニメーション開始/停止ボタン定義 bt_anim = tk.Button(root, width=6, height=2, text="Start", command=animation) # ステップ実行(戻る)ボタン定義 bt_back = tk.Button(root, width=6, height=2, text="Back", command=step_back) # ステップ実行(進む)ボタン定義 bt_next = tk.Button(root, width=6, height=2, text="Next", command=step_next) # リセットボタン定義 bt_reset = tk.Button(root, width=12, height=2, text="Reset", command=reset) # ドロップダウンリスト(オプションメニュー)定義 speed_vars = ["0.1 s","0.2 s","0.3 s","0.4 s","0.5 s","0.6 s","0.7 s","0.8 s","0.9 s","1.0 s"] speed_var = tk.StringVar() speed_var.set(speed_vars[0]) # デフォルト値 dl_speed = tk.OptionMenu(root, speed_var, *speed_vars) ######################################################## # ボタン配置 bt_anim.grid(row=0, rowspan=1, column=0, columnspan=4, padx=0, pady=0) # 再生スピード設定配置 dl_speed.grid(row=0, rowspan=1, column=4, columnspan=4, padx=0, pady=0) #lb_speed.grid(row=0, rowspan=1, column=7, columnspan=1, padx=0, pady=0) # ボタン配置 bt_back.grid(row=1, rowspan=1, column=0, columnspan=4, padx=0, pady=0) bt_next.grid(row=1, rowspan=1, column=4, columnspan=4, padx=0, pady=0) #時刻情報配置 lb_time.grid(row=2, rowspan=1, column=0, columnspan=8, padx=0, pady=0) # グリッド配置 for i in range(0,8): for j in range(0,8): color_grids[i][j].grid(row=(i+3), rowspan=1, column=j, columnspan=1, padx=1, pady=1) # ボタン配置 bt_reset.grid(row=11, rowspan=1, column=1, columnspan=6, padx=0, pady=0) ######################################################## # ウィンドウを閉じるときにアニメーションのスレッドが動いていたら、先にそちらを止めてから終了させる def window_close(): global animation_on if animation_on: animation_on = False root.destroy() root.protocol("WM_DELETE_WINDOW", window_close) # メインループ開始 root.mainloop()
グローバル変数を多用していて何だかカッコ悪いですが。。。昔はJavaでオブジェクト指向を意識しながらプログラムを書いていたものですが、最近は簡単な処理をPythonで書くことが多くて、すっかりオブジェクト指向の書き方を忘れてしまいました。
ともあれ、これで一応最低限のベースは作ったので、あとは必要に応じて機能拡張していけば良いと思います。
これでGrid-EYEを使って云々は一区切りです。次にやりたいことは色々あるのですが、さて、何から手をつけよう。
ディスカッション
コメント一覧
コメント失礼します。
質問なのですが、動作させるためのプログラムを改変することでセンサが取得する温度データの精度が変わることはあるのでしょうか?
かりあ様
すみません、一年以上前の話なのですっかり忘れてしまいました。。。私自身はこちらの方が作成くださったライブラリをそのまま使用しているだけですので、このライブラリを使っている限りは精度に変化はないと思いますが、ライブラリレベルまで、より突っ込んだレベルでプログラムを改変するのなら、精度が変わることもあるかもしれません。。。正直なところ、わからないです。申し訳ありません。