Servo AutoTest framework and pwr_button test.

BUG=chromium-os:16588
TEST=manually tested with servo and partner device

Change-Id: I1a89b60203c511ff716ba16cdea986dcfbe53659
Reviewed-on: http://gerrit.chromium.org/gerrit/3098
Reviewed-by: <craigdh@google.com>
Tested-by: <craigdh@google.com>
Reviewed-by: Nirnimesh <nirnimesh@chromium.org>
diff --git a/server/cros/servo.py b/server/cros/servo.py
new file mode 100755
index 0000000..1bfeec0
--- /dev/null
+++ b/server/cros/servo.py
@@ -0,0 +1,233 @@
+# Copyright (c) 2011 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.
+#
+# Expects to be run in an environment with sudo and no interactive password
+# prompt, such as within the Chromium OS development chroot.
+
+
+import time
+import xmlrpclib
+import subprocess
+
+
+class Servo:
+    """Manages control of a Servo board.
+
+    Servo is a board developed by hardware group to aide in the debug and
+    control of various partner devices. Servo's features include the simulation
+    of pressing the power button, closing the lid, and pressing Ctrl-d. This
+    class manages setting up and communicating with a servo demon (servod)
+    process. It provides both high-level functions for common servo tasks and
+    low-level functions for directly setting and reading gpios.
+    """
+
+    _server = None
+    _servod = None
+
+
+    def __init__(self, servo_port, xml_config='servo.xml', cold_reset=False):
+        """Sets up the servo communication infrastructure.
+
+        Args:
+          servo_port: Port the servod process should listen on.
+          xml_config: Configuration XML file for servod.
+          cold_reset: If True, cold reset device and boot during init,
+                      otherwise perform init with device running.
+        """
+        # launch servod
+        self._launch_servod(servo_port, xml_config)
+
+        # connect to servod
+        assert servo_port
+
+        self._connect_servod(servo_port)
+        if cold_reset:
+            self._init_seq_cold_reset_devmode()
+        else:
+            self._init_seq()
+
+
+
+    def power_long_press(self):
+        """Simulate a long (8 sec) power button press."""
+        self.power_key(8)
+
+
+    def power_normal_press(self):
+        """Simulate a normal (1 sec) power button press."""
+        self.power_key(1)
+
+
+    def power_short_press(self):
+        """Simulate a short (0.1 sec) power button press."""
+        self.power_key(0.1)
+
+
+    def power_key(self, secs=1):
+        """Simulate a power button press.
+
+        Args:
+          secs: Time in seconds to simulate the keypress.
+        """
+        self.set('pwr_button', 'press')
+        time.sleep(secs)
+        self.set('pwr_button', 'release')
+
+
+    def lid_open(self):
+        """Simulate opening the lid."""
+        self.set('lid_open', 'yes')
+
+
+    def lid_close(self):
+        """Simulate closing the lid."""
+        self.set('lid_open', 'no')
+
+
+    def ctrl_d(self, secs=0.5):
+        """Simulate Ctrl-d simultaneous button presses.
+
+        Args:
+          secs: Time in seconds to simulate the keypress.
+        """
+        self.set_nocheck('kbd_en', 'on')
+        self.set_nocheck('kbd_m1', 'r2_c2')
+        self.set_nocheck('kbd_m2', 'r1_c1')
+        time.sleep(secs)
+        self.set_nocheck('kbd_en', 'off')
+
+
+    def enter_key(self, secs=0.5):
+        """Simulate Enter key button press.
+
+        Args:
+          secs: Time in seconds to simulate the keypress.
+        """
+        self.set_nocheck('kbd_en', 'on')
+        self.set_nocheck('kbd_m1', 'r3_c2')
+        time.sleep(secs)
+        self.set_nocheck('kbd_en', 'off')
+
+
+    def refresh_key(self, secs=0.5):
+        """Simulate Refresh key (F3) button press.
+
+        Args:
+          secs: Time in seconds to simulate the keypress.
+        """
+        self.set_nocheck('kbd_en', 'on')
+        self.set_nocheck('kbd_m2', 'r2_c1')
+        time.sleep(secs)
+        self.set_nocheck('kbd_en', 'off')
+
+
+    def imaginary_key(self, secs=0.5):
+        """Simulate imaginary key button press.
+
+        Maps to a key that doesn't physically exist.
+
+        Args:
+          secs: Time in seconds to simulate the keypress.
+        """
+        self.set_nocheck('kbd_en', 'on')
+        self.set_nocheck('kbd_m2', 'r3_c1')
+        time.sleep(secs)
+        self.set_nocheck('kbd_en', 'off')
+
+
+    def boot_devmode(self):
+        """Boot a dev-mode device that is powered off."""
+        self.set('pwr_button', 'release')
+        time.sleep(1)
+        self.power_normal_press()
+        time.sleep(8)
+        self.ctrl_d()
+        time.sleep(15)
+
+    def _init_seq_cold_reset_devmode(self):
+        """Cold reset, init device, and boot in dev-mode."""
+        self._cold_reset()
+        self._init_seq()
+        self.set('dev_mode', 'on')
+        self.boot_devmode()
+
+
+    def get(self, gpio_name):
+        """Get the value of a gpio from Servod."""
+        assert gpio_name
+        return self._server.get(gpio_name)
+
+
+    def set(self, gpio_name, gpio_value):
+        """Set and check the value of a gpio using Servod."""
+        assert gpio_name and gpio_value
+        self._server.set(gpio_name, gpio_value)
+        assert gpio_value == self.get(gpio_name)
+
+
+    def set_nocheck(self, gpio_name, gpio_value):
+        """Set the value of a gpio using Servod."""
+        assert gpio_name and gpio_value
+        self._server.set(gpio_name, gpio_value)
+
+
+    def __del__(self):
+        """Kill the Servod process."""
+        assert self._servod
+        # kill servod one way or another
+        try:
+            # won't work without superuser privileges
+            self._servod.terminate()
+        except:
+            # should work without superuser privileges
+            assert subprocess.call(['sudo', 'kill', str(self._servod.pid)])
+
+
+    def _launch_servod(self, servo_port, xml_config='servo.xml'):
+        """Launch the servod process.
+
+        Args:
+          servo_port: Port to start servod listening on.
+          xml_config: XML configuration file for servod.
+        """
+        self._servod = subprocess.Popen(['sudo', 'servod', '-c',
+                                         str(xml_config),
+                                         '--host=localhost',
+                                         '--port=' + str(servo_port)],
+                                        0, None, None, None, subprocess.PIPE)
+        # wait for servod to initialize
+        timeout = 10
+        while ("Listening" not in self._servod.stderr.readline() and
+               self._servod.returncode is None and timeout > 0):
+            time.sleep(1)
+            timeout -= 1
+        assert self._servod.returncode is None and timeout
+
+
+    def _init_seq(self):
+        """Initiate starting state for servo."""
+        self.set('tx_dir', 'input')
+        self.set('servo_dut_tx', 'off')
+        self.set('lid_open', 'yes')
+        self.set('rec_mode', 'off')
+
+
+    def _cold_reset(self):
+        """Perform a cold reset of the EC.
+
+        Has the side effect of shutting off the device.
+        """
+        self.set('cold_reset', 'on')
+        time.sleep(2)
+        self.set('cold_reset', 'off')
+
+
+    def _connect_servod(self, servo_port=''):
+        """Connect to the Servod process with XMLRPC.
+
+        Args:
+          servo_port: Port the Servod process is listening on.
+        """
+        remote = 'http://localhost:%s' % servo_port
+        self._server = xmlrpclib.ServerProxy(remote)