Home | History | Annotate | Download | only in multimedia
      1 # Copyright 2014 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 """Facade to access the display-related functionality."""
      6 
      7 import logging
      8 import multiprocessing
      9 import numpy
     10 import os
     11 import re
     12 import shutil
     13 import time
     14 from autotest_lib.client.bin import utils
     15 from autotest_lib.client.common_lib import error
     16 from autotest_lib.client.common_lib import utils as common_utils
     17 from autotest_lib.client.common_lib.cros import retry
     18 from autotest_lib.client.cros import constants
     19 from autotest_lib.client.cros.graphics import graphics_utils
     20 from autotest_lib.client.cros.multimedia import facade_resource
     21 from autotest_lib.client.cros.multimedia import image_generator
     22 from autotest_lib.client.cros.power import sys_power
     23 from telemetry.internal.browser import web_contents
     24 
     25 class TimeoutException(Exception):
     26     """Timeout Exception class."""
     27     pass
     28 
     29 
     30 _FLAKY_CALL_RETRY_TIMEOUT_SEC = 60
     31 _FLAKY_DISPLAY_CALL_RETRY_DELAY_SEC = 2
     32 
     33 _retry_display_call = retry.retry(
     34         (KeyError, error.CmdError),
     35         timeout_min=_FLAKY_CALL_RETRY_TIMEOUT_SEC / 60.0,
     36         delay_sec=_FLAKY_DISPLAY_CALL_RETRY_DELAY_SEC)
     37 
     38 
     39 class DisplayFacadeNative(object):
     40     """Facade to access the display-related functionality.
     41 
     42     The methods inside this class only accept Python native types.
     43     """
     44 
     45     CALIBRATION_IMAGE_PATH = '/tmp/calibration.png'
     46     MINIMUM_REFRESH_RATE_EXPECTED = 25.0
     47     DELAY_TIME = 3
     48     MAX_TYPEC_PORT = 6
     49 
     50     def __init__(self, resource):
     51         """Initializes a DisplayFacadeNative.
     52 
     53         @param resource: A FacadeResource object.
     54         """
     55         self._resource = resource
     56         self._image_generator = image_generator.ImageGenerator()
     57 
     58 
     59     @facade_resource.retry_chrome_call
     60     def get_display_info(self):
     61         """Gets the display info from Chrome.system.display API.
     62 
     63         @return array of dict for display info.
     64         """
     65         extension = self._resource.get_extension(
     66                 constants.DISPLAY_TEST_EXTENSION)
     67         extension.ExecuteJavaScript('window.__display_info = null;')
     68         extension.ExecuteJavaScript(
     69                 "chrome.system.display.getInfo(function(info) {"
     70                 "window.__display_info = info;})")
     71         utils.wait_for_value(lambda: (
     72                 extension.EvaluateJavaScript("window.__display_info") != None),
     73                 expected_value=True)
     74         return extension.EvaluateJavaScript("window.__display_info")
     75 
     76 
     77     @facade_resource.retry_chrome_call
     78     def get_window_info(self):
     79         """Gets the current window info from Chrome.system.window API.
     80 
     81         @return a dict for the information of the current window.
     82         """
     83         extension = self._resource.get_extension()
     84         extension.ExecuteJavaScript('window.__window_info = null;')
     85         extension.ExecuteJavaScript(
     86                 "chrome.windows.getCurrent(function(info) {"
     87                 "window.__window_info = info;})")
     88         utils.wait_for_value(lambda: (
     89                 extension.EvaluateJavaScript("window.__window_info") != None),
     90                 expected_value=True)
     91         return extension.EvaluateJavaScript("window.__window_info")
     92 
     93 
     94     def _get_display_by_id(self, display_id):
     95         """Gets a display by ID.
     96 
     97         @param display_id: id of the display.
     98 
     99         @return: A dict of various display info.
    100         """
    101         for display in self.get_display_info():
    102             if display['id'] == display_id:
    103                 return display
    104         raise RuntimeError('Cannot find display ' + display_id)
    105 
    106 
    107     def get_display_modes(self, display_id):
    108         """Gets all the display modes for the specified display.
    109 
    110         @param display_id: id of the display to get modes from.
    111 
    112         @return: A list of DisplayMode dicts.
    113         """
    114         display = self._get_display_by_id(display_id)
    115         return display['modes']
    116 
    117 
    118     def get_display_rotation(self, display_id):
    119         """Gets the display rotation for the specified display.
    120 
    121         @param display_id: id of the display to get modes from.
    122 
    123         @return: Degree of rotation.
    124         """
    125         display = self._get_display_by_id(display_id)
    126         return display['rotation']
    127 
    128 
    129     def set_display_rotation(self, display_id, rotation,
    130                              delay_before_rotation=0, delay_after_rotation=0):
    131         """Sets the display rotation for the specified display.
    132 
    133         @param display_id: id of the display to get modes from.
    134         @param rotation: degree of rotation
    135         @param delay_before_rotation: time in second for delay before rotation
    136         @param delay_after_rotation: time in second for delay after rotation
    137         """
    138         time.sleep(delay_before_rotation)
    139         extension = self._resource.get_extension(
    140                 constants.DISPLAY_TEST_EXTENSION)
    141         extension.ExecuteJavaScript(
    142                 """
    143                 window.__set_display_rotation_has_error = null;
    144                 chrome.system.display.setDisplayProperties('%(id)s',
    145                     {"rotation": %(rotation)d}, () => {
    146                     if (chrome.runtime.lastError) {
    147                         console.error('Failed to set display rotation',
    148                             chrome.runtime.lastError);
    149                         window.__set_display_rotation_has_error = "failure";
    150                     } else {
    151                         window.__set_display_rotation_has_error = "success";
    152                     }
    153                 });
    154                 """
    155                 % {'id': display_id, 'rotation': rotation}
    156         )
    157         utils.wait_for_value(lambda: (
    158                 extension.EvaluateJavaScript(
    159                     'window.__set_display_rotation_has_error') != None),
    160                 expected_value=True)
    161         time.sleep(delay_after_rotation)
    162         result = extension.EvaluateJavaScript(
    163                 'window.__set_display_rotation_has_error')
    164         if result != 'success':
    165             raise RuntimeError('Failed to set display rotation: %r' % result)
    166 
    167 
    168     def get_available_resolutions(self, display_id):
    169         """Gets the resolutions from the specified display.
    170 
    171         @return a list of (width, height) tuples.
    172         """
    173         display = self._get_display_by_id(display_id)
    174         modes = display['modes']
    175         if 'widthInNativePixels' not in modes[0]:
    176             raise RuntimeError('Cannot find widthInNativePixels attribute')
    177         if display['isInternal']:
    178             logging.info("Getting resolutions of internal display")
    179             return list(set([(mode['width'], mode['height']) for mode in
    180                              modes]))
    181         return list(set([(mode['widthInNativePixels'],
    182                           mode['heightInNativePixels']) for mode in modes]))
    183 
    184 
    185     def get_internal_display_id(self):
    186         """Gets the internal display id.
    187 
    188         @return the id of the internal display.
    189         """
    190         for display in self.get_display_info():
    191             if display['isInternal']:
    192                 return display['id']
    193         raise RuntimeError('Cannot find internal display')
    194 
    195 
    196     def get_first_external_display_id(self):
    197         """Gets the first external display id.
    198 
    199         @return the id of the first external display; -1 if not found.
    200         """
    201         # Get the first external and enabled display
    202         for display in self.get_display_info():
    203             if display['isEnabled'] and not display['isInternal']:
    204                 return display['id']
    205         return -1
    206 
    207 
    208     def set_resolution(self, display_id, width, height, timeout=3):
    209         """Sets the resolution of the specified display.
    210 
    211         @param display_id: id of the display to set resolution for.
    212         @param width: width of the resolution
    213         @param height: height of the resolution
    214         @param timeout: maximal time in seconds waiting for the new resolution
    215                 to settle in.
    216         @raise TimeoutException when the operation is timed out.
    217         """
    218 
    219         extension = self._resource.get_extension(
    220                 constants.DISPLAY_TEST_EXTENSION)
    221         extension.ExecuteJavaScript(
    222                 """
    223                 window.__set_resolution_progress = null;
    224                 chrome.system.display.getInfo((info_array) => {
    225                     var mode;
    226                     for (var info of info_array) {
    227                         if (info['id'] == '%(id)s') {
    228                             for (var m of info['modes']) {
    229                                 if (m['width'] == %(width)d &&
    230                                     m['height'] == %(height)d) {
    231                                     mode = m;
    232                                     break;
    233                                 }
    234                             }
    235                             break;
    236                         }
    237                     }
    238                     if (mode === undefined) {
    239                         console.error('Failed to select the resolution ' +
    240                             '%(width)dx%(height)d');
    241                         window.__set_resolution_progress = "mode not found";
    242                         return;
    243                     }
    244 
    245                     chrome.system.display.setDisplayProperties('%(id)s',
    246                         {'displayMode': mode}, () => {
    247                             if (chrome.runtime.lastError) {
    248                                 window.__set_resolution_progress = "failed: " +
    249                                     chrome.runtime.lastError.message;
    250                             } else {
    251                                 window.__set_resolution_progress = "succeeded";
    252                             }
    253                         }
    254                     );
    255                 });
    256                 """
    257                 % {'id': display_id, 'width': width, 'height': height}
    258         )
    259         utils.wait_for_value(lambda: (
    260                 extension.EvaluateJavaScript(
    261                     'window.__set_resolution_progress') != None),
    262                 expected_value=True)
    263         result = extension.EvaluateJavaScript(
    264                 'window.__set_resolution_progress')
    265         if result != 'succeeded':
    266             raise RuntimeError('Failed to set resolution: %r' % result)
    267 
    268 
    269     @_retry_display_call
    270     def get_external_resolution(self):
    271         """Gets the resolution of the external screen.
    272 
    273         @return The resolution tuple (width, height)
    274         """
    275         return graphics_utils.get_external_resolution()
    276 
    277     def get_internal_resolution(self):
    278         """Gets the resolution of the internal screen.
    279 
    280         @return The resolution tuple (width, height) or None if internal screen
    281                 is not available
    282         """
    283         for display in self.get_display_info():
    284             if display['isInternal']:
    285                 bounds = display['bounds']
    286                 return (bounds['width'], bounds['height'])
    287         return None
    288 
    289 
    290     def set_content_protection(self, state):
    291         """Sets the content protection of the external screen.
    292 
    293         @param state: One of the states 'Undesired', 'Desired', or 'Enabled'
    294         """
    295         connector = self.get_external_connector_name()
    296         graphics_utils.set_content_protection(connector, state)
    297 
    298 
    299     def get_content_protection(self):
    300         """Gets the state of the content protection.
    301 
    302         @param output: The output name as a string.
    303         @return: A string of the state, like 'Undesired', 'Desired', or 'Enabled'.
    304                  False if not supported.
    305         """
    306         connector = self.get_external_connector_name()
    307         return graphics_utils.get_content_protection(connector)
    308 
    309 
    310     def get_external_crtc(self):
    311         """Gets the external crtc.
    312 
    313         @return The id of the external crtc."""
    314         return graphics_utils.get_external_crtc()
    315 
    316 
    317     def get_internal_crtc(self):
    318         """Gets the internal crtc.
    319 
    320         @retrun The id of the internal crtc."""
    321         return graphics_utils.get_internal_crtc()
    322 
    323 
    324     def take_internal_screenshot(self, path):
    325         """Takes internal screenshot.
    326 
    327         @param path: path to image file.
    328         """
    329         self.take_screenshot_crtc(path, self.get_internal_crtc())
    330 
    331 
    332     def take_external_screenshot(self, path):
    333         """Takes external screenshot.
    334 
    335         @param path: path to image file.
    336         """
    337         self.take_screenshot_crtc(path, self.get_external_crtc())
    338 
    339 
    340     def take_screenshot_crtc(self, path, id):
    341         """Captures the DUT screenshot, use id for selecting screen.
    342 
    343         @param path: path to image file.
    344         @param id: The id of the crtc to screenshot.
    345         """
    346 
    347         graphics_utils.take_screenshot_crop(path, crtc_id=id)
    348         return True
    349 
    350 
    351     def save_calibration_image(self, path):
    352         """Save the calibration image to the given path.
    353 
    354         @param path: path to image file.
    355         """
    356         shutil.copy(self.CALIBRATION_IMAGE_PATH, path)
    357         return True
    358 
    359 
    360     def take_tab_screenshot(self, output_path, url_pattern=None):
    361         """Takes a screenshot of the tab specified by the given url pattern.
    362 
    363         @param output_path: A path of the output file.
    364         @param url_pattern: A string of url pattern used to search for tabs.
    365                             Default is to look for .svg image.
    366         """
    367         if url_pattern is None:
    368             # If no URL pattern is provided, defaults to capture the first
    369             # tab that shows SVG image.
    370             url_pattern = '.svg'
    371 
    372         tabs = self._resource.get_tabs()
    373         for i in xrange(0, len(tabs)):
    374             if url_pattern in tabs[i].url:
    375                 data = tabs[i].Screenshot(timeout=5)
    376                 # Flip the colors from BGR to RGB.
    377                 data = numpy.fliplr(data.reshape(-1, 3)).reshape(data.shape)
    378                 data.tofile(output_path)
    379                 break
    380         return True
    381 
    382 
    383     def toggle_mirrored(self):
    384         """Toggles mirrored."""
    385         graphics_utils.screen_toggle_mirrored()
    386         return True
    387 
    388 
    389     def hide_cursor(self):
    390         """Hides mouse cursor."""
    391         graphics_utils.hide_cursor()
    392         return True
    393 
    394 
    395     def hide_typing_cursor(self):
    396         """Hides typing cursor."""
    397         graphics_utils.hide_typing_cursor()
    398         return True
    399 
    400 
    401     def is_mirrored_enabled(self):
    402         """Checks the mirrored state.
    403 
    404         @return True if mirrored mode is enabled.
    405         """
    406         return bool(self.get_display_info()[0]['mirroringSourceId'])
    407 
    408 
    409     def set_mirrored(self, is_mirrored):
    410         """Sets mirrored mode.
    411 
    412         @param is_mirrored: True or False to indicate mirrored state.
    413         @return True if success, False otherwise.
    414         """
    415         if self.is_mirrored_enabled() == is_mirrored:
    416             return True
    417 
    418         retries = 4
    419         while retries > 0:
    420             self.toggle_mirrored()
    421             result = utils.wait_for_value(self.is_mirrored_enabled,
    422                                           expected_value=is_mirrored,
    423                                           timeout_sec=3)
    424             if result == is_mirrored:
    425                 return True
    426             retries -= 1
    427         return False
    428 
    429 
    430     def is_display_primary(self, internal=True):
    431         """Checks if internal screen is primary display.
    432 
    433         @param internal: is internal/external screen primary status requested
    434         @return boolean True if internal display is primary.
    435         """
    436         for info in self.get_display_info():
    437             if info['isInternal'] == internal and info['isPrimary']:
    438                 return True
    439         return False
    440 
    441 
    442     def suspend_resume(self, suspend_time=10):
    443         """Suspends the DUT for a given time in second.
    444 
    445         @param suspend_time: Suspend time in second.
    446         """
    447         sys_power.do_suspend(suspend_time)
    448         return True
    449 
    450 
    451     def suspend_resume_bg(self, suspend_time=10):
    452         """Suspends the DUT for a given time in second in the background.
    453 
    454         @param suspend_time: Suspend time in second.
    455         """
    456         process = multiprocessing.Process(target=self.suspend_resume,
    457                                           args=(suspend_time,))
    458         process.start()
    459         return True
    460 
    461 
    462     @_retry_display_call
    463     def get_external_connector_name(self):
    464         """Gets the name of the external output connector.
    465 
    466         @return The external output connector name as a string, if any.
    467                 Otherwise, return False.
    468         """
    469         return graphics_utils.get_external_connector_name()
    470 
    471 
    472     def get_internal_connector_name(self):
    473         """Gets the name of the internal output connector.
    474 
    475         @return The internal output connector name as a string, if any.
    476                 Otherwise, return False.
    477         """
    478         return graphics_utils.get_internal_connector_name()
    479 
    480 
    481     def wait_external_display_connected(self, display):
    482         """Waits for the specified external display to be connected.
    483 
    484         @param display: The display name as a string, like 'HDMI1', or
    485                         False if no external display is expected.
    486         @return: True if display is connected; False otherwise.
    487         """
    488         result = utils.wait_for_value(self.get_external_connector_name,
    489                                       expected_value=display)
    490         return result == display
    491 
    492 
    493     @facade_resource.retry_chrome_call
    494     def move_to_display(self, display_id):
    495         """Moves the current window to the indicated display.
    496 
    497         @param display_id: The id of the indicated display.
    498         @return True if success.
    499 
    500         @raise TimeoutException if it fails.
    501         """
    502         display_info = self._get_display_by_id(display_id)
    503         if not display_info['isEnabled']:
    504             raise RuntimeError('Cannot find the indicated display')
    505         target_bounds = display_info['bounds']
    506 
    507         extension = self._resource.get_extension()
    508         # If the area of bounds is empty (here we achieve this by setting
    509         # width and height to zero), the window_sizer will automatically
    510         # determine an area which is visible and fits on the screen.
    511         # For more details, see chrome/browser/ui/window_sizer.cc
    512         # Without setting state to 'normal', if the current state is
    513         # 'minimized', 'maximized' or 'fullscreen', the setting of
    514         # 'left', 'top', 'width' and 'height' will be ignored.
    515         # For more details, see chrome/browser/extensions/api/tabs/tabs_api.cc
    516         extension.ExecuteJavaScript(
    517                 """
    518                 var __status = 'Running';
    519                 chrome.windows.update(
    520                         chrome.windows.WINDOW_ID_CURRENT,
    521                         {left: %d, top: %d, width: 0, height: 0,
    522                          state: 'normal'},
    523                         function(info) {
    524                             if (info.left == %d && info.top == %d &&
    525                                 info.state == 'normal')
    526                                 __status = 'Done'; });
    527                 """
    528                 % (target_bounds['left'], target_bounds['top'],
    529                    target_bounds['left'], target_bounds['top'])
    530         )
    531         extension.WaitForJavaScriptCondition(
    532                 "__status == 'Done'",
    533                 timeout=web_contents.DEFAULT_WEB_CONTENTS_TIMEOUT)
    534         return True
    535 
    536 
    537     def is_fullscreen_enabled(self):
    538         """Checks the fullscreen state.
    539 
    540         @return True if fullscreen mode is enabled.
    541         """
    542         return self.get_window_info()['state'] == 'fullscreen'
    543 
    544 
    545     def set_fullscreen(self, is_fullscreen):
    546         """Sets the current window to full screen.
    547 
    548         @param is_fullscreen: True or False to indicate fullscreen state.
    549         @return True if success, False otherwise.
    550         """
    551         extension = self._resource.get_extension()
    552         if not extension:
    553             raise RuntimeError('Autotest extension not found')
    554 
    555         if is_fullscreen:
    556             window_state = "fullscreen"
    557         else:
    558             window_state = "normal"
    559         extension.ExecuteJavaScript(
    560                 """
    561                 var __status = 'Running';
    562                 chrome.windows.update(
    563                         chrome.windows.WINDOW_ID_CURRENT,
    564                         {state: '%s'},
    565                         function() { __status = 'Done'; });
    566                 """
    567                 % window_state)
    568         utils.wait_for_value(lambda: (
    569                 extension.EvaluateJavaScript('__status') == 'Done'),
    570                 expected_value=True)
    571         return self.is_fullscreen_enabled() == is_fullscreen
    572 
    573 
    574     def load_url(self, url):
    575         """Loads the given url in a new tab. The new tab will be active.
    576 
    577         @param url: The url to load as a string.
    578         @return a str, the tab descriptor of the opened tab.
    579         """
    580         return self._resource.load_url(url)
    581 
    582 
    583     def load_calibration_image(self, resolution):
    584         """Opens a new tab and loads a full screen calibration
    585            image from the HTTP server.
    586 
    587         @param resolution: A tuple (width, height) of resolution.
    588         @return a str, the tab descriptor of the opened tab.
    589         """
    590         path = self.CALIBRATION_IMAGE_PATH
    591         self._image_generator.generate_image(resolution[0], resolution[1], path)
    592         os.chmod(path, 0644)
    593         tab_descriptor = self.load_url('file://%s' % path)
    594         return tab_descriptor
    595 
    596 
    597     def load_color_sequence(self, tab_descriptor, color_sequence):
    598         """Displays a series of colors on full screen on the tab.
    599         tab_descriptor is returned by any open tab API of display facade.
    600         e.g.,
    601         tab_descriptor = load_url('about:blank')
    602         load_color_sequence(tab_descriptor, color)
    603 
    604         @param tab_descriptor: Indicate which tab to test.
    605         @param color_sequence: An integer list for switching colors.
    606         @return A list of the timestamp for each switch.
    607         """
    608         tab = self._resource.get_tab_by_descriptor(tab_descriptor)
    609         color_sequence_for_java_script = (
    610                 'var color_sequence = [' +
    611                 ','.join("'#%06X'" % x for x in color_sequence) +
    612                 '];')
    613         # Paints are synchronized to the fresh rate of the screen by
    614         # window.requestAnimationFrame.
    615         tab.ExecuteJavaScript(color_sequence_for_java_script + """
    616             function render(timestamp) {
    617                 window.timestamp_list.push(timestamp);
    618                 if (window.count < color_sequence.length) {
    619                     document.body.style.backgroundColor =
    620                             color_sequence[count];
    621                     window.count++;
    622                     window.requestAnimationFrame(render);
    623                 }
    624             }
    625             window.count = 0;
    626             window.timestamp_list = [];
    627             window.requestAnimationFrame(render);
    628             """)
    629 
    630         # Waiting time is decided by following concerns:
    631         # 1. MINIMUM_REFRESH_RATE_EXPECTED: the minimum refresh rate
    632         #    we expect it to be. Real refresh rate is related to
    633         #    not only hardware devices but also drivers and browsers.
    634         #    Most graphics devices support at least 60fps for a single
    635         #    monitor, and under mirror mode, since the both frames
    636         #    buffers need to be updated for an input frame, the refresh
    637         #    rate will decrease by half, so here we set it to be a
    638         #    little less than 30 (= 60/2) to make it more tolerant.
    639         # 2. DELAY_TIME: extra wait time for timeout.
    640         tab.WaitForJavaScriptCondition(
    641                 'window.count == color_sequence.length',
    642                 timeout=(
    643                     (len(color_sequence) / self.MINIMUM_REFRESH_RATE_EXPECTED)
    644                     + self.DELAY_TIME))
    645         return tab.EvaluateJavaScript("window.timestamp_list")
    646 
    647 
    648     def close_tab(self, tab_descriptor):
    649         """Disables fullscreen and closes the tab of the given tab descriptor.
    650         tab_descriptor is returned by any open tab API of display facade.
    651         e.g.,
    652         1.
    653         tab_descriptor = load_url(url)
    654         close_tab(tab_descriptor)
    655 
    656         2.
    657         tab_descriptor = load_calibration_image(resolution)
    658         close_tab(tab_descriptor)
    659 
    660         @param tab_descriptor: Indicate which tab to be closed.
    661         """
    662         if tab_descriptor:
    663             # set_fullscreen(False) is necessary here because currently there
    664             # is a bug in tabs.Close(). If the current state is fullscreen and
    665             # we call close_tab() without setting state back to normal, it will
    666             # cancel fullscreen mode without changing system configuration, and
    667             # so that the next time someone calls set_fullscreen(True), the
    668             # function will find that current state is already 'fullscreen'
    669             # (though it is not) and do nothing, which will break all the
    670             # following tests.
    671             self.set_fullscreen(False)
    672             self._resource.close_tab(tab_descriptor)
    673         else:
    674             logging.error('close_tab: not a valid tab_descriptor')
    675 
    676         return True
    677 
    678 
    679     def reset_connector_if_applicable(self, connector_type):
    680         """Resets Type-C video connector from host end if applicable.
    681 
    682         It's the workaround sequence since sometimes Type-C dongle becomes
    683         corrupted and needs to be re-plugged.
    684 
    685         @param connector_type: A string, like "VGA", "DVI", "HDMI", or "DP".
    686         """
    687         if connector_type != 'HDMI' and connector_type != 'DP':
    688             return
    689         # Decide if we need to add --name=cros_pd
    690         usbpd_command = 'ectool --name=cros_pd usbpd'
    691         try:
    692             common_utils.run('%s 0' % usbpd_command)
    693         except error.CmdError:
    694             usbpd_command = 'ectool usbpd'
    695 
    696         port = 0
    697         while port < self.MAX_TYPEC_PORT:
    698             # We use usbpd to get Role information and then power cycle the
    699             # SRC one.
    700             command = '%s %d' % (usbpd_command, port)
    701             try:
    702                 output = common_utils.run(command).stdout
    703                 if re.compile('Role.*SRC').search(output):
    704                     logging.info('power-cycle Type-C port %d', port)
    705                     common_utils.run('%s sink' % command)
    706                     common_utils.run('%s auto' % command)
    707                 port += 1
    708             except error.CmdError:
    709                 break
    710