This commit is contained in:
matteo porta 2022-07-19 11:59:00 +02:00
parent 88e3520c3d
commit 0b15405d6e
41 changed files with 1980 additions and 1169 deletions

View File

@ -15,14 +15,10 @@ export QT_NO_WARNING_OUTPUT=0
python -B -u "./src/main.py" \
--auto-login-admin \
--auto-select \
--no-autotest \
--no-edgetpu \
--no-gpu \
--panel \
--sim-camera \
--sim-modbus \
--sim-os-label-printer \
--sim-serial \
--style windows \
$* 2> >(sed $'s/.*/\e[31m&\e[m/' >&2) # &
# --about \
@ -30,11 +26,19 @@ $* 2> >(sed $'s/.*/\e[31m&\e[m/' >&2) # &
# --auto-login-user \
# --autotests-archive \
# --camera-edits \
# --full-screen \
# --interact \
# --maximized \
# --no-autotest \
# --no-gui \
# --no-tflite \
# --recipes-management \
# --sim-archiver \
# --sim-camera \
# --sim-modbus \
# --sim-serial \
# --sim-vision \
# --steps-management \
# --users-management \
# sudo renice -n -10 $!
# fg

View File

@ -69,38 +69,41 @@ class ModbusComponent(Component):
# self.log.exception(traceback.format_exception(type(wrote), wrote, wrote.__traceback__))
raise wrote
def _decode(self, read, data_type="16bit_uint", *args, **kwargs):
def _decode(self, read, *args, data_type="16bit_uint", gain=1, offset=0, **kwargs):
decoder = BinaryPayloadDecoder.fromRegisters(read.registers, byteorder=self.byteorder, wordorder=self.wordorder)
return getattr(decoder, f"decode_{data_type}")(*args, **kwargs)
data = getattr(decoder, f"decode_{data_type}")(*args, **kwargs)
data = (data - offset) / gain
if data_type.endswith("uint"):
data = int(abs(data))
elif data_type.endswith("int"):
data = int(data)
else:
raise NotImplementedError(f"data_type {data_type!r} is not supported")
return data
def _encode(self, data, data_type="16bit_uint", *args, **kwargs):
def _encode(self, data, *args, data_type="16bit_uint", gain=1, offset=0, **kwargs):
builder = BinaryPayloadBuilder(byteorder=self.byteorder, wordorder=self.wordorder)
data = data * gain + offset
if data_type.endswith("uint"):
data = int(abs(data))
elif data_type.endswith("int"):
data = int(data)
else:
raise NotImplementedError(f"data_type {data_type!r} is not supported")
getattr(builder, f"add_{data_type}")(data, *args, **kwargs)
return builder.build()
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_"):
def read(self, register, *args, data_type="16bit_uint", gain=1, offset=0, **kwargs):
if data_type.startswith("16bit_"):
count = 1
elif args[0].startswith("32bit_"):
elif data_type.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)
raise NotImplementedError(f"data_type {data_type!r} is not supported")
return self._decode(self._read(register, count=count), *args, data_type=data_type, gain=gain, offset=offset, **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", {})
self._write(register, self._encode(data, *args, **kwargs))
def write(self, register, data, *args, data_type="16bit_uint", gain=1, offset=0, **kwargs):
self._write(register, self._encode(data, *args, data_type=data_type, gain=gain, offset=offset, **kwargs))
# def _get(self, data):
# # print("MODBUS", str(int(QThread.currentThreadId())), flush=True)

View File

@ -94,23 +94,78 @@ class TecnaMarpossProvasetT3P(ModbusComponent):
data = encoding_map[data]
return data
def read(self, register, *args, formatting=None, decoding_map=None, **kwargs):
def read(self, register, *args, data_type=None, gain=None, offset=None, formatting=None, decoding_map=None, **kwargs):
if type(register) is str:
_, s = self.registers[register]
register, s = self.registers[register]
if data_type is None:
data_type = s.get("dt", None)
if gain is None:
gain = s.get("g", None)
if offset is None:
offset = s.get("o", None)
if formatting is None:
formatting = s.get("f", None)
if decoding_map is None:
decoding_map = s.get("decoding", None)
return self._convert_from_format(super().read(register, *args, **kwargs), formatting=formatting, decoding_map=decoding_map)
if not len(args):
args = s.get("a", [])
if not len(kwargs):
kwargs = s.get("k", {})
if data_type is None:
data_type = "16bit_uint"
if gain is None:
gain = 1
if offset is None:
offset = 0
return self._convert_from_format(
super().read(
register,
*args,
data_type=data_type,
gain=gain,
offset=offset,
**kwargs,
),
formatting=formatting,
decoding_map=decoding_map,
)
def write(self, register, data, *args, formatting=None, encoding_map=None, **kwargs):
def write(self, register, data, *args, data_type=None, gain=None, offset=None, formatting=None, encoding_map=None, **kwargs):
if type(register) is str:
_, s = self.registers[register]
register, s = self.registers[register]
if data_type is None:
data_type = s.get("dt", None)
if gain is None:
gain = s.get("g", None)
if offset is None:
offset = s.get("o", None)
if formatting is None:
formatting = s.get("f", None)
if encoding_map is None:
encoding_map = s.get("encoding", None)
return super().write(register, self._convert_to_format(data, formatting=formatting, encoding_map=encoding_map), *args, **kwargs)
if not len(args):
args = s.get("a", [])
if not len(kwargs):
kwargs = s.get("k", {})
if data_type is None:
data_type = "16bit_uint"
if gain is None:
gain = 1
if offset is None:
offset = 0
return super().write(
register,
self._convert_to_format(
data,
formatting=formatting,
encoding_map=encoding_map,
),
*args,
data_type=data_type,
gain=gain,
offset=offset,
**kwargs,
)
def _get(self):
# print("TECNA", str(int(QThread.currentThreadId())), flush=True)
@ -125,10 +180,8 @@ class TecnaMarpossProvasetT3P(ModbusComponent):
"Running test: test type",
"Running test: sequence index",
]}
if info["Running test: active phase"] == "RESULT PRESENT":
if info["Running test: active phase"] == "END TEST, WAITING THE START OF A NEW TEST":
info.update(self.get_test_results())
elif info["Running test: active phase"] == "WAITING START":
pass
self.log.debug(str(info))
super()._get([info])
@ -155,30 +208,28 @@ class TecnaMarpossProvasetT3P(ModbusComponent):
"Running test: result",
]}
def write_recipe(self, recipe, table=100):
# "pressure_ramp": self.pressure_ramp_sb,
# "stabilization_cycles": self.stabilization_cycles_sb,
recipe_name = recipe.spec["part_number"][:16].encode("ascii")
def write_recipe(self, recipe, step, table=100):
recipe_name = recipe.part_number[:16].encode("ascii")
recipe_name += b"\x00" * (16 - len(recipe_name))
recipe = {
spec = {
"Flag: Instrument settings": 0b0000000000000000,
"Test program for read/write operation": table,
**{719 - 1 + i: (recipe_name[i * 2 + 1] << 8) + recipe_name[i * 2] for i in range(8)},
"Test type": "Leak Test",
"Test flags": 0b0110000001011100,
"T0 - Pre-filling time": recipe.spec["pre_filling_time"],
"P0 - Pre-filling pressure": recipe.spec["pre_filling_pressure"],
"T1 - Filling time": recipe.spec["filling_time"],
"T2 - Settling time": recipe.spec["settling_time"],
"PR- - Min pressure tolerance %": recipe.spec["settling_pressure_min_percent"],
"PR+ - Max pressure tolerance % (P+)": recipe.spec["settling_pressure_max_percent"],
"T3 - Measure time": recipe.spec["test_time"],
"Q- Lower test leak limit": recipe.spec["test_pressure_min_delta"],
"PREL - Nominal test pressure": recipe.spec["test_pressure"],
"Q+ Upper test leak limit": recipe.spec["test_pressure_max_delta"],
"FST - Discharge time": recipe.spec["flush_time"],
"FSL - Discharge limit": recipe.spec["flush_pressure"],
"T0 - Pre-filling time": step.spec["pre_filling_time"],
"P0 - Pre-filling pressure": step.spec["pre_filling_pressure"],
"T1 - Filling time": step.spec["filling_time"],
"T2 - Settling time": step.spec["settling_time"],
"PR- - Min pressure tolerance %": step.spec["settling_pressure_min_percent"],
"PR+ - Max pressure tolerance % (P+)": step.spec["settling_pressure_max_percent"],
"T3 - Measure time": step.spec["test_time"],
"Q- Lower test leak limit": step.spec["test_pressure_min_delta"],
"PREL - Nominal test pressure": step.spec["test_pressure"],
"Q+ Upper test leak limit": step.spec["test_pressure_max_delta"],
"FST - Discharge time": step.spec["flush_time"],
"FSL - Discharge limit": step.spec["flush_pressure"],
}
self.log.debug(str(recipe))
for register, value in recipe.items():
self.log.debug(str(spec))
for register, value in spec.items():
self.write(register, value)

View File

@ -174,20 +174,20 @@ registers = {
# 0=litre 1=cm3
"MEASURE UNITS: Flow rate": [617 - 1, {"dt": "16bit_uint", }],
# 0=liters/min 1=liters/h 2=m3/h
"Cage automation: closing time": [618 - 1, {"dt": "16bit_uint", }],
"Cage automation: closing time": [618 - 1, {"dt": "16bit_uint", "g": 10, }],
# Format: x.x seconds
"Cage automation: opening time": [619 - 1, {"dt": "16bit_uint", }],
"Cage automation: opening time": [619 - 1, {"dt": "16bit_uint", "g": 10, }],
# Format: x.x seconds
"Maximum pressure limit": [620 - 1, {"dt": "16bit_uint", }],
"Plug automation: closing time": [621 - 1, {"dt": "16bit_uint", }],
"Plug automation: closing time": [621 - 1, {"dt": "16bit_uint", "g": 10, }],
# Format: x.x seconds
"Plug automation: opening time": [622 - 1, {"dt": "16bit_uint", }],
"Plug automation: opening time": [622 - 1, {"dt": "16bit_uint", "g": 10, }],
# Format: x.x seconds
"Marker: result based driving": [623 - 1, {"dt": "16bit_uint", }],
# 0=Only passed
# 1=Only failed
# 2=All
"Marker: driving time": [624 - 1, {"dt": "16bit_uint", }],
"Marker: driving time": [624 - 1, {"dt": "16bit_uint", "g": 10, }],
# Format: x.x seconds
"PASSWORD: Administration": [631 - 1, {"dt": "32bit_uint"}],
"PASSWORD: Modify program": [633 - 1, {"dt": "32bit_uint"}],
@ -241,37 +241,37 @@ registers = {
"T3 - Measure time": [708 - 1, {"dt": "16bit_uint", "f": 26, }],
"PREL - Nominal test pressure": [709 - 1, {"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
"PR- - Min pressure tolerance %": [710 - 1, {"dt": "16bit_uint", }],
"PR- - Min pressure tolerance %": [710 - 1, {"dt": "16bit_uint", "g": 10, }],
# Format: x.x %
# NOTE: In blockage test minimum final pressure (format as indicated in register 23)
"Q+ Upper test leak limit": [711 - 1, {"dt": "16bit_uint", "f": 22, }],
"Q- Lower test leak limit": [712 - 1, {"dt": "16bit_uint", "f": 22, }],
"FST - Discharge time": [713 - 1, {"dt": "16bit_uint", "f": 26, }],
"VP - Test volume": [714 - 1, {"dt": "16bit_uint", "f": 25, }],
"PSDEL: Start delay": [716 - 1, {"dt": "16bit_uint", "f": 25, }],
"PSDEL: Start delay": [716 - 1, {"dt": "16bit_uint", "f": 25, "g": 100, }],
# Format: x.x in 1/10 seconds
"P% Pressure tol. (blockage test)": [717 - 1, {"dt": "16bit_uint", }],
"P% Pressure tol. (blockage test)": [717 - 1, {"dt": "16bit_uint", "g": 10, }],
# Format: x.x %
"AW: Aperture weight time (AT)": [740 - 1, {"dt": "16bit_uint", }],
"AN: Aperture number": [741 - 1, {"dt": "16bit_uint", }],
# Format x
"PV%": [742 - 1, {"dt": "16bit_uint", }],
"PV%": [742 - 1, {"dt": "16bit_uint", "g": 10, }],
# Set of electronic regulator in blockage test (Format x.x %)
"PB - Minimum burst pressure": [743 - 1, {"dt": "16bit_uint", "f": 23, }],
"BD - Burst gap / PD Delta Aperture": [744 - 1, {"dt": "16bit_uint", "f": 23, }],
"FSL - Discharge limit": [745 - 1, {"dt": "16bit_uint", "f": 23, }],
"RP% - Pressure ratio": [746 - 1, {"dt": "16bit_uint", }],
"RP% - Pressure ratio": [746 - 1, {"dt": "16bit_uint", "g": 100, }],
# Format; x.xx %
"PR+ - Max pressure tolerance % (P+)": [747 - 1, {"dt": "16bit_uint", }],
"PR+ - Max pressure tolerance % (P+)": [747 - 1, {"dt": "16bit_uint", "g": 10, }],
# Format: x.x %
# NOTE: In blockage test maximum final pressure (format as indicated in register 23)
"AV1: Advanced valve time": [748 - 1, {"dt": "16bit_uint", }],
"AV1: Advanced valve time": [748 - 1, {"dt": "16bit_uint", "g": 10, }],
# Format: x.x seconds
"AV2: Advanced valve time": [749 - 1, {"dt": "16bit_uint", }],
"AV2: Advanced valve time": [749 - 1, {"dt": "16bit_uint", "g": 10, }],
# Format: x.x seconds
"RVP%: volumetric ratio": [750 - 1, {"dt": "16bit_uint", }],
"RVP%: volumetric ratio": [750 - 1, {"dt": "16bit_uint", "g": 100, }],
# Format: x.xx (Range from 100.00 to 649.99)
"RVP%: max tolerance": [751 - 1, {"dt": "16bit_uint", }],
"RVP%: max tolerance": [751 - 1, {"dt": "16bit_uint", "g": 100, }],
# Format: x.xx (Range from 0.10 to 50.00)
"RAMPS: T0 configuration": [752 - 1, {"dt": "16bit_uint", }],
# | 15 14 13 12 11 10 9 8 | 7 6 5 4 3 2 1 0 |

View File

@ -116,7 +116,6 @@ class Vision(Component):
self.classes_map = {c["name"]: k for k, c in self.category_index.items()}
self.zone_detection_filter_mode = self.config[self.name].get("zone_detection_filter_mode", "box_touches")
self.zone_detection_preference_mode = self.config[self.name].get("zone_detection_preference_mode", "distance")
self.set_recipe("1.ini")
def get_center(self, rect):
return [(rect[0] + rect[2]) / 2, (rect[1] + rect[3]) / 2]
@ -333,7 +332,7 @@ class Vision(Component):
# Run inference
if lock:
self.lock.lock()
if self.simulate or tf_mode == "simulation":
if self.simulate or self.tf_mode == "simulation":
detections = {
"detection_scores": [[1.0]],
"detection_boxes": [[[0.2, 0.2, 0.8, 0.8]]],
@ -425,7 +424,7 @@ class Vision(Component):
def process_detections(self, detections):
if self.zones is None or not len(self.zones) or detections is None or not len(detections):
return
return {}
# MATCH DETECTIONS WITH RECIPE
results = dict.fromkeys(self.zones)
for detection in detections:

View File

@ -5,13 +5,14 @@ import logging
from playhouse.sqlite_ext import JSONField
from .models import Archive, Autotests, Log, Recipes, Session, Users, db
from .models import Archive, Autotests, Log, Recipes, Session, Steps, Users, db
models_reference = {
"archive": Archive,
"autotests": Autotests,
"log": Log,
"recipes": Recipes,
"steps": Steps,
"users": Users,
}
@ -58,32 +59,70 @@ if admin is None or not admin.is_admin:
Users.register(username="ADMIN", password="123123", roles=["admin"])
# register or reset user
Users.register(username="USER", password="user")
# register test steps
Steps.replace(
id=0,
name="TEST_LEAK_TEST",
type="leak",
spec={
"pre_filling_time": 5,
"pre_filling_pressure": 1000,
"filling_time": 5,
"settling_time": 10,
"settling_pressure_min_percent": 50,
"settling_pressure_max_percent": 50,
"test_time": 10,
"test_pressure_min_delta": 500,
"test_pressure": 1000,
"test_pressure_max_delta": 500,
"flush_time": 2,
"flush_pressure": 5,
},
description="TEST_DESCRIPTION",
archived=False,
).execute()
Steps.replace(
id=1,
name="TEST_LEAK_TEST",
type="leak",
spec={
"pre_filling_time": 5,
"pre_filling_pressure": 1000,
"filling_time": 5,
"settling_time": 10,
"settling_pressure_min_percent": 50,
"settling_pressure_max_percent": 50,
"test_time": 10,
"test_pressure_min_delta": 500,
"test_pressure": 1000,
"test_pressure_max_delta": 500,
"flush_time": 2,
"flush_pressure": 5,
},
description="TEST_DESCRIPTION",
archived=False,
).execute()
Steps.replace(
id=2,
name="TEST_VISION_TEST",
type="vision",
spec={
"recipe": "1.ini",
},
description="TEST_DESCRIPTION VISION_RECIPE: 1",
archived=False,
).execute()
# register test recipe
Recipes.replace(id=0, name="TEST", spec={
# recipe
"client": "TEST_CLIENT",
"part_number": "TEST_PART_NUMBER",
"station": "TEST_STATION",
"description": "TEST_DESCRIPTION",
# pre-filling
"pre_filling_time": 5,
"pre_filling_pressure": 3000,
# filling
"filling_time": 5,
"settling_time": 10,
"settling_pressure_min_percent": 10,
"settling_pressure_max_percent": 10,
# test
"test_time": 10,
"test_pressure_min_delta": 3.00,
"test_pressure": 3000,
"test_pressure_max_delta": 0.25,
# flush
"flush_time": 2,
"flush_pressure": 5,
# vision
"vision_recipe": 0,
}, archived=False).execute()
Recipes.replace(
id=0,
name="TEST",
client="TEST_CLIENT",
part_number="TEST_PART_NUMBER",
spec={"steps": [0, 1, 2, ]},
description="TEST_DESCRIPTION",
archived=False,
).execute()
if True:
# crud_db must be imported after db and models_reference are available

View File

@ -3,4 +3,5 @@ from .autotests import Autotests
from .base_model import db
from .log import Log
from .recipes import Recipes
from .steps import Steps
from .users import Session, Users

View File

@ -2,14 +2,23 @@ from peewee import AutoField, BooleanField, TextField
from playhouse.sqlite_ext import JSONField
from .base_model import BaseModel
from .steps import Steps
class Recipes(BaseModel):
id = AutoField(primary_key=True, unique=True, null=False)
name = TextField(null=False)
client = TextField(null=False)
part_number = TextField(null=False)
spec = JSONField(null=False) # keys inside spec must not overlap withthe model
description = TextField(null=False)
archived = BooleanField(null=False, default=False)
def get_steps(self):
s_ids = self.spec.get("steps", [])
s = {s.id: s for s in Steps.select().where(Steps.id << s_ids)}
return [s[s_id] for s_id in s_ids]
@classmethod
def delete(cls, *args, **kwargs):
# OVERRIDE DELETION

View File

@ -0,0 +1,22 @@
from peewee import AutoField, BooleanField, TextField
from playhouse.sqlite_ext import JSONField
from .base_model import BaseModel
class Steps(BaseModel):
id = AutoField(primary_key=True, unique=True, null=False)
name = TextField(null=False)
type = TextField(null=False)
spec = JSONField(null=False) # keys inside spec must not overlap withthe model
description = TextField(null=False)
archived = BooleanField(null=False, default=False)
@classmethod
def delete(cls, *args, **kwargs):
# OVERRIDE DELETION
# so that deleting a user will only archive it
return cls.update(archived=True)
class Meta:
table_name = "steps"

View File

@ -61,7 +61,8 @@ try:
from PyQt5.QtCore import QObject, QThread, pyqtSignal
from PyQt5.QtWidgets import QApplication, QMessageBox
from ui import (About, Archive, Autotests_Archive, Login, Main_Window,
Test, Users_Management)
Recipes_Management, Steps_Management, Test,
Users_Management)
class Main(QObject):
do = pyqtSignal(dict)
@ -79,7 +80,7 @@ try:
self.config = ConfigReader()
# INIT COMPONENT
self.components_specs = {
"archive_synchronizer": {"c": ArchiveSynchronizer},
# "archive_synchronizer": {"c": ArchiveSynchronizer},
"galaxy_camera": {"c": GalaxyCamera, "k": {"paused": True}},
"label_printer": {"c": Os_Label_Printer, "t": False},
"neo_pixels": {"c": NeoPixels, "t": False},
@ -119,9 +120,9 @@ try:
self.main_window.archive_a.triggered.connect(lambda checked, self=weakref.ref(self): self().main_window.open_dialog(Archive()))
if "--archive" in sys.argv:
self.main_window.archive_a.trigger()
self.main_window.autotests_archive_a.triggered.connect(lambda checked, self=weakref.ref(self): self().main_window.open_dialog(Autotests_Archive()))
if "--autotests-archive" in sys.argv:
self.main_window.autotests_archive_a.trigger()
# self.main_window.autotests_archive_a.triggered.connect(lambda checked, self=weakref.ref(self): self().main_window.open_dialog(Autotests_Archive()))
# if "--autotests-archive" in sys.argv:
# self.main_window.autotests_archive_a.trigger()
self.main_window.about_a.triggered.connect(lambda checked, self=weakref.ref(self): self().main_window.open_dialog(About()))
if "--about" in sys.argv:
self.main_window.about_a.trigger()
@ -130,6 +131,12 @@ try:
self.main_window.users_management_a.triggered.connect(lambda checked, self=weakref.ref(self): self().main_window.open_dialog(Users_Management()))
if "--users-management" in sys.argv:
self.main_window.users_management_a.trigger()
self.main_window.recipes_management_a.triggered.connect(lambda checked, self=weakref.ref(self): self().main_window.open_dialog(Recipes_Management()))
if "--recipes-management" in sys.argv:
self.main_window.recipes_management_a.trigger()
self.main_window.steps_management_a.triggered.connect(lambda checked, self=weakref.ref(self): self().main_window.open_dialog(Steps_Management()))
if "--steps-management" in sys.argv:
self.main_window.steps_management_a.trigger()
# OPEN LOGIN TAB
self.open_login()
# SHOW MAIN WINDOW

View File

@ -1,22 +1,34 @@
from .about import About
from .archive import Archive
from .autotests_archive import Autotests_Archive
from .crud import (Cell, Combo_Box_Cell_Widget, CopyPastableCrudQTableWidget,
Crud, External_Dialog_Cell_Widget,
Json_External_Dialog_Cell_Widget,
Json_External_Dialog_Editor_Cell_Widget,
Line_Edit_Cell_Widget)
from .dialog import Dialog
from .editor import Editor
from .helpers import calc_foreground_color, replace_widget
from .leak_step_editor import Leak_Step_Editor
from .login import Login
from .main_window import Main_Window
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 .recipe_spec_editor import Recipe_Spec_Editor
from .recipes_management import Recipes_Management
from .steps_management import Steps_Management
from .test import Test
from .test_admin_permission import Test_Admin_Permission
from .test_assembly import Test_Assembly
from .test_autotest import Test_Autotest
from .test_home import Test_Home
from .test_leak import Test_Leak
from .test_test import Test_Test
from .test_vision import Test_Vision
from .users_management import Users_Management
from .vision_step_editor import Vision_Step_Editor
from .widget import Widget
from .window import Window

View File

@ -1,4 +1,6 @@
from .CopyPastableCrudQTableWidget import CopyPastableCrudQTableWidget
from .crud import (Cell, Combo_Box_Cell_Widget, Crud,
External_Dialog_Cell_Widget,
Json_External_Dialog_Cell_Widget, Line_Edit_Cell_Widget)
Json_External_Dialog_Cell_Widget,
Json_External_Dialog_Editor_Cell_Widget,
Line_Edit_Cell_Widget)

View File

@ -6,10 +6,12 @@ from datetime import datetime
from lib.db import Crud_DB
from peewee import TextField
from PyQt5.QtCore import pyqtSignal
from PyQt5.QtCore import Qt, pyqtSignal
from PyQt5.QtWidgets import (QAbstractItemView, QComboBox, QDialog,
QGridLayout, QHeaderView, QLineEdit, QMessageBox,
QPlainTextEdit, QPushButton)
from ui.dialog import Dialog
from ui.editor import Editor
from ui.widget import Widget
@ -37,12 +39,14 @@ def from_str(data, field=None):
class Cell:
modified = pyqtSignal(bool)
def __init__(self, readonly=True, autocomplete=None, field_name=None, field_alias=None, field=None):
def __init__(self, readonly=True, autocomplete=None, field_name=None, field_alias=None, field=None, row_number=None, crud=None):
self.readonly = readonly
self.autocomplete = autocomplete
self.field_name = field_name
self.field_alias = field_alias
self.field = field
self.row_number = row_number
self.crud = crud
self.set_readonly(self.readonly)
self.value = None
self.is_modified = False
@ -96,9 +100,9 @@ class Cell:
class Line_Edit_Cell_Widget(QLineEdit, Cell):
def __init__(self, readonly=True, autocomplete=None, field_name=None, field_alias=None, field=None):
def __init__(self, readonly=True, autocomplete=None, field_name=None, field_alias=None, field=None, row_number=None, crud=None):
super().__init__()
Cell.__init__(self, readonly=readonly, autocomplete=autocomplete, field_name=field_name, field_alias=field_alias, field=field)
Cell.__init__(self, readonly=readonly, autocomplete=autocomplete, field_name=field_name, field_alias=field_alias, field=field, row_number=row_number, crud=crud)
def set_readonly(self, readonly):
self.setReadOnly(readonly)
@ -115,9 +119,9 @@ class Line_Edit_Cell_Widget(QLineEdit, Cell):
class Combo_Box_Cell_Widget(QComboBox, Cell):
def __init__(self, readonly=True, autocomplete=None, field_name=None, field_alias=None, field=None):
def __init__(self, readonly=True, autocomplete=None, field_name=None, field_alias=None, field=None, row_number=None, crud=None):
super().__init__()
Cell.__init__(self, readonly=readonly, autocomplete=autocomplete, field_name=field_name, field_alias=field_alias, field=field)
Cell.__init__(self, readonly=readonly, autocomplete=autocomplete, field_name=field_name, field_alias=field_alias, field=field, row_number=row_number, crud=crud)
def set_readonly(self, readonly):
self.setEditable(not readonly)
@ -137,15 +141,15 @@ class Combo_Box_Cell_Widget(QComboBox, Cell):
class External_Dialog_Cell_Widget(QPushButton, Cell):
def __init__(self, readonly=True, autocomplete=None, field_name=None, field_alias=None, field=None):
def __init__(self, readonly=True, autocomplete=None, field_name=None, field_alias=None, field=None, row_number=None, crud=None):
self.dialog = QDialog()
self.editor = QPlainTextEdit()
self.dialog.setLayout(QGridLayout())
self.dialog.layout().setSpacing(0)
self.dialog.layout().setContentsMargins(0, 0, 0, 0)
self.dialog.layout().addWidget(self.editor, 0, 0, -1, -1)
super().__init__(u"\u238B apri")
Cell.__init__(self, readonly=readonly, autocomplete=autocomplete, field_name=field_name, field_alias=field_alias, field=field)
super().__init__(u"\u238B modifica")
Cell.__init__(self, readonly=readonly, autocomplete=autocomplete, field_name=field_name, field_alias=field_alias, field=field, row_number=row_number, crud=crud)
self.dialog.setWindowTitle(self.field_alias)
self.clicked.connect(self.dialog.show)
@ -170,6 +174,35 @@ class Json_External_Dialog_Cell_Widget(External_Dialog_Cell_Widget):
return json.loads(self.editor.toPlainText())
class Json_External_Dialog_Editor_Cell_Widget(QPushButton, Cell):
def __init__(self, editor_widget_instance, readonly=True, autocomplete=None, field_name=None, field_alias=None, field=None, row_number=None, crud=None):
if not isinstance(editor_widget_instance, Editor):
raise AssertionError(f"editor_widget_instance {editor_widget_instance!r} must be a subclass of {Editor!r}")
self.editor = editor_widget_instance
self.dialog = Dialog()
self.dialog.setAttribute(Qt.WA_DeleteOnClose, on=False)
self.dialog.setCentralWidget(self.editor)
super().__init__(u"\u238B modifica")
Cell.__init__(self, readonly=readonly, autocomplete=autocomplete, field_name=field_name, field_alias=field_alias, field=field, row_number=row_number, crud=crud)
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)
class Crud(Widget):
modified = pyqtSignal(bool)
selected = pyqtSignal(list)
@ -378,7 +411,7 @@ class Crud(Widget):
r = self.row_upgrader(r, rn, self)
for fn, cn in self.select_index.items():
readonly = self.readonly is None or self.readonly is True or (self.readonly is not False and fn in self.readonly)
w = self.widget_classes.get(fn, self.default_widget_class)(readonly=readonly, autocomplete=self.autocomplete.get(fn, None), field_name=fn, field_alias=self.fields_aliases[fn], field=self.db.table_model._meta.fields.get(fn, None))
w = self.widget_classes.get(fn, self.default_widget_class)(readonly=readonly, autocomplete=self.autocomplete.get(fn, None), field_name=fn, field_alias=self.fields_aliases[fn], field=self.db.table_model._meta.fields.get(fn, None), row_number=rn, crud=self)
w.modified.connect(self.set_modified)
if fn in r:
w._render(data=r[fn], row_number=rn, crud=self)
@ -400,7 +433,7 @@ class Crud(Widget):
self.db_tw.setRowCount(rn + 1)
for fn, cn in self.select_index.items():
readonly = self.readonly is None or self.readonly is True or (self.readonly is not False and fn in self.readonly)
w = self.widget_classes.get(fn, self.default_widget_class)(readonly=readonly, autocomplete=self.autocomplete.get(fn, None), field_name=fn, field_alias=self.fields_aliases[fn], field=self.db.table_model._meta.fields.get(fn, None))
w = self.widget_classes.get(fn, self.default_widget_class)(readonly=readonly, autocomplete=self.autocomplete.get(fn, None), field_name=fn, field_alias=self.fields_aliases[fn], field=self.db.table_model._meta.fields.get(fn, None), row_number=rn, crud=self)
w.modified.connect(self.set_modified)
self.db_tw.setCellWidget(rn, cn, w)
self.db_tw.scrollToBottom()

View File

@ -0,0 +1 @@
from .editor import Editor

View File

@ -1,43 +1,22 @@
from PyQt5.QtCore import QObject
from PyQt5.QtWidgets import (QCheckBox, QComboBox, QDoubleSpinBox, QLineEdit,
QPlainTextEdit, QRadioButton, QSpinBox)
QListWidget, QPlainTextEdit, QRadioButton,
QSpinBox)
from ui.widget import Widget
class Recipe_Editor(Widget):
class Editor(Widget):
def __init__(self):
super().__init__()
self.spec = {
# recipe
"client": self.client_le,
"part_number": self.part_number_le,
"station": self.station_le,
"description": self.description_pte,
# pre-filling
"pre_filling_time": self.pre_filling_time_sb,
"pre_filling_pressure": self.pre_filling_pressure_sb,
# filling
"filling_time": self.filling_time_sb,
"settling_time": self.settling_time_sb,
"settling_pressure_min_percent": self.settling_pressure_min_percent_sb,
"settling_pressure_max_percent": self.settling_pressure_max_percent_sb,
# test
"test_time": self.test_time_sb,
"test_pressure_min_delta": self.test_pressure_min_delta_sb,
"test_pressure": self.test_pressure_sb,
"test_pressure_max_delta": self.test_pressure_max_delta_sb,
"cycles": self.cycles_sb,
# flush
"flush_time": self.flush_time_sb,
"flush_pressure": self.flush_pressure_sb,
# vision
"vision_recipe": self.vision_recipe_cb,
}
self.spec = {}
def set_readonly(self, readonly):
for w in self.spec.values():
if isinstance(w, QComboBox):
w.setDisabled(readonly)
else:
elif isinstance(w, QListWidget):
w.setDisabled(readonly)
elif isinstance(w, QObject):
w.setReadOnly(readonly)
def do_autocomplete(self, autocomplete):
@ -48,19 +27,30 @@ class Recipe_Editor(Widget):
if isinstance(w, QCheckBox):
w.setChecked(bool(v))
elif isinstance(w, QComboBox):
# while w.count() > 0:
# w.removeItem(0)
# w.clear()
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, QListWidget):
# while w.count() > 0:
# w.takeItem(0)
# w.clear()
w.addItems(list(map(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:
elif isinstance(w, QObject):
raise NotImplementedError(f"widget of type {type(w)!r} not implemented")
else:
# ignore default values
pass
def connect_modified(self, check_modified):
for k, w in self.spec.items():
@ -72,14 +62,19 @@ class Recipe_Editor(Widget):
w.valueChanged.connect(check_modified)
elif isinstance(w, QLineEdit):
w.textChanged.connect(check_modified)
elif isinstance(w, QListWidget):
w.currentItemChanged.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:
elif isinstance(w, QObject):
raise NotImplementedError(f"widget of type {type(w)!r} not implemented")
else:
# ignore default values
pass
def render(self, data, field_name=None, row_number=None, crud=None):
for k, v in data.items():
@ -94,14 +89,20 @@ class Recipe_Editor(Widget):
w.setValue(float(v))
elif isinstance(w, QLineEdit):
w.setText(str(v))
elif isinstance(w, QListWidget):
w.clear()
w.addItems(list(map(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:
elif isinstance(w, QObject):
raise NotImplementedError(f"widget of type {type(w)!r} not implemented")
else:
# ignore default values
pass
def parse(self, row_number=None, crud=None):
ret = {}
@ -114,12 +115,17 @@ class Recipe_Editor(Widget):
ret[k] = w.value()
elif isinstance(w, QLineEdit):
ret[k] = w.text()
elif isinstance(w, QListWidget):
ret[k] = [w.item(i).data(0) for i in range(w.count())]
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:
elif isinstance(w, QObject):
raise NotImplementedError(f"widget of type {type(w)!r} not implemented")
else:
# pass through default values
ret[k] = w
return ret

View File

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>Recipe_Editor</class>
<widget class="QWidget" name="Recipe_Editor">
<class>Editor</class>
<widget class="QWidget" name="Editor">
<property name="geometry">
<rect>
<x>0</x>
@ -11,58 +11,58 @@
</rect>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="2" column="1">
<widget class="QGroupBox" name="groupBox_2">
<item row="2" column="3">
<widget class="QGroupBox" name="groupBox_4">
<property name="title">
<string>Pre-Riempimento</string>
<string>Scarico</string>
</property>
<layout class="QGridLayout" name="gridLayout_3">
<item row="3" column="1">
<widget class="QSpinBox" name="pre_filling_pressure_sb">
<property name="maximum">
<number>9999</number>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QSpinBox" name="pre_filling_time_sb">
<property name="maximum">
<number>9999</number>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_5">
<layout class="QGridLayout" name="gridLayout_5">
<item row="0" column="0">
<widget class="QLabel" name="label_6">
<property name="text">
<string>Tempo</string>
</property>
</widget>
</item>
<item row="3" column="2">
<widget class="QLabel" name="label_19">
<item row="1" column="0">
<widget class="QLabel" name="label_30">
<property name="text">
<string>mbar</string>
<string>Pressione</string>
</property>
</widget>
</item>
<item row="1" column="2">
<widget class="QLabel" name="label_3">
<item row="1" column="1">
<widget class="QSpinBox" name="flush_pressure_sb">
<property name="maximum">
<number>9999</number>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QSpinBox" name="flush_time_sb">
<property name="maximum">
<number>9999</number>
</property>
</widget>
</item>
<item row="0" column="2">
<widget class="QLabel" name="label_18">
<property name="text">
<string>s</string>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="label_7">
<item row="1" column="2">
<widget class="QLabel" name="label_31">
<property name="text">
<string>Pressione</string>
<string>mbar</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item row="2" column="2">
<item row="2" column="1">
<widget class="QGroupBox" name="groupBox_7">
<property name="title">
<string>Riempimento</string>
@ -155,168 +155,7 @@
</layout>
</widget>
</item>
<item row="2" column="3">
<widget class="QGroupBox" name="groupBox_3">
<property name="title">
<string>Test</string>
</property>
<layout class="QGridLayout" name="gridLayout_4">
<item row="6" column="0">
<widget class="QLabel" name="label_17">
<property name="text">
<string>Cicli</string>
</property>
</widget>
</item>
<item row="3" column="2">
<widget class="QSpinBox" name="test_pressure_min_delta_sb">
<property name="minimum">
<number>0</number>
</property>
<property name="maximum">
<number>9999</number>
</property>
</widget>
</item>
<item row="0" column="3">
<widget class="QLabel" name="label_28">
<property name="text">
<string>s</string>
</property>
</widget>
</item>
<item row="5" column="2">
<widget class="QSpinBox" name="test_pressure_max_delta_sb">
<property name="maximum">
<number>9999</number>
</property>
</widget>
</item>
<item row="0" column="2">
<widget class="QSpinBox" name="test_time_sb">
<property name="maximum">
<number>9999</number>
</property>
</widget>
</item>
<item row="4" column="3">
<widget class="QLabel" name="label_23">
<property name="text">
<string>mbar</string>
</property>
</widget>
</item>
<item row="3" column="3">
<widget class="QLabel" name="label_22">
<property name="text">
<string>mbar</string>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="label_10">
<property name="text">
<string>Delta min</string>
</property>
</widget>
</item>
<item row="5" column="0">
<widget class="QLabel" name="label_12">
<property name="text">
<string>Delta max</string>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QLabel" name="label_16">
<property name="text">
<string>Tempo</string>
</property>
</widget>
</item>
<item row="5" column="3">
<widget class="QLabel" name="label_24">
<property name="text">
<string>mbar</string>
</property>
</widget>
</item>
<item row="4" column="2">
<widget class="QSpinBox" name="test_pressure_sb">
<property name="maximum">
<number>9999</number>
</property>
</widget>
</item>
<item row="4" column="0">
<widget class="QLabel" name="label_11">
<property name="text">
<string>Pressione</string>
</property>
</widget>
</item>
<item row="6" column="2">
<widget class="QSpinBox" name="cycles_sb">
<property name="maximum">
<number>9999</number>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item row="2" column="4">
<widget class="QGroupBox" name="groupBox_4">
<property name="title">
<string>Scarico</string>
</property>
<layout class="QGridLayout" name="gridLayout_5">
<item row="0" column="0">
<widget class="QLabel" name="label_6">
<property name="text">
<string>Tempo</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_30">
<property name="text">
<string>Pressione</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QSpinBox" name="flush_pressure_sb">
<property name="maximum">
<number>9999</number>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QSpinBox" name="flush_time_sb">
<property name="maximum">
<number>9999</number>
</property>
</widget>
</item>
<item row="0" column="2">
<widget class="QLabel" name="label_18">
<property name="text">
<string>s</string>
</property>
</widget>
</item>
<item row="1" column="2">
<widget class="QLabel" name="label_31">
<property name="text">
<string>mbar</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item row="3" column="1" colspan="4">
<item row="3" column="0" colspan="4">
<widget class="QGroupBox" name="groupBox_6">
<property name="title">
<string>Visione</string>
@ -335,26 +174,159 @@
</layout>
</widget>
</item>
<item row="0" column="1" colspan="4">
<item row="2" column="0">
<widget class="QGroupBox" name="groupBox_2">
<property name="title">
<string>Pre-Riempimento</string>
</property>
<layout class="QGridLayout" name="gridLayout_3">
<item row="3" column="1">
<widget class="QSpinBox" name="pre_filling_pressure_sb">
<property name="maximum">
<number>9999</number>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QSpinBox" name="pre_filling_time_sb">
<property name="maximum">
<number>9999</number>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_5">
<property name="text">
<string>Tempo</string>
</property>
</widget>
</item>
<item row="3" column="2">
<widget class="QLabel" name="label_19">
<property name="text">
<string>mbar</string>
</property>
</widget>
</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="0">
<widget class="QLabel" name="label_7">
<property name="text">
<string>Pressione</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item row="2" column="2">
<widget class="QGroupBox" name="groupBox_3">
<property name="title">
<string>Test</string>
</property>
<layout class="QGridLayout" name="gridLayout_4">
<item row="3" column="3">
<widget class="QLabel" name="label_22">
<property name="text">
<string>mbar</string>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="label_10">
<property name="text">
<string>Delta min</string>
</property>
</widget>
</item>
<item row="4" column="2">
<widget class="QSpinBox" name="test_pressure_sb">
<property name="maximum">
<number>9999</number>
</property>
</widget>
</item>
<item row="5" column="2">
<widget class="QSpinBox" name="test_pressure_max_delta_sb">
<property name="maximum">
<number>9999</number>
</property>
</widget>
</item>
<item row="0" column="2">
<widget class="QSpinBox" name="test_time_sb">
<property name="maximum">
<number>9999</number>
</property>
</widget>
</item>
<item row="5" column="3">
<widget class="QLabel" name="label_24">
<property name="text">
<string>mbar</string>
</property>
</widget>
</item>
<item row="5" column="0">
<widget class="QLabel" name="label_12">
<property name="text">
<string>Delta max</string>
</property>
</widget>
</item>
<item row="4" column="3">
<widget class="QLabel" name="label_23">
<property name="text">
<string>mbar</string>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QLabel" name="label_16">
<property name="text">
<string>Tempo</string>
</property>
</widget>
</item>
<item row="3" column="2">
<widget class="QSpinBox" name="test_pressure_min_delta_sb">
<property name="minimum">
<number>0</number>
</property>
<property name="maximum">
<number>9999</number>
</property>
</widget>
</item>
<item row="4" column="0">
<widget class="QLabel" name="label_11">
<property name="text">
<string>Pressione</string>
</property>
</widget>
</item>
<item row="0" column="3">
<widget class="QLabel" name="label_28">
<property name="text">
<string>s</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item row="0" column="0" colspan="4">
<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="1" column="0">
<widget class="QLabel" name="label">
<property name="text">
@ -362,16 +334,7 @@
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QLineEdit" name="station_le"/>
</item>
<item row="1" column="1">
<widget class="QLineEdit" name="part_number_le"/>
</item>
<item row="0" column="1">
<widget class="QLineEdit" name="client_le"/>
</item>
<item row="4" column="0" colspan="2">
<item row="3" column="0" colspan="2">
<widget class="QGroupBox" name="groupBox_5">
<property name="title">
<string>Descrizione</string>
@ -383,6 +346,19 @@
</layout>
</widget>
</item>
<item row="0" column="0">
<widget class="QLabel" name="label_2">
<property name="text">
<string>Cliente</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLineEdit" name="client_le"/>
</item>
<item row="1" column="1">
<widget class="QLineEdit" name="part_number_le"/>
</item>
</layout>
</widget>
</item>

View File

@ -1,7 +1,15 @@
import logging
import traceback
logger = logging.getLogger("replace_widget")
def replace_widget(parent, name, new, delete=False):
old = getattr(parent, name)
replaced = old.parentWidget().layout().replaceWidget(old, new)
if replaced is None:
if old is new:
logger.warning(f"\n{''.join(traceback.format_stack())}\nyou are trying to replace {old!r} with itself.")
return
if old.parentWidget().layout().replaceWidget(old, new) is None:
raise AssertionError(f"{name} not found, cannot replace it.")
old.hide()
setattr(parent, name, new)

View File

@ -0,0 +1 @@
from .leak_step_editor import Leak_Step_Editor

View File

@ -0,0 +1,24 @@
from ui.editor import Editor
class Leak_Step_Editor(Editor):
def __init__(self):
super().__init__()
self.spec.update({
# pre-filling
"pre_filling_time": self.pre_filling_time_sb,
"pre_filling_pressure": self.pre_filling_pressure_sb,
# filling
"filling_time": self.filling_time_sb,
"settling_time": self.settling_time_sb,
"settling_pressure_min_percent": self.settling_pressure_min_percent_sb,
"settling_pressure_max_percent": self.settling_pressure_max_percent_sb,
# test
"test_time": self.test_time_sb,
"test_pressure_min_delta": self.test_pressure_min_delta_sb,
"test_pressure": self.test_pressure_sb,
"test_pressure_max_delta": self.test_pressure_max_delta_sb,
# flush
"flush_time": self.flush_time_sb,
"flush_pressure": self.flush_pressure_sb,
})

View File

@ -0,0 +1,309 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>Leak Step Editor</class>
<widget class="QWidget" name="Leak Step Editor">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>845</width>
<height>194</height>
</rect>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="1" column="0">
<widget class="QGroupBox" name="groupBox_2">
<property name="title">
<string>Pre-Riempimento</string>
</property>
<layout class="QGridLayout" name="gridLayout_3">
<item row="3" column="1">
<widget class="QSpinBox" name="pre_filling_pressure_sb">
<property name="maximum">
<number>9999</number>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QSpinBox" name="pre_filling_time_sb">
<property name="maximum">
<number>9999</number>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_5">
<property name="text">
<string>Tempo</string>
</property>
</widget>
</item>
<item row="3" column="2">
<widget class="QLabel" name="label_19">
<property name="text">
<string>mbar</string>
</property>
</widget>
</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="0">
<widget class="QLabel" name="label_7">
<property name="text">
<string>Pressione</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item row="1" column="2">
<widget class="QGroupBox" name="groupBox_3">
<property name="title">
<string>Test</string>
</property>
<layout class="QGridLayout" name="gridLayout_4">
<item row="3" column="3">
<widget class="QLabel" name="label_22">
<property name="text">
<string>mbar</string>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="label_10">
<property name="text">
<string>Delta min</string>
</property>
</widget>
</item>
<item row="4" column="2">
<widget class="QSpinBox" name="test_pressure_sb">
<property name="maximum">
<number>9999</number>
</property>
</widget>
</item>
<item row="5" column="2">
<widget class="QSpinBox" name="test_pressure_max_delta_sb">
<property name="maximum">
<number>9999</number>
</property>
</widget>
</item>
<item row="0" column="2">
<widget class="QSpinBox" name="test_time_sb">
<property name="maximum">
<number>9999</number>
</property>
</widget>
</item>
<item row="5" column="3">
<widget class="QLabel" name="label_24">
<property name="text">
<string>mbar</string>
</property>
</widget>
</item>
<item row="5" column="0">
<widget class="QLabel" name="label_12">
<property name="text">
<string>Delta max</string>
</property>
</widget>
</item>
<item row="4" column="3">
<widget class="QLabel" name="label_23">
<property name="text">
<string>mbar</string>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QLabel" name="label_16">
<property name="text">
<string>Tempo</string>
</property>
</widget>
</item>
<item row="3" column="2">
<widget class="QSpinBox" name="test_pressure_min_delta_sb">
<property name="minimum">
<number>0</number>
</property>
<property name="maximum">
<number>9999</number>
</property>
</widget>
</item>
<item row="4" column="0">
<widget class="QLabel" name="label_11">
<property name="text">
<string>Pressione</string>
</property>
</widget>
</item>
<item row="0" column="3">
<widget class="QLabel" name="label_28">
<property name="text">
<string>s</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item row="1" column="1">
<widget class="QGroupBox" name="groupBox_7">
<property name="title">
<string>Riempimento</string>
</property>
<layout class="QGridLayout" name="gridLayout_7">
<item row="2" column="0">
<widget class="QLabel" name="label_14">
<property name="text">
<string>Soglia min</string>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QLabel" name="label_8">
<property name="text">
<string>Tempo</string>
</property>
</widget>
</item>
<item row="0" column="2">
<widget class="QLabel" name="label_20">
<property name="text">
<string>s</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QSpinBox" name="settling_pressure_min_percent_sb">
<property name="maximum">
<number>100</number>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QSpinBox" name="filling_time_sb">
<property name="maximum">
<number>9999</number>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="label_15">
<property name="text">
<string>Soglia max</string>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QSpinBox" name="settling_pressure_max_percent_sb">
<property name="maximum">
<number>100</number>
</property>
</widget>
</item>
<item row="2" column="2">
<widget class="QLabel" name="label_26">
<property name="text">
<string>%</string>
</property>
</widget>
</item>
<item row="3" column="2">
<widget class="QLabel" name="label_27">
<property name="text">
<string>%</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_13">
<property name="text">
<string>Assestamento</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QSpinBox" name="settling_time_sb">
<property name="maximum">
<number>9999</number>
</property>
</widget>
</item>
<item row="1" column="2">
<widget class="QLabel" name="label_25">
<property name="text">
<string>s</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item row="1" column="3">
<widget class="QGroupBox" name="groupBox_4">
<property name="title">
<string>Scarico</string>
</property>
<layout class="QGridLayout" name="gridLayout_5">
<item row="0" column="0">
<widget class="QLabel" name="label_6">
<property name="text">
<string>Tempo</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_30">
<property name="text">
<string>Pressione</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QSpinBox" name="flush_pressure_sb">
<property name="maximum">
<number>9999</number>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QSpinBox" name="flush_time_sb">
<property name="maximum">
<number>9999</number>
</property>
</widget>
</item>
<item row="0" column="2">
<widget class="QLabel" name="label_18">
<property name="text">
<string>s</string>
</property>
</widget>
</item>
<item row="1" column="2">
<widget class="QLabel" name="label_31">
<property name="text">
<string>mbar</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

View File

@ -31,6 +31,8 @@
<string>Amministrazione</string>
</property>
<addaction name="users_management_a"/>
<addaction name="recipes_management_a"/>
<addaction name="steps_management_a"/>
<addaction name="quit_a"/>
</widget>
<widget class="QMenu" name="menuStrumenti">
@ -38,7 +40,6 @@
<string>Strumenti</string>
</property>
<addaction name="archive_a"/>
<addaction name="autotests_archive_a"/>
</widget>
<addaction name="menuStrumenti"/>
<addaction name="admin_m"/>
@ -69,6 +70,16 @@
<string>Esci</string>
</property>
</action>
<action name="recipes_management_a">
<property name="text">
<string>Gestione ricette</string>
</property>
</action>
<action name="steps_management_a">
<property name="text">
<string>Gestione fasi di test</string>
</property>
</action>
</widget>
<resources/>
<connections/>

View File

@ -1 +0,0 @@
from .recipe_editor import Recipe_Editor

View File

@ -1,2 +1 @@
from .recipe_selection import (Json_Spec_External_Dialog_Cell_Widget,
Recipe_Selection)
from .recipe_selection import Recipe_Selection

View File

@ -1,58 +1,15 @@
import sys
from glob import iglob
from lib.db import Recipes, Users
from PyQt5.QtCore import Qt, QTimer, pyqtSignal
from PyQt5.QtCore import QTimer, pyqtSignal
from PyQt5.QtGui import QKeySequence
from PyQt5.QtWidgets import QPushButton, QShortcut
from ui.crud import Cell, Crud
from ui.dialog import Dialog
from PyQt5.QtWidgets import QShortcut
from ui.crud import Crud, Json_External_Dialog_Editor_Cell_Widget
from ui.helpers import replace_widget
from ui.recipe_editor import Recipe_Editor
from ui.recipe_spec_editor import Recipe_Spec_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):
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):
for k in list(row):
if k in row["spec"]:
row["spec"][k] = row.pop(k)
return True, row, False
class Recipe_Selection(Widget):
ok = pyqtSignal(Recipes)
@ -63,24 +20,10 @@ class Recipe_Selection(Widget):
readonly = False
crud_aliases = {
"name": "Ricetta",
"client": "Cliente",
"part_number": "N° disegno",
"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",
"description": "Desccrizione",
"archived": "Archiviata",
}
filters = None
@ -90,8 +33,9 @@ class Recipe_Selection(Widget):
"name": "Ricetta",
"client": "Cliente",
"part_number": "N° disegno",
"description": "Desccrizione",
"spec": "Specifica",
"description": "Desccrizione",
# "archived": "Archiviata",
}
filters = {"archived": False}
self.crud = Crud(
@ -103,14 +47,8 @@ class Recipe_Selection(Widget):
fields_aliases=crud_aliases,
autocomplete={
"archived": False,
"spec": {
# "vision_recipe": iglob("*.ini", root_dir="./config/vision/recipes/"), # only in python3.10
"vision_recipe": list(iglob("./config/vision/recipes/*.ini")),
},
},
row_upgrader=recipes_row_upgrader,
widget_classes={"spec": Json_Spec_External_Dialog_Cell_Widget, },
row_filter=recipes_row_filter,
widget_classes={"spec": lambda *args, **kwargs: Json_External_Dialog_Editor_Cell_Widget(Recipe_Spec_Editor(), *args, **kwargs), },
)
replace_widget(self, "crud_w", self.crud)
self.crud_modified = None

View File

@ -0,0 +1 @@
from .recipe_spec_editor import Recipe_Spec_Editor

View File

@ -0,0 +1,18 @@
from PyQt5.QtWidgets import QAbstractItemView
from ui.editor import Editor
class Recipe_Spec_Editor(Editor):
def __init__(self):
super().__init__()
self.spec.update({
"steps": self.steps_lw,
})
self.steps_lw.setDragDropMode(QAbstractItemView.InternalMove)
def set_readonly(self, readonly):
super().set_readonly(readonly)
self.steps_lw.setDisabled(readonly)
self.remove_step_b.setDisabled(readonly)
self.steps_available_lw.setDisabled(readonly)
self.add_step_b.setDisabled(readonly)

View File

@ -0,0 +1,68 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>Recipe Spec Editor</class>
<widget class="QWidget" name="Recipe Spec Editor">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>536</width>
<height>293</height>
</rect>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="1" column="1">
<widget class="QLabel" name="label_2">
<property name="text">
<string>Disponibili</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QListWidget" name="steps_lw"/>
</item>
<item row="3" column="1">
<widget class="QPushButton" name="add_step_b">
<property name="text">
<string>Aggiungi passo</string>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QPushButton" name="remove_step_b">
<property name="text">
<string>Rimuovi passo</string>
</property>
</widget>
</item>
<item row="0" column="0" colspan="2">
<widget class="QLabel" name="label_3">
<property name="text">
<string>Passi</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label">
<property name="text">
<string>Abilitati</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QListWidget" name="steps_available_lw"/>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

View File

@ -0,0 +1 @@
from .recipes_management import Recipes_Management

View File

@ -0,0 +1,29 @@
from ui.crud import Crud, Json_External_Dialog_Editor_Cell_Widget
from ui.recipe_spec_editor import Recipe_Spec_Editor
from ui.widget import Widget
class Recipes_Management(Widget):
def __init__(self):
super().__init__()
crud_aliases = {
"id": "Id",
"name": "Ricetta",
"client": "Cliente",
"part_number": "N° disegno",
"spec": "Specifica",
"description": "Desccrizione",
"archived": "Archiviata",
}
self.crud = Crud(
"recipes",
display_name="GESTIONE RICETTE",
readonly=["id"],
select=list(crud_aliases.keys()),
fields_aliases=crud_aliases,
autocomplete={
"archived": False,
},
widget_classes={"spec": lambda *args, **kwargs: Json_External_Dialog_Editor_Cell_Widget(Recipe_Spec_Editor(), *args, **kwargs), },
)
self.layout().addWidget(self.crud, 0, 0, -1, -1)

View File

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>Recipes management</class>
<widget class="QWidget" name="Recipes management">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>94</width>
<height>18</height>
</rect>
</property>
<layout class="QGridLayout" name="gridLayout"/>
</widget>
<resources/>
<connections/>
</ui>

View File

@ -0,0 +1 @@
from .steps_management import Steps_Management

View File

@ -0,0 +1,100 @@
from glob import iglob
from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import (QCheckBox, QComboBox, QDoubleSpinBox, QLineEdit,
QPlainTextEdit, QPushButton, QRadioButton,
QSpinBox)
from ui.crud import (Cell, Combo_Box_Cell_Widget, Crud,
Json_External_Dialog_Editor_Cell_Widget)
from ui.dialog import Dialog
from ui.leak_step_editor import Leak_Step_Editor
from ui.vision_step_editor import Vision_Step_Editor
from ui.widget import Widget
class Step_Spec_JEDECW(QPushButton, Cell):
def __init__(self, readonly=True, autocomplete=None, field_name=None, field_alias=None, field=None, row_number=None, crud=None):
self.editors = {
"vision": Vision_Step_Editor(),
"leak": Leak_Step_Editor(),
}
self.editor = None
self.editor_type = None
self.dialog = Dialog()
self.dialog.setAttribute(Qt.WA_DeleteOnClose, on=False)
super().__init__(u"\u238B modifica")
print(readonly, autocomplete, field_name, field_alias, field, row_number, crud)
super(QPushButton).__init__(self, readonly=readonly, autocomplete=autocomplete, field_name=field_name, field_alias=field_alias, field=field, row_number=row_number, crud=crud)
print(self.readonly, self.autocomplete, self.field_name, self.field_alias, self.field, self.row_number, self.crud)
print("readonly, autocomplete, field_name, field_alias, field, row_number, crud")
raise Exception()
self.crud.modified.connect(self.update_editor)
self.dialog.setWindowTitle(self.field_alias)
self.clicked.connect(self.dialog.show)
def update_editor(self):
print(self.editor, self.row_number, self.crud)
self.editor_type = self.crud.db_tw.cellWidget(self.row_number, self.crud.select_index["type"])._parse()
print(self.editor_type)
self.editor = self.editors[self.editor_type]
self.dialog.setCentralWidget(self.editor)
def set_readonly(self, readonly):
for editor in self.editors.values():
editor.set_readonly(readonly)
def do_autocomplete(self, autocomplete):
self.update_editor()
for editor_type, editor in self.editors.items():
if autocomplete is None:
editor.do_autocomplete(None)
elif editor_type in autocomplete:
editor.do_autocomplete(autocomplete[editor_type])
def connect_modified(self):
for editor in self.editors.values():
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)
class Steps_Management(Widget):
def __init__(self):
super().__init__()
crud_aliases = {
"id": "Id",
"name": "Fase di test",
"type": "Tipo",
"spec": "Specifica",
"description": "Desccrizione",
"archived": "Archiviata",
}
self.crud = Crud(
"steps",
display_name="GESTIONE FASI DI TEST",
readonly=["id"],
select=list(crud_aliases.keys()),
fields_aliases=crud_aliases,
autocomplete={
"type": [
"leak",
"vision",
],
"spec": {
"vision": {
# "recipe": iglob("*.ini", root_dir="./config/vision/recipes/"), # only in python3.10
"recipe": list(iglob("./config/vision/recipes/*.ini")),
},
},
"archived": False,
},
widget_classes={
"type": Combo_Box_Cell_Widget,
"spec": Step_Spec_JEDECW,
},
)
self.layout().addWidget(self.crud, 0, 0, -1, -1)

View File

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>Steps Management</class>
<widget class="QWidget" name="Steps Management">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>94</width>
<height>18</height>
</rect>
</property>
<layout class="QGridLayout" name="gridLayout"/>
</widget>
<resources/>
<connections/>
</ui>

View File

@ -3,7 +3,7 @@ import os
import sys
from datetime import datetime
from lib.db import Archive, Users
from lib.db import Archive, Steps, Users
from PyQt5.QtCore import QTimer
from ui.helpers import replace_widget
from ui.recipe_selection import Recipe_Selection
@ -32,28 +32,29 @@ class Test(Widget):
self.refresh_time(init=True)
# INIT RECIPE
self.recipe = None
self.step = None
# INIT CYCLE STATES
self.cycle_state = None
self.cycle_states = {
self.cycle_available_steps = {
# "assembly_1": Test_Assembly(self.select_step_img("assembly_1"), u"INSERIRE SENSORE"),
"autotest": Test_Assembly(None, u"ESEGUIRE PROCEDURA DI AUTOTEST", Test_Autotest()),
"done": Test_Assembly(self.select_step_img("success"), u"COLLAUDO COMPLETATO - RIMUOVERE IL SENSORE"),
"emergency": Test_Assembly(self.select_step_img("reset_emergency"), u"EMERGENZA INTERVENUTA - RIPRISTINARE PULSANTE E SELEZIONARE \"RESET EMERGENZA\" DAL MEN\u00d9 \"STRUMENTI\""),
"fail": Test_Assembly(self.select_step_img("fail"), u"CICLO INTERROTTO - RIMUOVERE IL SENSORE"),
"run_test": Test_Assembly(self.select_step_img("wait"), u"ESECUZIONE TEST IN CORSO - ATTENDERE", Test_Leak(components=self.components, recipe=self.recipe)),
"leak": Test_Assembly(None, u"ESECUZIONE TEST IN CORSO - ATTENDERE", Test_Leak(components=self.components, recipe=self.recipe, step=self.step)),
"select_recipe": Test_Assembly(None, u"SELEZIONARE IL CODICE DA COLLAUDARE", Recipe_Selection()),
"vision": Test_Assembly(None, u"ESEGUIRE PROCEDURA DI AUTOTEST", Test_Vision(components=self.components, recipe=self.recipe)),
"vision": Test_Assembly(None, u"ESEGUIRE PROCEDURA DI AUTOTEST", Test_Vision(components=self.components, recipe=self.recipe, step=self.step)),
"wait": Test_Assembly(self.select_step_img("wait"), u"ATTENDERE - PAUSA INTER CICLO"),
None: Test_Assembly(self.select_step_img("warning"), u"ATTENZIONE - LA RICETTA SELEZIONATA NON CONTIENE FASI DI TEST"),
}
self.cycle_loop = ["run_test", "vision", "done", "wait"]
self.cycle_steps = None
self.cycle_index = -1
# SETUP AUTOTEST
self.autotest_request = False
if "--no-autotest" not in sys.argv:
self.autotest_period = 12 * 60 * 60 * 1000
self.request_autotest("init")
else:
self.autotest_period = None
# if "--no-autotest" not in sys.argv:
# self.autotest_period = 12 * 60 * 60 * 1000
# self.request_autotest("init")
# else:
self.autotest_period = None
# INIT TEST DATA
self.data = {}
# INIT PIECES COUNTER ([pieces_ok, pieces_failed])
@ -61,7 +62,7 @@ class Test(Widget):
# CONNECT CYCLE CONTROLS
self.cancel_b.clicked.connect(self.fail_cycle)
self.change_recipe_b.clicked.connect(self.change_recipe)
for w in self.cycle_states.values():
for w in self.cycle_available_steps.values():
if hasattr(w, "ok"):
# custom ok handlers should call next again
if type(w.widget) is Recipe_Selection:
@ -128,79 +129,87 @@ class Test(Widget):
self.time_timer.start(self.autotest_period)
reason = "boot"
self.autotest_request = reason
self.cycle_states["autotest"].widget.set_reason(reason)
self.cycle_available_steps["autotest"].widget.set_reason(reason)
def request_periodic_autotest(self):
self.request_autotest("periodic")
def next(self, action=None):
self.log.debug(f"cycle next: cycle_state: {self.cycle_state!r} action: {action!r}")
current_w = self.cycle_states.get(self.cycle_state, None)
if current_w is not None and hasattr(current_w, "stop"):
current_w.stop()
self.log.debug(f"cycle next: cycle step: {self.step!r} action: {action!r}")
if self.step is not None:
current_w = self.cycle_available_steps[self.step.type]
if hasattr(current_w, "stop"):
current_w.stop()
if action == "change_recipe":
self.log.info(f"cycle next: action: {action!r}")
self.set_recipe(recipe=None)
self.cycle_state = "select_recipe"
self.step = Steps(type="select_recipe")
self.cycle_index = -1
# RESET TEST DATA
self.data.clear()
elif action == "fail":
self.log.info(f"cycle next: action: {action!r}")
if self.cycle_state in self.cycle_loop:
pass
# COUNT FAIL
self.pieces[1] += 1
# FAIL AND RESTART TEST
self.cycle_state = "fail"
self.step = Steps(type="fail")
self.cycle_index = -1
# RESET TEST DATA
self.data.clear()
elif action is not None:
raise NotImplementedError(f"cycle next: action {action!r} is not a valid action")
# if action did not set the next cycle_state
# set next cycle_state normally
if self.recipe is None:
# if action did not set the next cycle step
# set next cycle step normally
if self.recipe is None or self.cycle_steps is None:
# if recipe not set: select_recipe
self.cycle_state = "select_recipe"
else:
self.step = Steps(type="select_recipe")
elif action is None:
if self.cycle_index == -1 and self.autotest_request is not False:
# if cycle_loop is not started or has ended
# if cycle_steps is not started or has ended
# and autotest was requested
self.autotest_request = False
self.cycle_state = "autotest"
self.step = Steps(type="autotest")
if self.autotest_period is not None: # reset periodic autotest timer
self.time_timer.start(self.autotest_period)
elif len(self.cycle_steps):
# goto next step in cycle_steps
self.cycle_index = (self.cycle_index + 1) % len(self.cycle_steps)
self.step = self.cycle_steps[self.cycle_index]
else:
# goto next step in cycle_loop
self.cycle_index = (self.cycle_index + 1) % len(self.cycle_loop)
self.cycle_state = self.cycle_loop[self.cycle_index]
self.cycle_index = -1
self.step = Steps(type=None)
# enable/disable cycle controls
self.change_recipe_b.setEnabled(self.recipe is not None)
self.cancel_b.setEnabled(self.cycle_state not in {
self.cancel_b.setEnabled(self.step.type is not None and self.step.type not in {
"emergency",
"fail",
"select_recipe",
"wait",
})
self.log.info(f"cycle next: next cycle_state: {self.cycle_state!r}")
self.log.info(f"cycle next: next cycle step: {self.step!r}")
# INIT TEST DATA IF STARTING CYCLE LOOP
if self.cycle_index == 0:
self.data.clear()
w = self.cycle_states[self.cycle_state]
w = self.cycle_available_steps[self.step.type]
if hasattr(w, "start"):
w.start(recipe=self.recipe)
w.start(recipe=self.recipe, step=self.cycle_steps[self.cycle_index])
self.setCentralWidget(w)
if self.cycle_state == "done":
if self.step.type == "done":
self.done()
self.next_timer.start(2000)
elif self.cycle_state == "wait":
elif self.step.type == "fail":
self.next_timer.start(2000)
elif self.step.type == "wait":
self.next_timer.start(6000)
# UPDATE PIECES DISPLAY
self.pieces_count_l.setText(f"{self.pieces[0]} OK / {self.pieces[1]} NOK / {sum(self.pieces)} TOT")
def set_recipe(self, recipe=None):
self.recipe = recipe
if self.recipe is None:
self.cycle_steps = None
else:
self.cycle_steps = self.recipe.get_steps() + [Steps(type="done"), Steps(type="wait")]
# UPDATE RECIPE DISPLAY
if self.recipe is not None:
self.recipe_l.setText(self.recipe.name)
@ -222,7 +231,7 @@ class Test(Widget):
self.data["ok"] = self.data.get("ok", True) and self.data["leak"].get("ok", False)
self.next()
def done(self, ok=False, next=False):
def done(self, ok=True):
self.log.info("cycle done")
archived = Archive.archive(self.recipe, self.data, ok and self.data["ok"], overridden=self.data["overridden"])
self.log.info(f"cycle archived locally: {archived!r}")

View File

@ -4,15 +4,15 @@ from ui.test_test import Test_Test
class Test_Leak(Test_Test):
def __init__(self, components=None, recipe=None):
super().__init__(components, recipe)
def __init__(self, components=None, recipe=None, step=None):
super().__init__(components=components, recipe=recipe, step=step)
self.start_b.clicked.connect(lambda checked, self=weakref.ref(self): self().components["tecna_t3"].start_test())
self.stop_b.clicked.connect(lambda checked, self=weakref.ref(self): self().components["tecna_t3"].stop_test())
def start(self, recipe=None):
super().start(recipe=recipe)
def start(self, recipe=None, step=None):
super().start(recipe=recipe, step=step)
# setup test loop
self.components["tecna_t3"].write_recipe(self.recipe)
self.components["tecna_t3"].write_recipe(self.recipe, self.step)
self.get_connection = self.components["tecna_t3"].out.connect(self.get)
self.components["tecna_t3"].resume()
self.components["tecna_t3"].start_test()
@ -37,7 +37,7 @@ class Test_Leak(Test_Test):
ok = type(result) is str and "passed" in result.lower()
else:
result = None
ok = False
ok = None
super().get([{
"time": data.get("time", None),
"results": {
@ -45,7 +45,7 @@ class Test_Leak(Test_Test):
"result": result,
"data": data["tecna_t3"],
},
}], override=override)
}], override=override, fail=ok is False)
def visualize(self, data=None):
if data is None:

File diff suppressed because it is too large Load Diff

View File

@ -12,10 +12,11 @@ class Test_Test(Widget):
ok = pyqtSignal(dict)
ko = pyqtSignal(dict)
def __init__(self, components=None, recipe=None):
def __init__(self, components=None, recipe=None, step=None):
super().__init__()
self.components = components
self.recipe = recipe
self.step = step
# setup variables
self.ok_counter = 0
self.ok_counter_limit = 1
@ -24,6 +25,10 @@ class Test_Test(Widget):
self.ok_timer.setSingleShot(True)
self.ok_timer.setInterval(2000)
self.ok_timer.timeout.connect(self.emit_ok)
self.ko_timer = QTimer()
self.ko_timer.setSingleShot(True)
self.ko_timer.setInterval(2000)
self.ko_timer.timeout.connect(self.emit_ko)
# setup save frame button
self.last = None
self.save_b.setEnabled(False)
@ -54,8 +59,9 @@ class Test_Test(Widget):
self.status_palettes[""].setColor(QPalette.Base, QColor(255, 255, 0))
self.visualize()
def start(self, recipe=None):
def start(self, recipe=None, step=None):
self.recipe = recipe
self.step = step
self.visualize()
# start test
self.start_time = timing()
@ -68,19 +74,20 @@ class Test_Test(Widget):
def stop(self):
pass
def get(self, data=None, override=False):
if self.ok_timer.isActive():
def get(self, data=None, override=False, fail=False):
if self.ok_timer.isActive() or self.ko_timer.isActive():
# avoid proccessing if test was completed
return
if data is None:
if self.last is not None:
data = self.last
else:
data = {}
data = self.last if self.last is not None else {}
else:
data = data[-1]
data_usable = [d is not None for d in reversed(data)]
if not any(data_usable):
data = self.last if self.last is not None else {}
else:
data = data[-data_usable.index(True) - 1]
if not override:
result_ok = data.get("results", {}).get("ok", False)
result_ok = data.get("results", {}).get("ok", None)
else:
result_ok = True
self.last = {
@ -89,15 +96,20 @@ class Test_Test(Widget):
"duration": timing() - self.start_time,
"ok": result_ok,
}
if not override:
if fail:
self.ok_counter = 0
elif override:
self.ok_counter = self.ok_counter_limit
else:
if result_ok is True:
self.ok_counter += 1
else:
self.ok_counter = 0
else:
self.ok_counter = self.ok_counter_limit
# check if completed
if self.ok_counter >= self.ok_counter_limit:
if fail:
self.stop()
self.ko_timer.start()
elif self.ok_counter >= self.ok_counter_limit:
self.stop()
self.ok_timer.start()
self.visualize(self.last)
@ -118,7 +130,7 @@ class Test_Test(Widget):
self.state_l.setPixmap(self.status_imgs_small["warning"])
elif results is None:
self.state_l.setPixmap(self.status_imgs_small[None])
elif results.get("ok", False) is True:
elif results.get("ok", None) is True:
self.state_l.setPixmap(self.status_imgs_small[True])
else:
self.state_l.setPixmap(self.status_imgs_small[False])
@ -131,6 +143,10 @@ class Test_Test(Widget):
if img is None:
if overridden:
self.img = self.status_imgs_full["warning"]
elif results is None or results.get("ok", None) is None:
self.img = self.status_imgs_full[None]
elif results.get("ok", None) is False:
self.img = self.status_imgs_full[False]
else:
self.img = self.status_imgs_full[None]
else:
@ -165,3 +181,6 @@ class Test_Test(Widget):
def emit_ok(self):
self.ok.emit(self.last)
def emit_ko(self):
self.ko.emit(self.last)

View File

@ -8,20 +8,21 @@ from ui.test_test import Test_Test
class Test_Vision(Test_Test):
request_frame = pyqtSignal()
def __init__(self, components=None, recipe=None):
def __init__(self, components=None, recipe=None, step=None):
if "--sim-camera" not in sys.argv:
self.ok_counter_limit = 2
else:
self.ok_counter_limit = 1
super().__init__(components, recipe)
super().__init__(components=components, recipe=recipe, step=step)
def start(self, recipe=None):
super().start(recipe=recipe)
def start(self, recipe=None, step=None):
super().start(recipe=recipe, step=step)
# setup camera-vision loop
self.components["galaxy_camera"].set_period(period=None) # only get frame on request
self.components["galaxy_camera"].add_sources({"test_vision": self.request_frame})
self.request_frame_connection = self.components["vision"].out.connect(self.request_frame) # request new frame as soon as vision finishes
self.get_connection = self.components["vision"].out.connect(self.get)
self.components["vision"].set_recipe(self.step.spec.get("recipe", "1.ini"))
self.components["vision"].resume()
self.components["galaxy_camera"].resume()
self.request_frame.emit() # request first frame

View File

@ -0,0 +1 @@
from .vision_step_editor import Vision_Step_Editor

View File

@ -0,0 +1,9 @@
from ui.editor import Editor
class Vision_Step_Editor(Editor):
def __init__(self):
super().__init__()
self.spec.update({
"recipe": self.vision_recipe_cb,
})

View File

@ -0,0 +1,37 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>Vision Step Editor</class>
<widget class="QWidget" name="Vision Step Editor">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>174</width>
<height>91</height>
</rect>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="1" column="0" colspan="2">
<widget class="QGroupBox" name="groupBox_6">
<property name="title">
<string>Visione</string>
</property>
<layout class="QFormLayout" name="formLayout">
<item row="0" column="0">
<widget class="QLabel" name="label_29">
<property name="text">
<string>Ricetta</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QComboBox" name="vision_recipe_cb"/>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>