1# Copyright (c) 2012 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, time
6from autotest_lib.client.bin import test, utils
7from autotest_lib.client.common_lib import error
8from autotest_lib.client.cros import power_status, power_utils
9from autotest_lib.client.cros.graphics import graphics_utils
10
11
12def get_num_outputs_on():
13    """
14    Retrieves the number of connected outputs that are on.
15    @return: integer value of number of connected outputs that are on.
16    """
17
18    return graphics_utils.get_num_outputs_on();
19
20class power_BacklightControl(test.test):
21    version = 1
22    # Minimum number of steps expected between min and max brightness levels.
23    _min_num_steps = 4
24    # Minimum required percentage change in energy rate between transitions
25    # (max -> min, min-> off)
26    _energy_rate_change_threshold_percent = 5
27
28
29    def initialize(self):
30        """Perform necessary initialization prior to test run.
31
32        Private Attributes:
33          _backlight: power_utils.Backlight object
34        """
35        super(power_BacklightControl, self).initialize()
36        self._backlight = None
37
38
39    def run_once(self):
40        # Require that this test be run on battery with at least 5% charge
41        status = power_status.get_status()
42        status.assert_battery_state(5)
43
44        prefs = { 'has_ambient_light_sensor' : 0,
45                  'ignore_external_policy'   : 1,
46                  'plugged_dim_ms'           : 7200000,
47                  'plugged_off_ms'           : 9000000,
48                  'plugged_suspend_ms'       : 18000000,
49                  'unplugged_dim_ms'         : 7200000,
50                  'unplugged_off_ms'         : 9000000,
51                  'unplugged_suspend_ms'     : 18000000 }
52        self._pref_change = power_utils.PowerPrefChanger(prefs)
53
54        keyvals = {}
55        num_errors = 0
56
57        # These are the expected ratios of energy rate between max, min, and off
58        # (zero) brightness levels.  e.g. when changing from max to min, the
59        # energy rate must become <= (max_energy_rate * max_to_min_factor).
60        max_to_min_factor = \
61            1.0 - self._energy_rate_change_threshold_percent / 100.0
62        min_to_off_factor = \
63            1.0 - self._energy_rate_change_threshold_percent / 100.0
64        off_to_max_factor = 1.0 / (max_to_min_factor * min_to_off_factor)
65
66        # Determine the number of outputs that are on.
67        starting_num_outputs_on = get_num_outputs_on()
68        if starting_num_outputs_on == 0:
69            raise error.TestFail('At least one display output must be on.')
70        keyvals['starting_num_outputs_on'] = starting_num_outputs_on
71
72        self._backlight = power_utils.Backlight()
73        keyvals['max_brightness'] = self._backlight.get_max_level()
74        if keyvals['max_brightness'] <= self._min_num_steps:
75            raise error.TestFail('Must have at least %d backlight levels' %
76                                 (self._min_num_steps + 1))
77
78        keyvals['initial_brightness'] = self._backlight.get_level()
79
80        self._wait_for_stable_energy_rate()
81        keyvals['initial_power_w'] = self._get_current_energy_rate()
82
83        self._backlight_controller = power_utils.BacklightController()
84        self._backlight_controller.set_brightness_to_max()
85
86        current_brightness = \
87            utils.wait_for_value(self._backlight.get_level,
88                                 max_threshold=keyvals['max_brightness'])
89        if current_brightness != keyvals['max_brightness']:
90            num_errors += 1
91            logging.error(('Failed to increase brightness to max, ' + \
92                           'brightness is %d.') % current_brightness)
93        else:
94            self._wait_for_stable_energy_rate()
95            keyvals['max_brightness_power_w'] = self._get_current_energy_rate()
96
97        # Set brightness to minimum without going to zero.
98        # Note that we don't know what the minimum brightness is, so just set
99        # min_threshold=0 to use the timeout to wait for the brightness to
100        # settle.
101        self._backlight_controller.set_brightness_to_min()
102        current_brightness = utils.wait_for_value(
103            self._backlight.get_level,
104            min_threshold=(keyvals['max_brightness'] / 2 - 1))
105        if current_brightness >= keyvals['max_brightness'] / 2 or \
106           current_brightness == 0:
107            num_errors += 1
108            logging.error('Brightness is not at minimum non-zero level: %d' %
109                          current_brightness)
110        else:
111            self._wait_for_stable_energy_rate()
112            keyvals['min_brightness_power_w'] = self._get_current_energy_rate()
113
114        # Turn off the screen by decreasing brightness one more time with
115        # allow_off=True.
116        self._backlight_controller.decrease_brightness(True)
117        current_brightness = utils.wait_for_value(
118            self._backlight.get_level, min_threshold=0)
119        if current_brightness != 0:
120            num_errors += 1
121            logging.error('Brightness is %d, expecting 0.' % current_brightness)
122
123        # Wait for screen to turn off.
124        num_outputs_on = utils.wait_for_value(
125            get_num_outputs_on, min_threshold=(starting_num_outputs_on - 1))
126        keyvals['outputs_on_after_screen_off'] = num_outputs_on
127        if num_outputs_on >= starting_num_outputs_on:
128            num_errors += 1
129            logging.error('At least one display must have been turned off. ' + \
130                          'Number of displays on: %s' % num_outputs_on)
131        else:
132            self._wait_for_stable_energy_rate()
133            keyvals['screen_off_power_w'] = self._get_current_energy_rate()
134
135        # Set brightness to max.
136        self._backlight_controller.set_brightness_to_max()
137        current_brightness = utils.wait_for_value(
138            self._backlight.get_level, max_threshold=keyvals['max_brightness'])
139        if current_brightness != keyvals['max_brightness']:
140            num_errors += 1
141            logging.error(('Failed to increase brightness to max, ' + \
142                           'brightness is %d.') % current_brightness)
143
144        # Verify that the same number of outputs are on as before.
145        num_outputs_on = get_num_outputs_on()
146        keyvals['outputs_on_at_end'] = num_outputs_on
147        if num_outputs_on != starting_num_outputs_on:
148            num_errors += 1
149            logging.error(('Number of displays turned on should be same as ' + \
150                           'at start.  Number of displays on: %s') %
151                          num_outputs_on)
152
153        self._wait_for_stable_energy_rate()
154        keyvals['final_power_w'] = self._get_current_energy_rate()
155
156        # Energy rate must have changed significantly between transitions.
157        if 'max_brightness_power_w' in keyvals and \
158           'min_brightness_power_w' in keyvals and \
159           keyvals['min_brightness_power_w'] >= \
160               keyvals['max_brightness_power_w'] * max_to_min_factor:
161            num_errors += 1
162            logging.error('Power draw did not decrease enough when ' + \
163                          'brightness was decreased from max to min.')
164
165        if 'screen_off_power_w' in keyvals and \
166           'min_brightness_power_w' in keyvals and \
167           keyvals['screen_off_power_w'] >= \
168               keyvals['min_brightness_power_w'] * min_to_off_factor:
169            num_errors += 1
170            logging.error('Power draw did not decrease enough when screen ' + \
171                          'was turned off.')
172
173        if num_outputs_on == starting_num_outputs_on and \
174           'screen_off_power_w' in keyvals and \
175           keyvals['final_power_w'] <= \
176               keyvals['screen_off_power_w'] * off_to_max_factor:
177            num_errors += 1
178            logging.error('Power draw did not increase enough after ' + \
179                          'turning screen on.')
180
181        self.write_perf_keyval(keyvals)
182
183        if num_errors > 0:
184            raise error.TestFail('Test failed with %d errors' % num_errors)
185
186
187    def cleanup(self):
188        if self._backlight:
189            self._backlight.restore()
190        super(power_BacklightControl, self).cleanup()
191
192
193    def _get_current_energy_rate(self):
194        return power_status.get_status().battery[0].energy_rate
195
196
197    def _wait_for_stable_energy_rate(self,
198                                     max_variation_percent=5,
199                                     sample_delay_sec=1,
200                                     window_size=10,
201                                     timeout_sec=30):
202        """
203        Waits for the energy rate to stablize.  Stability criterion:
204            The last |window_size| samples of energy rate do not deviate from
205            their mean by more than |max_variation_percent|.
206
207        Arguments:
208            max_variation_percent   Percentage of allowed deviation from mean
209                                    energy rate to still be considered stable.
210            sample_delay_sec        Time to wait between each reading of the
211                                    energy rate.
212            window_size             Number of energy rate samples required to
213                                    measure stability.  If there are more
214                                    samples than this amount, use only the last
215                                    |window_size| values.
216            timeout_sec             If stability has not been attained after
217                                    this long, stop waiting.
218
219        Return value:
220            True if energy rate stabilized before timeout.
221            False if timed out waiting for energy rate to stabilize.
222        """
223        start_time = time.time()
224        samples = []
225        max_variation_factor = max_variation_percent / 100.0
226        while time.time() - start_time < timeout_sec:
227            current_rate = self._get_current_energy_rate()
228
229            # Remove the oldest value if the list of energy rate samples is at
230            # the maximum limit |window_size|, before appending a new value.
231            if len(samples) >= window_size:
232                samples = samples[1:]
233            samples.append(current_rate)
234
235            mean = sum(samples) / len(samples)
236            if len(samples) >= window_size and \
237               max(samples) <= mean * (1 + max_variation_factor) and \
238               min(samples) >= mean * (1 - max_variation_factor):
239                return True
240
241            time.sleep(sample_delay_sec)
242
243        return False
244