Update sdk/platform-tools to version 26.0.0.
[android_tools.git] / sdk / platform-tools / systrace / catapult / devil / devil / utils / lsusb.py
blob6cbf2567b984cf14d41409c1c9effdeda42ebcfe
1 # Copyright 2015 The Chromium Authors. All rights reserved.
2 # Use of this source code is governed by a BSD-style license that can be
3 # found in the LICENSE file.
5 import logging
6 import re
8 from devil.utils import cmd_helper
10 logger = logging.getLogger(__name__)
12 _COULDNT_OPEN_ERROR_RE = re.compile(r'Couldn\'t open device.*')
13 _INDENTATION_RE = re.compile(r'^( *)')
14 _LSUSB_BUS_DEVICE_RE = re.compile(r'^Bus (\d{3}) Device (\d{3}): (.*)')
15 _LSUSB_ENTRY_RE = re.compile(r'^ *([^ ]+) +([^ ]+) *([^ ].*)?$')
16 _LSUSB_GROUP_RE = re.compile(r'^ *([^ ]+.*):$')
19 def _lsusbv_on_device(bus_id, dev_id):
20 """Calls lsusb -v on device."""
21 _, raw_output = cmd_helper.GetCmdStatusAndOutputWithTimeout(
22 ['lsusb', '-v', '-s', '%s:%s' % (bus_id, dev_id)], timeout=10)
24 device = {'bus': bus_id, 'device': dev_id}
25 depth_stack = [device]
27 # This builds a nested dict -- a tree, basically -- that corresponds
28 # to the lsusb output. It looks first for a line containing
30 # "Bus <bus number> Device <device number>: ..."
32 # and uses that to create the root node. It then parses all remaining
33 # lines as a tree, with the indentation level determining the
34 # depth of the new node.
36 # This expects two kinds of lines:
37 # - "groups", which take the form
38 # "<Group name>:"
39 # and typically have children, and
40 # - "entries", which take the form
41 # "<entry name> <entry value> <possible entry description>"
42 # and typically do not have children (but can).
44 # This maintains a stack containing all current ancestor nodes in
45 # order to add new nodes to the proper place in the tree.
46 # The stack is added to when a new node is parsed. Nodes are removed
47 # from the stack when they are either at the same indentation level as
48 # or a deeper indentation level than the current line.
50 # e.g. the following lsusb output:
52 # Bus 123 Device 456: School bus
53 # Device Descriptor:
54 # bDeviceClass 5 Actual School Bus
55 # Configuration Descriptor:
56 # bLength 20 Rows
58 # would produce the following dict:
60 # {
61 # 'bus': 123,
62 # 'device': 456,
63 # 'desc': 'School bus',
64 # 'Device Descriptor': {
65 # 'bDeviceClass': {
66 # '_value': '5',
67 # '_desc': 'Actual School Bus',
68 # },
69 # 'Configuration Descriptor': {
70 # 'bLength': {
71 # '_value': '20',
72 # '_desc': 'Rows',
73 # },
74 # },
75 # }
76 # }
77 for line in raw_output.splitlines():
78 # Ignore blank lines.
79 if not line:
80 continue
81 # Filter out error mesage about opening device.
82 if _COULDNT_OPEN_ERROR_RE.match(line):
83 continue
84 # Find start of device information.
85 m = _LSUSB_BUS_DEVICE_RE.match(line)
86 if m:
87 if m.group(1) != bus_id:
88 logger.warning(
89 'Expected bus_id value: %r, seen %r', bus_id, m.group(1))
90 if m.group(2) != dev_id:
91 logger.warning(
92 'Expected dev_id value: %r, seen %r', dev_id, m.group(2))
93 device['desc'] = m.group(3)
94 continue
96 # Skip any lines that aren't indented, as they're not part of the
97 # device descriptor.
98 indent_match = _INDENTATION_RE.match(line)
99 if not indent_match:
100 continue
102 # Determine the indentation depth.
103 depth = 1 + len(indent_match.group(1)) / 2
104 if depth > len(depth_stack):
105 logger.error(
106 'lsusb parsing error: unexpected indentation: "%s"', line)
107 continue
109 # Pop everything off the depth stack that isn't a parent of
110 # this element.
111 while depth < len(depth_stack):
112 depth_stack.pop()
114 cur = depth_stack[-1]
116 m = _LSUSB_GROUP_RE.match(line)
117 if m:
118 new_group = {}
119 cur[m.group(1)] = new_group
120 depth_stack.append(new_group)
121 continue
123 m = _LSUSB_ENTRY_RE.match(line)
124 if m:
125 new_entry = {
126 '_value': m.group(2),
127 '_desc': m.group(3),
129 cur[m.group(1)] = new_entry
130 depth_stack.append(new_entry)
131 continue
133 logger.error('lsusb parsing error: unrecognized line: "%s"', line)
135 return device
137 def lsusb():
138 """Call lsusb and return the parsed output."""
139 _, lsusb_list_output = cmd_helper.GetCmdStatusAndOutputWithTimeout(
140 ['lsusb'], timeout=10)
141 devices = []
142 for line in lsusb_list_output.splitlines():
143 m = _LSUSB_BUS_DEVICE_RE.match(line)
144 if m:
145 bus_num = m.group(1)
146 dev_num = m.group(2)
147 try:
148 devices.append(_lsusbv_on_device(bus_num, dev_num))
149 except cmd_helper.TimeoutError:
150 # Will be blacklisted if it is in expected device file, but times out.
151 logger.info('lsusb -v %s:%s timed out.', bus_num, dev_num)
152 return devices
154 def raw_lsusb():
155 return cmd_helper.GetCmdOutput(['lsusb'])
157 def get_lsusb_serial(device):
158 try:
159 return device['Device Descriptor']['iSerial']['_desc']
160 except KeyError:
161 return None
163 def _is_android_device(device):
164 try:
165 # Hubs are not android devices.
166 if device['Device Descriptor']['bDeviceClass']['_value'] == '9':
167 return False
168 except KeyError:
169 pass
170 return get_lsusb_serial(device) is not None
172 def get_android_devices():
173 android_devices = (d for d in lsusb() if _is_android_device(d))
174 return [get_lsusb_serial(d) for d in android_devices]