diff --git a/config/csv_import/EXTRACTIONS/T3P stten12 10 02 2025.csv b/config/csv_import/EXTRACTIONS/T3P stten12 10 02 2025.csv new file mode 100644 index 0000000..49335d5 --- /dev/null +++ b/config/csv_import/EXTRACTIONS/T3P stten12 10 02 2025.csv @@ -0,0 +1,101 @@ +codice_ricetta,part_number,tempo_pre_riempimento,pressione_pre_riempimento,tempo_riempimento,tempo_assestamento,percentuale_minima_pressione_assestamento,percentuale_massima_pressione_assestamento,tempo_di_test,pressione_di_test_delta_minimo,pressione_di_test,pressione_di_test_delta_massimo,tempo_svuotamento,pressione_svuotamento,template_di_stampa,campo1,campo2 +5803148256,TUBAZIONE RITORNO RADIA,0,5000,0,1,10,10,1,0,5000,0,0.1,0,6,5803148256, +5803148257,TUBAZIONE RITORNO RADIA,0,5000,0,1,10,10,1,0,5000,0,0.1,0,6,5803148257, +5803148261,TUBAZIONE RITORNO RADIA,0,5000,0,1,10,10,1,0,5000,0,0.1,0,6,5803148261, +5803148263,TUBAZIONE RITORNO RADIA,0,5000,0,1,10,10,1,0,5000,0,0.1,0,6,5803148263, +5803148264,TUBAZIONE RITORNO RADIA,0,5000,0,1,10,10,1,0,5000,0,0.1,0,6,5803148264, +5803148265,TUBAZIONE RITORNO RADIA,0,5000,0,1,10,10,1,0,5000,0,0.1,0,6,5803148265, +5803148266,TUBAZIONE RITORNO RADIA,0,5000,0,1,10,10,1,0,5000,0,0.1,0,6,5803148266, +5803148267,TUBAZIONE RITORNO RADIA,0,5000,0,1,10,10,1,0,5000,0,0.1,0,6,5803148267, +5803148268,TUBAZIONE RITORNO RADIA,0,5000,0,1,10,10,1,0,5000,0,0.1,0,6,5803148268, +5803148269,TUBAZIONE RITORNO RADIA,0,5000,0,1,10,10,1,0,5000,0,0.1,0,6,5803148269, +5803166717,TUBAZIONE RITORNO RADIA,0,5000,0,1,10,10,1,0,5000,0,0.1,0,6,5803166717, +5803167128,TUBAZIONE RITORNO RADIA,0,5000,0,1,10,10,1,0,5000,0,0.1,0,6,5803167128, +5802966801,TUBAZIONE RITORNO RADIA,0,5000,0,1,10,10,1,0,5000,0,0.1,0,6,5802966801 R.01, +A68507016000,FUEL HOSE PUMP CPL,0,0,0,1,10,10,0,0,7200,0,0.1,0,4,A68507016000,KTM +16LA573CP REV01, HOSE-MCU-TO-MOTOR,0,0,0,0,20,20,2,0,3000,0,0.1,0,2, HOSE-MCU-TO-MO,P16 +PIPE-IN-PRE-COOL,16LA372CP,0,0,0,0,20,20,3,0,3500,0,0.1,0,2,16LA372CP REV01,P16 +5803083561, TUBO ASPIRAZIONE,0,3000,0,1,20,20,2,0,3000,0,0.1,0,6,5803083561,IVECO +5803083634, TUBO ASPIRAZIONE,0,3000,0,1,20,20,2,0,2000,0,0.1,0,6,5803083634,IVECO +16LA642CP REV01,HOSE-WCACS LINK PIPE,0,0,0,0,20,20,3,0,3500,0,0.1,0,2,HOSE-WCACS LINK ,P16 +16LA353CP REV02,HOSE-RH-HT-RADIATOR-TOP,0,2000,1,1,20,20,3,0,3500,0,0.1,0,2,HOSE-RH-HT-RADIA,P16 +16LA336CP REV02,16LA336CP,0,0,0,1,20,20,3,0,3500,0,0.1,0,2, HOSE-E-MOTOR,P16 +16L0180CP REV04,16L0180CP,0,0,0,1,20,20,3,0,3000,0,0.1,0,2,HOSE LH LTR OUT,P16 +HOSE-JNCTN TO W,16L0177CP,0,0,0,1,20,20,3,0,3500,0,0.1,0,2,16L0177CP REV03,P16 +HOSE-IPU-OUTLET,16L0170CP,0,0,0,1,20,20,2,0,3000,0,0.1,0,2,16L0170CP REV03,P16 +HOSE-TO-MCU,16L0167CP,0,0,0,1,20,20,2,0,3000,0,0.1,0,2,16L0167CP REV02,P16 +HOSE-LH-LTR-OUTL,16L0164CP,0,0,0,1,20,20,3,0,3000,0,0.1,0,2,16L0164CP REV02,P16 +16L0007CP REV03,16L0007CP,0,0,1,0,20,20,3,0,3000,0,0.1,0,2,HOSE-IPU-JNCTN-T,P16 +16FC246CP REV01,16FC246CP,0,0,0,0,20,20,3,0,1000,0,0.1,0,2,ASSY-OIL FILL,P16 +HOSE-WCACS-TO,16L0161CP,0,0,0,1,20,20,2,0,3000,0,0.1,0,2,16L0161CP REV04,P16 +HOSE-RH-LTR-OUT,16L0154CP,0,0,1,1,20,20,2,0,3000,0,0.1,0,2,16L0154CP REV03,P16 +16LA661CP REV01,HOSE-RH-HT-RADIATOR-BOTT,0,2000,1,0,20,20,3,0,3500,0,0.1,0,2,HOSE-RH-HT-RADIA,P16 +16L0058CP REV02,16L0058CP,0,0,0,0,20,20,3,0,3500,0,0.1,0,2,HOSE-LT-TUNEL-TO,P16 +16FC069CP REV01,ASSY CCV-INTAKE,0,0,0,0,20,20,2,0,1000,0,0.1,0,2,ASSY CCV-INTAKE,P16 +16L0145CP REV3,16L0145CP,0,0,0,1,20,20,2,0,3000,0,0.1,0,2,HOSE-HOSE-PUMP-T,P16 +16FC056CP REV02,ASSY-MAKE UP AIR,0,0,0,0,20,20,2,0,1000,0,0.1,0,2,ASSY-MAKE UP AIR,P16 +HOSE-IPU-OUTLET,16L0151CP,0,0,1,1,20,20,2,0,3000,0,0.1,0,2,16L0151CP REV03,P16 +16L0046CP REV03,HOSE-LH-HT-RADIATOR-TOP-,0,2000,1,0,20,20,3,0,3500,0,0.1,0,2,HOSE-LH-HT-RADIA,P16 +16L0075CP REV03,16L0075CP,0,0,0,1,20,20,3,0,2500,0,0.1,0,2,HOSE-HEADER-TANK,P16 +16FC250CP REV01,16FB640CP,0,0,0,0,20,20,1,0,200,0,0.1,0,2,ASSY CCV - PCV,P16 +MY83-6B748-BA,ENGINE OIL ASY-OUTLET,0,0,1,1,10,10,3,0,7000,0,0.0,0,4,MY83-6B748-BA,AST0N MARTIN +6689099A1,ARGO TRACT,0,0,0,1,10,10,2,0,6000,0,0.0,0,4,6689099A1, +6689101A1,ARGO TRACT,0,0,0,1,10,10,2,0,6000,0,0.0,0,4,6689101A1, +6689098A1,ARGO TRACT,0,0,0,1,10,10,2,0,6000,0,0.0,0,4,6689098A1, +5803031876,OIL FILLER PIPE,0,0,1,1,10,10,1,0,500,0,0.0,0,4,5803031876,IVECO +FUGA CALIBRATA,24003,0,7000,0,0,5,5,1,0,7000,0,0.1,0,5,24003 12MBAR,FUGA CALIBRATA +5801384842 REV 0,TUBO PER COMBUSTIBILE,0,0,2,1,10,10,1,0,8000,0,0.0,0,4,5801384842 REV 0,LANCIA BOLZANO +5802729983 REV 0,TUBO PER COMBUSTIBILE,0,0,2,1,10,10,1,0,8000,0,0.0,0,4,5802729983 REV 0,LANCIA BOLZANO +5802731910 REV 0,TUBO PER COMBUSTIBILE,0,0,2,1,10,10,1,0,8000,0,0.0,0,4,5802731910 REV 0,LANCIA BOLZANO +5801384841 REV 0,TUBO PER COMBUSTIBILE,0,0,2,1,10,10,1,0,8000,0,0.0,0,4,5801384841 REV0,LANCIA BOLZANO +5802915821,COMPL.MAN,0,0,0,1,15,15,1,0,5000,0,0.1,0,6,5802915821 R.02, +5802915822,COMPL.MAN,0,0,0,1,15,15,1,0,5000,0,0.1,0,6,5802915822 R.02, +504321532 REV 0,TUBO PER COMBUSTIBILE,0,0,2,1,10,10,1,0,8000,0,0.0,0,4,504321532 REV 0,LANCIA BOLZANO +5801970063 REV0,TUBO PER COMBUSTIBILE,0,0,2,1,10,10,1,0,8000,0,0.0,0,4,5801970063 REV0,LANCIA BOLZANO +60192054 REV01,TUBO PER COMBUSTIBILE,0,0,1,1,10,10,1,0,8000,0,0.0,0,4,60192054 REV01,LANCIA BOLZANO +5801862227,TUBO PER COMBUSTIBILE,0,0,1,1,10,10,1,0,8000,0,0.0,0,4,5801862227 REV0,LANCIA BOLZANO +5801970064 REV 0,TUBO PER COMBUSTIBILE,0,0,2,1,10,10,1,0,8000,0,0.0,0,4,5801970064 REV 0,LANCIA BOLZANO +5801970066 REV 1,TUBO PER COMBUSTIBILE,0,0,2,1,10,10,1,0,8000,0,0.0,0,4,5801970066 REV01,LANCIA BOLZANO +5801970065 REV0,TUBO PER COMBUSTIBILE,0,0,2,1,10,10,1,0,8000,0,0.0,0,4,5801970065 REV0,LANCIA BOLZANO +504321531 REV 0,TUBO PER COMBUSTIBILE,0,0,2,1,10,10,1,0,8000,0,0.0,0,4,504321531 REV 0,LANCIA BOLZANO +5801384840 REV0,TUBO PER COMBUSTIBILE,0,0,2,1,10,10,1,0,8000,0,0.0,0,4,5801384840 REV0,LANCIA BOLZANO +5801384839 REV0,TUBO PER COMBUSTIBILE,0,0,1,1,10,10,1,0,8000,0,0.0,0,4,5801384839 REV0,LANCIA BOLZANO +12970-13-2382,TUBEASSY-CLUTCH COOLING,0,0,0,0,10,10,3,0,7000,0,0.0,0,4,12970-13-2382,MULTIMATIC +12970-13-2384,TUBEASSY-CLUTCH COOLING,0,0,0,0,10,10,3,0,7000,0,0.0,0,4,12970-13-2384,MULTIMATIC +12970-13-2386,TUBEASSY-CLUTCH COOLING,0,0,0,0,10,10,2,0,7000,0,0.0,0,4,12970-13-2386,MULTIMATIC +5801970067 REV0,TUBO PER COMBUSTIBILE,0,0,2,1,10,10,1,0,8000,0,0.0,0,4,5801970067 REV0,LANCIA BOLZANO +61907016000 IN.D,FUEL HOSE PUMP CPL,0,0,0,1,10,10,0,0,7000,0,0.1,0,4,61907016000 IN.D,KTM +5802831451,TUBAZIONE RITORNO,0,0,0,1,10,10,1,0,5000,0,0.1,0,6,5802831451 R.03, +5802831453,TUBAZIONE MANDATA,0,0,0,1,10,10,1,0,5000,0,0.1,0,6,5802831453 R.03, +5802860939,TUBAZIONE TRAVERSA RADI,0,0,0,1,10,10,1,0,5000,0,0.1,0,6,5802860939 R.03, +5802831450,TUBAZIONE INTERMEDIE,0,0,0,1,10,10,1,0,5000,0,0.1,0,6,5802831450 R.03, +5802831452,TUBAZIONE INTERMEDIE,0,0,0,1,10,10,1,0,5000,0,0.1,0,6,5802831452 R.03, +5802860512,TUBAZIONE RITORNO RADIA,0,5000,0,1,10,10,1,0,5000,0,0.1,0,6,5802860512 R.03, +PY83-6B747-BA,ENGINE OIL ASSY-INLET,0,0,1,1,10,10,3,0,7000,0,0.0,0,4,PY83-6B747-BA,AST0N MARTIN +PY83-6B748-BA,ENGINE OIL ASY-OUTLET,0,0,1,1,10,10,3,0,7000,0,0.0,0,4,PY83-6B748-BA,AST0N MARTIN +5802908563,CONNECTION PIPE,0,0,1,2,20,20,1,0,2000,0,0.1,0,5,5802908563,FPT - 13535 +5802726236 REV02,OIL FILLER PIPE,0,0,1,1,10,10,1,0,500,0,0.1,0,4,5802726236 REV02,IVECO +5802726232 REV02,OIL FILLER PIPE,0,300,1,1,10,10,1,0,500,0,0.1,0,4,5802726232 REV02,IVECO +TBG-915988,TUBO PLASTICO SX,0,0,0,0,15,15,2,0,3100,0,0.0,0,4,TUBO PLASTICO SX,SAGOM TUBI +TBG-915989,TUBO PLASTICO DX,0,0,0,0,15,15,2,0,3100,0,0.0,0,4,TUBO PLASTICO DX,SAGOM TUBI +TBG-919634,TUBO PLASTICO SX,0,0,1,1,15,15,2,0,3100,0,0.0,0,1,TUBO PLASTICO ,SAGOM TUBI +16EA019SP,,0,0,2,1,10,10,6,0,2000,0,0.0,0,2,P16 MCLAREN,HOSE +91752479,HOSE FUEL,0,0,0,0,10,10,5,0,2000,0,0.0,0,5,91752479 REV.A,CNH - BNJ31 +91713309,HOSE FUEL,0,0,0,1,10,10,1,0,2000,0,0.0,0,5,91713309 REV.A,CNH - BNJ31 +TBG-919635,TUBO PLASTICO SX,0,0,1,1,15,15,2,0,3100,0,0.1,0,1,TUBO PLASTICO ,SAGOM TUBI +5803030264,DELIVERY PIPE COMPLETE,0,0,0,0,10,10,1,0,4000,0,0.0,0,4,5803030264 REV.0,IVECO +5803030758,DELIVERY PIPE COMPLETE,0,0,0,0,10,10,1,0,4000,0,0.0,0,4,5803030758 REV.0,IVECO +5803030799,DELIVERY PIPE COMPLETE,0,0,0,0,10,10,1,0,4000,0,0.0,0,4,5803030799 REV.0,IVECO +5803030800,DELIVERY PIPE COMPLETE,0,0,0,0,10,10,1,0,4000,0,0.0,0,4,5803030800 REV.0,IVECO +5803030840,DELIVERY PIPE COMPLETE,0,0,0,1,10,10,1,0,4000,0,0.0,0,4,5803030840 REV.0,IVECO +5803030842,DELIVERY PIPE COMPLETE,0,0,0,0,10,10,1,0,4000,0,0.0,0,4,5803030842 REV.0,IVECO +5803030878,DELIVERY PIPE COMPLETE,0,0,0,0,10,10,1,0,4000,0,0.0,0,4,5803030878 REV.0,IVECO +5802878056,,0,0,0,1,10,10,1,0,5000,0,0.0,0,6,5802878056 , +5802831451,,0,0,0,1,10,10,1,0,5000,0,0.0,0,6,5802831451 R.03, +5802967436,,0,0,0,1,10,10,1,0,5000,0,0.0,0,6,5802967436 R.03, +5802860512,,0,0,0,1,10,10,1,0,5000,0,0.0,0,6,5802860512 R.03, +5802989226,FPT,0,0,0,2,10,10,1,0,2000,0,0.0,0,4,5802989226, +5803028303,FPT,0,0,0,1,10,10,2,0,2000,0,0.0,0,4,5803028303, +5802989036,FPT,0,0,0,1,10,10,2,0,2000,0,0.0,0,4,5802989036, +5802269836,FPT,0,0,0,1,10,10,2,0,3000,0,0.0,0,4,5802269836, +000952054,j000952054,0,2200,0,0,5,5,3,0,2200,0,0.0,100,1,000952054,LANCIA BOLZANO diff --git a/config/csv_import/RICETTE-ST-TEN-11-FERRARI.csv b/config/csv_import/RICETTE-ST-TEN-11-FERRARI.csv new file mode 100644 index 0000000..7f2aed9 --- /dev/null +++ b/config/csv_import/RICETTE-ST-TEN-11-FERRARI.csv @@ -0,0 +1,5 @@ +verifica_codice_a_barre_abilitata,pressione_di_test_2,cliente,tempo_di_test_2,pressione_pre_riempimento,prova_tenuta_abilitata,modello_etichetta,tempo_di_test,pressione_pre_riempimento_2,prova_tenuta_abilitata_2,stampa_etichetta_abilitata,part_number,tempo_pre_riempimento_2,tempo_pre_riempimento,pressione_di_test,codice_a_barre,codice_ricetta +x,15000,Ferrari,10,1000,x,ferrari_30x16_203.prn,30,1000,x,x,000990684,0,0,2000,,000990684 +x,15000,Ferrari,10,1000,x,ferrari_30x16_203.prn,30,1000,x,x,000990685,0,0,2000,,000990685 +x,15000,Ferrari,10,1000,x,ferrari_30x16_203.prn,30,1000,x,x,000990686,0,0,2000,,000990686 +x,15000,Ferrai,10,1000,x,ferrari_30x16_203.prn,30,1000,x,x,000990708,0,0,5000,,000990708 diff --git a/config/machine_settings/st-ten-12.ini b/config/machine_settings/st-ten-12.ini index 7718e45..3ed3092 100644 --- a/config/machine_settings/st-ten-12.ini +++ b/config/machine_settings/st-ten-12.ini @@ -1,5 +1,5 @@ [machine] -description = ST-TEN-12 AUTOMATICO +description = ST-TEN-12 M2 C04 instruction_folder = st-ten-12 image_for_warning= st-ten-12 diff --git a/config/machine_settings/test-linux.ini b/config/machine_settings/test-linux.ini index 533934d..8b6ff12 100644 --- a/config/machine_settings/test-linux.ini +++ b/config/machine_settings/test-linux.ini @@ -13,7 +13,7 @@ neo_pixels: absent remote_api: absent tecna_t3: present vision_saver: absent -vision: absent +vision: present screwdriver: absent fixture_id: present barcode_recipe_selection: present @@ -26,11 +26,11 @@ poll_time: 10 hold_time: 10 [tecna_t3] -port: /dev/ttyUSB1 -model: t3p +port: /dev/ttyUSB0 +model: t3l [fixture_rfid] -port: USB0 +port: USB1 [digital_io] # OUTPUT MAP FOR VALVE CONTROL UNITS @@ -54,7 +54,6 @@ cliente: IVECO part_number: specificare part number canale_di_prova: 0 warning_img: -pid_pressure_correction: 105 dimensione_lotto_abilitata: n_componenti:1 istruzione_abilitata: x @@ -89,6 +88,8 @@ pressione_di_test_2: 1000 pressione_di_test_delta_massimo_2: 200 tempo_svuotamento_2: 1 pressione_svuotamento_2: 100 +pid_pressure_correction: 105 +pid_pressure_correction_2: 105 stampa_etichetta_abilitata: x modello_etichetta: ETA30x16_203dpi.prn diff --git a/config/vision/recipes/952054_dx.scsln b/config/vision/recipes/952054_dx.scsln new file mode 100644 index 0000000..73f2825 Binary files /dev/null and b/config/vision/recipes/952054_dx.scsln differ diff --git a/config/vision/recipes/952054_sx.scsln b/config/vision/recipes/952054_sx.scsln new file mode 100644 index 0000000..4fabb2d Binary files /dev/null and b/config/vision/recipes/952054_sx.scsln differ diff --git a/make_desktop_file_developer_test.sh b/make_desktop_file_developer_test.sh new file mode 100644 index 0000000..f7ca920 --- /dev/null +++ b/make_desktop_file_developer_test.sh @@ -0,0 +1,26 @@ +#!/bin/bash + +# Define the temporary file name for the .desktop file +SCRIPT="/tmp/shortcut-$(date +%s).desktop" + +# Create the .desktop file with the required content +echo "[Desktop Entry]" >> $SCRIPT +echo "Version=1.0" >> $SCRIPT +echo "Name=AVVIO PROGRAMMA COLLAUDO" >> $SCRIPT +echo "Comment=Shortcut to AVVIO PROGRAMMA COLLAUDO" >> $SCRIPT +echo "Exec=$HOME/PycharmProjects/st-ten-1/runmetest.sh" >> $SCRIPT +echo "Icon=$HOME/PycharmProjects/st-ten-1/src/ui/imgs/neo_red.ico" >> $SCRIPT +echo "Terminal=false" >> $SCRIPT +echo "Type=Application" >> $SCRIPT +echo "Categories=Utility;" >> $SCRIPT + +# Prompt for sudo password and copy the .desktop file to the user's Desktop +sudo cp $SCRIPT "$HOME/Desktop/AVVIO PROGRAMMA TEST.desktop" + +# Make the .desktop file executable +sudo chmod +x "$HOME/Desktop/AVVIO PROGRAMMA TEST.desktop" + +# Remove the temporary .desktop file +rm $SCRIPT + +echo "Shortcut created on Desktop" \ No newline at end of file diff --git a/runmetest.sh b/runmetest.sh new file mode 100755 index 0000000..08ce3eb --- /dev/null +++ b/runmetest.sh @@ -0,0 +1,4 @@ +#!/bin/bash -e +cd "$(dirname "$0")" +source "./venv/bin/activate" || source "./venv/Scripts/activate" || : +python -O "./src/main.py" --no-edgetpu --no-tflite --no-autotest --sim-archiver --sim-printer --system-id=test-linux --no-autotest --no-lock --sim-io --dev-portal --auto-login-admin $* diff --git a/src/components/archive_synchronizer.py b/src/components/archive_synchronizer.py index 28a0343..5c87fb3 100644 --- a/src/components/archive_synchronizer.py +++ b/src/components/archive_synchronizer.py @@ -273,7 +273,7 @@ class ArchiveSynchronizer(Component): self.log.error(f"id {record.id}: {path_in}: file not found.") return False except Forbidden as e: - if re.match("^Object '.*?' is subject to bucket's retention policy and cannot be deleted, overwritten or archived until .*?$", e._response.json()["error"]["message"]) is not None: + if re.match("^Object '.*?' is subject .*?$", e._response.json()["error"]["message"]) is not None: self.log.info(f"id {record.id}: {path_in}: already stored.") else: self.log.warning(f"id: {record.id}: failed to store remotely: {str(e)}: {e._response.json()}") diff --git a/src/components/component.py b/src/components/component.py index 81c3680..9fd0990 100644 --- a/src/components/component.py +++ b/src/components/component.py @@ -174,7 +174,7 @@ class Component(QObject): self._lock.release() return running - def wait_completion(self, timeout=5): + def wait_completion(self, timeout=10): """ waits until the requested action has been completed by the component this will return immediately if threaded=False was passed at component initialization diff --git a/src/components/hikrobot_sc/hikrobot_sc.py b/src/components/hikrobot_sc/hikrobot_sc.py index 880bdf6..27edd3b 100644 --- a/src/components/hikrobot_sc/hikrobot_sc.py +++ b/src/components/hikrobot_sc/hikrobot_sc.py @@ -14,11 +14,14 @@ from .hikrobot_dll import * class HikrobotSmartCamera(Component): - def __init__(self, config=None, name=None, period=0.5, lazy=True, paused=False, threaded=True): + def __init__(self, config=None, name=None, period=0.2, lazy=True, paused=False, threaded=True): super().__init__(config=config, name=name, period=period, lazy=lazy, paused=paused, threaded=threaded) self.simulate = "--sim-camera" in sys.argv self.num_cameras=1 self.connected = False + self.ok_memory = True + self.ok_frames = [] + self.ok_results =[] def config_changed(self): self.connected = False @@ -99,39 +102,53 @@ class HikrobotSmartCamera(Component): for cam_idx in range(self.num_cameras): cam = self.cam_list[cam_idx] self.log.info(f"GET FRAME CAMERA # {cam_idx}") - - # Trigger acquisition & test execution - nRet = mv_lib.MV_VS_SetCommandValue(cam["handle"], b"AcquisitionStart") - if nRet != MV_VS_OK: - self.log.error(f"CAM{cam_idx} AcquisitionStart failed! [{nRet&0xFFFFFFFF:#x}]") - self.connected = False - # get frame - frame_res = MV_VS_GetFrame(cam["handle"]) - if frame_res is not None: - res = frame_res["res"]["ScDeviceSolutionRunningResult"] - frame = frame_res["frame"] - # rotate frame - if rot[cam_idx] == "1": - frame = cv2.rotate(frame, cv2.ROTATE_90_CLOCKWISE) - if rot[cam_idx] == "2": - frame = cv2.rotate(frame, cv2.ROTATE_90_COUNTERCLOCKWISE) - if rot[cam_idx] == "3": - frame = cv2.rotate(frame, cv2.ROTATE_180) - - if concat_frame is None: - concat_frame = copy.deepcopy(frame) - else: - concat_frame = cv2.hconcat([concat_frame, frame]) - concat_results.append(not res) + if self.ok_results[cam_idx]: + # keep last good result + frame = self.ok_frames[cam_idx] + res = False else: - self.connected = False - super()._get([concat_frame, concat_results]) + # Trigger acquisition & test execution + nRet = mv_lib.MV_VS_SetCommandValue(cam["handle"], b"AcquisitionStart") + if nRet != MV_VS_OK: + self.log.error(f"CAM{cam_idx} AcquisitionStart failed! [{nRet&0xFFFFFFFF:#x}]") + self.connected = False + frame_res = None + else: + # get frame + frame_res = MV_VS_GetFrame(cam["handle"]) + + if frame_res is not None: + res = frame_res["res"]["ScDeviceSolutionRunningResult"] + frame = frame_res["frame"] + # rotate frame + if rot[cam_idx] == "1": + frame = cv2.rotate(frame, cv2.ROTATE_90_CLOCKWISE) + if rot[cam_idx] == "2": + frame = cv2.rotate(frame, cv2.ROTATE_90_COUNTERCLOCKWISE) + if rot[cam_idx] == "3": + frame = cv2.rotate(frame, cv2.ROTATE_180) + else: + self.connected = False + if concat_frame is None: + concat_frame = copy.deepcopy(frame) + else: + concat_frame = cv2.hconcat([concat_frame, frame]) + concat_results.append(not res) + if not res: + self.ok_results[cam_idx] = True + self.ok_frames[cam_idx] = copy.deepcopy(frame) + + super()._get([concat_frame, self.ok_results]) def resume(self): self.log.info(f"RESUMING") if not self.connected: self.config_changed() if self.connected: + # init OK result memory + self.ok_frames = [None for n in range(self.num_cameras)] + self.ok_results = [False for n in range(self.num_cameras)] + if len(self.cam_list) == self.num_cameras: # Dummy acquisition for each camera to avoid reading past images for cam_idx in range(self.num_cameras): diff --git a/src/components/modbus_component.py b/src/components/modbus_component.py index 7b30757..fc438ab 100644 --- a/src/components/modbus_component.py +++ b/src/components/modbus_component.py @@ -2,6 +2,7 @@ import sys import traceback import warnings +import pymodbus from PyQt5.QtWidgets import QMessageBox if "--sim-serial" in sys.argv: @@ -18,113 +19,190 @@ if "--sim-modbus" not in sys.argv: else: from components.dummies.pymodbus import ModbusClient -from PyQt5.QtCore import QMutex +from PyQt5.QtCore import QMutex, pyqtSignal from .component import Component class ModbusComponent(Component): + modbus_error_signal = pyqtSignal(str) + def __init__(self, config=None, name=None, period=1, lazy=True, paused=False, threaded=True, registers=None): super().__init__(config=config, name=name, period=period, lazy=lazy, paused=paused, threaded=threaded) self.registers = registers if registers is not None else {} self.lock = QMutex() def config_changed(self): - self.method = self.config[self.name].get("method", "rtu").lower() - 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()) - self.byteorder = getattr(Endian, self.config[self.name].get("byteorder", "Big").upper()) - self.wordorder = getattr(Endian, self.config[self.name].get("wordorder", "Little").upper()) - self.timeout = int(self.config[self.name].get("timeout", 1)) + + # Read configuration values + self.method = self.config[self.name].get("method", "rtu").lower() + 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()) + self.byteorder = getattr(Endian, self.config[self.name].get("byteorder", "Big").upper()) + self.wordorder = getattr(Endian, self.config[self.name].get("wordorder", "Little").upper()) + self.timeout = int(self.config[self.name].get("timeout", 1)) + + # Lock the interaction to ensure thread safety + self.lock.lock() + try: + # Initialize the Modbus client + self.client = ModbusClient( + method=self.method, + port=self.port, + stopbits=self.stopbits, + bytesize=self.bytesize, + parity=self.parity, + baudrate=self.baudrate, + timeout=self.timeout, + strict=False, + ) + if not self.client.connect(): + raise ConnectionError(f"Cannot connect to Modbus on port {self.port}") + + if not self.client.is_socket_open(): + raise ConnectionError(f"Connection socket not open on port {self.port}") + + except FileNotFoundError as e: + error_message = f"Serial port error: {self.port} not found. Check your configuration." + #self.log.error(error_message, exc_info=True) + self.modbus_error_signal.emit(error_message) + except Exception as e: + error_message = f"Configuration error: {str(e)}" + #self.log.error(error_message, exc_info=True) + self.modbus_error_signal.emit(error_message) + finally: + self.lock.unlock() + + def _read(self, register, count=1, **kwargs): + """Read holding registers with error handling.""" self.lock.lock() - self.client = ModbusClient( - method=self.method, - port=self.port, - stopbits=self.stopbits, - bytesize=self.bytesize, - parity=self.parity, - baudrate=self.baudrate, - timeout=self.timeout, - strict=False, - ) - if not self.client.connect(): - QMessageBox.critical(None, "ERRORE", f"ERRORE MODBUS - VERIFICARE CONNESSIONE USB") - exit(-1) - #raise ConnectionError("device not reachable (could not connect): {} ({})".format(self.name, self.port)) - - if not self.client.is_socket_open(): - raise ConnectionError("device not reachable (socket not open): {} ({})".format(self.name, self.port)) - self.lock.unlock() - - def _read(self, register, count=1,**kwargs): - while True: - self.lock.lock() - read = self.client.read_holding_registers(register, count=count,**kwargs) - self.lock.unlock() + try: + read = self.client.read_holding_registers(register, count=count, **kwargs) if read.isError(): - self.log.exception(traceback.format_exception(type(read), read, read.__traceback__)) - # raise read - else: - break - return read - - def _write(self, register, value,**kwargs): - while True: - self.lock.lock() - wrote = self.client.write_registers(register, value, skip_encode=True,**kwargs) + error_message = f"Modbus read failed: Could not read Modbus register {register}" + self.modbus_error_signal.emit(error_message) + raise ValueError(f"Modbus read error at register {register}") + return read + except pymodbus.exceptions.ConnectionException: + error_message = f"Modbus read failed: Connection error at port {self.port}" + #self.log.error(error_message, exc_info=True) + self.modbus_error_signal.emit(error_message) + return None # Return None to signal failure + except Exception as e: + error_message = f"Error reading Modbus register {register}: {str(e)}" + #self.log.error(error_message, exc_info=True) + self.modbus_error_signal.emit(error_message) + return None + finally: self.lock.unlock() + + def _write(self, register, value, **kwargs): + """Write to holding registers with error handling.""" + self.lock.lock() + try: + wrote = self.client.write_registers(register, value, skip_encode=True, **kwargs) + + # Check if the response indicates an error if wrote.isError(): - self.log.exception(traceback.format_exception(type(wrote), wrote, wrote.__traceback__)) - # raise wrote - else: - break + raise ValueError(f"Modbus write error at register {register}") + return wrote + except pymodbus.exceptions.ConnectionException as ce: + error_message = f"Modbus write failed: Connection error at port {self.port}" + #self.log.error(error_message, exc_info=True) + self.modbus_error_signal.emit(error_message) + except Exception as e: + error_message = f"Error writing Modbus register {register} with value {value}: {str(e)}" + #self.log.error(error_message, exc_info=True) + self.modbus_error_signal.emit(error_message) + finally: + self.lock.unlock() def _decode(self, read, *args, data_type="16bit_uint", gain=1, offset=0, **kwargs): - decoder = BinaryPayloadDecoder.fromRegisters(read.registers, byteorder=self.byteorder, wordorder=self.wordorder) - data = getattr(decoder, f"decode_{data_type}")(*args, **kwargs) - data = (data - offset) / gain - if data_type.endswith("uint"): - data = int(abs(data)) - elif data_type.endswith("int"): - data = int(data) - else: - raise NotImplementedError(f"data_type {data_type!r} is not supported") - return data + """Decode data safely.""" + if read is None: + error_message = "Error decoding Modbus data: No data to decode (read returned None)" + #self.log.error(error_message) + self.modbus_error_signal.emit(error_message) + return None + try: + decoder = BinaryPayloadDecoder.fromRegisters( + read.registers, byteorder=self.byteorder, wordorder=self.wordorder + ) + data = getattr(decoder, f"decode_{data_type}")(*args, **kwargs) + data = (data - offset) / gain + return int(abs(data)) if "uint" in data_type else int(data) + except AttributeError: + error_message = "Modbus read returned invalid data (NoneType encountered)" + #self.log.error(error_message) + self.modbus_error_signal.emit(error_message) + except Exception as e: + error_message = f"Error decoding Modbus data: {str(e)}" + #self.log.error(error_message, exc_info=True) + self.modbus_error_signal.emit(error_message) + return None def _encode(self, data, *args, data_type="16bit_uint", gain=1, offset=0, **kwargs): - builder = BinaryPayloadBuilder(byteorder=self.byteorder, wordorder=self.wordorder) - data = data * gain + offset - if data_type.endswith("uint"): - data = int(abs(data)) - elif data_type.endswith("int"): - data = int(data) - else: - raise NotImplementedError(f"data_type {data_type!r} is not supported") - getattr(builder, f"add_{data_type}")(data, *args, **kwargs) - return builder.build() + """Encode data for Modbus write with error handling.""" + try: + builder = BinaryPayloadBuilder(byteorder=self.byteorder, wordorder=self.wordorder) + data = data * gain + offset + if data_type.endswith("uint"): + data = int(abs(data)) + elif data_type.endswith("int"): + data = int(data) + else: + raise NotImplementedError(f"Data type {data_type!r} is not supported") + getattr(builder, f"add_{data_type}")(data, *args, **kwargs) + return builder.build() + except Exception as e: + error_message = f"Error encoding Modbus data: {str(e)}" + #self.log.error(error_message, exc_info=True) + self.modbus_error_signal.emit(error_message) + return None def read(self, register, *args, data_type="16bit_uint", gain=1, offset=0, **kwargs): - if data_type.startswith("16bit_"): - count = 1 - elif data_type.startswith("32bit_"): - count = 2 - else: - raise NotImplementedError(f"data_type {data_type!r} is not supported") - - return self._decode(self._read(register, count=count, **kwargs), *args, data_type=data_type, gain=gain,offset=offset) + """Read and decode Modbus register data with error handling.""" + try: + if data_type.startswith("16bit_"): + count = 1 + elif data_type.startswith("32bit_"): + count = 2 + else: + raise NotImplementedError(f"Data type {data_type!r} is not supported") + return self._decode( + self._read(register, count=count, **kwargs), + *args, + data_type=data_type, + gain=gain, + offset=offset, + ) + except Exception as e: + error_message = f"Error inside Modbus read: {str(e)}" + #self.log.error(error_message, exc_info=True) + self.modbus_error_signal.emit(error_message) + return None def write(self, register, data, *args, data_type="16bit_uint", gain=1, offset=0, **kwargs): - self._write(register, self._encode(data, *args, data_type=data_type, gain=gain, offset=offset),**kwargs) - - # def _get(self, data): - # # print("MODBUS", str(int(QThread.currentThreadId())), flush=True) - # super()._get(data) + """Encode and write data to Modbus registers with error handling.""" + try: + encoded_data = self._encode(data, *args, data_type=data_type, gain=gain, offset=offset) + if encoded_data is not None: + self._write(register, encoded_data, **kwargs) + except Exception as e: + error_message = f"Error inside Modbus write: {str(e)}" + #self.log.error(error_message, exc_info=True) + self.modbus_error_signal.emit(error_message) def __del__(self, event=None): - self.lock.lock() - if self.client.is_socket_open(): - self.client.close() - self.lock.unlock() + try: + self.lock.lock() + if self.client.is_socket_open(): + self.client.close() + except Exception as e: + error_message = f"Error during Modbus cleanup: {str(e)}" + #self.log.error(error_message, exc_info=True) + finally: + self.lock.unlock() \ No newline at end of file diff --git a/src/components/rfid_pn532.py b/src/components/rfid_pn532.py index 23796d3..ca1e750 100644 --- a/src/components/rfid_pn532.py +++ b/src/components/rfid_pn532.py @@ -85,5 +85,39 @@ class RFID_PN532(Component): self.rfid_error_signal.emit(False) self.mutex.unlock() - def write_tag(self,data): - self.data_to_write=copy.deepcopy(data) + def write_tag(self, data): + self.data_to_write = copy.deepcopy(data) + if not self.simulate: + if not self.connected: + self.open_device() + + if not self.connected: + self.log.error("Failed to connect to device") + return + + if self.mutex.tryLock(): + try: + target = self.clf.sense(RemoteTarget('106A'), RemoteTarget('106B'), RemoteTarget('212F')) + if target is not None: + tag = src.lib.nfc.tag.activate(self.clf, target) + if tag is not None and tag.ndef is not None: + self.log.info("Detected NFC tag, writing data...") + ndef_record = ndef.TextRecord(self.data_to_write) + + # IMPORTANT: DON'T CLOSE DEVICE BEFORE WRITING + tag.ndef.records = [ndef_record] # Write the record to the tag + self.log.info(f"Data '{self.data_to_write}' written successfully to tag.") + else: + self.log.error("Tag is not NDEF-compatible or invalid") + else: + self.log.debug("No tag present for writing") + except Exception as e: + self.log.error(f"Error during tag writing: {e}") + finally: + # Move closing outside the try block + self.mutex.unlock() # Unlock mutex always + + # Close AFTER writing and mutex release + if self.connected: + self.close_device() # close_device can raise exception + diff --git a/src/components/tecna_marposs_provaset_t3.py b/src/components/tecna_marposs_provaset_t3.py index 6cb70cb..1cecf41 100644 --- a/src/components/tecna_marposs_provaset_t3.py +++ b/src/components/tecna_marposs_provaset_t3.py @@ -1,5 +1,7 @@ +import time + from lib.db import Recipes, db -from PyQt5.QtCore import QSemaphore, pyqtSignal +from PyQt5.QtCore import QSemaphore, pyqtSignal, pyqtSlot from PyQt5.QtWidgets import QMessageBox from .component import Component @@ -11,9 +13,11 @@ from .tecna_marposs_provaset_t3p_registers import registers as t3p_registers class TecnaMarpossProvasetT3(ModbusComponent): _store_recipes_signal = pyqtSignal(object) _store_recipes_lock = QSemaphore(0) + tecna_error_signal = pyqtSignal(bool, str) # Emits (True, error_message) if error exists, else (False, "") def __init__(self, config=None, name=None, period=1, lazy=True, paused=False, threaded=True): super().__init__(config=config, name=name, period=period, lazy=lazy, paused=paused, threaded=threaded, registers=None) + self.modbus_error_signal.connect(self.handle_modbus_error) def config_changed(self): super().config_changed() @@ -26,13 +30,17 @@ class TecnaMarpossProvasetT3(ModbusComponent): else: raise NotImplementedError(f"tecna t3 model {self.model!r} not implemented.") self.set_measure_units() - self.units = self.get_measure_units() - self.max_program_number = self.read("Max number of programs") - self.saver_label_count = min(abs(int(self.config[self.name].get("saver_label_count", 1))), 0b1111) - self.saver_print_on_fail = 1 if self.config[self.name].get("saver_print_on_fail", "no").lower() in {"yes", "y", "on", "true", "1", "x"} else 0 - self.saver_label_template = min(abs(int(self.config[self.name].get("saver_label_template", 1))), 0b11111111) - self.model = self.config[self.name]["model"].lower() - self.log.info(f"units: {self.units}") + try: + self.units = self.get_measure_units() + self.max_program_number = self.read("Max number of programs") + self.saver_label_count = min(abs(int(self.config[self.name].get("saver_label_count", 1))), 0b1111) + self.saver_print_on_fail = 1 if self.config[self.name].get("saver_print_on_fail", "no").lower() in {"yes", "y", "on", "true", "1", "x"} else 0 + self.saver_label_template = min(abs(int(self.config[self.name].get("saver_label_template", 1))), 0b11111111) + self.model = self.config[self.name]["model"].lower() + self.log.info(f"units: {self.units}") + except Exception as e: + error_message = f"Error during config_changed operation: {str(e)}" + self.tecna_error_signal.emit(True, error_message) _pressure_units = {"mH2O": 0, "mbar": 1, "kPa": 2, "mmHg": 3, "inH2O": 4, "psi": 5, "mmH2O": 6, } # (se fondoscala <=6 bar) _leak_units = {"mmH2O": 0, "mbar": 1, "Pa": 2, "mmHg": 3, "inH2O": 4, "psi": 5, } @@ -125,111 +133,133 @@ class TecnaMarpossProvasetT3(ModbusComponent): return data @Component.reconfig_on_error - def read(self, register, *args, data_type=None, gain=None, offset=None, formatting=None, decoding_map=None, **kwargs): - if type(register) is str: - register, s = self.registers[register] + def read(self, register, *args, data_type=None, gain=None, offset=None, formatting=None, decoding_map=None, + **kwargs): + try: + if type(register) is str: + register, s = self.registers[register] + if data_type is None: + data_type = s.get("dt", None) + if gain is None: + gain = s.get("g", None) + if offset is None: + offset = s.get("o", None) + if formatting is None: + formatting = s.get("f", None) + if decoding_map is None: + decoding_map = s.get("decoding", None) + if not len(args): + args = s.get("a", []) + if not len(kwargs): + kwargs = s.get("k", {}) if data_type is None: - data_type = s.get("dt", None) + data_type = "16bit_uint" if gain is None: - gain = s.get("g", None) + gain = 1 if offset is None: - offset = s.get("o", None) - if formatting is None: - formatting = s.get("f", None) - if decoding_map is None: - decoding_map = s.get("decoding", None) - if not len(args): - args = s.get("a", []) - if not len(kwargs): - kwargs = s.get("k", {}) - if data_type is None: - data_type = "16bit_uint" - if gain is None: - gain = 1 - if offset is None: - offset = 0 - return self._convert_from_format( - super().read( + offset = 0 + return self._convert_from_format( + super().read( + register, + *args, + data_type=data_type, + gain=gain, + offset=offset, + **kwargs, + ), + formatting=formatting, + decoding_map=decoding_map, + ) + except Exception as e: + error_message = f"Error during read operation on register {register}: {str(e)}" + self.log.error(error_message, exc_info=True) + self.tecna_error_signal.emit(True, error_message) + raise # Re-raise the exception for further upstream handling if needed + + @Component.reconfig_on_error + @Component.reconfig_on_error + def write(self, register, data, *args, data_type=None, gain=None, offset=None, formatting=None, encoding_map=None, + **kwargs): + try: + if type(register) is str: + register, s = self.registers[register] + if data_type is None: + data_type = s.get("dt", None) + if gain is None: + gain = s.get("g", None) + if offset is None: + offset = s.get("o", None) + if formatting is None: + formatting = s.get("f", None) + if encoding_map is None: + encoding_map = s.get("encoding", None) + if not len(args): + args = s.get("a", []) + if not len(kwargs): + kwargs = s.get("k", {}) + if data_type is None: + data_type = "16bit_uint" + if gain is None: + gain = 1 + if offset is None: + offset = 0 + return super().write( register, + self._convert_to_format( + data, + formatting=formatting, + encoding_map=encoding_map, + ), *args, data_type=data_type, gain=gain, offset=offset, **kwargs, - ), - formatting=formatting, - decoding_map=decoding_map, - ) - - @Component.reconfig_on_error - def write(self, register, data, *args, data_type=None, gain=None, offset=None, formatting=None, encoding_map=None, **kwargs): - if type(register) is str: - register, s = self.registers[register] - if data_type is None: - data_type = s.get("dt", None) - if gain is None: - gain = s.get("g", None) - if offset is None: - offset = s.get("o", None) - if formatting is None: - formatting = s.get("f", None) - if encoding_map is None: - encoding_map = s.get("encoding", None) - if not len(args): - args = s.get("a", []) - if not len(kwargs): - kwargs = s.get("k", {}) - if data_type is None: - data_type = "16bit_uint" - if gain is None: - gain = 1 - if offset is None: - offset = 0 - return super().write( - register, - self._convert_to_format( - data, - formatting=formatting, - encoding_map=encoding_map, - ), - *args, - data_type=data_type, - gain=gain, - offset=offset, - **kwargs, - ) + ) + except Exception as e: + error_message = f"Error during write operation on register {register} with data {data}: {str(e)}" + self.log.error(error_message, exc_info=True) + self.tecna_error_signal.emit(True, error_message) + raise # Re-raise the exception for further upstream handling if needed @Component.reconfig_on_error def _get(self): - # print("TECNA", str(int(QThread.currentThreadId())), flush=True) - # READ INFO - info = {r: self.read(r) for r in [ - "Real time test pressure output", - "Real time differential pressure output", - "Real time pressure line regulator", - "Active alarm flags", - "Active test program number", - "Running test: active phase", - "Running test: test type", - "Running test: sequence index", - "Digital inputs status (mask)", - # "Digital outputs status (mask)", - ]} - if self.model == "t3p": - pass - elif self.model == "t3l": - info.update({r: self.read(r) for r in [ - "Active not severe alarm flags", - ]}) - else: - raise NotImplementedError(f"Tecna t3 model {self.model!r} not implemented.") - if info["Running test: active phase"] == "FINE TEST": # "END TEST, WAITING THE START OF A NEW TEST": - info.update(self.get_test_results()) - for round_me in ["measured leak"]: - if round_me in info.keys(): - info.update({round_me:float(f"{info[round_me]:.2f}")}) - self.log.debug(str(info)) - super()._get([info]) + try: + # READ INFO + info = {r: self.read(r) for r in [ + "Real time test pressure output", + "Real time differential pressure output", + "Real time pressure line regulator", + "Active alarm flags", + "Active test program number", + "Running test: active phase", + "Running test: test type", + "Running test: sequence index", + "Digital inputs status (mask)", + # "Digital outputs status (mask)", + ]} + if self.model == "t3p": + pass + elif self.model == "t3l": + info.update({r: self.read(r) for r in [ + "Active not severe alarm flags", + ]}) + else: + raise NotImplementedError(f"Tecna t3 model {self.model!r} not implemented.") + + if info["Running test: active phase"] == "FINE TEST": # "END TEST, WAITING THE START OF A NEW TEST": + info.update(self.get_test_results()) + + for round_me in ["measured leak"]: + if round_me in info.keys(): + info.update({round_me: float(f"{info[round_me]:.2f}")}) + self.log.debug(str(info)) + super()._get([info]) + except Exception as e: + error_message = f"Error during _get operation: {str(e)}" + self.log.error(error_message, exc_info=True) + self.tecna_error_signal.emit(True, error_message) + raise # Re-raise the exception for further upstream handling @Component.reconfig_on_error def _set(self, val=None): @@ -299,7 +329,7 @@ class TecnaMarpossProvasetT3(ModbusComponent): "FSL - Discharge limit": step.spec["flush_pressure"], "PSQ - Next sequence program PSOUT mode": 0, "RAMPS: T1 configuration": pid_ramps, - "PID: pressure correction": step.spec["pid_pressure_correction"], + "PID: pressure correction": step.spec.get("pid_pressure_correction",0), "Various flags": 0b0000000000010000 if self.config["recipes_defaults"]["tester_discharge_enable"] == "yes" else 0b0000000000000000 } @@ -402,6 +432,7 @@ class TecnaMarpossProvasetT3(ModbusComponent): # READ ALL PARAMETERS for register_name, field_name in spec.items(): recipe_data[field_name] = self.read(register_name) + time.sleep(0.01) recipe_name=self.int_array_to_str([recipe_data[f"R{wn}"] for wn in range(8)]) recipe_code=self.int_array_to_str([recipe_data[f"C{wn}"] for wn in range(12)]) recipe_f1=self.int_array_to_str([recipe_data[f"F1_{wn}"] for wn in range(8)]) @@ -446,3 +477,19 @@ class TecnaMarpossProvasetT3(ModbusComponent): break result+=char2 return result + + @pyqtSlot(str) + def handle_modbus_error(self, error_message): + """ + Handle errors received from the ModbusComponent and send + appropriate error signals for Tecna Marposs. + """ + if error_message: + # Emit the Tecna-specific error signal with True and the error message + self.tecna_error_signal.emit(True, error_message) + self.log.error(f"Modbus error encountered: {error_message}") + return True, error_message + else: + # Emit no error (False) + self.tecna_error_signal.emit(False, "") + return False, None \ No newline at end of file diff --git a/src/scripts/save_tecna_recipes_to_csv.py b/src/scripts/save_tecna_recipes_to_csv.py index 2d41a96..e3094eb 100644 --- a/src/scripts/save_tecna_recipes_to_csv.py +++ b/src/scripts/save_tecna_recipes_to_csv.py @@ -125,11 +125,21 @@ try: def read_recipes(self): self.recipes = {} max_num_recipes=self.components["tecna_t3"].max_program_number - max_num_recipes=300 + #max_num_recipes=100 logging.info(f"NUMBER OF RECIPES TO BE READ: {max_num_recipes}") for recipe_num in range(1, max_num_recipes+1): - logging.info(f"READING RECIPE #{recipe_num}") - self.recipes[recipe_num] = self.components["tecna_t3"].read_recipe(recipe_num) + recipe_ok=False + for retry in range(3): + logging.info(f"READING RECIPE #{recipe_num}") + try: + self.recipes[recipe_num] = self.components["tecna_t3"].read_recipe(recipe_num) + recipe_ok=True + break + except Exception as e: + logging.info(f"ERROR - RETRYING RECIPE #{recipe_num}") + if not recipe_ok: + self.recipes[recipe_num] = {"recipe_name":"READ_ERROR"} + def export_recipes(self, csv_path=None): if csv_path is None: diff --git a/src/ui/barcode_recipe_selection/barcode_recipe_selection.py b/src/ui/barcode_recipe_selection/barcode_recipe_selection.py index 28faf93..244a634 100644 --- a/src/ui/barcode_recipe_selection/barcode_recipe_selection.py +++ b/src/ui/barcode_recipe_selection/barcode_recipe_selection.py @@ -6,14 +6,52 @@ from PyQt5 import Qt from lib.helpers import timing from PyQt5.QtCore import Qt, QTimer, QThread from PyQt5.QtGui import QKeySequence, QPixmap, QPalette, QColor -from PyQt5.QtWidgets import QShortcut, QApplication +from PyQt5.QtWidgets import QShortcut, QApplication, QDialog, QVBoxLayout, QLabel, QPushButton, QPlainTextEdit, \ + QHBoxLayout, QMessageBox from ui.test_test import Test_Test from ui.widget import Widget from lib import db -from lib.db.models import Recipes +from lib.db.models import Recipes , Users test_scan="xxx\nyyy\nzzz" + +class TagWindow(QDialog): + def __init__(self, parent=None): + super().__init__(parent) + + self.setWindowTitle("Scrittura NFC") + self.setFixedSize(400, 300) + + layout = QVBoxLayout(self) + + + self.text_edit = QPlainTextEdit(self) + self.text_edit.setPlaceholderText("Scanerizza il QRcode della Dima") + self.text_edit.setFixedHeight(100) + + + layout.addWidget(self.text_edit) + button_layout = QHBoxLayout() + + + self.ok_button = QPushButton("Conferma Tag", self) + self.ok_button.clicked.connect(self.on_ok_clicked) + + # Create the Close button + self.close_button = QPushButton("Chiudi", self) + self.close_button.clicked.connect(self.close) + + button_layout.addWidget(self.ok_button) + button_layout.addWidget(self.close_button) + layout.addLayout(button_layout) + + def on_ok_clicked(self): + entered_text = self.text_edit.toPlainText() + print(f"Text Entered: {entered_text}") + self.accept() + + class Barcode_Recipe_Selection(Test_Test): def __init__(self, parent): super().__init__() @@ -34,6 +72,8 @@ class Barcode_Recipe_Selection(Test_Test): self.status_palettes["warning"].setColor(QPalette.Base, QColor(255, 165, 0)) self.status_palettes[""].setColor(QPalette.Base, QColor(255, 255, 0)) + self.tag_b.clicked.connect(self.open_tag_window) + self.recipe_selection_b.clicked.connect(self.parent.set_recipe_mode_table) self.barcode_input_l.setFocus() @@ -49,6 +89,12 @@ class Barcode_Recipe_Selection(Test_Test): self.ok_timer.setSingleShot(True) self.ok_timer.timeout.connect(self.set_recipe) + session= Users.get_session() + + if session.is_admin: + self.tag_b.setVisible(True) + else: + self.tag_b.setVisible(False) def start(self, recipe=None, step=None, pieces=None): @@ -113,4 +159,35 @@ class Barcode_Recipe_Selection(Test_Test): self.parent_assembly_widget().set_text("SCANSIONARE BARCODE SELEZIONE RICETTA") + def open_tag_window(self): + # Open the tag writing window + self.tag_window = TagWindow(self) + result = self.tag_window.exec_() # Get the result of the dialog (OK or Cancel) + if result != QDialog.Accepted: + return # If canceled, return + + entered_text = self.tag_window.text_edit.toPlainText().strip() + if not entered_text: + QMessageBox.warning( + self, + "Warning", + "The text is empty. Please enter text to write to the tag.", + ) + return + + # Check if the tag is empty + if self.parent.components["fixture_id"].current_data: + # Tag already contains data, ask for confirmation to overwrite + confirmation_result = QMessageBox.question( + self, + "Tag Already Written", + "The tag already contains data. Do you want to overwrite it?", + QMessageBox.Yes | QMessageBox.No, + ) + if confirmation_result == QMessageBox.No: + return # User does not want to overwrite, so return without writing + + # Write the entered text to the tag + self.parent.components["fixture_id"].write_tag(entered_text) + QMessageBox.information(self, "Success", "The tag has been successfully written.") diff --git a/src/ui/barcode_recipe_selection/barcode_recipe_selection.ui b/src/ui/barcode_recipe_selection/barcode_recipe_selection.ui index b45d4a3..33b402b 100644 --- a/src/ui/barcode_recipe_selection/barcode_recipe_selection.ui +++ b/src/ui/barcode_recipe_selection/barcode_recipe_selection.ui @@ -13,54 +13,146 @@ Test Connector - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - 600 - 0 - - - - - 600 - 200 - - - - - 20 - - - - - - - - - - - - 20 - - - - TORNA ALLA TABELLA RICETTE - - + + + + + + + + + Qt::Horizontal + + + + 308 + 20 + + + + + + + + + 20 + + + + TORNA ALLA TABELLA RICETTE + + + + + + + + 600 + 0 + + + + + 600 + 200 + + + + + 20 + + + + + + + + + + + Qt::Vertical + + + + 20 + 78 + + + + + + + + Qt::Horizontal + + + + 378 + 20 + + + + + + + + Qt::Vertical + + + + 20 + 78 + + + + + + + + Qt::Vertical + + + + 20 + 168 + + + + + + + + + + + 0 + 0 + + + + + 11 + + + + SCRIVI TAG NFC + + + + + + + Qt::Vertical + + + + 20 + 28 + + + + + diff --git a/src/ui/test/test.py b/src/ui/test/test.py index 033bac3..df3d1c5 100755 --- a/src/ui/test/test.py +++ b/src/ui/test/test.py @@ -28,7 +28,6 @@ from ui.test_vision import Test_Vision from ui.test_warning_img import Test_Warning_Img from ui.widget import Widget from components import ArchiveSynchronizer -from src.components.rfid_pn532 import RFID_PN532 class Test(Widget): @@ -58,17 +57,28 @@ class Test(Widget): else: self.flag_label.setVisible(False) + self.active_errors = [] # List to hold current errors (type: tuples of (message, is_error)) + self.current_error_index = 0 # Keeps track of the current error index during alternation + + # Timer for alternating errors + self.error_timer = QTimer() + self.error_timer.setInterval(2000) # Fire every 2 seconds + self.error_timer.timeout.connect(self.display_current_error) # Connect the timer to the display logic + # SHOW AND UPDATE TIME CLOCK self.refresh_time(init=True) # INIT RECIPE self.recipe = None - self.auto_import = Recipe_Selection - self.archive_synch = ArchiveSynchronizer(config=config) - - self.rfid = RFID_PN532(config=config) + if "fixture_id" in self.components: + self.rfid = self.components["fixture_id"] + self.rfid.rfid_error_signal.connect(self.handle_rfid_error) + if "tecna_t3" in self.components: + self.tecna = self.components["tecna_t3"] + self.tecna.tecna_error_signal.connect(self.handle_modbus_error) self.error_label.setText("") self.error_label.setStyleSheet("QLabel { color: red; }") - self.rfid.rfid_error_signal.connect(self.handle_rfid_error) + + if self.config["hardware_config"]["barcode_recipe_selection"] == "present": self.recipe_selection_mode = "barcode" @@ -78,6 +88,7 @@ class Test(Widget): self.tester_component = None if self.config["hardware_config"]["tecna_t3"] == "present": self.tester_component = "tecna_t3" + self.components["tecna_t3"].tecna_error_signal.connect(self.handle_modbus_error) elif self.config["hardware_config"]["furness_controls"] == "present": self.tester_component = "furness_control" @@ -828,13 +839,89 @@ class Test(Widget): self.fail_cycle() self.change_recipe() + def add_error(self, message, is_error): + """ + Add a new error message to the list of active errors. + + Args: + message (str): The error message to add. + is_error (bool): True if it's an error that should show in red, False for non-errors. + """ + # Avoid duplicates + if (message, is_error) not in self.active_errors: + self.active_errors.append((message, is_error)) + + # Start the timer if it's not already active + if not self.error_timer.isActive(): + self.error_timer.start() + + def remove_error(self, message): + """ + Remove an error message from the list of active errors. + + Args: + message (str): The error message to remove. + """ + self.active_errors = [err for err in self.active_errors if err[0] != message] + + # Reset the error index if necessary + if self.current_error_index >= len(self.active_errors): + self.current_error_index = 0 + + # Stop the timer if there are no more errors + if not self.active_errors: + self.error_label.setText("") + self.error_label.setStyleSheet("") + self.error_timer.stop() + + def display_current_error(self): + """ + Display the current error from the active errors list. If there are multiple errors, + it alternates between them every time this function is called. + """ + if self.active_errors: + # Get the current error message and update the label + message, is_error = self.active_errors[self.current_error_index] + self.error_label.setText(message) + if is_error: + self.error_label.setStyleSheet("color: red;") + else: + self.error_label.setStyleSheet("color: green;") + + # Move to the next error for the next call to this method + self.current_error_index = (self.current_error_index + 1) % len(self.active_errors) + else: + # Clear the label if there are no errors + self.error_label.setText("") + self.error_label.setStyleSheet("") + @pyqtSlot(bool) def handle_rfid_error(self, connected): + """ + Handle errors related to the RFID system. + + Args: + connected (bool): True if connected, False if not. + """ if connected: - self.error_label.setText("") # Update the GUI label + self.remove_error("Errore RFID.") # Remove the RFID error else: - self.error_label.setText("Errore RFID.") # Update the GUI label - self.error_label.setStyleSheet("color: red;") + self.add_error("Errore RFID.", True) # Add the RFID error + + @pyqtSlot(bool, str) + def handle_modbus_error(self, has_error, error_message): + """ + Handle Tecna/Modbus errors and manage the error list. + + Args: + has_error (bool): True if there is an error, False otherwise. + error_message (str): The error message to add. + """ + #print(f"DEBUG: Modbus error handler called - has_error={has_error}, error_message={error_message}") # Debugging + if has_error: + self.add_error(f"Errore Tecna", True) # Add the Modbus error + else: + self.remove_error(f"Errore Tecna") # Remove the Modbus error def update_label_with_args(self, extra_info=None): """ diff --git a/src/ui/test/test.ui b/src/ui/test/test.ui index 48bb155..c4f86fa 100755 --- a/src/ui/test/test.ui +++ b/src/ui/test/test.ui @@ -7,7 +7,7 @@ 0 0 1192 - 114 + 120 @@ -412,7 +412,7 @@ CONTATORE - + diff --git a/src/ui/test_leak/test_leak.py b/src/ui/test_leak/test_leak.py index cde34c8..b83c073 100644 --- a/src/ui/test_leak/test_leak.py +++ b/src/ui/test_leak/test_leak.py @@ -4,7 +4,7 @@ import weakref from PyQt5.QtGui import QPixmap from PyQt5.QtWidgets import QMessageBox, QDialog, QApplication -from PyQt5.QtCore import Qt +from PyQt5.QtCore import Qt, pyqtSlot from ui import Dialog from ui.test_instructions_reminder import Test_Instructions_Reminder from ui.test_test import Test_Test @@ -26,6 +26,8 @@ class Test_Leak(Test_Test): self.show_instruction_b.setVisible("show_instructions" in self.parent.config["hardware_config"].keys()) self.show_instruction_b.clicked.connect(self.show_instruction) + self.components[self.tester_component].tecna_error_signal.connect(self.handle_modbus_error) + def show_instruction(self): dialog=Dialog() dialog.setCentralWidget(Test_Instructions_Reminder(recipe=self.parent.recipe,bench_name=self.parent.config.machine_id)) @@ -312,3 +314,15 @@ class Test_Leak(Test_Test): def save_last(self): if self.last is None: return + + @pyqtSlot(bool, str) + def handle_modbus_error(self, error, error_message): + """ + Handle errors received from the Modbus component and stop the test gracefully. + """ + if error: + self.display_text( + text=f"ERRORE TECNA TEST INTERROTTO.", + bg_color="red", text_color="white" + ) + self.stop_test() diff --git a/src/ui/test_vision/test_vision.py b/src/ui/test_vision/test_vision.py index ef1104c..9d55bbc 100644 --- a/src/ui/test_vision/test_vision.py +++ b/src/ui/test_vision/test_vision.py @@ -15,7 +15,7 @@ class Test_Vision(Test_Test): def __init__(self, components=None, recipe=None, step=None, pieces=None, run_once=False, reset_on_start=True, enable_override=True): super().__init__(components=components, recipe=recipe, step=step, pieces=pieces, run_once=run_once, reset_on_start=reset_on_start, enable_override=enable_override) if "--sim-camera" not in sys.argv: - self.ok_counter_limit = 2 + self.ok_counter_limit = 3 else: self.ok_counter_limit = 1 # DETECTIONS TABLE