xref: /aosp_15_r20/external/autotest/client/site_tests/power_BatteryCharge/power_BatteryCharge.py (revision 9c5db1993ded3edbeafc8092d69fe5de2ee02df7)
1# Lint as: python2, python3
2# Copyright (c) 2010 The Chromium OS Authors. All rights reserved.
3# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
5
6import logging, time
7from autotest_lib.client.common_lib import error
8from autotest_lib.client.cros import service_stopper
9from autotest_lib.client.cros.power import power_status
10from autotest_lib.client.cros.power import power_test
11from autotest_lib.client.cros.power import power_utils
12
13
14class power_BatteryCharge(power_test.power_Test):
15    """class power_BatteryCharge."""
16    version = 1
17
18    def initialize(self, pdash_note=''):
19        """Perform necessary initialization prior to test run."""
20
21        if not power_utils.has_battery():
22            raise error.TestNAError('DUT has no battery. Test Skipped')
23
24        self.status = power_status.get_status()
25
26        if not self.status.on_ac():
27            raise error.TestNAError(
28                  'This test needs to be run with the AC power online')
29
30        super(power_BatteryCharge, self).initialize(seconds_period=20,
31                                                    pdash_note=pdash_note,
32                                                    force_discharge=False)
33
34        self._services = service_stopper.ServiceStopper(
35            service_stopper.ServiceStopper.POWER_DRAW_SERVICES + ['ui'])
36        self._services.stop_services()
37
38    def run_once(self, max_run_time=180, percent_charge_to_add=1,
39                 percent_initial_charge_max=None,
40                 percent_target_charge=None,
41                 use_design_charge_capacity=True):
42
43        """
44        max_run_time: maximum time the test will run for
45        percent_charge_to_add: percentage of the charge capacity charge to
46                  add. The target charge will be capped at the charge capacity.
47        percent_initial_charge_max: maxium allowed initial charge.
48        use_design_charge_capacity: If set, use charge_full_design rather than
49                  charge_full for calculations. charge_full represents
50                  wear-state of battery, vs charge_full_design representing
51                  ideal design state.
52        """
53
54        time_to_sleep = 60
55
56        self._backlight = power_utils.Backlight()
57        self._backlight.set_percent(0)
58
59        self.remaining_time = self.max_run_time = max_run_time
60
61        self.charge_full_design = self.status.battery.charge_full_design
62        self.charge_full = self.status.battery.charge_full
63        if use_design_charge_capacity:
64            self.charge_capacity = self.charge_full_design
65        else:
66            self.charge_capacity = self.charge_full
67
68        if self.charge_capacity == 0:
69            raise error.TestError('Failed to determine charge capacity')
70
71        self.initial_charge = self.status.battery.charge_now
72        percent_initial_charge = self.initial_charge * 100 / \
73                                 self.charge_capacity
74        if percent_initial_charge_max and percent_initial_charge > \
75                                          percent_initial_charge_max:
76            raise error.TestError('Initial charge (%f) higher than max (%f)'
77                      % (percent_initial_charge, percent_initial_charge_max))
78
79        current_charge = self.initial_charge
80        if percent_target_charge is None:
81            charge_to_add = self.charge_capacity * \
82                            float(percent_charge_to_add) / 100
83            target_charge = current_charge + charge_to_add
84        else:
85            target_charge = self.charge_capacity * \
86                            float(percent_target_charge) / 100
87
88        # trim target_charge if it exceeds charge capacity
89        if target_charge > self.charge_capacity:
90            target_charge = self.charge_capacity
91
92        logging.info('max_run_time: %d', self.max_run_time)
93        logging.info('initial_charge: %f', self.initial_charge)
94        logging.info('target_charge: %f', target_charge)
95
96        self.start_measurements()
97        while self.remaining_time and current_charge < target_charge:
98            if time_to_sleep > self.remaining_time:
99                time_to_sleep = self.remaining_time
100            self.remaining_time -= time_to_sleep
101
102            time.sleep(time_to_sleep)
103
104            self.status.refresh()
105
106            new_charge = self.status.battery.charge_now
107            logging.info('time_to_sleep: %d', time_to_sleep)
108            logging.info('charge_added: %f', (new_charge - current_charge))
109
110            current_charge = new_charge
111            logging.info('current_charge: %f', current_charge)
112
113            if self.status.battery.status == 'Full':
114                logging.info('Battery full, aborting!')
115                break
116            elif self.status.battery.status == 'Discharging':
117                # TestError might be raised if |use_design_charge_capacity|
118                # is True when testing with older battery.
119                if current_charge > self.charge_capacity * 0.97:
120                    logging.info('Battery full (Discharge on AC), aborting!')
121                else:
122                    raise error.TestError('This test needs to be run with the '
123                                          'battery charging on AC.')
124        self._end_time = time.time()
125
126    def postprocess_iteration(self):
127        """"Collect and log keyvals."""
128        keyvals = {}
129        keyvals['ah_charge_full'] = self.charge_full
130        keyvals['ah_charge_full_design'] = self.charge_full_design
131        keyvals['ah_charge_capacity'] = self.charge_capacity
132        keyvals['ah_initial_charge'] = self.initial_charge
133        keyvals['ah_final_charge'] = self.status.battery.charge_now
134        s_time_taken = self.max_run_time - self.remaining_time
135        min_time_taken = s_time_taken / 60.
136        keyvals['s_time_taken'] = s_time_taken
137        keyvals['percent_initial_charge'] = self.initial_charge * 100 / \
138                                            keyvals['ah_charge_capacity']
139        keyvals['percent_final_charge'] = keyvals['ah_final_charge'] * 100 / \
140                                          keyvals['ah_charge_capacity']
141
142        percent_charge_added = keyvals['percent_final_charge'] - \
143            keyvals['percent_initial_charge']
144        # Conditionally write charge current keyval only when the amount of
145        # charge added is > 50% to remove samples when test is run but battery
146        # is already mostly full.  Otherwise current will be ~0 and not
147        # meaningful.
148        if percent_charge_added > 50:
149            hrs_charging = keyvals['s_time_taken'] / 3600.
150            keyvals['a_avg50_charge_current'] = \
151                (keyvals['ah_final_charge'] - self.initial_charge) / \
152                hrs_charging
153
154        self.keyvals.update(keyvals)
155
156        self._keyvallogger.add_item('time_to_charge_min', min_time_taken,
157                                    'point', 'perf')
158        self._keyvallogger.add_item('initial_charge_ah', self.initial_charge,
159                                    'point', 'perf')
160        self._keyvallogger.add_item('final_charge_ah',
161                                    self.status.battery.charge_now, 'point',
162                                    'perf')
163        self._keyvallogger.add_item('charge_full_ah', self.charge_full,
164                                    'point', 'perf')
165        self._keyvallogger.add_item('charge_full_design_ah',
166                                    self.charge_full_design, 'point', 'perf')
167        self._keyvallogger.set_end(self._end_time)
168
169        super(power_BatteryCharge, self).postprocess_iteration()
170
171    def cleanup(self):
172        """Restore stop services and backlight level."""
173        if hasattr(self, '_services') and self._services:
174            self._services.restore_services()
175        if hasattr(self, '_backlight') and self._backlight:
176            self._backlight.restore()
177