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