Home | History | Annotate | Download | only in servo
      1 # Copyright 2016 The Chromium OS Authors. All rights reserved.
      2 # Use of this source code is governed by a BSD-style license that can be
      3 # found in the LICENSE file.
      4 
      5 import re
      6 import logging
      7 import time
      8 
      9 from autotest_lib.client.common_lib import error
     10 from autotest_lib.server.cros.servo import pd_console
     11 
     12 
     13 class PDDevice(object):
     14     """Base clase for all PD devices
     15 
     16     This class provides a set of APIs for expected Type C PD required actions
     17     in TypeC FAFT tests. The base class is specific for Type C devices that
     18     do not have any console access.
     19 
     20     """
     21 
     22     def is_src(self):
     23         """Checks if the port is connected as a source
     24 
     25         """
     26         raise NotImplementedError(
     27                 'is_src should be implemented in derived class')
     28 
     29     def is_snk(self):
     30         """Checks if the port is connected as a sink
     31 
     32         @returns None
     33         """
     34         raise NotImplementedError(
     35                 'is_snk should be implemented in derived class')
     36 
     37     def is_connected(self):
     38         """Checks if the port is connected
     39 
     40         @returns True if in a connected state, False otherwise
     41         """
     42         return self.is_src() or self.is_snk()
     43 
     44     def is_disconnected(self):
     45         """Checks if the port is disconnected
     46 
     47         """
     48         raise NotImplementedError(
     49                 'is_disconnected should be implemented in derived class')
     50 
     51     def is_ufp(self):
     52         """Checks if data role is UFP
     53 
     54         """
     55         raise NotImplementedError(
     56                 'is_ufp should be implemented in derived class')
     57 
     58     def is_dfp(self):
     59         """Checks if data role is DFP
     60 
     61         """
     62         raise NotImplementedError(
     63                 'is_dfp should be implemented in derived class')
     64 
     65     def is_drp(self):
     66         """Checks if dual role mode is supported
     67 
     68         """
     69         raise NotImplementedError(
     70                 'is_drp should be implemented in derived class')
     71 
     72     def dr_swap(self):
     73         """Attempts a data role swap
     74 
     75         """
     76         raise NotImplementedError(
     77                'dr_swap should be implemented in derived class')
     78 
     79     def pr_swap(self):
     80         """Attempts a power role swap
     81 
     82         """
     83         raise NotImplementedError(
     84                 'pr_swap should be implemented in derived class')
     85 
     86     def vbus_request(self, voltage):
     87         """Requests a specific VBUS voltage from SRC
     88 
     89         @param voltage: requested voltage level (5, 12, 20) in volts
     90         """
     91         raise NotImplementedError(
     92                 'vbus_request should be implemented in derived class')
     93 
     94     def soft_reset(self):
     95         """Initates a PD soft reset sequence
     96 
     97         """
     98         raise NotImplementedError(
     99                 'soft_reset should be implemented in derived class')
    100 
    101     def hard_reset(self):
    102         """Initates a PD hard reset sequence
    103 
    104         """
    105         raise NotImplementedError(
    106                 'hard_reset should be implemented in derived class')
    107 
    108     def drp_set(self, mode):
    109         """Sets dualrole mode
    110 
    111         @param mode: desired dual role setting (on, off, snk, src)
    112         """
    113         raise NotImplementedError(
    114                 'drp_set should be implemented in derived class')
    115 
    116     def drp_disconnect_connect(self, disc_time_sec):
    117         """Force PD disconnect/connect via drp settings
    118 
    119         @param disc_time_sec: Time in seconds between disconnect and reconnect
    120         """
    121         raise NotImplementedError(
    122                 'drp_disconnect_reconnect should be implemented in \
    123                 derived class')
    124 
    125     def cc_disconnect_connect(self, disc_time_sec):
    126         """Force PD disconnect/connect using Plankton fw command
    127 
    128         @param disc_time_sec: Time in seconds between disconnect and reconnect
    129         """
    130         raise NotImplementedError(
    131                 'cc_disconnect_reconnect should be implemented in \
    132                 derived class')
    133 
    134 
    135 class PDConsoleDevice(PDDevice):
    136     """Class for PD devices that have console access
    137 
    138     This class contains methods for common PD actions for any PD device which
    139     has UART console access. It inherits the PD device base class. In addition,
    140     it stores both the UART console and port for the PD device.
    141     """
    142 
    143     def __init__(self, console, port):
    144         """Initialization method
    145 
    146         @param console: UART console object
    147         @param port: USB PD port number
    148         """
    149         # Save UART console
    150         self.console = console
    151         # Instantiate PD utilities used by methods in this class
    152         self.utils = pd_console.PDConsoleUtils(console)
    153         # Save the PD port number for this device
    154         self.port = port
    155         # Not a Plankton device
    156         self.is_plankton = False
    157 
    158     def is_src(self):
    159         """Checks if the port is connected as a source
    160 
    161         @returns True if connected as SRC, False otherwise
    162         """
    163         state = self.utils.get_pd_state(self.port)
    164         return bool(state == self.utils.SRC_CONNECT)
    165 
    166     def is_snk(self):
    167         """Checks if the port is connected as a sink
    168 
    169         @returns True if connected as SNK, False otherwise
    170         """
    171         state = self.utils.get_pd_state(self.port)
    172         return bool(state == self.utils.SNK_CONNECT)
    173 
    174     def is_connected(self):
    175         """Checks if the port is connected
    176 
    177         @returns True if in a connected state, False otherwise
    178         """
    179         state = self.utils.get_pd_state(self.port)
    180         return bool(state == self.utils.SNK_CONNECT or
    181                     state == self.utils.SRC_CONNECT)
    182 
    183     def is_disconnected(self):
    184         """Checks if the port is disconnected
    185 
    186         @returns True if in a disconnected state, False otherwise
    187         """
    188         state = self.utils.get_pd_state(self.port)
    189         return bool(state == self.utils.SRC_DISC or
    190                     state == self.utils.SNK_DISC)
    191 
    192     def is_drp(self):
    193         """Checks if dual role mode is supported
    194 
    195         @returns True if dual role mode is 'on', False otherwise
    196         """
    197         return self.utils.is_pd_dual_role_enabled()
    198 
    199     def drp_disconnect_connect(self, disc_time_sec):
    200         """Disconnect/reconnect using drp mode settings
    201 
    202         A PD console device doesn't have an explicit connect/disconnect
    203         command. Instead, the dualrole mode setting is used to force
    204         disconnects in devices which support this feature. To disconnect,
    205         force the dualrole mode to be the opposite role of the current
    206         connected state.
    207 
    208         @param disc_time_sec: time in seconds to wait to reconnect
    209 
    210         @returns True if device disconnects, then returns to a connected
    211         state. False if either step fails.
    212         """
    213         # Dualrole mode must be supported
    214         if self.is_drp() is False:
    215             logging.warn('Device not DRP capable, unabled to force disconnect')
    216             return False
    217         # Force state will be the opposite of current connect state
    218         if self.is_src():
    219             drp_mode = 'snk'
    220             swap_state = self.utils.SNK_CONNECT
    221         else:
    222             drp_mode = 'src'
    223             swap_state = self.utils.SRC_CONNECT
    224         # Force disconnect
    225         self.drp_set(drp_mode)
    226         # Wait for disconnect time
    227         time.sleep(disc_time_sec)
    228         # Verify that the device is disconnected
    229         disconnect = self.is_disconnected()
    230 
    231         # If the other device is dualrole, then forcing dualrole mode will
    232         # only cause the disconnect to appear momentarily and reconnect
    233         # in the power role forced by the drp_set() call. For this case,
    234         # the role swap verifies that a disconnect/connect sequence occurred.
    235         if disconnect == False:
    236             time.sleep(self.utils.CONNECT_TIME)
    237             # Connected, verify if power role swap has ocurred
    238             if swap_state == self.utils.get_pd_state(self.port):
    239                 # Restore default dualrole mode
    240                 self.drp_set('on')
    241                 # Restore orignal power role
    242                 connect = self.pr_swap()
    243                 if connect == False:
    244                     logging.warn('DRP on both devices, 2nd power swap failed')
    245                 return connect
    246 
    247         # Restore default dualrole mode
    248         self.drp_set('on')
    249         # Allow enough time for protocol state machine
    250         time.sleep(self.utils.CONNECT_TIME)
    251         # Check if connected
    252         connect = self.is_connected()
    253         logging.info('Disconnect = %r, Connect = %r', disconnect, connect)
    254         return bool(disconnect and connect)
    255 
    256     def drp_set(self, mode):
    257         """Sets dualrole mode
    258 
    259         @param mode: desired dual role setting (on, off, snk, src)
    260 
    261         @returns True is set was successful, False otherwise
    262         """
    263         # Set desired dualrole mode
    264         self.utils.set_pd_dualrole(mode)
    265         # Get the expected output
    266         resp = self.utils.dualrole_resp[self.utils.dual_index[mode]]
    267         # Get current setting
    268         current = self.utils.get_pd_dualrole()
    269         # Verify that setting is correct
    270         return bool(resp == current)
    271 
    272     def try_src(self, enable):
    273         """Enables/Disables Try.SRC PD protocol setting
    274 
    275         @param enable: True to enable, False to disable
    276 
    277         @returns True is setting was successful, False if feature not
    278         supported by the device, or not set as desired.
    279         """
    280         # Create Try.SRC pd command
    281         cmd = 'pd trysrc %d' % int(enable)
    282         # Try.SRC on/off is output, if supported feature
    283         regex = ['Try\.SRC\s([\w]+)|(Parameter)']
    284         m = self.utils.send_pd_command_get_output(cmd, regex)
    285         # Determine if Try.SRC feature is supported
    286         trysrc = re.search('Try\.SRC\s([\w]+)', m[0][0])
    287         if not trysrc:
    288             logging.warn('Try.SRC not supported on this PD device')
    289             return False
    290         # TrySRC is supported on this PD device, verify setting.
    291         logging.info('Try.SRC mode = %s', trysrc.group(1))
    292         if enable:
    293             val = 'on'
    294         else:
    295             val = 'off'
    296         return bool(val == m[0][1])
    297 
    298     def soft_reset(self):
    299         """Initates a PD soft reset sequence
    300 
    301         To verify that a soft reset sequence was initiated, the
    302         reply message is checked to verify that the reset command
    303         was acknowledged by its port pair. The connect state should
    304         be same as it was prior to issuing the reset command.
    305 
    306         @returns True if the port pair acknowledges the the reset message
    307         and if following the command, the device returns to the same
    308         connected state. False otherwise.
    309         """
    310         RESET_DELAY = 0.5
    311         cmd = 'pd %d soft' % self.port
    312         state_before = self.utils.get_pd_state(self.port)
    313         reply = self.utils.send_pd_command_get_reply_msg(cmd)
    314         if reply != self.utils.PD_CONTROL_MSG_DICT['Accept']:
    315             return False
    316         time.sleep(RESET_DELAY)
    317         state_after = self.utils.get_pd_state(self.port)
    318         return state_before == state_after
    319 
    320     def hard_reset(self):
    321         """Initates a PD hard reset sequence
    322 
    323         To verify that a hard reset sequence was initiated, the
    324         console ouput is scanned for HARD RST TX. In addition, the connect
    325         state should be same as it was prior to issuing the reset command.
    326 
    327         @returns True if the port pair acknowledges that hard reset was
    328         initiated and if following the command, the device returns to the same
    329         connected state. False otherwise.
    330         """
    331         RESET_DELAY = 1.0
    332         cmd = 'pd %d hard' % self.port
    333         state_before = self.utils.get_pd_state(self.port)
    334         self.utils.enable_pd_console_debug()
    335         try:
    336             self.utils.send_pd_command_get_output(cmd, ['.*(HARD\sRST\sTX)'])
    337         except error.TestFail:
    338             logging.warn('HARD RST TX not found')
    339             return False
    340         finally:
    341             self.utils.disable_pd_console_debug()
    342 
    343         time.sleep(RESET_DELAY)
    344         state_after = self.utils.get_pd_state(self.port)
    345         return state_before == state_after
    346 
    347     def pr_swap(self):
    348         """Attempts a power role swap
    349 
    350         In order to attempt a power role swap the device must be
    351         connected and support dualrole mode. Once these two criteria
    352         are checked a power role command is issued. Following a delay
    353         to allow for a reconnection the new power role is checked
    354         against the power role prior to issuing the command.
    355 
    356         @returns True if the device has swapped power roles, False otherwise.
    357         """
    358         # Get starting state
    359         if not self.is_drp() and not self.drp_set('on'):
    360             logging.warn('Dualrole Mode not enabled!')
    361             return False
    362         if self.is_connected() == False:
    363             logging.warn('PD contract not established!')
    364             return False
    365         current_pr = self.utils.get_pd_state(self.port)
    366         swap_cmd = 'pd %d swap power' % self.port
    367         self.utils.send_pd_command(swap_cmd)
    368         time.sleep(self.utils.CONNECT_TIME)
    369         new_pr = self.utils.get_pd_state(self.port)
    370         logging.info('Power swap: %s -> %s', current_pr, new_pr)
    371         if self.is_connected() == False:
    372             logging.warn('Device not connected following PR swap attempt.')
    373             return False
    374         return current_pr != new_pr
    375 
    376 
    377 class PDPlanktonDevice(PDConsoleDevice):
    378     """Class for PD Plankton devices
    379 
    380     This class contains methods for PD funtions which are unique to the
    381     Plankton Type C factory testing board. It inherits all the methods
    382     for PD console devices.
    383     """
    384 
    385     def __init__(self, console, port):
    386         """Initialization method
    387 
    388         @param console: UART console for this device
    389         @param port: USB PD port number
    390         """
    391         # Instantiate the PD console object
    392         super(PDPlanktonDevice, self).__init__(console, 0)
    393         # Indicate this is Plankton device
    394         self.is_plankton = True
    395 
    396     def _toggle_plankton_drp(self):
    397         """Issue 'usbc_action drp' Plankton command
    398 
    399         @returns value of drp_enable in Plankton FW
    400         """
    401         drp_cmd = 'usbc_action drp'
    402         drp_re = ['DRP\s=\s(\d)']
    403         # Send DRP toggle command to Plankton and get value of 'drp_enable'
    404         m = self.utils.send_pd_command_get_output(drp_cmd, drp_re)
    405         return int(m[0][1])
    406 
    407     def _enable_plankton_drp(self):
    408         """Enable DRP mode on Plankton
    409 
    410         DRP mode can only be toggled and is not able to be explicitly
    411         enabled/disabled via the console. Therefore, this method will
    412         toggle DRP mode until the console reply indicates that this
    413         mode is enabled. The toggle happens a maximum of two times
    414         in case this is called when it's already enabled.
    415 
    416         @returns True when DRP mode is enabled, False if not successful
    417         """
    418         for attempt in xrange(2):
    419             if self._toggle_plankton_drp() == True:
    420                 logging.info('Plankton DRP mode enabled')
    421                 return True
    422         logging.error('Plankton DRP mode set failure')
    423         return False
    424 
    425     def _verify_state_sequence(self, states_list, console_log):
    426         """Compare PD state transitions to expected values
    427 
    428         @param states_list: list of expected PD state transitions
    429         @param console_log: console output which contains state names
    430 
    431         @returns True if the sequence matches, False otherwise
    432         """
    433         # For each state in the expected state transiton table, build
    434         # the regexp and search for it in the state transition log.
    435         for state in states_list:
    436             state_regx = r'C{0}\s+[\w]+:\s({1})'.format(self.port,
    437                                                         state)
    438             if re.search(state_regx, console_log) is None:
    439                 return False
    440         return True
    441 
    442     def cc_disconnect_connect(self, disc_time_sec):
    443         """Disconnect/reconnect using Plankton
    444 
    445         Plankton supports a feature which simulates a USB Type C disconnect
    446         and reconnect.
    447 
    448         @param disc_time_sec: Time in seconds for disconnect period.
    449         """
    450         DISC_DELAY = 100
    451         disc_cmd = 'fake_disconnect %d  %d' % (DISC_DELAY,
    452                                                disc_time_sec * 1000)
    453         self.utils.send_pd_command(disc_cmd)
    454 
    455     def drp_disconnect_connect(self, disc_time_sec):
    456         """Disconnect/reconnect using Plankton
    457 
    458         Utilize Plankton disconnect/connect utility and verify
    459         that both disconnect and reconnect actions were successful.
    460 
    461         @param disc_time_sec: Time in seconds for disconnect period.
    462 
    463         @returns True if device disconnects, then returns to a connected
    464         state. False if either step fails.
    465         """
    466         self.cc_disconnect_connect(disc_time_sec)
    467         time.sleep(disc_time_sec / 2)
    468         disconnect = self.is_disconnected()
    469         time.sleep(disc_time_sec / 2 + self.utils.CONNECT_TIME)
    470         connect = self.is_connected()
    471         return disconnect and connect
    472 
    473     def drp_set(self, mode):
    474         """Sets dualrole mode
    475 
    476         @param mode: desired dual role setting (on, off, snk, src)
    477 
    478         @returns True if dualrole mode matches the requested value or
    479         is successfully set to that value. False, otherwise.
    480         """
    481         # Get correct dualrole console response
    482         resp = self.utils.dualrole_resp[self.utils.dual_index[mode]]
    483         # Get current value of dualrole
    484         drp = self.utils.get_pd_dualrole()
    485         if drp == resp:
    486             return True
    487 
    488         if mode == 'on':
    489             # Setting dpr_enable on Plankton will set dualrole mode to on
    490             return self._enable_plankton_drp()
    491         else:
    492             # If desired setting is other than 'on', need to ensure that
    493             # drp mode on Plankton is disabled.
    494             if resp == 'on':
    495                 # This will turn off drp_enable flag and set dualmode to 'off'
    496                 return self._toggle_plankton_drp()
    497             # With drp_enable flag off, can set to desired setting
    498             return self.utils.set_pd_dualrole(mode)
    499 
    500     def _reset(self, cmd, states_list):
    501         """Initates a PD reset sequence
    502 
    503         Plankton device has state names available on the console. When
    504         a soft reset is issued the console log is extracted and then
    505         compared against the expected state transisitons.
    506 
    507         @param cmd: reset type (soft or hard)
    508         @param states_list: list of expected PD state transitions
    509 
    510         @returns True if state transitions match, False otherwise
    511         """
    512         # Want to grab all output until either SRC_READY or SNK_READY
    513         reply_exp = ['(.*)(C\d)\s+[\w]+:\s([\w]+_READY)']
    514         m = self.utils.send_pd_command_get_output(cmd, reply_exp)
    515         return self._verify_state_sequence(states_list, m[0][0])
    516 
    517     def soft_reset(self):
    518         """Initates a PD soft reset sequence
    519 
    520         @returns True if state transitions match, False otherwise
    521         """
    522         snk_reset_states = [
    523             'SOFT_RESET',
    524             'SNK_DISCOVERY',
    525             'SNK_REQUESTED',
    526             'SNK_TRANSITION',
    527             'SNK_READY'
    528         ]
    529 
    530         src_reset_states = [
    531             'SOFT_RESET',
    532             'SRC_DISCOVERY',
    533             'SRC_NEGOCIATE',
    534             'SRC_ACCEPTED',
    535             'SRC_POWERED',
    536             'SRC_TRANSITION',
    537             'SRC_READY'
    538         ]
    539 
    540         if self.is_src():
    541             states_list = src_reset_states
    542         elif self.is_snk():
    543             states_list = snk_reset_states
    544         else:
    545             raise error.TestFail('Port Pair not in a connected state')
    546 
    547         cmd = 'pd %d soft' % self.port
    548         return self._reset(cmd, states_list)
    549 
    550     def hard_reset(self):
    551         """Initates a PD hard reset sequence
    552 
    553         @returns True if state transitions match, False otherwise
    554         """
    555         snk_reset_states = [
    556             'HARD_RESET_SEND',
    557             'HARD_RESET_EXECUTE',
    558             'SNK_HARD_RESET_RECOVER',
    559             'SNK_DISCOVERY',
    560             'SNK_REQUESTED',
    561             'SNK_TRANSITION',
    562             'SNK_READY'
    563         ]
    564 
    565         src_reset_states = [
    566             'HARD_RESET_SEND',
    567             'HARD_RESET_EXECUTE',
    568             'SRC_HARD_RESET_RECOVER',
    569             'SRC_DISCOVERY',
    570             'SRC_NEGOCIATE',
    571             'SRC_ACCEPTED',
    572             'SRC_POWERED',
    573             'SRC_TRANSITION',
    574             'SRC_READY'
    575         ]
    576 
    577         if self.is_src():
    578             states_list = src_reset_states
    579         elif self.is_snk():
    580             states_list = snk_reset_states
    581         else:
    582             raise error.TestFail('Port Pair not in a connected state')
    583 
    584         cmd = 'pd %d hard' % self.port
    585         return self._reset(cmd, states_list)
    586 
    587 
    588 class PDPortPartner(object):
    589     """Methods used to instantiate PD device objects
    590 
    591     This class is initalized with a list of servo consoles. It
    592     contains methods to determine if USB PD devices are accessible
    593     via the consoles and attempts to determine USB PD port partners.
    594     A PD device is USB PD port specific, a single console may access
    595     multiple PD devices.
    596 
    597     """
    598 
    599     def __init__(self, consoles):
    600         """Initialization method
    601 
    602         @param consoles: list of servo consoles
    603         """
    604         self.consoles = consoles
    605 
    606     def _send_pd_state(self, port, console):
    607         """Tests if PD device exists on a given port number
    608 
    609         @param port: USB PD port number to try
    610         @param console: servo UART console
    611 
    612         @returns True if 'pd <port> state' command gives a valid
    613         response, False otherwise
    614         """
    615         cmd = 'pd %d state' % port
    616         regex = r'(Port C\d)|(Parameter)'
    617         m = console.send_command_get_output(cmd, [regex])
    618         # If PD port exists, then output will be Port C0 or C1
    619         regex = r'Port C{0}'.format(port)
    620         if re.search(regex, m[0][0]):
    621             return True
    622         return False
    623 
    624     def _find_num_pd_ports(self, console):
    625         """Determine number of PD ports for a given console
    626 
    627         @param console: uart console accssed via servo
    628 
    629         @returns: number of PD ports accessible via console
    630         """
    631         MAX_PORTS = 2
    632         num_ports = 0
    633         for port in xrange(MAX_PORTS):
    634             if self._send_pd_state(port, console):
    635                 num_ports += 1
    636         return num_ports
    637 
    638     def _is_pd_console(self, console):
    639         """Check if pd option exists in console
    640 
    641         @param console: uart console accssed via servo
    642 
    643         @returns: True if 'pd' is found, False otherwise
    644         """
    645         try:
    646             m = console.send_command_get_output('help', [r'(pd)\s+'])
    647             return True
    648         except error.TestFail:
    649             return False
    650 
    651     def _is_plankton_console(self, console):
    652         """Check for Plankton console
    653 
    654         This method looks for a console command option 'usbc_action' which
    655         is unique to Plankton PD devices.
    656 
    657         @param console: uart console accssed via servo
    658 
    659         @returns True if usbc_action command is present, False otherwise
    660         """
    661         try:
    662             m = console.send_command_get_output('help', [r'(usbc_action)'])
    663             return True
    664         except error.TestFail:
    665             return False
    666 
    667     def _check_port_pair(self, dev_pair):
    668         """Check if two PD devices could be connected
    669 
    670         If two USB PD devices are connected, then they should be in
    671         either the SRC_READY or SNK_READY states and have opposite
    672         power roles. In addition, they must be on different servo
    673         consoles.
    674 
    675         @param: list of two possible PD port parters
    676 
    677         @returns True if not the same console and both PD devices
    678         are a plausible pair based only on their PD states.
    679         """
    680         # Don't test if on the same servo console
    681         if dev_pair[0].console == dev_pair[1].console:
    682             logging.info('PD Devices are on same platform -> cant be a pair')
    683             return False
    684         # Must be SRC <--> SNK or SNK <--> SRC
    685         return bool((dev_pair[0].is_src() and dev_pair[1].is_snk()) or
    686                     (dev_pair[0].is_snk() and dev_pair[1].is_src()))
    687 
    688     def _verify_plankton_connection(self, dev_pair):
    689         """Verify DUT to Plankton PD connection
    690 
    691         This method checks for a Plankton PD connection for the
    692         given port by first verifying if a PD connection is present.
    693         If found, then it uses a Plankton feature to force a PD disconnect.
    694         If the port is no longer in the connected state, and following
    695         a delay, is found to be back in the connected state, then
    696         a DUT pd to Plankton connection is verified.
    697 
    698         @param dev_pair: list of two PD devices
    699 
    700         @returns True if DUT to Plankton pd connection is verified
    701         """
    702         DISC_CHECK_TIME = .5
    703         DISC_WAIT_TIME = 2
    704         CONNECT_TIME = 4
    705 
    706         if not self._check_port_pair(dev_pair):
    707             return False
    708 
    709         for index in xrange(len(dev_pair)):
    710             try:
    711                 # Force PD disconnect
    712                 dev_pair[index].cc_disconnect_connect(DISC_WAIT_TIME)
    713                 time.sleep(DISC_CHECK_TIME)
    714                 # Verify that both devices are now disconnected
    715                 if (dev_pair[0].is_disconnected() and
    716                     dev_pair[1].is_disconnected()):
    717                     # Allow enough time for reconnection
    718                     time.sleep(DISC_WAIT_TIME + CONNECT_TIME)
    719                     if self._check_port_pair(dev_pair):
    720                         # Have verifed a pd disconnect/reconnect sequence
    721                         logging.info('Plankton <-> DUT pair found')
    722                         return True
    723                 else:
    724                     # Delay to allow
    725                     time.sleep(DISC_WAIT_TIME + CONNECT_TIME)
    726             except NotImplementedError:
    727                 logging.info('dev %d is not Plankton', index)
    728         return False
    729 
    730     def identify_pd_devices(self):
    731         """Instantiate PD devices present in test setup
    732 
    733         @returns list of 2 PD devices if a DUT <-> Plankton found. If
    734         not found, then returns an empty list.
    735         """
    736         devices = []
    737         # For each possible uart console, check to see if a PD console
    738         # is present and determine the number of PD ports.
    739         for console in self.consoles:
    740             if self._is_pd_console(console):
    741                 is_plank = self._is_plankton_console(console)
    742                 num_ports = self._find_num_pd_ports(console)
    743                 # For each PD port that can be accessed via the console,
    744                 # instantiate either PDConsole or PDPlankton device.
    745                 for port in xrange(num_ports):
    746                     if is_plank:
    747                         logging.info('Plankton PD Device on port %d', port)
    748                         devices.append(PDPlanktonDevice(console, port))
    749                     else:
    750                         devices.append(PDConsoleDevice(console, port))
    751                         logging.info('Console PD Device on port %d', port)
    752 
    753         # Determine PD port partners in the list of PD devices. Note, that
    754         # there can be PD devices which are not accessible via a uart console,
    755         # but are connected to a PD port which is accessible.
    756         test_pair = []
    757         for deva in devices:
    758             for dev_idx in range(devices.index(deva) + 1, len(devices)):
    759                 devb = devices[dev_idx]
    760                 pair = [deva, devb]
    761                 if self._verify_plankton_connection(pair):
    762                     test_pair = pair
    763                     devices.remove(deva)
    764                     devices.remove(devb)
    765         return test_pair
    766 
    767