Merge remote-tracking branch 'origin/master'

This commit is contained in:
ST-TEN-11 2024-12-09 15:24:57 +01:00
commit 1d5845c872
68 changed files with 418 additions and 55 deletions

View File

@ -0,0 +1,49 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="1200"
height="700"
viewBox="0 0 1200 700"
version="1.1"
id="svg5"
xml:space="preserve"
inkscape:version="1.2.2 (1:1.2.2+202212051550+b0a8486541)"
sodipodi:docname="DEFAULT.svg"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"><sodipodi:namedview
id="namedview7"
pagecolor="#505050"
bordercolor="#eeeeee"
borderopacity="1"
inkscape:showpageshadow="0"
inkscape:pageopacity="0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#505050"
inkscape:document-units="mm"
showgrid="false"
inkscape:zoom="1.0570734"
inkscape:cx="365.15913"
inkscape:cy="357.59106"
inkscape:window-width="2560"
inkscape:window-height="1023"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="layer1" /><defs
id="defs2" /><g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"><text
xml:space="preserve"
style="font-style:normal;font-weight:normal;font-size:40px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#ff7f2a;fill-opacity:1;stroke:#aa4400"
x="277.31964"
y="373.9111"
id="text226"><tspan
sodipodi:role="line"
id="tspan224"
x="277.31964"
y="373.9111"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:53.3333px;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;stroke:#aa4400;fill:#ff7f2a">DISEGNO NON DISPONIBILE</tspan></text></g></svg>

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

View File

@ -56,18 +56,20 @@ status_root:api/device-info-update/
download_root:media/uploads/
images_path: data/images
poll_time: 60
hold_time: 30
poll_time: 10
hold_time: 10
service_account_json: config/machine_settings/gcloud_default.json
bucket_id: st_ten_img
[archive_synchronizer_extra]
archive_endpoint: https://r5portal.it/api/st-ten-save/
portal_address: https://r5portal.it/
archive_root:api/st-ten-save/
status_root:api/device-info-update/
download_root:media/uploads/
archive_endpoint: http://172.20.3.13/api/st-ten-save/
images_path: data/images
poll_time: 60
hold_time: 30
service_account_json: config/machine_settings/gcloud_default.json
bucket_id: st_ten_img
poll_time: 10
hold_time: 10
[label_printer]
platform: windows

View File

@ -5,6 +5,7 @@ image_for_warning= st-ten-1
[hardware_config]
archive_synchronizer: present
archive_synchronizer_extra: present
galaxy_camera: absent
uvc_camera: absent
label_printer: present
@ -18,6 +19,12 @@ screwdriver: absent
digital_io: present
external_flush_blow: absent
# VERO PROJECT LOCAL SERVER
[archive_synchronizer_extra]
portal_address: http://172.20.3.13:45006/
poll_time: 10
hold_time: 10
[tecna_t3]
port: COM4
model: t3l

View File

@ -5,6 +5,7 @@ image_for_warning= st-ten-14
[hardware_config]
archive_synchronizer: present
archive_synchronizer_extra: present
uvc_camera: absent
label_printer: present
neo_pixels: absent
@ -19,13 +20,19 @@ fixture_id: absent
discard_box: absent
enforce_piece_removal: no
# VERO PROJECT LOCAL SERVER
[archive_synchronizer_extra]
portal_address: http://172.20.3.13:45007/
poll_time: 10
hold_time: 10
[tecna_t3]
port: /dev/ttyUSB0
model: t3l
[label_printer]
platform: linux
printer: ZTC-ZD421-203dpi-ZPL
printer: Zebra_Technologies_ZTC_ZD421-203dpi_ZPL
[digital_io]
# OUTPUT MAP FOR FIXTURE CONNECTOR

View File

@ -0,0 +1,98 @@
[machine]
description = ST-TEN-15 AUTOMATICO
instruction_folder = st-ten-15
image_for_warning= st-ten-15
[hardware_config]
archive_synchronizer: present
uvc_camera: absent
label_printer: present
neo_pixels: absent
remote_api: absent
tecna_t3: present
vision_saver: absent
vision: absent
screwdriver: absent
digital_io: present
barcode_recipe_selection: present
fixture_id: present
discard_box: absent
second_leak_test: present
dual_channel: absent
#enforce_piece_removal: yes
[tecna_t3]
port: /dev/ttyUSB0
model: t3p
[label_printer]
platform: linux
printer: zd421
[digital_io]
# OUTPUT MAP FOR FIXTURE CONNECTOR
id_fixture: USB-5860,BID#0
discard_idx:12 # BIT NUMBER OF THE I/0 MODULE USED FOR DISCARD SENSING
[fixture_rfid]
port: dev/ttyUSB1
[recipe]
recipe_name_field: codice_ricetta
part_number_field: codice_prodotto
label_template_field: modello_etichetta
description_field: descrizione
[recipes_defaults]
tester_discharge_enable: yes
dimensione_lotto_abilitata: x
tempo_pre_riempimento: 0
pressione_pre_riempimento: 1000
tempo_riempimento: 15
tempo_assestamento: 15
tempo_di_test: 10
n_componenti:1
percentuale_minima_pressione_assestamento: 5
percentuale_massima_pressione_assestamento: 5
pressione_di_test_delta_minimo: 30
pressione_di_test: 7000
pressione_di_test_delta_massimo: 30
tempo_svuotamento: 0
pressione_svuotamento: 100
canale_di_prova: 1
prova_tenuta_abilitata_2:
tempo_pre_riempimento_2: 0
pressione_pre_riempimento_2: 1000
tempo_riempimento_2: 20
tempo_assestamento_2: 20
tempo_di_test_2: 10
percentuale_minima_pressione_assestamento_2: 5
percentuale_massima_pressione_assestamento_2: 5
pressione_di_test_delta_minimo_2: 30
pressione_di_test_2: 15000
pressione_di_test_delta_massimo_2: 30
tempo_svuotamento_2: 0
pressione_svuotamento_2: 100
canale_di_prova_2: 2
modello_etichetta: EtichettaR5_Montaggio_1prova.prn
pid_pressure_correction: 105
istruzione_abilitata_extra:
[autotest_leak]
enabled: true
pre_filling_time: 0
pre_filling_pressure: 1000
filling_time: 10
settling_time: 10
settling_pressure_min_percent: 5
settling_pressure_max_percent: 5
test_pressure: 7000
test_time: 10
test_pressure_qpos: 10 #Q+ Upper test leak limit
test_pressure_qneg: 30 #Q- Lower test leak limit
test_pressure_tt_qpos: 1 # Q+ Upper test leak limit (tube-tube)
test_pressure_tt_qneg: 5 # Q- Lower test leak limit (tube-tube)
flush_time: 1
flush_pressure: 100
relay_config: 1

View File

@ -2,8 +2,10 @@
description = ST-TEN-4 - REPARTO PREFORMATURA - MAPPANO
instruction_folder = st-ten-4
image_for_warning= st-ten-4
[hardware_config]
archive_synchronizer: present
archive_synchronizer_extra: present
uvc_camera: absent
label_printer: present
neo_pixels: absent
@ -13,6 +15,12 @@ vision_saver: absent
vision: absent
screwdriver: absent
# VERO PROJECT LOCAL SERVER
[archive_synchronizer_extra]
portal_address: http://172.20.3.13:45008/
poll_time: 10
hold_time: 10
[tecna_t3]
port: COM4
model: t3p

View File

@ -16,6 +16,7 @@ screwdriver: absent
#digital_io: present
digital_io_flush_blow: present
second_leak_test: present
barcode_recipe_selection: present
external_flush_blow: present # EXTERNAL BOX CONTROLLING MULTI-CHANNEL TEST (IF PRESENT), BLOW-CLEANING AND EXTERNAL FLUSH
dual_channel: present
#fixture_id: present

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 98 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 82 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

View File

@ -4,6 +4,7 @@ import re
import sys
import threading
import time
from datetime import datetime
from pathlib import Path
import requests
@ -12,13 +13,14 @@ import requests
from google.api_core.exceptions import Forbidden
from google.cloud import storage
from requests import JSONDecodeError
from lib.db import Archive, db
from lib.db.models import Log
from PyQt5.QtCore import QThread
from requests.adapters import HTTPAdapter, Retry
from urllib3.exceptions import InsecureRequestWarning
from .component import Component
from ui.helpers import get_main_window
# Suppress insecure request warning
requests.packages.urllib3.disable_warnings(category=InsecureRequestWarning)
@ -26,6 +28,7 @@ requests.packages.urllib3.disable_warnings(category=InsecureRequestWarning)
class ArchiveSynchronizer(Component):
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)
self.main_window = None
self.simulate = "--sim-archiver" in sys.argv
self.machine_status = "logged-in"
self.machine_id = None
@ -43,23 +46,50 @@ class ArchiveSynchronizer(Component):
self._do_set_period({"period": float(self.config[self.name]["poll_time"])})
self.hold_time = round(float(self.config[self.name]["hold_time"]) * 1000)
self.gcs_client = storage.Client.from_service_account_json(self.config[self.name]["service_account_json"])
self.gcs_client._http.mount("", HTTPAdapter(max_retries=Retry(total=0))) # this seems to be useless
self.gcs_client._http.adapters.move_to_end("", last=False) # this seems to be useless
self.bucket_id = self.config[self.name]["bucket_id"]
if self.name == "archive_synchronizer":
self.gcs_client = storage.Client.from_service_account_json(self.config[self.name]["service_account_json"])
self.gcs_client._http.mount("", HTTPAdapter(max_retries=Retry(total=0))) # this seems to be useless
self.gcs_client._http.adapters.move_to_end("", last=False) # this seems to be useless
self.bucket_id = self.config[self.name]["bucket_id"]
self.gcs_bucket = None
@db.connection_context()
def _get(self):
for record in list(Archive.select().where((Archive.archived != True) | (Archive.uploaded != True))): # using "is not True" breaks the query.. # list() forces the complete execution of the query unlocking the db unlike __enter__()
if self.main_window is None:
self.main_window = get_main_window()
if self.name != "archive_synchronizer_extra":
# MAIN SERVER
bit_pos = 0
unsaved_records = Archive.select().where((Archive.archived == 0) |
(Archive.archived == 2) |
(Archive.uploaded == 0))
else:
# EXTRA SERVER (VERO PROJECT SPA)
bit_pos = 1
unsaved_records = Archive.select().where((Archive.archived == 0) |
(Archive.archived == 1))
for record in unsaved_records:
if not self.simulate:
if record.archived is not True:
record.archived = self.remote_archive(record) is True
s = time.time()
save_ok = self.remote_archive(record) is True
e = time.time()
else:
save_ok=True
if record.uploaded is not True:
record.uploaded = self.remote_store(record) is True
else:
self.log.info("simulated archive synchronizer cycle")
record.save()
save_ok=True
if save_ok:
record.archived |= (1 << bit_pos)
self.log.info(f"({self.name}) id {record.id}: archived remotely")
else:
self.log.info(f"({self.name}) id {record.id}: failed to archive remotely")
self.main_window.run_request.emit(record.save, [], {})
if self.hold_time > 0:
QThread.msleep(self.hold_time)
self.gcs_bucket = None
@ -68,7 +98,9 @@ class ArchiveSynchronizer(Component):
self.machine_status="working"
super()._get()
self.update_machine_status()
if self.name == "archive_synchronizer":
self.update_machine_status()
def update_machine_status(self):
status_call = f"{self.status_endpoint}?machine-id={self.machine_id.upper()}&status={self.machine_status}"
@ -132,16 +164,28 @@ class ArchiveSynchronizer(Component):
if not self.simulate:
with requests.Session() as s:
s.mount("", HTTPAdapter(max_retries=Retry(total=0))) # this disables retries
r = requests.post(self.archive_endpoint, params={
"data": json.dumps(record.test_data),
"machine_id": self.machine_id,
"overridden": record.overridden,
"recipe": record.test_data.get("recipe", {}).get("name", None),
"result": "OK" if record.result else "KO",
"serial": record.id,
"time": record.time.isoformat(),
"user": record.user.username,
}, timeout=5, verify=False)
if self.name == "archive_synchronizer":
r = requests.post(self.archive_endpoint, params={
"data": json.dumps(record.test_data),
"machine_id": self.machine_id,
"overridden": record.overridden,
"recipe": record.test_data.get("recipe", {}).get("name", None),
"result": "OK" if record.result else "KO",
"serial": record.id,
"time": record.time.isoformat(),
"user": record.user.username,
}, timeout=5, verify=False)
else:
r = requests.get(self.archive_endpoint, params={
"machine_id": self.machine_id,
"overridden": record.overridden,
"recipe": record.test_data.get("recipe", {}).get("name", None),
"result": "OK" if record.result else "KO",
"serial": record.id,
"time": record.time.isoformat(),
"user": record.user.username,
}, timeout=5, verify=False)
if r.status_code != 200:
raise AssertionError("bad status response")
except AssertionError as e:
@ -150,6 +194,8 @@ class ArchiveSynchronizer(Component):
except (requests.ConnectionError, requests.Timeout) as e:
self.log.warning(f"id: {record.id}: failed to archive remotely, archive_endpoint might be unreachable: {str(e)}")
return False
self.log.info(f"Archived successfully: {record.id}")
return True
def remote_store(self, record):
@ -182,16 +228,16 @@ class ArchiveSynchronizer(Component):
return False
except Exception:
self.log.error(f"id: {record.id}: failed to store remotely:\n{traceback.format_exc()}")
self.log.info(f"id: {record.id}: stored remotely")
self.log.info(f"Stored successfully: {record.id}")
return True
def remote_fetch(self, remote_path=None, local_path=None):
"""
download a single file from the server.
Download a single file from the server.
:param remote_path: path of where to download the file from
:param local_path: path of where to save the file to
:return: a dictionary with errors if any occur.
:param remote_path: Path of where to download the file from
:param local_path: Path of where to save the file to
:return: A dictionary with errors if any occur.
"""
if remote_path is None:
raise ValueError("remote_path cannot be None")
@ -199,48 +245,68 @@ class ArchiveSynchronizer(Component):
raise ValueError("local_path cannot be None")
call_url = f"https://dev.r5portal.it/{remote_path}"
log_info_type = "Download"
log_time = datetime.now()
log_info = f"Attempted to download from {call_url}"
try:
if not self.simulate:
with requests.Session() as s:
self.log.info(f"Fetching file from: {call_url}")
# Make the HTTP GET request to fetch the file
response = s.get(call_url, timeout=5, verify=False)
# Log response details
self.log.info(f"HTTP Status Code: {response.status_code}")
#self.log.info(f"Response Headers: {response.headers}")
#self.log.info(f"Response Content: {response.content}")
# Handle HTTP errors
if response.status_code == 404:
self.log.warning(f"File not found: {call_url}. Please check the URL path.")
log_info += " - File not found"
self.log.warning(log_info)
self.log_to_db(log_time, log_info_type, log_info)
return {"error": "File not found"}
elif response.status_code == 403 or response.status_code == 401:
self.log.warning(f"Access forbidden or not logged in for file: {call_url}")
elif response.status_code in [403, 401]:
log_info += " - Access forbidden or not logged in"
self.log.warning(log_info)
self.log_to_db(log_time, log_info_type, log_info)
return {"error": "Access forbidden or not logged in"}
elif response.status_code != 200:
self.log.error(f"Unexpected HTTP response status: {response.status_code} for URL: {call_url}")
log_info += f" - Unexpected HTTP response status: {response.status_code}"
self.log.error(log_info)
self.log_to_db(log_time, log_info_type, log_info)
return {"error": "Unexpected HTTP response status"}
# Ensure the directory exists
os.makedirs(local_path, exist_ok=True)
# Save the file to the local path
local_file_path = os.path.join(local_path, os.path.basename(remote_path))
with open(local_file_path, "wb") as f:
f.write(response.content)
self.log.info(f"File downloaded successfully: {local_file_path}")
log_info += f" - File downloaded successfully: {local_file_path}"
self.log.info(log_info)
self.log_to_db(log_time, log_info_type, log_info)
return {"downloaded_file": local_file_path}
except requests.ConnectionError as e:
self.log.error(f"Connection error occurred while fetching the file: {str(e)}")
log_info += f" - Connection error: {str(e)}"
self.log.error(log_info)
self.log_to_db(log_time, log_info_type, log_info)
return {"error": "Connection error"}
except requests.Timeout as e:
self.log.error(f"Timeout error occurred while fetching the file: {str(e)}")
log_info += f" - Timeout error: {str(e)}"
self.log.error(log_info)
self.log_to_db(log_time, log_info_type, log_info)
return {"error": "Timeout error"}
except Exception as e:
self.log.error(f"An unexpected error occurred: {str(e)}")
log_info += f" - Unexpected error: {str(e)}"
self.log.error(log_info)
self.log_to_db(log_time, log_info_type, log_info)
return {"error": "Unexpected error"}
def log_to_db(self, log_time, log_info_type, log_info):
"""Save log information to the database."""
try:
# Use the Log class instead of the log instance
new_log_entry = Log(time=log_time, info_type=log_info_type, info=log_info)
new_log_entry.save() # Save the log entry to the database
except Exception as e:
self.log.error(f"Failed to save log to database: {str(e)}")
self.log.error(traceback.format_exc())

View File

@ -63,3 +63,4 @@ Users.register(username="USER", password="user")
if True:
# crud_db must be imported after db and models_reference are available
from .crud_db import Crud_DB

View File

@ -1,7 +1,7 @@
from datetime import datetime
from peewee import (AutoField, BooleanField, DateTimeField, ForeignKeyField,
TextField)
TextField, IntegerField)
from playhouse.sqlite_ext import JSONField
from .base_model import BaseModel, db
@ -16,7 +16,7 @@ class Archive(BaseModel):
overridden = BooleanField(null=False)
test_data = JSONField(null=False)
label = TextField(null=True)
archived = BooleanField(null=False, default=False)
archived = IntegerField(null=False, default=False)
uploaded = BooleanField(null=False, default=False)
@classmethod

View File

@ -5,7 +5,7 @@ from peewee import AutoField, DateTimeField, TextField
from .base_model import BaseModel, db
log = logging.getLogger("db_log")
db_logger = logging.getLogger("db_log")
class Log(BaseModel):
@ -18,7 +18,7 @@ class Log(BaseModel):
@db.atomic()
def log(cls, info_type, info=None):
cls.create(info_type=info_type, info=info)
log.info(f"{info_type}: {info}")
db_logger.info(f"{info_type}: {info}")
class Meta:
table_name = "log"

View File

@ -12,6 +12,7 @@ from datetime import datetime
from pathlib import Path
from ui.diagnostics import Diagnostics
from lib.helpers.single_process import SingleProcess
from ui.logs_management.info import Logs_Management
if platform.system().lower() == "windows":
sys.path.append(f"{os.getcwd()}\src\components")
@ -71,7 +72,7 @@ try:
from lib.helpers import ConfigReader
from PyQt5.QtCore import QObject, QThread, pyqtSignal, pyqtSlot
from PyQt5.QtWidgets import QApplication, QMessageBox
from ui import About, Archive, Login, Main_Window, Test, Users_Management, Recipe_Selection, \
from ui import About, Archive, Login, Main_Window, Test, Users_Management,Logs_Management ,Recipe_Selection, \
Barcode_Recipe_Selection
if "--vision" in sys.argv:
@ -100,6 +101,7 @@ try:
# INIT COMPONENT
self.components_specs = {
"archive_synchronizer": {"c": ArchiveSynchronizer},
"archive_synchronizer_extra": {"c": ArchiveSynchronizer},
"label_printer": {"c": Os_Label_Printer, "t": False},
"extra_label_printer": {"c": Os_Label_Printer, "t": False},
"label_printer_2": {"c": BrotherLabelPrinter, "t": False},
@ -173,7 +175,6 @@ try:
# GUI INIT
if "--no-gui" not in sys.argv:
# self.main_window = Main_Window(self.bench)
self.main_window = Main_Window()
# CONNECT MAIN WINDOW ACTIONS
self.main_window.logout_a.triggered.connect(lambda checked, selfie=weakref.ref(self): selfie().logout())
@ -190,6 +191,8 @@ try:
self.main_window.about_a.triggered.connect(
lambda checked, selfie=weakref.ref(self): selfie().main_window.open_dialog(About()))
self.main_window.download_a.triggered.connect(
lambda checked, selfie=weakref.ref(self): selfie().main_window.open_dialog(Logs_Management()))
self.main_window.quit_a.triggered.connect(quit_app)
self.main_window.users_management_a.triggered.connect(
lambda checked, selfie=weakref.ref(self): selfie().main_window.open_dialog(Users_Management()))

View File

@ -2,6 +2,7 @@ from .widget import Widget
from .window import Window
from .dialog import Dialog
from .about import About
from .logs_management import Logs_Management
from .archive import Archive
from .barcodes_step_editor import Barcodes_Step_Editor
from .barcode_recipe_selection import Barcode_Recipe_Selection

View File

@ -75,7 +75,7 @@ class Barcode_Recipe_Selection(Test_Test):
else:
lines = data.splitlines()
#lines = data.split("-")
candidates = [i for i in lines if len(i)==10]
candidates = [i for i in lines if len(i) in(10,12)]
if len(candidates)>0:
# RECIPE CODE FOUND
self.recipe=candidates[-1]

View File

@ -1,2 +1,3 @@
from .calc_foreground_color import calc_foreground_color
from .get_main_window import get_main_window
from .replace_widget import replace_widget

View File

@ -0,0 +1,8 @@
from PyQt5.QtWidgets import QApplication, QMainWindow
def get_main_window():
tws = QApplication.topLevelWidgets()
for w in tws:
if isinstance(w, QMainWindow):
return w

View File

@ -0,0 +1 @@
from .info import Logs_Management

View File

@ -0,0 +1,73 @@
import traceback
from lib.db import log # Presumendo che esista un modulo per accedere alla tabella "log"
from PyQt5.QtWidgets import QMessageBox, QTableWidget
from ui.crud import Crud, Line_Edit_Cell_Widget
from ui.widget import Widget
class Logs_Management(Widget):
def __init__(self):
super().__init__()
class Info_Line_Edit_Cell_Widget(Line_Edit_Cell_Widget):
def render(self, data, row_number=None, crud=None):
super().render(data, row_number=row_number, crud=crud)
def parse(self, action=None, row_number=None, crud=None):
return self.text()
crud_aliases = {
"time": "Data",
"info_type": "Operazione",
"info": "Info",
}
self.crud = Crud(
"log",
display_name="LOG MANAGEMENT",
readonly=["id"],
select=list(crud_aliases.keys()),
fields_aliases=crud_aliases,
autocomplete={},
widget_classes={
"time": Info_Line_Edit_Cell_Widget,
"info_type": Info_Line_Edit_Cell_Widget,
"info": Info_Line_Edit_Cell_Widget,
},
row_filter=self.row_filter
)
self.layout().addWidget(self.crud, 0, 0, -1, -1)
# Adjust the column widths based on content
self.adjust_column_widths()
def adjust_column_widths(self):
"""Adjust the widths of columns to fit their content."""
# Ensure that Crud has db_tw which is a QTableWidget
if hasattr(self.crud, 'db_tw') and isinstance(self.crud.db_tw, QTableWidget):
# Optionally, resize all columns to fit their contents initially
self.crud.db_tw.resizeColumnsToContents()
# Set a specific column width for columns; for example, set 'Info' column
for column in range(self.crud.db_tw.columnCount()):
self.crud.db_tw.setColumnWidth(column, 200) # Set width to 300 pixels
def row_filter(self, row, row_number, crud):
try:
log_entry = log.generate(
id=row["id"],
time=row["time"],
info_type=row["info_type"],
info=row["info"]
)
except AssertionError as e:
self.log.exception(traceback.format_exc())
self.crud.set_row_color(row_number, "red")
QMessageBox.critical(None, "Errore Salvataggio DB", f"Errore alla riga {row_number}:\n{str(e)}")
return False, None, True
row.update(log_entry)
return True, row, False

View File

@ -0,0 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>Users_Management</class>
<widget class="QWidget" name="Users_Management">
<property name="windowModality">
<enum>Qt::WindowModal</enum>
</property>
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>94</width>
<height>18</height>
</rect>
</property>
<property name="windowTitle">
<string>Logs Management</string>
</property>
<layout class="QGridLayout" name="gridLayout"/>
</widget>
<resources/>
<connections/>
</ui>

View File

@ -5,10 +5,16 @@ from ui.window import Window
class Main_Window(Window):
do = pyqtSignal(dict)
run_request = pyqtSignal(object, list, dict)
@staticmethod
def run_request_handler(fn, a, ka):
fn(*a, **ka)
def __init__(self):
super().__init__()
self.do.connect(self._do)
self.run_request.connect(self.run_request_handler)
# print("MAIN_WINDOW ", str(int(QThread.currentThreadId())), flush=True)
@staticmethod

View File

@ -38,6 +38,7 @@
<string>Informazioni</string>
</property>
<addaction name="about_a"/>
<addaction name="download_a"/>
</widget>
<widget class="QMenu" name="admin_m">
<property name="font">
@ -134,6 +135,11 @@
<string>Diagnostica</string>
</property>
</action>
<action name="download_a">
<property name="text">
<string>Aggiornamento</string>
</property>
</action>
</widget>
<resources/>
<connections/>

View File

@ -368,7 +368,9 @@ class Test(Widget):
elif self.step.step_type == "print":
compiled_label = self.print(self.archived, self.step.spec.get("template", "EtichettaR5"))
self.archived.label = compiled_label
self.archived.save()
self.log.info(f"Label printed. Saving...")
#self.archived.save()
self.main_window.main_window.run_request.emit(self.archived.save, [], {})
self.next_timer.start(500)
elif self.step.step_type == "wait":
self.next_timer.start(500)