[Python]tkinter バーコードリーダーアプリ

### 目的
Pythonでバーコードリーダーアプリを作りたいと思った。
GUIとして画面が欲しいので、tkinterでバーコード読み取り結果を画面表示するアプリを考えた。

### 環境
・Windows11
・WSL2(Ubuntu-22.04)
・python 3.10.12
・usbipd
・バーコードリーダー(Tera HW0002) 
　公式サイト : https://tera-digital.com/products/2d-barcode-scanner-hw0002-o
　Amazon : https://www.amazon.co.jp/dp/B07T9J9Q99

### ポイント
・バーコードリーダー
　仮想COM設定にする。
　設定方法は公式サイトのマニュアルを参照。
・BarcodeScannerクラス
　インスタンス生成時に受信スレッドが立つ。
　データ受信時にRecieveDataHandlerを発火させる。
　RecieveDataHandlerはイベントを実行する。
・Windowクラス
　変数はtk.StringVar()で宣言する。
　宣言した変数をWidgets(Label等)に紐づける。
　変数へのアクセスはset(), get()メソッドを使う。
　変数の値を更新する関数を用意し、データ受信時にcallbackしてもらうようにイベントに追加する。

### 課題
・Eventクラスは改良の余地あり

### ソースコード
```main.py
#!/usr/bin/env python3
import serial
import tkinter as tk
import BarcodeScanner as bs

class Window:
    def __init__(self) -> None:
        root = tk.Tk()
        root.wm_title("BarcodeScanner")

        self.frame = tk.Frame(root)
        self.frame.pack(padx=150, pady=0)

        self.lName = tk.Label(text="Scan Data:")
        self.lName.pack(side="left", padx=0, pady=20)

        self.lScanData = tk.Label()
        self.lScanData.pack(side="left", padx=0, pady=20)

        self.vScanData = tk.StringVar()
        self.vScanData.set("Wait for Read")

        self.lScanData["textvariable"] = self.vScanData

    def DisplayScanData(self, data):
        self.vScanData.set(data)
        print(f"input:{self.vScanData.get()}")
        

if __name__ == "__main__":

    path = "/dev/ttyUSB0" # change value by environment
    bcs = bs.BarcodeScanner(path,
            9600,
            serial.EIGHTBITS,
            serial.PARITY_EVEN,
            serial.STOPBITS_ONE,
            3)
    
    myapp = Window()

    bcs.rEvent.AddEvent(myapp.DisplayScanData)

    myapp.frame.mainloop()
```

```BarcodeScanner.py
import serial
import threading
import Event as ev

class BarcodeScanner:

    def __init__(self, path, br, bs, pr, sb, to):
        self.rBuffer = self.Buffer()
        self.rEvent = ev.Event()
        self.Open(path, br, bs, pr, sb, to)

    def Open(self, path, br, bs, pr, sb, to):
        self.ser = serial.Serial(path)
        self.ser.baudrate = br
        self.ser.bytesize = bs
        self.ser.parity = pr
        self.ser.stopbits = sb
        self.ser.timeout = to # sec
        self.th_rd = threading.Thread(target=self.RecieveData, daemon=True)
        self.th_rd.start()
    
    def Close(self):
        self.ser.close()
    
    def SendData(self, sd):
        self.ser.write(sd)

    def RecieveData(self):
        while True:
            if self.ser.in_waiting > 0:
                rd = self.ser.read(self.ser.in_waiting)
                self.RecieveDataHandler(rd)

    def RecieveDataHandler(self, indata:bytes):
        self.rBuffer.In(indata)
        buf = self.rBuffer.Read()
        if b'\x0D\x0A' in buf: # CRLF
            self.rEvent.ExecuteEvents(self.rBuffer.Out(endbyte=b'\x0D\x0A'))
        elif b'\x0D' in buf: # CR
            self.rEvent.ExecuteEvents(self.rBuffer.Out(endbyte=b'\x0D'))
        elif b'\x0A' in buf: # LF
            self.rEvent.ExecuteEvents(self.rBuffer.Out(endbyte=b'\x0A'))

    class Buffer:

        def __init__(self) -> None:
            self.buffer = None

        def In(self, data:bytes):
            if self.buffer is None:
                self.buffer = data
            else:
                self.buffer += data

        def Out(self, endbyte) -> bytes:
            if self.buffer is None:
                return None
            else:
                data, endbyte, over = self.buffer.partition(endbyte)
                self.Clear()
                return data
        
        def Read(self) -> bytes:
            return self.buffer

        def Clear(self):
            self.buffer = None
```

```Event.py
class Event:

    def __init__(self):
        self.events = []
    
    def ExecuteEvents(self, data):
        if len(self.events) > 0:
            for e in self.events:
                e(data)

    def AddEvent(self, func):
        self.events.append(func)
```

### 参考
・tkinter --- Tcl/Tk の Python インターフェース — Python 3.12.5 ドキュメント : https://docs.python.org/ja/3/library/tkinter.html