surveiller_dslr : reset USB automatique après 3 échecs de connexion Canon

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/<bus>/<dev>) 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 <noreply@anthropic.com>
This commit is contained in:
2026-06-10 14:02:29 +02:00
parent 4f12f77106
commit e4e5ee05d4

View File

@@ -97,9 +97,29 @@ async def _pusher_preview():
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}"})
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(): async def surveiller_dslr():
"""Surveille le DSLR en continu : detecte les deconnexions et reconnecte automatiquement.""" """Surveille le DSLR en continu : detecte les deconnexions et reconnecte automatiquement."""
global _dslr_erreurs global _dslr_erreurs
_echecs_connexion = 0
while True: while True:
await asyncio.sleep(5) await asyncio.sleep(5)
if capture_en_cours: 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 # Le thread preview capture en permanence — preview_dslr_ok reflète la santé réelle
if camera.preview_dslr_ok: if camera.preview_dslr_ok:
_dslr_erreurs = 0 _dslr_erreurs = 0
_echecs_connexion = 0
else: else:
_dslr_erreurs += 1 _dslr_erreurs += 1
if _dslr_erreurs >= 3: # 3 x 5s = 15s sans preview valide if _dslr_erreurs >= 3: # 3 x 5s = 15s sans preview valide
@@ -122,6 +143,7 @@ async def surveiller_dslr():
camera.connecter(source="gphoto2") camera.connecter(source="gphoto2")
if camera.connectee: if camera.connectee:
log.info("DSLR reconnecte avec succes") log.info("DSLR reconnecte avec succes")
_echecs_connexion = 0
await diffuser_ws({"type": "camera_ok"}) await diffuser_ws({"type": "camera_ok"})
else: else:
log.warning("Echec reconnexion DSLR — nouvelle tentative dans 5s") log.warning("Echec reconnexion DSLR — nouvelle tentative dans 5s")
@@ -133,9 +155,16 @@ async def surveiller_dslr():
camera.connecter(source="gphoto2") camera.connecter(source="gphoto2")
if camera.connectee: if camera.connectee:
log.info("DSLR connecte avec succes") log.info("DSLR connecte avec succes")
_echecs_connexion = 0
await diffuser_ws({"type": "camera_ok"}) await diffuser_ws({"type": "camera_ok"})
else: 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: except Exception as e:
log.debug(f"surveiller_dslr: {e}") log.debug(f"surveiller_dslr: {e}")