← Blog

Encolar y planificar impresiones en una granja

Qué hace una cola de flota: las tres estrategias, qué hace elegible a una impresora, el drenaje, el mantenimiento y los endpoints /api/v1/fleet.

granja de impresiónmulti-impresoraapiklipper

Encola una revisión de G-code una vez y deja que el planificador elija la máquina. PrintStash guarda cada laminado como una revisión de su modelo de origen y luego ejecuta una sola cola sobre todas las impresoras que hayas añadido, con reparto manual, a la impresora por defecto o a la menos ocupada, y con reordenar, redirigir, cancelar y reintentar sobre cualquier trabajo que siga esperando. Es un único servicio de Docker Compose, sin Redis y sin un contenedor de worker aparte. La base de datos guarda la cola, y una cola de tareas dentro del proceso existe solo para despertar al despachador antes de lo que lo haría su sondeo de dos segundos.

Es la herramienta adecuada cuando el cuello de botella de la granja es conseguir que un archivo de confianza llegue a una máquina libre. Es la herramienta equivocada si lo que quieres en realidad es un mural de cámaras, detección de espagueti o seguimiento de pedidos, que corresponden a un panel de granja y se cubren en la comparativa de software para granjas con Klipper. Todo lo que sigue describe la v0.11.4. La cola llegó en la v0.11.0 y el control de acceso por impresora que aplica llegó en la v0.11.1.

Los endpoints de /api/v1/fleet que aparecen más abajo están escritos aquí porque la referencia de la API todavía no los lista. Son las mismas rutas que llama la interfaz web.

De la biblioteca a la máquina

El bucle diario son tres pasos en la interfaz web, y ninguno necesita la API.

  1. Añade cada impresora en Printers -> Add printer con su dirección y su credencial. Conectar PrintStash a varias impresoras Klipper cubre la parte de Moonraker, normalmente el puerto 7125, una máquina a la vez.
  2. Abre el modelo, elige la revisión de G-code en la que confías y encólala en lugar de enviarla directamente a una máquina. Elige manual, por defecto o menos ocupada al hacerlo.
  3. Vigila la cola. El planificador reclama el primer trabajo no bloqueado y lo despacha. Todo lo que siga en cola se puede reordenar, redirigir o cancelar.

Lo que encolas es una revisión con los metadatos del slicer ya interpretados y un resultado asociado, no un nombre de archivo que esperas que esté al día. Seguir las revisiones de G-code verificadas es la otra mitad de eso.

Qué hacen las tres estrategias

manual apunta a una impresora por id y falla la petición si no se la das. default envía a la impresora marcada como predeterminada de la flota, y la base de datos permite exactamente una predeterminada viva a la vez, así que marcar una nueva borra la marca anterior. least_busy ordena las impresoras elegibles por el número de trabajos activos en cada una, luego por el last_seen_at más antiguo y luego por el id más bajo. Merece la pena conocer el desempate intermedio: cuando dos máquinas están igual de libres, gana aquella de la que hace más tiempo que no sabes nada, así que el trabajo se reparte en lugar de acumularse en la impresora que sondea más rápido.

Solo un superusuario puede encolar con default o least_busy. Una cuenta normal con permiso de impresión en una máquina puede encolar a esa máquina y a ninguna otra, así que en la práctica las estrategias de reparto son una herramienta de administrador. Eso coincide con lo que describen las notas de la v0.11.0.

“Activo” aquí significa un trabajo en queued, uploading, started, printing o paused. Los estados terminales son completed, cancelled y failed. Una lectura de la cola devuelve todos los trabajos activos en orden de posición más una página de historial terminal, veinte filas por defecto y cien como máximo.

Sea cual sea la estrategia, un trabajo encolado aterriza en exactamente una impresora. Imprimir la misma pieza en seis máquinas son seis trabajos encolados, y eso lo desarrolla enviar G-code a varias impresoras desde una sola app.

Qué hace elegible a una impresora

Una impresora es candidata al reparto solo cuando no está borrada, no está en modo drenaje, informa del estado ready, queda fuera de cualquier ventana de mantenimiento activa y usa un proveedor que declara tanto subida como arranque. Los cinco proveedores actuales declaran esas dos, así que hoy la condición de capacidad no excluye a nadie. Lo que saca una máquina del grupo en la práctica es el estado que informa, el modo drenaje o una ventana que hayas puesto tú.

Encolar cuando no hay nada elegible no falla. El trabajo se crea en queued con un blocked_reason anotado, como no_eligible_printer o printer_unavailable, y el resumen de flota lo cuenta en attention_jobs. El planificador vuelve a resolver el reparto en cada pasada, con una foto fresca del estado de las impresoras, las marcas de drenaje, las ventanas de mantenimiento y el número de trabajos activos, y ordena los trabajos no bloqueados delante de los bloqueados. Un trabajo con reparto a la menos ocupada encolado mientras toda la granja imprime no se queda, por tanto, con la asignación que recibió al encolarse. La siguiente pasada lo manda a un sitio mejor en cuanto se libera una máquina.

La misma pasada revisa también los permisos de quien encoló el trabajo, algo que es fácil pasar por alto. Si esa cuenta pierde el acceso de impresión a la impresora elegida, o el de edición a la colección del modelo, o se desactiva del todo, el trabajo se marca como bloqueado con printer_access_revoked, collection_access_revoked o requester_access_revoked en lugar de despacharse con una autoridad caducada.

Lo que el reparto nunca mira es si el hardware encaja: filamento cargado, diámetro de boquilla, tipo de bandeja. La estrategia de menos ocupada pondrá un laminado en ABS en una máquina con PLA cargado sin dudarlo. Puntuar los trabajos así no es un objetivo del proyecto, así que elegir la revisión adecuada para cada máquina sigue siendo tu trabajo, y mantener reproducibles las impresiones de la flota cubre las etiquetas de revisión que hacen eso llevadero en una granja heterogénea.

Drenaje y ventanas de mantenimiento

Una máquina que necesita un cambio de boquilla sale de la rotación sin desenchufarla. El modo drenaje evita que le llegue trabajo nuevo y deja en paz lo que esté imprimiendo. Una ventana de mantenimiento hace lo mismo entre un inicio y un fin fijos, y el planificador la esquiva hasta que la ventana se cierra. En ambos casos el trabajo en curso termina.

Hay además un registro de mantenimiento por impresora, distinto de las ventanas, donde cada entrada lleva una categoría, una nota y opcionalmente el valor de un contador con su unidad, así que “correa tensada a las 412 horas” es una fila y no un pósit. El resumen de flota informa de cuántas impresoras están en drenaje y cuántas están dentro de una ventana.

No hace falta estar sentado ante un escritorio para llevar nada de esto: gestionar la granja desde el móvil cubre la PWA instalada y el túnel que mantiene funcionando las mismas pantallas fuera de la red.

La API de la cola de flota

Todo está expuesto bajo /api/v1/fleet. Autentícate primero: una clave de API es una credencial de acceso, no un token Bearer, así que intercambia el usuario y la clave por un token de acceso de vida corta y envía ese. En una instalación Compose por defecto se llega a la API por el origen del frontend en el puerto 3000, porque el servicio api solo publica el 8000 en la red interna.

Terminal window
# 1. Exchange an API key (created under Settings -> Access) for an access token
curl -s -X POST http://localhost:3000/api/v1/auth/login \
-H "Content-Type: application/json" \
-d '{"username": "automation", "api_key": "<api-key>"}'
# -> {"access_token": "..."}
Terminal window
# 2. Enqueue a job and let PrintStash pick the least-busy eligible printer
TOKEN="<access-token>"
curl -X POST http://localhost:3000/api/v1/fleet/queue \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"file_id": 123, "strategy": "least_busy"}'

El reparto manual necesita el id de destino: {"file_id": 123, "strategy": "manual", "printer_id": 5}. El file_id tiene que ser un artefacto de G-code; un archivo .bgcode se rechaza con binary_gcode_not_printable, porque el cuerpo comprimido no se descomprime para enviarlo. Todos los payloads de flota prohíben los campos desconocidos, así que una errata en una clave es un 422 y no un ajuste ignorado en silencio.

El resto de la superficie:

  • GET /api/v1/fleet/queue?history_limit=20&history_offset=0 lista los trabajos activos más una página de historial terminal.
  • PATCH /api/v1/fleet/queue/{job_id} reordena (queue_position, empezando en 1) o redirige (strategy, printer_id). Si pasas expected_updated_at obtienes un 409 queue_job_changed en vez de sobrescribir la edición de otra persona.
  • DELETE /api/v1/fleet/queue/{job_id} cancela un trabajo. Tanto esta llamada como el PATCH solo aceptan un trabajo que siga en queued; cualquier cosa que ya esté subiendo o imprimiendo devuelve 409 queue_job_not_editable, y esa impresión se detiene desde los controles de la propia impresora.
  • POST /api/v1/fleet/queue/{job_id}/retry vuelve a encolar un trabajo fallido, pero solo uno marcado como reintentable.
  • PATCH /api/v1/fleet/printers/{printer_id}/routing acepta {"drain_mode": true, "drain_reason": "..."} o {"is_default": true}.
  • GET, POST, PATCH y DELETE sobre /api/v1/fleet/printers/{printer_id}/maintenance-windows gestionan las ventanas, con starts_at, ends_at y un reason opcional. Los mismos cuatro verbos sobre .../maintenance-log gestionan las entradas del registro.
  • GET /api/v1/fleet/summary devuelve total_printers, queued_jobs, active_jobs, draining_printers, maintenance_printers y attention_jobs, limitado a las impresoras que quien llama puede ver.

Los roles se aplican en los manejadores, no ocultando botones. Escribir en la cola requiere print en la impresora de destino más edit en la colección del modelo. Los cambios de reparto y las escrituras de mantenimiento requieren admin en la impresora. Leer las ventanas y el registro requiere view.

Qué le hace un reinicio al trabajo en vuelo

Subir y arrancar son dos llamadas remotas que no son transaccionales con la base de datos local, así que un error de transporte puede llegar después de que la impresora ya haya aceptado el archivo. Un despacho interrumpido entre la llamada al proveedor y la escritura final en la base de datos se reconcilia al arrancar como failed, con el error dispatch_outcome_unknown y la marca de reintentable desactivada, lo que significa que el endpoint de reintento lo rechaza. La fila sigue visible para que un operador vaya a mirar la máquina y decida. Los fallos ordinarios del proveedor, una impresora que no estaba libre o una lectura de almacenamiento que falló, se marcan como reintentables y se vuelven a encolar con una sola llamada.

Eso te cuesta algo de reconciliación manual después de un reinicio malo, y te compra la garantía de que ningún proceso automático arranca dos veces la misma impresión física. En una granja funcionando sola de noche, yo aceptaría ese intercambio siempre.

En una flota mixta

A la cola le da igual qué protocolo habla una impresora. Moonraker, OctoPrint, PrusaLink, Bambu LAN y Elegoo Centauri entran en la misma biblioteca y en la misma cola, y los cinco pueden recibir un trabajo encolado. Lo que cambia es lo que vuelve después, y qué más te deja hacer la máquina. Moonraker es el proveedor estable con el conjunto completo de capacidades; los otros cuatro están en beta y con conjuntos más estrechos. Gestionar archivos de impresión en una flota mixta de Klipper y OctoPrint tiene el flujo por proveedor, y la matriz de compatibilidad tiene la tabla que se mueve con cada versión.

Hay un número con el que conviene tener cuidado en una granja mixta. El consumo de filamento medido vuelve solo de Moonraker y todo lo demás recurre a la estimación del slicer, así que el coste por impresión no es comparable entre proveedores. La duración transcurrida medida es un campo aparte y esa sí vuelve también de PrusaLink, OctoPrint y Elegoo Centauri, aunque solo la de Moonraker se ha comprobado contra impresiones reales. Compara dentro de un mismo proveedor y trata cualquier cosa entre proveedores como indicativa.

Preguntas que surgen

¿Puedo cancelar un trabajo que ya ha empezado a imprimir?

Desde la cola de flota no. Tanto el endpoint de cancelación como el de edición aceptan un trabajo solo mientras sigue en queued; una vez que el planificador lo ha reclamado la llamada devuelve 409 queue_job_not_editable, y lo mismo vale para reordenar y redirigir. Detener una impresión en marcha es tarea de la propia impresora, o sea de Mainsail, Fluidd u OctoPrint, y PrintStash registra el resultado que vuelve. Usar PrintStash junto a OctoPrint, Fluidd y Mainsail cubre dónde está esa frontera.

¿Puedo sacar una impresora de la rotación sin detener lo que está imprimiendo?

Sí, y ahí está la diferencia entre rechazar trabajo nuevo y tirar del cable. El modo drenaje marca la impresora como no elegible para que el planificador deje de asignarle nada, y una ventana de mantenimiento puntual hace lo mismo entre una hora de inicio y una de fin que fijas tú. En ambos casos el trabajo que está en la cama llega hasta el final. El modo drenaje lleva una cadena de motivo opcional, que vale la pena rellenar cuando más de una persona opera la granja.

¿Por qué hay un trabajo parado en la cola si las impresoras parecen libres?

Mira primero su blocked_reason, porque un trabajo que no se pudo colocar se crea igualmente en lugar de rechazarse. Una máquina tiene que estar informando del estado ready, fuera del modo drenaje y fuera de cualquier ventana de mantenimiento para ser candidata, así que una impresora que solo es alcanzable no es necesariamente elegible. La otra causa es de autoridad y no de hardware: el planificador vuelve a comprobar en cada pasada los permisos de quien encoló el trabajo, y perder el acceso de impresión a la impresora o el de edición a la colección del modelo lo bloquea con printer_access_revoked o collection_access_revoked. Los trabajos bloqueados se ordenan detrás de los no bloqueados y el resumen de flota los cuenta en attention_jobs.

¿Quién puede encolar con reparto a la impresora por defecto o a la menos ocupada?

Solo un superusuario. Una cuenta normal puede encolar con manual y un printer_id explícito, en una impresora donde tenga el rol de impresión, y la API devuelve 403 para cualquier otra cosa, así que en la práctica las estrategias de reparto son una herramienta de administrador. El mismo límite se aplica al redirigir un trabajo que ya existe. Vale la pena tenerlo en cuenta si pensabas dar a los operadores una cola de autoservicio: pueden encolar a sus propias máquinas, pero repartir trabajo por la granja se queda en una cuenta de administrador o en una clave de API que pertenezca a una.

¿Podría un despacho fallido arrancar dos veces la misma impresión?

Automáticamente no. Subir y arrancar son llamadas remotas no transaccionales, así que un despacho interrumpido entre que el proveedor acepta la petición y la escritura final en la base de datos se registra como dispatch_outcome_unknown y se marca como no reintentable. El endpoint de reintento no lo toca y ningún proceso en segundo plano lo repite, lo que te deja a ti comprobar la máquina y reconciliar a mano. Los fallos en los que el resultado se conoce, como una impresora que no estaba libre cuando llegó el trabajo, se marcan como reintentables y se vuelven a encolar con una llamada.

¿Necesito Redis o un worker aparte para la cola?

Ninguno de los dos. La base de datos es la fuente de verdad del estado de la cola, y el planificador corre dentro del proceso de la API, reclamando un trabajo por pasada. Una cola de tareas en memoria lo despierta en cuanto se encola algo, y un sondeo con tiempo de espera de dos segundos recoge el trabajo de todos modos si esa notificación en memoria se pierde alguna vez, que es también cómo los trabajos encolados sobreviven a un reinicio. Hay una advertencia en la otra dirección: la topología soportada es un proceso de API por bóveda, y el arranque reclama un cerrojo y falla rápido si ya hay otro corriendo, así que no escales el contenedor de la API a dos réplicas esperando dos despachadores.

Fuentes