fix: timing flash + couleur idle LED + diagnostic auto-brightness
This commit is contained in:
@@ -228,6 +228,24 @@ class ConfigService:
|
||||
self._config.print.user_print_quota = quota
|
||||
self._config.print.user_print_max_copies = max_copies
|
||||
|
||||
def save_printer_excluded(self, printer_name: str, excluded: bool):
|
||||
"""Marque (ou démarque) une imprimante comme exclue du load-balancing."""
|
||||
with open(self.path, encoding="utf-8") as f:
|
||||
raw = yaml.safe_load(f) or {}
|
||||
printers = raw.get("print", {}).get("printers", [])
|
||||
for p in printers:
|
||||
if isinstance(p, dict) and p.get("name") == printer_name:
|
||||
p["excluded"] = excluded
|
||||
break
|
||||
raw.setdefault("print", {})["printers"] = printers
|
||||
with open(self.path, "w", encoding="utf-8") as f:
|
||||
yaml.dump(raw, f, allow_unicode=True, default_flow_style=False)
|
||||
if self._config:
|
||||
for p in self._config.print.printers:
|
||||
if isinstance(p, dict) and p.get("name") == printer_name:
|
||||
p["excluded"] = excluded
|
||||
break
|
||||
|
||||
def save_print_mode(self, mode: str):
|
||||
with open(self.path, encoding="utf-8") as f:
|
||||
raw = yaml.safe_load(f) or {}
|
||||
|
||||
@@ -191,26 +191,40 @@ class PhotoboothService:
|
||||
"""Tente de recuperer une frame JPEG du liveview de photobooth-app.
|
||||
|
||||
Essaie dans l'ordre :
|
||||
1. /api/stream/snapshot — endpoint snapshot direct (si disponible)
|
||||
2. /stream.mjpg — flux MJPEG classique, extrait la premiere frame
|
||||
3. /api/stream — flux MJPEG alternatif
|
||||
1. Endpoints snapshot direct (retour JPEG immédiat)
|
||||
2. Flux MJPEG — extrait la premiere frame
|
||||
|
||||
Retourne des bytes JPEG ou None si rien n'est disponible.
|
||||
"""
|
||||
# 1. Snapshot direct
|
||||
for path in ("/api/stream/snapshot",):
|
||||
# 1. Endpoints snapshot direct (differentes versions de photobooth-app)
|
||||
snapshot_paths = (
|
||||
"/api/stream/snapshot",
|
||||
"/api/video/preview/snapshot",
|
||||
"/api/livestream/snapshot",
|
||||
"/api/cam/stream/snapshot",
|
||||
)
|
||||
for path in snapshot_paths:
|
||||
try:
|
||||
async with httpx.AsyncClient(timeout=2.0) as client:
|
||||
r = await client.get(f"{self._base}{path}")
|
||||
if r.status_code == 200:
|
||||
ct = r.headers.get("content-type", "")
|
||||
if "jpeg" in ct or "image" in ct:
|
||||
logger.debug("Snapshot OK via %s", path)
|
||||
return r.content
|
||||
except Exception as e:
|
||||
logger.debug("Snapshot %s: %s", path, e)
|
||||
|
||||
# 2. Premiere frame d'un flux MJPEG
|
||||
for path in ("/stream.mjpg", "/api/stream"):
|
||||
# 2. Premiere frame d'un flux MJPEG (differentes versions)
|
||||
mjpeg_paths = (
|
||||
"/stream.mjpg",
|
||||
"/api/stream",
|
||||
"/api/video/stream",
|
||||
"/api/video/preview",
|
||||
"/api/livestream",
|
||||
"/livestream",
|
||||
)
|
||||
for path in mjpeg_paths:
|
||||
try:
|
||||
async with httpx.AsyncClient(timeout=3.0) as client:
|
||||
async with client.stream("GET", f"{self._base}{path}") as resp:
|
||||
@@ -223,12 +237,14 @@ class PhotoboothService:
|
||||
if start >= 0:
|
||||
end = buf.find(b"\xff\xd9", start)
|
||||
if end >= 0:
|
||||
logger.debug("MJPEG frame OK via %s", path)
|
||||
return buf[start:end + 2]
|
||||
if len(buf) > 500_000:
|
||||
break
|
||||
except Exception as e:
|
||||
logger.debug("MJPEG %s: %s", path, e)
|
||||
|
||||
logger.debug("Aucun endpoint liveview disponible sur %s", self._base)
|
||||
return None
|
||||
|
||||
async def close(self):
|
||||
|
||||
@@ -244,7 +244,13 @@ class PrinterService:
|
||||
if not Path(filename).exists():
|
||||
return {"success": False, "error": f"Fichier introuvable: {filename}"}
|
||||
|
||||
cmd = ["/bin/bash", script, filename, "image", "default", str(copies)]
|
||||
# Imprimantes exclues du load-balancing (flag excluded dans config)
|
||||
excluded_list = ",".join(
|
||||
self._printer_name(p)
|
||||
for p in self._cfg.printers
|
||||
if isinstance(p, dict) and p.get("excluded", False)
|
||||
)
|
||||
cmd = ["/bin/bash", script, filename, "image", "default", str(copies), excluded_list]
|
||||
try:
|
||||
proc = subprocess.run(
|
||||
cmd, capture_output=True, text=True, timeout=120
|
||||
@@ -361,11 +367,13 @@ class PrinterService:
|
||||
statuses = []
|
||||
for p in self._cfg.printers:
|
||||
name = self._printer_name(p)
|
||||
excluded = isinstance(p, dict) and bool(p.get("excluded", False))
|
||||
status = await asyncio.to_thread(self._get_printer_status, name)
|
||||
stats = await self._get_printer_db_stats(name)
|
||||
statuses.append({
|
||||
"name": name,
|
||||
"label": self._printer_label(p),
|
||||
"excluded": excluded,
|
||||
**status,
|
||||
**stats,
|
||||
})
|
||||
@@ -619,8 +627,8 @@ class PrinterService:
|
||||
async def enable_printer(self, printer_name: str) -> bool:
|
||||
"""Active l'imprimante CUPS (cupsenable) et accepte les nouveaux jobs."""
|
||||
try:
|
||||
r1 = subprocess.run(["cupsenable", printer_name], capture_output=True, timeout=10)
|
||||
r2 = subprocess.run(["cupsaccept", printer_name], capture_output=True, timeout=10)
|
||||
r1 = subprocess.run(["sudo", "cupsenable", printer_name], capture_output=True, timeout=10)
|
||||
r2 = subprocess.run(["sudo", "cupsaccept", printer_name], capture_output=True, timeout=10)
|
||||
return r1.returncode == 0 and r2.returncode == 0
|
||||
except Exception as e:
|
||||
logger.error("Erreur enable printer %s: %s", printer_name, e)
|
||||
@@ -629,7 +637,7 @@ class PrinterService:
|
||||
async def disable_printer(self, printer_name: str) -> bool:
|
||||
"""Désactive l'imprimante CUPS (cupsdisable)."""
|
||||
try:
|
||||
result = subprocess.run(["cupsdisable", printer_name], capture_output=True, timeout=10)
|
||||
result = subprocess.run(["sudo", "cupsdisable", printer_name], capture_output=True, timeout=10)
|
||||
return result.returncode == 0
|
||||
except Exception as e:
|
||||
logger.error("Erreur disable printer %s: %s", printer_name, e)
|
||||
@@ -638,7 +646,7 @@ class PrinterService:
|
||||
async def reject_jobs(self, printer_name: str) -> bool:
|
||||
"""Refuse les nouveaux jobs (cupsreject) sans stopper l'impression en cours."""
|
||||
try:
|
||||
result = subprocess.run(["cupsreject", printer_name], capture_output=True, timeout=10)
|
||||
result = subprocess.run(["sudo", "cupsreject", printer_name], capture_output=True, timeout=10)
|
||||
return result.returncode == 0
|
||||
except Exception as e:
|
||||
logger.error("Erreur reject printer %s: %s", printer_name, e)
|
||||
|
||||
Reference in New Issue
Block a user