Raspberry Pi Python GUI アプリケーション 04:メニュー、ボタン、リストボックス

今回は、GUI部品(ラベル、テキストボックス、チェックボックスラジオボタン)を表示してみます。

 

メニューを追加する都合上、ウィンドウとフレームの使用方法が前回と異なります。

おそらくは、今回のほうが正しいのかな、と思います。

 

大まかな流れとしては、以下となります。

・ウィンドウを生成(コンストラクタでタイトルを指定)

・ウィンドウのサイズ、最大化を抑制

・ウィンドウに対してメニューを設定(選択イベントも設定してみる)

・ウィンドウを引数に、フレームを生成

・フレームにGUI部品(メニュー以外)を設定

 

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

# 使用するライブラリのインポート
import tkinter as Tk

# 表示するウィンドウの幅と高さ
WINDOW_WIDTH = 500
WINDOW_HEIGHT = 220

class Window(Tk.Tk):
    """
    TKクラスを継承、Windowクラスとする
    """
    
    def __init__(self, text):
        """
        ウィンドウを生成する(タイトルを指定)
        """
        Tk.Tk.__init__(self)
        self.title(text)

    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.geometry(geometry)
    
    def disableMaximum(self):
        """
        最大化を制限する
        """
        self.resizable(False, False)

class guiParts():
    """
    GUI部品を追加表示する
    すべてグリッド、左寄せで表示
    """

    def addLabel(self, row, column, text):
        """
        ラベルを追加表示する
        """
        lbl = Tk.Label(self, text = text)
        lbl.grid(row = row, column = column, pady = 5, padx = 20, sticky = Tk.W)

        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)

        return btn

    def addListBox(self, row, column, items):
        """
        リストボックスを追加表示する
        """
        lbx = Tk.Listbox(self, height = len(items))
        lbx.grid(row = row, column = column, sticky = Tk.W + Tk.N)
        lbx.insert(Tk.END, *items)

        return lbx

    def addMenu(self, text, items, commands, mbar = None):
        """
        メニューを追加する
        """
        if mbar == None:
            mbar = Tk.Menu(self)
        mnu = Tk.Menu(mbar)
        for index, item in enumerate(items):
            label = item
            command = commands[index]
            if label == '':
                mnu.add_separator()
            else:
                mnu.add_command(label = label, command = command)
        mbar.add_cascade(label = text, menu = mnu)
        w.config(menu = mbar)
        return mbar
 
def printSelectMenu1():
    print('メニューが選択されました1')
def printSelectMenu2():
    print('メニューが選択されました2')

# 継承した場合など、実行されないようにする(現状、意味は特にない)
if __name__ == "__main__":
    # ウィンドウを生成、各設定
    w = Window('メインウィンドウ')
    w.setSize(WINDOW_WIDTH, WINDOW_HEIGHT)
    w.disableMaximum()

    # メニューを設定
    mbar = guiParts.addMenu(w, 'Menu1', ['ABC', '', 'DEF', 'GHI'], [None, None, printSelectMenu1, None])
    mbar = guiParts.addMenu(w, 'Menu2', ['AAA', 'BBB', '', 'DDD'], [printSelectMenu2, None, None, None], mbar)

    # フレームを生成
    f = Tk.Frame(w)

    # 部品を追加、表示
    guiParts.addLabel(f, 0, 0, 'ボタン:')
    guiParts.addButton(f, 0, 1, 'ボタン', 30)

    guiParts.addLabel(f, 1, 0, 'リストボックス:')
    lbc = guiParts.addListBox(f, 1, 1, ['ABC', 'DEF', 'GHI'])
    lbc.select_set(1, 2)        # index = 1 ~ 2 のアイテムを選択状態に
    print(lbc.curselection())   # 選択されているアイテムを取得

    f.pack()
    w.mainloop()

Raspberry Pi Python GUI アプリケーション 03:ラベル、テキストボックス、チェックボックス、ラジオボタン

今回は、GUI部品(ラベル、テキストボックス、チェックボックスラジオボタン)を表示してみます。

Python(tkinter)は少し自分の知っているものとしくみが異なるようで、ラベルなどを生成する関数にウィンドウ(フレーム)を渡すことで、その渡したウィンドウ(フレーム)に表示されます。

自分が知っている言語では、ラベルなどのオブジェクトを作成、ウィンドウ(ラベル)に追加する流れで部品が表示されました。

 

そんなライブラリですので、少し関数を組んで部品を追加表示するようにしてみました。

 

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

# 使用するライブラリのインポート
import tkinter as Tk

# 表示するウィンドウの幅と高さ
WINDOW_WIDTH = 500
WINDOW_HEIGHT = 220

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

    def setSize(selfwidthheightisCenter = 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(selftitle):
        """
        タイトルを指定する
        """
        self.master.title(title)
    
    def disableMaximum(self):
        """
        最大化を無効化
        """
        self.master.resizable(FalseFalse)

class guiParts():
    """
    GUI部品を追加表示する
    すべてグリッド、左寄せで表示
    """

    def addLabel(selfrowcolumntext):
        """
        ラベルを追加表示する
        """
        lbl = Tk.Label(selftext = text)
        lbl.grid(row = row, column = column, pady = 5padx = 20sticky = Tk.W)

        return lbl

    def addTextBox(selfrowcolumnwidthtext = None):
        """
        テキストボックスを追加表示する
        """
        textbox = Tk.Entry(selfwidth = width)
        textbox.grid(row = row, column = column, sticky = Tk.W)
        if text != None:
            textbox.insert(Tk.END, text)
        
        return textbox

    def addCheckBox(selfrowcolumntextvaliable = None):
        """
        チェックボックスを追加表示する
        """
        if valiable == None:
            valiable = Tk.BooleanVar()
        checkBox = Tk.Checkbutton(selftext = text, variable = valiable)
        checkBox.grid(row = row , column = column, sticky = Tk.W)

        return (checkBox, valiable)

    def addRadioButton(selfrowcolumntextonValuevaliable = None):
        """
        ラジオボタンを追加表示する
        """
        if valiable == None:
            valiable = Tk.IntVar()
        rbn = Tk.Radiobutton(selftext = text, value = onValue, variable = valiable)
        rbn.grid(row = row, column = column, sticky = Tk.W)

        return (rbn, valiable)

# 継承した場合など、実行されないようにする(現状、意味は特にない)
if __name__ == "__main__":
    # メインウィンドウを生成、各設定を行う
    w = Frame()
    w.setTitle('メインウィンドウ')
    w.setSize(WINDOW_WIDTH, WINDOW_HEIGHT)
    w.disableMaximum()

    # ラベル + テキストボックス
    guiParts.addLabel(w, 00'テキストボックス:')
    guiParts.addTextBox(w, 0120'ABC')

    # ラベル + チェックボックス
    guiParts.addLabel(w, 10'チェックボックス:')
    chk1 = guiParts.addCheckBox(w, 11'チェックボックス1')
    chk1[1].set(False)
    chk2 = guiParts.addCheckBox(w, 12'チェックボックス2')
    chk2[1].set(True)
   
    # ラベル + ラジオボタン
    guiParts.addLabel(w, 20'ラジオボタン:')
    ラジオボタン初期値オブジェクト(このオブジェクトを共有するラジオボタンがトグルとなる)
    valRadio = Tk.IntVar()
    guiParts.addRadioButton(w, 21'ラジオボタン1'1, valRadio)
    guiParts.addRadioButton(w, 22'ラジオボタン2'2, valRadio)
    valRadio.set(1)

    w.pack()
    w.mainloop()

Raspberry Pi 電子工作 08:温度・湿度センサーとLCDディスプレイ(I2C接続)にCPU、メモリ状態を表示

16文字2行のLCDディスプレイ。

これに、以下の情報を1秒ごとに表示します。

・現在の日付、時刻

・温度、湿度

・CPU温度と使用率、メモリの使用量と総量

 

温度・湿度計とディスプレイのGPIOへの接続は以前の記事を参考にしてください。

今回はPythonでのスクリプトを変更します。

 

 

1. Pythonプログラミング

  以下のコードで実現できます。

  CPU温度の取得方法は2種類用意しています使用するのは1種類)
  メモリの情報は、「psutil」を使用しています。標準ライブラリです。

  「psutil」はいろいろなハードウェア情報を取得できるようですので、掘っていくと面白いかもしれません。

  なお、今回は別ファイルに保存していたスクリプトもこちらへ移動しています。

 

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

# 使用するライブラリのインポート
from time import sleep
import smbus
import datetime
import subprocess
import re
import psutil

# 定数の定義
# LCDディスプレイのI2Cのアドレス
LCD_I2C_ADDR = 0x27
# LCDの1行目のアドレス
LCD_LINE_1 = 0x80
# LCDの2行目のアドレス
LCD_LINE_2 = 0xC0
# 1行あたりの文字数
LCD_WIDTH = 16
# バックライトのON/OFF(ON:0X08, OFF:0X00を指定)
LCD_BACKLIGHT = 0X08
LCD_CHR = 1
LCD_CMD = 0
ENABLE = 0b00000100
E_PULSE = 0.0005
E_DELAY = 0.0005

def init_lcd():
    """
    LCDの初期化処理
    """
    lcd_byte(0x33, LCD_CMD)
    lcd_byte(0x32, LCD_CMD)
    lcd_byte(0x06, LCD_CMD)
    lcd_byte(0x0C, LCD_CMD)
    lcd_byte(0x28, LCD_CMD)
    lcd_byte(0x01, LCD_CMD)
    sleep(E_DELAY)

def lcd_byte(bits, mode):
    """
    LCDへの書き込み2
    """
    bits_high = mode | (bits & 0xF0) | LCD_BACKLIGHT
    bits_low =  mode | ((bits << 4) & 0xF0) | LCD_BACKLIGHT
    bus.write_byte(LCD_I2C_ADDR, bits_high)
    lcd_toggle_enable(bits_high)
    bus.write_byte(LCD_I2C_ADDR, bits_low)
    lcd_toggle_enable(bits_low)

def lcd_toggle_enable(bits):
    """
    LCDへの書き込みサブ
    """
    sleep(E_DELAY)
    bus.write_byte(LCD_I2C_ADDR, (bits | ENABLE))
    sleep(E_PULSE)
    bus.write_byte(LCD_I2C_ADDR, (bits & ~ENABLE))
    sleep(E_DELAY)

def lcd_string(message: str, dispLine):
    """
    LCDへの文字の表示(半角文字列のみ)
    """
    # 表示対象行の初期化
    lcd_byte(dispLine, LCD_CMD)
    # 文字列を1行あたりの文字数に調整
    message = message.ljust(LCD_WIDTH)
    # 表示処理
    for dispIndex in range(LCD_WIDTH):
        lcd_byte(ord(message[dispIndex]), LCD_CHR)

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

# 使用するライブラリのインポート
import smbus
from time import sleep

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

def cpu_temp():
    """
    CPU温度の取得
    """
    f = open("/sys/class/thermal/thermal_zone0/temp","r")
    cpu_temp = 0
    for t in f:
        cpu_temp = t[:2]
        # 小数点以下2桁まで表示する場合
        # cpu_temp = t[:2]+"."+t[2:3]
    f.close()
    return cpu_temp

def cpu_temp_command():
    """
    CPU温度の取得(コマンドで取得)
    """
    proc = subprocess.run(["vcgencmd", "measure_temp"], stdout = subprocess.PIPE, stderr = subprocess.PIPE)
    cpu_temp = re.split("[=\']", proc.stdout.decode("utf8"))[1]
    return float(cpu_temp)

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

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)

# I2Cの取得
bus = smbus.SMBus(1)

try:
    # LCD初期化
    init_lcd()
    # 無限ループ、各情報を1秒間隔で表示
    dispMode = 0
    while True:
        if (dispMode == 0):
            sleep(1)
            # 「年月日と時分秒」を表示
            now = datetime.datetime.now()
            lcd_string(f'date: {now.strftime("%Y/%m/%d")}' , LCD_LINE_1)
            lcd_string(f'time: {now.strftime("%H:%M:%S")}', LCD_LINE_2)
            dispMode = 1
        elif (dispMode == 1):
            # 「温度と湿度」を表示
            templature , humidity = tempelature_humidity()
            lcd_string(f'temp: {str(templature)[:4]}\337C', LCD_LINE_1)
            lcd_string(f'hum : {str(humidity)[:4]}%', LCD_LINE_2)
            dispMode = 2
        elif (dispMode == 2):
            # 「CPU温度」「CPU使用率」と「メモリ情報」を表示
            mem_info = memory_info()
            lcd_string(f'cpu : {cpu_temp()}\337C {cpu_percent()}%', LCD_LINE_1)
            lcd_string(f'mem : {mem_info[0]}/{mem_info[1]}MB', LCD_LINE_2)
            dispMode = 0
finally:
    lcd_byte(0x01, LCD_CMD)

  

Raspberry Pi Python GUI アプリケーション 02:ウィンドウの設定

今回は、表示するウィンドウについて設定をいくつかしてみます。

設定するのは以下の内容です

・幅と高さ

・サイズ

・表示位置(今回は幅と高さ、ディスプレイの幅と高さから算出した中央に指定)

・タイトル(表示位置とサイズを表示) 

・最大化できなくする

 

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

# 使用するライブラリのインポート
import tkinter

# 継承した場合など、実行されないようにする(現状、意味は特にない)
if __name__ == "__main__":
    # 表示するウィンドウの幅と高さ(自由に指定できます)
    WINDOW_WIDTH = 500
    WINDOW_HEIGHT = 220

    # メインとなるウィンドウを作成
    main_window = tkinter.Tk()
    # ディスプレイの幅と高さを取得
    s_width = main_window.winfo_screenwidth()
    s_height = main_window.winfo_screenheight()
    # 表示する左上の座標を算出
    win_top = int((s_height - WINDOW_HEIGHT) / 2)
    win_left = int((s_width - WINDOW_WIDTH) / 2)
    # サイズと表示位置を指定した文字列
    geometry = "{0}x{1}+{2}+{3}".format(WINDOW_WIDTH, WINDOW_HEIGHT, win_left, win_top)
    # サイズと表示位置を中央に指定(幅 x 高さ + 左位置 + 上位置)
    main_window.geometry(geometry)
    # タイトル
    main_window.title("title : " + geometry)
    # 最大化を無効化
    main_window.resizable(0, 0)

    # 作成したウィンドウを表示
    main_window.mainloop()

 少し、プログラムらしくなってきました。

なお、このプログラムはPythonの環境が整っていれば、WindowsmacOSでも実行可能です(のはず)

Raspberry Pi Python GUI アプリケーション 01:ウィンドウの表示

Raspberry Pi OS がGUIで動いているなら、PythonGUIアプリケーションも作成できます。

今回はまず、ということで、ウィンドウを表示してみます。

タイトルと高さ、幅のみを指定したウィンドウを表示するだけです。

いろいろなライブラリがあるようですが、標準ライブラリである「tkinter」を使用していこうかと思います。

 

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

# 使用するライブラリのインポート
import tkinter

# メインとなるウィンドウを作成
main_window = tkinter.Tk()
# タイトル
main_window.title("タイトル : Hello World")
# サイズ(幅 x 高さ)
main_window.geometry("500x200")

# 作成したウィンドウを表示
main_window.mainloop()

 

面白くもないものですが、偉大なる1歩だと思います。 

Raspberry Pi OS の起動時に実行させたい

Raspberry Pi OS が起動した際に勝手に実行してほしい処理を設定する方法です。

最近入手したRaspberry Pi Zero にI2C接続でLCDディスプレイと湿度・温度計を接続して、現在年月日時間と室温と湿度、CPU温度をLCDディスプレイに表示するようにしてみようと思います。

 

LXTerminalで、以下のコマンドを実行、ファイルを更新します。

sudo vi /etc/rc.local

 

このファイルを変更すると、指定したコマンドがOS起動時にRoot権限で実行されます。

ということは、基本的には終了させることはできません(できないことはないが)

Pythonにてプログラムを書いているので、

python3 ***.py

と書きます。Pythonファイル(拡張子「.py」)のフルパスを指定します。

これで、起動すると(断線などがなければ)LCDディスプレイに情報が表示されます。
意外とCPU使用率にはまだ余裕があります(VNCにてリモート接続しても10%程度。メモリは30%程度しか使用していない)

 

Raspberry Pi Zeroは非常に安価ではありますが、LCDディスプレイと温度・湿度計、ほかのケーブルなどを加算するとそれなりの金額になります。

現状、コスパの非常に悪い時計、温度湿度計になっています。

もう少し機能を付加していこうかと思います。

Raspberry Pi に共有フォルダ

WindowsなどのPCから接続可能な、Raspberry Pi 上に作成した共有フォルダを作成する手順です。

Raspberry Pi はSDカードで動作するのですが、SDカードは突然使えなくなったりします。

できるだけ、日ごろのバックアップはしておいたほうがいいです。

 

 

1. Raspberry Pi に Sambaをインストール

  Raspberry Pi OSにSabmaをインストールします。以下のコマンドにて。(上の2つは最新に保ちましょうっていうおまじないです。定期的に実行しましょう。)

sudo apt-get -y update
sudo apt-get -y upgrade
sudo apt-get install samba

 

2. Sambaの設定ファイルを更新

  設定ファイルを更新します。

  なお、「vi」は何かと便利なので、使い方は覚えたほうが良いです。

sudo vi /etc/samba/smb.conf

 

  変更内容の例は、以下を参考に。初期状態であれば追記です。

  ゲストアカウントでのアクセスを許可(パスワードいらず)、書き込み可能な「/home/pi/Public」フォルダを共有して、ユーザー名は「pi」のような感じです。あまりセキュアではない設定例ですのでご注意を。

[Share]
comment = Raspberry Pi
path = /home/pi/Public
guest ok = yes
read only = no
browsable = yes
force user = pi

 

3. Sambaを再起動

  設定完了したらSambaを再起動します。

sudo systemctl restart smbd

 

4. 接続確認

  再起動できたら、Windowsマシンなどから覗いてみましょう。

  ユーザーを聞かれたら「pi」、パスワードは空でOKです。

  「Share」という共有フォルダでアクセスできます。