PythonのTkinterとSuica(NFC)で出退勤(その2-管理画面)

2021年3月22日

こんにちは

前回の続きです。

管理画面を作成します。

FeliCa(nfc)の登録・一覧表示と出退勤履歴の一覧表示をする画面です。

その前に、WindowsでNFCを扱う場合、「libusb」のインストール・設定が必要となります。

事前にご確認ください。(Raspberry PiのRasbianOSではインストール無しで動作しました)

「libusb」のインストール・設定がおわりましたら、以下Pythonライブラリをインストール。

pip install nfcpy
pip install tkcalendar

nfcpyはPythonでnfcを扱うのにつかうライブラリ、

tkcalendarについてはこちらをご参照ください。

大まかに管理画面を実装しました(細かい部分は別の記事にて対応します)

import tkinter
import nfc
from tkinter import messagebox
import sqlite3

from nfc.clf import Error
from tkinter import ttk
from tkcalendar import Calendar, DateEntry

class CrudNfc(tkinter.ttk.Frame):

    def __init__(self,master):
        super().__init__(master)
        self.pack()
        self.master.title("出退勤マスタ")
        self.master.geometry("800x700")

        # 適当に色を付けてます
        self.canvas = tkinter.Canvas(master, width=800, height=700, bg="pale green")
        # 登録と検索を分ける線    パラメーター:xの始点, yの始点, xの終点, yの終点
        self.canvas.create_line(20, 130, 760, 130, fill='green4', width = 3)
        self.canvas.place(x=0, y=0)

        # 登録ラベル
        self.register_label = tkinter.Label(master, text='nfc登録', foreground='black', background='deep sky blue', font=("Times New Roman", "10", "bold"))
        self.register_label.place(x=30, y=15, width=150, height=40)
        # 登録名前ラベル
        self.register_name_label = tkinter.Label(master, text='名前', foreground='linen', background='lightslategray', font=("Times New Roman", "10", "bold"))
        self.register_name_label.place(x=80, y=70, width=80, height=40)
        # 登録名前エントリ
        self.register_name_entry = tkinter.Entry(master)
        self.register_name_entry.place(x=200, y=70, width=350, height=40)
        # 登録ボタン
        self.attendance_button = tkinter.Button(master, text="登録", bg="coral", font=("Times New Roman", 20), command=self.click_register)
        self.attendance_button.place(x=600, y=70, width=150, height=40)
        # 検索ラベル
        self.search_label = tkinter.Label(master, text='出退勤検索', foreground='black', background='deep sky blue', font=("Times New Roman", "10", "bold"))
        self.search_label.place(x=30, y=160, width=150, height=40)
        # 検索名前ラベル
        self.search_name_label = tkinter.Label(master, text='名前', foreground='linen', background='lightslategray', font=("Times New Roman", "10", "bold"))
        self.search_name_label.place(x=80, y=210, width=80, height=40)
        # 検索名前エントリ
        self.search_name_entry = tkinter.Entry(master)
        self.search_name_entry.place(x=200, y=210, width=350, height=40)
        # 検索日付ラベル
        self.search_name_label = tkinter.Label(master, text='日付', foreground='linen', background='lightslategray', font=("Times New Roman", "10", "bold"))
        self.search_name_label.place(x=80, y=270, width=80, height=40)
        # 検索カレンダーのスタイル
        style = ttk.Style()
        style.theme_use('clam')
        style.configure('my.DateEntry',
                        fieldbackground='light green',
                        background='dark green',
                        foreground='dark blue',
                        arrowcolor='white')
        # 検索カレンダー(from)
        self.from_date = DateEntry(style='my.DateEntry')
        self.from_date.place(x=200, y=270, width=100, height=30)
        #「~」ラベル
        self.search_between_label = tkinter.Label(master, text='~', foreground='black', background='pale green', font=("Times New Roman", "10", "bold"))
        self.search_between_label.place(x=330, y=270, width=30, height=40)
        # 検索カレンダー(to)
        self.to_date = DateEntry(style='my.DateEntry')
        self.to_date.place(x=400, y=270, width=100, height=30)
        # 検索ボタン
        self.leaveWork_button = tkinter.Button(master, text="出退勤検索", bg="turquoise2", font=("Times New Roman", 20), command=self.click_select_list)
        self.leaveWork_button.place(x=600, y=230, width=150, height=40)

        self.dbname = 'database.db'
        self.conn = sqlite3.connect(self.dbname, isolation_level = None)
        self.nfctree = ttk.Treeview(master)

        # 列を作成(3列)
        self.nfctree["columns"] = (1,2)
        # # ヘッダーの設定
        self.nfctree["show"] = "headings"
        self.nfctree.heading(1,text="id")
        self.nfctree.heading(2,text="氏名")

        # # 各列の幅設定
        self.nfctree.column(1,width=10)
        self.nfctree.column(2,width=150)
        try:
            sql = "select id, name from nfc"
            for row in self.conn.execute(sql):
                self.nfctree.insert("", "end", values=(row))
        finally:
            self.conn.close

        # 検索結果ツリー
        self.nfctree.place(x=600, y=380)
        # 検索結果ラベル
        self.search_nfc_label = tkinter.Label(self.master, text='登録nfc一覧', foreground='linen', background='lightslategray', font=("Times New Roman", "10", "bold"))
        self.search_nfc_label.place(x=600, y=340, width=100, height=30)

        # 外部キー制約の使用を有効にする
        self.conn.execute("PRAGMA foreign_keys = 1")
        self.click_select_list()

    # 登録
    def click_register(self):

        if self.register_name_entry.get() != '':
            clf = nfc.ContactlessFrontend('usb')
            try:
                tag = clf.connect(rdwr={'on-connect': lambda tag: False})
                idm = tag.idm.hex()

                self.conn.execute("INSERT INTO nfc(idm, name) VALUES (?, ?)", (idm, self.register_name_entry.get()))
                self.conn.commit
                messagebox.showinfo('登録', '登録しました!')
            except sqlite3.IntegrityError:
                messagebox.showerror('登録できません!', '同じNFCが登録されています!')
            finally:
                self.conn.close
                clf.close()
        else:    
            messagebox.showerror('登録できません!', '名前を入れてください')

    # 一覧表示
    def click_select_list(self):

        self.tree = ttk.Treeview(self.master)
        # 列を作成(3列)
        self.tree["columns"] = (1,2,3,4)
        # # ヘッダーの設定
        self.tree["show"] = "headings"
        self.tree.heading(1,text="id")
        self.tree.heading(2,text="氏名")
        self.tree.heading(3,text="出勤時刻")
        self.tree.heading(4,text="退勤時刻")

        # # 各列の幅設定
        self.tree.column(1,width=10)
        self.tree.column(2,width=150)
        self.tree.column(3,width=150)
        self.tree.column(4,width=150)
        try:
            if self.search_name_entry.get() != '':
                for row in self.conn.execute("select timeCard.id, nfc.name, timeCard.attendanceTime, timeCard.leaveWorkTime from timeCard INNER JOIN nfc ON timeCard.idm = nfc.idm WHERE nfc.name = ? AND timeCard.attendanceTime BETWEEN ? AND ?", (self.search_name_entry.get(), self.from_date.get_date(), self.to_date.get_date())):
                    self.tree.insert("", "end", values=(row))
            else:
                for row in self.conn.execute("select timeCard.id, nfc.name, timeCard.attendanceTime, timeCard.leaveWorkTime from timeCard INNER JOIN nfc ON timeCard.idm = nfc.idm WHERE timeCard.attendanceTime BETWEEN ? AND ?", (self.from_date.get_date(), self.to_date.get_date())):
                    self.tree.insert("", "end", values=(row))
        finally:
            self.conn.close

        # 検索結果ツリー
        self.tree.place(x=80, y=380)
        # 検索結果ラベル        
        self.search_result_label = tkinter.Label(self.master, text='検索結果', foreground='linen', background='lightslategray', font=("Times New Roman", "10", "bold"))
        self.search_result_label.place(x=80, y=340, width=100, height=30)    

def main():
    root = tkinter.Tk()
    root = CrudNfc(master=root)
    root.mainloop()

if __name__ == "__main__":
    main()

これをWindowsの場合は管理者権限のPowerShellで起動してください。

Raspberry Piの場合はsudoで呼び出しましょう。

VisualStudio codeで起動すると、なぜかnfc notfoundが発生する…(原因調査中)

これが表示されます。(デザインが雑…ご容赦くださいまし…)

■nfcの登録方法

1.登録したいカードをPaSoRiの上に置く

2.登録の名前入力欄に名前を入力

3.登録ボタンを押して2秒くらい待つ

4.「登録しました」のメッセージボックスが表示されれば登録完了

5.この画面を閉じ、再度起動すると、下部の「登録nfc一覧」欄に登録した名前が表示されます。

同じnfcを複数回登録しようとするとエラーが発火するようになってます

■出退勤履歴の検索

「出退勤検索」を押すと出退勤履歴の一覧が表示されますが、まだ打刻の画面を作成してませんね

次回は打刻の画面をつくります。