#!/usr/bin/python

import re

def _hexstring2int(str):
	i = 0
	while str[i] == ' ':
		i = i + 1
	return eval(r'0x' + str[i:])

_parse_interface_descriptor_line_regex = re.compile(
	r'.*If#=(?P<interface_number>\s*\S*)' + \
	r'\s*Alt=(?P<alternate_setting_number>\s*\S*)' + \
	r'\s*#EPs=(?P<number_of_endpoints>\s*\S*)' + \
	r'\s*Cls=(?P<interface_class_number>[^(]*)\((?P<interface_class_name>[^)]*)\)' + \
	r'\s*Sub=(?P<interface_subclass>\s*\S*)' + \
	r'\s*Prot=(?P<interface_protocol>\s*\S*)' + \
	r'\s*Driver=(?P<interface_driver_name>\s*\S*)'
)
def _parse_interface_descriptor_line(line):
	m_dict = _parse_interface_descriptor_line_regex.search(line).groupdict()
	return {
		'interface_number'		: int(m_dict['interface_number']),
		'alternate_setting_number'	: int(m_dict['alternate_setting_number']),
		'number_of_endpoints'		: int(m_dict['number_of_endpoints']),
		'interface_class'		: (	_hexstring2int(m_dict['interface_class_number']),
							m_dict['interface_class_name']),
		'interface_subclass'		: _hexstring2int(m_dict['interface_subclass']),
		'interface_protocol'		: _hexstring2int(m_dict['interface_protocol']),
		'interface_driver_name'		: m_dict['interface_driver_name']
	}

_parse_string_descriptor_line_regex = re.compile(
	r'S:\s*(?P<key>\S*)=(?P<value>.*)'
)
def _parse_string_descriptor_line(line):
	m_dict = _parse_string_descriptor_line_regex.search(line).groupdict()
	return { m_dict['key'] : m_dict['value'] }

_parse_product_id_line_regex = re.compile(
	r'.*Vendor=(?P<vendor_id>\s*\S*)' + \
	r'\s*ProdID=(?P<product_id>\s*\S*)' + \
	r'\s*Rev=(?P<product_revision_a>[^.]*).(?P<product_revision_b>\S*)'
)
def _parse_product_id_line(line):
	m_dict = _parse_product_id_line_regex.search(line).groupdict()
	return {
		'vendor_id'			: _hexstring2int(m_dict['vendor_id']),
		'product_id'			: _hexstring2int(m_dict['product_id']),
		'product_revision'		: (	_hexstring2int(m_dict['product_revision_a']),
							_hexstring2int(m_dict['product_revision_b']))
	}

_parse_topology_line_regex = re.compile(
	r'.*Bus=(?P<bus_number>\s*\S*)' + \
	r'\s*Lev=(?P<level>\s*\S*)' + \
	r'\s*Prnt=(?P<parent_device_number>\s*\S*)' + \
	r'\s*Port=(?P<parent_connection_port>\s*\S*)' + \
	r'\s*Cnt=(?P<sibling_count>\s*\S*)' + \
	r'\s*Dev#=(?P<device_number>\s*\S*)' + \
	r'\s*Spd=(?P<device_speed>\s*\S*)' + \
	r'\s*MxCh=(?P<max_children>\s*\S*)'
)
def _parse_topology_line(line):
	m_dict = _parse_topology_line_regex.search(line).groupdict()
	return {
		'bus_number'			: int(m_dict['bus_number']),
		'level'				: int(m_dict['level']),
		'parent_device_number'		: int(m_dict['parent_device_number']),
		'parent_connection_port'	: int(m_dict['parent_connection_port']),
		'sibling_count'			: int(m_dict['sibling_count']),
		'device_number'			: int(m_dict['device_number']),
		'device_speed'			: float(m_dict['device_speed']),
		'max_children'			: int(m_dict['max_children']),
	}

_parse_endpoint_line_regex = re.compile(
	r'.*Ad=(?P<endpoint_address_number>[^(]*)\((?P<endpoint_address_char>\S)\)' + \
	r'\s*Atr=(?P<attributes_number>[^(]*)\((?P<attributes_string>[^)]*)\)' + \
	r'\s*MxPS=(?P<endpoint_max_packet_size>\s*\S*)' + \
	r'\s*Ivl=(?P<interval>\s*\S*)ms'
)
def _parse_endpoint_line(line):
	m_dict = _parse_endpoint_line_regex.search(line).groupdict()
	return {
		'endpoint_address'		: (	int(m_dict['endpoint_address_number']),
							m_dict['endpoint_address_char']),
		'attributes'			: (	int(m_dict['attributes_number']),
							m_dict['attributes_string']),
		'endpoint_max_packet_size'	: int(m_dict['endpoint_max_packet_size']),
		'interval'			: int(m_dict['interval']),
	}

_parse_device_descriptor_line_regex = re.compile(
	r'.*Ver=(?P<device_usb_version_a>\s*\S*)\.(?P<device_usb_version_b>\s*\S*)' + \
	r'\s*Cls=(?P<device_class_number>[^(]*)\((?P<device_class_name>[^)]*)\)' + \
	r'\s*Sub=(?P<device_subclass>\s*\S*)' + \
	r'\s*Prot=(?P<device_protocol>\s*\S*)' + \
	r'\s*MxPS=(?P<default_endpoint_max_packet_size>\s*\S*)' + \
	r'\s*#Cfgs=(?P<number_of_configs>\s*\S*)'
)
def _parse_device_descriptor_line(line):
	m_dict = _parse_device_descriptor_line_regex.search(line).groupdict()
	return {
		'device_usb_version'		: (	_hexstring2int(m_dict['device_usb_version_a']),
							_hexstring2int(m_dict['device_usb_version_b'])),
		'device_class'			: (	_hexstring2int(m_dict['device_class_number']),
							m_dict['device_class_name']),
		'device_subclass'		: _hexstring2int(m_dict['device_subclass']),
		'device_protocol'		: _hexstring2int(m_dict['device_protocol']),
		'default_endpoint_max_packet_size'	: int(m_dict['default_endpoint_max_packet_size']),
		'number_of_configs'		: _hexstring2int(m_dict['number_of_configs']),
	}

_parse_bandwidth_line_regex = re.compile(
	r'.*Alloc=(?P<allocation_a>[^/]*)/(?P<allocation_b>\s*\S*)\s*us\s*\((?P<allocation_percent>\s*\S*)%\),' + \
	r'\s*#Int=(?P<number_of_interupts>\s*\S*),' + \
	r'\s*#Iso=(?P<number_of_isochronous_requests>\s*\S*)'
)
def _parse_bandwidth_line(line):
	m_dict = _parse_bandwidth_line_regex.search(line).groupdict()
	return {
		'allocation'			: (	int(m_dict['allocation_a']),
							int(m_dict['allocation_b']),
							_hexstring2int(m_dict['allocation_percent'])),
		'number_of_interupts'		: int(m_dict['number_of_interupts']),
		'number_of_isochronous_requests': int(m_dict['number_of_isochronous_requests']),

	}
	
_parse_config_line_regex = re.compile(
	r'C:(?P<active>.)' + \
	r'\s*#Ifs=(?P<number_of_interfaces>\s*\S*)' + \
	r'\s*Cfg#=(?P<configuration_number>\s*\S*)' + \
	r'\s*Atr=(?P<attributes>\s*\S*)' + \
	r'\s*MxPwr=(?P<max_power>\s*\S*)mA'
)
def _parse_config_line(line):
	m_dict = _parse_config_line_regex.search(line).groupdict()
	return {
		'active'			: (m_dict['active'] == '*'),
		'number_of_interfaces'		: int(m_dict['number_of_interfaces']),
		'configuration_number'		: int(m_dict['configuration_number']),
		'attributes'			: _hexstring2int(m_dict['attributes']),
		'max_power'			: int(m_dict['max_power'])
	}

def scan_usb_devices():
	lines = open("/proc/bus/usb/devices").readlines()

	devices = []
	interfaces = []

	def line_type(lines = lines):
		return lines[0][0]

	def pop_line(lines = lines):
		return lines.pop(0)

	def scan_endpoint(lines=lines, line_type=line_type, pop_line=pop_line):
		if line_type() != 'E':
			raise ValueError, "Scaning endpoint, found '%s' line" % line_type()

		endpoint = _parse_endpoint_line(pop_line())

	def scan_interface(device=None, lines=lines, interfaces=interfaces, line_type=line_type,
			pop_line=pop_line, scan_endpoint=scan_endpoint):
		if line_type() != 'I':
			raise ValueError, "Scaning interface, found '%s' line" % line_type()

		interface = _parse_interface_descriptor_line(pop_line())
		interface['endpoints'] = []
		interface['device'] = device
		interfaces.append(interface)

		for i in range(interface['number_of_endpoints']):
			interface['endpoints'].append(scan_endpoint())

		return interface

	def scan_config(device, lines=lines, line_type=line_type,
			pop_line=pop_line, scan_interface=scan_interface):
		if line_type() != 'C':
			raise ValueError, "Scaning config, found '%s' line" % line_type()

		config = _parse_config_line(pop_line())
		config['interfaces'] = []

		for i in range(config['number_of_interfaces']):
			config['interfaces'].append(scan_interface(device))

	device = None
	while len(lines):
		if line_type() == "T":
			device = _parse_topology_line(pop_line())
			device.update({
				'configs'	: [],
			})
			device['configs'] = []
			devices.append(device)
		elif line_type() == 'B':
			device.update(_parse_bandwidth_line(pop_line()))
		elif line_type() == 'D':
			device.update(_parse_device_descriptor_line(pop_line()))
		elif line_type() == 'P':
			device.update(_parse_product_id_line(pop_line()))
		elif line_type() == 'S':
			device.update(_parse_string_descriptor_line(pop_line()))
		elif line_type() == 'C':
			device['configs'].append(scan_config(device))
		elif line_type() == 'I':
			scan_interface()
		else:
			raise ValueError, "Expecting Topology line 'T', found '%s'" % line_type()

	return (devices, interfaces)

