• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#!/usr/bin/python
2# Copyright 2016 The Chromium Authors. All rights reserved.
3# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
5
6
7'''
8This script provides tools to map BattOrs to phones.
9
10Phones are identified by the following string:
11
12"Phone serial number" - Serial number of the phone. This can be
13obtained via 'adb devices' or 'usb-devices', and is not expected
14to change for a given phone.
15
16BattOrs are identified by the following two strings:
17
18"BattOr serial number" - Serial number of the BattOr. This can be
19obtained via 'usb-devices', and is not expected to change for
20a given BattOr.
21
22"BattOr path" - The path of the form '/dev/ttyUSB*' that is used
23to communicate with the BattOr (the battor_agent binary takes
24this BattOr path as a parameter). The BattOr path is frequently
25reassigned by the OS, most often when the device is disconnected
26and then reconnected. Thus, the BattOr path cannot be expected
27to be stable.
28
29In a typical application, the user will require the BattOr path
30for the BattOr that is plugged into a given phone. For instance,
31the user will be running tracing on a particular phone, and will
32need to know which BattOr path to use to communicate with the BattOr
33to get the corresponding power trace.
34
35Getting this mapping requires two steps: (1) determining the
36mapping between phone serial numbers and BattOr serial numbers, and
37(2) getting the BattOr path corresponding to a given BattOr serial
38number.
39
40For step (1), we generate a JSON file giving this mapping. This
41JSON file consists of a list of items of the following form:
42[{'phone': <phone serial 1>, 'battor': <battor serial 1>},
43{'phone': <phone serial 2>, 'battor': <battor serial 2>}, ...]
44
45The default way to generate this JSON file is using the function
46GenerateSerialMapFile, which generates a mapping based on assuming
47that the system has two identical USB hubs connected to it, and
48the phone plugged into physical port number 1 on one hub corresponds
49to the BattOr plugged into physical port number 1 on the other hub,
50and similarly with physical port numbers 2, 3, etc. This generates
51the map file based on the structure at the time GenerateSerialMapFile called.
52Note that after the map file is generated, port numbers are no longer used;
53the user could move around the devices in the ports without affecting
54which phone goes with which BattOr. (Thus, if the user wanted to update the
55mapping to match the new port connections, the user would have to
56re-generate this file.)
57
58The script update_mapping.py will do this updating from the command line.
59
60If the user wanted to specify a custom mapping, the user could instead
61create the JSON file manually. (In this case, hubs would not be necessary
62and the physical ports connected would be irrelevant.)
63
64Step (2) is conducted through the function GetBattOrPathFromPhoneSerial,
65which takes a serial number mapping generated via step (1) and a phone
66serial number, then gets the corresponding BattOr serial number from the
67map and determines its BattOr path (e.g. /dev/ttyUSB0). Since BattOr paths
68can change if devices are connected and disconnected (even if connected
69or disconnected via the same port) this function should be called to
70determine the BattOr path every time before connecting to the BattOr.
71
72Note that if there is only one BattOr connected to the system, then
73GetBattOrPathFromPhoneSerial will always return that BattOr and will ignore
74the mapping file. Thus, if the user never has more than one BattOr connected
75to the system, the user will not need to generate mapping files.
76'''
77
78
79import json
80import collections
81
82from battor import battor_error
83from devil.utils import find_usb_devices
84from devil.utils import usb_hubs
85
86
87def GetBattOrList(device_tree_map):
88  return [x for x in find_usb_devices.GetTTYList()
89          if IsBattOr(x, device_tree_map)]
90
91
92def IsBattOr(tty_string, device_tree_map):
93  (bus, device) = find_usb_devices.GetBusDeviceFromTTY(tty_string)
94  node = device_tree_map[bus].FindDeviceNumber(device)
95  return '0403:6001' in node.desc
96
97
98def GetBattOrSerialNumbers(device_tree_map):
99  for x in find_usb_devices.GetTTYList():
100    if IsBattOr(x, device_tree_map):
101      (bus, device) = find_usb_devices.GetBusDeviceFromTTY(x)
102      devnode = device_tree_map[bus].FindDeviceNumber(device)
103      yield devnode.serial
104
105
106def ReadSerialMapFile(filename):
107  """Reads JSON file giving phone-to-battor serial number map.
108
109  Parses a JSON file consisting of a list of items of the following form:
110  [{'phone': <phone serial 1>, 'battor': <battor serial 1>},
111  {'phone': <phone serial 2>, 'battor': <battor serial 2>}, ...]
112
113  indicating which phone serial numbers should be matched with
114  which BattOr serial numbers. Returns dictionary of the form:
115
116  {<phone serial 1>: <BattOr serial 1>,
117   <phone serial 2>: <BattOr serial 2>}
118
119  Args:
120      filename: Name of file to read.
121  """
122  result = {}
123  with open(filename, 'r') as infile:
124    in_dict = json.load(infile)
125  for x in in_dict:
126    result[x['phone']] = x['battor']
127  return result
128
129def WriteSerialMapFile(filename, serial_map):
130  """Writes a map of phone serial numbers to BattOr serial numbers to file.
131
132  Writes a JSON file consisting of a list of items of the following form:
133  [{'phone': <phone serial 1>, 'battor': <battor serial 1>},
134  {'phone': <phone serial 2>, 'battor': <battor serial 2>}, ...]
135
136  indicating which phone serial numbers should be matched with
137  which BattOr serial numbers. Mapping is based on the physical port numbers
138  of the hubs that the BattOrs and phones are connected to.
139
140  Args:
141      filename: Name of file to write.
142      serial_map: Serial map {phone: battor}
143  """
144  result = []
145  for (phone, battor) in serial_map.iteritems():
146    result.append({'phone': phone, 'battor': battor})
147  with open(filename, 'w') as outfile:
148    json.dump(result, outfile)
149
150def GenerateSerialMap(hub_types=None):
151  """Generates a map of phone serial numbers to BattOr serial numbers.
152
153  Generates a dict of:
154  {<phone serial 1>: <battor serial 1>,
155   <phone serial 2>: <battor serial 2>}
156  indicating which phone serial numbers should be matched with
157  which BattOr serial numbers. Mapping is based on the physical port numbers
158  of the hubs that the BattOrs and phones are connected to.
159
160  Args:
161      hub_types: List of hub types to check for. If not specified, checks
162      for all defined hub types. (see usb_hubs.py for details)
163  """
164  if hub_types:
165    hub_types = [usb_hubs.GetHubType(x) for x in hub_types]
166  else:
167    hub_types = usb_hubs.ALL_HUBS
168
169  devtree = find_usb_devices.GetBusNumberToDeviceTreeMap()
170
171  # List of serial numbers in the system that represent BattOrs.
172  battor_serials = list(GetBattOrSerialNumbers(devtree))
173
174  # If there's only one BattOr in the system, then a serial number ma
175  # is not necessary.
176  if len(battor_serials) == 1:
177    return {}
178
179  # List of dictionaries, one for each hub, that maps the physical
180  # port number to the serial number of that hub. For instance, in a 2
181  # hub system, this could return [{1:'ab', 2:'cd'}, {1:'jkl', 2:'xyz'}]
182  # where 'ab' and 'cd' are the phone serial numbers and 'jkl' and 'xyz'
183  # are the BattOr serial numbers.
184  port_to_serial = find_usb_devices.GetAllPhysicalPortToSerialMaps(
185      hub_types, device_tree_map=devtree)
186
187  class serials(object):
188    def __init__(self):
189      self.phone = None
190      self.battor = None
191
192  # Map of {physical port number: [phone serial #, BattOr serial #]. This
193  # map is populated by executing the code below. For instance, in the above
194  # example, after the code below is executed, port_to_devices would equal
195  # {1: ['ab', 'jkl'], 2: ['cd', 'xyz']}
196  port_to_devices = collections.defaultdict(serials)
197  for hub in port_to_serial:
198    for (port, serial) in hub.iteritems():
199      if serial in battor_serials:
200        if port_to_devices[port].battor is not None:
201          raise battor_error.BattOrError('Multiple BattOrs on same port number')
202        else:
203          port_to_devices[port].battor = serial
204      else:
205        if port_to_devices[port].phone is not None:
206          raise battor_error.BattOrError('Multiple phones on same port number')
207        else:
208          port_to_devices[port].phone = serial
209
210  # Turn the port_to_devices map into a map of the form
211  # {phone serial number: BattOr serial number}.
212  result = {}
213  for pair in port_to_devices.values():
214    if pair.phone is None:
215      continue
216    if pair.battor is None:
217      raise battor_error.BattOrError(
218          'Phone detected with no corresponding BattOr')
219    result[pair.phone] = pair.battor
220  return result
221
222def GenerateSerialMapFile(filename, hub_types=None):
223  """Generates a serial map file and writes it."""
224  WriteSerialMapFile(filename, GenerateSerialMap(hub_types))
225
226def _PhoneToPathMap(serial, serial_map, devtree):
227  """Maps phone serial number to TTY path, assuming serial map is provided."""
228  try:
229    battor_serial = serial_map[serial]
230  except KeyError:
231    raise battor_error.BattOrError('Serial number not found in serial map.')
232  for tree in devtree.values():
233    for node in tree.AllNodes():
234      if isinstance(node, find_usb_devices.USBDeviceNode):
235        if node.serial == battor_serial:
236          bus_device_to_tty = find_usb_devices.GetBusDeviceToTTYMap()
237          bus_device = (node.bus_num, node.device_num)
238          try:
239            return bus_device_to_tty[bus_device]
240          except KeyError:
241            raise battor_error.BattOrError(
242                'Device with given serial number not a BattOr '
243                '(does not have TTY path)')
244
245
246def GetBattOrPathFromPhoneSerial(serial, serial_map=None,
247                                 serial_map_file=None):
248  """Gets the TTY path (e.g. '/dev/ttyUSB0')  to communicate with the BattOr.
249
250  (1) If serial_map is given, it is treated as a dictionary mapping
251  phone serial numbers to BattOr serial numbers. This function will get the
252  TTY path for the given BattOr serial number.
253
254  (2) If serial_map_file is given, it is treated as the name of a
255  phone-to-BattOr mapping file (generated with GenerateSerialMapFile)
256  and this will be loaded and used as the dict to map port numbers to
257  BattOr serial numbers.
258
259  You can only give one of serial_map and serial_map_file.
260
261  Args:
262    serial: Serial number of phone connected on the same physical port that
263    the BattOr is connected to.
264    serial_map: Map of phone serial numbers to BattOr serial numbers, given
265    as a dictionary.
266    serial_map_file: Map of phone serial numbers to BattOr serial numbers,
267    given as a file.
268    hub_types: List of hub types to check for. Used only if serial_map_file
269    is None.
270
271  Returns:
272    Device string used to communicate with device.
273
274  Raises:
275    ValueError: If serial number is not given.
276    BattOrError: If BattOr not found or unexpected USB topology.
277  """
278  # If there's only one BattOr connected to the system, just use that one.
279  # This allows for use on, e.g., a developer's workstation with no hubs.
280  devtree = find_usb_devices.GetBusNumberToDeviceTreeMap()
281  all_battors = GetBattOrList(devtree)
282  if len(all_battors) == 1:
283    return '/dev/' + all_battors[0]
284
285  if not serial:
286    raise battor_error.BattOrError(
287        'Two or more BattOrs connected, no serial provided')
288
289  if serial_map and serial_map_file:
290    raise ValueError('Cannot specify both serial_map and serial_map_file')
291
292  if serial_map_file:
293    serial_map = ReadSerialMapFile(serial_map_file)
294
295  tty_string = _PhoneToPathMap(serial, serial_map, devtree)
296
297  if not tty_string:
298    raise battor_error.BattOrError(
299        'No device with given serial number detected.')
300
301  if IsBattOr(tty_string, devtree):
302    return '/dev/' + tty_string
303  else:
304    raise battor_error.BattOrError(
305        'Device with given serial number is not a BattOr.')
306
307if __name__ == '__main__':
308  # Main function for testing purposes
309  print GenerateSerialMap()
310