addon
This commit is contained in:
348
backend/app/utils/usb_parser.py
Executable file
348
backend/app/utils/usb_parser.py
Executable file
@@ -0,0 +1,348 @@
|
||||
"""
|
||||
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)
|
||||
Reference in New Issue
Block a user