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