Add basic Plankton USB type C charging test

Plankton is a USB type C testing hardware that can be configured as
charger or power sink. Plankton is controlled by servod, this CL adds
Plankton class that provides high level type C controls.

BUG=chrome-os-partner:33362
TEST=manual
  test_that --fast -b typec_host_board due_ip_address \
  firmware_TypeCCharging

Change-Id: I48d6bc088144750103fccab3d9abbce1ce8c374d
Reviewed-on: https://chromium-review.googlesource.com/256275
Reviewed-by: Wai-Hong Tam <waihong@chromium.org>
Commit-Queue: Rong Chang <rongchang@chromium.org>
Tested-by: Rong Chang <rongchang@chromium.org>
diff --git a/server/cros/servo/plankton.py b/server/cros/servo/plankton.py
new file mode 100644
index 0000000..3453138
--- /dev/null
+++ b/server/cros/servo/plankton.py
@@ -0,0 +1,116 @@
+# Copyright 2015 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.
+
+import logging
+import re
+import time
+import xmlrpclib
+
+
+class PlanktonError(Exception):
+    pass
+
+
+class Plankton(object):
+    """Manages control of a Plankton board via servod XMLRPC.
+
+    Plankton is a testing board developed to aid in USB type C debug and
+    control of various type C host devices. Plankton's features include the
+    simulation of charger, USB 2.0 pass through, USB 3.0 hub, and display port
+    pass through. This class manages setting up and communication with a servo
+    daemon (servod) process. It provides high level functions for setting and
+    reading USB type C role, mux and common controls.
+    """
+
+    DEFAULT_SERVO_HOST = 'localhost'
+    DEFAULT_SERVO_PORT = 9999
+    # USB charging command delays in seconds.
+    USBC_COMMAND_DELAY = 0.5
+    # Plankton USBC commands.
+    USBC_ROLE = 'usbc_role'
+    RE_USBC_ROLE_VOLTAGE = re.compile(r'src(\d+)v')
+    USBC_CHARGING_VOLTAGES = {
+        0: 'sink',
+        5: 'src5v',
+        12: 'src12v',
+        20: 'src20v'}
+    VBUS_VOLTAGE_MV = 'vbus_voltage'
+    VBUS_CURRENT_MA = 'vbus_current'
+    VBUS_POWER_MW = 'vbus_power'
+
+
+    def __init__(self, args_dict=None):
+        """Sets up servo daemon communication.
+
+        @param args_dict: A dictionary contains plankton servod host and port.
+                          Example: {'plankton_host': 'localhost',
+                                    'plankton_port': 9999}
+        """
+        if args_dict is None:
+            args_dict = {}
+        plankton_host = args_dict.get('plankton_host', self.DEFAULT_SERVO_HOST)
+        plankton_port = args_dict.get('plankton_port', self.DEFAULT_SERVO_PORT)
+        remote = 'http://%s:%s' % (plankton_host, plankton_port)
+        self._server = xmlrpclib.ServerProxy(remote)
+
+
+    def set(self, control_name, value):
+        """Sets the value of a control using servod."""
+        assert control_name
+        self._server.set(control_name, value)
+
+
+    def get(self, control_name):
+        """Gets the value of a control from servod."""
+        assert control_name
+        return self._server.get(control_name)
+
+
+    @property
+    def vbus_voltage(self):
+        """Gets Plankton VBUS voltage in volts."""
+        return float(self.get(self.VBUS_VOLTAGE_MV)) / 1000.0
+
+
+    @property
+    def vbus_current(self):
+        """Gets Plankton VBUS current in amps."""
+        return float(self.get(self.VBUS_CURRENT_MA)) / 1000.0
+
+
+    @property
+    def vbus_power(self):
+        """Gets Plankton charging power in watts."""
+        return float(self.get(self.VBUS_POWER_MW)) / 1000.0
+
+
+    def get_charging_voltages(self):
+        """Gets the lists of available charging voltages."""
+        return self.USBC_CHARGING_VOLTAGES.keys()
+
+
+    def charge(self, voltage):
+        """Sets Plankton to provide power at specific voltage.
+
+        @param voltage: Specified charging voltage in volts.
+        """
+        if voltage not in self.USBC_CHARGING_VOLTAGES:
+            raise PlanktonError('Invalid charging voltage: %s' % voltage)
+
+        self.set(self.USBC_ROLE, self.USBC_CHARGING_VOLTAGES[voltage])
+        time.sleep(self.USBC_COMMAND_DELAY)
+
+
+    @property
+    def charging_voltage(self):
+        """Gets current charging voltage."""
+        usbc_role = self.get(self.USBC_ROLE)
+        match = self.RE_USBC_ROLE_VOLTAGE(usbc_role)
+        if match:
+            return int(match.group(1))
+
+        if usbc_role == self.USBC_CHARGING_VOLTAGES[0]:
+            return 0
+
+        raise PlanktonError('Invalid USBC role: %s' % usbc_role)