diff --git a/config/machine_settings/defaults.ini b/config/machine_settings/defaults.ini index a66eab0..4bdc4ca 100644 --- a/config/machine_settings/defaults.ini +++ b/config/machine_settings/defaults.ini @@ -31,7 +31,7 @@ platform: cups printer: -[tecna_marposs_provaset_t3] +[tecna_t3] address: COM3 baudrate: 115200 diff --git a/simulate.sh b/simulate.sh index 9afb7ff..867bb63 100755 --- a/simulate.sh +++ b/simulate.sh @@ -18,6 +18,7 @@ python -B -u "./src/main.py" \ --no-autotest \ --no-edgetpu \ --no-gpu \ +--panel \ --sim-camera \ --sim-modbus \ --sim-os-label-printer \ diff --git a/src/components/tecna_marposs_provaset_t3.py b/src/components/tecna_marposs_provaset_t3.py index 63075cf..08e2a67 100644 --- a/src/components/tecna_marposs_provaset_t3.py +++ b/src/components/tecna_marposs_provaset_t3.py @@ -105,22 +105,45 @@ class TecnaMarpossProvasetT3(ModbusComponent): ]} if info["Running test: type of test"] is None: info.update(self.get_test_results()) - self.start_test() self.log.debug(str(info)) super()._get([info]) - def start_test(self): - self.log.info("starting test") - self.write("Start testing", 1) + def start_test(self, table=1): + self.log.info(f"starting test table {table!r}") + self.write("Start testing", table) def stop_test(self): - self.log.warining("stopping test") + self.log.warning("stopping test") self.write("Stop the test in progress", 0) def get_test_results(self): + self.log.info("getting test results") return {r: self.read(r) for r in [ "Running test: measured leak", "Running test: calculated leak flow rate", "Running test: calculate RVP%", "Running test: result", ]} + + def write_recipe(self, recipe, table=1): + # "pressure_ramp": self.pressure_ramp_sb, + # "stabilization_cycles": self.stabilization_cycles_sb, + recipe = { + "Type of test": "LEAK TEST", + # "Test flags": recipe.spec[""], + "T0 - Pre-filling time": recipe.spec["pre_filling_time"], + "P0 - Pre-filling pressure": recipe.spec["pre_filling_pressure"], + "T1 - Filling time": recipe.spec["filling_time"], + "T2 - Settling time": recipe.spec["settling_time"], + "PR%- Lower tolerance on pressure / P- Minimum pressure": recipe.spec["settling_pressure_min_percent"], + "PR%+ Superior tolerance on pressure / P+ Pressure max": recipe.spec["settling_pressure_max_percent"], + "T3 - Measure time": recipe.spec["test_time"], + "Q- Leak limit": recipe.spec["test_pressure_min_delta"], + "PREL - Nominal test pressure": recipe.spec["test_pressure"], + "Q+ Upper limit": recipe.spec["test_pressure_max_delta"], + "FST - discharge time": recipe.spec["flush_time"], + "FSL - discharge limit": recipe.spec["flush_pressure"], + } + self.log.debug(str(recipe)) + for register, value in recipe.items(): + self.write(register, value) diff --git a/src/components/tecna_marposs_provaset_t3_registers.py b/src/components/tecna_marposs_provaset_t3_registers.py index 08d22f5..c22de06 100644 --- a/src/components/tecna_marposs_provaset_t3_registers.py +++ b/src/components/tecna_marposs_provaset_t3_registers.py @@ -56,7 +56,7 @@ registers = { 4: "not used", 5: "BLOCKAGE TEST PASSED", 100: "LEAK TEST FAILED - UPPER LIMIT", - 101: "LEAK TEST – FAILED ANOMALY", + 101: "LEAK TEST - FAILED ANOMALY", 102: "LEAK TEST - MAXIMUM LEAK FAILED", 103: "BURST - BREAKAGE PRESSURE DEFLECTION", 104: "VOLUMETRIC CONTROL - RVP% FAILED", @@ -65,14 +65,14 @@ registers = { 107: "BLOCKAGE - MAX PRESSURE FAILED", 108: "BLOCKAGE - MIN PRESSURE FAILED", 109: "BURST - MINIMUM PRESSURE FAILED", - 200: "LEAK TEST FAILED – PR% PRESSURE MINUM", + 200: "LEAK TEST FAILED - PR% PRESSURE MINUM", 201: "LEAK TEST FAILED - PR% PRESSURE MAX", - 202: "LEAK TEST FAILED – P0% PRESSURE MINUM", + 202: "LEAK TEST FAILED - P0% PRESSURE MINUM", 203: "LEAK TEST FAILED - P0% PRESSURE MAX", 204: "ERROR - INTERNAL ALARMS", 205: "ERROR - RELATIVE PRESSURE OUT OF RANGE", 206: "ERROR - DIFFERENTIAL PRESSURE OUT OF RANGE", - 207: "ERROR – PRE-FILLING VALVE NOT OPENED", + 207: "ERROR - PRE-FILLING VALVE NOT OPENED", 250: "TEST ABORTED", }, }], "Running test: type of test": [47, {"dt": "16bit_uint", "decoding": { @@ -130,13 +130,20 @@ registers = { # 0= Only passed # 1= Only failed # 2=All - "AUTOMATION: Marking – driving time": [624, {"dt": "16bit_uint", }], + "AUTOMATION: Marking - driving time": [624, {"dt": "16bit_uint", }], # Format: x.x seconds - "Type of test": [701, {"dt": "16bit_uint", }], - # 1=LEAK TEST - # 2=BLOCKAGE TEST - # 3=LEAK TEST WITH VOLUME - # 4=BURST TEST + "Selection number of test table on which you intend to work": [700, {"dt": "16bit_uint", }], + "Type of test": [701, {"dt": "16bit_uint", "encoding": { + "LEAK TEST": 1, + "BLOCKAGE TEST": 2, + "LEAK TEST WITH VOLUME": 3, + "BURST TEST": 4, + }, "decoding": { + 1: "LEAK TEST", + 2: "BLOCKAGE TEST", + 3: "LEAK TEST WITH VOLUME", + 4: "BURST TEST", + }, }], "Test flags": [702, {"dt": "16bit_uint", }], # T0/Pr: Phase filling mode T0 0= TIME 1= PRESSURE # T1/Pr: Phase filling mode T1 0= TIME 1=PRESSURE @@ -149,39 +156,39 @@ registers = { # AT: 0=Tare pressure disabled 1=Tare pressure enabled # HR: 0=Resolution on loss 1 Pa 1=Resolution on loss 0.1 Pa (models with full scale <= 2 bar) # CH: Selected test channel (2-channel T3P2C model only) - "T0 – Pre-filling time": [704, {"dt": "16bit_uint", "f": 26, }], - "P0 – Pre-filling pressure": [705, {"dt": "16bit_uint", "f": 23, }], + "T0 - Pre-filling time": [704, {"dt": "16bit_uint", "f": 26, }], + "P0 - Pre-filling pressure": [705, {"dt": "16bit_uint", "f": 23, }], # In order to use a negative (vacuum) value, this parameter must however be written positively, but the P0- bit must # also be selected in register 702 - "T1 – Filling time": [706, {"dt": "16bit_uint", "f": 26, }], - "T2 – Settling time": [707, {"dt": "16bit_uint", "f": 26, }], - "T3 – Measure time": [708, {"dt": "16bit_uint", "f": 26, }], - "PREL – Nominal test pressure": [709, {"dt": "16bit_uint", "f": 23, }], + "T1 - Filling time": [706, {"dt": "16bit_uint", "f": 26, }], + "T2 - Settling time": [707, {"dt": "16bit_uint", "f": 26, }], + "T3 - Measure time": [708, {"dt": "16bit_uint", "f": 26, }], + "PREL - Nominal test pressure": [709, {"dt": "16bit_uint", "f": 23, }], # To set a negative pressure (vacuum) this parameters must be written in absolute value and then set # bit Pr- in register 702 - "Pxx Minimum Pressure": [710, {"dt": "16bit_uint", }], + "PR%- Lower tolerance on pressure / P- Minimum pressure": [710, {"dt": "16bit_uint", }], # PR%- Lower tolerance on pressure: LEAK: Format: x.x % # P- Minimum pressure: BLOCKAGE: Format as indicated in register 23 # PR- Minimum pressure%: BURST: Format x.x% - "Q + Upper limit": [711, {"dt": "16bit_uint", "f": 22, }], + "Q+ Upper limit": [711, {"dt": "16bit_uint", "f": 22, }], # Format as indicated in the register 22 "Q- Leak limit": [712, {"dt": "16bit_uint", "f": 22, }], # Format as indicated in the register 22 - "FST –discharge time": [713, {"dt": "16bit_uint", "f": 26, }], + "FST - discharge time": [713, {"dt": "16bit_uint", "f": 26, }], # Format as indicated in the register 26 - "VP – Equivalent volume": [714, {"dt": "16bit_uint", "f": 25, }], + "VP - Equivalent volume": [714, {"dt": "16bit_uint", "f": 25, }], # Format as indicated in the register 25 "P% Pressure tolerance": [717, {"dt": "16bit_uint", }], # BLOCKAGE: Format x.x % - "PB – Minimum burst pressure": [743, {"dt": "16bit_uint", "f": 23, }], + "PB - Minimum burst pressure": [743, {"dt": "16bit_uint", "f": 23, }], # BURST: Format as indicated in the register 23 - "BD – Delta burst": [744, {"dt": "16bit_uint", "f": 23, }], + "BD - Delta burst": [744, {"dt": "16bit_uint", "f": 23, }], # BURST: Format as indicated in the register 23 - "FSL – discharge limit": [745, {"dt": "16bit_uint", "f": 23, }], + "FSL - discharge limit": [745, {"dt": "16bit_uint", "f": 23, }], # Format as indicated in the register 23 - "PR% + Superior tolerance on pressure": [747, {"dt": "16bit_uint", }], - # LEAK: Format: x.x % - # P + Pressure max BURST: Format as indicated in the register 23 + "PR%+ Superior tolerance on pressure / P+ Pressure max": [747, {"dt": "16bit_uint", }], + # PR% + Superior tolerance on pressure: LEAK: Format: x.x % + # P + Pressure max: BURST: Format as indicated in the register 23 "RVP%: volumetric ratio": [750, {"dt": "16bit_uint", }], # VOLUME+LEAK: Format x.xx (from 0.00 to 649.99) "RVP%: max tolerance": [751, {"dt": "16bit_uint", }], diff --git a/src/lib/db/__init__.py b/src/lib/db/__init__.py index 6d8ae33..6f64acf 100644 --- a/src/lib/db/__init__.py +++ b/src/lib/db/__init__.py @@ -64,24 +64,26 @@ Recipes.replace(id=0, name="TEST", spec={ "client": "TEST_CLIENT", "part_number": "TEST_PART_NUMBER", "station": "TEST_STATION", - # pressure - "pressure_min": 0, - "pressure_test": 1, - "pressure_max": 2, - "pressure_ramp": 3, - # test - "cleaning_time": 4, - "tolerance": 5, - "test_duration": 6, - "flush_duration": 7, - # stabilizarion - "stabilization_time": 8, - "stabilization_level_min": 9, - "stabilization_level_max": 10, - "stabilization_settling_time": 11, - "stabilization_cycles": 12, - # description "description": "TEST_DESCRIPTION", + # pre-filling + "pre_filling_time": 5, + "pre_filling_pressure": 3000, + # filling + "filling_time": 5, + "settling_time": 10, + "settling_pressure_min_percent": 20, + "settling_pressure_max_percent": 20, + # test + "test_time": 20, + "test_pressure_min_delta": 3.00, + "test_pressure": 3000, + "test_pressure_max_delta": 0.25, + "cycles": 1, + # flush + "flush_time": 2, + "flush_pressure": 5, + # vision + "vision_recipe": 0, }, archived=False).execute() if True: diff --git a/src/main.py b/src/main.py index 3503712..45f326b 100644 --- a/src/main.py +++ b/src/main.py @@ -84,7 +84,7 @@ try: "label_printer": {"c": Os_Label_Printer, "t": False}, "neo_pixels": {"c": NeoPixels, "t": False}, "remote_api": {"c": RemoteAPI, "k": {"main": self}}, - "tecna_marposs_provaset_t3": {"c": TecnaMarpossProvasetT3}, + "tecna_t3": {"c": TecnaMarpossProvasetT3, "k": {"paused": True}}, "vision_saver": {"c": VisionSaver, "t": False}, "vision": {"c": Vision, "k": {"paused": True}}, } diff --git a/src/ui/__init__.py b/src/ui/__init__.py index 2153c8f..8960c4a 100644 --- a/src/ui/__init__.py +++ b/src/ui/__init__.py @@ -14,6 +14,8 @@ from .test import Test from .test_admin_permission import Test_Admin_Permission from .test_autotest import Test_Autotest from .test_home import Test_Home +from .test_leak import Test_Leak +from .test_test import Test_Test from .test_vision import Test_Vision from .users_management import Users_Management from .widget import Widget diff --git a/src/ui/dialog/dialog.py b/src/ui/dialog/dialog.py index a38fa38..6f5ebcc 100644 --- a/src/ui/dialog/dialog.py +++ b/src/ui/dialog/dialog.py @@ -12,8 +12,8 @@ dialogs = {} class Dialog(QDialog): _closing = pyqtSignal() - def __init__(self): - super().__init__() + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) global dialogs dialogs[id(self)] = self self.setAttribute(Qt.WA_DeleteOnClose) diff --git a/src/ui/recipe_editor/recipe_editor.py b/src/ui/recipe_editor/recipe_editor.py index a584f82..66b1bc0 100644 --- a/src/ui/recipe_editor/recipe_editor.py +++ b/src/ui/recipe_editor/recipe_editor.py @@ -1,5 +1,5 @@ from PyQt5.QtWidgets import (QCheckBox, QComboBox, QDoubleSpinBox, QLineEdit, - QRadioButton, QSpinBox, QPlainTextEdit) + QPlainTextEdit, QRadioButton, QSpinBox) from ui.widget import Widget @@ -11,26 +11,26 @@ class Recipe_Editor(Widget): "client": self.client_le, "part_number": self.part_number_le, "station": self.station_le, - # pressure - "pressure_min": self.pressure_min_sb, - "pressure_test": self.pressure_test_sb, - "pressure_max": self.pressure_max_sb, - "pressure_ramp": self.pressure_ramp_sb, + "description": self.description_pte, + # pre-filling + "pre_filling_time": self.pre_filling_time_sb, + "pre_filling_pressure": self.pre_filling_pressure_sb, + # filling + "filling_time": self.filling_time_sb, + "settling_time": self.settling_time_sb, + "settling_pressure_min_percent": self.settling_pressure_min_percent_sb, + "settling_pressure_max_percent": self.settling_pressure_max_percent_sb, # test - "cleaning_time": self.cleaning_time_sb, - "tolerance": self.tolerance_sb, - "test_duration": self.test_duration_sb, - "flush_duration": self.flush_duration_sb, + "test_time": self.test_time_sb, + "test_pressure_min_delta": self.test_pressure_min_delta_sb, + "test_pressure": self.test_pressure_sb, + "test_pressure_max_delta": self.test_pressure_max_delta_sb, + "cycles": self.cycles_sb, + # flush + "flush_time": self.flush_time_sb, + "flush_pressure": self.flush_pressure_sb, # vision "vision_recipe": self.vision_recipe_cb, - # stabilizarion - "stabilization_time": self.stabilization_time_sb, - "stabilization_level_min": self.stabilization_level_min_sb, - "stabilization_level_max": self.stabilization_level_max_sb, - "stabilization_settling_time": self.stabilization_settling_time_sb, - "stabilization_cycles": self.stabilization_cycles_sb, - # description - "description": self.description_pte, } def set_readonly(self, readonly): diff --git a/src/ui/recipe_editor/recipe_editor.ui b/src/ui/recipe_editor/recipe_editor.ui index 222a36e..d8a3016 100644 --- a/src/ui/recipe_editor/recipe_editor.ui +++ b/src/ui/recipe_editor/recipe_editor.ui @@ -6,66 +6,42 @@ 0 0 - 494 - 733 + 845 + 696 - - - - Descrizione - - - - - - - - - Test + Pre-Riempimento - - - - - - - - - Tempo di prova + + + 9999 - - - - Tempo di scarico - - - - - - - + + + 9999 + + - Tempo di pulizia + Tempo - bar + mbar @@ -76,31 +52,290 @@ - - - - s - - - - - - - s - - - - Tolleranza + Pressione - + + + + Riempimento + + + + + + Soglia min + + + + + + + Tempo + + + + + + + s + + + + + + + 100 + + + + + + + 9999 + + + + + + + Soglia max + + + + + + + 100 + + + + + + + % + + + + + + + % + + + + + + + Assestamento + + + + + + + 9999 + + + + + + + s + + + + + + + + + + Test + + + + + + Cicli + + + + + + + 0 + + + 9999 + + + + + + + s + + + + + + + 9999 + + + + + + + 9999 + + + + + + + mbar + + + + + + + mbar + + + + + + + Delta min + + + + + + + Delta max + + + + + + + Tempo + + + + + + + mbar + + + + + + + 9999 + + + + + + + Pressione + + + + + + + 9999 + + + + + + + + + + Scarico + + + + + + Tempo + + + + + + + Pressione + + + + + + + 9999 + + + + + + + 9999 + + + + + + + s + + + + + + + mbar + + + + + + + + + + Visione + + + + + + Ricetta visione + + + + + + + + + + Ricetta @@ -120,12 +355,6 @@ - - - - - - @@ -133,191 +362,26 @@ - - - - - - - - - - Stabilizzazione - - - - - - Soglia max - - - - - - - Soglia min - - - - - - - - - - - - - + - - - - - - Tempo assestamento - - - - - - - Numero di cicli - - - - - - - Tempo - - - - - - - s - - - - - - - % - - - - - - - % - - - - - - - s - - - - - - - - - - Pressione - - - - - - - - - Min - - - - - - - Max - - - - - - - - - - bar - - - - - - - - - - - - - Test - - - - - - - bar - - - - - - - Rampa di salita - - - - - - - bar - - - - - - - bar/min - - - - - - - - - - Visione - - - - - - Ricetta visione - - + - + + + + + + Descrizione + + + + + + + diff --git a/src/ui/test/test.py b/src/ui/test/test.py index 36789bd..612252d 100755 --- a/src/ui/test/test.py +++ b/src/ui/test/test.py @@ -9,6 +9,7 @@ from ui.helpers import replace_widget from ui.recipe_selection import Recipe_Selection from ui.test_assembly import Test_Assembly from ui.test_autotest import Test_Autotest +from ui.test_leak import Test_Leak from ui.test_vision import Test_Vision from ui.widget import Widget @@ -39,13 +40,13 @@ class Test(Widget): "done": Test_Assembly(self.select_step_img("success"), u"COLLAUDO COMPLETATO - RIMUOVERE IL SENSORE"), "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 - RIMUOVERE IL SENSORE"), + "run_test": Test_Assembly(self.select_step_img("wait"), u"ESECUZIONE TEST IN CORSO - ATTENDERE", Test_Leak(components=self.components, recipe=self.recipe)), "select_recipe": Test_Assembly(None, u"SELEZIONARE IL CODICE DA COLLAUDARE", Recipe_Selection()), - "vision": Test_Assembly(None, u"ESEGUIRE PROCEDURA DI AUTOTEST", Test_Vision(self.components, None)), + "vision": Test_Assembly(None, u"ESEGUIRE PROCEDURA DI AUTOTEST", Test_Vision(components=self.components, recipe=self.recipe)), "wait": Test_Assembly(self.select_step_img("wait"), u"ATTENDERE - PAUSA INTER CICLO"), } - self.cycle_loop = ["vision", "done", "wait"] + self.cycle_loop = ["run_test", "vision", "done", "wait"] self.cycle_index = -1 - self.cycle_changing_state = False # SETUP AUTOTEST self.autotest_request = False if "--no-autotest" not in sys.argv: @@ -54,23 +55,25 @@ class Test(Widget): else: self.autotest_period = None # INIT TEST DATA - self.data = None + self.data = {} # INIT PIECES COUNTER ([pieces_ok, pieces_failed]) self.pieces = [0, 0] # CONNECT CYCLE CONTROLS self.cancel_b.clicked.connect(self.fail_cycle) self.change_recipe_b.clicked.connect(self.change_recipe) for w in self.cycle_states.values(): - if hasattr(w, "ko"): - w.ko.connect(self.fail_cycle) if hasattr(w, "ok"): # custom ok handlers should call next again if type(w.widget) is Recipe_Selection: w.ok.connect(self.set_recipe) + elif type(w.widget) is Test_Leak: + w.ok.connect(self.set_leak) elif type(w.widget) is Test_Vision: w.ok.connect(self.set_vision) else: w.ok.connect(self.next) + if hasattr(w, "ko"): + w.ko.connect(self.fail_cycle) # TESTING if "--test" in sys.argv: self.testing = True @@ -78,6 +81,9 @@ class Test(Widget): self.testing = False # /TESTING # START CYCLE + self.next_timer = QTimer() + self.next_timer.setSingleShot(True) + self.next_timer.timeout.connect(self.next) self.next() def refresh_time(self, init=False): @@ -113,90 +119,6 @@ class Test(Widget): def setCentralWidget(self, widget): replace_widget(self, "centralWidget", widget) - # def check_next(self, interpreted): - # self.disconnect(self.watched) - # if "digital_io" in interpreted: - # if interpreted["digital_io"]["emergency"] is True and self.cycle_state != "emergency": - # self.cycle_state = "emergency" - # self.next() - # elif interpreted["digital_io"]["emergency"] is False and self.cycle_state == "emergency" and self.is_first_cycle_time: - # self.is_first_cycle_time = False - # # reset cycle - # if self.recipe is None: - # self.cycle_state = -1 # go to recipe selection - # else: - # self.cycle_state = 0 # skip recipe selection - # self.next(fail=True) - # if type(self.centralWidget()) is not Test_Autotest: # IGNORE DURING AUTOTEST - # if self.cycle_state == 1: # WAIT FOR PIECE PRESENCE - # if self.testing is True and self.is_first_cycle_time: - # self.bench.inputs["io"].set_bit(*self.bench.parsers["digital_io"].presence1, True) - # self.bench.inputs["io"].set_bit(*self.bench.parsers["digital_io"].presence2, True) - # # /TESTING - # if self.is_first_cycle_time: - # self.is_first_cycle_time = False - # log_msg("cycle state:", self.cycle_state, "done", msg_type="test") - # if "digital_io" in interpreted and interpreted["digital_io"]["presence1"] and interpreted["digital_io"]["presence2"]: - # self.bench.parsers["digital_io"].handler({"lock": True}) - # self.next() - # elif self.cycle_state == 2: # PIECE INSERTED, LOCK PIECE AFTER DELAY - # if self.is_first_cycle_time: - # self.is_first_cycle_time = False - # log_msg("cycle state:", self.cycle_state, "done", msg_type="test") - # self.timer.start(2000) - # elif self.cycle_state == 4: # CAMERA TEST & BARCODE READ DONE, START ASSEMBLY PHASE - # if self.is_first_cycle_time: - # self.is_first_cycle_time = False - # log_msg("cycle state:", self.cycle_state, "done", msg_type="test") - # self.timer.start(2000) - # elif self.cycle_state == 5: # WAIT FOR SCREWDRIVER COUNTS - # if self.is_first_cycle_time and self.bench.parsers["pick_to_light"].request is None: - # self.bench.parsers["pick_to_light"].handler({ - # "vtfe10": self.recipe.vtfe10, - # "vtfe11": self.recipe.vtfe11, - # "vtfe13": self.recipe.vtfe13, - # }) - # log_msg("SCREWDRIVER ENABLE", msg_type="test") - # elif "pick_to_light" in interpreted and interpreted["pick_to_light"] is not None: - # screws = [interpreted["pick_to_light"][k] for k in ["vtfe10", "vtfe11", "vtfe13"]] - # needed = [self.bench.parsers["pick_to_light"].request[k] for k in ["vtfe10", "vtfe11", "vtfe13"]] - # done = all([screwed >= requested for screwed, requested in zip(screws, needed)]) - # self.screw_text(screws) - # if self.is_first_cycle_time and done: - # self.is_first_cycle_time = False - # log_msg("cycle state:", self.cycle_state, "done", msg_type="test") - # self.bench.parsers["pick_to_light"].handler(None) - # self.screw_text([0, 0, 0]) - # self.bench.parsers["digital_io"].handler({"lock": False}) - # self.next() - # elif self.cycle_state == 6: - # # TESTING - # if self.testing is True: - # self.bench.inputs["io"].set_bit(*self.bench.parsers["digital_io"].presence1, False) - # self.bench.inputs["io"].set_bit(*self.bench.parsers["digital_io"].presence2, False) - # # /TESTING - # if not self.is_done: - # self.is_done = True - # self.done(True) - # if self.is_done and "digital_io" in interpreted and not interpreted["digital_io"]["presence1"] and not interpreted["digital_io"]["presence2"] and self.is_first_cycle_time: - # self.is_first_cycle_time = False - # log_msg("cycle state:", self.cycle_state, "done", msg_type="test") - # if self.skip: - # # reset cycle - # self.cycle_state = 0 # skip recipe selection - # self.skip = False - # self.next() - # else: - # self.timer.start(2000) - # elif self.cycle_state == 7: # WAITING BEFORE NEW CYCLE - # if self.is_first_cycle_time: - # self.is_first_cycle_time = False - # log_msg("cycle state:", self.cycle_state, "wait", msg_type="test") - # # reset cycle - # self.cycle_state = 0 # skip recipe selection - # self.timer.start(6000) - # self.watched = self.bench.interpreter.update.connect(self.check_next) - def request_autotest(self, reason): # you can cancel the request calling request_autotest(False) self.log.info(f"cycle request autotest: reason: {reason!r} autotest_request: {self.autotest_request!r}") if reason == "init": @@ -219,11 +141,10 @@ class Test(Widget): if action == "change_recipe": self.log.info(f"cycle next: action: {action!r}") self.set_recipe(recipe=None) - self.cycle_changing_state = True self.cycle_state = "select_recipe" self.cycle_index = -1 # RESET TEST DATA - self.data = None + self.data.clear() elif action == "fail": self.log.info(f"cycle next: action: {action!r}") if self.cycle_state in self.cycle_loop: @@ -231,32 +152,29 @@ class Test(Widget): # COUNT FAIL self.pieces[1] += 1 # FAIL AND RESTART TEST - self.cycle_changing_state = True self.cycle_state = "fail" self.cycle_index = -1 # RESET TEST DATA - self.data = None + self.data.clear() elif action is not None: raise NotImplementedError(f"cycle next: action {action!r} is not a valid action") # if action did not set the next cycle_state # set next cycle_state normally - if not self.cycle_changing_state: - self.cycle_changing_state = True - if self.recipe is None: - # if recipe not set: select_recipe - self.cycle_state = "select_recipe" + if self.recipe is None: + # if recipe not set: select_recipe + self.cycle_state = "select_recipe" + else: + if self.cycle_index == -1 and self.autotest_request is not False: + # if cycle_loop is not started or has ended + # and autotest was requested + self.autotest_request = False + self.cycle_state = "autotest" + if self.autotest_period is not None: # reset periodic autotest timer + self.time_timer.start(self.autotest_period) else: - if self.cycle_index == -1 and self.autotest_request is not False: - # if cycle_loop is not started or has ended - # and autotest was requested - self.autotest_request = False - self.cycle_state = "autotest" - if self.autotest_period is not None: # reset periodic autotest timer - self.time_timer.start(self.autotest_period) - else: - # goto next step in cycle_loop - self.cycle_index = (self.cycle_index + 1) % len(self.cycle_loop) - self.cycle_state = self.cycle_loop[self.cycle_index] + # goto next step in cycle_loop + self.cycle_index = (self.cycle_index + 1) % len(self.cycle_loop) + self.cycle_state = self.cycle_loop[self.cycle_index] # enable/disable cycle controls self.change_recipe_b.setEnabled(self.recipe is not None) self.cancel_b.setEnabled(self.cycle_state not in { @@ -268,16 +186,18 @@ class Test(Widget): self.log.info(f"cycle next: next cycle_state: {self.cycle_state!r}") # INIT TEST DATA IF STARTING CYCLE LOOP if self.cycle_index == 0: - self.data = {} - if self.cycle_state == "done": - self.done() + self.data.clear() w = self.cycle_states[self.cycle_state] if hasattr(w, "start"): - w.start() + w.start(recipe=self.recipe) + self.setCentralWidget(w) + if self.cycle_state == "done": + self.done() + self.next_timer.start(2000) + elif self.cycle_state == "wait": + self.next_timer.start(6000) # UPDATE PIECES DISPLAY self.pieces_count_l.setText(f"{self.pieces[0]} OK / {self.pieces[1]} NOK / {sum(self.pieces)} TOT") - self.setCentralWidget(w) - self.cycle_changing_state = False def set_recipe(self, recipe=None): self.recipe = recipe @@ -296,7 +216,13 @@ class Test(Widget): self.data["ok"] = self.data.get("ok", True) and self.data["vision"].get("ok", False) self.next() - def done(self, ok=False): + def set_leak(self, leak=None): + self.data["leak"] = leak + self.data["overridden"] = self.data.get("overridden", False) or self.data["leak"].get("overridden", False) + self.data["ok"] = self.data.get("ok", True) and self.data["leak"].get("ok", False) + self.next() + + def done(self, ok=False, next=False): self.log.info("cycle done") 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}") diff --git a/src/ui/test_leak/__init__.py b/src/ui/test_leak/__init__.py new file mode 100644 index 0000000..cf34f7f --- /dev/null +++ b/src/ui/test_leak/__init__.py @@ -0,0 +1 @@ +from .test_leak import Test_Leak diff --git a/src/ui/test_leak/test_leak.py b/src/ui/test_leak/test_leak.py new file mode 100644 index 0000000..01a73d0 --- /dev/null +++ b/src/ui/test_leak/test_leak.py @@ -0,0 +1,55 @@ +from ui.test_test import Test_Test + + +class Test_Leak(Test_Test): + def start(self, recipe=None): + super().start(recipe=recipe) + # setup test loop + self.components["tecna_t3"].write_recipe(self.recipe) + self.get_connection = self.components["tecna_t3"].out.connect(self.get) + self.components["tecna_t3"].resume() + self.components["tecna_t3"].start_test() + + def stop(self): + # disable tes loop + self.components["tecna_t3"].stop_test() + self.components["tecna_t3"].pause() + self.disconnect(self.get_connection) + super().stop() + + def get(self, data=None, override=False): + if self.ok_timer.isActive(): + # avoid proccessing if vision was completed + return + if data is None or data[-1] is None: + super().get(None, override=override) + return + data = data[-1] + super().get([{ + "time": data.get("time", None), + "results": { + "ok": type(data["tecna_t3"]["Running test: result"]) is str and "passed" in data["tecna_t3"]["Running test: result"].lower(), + "result": data["tecna_t3"]["Running test: result"], + "data": data["tecna_t3"], + }, + }], override=override) + + def visualize(self, data=None): + if data is None: + data = {} + d = data.get("results", {}).get("data", {}) + for register, label in { + "Instrument status: active phase": self.test_phase_l, + "Test circuit pressure, in real time": self.circuit_pressure_l, + "Measured leak, in real time": self.leak_l, + "Regulated pressure, in real time": self.regulated_pressure_l, + # "Active alarm flags": self._l, + "Running test: type of test": self.test_type_l, + "Testing in progress: progressive sequence index": self.sequence_index_l, + }.items(): + label.setText(str(d.get(register, "-"))) + super().visualize(data) + + def save_last(self): + if self.last is None: + return diff --git a/src/ui/test_leak/test_leak.ui b/src/ui/test_leak/test_leak.ui new file mode 100644 index 0000000..8ee1332 --- /dev/null +++ b/src/ui/test_leak/test_leak.ui @@ -0,0 +1,230 @@ + + + Leak + + + + 0 + 0 + 322 + 329 + + + + Leak + + + + + + Test sequence index: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + Regulated pressure: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + Status: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + Leak: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + - + + + + + + + - + + + + + + + - + + + + + + + Circuit pressure: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + - + + + + + + + - + + + + + + + + 0 + 0 + + + + SALVA IMMAGINE + + + + + + + Instrument status: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + Type of test: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + Risultato + + + + + + + 0 + 0 + + + + %v / %m + + + + + + + + 0 + 0 + + + + + 32 + 32 + + + + - + + + + + + + + + + - + + + + + + + + 0 + 0 + + + + FORZA ACCETTAZIONE + + + + + + + + 0 + 0 + + + + - + + + + + + + Test phase + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + - + + + + + + + + diff --git a/src/ui/test_test/__init__.py b/src/ui/test_test/__init__.py new file mode 100644 index 0000000..3fe33a6 --- /dev/null +++ b/src/ui/test_test/__init__.py @@ -0,0 +1 @@ +from .test_test import Test_Test diff --git a/src/ui/test_test/test_test.py b/src/ui/test_test/test_test.py new file mode 100644 index 0000000..19e4220 --- /dev/null +++ b/src/ui/test_test/test_test.py @@ -0,0 +1,167 @@ +import sys + +from lib.helpers import timing +from PyQt5.QtCore import Qt, QTimer, pyqtSignal +from PyQt5.QtGui import QColor, QPalette, QPixmap +from ui import Dialog +from ui.test_admin_permission import Test_Admin_Permission +from ui.widget import Widget + + +class Test_Test(Widget): + ok = pyqtSignal(dict) + ko = pyqtSignal(dict) + + def __init__(self, components=None, recipe=None): + super().__init__() + self.components = components + self.recipe = recipe + # setup variables + self.ok_counter = 0 + self.ok_counter_limit = 1 + # setup controls + self.ok_timer = QTimer() + self.ok_timer.setSingleShot(True) + self.ok_timer.setInterval(2000) + self.ok_timer.timeout.connect(self.emit_ok) + # setup save frame button + self.last = None + self.save_b.setEnabled(False) + self.save_b.clicked.connect(self.save_last) + # setup override + self.admin_challenged = False + self.override_b.clicked.connect(self.override) + self.override_b.setEnabled(True) + # setup vision staus gui + self.status_imgs_full = { + True: QPixmap("src/ui/imgs/success.png"), + "": QPixmap("src/ui/imgs/neo.ico"), + "warning": QPixmap("src/ui/imgs/warning.png"), + False: QPixmap("src/ui/imgs/fail.png"), + None: QPixmap("src/ui/imgs/wait.png"), + } + self.status_imgs_small = {k: i.scaled(32, 32, Qt.KeepAspectRatio, Qt.SmoothTransformation) for k, i in self.status_imgs_full.items()} + self.status_palettes = { + True: QPalette(), + "": QPalette(), + "warning": QPalette(), + False: QPalette(), + None: QPalette(), + } + self.status_palettes[True].setColor(QPalette.Base, Qt.green) + self.status_palettes[False].setColor(QPalette.Base, Qt.red) + self.status_palettes["warning"].setColor(QPalette.Base, QColor(255, 165, 0)) + self.status_palettes[""].setColor(QPalette.Base, QColor(255, 255, 0)) + self.visualize() + + def start(self, recipe=None): + self.recipe = recipe + self.visualize() + # start test + self.start_time = timing() + # TESTING + if "--test" in sys.argv: + if hasattr(self, "_test_overridden_test_"): + del self._test_overridden_test_ + # /TESTING + + def stop(self): + pass + + def get(self, data=None, override=False): + if self.ok_timer.isActive(): + # avoid proccessing if test was completed + return + if data is None: + if self.last is not None: + data = self.last + else: + data = {} + else: + data = data[-1] + if not override: + result_ok = data.get("results", {}).get("ok", False) + else: + result_ok = True + self.last = { + **data, + "overridden": override, + "duration": timing() - self.start_time, + "ok": result_ok, + } + if not override: + if result_ok is True: + self.ok_counter += 1 + else: + self.ok_counter = 0 + else: + self.ok_counter = self.ok_counter_limit + # check if completed + if self.ok_counter >= self.ok_counter_limit: + self.stop() + self.ok_timer.start() + self.visualize(self.last) + # TESTING + if "--test" in sys.argv: + if not hasattr(self, "_test_overridden_test_"): + self._test_overridden_test_ = True + self.override_b.click() + # /TESTING + + def visualize(self, data=None, img=None): + if data is None: + data = {} + overridden = data.get("overridden", False) + results = data.get("results", None) + self.save_b.setEnabled(self.last is not None) + if overridden: + self.state_l.setPixmap(self.status_imgs_small["warning"]) + elif results is None: + self.state_l.setPixmap(self.status_imgs_small[None]) + elif results.get("ok", False) is True: + self.state_l.setPixmap(self.status_imgs_small[True]) + else: + self.state_l.setPixmap(self.status_imgs_small[False]) + self.ok_counter_pb.setMaximum(self.ok_counter_limit) + self.ok_counter_pb.setValue(min(self.ok_counter, self.ok_counter_limit)) + if self.ok_counter >= self.ok_counter_limit: + self.ok_counter_pb.setPalette(self.status_palettes[True]) + else: + self.ok_counter_pb.setPalette(self.status_palettes[False]) + if img is None: + if overridden: + self.img = self.status_imgs_full["warning"] + else: + self.img = self.status_imgs_full[None] + else: + self.img = img + self.resizeEvent() + + def save_last(self): + if self.last is None: + return + + def resizeEvent(self, event=None): + self.img_l.setPixmap(self.img.scaled(self.img_l.width(), self.img_l.height(), Qt.KeepAspectRatio, Qt.SmoothTransformation)) + + def challenge_admin(self, info): + if not self.admin_challenged: + d = Dialog(parent=self) + d.setCentralWidget(Test_Admin_Permission(info)) + d.setModal(True) + d.show() + r = d.exec() + if r == d.Accepted: + self.admin_challenged = True + elif r == d.Rejected: + self.admin_challenged ^= False + else: + raise AssertionError("Bad admin challenge dialog return code") + return self.admin_challenged + + def override(self): + if self.challenge_admin("Si sta tentando di bypassare il test"): + self.get(override=True) + + def emit_ok(self): + self.ok.emit(self.last) diff --git a/src/ui/test_test/test_test.ui b/src/ui/test_test/test_test.ui new file mode 100644 index 0000000..a3c4252 --- /dev/null +++ b/src/ui/test_test/test_test.ui @@ -0,0 +1,110 @@ + + + Test + + + + 0 + 0 + 618 + 835 + + + + Test + + + + + + + 0 + 0 + + + + FORZA ACCETTAZIONE + + + + + + + Risultato + + + + + + + 0 + 0 + + + + 50 + + + %v / %m + + + + + + + + 0 + 0 + + + + + 32 + 32 + + + + - + + + + + + + + + + + 0 + 0 + + + + + 600 + 700 + + + + - + + + + + + + + 0 + 0 + + + + SALVA IMMAGINE + + + + + + + + diff --git a/src/ui/test_vision/test_vision.py b/src/ui/test_vision/test_vision.py index 6a9f018..c79ea2e 100644 --- a/src/ui/test_vision/test_vision.py +++ b/src/ui/test_vision/test_vision.py @@ -1,158 +1,68 @@ import sys -from lib.helpers import timing -from PyQt5.QtCore import Qt, QTimer, pyqtSignal -from PyQt5.QtGui import QColor, QImage, QPalette, QPixmap -from ui import Dialog -from ui.test_admin_permission import Test_Admin_Permission -from ui.widget import Widget +from PyQt5.QtCore import pyqtSignal +from PyQt5.QtGui import QImage, QPixmap +from ui.test_test import Test_Test -class Test_Vision(Widget): - ok = pyqtSignal(dict) +class Test_Vision(Test_Test): request_frame = pyqtSignal() - def __init__(self, components, recipe): - super().__init__() - self.components = components - self.recipe = recipe - # setup vision variables - self.vision = {} - self.vision_ok_counter = 0 + def __init__(self, components=None, recipe=None): if "--sim-camera" not in sys.argv: - self.vision_ok_counter_limit = 2 + self.ok_counter_limit = 2 else: - self.vision_ok_counter_limit = 1 - # setup vision controls - self.ok_timer = QTimer() - self.ok_timer.setSingleShot(True) - self.ok_timer.setInterval(2000) - self.ok_timer.timeout.connect(self.emit_ok) - # setup save frame button - self.last_vision = None - self.save_frame_b.setEnabled(False) - self.save_frame_b.clicked.connect(self.save_last_vision) - # setup vision override - self.admin_challenged = False - self.vision_overridden = False - self.override_b.clicked.connect(self.override_vision) - self.override_b.setEnabled(True) - # setup vision staus gui - self.status_imgs_full = { - True: QPixmap("src/ui/imgs/success.png"), - "": QPixmap("src/ui/imgs/neo.ico"), - "warning": QPixmap("src/ui/imgs/warning.png"), - False: QPixmap("src/ui/imgs/fail.png"), - None: QPixmap("src/ui/imgs/wait.png"), - } - self.status_imgs_small = {k: i.scaled(32, 32, Qt.KeepAspectRatio, Qt.SmoothTransformation) for k, i in self.status_imgs_full.items()} - self.status_palettes = { - True: QPalette(), - "": QPalette(), - "warning": QPalette(), - False: QPalette(), - None: QPalette(), - } - self.status_palettes[True].setColor(QPalette.Base, Qt.green) - self.status_palettes[False].setColor(QPalette.Base, Qt.red) - self.status_palettes["warning"].setColor(QPalette.Base, QColor(255, 165, 0)) - self.status_palettes[""].setColor(QPalette.Base, QColor(255, 255, 0)) - self.visualize_vision() + self.ok_counter_limit = 1 + super().__init__(components, recipe) - def start(self): - self.visualize_vision() - # TESTING - if "--test" in sys.argv: - self.override_b.click() - # /TESTING + def start(self, recipe=None): + super().start(recipe=recipe) # setup camera-vision loop self.components["galaxy_camera"].set_period(period=None) # only get frame on request self.components["galaxy_camera"].add_sources({"test_vision": self.request_frame}) self.request_frame_connection = self.components["vision"].out.connect(self.request_frame) # request new frame as soon as vision finishes - self.process_vision_connection = self.components["vision"].out.connect(self.process_vision) + self.get_connection = self.components["vision"].out.connect(self.get) self.components["vision"].resume() self.components["galaxy_camera"].resume() - # start test - self.start_time = timing() self.request_frame.emit() # request first frame def stop(self): # disable camera-vision loop self.components["galaxy_camera"].pause() self.components["vision"].pause() - self.disconnect(self.process_vision_connection) + self.disconnect(self.get_connection) self.disconnect(self.request_frame_connection) self.components["galaxy_camera"].remove_sources(["test_vision", ]) + super().stop() - def process_vision(self, data=None, override=False): + def get(self, data=None, override=False): if self.ok_timer.isActive(): # avoid proccessing if vision was completed return - if data is None: - data = [{"vision": {}}] + if data is None or data[-1] is None: + super().get(None, override=override) + return data = data[-1] - time = data.get("time", None) - data = data["vision"] - frame = data.get("frame", None) - detections = data.get("detections", None) - results = data.get("results", None) - render = data.get("render", None) - if not override: - result_ok = data.get("vision", {}).get("ok", False) - else: - result_ok = True - self.last_vision = { - "time": time, - "frame": frame, - "detections": detections, - "results": results, - "render": render, - "overridden": override, - "vision_duration": timing() - self.start_time, - "ok": result_ok, - } - if not override: - if result_ok is True: - self.vision_ok_counter += 1 - else: - self.vision_ok_counter = 0 - else: - self.vision_ok_counter = self.vision_ok_counter_limit - # check if completed - if self.vision_ok_counter >= self.vision_ok_counter_limit: - self.stop() - self.ok_timer.start() - self.visualize_vision( - time=time, - frame=frame, - detections=detections, - results=results, - render=render, - overridden=override, - ) + super().get([{ + "time": data.get("time", None), + "frame": data["vision"].get("frame", None), + "detections": data["vision"].get("detections", None), + "results": data["vision"].get("results", None), + "render": data["vision"].get("render", None), + }], override=override) - def visualize_vision(self, time=None, frame=None, detections=None, results=None, render=None, overridden=False): - self.save_frame_b.setEnabled(self.last_vision is not None) + def visualize(self, data=None): + if data is None: + data = {} + overridden = data.get("overridden", False) + frame = data.get("frame", None) + render = data.get("render", None) if overridden: - self.state_l.setPixmap(self.status_imgs_small["warning"]) - elif results is None: - self.state_l.setPixmap(self.status_imgs_small[None]) - elif results.get("ok", False) is True: - self.state_l.setPixmap(self.status_imgs_small[True]) - else: - self.state_l.setPixmap(self.status_imgs_small[False]) - self.ok_counter_pb.setMaximum(self.vision_ok_counter_limit) - self.ok_counter_pb.setValue(min(self.vision_ok_counter, self.vision_ok_counter_limit)) - if self.vision_ok_counter >= self.vision_ok_counter_limit: - self.ok_counter_pb.setPalette(self.status_palettes[True]) - else: - self.ok_counter_pb.setPalette(self.status_palettes[False]) - if overridden: - self.img = self.status_imgs_full["warning"] + img = self.status_imgs_full["warning"] elif render is not None: - self.img = render + img = render elif frame is not None: - self.img = QPixmap.fromImage(QImage( + img = QPixmap.fromImage(QImage( frame.data, frame.shape[1], # width frame.shape[0], # height @@ -160,34 +70,14 @@ class Test_Vision(Widget): QImage.Format_RGB888 )) else: - self.img = self.status_imgs_full[None] - self.resizeEvent() + img = self.status_imgs_full[None] + super().visualize(data, img=img) - def save_last_vision(self): - if self.last_vision is None: + def save_last(self): + if self.last is None: return - def resizeEvent(self, event=None): - self.frame_l.setPixmap(self.img.scaled(self.frame_l.width(), self.frame_l.height(), Qt.KeepAspectRatio, Qt.SmoothTransformation)) - - def challenge_admin(self, info): - if not self.admin_challenged: - d = Dialog() - d.setCentralWidget(Test_Admin_Permission(info)) - d.setModal(True) - d.show() - r = d.exec() - if r == d.Accepted: - self.admin_challenged = True - elif r == d.Rejected: - self.admin_challenged ^= False - else: - raise AssertionError("Bad admin challenge dialog return code") - return self.admin_challenged - - def override_vision(self): - if self.challenge_admin("Si sta tentando di bypassare il test di visione"): - self.process_vision(override=True) - def emit_ok(self): - self.ok.emit(self.last_vision) + self.last.pop("frame", None) + self.last.pop("render", None) + self.ok.emit(self.last) diff --git a/src/ui/test_vision/test_vision.ui b/src/ui/test_vision/test_vision.ui index 711b3ef..98b5e97 100644 --- a/src/ui/test_vision/test_vision.ui +++ b/src/ui/test_vision/test_vision.ui @@ -72,7 +72,7 @@ - + 0 @@ -91,7 +91,7 @@ - + 0 diff --git a/src/ui/widget/widget.py b/src/ui/widget/widget.py index 25104f2..dc85718 100644 --- a/src/ui/widget/widget.py +++ b/src/ui/widget/widget.py @@ -8,8 +8,8 @@ from ui.dialog import Dialog class Widget(QWidget): - def __init__(self): - super().__init__() + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) self.setAttribute(Qt.WA_DeleteOnClose) me = self.__class__.__name__ u = get_resource("ui/{0}/{0}.ui".format(me.lower())) diff --git a/src/ui/window/window.py b/src/ui/window/window.py index 6999475..8e9ff6f 100644 --- a/src/ui/window/window.py +++ b/src/ui/window/window.py @@ -11,8 +11,8 @@ from ui.dialog import Dialog class Window(QMainWindow): _closing = pyqtSignal() - def __init__(self): - super().__init__() + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) self.setAttribute(Qt.WA_DeleteOnClose) self.setWindowFlags(Qt.Window) me = self.__class__.__name__