Fix preview + miroir : thread continu, grace period, lock gphoto2

- _thread_preview tourne TOUJOURS quand DSLR connecté (plus de condition
  _preview_actif) → capture_preview() appelé en continu → miroir reste levé
- Grace period 15 frames (~500ms) avant de signaler camera_erreur : évite
  la race condition qui tuait le preview avant la 1ère frame
- _preview_gphoto2 protégé par _gp_lock (accès concurrent safe)
- surveiller_dslr ne tourne plus quand le DSLR est en mode gphoto2 connecté

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-06-05 23:01:25 +02:00
parent 0a918666b3
commit 1364b62297
2 changed files with 22 additions and 9 deletions

View File

@@ -216,6 +216,7 @@ class Camera:
def _preview_gphoto2(self) -> bytes | None:
try:
with self._gp_lock:
fichier = self.camera.capture_preview()
donnees = bytes(fichier.get_data_and_size())
img = cv2.imdecode(np.frombuffer(donnees, dtype=np.uint8), cv2.IMREAD_COLOR)

View File

@@ -48,11 +48,16 @@ _preview_lock = threading.Lock()
_loop_principal: asyncio.AbstractEventLoop | None = None # loop asyncio principal
_preview_none_count = 0
_PREVIEW_GRACE_FRAMES = 15 # ~500ms avant de declarer une erreur
def _thread_preview():
"""Thread de fond : capture les frames DSLR en continu et les stocke."""
global _derniere_frame_preview, _preview_actif
"""Thread de fond : capture les frames DSLR en continu.
Tourne TOUJOURS quand le DSLR est connecte pour maintenir le miroir leve."""
global _derniere_frame_preview
while True:
if not _preview_actif or capture_en_cours:
if capture_en_cours or camera.mode != "gphoto2" or not camera.connectee:
time.sleep(0.05)
continue
try:
@@ -67,20 +72,26 @@ def _thread_preview():
async def _pusher_preview():
"""Tache asyncio : pousse la derniere frame a tous les clients WS abonnes."""
global _preview_actif
global _preview_actif, _preview_none_count
while True:
await asyncio.sleep(0.05) # 20 fps max envoye aux clients
if not _preview_actif:
_preview_none_count = 0
continue
if not clients_ws:
_preview_actif = False
continue
with _preview_lock:
donnees = _derniere_frame_preview
if donnees is None:
# Signaler l'erreur si le DSLR est sense etre connecte
if camera.mode == "gphoto2" and _preview_actif:
_preview_none_count += 1
# Grace period : attendre plusieurs frames avant de signaler l'erreur
if camera.mode == "gphoto2" and _preview_none_count > _PREVIEW_GRACE_FRAMES:
await diffuser_ws({"type": "camera_erreur", "message": "DSLR ne repond pas au preview"})
_preview_actif = False
_preview_none_count = 0
continue
_preview_none_count = 0
loop = asyncio.get_event_loop()
b64 = await loop.run_in_executor(None, lambda d=donnees: base64.b64encode(d).decode("ascii"))
await diffuser_ws({"type": "preview", "image": f"data:image/jpeg;base64,{b64}"})
@@ -90,7 +101,8 @@ async def surveiller_dslr():
global _dslr_erreurs
while True:
await asyncio.sleep(5)
if capture_en_cours or _preview_actif:
# Ne pas interférer pendant que le thread preview utilise la camera
if capture_en_cours or (camera.mode == "gphoto2" and camera.connectee):
continue
try:
if GPHOTO2_DISPONIBLE: