1# Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
2# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4
5from threading import Timer
6import logging
7import re
8import time
9
10from autotest_lib.client.common_lib import error
11from autotest_lib.server.cros.faft.firmware_test import FirmwareTest
12
13
14def delayed(seconds): # pylint:disable=missing-docstring
15    def decorator(f): # pylint:disable=missing-docstring
16        def wrapper(*args, **kargs): # pylint:disable=missing-docstring
17            t = Timer(seconds, f, args, kargs)
18            t.start()
19        return wrapper
20    return decorator
21
22
23class firmware_ECLidSwitch(FirmwareTest):
24    """
25    Servo based EC lid switch test.
26    """
27    version = 1
28
29    # Delay between closing and opening the lid
30    LID_DELAY = 1
31
32    # Delay to allow FAFT client receive command
33    RPC_DELAY = 2
34
35    # Delay between shutdown and wakeup by lid switch
36    WAKE_DELAY = 10
37
38    def initialize(self, host, cmdline_args):
39        super(firmware_ECLidSwitch, self).initialize(host, cmdline_args)
40        # Only run in normal mode
41        self.switcher.setup_mode('normal')
42
43    def _open_lid(self):
44        """Open lid by servo."""
45        self.servo.set('lid_open', 'yes')
46
47    def _close_lid(self):
48        """Close lid by servo."""
49        self.servo.set('lid_open', 'no')
50
51    @delayed(RPC_DELAY)
52    def delayed_open_lid(self):
53        """Delay by RPC_DELAY and then open lid by servo."""
54        self._open_lid()
55
56    @delayed(RPC_DELAY)
57    def delayed_close_lid(self):
58        """Delay by RPC_DELAY and then close lid by servo."""
59        self._close_lid()
60
61    def _wake_by_lid_switch(self):
62        """Wake DUT with lid switch."""
63        self._close_lid()
64        time.sleep(self.LID_DELAY)
65        self._open_lid()
66
67    def delayed_wake(self):
68        """
69        Confirm the device is in G3, wait for WAKE_DELAY, and then wake DUT
70        with lid switch.
71        """
72        self.check_shutdown_power_state(self.POWER_STATE_G3, pwr_retries=10)
73        time.sleep(self.WAKE_DELAY)
74        self._wake_by_lid_switch()
75
76    def immediate_wake(self):
77        """Confirm the device is in G3 and then wake DUT with lid switch."""
78        self.check_shutdown_power_state(self.POWER_STATE_G3, pwr_retries=10)
79        self._wake_by_lid_switch()
80
81    def shutdown_and_wake(self, shutdown_func, wake_func):
82        """Software shutdown and wake.
83
84        Args:
85          shutdown_func: Function to shut down DUT.
86          wake_func: Delayed function to wake DUT.
87        """
88        shutdown_func()
89        wake_func()
90
91    def _get_keyboard_backlight(self):
92        """Get keyboard backlight brightness.
93
94        Returns:
95          Backlight brightness percentage 0~100. If it is disabled, 0 is
96            returned.
97        """
98        cmd = 'ectool pwmgetkblight'
99        pattern_percent = re.compile(
100            'Current keyboard backlight percent: (\d*)')
101        pattern_disable = re.compile('Keyboard backlight disabled.')
102        lines = self.faft_client.system.run_shell_command_get_output(cmd)
103        for line in lines:
104            matched_percent = pattern_percent.match(line)
105            if matched_percent is not None:
106                return int(matched_percent.group(1))
107            matched_disable = pattern_disable.match(line)
108            if matched_disable is not None:
109                return 0
110        raise error.TestError('Cannot get keyboard backlight status.')
111
112    def _set_keyboard_backlight(self, value):
113        """Set keyboard backlight brightness.
114
115        Args:
116          value: Backlight brightness percentage 0~100.
117        """
118        cmd = 'ectool pwmsetkblight %d' % value
119        self.faft_client.system.run_shell_command(cmd)
120
121    def check_keycode(self):
122        """Check that lid open/close do not send power button keycode.
123
124        Returns:
125          True if no power button keycode is captured. Otherwise, False.
126        """
127        # Don't check the keycode if we don't have a keyboard.
128        if not self.check_ec_capability(['keyboard'], suppress_warning=True):
129            return True
130
131        self._open_lid()
132        self.delayed_close_lid()
133        if self.faft_client.system.check_keys([]) < 0:
134            return False
135        self.delayed_open_lid()
136        if self.faft_client.system.check_keys([]) < 0:
137            return False
138        return True
139
140    def check_backlight(self):
141        """Check if lid open/close controls keyboard backlight as expected.
142
143        Returns:
144          True if keyboard backlight is turned off when lid close and on when
145           lid open.
146        """
147        if not self.check_ec_capability(['kblight'], suppress_warning=True):
148            return True
149        ok = True
150        original_value = self._get_keyboard_backlight()
151        self._set_keyboard_backlight(100)
152
153        self._close_lid()
154        if self._get_keyboard_backlight() != 0:
155            logging.error("Keyboard backlight still on when lid close.")
156            ok = False
157        self._open_lid()
158        if self._get_keyboard_backlight() == 0:
159            logging.error("Keyboard backlight still off when lid open.")
160            ok = False
161
162        self._set_keyboard_backlight(original_value)
163        return ok
164
165    def check_keycode_and_backlight(self):
166        """
167        Disable powerd to prevent DUT shutting down during test. Then check
168        if lid switch event controls keycode and backlight as we expected.
169        """
170        ok = True
171        logging.info("Stopping powerd")
172        self.faft_client.system.run_shell_command('stop powerd')
173        if not self.check_keycode():
174            logging.error("check_keycode failed.")
175            ok = False
176        if not self.check_backlight():
177            logging.error("check_backlight failed.")
178            ok = False
179        logging.info("Restarting powerd")
180        self.faft_client.system.run_shell_command('start powerd')
181        return ok
182
183    def run_once(self):
184        """Runs a single iteration of the test."""
185        if not self.check_ec_capability(['lid']):
186            raise error.TestNAError("Nothing needs to be tested on this device")
187
188        logging.info("Shut down and then wake up DUT after a delay.")
189        self.switcher.mode_aware_reboot(
190                'custom',
191                lambda:self.shutdown_and_wake(
192                        shutdown_func=self.run_shutdown_cmd,
193                        wake_func=self.delayed_wake))
194        logging.info("Shut down and then wake up DUT immediately.")
195        self.switcher.mode_aware_reboot(
196                'custom',
197                lambda:self.shutdown_and_wake(
198                        shutdown_func=self.run_shutdown_cmd,
199                        wake_func=self.immediate_wake))
200        logging.info("Close and then open the lid when not logged in.")
201        self.switcher.mode_aware_reboot(
202                'custom',
203                lambda:self.shutdown_and_wake(
204                        shutdown_func=self._close_lid,
205                        wake_func=self.immediate_wake))
206        logging.info("Check keycode and backlight.")
207        self.check_state(self.check_keycode_and_backlight)
208