From d9efaa4620fba99ede7981b3965409f218a840d9 Mon Sep 17 00:00:00 2001 From: rattatwinko Date: Sat, 13 Sep 2025 17:29:30 +0200 Subject: [PATCH] fixed shit. now threading works fine and it looks better --- PfandApplication/main.py | 2 +- PfandApplication/pfand_scanner.py | 328 ++++++++++++++++++------------ quantities.json | 10 - 3 files changed, 204 insertions(+), 136 deletions(-) delete mode 100644 quantities.json diff --git a/PfandApplication/main.py b/PfandApplication/main.py index e172569..0a42f4a 100644 --- a/PfandApplication/main.py +++ b/PfandApplication/main.py @@ -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!)" diff --git a/PfandApplication/pfand_scanner.py b/PfandApplication/pfand_scanner.py index ccde75b..9153c85 100644 --- a/PfandApplication/pfand_scanner.py +++ b/PfandApplication/pfand_scanner.py @@ -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( + "", + 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("", 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( - "", - 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('', lambda e: confirm()) self.product_win.bind('', 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() \ No newline at end of file diff --git a/quantities.json b/quantities.json deleted file mode 100644 index 54e6832..0000000 --- a/quantities.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "Flaschen": 7, - "Bierflasche": 2, - "Kasten": 2, - "Dose": 3, - "Plastikflasche": 2, - "Monster": 2, - "Joghurt Glas": 2, - "nigger": 1 -} \ No newline at end of file