dev
This commit is contained in:
parent
73cf7cbf03
commit
c90ea4cf85
6
.gitignore
vendored
6
.gitignore
vendored
@ -2,6 +2,8 @@ bin/
|
||||
include/
|
||||
lib/
|
||||
lib64/
|
||||
__pycache__/
|
||||
share/
|
||||
glob
|
||||
random
|
||||
re
|
||||
@ -12,3 +14,7 @@ time
|
||||
lib
|
||||
lib64
|
||||
pyvenv.cfg
|
||||
fdr1gui.2.py
|
||||
gzip
|
||||
os
|
||||
plt
|
||||
|
||||
228
BlockStackApp.py
Normal file
228
BlockStackApp.py
Normal file
@ -0,0 +1,228 @@
|
||||
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):
|
||||
super().__init__()
|
||||
|
||||
self.initUI()
|
||||
self.check_usb_devices()
|
||||
|
||||
self.update_count = 11
|
||||
|
||||
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, QComboBox {
|
||||
background-color: #5A5A5A;
|
||||
border: 1px solid #6A6A6A;
|
||||
padding: 2px;
|
||||
margin: 2px;
|
||||
height: 25px;
|
||||
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 = {}
|
||||
|
||||
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.reset_data = False
|
||||
else:
|
||||
worker.diameter_total += diameter_value
|
||||
worker.diameter_count += 1
|
||||
if diameter_value < worker.diameter_min:
|
||||
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} ")
|
||||
|
||||
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()
|
||||
521
UpdateWorker.py
Normal file
521
UpdateWorker.py
Normal file
@ -0,0 +1,521 @@
|
||||
from PyQt5.QtCore import QThread, pyqtSignal, QSize, QTimer
|
||||
from PyQt5.QtWidgets import QWidget, QVBoxLayout, QHBoxLayout, QLabel, QSizePolicy, QPushButton, QComboBox
|
||||
from PyQt5.QtCore import Qt
|
||||
from PyQt5.QtGui import QFont
|
||||
|
||||
import serial
|
||||
import re
|
||||
import time
|
||||
import matplotlib.pyplot as plt
|
||||
import os
|
||||
import struct
|
||||
import requests
|
||||
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
|
||||
|
||||
class UpdateWorker(QThread):
|
||||
update_signal = pyqtSignal(bool, float, float, float, float)
|
||||
data_received = pyqtSignal(str)
|
||||
stop_signal = pyqtSignal()
|
||||
mode_signal = pyqtSignal(int)
|
||||
offset_signal = pyqtSignal(int)
|
||||
change_color_signal = pyqtSignal(str, str, str)
|
||||
|
||||
def __init__(self, serial_number, bench_id, port, block_id, main_app):
|
||||
super().__init__()
|
||||
print(f"id{bench_id} :\tport {port}")
|
||||
print(f"id{bench_id} :\tserial {serial_number}")
|
||||
self.block = self.add_block(serial_number, bench_id, port, block_id, main_app)
|
||||
self.main_app = main_app # Store the reference to the BlockStackApp instance
|
||||
self.serial_number = serial_number
|
||||
self.bench_id = bench_id
|
||||
self.port = port
|
||||
self.block_id = block_id
|
||||
self.running = True
|
||||
self.position_value = 0.0
|
||||
self.diameter_total = 0.0 # for average calculation
|
||||
self.diameter_count = 0
|
||||
self.diameter_value = 0.0
|
||||
self.diameter_min = 0.0
|
||||
self.diameter_max = 0.0
|
||||
self.density = 1.0
|
||||
self.prepare_flag = False
|
||||
self.prepare_ok = False
|
||||
self.reset_flag = False
|
||||
self.ignore_stop = False
|
||||
self.is_running = False
|
||||
self.reset_data = False
|
||||
self.homed = False
|
||||
self.can_start = False
|
||||
self.data_list = []
|
||||
self.profile_data = {} # Dictionary to store profile data
|
||||
|
||||
# run at startup
|
||||
self.update_profiles()
|
||||
|
||||
self.timer = QTimer()
|
||||
self.timer.timeout.connect(self.query_api)
|
||||
self.timer.start(3000)
|
||||
|
||||
def run(self):
|
||||
try:
|
||||
with serial.Serial(self.port, 1000000, timeout=1) as ser:
|
||||
ser.write(b'a')
|
||||
ser.write(b'F')
|
||||
ser.write(b'i')
|
||||
ser.write(b' ')
|
||||
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) and (self.diameter_value < 1.85)) or ((2.70 < self.diameter_value) and (self.diameter_value < 2.95)):
|
||||
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':
|
||||
self.stop_running()
|
||||
# if not self.ignore_stop:
|
||||
# self.change_color_signal.emit(self.block_id, 'btn_start', "#A00000")
|
||||
# self.is_running = False
|
||||
# self.save_data_to_file()
|
||||
# else:
|
||||
# self.ignore_stop = False
|
||||
elif data == '$NOTHOMED':
|
||||
self.homed = False
|
||||
self.change_color_signal.emit(self.block_id, 'btn_home', "#a0a000")
|
||||
elif data == '$HOMED':
|
||||
print(f"id{self.bench_id} :\tHoming ok")
|
||||
self.homed = True
|
||||
self.change_color_signal.emit(self.block_id, 'btn_home', "#5a5a5a")
|
||||
except serial.SerialException as e:
|
||||
print(f"Error with port {self.port}: {e}")
|
||||
|
||||
def save_data_to_file(self):
|
||||
#FIXME: set name
|
||||
#FIXME: add file header
|
||||
#FIXME: compress file
|
||||
#FIXME: upload to API
|
||||
file_path = os.path.join("data", f"{self.bench_id}_data.txt")
|
||||
|
||||
# Ensure the directory exists
|
||||
os.makedirs(os.path.dirname(file_path), exist_ok=True)
|
||||
|
||||
# Write the data to the file
|
||||
with open(file_path, "w") as file:
|
||||
for data in self.data_list:
|
||||
file.write(data + "\n")
|
||||
|
||||
def parse_data(self, data):
|
||||
self.data_list.append(data)
|
||||
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))
|
||||
self.update_signal.emit(self.is_running, self.diameter_value, self.position_value, self.diameter_min, self.diameter_max)
|
||||
self.reset_flag = True
|
||||
|
||||
offset_match = re.match(r"^#h:(\d+)", data)
|
||||
if offset_match:
|
||||
home_offset = int(offset_match.group(1))
|
||||
print(f"id{self.bench_id}:\tOffset: {home_offset:.2f}")
|
||||
self.block.btn_offset.setText(f"Offset: {home_offset:.2f}")
|
||||
|
||||
mode_match = re.match(r"^#o:(\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 int_to_two_bytes(self, value):
|
||||
if not (0 <= value <= 0xFFFF):
|
||||
raise ValueError("Value out of range for two bytes")
|
||||
|
||||
two_bytes = struct.pack('>H', value)
|
||||
return two_bytes
|
||||
|
||||
def set_limits(self, left, right):
|
||||
print(f"id{self.bench_id}:\tupdate limits to {left}+{right}")
|
||||
try:
|
||||
with serial.Serial(self.port, 1000000, timeout=1) as ser:
|
||||
ser.write(b'\x04')
|
||||
time.sleep(0.001)
|
||||
ser.write(self.int_to_two_bytes(left))
|
||||
time.sleep(0.003)
|
||||
ser.write(self.int_to_two_bytes(right))
|
||||
time.sleep(0.003)
|
||||
ser.write(b'\x04')
|
||||
time.sleep(0.001)
|
||||
ser.write(b'\x00')
|
||||
time.sleep(0.001)
|
||||
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 start_running(self):
|
||||
if self.prepare_ok and self.can_start:
|
||||
self.reset_data = True
|
||||
self.is_running = True
|
||||
|
||||
self.change_color_signal.emit(self.block_id, 'btn_start', "#00a000")
|
||||
self.change_color_signal.emit(self.block_id, 'btn_prepare', "#5A5A5A")
|
||||
|
||||
self.send_command('S')
|
||||
|
||||
def stop_running(self):
|
||||
if self.is_running:
|
||||
self.send_command('R')
|
||||
self.timer.start()
|
||||
|
||||
self.block.data_label.setText(
|
||||
f"Job done, processing..."
|
||||
)
|
||||
|
||||
self.change_color_signal.emit(self.block_id, 'btn_start', "#a00000")
|
||||
self.change_color_signal.emit(self.block_id, 'btn_prepare', "#5A5A5A")
|
||||
|
||||
self.is_running = False
|
||||
self.save_data_to_file()
|
||||
self.prepare_ok = False
|
||||
|
||||
|
||||
def clear_graph(self):
|
||||
self.line.set_data([], [])
|
||||
self.canvas.draw()
|
||||
|
||||
def add_block(self, serial_number, bench_id, port, block_id, main_app):
|
||||
block_widget = QWidget()
|
||||
block_layout = QHBoxLayout()
|
||||
block_layout.setAlignment(Qt.AlignLeft | Qt.AlignTop)
|
||||
|
||||
LABEL_SIZE = QSize(250, 20)
|
||||
BUTTON_SIZE = QSize(100, 25)
|
||||
VALUE_SIZE = QSize(80, 25)
|
||||
VALUEL_SIZE = QSize(25, 25)
|
||||
|
||||
monospaced_font = QFont("unexistent", 10)
|
||||
monospaced_font.setStyleHint(QFont.Monospace)
|
||||
|
||||
monospaced_bold_font = QFont("unexistent", 10)
|
||||
monospaced_bold_font.setStyleHint(QFont.Monospace)
|
||||
monospaced_bold_font.setBold(True)
|
||||
|
||||
monospaced_italics_font = QFont("unexistent", 10)
|
||||
monospaced_italics_font.setStyleHint(QFont.Monospace)
|
||||
monospaced_italics_font.setItalic(True)
|
||||
|
||||
left_layout = QVBoxLayout()
|
||||
data_label = QLabel("No API data")
|
||||
data_label.setTextFormat(Qt.RichText)
|
||||
data_label.setAlignment(Qt.AlignLeft | Qt.AlignTop)
|
||||
bench_label = QLabel(f"Bench ID: {bench_id}")
|
||||
bench_label.setToolTip(f"Serial number: {serial_number}\nCPU: Microchip PIC18F4431 @ 32 MHz\nPort: {port}\nBandwidth: 1 MBps\nPosition maxspd: 638 mm/s\nDiameter freq: ≈10.5 Hz")
|
||||
bench_label.setAlignment(Qt.AlignCenter)
|
||||
bench_label.setFixedSize(LABEL_SIZE)
|
||||
left_layout.addWidget(bench_label)
|
||||
left_layout.addWidget(data_label)
|
||||
|
||||
left2_layout = QHBoxLayout()
|
||||
left2L_layout = QVBoxLayout()
|
||||
left2V_layout = QVBoxLayout()
|
||||
max_label = QLabel("2.000 ")
|
||||
max_label.setFont(monospaced_bold_font)
|
||||
avg_label = QLabel("2.0000")
|
||||
avg_label.setFont(monospaced_font)
|
||||
cur_label = QLabel("2.000 ")
|
||||
cur_label.setFont(monospaced_bold_font)
|
||||
len_label = QLabel("12345.00")
|
||||
len_label.setFont(monospaced_font)
|
||||
wht_label = QLabel("12.345 ")
|
||||
wht_label.setFont(monospaced_italics_font)
|
||||
min_label = QLabel("2.000 ")
|
||||
min_label.setFont(monospaced_bold_font)
|
||||
maxL_label = QLabel("∨")
|
||||
avgL_label = QLabel("⌀̄")
|
||||
curL_label = QLabel("⌀")
|
||||
lenL_label = QLabel("L")
|
||||
whtL_label = QLabel("M")
|
||||
minL_label = QLabel("∧")
|
||||
avg_label.setAlignment(Qt.AlignRight)
|
||||
max_label.setAlignment(Qt.AlignRight)
|
||||
cur_label.setAlignment(Qt.AlignRight)
|
||||
len_label.setAlignment(Qt.AlignRight)
|
||||
wht_label.setAlignment(Qt.AlignRight)
|
||||
min_label.setAlignment(Qt.AlignRight)
|
||||
|
||||
avg_label.setToolTip("<i>Average diameter</i> in <b>mm</b>")
|
||||
max_label.setToolTip("<i>Maximum diameter</i> in <b>mm</b>")
|
||||
cur_label.setToolTip("<i>Current diameter</i> in <b>mm</b>")
|
||||
len_label.setToolTip("<i>Length</i> in <b>m</b>")
|
||||
wht_label.setToolTip("<i>Estimated mass</i> in <b>kg</b>")
|
||||
min_label.setToolTip("<i>Minimum diameter</i> in <b>mm</b>")
|
||||
|
||||
maxL_label.setToolTip("<i>Average diameter</i> in <b>mm</b>")
|
||||
avgL_label.setToolTip("<i>Maximum diameter</i> in <b>mm</b>")
|
||||
curL_label.setToolTip("<i>Current diameter</i> in <b>mm</b>")
|
||||
lenL_label.setToolTip("<i>Length</i> in <b>m</b>")
|
||||
whtL_label.setToolTip("<i>Estimated mass</i> in <b>kg</b>")
|
||||
minL_label.setToolTip("<i>Minimum diameter</i> in <b>mm</b>")
|
||||
|
||||
max_label.setFixedSize(VALUE_SIZE)
|
||||
avg_label.setFixedSize(VALUE_SIZE)
|
||||
cur_label.setFixedSize(VALUE_SIZE)
|
||||
len_label.setFixedSize(VALUE_SIZE)
|
||||
wht_label.setFixedSize(VALUE_SIZE)
|
||||
min_label.setFixedSize(VALUE_SIZE)
|
||||
|
||||
maxL_label.setFixedSize(VALUEL_SIZE)
|
||||
avgL_label.setFixedSize(VALUEL_SIZE)
|
||||
curL_label.setFixedSize(VALUEL_SIZE)
|
||||
lenL_label.setFixedSize(VALUEL_SIZE)
|
||||
whtL_label.setFixedSize(VALUEL_SIZE)
|
||||
minL_label.setFixedSize(VALUEL_SIZE)
|
||||
|
||||
left2V_layout.addWidget(max_label)
|
||||
left2V_layout.addWidget(avg_label)
|
||||
left2V_layout.addWidget(cur_label)
|
||||
left2V_layout.addWidget(len_label)
|
||||
left2V_layout.addWidget(wht_label)
|
||||
left2V_layout.addWidget(min_label)
|
||||
|
||||
left2L_layout.addWidget(maxL_label)
|
||||
left2L_layout.addWidget(avgL_label)
|
||||
left2L_layout.addWidget(curL_label)
|
||||
left2L_layout.addWidget(lenL_label)
|
||||
left2L_layout.addWidget(whtL_label)
|
||||
left2L_layout.addWidget(minL_label)
|
||||
|
||||
left2_layout.addLayout(left2L_layout)
|
||||
left2_layout.addLayout(left2V_layout)
|
||||
|
||||
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(self.start_running)
|
||||
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(self.stop_running)
|
||||
right_layout_1.addWidget(btn_stop)
|
||||
|
||||
btn_spare1 = QPushButton()
|
||||
right_layout_1.addWidget(btn_spare1)
|
||||
|
||||
|
||||
|
||||
btn_sleep = QPushButton("Sleep")
|
||||
btn_sleep.clicked.connect(lambda: self.send_command('a'))
|
||||
right_layout_2.addWidget(btn_sleep)
|
||||
|
||||
btn_home = QPushButton("Home")
|
||||
btn_home.clicked.connect(lambda: self.send_command(' '))
|
||||
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_offset = QPushButton()
|
||||
right_layout_2.addWidget(btn_offset)
|
||||
|
||||
btn_down_left = QPushButton("Offset +")
|
||||
btn_down_left.clicked.connect(lambda: self.send_command('b'))
|
||||
right_layout_3.addWidget(btn_down_left)
|
||||
|
||||
btn_up_left = QPushButton("Offset -")
|
||||
btn_up_left.clicked.connect(lambda: self.send_command('n'))
|
||||
right_layout_3.addWidget(btn_up_left)
|
||||
|
||||
btn_save_offset = QPushButton("Save Offset")
|
||||
btn_save_offset.clicked.connect(lambda: self.send_command('M'))
|
||||
right_layout_3.addWidget(btn_save_offset)
|
||||
|
||||
|
||||
|
||||
dropd_profile = QComboBox()
|
||||
|
||||
dropd_profile.addItem("Select a profile...")
|
||||
dropd_profile.model().item(0).setEnabled(False)
|
||||
|
||||
|
||||
dropd_profile.currentIndexChanged.connect(self.on_profile_changed)
|
||||
|
||||
dropd_profile.setFixedWidth(120)
|
||||
|
||||
view = dropd_profile.view()
|
||||
view.setMinimumWidth(350) # Set the minimum width of the dropdown list
|
||||
|
||||
right_layout_3.addWidget(dropd_profile)
|
||||
|
||||
btn_update_profiles = QPushButton("Reload profiles")
|
||||
btn_update_profiles.clicked.connect(self.update_profiles)
|
||||
right_layout_3.addWidget(btn_update_profiles)
|
||||
|
||||
|
||||
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(left2_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
|
||||
block_widget.bench_label = bench_label
|
||||
|
||||
block_widget.max_label = max_label
|
||||
block_widget.avg_label = avg_label
|
||||
block_widget.dia_label = cur_label
|
||||
block_widget.len_label = len_label
|
||||
block_widget.wht_label = wht_label
|
||||
block_widget.min_label = min_label
|
||||
block_widget.btn_offset = btn_offset
|
||||
block_widget.dropd_profile = dropd_profile
|
||||
|
||||
# Create a Matplotlib figure and canvas for the graph
|
||||
plt.style.use('dark_background')
|
||||
|
||||
fig, ax = plt.subplots()
|
||||
plt.subplots_adjust(left=-0.01, right=1, top=1.01, bottom=0)
|
||||
ax.set_facecolor('#3a3a3a')
|
||||
ax.set_xticks([]) # Hide x-axis values
|
||||
ax.set_frame_on(True)
|
||||
# ax.set_ylabel('Diameter (mm)') # Show y-axis label
|
||||
self.line, = ax.plot([], [], '#ff5000', linewidth=0.5)
|
||||
self.canvas = FigureCanvas(fig)
|
||||
self.canvas.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Ignored)
|
||||
center_layout.addWidget(self.canvas)
|
||||
|
||||
return block_widget
|
||||
|
||||
def on_profile_changed(self, index):
|
||||
# Get the selected ID
|
||||
selected_id = self.sender().itemData(index)
|
||||
|
||||
if selected_id in self.profile_data:
|
||||
start_value = self.profile_data[selected_id]['start']
|
||||
width_value = self.profile_data[selected_id]['width']
|
||||
|
||||
self.set_limits(start_value, width_value)
|
||||
|
||||
def update_profiles(self):
|
||||
url = f"http://462filament/api.php?data=profiles"
|
||||
try:
|
||||
response = requests.get(url)
|
||||
if response.status_code == 200:
|
||||
profiles = response.json()
|
||||
if profiles:
|
||||
self.block.dropd_profile.clear()
|
||||
self.block.dropd_profile.addItem("Select a profile...")
|
||||
self.block.dropd_profile.model().item(0).setEnabled(False)
|
||||
for profile in profiles:
|
||||
profile_id = profile["id"]
|
||||
profile_name = profile["show_name"]
|
||||
start_value = int(profile["start"])
|
||||
width_value = int(profile["width"])
|
||||
self.block.dropd_profile.addItem(profile_name, profile_id)
|
||||
self.profile_data[profile_id] = {'start': start_value, 'width': width_value}
|
||||
except requests.RequestException as e:
|
||||
print(f"Error making request to API: {e}")
|
||||
return None
|
||||
|
||||
def query_api(self):
|
||||
try:
|
||||
url = f"http://462filament/api.php?data=bench&benchid={self.bench_id}"
|
||||
response = requests.get(url)
|
||||
response.raise_for_status() # Raise an exception for HTTP errors
|
||||
api_job = response.json() # Parse the JSON response
|
||||
|
||||
if api_job is not None:
|
||||
print(f"id{self.bench_id}:\tgot job {api_job['spool']} from API")
|
||||
self.block.data_label.setText(
|
||||
f"Job/Spool: {api_job['spool']}<hr>"
|
||||
f"Fabricant: {api_job['vendor']}<br>"
|
||||
f"Matière: {api_job['material']}<br>"
|
||||
f"Lot: {api_job['batch']}<br>"
|
||||
f"Identifiant: {api_job['description']}<hr>"
|
||||
f"ρ = {api_job['density']}<br>"
|
||||
)
|
||||
self.density = float(api_job['density'])
|
||||
self.can_start = True
|
||||
self.timer.stop()
|
||||
else:
|
||||
print(f"id{self.bench_id}:\tno job from API")
|
||||
self.block.data_label.setText(
|
||||
f"API responded with no job"
|
||||
)
|
||||
self.can_start = False
|
||||
except requests.RequestException as e:
|
||||
print(f"Error querying API: {e}")
|
||||
508
fdr1gui.py
508
fdr1gui.py
@ -1,510 +1,6 @@
|
||||
import sys
|
||||
import glob
|
||||
import serial
|
||||
import re
|
||||
import requests
|
||||
from PyQt5.QtWidgets import (QApplication, QWidget, QVBoxLayout, QHBoxLayout,
|
||||
QLabel, QSizePolicy, QPushButton)
|
||||
from PyQt5.QtGui import QFont
|
||||
from PyQt5.QtCore import Qt, QThread, pyqtSignal, QSize
|
||||
import time
|
||||
import matplotlib.pyplot as plt
|
||||
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
|
||||
|
||||
class UpdateWorker(QThread):
|
||||
update_signal = pyqtSignal(bool, 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)
|
||||
|
||||
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
|
||||
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
|
||||
self.is_running = False
|
||||
|
||||
def run(self):
|
||||
try:
|
||||
with serial.Serial(self.port, 1000000, timeout=1) as ser:
|
||||
ser.write(b'a')
|
||||
ser.write(b'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")
|
||||
self.is_running = False
|
||||
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):
|
||||
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.is_running, self.diameter_value, self.position_value, self.diameter_min, self.diameter_max)
|
||||
self.reset_flag = True
|
||||
|
||||
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 start_running(self):
|
||||
self.is_running = True
|
||||
self.clear_graph()
|
||||
|
||||
def stop_running(self):
|
||||
self.is_running = False
|
||||
|
||||
def clear_graph(self):
|
||||
self.line.set_data([], [])
|
||||
self.canvas.draw()
|
||||
|
||||
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)
|
||||
VALUE_SIZE = QSize(80, 25)
|
||||
VALUEL_SIZE = QSize(25, 25)
|
||||
|
||||
monospaced_font = QFont("unexistent", 10)
|
||||
monospaced_font.setStyleHint(QFont.Monospace)
|
||||
|
||||
left_layout = QVBoxLayout()
|
||||
data_label = QLabel("No API data")
|
||||
data_label.setAlignment(Qt.AlignLeft | Qt.AlignTop)
|
||||
bench_label = QLabel(f"Bench ID: {bench_id}")
|
||||
bench_label.setAlignment(Qt.AlignCenter)
|
||||
bench_label.setFixedSize(LABEL_SIZE)
|
||||
left_layout.addWidget(bench_label)
|
||||
left_layout.addWidget(data_label)
|
||||
|
||||
left2_layout = QHBoxLayout()
|
||||
left2L_layout = QVBoxLayout()
|
||||
left2V_layout = QVBoxLayout()
|
||||
max_label = QLabel("2.000 ")
|
||||
max_label.setFont(monospaced_font)
|
||||
avg_label = QLabel("2.0000")
|
||||
avg_label.setFont(monospaced_font)
|
||||
cur_label = QLabel("2.000 ")
|
||||
cur_label.setFont(monospaced_font)
|
||||
len_label = QLabel("12345.00")
|
||||
len_label.setFont(monospaced_font)
|
||||
wht_label = QLabel("12.345")
|
||||
wht_label.setFont(monospaced_font)
|
||||
min_label = QLabel("2.000 ")
|
||||
min_label.setFont(monospaced_font)
|
||||
maxL_label = QLabel("∨")
|
||||
avgL_label = QLabel("⌀̄")
|
||||
curL_label = QLabel("⌀")
|
||||
lenL_label = QLabel("L")
|
||||
whtL_label = QLabel("M")
|
||||
minL_label = QLabel("∧")
|
||||
avg_label.setAlignment(Qt.AlignRight)
|
||||
max_label.setAlignment(Qt.AlignRight)
|
||||
cur_label.setAlignment(Qt.AlignRight)
|
||||
len_label.setAlignment(Qt.AlignRight)
|
||||
wht_label.setAlignment(Qt.AlignRight)
|
||||
min_label.setAlignment(Qt.AlignRight)
|
||||
|
||||
max_label.setFixedSize(VALUE_SIZE)
|
||||
avg_label.setFixedSize(VALUE_SIZE)
|
||||
cur_label.setFixedSize(VALUE_SIZE)
|
||||
len_label.setFixedSize(VALUE_SIZE)
|
||||
wht_label.setFixedSize(VALUE_SIZE)
|
||||
min_label.setFixedSize(VALUE_SIZE)
|
||||
|
||||
maxL_label.setFixedSize(VALUEL_SIZE)
|
||||
avgL_label.setFixedSize(VALUEL_SIZE)
|
||||
curL_label.setFixedSize(VALUEL_SIZE)
|
||||
lenL_label.setFixedSize(VALUEL_SIZE)
|
||||
whtL_label.setFixedSize(VALUEL_SIZE)
|
||||
minL_label.setFixedSize(VALUEL_SIZE)
|
||||
|
||||
left2V_layout.addWidget(max_label)
|
||||
left2V_layout.addWidget(avg_label)
|
||||
left2V_layout.addWidget(cur_label)
|
||||
left2V_layout.addWidget(len_label)
|
||||
left2V_layout.addWidget(wht_label)
|
||||
left2V_layout.addWidget(min_label)
|
||||
|
||||
left2L_layout.addWidget(maxL_label)
|
||||
left2L_layout.addWidget(avgL_label)
|
||||
left2L_layout.addWidget(curL_label)
|
||||
left2L_layout.addWidget(lenL_label)
|
||||
left2L_layout.addWidget(whtL_label)
|
||||
left2L_layout.addWidget(minL_label)
|
||||
|
||||
left2_layout.addLayout(left2L_layout)
|
||||
left2_layout.addLayout(left2V_layout)
|
||||
|
||||
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(self.start_running)
|
||||
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"))
|
||||
btn_stop.clicked.connect(self.stop_running)
|
||||
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(left2_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
|
||||
|
||||
block_widget.max_label = max_label
|
||||
block_widget.avg_label = avg_label
|
||||
block_widget.dia_label = cur_label
|
||||
block_widget.len_label = len_label
|
||||
block_widget.wht_label = wht_label
|
||||
block_widget.min_label = min_label
|
||||
|
||||
# Create a Matplotlib figure and canvas for the graph
|
||||
plt.style.use('dark_background')
|
||||
|
||||
fig, ax = plt.subplots()
|
||||
plt.subplots_adjust(left=0.0, right=0.9995, top=1, bottom=0.005)
|
||||
ax.set_facecolor('#3a3a3a')
|
||||
ax.set_xticks([]) # Hide x-axis values
|
||||
ax.set_frame_on(True)
|
||||
# ax.set_ylabel('Diameter (mm)') # Show y-axis label
|
||||
self.line, = ax.plot([], [], '#ff5000', linewidth=0.5) # Set line width to 0.5
|
||||
self.canvas = FigureCanvas(fig)
|
||||
center_layout.addWidget(self.canvas)
|
||||
|
||||
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, is_running, diameter_value, position_value, diameter_min, diameter_max):
|
||||
# Update the block with diameter_value and position_value
|
||||
worker = self.sender()
|
||||
diameter_avg = 2.0000
|
||||
est_wgh = 1234
|
||||
worker.block.min_label.setText(f"{diameter_min:.3f} ")
|
||||
worker.block.max_label.setText(f"{diameter_max:.3f} ")
|
||||
worker.block.dia_label.setText(f"{diameter_value:.3f} ")
|
||||
worker.block.avg_label.setText(f"{diameter_avg:.4f}")
|
||||
worker.block.len_label.setText(f"{20000/1000:.4f}")
|
||||
worker.block.wht_label.setText(f"{est_wgh/1000:.3f} ")
|
||||
worker.block.data_label.setText(f"No API data")
|
||||
|
||||
# Update the graph with the new diameter value
|
||||
if is_running:
|
||||
self.update_graph(worker, diameter_value)
|
||||
|
||||
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()
|
||||
from PyQt5.QtWidgets import QApplication
|
||||
from BlockStackApp import BlockStackApp
|
||||
|
||||
if __name__ == '__main__':
|
||||
app = QApplication(sys.argv)
|
||||
|
||||
26
requirements.txt
Normal file
26
requirements.txt
Normal file
@ -0,0 +1,26 @@
|
||||
certifi==2025.1.31
|
||||
charset-normalizer==3.4.1
|
||||
contourpy==1.3.1
|
||||
cycler==0.12.1
|
||||
fonttools==4.57.0
|
||||
future==1.0.0
|
||||
idna==3.10
|
||||
iso8601==2.1.0
|
||||
kiwisolver==1.4.8
|
||||
matplotlib==3.10.1
|
||||
numpy==2.2.4
|
||||
packaging==24.2
|
||||
pillow==11.1.0
|
||||
pyparsing==3.2.3
|
||||
PyQt5==5.15.11
|
||||
PyQt5-Qt5==5.15.16
|
||||
PyQt5_sip==12.17.0
|
||||
PyQtChart==5.15.7
|
||||
PyQtChart-Qt5==5.15.16
|
||||
pyserial==3.5
|
||||
python-dateutil==2.9.0.post0
|
||||
PyYAML==6.0.2
|
||||
requests==2.32.3
|
||||
serial==0.0.97
|
||||
six==1.17.0
|
||||
urllib3==2.3.0
|
||||
Loading…
Reference in New Issue
Block a user