From f9fd4c4f221d933e866996360f29d4af4c378af6 Mon Sep 17 00:00:00 2001 From: Jules Date: Wed, 8 Apr 2026 20:46:46 +0200 Subject: [PATCH] Fix erreur DSLR: detection immediate + message clair a l'ecran - Backend: detecte quand le preview DSLR echoue (fallback silencieux) et envoie camera_erreur au frontend (debounce 5s) - Frontend: overlay d'erreur avec message si preview DSLR indisponible - Frontend: si capture echoue, affiche le motif 3s avant retour accueil au lieu de retourner silencieusement Co-Authored-By: Claude Sonnet 4.6 --- backend/camera.py | 3 +++ backend/main.py | 9 +++++++++ frontend/js/camera.js | 26 +++++++++++++++++++++++--- 3 files changed, 35 insertions(+), 3 deletions(-) diff --git a/backend/camera.py b/backend/camera.py index efd8149..3d25761 100644 --- a/backend/camera.py +++ b/backend/camera.py @@ -64,6 +64,7 @@ class Camera: self.webcam = None self.webcam_index = -1 self._preview_webcam_fallback = None + self.preview_dslr_ok = True # True si le dernier preview DSLR a reussi def connecter(self, source=None) -> bool: """Connecte la camera. source: 'gphoto2', 'webcam:0', 'webcam:2', etc.""" @@ -202,7 +203,9 @@ class Camera: elif self.mode == "gphoto2": data = self._preview_gphoto2() if data: + self.preview_dslr_ok = True return data + self.preview_dslr_ok = False # Fallback webcam pour le preview si le DSLR ne supporte pas le live view if not self._preview_webcam_fallback: self._init_preview_fallback() diff --git a/backend/main.py b/backend/main.py index 430fd9f..d86eb78 100644 --- a/backend/main.py +++ b/backend/main.py @@ -37,6 +37,7 @@ clients_ws: list[WebSocket] = [] capture_en_cours = False _dslr_erreurs = 0 +_dernier_envoi_erreur_preview = 0.0 # timestamp du dernier camera_erreur envoye depuis le preview async def surveiller_dslr(): """Verifie periodiquement si un DSLR est branche et bascule dessus.""" @@ -529,6 +530,8 @@ async def traiter_message_ws(msg: dict, ws: WebSocket): if type_msg == "ping": await ws.send_json({"type": "pong"}) elif type_msg == "preview": + import time as _time + global _dernier_envoi_erreur_preview # Pendant une capture DSLR, utiliser directement la webcam fallback donnees = None if capture_en_cours and camera._preview_webcam_fallback: @@ -541,6 +544,12 @@ async def traiter_message_ws(msg: dict, ws: WebSocket): if donnees: b64 = base64.b64encode(donnees).decode("ascii") await ws.send_json({"type": "preview", "image": f"data:image/jpeg;base64,{b64}"}) + # Si le DSLR est cense etre connecte mais le preview echoue, signaler l'erreur + if camera.mode == "gphoto2" and not camera.preview_dslr_ok: + now = _time.monotonic() + if now - _dernier_envoi_erreur_preview > 5.0: + _dernier_envoi_erreur_preview = now + await ws.send_json({"type": "camera_erreur", "message": "Le DSLR ne repond pas au preview"}) async def diffuser_ws(message: dict): diff --git a/frontend/js/camera.js b/frontend/js/camera.js index 3102161..3ec8811 100644 --- a/frontend/js/camera.js +++ b/frontend/js/camera.js @@ -9,11 +9,28 @@ const EMOJI_CAR = { 3: '🤪', 2: '😱', 1: '🔥' }; // --- Preview live --- +function afficherErreurCapture(message) { + const el = document.getElementById('preview-erreur-camera'); + if (!el) { allerA('accueil'); return; } + el.querySelector('p').textContent = message; + el.querySelector('span').textContent = 'Retour dans 3 secondes...'; + el.classList.remove('cache'); + setTimeout(() => { + el.classList.add('cache'); + allerA('accueil'); + }, 3000); +} + function afficherErreurPreview(visible) { const el = document.getElementById('preview-erreur-camera'); if (!el) return; - if (visible) el.classList.remove('cache'); - else el.classList.add('cache'); + if (visible) { + el.querySelector('p').textContent = 'Appareil photo deconnecte'; + el.querySelector('span').textContent = 'Reconnexion en cours...'; + el.classList.remove('cache'); + } else { + el.classList.add('cache'); + } } function resetPreviewTimeout() { @@ -111,7 +128,10 @@ async function lancerCapture() { } if (resultat.erreur) { - allerA('accueil'); + arreterPreview(); + afficherErreurCapture(resultat.erreur === 'Limite de photos atteinte' + ? 'Limite de photos atteinte' + : 'Echec de la capture — verifier le DSLR'); return; }