From c82835cdb0e4774baf601f983d6d6bf9b8c9e379 Mon Sep 17 00:00:00 2001 From: Jonathan Roth Date: Mon, 7 Apr 2025 15:56:20 +0200 Subject: [PATCH] dev gui, mostly working --- fdr1gui.py | 392 +++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 287 insertions(+), 105 deletions(-) diff --git a/fdr1gui.py b/fdr1gui.py index dadb7ff..96c703e 100644 --- a/fdr1gui.py +++ b/fdr1gui.py @@ -3,28 +3,234 @@ import glob import serial import re import requests -import random from PyQt5.QtWidgets import (QApplication, QWidget, QVBoxLayout, QHBoxLayout, QLabel, QSizePolicy, QPushButton) from PyQt5.QtCore import Qt, QThread, pyqtSignal, QSize import time class UpdateWorker(QThread): - update_signal = pyqtSignal(int, int) + update_signal = pyqtSignal(float, float, float, float) + data_received = pyqtSignal(str) + stop_signal = pyqtSignal() + not_homed_signal = pyqtSignal() + mode_signal = pyqtSignal(int) + change_color_signal = pyqtSignal(str, str, str) # Signal to change button color with identifier - def __init__(self, block, serial_number, bench_id): + def __init__(self, serial_number, bench_id, port, block_id): super().__init__() - self.block = block + self.block = self.add_block(serial_number, bench_id, port, block_id) + self.serial_number = serial_number self.bench_id = bench_id - self.position_value = 0 + self.port = port + self.block_id = block_id # Unique identifier for the block + self.running = True + self.position_value = 0.0 + self.diameter_value = 0.0 + self.diameter_min = 0.0 + self.diameter_max = 0.0 + self.prepare_flag = False + self.prepare_ok = False + self.reset_flag = False + self.ignore_stop = False def run(self): - while True: - diameter_value = random.randint(0, 100) - self.position_value += 1 - self.update_signal.emit(diameter_value, self.position_value) - self.sleep(1) # Sleep for 1 second + try: + with serial.Serial(self.port, 1000000, timeout=1) as ser: + ser.write(b'a') # Send the character 'F' + ser.write(b'F') # Send the character 'F + self.change_color_signal.emit(self.block_id, 'btn_home', "#A0A000") + while self.running: + if ser.in_waiting > 0: + data = ser.readline().decode('utf-8').strip() + if data: + self.parse_data(data) + if self.reset_flag and self.prepare_flag: + if self.position_value > 100 and self.reset_flag: + self.ignore_stop = True + self.change_color_signal.emit(self.block_id, 'btn_prepare', "#5A5A5A") + ser.write(b'R') + self.prepare_flag = False + if 1.65 < self.diameter_value < 2.99: + self.prepare_ok = True + else: + self.prepare_ok = False + if self.prepare_ok: + self.change_color_signal.emit(self.block_id, 'btn_prepare', "#00A000") + self.change_color_signal.emit(self.block_id, 'btn_start', "#A0A000") + else: + self.change_color_signal.emit(self.block_id, 'btn_prepare', "#A00000") + else: + self.change_color_signal.emit(self.block_id, 'btn_prepare', "#0000FF") + else: + if data == '%STOP': + if not self.ignore_stop : + self.change_color_signal.emit(self.block_id, 'btn_start', "#A00000") + else: + self.ignore_stop = False + elif data == '$NOTHOMED': + self.not_homed_signal.emit() + except serial.SerialException as e: + print(f"Error with port {self.port}: {e}") + + def parse_data(self, data): + # Parse the data and update diameter_value and position_value + match = re.match(r"[#d]:([\d.-]+),p:([\d.-]+)", data) + if match: + self.diameter_value = float(match.group(1)) + self.position_value = float(match.group(2)) + if self.diameter_value < self.diameter_min: + self.diameter_min = self.diameter_value + if self.diameter_value > self.diameter_max: + self.diameter_max = self.diameter_value + self.update_signal.emit(self.diameter_value, self.position_value, self.diameter_min, self.diameter_max) + self.reset_flag = True + + # Parse mode and position data + mode_match = re.match(r"#o:(\d+),l:(\d+),r:(\d+),c:(\d+)", data) + if mode_match: + mode = int(mode_match.group(1)) + self.mode_signal.emit(mode) + match mode: + case 1: + self.change_color_signal.emit(self.block_id, 'btn_go_left', "#0000A0") + self.change_color_signal.emit(self.block_id, 'btn_go_right', "#5a5a5a") + self.change_color_signal.emit(self.block_id, 'btn_reset', "#5a5a5a") + case 2: + self.change_color_signal.emit(self.block_id, 'btn_go_left', "#5a5a5a") + self.change_color_signal.emit(self.block_id, 'btn_go_right', "#0000A0") + self.change_color_signal.emit(self.block_id, 'btn_reset', "#5a5a5a") + case 3: + self.change_color_signal.emit(self.block_id, 'btn_go_left', "#5a5a5a") + self.change_color_signal.emit(self.block_id, 'btn_go_right', "#5a5a5a") + self.change_color_signal.emit(self.block_id, 'btn_reset', "#00A0A0") + else: + self.mode_signal.emit(65535) + self.change_color_signal.emit(self.block_id, 'btn_go_left', "#5a5a5a") + self.change_color_signal.emit(self.block_id, 'btn_go_right', "#5a5a5a") + self.change_color_signal.emit(self.block_id, 'btn_reset', "#5A5A5A") + + def send_command(self, command): + try: + with serial.Serial(self.port, 1000000, timeout=1) as ser: + ser.write(command.encode()) + except serial.SerialException as e: + print(f"Could not send command to port {self.port}: {e}") + + def set_prepare(self, prepare): + self.position_value = 0 + self.prepare_flag = True + self.reset_flag = False + + def reset_diameter_limits(self): + self.diameter_min = self.diameter_value + self.diameter_max = self.diameter_value + + def add_block(self, serial_number, bench_id, port, block_id): + block_widget = QWidget() + block_layout = QHBoxLayout() + block_layout.setAlignment(Qt.AlignLeft | Qt.AlignTop) + + LABEL_SIZE = QSize(250, 20) + BUTTON_SIZE = QSize(100, 25) + + left_layout = QVBoxLayout() + data_label = QLabel("---") + bench_label = QLabel(f"Bench ID: {bench_id}") + bench_label.setFixedSize(LABEL_SIZE) + left_layout.addWidget(bench_label) + left_layout.addWidget(data_label) + + center_layout = QVBoxLayout() + graph_placeholder = QLabel("Graph Placeholder") + graph_placeholder.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Ignored) + center_layout.addWidget(graph_placeholder) + + right_layout = QHBoxLayout() + right_layout_1 = QVBoxLayout() + right_layout_2 = QVBoxLayout() + right_layout_3 = QVBoxLayout() + + btn_prepare = QPushButton("Prepare") + btn_prepare.clicked.connect(lambda: self.send_command('P')) + btn_prepare.clicked.connect(lambda: self.set_prepare(True)) + right_layout_1.addWidget(btn_prepare) + + btn_start = QPushButton("Start") + + btn_start.clicked.connect(lambda: self.change_color_signal.emit(self.block_id, 'btn_start', "#00a000")) + btn_start.clicked.connect(lambda: self.change_color_signal.emit(self.block_id, 'btn_prepare', "#5A5A5A")) + btn_start.clicked.connect(self.reset_diameter_limits) + btn_start.clicked.connect(lambda: self.send_command('S')) + right_layout_1.addWidget(btn_start) + + btn_reset = QPushButton("Reset/Zero") + btn_reset.clicked.connect(lambda: self.send_command('r')) + btn_reset.clicked.connect(lambda: self.change_color_signal.emit(self.block_id, 'btn_start', "#5A5A5A")) + right_layout_1.addWidget(btn_reset) + + btn_stop = QPushButton("Stop") + btn_stop.clicked.connect(lambda: self.send_command('R')) + btn_stop.clicked.connect(lambda: self.change_color_signal.emit(self.block_id, 'btn_start', "#a00000")) + btn_stop.clicked.connect(lambda: self.change_color_signal.emit(self.block_id, 'btn_prepare', "#5A5A5A")) + right_layout_1.addWidget(btn_stop) + + btn_sleep = QPushButton("Sleep") + btn_sleep.clicked.connect(lambda: self.send_command('a')) + btn_sleep.clicked.connect(lambda: self.change_color_signal.emit(self.block_id, 'btn_home', "#a0a000")) + right_layout_2.addWidget(btn_sleep) + + btn_home = QPushButton("Home") + btn_home.clicked.connect(lambda: self.send_command(' ')) + btn_home.clicked.connect(lambda: self.change_color_signal.emit(self.block_id, 'btn_home', "#5A5A5A")) + btn_home.clicked.connect(lambda: self.change_color_signal.emit(self.block_id, 'btn_start', "#5A5A5A")) + right_layout_2.addWidget(btn_home) + + btn_go_left = QPushButton("Go Left") + btn_go_left.clicked.connect(lambda: self.send_command('1')) + right_layout_2.addWidget(btn_go_left) + + btn_go_right = QPushButton("Go Right") + btn_go_right.clicked.connect(lambda: self.send_command('2')) + right_layout_2.addWidget(btn_go_right) + + btn_down_left = QPushButton("Down Left") + btn_down_left.clicked.connect(lambda: self.send_command('g')) + right_layout_3.addWidget(btn_down_left) + + btn_up_left = QPushButton("Up Left") + btn_up_left.clicked.connect(lambda: self.send_command('h')) + right_layout_3.addWidget(btn_up_left) + + btn_down_right = QPushButton("Down Right") + btn_down_right.clicked.connect(lambda: self.send_command('t')) + right_layout_3.addWidget(btn_down_right) + + btn_up_right = QPushButton("Up Right") + btn_up_right.clicked.connect(lambda: self.send_command('y')) + right_layout_3.addWidget(btn_up_right) + + right_layout.addLayout(right_layout_1) + right_layout.addLayout(right_layout_2) + right_layout.addLayout(right_layout_3) + + block_layout.addLayout(left_layout) + block_layout.addLayout(center_layout) + block_layout.addLayout(right_layout) + + block_widget.setLayout(block_layout) + + block_widget.btn_start = btn_start + block_widget.btn_home = btn_home + block_widget.btn_go_left = btn_go_left + block_widget.btn_go_right = btn_go_right + block_widget.btn_reset = btn_reset + block_widget.btn_prepare = btn_prepare + block_widget.data_label = data_label + + return block_widget + + class BlockStackApp(QWidget): def __init__(self): @@ -34,20 +240,14 @@ class BlockStackApp(QWidget): self.check_usb_devices() def initUI(self): - # Set up the layout self.layout = QVBoxLayout() - self.layout.setSpacing(5) # Set space between widgets to 5 pixels - self.layout.setContentsMargins(5, 5, 5, 5) # Set margins around the layout to 5 pixels - - # Align the layout to the left + self.layout.setSpacing(5) + self.layout.setContentsMargins(5, 5, 5, 5) self.layout.setAlignment(Qt.AlignLeft | Qt.AlignTop) - - # Set the layout to the QWidget self.setLayout(self.layout) self.setWindowTitle('462filament Bench GUI') - # Apply dark theme self.setStyleSheet(""" QWidget { background-color: #2E2E2E; @@ -72,38 +272,62 @@ class BlockStackApp(QWidget): } """) - # Show the window maximized self.showMaximized() def check_usb_devices(self): - # Check each /dev/ttyUSB? device + bench_list = [] + for port in glob.glob('/dev/ttyUSB*'): try: with serial.Serial(port, 1000000, timeout=1) as ser: - ser.write(b'i') # Send the character 'i' - time.sleep(1) # Wait for 1 second to receive data + 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: - block = self.add_block(serial_number, bench_id) - # Start a QThread for each block - self.worker = UpdateWorker(block, serial_number, bench_id) - self.worker.update_signal.connect(self.update_block) - self.worker.start() + 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 = {} # Dictionary to store blocks by block_id + + for port, bench_id, serial_number in bench_list: + block_id = f"{bench_id}_{port}" # Unique identifier for the block + self.worker = UpdateWorker(serial_number, bench_id, port, block_id) + 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 # Store the block with its identifier + def extract_serial_number(self, response): - # Extract the hexadecimal serial number using a regular expression 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): - # Make a request to the API to get the bench ID url = f"http://462filament/api.php?data=benchid&serial={serial_number}" try: response = requests.get(url) @@ -114,86 +338,44 @@ class BlockStackApp(QWidget): print(f"Error making request to API: {e}") return None - def add_block(self, serial_number, bench_id): - # Create a new block with three zones - block_widget = QWidget() - block_layout = QHBoxLayout() - block_layout.setAlignment(Qt.AlignLeft | Qt.AlignTop) + 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};") - LABEL_SIZE = QSize(250, 20) - BUTTON_SIZE = QSize(100, 25) + 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") - # Left zone - left_layout = QVBoxLayout() - data_label = QLabel(f"Est. weight: 0 g\nStop: at end") - bench_label = QLabel(f"Bench ID: {bench_id}") - bench_label.setFixedSize(LABEL_SIZE) - diameter_label = QLabel("Diameter: 0") - diameter_label.setFixedSize(LABEL_SIZE) - position_label = QLabel("Position: 0") - position_label.setFixedSize(LABEL_SIZE) - left_layout.addWidget(bench_label) - left_layout.addWidget(diameter_label) - left_layout.addWidget(position_label) - left_layout.addWidget(data_label) + def handle_data_received(self, data): + # Handle the data received from the serial port + print(f"Data received: {data}") - # Center zone (placeholder for graph) - center_layout = QVBoxLayout() - graph_placeholder = QLabel("Graph Placeholder") - graph_placeholder.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Ignored) - center_layout.addWidget(graph_placeholder) - - # Right zone with buttons - right_layout = QHBoxLayout() - right_layout_1 = QVBoxLayout() - right_layout_2 = QVBoxLayout() - right_layout_3 = QVBoxLayout() - buttons = [ - ("Home", " "), ("Sleep", "a"), ("Down Right", "t"), - ("Up Right", "y"), ("Down Left", "g"), ("Up Left", "h"), - ("Start", "S"), ("Stop", "R"), ("Reset", "r"), - ("Go Right", "1"), ("Go Left", "2") - ] - - btn_start = QPushButton(f"Start") - right_layout_1.addWidget(btn_start) - - - right_layout_1.addWidget(QPushButton(f"Reset")) - right_layout_1.addWidget(QPushButton(f"Stop")) - right_layout_1.addWidget(QPushButton(f"Home")) - right_layout_2.addWidget(QPushButton(f"Go Left")) - right_layout_2.addWidget(QPushButton(f"")) - right_layout_2.addWidget(QPushButton(f"Go Right")) - right_layout_2.addWidget(QPushButton(f"Sleep")) - right_layout_3.addWidget(QPushButton(f"Down Left")) - right_layout_3.addWidget(QPushButton(f"Up Left")) - right_layout_3.addWidget(QPushButton(f"Down Right")) - right_layout_3.addWidget(QPushButton(f"Up Right")) - - right_layout.addLayout(right_layout_1) - right_layout.addLayout(right_layout_2) - right_layout.addLayout(right_layout_3) - - # Add zones to the block layout - block_layout.addLayout(left_layout) - block_layout.addLayout(center_layout) - block_layout.addLayout(right_layout) - - block_widget.setLayout(block_layout) - self.layout.addWidget(block_widget) - - # Store labels for updating - block_widget.diameter_label = diameter_label - block_widget.position_label = position_label - - return block_widget - - def update_block(self, diameter_value, position_value): - # Update the block with random value and incrementing position_value + def update_block(self, diameter_value, position_value, diameter_min, diameter_max): + # Update the block with diameter_value and position_value worker = self.sender() - worker.block.diameter_label.setText(f"Diameter: {diameter_value}") - worker.block.position_label.setText(f"Position: {position_value}") + worker.block.data_label.setText(f"⌀: {diameter_min:.3f} < ⌀̄ {diameter_value:.3f} mm < {diameter_max:.3f}\n" + f"L: {position_value/1000:.4f} m\n" + f"ρ: {1} g·cm¯³\n" + "\n- - -\n\n" + f"M: 0 g\nStop: at end") if __name__ == '__main__': app = QApplication(sys.argv)