feat: détection erreurs imprimante via IPP direct (Selphy CP1300)
- script_print.sh : après lp, polling CUPS + sondage IPP direct ipp://IP:631/ipp/print Détecte input-tray-missing / media-empty en ~3-6s sans attendre le timeout CUPS - printer_service.py : ajout _get_printer_ip(), _query_ipp_direct() _get_printer_status() utilise IPP pour état/raisons/encre (source de vérité) - Timeout porté à 120s côté Python, fallback lpstat conservé
This commit is contained in:
@@ -136,8 +136,26 @@ if [ -z "$JOB_ID" ]; then
|
|||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# --- Preparation du sondage IPP direct sur l'imprimante ---
|
||||||
|
# La Selphy CP1300 expose un serveur IPP natif (port 631, sans auth).
|
||||||
|
# Cela permet de detecter les erreurs physiques en quelques secondes
|
||||||
|
# sans attendre un timeout CUPS (qui ne rapporte pas les erreurs papier).
|
||||||
|
|
||||||
|
PRINTER_IP=$(lpstat -v "$SELECTED_PRINTER" 2>/dev/null | grep -oE '[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+' | head -1)
|
||||||
|
IPP_URI=""
|
||||||
|
IPP_TEST="/usr/share/cups/ipptool/get-printer-attributes.test"
|
||||||
|
if [ -n "$PRINTER_IP" ] && command -v ipptool &>/dev/null && [ -f "$IPP_TEST" ]; then
|
||||||
|
IPP_URI="ipp://${PRINTER_IP}:631/ipp/print"
|
||||||
|
log_info "Sondage IPP direct actif: $IPP_URI"
|
||||||
|
else
|
||||||
|
log_info "Sondage IPP desactive (IP=${PRINTER_IP:-inconnu}, ipptool=$(command -v ipptool || echo absent))"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Mots-cles d'erreur dans printer-state-reasons
|
||||||
|
IPP_ERROR_REASONS="input-tray-missing|media-empty|media-needed|marker-supply-empty|cover-open|door-open|offline-report"
|
||||||
|
|
||||||
# --- Suivi du job CUPS (attend la completion physique) ---
|
# --- Suivi du job CUPS (attend la completion physique) ---
|
||||||
MAX_WAIT=90 # secondes max (Selphy CP1300 ~ 60s)
|
MAX_WAIT=90 # secondes max (Selphy CP1300 imprime en ~47s)
|
||||||
POLL_INTERVAL=3
|
POLL_INTERVAL=3
|
||||||
elapsed=0
|
elapsed=0
|
||||||
|
|
||||||
@@ -147,7 +165,7 @@ while [ $elapsed -lt $MAX_WAIT ]; do
|
|||||||
sleep $POLL_INTERVAL
|
sleep $POLL_INTERVAL
|
||||||
elapsed=$((elapsed + POLL_INTERVAL))
|
elapsed=$((elapsed + POLL_INTERVAL))
|
||||||
|
|
||||||
# Succes : job dans la liste des jobs termines
|
# 1. Succes : job dans la liste des jobs termines par CUPS
|
||||||
if lpstat -W completed 2>/dev/null | grep -q "^${JOB_ID} "; then
|
if lpstat -W completed 2>/dev/null | grep -q "^${JOB_ID} "; then
|
||||||
log_info "Job $JOB_ID termine avec succes (${elapsed}s)"
|
log_info "Job $JOB_ID termine avec succes (${elapsed}s)"
|
||||||
echo "PRINTED:${SELECTED_PRINTER}:${copies}"
|
echo "PRINTED:${SELECTED_PRINTER}:${copies}"
|
||||||
@@ -155,13 +173,27 @@ while [ $elapsed -lt $MAX_WAIT ]; do
|
|||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Erreur : imprimante stoppee (papier absent, capot ouvert, etc.)
|
# 2. Sondage IPP direct (detecte papier absent, capot ouvert, etc. en ~3s)
|
||||||
|
if [ -n "$IPP_URI" ]; then
|
||||||
|
ipp_reasons=$(ipptool -tv "$IPP_URI" "$IPP_TEST" 2>/dev/null \
|
||||||
|
| grep "printer-state-reasons" | head -1)
|
||||||
|
if echo "$ipp_reasons" | grep -qiE "$IPP_ERROR_REASONS"; then
|
||||||
|
reason=$(echo "$ipp_reasons" | grep -oiE "$IPP_ERROR_REASONS" | head -1)
|
||||||
|
log_error "Erreur imprimante detectee via IPP: $reason (${elapsed}s)"
|
||||||
|
cancel "$JOB_ID" 2>/dev/null || true
|
||||||
|
echo "PRINT_ERROR:${SELECTED_PRINTER}:${reason}"
|
||||||
|
[ -f "$filename_print" ] && rm -f "$filename_print" 2>/dev/null
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 3. Fallback CUPS : imprimante stoppee cote CUPS
|
||||||
printer_state=$(lpstat -p "$SELECTED_PRINTER" 2>/dev/null || true)
|
printer_state=$(lpstat -p "$SELECTED_PRINTER" 2>/dev/null || true)
|
||||||
if echo "$printer_state" | grep -qiE "stopped|disabled"; then
|
if echo "$printer_state" | grep -qiE "stopped|disabled"; then
|
||||||
reason=$(lpstat -l -p "$SELECTED_PRINTER" 2>/dev/null \
|
reason=$(lpstat -l -p "$SELECTED_PRINTER" 2>/dev/null \
|
||||||
| grep -i "Reason:" | head -1 \
|
| grep -i "Reason:" | head -1 \
|
||||||
| sed 's/.*Reason:[[:space:]]*//' | xargs)
|
| sed 's/.*Reason:[[:space:]]*//' | xargs)
|
||||||
log_error "Imprimante $SELECTED_PRINTER en erreur: ${reason:-stopped} (apres ${elapsed}s)"
|
log_error "Imprimante $SELECTED_PRINTER stoppee (CUPS): ${reason:-stopped} (${elapsed}s)"
|
||||||
cancel "$JOB_ID" 2>/dev/null || true
|
cancel "$JOB_ID" 2>/dev/null || true
|
||||||
echo "PRINT_ERROR:${SELECTED_PRINTER}:${reason:-stopped}"
|
echo "PRINT_ERROR:${SELECTED_PRINTER}:${reason:-stopped}"
|
||||||
[ -f "$filename_print" ] && rm -f "$filename_print" 2>/dev/null
|
[ -f "$filename_print" ] && rm -f "$filename_print" 2>/dev/null
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ from __future__ import annotations
|
|||||||
|
|
||||||
import asyncio
|
import asyncio
|
||||||
import logging
|
import logging
|
||||||
|
import re
|
||||||
import subprocess
|
import subprocess
|
||||||
import time
|
import time
|
||||||
import uuid
|
import uuid
|
||||||
@@ -258,6 +259,81 @@ class PrinterService:
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
return {"success": False, "error": str(e)}
|
return {"success": False, "error": str(e)}
|
||||||
|
|
||||||
|
# ── IPP direct ───────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
def _get_printer_ip(self, printer_name: str) -> str:
|
||||||
|
"""Extrait l'IP de l'imprimante depuis l'URI CUPS (lpstat -v)."""
|
||||||
|
try:
|
||||||
|
r = subprocess.run(
|
||||||
|
["lpstat", "-v", printer_name],
|
||||||
|
capture_output=True, text=True, timeout=5
|
||||||
|
)
|
||||||
|
m = re.search(r"(\d+\.\d+\.\d+\.\d+)", r.stdout)
|
||||||
|
return m.group(1) if m else ""
|
||||||
|
except Exception:
|
||||||
|
return ""
|
||||||
|
|
||||||
|
def _query_ipp_direct(self, printer_ip: str) -> dict:
|
||||||
|
"""Interroge directement le serveur IPP natif de l'imprimante (sans auth).
|
||||||
|
|
||||||
|
Retourne un dict avec keys: state, state_reasons, accepting, markers.
|
||||||
|
"""
|
||||||
|
ipp_test = "/usr/share/cups/ipptool/get-printer-attributes.test"
|
||||||
|
empty: dict = {"state": "", "state_reasons": [], "accepting": None, "markers": []}
|
||||||
|
if not printer_ip:
|
||||||
|
return empty
|
||||||
|
try:
|
||||||
|
r = subprocess.run(
|
||||||
|
["ipptool", "-tv", f"ipp://{printer_ip}:631/ipp/print", ipp_test],
|
||||||
|
capture_output=True, text=True, timeout=8
|
||||||
|
)
|
||||||
|
out = r.stdout
|
||||||
|
except Exception as e:
|
||||||
|
logger.debug("ipptool direct échec %s: %s", printer_ip, e)
|
||||||
|
return empty
|
||||||
|
|
||||||
|
result: dict = {"state": "", "state_reasons": [], "accepting": None, "markers": []}
|
||||||
|
|
||||||
|
# printer-state
|
||||||
|
m = re.search(r"printer-state \(enum\)\s*=\s*(\S+)", out)
|
||||||
|
if m:
|
||||||
|
result["state"] = m.group(1).lower() # idle / processing / stopped
|
||||||
|
|
||||||
|
# printer-state-reasons
|
||||||
|
m = re.search(r"printer-state-reasons \([^)]+\)\s*=\s*(.+)", out)
|
||||||
|
if m:
|
||||||
|
reasons = [r.strip() for r in m.group(1).split(",")]
|
||||||
|
result["state_reasons"] = [r for r in reasons if r and r != "none"]
|
||||||
|
|
||||||
|
# printer-is-accepting-jobs
|
||||||
|
m = re.search(r"printer-is-accepting-jobs \(boolean\)\s*=\s*(\S+)", out)
|
||||||
|
if m:
|
||||||
|
result["accepting"] = m.group(1).lower() == "true"
|
||||||
|
|
||||||
|
# marker-levels (encre/ruban)
|
||||||
|
names_m = re.search(r"marker-names \([^)]+\)\s*=\s*(.+)", out)
|
||||||
|
levels_m = re.search(r"marker-levels \([^)]+\)\s*=\s*(.+)", out)
|
||||||
|
colors_m = re.search(r"marker-colors \([^)]+\)\s*=\s*(.+)", out)
|
||||||
|
types_m = re.search(r"marker-types \([^)]+\)\s*=\s*(.+)", out)
|
||||||
|
if names_m and levels_m:
|
||||||
|
names = [n.strip().strip('"') for n in names_m.group(1).split(",")]
|
||||||
|
levels = [l.strip() for l in levels_m.group(1).split(",")]
|
||||||
|
colors = [c.strip().strip('"') for c in colors_m.group(1).split(",")] if colors_m else []
|
||||||
|
types = [t.strip().strip('"') for t in types_m.group(1).split(",")] if types_m else []
|
||||||
|
for i, name in enumerate(names):
|
||||||
|
try:
|
||||||
|
lvl = int(levels[i]) if i < len(levels) else -1
|
||||||
|
except ValueError:
|
||||||
|
lvl = -1
|
||||||
|
result["markers"].append({
|
||||||
|
"name": name,
|
||||||
|
"level": lvl,
|
||||||
|
"color": colors[i] if i < len(colors) else "",
|
||||||
|
"type": types[i] if i < len(types) else "",
|
||||||
|
})
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
# ── Helpers ───────────────────────────────────────────────────────────────
|
# ── Helpers ───────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
def _printer_name(self, p) -> str:
|
def _printer_name(self, p) -> str:
|
||||||
@@ -307,18 +383,17 @@ class PrinterService:
|
|||||||
return {"stats_done": 0, "stats_error": 0, "stats_cancelled": 0}
|
return {"stats_done": 0, "stats_error": 0, "stats_cancelled": 0}
|
||||||
|
|
||||||
def _get_printer_status(self, printer_name: str) -> dict:
|
def _get_printer_status(self, printer_name: str) -> dict:
|
||||||
"""Statut CUPS complet pour une imprimante (état, jobs, accepting, uri, modèle)."""
|
"""Statut complet : CUPS pour les jobs, IPP direct pour état/erreurs/encre."""
|
||||||
try:
|
try:
|
||||||
# État de l'imprimante
|
# ── Etat de base via lpstat ──
|
||||||
r_state = subprocess.run(
|
r_state = subprocess.run(
|
||||||
["lpstat", "-p", printer_name],
|
["lpstat", "-p", printer_name],
|
||||||
capture_output=True, text=True, timeout=5
|
capture_output=True, text=True, timeout=5
|
||||||
)
|
)
|
||||||
out = r_state.stdout.lower()
|
|
||||||
|
|
||||||
if r_state.returncode != 0 or "not found" in (r_state.stderr or "").lower():
|
if r_state.returncode != 0 or "not found" in (r_state.stderr or "").lower():
|
||||||
return {"state": "offline", "accepting": False, "jobs": [], "jobs_count": 0}
|
return {"state": "offline", "accepting": False, "jobs": [], "jobs_count": 0}
|
||||||
|
|
||||||
|
out = r_state.stdout.lower()
|
||||||
if "idle" in out:
|
if "idle" in out:
|
||||||
state = "idle"
|
state = "idle"
|
||||||
elif "printing" in out or "processing" in out:
|
elif "printing" in out or "processing" in out:
|
||||||
@@ -328,18 +403,19 @@ class PrinterService:
|
|||||||
else:
|
else:
|
||||||
state = "unknown"
|
state = "unknown"
|
||||||
|
|
||||||
# Est-ce que l'imprimante accepte les nouveaux jobs ?
|
# Est-ce que l'imprimante accepte les nouveaux jobs (CUPS) ?
|
||||||
r_accept = subprocess.run(
|
r_accept = subprocess.run(
|
||||||
["lpstat", "-a", printer_name],
|
["lpstat", "-a", printer_name],
|
||||||
capture_output=True, text=True, timeout=5
|
capture_output=True, text=True, timeout=5
|
||||||
)
|
)
|
||||||
accepting = "accepting" in r_accept.stdout.lower()
|
accepting = "accepting" in r_accept.stdout.lower()
|
||||||
|
|
||||||
# Liste des jobs CUPS en cours
|
# Jobs CUPS en cours
|
||||||
jobs = self._get_cups_jobs(printer_name)
|
jobs = self._get_cups_jobs(printer_name)
|
||||||
|
|
||||||
# URI du périphérique (adresse IP / protocole)
|
# URI du périphérique
|
||||||
uri = ""
|
uri = ""
|
||||||
|
printer_ip = ""
|
||||||
try:
|
try:
|
||||||
r_uri = subprocess.run(
|
r_uri = subprocess.run(
|
||||||
["lpstat", "-v", printer_name],
|
["lpstat", "-v", printer_name],
|
||||||
@@ -350,14 +426,15 @@ class PrinterService:
|
|||||||
parts = line.split(":", 2)
|
parts = line.split(":", 2)
|
||||||
uri = (parts[1].strip() + ":" + parts[2].strip()) if len(parts) > 2 else ""
|
uri = (parts[1].strip() + ":" + parts[2].strip()) if len(parts) > 2 else ""
|
||||||
break
|
break
|
||||||
|
m = re.search(r"(\d+\.\d+\.\d+\.\d+)", r_uri.stdout)
|
||||||
|
printer_ip = m.group(1) if m else ""
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
# Modèle, localisation et raisons d'erreur (lpstat -l -p)
|
# Modèle / location via lpstat -l -p
|
||||||
model = ""
|
model = ""
|
||||||
location = ""
|
location = ""
|
||||||
reasons: list[str] = []
|
reasons: list[str] = []
|
||||||
state_message = ""
|
|
||||||
try:
|
try:
|
||||||
r_info = subprocess.run(
|
r_info = subprocess.run(
|
||||||
["lpstat", "-l", "-p", printer_name],
|
["lpstat", "-l", "-p", printer_name],
|
||||||
@@ -372,24 +449,44 @@ class PrinterService:
|
|||||||
elif l.lower().startswith("reason:"):
|
elif l.lower().startswith("reason:"):
|
||||||
raw = l.split(":", 1)[1].strip()
|
raw = l.split(":", 1)[1].strip()
|
||||||
if raw and raw.lower() not in ("none", ""):
|
if raw and raw.lower() not in ("none", ""):
|
||||||
label = self._REASON_LABELS.get(raw, raw)
|
reasons.append(self._REASON_LABELS.get(raw, raw))
|
||||||
reasons.append(label)
|
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
# Niveaux d'encre/ruban via attributs IPP (localhost CUPS)
|
# ── Sondage IPP direct sur l'imprimante (source de vérité) ──
|
||||||
markers = self._get_marker_levels(printer_name)
|
markers: list[dict] = []
|
||||||
|
if printer_ip:
|
||||||
|
ipp = self._query_ipp_direct(printer_ip)
|
||||||
|
if ipp["state"]:
|
||||||
|
# Mappe les états IPP → notre convention
|
||||||
|
ipp_state_map = {
|
||||||
|
"idle": "idle",
|
||||||
|
"processing": "printing",
|
||||||
|
"stopped": "disabled",
|
||||||
|
}
|
||||||
|
state = ipp_state_map.get(ipp["state"], ipp["state"])
|
||||||
|
if ipp["accepting"] is not None:
|
||||||
|
accepting = ipp["accepting"]
|
||||||
|
if ipp["state_reasons"]:
|
||||||
|
reasons = [
|
||||||
|
self._REASON_LABELS.get(r, r)
|
||||||
|
for r in ipp["state_reasons"]
|
||||||
|
]
|
||||||
|
if ipp["markers"]:
|
||||||
|
markers = ipp["markers"]
|
||||||
|
else:
|
||||||
|
markers = self._get_marker_levels(printer_name)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"state": state,
|
"state": state,
|
||||||
"accepting": accepting,
|
"accepting": accepting,
|
||||||
"jobs": jobs,
|
"jobs": jobs,
|
||||||
"jobs_count": len(jobs),
|
"jobs_count": len(jobs),
|
||||||
"uri": uri,
|
"uri": uri,
|
||||||
"model": model,
|
"model": model,
|
||||||
"location": location,
|
"location": location,
|
||||||
"reasons": reasons,
|
"reasons": reasons,
|
||||||
"markers": markers,
|
"markers": markers,
|
||||||
}
|
}
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
return {"state": "error", "accepting": False, "jobs": [], "jobs_count": 0, "error": str(e)}
|
return {"state": "error", "accepting": False, "jobs": [], "jobs_count": 0, "error": str(e)}
|
||||||
|
|||||||
@@ -0,0 +1,133 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# =============================================================================
|
||||||
|
# script_print.sh - Impression photo pour photomaton
|
||||||
|
# =============================================================================
|
||||||
|
# Usage: script_print.sh "<filename>" "<media_type>" "<action_config_name>" "<copies>"
|
||||||
|
# Appele par photobooth-app via l'action Share "Impression"
|
||||||
|
#
|
||||||
|
# Imprimantes: Canon Selphy CP1300 x2 via WiFi (blanche + noire)
|
||||||
|
# Load balancing: utilise l'imprimante idle, sinon file d'attente sur la 1ere
|
||||||
|
#
|
||||||
|
# Sortie stdout parsable par Node-RED:
|
||||||
|
# PRINTED:<printer_name>:<copies> en cas de succes
|
||||||
|
# PRINT_ERROR:<printer_name> en cas d'erreur
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
set -uo pipefail
|
||||||
|
|
||||||
|
# --- Parametres ---
|
||||||
|
filename="${1:-}"
|
||||||
|
media_type="${2:-}"
|
||||||
|
action_config_name="${3:-}"
|
||||||
|
copies="${4:-1}"
|
||||||
|
|
||||||
|
# --- Noms des imprimantes CUPS (WiFi) ---
|
||||||
|
PRINTER_1="Selphy_Blanche_WiFi"
|
||||||
|
PRINTER_2="Selphy_Noire_WiFi"
|
||||||
|
|
||||||
|
# --- Options d'impression Selphy ---
|
||||||
|
# Mode raw : envoie le JPEG directement a la Selphy
|
||||||
|
# Postcard.Fullbleed : impression sans bordures
|
||||||
|
PRINT_OPTIONS="-o media=Postcard.Fullbleed -o raw"
|
||||||
|
|
||||||
|
# --- Calibrage bordures pour impression Fullbleed ---
|
||||||
|
# La Selphy deborde de quelques mm en mode Fullbleed
|
||||||
|
# On ajoute des marges noires pour compenser et eviter de couper la photo
|
||||||
|
# Valeurs calibrees pour Canon Selphy CP1300 via WiFi
|
||||||
|
PRINT_EXTENT="2114x1418"
|
||||||
|
PRINT_ROLL="-12+8"
|
||||||
|
|
||||||
|
# --- Logging ---
|
||||||
|
LOG_TAG="photomaton-print"
|
||||||
|
log_info() { logger -t "$LOG_TAG" "[INFO] $1"; echo "[INFO] $1"; }
|
||||||
|
log_error() { logger -t "$LOG_TAG" "[ERROR] $1"; echo "[ERROR] $1"; }
|
||||||
|
|
||||||
|
# --- Validation ---
|
||||||
|
if [ -z "$filename" ]; then
|
||||||
|
log_error "Aucun fichier specifie"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ ! -f "$filename" ]; then
|
||||||
|
log_error "Fichier introuvable: $filename"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$copies" -lt 1 ] || [ "$copies" -gt 3 ]; then
|
||||||
|
log_error "Nombre de copies invalide: $copies (doit etre entre 1 et 3)"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# --- Preparation de l'image pour impression ---
|
||||||
|
# Resize a 2000x1333 (ratio 3:2), ajout de marges noires pour compenser
|
||||||
|
# le debordement Fullbleed, puis decalage pour centrer correctement
|
||||||
|
filename_no_ext="${filename%.jpg}"
|
||||||
|
filename_print="${filename_no_ext}_print.jpg"
|
||||||
|
|
||||||
|
if command -v convert &>/dev/null; then
|
||||||
|
log_info "Preparation de l'image pour impression Fullbleed..."
|
||||||
|
if convert "$filename" \
|
||||||
|
-resize 2000x1333 \
|
||||||
|
-background black \
|
||||||
|
-gravity center \
|
||||||
|
-extent ${PRINT_EXTENT} \
|
||||||
|
-roll ${PRINT_ROLL} \
|
||||||
|
-quality 95 \
|
||||||
|
"$filename_print" 2>/dev/null; then
|
||||||
|
FILE_TO_PRINT="$filename_print"
|
||||||
|
log_info "Image preparee: $filename_print"
|
||||||
|
else
|
||||||
|
log_error "Echec de la preparation, impression de l'original"
|
||||||
|
FILE_TO_PRINT="$filename"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
log_error "ImageMagick (convert) non installe, impression de l'original"
|
||||||
|
FILE_TO_PRINT="$filename"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# --- Selection de l'imprimante (load balancing) ---
|
||||||
|
printer_1_status=$(lpstat -p "$PRINTER_1" 2>/dev/null || echo "not found")
|
||||||
|
printer_2_status=$(lpstat -p "$PRINTER_2" 2>/dev/null || echo "not found")
|
||||||
|
|
||||||
|
SELECTED_PRINTER=""
|
||||||
|
|
||||||
|
if [[ "$printer_1_status" == *"idle"* ]]; then
|
||||||
|
SELECTED_PRINTER="$PRINTER_1"
|
||||||
|
log_info "Imprimante selectionnee: $PRINTER_1 (idle)"
|
||||||
|
elif [[ "$printer_2_status" == *"idle"* ]]; then
|
||||||
|
SELECTED_PRINTER="$PRINTER_2"
|
||||||
|
log_info "Imprimante selectionnee: $PRINTER_2 (idle)"
|
||||||
|
else
|
||||||
|
# Aucune idle : choisir celle avec le moins de jobs en attente
|
||||||
|
jobs_1=$(lpstat -o "$PRINTER_1" 2>/dev/null | wc -l)
|
||||||
|
jobs_2=$(lpstat -o "$PRINTER_2" 2>/dev/null | wc -l)
|
||||||
|
|
||||||
|
if [ "$jobs_1" -le "$jobs_2" ] 2>/dev/null; then
|
||||||
|
SELECTED_PRINTER="$PRINTER_1"
|
||||||
|
else
|
||||||
|
SELECTED_PRINTER="$PRINTER_2"
|
||||||
|
fi
|
||||||
|
log_info "Aucune imprimante idle, file d'attente sur: $SELECTED_PRINTER (jobs: P1=$jobs_1, P2=$jobs_2)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# --- Impression ---
|
||||||
|
log_info "Impression x${copies} sur $SELECTED_PRINTER: $FILE_TO_PRINT"
|
||||||
|
|
||||||
|
# Envoi du job CUPS et capture du job ID
|
||||||
|
job_output=$(lp -n "$copies" -d "$SELECTED_PRINTER" $PRINT_OPTIONS "$FILE_TO_PRINT" 2>&1)
|
||||||
|
lp_exit=$?
|
||||||
|
|
||||||
|
if [ $lp_exit -ne 0 ]; then
|
||||||
|
log_error "lp a echoue (exit $lp_exit): $job_output"
|
||||||
|
echo "PRINT_ERROR:${SELECTED_PRINTER}:lp_failed"
|
||||||
|
[ -f "$filename_print" ] && rm -f "$filename_print" 2>/dev/null
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Extraction du job ID (ex: "request id is Selphy_Blanche_WiFi-42 (1 file(s))")
|
||||||
|
JOB_ID=$(echo "$job_output" | grep -oE '[A-Za-z0-9_-]+-[0-9]+' | head -1)
|
||||||
|
log_info "Job CUPS accepte: ${JOB_ID:-inconnu}"
|
||||||
|
|
||||||
|
# Si le job ID est introuvable, comportement legacy (retour immediat)
|
||||||
|
if [ -z "$JOB_ID" ]; then
|
||||||
|
log_info "Job ID non extrait – imp
|
||||||
Reference in New Issue
Block a user