fixed shit. now threading works fine and it looks better

This commit is contained in:
2025-09-13 17:29:30 +02:00
parent cf55db126d
commit d9efaa4620
3 changed files with 204 additions and 136 deletions

View File

@@ -727,7 +727,7 @@ class PfandCalculator:
about_uscan,
text=(
"µScan - Der bessere Barcode Scanner\n"
"Version 2.2.2\n"
"Version 2.3.5\n"
"µScan erfordert einen UI Reload (Strg+R) in der Root Anwendung\n"
"µScan ist für mehrere Barcodes gemacht, die in einer kurzen Zeit gescannt werden sollten\n"
"Beachte das µScan eine Kamera benötigt die mindestens 30FPS aufnehmen kann (Process-FPS können eingestellt werden!)"

View File

@@ -43,23 +43,29 @@ class PfandScanner:
# Collapsible scan list state
self.scan_list_collapsed = tk.BooleanVar(value=False)
# Threading control
self.running = True
self.camera_thread = None
self.process_thread = None
self.init_gui()
self.init_camera()
self.queue = queue.Queue()
self.frame_queue = queue.Queue(maxsize=2) # Limit queue size to prevent memory issues
self.pfand_values = {
"EINWEG": 0.25,
"MEHRWEG": 0.15,
"DOSE": 0.25,
}
self.update_preview()
# Start threads
self.start_threads()
self.window.protocol("WM_DELETE_WINDOW", self.on_closing)
self.process_queue()
def setup_styles(self):
"""Setup custom styles for a modern look"""
self.style = ttk.Style()
self.style.theme_use('clam')
@@ -84,7 +90,6 @@ class PfandScanner:
self.style.configure('Camera.TFrame', relief='solid', borderwidth=2)
def load_data(self):
"""Load products from products.json and quantities from quantities.json"""
# Load products
if os.path.exists(self.products_file):
try:
@@ -132,10 +137,10 @@ class PfandScanner:
header_frame = ttk.Frame(self.main_frame)
header_frame.grid(row=0, column=0, columnspan=3, sticky="ew", pady=(0, 10))
title_label = ttk.Label(header_frame, text="µScan V2.3.0", style='Title.TLabel')
title_label = ttk.Label(header_frame, text="µScan", style='Title.TLabel')
title_label.pack(side="left")
status_label = ttk.Label(header_frame, text="Erweiterte Barcode-Scanner", style='Info.TLabel')
status_label = ttk.Label(header_frame, text="v2.3.5", style='Info.TLabel')
status_label.pack(side="right")
# Control Panel (Left)
@@ -417,52 +422,133 @@ class PfandScanner:
return frame
def update_preview(self):
try:
if not hasattr(self, 'cap') or not self.cap.isOpened():
self.window.after(100, self.update_preview)
return
ret, frame = self.cap.read()
if ret:
if not self.autofocus_var.get():
self.cap.set(cv2.CAP_PROP_FOCUS, self.focus_slider.get())
def start_threads(self):
"""Start camera and processing threads"""
self.camera_thread = threading.Thread(target=self.camera_worker, daemon=True)
self.camera_thread.start()
self.process_thread = threading.Thread(target=self.process_worker, daemon=True)
self.process_thread.start()
# Start UI update loop
self.update_preview()
self.process_queue()
current_time = time.time()
if current_time - self.last_process_time >= self.process_interval:
processed_frame = self.adjust_image(frame)
barcodes = decode(processed_frame) or decode(frame)
self.current_barcodes = barcodes
def camera_worker(self):
"""Worker thread for camera capture and processing"""
while self.running:
try:
if not hasattr(self, 'cap') or not self.cap.isOpened():
time.sleep(0.1)
continue
for barcode in barcodes:
barcode_data = barcode.data.decode('utf-8')
self.queue.put(barcode_data)
self.last_process_time = current_time
ret, frame = self.cap.read()
if ret:
if not self.autofocus_var.get():
self.cap.set(cv2.CAP_PROP_FOCUS, self.focus_slider.get())
# Draw barcode outlines
frame_with_outlines = self.draw_barcode_outline(frame.copy(), self.current_barcodes)
# Convert and display
cv2image = cv2.cvtColor(frame_with_outlines, cv2.COLOR_BGR2RGBA)
img = Image.fromarray(cv2image)
# Resize to fit label while maintaining aspect ratio
label_width = self.camera_label.winfo_width()
label_height = self.camera_label.winfo_height()
if label_width > 1 and label_height > 1:
img.thumbnail((label_width, label_height), Image.Resampling.LANCZOS)
imgtk = ImageTk.PhotoImage(image=img)
self.camera_label.imgtk = imgtk
self.camera_label.configure(image=imgtk)
# Update session time
self.update_statistics()
current_time = time.time()
if current_time - self.last_process_time >= self.process_interval:
processed_frame = self.adjust_image(frame)
barcodes = decode(processed_frame) or decode(frame)
self.current_barcodes = barcodes
for barcode in barcodes:
barcode_data = barcode.data.decode('utf-8')
self.queue.put(barcode_data)
self.last_process_time = current_time
# Draw barcode outlines
frame_with_outlines = self.draw_barcode_outline(frame.copy(), self.current_barcodes)
# Put frame in queue for UI thread
try:
if self.frame_queue.full():
self.frame_queue.get_nowait() # Discard old frame
self.frame_queue.put(frame_with_outlines, timeout=0.1)
except queue.Full:
pass # Skip this frame if queue is full
except Exception as e:
print(f"Fehler in der Kamera-Verarbeitung: {e}")
time.sleep(0.1)
def process_worker(self):
"""Worker thread for barcode processing"""
while self.running:
try:
barcode_data = self.queue.get(timeout=0.1)
now = datetime.now()
# Rate limiting
timestamps = self.barcode_times.get(barcode_data, [])
timestamps = [t for t in timestamps if now - t <= timedelta(seconds=5)]
if len(timestamps) >= 3:
continue
timestamps.append(now)
self.barcode_times[barcode_data] = timestamps
current_time = now.strftime("%H:%M:%S")
pfand_type = "EINWEG" if len(barcode_data) == 13 else "MEHRWEG" if len(barcode_data) == 8 else "DOSE"
deposit = self.pfand_values.get(pfand_type, 0.00)
# Update UI in main thread
self.window.after(0, self.add_to_treeview, current_time, barcode_data, pfand_type, deposit)
# Show product selection dialog
if barcode_data not in self.prompted_barcodes:
self.prompted_barcodes.add(barcode_data)
self.window.after(0, self.show_product_selection, barcode_data)
except queue.Empty:
pass
except Exception as e:
print(f"Fehler in der Warteschlangenverarbeitung: {e}")
def add_to_treeview(self, current_time, barcode_data, pfand_type, deposit):
"""Add item to treeview (called from main thread)"""
# Determine row color
row_count = len(self.tree.get_children())
tag = 'evenrow' if row_count % 2 == 0 else 'oddrow'
self.tree.insert("", 0, values=(current_time, barcode_data, pfand_type, f"{deposit:.2f}"), tags=(tag,))
# Update statistics
self.total_scans += 1
self.update_statistics()
# Sound notification
if self.beep_var.get():
self.window.bell()
def update_preview(self):
"""Update camera preview (called from main thread)"""
try:
# Get frame from queue
frame = self.frame_queue.get_nowait()
# Convert and display
cv2image = cv2.cvtColor(frame, cv2.COLOR_BGR2RGBA)
img = Image.fromarray(cv2image)
# Resize to fit label while maintaining aspect ratio
label_width = self.camera_label.winfo_width()
label_height = self.camera_label.winfo_height()
if label_width > 1 and label_height > 1:
img.thumbnail((label_width, label_height), Image.Resampling.LANCZOS)
imgtk = ImageTk.PhotoImage(image=img)
self.camera_label.imgtk = imgtk
self.camera_label.configure(image=imgtk)
except queue.Empty:
pass # No new frame available
except Exception as e:
print(f"Fehler in der Video-Vorschau: {e}")
self.window.after(10, self.update_preview)
# Schedule next update
if self.running:
self.window.after(30, self.update_preview)
def update_statistics(self):
session_time = datetime.now() - self.session_start
@@ -481,76 +567,96 @@ class PfandScanner:
self.product_win = tk.Toplevel(self.window)
self.product_win.title("Produkt auswählen")
self.product_win.geometry("500x400")
self.product_win.minsize(450, 350)
self.product_win.resizable(True, True)
# Center the window
self.product_win.transient(self.window)
self.product_win.grab_set()
# Configure grid weights
# Configure grid weights for proper expansion
self.product_win.columnconfigure(0, weight=1)
self.product_win.rowconfigure(1, weight=1)
# Header frame
header_frame = ttk.Frame(self.product_win, padding="20")
header_frame.grid(row=0, column=0, sticky="ew")
header_frame = ttk.Frame(self.product_win, padding="10")
header_frame.grid(row=0, column=0, sticky="ew", padx=10, pady=(10, 5))
header_frame.columnconfigure(0, weight=1)
ttk.Label(header_frame, text="Produkt für Barcode auswählen:", style='Heading.TLabel').pack(pady=(0, 5))
ttk.Label(header_frame, text=f"'{barcode_data}'", style='Info.TLabel', font=('Courier', 10)).pack(pady=(0, 10))
ttk.Label(header_frame, text="Produkt für Barcode auswählen:",
style='Heading.TLabel').grid(row=0, column=0, sticky="w")
ttk.Label(header_frame, text=f"'{barcode_data}'",
style='Info.TLabel', font=('Courier', 10)).grid(row=1, column=0, sticky="w", pady=(0, 5))
# Main content frame
content_frame = ttk.Frame(self.product_win, padding="20")
content_frame.grid(row=1, column=0, sticky="nsew")
# Main content frame with scrollable area
content_frame = ttk.Frame(self.product_win)
content_frame.grid(row=1, column=0, sticky="nsew", padx=10, pady=5)
content_frame.columnconfigure(0, weight=1)
content_frame.rowconfigure(0, weight=1)
selected_product = tk.StringVar()
# Create a canvas with scrollbar for the product list
# Create a canvas with scrollbar for the product list
canvas = tk.Canvas(content_frame, bg='white', highlightthickness=0)
scrollbar = ttk.Scrollbar(content_frame, orient="vertical", command=canvas.yview)
canvas.configure(yscrollcommand=scrollbar.set)
# Frame inside canvas
scrollable_frame = ttk.Frame(canvas)
window_id = canvas.create_window((0, 0), window=scrollable_frame, anchor="nw")
# Update scrollregion when frame contents change
scrollable_frame.bind(
"<Configure>",
lambda e: canvas.configure(scrollregion=canvas.bbox("all"))
)
# Resize inner frame width when canvas resizes
def on_canvas_configure(event):
canvas.itemconfig(window_id, width=event.width)
canvas.bind("<Configure>", on_canvas_configure)
# Place canvas + scrollbar
canvas.grid(row=0, column=0, sticky="nsew")
scrollbar.grid(row=0, column=1, sticky="ns")
# Add products to scrollable frame
if self.products:
# Create scrollable frame for products
canvas = tk.Canvas(content_frame, bg='white')
scrollbar = ttk.Scrollbar(content_frame, orient="vertical", command=canvas.yview)
scrollable_frame = ttk.Frame(canvas)
scrollable_frame.bind(
"<Configure>",
lambda e: canvas.configure(scrollregion=canvas.bbox("all"))
)
canvas.create_window((0, 0), window=scrollable_frame, anchor="nw")
canvas.configure(yscrollcommand=scrollbar.set)
for i, product in enumerate(self.products):
current_quantity = self.quantities.get(product, 0)
price = self.prices.get(product, 0.00)
product_frame = ttk.Frame(scrollable_frame, padding="5")
product_frame.pack(fill="x", pady=2)
product_frame.columnconfigure(1, weight=1)
ttk.Radiobutton(
product_frame,
text=f"{product}",
text=product,
variable=selected_product,
value=product
).pack(side="left")
).grid(row=0, column=0, sticky="w")
info_text = f"(Aktuell: {current_quantity}, Preis: €{price:.2f})"
ttk.Label(product_frame, text=info_text, style='Info.TLabel').pack(side="right")
canvas.grid(row=0, column=0, sticky="nsew")
scrollbar.grid(row=0, column=1, sticky="ns")
info_text = f"Aktuell: {current_quantity}, Preis: €{price:.2f}"
ttk.Label(product_frame, text=info_text, style='Info.TLabel').grid(row=0, column=1, sticky="e", padx=(10, 0))
else:
ttk.Label(content_frame, text="Noch keine Produkte definiert.", style='Info.TLabel').pack(pady=20)
no_products_label = ttk.Label(scrollable_frame, text="Noch keine Produkte definiert.",
style='Info.TLabel', justify="center")
no_products_label.pack(pady=20)
# Button frame
button_frame = ttk.Frame(self.product_win, padding="20")
button_frame.grid(row=2, column=0, sticky="ew")
# Configure button frame columns
# Button frame at the bottom
button_frame = ttk.Frame(self.product_win, padding="10")
button_frame.grid(row=2, column=0, sticky="ew", padx=10, pady=(5, 10))
# Configure button frame columns for proper spacing
button_frame.columnconfigure(0, weight=1)
button_frame.columnconfigure(1, weight=0)
button_frame.columnconfigure(2, weight=0)
button_frame.columnconfigure(3, weight=0)
# Helper functions for buttons
def confirm():
product = selected_product.get()
if product:
@@ -573,7 +679,6 @@ class PfandScanner:
def cancel():
self.product_win.destroy()
# Add new product button
def add_new_product():
new_product = simpledialog.askstring("Neues Produkt", "Name des neuen Produkts:")
if new_product and new_product.strip():
@@ -603,66 +708,39 @@ class PfandScanner:
else:
messagebox.showwarning("Produkt existiert", "Dieses Produkt ist bereits vorhanden.")
# Buttons with better layout
# Buttons with proper layout
ttk.Button(button_frame, text="Neues Produkt", command=add_new_product).grid(row=0, column=0, sticky="w", padx=5)
ttk.Button(button_frame, text="Abbrechen", command=cancel).grid(row=0, column=1, padx=5)
ttk.Button(button_frame, text="Bestätigen", command=confirm).grid(row=0, column=2, padx=5)
ttk.Button(button_frame, text="Abbrechen", command=cancel).grid(row=0, column=2, padx=5)
ttk.Button(button_frame, text="Bestätigen", command=confirm).grid(row=0, column=3, padx=5)
# Bind Enter key to confirm
self.product_win.bind('<Return>', lambda e: confirm())
self.product_win.bind('<Escape>', lambda e: cancel())
# Set focus to the window
self.product_win.focus_set()
def process_queue(self):
try:
barcode_data = self.queue.get(timeout=0.1)
now = datetime.now()
# Rate limiting
timestamps = self.barcode_times.get(barcode_data, [])
timestamps = [t for t in timestamps if now - t <= timedelta(seconds=5)]
if len(timestamps) >= 3:
return
timestamps.append(now)
self.barcode_times[barcode_data] = timestamps
current_time = now.strftime("%H:%M:%S")
pfand_type = "EINWEG" if len(barcode_data) == 13 else "MEHRWEG" if len(barcode_data) == 8 else "DOSE"
deposit = self.pfand_values.get(pfand_type, 0.00)
# Determine row color
row_count = len(self.tree.get_children())
tag = 'evenrow' if row_count % 2 == 0 else 'oddrow'
self.tree.insert("", 0, values=(current_time, barcode_data, pfand_type, f"{deposit:.2f}"), tags=(tag,))
# Update statistics
self.total_scans += 1
# Note: Total value is updated in product selection dialog
# Sound notification
if self.beep_var.get():
self.window.bell()
# Show product selection dialog
if barcode_data not in self.prompted_barcodes:
self.prompted_barcodes.add(barcode_data)
self.window.after(0, self.show_product_selection, barcode_data)
except queue.Empty:
# This method is now handled by the process_worker thread
pass
except Exception as e:
print(f"Fehler in der Warteschlangenverarbeitung: {e}")
finally:
# Schedule next check
if self.running:
self.window.after(100, self.process_queue)
def on_closing(self):
"""Clean shutdown of the application"""
self.running = False
# Wait for camera thread to finish
if self.camera_thread and self.camera_thread.is_alive():
self.camera_thread.join(timeout=1.0)
if self.process_thread and self.process_thread.is_alive():
self.process_thread.join(timeout=1.0)
try:
if hasattr(self, 'cap') and self.cap and self.cap.isOpened():
self.cap.release()
@@ -674,9 +752,9 @@ class PfandScanner:
if __name__ != "__main__":
def launch_pfand_scanner():
scanner_window = tk.Toplevel()
PfandScanner(scanner_window, "µScan V2.3.0 - Verbesserte Deutsche Version")
PfandScanner(scanner_window, "µScan V2.3.5")
else:
# For standalone testing
root = tk.Tk()
app = PfandScanner(root, "µScan V2.3.0 - Verbesserte Deutsche Version")
app = PfandScanner(root, "µScan V2.3.5")
root.mainloop()

View File

@@ -1,10 +0,0 @@
{
"Flaschen": 7,
"Bierflasche": 2,
"Kasten": 2,
"Dose": 3,
"Plastikflasche": 2,
"Monster": 2,
"Joghurt Glas": 2,
"nigger": 1
}