diff --git a/config/machine_settings/defaults.ini b/config/machine_settings/defaults.ini index 436bc4e..bd2c8b4 100644 --- a/config/machine_settings/defaults.ini +++ b/config/machine_settings/defaults.ini @@ -2,7 +2,7 @@ tecna_t3: present neo_pixels: present galaxy_camera: present -multimeter: absent +multicomp: present [galaxy_camera] horizontal_resolution: 2448 @@ -45,6 +45,10 @@ admin_pin: 603 modify_pin: 603 select_pin: 603 +[multicomp] +port: COM3 +baudrate: 115200 + [neo_pixels] port: COM1 baudrate: 9600 diff --git a/config/machine_settings/this.ini b/config/machine_settings/this.ini index 36a8bdc..802f921 100644 --- a/config/machine_settings/this.ini +++ b/config/machine_settings/this.ini @@ -1,2 +1,5 @@ [tecna_t3] port: /dev/ttyUSB0 + +[multicomp] +port: /dev/ttyUSB1 diff --git a/src/components/__init__.py b/src/components/__init__.py index a71b5e3..7001f5a 100644 --- a/src/components/__init__.py +++ b/src/components/__init__.py @@ -2,11 +2,12 @@ from .archive_synchronizer import ArchiveSynchronizer from .consumer import Consumer from .galaxy_camera import GalaxyCamera from .modbus_component import ModbusComponent +from .multicomp_730424 import Multicomp730424 from .neo_pixels import NeoPixels from .os_label_printer import Os_Label_Printer from .remote_api import RemoteAPI from .serial_label_printer import Serial_Label_Printer from .tecna_marposs_provaset_t3p import TecnaMarpossProvasetT3P from .test_component import TestComponent -#from .vision import Vision +from .vision import Vision from .vision_saver import VisionSaver diff --git a/src/components/multicomp_730424.py b/src/components/multicomp_730424.py index 80851b9..5ff4bfb 100644 --- a/src/components/multicomp_730424.py +++ b/src/components/multicomp_730424.py @@ -1,33 +1,70 @@ import sys +import traceback -if "--sim-multimeter" in sys.argv: +if "--sim-serial" in sys.argv: from components.dummies.serial import serial else: import serial -from PyQt5.QtWidgets import QMessageBox - from .component import Component -class SerialMultimeter(Component): - def __init__(self, config=None, name=None): - super().__init__(config=config, name=name, threaded=False) +class Multicomp730424(Component): + def __init__(self, config=None, name=None, period=1, lazy=True, paused=False, threaded=True): + self.conn = None + super().__init__(config=config, name=name, period=period, lazy=lazy, paused=paused, threaded=threaded) + + def config_changed(self): + self.port = self.config[self.name]["port"] + self.baudrate = int(self.config[self.name]["baudrate"]) + 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, + ) + + def set_resistance_scale(self, resistance): + self.write(f"CONF:RES {resistance}") + + def read(self): + try: + response = self.conn.readline() + except serial.serialutil.SerialException: + self.log.exception(traceback.format_exc()) + return None + try: + decoded = response.decode("ascii") + except UnicodeDecodeError: + return response + try: + value = float(decoded) + except ValueError: + return decoded + return value def write(self, command): try: - conn = serial.Serial( - self.address, - baudrate=self.baudrate, - stopbits=self.stopbits, - parity=self.parity, - bytesize=self.bytesize, - ) - conn.write(bytes(command, encoding="ascii")) - conn.close() - except serial.serialutil.SerialException as e: - QMessageBox.critical( - None, - "Errore Connessione Multimetro", - "Non e stato possibile connettersi al multimetro digitale.\n Verificare che lo strumento sia acceso e collegato\nErrore: " + str(e) - ) + self.conn.write(f"{command}\n".encode("ascii")) + except serial.serialutil.SerialException: + self.log.exception(traceback.format_exc()) + + def _get(self): + info = {} + try: + for value_name, command in { + "scale": "CONF:RES?", + "measure": "MEAS?", + }.items(): + self.write(command) + info[value_name] = self.read() + except serial.serialutil.SerialException: + self.log.exception(traceback.format_exc()) + self.log.debug(str(info)) + super()._get([info]) diff --git a/src/components/neo_pixels.py b/src/components/neo_pixels.py index 40e9c1a..611040e 100644 --- a/src/components/neo_pixels.py +++ b/src/components/neo_pixels.py @@ -48,7 +48,6 @@ class NeoPixels(Component): response = self.conn.readline() if response != b"ok\n": self.log.error(response.strip()) - # self.conn.close() except serial.serialutil.SerialException: self.log.exception(traceback.format_exc()) diff --git a/src/main.py b/src/main.py index 79ee385..c97a35e 100644 --- a/src/main.py +++ b/src/main.py @@ -52,8 +52,8 @@ logging.basicConfig( try: # IMPORT PROJECT ONLY AFTER SETTING UP SIGNAL, FAULTHANDLER AND LOGGHING - from components import (ArchiveSynchronizer, GalaxyCamera, NeoPixels, - Os_Label_Printer, RemoteAPI, + from components import (ArchiveSynchronizer, GalaxyCamera, Multicomp730424, + NeoPixels, Os_Label_Printer, RemoteAPI, TecnaMarpossProvasetT3P, VisionSaver) from lib.db import Users from lib.helpers import ConfigReader @@ -78,22 +78,13 @@ try: # INIT COMPONENT self.components_specs = { # "archive_synchronizer": {"c": ArchiveSynchronizer}, - "galaxy_camera": {"c": GalaxyCamera, "k": {"paused": True}}, "label_printer": {"c": Os_Label_Printer, "t": False}, - "neo_pixels": {"c": NeoPixels, "t": False}, + "multicomp": {"c": Multicomp730424, "k": {"paused": True}}, "remote_api": {"c": RemoteAPI, "k": {"main": self}}, - "tecna_t3": {"c": TecnaMarpossProvasetT3P, "k": {"paused": True}}, - "vision_saver": {"c": VisionSaver, "t": False}, - } - - for key in self.components_specs.copy().keys(): - if key in self.config["hardware_config"].keys() and self.config["hardware_config"][key] != "present": - self.components_specs.pop(key) - if self.config["hardware_config"]["vision"] is "present": - from src.components.vision import Vision - self.components_specs["vision"]= {"c": Vision, "k": {"paused": True}} - + for component_name in list(self.components_specs): + if self.config.get("hardware_config", {}).get(component_name, None) != "present": + self.components_specs.pop(component_name) self.components = {} self.threads = {} for component_name, spec in self.components_specs.items(): diff --git a/src/test/multicomp.py b/src/test/multicomp.py index 9ecf60a..8716737 100644 --- a/src/test/multicomp.py +++ b/src/test/multicomp.py @@ -14,14 +14,15 @@ conn = serial.Serial( timeout=1 ) -cmd_list = (("*IDN?\n", True), # QUERY ID - ("CONF:RES 500\n", False), # SET RESISTANCE MEASURE, 500 Ohm range - ("MEAS?\n", True), # GET MEASURE - ("MEAS?\n", True), # GET MEASURE - ("MEAS?\n", True)) # GET MEASURE +cmd_list = [ + ["*IDN?\n", True], # QUERY ID + ["CONF:RES 500\n", False], # SET RESISTANCE MEASURE, 500 Ohm range + ["MEAS?\n", True], # GET MEASURE + ["MEAS?\n", True], # GET MEASURE + ["MEAS?\n", True], # GET MEASURE +] -for cmditem in cmd_list: - cmd, expect_response = cmditem +for [cmd, expect_response] in cmd_list: conn.write(bytes(cmd, encoding="ascii")) if expect_response: response = conn.readline() diff --git a/src/ui/__init__.py b/src/ui/__init__.py index ff03385..b6d7ef0 100644 --- a/src/ui/__init__.py +++ b/src/ui/__init__.py @@ -20,6 +20,7 @@ from .recipe_selection import Recipe_Selection from .recipe_spec_and_step_editor import Recipe_Spec_And_Step_Editor from .recipe_spec_editor import Recipe_Spec_Editor from .recipes_management import Recipes_Management +from .resistance_step_editor import Resistance_Step_Editor from .steps_management import Steps_Management from .test import Test from .test_admin_permission import Test_Admin_Permission @@ -28,6 +29,7 @@ from .test_autotest import Test_Autotest from .test_barcodes import Test_Barcodes from .test_home import Test_Home from .test_leak import Test_Leak +from .test_resistance import Test_Resistance from .test_test import Test_Test from .test_vision import Test_Vision from .users_management import Users_Management diff --git a/src/ui/recipe_spec_and_step_editor/recipe_spec_and_step_editor.py b/src/ui/recipe_spec_and_step_editor/recipe_spec_and_step_editor.py index bec3257..b2cca9f 100644 --- a/src/ui/recipe_spec_and_step_editor/recipe_spec_and_step_editor.py +++ b/src/ui/recipe_spec_and_step_editor/recipe_spec_and_step_editor.py @@ -2,6 +2,7 @@ from lib.db import Steps from ui.editor import Editor from ui.helpers import replace_widget from ui.leak_step_editor import Leak_Step_Editor +from ui.resistance_step_editor import Resistance_Step_Editor from ui.vision_step_editor import Vision_Step_Editor @@ -27,6 +28,12 @@ class Recipe_Spec_And_Step_Editor(Editor): "widget": "leak_editor_2_w", "editor": Leak_Step_Editor(), }, + "resistance": { + "type": "resistance", + "enable": self.resistance_eabled_cb, + "widget": "resistance_editor_w", + "editor": Resistance_Step_Editor(), + }, "vision": { "type": "vision", "enable": self.vision_enabled_cb, diff --git a/src/ui/recipe_spec_and_step_editor/recipe_spec_and_step_editor.ui b/src/ui/recipe_spec_and_step_editor/recipe_spec_and_step_editor.ui index f8eb4e2..6816af1 100644 --- a/src/ui/recipe_spec_and_step_editor/recipe_spec_and_step_editor.ui +++ b/src/ui/recipe_spec_and_step_editor/recipe_spec_and_step_editor.ui @@ -6,8 +6,8 @@ 0 0 - 739 - 190 + 292 + 152 @@ -169,6 +169,49 @@ Fasi di Test + + + Test resistenza + + + + + + Qt::Horizontal + + + + 66 + 20 + + + + + + + + Qt::Horizontal + + + + 65 + 20 + + + + + + + + Fase Abilitata + + + + + + + + Test telecamera diff --git a/src/ui/resistance_step_editor/__init__.py b/src/ui/resistance_step_editor/__init__.py new file mode 100644 index 0000000..4b1af3e --- /dev/null +++ b/src/ui/resistance_step_editor/__init__.py @@ -0,0 +1 @@ +from .resistance_step_editor import Resistance_Step_Editor diff --git a/src/ui/resistance_step_editor/resistance_step_editor.py b/src/ui/resistance_step_editor/resistance_step_editor.py new file mode 100644 index 0000000..bc05c30 --- /dev/null +++ b/src/ui/resistance_step_editor/resistance_step_editor.py @@ -0,0 +1,11 @@ +from ui.editor import Editor + + +class Resistance_Step_Editor(Editor): + def __init__(self, cell_widget=None): + super().__init__(cell_widget=cell_widget) + self.spec.update({ + "scale": self.scale_sb, + "expected": self.expected_sb, + "tolerance": self.tolerance_sb, + }) diff --git a/src/ui/resistance_step_editor/resistance_step_editor.ui b/src/ui/resistance_step_editor/resistance_step_editor.ui new file mode 100644 index 0000000..338c1a5 --- /dev/null +++ b/src/ui/resistance_step_editor/resistance_step_editor.ui @@ -0,0 +1,112 @@ + + + Resistance_Step_Editor + + + + 0 + 0 + 197 + 130 + + + + Resistance Step Editor + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 10 + 50 + false + + + + Resistenza + + + + + + 9999 + + + + + + + 9999 + + + + + + + ohm + + + + + + + Scala + + + + + + + ohm + + + + + + + Valore atteso + + + + + + + Tolleranza + + + + + + + 9999 + + + + + + + ohm + + + + + + + + + + + diff --git a/src/ui/test/test.py b/src/ui/test/test.py index de5a7d8..796fb48 100755 --- a/src/ui/test/test.py +++ b/src/ui/test/test.py @@ -1,6 +1,7 @@ import logging import os import sys +import weakref from datetime import datetime from lib.db import Archive, Steps, Users @@ -13,6 +14,7 @@ from ui.test_assembly import Test_Assembly from ui.test_autotest import Test_Autotest from ui.test_barcodes import Test_Barcodes from ui.test_leak import Test_Leak +from ui.test_resistance import Test_Resistance from ui.test_vision import Test_Vision from ui.widget import Widget @@ -46,6 +48,7 @@ class Test(Widget): "fail": Test_Assembly(img_path=self.select_step_img("fail"), text=u"CICLO INTERROTTO", widget=None), "leak": Test_Assembly(img_path=None, text=None, widget=Test_Leak(components=self.components, recipe=self.recipe, step=self.step)), "print": Test_Assembly(img_path=self.select_step_img("print"), text=u"STAMPA ETICHETTA IN CORSO", widget=None), + "resistance": Test_Assembly(img_path=None, text=u"VERIFICARE CONTROLLO RESISTENZA", widget=Test_Resistance(components=self.components, recipe=self.recipe, step=self.step)), "select_recipe": Test_Assembly(img_path=None, text=u"SELEZIONARE IL CODICE DA COLLAUDARE", widget=Recipe_Selection()), "vision": Test_Assembly(img_path=None, text=u"VERIFICARE CONTROLLO CON TELECAMERA", widget=Test_Vision(components=self.components, recipe=self.recipe, step=self.step)), "wait": Test_Assembly(img_path=self.select_step_img("wait"), text=u"ATTENDERE - PAUSA INTER CICLO", widget=None), @@ -68,17 +71,13 @@ class Test(Widget): # 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_available_steps.values(): + for step_name, w in self.cycle_available_steps.items(): if hasattr(w, "ok"): # custom ok handlers should call next again - if type(w.widget) is Recipe_Selection: + if isinstance(w.widget, (Recipe_Selection)): w.ok.connect(self.set_recipe) - elif type(w.widget) is Test_Barcodes: - w.ok.connect(self.set_barcodes) - 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) + elif isinstance(w.widget, (Test_Barcodes, Test_Leak, Test_Resistance, Test_Vision)): + w.ok.connect(lambda data=None, step_name=step_name, self=weakref.ref(self): self().set_step(step_name, data)) else: w.ok.connect(self.next) if hasattr(w, "ko"): @@ -247,42 +246,28 @@ class Test(Widget): self.recipe_l.setText("NON SELEZIONATA") self.recipe_l.setStyleSheet("QLabel { color: red; }") - def set_barcodes(self, barcodes=None): - if "barcodes" not in self.data: - self.data["barcodes"] = {} - self.data["barcodes"].update(barcodes) - self.next() - - def set_leak(self, leak=None): - if "leak" not in self.data: - self.data["leak"] = {} - leak["step"] = model_to_dict(self.step) - 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"] = {} - vision["step"] = model_to_dict(self.step) - 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) + def set_step(self, step_name, data=None): + if step_name not in self.data: + self.data[step_name] = {} + data["step"] = model_to_dict(self.step) + self.data[step_name][len(self.data[step_name])] = data + self.data["overridden"] = self.data["overridden"] or data.get("overridden", False) + self.data["ok"] = self.data["ok"] and data.get("ok", False) self.next() def done(self, ok=True): self.log.info("cycle done") - vision_test_1 = self.data.get("vision", {}).get(0, {}) - out_paths = self.components["vision_saver"].save( - save_time=vision_test_1.get("time", None), - frame=vision_test_1.get("frame", None), - # vision=vision_test_1.get("detections", None), - ) - self.log.info(f"cycle vision saved locally: {out_paths!r}") - for vision in self.data["vision"].values(): - vision.pop("frame", None) - vision.pop("render", None) + if "vision" in self.data: + vision_test_1 = self.data.get("vision", {}).get(0, {}) + out_paths = self.components["vision_saver"].save( + save_time=vision_test_1.get("time", None), + frame=vision_test_1.get("frame", None), + # vision=vision_test_1.get("detections", None), + ) + self.log.info(f"cycle vision saved locally: {out_paths!r}") + for vision in self.data.get("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 diff --git a/src/ui/test_leak/test_leak.py b/src/ui/test_leak/test_leak.py index 98d1167..67c8961 100644 --- a/src/ui/test_leak/test_leak.py +++ b/src/ui/test_leak/test_leak.py @@ -21,7 +21,7 @@ class Test_Leak(Test_Test): self.stop_b.setEnabled(False) def stop(self): - # disable tes loop + # disable test loop self.components["tecna_t3"].stop_test() self.components["tecna_t3"].pause() self.disconnect(self.get_connection) diff --git a/src/ui/test_resistance/__init__.py b/src/ui/test_resistance/__init__.py new file mode 100644 index 0000000..bcb1193 --- /dev/null +++ b/src/ui/test_resistance/__init__.py @@ -0,0 +1 @@ +from .test_resistance import Test_Resistance diff --git a/src/ui/test_resistance/test_resistance.py b/src/ui/test_resistance/test_resistance.py new file mode 100644 index 0000000..9976375 --- /dev/null +++ b/src/ui/test_resistance/test_resistance.py @@ -0,0 +1,73 @@ +from ui.test_test import Test_Test + + +class Test_Resistance(Test_Test): + def __init__(self, components=None, recipe=None, step=None): + super().__init__(components=components, recipe=recipe, step=step) + + def start(self, recipe=None, step=None): + super().start(recipe=recipe, step=step) + # setup test loop + self.components["multicomp"].set_resistance_scale(self.step.spec.get("expected", 0)) + self.get_connection = self.components["multicomp"].out.connect(self.get) + self.components["multicomp"].resume() + if self.step is not None: + self.expected_l.setText(str(self.step.spec.get("expected", "-"))) + self.tolerance_l.setText(str(self.step.spec.get("tolerance", "-"))) + else: + self.expected_l.setText("-") + self.tolerance_l.setText("-") + + def stop(self): + # disable test loop + self.components["multicomp"].pause() + self.disconnect(self.get_connection) + self.expected_l.setText("-") + self.tolerance_l.setText("-") + super().stop() + + def get(self, data=None, override=False): + if self.done: # avoid proccessing if completed + return + if data is None or data[-1] is None: + super().get(None, override=override) + return + data = data[-1] + result = data["multicomp"].get("measure", None) + expected = self.step.spec.get("expected", "-") + tolerance = self.step.spec.get("tolerance", "-") + if all([ + isinstance(result, (int, float)), + isinstance(expected, (int, float)), + isinstance(tolerance, (int, float)), + abs(result - expected) <= tolerance + ]): + ok = True + else: + ok = None + super().get([{ + "time": data.get("time", None), + "results": { + "ok": ok, + "result": result, + "data": data["multicomp"], + }, + }], override=override, fail=ok is False) + + def visualize(self, data=None): + if data is None: + data = {} + d = data.get("results", {}).get("data", {}) + for k, l in { + "scale": self.scale_l, + "measure": self.resistance_l, + }.items(): + v = d.get(k, "-") + if type(v) is float: + v = round(v, 2) + l.setText(str(v)) + super().visualize(data) + + def save_last(self): + if self.last is None: + return diff --git a/src/ui/test_resistance/test_resistance.ui b/src/ui/test_resistance/test_resistance.ui new file mode 100644 index 0000000..46fb8e6 --- /dev/null +++ b/src/ui/test_resistance/test_resistance.ui @@ -0,0 +1,360 @@ + + + Test_Resistance + + + + 0 + 0 + 301 + 324 + + + + Test Resistance + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 12 + 75 + true + + + + PROVA RESISTENZA + + + + + + + 16 + 50 + false + + + + Scala misura + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + 16 + 50 + false + + + + ohm + + + + + + + + 16 + 50 + false + + + + ohm + + + + + + + + 0 + 0 + + + + - + + + + + + + + 130 + 16777215 + + + + + 20 + 50 + false + + + + background-color: rgb(255, 255, 255); +border: 1px solid black; + + + + - + + + + + + + + 130 + 16777215 + + + + + 20 + 50 + false + + + + background-color: rgb(255, 255, 255); +border: 1px solid black; + + + + - + + + + + + + + 130 + 16777215 + + + + + 20 + 50 + false + + + + background-color: rgb(255, 255, 255); +border: 1px solid black; + + + + - + + + + + + + + 16 + 50 + false + + + + Resistenza attesa + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + 16 + 50 + false + + + + Tolleranza + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + 16 + 50 + false + + + + ohm + + + + + + + + 16 + 50 + false + + + + Resistenza effettiva + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + 130 + 16777215 + + + + + 20 + 50 + false + + + + background-color: rgb(255, 255, 255); +border: 1px solid black; + + + + - + + + + + + + + 16 + 50 + false + + + + ohm + + + + + + + + + + + 0 + 0 + + + + + 12 + 75 + true + + + + Risultato + + + + + + + 0 + 0 + + + + %v / %m + + + + + + + + 0 + 0 + + + + + 32 + 32 + + + + - + + + + + + + + + + + 0 + 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 e90dcb5..986effe 100644 --- a/src/ui/test_vision/test_vision.py +++ b/src/ui/test_vision/test_vision.py @@ -34,7 +34,8 @@ class Test_Vision(Test_Test): 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.get_connection = self.components["vision"].out.connect(self.get) - self.components["neo_pixels"].set_all_pixel_color("#ffffff") + if "neo_pixels" in self.components: + self.components["neo_pixels"].set_all_pixel_color("#ffffff") self.components["vision"].set_recipe(self.step.spec.get("recipe", None)) self.components["vision"].resume() self.components["galaxy_camera"].resume() @@ -113,13 +114,17 @@ class Test_Vision(Test_Test): img = self.status_imgs_full[None] if self.done: if data.get("overridden", False): - self.components["neo_pixels"].set_all_pixel_color("#ffff00") + if "neo_pixels" in self.components: + self.components["neo_pixels"].set_all_pixel_color("#ffff00") elif self.ok_counter >= self.ok_counter_limit: - self.components["neo_pixels"].set_all_pixel_color("#00ff00") + if "neo_pixels" in self.components: + self.components["neo_pixels"].set_all_pixel_color("#00ff00") else: - self.components["neo_pixels"].set_all_pixel_color("#ff0000") + if "neo_pixels" in self.components: + self.components["neo_pixels"].set_all_pixel_color("#ff0000") else: - self.components["neo_pixels"].set_all_pixel_color("#ffffff") + if "neo_pixels" in self.components: + self.components["neo_pixels"].set_all_pixel_color("#ffffff") # DETECTIONS TABLE if results is not None: self.results_tw.setRowCount(len(results)) @@ -201,8 +206,10 @@ class Test_Vision(Test_Test): def emit_ok(self): super().emit_ok() - self.components["neo_pixels"].set_all_pixel_color("#000000") + if "neo_pixels" in self.components: + self.components["neo_pixels"].set_all_pixel_color("#000000") def emit_ko(self): super().emit_ko() - self.components["neo_pixels"].set_all_pixel_color("#000000") + if "neo_pixels" in self.components: + self.components["neo_pixels"].set_all_pixel_color("#000000")