Home | History | Annotate | Download | only in scripts
      1 #!/usr/bin/python
      2 #
      3 # Copyright (C) 2016 The Android Open Source Project
      4 #
      5 # Licensed under the Apache License, Version 2.0 (the "License");
      6 # you may not use this file except in compliance with the License.
      7 # You may obtain a copy of the License at
      8 #
      9 #      http://www.apache.org/licenses/LICENSE-2.0
     10 #
     11 # Unless required by applicable law or agreed to in writing, software
     12 # distributed under the License is distributed on an "AS IS" BASIS,
     13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     14 # See the License for the specific language governing permissions and
     15 # limitations under the License.
     16 
     17 """Reset a USB device (presumbly android phone) by serial number.
     18 
     19 Given a serial number, inspects connected USB devices and issues USB
     20 reset to the one that matches. Python version written by Than
     21 McIntosh, based on a perl version from Chris Ferris. Intended for use
     22 on linux.
     23 
     24 """
     25 
     26 import fcntl
     27 import getopt
     28 import locale
     29 import os
     30 import re
     31 import shlex
     32 import subprocess
     33 import sys
     34 
     35 # Serial number of device that we want to reset
     36 flag_serial = None
     37 
     38 # Debugging verbosity level (0 -> no output)
     39 flag_debug = 0
     40 
     41 USBDEVFS_RESET = ord("U") << (4*2) | 20
     42 
     43 
     44 def verbose(level, msg):
     45   """Print debug trace output of verbosity level is >= value in 'level'."""
     46   if level <= flag_debug:
     47     sys.stderr.write(msg + "\n")
     48 
     49 
     50 def increment_verbosity():
     51   """Increment debug trace level by 1."""
     52   global flag_debug
     53   flag_debug += 1
     54 
     55 
     56 def issue_ioctl_to_device(device):
     57   """Issue USB reset ioctl to device."""
     58 
     59   try:
     60     fd = open(device, "wb")
     61   except IOError as e:
     62     error("unable to open device %s: "
     63           "%s" % (device, e.strerror))
     64   verbose(1, "issuing USBDEVFS_RESET ioctl() to %s" % device)
     65   fcntl.ioctl(fd, USBDEVFS_RESET, 0)
     66   fd.close()
     67 
     68 
     69 # perform default locale setup if needed
     70 def set_default_lang_locale():
     71   if "LANG" not in os.environ:
     72     warning("no env setting for LANG -- using default values")
     73     os.environ["LANG"] = "en_US.UTF-8"
     74     os.environ["LANGUAGE"] = "en_US:"
     75 
     76 
     77 def warning(msg):
     78   """Issue a warning to stderr."""
     79   sys.stderr.write("warning: " + msg + "\n")
     80 
     81 
     82 def error(msg):
     83   """Issue an error to stderr, then exit."""
     84   sys.stderr.write("error: " + msg + "\n")
     85   exit(1)
     86 
     87 
     88 # invoke command, returning array of lines read from it
     89 def docmdlines(cmd, nf=None):
     90   """Run a command via subprocess, returning output as an array of lines."""
     91   verbose(2, "+ docmdlines executing: %s" % cmd)
     92   args = shlex.split(cmd)
     93   mypipe = subprocess.Popen(args, stdout=subprocess.PIPE)
     94   encoding = locale.getdefaultlocale()[1]
     95   pout, perr = mypipe.communicate()
     96   if mypipe.returncode != 0:
     97     if perr:
     98       decoded_err = perr.decode(encoding)
     99       warning(decoded_err)
    100     if nf:
    101       return None
    102     error("command failed (rc=%d): cmd was %s" % (mypipe.returncode, args))
    103   decoded = pout.decode(encoding)
    104   lines = decoded.strip().split("\n")
    105   return lines
    106 
    107 
    108 def perform():
    109   """Main driver routine."""
    110   lines = docmdlines("usb-devices")
    111   dmatch = re.compile(r"^\s*T:\s*Bus\s*=\s*(\d+)\s+.*\s+Dev#=\s*(\d+).*$")
    112   smatch = re.compile(r"^\s*S:\s*SerialNumber=(.*)$")
    113   device = None
    114   found = False
    115   for line in lines:
    116     m = dmatch.match(line)
    117     if m:
    118       p1 = int(m.group(1))
    119       p2 = int(m.group(2))
    120       device = "/dev/bus/usb/%03d/%03d" % (p1, p2)
    121       verbose(1, "setting device: %s" % device)
    122       continue
    123     m = smatch.match(line)
    124     if m:
    125       ser = m.group(1)
    126       if ser == flag_serial:
    127         verbose(0, "matched serial %s to device "
    128                 "%s, invoking reset" % (ser, device))
    129         issue_ioctl_to_device(device)
    130         found = True
    131         break
    132   if not found:
    133     error("unable to locate device with serial number %s" % flag_serial)
    134 
    135 
    136 def usage(msgarg):
    137   """Print usage and exit."""
    138   if msgarg:
    139     sys.stderr.write("error: %s\n" % msgarg)
    140   print """\
    141     usage:  %s [options] XXYYZZ
    142 
    143     where XXYYZZ is the serial number of a connected Android device.
    144 
    145     options:
    146     -d    increase debug msg verbosity level
    147 
    148     """ % os.path.basename(sys.argv[0])
    149   sys.exit(1)
    150 
    151 
    152 def parse_args():
    153   """Command line argument parsing."""
    154   global flag_serial
    155 
    156   try:
    157     optlist, args = getopt.getopt(sys.argv[1:], "d")
    158   except getopt.GetoptError as err:
    159     # unrecognized option
    160     usage(str(err))
    161   if not args or len(args) != 1:
    162     usage("supply a single device serial number as argument")
    163   flag_serial = args[0]
    164 
    165   for opt, _ in optlist:
    166     if opt == "-d":
    167       increment_verbosity()
    168 
    169 
    170 set_default_lang_locale()
    171 parse_args()
    172 perform()
    173