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:
@@ -216,8 +216,9 @@ class Camera:
|
|||||||
|
|
||||||
def _preview_gphoto2(self) -> bytes | None:
|
def _preview_gphoto2(self) -> bytes | None:
|
||||||
try:
|
try:
|
||||||
fichier = self.camera.capture_preview()
|
with self._gp_lock:
|
||||||
donnees = bytes(fichier.get_data_and_size())
|
fichier = self.camera.capture_preview()
|
||||||
|
donnees = bytes(fichier.get_data_and_size())
|
||||||
img = cv2.imdecode(np.frombuffer(donnees, dtype=np.uint8), cv2.IMREAD_COLOR)
|
img = cv2.imdecode(np.frombuffer(donnees, dtype=np.uint8), cv2.IMREAD_COLOR)
|
||||||
if img is not None:
|
if img is not None:
|
||||||
h, w = img.shape[:2]
|
h, w = img.shape[:2]
|
||||||
|
|||||||
@@ -48,11 +48,16 @@ _preview_lock = threading.Lock()
|
|||||||
_loop_principal: asyncio.AbstractEventLoop | None = None # loop asyncio principal
|
_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():
|
def _thread_preview():
|
||||||
"""Thread de fond : capture les frames DSLR en continu et les stocke."""
|
"""Thread de fond : capture les frames DSLR en continu.
|
||||||
global _derniere_frame_preview, _preview_actif
|
Tourne TOUJOURS quand le DSLR est connecte pour maintenir le miroir leve."""
|
||||||
|
global _derniere_frame_preview
|
||||||
while True:
|
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)
|
time.sleep(0.05)
|
||||||
continue
|
continue
|
||||||
try:
|
try:
|
||||||
@@ -67,20 +72,26 @@ def _thread_preview():
|
|||||||
|
|
||||||
async def _pusher_preview():
|
async def _pusher_preview():
|
||||||
"""Tache asyncio : pousse la derniere frame a tous les clients WS abonnes."""
|
"""Tache asyncio : pousse la derniere frame a tous les clients WS abonnes."""
|
||||||
global _preview_actif
|
global _preview_actif, _preview_none_count
|
||||||
while True:
|
while True:
|
||||||
await asyncio.sleep(0.05) # 20 fps max envoye aux clients
|
await asyncio.sleep(0.05) # 20 fps max envoye aux clients
|
||||||
|
if not _preview_actif:
|
||||||
|
_preview_none_count = 0
|
||||||
|
continue
|
||||||
if not clients_ws:
|
if not clients_ws:
|
||||||
_preview_actif = False
|
_preview_actif = False
|
||||||
continue
|
continue
|
||||||
with _preview_lock:
|
with _preview_lock:
|
||||||
donnees = _derniere_frame_preview
|
donnees = _derniere_frame_preview
|
||||||
if donnees is None:
|
if donnees is None:
|
||||||
# Signaler l'erreur si le DSLR est sense etre connecte
|
_preview_none_count += 1
|
||||||
if camera.mode == "gphoto2" and _preview_actif:
|
# 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"})
|
await diffuser_ws({"type": "camera_erreur", "message": "DSLR ne repond pas au preview"})
|
||||||
_preview_actif = False
|
_preview_actif = False
|
||||||
|
_preview_none_count = 0
|
||||||
continue
|
continue
|
||||||
|
_preview_none_count = 0
|
||||||
loop = asyncio.get_event_loop()
|
loop = asyncio.get_event_loop()
|
||||||
b64 = await loop.run_in_executor(None, lambda d=donnees: base64.b64encode(d).decode("ascii"))
|
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}"})
|
await diffuser_ws({"type": "preview", "image": f"data:image/jpeg;base64,{b64}"})
|
||||||
@@ -90,7 +101,8 @@ async def surveiller_dslr():
|
|||||||
global _dslr_erreurs
|
global _dslr_erreurs
|
||||||
while True:
|
while True:
|
||||||
await asyncio.sleep(5)
|
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
|
continue
|
||||||
try:
|
try:
|
||||||
if GPHOTO2_DISPONIBLE:
|
if GPHOTO2_DISPONIBLE:
|
||||||
|
|||||||
Reference in New Issue
Block a user