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 xmlrpc.client 19from subprocess import call 20 21from acts import signals 22 23MOBLY_CONTROLLER_CONFIG_NAME = "ChameleonDevice" 24ACTS_CONTROLLER_REFERENCE_NAME = "chameleon_devices" 25 26CHAMELEON_DEVICE_EMPTY_CONFIG_MSG = "Configuration is empty, abort!" 27CHAMELEON_DEVICE_NOT_LIST_CONFIG_MSG = "Configuration should be a list, abort!" 28 29audio_bus_endpoints = { 30 'CROS_HEADPHONE': 'Cros device headphone', 31 'CROS_EXTERNAL_MICROPHONE': 'Cros device external microphone', 32 'PERIPHERAL_MICROPHONE': 'Peripheral microphone', 33 'PERIPHERAL_SPEAKER': 'Peripheral speaker', 34 'FPGA_LINEOUT': 'Chameleon FPGA line-out', 35 'FPGA_LINEIN': 'Chameleon FPGA line-in', 36 'BLUETOOTH_OUTPUT': 'Bluetooth module output', 37 'BLUETOOTH_INPUT': 'Bluetooth module input' 38} 39 40 41class ChameleonDeviceError(signals.ControllerError): 42 pass 43 44 45def create(configs): 46 if not configs: 47 raise ChameleonDeviceError(CHAMELEON_DEVICE_EMPTY_CONFIG_MSG) 48 elif not isinstance(configs, list): 49 raise ChameleonDeviceError(CHAMELEON_DEVICE_NOT_LIST_CONFIG_MSG) 50 elif isinstance(configs[0], str): 51 # Configs is a list of IP addresses 52 chameleons = get_instances(configs) 53 return chameleons 54 55 56def destroy(chameleons): 57 for chameleon in chameleons: 58 del chameleon 59 60 61def get_info(chameleons): 62 """Get information on a list of ChameleonDevice objects. 63 64 Args: 65 ads: A list of ChameleonDevice objects. 66 67 Returns: 68 A list of dict, each representing info for ChameleonDevice objects. 69 """ 70 device_info = [] 71 for chameleon in chameleons: 72 info = {"address": chameleon.address, "port": chameleon.port} 73 device_info.append(info) 74 return device_info 75 76 77def get_instances(ips): 78 """Create ChameleonDevice instances from a list of IPs. 79 80 Args: 81 ips: A list of Chameleon IPs. 82 83 Returns: 84 A list of ChameleonDevice objects. 85 """ 86 return [ChameleonDevice(ip) for ip in ips] 87 88 89class ChameleonDevice: 90 """Class representing a Chameleon device. 91 92 Each object of this class represents one Chameleon device in ACTS. 93 94 Attributes: 95 address: The full address to contact the Chameleon device at 96 client: The ServiceProxy of the XMLRPC client. 97 log: A logger object. 98 port: The TCP port number of the Chameleon device. 99 """ 100 101 def __init__(self, ip="", port=9992): 102 self.ip = ip 103 self.log = logging.getLogger() 104 self.port = port 105 self.address = "http://{}:{}".format(ip, self.port) 106 try: 107 self.client = xmlrpc.client.ServerProxy(self.address, 108 allow_none=True, 109 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