diff --git a/config/machine_settings/defaults.ini b/config/machine_settings/defaults.ini index 83b4057..24e5382 100644 --- a/config/machine_settings/defaults.ini +++ b/config/machine_settings/defaults.ini @@ -32,12 +32,12 @@ minimum_disk_free_space_gb: 20 resize_resolution: 612x512 [archive_synchronizer] -archive_endpoint: https://r5portal.it/api/echo/ +archive_endpoint: https://r5portal.it/api/st-ten-save/ images_path: data/images poll_time: 60 hold_time: 1 -service_account_json: data/secrets/errecinque-prodserver-9d4024fe9eed.json -bucket_id: MACHINE +service_account_json: data/secrets/errecinque-prodserver-d132c3d469cc.json +bucket_id: st_ten_img [label_printer] platform: windows diff --git a/config/machine_settings/this.ini b/config/machine_settings/this.ini index 18579c6..9249792 100644 --- a/config/machine_settings/this.ini +++ b/config/machine_settings/this.ini @@ -1,5 +1,5 @@ [hardware_config] -archive_synchronizer: absent +archive_synchronizer: present galaxy_camera: present label_printer: present multicomp: present @@ -18,3 +18,7 @@ port: /dev/ttyACM0 [neo_pixels] port: /dev/ttyACM1 + +[archive_synchronizer] +poll_time: 6 +hold_time: 1 diff --git a/src/components/archive_synchronizer.py b/src/components/archive_synchronizer.py index 436d02f..bb9bb2a 100644 --- a/src/components/archive_synchronizer.py +++ b/src/components/archive_synchronizer.py @@ -1,4 +1,5 @@ import json +import os import re import sys import traceback @@ -25,7 +26,7 @@ class ArchiveSynchronizer(Component): def config_changed(self): self.machine_id = self.config.machine_id self.archive_endpoint = self.config[self.name]["archive_endpoint"] - self.images_path = self.config[self.name]["images_path"] + # self.images_path = self.config[self.name]["images_path"] self._do_set_period({"period": float(self.config[self.name]["poll_time"])}) self.hold_time = round(float(self.config[self.name]["hold_time"]) * 1000) self.gcs_client = storage.Client.from_service_account_json(self.config[self.name]["service_account_json"]) @@ -55,13 +56,14 @@ class ArchiveSynchronizer(Component): with requests.Session() as s: s.mount("", HTTPAdapter(max_retries=Retry(total=0))) # this disables retries r = requests.post(self.archive_endpoint, params={ + "data": json.dumps(record.test_data), "machine_id": self.machine_id, + "overridden": record.overridden, + "recipe": record.test_data.get("recipe", {}).get("name", None), + "result": "OK" if record.result else "KO", + "serial": record.id, "time": record.time.isoformat(), "user": record.user.username, - "recipe": record.recipe.name, - "test_data": json.dumps(record.test_data), - "result": record.result, - "overridden": record.overridden, }, timeout=5, verify=False) if r.status_code != 200: raise AssertionError("bad status response") @@ -78,6 +80,8 @@ class ArchiveSynchronizer(Component): return True def remote_store(self, record): + if "vision" not in record.test_data: + return True try: self.gcs_bucket = self.gcs_client.get_bucket(self.bucket_id, timeout=(5, 30), retry=None) # retry=None seems to be ignored except Exception: @@ -85,24 +89,25 @@ class ArchiveSynchronizer(Component): self.gcs_bucket = None if self.gcs_bucket is None: return False - dt = record.vision_time - timestamp = dt.strftime(self.time_format) - img_in = f"{self.images_path}/frames/{dt.strftime('%Y')}/{dt.strftime('%m')}/{timestamp}.png" - img_out = f"{self.bench.type}/{dt.strftime('%Y')}/{dt.strftime('%m')}/{timestamp}.png" - try: - blob = self.gcs_bucket.blob(img_out) - if not self.simulate: - blob.upload_from_filename(filename=img_in) - except FileNotFoundError: - self.log.error(f"id {record.id}: {img_in}: file not found.") - return False - except Forbidden as e: - if re.match("^Object '.*?' is subject to bucket's retention policy and cannot be deleted, overwritten or archived until .*?$", e._response.json()["error"]["message"]) is not None: - self.log.info(f"id {record.id}: {img_in}: already stored.") - else: - self.log.warning(f"id: {record.id}: failed to store remotely: {str(e)}: {e._response.json()}") + for path in record.test_data["vision"]["0"]["files"]: + dt = record.time + # path_in = f"{self.images_path}/{dt.strftime('%Y')}/{dt.strftime('%m')}/{os.path.basename(path)}" + path_in = path + path_out = f"{self.machine_id}/{dt.strftime('%Y')}/{dt.strftime('%m')}/{os.path.basename(path)}" + try: + blob = self.gcs_bucket.blob(path_out) + if not self.simulate: + blob.upload_from_filename(filename=path_in) + except FileNotFoundError: + self.log.error(f"id {record.id}: {path_in}: file not found.") return False - except Exception: - self.log.error(f"id: {record.id}: failed to store remotely:\n{traceback.format_exc()}") - self.log.info(f"id: {record.id}: stored remotely") + except Forbidden as e: + if re.match("^Object '.*?' is subject to bucket's retention policy and cannot be deleted, overwritten or archived until .*?$", e._response.json()["error"]["message"]) is not None: + self.log.info(f"id {record.id}: {path_in}: already stored.") + else: + self.log.warning(f"id: {record.id}: failed to store remotely: {str(e)}: {e._response.json()}") + return False + except Exception: + self.log.error(f"id: {record.id}: failed to store remotely:\n{traceback.format_exc()}") + self.log.info(f"id: {record.id}: stored remotely") return True diff --git a/src/components/vision_saver.py b/src/components/vision_saver.py index 5dcce49..94b942f 100755 --- a/src/components/vision_saver.py +++ b/src/components/vision_saver.py @@ -81,7 +81,7 @@ class VisionSaver(Component): # save vision with open(out_paths[-1], "w") as f: json.dump(vision, f) - return out_paths + return list(map(str, out_paths)) def remove_older_images_if_needed(self): if self.minimum_disk_free_space_gb is None: diff --git a/src/ui/archive/archive.py b/src/ui/archive/archive.py index 99c27de..ace88b2 100755 --- a/src/ui/archive/archive.py +++ b/src/ui/archive/archive.py @@ -15,7 +15,7 @@ class Archive(Widget): # "id": "Id", # "time": "Data e ora", # "user": "Operatore", - # "recipe": "Ricetta", + # "test_data.recipe.name": "Ricetta", # "result": "Esito", # "overridden": "Esito forzato", # "test_data": "Dati del test", @@ -27,7 +27,7 @@ class Archive(Widget): crud_aliases = { "time": "Data e ora", "user": "Operatore", - "recipe": "Ricetta", + "test_data.recipe.name": "Ricetta", "result": "Esito", "overridden": "Esito forzato", "test_data": "Dati del test", diff --git a/src/ui/crud/crud.py b/src/ui/crud/crud.py index 83ee0bc..0d55681 100755 --- a/src/ui/crud/crud.py +++ b/src/ui/crud/crud.py @@ -363,7 +363,7 @@ class Crud(Widget): if text != self.filters.get(fn, None): self.filters[fn] = text self.db.filter(self.select[cn], text) - self.refresh() + self.refresh(filter_cn=cn) @staticmethod def default_row_upgrader(row, row_number, crud): @@ -393,7 +393,7 @@ class Crud(Widget): fails.append(str(e)) return r, fail, fails - def refresh(self, action=None, page=0): + def refresh(self, action=None, page=0, filter_cn=None): # IGNORE SIGNALS WHILE UPDATING self.db_tw.blockSignals(True) self._modified = False # force reset modified status @@ -472,6 +472,10 @@ class Crud(Widget): w.setPlaceholderText("Non filtrabile") w.setEnabled(False) self.db_tw.setCellWidget(0, cn, w) + if filter_cn is not None: + w = self.db_tw.cellWidget(0, filter_cn) + if w is not None: + w.setFocus() # INSERT UPDATED DATA for rn, r in enumerate(data, start=1): # start=1 because rn starts from 1 (filters line) r = self.row_upgrader(r, rn, self) diff --git a/src/ui/test/test.py b/src/ui/test/test.py index ac81039..f38e547 100755 --- a/src/ui/test/test.py +++ b/src/ui/test/test.py @@ -309,7 +309,7 @@ class Test(Widget): self.data[step_name] = {} if data is not None: data["step"] = model_to_dict(self.step) - self.data[step_name][len(self.data[step_name])] = data + self.data[step_name][str(len(self.data[step_name]))] = data self.data["overridden"] = self.data["overridden"] or data.get("overridden", False) self.data["ok"] = self.data["ok"] and data.get("ok", False) self.next() @@ -317,12 +317,13 @@ class Test(Widget): def done(self, ok=True): self.log.info("cycle done") if "vision" in self.data: - vision_test_1 = self.data.get("vision", {}).get(0, {}) + vision_test_1 = self.data.get("vision", {}).get("0", {}) out_paths = self.components["vision_saver"].save( save_time=vision_test_1.get("time", None), frame=vision_test_1.get("frame", None), # vision=vision_test_1.get("detections", None), ) + self.data.get("vision", {}).get("0", {})["files"] = out_paths self.log.info(f"cycle vision saved locally: {out_paths!r}") for vision in self.data.get("vision", {}).values(): vision.pop("frame", None) @@ -347,7 +348,7 @@ class Test(Widget): self.log.info("cycle printed already compiled label") # LABEL PRINT recipe = archived.test_data.get("recipe", {}) - leak_test_1 = archived.test_data.get("leak", {}).get(0, {}) + leak_test_1 = archived.test_data.get("leak", {}).get("0", {}) leak_test_1_step = leak_test_1.get("step", {}) leak_test_1_step_spec = leak_test_1_step.get("spec", {}) leak_test_1_results = leak_test_1.get("results", {})