Andrew's FabAcademy Journey

Inventory Tracking System for Storerooms

Prepared by Andrew | Last Updated: May 27, 2025, 04:23 AM CAT

This Fab Academy project automates tool and item management in storerooms, addressing inefficiencies and loss due to misplaced or untracked tools.

Problem

Tools in storerooms are often misplaced or untracked, leading to inefficiencies and loss. Manual tracking is error-prone and time-consuming.

Students_line
Slow_Queue
Loan_Register

Solution

The Inventory Tracking System automates tool management by:

This system enhances efficiency, reduces loss, and ensures reliability with a manual fallback if the RFID reader fails.

Prior Work

Fab Academy Skills

  • System Integration: Combined hardware (PCB, enclosure) and software (Python) for a standalone system.
  • Diagram

Hardware Design

  • Microcontroller: Raspberry Pi 3B+ for processing and GUI management.
  • Pi 3B
  • Input Device: RF Ideas RDR-7081AKU USB RFID reader and RFID-RC522 for redundancy.
  • RDR-7081AKU?
  • Output Device: Tkinter GUI on Raspberry Pi; optional 16x2 I2C LCD for redundancy.

Fabrication

  • Enclosure: 3D-printed 150x100x50 mm PLA enclosure Solidworks for Raspberry Pi and RDR-7081AKU.
  • All enclosed
  • PCB: I Milled PCB for power and I2C connections.
  • Custom PCB

Software Development

  • Tool and Borrower Registration: Python script on Raspberry Pi reads RFID UIDs, logs borrower IDs, and stores data in CSV (tool_id, borrower_id, timestamp, action).
  • Tracking Logic:
    • Check-out: Scan tool → Scan borrower ID or manual entry → Log “checked out”.
    • Display status on Tkinter GUI and optional LCD.
  • Error Handling: Validates UIDs, alerts via GUI for invalid scans.

Implementation Steps

  • Step 1: Set Up the Raspberry Pi 3 Environment
    • Install Windows Subsystem for Linux to in stall Ubuntu in order to run Pi Os and python applications/li> WSL
    • Run Ubuntu to work on raspberry installations
    • Ubuntu
    • Install Raspberry Pi 3 Operating System

    • Pi Os

    • Writing to SD CARD

    • Writing Os to sdcard

    Time: 06:50 AM CAT, May 27, 2025

    • Installed LXDE Remote Desktop to host the Raspberry Pi 3 desktop remotely, ensuring keyboard and mouse connectivity.
    • LXDE Remote Desktop
    • Update the system and install Python 3.13.3.
    • python-3133
    • Install required Python libraries (pyserial for serial communication, keyboard for USB HID input).
    • pyserial
    • Verify installations to confirm Python version and library presence.
    • Version 3.12
      Pyserial keyboard
    • Duration: ~15-20 minutes.
  • Step 2: Prepare the Hardware Connections

    Time: 07:10 AM CAT, May 27, 2025

    • Connect the RDR-7081AKU USB RFID reader to a USB port on the Raspberry Pi 3.
    • Connect the RP2040 to the Raspberry Pi 3 via USB (serial port).
    • Connection pins Rp2040 to RaspberryPi 3
    • Rp2040 to Pi
    • Wire the RFID-RC522 to the RP2040 with SPI pins.
    • RC522 to RP2040
    • Connect the 16x2 I2C LCD to the Raspberry Pi with I2C pins.
    • Pi to LCD Pin connections
    • Test the serial connection to confirm RP2040 detection.
    • Detecting RP2040
    • Test I2C connection
    • Duration: ~10-15 minutes.
  • Step 3: Set Up the CSV File and Initial Data

    Time: 07:25 AM CAT, May 27, 2025

    • Create a transactions.csv file in the working directory.
    • Add header and initial data (e.g., sample tool transactions).
    • Create an initial inventory dictionary with sample tools (e.g., Hammer: 5, Screwdriver: 10).
    • Transactions log
    • Duration: ~10 minutes.
  • Step 4: Write the Tkinter GUI Code

    Time: 07:35 AM CAT, May 27, 2025

    • Use Visual Studio Code to edit and test the Python Tkinter GUI code, confirming successful display of the GUI.
    • Testing Tkinter Code on Vscode
    • Paste the Python Tkinter GUI code into the Raspberry Pi terminal to create or run the inventory tracking script.
    • Inventory_gui.py
    • Duration: ~30-40 minutes.
  • Step 5: Program the RP2040

    Time: 08:15 AM CAT, May 27, 2025

    • Install Micropython on the Raspberry Pi 3 RP2040
    • RP2040 Com7
    • Use Thonny to program the RP2040 with sketches for controlling the RFID-RC522.
    • Main
                                      import tkinter as tk
                                      from tkinter import ttk, scrolledtext, messagebox, filedialog
                                      import serial
                                      import csv
                                      import threading
                                      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()
      
                                              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.")
      
                                              self.inventory = {
                                                  "Hammer": 5,
                                                  "Screwdriver": 10,
                                                  "Wrench": 8,
                                                  "Pliers": 3
                                              }
                                              self.max_inventory = {
                                                  "Hammer": 10,
                                                  "Screwdriver": 20,
                                                  "Wrench": 15,
                                                  "Pliers": 5
                                              }
                                              self.tool_tags = {
                                                  "75315881": "Hammer",
                                                  "50882845": "Screwdriver",
                                                  "6301682": "Wrench",
                                                  "31129697": "Pliers"
                                              }
      
                                              self.valid_users = {f"USER{i:03d}" for i in range(1, 8)}
      
                                              self.last_uid = ""
                                              self.status_message = "Waiting for scan..."
                                              self.verification_status = "Pending"
                                              self.last_action = ""
                                              self.selected_tool = None
                                              self.scan_mode = tk.BooleanVar(value=True)
                                              self.tool_scanned = False
                                              self.uid_confirmed = False
      
                                              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"Failed to initialize LCD: {e}")
                                                  self.lcd = None
      
                                              self.create_gui()
                                              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 Entered 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="Scan Tool Tag or Enter UID:").pack(anchor="w")
                                              self.uid_entry = tk.Entry(borrower_frame)
                                              self.uid_entry.pack(anchor="w")
                                              self.uid_entry.bind("", self.process_rdr_input)
                                              self.uid_entry.bind("", lambda event: self.confirm_uid())
      
                                              ttk.Button(borrower_frame, text="Confirm UID", command=self.confirm_uid).pack(anchor="w", pady=5)
                                              ttk.Checkbutton(borrower_frame, text="Manual Entry Mode", variable=self.scan_mode).pack(anchor="w", pady=5)
      
                                              if self.pico_serial:
                                                  ttk.Button(borrower_frame, text="Scan with RP2040/RC522", command=self.trigger_rfid_scan).pack(anchor="w", pady=5)
      
                                              ttk.Button(borrower_frame, text="Export Log", command=self.export_log).pack(anchor="w", pady=5)
                                              ttk.Button(borrower_frame, text="Reset", command=self.reset_system).pack(anchor="w", pady=5)
      
                                              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")
                                              for col in ("Tool", "Quantity", "Increase", "Decrease"):
                                                  self.inventory_tree.heading(col, text=col)
                                              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.inventory_tree.bind("", self.handle_tree_click)
                                              self.update_inventory_display()
      
                                              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):
                                              with self.inventory_lock:
                                                  self.selected_tool = self.tool_var.get()
                                                  self.tool_scanned = True
                                                  self.status_message = f"Tool selected: {self.selected_tool}. Enter UID."
                                                  self.verification_status = "Pending"
                                                  print(f"[DEBUG] Tool manually selected: {self.selected_tool}")
                                                  self.update_lcd(self.status_message)
                                                  self.uid_entry.focus_set()
                                                  self.update_gui()
      
                                          def process_rdr_input(self, event):
                                              if self.scan_mode.get():
                                                  input_text = self.uid_entry.get().strip().lstrip('0')
                                                  print(f"[DEBUG] RDR input detected: '{input_text}'")
                                                  if input_text and not self.tool_scanned and input_text in self.tool_tags:
                                                      print(f"[DEBUG] Tool tag recognized: {input_text}")
                                                      self.uid_entry.delete(0, tk.END)
                                                      with self.inventory_lock:
                                                          self.selected_tool = self.tool_tags[input_text]
                                                          self.tool_var.set(self.selected_tool)
                                                          self.tool_scanned = True
                                                          self.status_message = f"Tool '{self.selected_tool}' scanned. Enter UID."
                                                          self.verification_status = "Pending"
                                                          self.update_lcd(self.status_message)
                                                          self.uid_entry.focus_set()
                                                          self.update_gui()
      
                                          def trigger_rfid_scan(self):
                                              if self.pico_serial:
                                                  try:
                                                      self.pico_serial.write(b"SCAN\n")
                                                      message = self.pico_serial.readline().decode().strip()
                                                      print(f"[DEBUG] RP2040 message: {message}")
                                                      if message.startswith("RFID:"):
                                                          tag_id = message.split("RFID:")[1].lstrip('0')
                                                          if tag_id in self.tool_tags:
                                                              self.selected_tool = self.tool_tags[tag_id]
                                                              self.tool_var.set(self.selected_tool)
                                                              self.tool_scanned = True
                                                              self.status_message = f"Tool '{self.selected_tool}' scanned via RP2040. Enter UID."
                                                              self.verification_status = "Pending"
                                                          else:
                                                              self.status_message = f"Unknown tag: {tag_id}"
                                                              self.verification_status = "Failed"
                                                          self.update_lcd(self.status_message)
                                                          self.update_gui()
                                                  except Exception as e:
                                                      self.status_message = "RP2040 scan error."
                                                      print(f"[DEBUG] RP2040 error: {e}")
                                                      self.update_gui()
                                              else:
                                                  self.status_message = "RP2040 serial port not available."
                                                  self.update_gui()
      
                                          def confirm_uid(self, event=None):
                                              uid = self.uid_entry.get().strip().lstrip('0').upper()
                                              print(f"[DEBUG] Confirm UID input: {uid}")
                                              if not uid:
                                                  self.status_message = "Please enter a UID."
                                                  self.verification_status = "Pending"
                                                  self.update_gui()
                                                  return
      
                                              if uid.isdigit():
                                                  if uid in self.tool_tags:
                                                      print(f"[DEBUG] Tool tag recognized in Confirm UID: {uid}")
                                                      self.uid_entry.delete(0, tk.END)
                                                      with self.inventory_lock:
                                                          self.selected_tool = self.tool_tags[uid]
                                                          self.tool_var.set(self.selected_tool)
                                                          self.tool_scanned = True
                                                          self.status_message = f"Tool '{self.selected_tool}' scanned (Confirm UID). Enter UID."
                                                          self.verification_status = "Pending"
                                                          self.update_lcd(self.status_message)
                                                          self.uid_entry.focus_set()
                                                          self.update_gui()
                                                      return
                                                  else:
                                                      self.status_message = "Unrecognized tag. Please enter a valid UID."
                                                      self.verification_status = "Failed"
                                                      self.uid_entry.delete(0, tk.END)
                                                      self.update_gui()
                                                      return
      
                                              if uid in self.valid_users:
                                                  self.last_uid = uid
                                                  self.status_message = f"UID Confirmed: {uid}"
                                                  self.verification_status = "Valid"
                                                  print(f"[DEBUG] UID valid: {uid}")
                                                  self._process_uid(uid)
                                                  self.uid_confirmed = True
                                              else:
                                                  self.status_message = f"Invalid UID: {uid}"
                                                  self.verification_status = "Failed"
                                                  print(f"[DEBUG] UID invalid: {uid}")
                                                  self.update_lcd(self.status_message)
                                                  self.update_gui()
      
                                          def _process_uid(self, uid):
                                              if not self.selected_tool:
                                                  tool_from_dropdown = self.tool_var.get()
                                                  if tool_from_dropdown:
                                                      self.selected_tool = tool_from_dropdown
                                                      self.tool_scanned = True
                                                  else:
                                                      self.status_message = "No tool selected."
                                                      self.verification_status = "Failed"
                                                      self.update_gui()
                                                      return
                                              print(f"[DEBUG] Processing transaction for UID {uid} and tool {self.selected_tool}")
                                              self.process_rfid_scan(uid)
                                              self.uid_entry.delete(0, tk.END)
      
                                          def process_rfid_scan(self, uid):
                                              is_checked_out = self.check_transaction_status(uid, self.selected_tool)
                                              print(f"[DEBUG] Is checked out? {is_checked_out}")
                                              with self.inventory_lock:
                                                  if is_checked_out:
                                                      self.verification_status = "Valid (Return)"
                                                      self.last_action = "CHECKIN"
                                                      self.inventory[self.selected_tool] = min(
                                                          self.inventory[self.selected_tool] + 1,
                                                          self.max_inventory[self.selected_tool]
                                                      )
                                                      self.status_message = f"Checkin: {uid}"
                                                      self.log_transaction(self.selected_tool, uid, "CHECKIN")
                                                  else:
                                                      if self.inventory[self.selected_tool] == 0:
                                                          self.verification_status = "Failed"
                                                          self.last_action = "FAILED"
                                                          self.status_message = "No stock available"
                                                          print(f"[DEBUG] Checkout failed: No stock for {self.selected_tool}")
                                                      else:
                                                          self.verification_status = "Valid (Checkout)"
                                                          self.last_action = "CHECKOUT"
                                                          self.inventory[self.selected_tool] = max(
                                                              self.inventory[self.selected_tool] - 1,
                                                              0
                                                          )
                                                          self.status_message = f"Checkout: {uid}"
                                                          self.log_transaction(self.selected_tool, uid, "CHECKOUT")
                                              prin t(f"[DEBUG] Transaction: {self.last_action}")
                                              self.update_lcd(self.status_message)
                                              self.update_inventory_display()
                                              self.update_gui()
      
                                          def log_transaction(self, tool, uid, action):
                                              timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
                                              with open("transactions.csv", "a", newline="") as f:
                                                  csv.writer(f).writerow([tool, uid, action, timestamp])
                                              self.load_transaction_logs()
                                              print(f"[DEBUG] Transaction logged: {tool}, {uid}, {action}")
      
                                          def update_lcd(self, message):
                                              if self.lcd:
                                                  try:
                                                      self.lcd.clear()
                                                      line1 = message[:16]
                                                      line2 = message[16:32]
                                                      self.lcd.cursor_pos = (0, 0)
                                                      self.lcd.write_string(line1)
                                                      self.lcd.cursor_pos = (1, 0)
                                                      self.lcd.write_string(line2)
                                                  except Exception:
                                                      self.lcd = None
      
                                          def update_inventory_display(self):
                                              self.inventory_tree.delete(*self.inventory_tree.get_children())
                                              for tool, qty in self.inventory.items():
                                                  self.inventory_tree.insert("", "end", values=(tool, qty, "+", "-"))
      
                                          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" and self.inventory[tool] < self.max_inventory[tool]:
                                                      self.inventory[tool] += 1
                                                      self.log_transaction(tool, "Manual", "MANUAL_ADD")
                                                  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 f:
                                                      reader = csv.reader(f)
                                                      logs = list(reader)
                                                      self.log_text.delete(1.0, tk.END)
                                                      if len(logs) <= 1:
                                                          self.log_text.insert(tk.END, "No transactions yet.\n")
                                                      else:
                                                          for log in logs[-10:]:
                                                              self.log_text.insert(tk.END, f"{log[3]}: {log[0]} - {log[1]} - {log[2]}\n")
                                              except FileNotFoundError:
                                                  with open("transactions.csv", "w") as f:
                                                      csv.writer(f).writerow(["tool", "borrower", "action", "timestamp"])
                                                  self.log_text.insert(tk.END, "No transactions yet.\n")
      
                                          def check_transaction_status(self, uid, tool):
                                              try:
                                                  with open("transactions.csv", "r") as f:
                                                      reader = csv.reader(f)
                                                      for row in reversed(list(reader)[1:]):
                                                          if row[0] == tool and row[1] == uid:
                                                              return row[2] == "CHECKOUT"
                                              except FileNotFoundError:
                                                  return False
                                              return False
      
                                          def reset_system(self):
                                              with self.inventory_lock:
                                                  self.selected_tool = None
                                                  self.tool_scanned = False
                                                  self.uid_confirmed = False
                                                  self.last_uid = ""
                                                  self.status_message = "Waiting for scan..."
                                                  self.verification_status = "Pending"
                                                  self.last_action = ""
                                                  self.uid_entry.delete(0, tk.END)
                                                  self.update_lcd(self.status_message)
                                                  self.update_gui()
      
                                          def update_gui(self):
                                              self.status_label.config(text=f"Status: {self.status_message}")
                                              self.uid_label.config(text=f"Last Entered 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}")
                                              self.root.after(1000, self.update_gui)
      
                                          def export_log(self):
                                              file_path = filedialog.asksaveasfilename(
                                                  defaultextension=".csv",
                                                  filetypes=[("CSV files", "*.csv"), ("Text files", "*.txt")]
                                              )
                                              if file_path:
                                                  with open("transactions.csv", "r") as infile:
                                                      rows = list(csv.reader(infile))
                                                  with open(file_path, "w", newline="") as outfile:
                                                      writer = csv.writer(outfile)
                                                      writer.writerows(rows)
                                                  messagebox.showinfo("Export Successful", f"Log exported to {file_path}")
      
                                      if __name__ == "__main__":
                                          root = tk.Tk()
                                          app = InventoryGUI(root)
                                          root.mainloop()
                              
    • Verify the RP2040 initializes MFRC and writes ready to scan.
    • Ready to scan cards
  • Step 6: Test the Integration

    Time: 08:45 AM CAT, May 27, 2025

    • Run the Tkinter GUI script
    • Scan an RFID tag with the RDR-7081AKU and verify UID display and LCD outputIt works
    • Test RFID-RC522 scanning as a fallbackDisplays Triggers RP2040 on LCD but does not scan cards
    • Send a manual message via the GUI to test LCD displayIt refuses typing errors as reflected on the LCD and the gui
    • csv log
    • Check the transactions.csv file for updated entriesAvailable

    • Ready to scan cards
    • Test
  • Bill of Materials (BOM)

    • New Components:
      • Raspberry Pi, wires ($47.10).
      • RFID tags multicolor(20) ~$10.
      • PLA filament (~900g) ~$16.99.
      • MicroSD card (8 GB) ~$8.98.
      • LCD ~$12.99.
      • PCB (custom) ~$10.
      • Misc (headers, M3 bolts and nuts, M3 self tapping screws) ~$4.
      • RDR-7081AKU ($124.99)
    • Total New Cost: ~$235.05 (The cost is higher because of the RDR-7081AKU).

    Timeline (Completed)

      Timeline

    Deliverables

    • Hardware: Custom PCB with Raspberry Pi, RDR-7081AKU, RFID-RC522, and optional LCD.
    • Fabrication: 3D-printed PLA enclosure (150x130x90 mm)
    • Software: Python code for RFID scanning, Tkinter GUI, and CSV logging.
    • Documentation: HTML webpage with schematics, code, photos, and video.
    • Demo: Video showing tool check-out/check-in with borrower registration.

    Questions to be Answered

    • Will the RDR-7081AKU reliably scan all 20 tags in a busy storeroomLooking at the test it can manage as it doesnt miss a scan
    • Is the 150x130x92 mm enclosure sufficient for the Raspberry Pi and RDR-7081AKU? In terms of space its sufficient but I need to modify to allow cables to fot in for assembly
    • Timeline
    • Can the optional LCD enhance redundancy without complicating the design?
    • No It complemets the GUI by simultaneously displaying the messages as they get displayed in the GUI. The message on the LCD above simultaneously complents the message on the GUI

    • Timeline

    • Will the manual entry redundancy handle all edge cases?Yes, it refuses typing errors making it possible to handle all without mistakes. It also indicates no stock available when all tools are borrowed


    Evaluation

    • Functionality:
      • RDR-7081AKU scans and logs transactions correctly (tested with 4 tools, 20 borrowers).
      • Manual entry updates inventory and logs transactions.
      • GUI reflects real-time changes.
    • Design Quality:
      • Enclosure fits all components with accessible USB ports.
      • PCB routes power cleanly, tested with multimeter.
    • Performance:
      • RFID scan speed 2 seconds.
      • GUI updates within 2 seconds of actions.
    • Documentation:
      • Submitted schematics (KiCAD), code (Python), 3D/STL files, photos/videos.
      • Files for Download:

        Zip Files for Downloads

        Inventry Management Project files for Download

      • I have always wanted a system like this one and FabAcademy has really opened my eyes that a such system and many others can be self made when you have the right tools. I have leart that concepts such as machine learning machine leaning, digital fabrication can all be combined to eqip us to do alomst anything. I have learnt skills in each unit being design, fabrication, electronics, programming, integration, dessemination, interllectual property and income generation.