blob: 1cf39cde4344f6a199db1f20bd8f3d215c64c31b [file] [log] [blame]
Simran Basia9f41032012-05-11 14:21:58 -07001# Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
Todd Broche505b8d2011-03-21 18:19:54 -07002# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4"""Servo Server."""
Simran Basia9f41032012-05-11 14:21:58 -07005import fnmatch
Todd Broche505b8d2011-03-21 18:19:54 -07006import imp
7import logging
Simran Basia9f41032012-05-11 14:21:58 -07008import os
9import shutil
Todd Broche505b8d2011-03-21 18:19:54 -070010import SimpleXMLRPCServer
Simran Basia9f41032012-05-11 14:21:58 -070011import subprocess
12import tempfile
Todd Broch7a91c252012-02-03 12:37:45 -080013import time
Simran Basia9f41032012-05-11 14:21:58 -070014import urllib
Todd Broche505b8d2011-03-21 18:19:54 -070015
16# TODO(tbroch) deprecate use of relative imports
Vic Yangbe6cf262012-09-10 10:40:56 +080017from drv.hw_driver import HwDriverError
Todd Broche505b8d2011-03-21 18:19:54 -070018import ftdigpio
19import ftdii2c
Todd Brochdbb09982011-10-02 07:14:26 -070020import ftdi_common
Todd Broch47c43f42011-05-26 15:11:31 -070021import ftdiuart
Todd Broche505b8d2011-03-21 18:19:54 -070022
23MAX_I2C_CLOCK_HZ = 100000
24
Todd Brochdbb09982011-10-02 07:14:26 -070025
Todd Broche505b8d2011-03-21 18:19:54 -070026class ServodError(Exception):
27 """Exception class for servod."""
28
29class Servod(object):
30 """Main class for Servo debug/controller Daemon."""
Simran Basia9f41032012-05-11 14:21:58 -070031 _USB_DETECTION_DELAY = 10
32 _HTTP_PREFIX = "http://"
33
Todd Brochdbb09982011-10-02 07:14:26 -070034 def __init__(self, config, vendor, product, serialname=None, interfaces=None):
Todd Broche505b8d2011-03-21 18:19:54 -070035 """Servod constructor.
36
37 Args:
38 config: instance of SystemConfig containing all controls for
39 particular Servod invocation
40 vendor: usb vendor id of FTDI device
41 product: usb product id of FTDI device
Todd Brochad034442011-05-25 15:05:29 -070042 serialname: string of device serialname/number as defined in FTDI eeprom.
Todd Brochdbb09982011-10-02 07:14:26 -070043 interfaces: list of strings of interface types the server will instantiate
44
45 Raises:
46 ServodError: if unable to locate init method for particular interface
Todd Broche505b8d2011-03-21 18:19:54 -070047 """
48 self._logger = logging.getLogger("Servod")
49 self._logger.debug("")
50 self._vendor = vendor
51 self._product = product
Todd Brochad034442011-05-25 15:05:29 -070052 self._serialname = serialname
Todd Broche505b8d2011-03-21 18:19:54 -070053 self._syscfg = config
54 # list of objects (Fi2c, Fgpio) to physical interfaces (gpio, i2c) that ftdi
55 # interfaces are mapped to
56 self._interface_list = []
57 # Dict of Dict to map control name, function name to to tuple (params, drv)
58 # Ex) _drv_dict[name]['get'] = (params, drv)
59 self._drv_dict = {}
60
Todd Brochdbb09982011-10-02 07:14:26 -070061 # Note, interface i is (i - 1) in list
62 if not interfaces:
63 interfaces = ftdi_common.INTERFACE_DEFAULTS[vendor][product]
64
65 for i, name in enumerate(interfaces):
Todd Broch8a77a992012-01-27 09:46:08 -080066 # servos with multiple FTDI are guaranteed to have contiguous USB PIDs
67 if i and ((i % ftdi_common.MAX_FTDI_INTERFACES_PER_DEVICE) == 0):
68 self._product += 1
69 self._logger.info("Changing to next FTDI part @ pid = 0x%04x",
70 self._product)
71
Todd Brochdbb09982011-10-02 07:14:26 -070072 self._logger.info("Initializing FTDI interface %d to %s", i + 1, name)
73 try:
74 func = getattr(self, '_init_%s' % name)
75 except AttributeError:
76 raise ServodError("Unable to locate init for interface %s" % name)
Todd Brocha9c74692012-01-24 22:54:22 -080077 result = func((i % ftdi_common.MAX_FTDI_INTERFACES_PER_DEVICE) + 1)
Todd Broch888da782011-10-07 14:29:09 -070078 if isinstance(result, tuple):
79 self._interface_list.extend(result)
80 else:
81 self._interface_list.append(result)
Todd Broche505b8d2011-03-21 18:19:54 -070082
Todd Broch3ec8df02012-11-20 10:53:03 -080083 def __del__(self):
84 """Servod deconstructor."""
85 for interface in self._interface_list:
86 del(interface)
87
Todd Brochb3048492012-01-15 21:52:41 -080088 def _init_dummy(self, interface):
89 """Initialize dummy interface.
90
91 Dummy interface is just a mechanism to reserve that interface for non servod
92 interaction. Typically the interface will be managed by external
93 third-party tools like openOCD or urjtag for JTAG or flashrom for SPI
94 interfaces.
95
96 TODO(tbroch): Investigate merits of incorporating these third-party
97 interfaces into servod or creating a communication channel between them
98
99 Returns: None
100 """
101 return None
102
Todd Broche505b8d2011-03-21 18:19:54 -0700103 def _init_gpio(self, interface):
104 """Initialize gpio driver interface and open for use.
105
106 Args:
107 interface: interface number of FTDI device to use.
108
109 Returns:
110 Instance object of interface.
Todd Broch6de9dc62012-04-09 15:23:53 -0700111
112 Raises:
113 ServodError: If init fails
Todd Broche505b8d2011-03-21 18:19:54 -0700114 """
Todd Brochad034442011-05-25 15:05:29 -0700115 fobj = ftdigpio.Fgpio(self._vendor, self._product, interface,
116 self._serialname)
Todd Broch6de9dc62012-04-09 15:23:53 -0700117 try:
118 fobj.open()
119 except ftdigpio.FgpioError as e:
120 raise ServodError('Opening gpio interface. %s ( %d )' % (e.msg, e.value))
121
Todd Broche505b8d2011-03-21 18:19:54 -0700122 return fobj
123
124 def _init_i2c(self, interface):
125 """Initialize i2c interface and open for use.
126
127 Args:
128 interface: interface number of FTDI device to use
129
130 Returns:
131 Instance object of interface
Todd Broch6de9dc62012-04-09 15:23:53 -0700132
133 Raises:
134 ServodError: If init fails
Todd Broche505b8d2011-03-21 18:19:54 -0700135 """
Todd Brochad034442011-05-25 15:05:29 -0700136 fobj = ftdii2c.Fi2c(self._vendor, self._product, interface,
137 self._serialname)
Todd Broch6de9dc62012-04-09 15:23:53 -0700138 try:
139 fobj.open()
140 except ftdii2c.Fi2cError as e:
141 raise ServodError('Opening i2c interface. %s ( %d )' % (e.msg, e.value))
142
Todd Broche505b8d2011-03-21 18:19:54 -0700143 # Set the frequency of operation of the i2c bus.
144 # TODO(tbroch) make configureable
145 fobj.setclock(MAX_I2C_CLOCK_HZ)
Todd Broch6de9dc62012-04-09 15:23:53 -0700146
Todd Broche505b8d2011-03-21 18:19:54 -0700147 return fobj
148
Todd Broch47c43f42011-05-26 15:11:31 -0700149 def _init_uart(self, interface):
150 """Initialize uart inteface and open for use
151
152 Note, the uart runs in a separate thread (pthreads). Users wishing to
153 interact with it will query control for the pty's pathname and connect
154 with there favorite console program. For example:
155 cu -l /dev/pts/22
156
157 Args:
158 interface: interface number of FTDI device to use
159
160 Returns:
161 Instance object of interface
Todd Broch6de9dc62012-04-09 15:23:53 -0700162
163 Raises:
164 ServodError: If init fails
Todd Broch47c43f42011-05-26 15:11:31 -0700165 """
Jeremy Thorpe9e110062012-10-25 10:40:00 -0700166 fobj = ftdiuart.Fuart(self._vendor, self._product, interface,
167 self._serialname)
Todd Broch6de9dc62012-04-09 15:23:53 -0700168 try:
169 fobj.run()
170 except ftdiuart.FuartError as e:
171 raise ServodError('Running uart interface. %s ( %d )' % (e.msg, e.value))
172
Todd Broch47c43f42011-05-26 15:11:31 -0700173 self._logger.info("%s" % fobj.get_pty())
174 return fobj
175
Todd Broch888da782011-10-07 14:29:09 -0700176 def _init_gpiouart(self, interface):
177 """Initialize special gpio + uart interface and open for use
178
179 Note, the uart runs in a separate thread (pthreads). Users wishing to
180 interact with it will query control for the pty's pathname and connect
181 with there favorite console program. For example:
182 cu -l /dev/pts/22
183
184 Args:
185 interface: interface number of FTDI device to use
186
187 Returns:
188 Instance objects of interface
Todd Broch6de9dc62012-04-09 15:23:53 -0700189
190 Raises:
191 ServodError: If init fails
Todd Broch888da782011-10-07 14:29:09 -0700192 """
193 fgpio = self._init_gpio(interface)
Jeremy Thorpe9e110062012-10-25 10:40:00 -0700194 fuart = ftdiuart.Fuart(self._vendor, self._product, interface,
195 self._serialname, fgpio._fc)
Todd Broch6de9dc62012-04-09 15:23:53 -0700196 try:
197 fuart.run()
198 except ftdiuart.FuartError as e:
199 raise ServodError('Running uart interface. %s ( %d )' % (e.msg, e.value))
200
Todd Broch888da782011-10-07 14:29:09 -0700201 self._logger.info("uart pty: %s" % fuart.get_pty())
202 return fgpio, fuart
203
Tom Wai-Hong Tam28f0a5f2012-08-21 12:49:57 +0800204 def _camel_case(self, string):
205 output = ''
206 for s in string.split('_'):
207 if output:
208 output += s.capitalize()
209 else:
210 output = s
211 return output
212
Todd Broche505b8d2011-03-21 18:19:54 -0700213 def _get_param_drv(self, control_name, is_get=True):
214 """Get access to driver for a given control.
215
216 Note, some controls have different parameter dictionaries for 'getting' the
217 control's value versus 'setting' it. Boolean is_get distinguishes which is
218 being requested.
219
220 Args:
221 control_name: string name of control
222 is_get: boolean to determine
223
224 Returns:
225 tuple (param, drv) where:
226 param: param dictionary for control
227 drv: instance object of driver for particular control
228
229 Raises:
230 ServodError: Error occurred while examining params dict
231 """
232 self._logger.debug("")
233 # if already setup just return tuple from driver dict
234 if control_name in self._drv_dict:
235 if is_get and ('get' in self._drv_dict[control_name]):
236 return self._drv_dict[control_name]['get']
237 if not is_get and ('set' in self._drv_dict[control_name]):
238 return self._drv_dict[control_name]['set']
239
240 params = self._syscfg.lookup_control_params(control_name, is_get)
241 if 'drv' not in params:
242 self._logger.error("Unable to determine driver for %s" % control_name)
243 raise ServodError("'drv' key not found in params dict")
244 if 'interface' not in params:
245 self._logger.error("Unable to determine interface for %s" %
246 control_name)
247
248 raise ServodError("'interface' key not found in params dict")
249 index = int(params['interface']) - 1
250 interface = self._interface_list[index]
251 servo_pkg = imp.load_module('servo', *imp.find_module('servo'))
252 drv_pkg = imp.load_module('drv',
253 *imp.find_module('drv', servo_pkg.__path__))
254 drv_name = params['drv']
255 drv_module = getattr(drv_pkg, drv_name)
Tom Wai-Hong Tam28f0a5f2012-08-21 12:49:57 +0800256 drv_class = getattr(drv_module, self._camel_case(drv_name))
Todd Broche505b8d2011-03-21 18:19:54 -0700257 drv = drv_class(interface, params)
258 if control_name not in self._drv_dict:
259 self._drv_dict[control_name] = {}
260 if is_get:
261 self._drv_dict[control_name]['get'] = (params, drv)
262 else:
263 self._drv_dict[control_name]['set'] = (params, drv)
264 return (params, drv)
265
266 def doc_all(self):
267 """Return all documenation for controls.
268
269 Returns:
270 string of <doc> text in config file (xml) and the params dictionary for
271 all controls.
272
273 For example:
274 warm_reset :: Reset the device warmly
275 ------------------------> {'interface': '1', 'map': 'onoff_i', ... }
276 """
277 return self._syscfg.display_config()
278
279 def doc(self, name):
280 """Retreive doc string in system config file for given control name.
281
282 Args:
283 name: name string of control to get doc string
284
285 Returns:
286 doc string of name
287
288 Raises:
289 NameError: if fails to locate control
290 """
291 self._logger.debug("name(%s)" % (name))
292 if self._syscfg.is_control(name):
293 return self._syscfg.get_control_docstring(name)
294 else:
295 raise NameError("No control %s" %name)
296
Simran Basia9f41032012-05-11 14:21:58 -0700297 def _get_usb_port_set(self):
298 """Gets a set of USB disks currently connected to the system
299
300 Returns:
301 A set of USB disk paths.
302 """
303 usb_set = fnmatch.filter(os.listdir("/dev/"), "sd[a-z]")
304 return set(["/dev/" + dev for dev in usb_set])
305
306 def _probe_host_usb_dev(self):
307 """Probe the USB disk device plugged in the servo from the host side.
308
309 Method can fail by:
310 1) Having multiple servos connected and returning incorrect /dev/sdX of
311 another servo.
312 2) Finding multiple /dev/sdX and returning None.
313
314 Returns:
315 USB disk path if one and only one USB disk path is found, otherwise None.
316 """
317 original_value = self.get("usb_mux_sel1")
318 # Make the host unable to see the USB disk.
319 if original_value != "dut_sees_usbkey":
320 self.set("usb_mux_sel1", "dut_sees_usbkey")
321 time.sleep(self._USB_DETECTION_DELAY)
322
323 no_usb_set = self._get_usb_port_set()
324 # Make the host able to see the USB disk.
325 self.set("usb_mux_sel1", "servo_sees_usbkey")
326 time.sleep(self._USB_DETECTION_DELAY)
327
328 has_usb_set = self._get_usb_port_set()
329 # Back to its original value.
330 if original_value != "servo_sees_usbkey":
331 self.set("usb_mux_sel1", original_value)
332 time.sleep(self._USB_DETECTION_DELAY)
333 # Subtract the two sets to find the usb device.
334 diff_set = has_usb_set - no_usb_set
335 if len(diff_set) == 1:
336 return diff_set.pop()
337 else:
338 return None
339
340 def download_image_to_usb(self, image_path):
341 """Download image and save to the USB device found by probe_host_usb_dev.
342 If the image_path is a URL, it will download this url to the USB path;
343 otherwise it will simply copy the image_path's contents to the USB path.
344
345 Args:
346 image_path: path or url to the recovery image.
347
348 Returns:
349 True|False: True if process completed successfully, False if error
350 occurred.
351 Can't return None because XMLRPC doesn't allow it. PTAL at tbroch's
352 comment at the end of set().
353 """
354 self._logger.debug("image_path(%s)" % image_path)
355 self._logger.debug("Detecting USB stick device...")
356 usb_dev = self._probe_host_usb_dev()
357 if not usb_dev:
358 self._logger.error("No usb device connected to servo")
359 return False
360
361 try:
362 if image_path.startswith(self._HTTP_PREFIX):
363 self._logger.debug("Image path is a URL, downloading image")
364 urllib.urlretrieve(image_path, usb_dev)
365 else:
366 shutil.copyfile(image_path, usb_dev)
367 except IOError as e:
368 self._logger.error("Failed to transfer image to USB device: %s ( %d ) ",
369 e.strerror, e.errno)
370 return False
371 except urllib.ContentTooShortError:
372 self._logger.error("Failed to download URL: %s to USB device: %s",
373 image_path, usb_dev)
374 return False
375 except BaseException as e:
376 self._logger.error("Unexpected exception downloading %s to %s: %s",
377 image_path, usb_dev, str(e))
378 return False
379 return True
380
381 def make_image_noninteractive(self):
382 """Makes the recovery image noninteractive.
383
384 A noninteractive image will reboot automatically after installation
385 instead of waiting for the USB device to be removed to initiate a system
386 reboot.
387
388 Mounts partition 1 of the image stored on usb_dev and creates a file
389 called "non_interactive" so that the image will become noninteractive.
390
391 Returns:
392 True|False: True if process completed successfully, False if error
393 occurred.
394 """
395 result = True
396 usb_dev = self._probe_host_usb_dev()
397 if not usb_dev:
398 self._logger.error("No usb device connected to servo")
399 return False
400 # Create TempDirectory
401 tmpdir = tempfile.mkdtemp()
402 if tmpdir:
403 # Mount drive to tmpdir.
404 partition_1 = "%s1" % usb_dev
405 rc = subprocess.call(["mount", partition_1, tmpdir])
406 if rc == 0:
407 # Create file 'non_interactive'
408 non_interactive_file = os.path.join(tmpdir, "non_interactive")
409 try:
410 open(non_interactive_file, "w").close()
411 except IOError as e:
412 self._logger.error("Failed to create file %s : %s ( %d )",
413 non_interactive_file, e.strerror, e.errno)
414 result = False
415 except BaseException as e:
416 self._logger.error("Unexpected Exception creating file %s : %s",
417 non_interactive_file, str(e))
418 result = False
419 # Unmount drive regardless if file creation worked or not.
420 rc = subprocess.call(["umount", partition_1])
421 if rc != 0:
422 self._logger.error("Failed to unmount USB Device")
423 result = False
424 else:
425 self._logger.error("Failed to mount USB Device")
426 result = False
427
428 # Delete tmpdir. May throw exception if 'umount' failed.
429 try:
430 os.rmdir(tmpdir)
431 except OSError as e:
432 self._logger.error("Failed to remove temp directory %s : %s",
433 tmpdir, str(e))
434 return False
435 except BaseException as e:
436 self._logger.error("Unexpected Exception removing tempdir %s : %s",
437 tmpdir, str(e))
438 return False
439 else:
440 self._logger.error("Failed to create temp directory.")
441 return False
442 return result
443
Todd Broche505b8d2011-03-21 18:19:54 -0700444 def get(self, name):
445 """Get control value.
446
447 Args:
448 name: name string of control
449
450 Returns:
451 Response from calling drv get method. Value is reformatted based on
452 control's dictionary parameters
453
454 Raises:
455 HwDriverError: Error occurred while using drv
456 """
457 self._logger.debug("name(%s)" % (name))
458 (param, drv) = self._get_param_drv(name)
459 try:
460 val = drv.get()
461 rd_val = self._syscfg.reformat_val(param, val)
Todd Brochb042e7a2011-12-14 17:41:36 -0800462 self._logger.debug("%s = %s" % (name, rd_val))
Todd Broche505b8d2011-03-21 18:19:54 -0700463 return rd_val
Todd Brochfbc499d2011-06-16 16:09:58 -0700464 except AttributeError, error:
465 self._logger.error("Getting %s: %s" % (name, error))
466 raise
Vic Yangbe6cf262012-09-10 10:40:56 +0800467 except HwDriverError:
Todd Broche505b8d2011-03-21 18:19:54 -0700468 self._logger.error("Getting %s" % (name))
469 raise
Todd Brochd6061672012-05-11 15:52:47 -0700470
Todd Broche505b8d2011-03-21 18:19:54 -0700471 def get_all(self, verbose):
472 """Get all controls values.
473
474 Args:
475 verbose: Boolean on whether to return doc info as well
476
477 Returns:
478 string creating from trying to get all values of all controls. In case of
479 error attempting access to control, response is 'ERR'.
480 """
Vadim Bendeburyb07944c2013-01-16 10:47:10 -0800481 rsp = []
Todd Broche505b8d2011-03-21 18:19:54 -0700482 for name in self._syscfg.syscfg_dict['control']:
483 self._logger.debug("name = %s" %name)
484 try:
485 value = self.get(name)
486 except Exception:
487 value = "ERR"
488 pass
489 if verbose:
Vadim Bendeburyb07944c2013-01-16 10:47:10 -0800490 rsp.append("GET %s = %s :: %s" % (name, value, self.doc(name)))
Todd Broche505b8d2011-03-21 18:19:54 -0700491 else:
Vadim Bendeburyb07944c2013-01-16 10:47:10 -0800492 rsp.append("%s:%s" % (name, value))
493 return '\n'.join(sorted(rsp))
Todd Broche505b8d2011-03-21 18:19:54 -0700494
495 def set(self, name, wr_val_str):
496 """Set control.
497
498 Args:
499 name: name string of control
500 wr_val_str: value string to write. Can be integer, float or a
501 alpha-numerical that is mapped to a integer or float.
502
503 Raises:
504 HwDriverError: Error occurred while using driver
505 """
Todd Broch7a91c252012-02-03 12:37:45 -0800506 if name == 'sleep':
507 time.sleep(float(wr_val_str))
508 return True
509
Todd Broche505b8d2011-03-21 18:19:54 -0700510 self._logger.debug("name(%s) wr_val(%s)" % (name, wr_val_str))
511 (params, drv) = self._get_param_drv(name, False)
512 wr_val = self._syscfg.resolve_val(params, wr_val_str)
513 try:
514 drv.set(wr_val)
Vic Yangbe6cf262012-09-10 10:40:56 +0800515 except HwDriverError:
Todd Broche505b8d2011-03-21 18:19:54 -0700516 self._logger.error("Setting %s -> %s" % (name, wr_val_str))
517 raise
518 # TODO(tbroch) Figure out why despite allow_none=True for both xmlrpc server
519 # & client I still have to return something to appease the
520 # marshall/unmarshall
521 return True
522
Todd Brochd6061672012-05-11 15:52:47 -0700523 def hwinit(self, verbose=False):
524 """Initialize all controls.
525
526 These values are part of the system config XML files of the form
527 init=<value>. This command should be used by clients wishing to return the
528 servo and DUT its connected to a known good/safe state.
529
530 Args:
531 verbose: boolean, if True prints info about control initialized.
532 Otherwise prints nothing.
533 """
Todd Broch3ec8df02012-11-20 10:53:03 -0800534 success = True
Todd Brochd9acf0a2012-12-05 13:43:06 -0800535 for control_name, value in self._syscfg.hwinit:
Todd Broch3ec8df02012-11-20 10:53:03 -0800536 try:
537 self.set(control_name, value)
538 except Exception as e:
539 success = False
540 self._logger.error("Problem initializing %s -> %s :: %s",
541 control_name, value, str(e))
Todd Brochd6061672012-05-11 15:52:47 -0700542 if verbose:
543 self._logger.info('Initialized %s to %s', control_name, value)
Todd Broch3ec8df02012-11-20 10:53:03 -0800544
545 return success
Todd Brochd6061672012-05-11 15:52:47 -0700546
Todd Broche505b8d2011-03-21 18:19:54 -0700547 def echo(self, echo):
548 """Dummy echo function for testing/examples.
549
550 Args:
551 echo: string to echo back to client
552 """
553 self._logger.debug("echo(%s)" % (echo))
554 return "ECH0ING: %s" % (echo)
555
Todd Brochdbb09982011-10-02 07:14:26 -0700556
Todd Broche505b8d2011-03-21 18:19:54 -0700557def test():
558 """Integration testing.
559
560 TODO(tbroch) Enhance integration test and add unittest (see mox)
561 """
562 logging.basicConfig(level=logging.DEBUG,
563 format="%(asctime)s - %(name)s - " +
564 "%(levelname)s - %(message)s")
565 # configure server & listen
566 servod_obj = Servod(1)
567 # 4 == number of interfaces on a FT4232H device
568 for i in xrange(4):
569 if i == 1:
570 # its an i2c interface ... see __init__ for details and TODO to make
571 # this configureable
572 servod_obj._interface_list[i].wr_rd(0x21, [0], 1)
573 else:
574 # its a gpio interface
575 servod_obj._interface_list[i].wr_rd(0)
576
577 server = SimpleXMLRPCServer.SimpleXMLRPCServer(("localhost", 9999),
578 allow_none=True)
579 server.register_introspection_functions()
580 server.register_multicall_functions()
581 server.register_instance(servod_obj)
582 logging.info("Listening on localhost port 9999")
583 server.serve_forever()
584
585if __name__ == "__main__":
586 test()
587
588 # simple client transaction would look like
589 """
590 remote_uri = 'http://localhost:9999'
591 client = xmlrpclib.ServerProxy(remote_uri, verbose=False)
592 send_str = "Hello_there"
593 print "Sent " + send_str + ", Recv " + client.echo(send_str)
594 """