1#!/usr/bin/env python3 2# 3# Copyright 2017 - The Android Open Source Project 4# 5# Licensed under the Apache License, Version 2.0 (the "License"); 6# you may not use this file except in compliance with the License. 7# You may obtain a copy of the License at 8# 9# http://www.apache.org/licenses/LICENSE-2.0 10# 11# Unless required by applicable law or agreed to in writing, software 12# distributed under the License is distributed on an "AS IS" BASIS, 13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14# See the License for the specific language governing permissions and 15# limitations under the License. 16 17import logging 18import time 19import xmlrpc.client 20from subprocess import call 21 22from acts import signals 23 24MOBLY_CONTROLLER_CONFIG_NAME = "ChameleonDevice" 25ACTS_CONTROLLER_REFERENCE_NAME = "chameleon_devices" 26 27CHAMELEON_DEVICE_EMPTY_CONFIG_MSG = "Configuration is empty, abort!" 28CHAMELEON_DEVICE_NOT_LIST_CONFIG_MSG = "Configuration should be a list, abort!" 29 30audio_bus_endpoints = { 31 'CROS_HEADPHONE': 'Cros device headphone', 32 'CROS_EXTERNAL_MICROPHONE': 'Cros device external microphone', 33 'PERIPHERAL_MICROPHONE': 'Peripheral microphone', 34 'PERIPHERAL_SPEAKER': 'Peripheral speaker', 35 'FPGA_LINEOUT': 'Chameleon FPGA line-out', 36 'FPGA_LINEIN': 'Chameleon FPGA line-in', 37 'BLUETOOTH_OUTPUT': 'Bluetooth module output', 38 'BLUETOOTH_INPUT': 'Bluetooth module input' 39} 40 41 42class ChameleonDeviceError(signals.ControllerError): 43 pass 44 45 46def create(configs): 47 if not configs: 48 raise ChameleonDeviceError(CHAMELEON_DEVICE_EMPTY_CONFIG_MSG) 49 elif not isinstance(configs, list): 50 raise ChameleonDeviceError(CHAMELEON_DEVICE_NOT_LIST_CONFIG_MSG) 51 elif isinstance(configs[0], str): 52 # Configs is a list of IP addresses 53 chameleons = get_instances(configs) 54 return chameleons 55 56 57def destroy(chameleons): 58 for chameleon in chameleons: 59 del chameleon 60 61 62def get_info(chameleons): 63 """Get information on a list of ChameleonDevice objects. 64 65 Args: 66 ads: A list of ChameleonDevice objects. 67 68 Returns: 69 A list of dict, each representing info for ChameleonDevice objects. 70 """ 71 device_info = [] 72 for chameleon in chameleons: 73 info = {"address": chameleon.address, "port": chameleon.port} 74 device_info.append(info) 75 return device_info 76 77 78def get_instances(ips): 79 """Create ChameleonDevice instances from a list of IPs. 80 81 Args: 82 ips: A list of Chameleon IPs. 83 84 Returns: 85 A list of ChameleonDevice objects. 86 """ 87 return [ChameleonDevice(ip) for ip in ips] 88 89 90class ChameleonDevice: 91 """Class representing a Chameleon device. 92 93 Each object of this class represents one Chameleon device in ACTS. 94 95 Attributes: 96 address: The full address to contact the Chameleon device at 97 client: The ServiceProxy of the XMLRPC client. 98 log: A logger object. 99 port: The TCP port number of the Chameleon device. 100 """ 101 102 def __init__(self, ip="", port=9992): 103 self.ip = ip 104 self.log = logging.getLogger() 105 self.port = port 106 self.address = "http://{}:{}".format(ip, self.port) 107 try: 108 self.client = xmlrpc.client.ServerProxy( 109 self.address, allow_none=True, verbose=False) 110 except ConnectionRefusedError as err: 111 self.log.exception( 112 "Failed to connect to Chameleon Device at: {}".format( 113 self.address)) 114 self.client.Reset() 115 116 def pull_file(self, chameleon_location, destination): 117 """Pulls a file from the Chameleon device. Usually the raw audio file. 118 119 Args: 120 chameleon_location: The path to the file on the Chameleon device 121 destination: The destination to where to pull it locally. 122 """ 123 # TODO: (tturney) implement 124 self.log.error("Definition not yet implemented") 125 126 def start_capturing_audio(self, port_id, has_file=True): 127 """Starts capturing audio. 128 129 Args: 130 port_id: The ID of the audio input port. 131 has_file: True for saving audio data to file. False otherwise. 132 """ 133 self.client.StartCapturingAudio(port_id, has_file) 134 135 def stop_capturing_audio(self, port_id): 136 """Stops capturing audio. 137 138 Args: 139 port_id: The ID of the audio input port. 140 Returns: 141 List contain the location of the recorded audio and a dictionary 142 of values relating to the raw audio including: file_type, channel, 143 sample_format, and rate. 144 """ 145 return self.client.StopCapturingAudio(port_id) 146 147 def audio_board_connect(self, bus_number, endpoint): 148 """Connects an endpoint to an audio bus. 149 150 Args: 151 bus_number: 1 or 2 for audio bus 1 or bus 2. 152 endpoint: An endpoint defined in audio_bus_endpoints. 153 """ 154 self.client.AudioBoardConnect(bus_number, endpoint) 155 156 def audio_board_disconnect(self, bus_number, endpoint): 157 """Connects an endpoint to an audio bus. 158 159 Args: 160 bus_number: 1 or 2 for audio bus 1 or bus 2. 161 endpoint: An endpoint defined in audio_bus_endpoints. 162 """ 163 self.client.AudioBoardDisconnect(bus_number, endpoint) 164 165 def audio_board_disable_bluetooth(self): 166 """Disables Bluetooth module on audio board.""" 167 self.client.AudioBoardDisableBluetooth() 168 169 def audio_board_clear_routes(self, bus_number): 170 """Clears routes on an audio bus. 171 172 Args: 173 bus_number: 1 or 2 for audio bus 1 or bus 2. 174 """ 175 self.client.AudioBoardClearRoutes(bus_number) 176 177 def scp(self, source, destination): 178 """Copies files from the Chameleon device to the host machine. 179 180 Args: 181 source: The file path on the Chameleon board. 182 dest: The file path on the host machine. 183 """ 184 cmd = "scp root@{}:/{} {}".format(self.ip, source, destination) 185 try: 186 call(cmd.split(" ")) 187 except FileNotFoundError as err: 188 self.log.exception("File not found {}".format(source)) 189