Raspberry Pi Python GUI アプリケーション 08:LCDに現在の日時、温度・湿度、CPU・メモリ情報を表示

入手したLCDディスプレイですが、サイズは非常に小さいながらも、2行しか表示できなかったディスプレイに比べ、表現力が随分上がったので楽しいです。

Amazonで2つ購入したのですが、1つは正常動作、もう一つは画面表示はできるのですが、タッチ操作ができません。

現在、問い合わせ中。

お隣の大陸まで送り返せと言われても…返品不要で別なもの送ってくれないかなぁ。

 

文字の装飾は全くしていませんが、今回はこんな仕様。

・ウィンドウを1つ表示して、以下の情報を定期的に書き換える

 ・現在の日時、時刻を0.5秒間隔で

 ・I2C接続した温度・湿度センサーの情報を2秒間隔で

 ・CPU温度、使用率とメモリ使用量、総量と使用率を2秒間隔で

 

 

1. 配線図

  LCDディスプレイは、Pin番号の1~28に差し込みます。

  実は、すべてのピンを使用しているわけではなく、SDA、SCLあたりは使っていません。

  このディスプレイはI2C接続ではないため、「刺さってはいるが、配線していない」状態です。

  LCDディスプレイを差し込むと、1~28のPinは使用できなくなるので、分岐ケーブルなどを使用する、もしくはデータシートを参照して、必要なPinのみをLCDディスプレイに差し込む、などの手段があります。

  配線図、わかりづらくてすみません。

f:id:kobatom5278:20210225221757p:plain

2. Pythonプログラミング

  メインスレッドは画面表示自体です。

  ほかに、3つスレッドを作成して、それぞれでウィンドウのラベルを書き換えています。

  それほど面倒なことはしていないのですが、ラベルクラスにフォーマットプロパティを追加、そのフォーマットで値を表示してあげるような処理を組んでいます。

  表示したい各情報の取得は以前の記事のものをちょっと変更したくらいで、基本は変わりません。

 

# -*- coding: utf-8 -*-
# 日本語コメントをエラーとしないように、UTF-8で保存

# 使用するライブラリのインポート
import datetime
import threading
import tkinter as Tk
from time import sleep
import psutil
import smbus

class Frame(Tk.Frame):
    """
    フレームを拡張する
    """
    def __init__(self, master = None):
        # ウィンドウを作成
        Tk.Frame.__init__(self, master)

    def setSize(self, width, height, isCenter = True):
        """
        サイズを指定、ディスプレイ中央に表示する
        """
        # サイズを指定した文字列
        geometry = "{0}x{1}".format(width, height)

        if isCenter:    # ディスプレイ中央に表示する            
            # ディスプレイの幅と高さを取得
            s_width = self.winfo_screenwidth()
            s_height = self.winfo_screenheight()
            # 表示する左上の座標を算出        
            win_left = int((s_width - width) / 2)
            win_top = int((s_height - height) / 2)
            # 表示位置を指定した文字列を追加
            geometry += "+{0}+{1}".format(win_left, win_top)

        # サイズと表示位置を中央に指定(幅 x 高さ + 左位置 + 上位置)
        self.master.geometry(geometry)
    
    def setTitle(self, title):
        """
        タイトルを指定する
        """
        self.master.title(title)
    
    def disableMaximum(self):
        """
        最大化を無効化
        """
        self.master.resizable(False, False)

class label(Tk.Label):
    """
    labelクラスを拡張する
    """
    def setFormat(self, value):
        """
        フォーマット書式を指定する
        """
        self.__dict__['format'] = value
    
    def disp_formated(self, *args):
        """
        指定されているフォーマット書式で表示する
        """
        self['text'] = str(self.__dict__['format']).format(*args)

class guiParts():
    """
    GUI部品を追加表示する
    すべてグリッド、左寄せで表示
    """
    def addLabel(self, row, column, text = '', format = ''):
        """
        ラベルを追加表示する
        """
        # 初期表示テキストが設定されておらず、フォーマットが設定されている場合は、フォーマット文字列を表示
        if (text == '' and format != ''):
            lbl = label(self, text = format)
        else:
            lbl = label(self, text = text)

        # 表示フォーマットを設定        
        lbl.setFormat(format)

        lbl.grid(row = row, column = column, pady = 5, padx = 20, sticky = Tk.W)
        
        return lbl

class raspiinfo():
    @staticmethod
    def tempelature_humidity():
        """
        温度と湿度を取得する
        """
        ADR = 0x44
        # I2Cの取得
        ic2 = smbus.SMBus(1)
        # 測定頻度の設定
        ic2.write_byte_data(ADR, 0x23, 0x34)
        sleep(0.5)
        # 測定値の読出し
        ic2.write_byte_data(ADR, 0xe0, 0x0)
        sleep(0.5)
        # 6バイト取得
        d = ic2.read_i2c_block_data(ADR, 0x0, 6)

        # 0~1バイトが温度
        # 測定データ(生データ)を取得
        t = ((d[0]) << 8) | (d[1])
        # 換算する
        t = -45 + (175 * t / 65535)

        # 3~4バイトが湿度
        # 測定データ(生データ)を取得
        h = ((d[3]) << 8) | (d[4])
        # 換算する
        h = 100 * h / 65535

        # 結果を返却
        return t, h

    @staticmethod
    def cpu_temp():
        """
        CPU温度の取得
        """
        f = open("/sys/class/thermal/thermal_zone0/temp","r")
        cpu_temp = 0.0
        for t in f:
            cpu_temp = t[:2]+"."+t[2:]
        f.close()
        return float(cpu_temp)

    @staticmethod
    def cpu_percent():
        """
        CPU使用率の取得
        """
        cpu_percent = psutil.cpu_percent(interval=1)
        return round(cpu_percent)

    @staticmethod
    def memory_info():
        """
        メモリ情報(使用量[MB]、総量[MB])の取得
        """
        mem = psutil.virtual_memory()
        mem_used = round(mem.used / 1024 / 1024)
        mem_total = round(mem.total / 1024 / 1024)
        return (mem_used, mem_total)

class dispSub():
    @staticmethod
    def disp_clock():
        """
        現在時刻を表示
        """
        # 表示情報の取得
        now = datetime.datetime.now()
        # 表示
        lblClock.disp_formated(now)

    @staticmethod
    def disp_temp():
        """
        室温と湿度を表示
        """
        try:
            # 表示情報の取得
            templature , humidity = raspiinfo.tempelature_humidity()
            # 表示
            lblTemp.disp_formated(templature)
            lblHum.disp_formated(humidity)
        except Exception as e:
            print(e)
            lblTemp.disp_formated(0)
            lblHum.disp_formated(0)

    @staticmethod
    def disp_cpu():
        """
        CPU温度とCPU使用率、メモリ情報を表示
        """    
        # 表示情報の取得
        cpu_info = (raspiinfo.cpu_temp(), raspiinfo.cpu_percent())
        mem_info = raspiinfo.memory_info()
        # 表示
        lblCpuTemp.disp_formated(cpu_info[0])
        lblCpuPer.disp_formated(cpu_info[1])
        lblMem.disp_formated(mem_info[0], mem_info[1], (mem_info[0] / mem_info[1]) * 100)

    @staticmethod
    def execute_disp_clock():
        """
        別スレッドで現在時刻表示処理実行
        """
        while runThread:
            # 現在時刻を表示する
            dispSub.disp_clock()
            # 待つ(500mS)
            sleep(0.5)

    @staticmethod
    def execute_disp_temp():
        """
        別スレッドで室温と温度を表示処理実行
        """
        while runThread:
            # 現在室温、湿度を表示する
            dispSub.disp_temp()
            # 待つ(2000mS)
            sleep(2)

    @staticmethod
    def execute_disp_cpu():
        """
        別スレッドでCPU情報とメモリ情報を表示処理実行
        """
        while runThread:
            # 現在室温、湿度を表示する
            dispSub.disp_cpu()
            # 待つ(2000mS)
            sleep(2)

if __name__ == "__main__":
    global w
    # メインウィンドウを生成、各設定を行う
    w = Frame()
    w.setTitle('Raspberry Pi 情報')
    w.setSize(500, 220)
    w.pack()

    # 項目ラベル
    guiParts.addLabel(w, 0, 0, '現在の日時 時刻:')
    guiParts.addLabel(w, 1, 0, '室温:')
    guiParts.addLabel(w, 2, 0, '湿度:')
    guiParts.addLabel(w, 3, 0, 'CPU温度:')
    guiParts.addLabel(w, 4, 0, 'CPU使用率:')
    guiParts.addLabel(w, 5, 0, 'メモリ情報:')
    # 項目値
    lblClock    = guiParts.addLabel(w, 0, 1, format = '{0:%Y/%m/%d(%a) %H:%M:%S}')
    lblTemp     = guiParts.addLabel(w, 1, 1, format = '{0:>5.1f} [°C]')
    lblHum      = guiParts.addLabel(w, 2, 1, format = '{0:>5.1f} [%]')
    lblCpuTemp  = guiParts.addLabel(w, 3, 1, format = '{0:>5.1f} [°C]')
    lblCpuPer   = guiParts.addLabel(w, 4, 1, format = '{0:>5.1f} [%]')
    lblMem      = guiParts.addLabel(w, 5, 1, format = '{0:>4} / {1:>4} [MB] ({2:>5.1f} [%])')

    # 別スレッドでの実行フラグをONに
    global runThread
    runThread = True
    # 別スレッドで再表示処理を実行
    threads = []
    threads.append(threading.Thread(target=dispSub.execute_disp_clock))
    threads.append(threading.Thread(target=dispSub.execute_disp_temp))
    threads.append(threading.Thread(target=dispSub.execute_disp_cpu))
    for thread in threads:
        thread.start()

    # メインウィンドウをメインスレッドとして実行
    w.mainloop()

    # メインウィンドウが閉じられたら別スレッド終了フラグをOFFに
    runThread = False
    # 別スレッドが終了するのを待ち合わせる
    for thread in threads:
        thread.join()
    # アプリケーション終了
    print('finished !!')