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