From e4e5ee05d4f27f1aa34ea1c71a01939ffa1a692d Mon Sep 17 00:00:00 2001 From: Jules Date: Wed, 10 Jun 2026 14:02:29 +0200 Subject: [PATCH] =?UTF-8?q?surveiller=5Fdslr=20:=20reset=20USB=20automatiq?= =?UTF-8?q?ue=20apr=C3=A8s=203=20=C3=A9checs=20de=20connexion=20Canon?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Quand gphoto2 détecte le DSLR mais que camera.init() timeout répétitivement (USB stall après kill -9 ou déconnexion brutale), on tente un reset USB ioctl (USBDEVFS_RESET sur /dev/bus/usb//) toutes les 3 tentatives (~15s). Évite de rester bloqué indéfiniment sur "Timeout reading from or writing to the port". Co-Authored-By: Claude Sonnet 4.6 --- backend/main.py | 31 ++++++++++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/backend/main.py b/backend/main.py index bdcd38a..e4943ec 100644 --- a/backend/main.py +++ b/backend/main.py @@ -97,9 +97,29 @@ async def _pusher_preview(): 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}"}) +def _usb_reset_canon(): + """Reset USB ioctl du Canon EOS (vendor 04a9) pour débloquer un port stall.""" + import fcntl, glob + USBDEVFS_RESET = 0x5514 + try: + for f in glob.glob("/sys/bus/usb/devices/*/idVendor"): + if open(f).read().strip() == "04a9": + d = __import__("os.path", fromlist=["dirname"]).dirname(f) + bus = open(d + "/busnum").read().strip().zfill(3) + dev = open(d + "/devnum").read().strip().zfill(3) + path = f"/dev/bus/usb/{bus}/{dev}" + with open(path, "wb") as fh: + fcntl.ioctl(fh, USBDEVFS_RESET, 0) + log.info(f"USB reset Canon: {path}") + return + except Exception as e: + log.debug(f"_usb_reset_canon: {e}") + + async def surveiller_dslr(): """Surveille le DSLR en continu : detecte les deconnexions et reconnecte automatiquement.""" global _dslr_erreurs + _echecs_connexion = 0 while True: await asyncio.sleep(5) if capture_en_cours: @@ -111,6 +131,7 @@ async def surveiller_dslr(): # Le thread preview capture en permanence — preview_dslr_ok reflète la santé réelle if camera.preview_dslr_ok: _dslr_erreurs = 0 + _echecs_connexion = 0 else: _dslr_erreurs += 1 if _dslr_erreurs >= 3: # 3 x 5s = 15s sans preview valide @@ -122,6 +143,7 @@ async def surveiller_dslr(): camera.connecter(source="gphoto2") if camera.connectee: log.info("DSLR reconnecte avec succes") + _echecs_connexion = 0 await diffuser_ws({"type": "camera_ok"}) else: log.warning("Echec reconnexion DSLR — nouvelle tentative dans 5s") @@ -133,9 +155,16 @@ async def surveiller_dslr(): camera.connecter(source="gphoto2") if camera.connectee: log.info("DSLR connecte avec succes") + _echecs_connexion = 0 await diffuser_ws({"type": "camera_ok"}) else: - log.warning("Echec connexion DSLR detecte") + _echecs_connexion += 1 + log.warning(f"Echec connexion DSLR ({_echecs_connexion})") + # Après 3 échecs consécutifs (~15s), reset USB pour débloquer un port stall + if _echecs_connexion % 3 == 0: + log.warning("Reset USB Canon tentative de deblocage...") + _usb_reset_canon() + await asyncio.sleep(3) except Exception as e: log.debug(f"surveiller_dslr: {e}")