From 3722af34a1a6f7e9579b7b866410786715f4c15e Mon Sep 17 00:00:00 2001 From: matteo porta Date: Mon, 1 Aug 2022 13:29:12 +0200 Subject: [PATCH] wip --- config/machine_settings/defaults.ini | 7 +- config/machine_settings/hostnames.ini | 2 + config/vision/labels/labels.pbtxt | 2 +- .../recipes/termorestringente_923578.ini | 4 +- runme.sh | 2 +- src/components/neo_pixels.py | 29 +- src/components/tecna_marposs_provaset_t3p.py | 2 +- .../tecna_marposs_provaset_t3p_registers.py | 18 +- src/components/vision.py | 24 +- src/components/vision_saver.py | 17 +- src/test/multicomp.py | 5 +- src/test/neo_lights.py | 6 +- src/ui/archive/archive.py | 16 +- src/ui/autotests_archive/autotests_archive.py | 16 +- src/ui/crud/CopyPastableCrudQTableWidget.py | 5 +- src/ui/crud/crud.py | 19 +- src/ui/editor/editor.py | 9 +- src/ui/steps_management/steps_management.py | 4 +- src/ui/test/test.py | 48 +- src/ui/test_leak/test_leak.ui | 1119 +++++++++-------- src/ui/test_vision/test_vision.py | 5 +- src/ui/users_management/users_management.py | 2 +- 22 files changed, 754 insertions(+), 607 deletions(-) diff --git a/config/machine_settings/defaults.ini b/config/machine_settings/defaults.ini index 3ac4deb..6e689c4 100644 --- a/config/machine_settings/defaults.ini +++ b/config/machine_settings/defaults.ini @@ -9,14 +9,15 @@ exposure_time: 10000 ; vertical_crop_resolution: 2048 ; rotate_90_clockwise times: 0 ; auto_white_balance: off -balance_red: 1.66 +balance_red: 1.85 balance_green: 1.0 -balance_blue: 1.12 +balance_blue: 1.5 [vision_saver] time_format: %Y-%m-%d_%H-%M-%S path: ./data/images minimum_disk_free_space_gb: 20 +resize_resolution: 612x512 [archive_synchronizer] archive_endpoint: https://r5portal.it/api/echo/ @@ -44,6 +45,6 @@ baudrate: 9600 [vision] detection_threshold: 0.5 -neural_network: hs3-20000 +neural_network: hs5-20000 ; recipes_path: ./config/vision_test_recipes diff --git a/config/machine_settings/hostnames.ini b/config/machine_settings/hostnames.ini index ec8cff8..f53d866 100644 --- a/config/machine_settings/hostnames.ini +++ b/config/machine_settings/hostnames.ini @@ -2,3 +2,5 @@ this: this mino: mino DESKTOP-VOCOB38: vm +neodl-MS-7A62: neo-dl +ST-RES-1: stres1 diff --git a/config/vision/labels/labels.pbtxt b/config/vision/labels/labels.pbtxt index 5cc99f0..4ab5798 100644 --- a/config/vision/labels/labels.pbtxt +++ b/config/vision/labels/labels.pbtxt @@ -1,7 +1,7 @@ item { id: 1 name: 'hs-ok' - color: '0x000000' + color: '0x55AA55' } item { id: 2 diff --git a/config/vision/recipes/termorestringente_923578.ini b/config/vision/recipes/termorestringente_923578.ini index a68160e..9f9a83d 100644 --- a/config/vision/recipes/termorestringente_923578.ini +++ b/config/vision/recipes/termorestringente_923578.ini @@ -22,7 +22,7 @@ instruction: CONTROLLARE PRESENZA TERMORESTRINGENTE [markers] [zones] -p1: 800,850 300,100 hs-ok # HEATSINK PRESENT +p1: 1000,500 800,800 hs-ok # HEATSINK PRESENT [labels] -p1: 500,600 120 0xffffffff 0xff000000 4 TERMORESTRINGENTE +p1: 660,1200 120 0xffffffff 0xff000000 4 TERMORESTRINGENTE diff --git a/runme.sh b/runme.sh index bec3ef5..c707543 100755 --- a/runme.sh +++ b/runme.sh @@ -1,4 +1,4 @@ #!/bin/bash -e cd "$(dirname "$0")" source "./venv/bin/activate" || source "./venv/Scripts/activate" || : -python -O "./src/main.py" +python -O "./src/main.py" --no-edgetpu --no-tflite diff --git a/src/components/neo_pixels.py b/src/components/neo_pixels.py index 7481d8a..40e9c1a 100644 --- a/src/components/neo_pixels.py +++ b/src/components/neo_pixels.py @@ -1,6 +1,8 @@ import sys import traceback +from PyQt5.QtCore import QThread + if "--sim-serial" in sys.argv: from components.dummies.serial import serial else: @@ -15,6 +17,7 @@ C_UINT32_MAX = c_uint32(-1).value class NeoPixels(Component): def __init__(self, config=None, name=None): + self.conn = None super().__init__(config=config, name=name, threaded=False) def config_changed(self): @@ -23,6 +26,17 @@ class NeoPixels(Component): self.stopbits = getattr(serial, self.config[self.name].get("stopbits", "stopbits_one").upper()) self.parity = getattr(serial, self.config[self.name].get("parity", "parity_none").upper()) self.bytesize = getattr(serial, self.config[self.name].get("bytesize", "eightbits").upper()) + if self.conn is not None: + self.conn.close() + self.conn = serial.Serial( + self.port, + baudrate=self.baudrate, + stopbits=self.stopbits, + parity=self.parity, + bytesize=self.bytesize, + ) + QThread.msleep(5000) + self.set_all_pixel_color("#000000") def set(self, pixel, color): if type(pixel) is not int or pixel < 0 or pixel > C_UINT32_MAX: @@ -30,18 +44,11 @@ class NeoPixels(Component): if not color.startswith("#") or len(color) != 7: raise ValueError("the color parameter must be in the html hex format: '#RRGGBB'") try: - conn = serial.Serial( - self.port, - baudrate=self.baudrate, - stopbits=self.stopbits, - parity=self.parity, - bytesize=self.bytesize, - ) - conn.write(int.to_bytes(pixel, length=4, byteorder="big") + bytes.fromhex(color[1:7]) + b"\n") - response = conn.readline() - if response != "ok\n": + self.conn.write(int.to_bytes(pixel, length=4, byteorder="big") + bytes.fromhex(color[1:7]) + b"\n") + response = self.conn.readline() + if response != b"ok\n": self.log.error(response.strip()) - conn.close() + # self.conn.close() except serial.serialutil.SerialException: self.log.exception(traceback.format_exc()) diff --git a/src/components/tecna_marposs_provaset_t3p.py b/src/components/tecna_marposs_provaset_t3p.py index 3daee7b..c1c5376 100644 --- a/src/components/tecna_marposs_provaset_t3p.py +++ b/src/components/tecna_marposs_provaset_t3p.py @@ -180,7 +180,7 @@ class TecnaMarpossProvasetT3P(ModbusComponent): "Running test: test type", "Running test: sequence index", ]} - if info["Running test: active phase"] == "END TEST, WAITING THE START OF A NEW TEST": + if info["Running test: active phase"] == "FINE TEST": # "END TEST, WAITING THE START OF A NEW TEST": info.update(self.get_test_results()) self.log.debug(str(info)) super()._get([info]) diff --git a/src/components/tecna_marposs_provaset_t3p_registers.py b/src/components/tecna_marposs_provaset_t3p_registers.py index 071c46a..9ab50ce 100644 --- a/src/components/tecna_marposs_provaset_t3p_registers.py +++ b/src/components/tecna_marposs_provaset_t3p_registers.py @@ -7,7 +7,7 @@ registers = { "Life counter: TOTAL TESTS": [8 - 1, {"dt": "32bit_uint", }], "Real time test pressure output": [11 - 1, {"dt": "32bit_int", "f": 21, }], "Real time differential pressure output": [13 - 1, {"dt": "32bit_int", "f": 22, }], - "Real time pressure line regulator": [15 - 1, {"dt": "16bit_int", "f": 23, }], + "Real time pressure line regulator": [15 - 1, {"dt": "16bit_int", "f": 23, "g": 0.1}], # TODO: REMOVE "g" "Active alarm flags": [16 - 1, {"dt": "16bit_uint", }], # | 15 | 14 | 13 | 12 | 11 | 10 | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | # | | | | A5 | A4 | A3 | A2 | A1 | | | | | | E3 | E2 | E1 | @@ -28,9 +28,9 @@ registers = { "Running test: flow rate format": [27 - 1, {"dt": "16bit_uint", }], # white "Active test program number": [31 - 1, {"dt": "16bit_uint", }], "Running test: active phase": [32 - 1, {"dt": "16bit_uint", "decoding": { - 0: "WAITING START", - 10: "CHECK BARCODE", - 20: "WAITING BARCODE", + 0: "ATTESA START", # "WAITING START", + 10: "CONTROLLO BARCODE", # "CHECK BARCODE", + 20: "ATTESA BARCODE", # "WAITING BARCODE", 30: "START TEST", 40: "START CAGE", 50: "START PLUG", @@ -38,10 +38,10 @@ registers = { 70: "WAIT DELAY ACKNOWLEDGE COMMAND", 80: "WAIT DELAY", 90: "WAIT ACKNOWLEDGE", - 100: "T0 PRE-FILL", - 110: "T1 FILLING", - 120: "T2 SETTLING", - 130: "T3 MEASURE", + 100: "T0 PRE-RIEMPIMENTO", # "T0 PRE-FILL", + 110: "T1 RIEMPIMENTO", # "T1 FILLING", + 120: "T2 ASSESTAMENTO", # "T2 SETTLING", + 130: "T3 MISURA", # "T3 MEASURE", 140: "WAITING OPERATOR RESULT ACKNOWLEDGE", 150: "RESULT PRESENT", 160: "MARKING", @@ -49,7 +49,7 @@ registers = { 180: "END SEQUENCE PROGRAM", 190: "END PLUG", 200: "END CAGE", - 210: "END TEST, WAITING THE START OF A NEW TEST", + 210: "FINE TEST", # WAITING THE START OF A NEW TEST", }, }], "Running test: phase backwards time": [33 - 1, {"dt": "16bit_uint", "f": 26, }], "Running test: filling pressure": [34 - 1, {"dt": "32bit_int", "f": 21, }], diff --git a/src/components/vision.py b/src/components/vision.py index bce2d84..378c0a7 100644 --- a/src/components/vision.py +++ b/src/components/vision.py @@ -314,7 +314,7 @@ class Vision(Component): if tf_mode is None and "normal" in self.allowed_modes: try: # create tensorflow model - model = tf.saved_model.load(str(self.models_dir / model_name)) + model = tf.saved_model.load(str(self.models_dir / model_name)).signatures["serving_default"] tf_mode = "normal" except Exception: self.log.exception(traceback.format_exc()) @@ -339,7 +339,10 @@ class Vision(Component): if self.interpreter is not None and frame.shape != self.interpreter.get_input_details()[0]["shape"][1:3]: tensor = np.expand_dims(cv2.resize(frame, self.interpreter.get_input_details()[0]["shape"][1:3], interpolation=cv2.INTER_LINEAR), axis=0) else: - tensor = np.expand_dims(frame, axis=0) + frame_resized = cv2.resize(frame, (256, 256), interpolation=cv2.INTER_LINEAR) + tensor = tf.convert_to_tensor(np.asarray(frame_resized)) + tensor = tensor[tf.newaxis, ...] + # tensor = np.expand_dims(frame, axis=0) # Run inference if lock: self.lock.lock() @@ -389,25 +392,28 @@ class Vision(Component): detections = self.model(tensor) if lock: self.lock.unlock() + detections = { + "detection_scores": detections["detection_scores"].numpy().tolist(), + "detection_boxes": detections["detection_boxes"].numpy().tolist(), + "detection_classes": detections["detection_classes"], + } # WARNING: results other than the ones related to tensor[-1] will be discarded parsed_detections = [] - for d_box, d_class, d_score in zip( # , d_mask in zip( + for d_score, d_box, d_class in zip( # , d_mask in zip( + detections["detection_scores"][-1], detections["detection_boxes"][-1], detections["detection_classes"][-1], - detections["detection_scores"][-1], # detections["detection_masks"][-1], ): if d_score < self.detection_threshold: continue box = list(d_box) - # box = d_box.numpy().tolist() box = [i * s for i, s in zip(box, frame.shape[:2] * 2)] # rescale detection to frame size detection = { - "class": self.category_index[int(d_class)], - "score": float(d_score), - # "score": d_score.numpy().tolist(), - # "mask": d_mask.numpy().tolist(), + "score": d_score, "box": box, + "class": self.category_index[int(d_class)], + # "mask": d_mask, "center": self.get_center(box), "size": self.get_size(box), } diff --git a/src/components/vision_saver.py b/src/components/vision_saver.py index ea610c6..e5e25ac 100755 --- a/src/components/vision_saver.py +++ b/src/components/vision_saver.py @@ -17,19 +17,30 @@ class VisionSaver(Component): def config_changed(self): self.location = Path(self.config[self.name]["path"]) os.makedirs(self.location, exist_ok=True) + self.resize_resolution = self.config[self.name].get("resize_resolution", None) + if self.resize_resolution is not None: + self.resize_resolution = list(map(int, self.resize_resolution.split("x"))) self.mask_zones = self.config[self.name].get("mask_zones", None) self.minimum_disk_free_space_gb = self.config[self.name].get("minimum_disk_free_space_gb", None) if self.minimum_disk_free_space_gb is not None: self.minimum_disk_free_space_gb = float(self.minimum_disk_free_space_gb) self.time_format = self.config[self.name]["time_format"] - def save(self, save_time, img, mask=True): - timestamp = datetime.fromtimestamp(save_time).strftime(self.time_format) + def save(self, save_time, img, mask=True, resize=True): + if type(save_time) is float: + save_time = int(save_time) + if type(save_time) is int: + save_time = datetime.fromtimestamp(save_time) + if type(save_time) is not datetime: + raise ValueError(f"save_time must be float int or datetime, not {type(save_time)}") + timestamp = save_time.strftime(self.time_format) save_dir = self.location / save_time.strftime("%Y") / save_time.strftime("%m") os.makedirs(save_dir, exist_ok=True) out_path = save_dir / f"{timestamp}.png" self.log.info(f"saving {out_path}") img = cv2.cvtColor(img, cv2.COLOR_RGB2BGR) + if resize and self.resize_resolution is not None: + img = cv2.resize(img, self.resize_resolution, interpolation=cv2.INTER_LINEAR) if mask and self.mask_zones is not None: height, width, channels = img.shape out = np.full( @@ -41,7 +52,7 @@ class VisionSaver(Component): out[zone[1]:zone[3], zone[0]:zone[2]] = img[zone[1]:zone[3], zone[0]:zone[2]] else: out = img - cv2.imwrite(out_path, out) + cv2.imwrite(str(out_path), out) return out_path def remove_older_images_if_needed(self): diff --git a/src/test/multicomp.py b/src/test/multicomp.py index c06d821..9ecf60a 100644 --- a/src/test/multicomp.py +++ b/src/test/multicomp.py @@ -2,8 +2,11 @@ import time import serial +# tester_port="/dev/ttyUSB0" +tester_port = "COM1" + conn = serial.Serial( - "/dev/ttyUSB0", + tester_port, baudrate=115200, stopbits=serial.STOPBITS_ONE, parity=serial.PARITY_NONE, diff --git a/src/test/neo_lights.py b/src/test/neo_lights.py index c9a3394..411b994 100644 --- a/src/test/neo_lights.py +++ b/src/test/neo_lights.py @@ -1,16 +1,20 @@ +import time from ctypes import c_uint32 import serial C_UINT32_MAX = c_uint32(-1).value conn = serial.Serial( - "/dev/ttyACM1", + "COM4", baudrate=9600, stopbits=serial.STOPBITS_ONE, parity=serial.PARITY_NONE, bytesize=serial.EIGHTBITS, + rtscts=False, + xonxoff=False ) pixel = C_UINT32_MAX +time.sleep(5) color = "#ff00ff" conn.write(int.to_bytes(pixel, length=4, byteorder="big") + bytes.fromhex(color[1:7]) + b"\n") response = conn.readline() diff --git a/src/ui/archive/archive.py b/src/ui/archive/archive.py index d706234..13ba81b 100755 --- a/src/ui/archive/archive.py +++ b/src/ui/archive/archive.py @@ -46,19 +46,19 @@ class Archive(Widget): replace_widget(self, "crud_w", self.crud) self.selected = None self.print_b.setEnabled(False) - self.crud.db_tw.setSelectionBehavior(QAbstractItemView.SelectRows) - self.crud.db_tw.setSelectionMode(QAbstractItemView.SingleSelection) - self.crud.db_tw.itemSelectionChanged.connect(self.check) + self.crud().db_tw.setSelectionBehavior(QAbstractItemView.SelectRows) + self.crud().db_tw.setSelectionMode(QAbstractItemView.SingleSelection) + self.crud().db_tw.itemSelectionChanged.connect(self.check) self.print_b.clicked.connect(self.print_label) def check(self): - if not self.crud.modified: - selected = self.crud.get_selected_rows() + if not self.crud().modified: + selected = self.crud().get_selected_rows() if len(selected) == 1: selected = selected[0] - 1 # - 1 because rn starts from 1 (filters line) - if selected >= 0 and selected < len(self.crud.data_index): - selected = self.crud.data_index[selected] - self.selected = self.crud.db.table_model.get_by_id(selected) + if selected >= 0 and selected < len(self.crud().data_index): + selected = self.crud().data_index[selected] + self.selected = self.crud().db.table_model.get_by_id(selected) self.print_b.setEnabled(True) return self.selected = None diff --git a/src/ui/autotests_archive/autotests_archive.py b/src/ui/autotests_archive/autotests_archive.py index 8768564..3d24536 100755 --- a/src/ui/autotests_archive/autotests_archive.py +++ b/src/ui/autotests_archive/autotests_archive.py @@ -46,19 +46,19 @@ class Autotests_Archive(Widget): replace_widget(self, "crud_w", self.crud) self.selected = None self.print_b.setEnabled(False) - self.crud.db_tw.setSelectionBehavior(QAbstractItemView.SelectRows) - self.crud.db_tw.setSelectionMode(QAbstractItemView.SingleSelection) - self.crud.db_tw.itemSelectionChanged.connect(self.check) + self.crud().db_tw.setSelectionBehavior(QAbstractItemView.SelectRows) + self.crud().db_tw.setSelectionMode(QAbstractItemView.SingleSelection) + self.crud().db_tw.itemSelectionChanged.connect(self.check) self.print_b.clicked.connect(self.print_label) def check(self): - if not self.crud.modified: - selected = self.crud.get_selected_rows() + if not self.crud().modified: + selected = self.crud().get_selected_rows() if len(selected) == 1: selected = selected[0] - 1 # - 1 because rn starts from 1 (filters line) - if selected >= 0 and selected < len(self.crud.data_index): - selected = self.crud.data_index[selected] - self.selected = self.crud.db.table_model.get_by_id(selected) + if selected >= 0 and selected < len(self.crud().data_index): + selected = self.crud().data_index[selected] + self.selected = self.crud().db.table_model.get_by_id(selected) self.print_b.setEnabled(True) return self.selected = None diff --git a/src/ui/crud/CopyPastableCrudQTableWidget.py b/src/ui/crud/CopyPastableCrudQTableWidget.py index 298e00b..7709c73 100644 --- a/src/ui/crud/CopyPastableCrudQTableWidget.py +++ b/src/ui/crud/CopyPastableCrudQTableWidget.py @@ -1,4 +1,5 @@ import sys +import weakref from PyQt5.QtCore import QEvent, Qt from PyQt5.QtWidgets import QApplication, QTableWidget @@ -28,7 +29,7 @@ class CopyPastableCrudQTableWidget(QTableWidget): copy_sparse[row] = {} cell = self.cellWidget(row, col) if cell is not None: - copy_sparse[row][col] = cell.parse(row_number=row, crud=self.crud) + copy_sparse[row][col] = cell.parse(row_number=row, crud=weakref.ref(self.crud)) copy_dense = [["" for col in range(max_col - min_col + 1)] for row in range(max_row - min_row + 1)] for row, row_cells in copy_sparse.items(): for col, cell_text in row_cells.items(): @@ -54,7 +55,7 @@ class CopyPastableCrudQTableWidget(QTableWidget): c = min_col + col cell = self.cellWidget(r, c) if cell is not None: - cell.render(cell_text, field_name=self.crud.select[c] if self.crud is not None else None, row_number=r, crud=self.crud) + cell.render(cell_text, field_name=self.crud.select[c] if self.crud is not None else None, row_number=r, crud=weakref.ref(self.crud)) super().keyPressEvent(event) QApplication.sendEvent(self, event) return True diff --git a/src/ui/crud/crud.py b/src/ui/crud/crud.py index 750b0f9..2f0d7f3 100755 --- a/src/ui/crud/crud.py +++ b/src/ui/crud/crud.py @@ -6,7 +6,7 @@ from datetime import datetime from lib.db import Crud_DB from peewee import TextField -from PyQt5.QtCore import QObject, Qt, pyqtSignal +from PyQt5.QtCore import Qt, pyqtSignal from PyQt5.QtWidgets import (QAbstractItemView, QComboBox, QDialog, QGridLayout, QHeaderView, QLineEdit, QMessageBox, QPlainTextEdit, QPushButton) @@ -46,7 +46,12 @@ class Cell: self.field_alias = field_alias self.field = field self.row_number = row_number - self.crud = crud + if crud is None: + self.crud = None + elif type(crud) is weakref.ReferenceType: + self.crud = crud + else: + self.crud = weakref.ref(crud) self.value = None self.is_modified = False self.connected_modified = False @@ -181,7 +186,7 @@ class Json_External_Dialog_Editor_Cell_Widget(QPushButton, Cell): def __init__(self, editor_widget_class, *args, readonly=True, autocomplete=None, field_name=None, field_alias=None, field=None, row_number=None, crud=None, **kwargs): if not issubclass(editor_widget_class, Editor): raise AssertionError(f"editor_widget_class {editor_widget_class!r} must be a subclass of {Editor!r}") - self.editor = editor_widget_class(*args, cell_widget=self, **kwargs) + self.editor = editor_widget_class(*args, cell_widget=weakref.ref(self), **kwargs) super().__init__(u"\u238B modifica") Cell.__init__(self, readonly=readonly, autocomplete=autocomplete, field_name=field_name, field_alias=field_alias, field=field, row_number=row_number, crud=crud) self.dialog = Dialog() @@ -335,7 +340,7 @@ class Crud(Widget): for fn, cn in self.select_index.items(): w = self.db_tw.cellWidget(rn, cn) try: - r[fn] = w._parse(row_number=rn, crud=self) + r[fn] = w._parse(row_number=rn, crud=weakref.ref(self)) except Exception as e: self.log.exception(traceback.format_exc()) self.set_row_color(rn, "red") @@ -427,12 +432,12 @@ class Crud(Widget): r = self.row_upgrader(r, rn, self) for fn, cn in self.select_index.items(): readonly = self.readonly is None or self.readonly is True or (self.readonly is not False and fn in self.readonly) - w = self.widget_classes.get(fn, self.default_widget_class)(readonly=readonly, autocomplete=self.autocomplete.get(fn, None), field_name=fn, field_alias=self.fields_aliases[fn], field=self.db.table_model._meta.fields.get(fn, None), row_number=rn, crud=self) + w = self.widget_classes.get(fn, self.default_widget_class)(readonly=readonly, autocomplete=self.autocomplete.get(fn, None), field_name=fn, field_alias=self.fields_aliases[fn], field=self.db.table_model._meta.fields.get(fn, None), row_number=rn, crud=weakref.ref(self)) w.set_readonly(readonly=w.readonly) w.do_autocomplete(w.autocomplete) w.modified.connect(self.set_modified) if fn in r: - w._render(data=r[fn], row_number=rn, crud=self) + w._render(data=r[fn], row_number=rn, crud=weakref.ref(self)) self.db_tw.setCellWidget(rn, cn, w) # REENABLE EVENTS AFTER UPDATE self.db_tw.blockSignals(False) @@ -452,7 +457,7 @@ class Crud(Widget): self.db_tw.setRowCount(rn + 1) for fn, cn in self.select_index.items(): readonly = self.readonly is None or self.readonly is True or (self.readonly is not False and fn in self.readonly) - w = self.widget_classes.get(fn, self.default_widget_class)(readonly=readonly, autocomplete=self.autocomplete.get(fn, None), field_name=fn, field_alias=self.fields_aliases[fn], field=self.db.table_model._meta.fields.get(fn, None), row_number=rn, crud=self) + w = self.widget_classes.get(fn, self.default_widget_class)(readonly=readonly, autocomplete=self.autocomplete.get(fn, None), field_name=fn, field_alias=self.fields_aliases[fn], field=self.db.table_model._meta.fields.get(fn, None), row_number=rn, crud=weakref.ref(self)) w.set_readonly(readonly=w.readonly) w.do_autocomplete(w.autocomplete) w.modified.connect(self.set_modified) diff --git a/src/ui/editor/editor.py b/src/ui/editor/editor.py index defa791..b9809ca 100644 --- a/src/ui/editor/editor.py +++ b/src/ui/editor/editor.py @@ -1,3 +1,5 @@ +import weakref + from PyQt5.QtCore import QObject from PyQt5.QtWidgets import (QCheckBox, QComboBox, QDoubleSpinBox, QLineEdit, QListWidget, QPlainTextEdit, QRadioButton, @@ -8,7 +10,12 @@ from ui.widget import Widget class Editor(Widget): def __init__(self, cell_widget=None): super().__init__() - self.cell_widget = cell_widget + if cell_widget is None: + self.cell_widget = None + elif type(cell_widget) is weakref.ReferenceType: + self.cell_widget = cell_widget + else: + self.cell_widget = weakref.ref(cell_widget) self.spec = {} def set_readonly(self, readonly=True): diff --git a/src/ui/steps_management/steps_management.py b/src/ui/steps_management/steps_management.py index c17e86c..fde6b0d 100644 --- a/src/ui/steps_management/steps_management.py +++ b/src/ui/steps_management/steps_management.py @@ -22,12 +22,12 @@ class Step_Spec_JEDECW(QPushButton, Cell): super().__init__(u"\u238B modifica") Cell.__init__(self, readonly=readonly, autocomplete=autocomplete, field_name=field_name, field_alias=field_alias, field=field, row_number=row_number, crud=crud) self.update_editor() - self.crud.modified.connect(self.update_editor) + self.crud().modified.connect(self.update_editor) self.dialog.setWindowTitle(self.field_alias) self.clicked.connect(self.dialog.show) def update_editor(self): - self.editor_type = self.crud.db_tw.cellWidget(self.row_number, self.crud.select_index["type"])._parse() + self.editor_type = self.crud().db_tw.cellWidget(self.row_number, self.crud().select_index["type"])._parse() self.editor = self.editors[self.editor_type] if self.dialog.centralWidget is not self.editor: self.dialog.setCentralWidget(self.editor) diff --git a/src/ui/test/test.py b/src/ui/test/test.py index c5533ea..dbf5031 100755 --- a/src/ui/test/test.py +++ b/src/ui/test/test.py @@ -44,7 +44,7 @@ class Test(Widget): "done": Test_Assembly(self.select_step_img("success"), u"COLLAUDO COMPLETATO"), "emergency": Test_Assembly(self.select_step_img("reset_emergency"), u"EMERGENZA INTERVENUTA - RIPRISTINARE PULSANTE E SELEZIONARE \"RESET EMERGENZA\" DAL MEN\u00d9 \"STRUMENTI\""), "fail": Test_Assembly(self.select_step_img("fail"), u"CICLO INTERROTTO"), - "leak": Test_Assembly(None, u"PREMERE START PER COMINCIARE LA PROVA TENUTA E ATTENDERE IL SUO COMPLETAMENTO", Test_Leak(components=self.components, recipe=self.recipe, step=self.step)), + "leak": Test_Assembly(None, u"PREMERE START PER INIZIARE LA PROVA TENUTA", Test_Leak(components=self.components, recipe=self.recipe, step=self.step)), "print": Test_Assembly(self.select_step_img("print"), u"STAMPA ETICHETTA IN CORSO"), "select_recipe": Test_Assembly(None, u"SELEZIONARE IL CODICE DA COLLAUDARE", Recipe_Selection()), "vision": Test_Assembly(None, u"VERIFICARE CONTROLLO CON TELECAMERA", Test_Vision(components=self.components, recipe=self.recipe, step=self.step)), @@ -252,24 +252,32 @@ class Test(Widget): def set_leak(self, leak=None): if "leak" not in self.data: - self.data["leak"] = [] + self.data["leak"] = {} leak["step"] = model_to_dict(self.step) - self.data["leak"].append(leak) + self.data["leak"][len(self.data["leak"])] = leak self.data["overridden"] = self.data["overridden"] or leak.get("overridden", False) self.data["ok"] = self.data["ok"] and leak.get("ok", False) self.next() def set_vision(self, vision=None): if "vision" not in self.data: - self.data["vision"] = [] + self.data["vision"] = {} vision["step"] = model_to_dict(self.step) - self.data["vision"].append(vision) + self.data["vision"][len(self.data["vision"])] = vision self.data["overridden"] = self.data["overridden"] or vision.get("overridden", False) self.data["ok"] = self.data["ok"] and vision.get("ok", False) self.next() def done(self, ok=True): self.log.info("cycle done") + t = self.data.get("vision", {}).get(0, {}).get("time", None) + f = self.data.get("vision", {}).get(0, {}).get("frame", None) + if t is not None and f is not None: + file_path = self.components["vision_saver"].save(t, f) + self.log.info(f"cycle vision image saved locally: {file_path!r}") + for vision in self.data["vision"].values(): + vision.pop("frame", None) + vision.pop("render", None) archived = Archive.archive(self.recipe, self.data, ok and self.data["ok"], overridden=self.data["overridden"]) self.log.info(f"cycle archived locally: {archived!r}") # COUNT OK @@ -279,24 +287,28 @@ class Test(Widget): def print(self, archived=None): self.log.info("cycle print") # LABEL PRINT + datamatrix = str(archived.recipe.part_number) + archived.time.strftime("%m%y") + f"{archived.id:0>5}" + pmax = self.data.get("leak", {}).get(0, {}).get("step", {}).get("spec", {}).get("test_pressure", "-") + pmin = self.data.get("leak", {}).get(0, {}).get("results", {}).get("data", {}).get("Running test: pressure at the end of settling", "-") + leak = self.data.get("leak", {}).get(0, {}).get("results", {}).get("data", {}).get("Running test: measured leak", "-") context = { - "DATAMATRIX": str(self.data.get("barcodes", {}).get("serial", "")), - "DATAMATRIX_TEXT": str(self.data.get("barcodes", {}).get("serial", "-")), - "PIECE_NUM": str(self.data.get("barcodes", {}).get("serial", "-")), - "DATETIME": str(archived.time.isoformat()), + "DATAMATRIX": datamatrix, + "DATAMATRIX_TEXT": datamatrix, + "PIECE_NUM": str(archived.id), + "DATETIME": archived.time.strftime("%d/%m/%Y %H:%M:%S"), "SHIFT": str(get_shift(archived.time)), "STATION": str(self.machine_id), "OPERATOR": str(Users.get_session().user.username), - "PMAX": str(self.data.get("leak", [{}, {}])[0].get("step", {}).get("spec", {}).get("test_pressure", "-")), - "PMIN": str(self.data.get("leak", [{}, {}])[0].get("results", {}).get("data", {}).get("Running test: pressure at the end of settling", "-")), - "LEAK": str(self.data.get("leak", [{}, {}])[0].get("results", {}).get("data", {}).get("Running test: measured leak", "-")), - "TFILL": str(self.data.get("leak", [{}, {}])[0].get("step", {}).get("spec", {}).get("filling_time", "-")), - "TSTAB": str(self.data.get("leak", [{}, {}])[0].get("step", {}).get("spec", {}).get("settling_time", "-")), - "TTEST": str(self.data.get("leak", [{}, {}])[0].get("step", {}).get("spec", {}).get("test_time", "-")), - "MAXLEAK": str(self.data.get("leak", [{}, {}])[0].get("step", {}).get("spec", {}).get("test_pressure_max_delta", "-")), - "PTESTMIN": str(self.data.get("leak", [{}, {}])[0].get("step", {}).get("spec", {}).get("test_pressure_min_delta", "-")), + "PMAX": f"{pmax:.3f}" if type(pmax) is not str else pmax, + "PMIN": f"{pmin:.3f}" if type(pmin) is not str else pmin, + "LEAK": f"{leak:.3f}" if type(leak) is not str else leak, + "TFILL": str(self.data.get("leak", {}).get(0, {}).get("step", {}).get("spec", {}).get("filling_time", "-")), + "TSTAB": str(self.data.get("leak", {}).get(0, {}).get("step", {}).get("spec", {}).get("settling_time", "-")), + "TTEST": str(self.data.get("leak", {}).get(0, {}).get("step", {}).get("spec", {}).get("test_time", "-")), + "MAXLEAK": str(self.data.get("leak", {}).get(0, {}).get("step", {}).get("spec", {}).get("test_pressure_max_delta", "-")), + "PTESTMIN": str(self.data.get("leak", {}).get(0, {}).get("step", {}).get("spec", {}).get("test_pressure_min_delta", "-")), "RESULT_L1": "ESITO" + str(" FORZATO" if self.data.get("overridden", False) else ""), - "RESULT_L2": str("CONFORME" if self.data.get("leak", [{}, {}])[0].get("results", {}).get("ok", False) else "SCARTO"), + "RESULT_L2": str("CONFORME" if self.data.get("leak", {}).get(0, {}).get("results", {}).get("ok", False) else "SCARTO"), } self.components["label_printer"].print_label("EtichettaR5", context=context) self.log.info(f"cycle printed: {context!r}") diff --git a/src/ui/test_leak/test_leak.ui b/src/ui/test_leak/test_leak.ui index 2ed8e36..af9e28f 100644 --- a/src/ui/test_leak/test_leak.ui +++ b/src/ui/test_leak/test_leak.ui @@ -6,505 +6,14 @@ 0 0 - 305 - 459 + 980 + 704 Test Leak - - - - - 0 - 0 - - - - - 0 - 100 - - - - - - - - - 0 - 0 - 0 - - - - - - - 255 - 112 - 114 - - - - - - - 255 - 127 - 127 - - - - - - - 255 - 63 - 63 - - - - - - - 127 - 0 - 0 - - - - - - - 170 - 0 - 0 - - - - - - - 0 - 0 - 0 - - - - - - - 255 - 255 - 255 - - - - - - - 0 - 0 - 0 - - - - - - - 255 - 112 - 114 - - - - - - - 255 - 112 - 114 - - - - - - - 0 - 0 - 0 - - - - - - - 255 - 127 - 127 - - - - - - - 255 - 255 - 220 - - - - - - - 0 - 0 - 0 - - - - - - - 0 - 0 - 0 - - - - - - - - - 0 - 0 - 0 - - - - - - - 255 - 112 - 114 - - - - - - - 255 - 127 - 127 - - - - - - - 255 - 63 - 63 - - - - - - - 127 - 0 - 0 - - - - - - - 170 - 0 - 0 - - - - - - - 0 - 0 - 0 - - - - - - - 255 - 255 - 255 - - - - - - - 0 - 0 - 0 - - - - - - - 255 - 112 - 114 - - - - - - - 255 - 112 - 114 - - - - - - - 0 - 0 - 0 - - - - - - - 255 - 127 - 127 - - - - - - - 255 - 255 - 220 - - - - - - - 0 - 0 - 0 - - - - - - - 0 - 0 - 0 - - - - - - - - - 127 - 0 - 0 - - - - - - - 255 - 112 - 114 - - - - - - - 255 - 127 - 127 - - - - - - - 255 - 63 - 63 - - - - - - - 127 - 0 - 0 - - - - - - - 170 - 0 - 0 - - - - - - - 127 - 0 - 0 - - - - - - - 255 - 255 - 255 - - - - - - - 127 - 0 - 0 - - - - - - - 255 - 112 - 114 - - - - - - - 255 - 112 - 114 - - - - - - - 0 - 0 - 0 - - - - - - - 255 - 0 - 0 - - - - - - - 255 - 255 - 220 - - - - - - - 0 - 0 - 0 - - - - - - - 0 - 0 - 0 - - - - - - - - - 20 - 75 - true - - - - background-color:rgb(255, 112, 114); - - - STOP - - - - - - - - 0 - 0 - - - - - 12 - 75 - true - - - - FORZA ACCETTAZIONE - - - @@ -522,9 +31,9 @@ - 12 - 75 - true + 16 + 50 + false @@ -539,9 +48,9 @@ - 12 - 75 - true + 16 + 50 + false @@ -556,9 +65,9 @@ - 24 - 75 - true + 16 + 50 + false @@ -573,9 +82,9 @@ - 12 - 75 - true + 16 + 50 + false @@ -588,6 +97,24 @@ + + + 130 + 16777215 + + + + + 20 + 50 + false + + + + background-color: rgb(255, 255, 255); +border: 1px solid black; + + - @@ -595,6 +122,24 @@ + + + 130 + 16777215 + + + + + 20 + 50 + false + + + + background-color: rgb(255, 255, 255); +border: 1px solid black; + + - @@ -604,9 +149,9 @@ - 12 - 75 - true + 16 + 50 + false @@ -632,6 +177,24 @@ + + + 130 + 16777215 + + + + + 20 + 50 + false + + + + background-color: rgb(255, 255, 255); +border: 1px solid black; + + - @@ -641,9 +204,9 @@ - 12 - 75 - true + 16 + 50 + false @@ -656,6 +219,13 @@ + + + 16 + 50 + false + + mbar @@ -663,6 +233,13 @@ + + + 16 + 50 + false + + mbar @@ -670,6 +247,13 @@ + + + 16 + 50 + false + + mbar @@ -679,11 +263,16 @@ - 24 - 75 - true + 16 + 50 + false + + background-color: rgb(255, 255, 255); +border: 1px solid black; + + - @@ -691,6 +280,18 @@ + + + 16 + 50 + false + + + + background-color: rgb(255, 255, 255); +border: 1px solid black; + + - @@ -698,6 +299,18 @@ + + + 16 + 50 + false + + + + background-color: rgb(255, 255, 255); +border: 1px solid black; + + - @@ -1231,6 +844,477 @@ + + + + + 0 + 0 + + + + + 0 + 100 + + + + + + + + + 0 + 0 + 0 + + + + + + + 255 + 112 + 114 + + + + + + + 255 + 127 + 127 + + + + + + + 255 + 63 + 63 + + + + + + + 127 + 0 + 0 + + + + + + + 170 + 0 + 0 + + + + + + + 0 + 0 + 0 + + + + + + + 255 + 255 + 255 + + + + + + + 0 + 0 + 0 + + + + + + + 255 + 112 + 114 + + + + + + + 255 + 112 + 114 + + + + + + + 0 + 0 + 0 + + + + + + + 255 + 127 + 127 + + + + + + + 255 + 255 + 220 + + + + + + + 0 + 0 + 0 + + + + + + + 0 + 0 + 0 + + + + + + + + + 0 + 0 + 0 + + + + + + + 255 + 112 + 114 + + + + + + + 255 + 127 + 127 + + + + + + + 255 + 63 + 63 + + + + + + + 127 + 0 + 0 + + + + + + + 170 + 0 + 0 + + + + + + + 0 + 0 + 0 + + + + + + + 255 + 255 + 255 + + + + + + + 0 + 0 + 0 + + + + + + + 255 + 112 + 114 + + + + + + + 255 + 112 + 114 + + + + + + + 0 + 0 + 0 + + + + + + + 255 + 127 + 127 + + + + + + + 255 + 255 + 220 + + + + + + + 0 + 0 + 0 + + + + + + + 0 + 0 + 0 + + + + + + + + + 127 + 0 + 0 + + + + + + + 255 + 112 + 114 + + + + + + + 255 + 127 + 127 + + + + + + + 255 + 63 + 63 + + + + + + + 127 + 0 + 0 + + + + + + + 170 + 0 + 0 + + + + + + + 127 + 0 + 0 + + + + + + + 255 + 255 + 255 + + + + + + + 127 + 0 + 0 + + + + + + + 255 + 112 + 114 + + + + + + + 255 + 112 + 114 + + + + + + + 0 + 0 + 0 + + + + + + + 255 + 0 + 0 + + + + + + + 255 + 255 + 220 + + + + + + + 0 + 0 + 0 + + + + + + + 0 + 0 + 0 + + + + + + + + + 20 + 75 + true + + + + background-color:rgb(255, 112, 114); + + + STOP + + + @@ -1239,6 +1323,13 @@ 0 + + + 12 + 75 + true + + FORZA ACCETTAZIONE diff --git a/src/ui/test_vision/test_vision.py b/src/ui/test_vision/test_vision.py index 3d1ebdf..6d3c7db 100644 --- a/src/ui/test_vision/test_vision.py +++ b/src/ui/test_vision/test_vision.py @@ -86,15 +86,12 @@ class Test_Vision(Test_Test): def save_last(self): if self.last is None: return + self.components["vision_saver"].save(self.last["time"], self.last["frame"]) def emit_ok(self): - self.last.pop("frame", None) - self.last.pop("render", None) super().emit_ok() self.components["neo_pixels"].set_all_pixel_color("#000000") def emit_ko(self): - self.last.pop("frame", None) - self.last.pop("render", None) super().emit_ko() self.components["neo_pixels"].set_all_pixel_color("#000000") diff --git a/src/ui/users_management/users_management.py b/src/ui/users_management/users_management.py index d4ca05d..ab54004 100644 --- a/src/ui/users_management/users_management.py +++ b/src/ui/users_management/users_management.py @@ -69,7 +69,7 @@ class Users_Management(Widget): user = Users.generate(username=row["username"], password=row["password"], roles=row["roles"]) except AssertionError as e: self.log.exception(traceback.format_exc()) - crud.set_row_color(row_number, "red") + crud().set_row_color(row_number, "red") QMessageBox.critical(None, "Errore Salvataggio DB", f"Errore alla riga {row_number}:\n{str(e)}") return False, None, True row.update(user)