Flash Canon configurable, choix de cadre a l'impression, fixes capture/reconnexion DSLR

- Flash integre Canon activable/desactivable depuis l'admin (configurer_flash)
- Popup de choix de cadre juste avant impression, layout sans defilement (flex nowrap + aspect-ratio)
- Capture : ignore camera_erreur pendant une capture en cours (laisse l'API trancher)
- surveiller_dslr : grace period preview augmentee a 2s, reapplique la config camera apres reconnexion
- start.sh simplifie (Chromium gere par kiosk-session.sh), remote debugging port 9222 active
This commit is contained in:
2026-06-21 21:59:36 +02:00
parent c7225cc9f6
commit fe0a559c85
10 changed files with 217 additions and 60 deletions

View File

@@ -49,7 +49,7 @@ _loop_principal: asyncio.AbstractEventLoop | None = None # loop asyncio princip
_preview_none_count = 0
_PREVIEW_GRACE_FRAMES = 15 # ~500ms avant de declarer une erreur
_PREVIEW_GRACE_FRAMES = 40 # 40 x 50ms = 2s avant de declarer une erreur (Canon LiveView peut être lent à démarrer)
def _thread_preview():
@@ -88,6 +88,7 @@ async def _pusher_preview():
# Grace period : attendre plusieurs frames avant de signaler l'erreur
# Ne pas signaler pendant une capture (race condition HTTP/WS)
if camera.mode == "gphoto2" and _preview_none_count > _PREVIEW_GRACE_FRAMES and not capture_en_cours:
log.warning(f"camera_erreur envoyé : preview None depuis {_preview_none_count * 50}ms")
await diffuser_ws({"type": "camera_erreur", "message": "DSLR ne repond pas au preview"})
_preview_actif = False
_preview_none_count = 0
@@ -134,8 +135,8 @@ async def surveiller_dslr():
_echecs_connexion = 0
else:
_dslr_erreurs += 1
if _dslr_erreurs >= 3: # 3 x 5s = 15s sans preview valide
log.warning("DSLR ne repond plus (preview KO depuis 15s), reconnexion forcee...")
if _dslr_erreurs >= 3 and not _preview_actif: # Ne pas couper pendant le countdown
log.warning(f"camera_erreur : DSLR preview KO depuis {_dslr_erreurs * 5}s, reconnexion forcee...")
_dslr_erreurs = 0
camera.deconnecter()
await diffuser_ws({"type": "camera_erreur", "message": "Appareil photo deconnecte"})
@@ -144,6 +145,7 @@ async def surveiller_dslr():
if camera.connectee:
log.info("DSLR reconnecte avec succes")
_echecs_connexion = 0
_appliquer_config_camera()
await diffuser_ws({"type": "camera_ok"})
else:
log.warning("Echec reconnexion DSLR — nouvelle tentative dans 5s")
@@ -156,6 +158,7 @@ async def surveiller_dslr():
if camera.connectee:
log.info("DSLR connecte avec succes")
_echecs_connexion = 0
_appliquer_config_camera()
await diffuser_ws({"type": "camera_ok"})
else:
_echecs_connexion += 1
@@ -164,6 +167,13 @@ async def surveiller_dslr():
log.debug(f"surveiller_dslr: {e}")
def _appliquer_config_camera():
"""Applique les paramètres caméra persistants (flash, etc.) après connexion."""
cfg = charger_config()
flash_integre = cfg.get("camera", {}).get("flash_integre", True)
camera.configurer_flash(flash_integre)
@asynccontextmanager
async def lifespan(app: FastAPI):
"""Demarrage et arret de l'application."""
@@ -171,6 +181,7 @@ async def lifespan(app: FastAPI):
log.info("Demarrage du photobooth")
_loop_principal = asyncio.get_event_loop()
camera.connecter()
_appliquer_config_camera()
# Thread de capture preview (bloquant, tourne en parallele)
t = threading.Thread(target=_thread_preview, daemon=True)
t.start()
@@ -276,6 +287,9 @@ async def api_config():
async def api_config_update(modifications: dict):
config = mettre_a_jour_config(modifications)
await diffuser_ws({"type": "config_maj", "config": config})
# Appliquer immédiatement les paramètres caméra si modifiés
if "camera" in modifications and camera.connectee:
_appliquer_config_camera()
return config
@@ -430,6 +444,13 @@ async def api_systeme_info():
}
@app.post("/api/log-js")
async def api_log_js(request: Request):
data = await request.json()
log.warning(f"[JS] {data.get('msg', '?')}")
return {"ok": True}
@app.get("/api/camera/statut")
async def api_camera_statut():
return {
@@ -476,6 +497,8 @@ async def api_camera_reconnecter(body: dict = {}):
camera.deconnecter()
source = body.get("source")
ok = camera.connecter(source=source)
if ok:
_appliquer_config_camera()
return {"connectee": ok, "mode": camera.mode}
@@ -1073,8 +1096,10 @@ async def traiter_message_ws(msg: dict, ws: WebSocket):
elif type_msg == "preview_start":
global _preview_actif
_preview_actif = True
log.info("WS: preview_start recu")
elif type_msg == "preview_stop":
_preview_actif = False
log.info("WS: preview_stop recu")
async def diffuser_ws(message: dict):