1# Copyright 2015 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"""This module provides the audio board interface."""
5
6import logging
7
8from autotest_lib.client.cros.chameleon import chameleon_audio_ids as ids
9
10
11class AudioBoard(object):
12    """AudioBoard is an abstraction of an audio board on a Chameleon board.
13
14    It provides methods to control audio board.
15
16    A ChameleonConnection object is passed to the construction.
17
18    """
19
20    def __init__(self, chameleon_connection):
21        """Constructs an AudioBoard.
22
23        @param chameleon_connection: A ChameleonConnection object.
24
25        @returns: An AudioBoard object.
26
27        """
28        self._audio_buses = {
29                1: AudioBus(1, chameleon_connection),
30                2: AudioBus(2, chameleon_connection)
31        }
32        self._chameleon_connection = chameleon_connection
33        self._jack_plugger = None
34        self._bluetooth_controller = BluetoothController(chameleon_connection)
35
36    def get_audio_bus(self, bus_index):
37        """Gets an audio bus on this audio board.
38
39        @param bus_index: The bus index 1 or 2.
40
41        @returns: An AudioBus object.
42
43        """
44        return self._audio_buses[bus_index]
45
46    def get_jack_plugger(self):
47        """Gets an AudioJackPlugger on this audio board.
48
49        @returns: An AudioJackPlugger object if there is an audio jack plugger.
50                  None if there is no audio jack plugger.
51        @raises:
52            AudioJackPluggerException if there is no jack plugger on this audio
53            board.
54
55        """
56        if self._jack_plugger is None:
57            try:
58                self._jack_plugger = AudioJackPlugger(
59                        self._chameleon_connection)
60            except AudioJackPluggerException as e:
61                logging.error(
62                        'There is no jack plugger on this audio board. Please '
63                        'check the jack plugger if all labels are correctly '
64                        'configured.')
65                self._jack_plugger = None
66        return self._jack_plugger
67
68    def get_bluetooth_controller(self):
69        """Gets an BluetoothController on this audio board.
70
71        @returns: An BluetoothController object.
72
73        """
74        return self._bluetooth_controller
75
76
77class AudioBus(object):
78    """AudioBus is an abstraction of an audio bus on an audio board.
79
80    It provides methods to control audio bus.
81
82    A ChameleonConnection object is passed to the construction.
83
84    @properties:
85        bus_index: The bus index 1 or 2.
86
87    """
88    # Maps port id defined in chameleon_audio_ids to endpoint name used in
89    # chameleond audio bus API.
90    _PORT_ID_AUDIO_BUS_ENDPOINT_MAP = {
91            ids.ChameleonIds.LINEIN: 'Chameleon FPGA line-in',
92            ids.ChameleonIds.LINEOUT: 'Chameleon FPGA line-out',
93            ids.CrosIds.HEADPHONE: 'Cros device headphone',
94            ids.CrosIds.EXTERNAL_MIC: 'Cros device external microphone',
95            ids.PeripheralIds.SPEAKER: 'Peripheral speaker',
96            ids.PeripheralIds.MIC: 'Peripheral microphone',
97            ids.PeripheralIds.BLUETOOTH_DATA_RX: 'Bluetooth module output',
98            ids.PeripheralIds.BLUETOOTH_DATA_TX: 'Bluetooth module input'
99    }
100
101    class AudioBusSnapshot(object):
102        """Abstracts the snapshot of AudioBus for user to restore it later."""
103
104        def __init__(self, endpoints):
105            """Initializes an AudioBusSnapshot.
106
107            @param endpoints: A set of endpoints to keep a copy.
108
109            """
110            self._endpoints = endpoints.copy()
111
112    def __init__(self, bus_index, chameleon_connection):
113        """Constructs an AudioBus.
114
115        @param bus_index: The bus index 1 or 2.
116        @param chameleon_connection: A ChameleonConnection object.
117
118        """
119        self.bus_index = bus_index
120        self._chameleond_proxy = chameleon_connection
121        self._connected_endpoints = set()
122
123    def _get_endpoint_name(self, port_id):
124        """Gets the endpoint name used in audio bus API.
125
126        @param port_id: A string, that is, id in ChameleonIds, CrosIds, or
127                        PeripheralIds defined in chameleon_audio_ids.
128
129        @returns: The endpoint name for the port used in audio bus API.
130
131        """
132        return self._PORT_ID_AUDIO_BUS_ENDPOINT_MAP[port_id]
133
134    def _connect_endpoint(self, endpoint):
135        """Connects an endpoint to audio bus.
136
137        @param endpoint: An endpoint name in _PORT_ID_AUDIO_BUS_ENDPOINT_MAP.
138
139        """
140        logging.debug('Audio bus %s is connecting endpoint %s', self.bus_index,
141                      endpoint)
142        self._chameleond_proxy.AudioBoardConnect(self.bus_index, endpoint)
143        self._connected_endpoints.add(endpoint)
144
145    def _disconnect_endpoint(self, endpoint):
146        """Disconnects an endpoint from audio bus.
147
148        @param endpoint: An endpoint name in _PORT_ID_AUDIO_BUS_ENDPOINT_MAP.
149
150        """
151        logging.debug('Audio bus %s is disconnecting endpoint %s',
152                      self.bus_index, endpoint)
153        self._chameleond_proxy.AudioBoardDisconnect(self.bus_index, endpoint)
154        self._connected_endpoints.remove(endpoint)
155
156    def connect(self, port_id):
157        """Connects an audio port to this audio bus.
158
159        @param port_id: A string, that is, id in ChameleonIds, CrosIds, or
160                        PeripheralIds defined in chameleon_audio_ids.
161
162        """
163        endpoint = self._get_endpoint_name(port_id)
164        self._connect_endpoint(endpoint)
165
166    def disconnect(self, port_id):
167        """Disconnects an audio port from this audio bus.
168
169        @param port_id: A string, that is, id in ChameleonIds, CrosIds, or
170                        PeripheralIds defined in chameleon_audio_ids.
171
172        """
173        endpoint = self._get_endpoint_name(port_id)
174        self._disconnect_endpoint(endpoint)
175
176    def clear(self):
177        """Disconnects all audio port from this audio bus."""
178        self._disconnect_all_endpoints()
179
180    def _disconnect_all_endpoints(self):
181        """Disconnects all endpoints from this audio bus."""
182        for endpoint in self._connected_endpoints.copy():
183            self._disconnect_endpoint(endpoint)
184
185    def get_snapshot(self):
186        """Gets the snapshot of AudioBus so user can restore it later.
187
188        @returns: An AudioBus.AudioBusSnapshot object.
189
190        """
191        return self.AudioBusSnapshot(self._connected_endpoints)
192
193    def restore_snapshot(self, snapshot):
194        """Restore the snapshot.
195
196        @param: An AudioBus.AudioBusSnapshot object got from get_snapshot.
197
198        """
199        self._disconnect_all_endpoints()
200        logging.debug('Restoring snapshot with %s', snapshot._endpoints)
201        for endpoint in snapshot._endpoints:
202            self._connect_endpoint(endpoint)
203
204
205class AudioJackPluggerException(Exception):
206    """Errors in AudioJackPlugger."""
207    pass
208
209
210class AudioJackPlugger(object):
211    """AudioJackPlugger is an abstraction of plugger controlled by audio board.
212
213    There is a motor in the audio box which can plug/unplug 3.5mm 4-ring
214    audio cable to/from audio jack of Cros deivce.
215    This motor is controlled by audio board.
216
217    A ChameleonConnection object is passed to the construction.
218
219    """
220
221    def __init__(self, chameleon_connection):
222        """Constructs an AudioJackPlugger.
223
224        @param chameleon_connection: A ChameleonConnection object.
225
226        @raises:
227            AudioJackPluggerException if there is no jack plugger on
228            this audio board.
229
230        """
231        self._chameleond_proxy = chameleon_connection
232        if not self._chameleond_proxy.AudioBoardHasJackPlugger():
233            raise AudioJackPluggerException(
234                    'There is no jack plugger on audio board. '
235                    'Perhaps the audio board is not connected to audio box.')
236
237    def plug(self):
238        """Plugs the audio cable into audio jack of Cros device."""
239        self._chameleond_proxy.AudioBoardAudioJackPlug()
240        logging.info('Plugged 3.5mm audio cable to Cros device.')
241
242    def unplug(self):
243        """Unplugs the audio cable from audio jack of Cros device."""
244        self._chameleond_proxy.AudioBoardAudioJackUnplug()
245        logging.info('Unplugged 3.5mm audio cable from Cros device.')
246
247
248class BluetoothController(object):
249    """An abstraction of bluetooth module on audio board.
250
251    There is a bluetooth module on the audio board. It can be controlled through
252    API provided by chameleon proxy.
253
254    """
255
256    def __init__(self, chameleon_connection):
257        """Constructs an BluetoothController.
258
259        @param chameleon_connection: A ChameleonConnection object.
260
261        """
262        self._chameleond_proxy = chameleon_connection
263
264    def reset(self):
265        """Resets the bluetooth module."""
266        self._chameleond_proxy.AudioBoardResetBluetooth()
267        logging.info('Resets bluetooth module on audio board.')
268
269    def disable(self):
270        """Disables the bluetooth module."""
271        self._chameleond_proxy.AudioBoardDisableBluetooth()
272        logging.info('Disables bluetooth module on audio board.')
273
274    def is_enabled(self):
275        """Checks if the bluetooth module is enabled.
276
277        @returns: True if bluetooth module is enabled. False otherwise.
278
279        """
280        return self._chameleond_proxy.AudioBoardIsBluetoothEnabled()
281