Home | History | Annotate | Download | only in utils
      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 '''
      8 This script provides tools to map BattOrs to phones.
      9 
     10 Phones are identified by the following string:
     11 
     12 "Phone serial number" - Serial number of the phone. This can be
     13 obtained via 'adb devices' or 'usb-devices', and is not expected
     14 to change for a given phone.
     15 
     16 BattOrs are identified by the following two strings:
     17 
     18 "BattOr serial number" - Serial number of the BattOr. This can be
     19 obtained via 'usb-devices', and is not expected to change for
     20 a given BattOr.
     21 
     22 "BattOr path" - The path of the form '/dev/ttyUSB*' that is used
     23 to communicate with the BattOr (the battor_agent binary takes
     24 this BattOr path as a parameter). The BattOr path is frequently
     25 reassigned by the OS, most often when the device is disconnected
     26 and then reconnected. Thus, the BattOr path cannot be expected
     27 to be stable.
     28 
     29 In a typical application, the user will require the BattOr path
     30 for the BattOr that is plugged into a given phone. For instance,
     31 the user will be running tracing on a particular phone, and will
     32 need to know which BattOr path to use to communicate with the BattOr
     33 to get the corresponding power trace.
     34 
     35 Getting this mapping requires two steps: (1) determining the
     36 mapping between phone serial numbers and BattOr serial numbers, and
     37 (2) getting the BattOr path corresponding to a given BattOr serial
     38 number.
     39 
     40 For step (1), we generate a JSON file giving this mapping. This
     41 JSON 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 
     45 The default way to generate this JSON file is using the function
     46 GenerateSerialMapFile, which generates a mapping based on assuming
     47 that the system has two identical USB hubs connected to it, and
     48 the phone plugged into physical port number 1 on one hub corresponds
     49 to the BattOr plugged into physical port number 1 on the other hub,
     50 and similarly with physical port numbers 2, 3, etc. This generates
     51 the map file based on the structure at the time GenerateSerialMapFile called.
     52 Note that after the map file is generated, port numbers are no longer used;
     53 the user could move around the devices in the ports without affecting
     54 which phone goes with which BattOr. (Thus, if the user wanted to update the
     55 mapping to match the new port connections, the user would have to
     56 re-generate this file.)
     57 
     58 The script update_mapping.py will do this updating from the command line.
     59 
     60 If the user wanted to specify a custom mapping, the user could instead
     61 create the JSON file manually. (In this case, hubs would not be necessary
     62 and the physical ports connected would be irrelevant.)
     63 
     64 Step (2) is conducted through the function GetBattorPathFromPhoneSerial,
     65 which takes a serial number mapping generated via step (1) and a phone
     66 serial number, then gets the corresponding BattOr serial number from the
     67 map and determines its BattOr path (e.g. /dev/ttyUSB0). Since BattOr paths
     68 can change if devices are connected and disconnected (even if connected
     69 or disconnected via the same port) this function should be called to
     70 determine the BattOr path every time before connecting to the BattOr.
     71 
     72 Note that if there is only one BattOr connected to the system, then
     73 GetBattorPathFromPhoneSerial will always return that BattOr and will ignore
     74 the mapping file. Thus, if the user never has more than one BattOr connected
     75 to the system, the user will not need to generate mapping files.
     76 '''
     77 
     78 
     79 import json
     80 import collections
     81 
     82 from battor import battor_error
     83 from devil.utils import find_usb_devices
     84 from devil.utils import usb_hubs
     85 
     86 
     87 def GetBattorList(device_tree_map):
     88   return [x for x in find_usb_devices.GetTTYList()
     89           if IsBattor(x, device_tree_map)]
     90 
     91 
     92 def 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 
     98 def 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 
    106 def 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 
    129 def 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 
    150 def 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.
    162       Defaults to ['plugable_7port',
    163                    'plugable_7port_usb3_part2',
    164                    'plugable_7port_usb3_part3']
    165       (see usb_hubs.py for details)
    166   """
    167   hub_types = [usb_hubs.GetHubType(x)
    168                for x in hub_types or ['plugable_7port',
    169                                       'plugable_7port_usb3_part2',
    170                                       'plugable_7port_usb3_part3']]
    171   devtree = find_usb_devices.GetBusNumberToDeviceTreeMap()
    172 
    173   # List of serial numbers in the system that represent BattOrs.
    174   battor_serials = list(GetBattorSerialNumbers(devtree))
    175 
    176   # If there's only one BattOr in the system, then a serial number ma
    177   # is not necessary.
    178   if len(battor_serials) == 1:
    179     return {}
    180 
    181   # List of dictionaries, one for each hub, that maps the physical
    182   # port number to the serial number of that hub. For instance, in a 2
    183   # hub system, this could return [{1:'ab', 2:'cd'}, {1:'jkl', 2:'xyz'}]
    184   # where 'ab' and 'cd' are the phone serial numbers and 'jkl' and 'xyz'
    185   # are the BattOr serial numbers.
    186   port_to_serial = find_usb_devices.GetAllPhysicalPortToSerialMaps(
    187       hub_types, device_tree_map=devtree)
    188 
    189   class serials(object):
    190     def __init__(self):
    191       self.phone = None
    192       self.battor = None
    193 
    194   # Map of {physical port number: [phone serial #, BattOr serial #]. This
    195   # map is populated by executing the code below. For instance, in the above
    196   # example, after the code below is executed, port_to_devices would equal
    197   # {1: ['ab', 'jkl'], 2: ['cd', 'xyz']}
    198   port_to_devices = collections.defaultdict(serials)
    199   for hub in port_to_serial:
    200     for (port, serial) in hub.iteritems():
    201       if serial in battor_serials:
    202         if port_to_devices[port].battor is not None:
    203           raise battor_error.BattorError('Multiple BattOrs on same port number')
    204         else:
    205           port_to_devices[port].battor = serial
    206       else:
    207         if port_to_devices[port].phone is not None:
    208           raise battor_error.BattorError('Multiple phones on same port number')
    209         else:
    210           port_to_devices[port].phone = serial
    211 
    212   # Turn the port_to_devices map into a map of the form
    213   # {phone serial number: BattOr serial number}.
    214   result = {}
    215   for pair in port_to_devices.values():
    216     if pair.phone is None:
    217       raise battor_error.BattorError(
    218           'BattOr detected with no corresponding phone')
    219     if pair.battor is None:
    220       raise battor_error.BattorError(
    221           'Phone detected with no corresponding BattOr')
    222     result[pair.phone] = pair.battor
    223   return result
    224 
    225 def GenerateSerialMapFile(filename, hub_types=None):
    226   """Generates a serial map file and writes it."""
    227   WriteSerialMapFile(filename, GenerateSerialMap(hub_types))
    228 
    229 def _PhoneToPathMap(serial, serial_map, devtree):
    230   """Maps phone serial number to TTY path, assuming serial map is provided."""
    231   try:
    232     battor_serial = serial_map[serial]
    233   except KeyError:
    234     raise battor_error.BattorError('Serial number not found in serial map.')
    235   for tree in devtree.values():
    236     for node in tree.AllNodes():
    237       if isinstance(node, find_usb_devices.USBDeviceNode):
    238         if node.serial == battor_serial:
    239           bus_device_to_tty = find_usb_devices.GetBusDeviceToTTYMap()
    240           bus_device = (node.bus_num, node.device_num)
    241           try:
    242             return bus_device_to_tty[bus_device]
    243           except KeyError:
    244             raise battor_error.BattorError(
    245                 'Device with given serial number not a BattOr '
    246                 '(does not have TTY path)')
    247 
    248 
    249 def GetBattorPathFromPhoneSerial(serial, serial_map=None,
    250                                  serial_map_file=None):
    251   """Gets the TTY path (e.g. '/dev/ttyUSB0')  to communicate with the BattOr.
    252 
    253   (1) If serial_map is given, it is treated as a dictionary mapping
    254   phone serial numbers to BattOr serial numbers. This function will get the
    255   TTY path for the given BattOr serial number.
    256 
    257   (2) If serial_map_file is given, it is treated as the name of a
    258   phone-to-BattOr mapping file (generated with GenerateSerialMapFile)
    259   and this will be loaded and used as the dict to map port numbers to
    260   BattOr serial numbers.
    261 
    262   You can only give one of serial_map and serial_map_file.
    263 
    264   Args:
    265     serial: Serial number of phone connected on the same physical port that
    266     the BattOr is connected to.
    267     serial_map: Map of phone serial numbers to BattOr serial numbers, given
    268     as a dictionary.
    269     serial_map_file: Map of phone serial numbers to BattOr serial numbers,
    270     given as a file.
    271     hub_types: List of hub types to check for. Used only if serial_map_file
    272     is None.
    273 
    274   Returns:
    275     Device string used to communicate with device.
    276 
    277   Raises:
    278     ValueError: If serial number is not given.
    279     BattorError: If BattOr not found or unexpected USB topology.
    280   """
    281   # If there's only one BattOr connected to the system, just use that one.
    282   # This allows for use on, e.g., a developer's workstation with no hubs.
    283   devtree = find_usb_devices.GetBusNumberToDeviceTreeMap()
    284   all_battors = GetBattorList(devtree)
    285   if len(all_battors) == 1:
    286     return '/dev/' + all_battors[0]
    287 
    288   if not serial:
    289     raise battor_error.BattorError(
    290         'Two or more BattOrs connected, no serial provided')
    291 
    292   if serial_map and serial_map_file:
    293     raise ValueError('Cannot specify both serial_map and serial_map_file')
    294 
    295   if serial_map_file:
    296     serial_map = ReadSerialMapFile(serial_map_file)
    297 
    298   tty_string = _PhoneToPathMap(serial, serial_map, devtree)
    299 
    300   if not tty_string:
    301     raise battor_error.BattorError(
    302         'No device with given serial number detected.')
    303 
    304   if IsBattor(tty_string, devtree):
    305     return '/dev/' + tty_string
    306   else:
    307     raise battor_error.BattorError(
    308         'Device with given serial number is not a BattOr.')
    309