1*9c5db199SXin Li# Lint as: python2, python3 2*9c5db199SXin Li# Copyright (c) 2014 The Chromium OS Authors. All rights reserved. 3*9c5db199SXin Li# Use of this source code is governed by a BSD-style license that can be 4*9c5db199SXin Li# found in the LICENSE file. 5*9c5db199SXin Li 6*9c5db199SXin Lifrom __future__ import absolute_import 7*9c5db199SXin Lifrom __future__ import division 8*9c5db199SXin Lifrom __future__ import print_function 9*9c5db199SXin Liimport atexit 10*9c5db199SXin Liimport six.moves.http_client 11*9c5db199SXin Liimport six 12*9c5db199SXin Liimport logging 13*9c5db199SXin Liimport os 14*9c5db199SXin Liimport socket 15*9c5db199SXin Liimport time 16*9c5db199SXin Lifrom six.moves import range 17*9c5db199SXin Liimport six.moves.xmlrpc_client 18*9c5db199SXin Lifrom contextlib import contextmanager 19*9c5db199SXin Li 20*9c5db199SXin Litry: 21*9c5db199SXin Li from PIL import Image 22*9c5db199SXin Liexcept ImportError: 23*9c5db199SXin Li Image = None 24*9c5db199SXin Li 25*9c5db199SXin Lifrom autotest_lib.client.bin import utils 26*9c5db199SXin Lifrom autotest_lib.client.common_lib import error 27*9c5db199SXin Lifrom autotest_lib.client.cros.chameleon import audio_board 28*9c5db199SXin Lifrom autotest_lib.client.cros.chameleon import chameleon_bluetooth_audio 29*9c5db199SXin Lifrom autotest_lib.client.cros.chameleon import edid as edid_lib 30*9c5db199SXin Lifrom autotest_lib.client.cros.chameleon import usb_controller 31*9c5db199SXin Li 32*9c5db199SXin Li 33*9c5db199SXin LiCHAMELEON_PORT = 9992 34*9c5db199SXin LiCHAMELEOND_LOG_REMOTE_PATH = '/var/log/chameleond' 35*9c5db199SXin LiDAEMON_LOG_REMOTE_PATH = '/var/log/daemon.log' 36*9c5db199SXin LiBTMON_LOG_REMOTE_PATH = '/var/log/btsnoop.log' 37*9c5db199SXin LiCHAMELEON_READY_TEST = 'GetSupportedPorts' 38*9c5db199SXin Li 39*9c5db199SXin Li 40*9c5db199SXin Liclass ChameleonConnectionError(error.TestError): 41*9c5db199SXin Li """Indicates that connecting to Chameleon failed. 42*9c5db199SXin Li 43*9c5db199SXin Li It is fatal to the test unless caught. 44*9c5db199SXin Li """ 45*9c5db199SXin Li pass 46*9c5db199SXin Li 47*9c5db199SXin Li 48*9c5db199SXin Liclass _Method(object): 49*9c5db199SXin Li """Class to save the name of the RPC method instead of the real object. 50*9c5db199SXin Li 51*9c5db199SXin Li It keeps the name of the RPC method locally first such that the RPC method 52*9c5db199SXin Li can be evaluated to a real object while it is called. Its purpose is to 53*9c5db199SXin Li refer to the latest RPC proxy as the original previous-saved RPC proxy may 54*9c5db199SXin Li be lost due to reboot. 55*9c5db199SXin Li 56*9c5db199SXin Li The call_server is the method which does refer to the latest RPC proxy. 57*9c5db199SXin Li 58*9c5db199SXin Li This class and the re-connection mechanism in ChameleonConnection is 59*9c5db199SXin Li copied from third_party/autotest/files/server/cros/faft/rpc_proxy.py 60*9c5db199SXin Li 61*9c5db199SXin Li """ 62*9c5db199SXin Li def __init__(self, call_server, name): 63*9c5db199SXin Li """Constructs a _Method. 64*9c5db199SXin Li 65*9c5db199SXin Li @param call_server: the call_server method 66*9c5db199SXin Li @param name: the method name or instance name provided by the 67*9c5db199SXin Li remote server 68*9c5db199SXin Li 69*9c5db199SXin Li """ 70*9c5db199SXin Li self.__call_server = call_server 71*9c5db199SXin Li self._name = name 72*9c5db199SXin Li 73*9c5db199SXin Li 74*9c5db199SXin Li def __getattr__(self, name): 75*9c5db199SXin Li """Support a nested method. 76*9c5db199SXin Li 77*9c5db199SXin Li For example, proxy.system.listMethods() would need to use this method 78*9c5db199SXin Li to get system and then to get listMethods. 79*9c5db199SXin Li 80*9c5db199SXin Li @param name: the method name or instance name provided by the 81*9c5db199SXin Li remote server 82*9c5db199SXin Li 83*9c5db199SXin Li @return: a callable _Method object. 84*9c5db199SXin Li 85*9c5db199SXin Li """ 86*9c5db199SXin Li return _Method(self.__call_server, "%s.%s" % (self._name, name)) 87*9c5db199SXin Li 88*9c5db199SXin Li 89*9c5db199SXin Li def __call__(self, *args, **dargs): 90*9c5db199SXin Li """The call method of the object. 91*9c5db199SXin Li 92*9c5db199SXin Li @param args: arguments for the remote method. 93*9c5db199SXin Li @param kwargs: keyword arguments for the remote method. 94*9c5db199SXin Li 95*9c5db199SXin Li @return: the result returned by the remote method. 96*9c5db199SXin Li 97*9c5db199SXin Li """ 98*9c5db199SXin Li return self.__call_server(self._name, *args, **dargs) 99*9c5db199SXin Li 100*9c5db199SXin Li 101*9c5db199SXin Liclass ChameleonConnection(object): 102*9c5db199SXin Li """ChameleonConnection abstracts the network connection to the board. 103*9c5db199SXin Li 104*9c5db199SXin Li When a chameleon board is rebooted, a xmlrpc call would incur a 105*9c5db199SXin Li socket error. To fix the error, a client has to reconnect to the server. 106*9c5db199SXin Li ChameleonConnection is a wrapper of chameleond proxy created by 107*9c5db199SXin Li xmlrpclib.ServerProxy(). ChameleonConnection has the capability to 108*9c5db199SXin Li automatically reconnect to the server when such socket error occurs. 109*9c5db199SXin Li The nice feature is that the auto re-connection is performed inside this 110*9c5db199SXin Li wrapper and is transparent to the caller. 111*9c5db199SXin Li 112*9c5db199SXin Li Note: 113*9c5db199SXin Li 1. When running chameleon autotests in lab machines, it is 114*9c5db199SXin Li ChameleonConnection._create_server_proxy() that is invoked. 115*9c5db199SXin Li 2. When running chameleon autotests in local chroot, it is 116*9c5db199SXin Li rpc_server_tracker.xmlrpc_connect() in server/hosts/chameleon_host.py 117*9c5db199SXin Li that is invoked. 118*9c5db199SXin Li 119*9c5db199SXin Li ChameleonBoard and ChameleonPort use it for accessing Chameleon RPC. 120*9c5db199SXin Li 121*9c5db199SXin Li """ 122*9c5db199SXin Li 123*9c5db199SXin Li def __init__(self, hostname, port=CHAMELEON_PORT, proxy_generator=None, 124*9c5db199SXin Li ready_test_name=CHAMELEON_READY_TEST): 125*9c5db199SXin Li """Constructs a ChameleonConnection. 126*9c5db199SXin Li 127*9c5db199SXin Li @param hostname: Hostname the chameleond process is running. 128*9c5db199SXin Li @param port: Port number the chameleond process is listening on. 129*9c5db199SXin Li @param proxy_generator: a function to generate server proxy. 130*9c5db199SXin Li @param ready_test_name: run this method on the remote server ot test 131*9c5db199SXin Li if the server is connected correctly. 132*9c5db199SXin Li 133*9c5db199SXin Li @raise ChameleonConnectionError if connection failed. 134*9c5db199SXin Li """ 135*9c5db199SXin Li self._hostname = hostname 136*9c5db199SXin Li self._port = port 137*9c5db199SXin Li 138*9c5db199SXin Li # Note: it is difficult to put the lambda function as the default 139*9c5db199SXin Li # value of the proxy_generator argument. In that case, the binding 140*9c5db199SXin Li # of arguments (hostname and port) would be delayed until run time 141*9c5db199SXin Li # which requires to pass an instance as an argument to labmda. 142*9c5db199SXin Li # That becomes cumbersome since server/hosts/chameleon_host.py 143*9c5db199SXin Li # would also pass a lambda without argument to instantiate this object. 144*9c5db199SXin Li # Use the labmda function as follows would bind the needed arguments 145*9c5db199SXin Li # immediately which is much simpler. 146*9c5db199SXin Li self._proxy_generator = proxy_generator or self._create_server_proxy 147*9c5db199SXin Li 148*9c5db199SXin Li self._ready_test_name = ready_test_name 149*9c5db199SXin Li self._chameleond_proxy = None 150*9c5db199SXin Li 151*9c5db199SXin Li 152*9c5db199SXin Li def _create_server_proxy(self): 153*9c5db199SXin Li """Creates the chameleond server proxy. 154*9c5db199SXin Li 155*9c5db199SXin Li @param hostname: Hostname the chameleond process is running. 156*9c5db199SXin Li @param port: Port number the chameleond process is listening on. 157*9c5db199SXin Li 158*9c5db199SXin Li @return ServerProxy object to chameleond. 159*9c5db199SXin Li 160*9c5db199SXin Li @raise ChameleonConnectionError if connection failed. 161*9c5db199SXin Li 162*9c5db199SXin Li """ 163*9c5db199SXin Li remote = 'http://%s:%s' % (self._hostname, self._port) 164*9c5db199SXin Li chameleond_proxy = six.moves.xmlrpc_client.ServerProxy(remote, allow_none=True) 165*9c5db199SXin Li logging.info('ChameleonConnection._create_server_proxy() called') 166*9c5db199SXin Li # Call a RPC to test. 167*9c5db199SXin Li try: 168*9c5db199SXin Li getattr(chameleond_proxy, self._ready_test_name)() 169*9c5db199SXin Li except (socket.error, 170*9c5db199SXin Li six.moves.xmlrpc_client.ProtocolError, 171*9c5db199SXin Li six.moves.http_client.BadStatusLine) as e: 172*9c5db199SXin Li raise ChameleonConnectionError(e) 173*9c5db199SXin Li return chameleond_proxy 174*9c5db199SXin Li 175*9c5db199SXin Li 176*9c5db199SXin Li def _reconnect(self): 177*9c5db199SXin Li """Reconnect to chameleond.""" 178*9c5db199SXin Li self._chameleond_proxy = self._proxy_generator() 179*9c5db199SXin Li 180*9c5db199SXin Li 181*9c5db199SXin Li def __call_server(self, name, *args, **kwargs): 182*9c5db199SXin Li """Bind the name to the chameleond proxy and execute the method. 183*9c5db199SXin Li 184*9c5db199SXin Li @param name: the method name or instance name provided by the 185*9c5db199SXin Li remote server. 186*9c5db199SXin Li @param args: arguments for the remote method. 187*9c5db199SXin Li @param kwargs: keyword arguments for the remote method. 188*9c5db199SXin Li 189*9c5db199SXin Li @return: the result returned by the remote method. 190*9c5db199SXin Li 191*9c5db199SXin Li @raise ChameleonConnectionError if the call failed after a reconnection. 192*9c5db199SXin Li 193*9c5db199SXin Li """ 194*9c5db199SXin Li try: 195*9c5db199SXin Li return getattr(self._chameleond_proxy, name)(*args, **kwargs) 196*9c5db199SXin Li except (AttributeError, socket.error): 197*9c5db199SXin Li # Reconnect and invoke the method again. 198*9c5db199SXin Li logging.info('Reconnecting chameleond proxy: %s', name) 199*9c5db199SXin Li self._reconnect() 200*9c5db199SXin Li try: 201*9c5db199SXin Li return getattr(self._chameleond_proxy, name)(*args, **kwargs) 202*9c5db199SXin Li except (socket.error) as e: 203*9c5db199SXin Li raise ChameleonConnectionError( 204*9c5db199SXin Li ("The RPC call %s still failed with %s" 205*9c5db199SXin Li " after a reconnection.") % (name, e)) 206*9c5db199SXin Li return None 207*9c5db199SXin Li 208*9c5db199SXin Li def __getattr__(self, name): 209*9c5db199SXin Li """Get the callable _Method object. 210*9c5db199SXin Li 211*9c5db199SXin Li @param name: the method name or instance name provided by the 212*9c5db199SXin Li remote server 213*9c5db199SXin Li 214*9c5db199SXin Li @return: a callable _Method object. 215*9c5db199SXin Li 216*9c5db199SXin Li """ 217*9c5db199SXin Li return _Method(self.__call_server, name) 218*9c5db199SXin Li 219*9c5db199SXin Li 220*9c5db199SXin Liclass ChameleonBoard(object): 221*9c5db199SXin Li """ChameleonBoard is an abstraction of a Chameleon board. 222*9c5db199SXin Li 223*9c5db199SXin Li A Chameleond RPC proxy is passed to the construction such that it can 224*9c5db199SXin Li use this proxy to control the Chameleon board. 225*9c5db199SXin Li 226*9c5db199SXin Li User can use host to access utilities that are not provided by 227*9c5db199SXin Li Chameleond XMLRPC server, e.g. send_file and get_file, which are provided by 228*9c5db199SXin Li ssh_host.SSHHost, which is the base class of ChameleonHost. 229*9c5db199SXin Li 230*9c5db199SXin Li """ 231*9c5db199SXin Li 232*9c5db199SXin Li def __init__(self, chameleon_connection, chameleon_host=None): 233*9c5db199SXin Li """Construct a ChameleonBoard. 234*9c5db199SXin Li 235*9c5db199SXin Li @param chameleon_connection: ChameleonConnection object. 236*9c5db199SXin Li @param chameleon_host: ChameleonHost object. None if this ChameleonBoard 237*9c5db199SXin Li is not created by a ChameleonHost. 238*9c5db199SXin Li """ 239*9c5db199SXin Li self.host = chameleon_host 240*9c5db199SXin Li self._output_log_file = None 241*9c5db199SXin Li self._chameleond_proxy = chameleon_connection 242*9c5db199SXin Li self._usb_ctrl = usb_controller.USBController(chameleon_connection) 243*9c5db199SXin Li if self._chameleond_proxy.HasAudioBoard(): 244*9c5db199SXin Li self._audio_board = audio_board.AudioBoard(chameleon_connection) 245*9c5db199SXin Li else: 246*9c5db199SXin Li self._audio_board = None 247*9c5db199SXin Li logging.info('There is no audio board on this Chameleon.') 248*9c5db199SXin Li self._bluetooth_ref_controller = ( 249*9c5db199SXin Li chameleon_bluetooth_audio. 250*9c5db199SXin Li BluetoothRefController(chameleon_connection) 251*9c5db199SXin Li ) 252*9c5db199SXin Li 253*9c5db199SXin Li 254*9c5db199SXin Li def reset(self): 255*9c5db199SXin Li """Resets Chameleon board.""" 256*9c5db199SXin Li self._chameleond_proxy.Reset() 257*9c5db199SXin Li 258*9c5db199SXin Li 259*9c5db199SXin Li def setup_and_reset(self, output_dir=None): 260*9c5db199SXin Li """Setup and reset Chameleon board. 261*9c5db199SXin Li 262*9c5db199SXin Li @param output_dir: Setup the output directory. 263*9c5db199SXin Li None for just reset the board. 264*9c5db199SXin Li """ 265*9c5db199SXin Li if output_dir and self.host is not None: 266*9c5db199SXin Li logging.info('setup_and_reset: dir %s, chameleon host %s', 267*9c5db199SXin Li output_dir, self.host.hostname) 268*9c5db199SXin Li log_dir = os.path.join(output_dir, 'chameleond', self.host.hostname) 269*9c5db199SXin Li # Only clear the chameleon board log and register get log callback 270*9c5db199SXin Li # when we first create the log_dir. 271*9c5db199SXin Li if not os.path.exists(log_dir): 272*9c5db199SXin Li # remove old log. 273*9c5db199SXin Li self.host.run('>%s' % CHAMELEOND_LOG_REMOTE_PATH) 274*9c5db199SXin Li os.makedirs(log_dir) 275*9c5db199SXin Li self._output_log_file = os.path.join(log_dir, 'log') 276*9c5db199SXin Li atexit.register(self._get_log) 277*9c5db199SXin Li self.reset() 278*9c5db199SXin Li 279*9c5db199SXin Li 280*9c5db199SXin Li def register_raspPi_log(self, output_dir): 281*9c5db199SXin Li """Register log for raspberry Pi 282*9c5db199SXin Li 283*9c5db199SXin Li This method log bluetooth related files on Raspberry Pi. 284*9c5db199SXin Li If the host is not running on Raspberry Pi, some files may be ignored. 285*9c5db199SXin Li """ 286*9c5db199SXin Li log_dir = os.path.join(output_dir, 'chameleond', self.host.hostname) 287*9c5db199SXin Li 288*9c5db199SXin Li if not os.path.exists(log_dir): 289*9c5db199SXin Li os.makedirs(log_dir) 290*9c5db199SXin Li 291*9c5db199SXin Li def log_new_gen(source_path): 292*9c5db199SXin Li """Generate function to save logs logging during the test 293*9c5db199SXin Li 294*9c5db199SXin Li @param source_path: The log file path that want to be saved 295*9c5db199SXin Li 296*9c5db199SXin Li @return: Function to save the logs if file in source_path exists, 297*9c5db199SXin Li None otherwise. 298*9c5db199SXin Li """ 299*9c5db199SXin Li 300*9c5db199SXin Li # Check if the file exists 301*9c5db199SXin Li file_exist = self.host.run('[ -f %s ] || echo "not found"' % 302*9c5db199SXin Li source_path).stdout.strip() 303*9c5db199SXin Li if file_exist == 'not found': 304*9c5db199SXin Li return None 305*9c5db199SXin Li 306*9c5db199SXin Li byte_to_skip = self.host.run('stat --printf="%%s" %s' % 307*9c5db199SXin Li source_path).stdout.strip() 308*9c5db199SXin Li file_name = os.path.basename(source_path) 309*9c5db199SXin Li target_path = os.path.join(log_dir, file_name) 310*9c5db199SXin Li 311*9c5db199SXin Li def log_new(): 312*9c5db199SXin Li """Save the newly added logs""" 313*9c5db199SXin Li tmp_file_path = source_path+'.new' 314*9c5db199SXin Li 315*9c5db199SXin Li # Store a temporary file with newly added content 316*9c5db199SXin Li # Set the start point as byte_to_skip + 1 317*9c5db199SXin Li self.host.run('tail -c +%s %s > %s' % (int(byte_to_skip)+1, 318*9c5db199SXin Li source_path, 319*9c5db199SXin Li tmp_file_path)) 320*9c5db199SXin Li self.host.get_file(tmp_file_path, target_path) 321*9c5db199SXin Li self.host.run('rm %s' % tmp_file_path) 322*9c5db199SXin Li return log_new 323*9c5db199SXin Li 324*9c5db199SXin Li for source_path in [CHAMELEOND_LOG_REMOTE_PATH, DAEMON_LOG_REMOTE_PATH]: 325*9c5db199SXin Li log_new_func = log_new_gen(source_path) 326*9c5db199SXin Li if log_new_func: 327*9c5db199SXin Li atexit.register(log_new_func) 328*9c5db199SXin Li 329*9c5db199SXin Li 330*9c5db199SXin Li def btmon_atexit_gen(btmon_pid): 331*9c5db199SXin Li """Generate a function to kill the btmon process and save the log 332*9c5db199SXin Li 333*9c5db199SXin Li @param btmon_pid: PID of the btmon process 334*9c5db199SXin Li """ 335*9c5db199SXin Li 336*9c5db199SXin Li def btmon_atexit(): 337*9c5db199SXin Li """Kill the btmon with specified PID and save the log""" 338*9c5db199SXin Li 339*9c5db199SXin Li file_name = os.path.basename(BTMON_LOG_REMOTE_PATH) 340*9c5db199SXin Li target_path = os.path.join(log_dir, file_name) 341*9c5db199SXin Li 342*9c5db199SXin Li self.host.run('kill %d' % btmon_pid) 343*9c5db199SXin Li self.host.get_file(BTMON_LOG_REMOTE_PATH, target_path) 344*9c5db199SXin Li return btmon_atexit 345*9c5db199SXin Li 346*9c5db199SXin Li 347*9c5db199SXin Li # Kill all btmon process before creating a new one 348*9c5db199SXin Li self.host.run('pkill btmon || true') 349*9c5db199SXin Li 350*9c5db199SXin Li # Get available btmon options in the chameleon host 351*9c5db199SXin Li btmon_options = '' 352*9c5db199SXin Li btmon_help = self.host.run('btmon --help').stdout 353*9c5db199SXin Li 354*9c5db199SXin Li for option in 'SA': 355*9c5db199SXin Li if '-%s' % option in btmon_help: 356*9c5db199SXin Li btmon_options += option 357*9c5db199SXin Li 358*9c5db199SXin Li # Store btmon log 359*9c5db199SXin Li btmon_pid = int(self.host.run_background('btmon -%sw %s' 360*9c5db199SXin Li % (btmon_options, 361*9c5db199SXin Li BTMON_LOG_REMOTE_PATH))) 362*9c5db199SXin Li if btmon_pid > 0: 363*9c5db199SXin Li atexit.register(btmon_atexit_gen(btmon_pid)) 364*9c5db199SXin Li 365*9c5db199SXin Li 366*9c5db199SXin Li def reboot(self): 367*9c5db199SXin Li """Reboots Chameleon board.""" 368*9c5db199SXin Li self._chameleond_proxy.Reboot() 369*9c5db199SXin Li 370*9c5db199SXin Li 371*9c5db199SXin Li def get_bt_commit_hash(self): 372*9c5db199SXin Li """ Read the current git commit hash of chameleond.""" 373*9c5db199SXin Li return self._chameleond_proxy.get_bt_commit_hash() 374*9c5db199SXin Li 375*9c5db199SXin Li 376*9c5db199SXin Li def _get_log(self): 377*9c5db199SXin Li """Get log from chameleon. It will be registered by atexit. 378*9c5db199SXin Li 379*9c5db199SXin Li It's a private method. We will setup output_dir before using this 380*9c5db199SXin Li method. 381*9c5db199SXin Li """ 382*9c5db199SXin Li self.host.get_file(CHAMELEOND_LOG_REMOTE_PATH, self._output_log_file) 383*9c5db199SXin Li 384*9c5db199SXin Li def log_message(self, msg): 385*9c5db199SXin Li """Log a message in chameleond log and system log.""" 386*9c5db199SXin Li self._chameleond_proxy.log_message(msg) 387*9c5db199SXin Li 388*9c5db199SXin Li def get_all_ports(self): 389*9c5db199SXin Li """Gets all the ports on Chameleon board which are connected. 390*9c5db199SXin Li 391*9c5db199SXin Li @return: A list of ChameleonPort objects. 392*9c5db199SXin Li """ 393*9c5db199SXin Li ports = self._chameleond_proxy.ProbePorts() 394*9c5db199SXin Li return [ChameleonPort(self._chameleond_proxy, port) for port in ports] 395*9c5db199SXin Li 396*9c5db199SXin Li 397*9c5db199SXin Li def get_all_inputs(self): 398*9c5db199SXin Li """Gets all the input ports on Chameleon board which are connected. 399*9c5db199SXin Li 400*9c5db199SXin Li @return: A list of ChameleonPort objects. 401*9c5db199SXin Li """ 402*9c5db199SXin Li ports = self._chameleond_proxy.ProbeInputs() 403*9c5db199SXin Li return [ChameleonPort(self._chameleond_proxy, port) for port in ports] 404*9c5db199SXin Li 405*9c5db199SXin Li 406*9c5db199SXin Li def get_all_outputs(self): 407*9c5db199SXin Li """Gets all the output ports on Chameleon board which are connected. 408*9c5db199SXin Li 409*9c5db199SXin Li @return: A list of ChameleonPort objects. 410*9c5db199SXin Li """ 411*9c5db199SXin Li ports = self._chameleond_proxy.ProbeOutputs() 412*9c5db199SXin Li return [ChameleonPort(self._chameleond_proxy, port) for port in ports] 413*9c5db199SXin Li 414*9c5db199SXin Li 415*9c5db199SXin Li def get_label(self): 416*9c5db199SXin Li """Gets the label which indicates the display connection. 417*9c5db199SXin Li 418*9c5db199SXin Li @return: A string of the label, like 'hdmi', 'dp_hdmi', etc. 419*9c5db199SXin Li """ 420*9c5db199SXin Li connectors = [] 421*9c5db199SXin Li for port in self._chameleond_proxy.ProbeInputs(): 422*9c5db199SXin Li if self._chameleond_proxy.HasVideoSupport(port): 423*9c5db199SXin Li connector = self._chameleond_proxy.GetConnectorType(port).lower() 424*9c5db199SXin Li connectors.append(connector) 425*9c5db199SXin Li # Eliminate duplicated ports. It simplifies the labels of dual-port 426*9c5db199SXin Li # devices, i.e. dp_dp categorized into dp. 427*9c5db199SXin Li return '_'.join(sorted(set(connectors))) 428*9c5db199SXin Li 429*9c5db199SXin Li 430*9c5db199SXin Li def get_audio_board(self): 431*9c5db199SXin Li """Gets the audio board on Chameleon. 432*9c5db199SXin Li 433*9c5db199SXin Li @return: An AudioBoard object. 434*9c5db199SXin Li """ 435*9c5db199SXin Li return self._audio_board 436*9c5db199SXin Li 437*9c5db199SXin Li 438*9c5db199SXin Li def get_usb_controller(self): 439*9c5db199SXin Li """Gets the USB controller on Chameleon. 440*9c5db199SXin Li 441*9c5db199SXin Li @return: A USBController object. 442*9c5db199SXin Li """ 443*9c5db199SXin Li return self._usb_ctrl 444*9c5db199SXin Li 445*9c5db199SXin Li 446*9c5db199SXin Li def get_bluetooth_base(self): 447*9c5db199SXin Li """Gets the Bluetooth base object on Chameleon. 448*9c5db199SXin Li 449*9c5db199SXin Li This is a base object that does not emulate any Bluetooth device. 450*9c5db199SXin Li 451*9c5db199SXin Li @return: A BluetoothBaseFlow object. 452*9c5db199SXin Li """ 453*9c5db199SXin Li return self._chameleond_proxy.bluetooth_base 454*9c5db199SXin Li 455*9c5db199SXin Li 456*9c5db199SXin Li def get_bluetooth_tester(self): 457*9c5db199SXin Li """Gets the Bluetooth tester object on Chameleon. 458*9c5db199SXin Li 459*9c5db199SXin Li @return: A BluetoothTester object. 460*9c5db199SXin Li """ 461*9c5db199SXin Li return self._chameleond_proxy.bluetooth_tester 462*9c5db199SXin Li 463*9c5db199SXin Li 464*9c5db199SXin Li def get_bluetooth_audio(self): 465*9c5db199SXin Li """Gets the Bluetooth audio object on Chameleon. 466*9c5db199SXin Li 467*9c5db199SXin Li @return: A RaspiBluetoothAudioFlow object. 468*9c5db199SXin Li """ 469*9c5db199SXin Li return self._chameleond_proxy.bluetooth_audio 470*9c5db199SXin Li 471*9c5db199SXin Li 472*9c5db199SXin Li def get_bluetooth_hid_mouse(self): 473*9c5db199SXin Li """Gets the emulated Bluetooth (BR/EDR) HID mouse on Chameleon. 474*9c5db199SXin Li 475*9c5db199SXin Li @return: A BluetoothHIDMouseFlow object. 476*9c5db199SXin Li """ 477*9c5db199SXin Li return self._chameleond_proxy.bluetooth_mouse 478*9c5db199SXin Li 479*9c5db199SXin Li 480*9c5db199SXin Li def get_bluetooth_hid_keyboard(self): 481*9c5db199SXin Li """Gets the emulated Bluetooth (BR/EDR) HID keyboard on Chameleon. 482*9c5db199SXin Li 483*9c5db199SXin Li @return: A BluetoothHIDKeyboardFlow object. 484*9c5db199SXin Li """ 485*9c5db199SXin Li return self._chameleond_proxy.bluetooth_keyboard 486*9c5db199SXin Li 487*9c5db199SXin Li def get_ble_fast_pair(self): 488*9c5db199SXin Li """Gets the emulated Bluetooth Fast Pair device on Chameleon. 489*9c5db199SXin Li 490*9c5db199SXin Li @return: A RaspiBLEFastPair object. 491*9c5db199SXin Li """ 492*9c5db199SXin Li return self._chameleond_proxy.ble_fast_pair 493*9c5db199SXin Li 494*9c5db199SXin Li def get_bluetooth_ref_controller(self): 495*9c5db199SXin Li """Gets the emulated BluetoothRefController. 496*9c5db199SXin Li 497*9c5db199SXin Li @return: A BluetoothRefController object. 498*9c5db199SXin Li """ 499*9c5db199SXin Li return self._bluetooth_ref_controller 500*9c5db199SXin Li 501*9c5db199SXin Li 502*9c5db199SXin Li def get_avsync_probe(self): 503*9c5db199SXin Li """Gets the avsync probe device on Chameleon. 504*9c5db199SXin Li 505*9c5db199SXin Li @return: An AVSyncProbeFlow object. 506*9c5db199SXin Li """ 507*9c5db199SXin Li return self._chameleond_proxy.avsync_probe 508*9c5db199SXin Li 509*9c5db199SXin Li 510*9c5db199SXin Li def get_motor_board(self): 511*9c5db199SXin Li """Gets the motor_board device on Chameleon. 512*9c5db199SXin Li 513*9c5db199SXin Li @return: An MotorBoard object. 514*9c5db199SXin Li """ 515*9c5db199SXin Li return self._chameleond_proxy.motor_board 516*9c5db199SXin Li 517*9c5db199SXin Li 518*9c5db199SXin Li def get_mac_address(self): 519*9c5db199SXin Li """Gets the MAC address of Chameleon. 520*9c5db199SXin Li 521*9c5db199SXin Li @return: A string for MAC address. 522*9c5db199SXin Li """ 523*9c5db199SXin Li return self._chameleond_proxy.GetMacAddress() 524*9c5db199SXin Li 525*9c5db199SXin Li 526*9c5db199SXin Li def get_bluetooth_a2dp_sink(self): 527*9c5db199SXin Li """Gets the Bluetooth A2DP sink on chameleon host. 528*9c5db199SXin Li 529*9c5db199SXin Li @return: A BluetoothA2DPSinkFlow object. 530*9c5db199SXin Li """ 531*9c5db199SXin Li return self._chameleond_proxy.bluetooth_a2dp_sink 532*9c5db199SXin Li 533*9c5db199SXin Li def get_ble_mouse(self): 534*9c5db199SXin Li """Gets the BLE mouse (nRF52) on chameleon host. 535*9c5db199SXin Li 536*9c5db199SXin Li @return: A BluetoothHIDFlow object. 537*9c5db199SXin Li """ 538*9c5db199SXin Li return self._chameleond_proxy.ble_mouse 539*9c5db199SXin Li 540*9c5db199SXin Li def get_ble_keyboard(self): 541*9c5db199SXin Li """Gets the BLE keyboard on chameleon host. 542*9c5db199SXin Li 543*9c5db199SXin Li @return: A BluetoothHIDFlow object. 544*9c5db199SXin Li """ 545*9c5db199SXin Li return self._chameleond_proxy.ble_keyboard 546*9c5db199SXin Li 547*9c5db199SXin Li def get_ble_phone(self): 548*9c5db199SXin Li """Gets the emulated Bluetooth phone on Chameleon. 549*9c5db199SXin Li 550*9c5db199SXin Li @return: A RaspiPhone object. 551*9c5db199SXin Li """ 552*9c5db199SXin Li return self._chameleond_proxy.ble_phone 553*9c5db199SXin Li 554*9c5db199SXin Li def get_platform(self): 555*9c5db199SXin Li """ Get the Hardware Platform of the chameleon host 556*9c5db199SXin Li 557*9c5db199SXin Li @return: CHROMEOS/RASPI 558*9c5db199SXin Li """ 559*9c5db199SXin Li return self._chameleond_proxy.get_platform() 560*9c5db199SXin Li 561*9c5db199SXin Li 562*9c5db199SXin Liclass ChameleonPort(object): 563*9c5db199SXin Li """ChameleonPort is an abstraction of a general port of a Chameleon board. 564*9c5db199SXin Li 565*9c5db199SXin Li It only contains some common methods shared with audio and video ports. 566*9c5db199SXin Li 567*9c5db199SXin Li A Chameleond RPC proxy and an port_id are passed to the construction. 568*9c5db199SXin Li The port_id is the unique identity to the port. 569*9c5db199SXin Li """ 570*9c5db199SXin Li 571*9c5db199SXin Li def __init__(self, chameleond_proxy, port_id): 572*9c5db199SXin Li """Construct a ChameleonPort. 573*9c5db199SXin Li 574*9c5db199SXin Li @param chameleond_proxy: Chameleond RPC proxy object. 575*9c5db199SXin Li @param port_id: The ID of the input port. 576*9c5db199SXin Li """ 577*9c5db199SXin Li self.chameleond_proxy = chameleond_proxy 578*9c5db199SXin Li self.port_id = port_id 579*9c5db199SXin Li 580*9c5db199SXin Li 581*9c5db199SXin Li def get_connector_id(self): 582*9c5db199SXin Li """Returns the connector ID. 583*9c5db199SXin Li 584*9c5db199SXin Li @return: A number of connector ID. 585*9c5db199SXin Li """ 586*9c5db199SXin Li return self.port_id 587*9c5db199SXin Li 588*9c5db199SXin Li 589*9c5db199SXin Li def get_connector_type(self): 590*9c5db199SXin Li """Returns the human readable string for the connector type. 591*9c5db199SXin Li 592*9c5db199SXin Li @return: A string, like "VGA", "DVI", "HDMI", or "DP". 593*9c5db199SXin Li """ 594*9c5db199SXin Li return self.chameleond_proxy.GetConnectorType(self.port_id) 595*9c5db199SXin Li 596*9c5db199SXin Li 597*9c5db199SXin Li def has_audio_support(self): 598*9c5db199SXin Li """Returns if the input has audio support. 599*9c5db199SXin Li 600*9c5db199SXin Li @return: True if the input has audio support; otherwise, False. 601*9c5db199SXin Li """ 602*9c5db199SXin Li return self.chameleond_proxy.HasAudioSupport(self.port_id) 603*9c5db199SXin Li 604*9c5db199SXin Li 605*9c5db199SXin Li def has_video_support(self): 606*9c5db199SXin Li """Returns if the input has video support. 607*9c5db199SXin Li 608*9c5db199SXin Li @return: True if the input has video support; otherwise, False. 609*9c5db199SXin Li """ 610*9c5db199SXin Li return self.chameleond_proxy.HasVideoSupport(self.port_id) 611*9c5db199SXin Li 612*9c5db199SXin Li 613*9c5db199SXin Li def plug(self): 614*9c5db199SXin Li """Asserts HPD line to high, emulating plug.""" 615*9c5db199SXin Li logging.info('Plug Chameleon port %d', self.port_id) 616*9c5db199SXin Li self.chameleond_proxy.Plug(self.port_id) 617*9c5db199SXin Li 618*9c5db199SXin Li 619*9c5db199SXin Li def unplug(self): 620*9c5db199SXin Li """Deasserts HPD line to low, emulating unplug.""" 621*9c5db199SXin Li logging.info('Unplug Chameleon port %d', self.port_id) 622*9c5db199SXin Li self.chameleond_proxy.Unplug(self.port_id) 623*9c5db199SXin Li 624*9c5db199SXin Li 625*9c5db199SXin Li def set_plug(self, plug_status): 626*9c5db199SXin Li """Sets plug/unplug by plug_status. 627*9c5db199SXin Li 628*9c5db199SXin Li @param plug_status: True to plug; False to unplug. 629*9c5db199SXin Li """ 630*9c5db199SXin Li if plug_status: 631*9c5db199SXin Li self.plug() 632*9c5db199SXin Li else: 633*9c5db199SXin Li self.unplug() 634*9c5db199SXin Li 635*9c5db199SXin Li 636*9c5db199SXin Li @property 637*9c5db199SXin Li def plugged(self): 638*9c5db199SXin Li """ 639*9c5db199SXin Li @returns True if this port is plugged to Chameleon, False otherwise. 640*9c5db199SXin Li 641*9c5db199SXin Li """ 642*9c5db199SXin Li return self.chameleond_proxy.IsPlugged(self.port_id) 643*9c5db199SXin Li 644*9c5db199SXin Li 645*9c5db199SXin Liclass ChameleonVideoInput(ChameleonPort): 646*9c5db199SXin Li """ChameleonVideoInput is an abstraction of a video input port. 647*9c5db199SXin Li 648*9c5db199SXin Li It contains some special methods to control a video input. 649*9c5db199SXin Li """ 650*9c5db199SXin Li 651*9c5db199SXin Li _DUT_STABILIZE_TIME = 3 652*9c5db199SXin Li _DURATION_UNPLUG_FOR_EDID = 5 653*9c5db199SXin Li _TIMEOUT_VIDEO_STABLE_PROBE = 10 654*9c5db199SXin Li _EDID_ID_DISABLE = -1 655*9c5db199SXin Li _FRAME_RATE = 60 656*9c5db199SXin Li 657*9c5db199SXin Li def __init__(self, chameleon_port): 658*9c5db199SXin Li """Construct a ChameleonVideoInput. 659*9c5db199SXin Li 660*9c5db199SXin Li @param chameleon_port: A general ChameleonPort object. 661*9c5db199SXin Li """ 662*9c5db199SXin Li self.chameleond_proxy = chameleon_port.chameleond_proxy 663*9c5db199SXin Li self.port_id = chameleon_port.port_id 664*9c5db199SXin Li self._original_edid = None 665*9c5db199SXin Li 666*9c5db199SXin Li 667*9c5db199SXin Li def wait_video_input_stable(self, timeout=None): 668*9c5db199SXin Li """Waits the video input stable or timeout. 669*9c5db199SXin Li 670*9c5db199SXin Li @param timeout: The time period to wait for. 671*9c5db199SXin Li 672*9c5db199SXin Li @return: True if the video input becomes stable within the timeout 673*9c5db199SXin Li period; otherwise, False. 674*9c5db199SXin Li """ 675*9c5db199SXin Li is_input_stable = self.chameleond_proxy.WaitVideoInputStable( 676*9c5db199SXin Li self.port_id, timeout) 677*9c5db199SXin Li 678*9c5db199SXin Li # If video input of Chameleon has been stable, wait for DUT software 679*9c5db199SXin Li # layer to be stable as well to make sure all the configurations have 680*9c5db199SXin Li # been propagated before proceeding. 681*9c5db199SXin Li if is_input_stable: 682*9c5db199SXin Li logging.info('Video input has been stable. Waiting for the DUT' 683*9c5db199SXin Li ' to be stable...') 684*9c5db199SXin Li time.sleep(self._DUT_STABILIZE_TIME) 685*9c5db199SXin Li return is_input_stable 686*9c5db199SXin Li 687*9c5db199SXin Li 688*9c5db199SXin Li def read_edid(self): 689*9c5db199SXin Li """Reads the EDID. 690*9c5db199SXin Li 691*9c5db199SXin Li @return: An Edid object or NO_EDID. 692*9c5db199SXin Li """ 693*9c5db199SXin Li edid_binary = self.chameleond_proxy.ReadEdid(self.port_id) 694*9c5db199SXin Li if edid_binary is None: 695*9c5db199SXin Li return edid_lib.NO_EDID 696*9c5db199SXin Li # Read EDID without verify. It may be made corrupted as intended 697*9c5db199SXin Li # for the test purpose. 698*9c5db199SXin Li return edid_lib.Edid(edid_binary.data, skip_verify=True) 699*9c5db199SXin Li 700*9c5db199SXin Li 701*9c5db199SXin Li def apply_edid(self, edid): 702*9c5db199SXin Li """Applies the given EDID. 703*9c5db199SXin Li 704*9c5db199SXin Li @param edid: An Edid object or NO_EDID. 705*9c5db199SXin Li """ 706*9c5db199SXin Li if edid is edid_lib.NO_EDID: 707*9c5db199SXin Li self.chameleond_proxy.ApplyEdid(self.port_id, self._EDID_ID_DISABLE) 708*9c5db199SXin Li else: 709*9c5db199SXin Li edid_binary = six.moves.xmlrpc_client.Binary(edid.data) 710*9c5db199SXin Li edid_id = self.chameleond_proxy.CreateEdid(edid_binary) 711*9c5db199SXin Li self.chameleond_proxy.ApplyEdid(self.port_id, edid_id) 712*9c5db199SXin Li self.chameleond_proxy.DestroyEdid(edid_id) 713*9c5db199SXin Li 714*9c5db199SXin Li def set_edid_from_file(self, filename, check_video_input=True): 715*9c5db199SXin Li """Sets EDID from a file. 716*9c5db199SXin Li 717*9c5db199SXin Li The method is similar to set_edid but reads EDID from a file. 718*9c5db199SXin Li 719*9c5db199SXin Li @param filename: path to EDID file. 720*9c5db199SXin Li @param check_video_input: False to disable wait_video_input_stable. 721*9c5db199SXin Li """ 722*9c5db199SXin Li self.set_edid(edid_lib.Edid.from_file(filename), 723*9c5db199SXin Li check_video_input=check_video_input) 724*9c5db199SXin Li 725*9c5db199SXin Li def set_edid(self, edid, check_video_input=True): 726*9c5db199SXin Li """The complete flow of setting EDID. 727*9c5db199SXin Li 728*9c5db199SXin Li Unplugs the port if needed, sets EDID, plugs back if it was plugged. 729*9c5db199SXin Li The original EDID is stored so user can call restore_edid after this 730*9c5db199SXin Li call. 731*9c5db199SXin Li 732*9c5db199SXin Li @param edid: An Edid object. 733*9c5db199SXin Li @param check_video_input: False to disable wait_video_input_stable. 734*9c5db199SXin Li """ 735*9c5db199SXin Li plugged = self.plugged 736*9c5db199SXin Li if plugged: 737*9c5db199SXin Li self.unplug() 738*9c5db199SXin Li 739*9c5db199SXin Li self._original_edid = self.read_edid() 740*9c5db199SXin Li 741*9c5db199SXin Li logging.info('Apply EDID on port %d', self.port_id) 742*9c5db199SXin Li self.apply_edid(edid) 743*9c5db199SXin Li 744*9c5db199SXin Li if plugged: 745*9c5db199SXin Li time.sleep(self._DURATION_UNPLUG_FOR_EDID) 746*9c5db199SXin Li self.plug() 747*9c5db199SXin Li if check_video_input: 748*9c5db199SXin Li self.wait_video_input_stable(self._TIMEOUT_VIDEO_STABLE_PROBE) 749*9c5db199SXin Li 750*9c5db199SXin Li def restore_edid(self): 751*9c5db199SXin Li """Restores original EDID stored when set_edid was called.""" 752*9c5db199SXin Li current_edid = self.read_edid() 753*9c5db199SXin Li if (self._original_edid and 754*9c5db199SXin Li self._original_edid.data != current_edid.data): 755*9c5db199SXin Li logging.info('Restore the original EDID.') 756*9c5db199SXin Li self.apply_edid(self._original_edid) 757*9c5db199SXin Li 758*9c5db199SXin Li 759*9c5db199SXin Li @contextmanager 760*9c5db199SXin Li def use_edid(self, edid, check_video_input=True): 761*9c5db199SXin Li """Uses the given EDID in a with statement. 762*9c5db199SXin Li 763*9c5db199SXin Li It sets the EDID up in the beginning and restores to the original 764*9c5db199SXin Li EDID in the end. This function is expected to be used in a with 765*9c5db199SXin Li statement, like the following: 766*9c5db199SXin Li 767*9c5db199SXin Li with chameleon_port.use_edid(edid): 768*9c5db199SXin Li do_some_test_on(chameleon_port) 769*9c5db199SXin Li 770*9c5db199SXin Li @param edid: An EDID object. 771*9c5db199SXin Li @param check_video_input: False to disable wait_video_input_stable. 772*9c5db199SXin Li """ 773*9c5db199SXin Li # Set the EDID up in the beginning. 774*9c5db199SXin Li self.set_edid(edid, check_video_input=check_video_input) 775*9c5db199SXin Li 776*9c5db199SXin Li try: 777*9c5db199SXin Li # Yeild to execute the with statement. 778*9c5db199SXin Li yield 779*9c5db199SXin Li finally: 780*9c5db199SXin Li # Restore the original EDID in the end. 781*9c5db199SXin Li self.restore_edid() 782*9c5db199SXin Li 783*9c5db199SXin Li def use_edid_file(self, filename, check_video_input=True): 784*9c5db199SXin Li """Uses the given EDID file in a with statement. 785*9c5db199SXin Li 786*9c5db199SXin Li It sets the EDID up in the beginning and restores to the original 787*9c5db199SXin Li EDID in the end. This function is expected to be used in a with 788*9c5db199SXin Li statement, like the following: 789*9c5db199SXin Li 790*9c5db199SXin Li with chameleon_port.use_edid_file(filename): 791*9c5db199SXin Li do_some_test_on(chameleon_port) 792*9c5db199SXin Li 793*9c5db199SXin Li @param filename: A path to the EDID file. 794*9c5db199SXin Li @param check_video_input: False to disable wait_video_input_stable. 795*9c5db199SXin Li """ 796*9c5db199SXin Li return self.use_edid(edid_lib.Edid.from_file(filename), 797*9c5db199SXin Li check_video_input=check_video_input) 798*9c5db199SXin Li 799*9c5db199SXin Li def fire_hpd_pulse(self, deassert_interval_usec, assert_interval_usec=None, 800*9c5db199SXin Li repeat_count=1, end_level=1): 801*9c5db199SXin Li 802*9c5db199SXin Li """Fires one or more HPD pulse (low -> high -> low -> ...). 803*9c5db199SXin Li 804*9c5db199SXin Li @param deassert_interval_usec: The time in microsecond of the 805*9c5db199SXin Li deassert pulse. 806*9c5db199SXin Li @param assert_interval_usec: The time in microsecond of the 807*9c5db199SXin Li assert pulse. If None, then use the same value as 808*9c5db199SXin Li deassert_interval_usec. 809*9c5db199SXin Li @param repeat_count: The count of HPD pulses to fire. 810*9c5db199SXin Li @param end_level: HPD ends with 0 for LOW (unplugged) or 1 for 811*9c5db199SXin Li HIGH (plugged). 812*9c5db199SXin Li """ 813*9c5db199SXin Li self.chameleond_proxy.FireHpdPulse( 814*9c5db199SXin Li self.port_id, deassert_interval_usec, 815*9c5db199SXin Li assert_interval_usec, repeat_count, int(bool(end_level))) 816*9c5db199SXin Li 817*9c5db199SXin Li 818*9c5db199SXin Li def fire_mixed_hpd_pulses(self, widths): 819*9c5db199SXin Li """Fires one or more HPD pulses, starting at low, of mixed widths. 820*9c5db199SXin Li 821*9c5db199SXin Li One must specify a list of segment widths in the widths argument where 822*9c5db199SXin Li widths[0] is the width of the first low segment, widths[1] is that of 823*9c5db199SXin Li the first high segment, widths[2] is that of the second low segment... 824*9c5db199SXin Li etc. The HPD line stops at low if even number of segment widths are 825*9c5db199SXin Li specified; otherwise, it stops at high. 826*9c5db199SXin Li 827*9c5db199SXin Li @param widths: list of pulse segment widths in usec. 828*9c5db199SXin Li """ 829*9c5db199SXin Li self.chameleond_proxy.FireMixedHpdPulses(self.port_id, widths) 830*9c5db199SXin Li 831*9c5db199SXin Li 832*9c5db199SXin Li def capture_screen(self): 833*9c5db199SXin Li """Captures Chameleon framebuffer. 834*9c5db199SXin Li 835*9c5db199SXin Li @return An Image object. 836*9c5db199SXin Li """ 837*9c5db199SXin Li if six.PY2: 838*9c5db199SXin Li return Image.fromstring( 839*9c5db199SXin Li 'RGB', 840*9c5db199SXin Li self.get_resolution(), 841*9c5db199SXin Li self.chameleond_proxy.DumpPixels(self.port_id).data) 842*9c5db199SXin Li return Image.frombytes( 843*9c5db199SXin Li 'RGB', 844*9c5db199SXin Li self.get_resolution(), 845*9c5db199SXin Li self.chameleond_proxy.DumpPixels(self.port_id).data) 846*9c5db199SXin Li 847*9c5db199SXin Li 848*9c5db199SXin Li def get_resolution(self): 849*9c5db199SXin Li """Gets the source resolution. 850*9c5db199SXin Li 851*9c5db199SXin Li @return: A (width, height) tuple. 852*9c5db199SXin Li """ 853*9c5db199SXin Li # The return value of RPC is converted to a list. Convert it back to 854*9c5db199SXin Li # a tuple. 855*9c5db199SXin Li return tuple(self.chameleond_proxy.DetectResolution(self.port_id)) 856*9c5db199SXin Li 857*9c5db199SXin Li 858*9c5db199SXin Li def set_content_protection(self, enable): 859*9c5db199SXin Li """Sets the content protection state on the port. 860*9c5db199SXin Li 861*9c5db199SXin Li @param enable: True to enable; False to disable. 862*9c5db199SXin Li """ 863*9c5db199SXin Li self.chameleond_proxy.SetContentProtection(self.port_id, enable) 864*9c5db199SXin Li 865*9c5db199SXin Li 866*9c5db199SXin Li def is_content_protection_enabled(self): 867*9c5db199SXin Li """Returns True if the content protection is enabled on the port. 868*9c5db199SXin Li 869*9c5db199SXin Li @return: True if the content protection is enabled; otherwise, False. 870*9c5db199SXin Li """ 871*9c5db199SXin Li return self.chameleond_proxy.IsContentProtectionEnabled(self.port_id) 872*9c5db199SXin Li 873*9c5db199SXin Li 874*9c5db199SXin Li def is_video_input_encrypted(self): 875*9c5db199SXin Li """Returns True if the video input on the port is encrypted. 876*9c5db199SXin Li 877*9c5db199SXin Li @return: True if the video input is encrypted; otherwise, False. 878*9c5db199SXin Li """ 879*9c5db199SXin Li return self.chameleond_proxy.IsVideoInputEncrypted(self.port_id) 880*9c5db199SXin Li 881*9c5db199SXin Li 882*9c5db199SXin Li def start_monitoring_audio_video_capturing_delay(self): 883*9c5db199SXin Li """Starts an audio/video synchronization utility.""" 884*9c5db199SXin Li self.chameleond_proxy.StartMonitoringAudioVideoCapturingDelay() 885*9c5db199SXin Li 886*9c5db199SXin Li 887*9c5db199SXin Li def get_audio_video_capturing_delay(self): 888*9c5db199SXin Li """Gets the time interval between the first audio/video cpatured data. 889*9c5db199SXin Li 890*9c5db199SXin Li @return: A floating points indicating the time interval between the 891*9c5db199SXin Li first audio/video data captured. If the result is negative, 892*9c5db199SXin Li then the first video data is earlier, otherwise the first 893*9c5db199SXin Li audio data is earlier. 894*9c5db199SXin Li """ 895*9c5db199SXin Li return self.chameleond_proxy.GetAudioVideoCapturingDelay() 896*9c5db199SXin Li 897*9c5db199SXin Li 898*9c5db199SXin Li def start_capturing_video(self, box=None): 899*9c5db199SXin Li """ 900*9c5db199SXin Li Captures video frames. Asynchronous, returns immediately. 901*9c5db199SXin Li 902*9c5db199SXin Li @param box: int tuple, (x, y, width, height) pixel coordinates. 903*9c5db199SXin Li Defines the rectangular boundary within which to capture. 904*9c5db199SXin Li """ 905*9c5db199SXin Li 906*9c5db199SXin Li if box is None: 907*9c5db199SXin Li self.chameleond_proxy.StartCapturingVideo(self.port_id) 908*9c5db199SXin Li else: 909*9c5db199SXin Li self.chameleond_proxy.StartCapturingVideo(self.port_id, *box) 910*9c5db199SXin Li 911*9c5db199SXin Li 912*9c5db199SXin Li def stop_capturing_video(self): 913*9c5db199SXin Li """ 914*9c5db199SXin Li Stops the ongoing video frame capturing. 915*9c5db199SXin Li 916*9c5db199SXin Li """ 917*9c5db199SXin Li self.chameleond_proxy.StopCapturingVideo() 918*9c5db199SXin Li 919*9c5db199SXin Li 920*9c5db199SXin Li def get_captured_frame_count(self): 921*9c5db199SXin Li """ 922*9c5db199SXin Li @return: int, the number of frames that have been captured. 923*9c5db199SXin Li 924*9c5db199SXin Li """ 925*9c5db199SXin Li return self.chameleond_proxy.GetCapturedFrameCount() 926*9c5db199SXin Li 927*9c5db199SXin Li 928*9c5db199SXin Li def read_captured_frame(self, index): 929*9c5db199SXin Li """ 930*9c5db199SXin Li @param index: int, index of the desired captured frame. 931*9c5db199SXin Li @return: xmlrpclib.Binary object containing a byte-array of the pixels. 932*9c5db199SXin Li 933*9c5db199SXin Li """ 934*9c5db199SXin Li 935*9c5db199SXin Li frame = self.chameleond_proxy.ReadCapturedFrame(index) 936*9c5db199SXin Li return Image.fromstring('RGB', 937*9c5db199SXin Li self.get_captured_resolution(), 938*9c5db199SXin Li frame.data) 939*9c5db199SXin Li 940*9c5db199SXin Li 941*9c5db199SXin Li def get_captured_checksums(self, start_index=0, stop_index=None): 942*9c5db199SXin Li """ 943*9c5db199SXin Li @param start_index: int, index of the frame to start with. 944*9c5db199SXin Li @param stop_index: int, index of the frame (excluded) to stop at. 945*9c5db199SXin Li @return: a list of checksums of frames captured. 946*9c5db199SXin Li 947*9c5db199SXin Li """ 948*9c5db199SXin Li return self.chameleond_proxy.GetCapturedChecksums(start_index, 949*9c5db199SXin Li stop_index) 950*9c5db199SXin Li 951*9c5db199SXin Li 952*9c5db199SXin Li def get_captured_fps_list(self, time_to_start=0, total_period=None): 953*9c5db199SXin Li """ 954*9c5db199SXin Li @param time_to_start: time in second, support floating number, only 955*9c5db199SXin Li measure the period starting at this time. 956*9c5db199SXin Li If negative, it is the time before stop, e.g. 957*9c5db199SXin Li -2 meaning 2 seconds before stop. 958*9c5db199SXin Li @param total_period: time in second, integer, the total measuring 959*9c5db199SXin Li period. If not given, use the maximum time 960*9c5db199SXin Li (integer) to the end. 961*9c5db199SXin Li @return: a list of fps numbers, or [-1] if any error. 962*9c5db199SXin Li 963*9c5db199SXin Li """ 964*9c5db199SXin Li checksums = self.get_captured_checksums() 965*9c5db199SXin Li 966*9c5db199SXin Li frame_to_start = int(round(time_to_start * self._FRAME_RATE)) 967*9c5db199SXin Li if total_period is None: 968*9c5db199SXin Li # The default is the maximum time (integer) to the end. 969*9c5db199SXin Li total_period = (len(checksums) - frame_to_start) // self._FRAME_RATE 970*9c5db199SXin Li frame_to_stop = frame_to_start + total_period * self._FRAME_RATE 971*9c5db199SXin Li 972*9c5db199SXin Li if frame_to_start >= len(checksums) or frame_to_stop >= len(checksums): 973*9c5db199SXin Li logging.error('The given time interval is out-of-range.') 974*9c5db199SXin Li return [-1] 975*9c5db199SXin Li 976*9c5db199SXin Li # Only pick the checksum we are interested. 977*9c5db199SXin Li checksums = checksums[frame_to_start:frame_to_stop] 978*9c5db199SXin Li 979*9c5db199SXin Li # Count the unique checksums per second, i.e. FPS 980*9c5db199SXin Li logging.debug('Output the fps info below:') 981*9c5db199SXin Li fps_list = [] 982*9c5db199SXin Li for i in range(0, len(checksums), self._FRAME_RATE): 983*9c5db199SXin Li unique_count = 0 984*9c5db199SXin Li debug_str = '' 985*9c5db199SXin Li for j in range(i, i + self._FRAME_RATE): 986*9c5db199SXin Li if j == 0 or checksums[j] != checksums[j - 1]: 987*9c5db199SXin Li unique_count += 1 988*9c5db199SXin Li debug_str += '*' 989*9c5db199SXin Li else: 990*9c5db199SXin Li debug_str += '.' 991*9c5db199SXin Li fps_list.append(unique_count) 992*9c5db199SXin Li logging.debug('%2dfps %s', unique_count, debug_str) 993*9c5db199SXin Li 994*9c5db199SXin Li return fps_list 995*9c5db199SXin Li 996*9c5db199SXin Li 997*9c5db199SXin Li def search_fps_pattern(self, pattern_diff_frame, pattern_window=None, 998*9c5db199SXin Li time_to_start=0): 999*9c5db199SXin Li """Search the captured frames and return the time where FPS is greater 1000*9c5db199SXin Li than given FPS pattern. 1001*9c5db199SXin Li 1002*9c5db199SXin Li A FPS pattern is described as how many different frames in a sliding 1003*9c5db199SXin Li window. For example, 5 differnt frames in a window of 60 frames. 1004*9c5db199SXin Li 1005*9c5db199SXin Li @param pattern_diff_frame: number of different frames for the pattern. 1006*9c5db199SXin Li @param pattern_window: number of frames for the sliding window. Default 1007*9c5db199SXin Li is 1 second. 1008*9c5db199SXin Li @param time_to_start: time in second, support floating number, 1009*9c5db199SXin Li start to search from the given time. 1010*9c5db199SXin Li @return: the time matching the pattern. -1.0 if not found. 1011*9c5db199SXin Li 1012*9c5db199SXin Li """ 1013*9c5db199SXin Li if pattern_window is None: 1014*9c5db199SXin Li pattern_window = self._FRAME_RATE 1015*9c5db199SXin Li 1016*9c5db199SXin Li checksums = self.get_captured_checksums() 1017*9c5db199SXin Li 1018*9c5db199SXin Li frame_to_start = int(round(time_to_start * self._FRAME_RATE)) 1019*9c5db199SXin Li first_checksum = checksums[frame_to_start] 1020*9c5db199SXin Li 1021*9c5db199SXin Li for i in range(frame_to_start + 1, len(checksums) - pattern_window): 1022*9c5db199SXin Li unique_count = 0 1023*9c5db199SXin Li for j in range(i, i + pattern_window): 1024*9c5db199SXin Li if j == 0 or checksums[j] != checksums[j - 1]: 1025*9c5db199SXin Li unique_count += 1 1026*9c5db199SXin Li if unique_count >= pattern_diff_frame: 1027*9c5db199SXin Li return float(i) / self._FRAME_RATE 1028*9c5db199SXin Li 1029*9c5db199SXin Li return -1.0 1030*9c5db199SXin Li 1031*9c5db199SXin Li 1032*9c5db199SXin Li def get_captured_resolution(self): 1033*9c5db199SXin Li """ 1034*9c5db199SXin Li @return: (width, height) tuple, the resolution of captured frames. 1035*9c5db199SXin Li 1036*9c5db199SXin Li """ 1037*9c5db199SXin Li return self.chameleond_proxy.GetCapturedResolution() 1038*9c5db199SXin Li 1039*9c5db199SXin Li 1040*9c5db199SXin Li 1041*9c5db199SXin Liclass ChameleonAudioInput(ChameleonPort): 1042*9c5db199SXin Li """ChameleonAudioInput is an abstraction of an audio input port. 1043*9c5db199SXin Li 1044*9c5db199SXin Li It contains some special methods to control an audio input. 1045*9c5db199SXin Li """ 1046*9c5db199SXin Li 1047*9c5db199SXin Li def __init__(self, chameleon_port): 1048*9c5db199SXin Li """Construct a ChameleonAudioInput. 1049*9c5db199SXin Li 1050*9c5db199SXin Li @param chameleon_port: A general ChameleonPort object. 1051*9c5db199SXin Li """ 1052*9c5db199SXin Li self.chameleond_proxy = chameleon_port.chameleond_proxy 1053*9c5db199SXin Li self.port_id = chameleon_port.port_id 1054*9c5db199SXin Li 1055*9c5db199SXin Li 1056*9c5db199SXin Li def start_capturing_audio(self): 1057*9c5db199SXin Li """Starts capturing audio.""" 1058*9c5db199SXin Li return self.chameleond_proxy.StartCapturingAudio(self.port_id) 1059*9c5db199SXin Li 1060*9c5db199SXin Li 1061*9c5db199SXin Li def stop_capturing_audio(self): 1062*9c5db199SXin Li """Stops capturing audio. 1063*9c5db199SXin Li 1064*9c5db199SXin Li Returns: 1065*9c5db199SXin Li A tuple (remote_path, format). 1066*9c5db199SXin Li remote_path: The captured file path on Chameleon. 1067*9c5db199SXin Li format: A dict containing: 1068*9c5db199SXin Li file_type: 'raw' or 'wav'. 1069*9c5db199SXin Li sample_format: 'S32_LE' for 32-bit signed integer in little-endian. 1070*9c5db199SXin Li Refer to aplay manpage for other formats. 1071*9c5db199SXin Li channel: channel number. 1072*9c5db199SXin Li rate: sampling rate. 1073*9c5db199SXin Li """ 1074*9c5db199SXin Li remote_path, data_format = self.chameleond_proxy.StopCapturingAudio( 1075*9c5db199SXin Li self.port_id) 1076*9c5db199SXin Li return remote_path, data_format 1077*9c5db199SXin Li 1078*9c5db199SXin Li 1079*9c5db199SXin Liclass ChameleonAudioOutput(ChameleonPort): 1080*9c5db199SXin Li """ChameleonAudioOutput is an abstraction of an audio output port. 1081*9c5db199SXin Li 1082*9c5db199SXin Li It contains some special methods to control an audio output. 1083*9c5db199SXin Li """ 1084*9c5db199SXin Li 1085*9c5db199SXin Li def __init__(self, chameleon_port): 1086*9c5db199SXin Li """Construct a ChameleonAudioOutput. 1087*9c5db199SXin Li 1088*9c5db199SXin Li @param chameleon_port: A general ChameleonPort object. 1089*9c5db199SXin Li """ 1090*9c5db199SXin Li self.chameleond_proxy = chameleon_port.chameleond_proxy 1091*9c5db199SXin Li self.port_id = chameleon_port.port_id 1092*9c5db199SXin Li 1093*9c5db199SXin Li 1094*9c5db199SXin Li def start_playing_audio(self, path, data_format): 1095*9c5db199SXin Li """Starts playing audio. 1096*9c5db199SXin Li 1097*9c5db199SXin Li @param path: The path to the file to play on Chameleon. 1098*9c5db199SXin Li @param data_format: A dict containing data format. Currently Chameleon 1099*9c5db199SXin Li only accepts data format: 1100*9c5db199SXin Li dict(file_type='raw', sample_format='S32_LE', 1101*9c5db199SXin Li channel=8, rate=48000). 1102*9c5db199SXin Li 1103*9c5db199SXin Li """ 1104*9c5db199SXin Li self.chameleond_proxy.StartPlayingAudio(self.port_id, path, data_format) 1105*9c5db199SXin Li 1106*9c5db199SXin Li 1107*9c5db199SXin Li def stop_playing_audio(self): 1108*9c5db199SXin Li """Stops capturing audio.""" 1109*9c5db199SXin Li self.chameleond_proxy.StopPlayingAudio(self.port_id) 1110*9c5db199SXin Li 1111*9c5db199SXin Li 1112*9c5db199SXin Lidef make_chameleon_hostname(dut_hostname): 1113*9c5db199SXin Li """Given a DUT's hostname, returns the hostname of its Chameleon. 1114*9c5db199SXin Li 1115*9c5db199SXin Li @param dut_hostname: Hostname of a DUT. 1116*9c5db199SXin Li 1117*9c5db199SXin Li @return Hostname of the DUT's Chameleon. 1118*9c5db199SXin Li """ 1119*9c5db199SXin Li host_parts = dut_hostname.split('.') 1120*9c5db199SXin Li host_parts[0] = host_parts[0] + '-chameleon' 1121*9c5db199SXin Li return '.'.join(host_parts) 1122*9c5db199SXin Li 1123*9c5db199SXin Li 1124*9c5db199SXin Lidef make_btpeer_hostnames(dut_hostname): 1125*9c5db199SXin Li """Given a DUT's hostname, returns the hostname of its bluetooth peers. 1126*9c5db199SXin Li 1127*9c5db199SXin Li A DUT can have up to 4 Bluetooth peers named hostname-btpeer[1-4] 1128*9c5db199SXin Li @param dut_hostname: Hostname of a DUT. 1129*9c5db199SXin Li 1130*9c5db199SXin Li @return List of hostname of the DUT's Bluetooth peer devices 1131*9c5db199SXin Li """ 1132*9c5db199SXin Li hostnames = [] 1133*9c5db199SXin Li host_parts = dut_hostname.split('.') 1134*9c5db199SXin Li for i in range(1,5): 1135*9c5db199SXin Li hostname_prefix = host_parts[0] + '-btpeer' +str(i) 1136*9c5db199SXin Li hostname = [hostname_prefix] 1137*9c5db199SXin Li hostname.extend(host_parts[1:]) 1138*9c5db199SXin Li hostnames.append('.'.join(hostname)) 1139*9c5db199SXin Li return hostnames 1140*9c5db199SXin Li 1141*9c5db199SXin Lidef create_chameleon_board(dut_hostname, args): 1142*9c5db199SXin Li """Creates a ChameleonBoard object with either DUT's hostname or arguments. 1143*9c5db199SXin Li 1144*9c5db199SXin Li If the DUT's hostname is in the lab zone, it connects to the Chameleon by 1145*9c5db199SXin Li append the hostname with '-chameleon' suffix. If not, checks if the args 1146*9c5db199SXin Li contains the key-value pair 'chameleon_host=IP'. 1147*9c5db199SXin Li 1148*9c5db199SXin Li @param dut_hostname: Hostname of a DUT. 1149*9c5db199SXin Li @param args: A string of arguments passed from the command line. 1150*9c5db199SXin Li 1151*9c5db199SXin Li @return A ChameleonBoard object. 1152*9c5db199SXin Li 1153*9c5db199SXin Li @raise ChameleonConnectionError if unknown hostname. 1154*9c5db199SXin Li """ 1155*9c5db199SXin Li connection = None 1156*9c5db199SXin Li hostname = make_chameleon_hostname(dut_hostname) 1157*9c5db199SXin Li if utils.host_is_in_lab_zone(hostname): 1158*9c5db199SXin Li connection = ChameleonConnection(hostname) 1159*9c5db199SXin Li else: 1160*9c5db199SXin Li args_dict = utils.args_to_dict(args) 1161*9c5db199SXin Li hostname = args_dict.get('chameleon_host', None) 1162*9c5db199SXin Li port = args_dict.get('chameleon_port', CHAMELEON_PORT) 1163*9c5db199SXin Li if hostname: 1164*9c5db199SXin Li connection = ChameleonConnection(hostname, port) 1165*9c5db199SXin Li else: 1166*9c5db199SXin Li raise ChameleonConnectionError('No chameleon_host is given in args') 1167*9c5db199SXin Li 1168*9c5db199SXin Li return ChameleonBoard(connection) 1169