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 <noreply@anthropic.com>
This commit is contained in:
2026-04-08 20:46:46 +02:00
parent ef6679dc97
commit f9fd4c4f22
3 changed files with 35 additions and 3 deletions

View File

@@ -64,6 +64,7 @@ class Camera:
self.webcam = None self.webcam = None
self.webcam_index = -1 self.webcam_index = -1
self._preview_webcam_fallback = None self._preview_webcam_fallback = None
self.preview_dslr_ok = True # True si le dernier preview DSLR a reussi
def connecter(self, source=None) -> bool: def connecter(self, source=None) -> bool:
"""Connecte la camera. source: 'gphoto2', 'webcam:0', 'webcam:2', etc.""" """Connecte la camera. source: 'gphoto2', 'webcam:0', 'webcam:2', etc."""
@@ -202,7 +203,9 @@ class Camera:
elif self.mode == "gphoto2": elif self.mode == "gphoto2":
data = self._preview_gphoto2() data = self._preview_gphoto2()
if data: if data:
self.preview_dslr_ok = True
return data return data
self.preview_dslr_ok = False
# Fallback webcam pour le preview si le DSLR ne supporte pas le live view # Fallback webcam pour le preview si le DSLR ne supporte pas le live view
if not self._preview_webcam_fallback: if not self._preview_webcam_fallback:
self._init_preview_fallback() self._init_preview_fallback()

View File

@@ -37,6 +37,7 @@ clients_ws: list[WebSocket] = []
capture_en_cours = False capture_en_cours = False
_dslr_erreurs = 0 _dslr_erreurs = 0
_dernier_envoi_erreur_preview = 0.0 # timestamp du dernier camera_erreur envoye depuis le preview
async def surveiller_dslr(): async def surveiller_dslr():
"""Verifie periodiquement si un DSLR est branche et bascule dessus.""" """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": if type_msg == "ping":
await ws.send_json({"type": "pong"}) await ws.send_json({"type": "pong"})
elif type_msg == "preview": elif type_msg == "preview":
import time as _time
global _dernier_envoi_erreur_preview
# Pendant une capture DSLR, utiliser directement la webcam fallback # Pendant une capture DSLR, utiliser directement la webcam fallback
donnees = None donnees = None
if capture_en_cours and camera._preview_webcam_fallback: if capture_en_cours and camera._preview_webcam_fallback:
@@ -541,6 +544,12 @@ async def traiter_message_ws(msg: dict, ws: WebSocket):
if donnees: if donnees:
b64 = base64.b64encode(donnees).decode("ascii") b64 = base64.b64encode(donnees).decode("ascii")
await ws.send_json({"type": "preview", "image": f"data:image/jpeg;base64,{b64}"}) 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): async def diffuser_ws(message: dict):

View File

@@ -9,11 +9,28 @@ const EMOJI_CAR = { 3: '🤪', 2: '😱', 1: '🔥' };
// --- Preview live --- // --- 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) { function afficherErreurPreview(visible) {
const el = document.getElementById('preview-erreur-camera'); const el = document.getElementById('preview-erreur-camera');
if (!el) return; if (!el) return;
if (visible) el.classList.remove('cache'); if (visible) {
else el.classList.add('cache'); 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() { function resetPreviewTimeout() {
@@ -111,7 +128,10 @@ async function lancerCapture() {
} }
if (resultat.erreur) { 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; return;
} }