USB rebind Canon : unbind/rebind sysfs pour recovery hard DSLR PTP freeze

Escalade progressive : ioctl reset (tentative 2), unbind/rebind USB (tentative 3+)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-06-23 08:35:03 +02:00
parent 97e3f6508d
commit 29a7db028e

View File

@@ -123,6 +123,30 @@ def _usb_reset_canon():
log.debug(f"_usb_reset_canon: {e}") log.debug(f"_usb_reset_canon: {e}")
def _usb_rebind_canon():
"""Unbind/rebind USB Canon — simule un debranchement/rebranchement physique."""
import glob
try:
for f in glob.glob("/sys/bus/usb/devices/*/idVendor"):
if open(f).read().strip() == "04a9":
dev_id = __import__("os.path", fromlist=["basename"]).basename(
__import__("os.path", fromlist=["dirname"]).dirname(f)
)
log.info(f"USB rebind Canon: unbind {dev_id}")
with open("/sys/bus/usb/drivers/usb/unbind", "w") as fh:
fh.write(dev_id)
import time
time.sleep(2)
with open("/sys/bus/usb/drivers/usb/bind", "w") as fh:
fh.write(dev_id)
log.info(f"USB rebind Canon: bind {dev_id}")
time.sleep(3)
return True
except Exception as e:
log.warning(f"_usb_rebind_canon: {e}")
return False
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.
Apres plusieurs echecs, fait un reset USB ioctl pour debloquer le port PTP.""" Apres plusieurs echecs, fait un reset USB ioctl pour debloquer le port PTP."""
@@ -158,11 +182,20 @@ async def surveiller_dslr():
async def _reconnecter_dslr_avec_reset(echecs: int): async def _reconnecter_dslr_avec_reset(echecs: int):
"""Reconnexion DSLR avec reset USB progressif.""" """Reconnexion DSLR avec escalade progressive :
1-2 : ioctl reset
3+ : unbind/rebind USB (simule debranchement physique)
"""
camera.deconnecter() camera.deconnecter()
await diffuser_ws({"type": "camera_erreur", "message": "Appareil photo deconnecte"}) await diffuser_ws({"type": "camera_erreur", "message": "Appareil photo deconnecte"})
if echecs >= 2: if echecs >= 3:
log.info(f"USB rebind Canon (echec #{echecs})...")
ok = await asyncio.get_event_loop().run_in_executor(None, _usb_rebind_canon)
if not ok:
_usb_reset_canon()
await asyncio.sleep(4)
elif echecs >= 2:
log.info(f"Reset USB Canon (echec #{echecs})...") log.info(f"Reset USB Canon (echec #{echecs})...")
_usb_reset_canon() _usb_reset_canon()
await asyncio.sleep(4) await asyncio.sleep(4)
@@ -176,15 +209,6 @@ async def _reconnecter_dslr_avec_reset(echecs: int):
await diffuser_ws({"type": "camera_ok"}) await diffuser_ws({"type": "camera_ok"})
else: else:
log.warning(f"Echec reconnexion DSLR (tentative {echecs})") log.warning(f"Echec reconnexion DSLR (tentative {echecs})")
if echecs >= 3:
log.info("Reset USB force supplementaire...")
_usb_reset_canon()
await asyncio.sleep(5)
camera.connecter(source="gphoto2")
if camera.connectee:
log.info("DSLR reconnecte apres double reset")
_appliquer_config_camera()
await diffuser_ws({"type": "camera_ok"})
def _appliquer_config_camera(): def _appliquer_config_camera():