FAFT: Support sending multiple controls in one connection to servod

Originally, servod accepts one control per connection. For exmaple, a power
button press is composed of two connections, one for press and one for release.

We found that this kind of time-sensitive behaviors failed under a congested
network environment. For example, a 0.1s power button press to power on DUT
actaully took 1s to be done, which shut the DUT down again.

A patch on servod accepts multiple controls per one connection, see:
https://gerrit.chromium.org/gerrit/46309

This CL fixes FAFT to support sending multiple controls to servod.
It falls back to the original one control per connection when it sees
servod is too old.

BUG=chrome-os-partner:18047,chromeos-os-partner:18048
TEST=manual
Using the original servod, only one control per connection supported,
run firmware_FAFTSetup and firmware_ConsecutiveBoot passed.

Using the new servod, multiple controls per connection supported,
run firmware_FAFTSetup and firmware_ConsecutiveBoot passed.

The tests firmware_FAFTSetup and firmware_ConsecutiveBoot cover the following
behaviors this CL changed:
 - cold reset,
 - warm reset,
 - power button press.

Change-Id: Icfa2439cc8128be7e6648d0ab89ccbba70d53006
Reviewed-on: https://gerrit.chromium.org/gerrit/46509
Reviewed-by: Vic Yang <victoryang@chromium.org>
Commit-Queue: Tom Wai-Hong Tam <waihong@chromium.org>
Tested-by: Tom Wai-Hong Tam <waihong@chromium.org>
diff --git a/server/cros/servo/servo.py b/server/cros/servo/servo.py
index 430c82e..a565449 100644
--- a/server/cros/servo/servo.py
+++ b/server/cros/servo/servo.py
@@ -221,9 +221,9 @@
         Args:
           secs: Time in seconds to simulate the keypress.
         """
-        self.set_nocheck('pwr_button', 'press')
-        time.sleep(secs)
-        self.set_nocheck('pwr_button', 'release')
+        self.set_get_all(['pwr_button:press',
+                          'sleep:%.4f' % secs,
+                          'pwr_button:release'])
         # TODO(tbroch) Different systems have different release times on the
         # power button that this loop addresses.  Longer term we may want to
         # make this delay platform specific.
@@ -376,11 +376,11 @@
         This has the side effect of shutting off the device.  The
         device is guaranteed to be off at the end of this call.
         """
+        self.set_get_all(['cold_reset:on',
+                          'sleep:%.4f' % self._DUT_RESET_DELAY,
+                          'cold_reset:off'])
         # After the reset, give the EC the time it needs to
         # re-initialize.
-        self.set('cold_reset', 'on')
-        time.sleep(self._DUT_RESET_DELAY)
-        self.set('cold_reset', 'off')
         time.sleep(self._EC_RESET_DELAY)
 
 
@@ -389,9 +389,9 @@
 
         Has the side effect of restarting the device.
         """
-        self.set('warm_reset', 'on')
-        time.sleep(self._DUT_RESET_DELAY)
-        self.set('warm_reset', 'off')
+        self.set_get_all(['warm_reset:on',
+                          'sleep:%.4f' % self._DUT_RESET_DELAY,
+                          'warm_reset:off'])
 
 
     def _get_xmlrpclib_exception(self, xmlexc):
@@ -452,6 +452,39 @@
             raise error.TestFail(err_msg)
 
 
+    def set_get_all(self, controls):
+        """Set &| get one or more control values.
+
+        @param controls: list of strings, controls to set &| get.
+
+        @raise: error.TestError in case error occurs setting/getting values.
+        """
+        rv = []
+        try:
+            rv = self._server.set_get_all(controls)
+        except xmlrpclib.Fault as e:
+            # TODO(waihong): Remove the following backward compatibility when
+            # the new versions of hdctools are deployed.
+            if 'not supported' in str(e):
+                logging.warning('The servod is too old that set_get_all '
+                        'not supported. Use set and get instead.')
+                for control in controls:
+                    if ':' in control:
+                        (name, value) = control.split(':')
+                        if name == 'sleep':
+                            time.sleep(float(value))
+                        else:
+                            self.set_nocheck(name, value)
+                        rv.append(True)
+                    else:
+                        rv.append(self.get(name))
+            else:
+                err_msg = "Problem with '%s' :: %s" % \
+                    (controls, self._get_xmlrpclib_exception(e))
+                raise error.TestFail(err_msg)
+        return rv
+
+
     # TODO(waihong) It may fail if multiple servo's are connected to the same
     # host. Should look for a better way, like the USB serial name, to identify
     # the USB device.