from PyQt5.QtWidgets import QApplication, QWidget, QVBoxLayout, QHBoxLayout, QLabel, QSizePolicy, QPushButton from PyQt5.QtCore import Qt import glob import requests import serial import re import time import math from UpdateWorker import UpdateWorker class BlockStackApp(QWidget): def __init__(self, fullscreen): super().__init__() self.initUI(fullscreen) self.check_usb_devices() self.update_count = 11 def initUI(self, fullscreen): self.layout = QVBoxLayout() self.layout.setSpacing(5) self.layout.setContentsMargins(5, 5, 5, 5) self.layout.setAlignment(Qt.AlignLeft | Qt.AlignTop) self.setLayout(self.layout) self.setWindowTitle('462filament Bench GUI') self.setStyleSheet(""" QWidget { background-color: #2E2E2E; color: #FFFFFF; height: 100px; } QLabel { background-color: #4A4A4A; border: 1px solid #5A5A5A; padding: 2px; margin: 2px; height: 30px; width: 250px; } QPushButton, QComboBox { background-color: #5A5A5A; border: 1px solid #6A6A6A; padding: 2px; margin: 2px; height: 25px; width: 100px; } """) if fullscreen: self.showFullScreen() else: self.showMaximized() def check_usb_devices(self): bench_list = [] for port in glob.glob('/dev/ttyUSB*'): try: with serial.Serial(port, 1000000, timeout=1) as ser: response = ser.read_all().decode('utf-8').strip() ser.write(b'i') time.sleep(1) response = ser.read_all().decode('utf-8').strip() serial_number = self.extract_serial_number(response) if serial_number: print(f"Found bench on port {port}") bench_id = self.get_bench_id(serial_number) if bench_id: bench_list.append((port, bench_id, serial_number)) else: print(f"Second check port {port}") response = ser.read_all().decode('utf-8').strip() ser.write(b'i') time.sleep(1) response = ser.read_all().decode('utf-8').strip() serial_number = self.extract_serial_number(response) if serial_number: print(f"Found bench on port {port}") bench_id = self.get_bench_id(serial_number) if bench_id: bench_list.append((port, bench_id, serial_number)) else: print(f"Ignoring port {port}") except serial.SerialException as e: print(f"Could not open port {port}: {e}") bench_list.sort(key=lambda x: x[1]) self.blocks = {} for port, bench_id, serial_number in bench_list: block_id = f"{bench_id}" self.worker = UpdateWorker(serial_number, bench_id, port, block_id, self) self.layout.addWidget(self.worker.block) self.worker.change_color_signal.connect(self.change_button_color) self.worker.update_signal.connect(self.update_block) self.worker.start() self.blocks[block_id] = self.worker.block def extract_serial_number(self, response): match = re.match(r"id:([0-9A-Fa-f]+)", response) if match: return match.group(1) return None def get_bench_id(self, serial_number): url = f"http://462filament/api.php?data=benchid&serial={serial_number}" try: response = requests.get(url) if response.status_code == 200: data = response.json() return data.get("id") except requests.RequestException as e: print(f"Error making request to API: {e}") return None def change_button_color(self, block_id, button_name, color): block = self.blocks.get(block_id) if block: button = getattr(block, button_name, None) if button: button.setStyleSheet(f"background-color: {color};") def update_mode(self, mode): # Update button colors based on mode if mode == 1: self.change_button_color(self.sender().block.btn_go_left, "#0000FF") self.change_button_color(self.sender().block.btn_go_right, "#5A5A5A") self.change_button_color(self.sender().block.btn_reset, "#5A5A5A") elif mode == 2: self.change_button_color(self.sender().block.btn_go_left, "#5A5A5A") self.change_button_color(self.sender().block.btn_go_right, "#0000FF") self.change_button_color(self.sender().block.btn_reset, "#5A5A5A") elif mode == 65535: self.change_button_color(self.sender().block.btn_go_left, "#5A5A5A") self.change_button_color(self.sender().block.btn_go_right, "#5A5A5A") self.change_button_color(self.sender().block.btn_reset, "#5A5A5A") else: self.change_button_color(self.sender().block.btn_go_left, "#5A5A5A") self.change_button_color(self.sender().block.btn_go_right, "#5A5A5A") self.change_button_color(self.sender().block.btn_reset, "#0000FF") def handle_data_received(self, data): # Handle the data received from the serial port print(f"Data received: {data}") def update_block(self, is_running, diameter_value, position_value): # Update the block with diameter_value and position_value worker = self.sender() diameter_avg = 2.0000 est_wgh = 0 worker.block.dia_label.setText(f"{diameter_value:.3f} ") worker.block.len_label.setText(f"{position_value/1000:.3f}") if is_running: if worker.reset_data: worker.diameter_total = diameter_value worker.diameter_count = 1 worker.diameter_min = diameter_value worker.diameter_max = diameter_value worker.clear_graph() worker.data_list.clear() worker.reset_data = False else: worker.diameter_total += diameter_value # to calc average diameter worker.diameter_count += 1 if diameter_value < worker.diameter_min: # process min/max worker.diameter_min = diameter_value if diameter_value > worker.diameter_max: worker.diameter_max = diameter_value worker.block.min_label.setText(f"{worker.diameter_min:.3f} ") worker.block.max_label.setText(f"{worker.diameter_max:.3f} ") # push data to memory for report worker.data_list.append((position_value, diameter_value)) # push data to graph and update it self.update_graph(worker, diameter_value) # calculate and display average diameter diameter_avg = worker.diameter_total / worker.diameter_count if worker.diameter_count > 0 else 0.0 worker.block.avg_label.setText(f"{diameter_avg:.4f}") # calculate and display estimated weight vol = math.pi * (diameter_avg / 2) ** 2 * position_value # all in mm → mm³ est_wgh = worker.density * vol/1000 # density in g·cm³, vol in mm³ worker.block.wht_label.setText(f"{est_wgh/1000:.3f} ") def update_graph(self, worker, diameter_value): # Update the graph with the new diameter value x_data = worker.line.get_xdata() y_data = worker.line.get_ydata() x_data = list(x_data) y_data = list(y_data) # Append new data point x_data.append(len(x_data)) y_data.append(diameter_value) # Update the line with the new data worker.line.set_data(x_data, y_data) # Rescale the graph worker.line.axes.relim() worker.line.axes.autoscale_view() # Redraw the canvas worker.canvas.draw()