blob: 12f8fa22550e3b002c4c500783e51978735d3f22 [file] [log] [blame]
# Copyright 2013 The ChromiumOS Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""A test to ensure the power type and status of device under test.
Description
-----------
The test detects the power type of the underlying device. It is also used to
ask operator to unplug the AC from the device.
Test Procedure
--------------
To test if plugged AC power is detected properly:
1. Plug the designated power source to the device, and unplug all the other
power sources.
2. Starts the test.
To test AC power unplugged:
1. Starts the test.
2. Follow the instruction on the UI to unplug the power.
Dependency
----------
- Need a power source.
For plugged with required power range:
- Need a USB PD power source with required power range.
Examples
--------
To test AC unplugged, add this to test list::
{
"pytest_name": "ac_power",
"args": {
"online": false
}
}
To test USBPD 45W plugged on usbpd port 0::
{
"pytest_name": "ac_power",
"args": {
"power_type": "USB_PD",
"usbpd_power_range": [0, 45, 45],
"online": true
}
}
"""
import numbers
from typing import List, Optional
from cros.factory.device import device_utils
from cros.factory.test.fixture import bft_fixture
from cros.factory.test.i18n import _
from cros.factory.test import session
from cros.factory.test import test_case
from cros.factory.test import test_ui
from cros.factory.testlog import testlog
from cros.factory.utils.arg_utils import Arg
_PROBE_TIMES_ID = 'probed_times'
_AC_STATUS_ID = 'ac_status'
_AC_POWER_ID = 'ac_power'
_AC_TYPE_USB_PD = 'USB_PD'
class ACPowerTestArgs:
power_type: Optional[str]
usbpd_power_range: Optional[List[int]]
online: bool
bft_fixture_class_name: Optional[str]
bft_fixture_params: Optional[dict]
retries: Optional[int]
polling_period_secs: numbers.Real
silent_warning: int
class ACPowerTest(test_case.TestCase):
"""A test to instruct the operator to plug/unplug AC power.
Args:
power_type: The type of the power. None to skip power type check.
usbpd_power_range: The required usbpd power range (min, max). None to skip
power range check.
online: True if expecting AC power. Otherwise, False.
bft_fixture_class_name: Fully-qualified class name of the BFTFixture
implementation to use.
bft_fixture_params: A dictionary of parameters for the BFTFixture class's
`__init__` method.
retries: Maximum number of retries allowed to pass the test.
polling_period_secs: Polling period in seconds.
silent_warning: Skips first N charger type mismatch before giving a
warning.
"""
related_components = tuple()
ARGS = [
Arg('power_type', str, 'Type of the power source', default=None),
Arg('usbpd_power_range', list,
'The required power usbpd power range [usbpd_port, min, max]',
default=None),
Arg('online', bool, 'True if expecting AC power', default=True),
Arg(
'bft_fixture_class_name', str,
'Fully-qualified class name of the BFTFixture implementation to use.',
default=None),
Arg(
'bft_fixture_params', dict,
'A dictionary of parameters for the BFTFixture class `Init()` '
'method.', default=None),
Arg(
'retries', int, 'Maximum number of retries allowed to pass the test. '
'0 means only probe once. Default None means probe forever.',
default=None),
Arg('polling_period_secs', numbers.Real, 'Polling period in seconds.',
default=1),
Arg(
'silent_warning', int,
'Skips first N charger type mismatch before giving a warning. '
'Because EC needs about 1.6 seconds to identify charger type after '
'it is plugged in, it skips first N mismatched probe.', default=2),
]
args: ACPowerTestArgs
ui: test_ui.StandardUI
def setUp(self):
self._power = device_utils.CreateDUTInterface().power
if not self.args.online:
instruction = _('Unplug the charger.')
elif self.args.power_type:
instruction = _('Plug in the charger ({type})', type=self.args.power_type)
else:
instruction = _('Plug in the charger')
self.ui.SetInstruction(instruction)
self.ui.SetState(
f'<div id="{_PROBE_TIMES_ID}"></div><div '
f'id="{_AC_STATUS_ID}"></div><div id="{_AC_POWER_ID}"></div>')
self._power_state = {}
self._last_type = None
self._last_ac_present = None
self._skip_warning_remains = self.args.silent_warning
if self.args.usbpd_power_range is not None:
testlog.UpdateParam(
name='usbpd_power',
description='Detected usbpd power.',
value_unit='mW')
# Prepare fixture auto test if needed.
self.fixture = None
if self.args.bft_fixture_class_name:
self.fixture = bft_fixture.CreateBFTFixture(
self.args.bft_fixture_class_name, self.args.bft_fixture_params)
def UpdateACPower(self, watt, min_watt, max_watt):
self.ui.SetHTML(
_(
'Detected power {watt} W, '
'required power range ({min_watt} W, {max_watt} W)', watt=watt,
min_watt=min_watt, max_watt=max_watt), id=_AC_POWER_ID)
def UpdateACStatus(self, status):
self.ui.SetHTML(status, id=_AC_STATUS_ID)
def UpdateProbeTimes(self, num_probes):
self.ui.SetHTML(
_('Probed {times} / {total}', times=num_probes,
total=self.args.retries), id=_PROBE_TIMES_ID)
def CheckCondition(self):
ac_present = self._power.CheckACPresent()
current_type = self._power.GetACType()
# Reset silent warning countdown when AC present status change.
# Also reset _last_type as we want to give a warning for each
# mismatched charger attached.
if self._last_ac_present != ac_present:
self._last_ac_present = ac_present
self._skip_warning_remains = self.args.silent_warning
self._last_type = None
if ac_present != self.args.online:
if not ac_present:
self.UpdateACStatus(_('No AC adapter'))
return False
if self.args.power_type and self.args.power_type != current_type:
if self._skip_warning_remains > 0:
self.UpdateACStatus(_('Identifying AC adapter...'))
self._skip_warning_remains -= 1
elif self._last_type != current_type:
self.UpdateACStatus(_('AC adapter type: {type}', type=current_type))
session.console.warning('Expecting %s but see %s', self.args.power_type,
current_type)
self._last_type = current_type
return False
if self.args.usbpd_power_range and self.args.power_type == _AC_TYPE_USB_PD:
usbpd_power_infos = self._power.GetUSBPDPowerInfo()
port, power_min, power_max = self.args.usbpd_power_range
# USBPortInfo: (id, state, voltage (mV), current (mA))
for info in usbpd_power_infos:
if info.id != port:
continue
power_watt = info.voltage * info.current // 1000000
self.UpdateACPower(power_watt, power_min, power_max)
result = testlog.CheckNumericParam(
'usbpdpower', power_watt, min=power_min, max=power_max)
if not result:
session.console.warning(
f'Expecting ({power_min}, {power_max}) watt usbpd power but see '
f'{power_watt}')
return result
return False
return True
def runTest(self):
if self.fixture:
self.fixture.SetDeviceEngaged(bft_fixture.BFTFixture.Device.AC_ADAPTER,
self.args.online)
num_probes = 0
while True:
if self.args.retries is not None:
self.UpdateProbeTimes(num_probes)
if self.CheckCondition():
break
num_probes += 1
if self.args.retries is not None and num_probes > self.args.retries:
self.FailTask(f'Failed after probing {int(num_probes)} times')
# Prevent busy polling.
self.Sleep(self.args.polling_period_secs)