| # Copyright 2013 The Chromium OS Authors. All rights reserved. |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| |
| """A factory test to test battery charging/discharging current. |
| |
| Description |
| ----------- |
| Test battery charging and discharging current. |
| |
| If `usbpd_info` is set, also prompt operator to insert a power adapter of given |
| voltage to the given USB type C port. |
| |
| The `usbpd_info` is a sequence `(usbpd_port, min_millivolt, max_millivolt)`, |
| represent the USB type C port to insert power adapter: |
| |
| - ``usbpd_port``: (int) usbpd_port number. Specify which port to insert power |
| line. |
| - ``min_millivolt``: (int) The minimum millivolt the power must provide. |
| - ``max_millivolt``: (int) The maximum millivolt the power must provide. |
| |
| Test Procedure |
| -------------- |
| 1. If `max_battery_level` is set, check that initial battery level is lower |
| than the value. |
| 2. If `usbpd_info` is set, prompt the operator to insert a power adapter of |
| given voltage to the given USB type C port, and pass this step when one is |
| detected. |
| 3. If `min_charging_current` is set, force the power into charging mode, and |
| check if the charging current is larger than the value. |
| 4. If `min_discharging_current` is set, force the power into discharging mode, |
| and check if the discharging current is larger than the value. |
| |
| Each step would fail after `timeout_secs` seconds. |
| |
| Dependency |
| ---------- |
| Device API cros.factory.device.power. |
| |
| If `usbpd_info` is set, device API cros.factory.device.usb_c.GetPDPowerStatus |
| is also used. |
| |
| Examples |
| -------- |
| To check battery can charge and discharge, add this in test list:: |
| |
| { |
| "pytest_name": "battery_current", |
| "args": { |
| "min_charging_current": 250, |
| "min_discharging_current": 400 |
| } |
| } |
| |
| To check that a 15V USB type C power adapter is connected to port 0, add this |
| in test list:: |
| |
| { |
| "pytest_name": "battery_current", |
| "args": { |
| "usbpd_info": [0, 14500, 15500], |
| "usbpd_prompt": "i18n! USB TypeC" |
| } |
| } |
| """ |
| |
| import logging |
| |
| import factory_common # pylint: disable=unused-import |
| from cros.factory.device import device_utils |
| from cros.factory.test.i18n import _ |
| from cros.factory.test.i18n import arg_utils as i18n_arg_utils |
| from cros.factory.test import test_case |
| from cros.factory.utils.arg_utils import Arg |
| from cros.factory.utils import sync_utils |
| |
| |
| def _GetPromptText(current, target): |
| return _( |
| 'Waiting for {target_status} current to meet {target_current} mA.' |
| ' (Currently {status} at {current} mA)', |
| target_status=_('charging') if target >= 0 else _('discharging'), |
| target_current=abs(target), |
| status=_('charging') if current >= 0 else _('discharging'), |
| current=abs(current)) |
| |
| |
| class BatteryCurrentTest(test_case.TestCase): |
| """A factory test to test battery charging/discharging current.""" |
| ARGS = [ |
| Arg('min_charging_current', int, |
| 'minimum allowed charging current', default=None), |
| Arg('min_discharging_current', int, |
| 'minimum allowed discharging current', default=None), |
| Arg('timeout_secs', int, |
| 'Test timeout value', default=10), |
| Arg('max_battery_level', int, |
| 'maximum allowed starting battery level', default=None), |
| Arg('usbpd_info', list, |
| 'A sequence [usbpd_port, min_millivolt, max_millivolt] used to ' |
| 'select a particular port from a multi-port DUT.', |
| default=None), |
| Arg('use_max_voltage', bool, |
| 'Use the negotiated max voltage in `ectool usbpdpower` to check ' |
| 'charger voltage, in case that instant voltage is not supported.', |
| default=False), |
| i18n_arg_utils.I18nArg('usbpd_prompt', |
| 'prompt operator which port to insert', |
| default='') |
| ] |
| |
| def setUp(self): |
| self._dut = device_utils.CreateDUTInterface() |
| self._power = self._dut.power |
| if self.args.usbpd_info: |
| self._CheckUSBPDInfoArg(self.args.usbpd_info) |
| self._usbpd_port = self.args.usbpd_info[0] |
| self._usbpd_min_millivolt = self.args.usbpd_info[1] |
| self._usbpd_max_millivolt = self.args.usbpd_info[2] |
| self._usbpd_prompt = self.args.usbpd_prompt |
| |
| def _CheckUSBPDInfoArg(self, info): |
| if len(info) == 5: |
| check_types = (int, basestring, basestring, int, int) |
| elif len(info) == 3: |
| check_types = (int, int, int) |
| else: |
| raise ValueError('ERROR: invalid usbpd_info item: ' + str(info)) |
| |
| for i in xrange(len(info)): |
| if not isinstance(info[i], check_types[i]): |
| logging.error('(%s)usbpd_info[%d] type is not %s', type(info[i]), i, |
| check_types[i]) |
| raise ValueError('ERROR: invalid usbpd_info[%d]: ' % i + str(info)) |
| |
| def _LogCurrent(self, current): |
| if current >= 0: |
| logging.info('Charging current = %d mA', current) |
| else: |
| logging.info('Discharging current = %d mA', -current) |
| |
| def _CheckUSBPD(self): |
| for unused_i in range(10): |
| status = self._dut.usb_c.GetPDPowerStatus() |
| voltage_field = ('max_millivolt' if self.args.use_max_voltage else |
| 'millivolt') |
| if voltage_field not in status[self._usbpd_port]: |
| self.ui.SetState( |
| _('Insert power to {prompt}({voltage}mV)', |
| prompt=self._usbpd_prompt, |
| voltage=0)) |
| logging.info('No millivolt detected in port %d', self._usbpd_port) |
| return False |
| millivolt = status[self._usbpd_port][voltage_field] |
| logging.info('millivolt %d, acceptable range (%d, %d)', millivolt, |
| self._usbpd_min_millivolt, self._usbpd_max_millivolt) |
| self.ui.SetState( |
| _('Insert power to {prompt}({voltage}mV)', |
| prompt=self._usbpd_prompt, |
| voltage=millivolt)) |
| if not (self._usbpd_min_millivolt <= millivolt <= |
| self._usbpd_max_millivolt): |
| return False |
| self.Sleep(0.1) |
| return True |
| |
| def _CheckCharge(self): |
| current = self._power.GetBatteryCurrent() |
| target = self.args.min_charging_current |
| self._LogCurrent(current) |
| self.ui.SetState(_GetPromptText(current, target)) |
| return current >= target |
| |
| def _CheckDischarge(self): |
| current = self._power.GetBatteryCurrent() |
| target = self.args.min_discharging_current |
| self._LogCurrent(current) |
| self.ui.SetState(_GetPromptText(current, -target)) |
| return -current >= target |
| |
| def runTest(self): |
| """Main entrance of charger test.""" |
| self.assertTrue(self._power.CheckBatteryPresent()) |
| if self.args.max_battery_level: |
| self.assertLessEqual(self._power.GetChargePct(), |
| self.args.max_battery_level, |
| 'Starting battery level too high') |
| if self.args.usbpd_info is not None: |
| sync_utils.PollForCondition( |
| poll_method=self._CheckUSBPD, poll_interval_secs=0.5, |
| condition_name='CheckUSBPD', |
| timeout_secs=self.args.timeout_secs) |
| if self.args.min_charging_current: |
| self._power.SetChargeState(self._power.ChargeState.CHARGE) |
| sync_utils.PollForCondition( |
| poll_method=self._CheckCharge, poll_interval_secs=0.5, |
| condition_name='ChargeCurrent', |
| timeout_secs=self.args.timeout_secs) |
| if self.args.min_discharging_current: |
| self._power.SetChargeState(self._power.ChargeState.DISCHARGE) |
| sync_utils.PollForCondition( |
| poll_method=self._CheckDischarge, poll_interval_secs=0.5, |
| condition_name='DischargeCurrent', |
| timeout_secs=self.args.timeout_secs) |
| |
| def tearDown(self): |
| # Must enable charger to charge or we will drain the battery! |
| self._power.SetChargeState(self._power.ChargeState.CHARGE) |