Commit 58123b41 by Lucía Elizo Gómez

Plugin biaprint

parent 022cb767
""" """
Kadi4Mat plugin: Biaprint Kadi4Mat plugin: Biaprint
-------------------------- --------------------------
Endpoint para recibir sesiones de impresion 3D desde la Biaprint, Endpoint para recibir sesiones de impresion 3D desde la Raspberry Pi4,
crear un Record con metricas y archivos, y vincularlo a coleccion crear un Record con metricas y archivos, y vincularlo a coleccion
del año actual. del año actual.
...@@ -12,13 +12,13 @@ Hooks: ...@@ -12,13 +12,13 @@ Hooks:
Endpoint: Endpoint:
POST /api/biaprint/folder POST /api/biaprint/folder
Autenticación: Autenticacion:
Authorization: Bearer <personal_access_token> (scope: record.create + collection.create) Authorization: Bearer <personal_access_token> (scope: record.create + collection.create (full access para evitar fallos))
Form-data: Form-data:
folder_name (str) nombre de la carpeta / sesion folder_name (str) nombre de la carpeta
description (str) descripcion opcional description (str) descripcion
files[] (file, varios) archivos de la sesion files[] (file, varios) archivos de la impresion
""" """
import json import json
...@@ -91,27 +91,27 @@ def _analizar_sesion(samples: list) -> dict: ...@@ -91,27 +91,27 @@ def _analizar_sesion(samples: list) -> dict:
def safe_mean(lst): return round(sum(lst) / len(lst), 3) if lst else None def safe_mean(lst): return round(sum(lst) / len(lst), 3) if lst else None
# ── Tiempo ── # ── Tiempo ──
t_inicio = primero.get("wall_time_utc", "") t_inicio = primero.get("wall_time_utc", "") # Primer valor de tiempo (sample = 0) (Tiempo inicio)
t_fin = ultimo.get("wall_time_utc", "") t_fin = ultimo.get("wall_time_utc", "") # Ultimo valor de tiempo del ultimo sample (Tiempo fin)
duracion_s = None duracion_s = None
try: try:
dt0 = datetime.fromisoformat(t_inicio) dt0 = datetime.fromisoformat(t_inicio)
dt1 = datetime.fromisoformat(t_fin) dt1 = datetime.fromisoformat(t_fin)
duracion_s = round((dt1 - dt0).total_seconds(), 1) duracion_s = round((dt1 - dt0).total_seconds(), 1) # Duracion total en segundos
except Exception: except Exception:
pass pass
# ── Hotend ── # ── Hotend ──
hotend_temps = vals(["hotend", "temp_c"]) hotend_temps = vals(["hotend", "temp_c"]) # Crea un array con todos los valores de la temperatura del hotend
hotend_target = None hotend_target = None
for s in samples: for s in samples:
t = s.get("printer", {}).get("hotend", {}).get("target_c") t = s.get("printer", {}).get("hotend", {}).get("target_c") # Busca la temperatura objetivo
if t and t > 0: if t and t > 0:
hotend_target = t hotend_target = t
break break
# ── Cama ── # ── Cama ──
cama_temps = vals(["cama", "temp_c"]) cama_temps = vals(["cama", "temp_c"]) # Crea un array con todos los valores de la temperatura de la cama
cama_target = None cama_target = None
for s in samples: for s in samples:
t = s.get("printer", {}).get("cama", {}).get("target_c") t = s.get("printer", {}).get("cama", {}).get("target_c")
...@@ -119,6 +119,7 @@ def _analizar_sesion(samples: list) -> dict: ...@@ -119,6 +119,7 @@ def _analizar_sesion(samples: list) -> dict:
cama_target = t cama_target = t
break break
# Diccionario metadatos
return { return {
# Identificacion # Identificacion
"gcode_file": primero.get("printer", {}).get("estado", {}).get("filename", ""), "gcode_file": primero.get("printer", {}).get("estado", {}).get("filename", ""),
...@@ -236,7 +237,7 @@ def _build_extras(metricas: dict) -> list: ...@@ -236,7 +237,7 @@ def _build_extras(metricas: dict) -> list:
def _is_camera_photo(filename: str) -> bool: def _is_camera_photo(filename: str) -> bool:
"""Devuelve True si el archivo viene de una subcarpeta de cámara """Devuelve True si el archivo viene de una subcarpeta
(cam1/ o cam2/) y tiene formato de imagen. """ (cam1/ o cam2/) y tiene formato de imagen. """
parts = Path(filename).parts parts = Path(filename).parts
ext = Path(filename).suffix.lower() ext = Path(filename).suffix.lower()
...@@ -244,8 +245,7 @@ def _is_camera_photo(filename: str) -> bool: ...@@ -244,8 +245,7 @@ def _is_camera_photo(filename: str) -> bool:
def _get_or_create_collection(host: str, pat: str, year_title: str): def _get_or_create_collection(host: str, pat: str, year_title: str):
"""Busca la colección del año por título exacto. """Busca la coleccion (año actual) por titulo. Si no existe, la crea."""
Si no existe, la crea."""
import requests as req import requests as req
session = req.Session() session = req.Session()
...@@ -331,10 +331,7 @@ def _upload_file_to_record(host: str, pat: str, record_id: int, ...@@ -331,10 +331,7 @@ def _upload_file_to_record(host: str, pat: str, record_id: int,
filename: str, data: bytes): filename: str, data: bytes):
"""Sube un archivo al record: """Sube un archivo al record:
1. POST /records/{id}/uploads → registra nombre y tamaño 1. POST /records/{id}/uploads → registra nombre y tamaño
2. PUT /records/{id}/uploads/{upload_id} → envía el binario 2. PUT /records/{id}/uploads/{upload_id} → envia el contenido
Firma corregida: recibe los datos ya leídos (bytes) en lugar de
una Path, porque los ficheros llegan como streams de Flask.
""" """
import mimetypes import mimetypes
import requests as req import requests as req
...@@ -360,7 +357,7 @@ def _upload_file_to_record(host: str, pat: str, record_id: int, ...@@ -360,7 +357,7 @@ def _upload_file_to_record(host: str, pat: str, record_id: int,
upload_id = r.json()["id"] upload_id = r.json()["id"]
# Paso 2: enviar binario # Paso 2: enviar contenido
r2 = session.put( r2 = session.put(
f"{host}/api/v1/records/{record_id}/uploads/{upload_id}", f"{host}/api/v1/records/{record_id}/uploads/{upload_id}",
data=data, data=data,
...@@ -379,7 +376,7 @@ def _upload_file_to_record(host: str, pat: str, record_id: int, ...@@ -379,7 +376,7 @@ def _upload_file_to_record(host: str, pat: str, record_id: int,
@api_bp.post("/biaprint/folder") @api_bp.post("/biaprint/folder")
@scopes_required("record.create") @scopes_required("record.create")
def upload_folder(): def upload_folder():
"""Recibe una sesion de impresion 3D de la Biaprint y registra en Kadi4Mat: """Recibe una sesion de impresion 3D de la Raspberry Pi y registra en Kadi4Mat:
1. Analiza el .json si viene incluido 1. Analiza el .json si viene incluido
2. Calcula metricas, tags y extras 2. Calcula metricas, tags y extras
3. Busca o crea la coleccion del año actual 3. Busca o crea la coleccion del año actual
...@@ -403,7 +400,7 @@ def upload_folder(): ...@@ -403,7 +400,7 @@ def upload_folder():
cfg = get_plugin_config(PLUGIN_NAME) cfg = get_plugin_config(PLUGIN_NAME)
host = (cfg.get("host", "https://localhost") if cfg else "https://localhost").rstrip("/") host = (cfg.get("host", "https://localhost") if cfg else "https://localhost").rstrip("/")
# Extraer el PAT directamente del header Authorization. # Extraer el PAT del header Authorization.
_auth_header = request.headers.get("Authorization", "") _auth_header = request.headers.get("Authorization", "")
if _auth_header.lower().startswith("bearer "): if _auth_header.lower().startswith("bearer "):
pat = _auth_header[7:].strip() pat = _auth_header[7:].strip()
...@@ -411,7 +408,7 @@ def upload_folder(): ...@@ -411,7 +408,7 @@ def upload_folder():
return json_error_response(401, description="Header Authorization Bearer requerido.") return json_error_response(401, description="Header Authorization Bearer requerido.")
# ── Leer todos los archivos en memoria ───────────────── # ── Leer todos los archivos en memoria ─────────────────
file_buffers = {} # filename → bytes file_buffers = {}
for f in uploaded_files: for f in uploaded_files:
if f.filename: if f.filename:
file_buffers[f.filename] = f.read() file_buffers[f.filename] = f.read()
...@@ -422,7 +419,7 @@ def upload_folder(): ...@@ -422,7 +419,7 @@ def upload_folder():
extras = [] extras = []
for filename, data in file_buffers.items(): for filename, data in file_buffers.items():
if Path(filename).name == "session_data.json": if Path(filename).name == "session_data.json": # El nombre del archivo JSON debe ser este
try: try:
samples = json.loads(data.decode("utf-8")) samples = json.loads(data.decode("utf-8"))
if isinstance(samples, list): if isinstance(samples, list):
...@@ -443,7 +440,7 @@ def upload_folder(): ...@@ -443,7 +440,7 @@ def upload_folder():
description = ( description = (
f"Sesion de impresion 3D registrada el {fecha}. " f"Sesion de impresion 3D registrada el {fecha}. "
f"Archivo: {gcode}. " f"Archivo: {gcode}. "
f"Duración de impresion: {dur} s. " f"Duracion de impresion: {dur} s. "
f"Filamento usado: {fil} mm." f"Filamento usado: {fil} mm."
) )
...@@ -454,7 +451,7 @@ def upload_folder(): ...@@ -454,7 +451,7 @@ def upload_folder():
collection_data, col_created = _get_or_create_collection(host, pat, year_title) collection_data, col_created = _get_or_create_collection(host, pat, year_title)
collection_id = collection_data["id"] collection_id = collection_data["id"]
except Exception as exc: except Exception as exc:
return json_error_response(500, description=f"Error con la colección: {exc}") return json_error_response(500, description=f"Error con la coleccion: {exc}")
# ── Crear Record por API REST ────────────────────────────────────────── # ── Crear Record por API REST ──────────────────────────────────────────
identifier = _make_identifier(folder_name) identifier = _make_identifier(folder_name)
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment