Escalade DSLR recovery : uhubctl power cycle + backoff 60s après 10 échecs
Niveaux : ioctl reset (2) -> unbind/rebind (3-5) -> uhubctl 5V cut (6+) -> backoff 60s (10+) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -151,13 +151,50 @@ def _usb_rebind_canon():
|
||||
return False
|
||||
|
||||
|
||||
def _usb_power_cycle_canon():
|
||||
"""Coupe et retablit l'alimentation 5V du port USB Canon via uhubctl.
|
||||
Necessite un hub USB compatible power switching (Realtek, etc.)."""
|
||||
import glob, subprocess, time
|
||||
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)
|
||||
)
|
||||
if "." in dev_id:
|
||||
hub = dev_id.rsplit(".", 1)[0]
|
||||
port = dev_id.rsplit(".", 1)[1]
|
||||
else:
|
||||
hub = dev_id.rsplit("-", 1)[0]
|
||||
port = dev_id.rsplit("-", 1)[1]
|
||||
log.info(f"uhubctl power OFF: hub={hub} port={port}")
|
||||
subprocess.run(
|
||||
["sudo", "uhubctl", "-l", hub, "-p", port, "-a", "off"],
|
||||
timeout=10, capture_output=True,
|
||||
)
|
||||
time.sleep(8)
|
||||
subprocess.run(
|
||||
["sudo", "uhubctl", "-l", hub, "-p", port, "-a", "on"],
|
||||
timeout=10, capture_output=True,
|
||||
)
|
||||
log.info(f"uhubctl power ON: hub={hub} port={port}")
|
||||
time.sleep(8)
|
||||
return True
|
||||
except Exception as e:
|
||||
log.warning(f"_usb_power_cycle_canon: {e}")
|
||||
return False
|
||||
|
||||
|
||||
async def surveiller_dslr():
|
||||
"""Surveille le DSLR en continu : detecte les deconnexions et reconnecte automatiquement.
|
||||
Apres plusieurs echecs, fait un reset USB ioctl pour debloquer le port PTP."""
|
||||
Escalade : ioctl reset -> unbind/rebind -> uhubctl power cycle -> backoff long."""
|
||||
global _dslr_erreurs
|
||||
_echecs_connexion = 0
|
||||
while True:
|
||||
await asyncio.sleep(5)
|
||||
if _echecs_connexion >= 10:
|
||||
await asyncio.sleep(60)
|
||||
else:
|
||||
await asyncio.sleep(5)
|
||||
if capture_en_cours:
|
||||
continue
|
||||
try:
|
||||
@@ -175,7 +212,6 @@ async def surveiller_dslr():
|
||||
_echecs_connexion += 1
|
||||
await _reconnecter_dslr_avec_reset(_echecs_connexion)
|
||||
else:
|
||||
# Non connecte : chercher et connecter un DSLR disponible
|
||||
dslrs = gp.Camera.autodetect()
|
||||
if len(dslrs) > 0:
|
||||
_echecs_connexion += 1
|
||||
@@ -187,13 +223,19 @@ async def surveiller_dslr():
|
||||
|
||||
async def _reconnecter_dslr_avec_reset(echecs: int):
|
||||
"""Reconnexion DSLR avec escalade progressive :
|
||||
1-2 : ioctl reset
|
||||
3+ : unbind/rebind USB (simule debranchement physique)
|
||||
1 : simple reconnexion
|
||||
2 : ioctl reset
|
||||
3-5 : unbind/rebind USB
|
||||
6+ : uhubctl power cycle (coupe 5V du port)
|
||||
10+ : backoff 60s entre tentatives
|
||||
"""
|
||||
camera.deconnecter()
|
||||
await diffuser_ws({"type": "camera_erreur", "message": "Appareil photo deconnecte"})
|
||||
|
||||
if echecs >= 3:
|
||||
if echecs >= 6:
|
||||
log.info(f"USB power cycle Canon (echec #{echecs})...")
|
||||
await asyncio.get_event_loop().run_in_executor(None, _usb_power_cycle_canon)
|
||||
elif 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:
|
||||
|
||||
Reference in New Issue
Block a user