| # -*- coding: utf-8 -*- |
| # |
| # Copyright 2014 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. |
| |
| """Test Raiden USB type-C port charging function. |
| |
| This test can be tested locally or in remote ADB connection manner. |
| |
| Use Plankton-Raiden board control to verify device battery current. It supports |
| charge-5V, charge-12V, charge-20V, and discharge. This test also takes INA |
| current and voltage value on Plankton-Raiden board into account as a judgement. |
| """ |
| |
| import logging |
| import time |
| import unittest |
| |
| import factory_common # pylint: disable=unused-import |
| from cros.factory.device import device_utils |
| from cros.factory.test import factory |
| from cros.factory.test.fixture import bft_fixture |
| from cros.factory.test import test_ui |
| from cros.factory.test import ui_templates |
| from cros.factory.test.utils import stress_manager |
| from cros.factory.utils.arg_utils import Arg |
| from cros.factory.utils import sync_utils |
| from cros.factory.utils import time_utils |
| from cros.factory.utils import type_utils |
| |
| _TEST_TITLE = test_ui.MakeLabel('Raiden Charging Test', u'Raiden 充电测试') |
| _TESTING_ADB_CONNECTION = test_ui.MakeLabel( |
| 'Waiting for ADB device connection...', |
| u'等待机器连线...') |
| _TESTING_PROTECT = test_ui.MakeLabel( |
| 'Checking Plankton INA current for protection...', |
| u'检查电流保护...') |
| _TESTING_CHARGE = lambda v: test_ui.MakeLabel( |
| 'Testing battery %dV charging...' % v, |
| u'测试电池 %dV 充电中...' % v) |
| _TESTING_DISCHARGE = test_ui.MakeLabel('Testing battery discharging...', |
| u'测试电池放电中...') |
| _CSS = 'body { font-size: 2em; }' |
| |
| |
| class RaidenChargeBFTTest(unittest.TestCase): |
| """Tests raiden port charge functionality.""" |
| ARGS = [ |
| Arg('adb_remote_test', bool, 'Run test against remote ADB target.', |
| default=False), |
| Arg('bft_fixture', dict, bft_fixture.TEST_ARG_HELP), |
| Arg('charge_duration_secs', (int, float), |
| 'The duration in seconds to charge the battery', default=5), |
| Arg('discharge_duration_secs', (int, float), |
| 'The duration in seconds to discharge the battery', default=5), |
| Arg('min_charge_5V_current_mA', (int, float), |
| 'The minimum charge current in mA that the battery ' |
| 'needs to reach during charge-5V test, if is None, ' |
| 'it would not check on this voltage level', optional=True), |
| Arg('min_charge_12V_current_mA', (int, float), |
| 'The minimum charge current in mA that the battery ' |
| 'needs to reach during charge-12V test, if is None, ' |
| 'it would not check on this voltage level', optional=True), |
| Arg('min_charge_20V_current_mA', (int, float), |
| 'The minimum charge current in mA that the battery ' |
| 'needs to reach during charge-20V test, if is None, ' |
| 'it would not check on this voltage level', optional=True), |
| Arg('min_discharge_current_mA', (int, float), |
| 'The minimum discharge current in mA that the battery ' |
| 'needs to reach during discharging test, if is None, ' |
| 'it would not check on this voltage level', optional=True), |
| Arg('current_sampling_period_secs', (int, float), |
| 'The period in seconds to sample ' |
| 'charge/discharge current during test', |
| default=0.5), |
| Arg('check_battery_cycle', bool, |
| 'Whether to check battery cycle count is lower than threshold', |
| default=False), |
| Arg('battery_cycle_threshold', int, |
| 'The threshold for battery cycle count', |
| default=0), |
| Arg('check_current_max', bool, |
| 'Whether to check battery current max is not zero', |
| default=False), |
| Arg('check_protect_ina_current', bool, |
| 'If set True, it would check if Plankton 5V INA current is within ' |
| 'protect_ina_current_range first for high-V protection', |
| default=True), |
| Arg('protect_ina_current_range', tuple, |
| 'A tuple for indicating reasonable current in mA of charge-5V from ' |
| 'Plankton INA for high-V protection', |
| default=(2000, 3400)), |
| Arg('protect_ina_retry_times', int, |
| 'Retry times for checking 5V INA current for high-V protection, ' |
| 'interval with 1 second', |
| default=5), |
| Arg('check_ina_current', bool, |
| 'If set True, it would check Plankton INA current during testing ' |
| 'whether within ina_current_charge_range for charging, or ' |
| 'ina_current_discharge_range for discharge', |
| default=True), |
| Arg('ina_current_charge_range', tuple, |
| 'A tuple for indicating reasonable current in mA during charging ' |
| 'from Plankton INA', |
| default=(2000, 3400)), |
| Arg('ina_current_discharge_range', tuple, |
| 'A tuple for indicating reasonable current in mA during discharging ' |
| 'from Plankton INA', |
| default=(-3500, 0)), |
| Arg('ina_voltage_tolerance', float, |
| 'Tolerance ratio for Plankton INA voltage compared to expected ' |
| 'charging voltage', |
| default=0.12), |
| Arg('monitor_plankton_voltage_only', bool, |
| 'If set True, it would only check Plankton INA voltage whether as ' |
| 'expected (not check DUT side)', |
| default=False) |
| ] |
| |
| _SUPPORT_CHARGE_VOLT = [5, 12, 20] # supported charging voltage |
| _DISCHARGE_VOLT = 5 # discharging voltage |
| |
| def setUp(self): |
| self._dut = device_utils.CreateDUTInterface() |
| self._power = self._dut.power |
| self.VerifyArgs() |
| self._ui = test_ui.UI(css=_CSS) |
| self._template = ui_templates.OneSection(self._ui) |
| self._template.SetTitle(_TEST_TITLE) |
| self._bft_fixture = bft_fixture.CreateBFTFixture(**self.args.bft_fixture) |
| self._adb_remote_test = self.args.adb_remote_test |
| if self._adb_remote_test: |
| self._template.SetState(_TESTING_ADB_CONNECTION) |
| self._bft_fixture.SetDeviceEngaged('ADB_HOST', engage=True) |
| |
| def tearDown(self): |
| if self._adb_remote_test: |
| # Set back the state of ADB-connected before leaving test. It can make |
| # sequential tests smoother by reducing ADB connection wait time. |
| self._bft_fixture.SetDeviceEngaged('ADB_HOST', engage=True) |
| self._bft_fixture.Disconnect() |
| |
| def VerifyArgs(self): |
| """Verifies arguments feasibility. |
| |
| Raises: |
| FactoryTestFailure: If arguments are not reasonable. |
| """ |
| if (self.args.check_protect_ina_current and |
| (self.args.protect_ina_current_range[0] > |
| self.args.protect_ina_current_range[1])): |
| raise factory.FactoryTestFailure( |
| 'protect_ina_current_range range is invalid') |
| if (self.args.check_ina_current and |
| (self.args.ina_current_charge_range[0] > |
| self.args.ina_current_charge_range[1])): |
| raise factory.FactoryTestFailure( |
| 'ina_current_charge_range range is invalid') |
| if (self.args.check_ina_current and |
| (self.args.ina_current_discharge_range[0] > |
| self.args.ina_current_discharge_range[1])): |
| raise factory.FactoryTestFailure( |
| 'ina_current_discharge_range range is invalid') |
| if (self.args.min_charge_5V_current_mA is not None and |
| self.args.min_charge_5V_current_mA < 0): |
| raise factory.FactoryTestFailure( |
| 'min_charge_5V_current_mA must not be less than zero') |
| if (self.args.min_charge_12V_current_mA is not None and |
| self.args.min_charge_12V_current_mA < 0): |
| raise factory.FactoryTestFailure( |
| 'min_charge_12V_current_mA must not be less than zero') |
| if (self.args.min_charge_20V_current_mA is not None and |
| self.args.min_charge_20V_current_mA < 0): |
| raise factory.FactoryTestFailure( |
| 'min_charge_20V_current_mA must not be less than zero') |
| if (self.args.min_discharge_current_mA is not None and |
| not self.args.min_discharge_current_mA < 0): |
| raise factory.FactoryTestFailure( |
| 'min_discharge_current_mA must be less than zero') |
| |
| def SampleCurrentAndVoltage(self, duration_secs, charging): |
| """Samples battery current and Plankton INA current/voltage for duration. |
| |
| Args: |
| duration_secs: The duration in seconds to sample current. |
| charging: True if testing charging; False if discharging. |
| |
| Returns: |
| A tuple of three lists (sampled battery current, sampled INA current, |
| sampled INA voltage). |
| """ |
| sampled_battery_current = [] |
| sampled_ina_current = [] |
| sampled_ina_voltage = [] |
| end_time = time_utils.MonotonicTime() + duration_secs |
| while time_utils.MonotonicTime() < end_time: |
| # Skip sampling ADB target current while discharging since we lose ADB |
| # connection during that time. |
| if not (self._adb_remote_test and not charging): |
| sampled_battery_current.append(self._power.GetBatteryCurrent()) |
| ina_values = self._bft_fixture.ReadINAValues() |
| sampled_ina_current.append(ina_values['current']) |
| sampled_ina_voltage.append(ina_values['voltage']) |
| time.sleep(self.args.current_sampling_period_secs) |
| |
| if not (self._adb_remote_test and not charging): |
| logging.info('Sampled battery current: %s', str(sampled_battery_current)) |
| logging.info('Sampled ina current: %s', str(sampled_ina_current)) |
| logging.info('Sampled ina voltage: %s', str(sampled_ina_voltage)) |
| return (sampled_battery_current, sampled_ina_current, sampled_ina_voltage) |
| |
| def MonitorINAVoltage(self, timeout_secs, testing_volt): |
| """Polls Plankton INA voltage until it reaches the expected voltage. |
| |
| Args: |
| timeout_secs: The duration of polling interval in seconds. |
| testing_volt: The testing voltage in V. |
| |
| Returns: |
| True if voltage meets as expected during polling cycle; otherwise False. |
| """ |
| tolerance = testing_volt * 1000 * self.args.ina_voltage_tolerance |
| def _PollINAVoltage(): |
| ina_voltage = self._bft_fixture.ReadINAValues()['voltage'] |
| logging.info('Monitored ina voltage: %d', ina_voltage) |
| return abs(ina_voltage - testing_volt * 1000.0) <= tolerance |
| |
| try: |
| sync_utils.WaitFor(_PollINAVoltage, timeout_secs, |
| self.args.current_sampling_period_secs) |
| return True |
| except type_utils.TimeoutError: |
| ina_voltage = self._bft_fixture.ReadINAValues()['voltage'] |
| factory.console.error('Expected voltage: %d mV, got: %d mV', |
| testing_volt * 1000, ina_voltage) |
| return False |
| |
| def Check5VINACurrent(self): |
| """Checks if Plankton INA is within range for charge-5V. |
| |
| If charge-5V current is within range, returns immediately. Otherwise, retry |
| args.protect_ina_retry_times before failing the test. |
| """ |
| if not self.args.check_protect_ina_current: |
| return |
| current_min, current_max = self.args.protect_ina_current_range |
| |
| self._template.SetState(_TESTING_PROTECT) |
| self._bft_fixture.SetDeviceEngaged('CHARGE_5V', engage=True) |
| |
| ina_current = 0 |
| retry = self.args.protect_ina_retry_times |
| while retry > 0: |
| time.sleep(1) # Wait for INA stable |
| ina_current = self._bft_fixture.ReadINAValues()['current'] |
| logging.info('Current of plankton INA = %d mA', ina_current) |
| if current_min <= ina_current <= current_max: |
| break |
| retry -= 1 |
| |
| if retry <= 0: |
| self.fail('Plankton INA current %d mA out of range [%d, %d] ' |
| 'after %d retry.' % |
| (ina_current, current_min, current_max, |
| self.args.protect_ina_retry_times)) |
| |
| def TestCharging(self, current_min_threshold, testing_volt): |
| """Tests charge scenario. It will monitor within args.charge_duration_secs. |
| |
| Args: |
| current_min_threshold: Minimum threshold to check charging current. If |
| None, skip the test. |
| testing_volt: An integer to specify testing charge voltage. |
| |
| Raises: |
| FactoryTestFailure: If the sampled battery charge current does not pass |
| the given threshold in dargs. |
| """ |
| if current_min_threshold is None: |
| return |
| |
| if testing_volt not in self._SUPPORT_CHARGE_VOLT: |
| raise factory.FactoryTestFailure( |
| 'Specified test voltage %d is not in supported list: %r' % |
| (testing_volt, self._SUPPORT_CHARGE_VOLT)) |
| |
| command_device = 'CHARGE_%dV' % testing_volt |
| logging.info('Testing %s...', command_device) |
| |
| self._template.SetState(_TESTING_CHARGE(testing_volt)) |
| |
| # Plankton-Raiden board setting: engage |
| self._bft_fixture.SetDeviceEngaged(command_device, engage=True) |
| |
| if self.args.monitor_plankton_voltage_only: |
| if not self.MonitorINAVoltage(self.args.charge_duration_secs, |
| testing_volt): |
| raise factory.FactoryTestFailure( |
| 'INA voltage did not meet the expected one.') |
| return |
| |
| (sampled_battery_current, sampled_ina_current, sampled_ina_voltage) = ( |
| self.SampleCurrentAndVoltage(self.args.charge_duration_secs, |
| charging=True)) |
| # Fail if all battery current samples are below threshold. |
| if not any(c > current_min_threshold for c in sampled_battery_current): |
| raise factory.FactoryTestFailure( |
| 'Battery charge current did not reach defined threshold %f mA' % |
| current_min_threshold) |
| # Fail if all Plankton INA voltage samples are not within specified range. |
| self.CheckINASampleVoltage(sampled_ina_voltage, testing_volt, charging=True) |
| # If args.check_ina_current, fail if average of Plankton INA current |
| # samples is not within specified range. |
| self.CheckINASampleCurrent(sampled_ina_current, charging=True) |
| |
| def TestDischarging(self): |
| """Tests discharging within args.discharge_duration_secs. |
| |
| For local test scenario, the test runs under high system load to maximize |
| battery discharge current. |
| |
| For adb remote test, it only measures Plankton INA voltage and current since |
| we lose adb connection during device discharging. |
| |
| Raises: |
| FactoryTestFailure: If the sampled battery discharge current does not pass |
| the given threshold in dargs. |
| """ |
| current_min_threshold = self.args.min_discharge_current_mA |
| if current_min_threshold is None: |
| return |
| |
| logging.info('Testing discharge...') |
| self._template.SetState(_TESTING_DISCHARGE) |
| self._bft_fixture.SetDeviceEngaged('CHARGE_5V', engage=False) |
| |
| if self.args.monitor_plankton_voltage_only: |
| if not self.MonitorINAVoltage(self.args.charge_duration_secs, 5): |
| raise factory.FactoryTestFailure( |
| 'INA voltage did not meet the expected one.') |
| return |
| |
| if self._adb_remote_test: |
| (_, sampled_ina_current, sampled_ina_voltage) = ( |
| self.SampleCurrentAndVoltage(self.args.discharge_duration_secs, |
| charging=False)) |
| else: |
| # Discharge under high system load. |
| with stress_manager.StressManager(self._dut).Run( |
| self.args.discharge_duration_secs): |
| (sampled_battery_current, sampled_ina_current, sampled_ina_voltage) = ( |
| self.SampleCurrentAndVoltage( |
| self.args.discharge_duration_secs, charging=False)) |
| # Fail if all samples are over threshold. |
| if not any(c < current_min_threshold for c in sampled_battery_current): |
| raise factory.FactoryTestFailure( |
| 'Battery discharge current did not reach defined threshold %f mA' % |
| current_min_threshold) |
| |
| # Fail if all Plankton INA voltage samples are not within specified range. |
| self.CheckINASampleVoltage( |
| sampled_ina_voltage, self._DISCHARGE_VOLT, charging=False) |
| # If args.check_ina_current, fail if average of Plankton INA current |
| # samples is not within specified range. |
| self.CheckINASampleCurrent(sampled_ina_current, charging=False) |
| |
| def CheckINASampleVoltage(self, ina_sample, testing_volt, charging): |
| """Checks if average INA voltage is within range on Plankton. |
| |
| Args: |
| ina_sample: A list of sampled Plankton INA voltage. |
| testing_volt: Expected testing voltage in V. |
| charging: True if testing charging; False if discharging. |
| |
| Raises: |
| FactoryTestFailure if samples are not within range. |
| """ |
| tolerance = testing_volt * 1000 * self.args.ina_voltage_tolerance |
| # Fail if error ratios of all voltage samples are higher than tolerance |
| if not any(abs(v - testing_volt * 1000.0) <= tolerance for v in ina_sample): |
| raise factory.FactoryTestFailure( |
| 'Plankton INA voltage did not meet expected %s %dV, sampled voltage ' |
| '= %s' % ('charge' if charging else 'discharge', |
| testing_volt, str(ina_sample))) |
| |
| def CheckINASampleCurrent(self, ina_sample, charging): |
| """Checks if average INA current is within range on Plankton. |
| |
| Args: |
| ina_sample: A list of sampled Plankton INA current. |
| charging: True if testing charging; False if discharging. |
| |
| Raises: |
| FactoryTestFailure if samples are not within range. |
| """ |
| if not self.args.check_ina_current: |
| return |
| if charging: |
| ina_min, ina_max = self.args.ina_current_charge_range |
| else: |
| ina_min, ina_max = self.args.ina_current_discharge_range |
| # Fail if average is not within range. |
| # Neglect first 2 samples since they may on current up-lifting stage. |
| ina_sample = ina_sample if len(ina_sample) < 2 else ina_sample[2:] |
| average = sum(ina_sample) / len(ina_sample) |
| logging.info('Average Plankton INA current = %d mA', average) |
| if not ina_min < average < ina_max: |
| raise factory.FactoryTestFailure( |
| 'Plankton INA %s current average %d mA did not within %d - %d' % |
| ('charge' if charging else 'discharge', average, ina_min, ina_max)) |
| |
| def runTest(self): |
| """Runs charge test. |
| |
| Raises: |
| FactoryTestFailure: Battery attribute error. |
| """ |
| if not self._power.CheckBatteryPresent(): |
| raise factory.FactoryTestFailure( |
| 'Cannot locate battery sysfs path. Missing battery?') |
| if (self.args.check_battery_cycle and |
| int(self._power.GetBatteryAttribute('cycle_count').strip()) > ( |
| self.args.battery_cycle_threshold)): |
| raise factory.FactoryTestFailure('Battery cycle count is higher than %d' % |
| self.args.battery_cycle_threshold) |
| if (self.args.check_current_max and |
| int(self._power.GetBatteryAttribute('current_max').strip()) == 0): |
| raise factory.FactoryTestFailure('Battery current max is zero') |
| |
| if self._adb_remote_test: |
| # Get adb target battery capacity and warn if almost full |
| capacity = self._power.GetBatteryCapacity() |
| factory.console.info('Current battery capacity = %d %%', capacity) |
| if capacity > 95: |
| factory.console.warning('Current battery capacity is almost full. ' |
| 'It may cause charge failure!!') |
| else: |
| # Set charge state to 'charge' |
| self._power.SetChargeState(self._power.ChargeState.CHARGE) |
| logging.info('Set charge state: CHARGE') |
| |
| self.Check5VINACurrent() |
| self.TestCharging(self.args.min_charge_5V_current_mA, testing_volt=5) |
| self.TestCharging(self.args.min_charge_12V_current_mA, testing_volt=12) |
| self.TestCharging(self.args.min_charge_20V_current_mA, testing_volt=20) |
| self.TestDischarging() |