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. 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 222 def GenerateSerialMapFile(filename, hub_types=None): 223 """Generates a serial map file and writes it.""" 224 WriteSerialMapFile(filename, GenerateSerialMap(hub_types)) 225 226 def _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 246 def 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 307 if __name__ == '__main__': 308 # Main function for testing purposes 309 print GenerateSerialMap() 310