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