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

なんか、いまいちマルチスレッド化した各種情報表示処理がうまく終了してくれません。

一応改良版です。

ついでに、「ホスト名」「IPアドレス(V4)(インターフェース毎)」を表示するように修正しました。

手持ちの小さなLCDに表示するため、2行から1行に修正したりしています。

次は、何を表示しようか。

 

Pythonプログラミング

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

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

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 setFullScreen(self, isFullScreen = True):
        """
        ウィンドウのフルスクリーンを設定
        """
        self.master.fullScreenState = isFullScreen
        self.master.attributes("-fullscreen", self.master.fullScreenState)

    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 = '', isLeft = True):
        """
        ラベルを追加表示する
        """
        # 初期表示テキストが設定されておらず、フォーマットが設定されている場合は、フォーマット文字列を表示
        if (text == '' and format != ''):
            lbl = label(self, text = format)
        else:
            lbl = label(self, text = text)
        
        # 表示フォント設定
        lbl['font'] = ("", 20)

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

        if isLeft:
            lbl.grid(row = row, column = column, pady = 5, padx = 20, sticky = Tk.W)
        else:
            lbl.grid(row = row, column = column, pady = 5, padx = 20, sticky = Tk.E)
        
        return lbl

    def addButton(self, row, column, text, width = None):
        """
        ボタンを追加表示する
        """
        btn = Tk.Button(self, text = text, width = width)
        btn.grid(row = row, column = column, sticky = Tk.W)
        # 表示フォント設定
        btn['font'] = ("", 15)

        return btn

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)

    @staticmethod
    def IP_info():
        """
        ホスト名、IPアドレスの取得(IPアドレスはIF:IP(V4)アドレスkジェイ式で複数)
        """
        host = socket.gethostname()
        ip = []
        ipinfo = psutil.net_if_addrs()
        for _if in ipinfo.keys():                           # if毎の属性値
            for ifattr in str(ipinfo[_if]).split(','):      # ifの属性値
                if ifattr.strip().startswith('address='):
                    if ifattr.strip().split('=')[1] == "'127.0.0.1'":
                        # Localアドレスははじく
                        break
                    else:
                        ip.append('{0}:{1}'.format(_if,ifattr.strip().split("=")[1].strip("'")))
                        break

        return (host, ip)

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, humidity)
        except Exception as e:
            print(e)
            lblTemp.disp_formated(0, 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], cpu_info[1])
        lblMem.disp_formated(mem_info[0], mem_info[1], (mem_info[0] / mem_info[1]) * 100)

    @staticmethod
    def disp_if():
        """
        ホスト名とIPアドレスを表示
        """    
        # 表示情報の取得
        ip_info = raspiinfo.IP_info()
        # 表示
        lblHost.disp_formated(ip_info[0])
        lblIP.disp_formated(ip_info[1])

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

            # 500mS待ち受ける
            global runThread
            for t in range(0, 5):
                if runThread:
                    # 待つ(100mS)
                    sleep(0.1)
                else:
                    return

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

            # 2000mS待ち受ける
            global runThread
            for t in range(0, 20):
                if runThread:
                    # 待つ(100mS)
                    sleep(0.1)
                else:
                    return

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

            # 2000mS待ち受ける
            global runThread
            for t in range(0, 20):
                if runThread:
                    # 待つ(100mS)
                    sleep(0.1)
                else:
                    return

    @staticmethod
    def execute_disp_IF():
        """
        別スレッドでホスト名とIPアドレスを表示処理実行
        """
        while True:
            # 現在室温、湿度を表示する
            dispSub.disp_if()

            # 10S待ち受ける
            global runThread
            for t in range(0, 100):
                if runThread:
                    # 待つ(100mS)
                    sleep(0.1)
                else:
                    return

def clickQuit(event):
    """
    終了する
    """
    # 別スレッド終了フラグをOFFに
    global runThread
    runThread = False
    # 別スレッドを終了させる
    global threads
    for thread in threads:
        thread._stop()
    # ウィンドウを閉じる
    global w
    w.master.destroy()
    # アプリケーション終了
    print('finished !!')

if __name__ == "__main__":
    global w
    # メインウィンドウを生成、各設定を行う
    w = Frame()
    w.setTitle('Raspberry Pi 情報')
    # サイズは指定せず、フルスクリーンで表示する
    w.setFullScreen(True)     
    w.pack()

    # 項目ラベル
    guiParts.addLabel(w, 0, 0, '日時 時刻:'        , isLeft = False)
    guiParts.addLabel(w, 1, 0, '室温 / 湿度:'      , isLeft = False)
    guiParts.addLabel(w, 2, 0, 'CPU温度 / 使用率:' , isLeft = False)
    guiParts.addLabel(w, 3, 0, 'メモリ情報:'       , isLeft = False)
    guiParts.addLabel(w, 4, 0, 'host:'            , isLeft = False)
    guiParts.addLabel(w, 5, 0, 'IP(V4):'          , isLeft = False)
    # 項目値
    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] / {1:>5.1f} [%]')
    lblCpuTemp  = guiParts.addLabel(w, 2, 1, format = '{0:>5.1f} [°C] / {1:>5.1f} [%]')
    lblMem      = guiParts.addLabel(w, 3, 1, format = '{0:>4} / {1:>4} [MB] ({2:>5.1f} [%])')
    lblHost     = guiParts.addLabel(w, 4, 1, format = '{0}')
    lblIP       = guiParts.addLabel(w, 5, 1, format = '{0}')
    btnQuit     = guiParts.addButton(w, 6, 1, '終了', 10)

    # 別スレッドでの実行フラグをONに
    global runThread
    runThread = True
    # 別スレッドで再表示処理を実行
    global threads
    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))
    threads.append(threading.Thread(target=dispSub.execute_disp_IF))
    for thread in threads:
        thread.start()

    # 終了ボタンに終了処理をバインド
    btnQuit.bind('<1>', clickQuit)

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