Merge branch 'master' of https://gitlab.com/neosystems/errecinque/mappano/st-ten-1
# Conflicts: # config/machine_settings/defaults.ini # init.sh # simulate.sh # src/components/__init__.py # src/components/component.py # src/lib/db/__init__.py # src/lib/db/models/recipes.py # src/main.py # src/requirements.txt # src/ui/__init__.py # src/ui/crud/crud.py # src/ui/dialog/dialog.py # src/ui/login/login.py # src/ui/recipe_selection/recipe_selection.py
This commit is contained in:
commit
f99891cf25
14
config/label_templates/5803001456.prn
Normal file
14
config/label_templates/5803001456.prn
Normal file
@ -0,0 +1,14 @@
|
||||
CT~~CD,~CC^~CT~
|
||||
^XA~TA000~JSN^LT0^MNW^MTT^PON^PMN^LH0,0^JMA^PR4,4~SD15^JUS^LRN^CI0^XZ
|
||||
^XA
|
||||
^MMT
|
||||
^PW378
|
||||
^LL0213
|
||||
^LS0
|
||||
^FT213,199^BQN,2,6
|
||||
^FH\^FDLA,5803001456^FS
|
||||
^FT28,58^A0N,33,33^FH\^FD5803001456^FS
|
||||
^FT28,104^A0N,33,33^FH\^FDN: $NUM^FS
|
||||
^FT28,148^A0N,33,33^FH\^FD$DATE^FS
|
||||
^FT28,185^A0N,33,33^FH\^FD$TIME^FS
|
||||
^PQ1,0,1,Y^XZ
|
||||
@ -3,7 +3,7 @@ parameter: default
|
||||
|
||||
[vision_saver]
|
||||
time_format: %Y-%m-%d_%H-%M-%S
|
||||
location: ./data/images
|
||||
path: ./data/images
|
||||
minimum_disk_free_space_gb: 20
|
||||
|
||||
[archive_synchronizer]
|
||||
@ -13,3 +13,12 @@ poll_time: 60
|
||||
hold_time: 1
|
||||
service_account_json: data/secrets/MACHINE-bucket-sevice-account_errecinque-prodserver-HEXHEXHEXHEX.json
|
||||
bucket_id: MACHINE
|
||||
|
||||
[label_printer]
|
||||
platform: cups
|
||||
printer:
|
||||
|
||||
|
||||
[tecna_marposs_provaset_t3]
|
||||
address: COM3
|
||||
baudrate: 115200
|
||||
|
||||
26
init.sh
26
init.sh
@ -2,17 +2,6 @@
|
||||
set -x
|
||||
here="$(realpath "$(dirname "$0")")"
|
||||
cd "$here"
|
||||
python="python3.10"
|
||||
|
||||
echo "---------- Create support directories ----------"
|
||||
mkdir -p "tmp"
|
||||
mkdir -p "test_images"
|
||||
mkdir -p "neural_networks"
|
||||
mkdir -p "data/database"
|
||||
echo "---------- install APT packages ----------"
|
||||
sudo apt-get install -y "${python}-venv" "${python}-dev" "python3-pip" "python3-setuptools" "p7zip" "p7zip-full" "p7zip-rar"
|
||||
sudo apt-get install -y "libxcb-xinerama0"
|
||||
|
||||
|
||||
echo "---------- initialize venv ----------"
|
||||
lsof "./venv/bin/python" | awk 'NR > 1 {print $2}' | xargs kill || :
|
||||
@ -23,19 +12,4 @@ source "./venv/bin/activate" || source "./venv/Scripts/activate" || :
|
||||
python -m pip install --upgrade pip
|
||||
python -m pip install --upgrade -r "src/requirements.txt"
|
||||
|
||||
cd "$here/tmp"
|
||||
wget --continue --timestamping "http://downloads.get-cameras.com/Galaxy_Linux-x86_Gige-U3_32bits-64bits_1.2.2107.9261.tar.gz"
|
||||
7z x -y "Galaxy_Linux-x86_Gige-U3_32bits-64bits_1.2.2107.9261.tar.gz"
|
||||
7z x -y "Galaxy_Linux-x86_Gige-U3_32bits-64bits_1.2.2107.9261.tar"
|
||||
cd "Galaxy_Linux-x86_Gige-U3_32bits-64bits_1.2.2107.9261"
|
||||
chmod +x "Galaxy_camera.run"
|
||||
echo -en "\nY\nY\nEn\nY\n" | ./Galaxy_camera.run
|
||||
cd "$here/tmp"
|
||||
wget --continue --timestamping "http://downloads.get-cameras.com/Galaxy_Linux_Python_2.0.2106.9041.tar.gz"
|
||||
7z x -y "Galaxy_Linux_Python_2.0.2106.9041.tar.gz"
|
||||
7z x -y "Galaxy_Linux_Python_2.0.2106.9041.tar"
|
||||
cd "Galaxy_Linux_Python_2.0.2106.9041/api"
|
||||
"${python}" setup.py build
|
||||
"${python}" setup.py install
|
||||
|
||||
cd "$here"
|
||||
|
||||
@ -11,15 +11,20 @@ export QT_FATAL_WARNINGS=0
|
||||
export QT_NO_DEBUG_OUTPUT=1
|
||||
export QT_NO_INFO_OUTPUT=1
|
||||
export QT_NO_WARNING_OUTPUT=0
|
||||
# export QT_QPA_PLATFORM=${XDG_SESSION_TYPE}
|
||||
python -B -u "./src/main.py" \
|
||||
--auto-login \
|
||||
--auto-login-admin \
|
||||
--auto-select \
|
||||
--sim-modbus \
|
||||
--sim-os-label-printer \
|
||||
--style windows \
|
||||
$* 2> >(sed $'s/.*/\e[31m&\e[m/' >&2) # &
|
||||
# --about \
|
||||
# --archive \
|
||||
# --auto-login-user \
|
||||
# --autotests-archive \
|
||||
# --sim-archiver \
|
||||
# --sim-serial-label-printer \
|
||||
# --users-management \
|
||||
# sudo renice -n -10 $!
|
||||
# fg
|
||||
|
||||
@ -1,4 +1,8 @@
|
||||
from .archive_synchronizer import ArchiveSynchronizer
|
||||
from .modbus_component import ModbusComponent
|
||||
from .os_label_printer import Os_Label_Printer
|
||||
from .remote_api import RemoteAPI
|
||||
from .serial_label_printer import Serial_Label_Printer
|
||||
from .tecna_marposs_provaset_t3 import TecnaMarpossProvasetT3
|
||||
from .test_component import TestComponent
|
||||
from .vision_saver import VisionSaver
|
||||
|
||||
@ -35,7 +35,7 @@ class Component(QObject):
|
||||
self._lock.acquire(max(self._lock.available(), 1))
|
||||
self._timer = None
|
||||
self.log = logging.getLogger(f"{self.__class__.__name__} ({self.name})")
|
||||
if not threaded:
|
||||
if not self._threaded:
|
||||
self.start()
|
||||
|
||||
def _config_changed(self):
|
||||
@ -51,8 +51,9 @@ class Component(QObject):
|
||||
self._resume.connect(self._do_resume)
|
||||
self._set_sources.connect(self._do_set_sources)
|
||||
self._set_period.connect(self._do_set_period)
|
||||
self._init_periodic()
|
||||
self.config.updated.connect(self._config_changed)
|
||||
self._config_changed()
|
||||
self._init_periodic()
|
||||
self._started = True
|
||||
if not self._paused:
|
||||
self._do_resume()
|
||||
|
||||
1
src/components/dummies/pymodbus/__init__.py
Normal file
1
src/components/dummies/pymodbus/__init__.py
Normal file
@ -0,0 +1 @@
|
||||
from .modbus_client import *
|
||||
29
src/components/dummies/pymodbus/modbus_client.py
Normal file
29
src/components/dummies/pymodbus/modbus_client.py
Normal file
@ -0,0 +1,29 @@
|
||||
class ModbusClient:
|
||||
def __init__(self, *args, **kwargs):
|
||||
print(__name__, "initialized with", args, kwargs, flush=True)
|
||||
|
||||
def connect(self):
|
||||
self._is_socket_open = True
|
||||
return self._is_socket_open
|
||||
|
||||
def read_holding_registers(self, r, count=1, unit=None):
|
||||
# print(__name__, unit, str(r) + "-" + str(r + count - 1), "read", flush=True)
|
||||
return Readout([0] * count)
|
||||
|
||||
def write_register(self, r, d, unit=None):
|
||||
print(__name__, unit, d, r, "write", flush=True)
|
||||
return Readout()
|
||||
|
||||
def close(self):
|
||||
self._is_socket_open = False
|
||||
|
||||
def is_socket_open(self):
|
||||
return self._is_socket_open
|
||||
|
||||
|
||||
class Readout:
|
||||
def __init__(self, data=None):
|
||||
self.registers = data
|
||||
|
||||
def isError(self):
|
||||
return False
|
||||
5
src/components/dummies/serial/__init__.py
Executable file
5
src/components/dummies/serial/__init__.py
Executable file
@ -0,0 +1,5 @@
|
||||
import serial
|
||||
|
||||
from .serial import Serial
|
||||
|
||||
serial.Serial = Serial
|
||||
18
src/components/dummies/serial/serial.py
Executable file
18
src/components/dummies/serial/serial.py
Executable file
@ -0,0 +1,18 @@
|
||||
import logging
|
||||
|
||||
|
||||
class Serial:
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.log = logging.getLogger(f"dummy {self.__class__.__name__} ({id(self)})")
|
||||
self.log.debug(f"initialized with *{args}, **{kwargs}")
|
||||
|
||||
def write(self, d):
|
||||
self.log.debug(f"write: {d!r}")
|
||||
|
||||
def read(self, n):
|
||||
d = b"\x00" * n
|
||||
self.log.debug(f"read: {d!r}")
|
||||
return d
|
||||
|
||||
def close(self):
|
||||
self.log.debug("close")
|
||||
115
src/components/modbus_component.py
Normal file
115
src/components/modbus_component.py
Normal file
@ -0,0 +1,115 @@
|
||||
import sys
|
||||
import traceback
|
||||
|
||||
import serial
|
||||
from pymodbus.constants import Endian
|
||||
# from pymodbus.exceptions import ModbusIOException
|
||||
from pymodbus.payload import BinaryPayloadBuilder, BinaryPayloadDecoder
|
||||
from PyQt5.QtCore import QMutex
|
||||
|
||||
if "--sim-modbus" not in sys.argv:
|
||||
from pymodbus.client.sync import ModbusSerialClient as ModbusClient
|
||||
else:
|
||||
from components.dummies.pymodbus import ModbusClient
|
||||
|
||||
from .component import Component
|
||||
|
||||
# from pymodbus.client.sync import ModbusSerialClient as ModbusClient
|
||||
# import serial
|
||||
# client = ModbusClient(method="rtu", port="COM3", stopbits=serial.STOPBITS_ONE, bytesize=serial.EIGHTBITS, parity=serial.PARITY_NONE, baudrate=115200, timeout=1, strict=False)
|
||||
# client.connect()
|
||||
# client.read_holding_registers(1, count=1)
|
||||
|
||||
|
||||
class ModbusComponent(Component):
|
||||
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.address = self.config[self.name]["address"]
|
||||
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").title())
|
||||
self.wordorder = getattr(Endian, self.config[self.name].get("wordorder", "Big").title())
|
||||
self.timeout = int(self.config[self.name].get("timeout", 1))
|
||||
self.lock.lock()
|
||||
self.client = ModbusClient(
|
||||
method="rtu",
|
||||
port=self.address,
|
||||
stopbits=self.stopbits,
|
||||
bytesize=self.bytesize,
|
||||
parity=self.parity,
|
||||
baudrate=self.baudrate,
|
||||
timeout=self.timeout,
|
||||
strict=False
|
||||
)
|
||||
if not self.client.connect():
|
||||
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):
|
||||
self.lock.lock()
|
||||
read = self.client.read_holding_registers(register, count=count)
|
||||
self.lock.unlock()
|
||||
if read.isError():
|
||||
self.log.exception(traceback.format_exception(read))
|
||||
return None
|
||||
return read
|
||||
|
||||
def _write(self, register, value):
|
||||
self.lock.lock()
|
||||
wrote = self.client.write_register(register, value)
|
||||
self.lock.unlock()
|
||||
if wrote.isError():
|
||||
self.log.exception(traceback.format_exception(wrote))
|
||||
return False
|
||||
return True
|
||||
|
||||
def _decode(self, read, data_type, *args, **kwargs):
|
||||
decoder = BinaryPayloadDecoder.fromRegisters(read.registers, byteorder=self.byteorder, wordorder=self.wordorder)
|
||||
return getattr(decoder, f"decode_{data_type}")(*args, **kwargs)
|
||||
|
||||
def _encode(self, data, data_type, *args, **kwargs):
|
||||
builder = BinaryPayloadBuilder(byteorder=self.byteorder, wordorder=self.wordorder)
|
||||
getattr(builder, f"add_{data_type}")(data, *args, **kwargs)
|
||||
return builder.build()
|
||||
|
||||
def read(self, register, *args, **kwargs):
|
||||
if type(register) is str:
|
||||
register, s = self.registers[register]
|
||||
if not len(args):
|
||||
args = [s["dt"], *s.get("a", [])]
|
||||
if not len(kwargs):
|
||||
kwargs = s.get("k", {})
|
||||
if args[0].startswith("16bit_"):
|
||||
count = 1
|
||||
elif args[0].startswith("32bit_"):
|
||||
count = 2
|
||||
else:
|
||||
raise NotImplementedError(f"data_type {args[0]!r} is not supported")
|
||||
return self._decode(self._read(register, count=count), *args, **kwargs)
|
||||
|
||||
def write(self, register, data, *args, **kwargs):
|
||||
if type(register) is str:
|
||||
register, s = self.registers[register]
|
||||
if not len(args):
|
||||
args = [s["dt"], *s.get("a", [])]
|
||||
if not len(kwargs):
|
||||
kwargs = s.get("k", {})
|
||||
return self._write(register, self._encode(data, *args, **kwargs))
|
||||
|
||||
# def _get(self, data):
|
||||
# # print("MODBUS", str(int(QThread.currentThreadId())), flush=True)
|
||||
# super()._get(data)
|
||||
|
||||
def __del__(self, event=None):
|
||||
self.lock.lock()
|
||||
if self.client.is_socket_open():
|
||||
self.client.close()
|
||||
self.lock.unlock()
|
||||
49
src/components/os_label_printer.py
Normal file
49
src/components/os_label_printer.py
Normal file
@ -0,0 +1,49 @@
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
from PyQt5.QtWidgets import QMessageBox
|
||||
|
||||
from .component import Component
|
||||
|
||||
|
||||
class Os_Label_Printer(Component):
|
||||
def __init__(self, config=None, name=None):
|
||||
super().__init__(config=config, name=name, threaded=False)
|
||||
if "--sim-os-label-printer" in sys.argv:
|
||||
self.simulate = True
|
||||
|
||||
def config_changed(self):
|
||||
self.platform = self.config["label_printer"]["platform"]
|
||||
# for windows:
|
||||
# cmd
|
||||
# wmic printer list brief
|
||||
# powershell
|
||||
# Get-Printer
|
||||
# for cups (linux, osx)
|
||||
# lpstat -p -d
|
||||
self.printer = self.config["label_printer"]["printer"]
|
||||
|
||||
def print_label(self, template, archived):
|
||||
# LOAD LABEL TEMPLATE
|
||||
with open(f"config/label_templates/{template}.prn") as f:
|
||||
label = f.read()
|
||||
# LABEL PRINT
|
||||
label = label.replace("$PH1", archived.barcode).replace("$PH2", archived.barcode)
|
||||
label_file = "a"
|
||||
if self.platform == "windows":
|
||||
cmd = f'print /d:"{self.printer}" "{label_file}"'
|
||||
elif self.platform == "cups":
|
||||
cmd = f'lp -d "{self.printer}" "{label_file}"'
|
||||
else:
|
||||
raise NotImplementedError(f"platform {self.platform!r} is not supported")
|
||||
if not self.simulate:
|
||||
p = subprocess.run(cmd, stderr=subprocess.STDOUT, stdout=subprocess.PIPE, shell=True) # unsafe
|
||||
if p.returncode != 0:
|
||||
self.log.exception(f"failed to print: returncode: {p.returncode}\noutput:\n{p.stdout}")
|
||||
QMessageBox.critical(
|
||||
None,
|
||||
"Errore Stampante",
|
||||
f"Non e stato possibile stampare l'etichetta.\n\nErrore:\nreturncode: {p.returncode}\noutput:\n{p.stdout}"
|
||||
)
|
||||
return False
|
||||
return True
|
||||
45
src/components/serial_label_printer.py
Normal file
45
src/components/serial_label_printer.py
Normal file
@ -0,0 +1,45 @@
|
||||
import sys
|
||||
|
||||
if "--sim-serial-label-printer" in sys.argv:
|
||||
import components.dummies.serial as serial
|
||||
else:
|
||||
import serial
|
||||
|
||||
from PyQt5.QtWidgets import QMessageBox
|
||||
|
||||
from .component import Component
|
||||
|
||||
|
||||
class Serial_Label_Printer(Component):
|
||||
def __init__(self, config=None, name=None):
|
||||
super().__init__(config=config, name=name, threaded=False)
|
||||
|
||||
def config_changed(self):
|
||||
self.address = self.config["label_printer"]["address"]
|
||||
self.baudrate = int(self.config["label_printer"]["baudrate"])
|
||||
self.stopbits = getattr(serial, self.config["label_printer"].get("stopbits", "stopbits_one").upper())
|
||||
self.parity = getattr(serial, self.config["label_printer"].get("parity", "parity_none").upper())
|
||||
self.bytesize = getattr(serial, self.config["label_printer"].get("bytesize", "eightbits").upper())
|
||||
|
||||
def print_label(self, template, archived):
|
||||
# LOAD LABEL TEMPLATE
|
||||
with open(f"config/label_templates/{template}.prn") as f:
|
||||
label = f.read()
|
||||
# LABEL PRINT
|
||||
label = label.replace("$PH1", archived.barcode).replace("$PH2", archived.barcode)
|
||||
try:
|
||||
conn = serial.Serial(
|
||||
self.address,
|
||||
baudrate=self.baudrate,
|
||||
stopbits=self.stopbits,
|
||||
parity=self.parity,
|
||||
bytesize=self.bytesize,
|
||||
)
|
||||
conn.write(bytes(label, encoding="ascii"))
|
||||
conn.close()
|
||||
except serial.serialutil.SerialException as e:
|
||||
QMessageBox.critical(
|
||||
None,
|
||||
"Errore Connessione Stampante",
|
||||
"Non e stato possibile connettersi alla stampante di etichette\nL'etichetta non verra stampata.\n\nErrore:\n" + str(e)
|
||||
)
|
||||
108
src/components/tecna_marposs_provaset_t3.py
Normal file
108
src/components/tecna_marposs_provaset_t3.py
Normal file
@ -0,0 +1,108 @@
|
||||
from .modbus_component import ModbusComponent
|
||||
from .tecna_marposs_provaset_t3_registers import registers
|
||||
|
||||
# from pymodbus.client.sync import ModbusSerialClient as ModbusClient
|
||||
# import serial
|
||||
# client = ModbusClient(method="rtu", port="COM3", stopbits=serial.STOPBITS_ONE, bytesize=serial.EIGHTBITS, parity=serial.PARITY_NONE, baudrate=115200, timeout=1, strict=False)
|
||||
# client.connect()
|
||||
# client.read_holding_registers(1, count=1)
|
||||
|
||||
|
||||
class TecnaMarpossProvasetT3(ModbusComponent):
|
||||
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=registers)
|
||||
|
||||
def config_changed(self):
|
||||
super().config_changed()
|
||||
# self.set_measure_units()
|
||||
self.units = self.get_measure_units()
|
||||
|
||||
_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, }
|
||||
_leak_flow_units = {"cm3/min": 0, "cm3/h": 1, }
|
||||
_volume_units = {"litri": 0, "cm3": 1, }
|
||||
_time_units = {"seconds": 0, }
|
||||
_flow_units = {"liters/min": 0, "liters/h": 1, "m3/h": 2, }
|
||||
_pressure_units_map = {v: k for k, v in _pressure_units.items()}
|
||||
_leak_units_map = {v: k for k, v in _leak_units.items()}
|
||||
_leak_flow_units_map = {v: k for k, v in _leak_flow_units.items()}
|
||||
_volume_units_map = {v: k for k, v in _volume_units.items()}
|
||||
_time_units_map = {v: k for k, v in _time_units.items()}
|
||||
_flow_units_map = {v: k for k, v in _flow_units.items()}
|
||||
|
||||
def set_measure_units(self):
|
||||
return [
|
||||
self.write("MEASURE UNITS: pressure measure units", self._pressure_units["mbar"]),
|
||||
self.write("MEASURE UNITS: Leak measure units", self._leak_units["mbar"]),
|
||||
self.write("MEASURE UNITS: leak flow rate measure units", self._leak_flow_units["cm3/min"]),
|
||||
self.write("MEASURE UNITS: Volume", self._volume_units["litri"]),
|
||||
self.write("MEASURE UNITS: Flow rate measure units", self._flow_units["liters/min"]),
|
||||
]
|
||||
|
||||
def get_measure_units(self):
|
||||
units = {}
|
||||
for [register, unit_map, unit_names] in [
|
||||
["Relative pressure variable format - high resolution", self._pressure_units_map, ["pressure_hr", "red", "r", 21, ]], # also by documentation color and register number
|
||||
["Relative pressure variable format - low resolution", self._pressure_units_map, ["pressure_lr", "purple", "p", 22, ]], # also by documentation color and register number
|
||||
["Format of the variables related to the measurement of the differential leak pressure", self._leak_units_map, ["leak", "yellow", "y", 23, ]], # also by documentation color and register number
|
||||
["Format of the variables related to the calculated leak flow", self._leak_flow_units_map, ["leak_flow", "blue", "b", 24, ]], # also by documentation color and register number
|
||||
["Format of volume variables", self._volume_units_map, ["volume", "green", "g", 25, ]], # also by documentation color and register number
|
||||
["Format of time variables", self._time_units_map, ["time", "tangerine", "t", 26, ]], # also by documentation color and register number
|
||||
["Format of variables related to flow measurements", self._flow_units_map, ["flow", "orange", "o", 27, ]], # also by documentation color and register number
|
||||
]:
|
||||
v = self.read(register)
|
||||
unit = [10**(-((v >> 8) & 0xff)), unit_map[v & 0xff]]
|
||||
for unit_name in unit_names:
|
||||
units[unit_name] = unit
|
||||
return units
|
||||
|
||||
def _convert_from_format(self, data, format=None):
|
||||
if format is None:
|
||||
return data
|
||||
# units = self.units[format]
|
||||
# return [data * units[0], units[1]]
|
||||
return data * self.units[format][0]
|
||||
|
||||
def _convert_to_format(self, data, format=None):
|
||||
if format is None:
|
||||
return data
|
||||
return int(data / self.units[format][0])
|
||||
|
||||
def read(self, register, *args, format=None, **kwargs):
|
||||
if type(register) is str:
|
||||
_, s = self.registers[register]
|
||||
if format is None:
|
||||
format = s.get("f")
|
||||
return self._convert_from_format(super().read(register, *args, **kwargs), format=format)
|
||||
|
||||
def write(self, register, data, *args, format=None, **kwargs):
|
||||
if type(register) is str:
|
||||
_, s = self.registers[register]
|
||||
if format is None:
|
||||
format = s.get("f")
|
||||
return super().write(register, self._convert_to_format(data, format=format), *args, **kwargs)
|
||||
|
||||
def _get(self):
|
||||
# print("TECNA", str(int(QThread.currentThreadId())), flush=True)
|
||||
# READ INFO
|
||||
if self.units is None:
|
||||
self.set_measure_units()
|
||||
self.units = self.get_measure_units()
|
||||
info = {r: self.read(r) for r in [
|
||||
"Instrument status: active phase",
|
||||
"Test circuit pressure, in real time",
|
||||
"Measured leak, in real time",
|
||||
"Regulated pressure, in real time",
|
||||
"Active alarm flags",
|
||||
"Running test: type of test",
|
||||
"Testing in progress: progressive sequence index",
|
||||
]}
|
||||
super()._get([info])
|
||||
|
||||
def get_test_results(self):
|
||||
return {r: self.read(r) for r in [
|
||||
"Running test: measured leak",
|
||||
"Running test: calculated leak flow rate",
|
||||
"Running test: calculate RVP%",
|
||||
"Running test: result",
|
||||
]}
|
||||
169
src/components/tecna_marposs_provaset_t3_registers.py
Normal file
169
src/components/tecna_marposs_provaset_t3_registers.py
Normal file
@ -0,0 +1,169 @@
|
||||
registers = {
|
||||
"Instrument type": [1, {"dt": "16bit_uint", }],
|
||||
"Test counter: FAILED": [2, {"dt": "32bit_uint", }],
|
||||
"Test counter: TOTAL TESTS": [4, {"dt": "32bit_uint", }],
|
||||
"Life counter: FAILED": [6, {"dt": "32bit_uint", }],
|
||||
"Life counter: TOTAL TESTS": [8, {"dt": "32bit_uint", }],
|
||||
"Test circuit pressure, in real time": [11, {"dt": "32bit_int", "f": 21, }],
|
||||
"Measured leak, in real time": [13, {"dt": "32bit_int", "f": 22, }],
|
||||
"Regulated pressure, in real time": [15, {"dt": "16bit_int", "f": 23, }],
|
||||
"Active alarm flags": [16, {"dt": "16bit_uint", }],
|
||||
"Relative pressure variable format - high resolution": [21, {"dt": "16bit_uint", }],
|
||||
"Format of the variables related to the measurement of the differential leak pressure": [22, {"dt": "16bit_uint", }],
|
||||
"Relative pressure variable format - low resolution": [23, {"dt": "16bit_uint", }],
|
||||
"Format of the variables related to the calculated leak flow": [24, {"dt": "16bit_uint", }],
|
||||
"Format of volume variables": [25, {"dt": "16bit_uint", }],
|
||||
"Format of time variables": [26, {"dt": "16bit_uint", }],
|
||||
"Format of variables related to flow measurements": [27, {"dt": "16bit_uint", }],
|
||||
"Instrument status: table of parameters in use": [31, {"dt": "16bit_uint", }],
|
||||
"Instrument status: active phase": [32, {"dt": "16bit_uint", }],
|
||||
# 0 = NO TEST EXECUTED: WAITING START NEW TEST
|
||||
# 10 = TEST IN PROGRESS: CHECK BARCODE CODE (if enabled)
|
||||
# 20 = TEST IN PROGRESS: WAITING READING BARCODE CODE (if enabled)
|
||||
# 30 = TEST IN PROGRESS: TEST INITIALIZATION
|
||||
# 40 = TEST IN PROGRESS: AUTOMATIC CLOSING 1 (CAGE)
|
||||
# 50 = TEST IN PROGRESS: AUTOMATIC CLOSING 2 (PLUG)
|
||||
# 60 = TEST IN PROGRESS: ACTIVE TEST PROGRAM INITIALIZATION
|
||||
# 70 = TEST IN PROGRESS: WAITING ACKNOWLEDGE WITH BI-START COMMAND (if used)
|
||||
# 80 = TEST IN PROGRESS: WAITING DELAY (PSDEL parameter)
|
||||
# 90 = TEST IN PROGRESS: WAITING CONSENT SIGNAL (PSIN parameter)
|
||||
# 100 = TEST IN PROGRESS: PHASE T0 - PRE-FILLING
|
||||
# 110 = TEST IN PROGRESS: PHASE T1 - FILLING
|
||||
# 120 = TEST IN PROGRESS: PHASE T2 - ASSESTMENT
|
||||
# 130 = TEST IN PROGRESS: PHASE T3- SIZE
|
||||
# 140 = TEST IN PROGRESS: WAITING CONFIRMATION EXAMINED BY OPERATOR (if used)
|
||||
# 150 = TEST IN PROGRESS: TEST RESULT PRESENT
|
||||
# 160 = TEST IN PROGRESS: MARKING (only if last table of a sequence or not in sequence mode)
|
||||
# 170 = TEST IN PROGRESS: PRESSURE DISCHARGE
|
||||
# 180 = TEST IN PROGRESS: END OF TESTING TABLE - IF IN SEQUENCE MODE THE CYCLE STARTS FROM
|
||||
# PHASE 60 WITH THE NEXT TABLE OF THE SEQUENCE
|
||||
# 190 = TEST IN PROGRESS: AUTOMATIC OPENING 2 (BUFFER)
|
||||
# 200 = TEST IN PROGRESS: AUTOMATIC OPENING 1 (CAGE)
|
||||
# 210 = TEST TERMINATED: WAITING THE START OF A NEW TEST
|
||||
"Running test: active phase backwards time": [33, {"dt": "16bit_uint", "f": 26, }],
|
||||
"Running test: T1 phase end pressure": [34, {"dt": "32bit_int", "f": 21, }],
|
||||
"Running test: T2 phase end pressure": [36, {"dt": "32bit_int", "f": 21, }],
|
||||
"Running test: burst pressure": [38, {"dt": "32bit_int", "f": 21, }],
|
||||
"Running test: measured leak": [40, {"dt": "32bit_int", "f": 22, }],
|
||||
"Running test: calculated leak flow rate": [42, {"dt": "32bit_int", "f": 24, }],
|
||||
"Running test: calculate RVP%": [44, {"dt": "32bit_int", }],
|
||||
"Running test: result": [46, {"dt": "16bit_uint", }],
|
||||
# 1 = LEAK TEST PASSED
|
||||
# 2 = BURST TEST PASSED WITH BURST
|
||||
# 3 = BURST TEST PASSED WITHOUT BURST
|
||||
# 4 = not used
|
||||
# 5 = BLOCKAGE TEST PASSED
|
||||
# 100 = LEAK TEST FAILED - UPPER LIMIT
|
||||
# 101 = LEAK TEST – FAILED ANOMALY
|
||||
# 102 = LEAK TEST - MAXIMUM LEAK FAILED
|
||||
# 103 = BURST - BREAKAGE PRESSURE DEFLECTION
|
||||
# 104 = VOLUMETRIC CONTROL - RVP% FAILED
|
||||
# 105 = not used
|
||||
# 106 = not used
|
||||
# 107 = BLOCKAGE - MAX PRESSURE FAILED
|
||||
# 108 = BLOCKAGE - MIN PRESSURE FAILED
|
||||
# 109 = BURST - MINIMUM PRESSURE FAILED
|
||||
# 200 = LEAK TEST FAILED – PR% PRESSURE MINUM
|
||||
# 201 = LEAK TEST FAILED - PR% PRESSURE MAX
|
||||
# 202 = LEAK TEST FAILED – P0% PRESSURE MINUM
|
||||
# 203 = LEAK TEST FAILED - P0% PRESSURE MAX
|
||||
# 204 = ERROR - INTERNAL ALARMS
|
||||
# 205 = ERROR - RELATIVE PRESSURE OUT OF RANGE
|
||||
# 206 = ERROR - DIFFERENTIAL PRESSURE OUT OF RANGE
|
||||
# 207 = ERROR – PRE-FILLING VALVE NOT OPENED
|
||||
# 250 = TEST ABORTED
|
||||
"Running test: type of test": [47, {"dt": "16bit_uint", }],
|
||||
# 1 = LEAK TEST
|
||||
# 2 = BLOCKAGE TEST
|
||||
# 3 = LEAK TEST WITH VOLUME CHECK
|
||||
# 4 = BURST TEST
|
||||
"Testing in progress: progressive sequence index": [48, {"dt": "16bit_uint", }],
|
||||
"Testing in progress: graphical sampling rate": [49, {"dt": "16bit_uint", }],
|
||||
"Testing in progress: number of samples of the graph": [50, {"dt": "16bit_uint", }],
|
||||
"DISLPAY and SOUND: Language": [601, {"dt": "16bit_uint", }],
|
||||
# 0=ITALIANO
|
||||
# 1=ENGLISH
|
||||
"INSTRUMENT SETTING: Test table from": [602, {"dt": "16bit_uint", }],
|
||||
# 0=PARAMETER
|
||||
# 1=PLC BASE 1
|
||||
# 2=PLC BASE 2
|
||||
"MEASURE UNITS: pressure measure units": [606, {"dt": "16bit_uint", }],
|
||||
# 0=mH2O 1=mbar 2=kPa 3=mmHg 4=inH2O 5=psi 6=mmH2O (se fondoscala <=6 bar)
|
||||
"MEASURE UNITS: Leak measure units": [607, {"dt": "16bit_uint", }],
|
||||
# 0=mmH2O 1=mbar 2=Pa 3=mmHg 4=inH2O 5=psi
|
||||
"MEASURE UNITS: leak flow rate measure units": [608, {"dt": "16bit_uint", }],
|
||||
# 0=cm3/min 1=cm3/h
|
||||
"MEASURE UNITS: Volume": [609, {"dt": "16bit_uint", }],
|
||||
# 0=litri 1=cm3
|
||||
"MEASURE UNITS: Flow rate measure units": [617, {"dt": "16bit_uint", }],
|
||||
# 0=liters/min 1=liters/h 2=m3/h
|
||||
"AUTOMATION: Cage - closing time": [618, {"dt": "16bit_uint", }],
|
||||
# Format: x.x seconds
|
||||
"AUTOMATION: Cage - opening time": [619, {"dt": "16bit_uint", }],
|
||||
# Format: x.x seconds
|
||||
"INSTRUMENT SETTINGS: MAX pressure": [620, {"dt": "16bit_uint", }],
|
||||
"AUTOMATION: Buffer - closing time": [621, {"dt": "16bit_uint", }],
|
||||
# Format: x.x seconds
|
||||
"AUTOMATION: Buffer - opening time": [622, {"dt": "16bit_uint", }],
|
||||
# Format: x.x seconds
|
||||
"AUTOMATION: Marking - result": [623, {"dt": "16bit_uint", }],
|
||||
# 0= Only passed
|
||||
# 1= Only failed
|
||||
# 2=All
|
||||
"AUTOMATION: Marking – driving time": [624, {"dt": "16bit_uint", }],
|
||||
# Format: x.x seconds
|
||||
"Type of test": [701, {"dt": "16bit_uint", }],
|
||||
# 1=LEAK TEST
|
||||
# 2=BLOCKAGE TEST
|
||||
# 3=LEAK TEST WITH VOLUME
|
||||
# 4=BURST TEST
|
||||
"Test flags": [702, {"dt": "16bit_uint", }],
|
||||
# T0/Pr: Phase filling mode T0 0= TIME 1= PRESSURE
|
||||
# T1/Pr: Phase filling mode T1 0= TIME 1=PRESSURE
|
||||
# T3/Q: Phase mode T3 0= TIME 1=TERMINATE IMMEDIATELY IF FAILED
|
||||
# TYPE PID: 0=FAST 1=MEDIUM 2=SLOW 4 = FIXED 5 = AUTOMATIC 6= PULSES (if enabled SWLP option)
|
||||
# P0-: 0=Positive P0 pressure 1=P0 negative pressure (if enabled V or N option for vacuum tests)
|
||||
# Pr-: 0=Positive pressure Pr 1=Pr negative pressure (if enabled V or N option for vacuum tests)
|
||||
# Q+: 0=Q + positive parameter 1=parameter Q + negative
|
||||
# Q-: 0=Q-positive parameter 1=Q-negative parameter
|
||||
# AT: 0=Tare pressure disabled 1=Tare pressure enabled
|
||||
# HR: 0=Resolution on loss 1 Pa 1=Resolution on loss 0.1 Pa (models with full scale <= 2 bar)
|
||||
# CH: Selected test channel (2-channel T3P2C model only)
|
||||
"T0 – Pre-filling time": [704, {"dt": "16bit_uint", "f": 26, }],
|
||||
"P0 – Pre-filling pressure": [705, {"dt": "16bit_uint", "f": 23, }],
|
||||
# In order to use a negative (vacuum) value, this parameter must however be written positively, but the P0- bit must
|
||||
# also be selected in register 702
|
||||
"T1 – Filling time": [706, {"dt": "16bit_uint", "f": 26, }],
|
||||
"T2 – Settling time": [707, {"dt": "16bit_uint", "f": 26, }],
|
||||
"T3 – Measure time": [708, {"dt": "16bit_uint", "f": 26, }],
|
||||
"PREL – Nominal test pressure": [709, {"dt": "16bit_uint", "f": 23, }],
|
||||
# To set a negative pressure (vacuum) this parameters must be written in absolute value and then set
|
||||
# bit Pr- in register 702
|
||||
"Pxx Minimum Pressure": [710, {"dt": "16bit_uint", }],
|
||||
# PR%- Lower tolerance on pressure: LEAK: Format: x.x %
|
||||
# P- Minimum pressure: BLOCKAGE: Format as indicated in register 23
|
||||
# PR- Minimum pressure%: BURST: Format x.x%
|
||||
"Q + Upper limit": [711, {"dt": "16bit_uint", "f": 22, }],
|
||||
# Format as indicated in the register 22
|
||||
"Q- Leak limit": [712, {"dt": "16bit_uint", "f": 22, }],
|
||||
# Format as indicated in the register 22
|
||||
"FST –discharge time": [713, {"dt": "16bit_uint", "f": 26, }],
|
||||
# Format as indicated in the register 26
|
||||
"VP – Equivalent volume": [714, {"dt": "16bit_uint", "f": 25, }],
|
||||
# Format as indicated in the register 25
|
||||
"P% Pressure tolerance": [717, {"dt": "16bit_uint", }],
|
||||
# BLOCKAGE: Format x.x %
|
||||
"PB – Minimum burst pressure": [743, {"dt": "16bit_uint", "f": 23, }],
|
||||
# BURST: Format as indicated in the register 23
|
||||
"BD – Delta burst": [744, {"dt": "16bit_uint", "f": 23, }],
|
||||
# BURST: Format as indicated in the register 23
|
||||
"FSL – discharge limit": [745, {"dt": "16bit_uint", "f": 23, }],
|
||||
# Format as indicated in the register 23
|
||||
"PR% + Superior tolerance on pressure": [747, {"dt": "16bit_uint", }],
|
||||
# LEAK: Format: x.x %
|
||||
# P + Pressure max BURST: Format as indicated in the register 23
|
||||
"RVP%: volumetric ratio": [750, {"dt": "16bit_uint", }],
|
||||
# VOLUME+LEAK: Format x.xx (from 0.00 to 649.99)
|
||||
"RVP%: max tolerance": [751, {"dt": "16bit_uint", }],
|
||||
# VOLUME+LEAK: Format: x.xx
|
||||
}
|
||||
@ -5,7 +5,7 @@ import logging
|
||||
|
||||
from playhouse.sqlite_ext import JSONField
|
||||
|
||||
from .models import Archive, Log, Recipes, Session, Users, db, Autotests
|
||||
from .models import Archive, Autotests, Log, Recipes, Session, Users, db
|
||||
|
||||
models_reference = {
|
||||
"archive": Archive,
|
||||
@ -60,10 +60,28 @@ if admin is None or not admin.is_admin:
|
||||
Users.register(username="USER", password="user")
|
||||
# register test recipe
|
||||
Recipes.replace(id=0, name="TEST", spec={
|
||||
# recipe
|
||||
"client": "TEST_CLIENT",
|
||||
"part_number": "TEST_PART_NUMBER",
|
||||
"station": "TEST_STATION",
|
||||
# pressure
|
||||
"pressure_min": 0,
|
||||
"pressure_test": 1,
|
||||
"pressure_max": 2,
|
||||
"pressure_ramp": 3,
|
||||
# test
|
||||
"cleaning_time": 4,
|
||||
"tolerance": 5,
|
||||
"test_duration": 6,
|
||||
"flush_duration": 7,
|
||||
# stabilizarion
|
||||
"stabilization_time": 8,
|
||||
"stabilization_level_min": 9,
|
||||
"stabilization_level_max": 10,
|
||||
"stabilization_settling_time": 11,
|
||||
"stabilization_cycles": 12,
|
||||
# description
|
||||
"description": "TEST_DESCRIPTION",
|
||||
"vision_recipe": "TEST_VISION_RECIPE",
|
||||
}, archived=False).execute()
|
||||
|
||||
if True:
|
||||
|
||||
@ -7,7 +7,7 @@ from .base_model import BaseModel
|
||||
class Recipes(BaseModel):
|
||||
id = AutoField(primary_key=True, unique=True, null=False)
|
||||
name = TextField(null=False)
|
||||
spec = JSONField(null=False)
|
||||
spec = JSONField(null=False) # keys inside spec must not overlap withthe model
|
||||
archived = BooleanField(null=False, default=False)
|
||||
|
||||
@classmethod
|
||||
|
||||
202
src/main.py
202
src/main.py
@ -1,5 +1,4 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import faulthandler
|
||||
import logging
|
||||
import os
|
||||
@ -35,7 +34,7 @@ logging.basicConfig(
|
||||
handlers=[
|
||||
logging.StreamHandler(stream=sys.stderr),
|
||||
logging.FileHandler(
|
||||
logs_dir / f"{datetime.now().strftime('%Y-%m-%dT%H-%M-%S%z')}.log",
|
||||
logs_dir / f"{datetime.now().isoformat()}.log",
|
||||
mode="a",
|
||||
encoding="utf-8",
|
||||
delay=False,
|
||||
@ -47,9 +46,10 @@ logging.basicConfig(
|
||||
errors="surrogateescape",
|
||||
)
|
||||
|
||||
if True:
|
||||
try:
|
||||
# IMPORT PROJECT ONLY AFTER SETTING UP SIGNAL, FAULTHANDLER AND LOGGHING
|
||||
from components import ArchiveSynchronizer, RemoteAPI, TestComponent
|
||||
from components import (ArchiveSynchronizer, Os_Label_Printer, RemoteAPI,
|
||||
TecnaMarpossProvasetT3, TestComponent, VisionSaver)
|
||||
from lib.db import Users
|
||||
from lib.helpers import ConfigReader
|
||||
from PyQt5.QtCore import QObject, QThread, pyqtSignal
|
||||
@ -57,108 +57,110 @@ if True:
|
||||
from ui import (About, Archive, Autotests_Archive, Login, Main_Window,
|
||||
Test, Users_Management)
|
||||
|
||||
class Main(QObject):
|
||||
do = pyqtSignal(dict)
|
||||
|
||||
class Main(QObject):
|
||||
do = pyqtSignal(dict)
|
||||
@staticmethod
|
||||
def _do(config):
|
||||
return config["f"](*config.get("a", []), **config.get("k", {}))
|
||||
|
||||
@staticmethod
|
||||
def _do(config):
|
||||
return config["f"](*config.get("a", []), **config.get("k", {}))
|
||||
|
||||
def __init__(self, parent=None):
|
||||
# print(f"MAIN {int(QThread.currentThreadId())}", flush=True)
|
||||
super().__init__()
|
||||
self.do.connect(self._do)
|
||||
try:
|
||||
# READ CONFIG
|
||||
self.config = ConfigReader()
|
||||
# INIT COMPONENT
|
||||
self.components_specs = {
|
||||
"archive_synchronizer": {"c": ArchiveSynchronizer, "a": [], "k": {}},
|
||||
"remote_api": {"c": RemoteAPI, "a": [], "k": {"main": self}},
|
||||
"test_component": {"c": TestComponent, "a": [], "k": {}},
|
||||
}
|
||||
self.components = {}
|
||||
self.threads = {}
|
||||
for component_name, spec in self.components_specs.items():
|
||||
self.components[component_name] = spec["c"](*spec["a"], config=self.config, name=component_name, **spec["k"])
|
||||
self.threads[component_name] = QThread()
|
||||
self.threads[component_name].setTerminationEnabled(True)
|
||||
self.components[component_name].moveToThread(self.threads[component_name])
|
||||
for component_name in self.components_specs:
|
||||
self.threads[component_name].started.connect(self.components[component_name].start)
|
||||
self.threads[component_name].start()
|
||||
self.components[component_name].wait_ready()
|
||||
except Exception as e:
|
||||
logging.exception(traceback.format_exc())
|
||||
QMessageBox.critical(None, "Errore Banco", f"Non e stato possibile connettersi al banco:\n\n{e}")
|
||||
#quit()
|
||||
# GUI INIT
|
||||
# self.main_window = Main_Window(self.bench)
|
||||
self.main_window = Main_Window()
|
||||
# CONNECT MAIN WINDOW ACTIONS
|
||||
self.main_window.archive_a.triggered.connect(self.open_archive)
|
||||
if "--archive" in sys.argv:
|
||||
self.main_window.archive_a.trigger()
|
||||
self.main_window.autotests_archive_a.triggered.connect(self.open_autotests_archive)
|
||||
if "--autotests-archive" in sys.argv:
|
||||
self.main_window.autotests_archive_a.trigger()
|
||||
self.main_window.about_a.triggered.connect(self.open_about)
|
||||
if "--about" in sys.argv:
|
||||
self.main_window.about_a.trigger()
|
||||
self.main_window.admin_m.menuAction().setVisible(False) # admin menu should not be visible before an admin logs in
|
||||
self.main_window.users_management_a.triggered.connect(self.open_users_management)
|
||||
if "--users-management" in sys.argv:
|
||||
self.main_window.users_management_a.trigger()
|
||||
# OPEN LOGIN TAB
|
||||
self.open_login()
|
||||
# SHOW MAIN WINDOW
|
||||
if "--panel" in sys.argv:
|
||||
self.main_window.show()
|
||||
elif "--maximized" in sys.argv:
|
||||
self.main_window.showMaximized()
|
||||
elif "--full-screen" in sys.argv:
|
||||
self.main_window.showFullScreen()
|
||||
else:
|
||||
self.main_window.show()
|
||||
|
||||
def open_archive(self):
|
||||
self.main_window.open_dialog(Archive())
|
||||
|
||||
def open_autotests_archive(self):
|
||||
self.main_window.open_dialog(Autotests_Archive())
|
||||
|
||||
def open_users_management(self):
|
||||
self.main_window.open_dialog(Users_Management())
|
||||
|
||||
def open_about(self):
|
||||
about_widget = About()
|
||||
self.main_window.open_dialog(about_widget)
|
||||
|
||||
def open_login(self):
|
||||
tab = Login()
|
||||
tab.successful_login.connect(self.logghed_in)
|
||||
self.main_window.open_tab(tab)
|
||||
|
||||
def logghed_in(self):
|
||||
session = Users.get_session()
|
||||
if session is not None:
|
||||
if session.is_admin:
|
||||
self.main_window.admin_m.menuAction().setVisible(True)
|
||||
def __init__(self, parent=None):
|
||||
# print(f"MAIN {int(QThread.currentThreadId())}", flush=True)
|
||||
super().__init__()
|
||||
self.do.connect(self._do)
|
||||
try:
|
||||
# READ CONFIG
|
||||
self.config = ConfigReader()
|
||||
# INIT COMPONENT
|
||||
self.components_specs = {
|
||||
"archive_synchronizer": {"c": ArchiveSynchronizer},
|
||||
"remote_api": {"c": RemoteAPI, "k": {"main": self}},
|
||||
"test_component": {"c": TestComponent},
|
||||
"vision_savert": {"c": VisionSaver, "t": False},
|
||||
"label_printer": {"c": Os_Label_Printer, "t": False},
|
||||
"tecna_marposs_provaset_t3": {"c": TecnaMarpossProvasetT3},
|
||||
}
|
||||
self.components = {}
|
||||
self.threads = {}
|
||||
for component_name, spec in self.components_specs.items():
|
||||
self.components[component_name] = spec["c"](*spec.get("a", []), config=self.config, name=component_name, **spec.get("k", {}))
|
||||
if spec.get("t", True):
|
||||
self.threads[component_name] = QThread()
|
||||
self.threads[component_name].setTerminationEnabled(True)
|
||||
self.components[component_name].moveToThread(self.threads[component_name])
|
||||
for component_name, thread in self.threads.items():
|
||||
component = self.components[component_name]
|
||||
thread.started.connect(component.start)
|
||||
thread.start()
|
||||
component.wait_ready()
|
||||
except Exception as e:
|
||||
logging.exception(traceback.format_exc())
|
||||
QMessageBox.critical(None, "Errore Banco", f"Non e stato possibile connettersi al banco:\n\n{e}")
|
||||
quit()
|
||||
# GUI INIT
|
||||
# self.main_window = Main_Window(self.bench)
|
||||
self.main_window = Main_Window()
|
||||
# CONNECT MAIN WINDOW ACTIONS
|
||||
self.main_window.archive_a.triggered.connect(self.open_archive)
|
||||
if "--archive" in sys.argv:
|
||||
self.main_window.archive_a.trigger()
|
||||
self.main_window.autotests_archive_a.triggered.connect(self.open_autotests_archive)
|
||||
if "--autotests-archive" in sys.argv:
|
||||
self.main_window.autotests_archive_a.trigger()
|
||||
self.main_window.about_a.triggered.connect(self.open_about)
|
||||
if "--about" in sys.argv:
|
||||
self.main_window.about_a.trigger()
|
||||
self.main_window.admin_m.menuAction().setVisible(False) # admin menu should not be visible before an admin logs in
|
||||
self.main_window.users_management_a.triggered.connect(self.open_users_management)
|
||||
if "--users-management" in sys.argv:
|
||||
self.main_window.users_management_a.trigger()
|
||||
# OPEN LOGIN TAB
|
||||
self.open_login()
|
||||
# SHOW MAIN WINDOW
|
||||
if "--panel" in sys.argv:
|
||||
self.main_window.show()
|
||||
elif "--maximized" in sys.argv:
|
||||
self.main_window.showMaximized()
|
||||
elif "--full-screen" in sys.argv:
|
||||
self.main_window.showFullScreen()
|
||||
else:
|
||||
self.main_window.admin_m.menuAction().setVisible(False)
|
||||
self.open_test()
|
||||
self.main_window.show()
|
||||
|
||||
def open_test(self):
|
||||
self.main_window.open_tab(Test(self.config.machine_id))
|
||||
def open_archive(self):
|
||||
self.main_window.open_dialog(Archive())
|
||||
|
||||
def open_autotests_archive(self):
|
||||
self.main_window.open_dialog(Autotests_Archive())
|
||||
|
||||
if __name__ == "__main__":
|
||||
try:
|
||||
def open_users_management(self):
|
||||
self.main_window.open_dialog(Users_Management())
|
||||
|
||||
def open_about(self):
|
||||
about_widget = About()
|
||||
self.main_window.open_dialog(about_widget)
|
||||
|
||||
def open_login(self):
|
||||
tab = Login()
|
||||
tab.successful_login.connect(self.logghed_in)
|
||||
self.main_window.open_tab(tab)
|
||||
|
||||
def logghed_in(self):
|
||||
session = Users.get_session()
|
||||
if session is not None:
|
||||
if session.is_admin:
|
||||
self.main_window.admin_m.menuAction().setVisible(True)
|
||||
else:
|
||||
self.main_window.admin_m.menuAction().setVisible(False)
|
||||
self.open_test()
|
||||
|
||||
def open_test(self):
|
||||
self.main_window.open_tab(Test(self.config.machine_id))
|
||||
|
||||
if __name__ == "__main__":
|
||||
app = QApplication(sys.argv)
|
||||
main = Main()
|
||||
app.exec()
|
||||
except Exception:
|
||||
logging.exception(traceback.format_exc())
|
||||
# extype, value, tb = sys.exc_info()
|
||||
# pdb.post_mortem(tb)
|
||||
except Exception:
|
||||
logging.exception(traceback.format_exc())
|
||||
# extype, value, tb = sys.exc_info()
|
||||
# pdb.post_mortem(tb)
|
||||
|
||||
@ -4,5 +4,9 @@ google-cloud-storage
|
||||
numpy
|
||||
opencv-python-headless
|
||||
peewee
|
||||
pillow
|
||||
pymodbus
|
||||
pyqt5
|
||||
pyserial
|
||||
requests
|
||||
zebra
|
||||
|
||||
@ -8,6 +8,7 @@ from .qml_circular_gauge import Qml_Circular_Gauge
|
||||
from .qml_led import Qml_Led
|
||||
from .qml_switch import Qml_Switch
|
||||
from .qml_widget import Qml_Widget
|
||||
from .recipe_editor import Recipe_Editor
|
||||
from .recipe_selection import Recipe_Selection
|
||||
from .test import Test
|
||||
from .test_autotest import Test_Autotest
|
||||
|
||||
@ -45,8 +45,8 @@ class Cell:
|
||||
self.set_readonly(self.readonly)
|
||||
self.value = None
|
||||
self.is_modified = False
|
||||
self.connected_modified = False
|
||||
self.do_autocomplete(self.autocomplete)
|
||||
self.connect_modified()
|
||||
|
||||
def set_readonly(self, readonly):
|
||||
raise NotImplementedError()
|
||||
@ -61,6 +61,11 @@ class Cell:
|
||||
self.value = data
|
||||
self.is_modified = False
|
||||
self.render(data, *args, **kwargs)
|
||||
if not self.connected_modified:
|
||||
# only connect after first render
|
||||
# to avoid false modified signals trigghered by autocomplete
|
||||
self.connect_modified()
|
||||
self.connected_modified = True
|
||||
|
||||
def render(self, data, field_name=None, row_number=None, crud=None):
|
||||
raise NotImplementedError()
|
||||
@ -90,7 +95,6 @@ class Cell:
|
||||
|
||||
|
||||
class Line_Edit_Cell_Widget(QLineEdit, Cell):
|
||||
|
||||
def __init__(self, readonly=True, autocomplete=None, field_name=None, field_alias=None, field=None):
|
||||
super().__init__()
|
||||
Cell.__init__(self, readonly=readonly, autocomplete=autocomplete, field_name=field_name, field_alias=field_alias, field=field)
|
||||
@ -98,9 +102,6 @@ class Line_Edit_Cell_Widget(QLineEdit, Cell):
|
||||
def set_readonly(self, readonly):
|
||||
self.setReadOnly(readonly)
|
||||
|
||||
# def do_autocomplete(self, autocomplete):
|
||||
# pass
|
||||
|
||||
def connect_modified(self):
|
||||
self.textChanged.connect(self.check_modified)
|
||||
|
||||
@ -150,9 +151,6 @@ class External_Dialog_Cell_Widget(QPushButton, Cell):
|
||||
def set_readonly(self, readonly):
|
||||
self.editor.setReadOnly(readonly)
|
||||
|
||||
# def do_autocomplete(self, autocomplete):
|
||||
# pass
|
||||
|
||||
def connect_modified(self):
|
||||
self.editor.textChanged.connect(self.check_modified)
|
||||
|
||||
@ -172,6 +170,9 @@ class Json_External_Dialog_Cell_Widget(External_Dialog_Cell_Widget):
|
||||
|
||||
|
||||
class Crud(Widget):
|
||||
modified = pyqtSignal(bool)
|
||||
selected = pyqtSignal(list)
|
||||
|
||||
def __init__(self, table_name, readonly=False, select=None, filters=None, fields_aliases=None, autocomplete=None, pagination=250, display_name=None, row_upgrader=None, widget_classes=None, row_filter=None):
|
||||
super().__init__()
|
||||
self.table_name = table_name
|
||||
@ -279,12 +280,14 @@ class Crud(Widget):
|
||||
return True, row, False
|
||||
|
||||
def set_modified(self, is_modified=True):
|
||||
self.modified = self.modified or is_modified
|
||||
self._modified = self._modified or is_modified
|
||||
self.modified.emit(self._modified)
|
||||
|
||||
def refresh(self, action=None, page=0):
|
||||
# IGNORE SIGNALS WHILE UPDATING
|
||||
self.db_tw.blockSignals(True)
|
||||
self.modified = False
|
||||
self._modified = False # force reset modified status
|
||||
self.set_modified(self._modified)
|
||||
if action == "init":
|
||||
self.sorting = {}
|
||||
self.previous_selection = []
|
||||
@ -314,7 +317,7 @@ class Crud(Widget):
|
||||
for fn, cn in self.select_index.items():
|
||||
w = self.db_tw.cellWidget(rn, cn)
|
||||
try:
|
||||
r[fn] = w.parse(row_number=rn, crud=self)
|
||||
r[fn] = w._parse(row_number=rn, crud=self)
|
||||
except Exception:
|
||||
self.log.exception(traceback.format_exc())
|
||||
self.set_row_color(rn, "red")
|
||||
@ -381,7 +384,7 @@ class Crud(Widget):
|
||||
return self.refresh("commit")
|
||||
|
||||
def add(self):
|
||||
self.modified = True
|
||||
self.set_modified(True)
|
||||
self.data_index.append(None)
|
||||
rn = self.db_tw.rowCount()
|
||||
self.db_tw.setRowCount(rn + 1)
|
||||
@ -393,7 +396,7 @@ class Crud(Widget):
|
||||
self.db_tw.scrollToBottom()
|
||||
|
||||
def delete(self):
|
||||
self.modified = True
|
||||
self.set_modified(True)
|
||||
selected = self.get_selected_rows()
|
||||
if len(selected) == 0:
|
||||
return
|
||||
@ -422,6 +425,7 @@ class Crud(Widget):
|
||||
|
||||
def show_selection(self):
|
||||
selected = self.get_selected_rows()
|
||||
self.selected.emit(selected)
|
||||
if self.previous_selection == selected:
|
||||
return
|
||||
for row_number in self.previous_selection:
|
||||
@ -438,3 +442,7 @@ class Crud(Widget):
|
||||
w.setStyleSheet(f"background-color: {color};")
|
||||
else:
|
||||
w.setStyleSheet("")
|
||||
|
||||
def emit(self):
|
||||
self.set_modified(self._modified)
|
||||
self.show_selection()
|
||||
|
||||
@ -33,4 +33,4 @@ class Dialog(QDialog):
|
||||
def closeEvent(self, *args):
|
||||
self._closing.emit()
|
||||
global dialogs
|
||||
del dialogs[id(self)]
|
||||
dialogs.pop(id(self), None)
|
||||
|
||||
@ -18,9 +18,13 @@ class Login(Widget):
|
||||
QShortcut(QKeySequence("Return"), self).activated.connect(self.login_b.click)
|
||||
self.login_b.clicked.connect(self.try_login)
|
||||
# TESTING
|
||||
if "--auto-login" in sys.argv or "--test" in sys.argv:
|
||||
if "--auto-login-admin" in sys.argv:
|
||||
self.user_cb.setCurrentText("ADMIN")
|
||||
self.password_le.setText("123123")
|
||||
if "--auto-login-user" in sys.argv or "--test" in sys.argv:
|
||||
self.user_cb.setCurrentText("USER")
|
||||
self.password_le.setText("user")
|
||||
if "--auto-login-admin" in sys.argv or "--auto-login-user" in sys.argv or "--test" in sys.argv:
|
||||
self.test_timer = QTimer()
|
||||
self.test_timer.setSingleShot(True)
|
||||
self.test_timer.timeout.connect(self.login_b.clicked.emit)
|
||||
|
||||
1
src/ui/recipe_editor/__init__.py
Normal file
1
src/ui/recipe_editor/__init__.py
Normal file
@ -0,0 +1 @@
|
||||
from .recipe_editor import Recipe_Editor
|
||||
120
src/ui/recipe_editor/recipe_editor.py
Normal file
120
src/ui/recipe_editor/recipe_editor.py
Normal file
@ -0,0 +1,120 @@
|
||||
from PyQt5.QtWidgets import (QCheckBox, QComboBox, QDoubleSpinBox, QLineEdit,
|
||||
QRadioButton, QSpinBox, QPlainTextEdit)
|
||||
from ui.widget import Widget
|
||||
|
||||
|
||||
class Recipe_Editor(Widget):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.spec = {
|
||||
# recipe
|
||||
"client": self.client_le,
|
||||
"part_number": self.part_number_le,
|
||||
"station": self.station_le,
|
||||
# pressure
|
||||
"pressure_min": self.pressure_min_sb,
|
||||
"pressure_test": self.pressure_test_sb,
|
||||
"pressure_max": self.pressure_max_sb,
|
||||
"pressure_ramp": self.pressure_ramp_sb,
|
||||
# test
|
||||
"cleaning_time": self.cleaning_time_sb,
|
||||
"tolerance": self.tolerance_sb,
|
||||
"test_duration": self.test_duration_sb,
|
||||
"flush_duration": self.flush_duration_sb,
|
||||
# stabilizarion
|
||||
"stabilization_time": self.stabilization_time_sb,
|
||||
"stabilization_level_min": self.stabilization_level_min_sb,
|
||||
"stabilization_level_max": self.stabilization_level_max_sb,
|
||||
"stabilization_settling_time": self.stabilization_settling_time_sb,
|
||||
"stabilization_cycles": self.stabilization_cycles_sb,
|
||||
# description
|
||||
"description": self.description_pte,
|
||||
}
|
||||
|
||||
def set_readonly(self, readonly):
|
||||
for w in self.spec.values():
|
||||
w.setReadOnly(readonly)
|
||||
|
||||
def do_autocomplete(self, autocomplete):
|
||||
if autocomplete is None:
|
||||
return
|
||||
for k, v in autocomplete.items():
|
||||
w = self.spec[k]
|
||||
if isinstance(w, QCheckBox):
|
||||
w.setChecked(bool(v))
|
||||
elif isinstance(w, QComboBox):
|
||||
w.addItems(list(map(str, v)))
|
||||
elif isinstance(w, QDoubleSpinBox):
|
||||
w.setValue(float(v))
|
||||
elif isinstance(w, QLineEdit):
|
||||
w.setText(str(v))
|
||||
elif isinstance(w, QRadioButton):
|
||||
w.setChecked(bool(v))
|
||||
elif isinstance(w, QSpinBox):
|
||||
w.setValue(int(v))
|
||||
elif isinstance(w, QPlainTextEdit):
|
||||
w.setPlainText(str(v))
|
||||
else:
|
||||
raise NotImplementedError(f"widget of type {type(w)!r} not implemented")
|
||||
|
||||
def connect_modified(self, check_modified):
|
||||
for k, w in self.spec.items():
|
||||
if isinstance(w, QCheckBox):
|
||||
w.toggled.connect(check_modified)
|
||||
elif isinstance(w, QComboBox):
|
||||
w.currentTextChanged.connect(check_modified)
|
||||
elif isinstance(w, QDoubleSpinBox):
|
||||
w.valueChanged.connect(check_modified)
|
||||
elif isinstance(w, QLineEdit):
|
||||
w.textChanged.connect(check_modified)
|
||||
elif isinstance(w, QRadioButton):
|
||||
w.toggled.connect(check_modified)
|
||||
elif isinstance(w, QSpinBox):
|
||||
w.valueChanged.connect(check_modified)
|
||||
elif isinstance(w, QPlainTextEdit):
|
||||
w.textChanged.connect(check_modified)
|
||||
else:
|
||||
raise NotImplementedError(f"widget of type {type(w)!r} not implemented")
|
||||
|
||||
def render(self, data, field_name=None, row_number=None, crud=None):
|
||||
for k, v in data.items():
|
||||
if k not in self.spec:
|
||||
continue
|
||||
w = self.spec[k]
|
||||
if isinstance(w, QCheckBox):
|
||||
w.setChecked(bool(v))
|
||||
elif isinstance(w, QComboBox):
|
||||
w.setCurrentText(str(v))
|
||||
elif isinstance(w, QDoubleSpinBox):
|
||||
w.setValue(float(v))
|
||||
elif isinstance(w, QLineEdit):
|
||||
w.setText(str(v))
|
||||
elif isinstance(w, QRadioButton):
|
||||
w.setChecked(bool(v))
|
||||
elif isinstance(w, QSpinBox):
|
||||
w.setValue(int(v))
|
||||
elif isinstance(w, QPlainTextEdit):
|
||||
w.setPlainText(str(v))
|
||||
else:
|
||||
raise NotImplementedError(f"widget of type {type(w)!r} not implemented")
|
||||
|
||||
def parse(self, row_number=None, crud=None):
|
||||
ret = {}
|
||||
for k, w in self.spec.items():
|
||||
if isinstance(w, QCheckBox):
|
||||
ret[k] = w.isChecked()
|
||||
elif isinstance(w, QComboBox):
|
||||
ret[k] = w.currentText()
|
||||
elif isinstance(w, QDoubleSpinBox):
|
||||
ret[k] = w.value()
|
||||
elif isinstance(w, QLineEdit):
|
||||
ret[k] = w.text()
|
||||
elif isinstance(w, QRadioButton):
|
||||
ret[k] = w.isChecked()
|
||||
elif isinstance(w, QSpinBox):
|
||||
ret[k] = w.value()
|
||||
elif isinstance(w, QPlainTextEdit):
|
||||
ret[k] = w.toPlainText()
|
||||
else:
|
||||
raise NotImplementedError(f"widget of type {type(w)!r} not implemented")
|
||||
return ret
|
||||
310
src/ui/recipe_editor/recipe_editor.ui
Normal file
310
src/ui/recipe_editor/recipe_editor.ui
Normal file
@ -0,0 +1,310 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>Recipe_Editor</class>
|
||||
<widget class="QWidget" name="Recipe_Editor">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>494</width>
|
||||
<height>654</height>
|
||||
</rect>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<item row="0" column="2">
|
||||
<widget class="QGroupBox" name="groupBox_3">
|
||||
<property name="title">
|
||||
<string>Pressione</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout_4">
|
||||
<item row="4" column="2">
|
||||
<widget class="QSpinBox" name="pressure_max_sb"/>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="label_10">
|
||||
<property name="text">
|
||||
<string>Min</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="0">
|
||||
<widget class="QLabel" name="label_12">
|
||||
<property name="text">
|
||||
<string>Max</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="2">
|
||||
<widget class="QSpinBox" name="pressure_min_sb"/>
|
||||
</item>
|
||||
<item row="2" column="3">
|
||||
<widget class="QLabel" name="label_22">
|
||||
<property name="text">
|
||||
<string>bar</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="2">
|
||||
<widget class="QSpinBox" name="pressure_test_sb"/>
|
||||
</item>
|
||||
<item row="5" column="2">
|
||||
<widget class="QSpinBox" name="pressure_ramp_sb"/>
|
||||
</item>
|
||||
<item row="3" column="0">
|
||||
<widget class="QLabel" name="label_11">
|
||||
<property name="text">
|
||||
<string>Test</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="3">
|
||||
<widget class="QLabel" name="label_24">
|
||||
<property name="text">
|
||||
<string>bar</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="0">
|
||||
<widget class="QLabel" name="label_6">
|
||||
<property name="text">
|
||||
<string>Rampa di salita</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="3">
|
||||
<widget class="QLabel" name="label_23">
|
||||
<property name="text">
|
||||
<string>bar</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="3">
|
||||
<widget class="QLabel" name="label_18">
|
||||
<property name="text">
|
||||
<string>bar/min</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1" colspan="2">
|
||||
<widget class="QGroupBox" name="groupBox_5">
|
||||
<property name="title">
|
||||
<string>Descrizione</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout_6">
|
||||
<item row="0" column="0">
|
||||
<widget class="QPlainTextEdit" name="description_pte"/>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QGroupBox" name="groupBox_2">
|
||||
<property name="title">
|
||||
<string>Test</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout_3">
|
||||
<item row="4" column="0">
|
||||
<widget class="QLabel" name="label_8">
|
||||
<property name="text">
|
||||
<string>Tempo di prova</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="0">
|
||||
<widget class="QLabel" name="label_9">
|
||||
<property name="text">
|
||||
<string>Tempo di scarico</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label_5">
|
||||
<property name="text">
|
||||
<string>Tempo di pulizia</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="1">
|
||||
<widget class="QSpinBox" name="test_duration_sb"/>
|
||||
</item>
|
||||
<item row="3" column="0">
|
||||
<widget class="QLabel" name="label_7">
|
||||
<property name="text">
|
||||
<string>Tolleranza</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="1">
|
||||
<widget class="QSpinBox" name="flush_duration_sb"/>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QSpinBox" name="cleaning_time_sb"/>
|
||||
</item>
|
||||
<item row="3" column="1">
|
||||
<widget class="QSpinBox" name="tolerance_sb"/>
|
||||
</item>
|
||||
<item row="1" column="2">
|
||||
<widget class="QLabel" name="label_3">
|
||||
<property name="text">
|
||||
<string>s</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="2">
|
||||
<widget class="QLabel" name="label_19">
|
||||
<property name="text">
|
||||
<string>bar</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="2">
|
||||
<widget class="QLabel" name="label_20">
|
||||
<property name="text">
|
||||
<string>s</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="2">
|
||||
<widget class="QLabel" name="label_21">
|
||||
<property name="text">
|
||||
<string>s</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QGroupBox" name="groupBox">
|
||||
<property name="title">
|
||||
<string>Ricetta</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout_2">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="text">
|
||||
<string>Cliente</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="label_4">
|
||||
<property name="text">
|
||||
<string>Stazione</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="2">
|
||||
<widget class="QLineEdit" name="station_le"/>
|
||||
</item>
|
||||
<item row="0" column="2">
|
||||
<widget class="QLineEdit" name="client_le"/>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>N° disegno</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="2">
|
||||
<widget class="QLineEdit" name="part_number_le"/>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="2">
|
||||
<widget class="QGroupBox" name="groupBox_4">
|
||||
<property name="title">
|
||||
<string>Stabilizzazione</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout_5">
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="label_15">
|
||||
<property name="text">
|
||||
<string>Soglia max</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label_14">
|
||||
<property name="text">
|
||||
<string>Soglia min</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="1">
|
||||
<widget class="QSpinBox" name="stabilization_cycles_sb"/>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QSpinBox" name="stabilization_time_sb"/>
|
||||
</item>
|
||||
<item row="3" column="1">
|
||||
<widget class="QSpinBox" name="stabilization_settling_time_sb"/>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QSpinBox" name="stabilization_level_max_sb"/>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QSpinBox" name="stabilization_level_min_sb"/>
|
||||
</item>
|
||||
<item row="3" column="0">
|
||||
<widget class="QLabel" name="label_16">
|
||||
<property name="text">
|
||||
<string>Tempo assestamento</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="0">
|
||||
<widget class="QLabel" name="label_17">
|
||||
<property name="text">
|
||||
<string>Numero di cicli</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label_13">
|
||||
<property name="text">
|
||||
<string>Tempo</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="2">
|
||||
<widget class="QLabel" name="label_25">
|
||||
<property name="text">
|
||||
<string>s</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="2">
|
||||
<widget class="QLabel" name="label_26">
|
||||
<property name="text">
|
||||
<string>%</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="2">
|
||||
<widget class="QLabel" name="label_27">
|
||||
<property name="text">
|
||||
<string>%</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="2">
|
||||
<widget class="QLabel" name="label_28">
|
||||
<property name="text">
|
||||
<string>s</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
||||
@ -1,27 +1,54 @@
|
||||
import sys
|
||||
|
||||
from lib.db import Recipes
|
||||
from PyQt5.QtCore import QTimer, pyqtSignal
|
||||
from lib.db import Recipes, Users
|
||||
from PyQt5.QtCore import Qt, QTimer, pyqtSignal
|
||||
from PyQt5.QtGui import QKeySequence
|
||||
from PyQt5.QtWidgets import QShortcut
|
||||
from ui.crud import Crud
|
||||
from PyQt5.QtWidgets import QPushButton, QShortcut
|
||||
from ui.crud import Cell, Crud
|
||||
from ui.dialog import Dialog
|
||||
from ui.helpers import replace_widget
|
||||
from ui.recipe_editor import Recipe_Editor
|
||||
from ui.widget import Widget
|
||||
|
||||
|
||||
class Json_Spec_External_Dialog_Cell_Widget(QPushButton, Cell):
|
||||
def __init__(self, readonly=True, autocomplete=None, field_name=None, field_alias=None, field=None):
|
||||
self.dialog = Dialog()
|
||||
self.dialog.setAttribute(Qt.WA_DeleteOnClose, on=False)
|
||||
self.editor = Recipe_Editor()
|
||||
self.dialog.setCentralWidget(self.editor)
|
||||
super().__init__(u"\u238B apri")
|
||||
Cell.__init__(self, readonly=readonly, autocomplete=autocomplete, field_name=field_name, field_alias=field_alias, field=field)
|
||||
self.dialog.setWindowTitle(self.field_alias)
|
||||
self.clicked.connect(self.dialog.show)
|
||||
|
||||
def set_readonly(self, readonly):
|
||||
self.editor.set_readonly(readonly)
|
||||
|
||||
def do_autocomplete(self, autocomplete):
|
||||
self.editor.do_autocomplete(autocomplete)
|
||||
|
||||
def connect_modified(self):
|
||||
self.editor.connect_modified(self.check_modified)
|
||||
|
||||
def render(self, data, field_name=None, row_number=None, crud=None):
|
||||
self.editor.render(data, field_name=field_name, row_number=row_number, crud=crud)
|
||||
|
||||
def parse(self, row_number=None, crud=None):
|
||||
return self.editor.parse(row_number=row_number, crud=crud)
|
||||
|
||||
|
||||
def recipes_row_upgrader(row, row_number, crud):
|
||||
row["client"] = row["spec"]["client"]
|
||||
row["part_number"] = row["spec"]["part_number"]
|
||||
row["description"] = row["spec"]["description"]
|
||||
row["vision_recipe"] = row["spec"]["vision_recipe"]
|
||||
if len(row.keys() & row["spec"].keys()):
|
||||
raise AssertionError("field keys in Recipes model MUST NOT be present inside Recipes.spec")
|
||||
row.update(row["spec"])
|
||||
return row
|
||||
|
||||
|
||||
def recipes_row_filter(row, row_number, crud):
|
||||
row["spec"]["client"] = row.pop("client")
|
||||
row["spec"]["part_number"] = row.pop("part_number")
|
||||
row["spec"]["description"] = row.pop("description")
|
||||
row["spec"]["vision_recipe"] = row.pop("vision_recipe")
|
||||
for k in list(row):
|
||||
if k in row["spec"]:
|
||||
row["spec"][k] = row.pop(k)
|
||||
return True, row, False
|
||||
|
||||
|
||||
@ -30,30 +57,63 @@ class Recipe_Selection(Widget):
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
crud_aliases = {
|
||||
"name": "Ricetta",
|
||||
"client": "Cliente",
|
||||
"part_number": "N° disegno",
|
||||
"description": "Descrizione",
|
||||
"vision_recipe": "Ricetta visione",
|
||||
}
|
||||
session = Users.get_session()
|
||||
if session.is_admin:
|
||||
readonly = False
|
||||
crud_aliases = {
|
||||
"name": "Ricetta",
|
||||
"spec": "Specifica",
|
||||
# "client": "Cliente",
|
||||
# "part_number": "N° disegno",
|
||||
# "station": "Stazione",
|
||||
# "cleaning_time": "Tempo di pulizia",
|
||||
# "pressure_ramp": "Rampa di salita",
|
||||
# "tolerance": "Tolleranza",
|
||||
# "test_duration": "Tempo di prova",
|
||||
# "flush_duration": "Tempo di scarico",
|
||||
# "pressure_min": "Pressione min",
|
||||
# "pressure_test": "Pressione test",
|
||||
# "pressure_max": "Pressione max",
|
||||
# "stabilization_time": "Tempo di stabilizzazione",
|
||||
# "stabilization_level_min": "Soglia di stabilizzazione min",
|
||||
# "stabilization_level_max": "Soglia di stabilizzazione max",
|
||||
# "stabilization_settling_time": "Tempo di assestamento",
|
||||
# "stabilization_cycles": "Numero di cicli",
|
||||
# "description": "Desccrizione",
|
||||
"archived": "Archiviata",
|
||||
}
|
||||
filters = None
|
||||
else:
|
||||
readonly = True
|
||||
crud_aliases = {
|
||||
"name": "Ricetta",
|
||||
"client": "Cliente",
|
||||
"part_number": "N° disegno",
|
||||
"description": "Desccrizione",
|
||||
"spec": "Specifica",
|
||||
}
|
||||
filters = {"archived": False}
|
||||
self.crud = Crud(
|
||||
"recipes",
|
||||
display_name="SELEZIONE RICETTA",
|
||||
readonly=True,
|
||||
readonly=readonly,
|
||||
select=list(crud_aliases.keys()),
|
||||
filters={"archived": False},
|
||||
filters=filters,
|
||||
fields_aliases=crud_aliases,
|
||||
autocomplete={"archived": False},
|
||||
row_upgrader=recipes_row_upgrader,
|
||||
widget_classes={"spec": Json_Spec_External_Dialog_Cell_Widget, },
|
||||
row_filter=recipes_row_filter,
|
||||
)
|
||||
replace_widget(self, "crud_w", self.crud)
|
||||
self.crud_modified = None
|
||||
self.selected = None
|
||||
self.select_b.setEnabled(False)
|
||||
QShortcut(QKeySequence("Return"), self).activated.connect(self.select_b.click)
|
||||
self.crud.db_tw.itemSelectionChanged.connect(self.check)
|
||||
self.select_b.clicked.connect(self.select)
|
||||
QShortcut(QKeySequence("Return"), self).activated.connect(self.select_b.click)
|
||||
self.crud.modified.connect(self.check_modified)
|
||||
self.crud.selected.connect(self.check_selected)
|
||||
self.crud.emit()
|
||||
# TESTING
|
||||
if "--auto-select" in sys.argv or "--test" in sys.argv:
|
||||
drawing = "TEST"
|
||||
@ -69,18 +129,22 @@ class Recipe_Selection(Widget):
|
||||
self.test_timer.start(500)
|
||||
# /TESTING
|
||||
|
||||
def check(self):
|
||||
if not self.crud.modified:
|
||||
selected = self.crud.get_selected_rows()
|
||||
if len(selected) == 1:
|
||||
selected = selected[0] - 1 # - 1 because rn starts from 1 (filters line)
|
||||
if selected >= 0 and selected < len(self.crud.data_index):
|
||||
selected = self.crud.data_index[selected]
|
||||
self.selected = self.crud.db.table_model.get_by_id(selected)
|
||||
self.select_b.setEnabled(True)
|
||||
return
|
||||
self.selected = None
|
||||
self.select_b.setEnabled(False)
|
||||
def check_modified(self, modified):
|
||||
self.crud_modified = modified
|
||||
self.check(self.crud_modified, self.selected)
|
||||
|
||||
def check_selected(self, selected):
|
||||
if len(selected) == 1:
|
||||
selected = selected[0] - 1 # - 1 because rn starts from 1 (filters line)
|
||||
if selected >= 0 and selected < len(self.crud.data_index):
|
||||
selected = self.crud.data_index[selected]
|
||||
self.selected = self.crud.db.table_model.get_by_id(selected)
|
||||
else:
|
||||
self.selected = None
|
||||
self.check(self.crud_modified, self.selected)
|
||||
|
||||
def check(self, modified, selected):
|
||||
self.select_b.setEnabled(modified is False and selected is not None)
|
||||
|
||||
def select(self):
|
||||
if self.selected is not None:
|
||||
|
||||
Loading…
Reference in New Issue
Block a user