blob: 70a35dd370a0540ced2429a397969c6b07c81ec5 [file] [log] [blame]
# Copyright 2022 The ChromiumOS Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Test and check cec power control feature on Chrome OS device.
Description
-----------
This test checks cec power control feature by turning off and turning on the
display power. Cec power control consists of following functions:
(1) Wake up a monitor.
(2) Put a monitor on standby.
(3) Get the display status of a monitor.
To check if a ChromeOS device support this feature or not:
(1) It depends on the HW design. There is an HDMI CEC pin in the HDMI connector.
(2) CEC function(i.e. ``cec-ctl``) has to be implemented to support it.
In case when a monitor cannot return the power status, we can set
``manual_mode`` to check the power status manually.
Test Procedure
--------------
This is an automated test without user interaction. The HDMI port should be
connected to a CEC-enabled TV.
If ``manual_mode`` is set, the operator has to press the key to indicate the
display status is on. Else the display status is off.
Dependency
----------
- HDMI-CEC support for display control in ChromeOS(cec-ctl)
- The driver of specified button source: GPIO.
Examples
--------
Read system information and power status for the monitor in /dev/cec1 and
verify that it is on::
{
"pytest_name": "cec",
"args": {
"index": 1,
"power_on": true
}
}
Read system information for the monitor in /dev/cec0 and check power status
manually. Verify that display can be turned on/off via CEC::
{
"pytest_name": "cec",
"args": {
"manual_mode": true,
"power_on": true,
"power_off": true
}
}
"""
import abc
from enum import IntEnum
import logging
import re
import subprocess
from cros.factory.device import device_utils
from cros.factory.test.i18n import test_ui as i18n_test_ui
from cros.factory.test import test_case
from cros.factory.test import test_ui
from cros.factory.utils.arg_utils import Arg
_CSS_CEC = """
#cec-title {
font-size: 2em;
width: 70%;
}
"""
_CSS_CEC_MANUAL = """
#cec-manual {
font-size: 1.6em;
width: 70%;
}
"""
_HTML_CEC = '<div id="cec-title"></div>'
_HTML_CEC_MANUAL = '<div id="cec-manual"></div>'
_MSG_CEC_INFO = i18n_test_ui.MakeI18nLabel(
'This test will control TV display power via HDMI CEC pin.<br>'
'Display would be turned off and then turned on back')
_MSG_CEC_MANUAL_INFO = i18n_test_ui.MakeI18nLabel(
'<br>Currently in manual mode.<br>Press Enter if display status is ON.'
'<br>Press Space if display status is OFF')
class Status(IntEnum):
ERROR = -1
ON = 0
OFF = 1
TO_ON = 2
TO_OFF = 3
class ICecController(abc.ABC):
"""Base class for CEC controllers."""
def __init__(self, dut):
"""Constructor.
Args:
dut: the DUT environment object.
"""
self._dut = dut
def SetUp(self):
"""Configure the CEC adapter."""
raise NotImplementedError
def GetDisplayStatus(self):
"""Returns the display status of the monitor.
Gets the display status by sending a status request and analyze output
text via the CEC controller.
Returns:
display power status:
-1: connection error
0: On
1: Standby
2: In transition Standby to On
3: In transition On to Standby
"""
raise NotImplementedError
def DisplayTurnOn(self):
"""Turns on the TV."""
raise NotImplementedError
def DisplayTurnOff(self):
"""Turns off the TV."""
raise NotImplementedError
class IEcCecController(ICecController):
"""Class for EC-based CEC controller."""
def GetDisplayStatus(self):
raise NotImplementedError
def DisplayTurnOn(self):
raise NotImplementedError
def DisplayTurnOff(self):
raise NotImplementedError
class ApCecController(ICecController):
"""Class for AP-based CEC controller."""
def __init__(self, dut, index):
super().__init__(dut)
self.index = index
def SetUp(self):
self._dut.CheckCall(f'cec-ctl --playback -s -d {int(self.index)}')
def GetDisplayStatus(self):
try:
output = self._dut.CheckOutput(
f'cec-ctl --to 0 --give-device-power-status -s -d {int(self.index)}')
except subprocess.CalledProcessError:
return Status.ERROR
logging.info(output)
match = re.search(r'pwr-state: \w+ \((?P<status>\w+)\)', output)
if match is None:
raise RuntimeError('Power status not received.')
status = int(match['status'], 16)
if status not in set(Status):
return Status.ERROR
return status
def DisplayTurnOn(self):
self._dut.CheckCall(
f'cec-ctl --to 0 --image-view-on -s -d {int(self.index)}')
def DisplayTurnOff(self):
self._dut.CheckCall(f'cec-ctl --to 0 --standby -s -d {int(self.index)}')
class CecTestArgs:
standby_wait_time: float
image_view_on_wait_time: float
description_wait_time: float
initial_status: int
power_on: bool
power_off: bool
controller_type: str
index: int
manual_mode: bool
class CecTest(test_case.TestCase):
""" The task to check CEC display power message feature. """
related_components = tuple()
ARGS = [
Arg(
'standby_wait_time', float,
'Waiting time after standby test to make sure status request could '
'return correct value. Increase it if needed.', 5.0),
Arg(
'image_view_on_wait_time', float,
'Waiting time after image view on test to make sure status request '
'could get correct value. Adjust it according to testing machine.',
10.0),
Arg('description_wait_time', float,
'Waiting time for tester to read description.', 2.0),
Arg('initial_status', int, 'Initial TV status (0 = ON, 1 = OFF).', 0),
Arg('power_on', bool, 'Test power on command.', True),
Arg('power_off', bool, 'Test power off command.', True),
Arg('controller_type', str,
'The CEC controller type (EC or AP) for the device.', 'AP'),
Arg(
'index', int, 'The index of the CEC device. The CEC device is at '
'/dev/cec``index``', 0),
Arg('manual_mode', bool, 'Manually check if power status is on or off.',
False)
]
args: CecTestArgs
ui: test_ui.StandardUI
cec: ICecController
def runTest(self):
""" This test task checks HDMI CEC feature. """
status = self.GetDisplayStatus()
if status == Status.ERROR:
raise RuntimeError('The CEC connection is broken.')
if status != self.args.initial_status:
raise RuntimeError(
f'The TV is in wrong power state. Current status: {status}; '
f'Expected status: {self.args.initial_status}.')
if self.args.power_off:
logging.info('Turning off the TV...')
self.CheckDisplayTurnOff()
if self.args.power_on:
logging.info('Turning on the TV...')
self.CheckDisplayTurnOn()
def setUp(self):
""" Initializes CEC environment. """
self.ui.AppendCSS(_CSS_CEC)
self.ui.SetState(_HTML_CEC)
self.ui.SetHTML(_MSG_CEC_INFO, id='cec-title')
if self.args.manual_mode:
self.ui.AppendCSS(_CSS_CEC_MANUAL)
self.ui.SetState(_HTML_CEC_MANUAL, append=True)
self.ui.SetHTML(_MSG_CEC_MANUAL_INFO, id='cec-manual')
self.Sleep(self.args.description_wait_time)
self._dut = device_utils.CreateDUTInterface()
if self.args.controller_type == 'EC':
self.cec = IEcCecController(self._dut)
elif self.args.controller_type == 'AP':
self.cec = ApCecController(self._dut, self.args.index)
else:
raise ValueError(
f'Controller type {self.args.controller_type} not supported.')
self.cec.SetUp()
def CheckDisplayTurnOn(self):
""" Sends an image_view_on CEC message and checks the display status.
Turns on the monitor from the device via the CEC controller and checks
that the display status is Status.ON or Status.TO_ON.
Returns:
None.
Raises:
RuntimeError if the display status if not Status.ON or Status.TO_ON.
"""
self.cec.DisplayTurnOn()
self.Sleep(self.args.image_view_on_wait_time)
status = self.GetDisplayStatus()
logging.info('Display status: %s.', status)
if status in (Status.ON, Status.TO_ON):
return
raise RuntimeError('CEC display turn on test failed.')
def CheckDisplayTurnOff(self):
""" Sends a sendby CEC message and checks the display status.
Turns off the monitor from the device via the CEC controller and checks
that the display status is Status.OFF or Status.TO_OFF.
Returns:
None.
Raises:
RuntimeError if the display status if not Status.OFF or Status.TO_OFF.
"""
self.cec.DisplayTurnOff()
self.Sleep(self.args.standby_wait_time)
status = self.GetDisplayStatus()
logging.info('Display status: %s.', status)
if status in (Status.OFF, Status.TO_OFF):
return
raise RuntimeError('CEC display stand by test failed.')
def GetDisplayStatus(self):
""" Gets the display status.
Gets the display status in different mode:
(1) Manual mode: An operator pressing the keys to send display status.
(2) Normal mode: CEC controller API.
Returns:
A status defined in ``class Status``.
"""
if self.args.manual_mode:
key_pressed = self.ui.WaitKeysOnce([test_ui.ENTER_KEY, test_ui.SPACE_KEY])
return Status.ON if key_pressed == test_ui.ENTER_KEY else Status.OFF
return self.cec.GetDisplayStatus()