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/ download_root:media/uploads/
images_path: data/images images_path: data/images
poll_time: 60 poll_time: 10
hold_time: 30 hold_time: 10
service_account_json: config/machine_settings/gcloud_default.json service_account_json: config/machine_settings/gcloud_default.json
bucket_id: st_ten_img bucket_id: st_ten_img
[archive_synchronizer_extra] [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 images_path: data/images
poll_time: 60 poll_time: 10
hold_time: 30 hold_time: 10
service_account_json: config/machine_settings/gcloud_default.json
bucket_id: st_ten_img
[label_printer] [label_printer]
platform: windows platform: windows

View File

@ -5,6 +5,7 @@ image_for_warning= st-ten-1
[hardware_config] [hardware_config]
archive_synchronizer: present archive_synchronizer: present
archive_synchronizer_extra: present
galaxy_camera: absent galaxy_camera: absent
uvc_camera: absent uvc_camera: absent
label_printer: present label_printer: present
@ -18,6 +19,12 @@ screwdriver: absent
digital_io: present digital_io: present
external_flush_blow: absent 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] [tecna_t3]
port: COM4 port: COM4
model: t3l model: t3l

View File

@ -5,6 +5,7 @@ image_for_warning= st-ten-14
[hardware_config] [hardware_config]
archive_synchronizer: present archive_synchronizer: present
archive_synchronizer_extra: present
uvc_camera: absent uvc_camera: absent
label_printer: present label_printer: present
neo_pixels: absent neo_pixels: absent
@ -19,13 +20,19 @@ fixture_id: absent
discard_box: absent discard_box: absent
enforce_piece_removal: no 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] [tecna_t3]
port: /dev/ttyUSB0 port: /dev/ttyUSB0
model: t3l model: t3l
[label_printer] [label_printer]
platform: linux platform: linux
printer: ZTC-ZD421-203dpi-ZPL printer: Zebra_Technologies_ZTC_ZD421-203dpi_ZPL
[digital_io] [digital_io]
# OUTPUT MAP FOR FIXTURE CONNECTOR # 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 description = ST-TEN-4 - REPARTO PREFORMATURA - MAPPANO
instruction_folder = st-ten-4 instruction_folder = st-ten-4
image_for_warning= st-ten-4 image_for_warning= st-ten-4
[hardware_config] [hardware_config]
archive_synchronizer: present archive_synchronizer: present
archive_synchronizer_extra: present
uvc_camera: absent uvc_camera: absent
label_printer: present label_printer: present
neo_pixels: absent neo_pixels: absent
@ -13,6 +15,12 @@ vision_saver: absent
vision: absent vision: absent
screwdriver: 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] [tecna_t3]
port: COM4 port: COM4
model: t3p model: t3p

View File

@ -16,6 +16,7 @@ screwdriver: absent
#digital_io: present #digital_io: present
digital_io_flush_blow: present digital_io_flush_blow: present
second_leak_test: 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 external_flush_blow: present # EXTERNAL BOX CONTROLLING MULTI-CHANNEL TEST (IF PRESENT), BLOW-CLEANING AND EXTERNAL FLUSH
dual_channel: present dual_channel: present
#fixture_id: 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 sys
import threading import threading
import time import time
from datetime import datetime
from pathlib import Path from pathlib import Path
import requests import requests
@ -12,13 +13,14 @@ import requests
from google.api_core.exceptions import Forbidden from google.api_core.exceptions import Forbidden
from google.cloud import storage from google.cloud import storage
from requests import JSONDecodeError from requests import JSONDecodeError
from lib.db import Archive, db from lib.db import Archive, db
from lib.db.models import Log
from PyQt5.QtCore import QThread from PyQt5.QtCore import QThread
from requests.adapters import HTTPAdapter, Retry from requests.adapters import HTTPAdapter, Retry
from urllib3.exceptions import InsecureRequestWarning from urllib3.exceptions import InsecureRequestWarning
from .component import Component from .component import Component
from ui.helpers import get_main_window
# Suppress insecure request warning # Suppress insecure request warning
requests.packages.urllib3.disable_warnings(category=InsecureRequestWarning) requests.packages.urllib3.disable_warnings(category=InsecureRequestWarning)
@ -26,6 +28,7 @@ requests.packages.urllib3.disable_warnings(category=InsecureRequestWarning)
class ArchiveSynchronizer(Component): class ArchiveSynchronizer(Component):
def __init__(self, config=None, name=None, period=1, lazy=True, paused=False, threaded=True): 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) 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.simulate = "--sim-archiver" in sys.argv
self.machine_status = "logged-in" self.machine_status = "logged-in"
self.machine_id = None self.machine_id = None
@ -43,23 +46,50 @@ class ArchiveSynchronizer(Component):
self._do_set_period({"period": float(self.config[self.name]["poll_time"])}) 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.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"]) if self.name == "archive_synchronizer":
self.gcs_client._http.mount("", HTTPAdapter(max_retries=Retry(total=0))) # this seems to be useless self.gcs_client = storage.Client.from_service_account_json(self.config[self.name]["service_account_json"])
self.gcs_client._http.adapters.move_to_end("", last=False) # this seems to be useless self.gcs_client._http.mount("", HTTPAdapter(max_retries=Retry(total=0))) # this seems to be useless
self.bucket_id = self.config[self.name]["bucket_id"] 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 self.gcs_bucket = None
@db.connection_context() @db.connection_context()
def _get(self): 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 not self.simulate:
if record.archived is not True: 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: if record.uploaded is not True:
record.uploaded = self.remote_store(record) is True record.uploaded = self.remote_store(record) is True
else: else:
self.log.info("simulated archive synchronizer cycle") 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: if self.hold_time > 0:
QThread.msleep(self.hold_time) QThread.msleep(self.hold_time)
self.gcs_bucket = None self.gcs_bucket = None
@ -68,7 +98,9 @@ class ArchiveSynchronizer(Component):
self.machine_status="working" self.machine_status="working"
super()._get() super()._get()
self.update_machine_status()
if self.name == "archive_synchronizer":
self.update_machine_status()
def update_machine_status(self): def update_machine_status(self):
status_call = f"{self.status_endpoint}?machine-id={self.machine_id.upper()}&status={self.machine_status}" 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: if not self.simulate:
with requests.Session() as s: with requests.Session() as s:
s.mount("", HTTPAdapter(max_retries=Retry(total=0))) # this disables retries s.mount("", HTTPAdapter(max_retries=Retry(total=0))) # this disables retries
r = requests.post(self.archive_endpoint, params={ if self.name == "archive_synchronizer":
"data": json.dumps(record.test_data), r = requests.post(self.archive_endpoint, params={
"machine_id": self.machine_id, "data": json.dumps(record.test_data),
"overridden": record.overridden, "machine_id": self.machine_id,
"recipe": record.test_data.get("recipe", {}).get("name", None), "overridden": record.overridden,
"result": "OK" if record.result else "KO", "recipe": record.test_data.get("recipe", {}).get("name", None),
"serial": record.id, "result": "OK" if record.result else "KO",
"time": record.time.isoformat(), "serial": record.id,
"user": record.user.username, "time": record.time.isoformat(),
}, timeout=5, verify=False) "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: if r.status_code != 200:
raise AssertionError("bad status response") raise AssertionError("bad status response")
except AssertionError as e: except AssertionError as e:
@ -150,6 +194,8 @@ class ArchiveSynchronizer(Component):
except (requests.ConnectionError, requests.Timeout) as e: 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)}") self.log.warning(f"id: {record.id}: failed to archive remotely, archive_endpoint might be unreachable: {str(e)}")
return False return False
self.log.info(f"Archived successfully: {record.id}")
return True return True
def remote_store(self, record): def remote_store(self, record):
@ -182,16 +228,16 @@ class ArchiveSynchronizer(Component):
return False return False
except Exception: except Exception:
self.log.error(f"id: {record.id}: failed to store remotely:\n{traceback.format_exc()}") 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 return True
def remote_fetch(self, remote_path=None, local_path=None): 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 remote_path: Path of where to download the file from
:param local_path: path of where to save the file to :param local_path: Path of where to save the file to
:return: a dictionary with errors if any occur. :return: A dictionary with errors if any occur.
""" """
if remote_path is None: if remote_path is None:
raise ValueError("remote_path cannot be None") raise ValueError("remote_path cannot be None")
@ -199,48 +245,68 @@ class ArchiveSynchronizer(Component):
raise ValueError("local_path cannot be None") raise ValueError("local_path cannot be None")
call_url = f"https://dev.r5portal.it/{remote_path}" 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: try:
if not self.simulate: if not self.simulate:
with requests.Session() as s: with requests.Session() as s:
self.log.info(f"Fetching file from: {call_url}") 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) 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"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: 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"} return {"error": "File not found"}
elif response.status_code == 403 or response.status_code == 401: elif response.status_code in [403, 401]:
self.log.warning(f"Access forbidden or not logged in for file: {call_url}") 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"} return {"error": "Access forbidden or not logged in"}
elif response.status_code != 200: 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"} return {"error": "Unexpected HTTP response status"}
# Ensure the directory exists
os.makedirs(local_path, exist_ok=True) 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)) local_file_path = os.path.join(local_path, os.path.basename(remote_path))
with open(local_file_path, "wb") as f: with open(local_file_path, "wb") as f:
f.write(response.content) 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} return {"downloaded_file": local_file_path}
except requests.ConnectionError as e: 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"} return {"error": "Connection error"}
except requests.Timeout as e: 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"} return {"error": "Timeout error"}
except Exception as e: 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"} 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: if True:
# crud_db must be imported after db and models_reference are available # crud_db must be imported after db and models_reference are available
from .crud_db import Crud_DB from .crud_db import Crud_DB

View File

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

View File

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

View File

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

View File

@ -75,7 +75,7 @@ class Barcode_Recipe_Selection(Test_Test):
else: else:
lines = data.splitlines() lines = data.splitlines()
#lines = data.split("-") #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: if len(candidates)>0:
# RECIPE CODE FOUND # RECIPE CODE FOUND
self.recipe=candidates[-1] self.recipe=candidates[-1]

View File

@ -1,2 +1,3 @@
from .calc_foreground_color import calc_foreground_color from .calc_foreground_color import calc_foreground_color
from .get_main_window import get_main_window
from .replace_widget import replace_widget 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): class Main_Window(Window):
do = pyqtSignal(dict) do = pyqtSignal(dict)
run_request = pyqtSignal(object, list, dict)
@staticmethod
def run_request_handler(fn, a, ka):
fn(*a, **ka)
def __init__(self): def __init__(self):
super().__init__() super().__init__()
self.do.connect(self._do) self.do.connect(self._do)
self.run_request.connect(self.run_request_handler)
# print("MAIN_WINDOW ", str(int(QThread.currentThreadId())), flush=True) # print("MAIN_WINDOW ", str(int(QThread.currentThreadId())), flush=True)
@staticmethod @staticmethod

View File

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

View File

@ -368,7 +368,9 @@ class Test(Widget):
elif self.step.step_type == "print": elif self.step.step_type == "print":
compiled_label = self.print(self.archived, self.step.spec.get("template", "EtichettaR5")) compiled_label = self.print(self.archived, self.step.spec.get("template", "EtichettaR5"))
self.archived.label = compiled_label 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) self.next_timer.start(500)
elif self.step.step_type == "wait": elif self.step.step_type == "wait":
self.next_timer.start(500) self.next_timer.start(500)