1# Copyright 2014 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
5import logging
6import time
7from collections import namedtuple
8
9from autotest_lib.client.bin import utils
10from autotest_lib.client.common_lib import error
11from autotest_lib.client.cros.chameleon import chameleon
12
13ChameleonPorts = namedtuple('ChameleonPorts', 'connected failed')
14
15
16class ChameleonPortFinder(object):
17    """
18    Responsible for finding all ports connected to the chameleon board.
19
20    It does not verify if these ports are connected to DUT.
21
22    """
23
24    def __init__(self, chameleon_board):
25        """
26        @param chameleon_board: a ChameleonBoard object representing the
27                                Chameleon board whose ports we are interested
28                                in finding.
29
30        """
31        self.chameleon_board = chameleon_board
32        self.connected = None
33        self.failed = None
34
35
36    def find_all_ports(self):
37        """
38        @returns a named tuple ChameleonPorts() containing a list of connected
39                 ports as the first element and failed ports as second element.
40
41        """
42        self.connected = self.chameleon_board.get_all_ports()
43        self.failed = []
44
45        return ChameleonPorts(self.connected, self.failed)
46
47
48    def find_port(self, interface):
49        """
50        @param interface: string, the interface. e.g: HDMI, DP, VGA
51        @returns a ChameleonPort object if port is found, else None.
52
53        """
54        connected_ports = self.find_all_ports().connected
55
56        for port in connected_ports:
57            if port.get_connector_type().lower() == interface.lower():
58                return port
59
60        return None
61
62
63    def __str__(self):
64        ports_to_str = lambda ports: ', '.join(
65                '%s(%d)' % (p.get_connector_type(), p.get_connector_id())
66                for p in ports)
67
68        if self.connected is None:
69            text = 'No port information. Did you run find_all_ports()?'
70        elif self.connected == []:
71            text = 'No port detected on the Chameleon board.'
72        else:
73            text = ('Detected %d connected port(s): %s. \t'
74                    % (len(self.connected), ports_to_str(self.connected)))
75
76        if self.failed:
77            text += ('DUT failed to detect Chameleon ports: %s'
78                     % ports_to_str(self.failed))
79
80        return text
81
82
83class ChameleonInputFinder(ChameleonPortFinder):
84    """
85    Responsible for finding all input ports connected to the chameleon board.
86
87    """
88
89    def find_all_ports(self):
90        """
91        @returns a named tuple ChameleonPorts() containing a list of connected
92                 input ports as the first element and failed ports as second
93                 element.
94
95        """
96        self.connected = self.chameleon_board.get_all_inputs()
97        self.failed = []
98
99        return ChameleonPorts(self.connected, self.failed)
100
101
102class ChameleonOutputFinder(ChameleonPortFinder):
103    """
104    Responsible for finding all output ports connected to the chameleon board.
105
106    """
107
108    def find_all_ports(self):
109        """
110        @returns a named tuple ChameleonPorts() containing a list of connected
111                 output ports as the first element and failed ports as the
112                 second element.
113
114        """
115        self.connected = self.chameleon_board.get_all_outputs()
116        self.failed = []
117
118        return ChameleonPorts(self.connected, self.failed)
119
120
121class ChameleonVideoInputFinder(ChameleonInputFinder):
122    """
123    Responsible for finding all video inputs connected to the chameleon board.
124
125    It also verifies if these ports are connected to DUT.
126
127    """
128
129    REPLUG_DELAY_SEC = 1
130
131    def __init__(self, chameleon_board, display_facade):
132        """
133        @param chameleon_board: a ChameleonBoard object representing the
134                                Chameleon board whose ports we are interested
135                                in finding.
136        @param display_facade: a display facade object, to access the DUT
137                               display functionality, either locally or
138                               remotely.
139
140        """
141        super(ChameleonVideoInputFinder, self).__init__(chameleon_board)
142        self.display_facade = display_facade
143        self._TIMEOUT_VIDEO_STABLE_PROBE = 10
144
145
146    def _yield_all_ports(self, failed_ports=None, raise_error=False):
147        """
148        Yields all connected video ports and ensures every of them plugged.
149
150        @param failed_ports: A list to append the failed port or None.
151        @param raise_error: True to raise TestFail if no connected video port.
152        @yields every connected ChameleonVideoInput which is ensured plugged
153                before yielding.
154
155        @raises TestFail if raise_error is True and no connected video port.
156
157        """
158        yielded = False
159        all_ports = super(ChameleonVideoInputFinder, self).find_all_ports()
160
161        # unplug all ports
162        for port in all_ports.connected:
163            if port.has_video_support():
164                chameleon.ChameleonVideoInput(port).unplug()
165
166        for port in all_ports.connected:
167            # Skip the non-video port.
168            if not port.has_video_support():
169                continue
170
171            video_port = chameleon.ChameleonVideoInput(port)
172            connector_type = video_port.get_connector_type()
173            # Plug the port to make it visible.
174            video_port.plug()
175            try:
176                # DUT takes some time to respond. Wait until the video signal
177                # to stabilize and wait for the connector change.
178                video_stable = video_port.wait_video_input_stable(
179                        self._TIMEOUT_VIDEO_STABLE_PROBE)
180                output = utils.wait_for_value_changed(
181                        self.display_facade.get_external_connector_name,
182                        old_value=False)
183
184                if not output:
185                    logging.warn('Maybe flaky that no display detected. Retry.')
186                    video_port.unplug()
187                    time.sleep(self.REPLUG_DELAY_SEC)
188                    video_port.plug()
189                    video_stable = video_port.wait_video_input_stable(
190                            self._TIMEOUT_VIDEO_STABLE_PROBE)
191                    output = utils.wait_for_value_changed(
192                            self.display_facade.get_external_connector_name,
193                            old_value=False)
194
195                logging.info('CrOS detected external connector: %r', output)
196
197                if output:
198                    yield video_port
199                    yielded = True
200                else:
201                    if failed_ports is not None:
202                       failed_ports.append(video_port)
203                    logging.error('CrOS failed to see any external display')
204                    if not video_stable:
205                        logging.warn('Chameleon timed out waiting CrOS video')
206            finally:
207                # Unplug the port not to interfere with other tests.
208                video_port.unplug()
209
210        if raise_error and not yielded:
211            raise error.TestFail('No connected video port found between CrOS '
212                                 'and Chameleon.')
213
214
215    def iterate_all_ports(self):
216        """
217        Iterates all connected video ports and ensures every of them plugged.
218
219        It is used via a for statement, like the following:
220
221            finder = ChameleonVideoInputFinder(chameleon_board, display_facade)
222            for chameleon_port in finder.iterate_all_ports()
223                # chameleon_port is automatically plugged before this line.
224                do_some_test_on(chameleon_port)
225                # chameleon_port is automatically unplugged after this line.
226
227        @yields every connected ChameleonVideoInput which is ensured plugged
228                before yeilding.
229
230        @raises TestFail if no connected video port.
231
232        """
233        return self._yield_all_ports(raise_error=True)
234
235
236    def find_all_ports(self):
237        """
238        @returns a named tuple ChameleonPorts() containing a list of connected
239                 video inputs as the first element and failed ports as second
240                 element.
241
242        """
243        dut_failed_ports = []
244        connected_ports = list(self._yield_all_ports(dut_failed_ports))
245        self.connected = connected_ports
246        self.failed = dut_failed_ports
247
248        return ChameleonPorts(connected_ports, dut_failed_ports)
249
250
251class ChameleonAudioInputFinder(ChameleonInputFinder):
252    """
253    Responsible for finding all audio inputs connected to the chameleon board.
254
255    It does not verify if these ports are connected to DUT.
256
257    """
258
259    def find_all_ports(self):
260        """
261        @returns a named tuple ChameleonPorts() containing a list of connected
262                 audio inputs as the first element and failed ports as second
263                 element.
264
265        """
266        all_ports = super(ChameleonAudioInputFinder, self).find_all_ports()
267        self.connected = [chameleon.ChameleonAudioInput(port)
268                          for port in all_ports.connected
269                          if port.has_audio_support()]
270        self.failed = []
271
272        return ChameleonPorts(self.connected, self.failed)
273
274
275class ChameleonAudioOutputFinder(ChameleonOutputFinder):
276    """
277    Responsible for finding all audio outputs connected to the chameleon board.
278
279    It does not verify if these ports are connected to DUT.
280
281    """
282
283    def find_all_ports(self):
284        """
285        @returns a named tuple ChameleonPorts() containing a list of connected
286                 audio outputs as the first element and failed ports as second
287                 element.
288
289        """
290        all_ports = super(ChameleonAudioOutputFinder, self).find_all_ports()
291        self.connected = [chameleon.ChameleonAudioOutput(port)
292                          for port in all_ports.connected
293                          if port.has_audio_support()]
294        self.failed = []
295
296        return ChameleonPorts(self.connected, self.failed)
297