Prepared by Tshepho | Last Updated: May 27, 2025, 04:23 AM CAT
The Inventory Tracking System will automate tool and item management in a storeroom by:
tkinter
and csv
documentation.
import tkinter as tk from tkinter import ttk, scrolledtext, messagebox import serial import csv import threading import time from datetime import datetime from RPLCD.i2c import CharLCD class InventoryGUI: def __init__(self, root): self.root = root self.root.title("Inventory Tracking System") self.root.geometry("800x600") self.inventory_lock = threading.Lock() # Serial port for RP2040 (Raspberry Pi Pico) try: self.pico_serial = serial.Serial('/dev/ttyACM0', 9600, timeout=1) except serial.SerialException: self.pico_serial = None print("Warning: RP2040 serial port not found.") # Optional serial port for other devices try: self.serial_port = serial.Serial('/dev/ttyUSB0', 9600, timeout=1) # Adjust if needed except serial.SerialException: self.serial_port = None print("Warning: General serial port not found.") self.inventory = { "Hammer": 5, "Screwdriver": 10, "Wrench": 8, "Pliers": 3 } self.max_inventory = { "Hammer": 10, "Screwdriver": 20, "Wrench": 15, "Pliers": 5 } self.last_uid = "" self.status_message = "Waiting for scan..." self.verification_status = "Pending" self.last_action = "" self.failover_mode = False self.selected_tool = "Hammer" # Initialize LCD (I2C address 0x27, 16x2 display) try: self.lcd = CharLCD(i2c_expander='PCF8574', address=0x27, port=1, cols=16, rows=2) self.lcd.clear() self.lcd.write_string("Waiting for scan") except Exception as e: print(f"Warning: LCD initialization failed: {e}") self.lcd = None # Flag and entry for RDR-7081AKU keystrokes self.rdr_7081_input = "" self.uid_entry = tk.Entry(root) self.uid_entry.pack() self.uid_entry.bind("", self.process_rdr_7081_input) self.create_gui() # Start RFID scanning thread for RP2040 threading.Thread(target=self.handle_rfid_scans, daemon=True).start() # Start thread to process RDR-7081AKU input threading.Thread(target=self.handle_rdr_7081_input_thread, daemon=True).start() self.update_gui() def create_gui(self): status_frame = ttk.LabelFrame(self.root, text="System Status", padding=10) status_frame.pack(fill="x", padx=10, pady=5) self.status_label = ttk.Label(status_frame, text="Status: Waiting for scan...") self.status_label.pack(anchor="w") self.uid_label = ttk.Label(status_frame, text="Last Scanned UID: None") self.uid_label.pack(anchor="w") self.verify_label = ttk.Label(status_frame, text="Verification: Pending") self.verify_label.pack(anchor="w") self.action_label = ttk.Label(status_frame, text="Last Action: None") self.action_label.pack(anchor="w") borrower_frame = ttk.LabelFrame(self.root, text="Borrower Registration", padding=10) borrower_frame.pack(fill="x", padx=10, pady=5) ttk.Label(borrower_frame, text="Select Tool:").pack(anchor="w") self.tool_var = tk.StringVar(value="Hammer") tool_dropdown = ttk.Combobox(borrower_frame, textvariable=self.tool_var, values=list(self.inventory.keys())) tool_dropdown.pack(anchor="w") tool_dropdown.bind("< >", self.update_selected_tool) ttk.Label(borrower_frame, text="Borrower UID:").pack(anchor="w") self.borrower_entry = ttk.Entry(borrower_frame) self.borrower_entry.pack(anchor="w") ttk.Button(borrower_frame, text="Register Borrower", command=self.register_borrower).pack(anchor="w", pady=5) ttk.Button(borrower_frame, text="Scan with RP2040/RC522", command=self.trigger_rfid_scan).pack(anchor="w") inventory_frame = ttk.LabelFrame(self.root, text="Inventory", padding=10) inventory_frame.pack(fill="both", expand=True, padx=10, pady=5) self.inventory_tree = ttk.Treeview(inventory_frame, columns=("Tool", "Quantity", "Increase", "Decrease"), show="headings") self.inventory_tree.heading("Tool", text="Tool") self.inventory_tree.heading("Quantity", text="Quantity") self.inventory_tree.heading("Increase", text="Add") self.inventory_tree.heading("Decrease", text="Remove") self.inventory_tree.column("Tool", width=150) self.inventory_tree.column("Quantity", width=100) self.inventory_tree.column("Increase", width=50) self.inventory_tree.column("Decrease", width=50) self.inventory_tree.pack(fill="both", expand=True) self.update_inventory_display() self.inventory_tree.bind(" ", self.handle_tree_click) log_frame = ttk.LabelFrame(self.root, text="Transaction Logs", padding=10) log_frame.pack(fill="both", expand=True, padx=10, pady=5) self.log_text = scrolledtext.ScrolledText(log_frame, height=10, wrap=tk.WORD) self.log_text.pack(fill="both", expand=True) self.load_transaction_logs() def update_selected_tool(self, event): self.selected_tool = self.tool_var.get() def trigger_rfid_scan(self): if self.pico_serial: self.status_message = "Triggered RP2040/RC522 scan..." self.update_lcd(self.status_message) else: self.status_message = "RP2040 serial port not available." def handle_rfid_scans(self): while True: try: if self.pico_serial and self.pico_serial.in_waiting > 0: message = self.pico_serial.readline().decode().strip() if message.startswith("RFID:"): self.last_uid = message.split("RFID:")[1] self.status_message = f"Scanned via RP2040/RC522: {self.last_uid}" self.verification_status = "Pending" self.update_lcd(self.status_message) self.process_rfid_scan(self.last_uid) time.sleep(0.1) # Small delay to prevent CPU overload except Exception as e: self.status_message = f"RFID scan error: {e}" self.update_lcd(self.status_message) print(f"Error during RFID scan: {e}") time.sleep(1) def handle_rdr_7081_input_thread(self): while True: if self.rdr_7081_input and "\n" in self.rdr_7081_input: uid = self.rdr_7081_input.strip() if uid: self.last_uid = uid self.status_message = f"Scanned via RDR-7081AKU: {self.last_uid}" self.verification_status = "Pending" self.update_lcd(self.status_message) self.process_rfid_scan(self.last_uid) self.rdr_7081_input = "" time.sleep(0.1) def process_rdr_7081_input(self, event): """Process keystrokes from RDR-7081AKU card reader""" if event.char: # Only process printable characters self.rdr_7081_input += event.char def process_rfid_scan(self, uid): is_checked_out = self.check_transaction_status(uid, self.selected_tool) with self.inventory_lock: if is_checked_out: self.verification_status = "Valid UID (Return)" self.last_action = "CHECKIN" self.inventory[self.selected_tool] += 1 if self.inventory[self.selected_tool] > self.max_inventory[self.selected_tool]: self.inventory[self.selected_tool] = self.max_inventory[self.selected_tool] self.log_transaction(self.selected_tool, uid, "CHECKIN") else: try: with open("transactions.csv", "r") as file: uids = [row[1] for row in csv.reader(file) if len(row) >= 3 and row[2] == "CHECKOUT"] if uid in uids: self.verification_status = "Duplicate UID (Checkout)" self.last_action = "FAILED" else: self.verification_status = "Valid UID (Checkout)" self.last_action = "CHECKOUT" self.inventory[self.selected_tool] -= 1 if self.inventory[self.selected_tool] < 0: self.inventory[self.selected_tool] = 0 self.log_transaction(self.selected_tool, uid, "CHECKOUT") except FileNotFoundError: self.verification_status = "Valid UID (Checkout)" self.last_action = "CHECKOUT" self.inventory[self.selected_tool] -= 1 if self.inventory[self.selected_tool] < 0: self.inventory[self.selected_tool] = 0 self.log_transaction(self.selected_tool, uid, "CHECKOUT") self.update_inventory_display() def update_lcd(self, message): if self.lcd: # Check if LCD is initialized try: self.lcd.clear() self.lcd.write_string(message[:16]) # Truncate to 16 characters for 16x2 LCD except Exception as e: print(f"Error updating LCD: {e}") def register_borrower(self): borrower_uid = self.borrower_entry.get().strip() if not borrower_uid: messagebox.showerror("Error", "Please enter a Borrower UID.") return self.last_uid = borrower_uid is_checked_out = self.check_transaction_status(borrower_uid, self.selected_tool) with self.inventory_lock: if is_checked_out: self.verification_status = "Valid UID (Return)" self.last_action = "CHECKIN" self.inventory[self.selected_tool] += 1 if self.inventory[self.selected_tool] > self.max_inventory[self.selected_tool]: self.inventory[self.selected_tool] = self.max_inventory[self.selected_tool] messagebox.showwarning("Warning", f"Maximum quantity reached for {self.selected_tool}.") self.log_transaction(self.selected_tool, borrower_uid, "CHECKIN") else: try: with open("transactions.csv", "r") as file: uids = [row[1] for row in csv.reader(file) if len(row) >= 3 and row[2] == "CHECKOUT"] if borrower_uid in uids: self.verification_status = "Duplicate UID (Checkout)" self.last_action = "FAILED" messagebox.showerror("Error", "This UID is already checked out.") return except FileNotFoundError: pass self.verification_status = "Valid UID (Checkout)" self.last_action = "CHECKOUT" self.inventory[self.selected_tool] -= 1 if self.inventory[self.selected_tool] < 0: self.inventory[self.selected_tool] = 0 messagebox.showwarning("Warning", "Quantity cannot go below 0.") self.log_transaction(self.selected_tool, borrower_uid, "CHECKOUT") self.update_inventory_display() if self.pico_serial: self.pico_serial.write(f"TEXT:{self.last_action} {self.last_uid}\n".encode()) self.borrower_entry.delete(0, tk.END) def update_inventory_display(self): self.inventory_tree.delete(*self.inventory_tree.get_children()) for tool, quantity in self.inventory.items(): self.inventory_tree.insert("", "end", values=(tool, quantity, "+", "−")) def handle_tree_click(self, event): item = self.inventory_tree.selection() if not item: return item = item[0] column = self.inventory_tree.identify_column(event.x) tool = self.inventory_tree.item(item, "values")[0] with self.inventory_lock: if column == "#3": if self.inventory[tool] < self.max_inventory[tool]: self.inventory[tool] += 1 self.log_transaction(tool, "Manual", "MANUAL_ADD") else: messagebox.showwarning("Warning", f"Maximum quantity reached for {tool}.") elif column == "#4" and self.inventory[tool] > 0: self.inventory[tool] -= 1 self.log_transaction(tool, "Manual", "MANUAL_SUBTRACT") self.update_inventory_display() def load_transaction_logs(self): try: with open("transactions.csv", "r") as file: reader = csv.reader(file) logs = list(reader) self.log_text.delete(1.0, tk.END) if not logs or logs[0] != ["tool", "borrower", "action", "timestamp"]: self.log_text.insert(tk.END, "No transactions yet.\n") return logs = logs[1:] for log in logs[-10:]: if len(log) >= 4: self.log_text.insert(tk.END, f"{log[3]}: {log[0]} - {log[1]} - {log[2]}\n") except FileNotFoundError: with open("transactions.csv", "w", newline="") as file: writer = csv.writer(file) writer.writerow(["tool", "borrower", "action", "timestamp"]) self.log_text.delete(1.0, tk.END) self.log_text.insert(tk.END, "No transactions yet.\n") def check_transaction_status(self, borrower, tool): try: with open("transactions.csv", "r") as file: reader = csv.reader(file) transactions = list(reader)[1:] for transaction in reversed(transactions): if len(transaction) >= 3 and transaction[0] == tool and transaction[1] == borrower: return transaction[2] == "CHECKOUT" except FileNotFoundError: return False return False def handle_serial_communication(self): while True: try: if self.serial_port and self.serial_port.in_waiting > 0: message = self.serial_port.readline().decode().strip() if message.startswith("VERIFY:"): uid = message.split(":")[1] is_checked_out = self.check_transaction_status(uid, self.selected_tool) with self.inventory_lock: if is_checked_out: self.verification_status = "Valid UID (Return)" self.serial_port.write("CONFIRM:VALID\n".encode()) self.last_action = "CHECKIN" self.inventory[self.selected_tool] += 1 if self.inventory[self.selected_tool] > self.max_inventory[self.selected_tool]: self.inventory[self.selected_tool] = self.max_inventory[self.selected_tool] self.log_transaction(self.selected_tool, uid, "CHECKIN") else: try: with open("transactions.csv", "r") as file: rows = list(csv.reader(file))[1:] uids = [r[1] for r in rows if len(r) >= 3 and r[2] == "CHECKOUT"] if uid in uids: self.verification_status = "Duplicate UID (Checkout)" self.serial_port.write("CONFIRM:INVALID\n".encode()) self.last_action = "FAILED" else: self.verification_status = "Valid UID (Checkout)" self.serial_port.write("CONFIRM:VALID\n".encode()) self.last_action = "CHECKOUT" self.inventory[self.selected_tool] -= 1 if self.inventory[self.selected_tool] < 0: self.inventory[self.selected_tool] = 0 self.log_transaction(self.selected_tool, uid, "CHECKOUT") except FileNotFoundError: self.verification_status = "Valid UID (Checkout)" self.serial_port.write("CONFIRM:VALID\n".encode()) self.last_action = "CHECKOUT" self.inventory[self.selected_tool] -= 1 if self.inventory[self.selected_tool] < 0: self.inventory[self.selected_tool] = 0 self.log_transaction(self.selected_tool, uid, "CHECKOUT") except Exception as e: self.status_message = f"Serial error: {e}" self.update_lcd(self.status_message) time.sleep(1) def log_transaction(self, tool, borrower, action): timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S") with open("transactions.csv", "a", newline="") as file: writer = csv.writer(file) writer.writerow([tool, borrower, action, timestamp]) self.load_transaction_logs() def update_gui(self): self.status_label.config(text=f"Status: {self.status_message}") self.uid_label.config(text=f"Last Scanned UID: {self.last_uid}") self.verify_label.config(text=f"Verification: {self.verification_status}") self.action_label.config(text=f"Last Action: {self.last_action}") if self.pico_serial and self.last_uid: try: self.pico_serial.write(f"TEXT:{self.last_action} {self.last_uid}\n".encode()) except Exception as e: print(f"Error writing to Pico serial: {e}") self.root.after(1000, self.update_gui) def on_closing(self): if self.pico_serial: self.pico_serial.close() if self.serial_port: self.serial_port.close() if self.lcd: self.lcd.clear() self.root.destroy() if __name__ == "__main__": try: root = tk.Tk() app = InventoryGUI(root) root.protocol("WM_DELETE_WINDOW", app.on_closing) root.mainloop() except KeyboardInterrupt: print("\nKeyboardInterrupt received. Shutting down gracefully...") if 'app' in locals(): app.on_closing()
The RDR-7081AKU's reliability in scanning all 20 tags in a busy storeroom depends on several factors. The device is designed to read 125 kHz RFID tags with a typical range of a few centimeters, and the script processes input via the
process_rdr_7081_inputmethod, which handles keystroke events terminated by a newline. In a busy environment, factors like tag proximity, interference from metal objects, or overlapping signals could affect performance. The current implementation includes a thread
handle_rdr_7081_input_threadto continuously monitor input, but it lacks explicit anti-collision handling for multiple tags. Test results will indicate how it fares For 20 tags, testing under storeroom conditions (tag spacing, reader positioning) is recommended. If issues arise, consider adjusting the reader's sensitivity or integrating a more robust RFID reader with anti-collision support.
Zip Files for Downloads
Inventry Management Project files for DownloadUSB to C,
HDMI,
ethernet cable with ready line internet or WIFI where you can SSH the system can work.I have learnt skills in each unit being design, fabrication, electronics, programming, integration, dessemination, interllectual property and income generation.