Files
serv_benchmark/backend/app/utils/usb_parser.py
Gilles Soulier c67befc549 addon
2026-01-05 16:08:01 +01:00

349 lines
12 KiB
Python
Executable File

"""
Linux BenchTools - USB Device Parser
Parses output from 'lsusb -v' command
"""
import re
from typing import Dict, Any, Optional, List
def parse_lsusb_verbose(lsusb_output: str) -> Dict[str, Any]:
"""
Parse the output of 'lsusb -v' command
Args:
lsusb_output: Raw text output from 'lsusb -v' command
Returns:
Dictionary with parsed USB device information
"""
result = {
"vendor_id": None,
"product_id": None,
"usb_device_id": None,
"marque": None,
"modele": None,
"fabricant": None,
"produit": None,
"numero_serie": None,
"usb_version": None,
"device_class": None,
"device_subclass": None,
"device_protocol": None,
"max_power_ma": None,
"speed": None,
"manufacturer": None,
"product": None,
"interfaces": [],
"raw_info": {}
}
lines = lsusb_output.strip().split('\n')
current_interface = None
for line in lines:
# Bus and Device info
# Example: Bus 002 Device 003: ID 0781:5567 SanDisk Corp. Cruzer Blade
match = re.match(r'Bus\s+(\d+)\s+Device\s+(\d+):\s+ID\s+([0-9a-f]{4}):([0-9a-f]{4})\s+(.*)', line)
if match:
result["raw_info"]["bus"] = match.group(1)
result["raw_info"]["device"] = match.group(2)
result["vendor_id"] = match.group(3)
result["product_id"] = match.group(4)
result["usb_device_id"] = f"{match.group(3)}:{match.group(4)}"
# Parse manufacturer and product from the description
desc = match.group(5)
parts = desc.split(' ', 1)
if len(parts) == 2:
result["marque"] = parts[0]
result["modele"] = parts[1]
else:
result["modele"] = desc
continue
# idVendor
match = re.search(r'idVendor\s+0x([0-9a-f]{4})\s+(.*)', line)
if match:
if not result["vendor_id"]:
result["vendor_id"] = match.group(1)
result["manufacturer"] = match.group(2).strip()
if not result["marque"]:
result["marque"] = result["manufacturer"]
if result.get("vendor_id") and result.get("product_id") and not result.get("usb_device_id"):
result["usb_device_id"] = f"{result['vendor_id']}:{result['product_id']}"
continue
# idProduct
match = re.search(r'idProduct\s+0x([0-9a-f]{4})\s+(.*)', line)
if match:
if not result["product_id"]:
result["product_id"] = match.group(1)
result["product"] = match.group(2).strip()
if not result["modele"]:
result["modele"] = result["product"]
if result.get("vendor_id") and result.get("product_id") and not result.get("usb_device_id"):
result["usb_device_id"] = f"{result['vendor_id']}:{result['product_id']}"
continue
# bcdUSB (USB version)
match = re.search(r'bcdUSB\s+([\d.]+)', line)
if match:
result["usb_version"] = match.group(1)
continue
# bDeviceClass
match = re.search(r'bDeviceClass\s+(\d+)\s+(.*)', line)
if match:
result["device_class"] = match.group(2).strip()
result["raw_info"]["device_class_code"] = match.group(1)
continue
# bDeviceSubClass
match = re.search(r'bDeviceSubClass\s+(\d+)\s*(.*)', line)
if match:
result["device_subclass"] = match.group(2).strip() if match.group(2) else match.group(1)
continue
# bDeviceProtocol
match = re.search(r'bDeviceProtocol\s+(\d+)\s*(.*)', line)
if match:
result["device_protocol"] = match.group(2).strip() if match.group(2) else match.group(1)
continue
# MaxPower
match = re.search(r'MaxPower\s+(\d+)mA', line)
if match:
result["max_power_ma"] = int(match.group(1))
continue
# iManufacturer
match = re.search(r'iManufacturer\s+\d+\s+(.*)', line)
if match and not result["manufacturer"]:
result["manufacturer"] = match.group(1).strip()
if not result["fabricant"]:
result["fabricant"] = result["manufacturer"]
continue
# iProduct
match = re.search(r'iProduct\s+\d+\s+(.*)', line)
if match and not result["product"]:
result["product"] = match.group(1).strip()
if not result["produit"]:
result["produit"] = result["product"]
continue
# iSerial
match = re.search(r'iSerial\s+\d+\s+(.*)', line)
if match:
serial = match.group(1).strip()
if serial and serial != "0":
result["numero_serie"] = serial
continue
# Speed (from Device Descriptor or Status)
match = re.search(r'Device Status:.*?Speed:\s*(\w+)', line)
if match:
result["speed"] = match.group(1)
continue
# Alternative speed detection
if "480M" in line or "high-speed" in line.lower() or "high speed" in line.lower():
result["speed"] = "High Speed (480 Mbps)"
elif "5000M" in line or "super-speed" in line.lower() or "super speed" in line.lower():
result["speed"] = "Super Speed (5 Gbps)"
elif "10000M" in line or "superspeed+" in line.lower():
result["speed"] = "SuperSpeed+ (10 Gbps)"
elif "12M" in line or "full-speed" in line.lower() or "full speed" in line.lower():
result["speed"] = "Full Speed (12 Mbps)"
elif "1.5M" in line or "low-speed" in line.lower() or "low speed" in line.lower():
result["speed"] = "Low Speed (1.5 Mbps)"
# Interface information
match = re.search(r'Interface Descriptor:', line)
if match:
current_interface = {}
result["interfaces"].append(current_interface)
continue
if current_interface is not None:
# bInterfaceClass
match = re.search(r'bInterfaceClass\s+(\d+)\s+(.*)', line)
if match:
current_interface["class"] = match.group(2).strip()
current_interface["class_code"] = match.group(1)
continue
# bInterfaceSubClass
match = re.search(r'bInterfaceSubClass\s+(\d+)\s*(.*)', line)
if match:
current_interface["subclass"] = match.group(2).strip() if match.group(2) else match.group(1)
continue
# bInterfaceProtocol
match = re.search(r'bInterfaceProtocol\s+(\d+)\s*(.*)', line)
if match:
current_interface["protocol"] = match.group(2).strip() if match.group(2) else match.group(1)
continue
# Clean up empty values
for key in list(result.keys()):
if result[key] == "" or result[key] == "0":
result[key] = None
# Determine peripheral type from class
result["type_principal"] = _determine_peripheral_type(result)
result["sous_type"] = _determine_peripheral_subtype(result)
return result
def _determine_peripheral_type(usb_info: Dict[str, Any]) -> str:
"""Determine peripheral type from USB class information"""
device_class = (usb_info.get("device_class") or "").lower()
# Check interfaces if device class is not specific
if not device_class or "vendor specific" in device_class or device_class == "0":
interfaces = usb_info.get("interfaces", [])
if interfaces:
interface_class = (interfaces[0].get("class") or "").lower()
else:
interface_class = ""
else:
interface_class = device_class
# Map USB classes to peripheral types
class_map = {
"hub": "USB",
"audio": "Audio",
"hid": "USB",
"human interface device": "USB",
"printer": "Imprimante",
"mass storage": "Stockage",
"video": "Video",
"wireless": "Sans-fil",
"bluetooth": "Bluetooth",
"smart card": "Securite",
"application specific": "USB",
"vendor specific": "USB"
}
for key, ptype in class_map.items():
if key in interface_class:
return ptype
# Default
return "USB"
def _determine_peripheral_subtype(usb_info: Dict[str, Any]) -> Optional[str]:
"""Determine peripheral subtype from USB class information"""
device_class = (usb_info.get("device_class") or "").lower()
interfaces = usb_info.get("interfaces", [])
if interfaces:
interface_class = (interfaces[0].get("class") or "").lower()
interface_subclass = (interfaces[0].get("subclass") or "").lower()
else:
interface_class = ""
interface_subclass = ""
# HID devices
if "hid" in device_class or "hid" in interface_class or "human interface" in interface_class:
if "mouse" in interface_subclass or "mouse" in str(usb_info.get("modele", "")).lower():
return "Souris"
elif "keyboard" in interface_subclass or "keyboard" in str(usb_info.get("modele", "")).lower():
return "Clavier"
elif "gamepad" in interface_subclass or "joystick" in interface_subclass:
return "Manette"
else:
return "Peripherique HID"
# Mass storage
if "mass storage" in interface_class:
model = str(usb_info.get("modele", "")).lower()
if "card reader" in model or "reader" in model:
return "Lecteur de cartes"
else:
return "Cle USB"
# Audio
if "audio" in interface_class:
if "microphone" in interface_subclass:
return "Microphone"
elif "speaker" in interface_subclass:
return "Haut-parleur"
else:
return "Audio"
# Video
if "video" in interface_class:
return "Webcam"
# Wireless
if "wireless" in interface_class or "bluetooth" in interface_class:
if "bluetooth" in interface_class:
return "Bluetooth"
else:
return "Adaptateur sans-fil"
# Printer
if "printer" in interface_class:
return "Imprimante"
return None
def parse_lsusb_simple(lsusb_output: str) -> List[Dict[str, Any]]:
"""
Parse the output of simple 'lsusb' command (without -v)
Args:
lsusb_output: Raw text output from 'lsusb' command
Returns:
List of dictionaries with basic USB device information
"""
devices = []
for line in lsusb_output.strip().split('\n'):
# Example: Bus 002 Device 003: ID 0781:5567 SanDisk Corp. Cruzer Blade
match = re.match(r'Bus\s+(\d+)\s+Device\s+(\d+):\s+ID\s+([0-9a-f]{4}):([0-9a-f]{4})\s+(.*)', line)
if match:
desc = match.group(5)
parts = desc.split(' ', 1)
device = {
"bus": match.group(1),
"device": match.group(2),
"vendor_id": match.group(3),
"product_id": match.group(4),
"marque": parts[0] if len(parts) >= 1 else None,
"modele": parts[1] if len(parts) == 2 else desc,
"type_principal": "USB",
"sous_type": None
}
devices.append(device)
return devices
def create_device_name(usb_info: Dict[str, Any]) -> str:
"""Generate a readable device name from USB info"""
parts = []
if usb_info.get("marque"):
parts.append(usb_info["marque"])
if usb_info.get("modele"):
parts.append(usb_info["modele"])
if not parts:
parts.append("Peripherique USB")
if usb_info.get("vendor_id") and usb_info.get("product_id"):
parts.append(f"({usb_info['vendor_id']}:{usb_info['product_id']})")
return " ".join(parts)