384 lines
16 KiB
Python
384 lines
16 KiB
Python
import sys
|
||
import glob
|
||
import serial
|
||
import re
|
||
import requests
|
||
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(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, serial_number, bench_id, port, block_id):
|
||
super().__init__()
|
||
self.block = self.add_block(serial_number, bench_id, port, block_id)
|
||
|
||
self.serial_number = serial_number
|
||
self.bench_id = bench_id
|
||
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):
|
||
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):
|
||
super().__init__()
|
||
|
||
self.initUI()
|
||
self.check_usb_devices()
|
||
|
||
def initUI(self):
|
||
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 {
|
||
background-color: #5A5A5A;
|
||
border: 1px solid #6A6A6A;
|
||
padding: 2px;
|
||
margin: 2px;
|
||
height: 30px;
|
||
width: 100px;
|
||
}
|
||
""")
|
||
|
||
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 = {} # 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):
|
||
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, diameter_value, position_value, diameter_min, diameter_max):
|
||
# Update the block with diameter_value and position_value
|
||
worker = self.sender()
|
||
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)
|
||
ex = BlockStackApp()
|
||
sys.exit(app.exec_())
|