Canon: fiabilite veille/reveil + detection PTP freeze + UX countdown + fix copies impression
- Canon PTP freeze: 5 keepalive FAIL consecutifs → deconnexion forcee - Canon veille: reset timestamps reconnexion (pas de boucle de mort) - Canon reveil: guard _reveil_en_cours dans thread preview + surveiller_dslr - Canon capture: attente auto reveil (60s max) au lieu de 503 immediat - Canon reconnexion quand _echecs_connexion == 0 - UX: sequence "Attention!" → "Preparez-vous!" → 3 → 2 → 1 - UX: pre-messages uniquement photo simple + 1ere photo pellicule - UX: pas de compteur "Photo 1/1" en mode simple - UX: pas de cadre propose pour pellicule - UX: taille texte countdown reduite pour Surface (scale-factor 3) - Impression: N jobs lp separes au lieu de lp -n (Mitsubishi ignore -n) - Bouton Terminer grise pendant impression Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
114
backend/main.py
114
backend/main.py
@@ -83,6 +83,7 @@ _canon_stats = {
|
||||
}
|
||||
_CANON_VEILLE_MINUTES = 30 # couper le Canon après X min sans activité utilisateur
|
||||
_canon_en_veille = False
|
||||
_reveil_en_cours = False
|
||||
|
||||
def _thread_preview():
|
||||
"""Thread de fond : capture les frames DSLR.
|
||||
@@ -103,7 +104,7 @@ def _thread_preview():
|
||||
_canon_stats["derniere_capture"],
|
||||
_canon_stats["derniere_preview"],
|
||||
)
|
||||
if derniere_activite > 0 and not _preview_actif and not _canon_en_veille:
|
||||
if derniere_activite > 0 and not _preview_actif and not _canon_en_veille and not _reveil_en_cours:
|
||||
inactif_min = (now - derniere_activite) / 60
|
||||
if inactif_min >= _CANON_VEILLE_MINUTES and relais.est_connecte():
|
||||
log.info(f"Canon inactif depuis {inactif_min:.0f} min — mise en veille relais")
|
||||
@@ -124,14 +125,21 @@ def _thread_preview():
|
||||
_derniere_keepalive = now
|
||||
if ok:
|
||||
_canon_stats["keepalive_ok"] += 1
|
||||
_canon_stats["keepalive_fail_consecutifs"] = 0
|
||||
_canon_stats["dernier_keepalive_ok"] = now
|
||||
if now - _dernier_log_keepalive > 300:
|
||||
log.info(f"Canon keepalive OK (total: {_canon_stats['keepalive_ok']}, fail: {_canon_stats['keepalive_fail']})")
|
||||
_dernier_log_keepalive = now
|
||||
else:
|
||||
_canon_stats["keepalive_fail"] += 1
|
||||
_canon_stats["keepalive_fail_consecutifs"] = _canon_stats.get("keepalive_fail_consecutifs", 0) + 1
|
||||
_canon_stats["dernier_keepalive_fail"] = now
|
||||
log.warning(f"Canon keepalive FAIL #{_canon_stats['keepalive_fail']}")
|
||||
consec = _canon_stats["keepalive_fail_consecutifs"]
|
||||
log.warning(f"Canon keepalive FAIL #{_canon_stats['keepalive_fail']} (consec: {consec})")
|
||||
if consec >= 5:
|
||||
log.error(f"Canon PTP freeze detecte — {consec} keepalive FAIL consecutifs, deconnexion forcee")
|
||||
_canon_stats["keepalive_fail_consecutifs"] = 0
|
||||
camera.deconnecter()
|
||||
time.sleep(1)
|
||||
continue
|
||||
try:
|
||||
@@ -362,7 +370,7 @@ async def surveiller_dslr():
|
||||
await asyncio.sleep(60)
|
||||
else:
|
||||
await asyncio.sleep(5)
|
||||
if capture_en_cours:
|
||||
if capture_en_cours or _reveil_en_cours:
|
||||
continue
|
||||
try:
|
||||
if not GPHOTO2_DISPONIBLE:
|
||||
@@ -373,7 +381,13 @@ async def surveiller_dslr():
|
||||
with camera._gp_lock:
|
||||
camera.camera.get_config()
|
||||
except Exception:
|
||||
pass
|
||||
_dslr_erreurs += 1
|
||||
if _dslr_erreurs >= 3:
|
||||
log.warning(f"Canon get_config echoue {_dslr_erreurs} fois — reconnexion forcee")
|
||||
_dslr_erreurs = 0
|
||||
_echecs_connexion += 1
|
||||
await _reconnecter_dslr_avec_reset(_echecs_connexion)
|
||||
continue
|
||||
if camera.preview_dslr_ok:
|
||||
_dslr_erreurs = 0
|
||||
_echecs_connexion = 0
|
||||
@@ -398,14 +412,28 @@ async def surveiller_dslr():
|
||||
_usb_reset_canon()
|
||||
await asyncio.sleep(3)
|
||||
await _reconnecter_dslr_avec_reset(_echecs_connexion)
|
||||
elif _echecs_connexion > 0 and _echecs_connexion % 10 == 0:
|
||||
if relais.est_connecte() and _echecs_connexion % 20 == 0:
|
||||
log.info("Canon absent USB — power cycle relais...")
|
||||
await asyncio.get_event_loop().run_in_executor(None, relais.power_cycle_canon, 10.0)
|
||||
await asyncio.sleep(20)
|
||||
else:
|
||||
log.info("DSLR non detecte en USB, tentative USB rebind...")
|
||||
await asyncio.get_event_loop().run_in_executor(None, _usb_rebind_canon)
|
||||
else:
|
||||
if _echecs_connexion > 0:
|
||||
_echecs_connexion += 1
|
||||
if relais.est_connecte() and _echecs_connexion % 10 == 0:
|
||||
log.warning(f"Canon absent USB (tentative {_echecs_connexion}) — power cycle relais...")
|
||||
await asyncio.get_event_loop().run_in_executor(None, relais.power_cycle_canon, 10.0)
|
||||
await asyncio.sleep(25)
|
||||
else:
|
||||
log.info(f"DSLR non detecte en USB (tentative {_echecs_connexion}), rebind...")
|
||||
await asyncio.get_event_loop().run_in_executor(None, _usb_rebind_canon)
|
||||
elif _canon_sur_usb():
|
||||
log.info("Canon detecte sur USB sans echec precedent — tentative connexion...")
|
||||
_echecs_connexion = 1
|
||||
camera.connecter(source="gphoto2")
|
||||
if camera.connectee:
|
||||
log.info("Canon reconnecte automatiquement")
|
||||
_canon_stats["connexions"] += 1
|
||||
_canon_stats["derniere_preview"] = time.time()
|
||||
_canon_stats["derniere_capture"] = time.time()
|
||||
_appliquer_config_camera()
|
||||
_echecs_connexion = 0
|
||||
await diffuser_ws({"type": "camera_ok"})
|
||||
except Exception as e:
|
||||
log.debug(f"surveiller_dslr: {e}")
|
||||
|
||||
@@ -457,6 +485,8 @@ async def _reconnecter_dslr_avec_reset(echecs: int):
|
||||
if camera.connectee:
|
||||
log.info("DSLR reconnecte avec succes")
|
||||
_canon_stats["connexions"] += 1
|
||||
_canon_stats["derniere_preview"] = time.time()
|
||||
_canon_stats["derniere_capture"] = time.time()
|
||||
_appliquer_config_camera()
|
||||
await diffuser_ws({"type": "camera_ok"})
|
||||
else:
|
||||
@@ -618,22 +648,29 @@ async def _surveiller_imprimante():
|
||||
|
||||
def _reveiller_canon():
|
||||
"""Réveil Canon depuis veille relais : restaure alimentation, attend boot, reconnecte."""
|
||||
log.info("Réveil Canon — restauration alimentation relais")
|
||||
relais.desactiver("canon")
|
||||
time.sleep(15)
|
||||
relais.desactiver("canon_usb")
|
||||
time.sleep(10)
|
||||
if _canon_sur_usb():
|
||||
log.info("Canon visible USB après réveil — connexion...")
|
||||
camera.connecter(source="gphoto2")
|
||||
if camera.connectee:
|
||||
_appliquer_config_camera()
|
||||
log.info("Canon reconnecté après réveil")
|
||||
_canon_stats["connexions"] += 1
|
||||
global _reveil_en_cours
|
||||
_reveil_en_cours = True
|
||||
try:
|
||||
log.info("Réveil Canon — restauration alimentation relais")
|
||||
relais.desactiver("canon")
|
||||
time.sleep(15)
|
||||
relais.desactiver("canon_usb")
|
||||
time.sleep(25)
|
||||
if _canon_sur_usb():
|
||||
log.info("Canon visible USB après réveil — connexion...")
|
||||
camera.connecter(source="gphoto2")
|
||||
if camera.connectee:
|
||||
_appliquer_config_camera()
|
||||
_canon_stats["derniere_preview"] = time.time()
|
||||
_canon_stats["derniere_capture"] = time.time()
|
||||
log.info("Canon reconnecté après réveil")
|
||||
_canon_stats["connexions"] += 1
|
||||
else:
|
||||
log.warning("Canon visible USB mais connexion gphoto2 échouée")
|
||||
else:
|
||||
log.warning("Canon visible USB mais connexion gphoto2 échouée")
|
||||
else:
|
||||
log.warning("Canon absent USB après réveil relais — vérifier branchement physique")
|
||||
log.warning("Canon absent USB après réveil relais — vérifier branchement physique")
|
||||
finally:
|
||||
_reveil_en_cours = False
|
||||
|
||||
|
||||
def _canon_sur_usb() -> bool:
|
||||
@@ -846,9 +883,21 @@ async def api_config_update(modifications: dict):
|
||||
|
||||
@app.post("/api/capturer")
|
||||
async def api_capturer():
|
||||
global capture_en_cours, _preview_actif
|
||||
global capture_en_cours, _preview_actif, _canon_en_veille
|
||||
if _canon_en_veille and not _reveil_en_cours:
|
||||
log.info("Canon en veille lors de la capture — réveil automatique")
|
||||
_canon_en_veille = False
|
||||
await asyncio.get_event_loop().run_in_executor(None, _reveiller_canon)
|
||||
if _reveil_en_cours:
|
||||
log.info("Canon en cours de réveil — attente max 60s")
|
||||
for _ in range(60):
|
||||
if not _reveil_en_cours:
|
||||
break
|
||||
await asyncio.sleep(1)
|
||||
if not camera.connectee:
|
||||
return JSONResponse({"erreur": "Appareil photo en cours de démarrage — réessayez"}, status_code=503)
|
||||
capture_en_cours = True
|
||||
_preview_actif = False # Eviter faux camera_erreur (race condition HTTP avant preview_stop)
|
||||
_preview_actif = False
|
||||
_canon_stats["captures"] += 1
|
||||
_canon_stats["derniere_capture"] = time.time()
|
||||
config = charger_config()
|
||||
@@ -1107,7 +1156,7 @@ async def api_relais_veille():
|
||||
|
||||
@app.post("/api/relais/reveil")
|
||||
async def api_relais_reveil():
|
||||
global _veille_eclairage
|
||||
global _veille_eclairage, _canon_en_veille
|
||||
_veille_eclairage = False
|
||||
if relais.est_connecte():
|
||||
config = charger_config()
|
||||
@@ -1116,6 +1165,10 @@ async def api_relais_reveil():
|
||||
relais.activer("projecteur_gauche")
|
||||
_proj_etat["proj1"] = True
|
||||
_proj_etat["proj2"] = False
|
||||
if _canon_en_veille:
|
||||
log.info("Réveil Canon déclenché par reveil éclairage (HTTP)")
|
||||
_canon_en_veille = False
|
||||
asyncio.get_event_loop().run_in_executor(None, _reveiller_canon)
|
||||
log.info("Eclairage reveille (1 projecteur)")
|
||||
return {"ok": True}
|
||||
|
||||
@@ -1634,6 +1687,7 @@ async def api_creer_evenement(donnees: dict):
|
||||
return JSONResponse({"erreur": "Nom requis"}, status_code=400)
|
||||
event = creer_evenement(
|
||||
nom,
|
||||
event_id=donnees.get("event_id"),
|
||||
theme=donnees.get("theme", "base"),
|
||||
couleur_primaire=donnees.get("couleur_primaire", "#e91e63"),
|
||||
couleur_secondaire=donnees.get("couleur_secondaire", "#ffffff"),
|
||||
|
||||
Reference in New Issue
Block a user