blob: 271ce182b4721e67aa0fa4b6cf2ba9aaef41abd1 [file] [log] [blame]
Charlie Mooneybbc05f52015-03-24 13:36:22 -07001# Copyright (c) 2014 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
5"""This module launches the cherrypy server for webplot and sets up
6web sockets to handle the messages between the clients and the server.
7"""
8
9import argparse
10import base64
11import json
12import logging
13import os
14import re
15import subprocess
Charlie Mooneyc68f9c32015-04-16 15:23:22 -070016import time
Charlie Mooneybbc05f52015-03-24 13:36:22 -070017import threading
18
19import cherrypy
20
Charlie Mooneybbc05f52015-03-24 13:36:22 -070021from ws4py import configure_logger
22from ws4py.messaging import TextMessage
23from ws4py.server.cherrypyserver import WebSocketPlugin, WebSocketTool
24from ws4py.websocket import WebSocket
25
Charlie Mooney04b41532015-04-02 12:41:37 -070026from remote import ChromeOSTouchDevice, AndroidTouchDevice
Charlie Mooneybbc05f52015-03-24 13:36:22 -070027
28
29# The WebSocket connection state object.
30state = None
31
32# The touch events are saved in this file as default.
33SAVED_FILE = '/tmp/webplot.dat'
34SAVED_IMAGE = '/tmp/webplot.png'
35
36
Joseph Hwang4782a042015-04-08 17:15:50 +080037def SimpleSystem(cmd):
38 """Execute a system command."""
39 ret = subprocess.call(cmd, shell=True)
40 if ret:
41 logging.warning('Command (%s) failed (ret=%s).', cmd, ret)
42 return ret
43
44
45def SimpleSystemOutput(cmd):
46 """Execute a system command and get its output."""
47 try:
48 proc = subprocess.Popen(
49 cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
50 stdout, _ = proc.communicate()
51 except Exception, e:
52 logging.warning('Command (%s) failed (%s).', cmd, e)
53 else:
54 return None if proc.returncode else stdout.strip()
55
56
57def IsDestinationPortEnabled(port):
58 """Check if the destination port is enabled in iptables.
59
60 If port 8000 is enabled, it looks like
61 ACCEPT tcp -- 0.0.0.0/0 0.0.0.0/0 ctstate NEW tcp dpt:8000
62 """
63 pattern = re.compile('ACCEPT\s+tcp.+\s+ctstate\s+NEW\s+tcp\s+dpt:%d' % port)
64 rules = SimpleSystemOutput('sudo iptables -L INPUT -n --line-number')
65 for rule in rules.splitlines():
66 if pattern.search(rule):
67 return True
68 return False
69
70
71def EnableDestinationPort(port):
72 """Enable the destination port for input traffic in iptables."""
73 if IsDestinationPortEnabled(port):
74 cherrypy.log('Port %d has been already enabled in iptables.' % port)
75 else:
76 cherrypy.log('To enable port %d in iptables.' % port)
77 cmd = ('sudo iptables -A INPUT -p tcp -m conntrack --ctstate NEW '
78 '--dport %d -j ACCEPT' % port)
79 if SimpleSystem(cmd) != 0:
80 raise Error('Failed to enable port in iptables: %d.' % port)
81
82
Charlie Mooneybbc05f52015-03-24 13:36:22 -070083def InterruptHandler():
84 """An interrupt handler for both SIGINT and SIGTERM
85
86 The stop procedure triggered is as follows:
87 1. This handler sends a 'quit' message to the listening client.
88 2. The client sends the canvas image back to the server in its quit message.
89 3. WebplotWSHandler.received_message() saves the image.
90 4. WebplotWSHandler.received_message() handles the 'quit' message.
91 The cherrypy engine exits if this is the last client.
92 """
93 cherrypy.log('Cherrypy engine is sending quit message to clients.')
Joseph Hwang95bf52a2015-04-14 13:09:39 +080094 state.QuitAndShutdown()
95
96
97def _IOError(e, filename):
98 err_msg = ['\n', '!' * 60, str(e),
99 'It is likely that %s is owned by root.' % filename,
100 'Please remove the file and then run webplot again.',
101 '!' * 60, '\n']
102 cherrypy.log('\n'.join(err_msg))
Charlie Mooneybbc05f52015-03-24 13:36:22 -0700103
Charlie Mooneyc68f9c32015-04-16 15:23:22 -0700104image_lock = threading.Event()
105image_string = ''
Charlie Mooneybbc05f52015-03-24 13:36:22 -0700106
107class WebplotWSHandler(WebSocket):
108 """The web socket handler for webplot."""
109
110 def opened(self):
111 """This method is called when the handler is opened."""
112 cherrypy.log('WS handler is opened!')
113
114 def received_message(self, msg):
115 """A callback for received message."""
Charlie Mooneybbc05f52015-03-24 13:36:22 -0700116 data = msg.data.split(':', 1)
117 mtype = data[0].lower()
118 content = data[1] if len(data) == 2 else None
Joseph Hwang0c1fa7d2015-04-09 17:01:45 +0800119
120 # Do not print the image data since it is too large.
121 if mtype != 'save':
122 cherrypy.log('Received message: %s' % str(msg.data))
123
Charlie Mooneybbc05f52015-03-24 13:36:22 -0700124 if mtype == 'quit':
125 # A shutdown message requested by the user.
Charlie Mooneybbc05f52015-03-24 13:36:22 -0700126 cherrypy.log('The user requests to shutdown the cherrypy server....')
127 state.DecCount()
128 elif mtype == 'save':
Charlie Mooneyb476e892015-04-02 13:25:49 -0700129 cherrypy.log('All data saved to "%s"' % SAVED_FILE)
130 self.SaveImage(content, SAVED_IMAGE)
131 cherrypy.log('Plot image saved to "%s"' % SAVED_IMAGE)
Charlie Mooneybbc05f52015-03-24 13:36:22 -0700132 else:
133 cherrypy.log('Unknown message type: %s' % mtype)
134
135 def closed(self, code, reason="A client left the room."):
136 """This method is called when the handler is closed."""
137 cherrypy.log('A client requests to close WS.')
138 cherrypy.engine.publish('websocket-broadcast', TextMessage(reason))
139
140 @staticmethod
141 def SaveImage(image_data, image_file):
142 """Decoded the base64 image data and save it in the file."""
Charlie Mooneyc68f9c32015-04-16 15:23:22 -0700143 global image_string
144 image_string = base64.b64decode(image_data)
145 image_lock.set()
Joseph Hwangafc092c2015-04-21 11:32:45 +0800146 try:
147 with open(image_file, 'w') as f:
148 f.write(image_string)
149 except IOError as e:
150 _IOError(e, image_file)
151 state.QuitAndShutdown()
Charlie Mooneybbc05f52015-03-24 13:36:22 -0700152
153
Charlie Mooneybbc05f52015-03-24 13:36:22 -0700154class ConnectionState(object):
155 """A ws connection state object for shutting down the cherrypy server.
156
157 It shuts down the cherrypy server when the count is down to 0 and is not
158 increased before the shutdown_timer expires.
159
160 Note that when a page refreshes, it closes the WS connection first and
161 then re-connects immediately. This is why we would like to wait a while
162 before actually shutting down the server.
163 """
164 TIMEOUT = 1.0
165
166 def __init__(self):
167 self.count = 0;
168 self.lock = threading.Lock()
169 self.shutdown_timer = None
Joseph Hwang95bf52a2015-04-14 13:09:39 +0800170 self.quit_flag = False
Charlie Mooneybbc05f52015-03-24 13:36:22 -0700171
172 def IncCount(self):
173 """Increase the connection count, and cancel the shutdown timer if exists.
174 """
175 self.lock.acquire()
176 self.count += 1;
177 cherrypy.log(' WS connection count: %d' % self.count)
178 if self.shutdown_timer:
179 self.shutdown_timer.cancel()
180 self.shutdown_timer = None
181 self.lock.release()
182
183 def DecCount(self):
184 """Decrease the connection count, and start a shutdown timer if no other
185 clients are connecting to the server.
186 """
187 self.lock.acquire()
188 self.count -= 1;
189 cherrypy.log(' WS connection count: %d' % self.count)
190 if self.count == 0:
191 self.shutdown_timer = threading.Timer(self.TIMEOUT, self.Shutdown)
192 self.shutdown_timer.start()
193 self.lock.release()
194
Joseph Hwang3f561d32015-04-09 16:33:56 +0800195 def ShutdownWhenNoConnections(self):
196 """Shutdown cherrypy server when there is no client connection."""
197 self.lock.acquire()
198 if self.count == 0 and self.shutdown_timer is None:
199 self.Shutdown()
200 self.lock.release()
201
Charlie Mooneybbc05f52015-03-24 13:36:22 -0700202 def Shutdown(self):
203 """Shutdown the cherrypy server."""
204 cherrypy.log('Shutdown timer expires. Cherrypy server for Webplot exits.')
205 cherrypy.engine.exit()
206
Joseph Hwang95bf52a2015-04-14 13:09:39 +0800207 def QuitAndShutdown(self):
208 """The server notifies clients to quit and then shuts down."""
209 if not self.quit_flag:
210 self.quit_flag = True
211 cherrypy.engine.publish('websocket-broadcast', TextMessage('quit'))
212 self.ShutdownWhenNoConnections()
213
Charlie Mooneybbc05f52015-03-24 13:36:22 -0700214
215class Root(object):
216 """A class to handle requests about docroot."""
217
218 def __init__(self, ip, port, touch_min_x, touch_max_x, touch_min_y,
219 touch_max_y, touch_min_pressure, touch_max_pressure):
220 self.ip = ip
221 self.port = port
222 self.touch_min_x = touch_min_x
223 self.touch_max_x = touch_max_x
224 self.touch_min_y = touch_min_y
225 self.touch_max_y = touch_max_y
226 self.touch_min_pressure = touch_min_pressure
227 self.touch_max_pressure = touch_max_pressure
228 self.scheme = 'ws'
229 cherrypy.log('Root address: (%s, %s)' % (ip, str(port)))
230 cherrypy.log('scheme: %s' % self.scheme)
231
232 @cherrypy.expose
233 def index(self):
234 """This is the default index.html page."""
235 websocket_dict = {
236 'websocketUrl': '%s://%s:%s/ws' % (self.scheme, self.ip, self.port),
237 'touchMinX': str(self.touch_min_x),
238 'touchMaxX': str(self.touch_max_x),
239 'touchMinY': str(self.touch_min_y),
240 'touchMaxY': str(self.touch_max_y),
241 'touchMinPressure': str(self.touch_min_pressure),
242 'touchMaxPressure': str(self.touch_max_pressure),
243 }
244 root_page = os.path.join(os.path.abspath(os.path.dirname(__file__)),
245 'webplot.html')
246 with open(root_page) as f:
247 return f.read() % websocket_dict
248
249 @cherrypy.expose
250 def ws(self):
251 """This handles the request to create a new web socket per client."""
252 cherrypy.log('A new client requesting for WS')
253 cherrypy.log('WS handler created: %s' % repr(cherrypy.request.ws_handler))
254 state.IncCount()
255
256
Joseph Hwang4782a042015-04-08 17:15:50 +0800257class Webplot(threading.Thread):
258 """The server handling the Plotting of finger traces.
259
260 Use case 1: embedding Webplot as a plotter in an application
261
262 # Instantiate a webplot server and starts the daemon.
263 plot = Webplot(server_addr, server_port, device)
264 plot.start()
265
266 # Repeatedly get a snapshot and add it for plotting.
267 while True:
268 # GetSnapshot() is essentially device.NextSnapshot()
269 snapshot = plot.GetSnapshot()
270 if not snapshot:
271 break
272 # Add the snapshot to the plotter for plotting.
273 plot.AddSnapshot(snapshot)
274
275 # Save a screen dump
276 plot.Save()
277
278 # Notify the browser to clear the screen.
279 plot.Clear()
280
281 # Notify both the browser and the cherrypy engine to quit.
282 plot.Quit()
Charlie Mooneybbc05f52015-03-24 13:36:22 -0700283
284
Joseph Hwang4782a042015-04-08 17:15:50 +0800285 Use case 2: using webplot standalone
Charlie Mooneybbc05f52015-03-24 13:36:22 -0700286
Joseph Hwang4782a042015-04-08 17:15:50 +0800287 # Instantiate a webplot server and starts the daemon.
288 plot = Webplot(server_addr, server_port, device)
289 plot.start()
Charlie Mooneybbc05f52015-03-24 13:36:22 -0700290
Joseph Hwang4782a042015-04-08 17:15:50 +0800291 # Get touch snapshots from the touch device and have clients plot them.
292 webplot.GetAndPlotSnapshots()
Charlie Mooneybbc05f52015-03-24 13:36:22 -0700293 """
Charlie Mooneybbc05f52015-03-24 13:36:22 -0700294
Joseph Hwange9cfd642015-04-20 16:00:33 +0800295 def __init__(self, server_addr, server_port, device, saved_file=SAVED_FILE,
296 logging=False):
Joseph Hwang4782a042015-04-08 17:15:50 +0800297 self._server_addr = server_addr
298 self._server_port = server_port
299 self._device = device
300 self._saved_file = saved_file
301 super(Webplot, self).__init__(name='webplot thread')
Charlie Mooneybbc05f52015-03-24 13:36:22 -0700302
Joseph Hwang4782a042015-04-08 17:15:50 +0800303 self.daemon = True
304 self._prev_tids = []
305
Joseph Hwange9cfd642015-04-20 16:00:33 +0800306 # The logging is turned off by default when imported as a module so that
307 # it does not mess up the screen.
308 if not logging:
309 cherrypy.log.screen = None
310
Joseph Hwang4782a042015-04-08 17:15:50 +0800311 # Allow input traffic in iptables.
312 EnableDestinationPort(self._server_port)
313
314 # Create a ws connection state object to wait for the condition to
315 # shutdown the whole process.
316 global state
317 state = ConnectionState()
318
319 cherrypy.config.update({
320 'server.socket_host': self._server_addr,
321 'server.socket_port': self._server_port,
322 })
323
324 WebSocketPlugin(cherrypy.engine).subscribe()
325 cherrypy.tools.websocket = WebSocketTool()
326
327 # If the cherrypy server exits for whatever reason, close the device
328 # for required cleanup. Otherwise, there might exist local/remote
329 # zombie processes.
Charlie Mooneyc68f9c32015-04-16 15:23:22 -0700330 cherrypy.engine.subscribe('exit', self._device.__del__)
Joseph Hwang4782a042015-04-08 17:15:50 +0800331
332 cherrypy.engine.signal_handler.handlers['SIGINT'] = InterruptHandler
333 cherrypy.engine.signal_handler.handlers['SIGTERM'] = InterruptHandler
334
335 def run(self):
336 """Start the cherrypy engine."""
Charlie Mooneyc68f9c32015-04-16 15:23:22 -0700337 x_min, x_max = self._device.RangeX()
338 y_min, y_max = self._device.RangeY()
339 p_min, p_max = self._device.RangeP()
Joseph Hwang4782a042015-04-08 17:15:50 +0800340
341 cherrypy.quickstart(
342 Root(self._server_addr, self._server_port,
343 x_min, x_max, y_min, y_max, p_min, p_max),
344 '',
345 config={
346 '/': {
347 'tools.staticdir.root':
348 os.path.abspath(os.path.dirname(__file__)),
349 'tools.staticdir.on': True,
350 'tools.staticdir.dir': '',
351 },
352 '/ws': {
353 'tools.websocket.on': True,
354 'tools.websocket.handler_cls': WebplotWSHandler,
355 },
356 }
357 )
358
359 def _ConvertNamedtupleToDict(self, snapshot):
360 """Convert namedtuples to ordinary dictionaries and add leaving slots.
361
362 This is to make a snapshot json serializable. Otherwise, the namedtuples
363 would be transmitted as arrays which is less readable.
364
365 A snapshot looks like
366 MtSnapshot(
367 syn_time=1420524008.368854,
368 button_pressed=False,
369 fingers=[
370 MtFinger(tid=162, slot=0, syn_time=1420524008.368854, x=524,
371 y=231, pressure=45),
372 MtFinger(tid=163, slot=1, syn_time=1420524008.368854, x=677,
373 y=135, pressure=57)
374 ]
375 )
376
377 Note:
378 1. that there are two levels of namedtuples to convert.
379 2. The leaving slots are used to notify javascript that a finger is leaving
380 so that the corresponding finger color could be released for reuse.
381 """
382 # Convert MtSnapshot.
383 converted = dict(snapshot.__dict__.items())
384
385 # Convert MtFinger.
386 converted['fingers'] = [dict(finger.__dict__.items())
387 for finger in converted['fingers']]
388 converted['raw_events'] = [str(event) for event in converted['raw_events']]
389
390 # Add leaving fingers to notify js for reclaiming the finger colors.
391 curr_tids = [finger['tid'] for finger in converted['fingers']]
392 for tid in set(self._prev_tids) - set(curr_tids):
393 leaving_finger = {'tid': tid, 'leaving': True}
394 converted['fingers'].append(leaving_finger)
395 self._prev_tids = curr_tids
396
Joseph Hwang02e829c2015-04-13 17:09:07 +0800397 # Convert raw events from a list of classes to a list of its strings
398 # so that the raw_events is serializable.
399 converted['raw_events'] = [str(event) for event in converted['raw_events']]
400
Joseph Hwang4782a042015-04-08 17:15:50 +0800401 return converted
402
403 def GetSnapshot(self):
404 """Get a snapshot from the touch device."""
Charlie Mooneyc68f9c32015-04-16 15:23:22 -0700405 return self._device.NextSnapshot()
Joseph Hwang4782a042015-04-08 17:15:50 +0800406
407 def AddSnapshot(self, snapshot):
408 """Convert the snapshot to a proper format and publish it to clients."""
409 snapshot = self._ConvertNamedtupleToDict(snapshot)
410 cherrypy.engine.publish('websocket-broadcast', json.dumps(snapshot))
Joseph Hwang02e829c2015-04-13 17:09:07 +0800411 return snapshot
Joseph Hwang4782a042015-04-08 17:15:50 +0800412
413 def GetAndPlotSnapshots(self):
414 """Get and plot snapshots."""
415 cherrypy.log('Start getting the live stream snapshots....')
Joseph Hwang95bf52a2015-04-14 13:09:39 +0800416 try:
417 with open(self._saved_file, 'w') as f:
418 while True:
419 try:
420 snapshot = self.GetSnapshot()
421 if not snapshot:
422 cherrypy.log('webplot is terminated.')
423 break
424 converted_snapshot = self.AddSnapshot(snapshot)
425 f.write('\n'.join(converted_snapshot['raw_events']) + '\n')
426 f.flush()
427 except KeyboardInterrupt:
428 cherrypy.log('Keyboard Interrupt accepted')
429 cherrypy.log('webplot is being terminated...')
430 state.QuitAndShutdown()
431 except IOError as e:
432 _IOError(e, self._saved_file)
433 state.QuitAndShutdown()
Joseph Hwang4782a042015-04-08 17:15:50 +0800434
435 def Publish(self, msg):
436 """Publish a message to clients."""
437 cherrypy.engine.publish('websocket-broadcast', TextMessage(msg))
438
439 def Clear(self):
440 """Notify clients to clear the display."""
441 self.Publish('clear')
442
443 def Quit(self):
444 """Notify clients to quit.
445
446 Note that the cherrypy engine would quit accordingly.
447 """
Joseph Hwang95bf52a2015-04-14 13:09:39 +0800448 state.QuitAndShutdown()
Joseph Hwang4782a042015-04-08 17:15:50 +0800449
Charlie Mooneyc68f9c32015-04-16 15:23:22 -0700450 def Save(self, wait_for_image=False):
451 """Notify clients to save the screen, then wait for the file to appear
452 on disk and return it.
453 """
454 global image_lock
455 global image_string
456
457 # Trigger a save action
Joseph Hwang4782a042015-04-08 17:15:50 +0800458 self.Publish('save')
459
Charlie Mooneyc68f9c32015-04-16 15:23:22 -0700460 # Block until the server has completed saving it to disk
461 image_lock.wait()
462 image_lock.clear()
463 return image_string
464
Joseph Hwang4782a042015-04-08 17:15:50 +0800465 def Url(self):
466 """The url the server is serving at."""
467 return 'http://%s:%d' % (self._server_addr, self._server_port)
Charlie Mooneybbc05f52015-03-24 13:36:22 -0700468
469
Joseph Hwanga1c84782015-04-14 15:07:00 +0800470def _CheckLegalUser():
471 """If this program is run in chroot, it should not be run as root for security
472 reason.
473 """
474 if os.path.exists('/etc/cros_chroot_version') and os.getuid() == 0:
475 print ('You should run webplot in chroot as a regular user '
476 'instead of as root.\n')
477 exit(1)
478
479
Charlie Mooneybbc05f52015-03-24 13:36:22 -0700480def _ParseArguments():
481 """Parse the command line options."""
482 parser = argparse.ArgumentParser(description='Webplot Server')
Charlie Mooney8026f2a2015-04-02 13:01:28 -0700483 parser.add_argument('-d', '--dut_addr', default=None,
Charlie Mooneybbc05f52015-03-24 13:36:22 -0700484 help='the address of the dut')
Joseph Hwang59db4412015-04-09 12:43:16 +0800485
486 # Make an exclusive group to make the webplot.py command option
487 # consistent with the webplot.sh script command option.
488 # What is desired:
489 # When no command option specified in webplot.sh/webplot.py: grab is True
490 # When '--grab' option specified in webplot.sh/webplot.py: grab is True
491 # When '--nograb' option specified in webplot.sh/webplot.py: grab is False
492 grab_group = parser.add_mutually_exclusive_group()
493 grab_group.add_argument('--grab', help='grab the device exclusively',
494 action='store_true')
495 grab_group.add_argument('--nograb', help='do not grab the device',
496 action='store_true')
497
Charlie Mooneybbc05f52015-03-24 13:36:22 -0700498 parser.add_argument('--is_touchscreen', help='the DUT is touchscreen',
499 action='store_true')
Joseph Hwang59db4412015-04-09 12:43:16 +0800500 parser.add_argument('-p', '--server_port', default=80, type=int,
501 help='the port the web server to listen to (default: 80)')
502 parser.add_argument('-s', '--server_addr', default='localhost',
503 help='the address the webplot http server listens to')
504 parser.add_argument('-t', '--dut_type', default='chromeos', type=str.lower,
Charlie Mooneybbc05f52015-03-24 13:36:22 -0700505 help='dut type: chromeos, android')
506 args = parser.parse_args()
Joseph Hwang59db4412015-04-09 12:43:16 +0800507
508 args.grab = not args.nograb
509
Charlie Mooneybbc05f52015-03-24 13:36:22 -0700510 return args
511
512
513def Main():
514 """The main function to launch webplot service."""
Joseph Hwanga1c84782015-04-14 15:07:00 +0800515 _CheckLegalUser()
516
Joseph Hwange9cfd642015-04-20 16:00:33 +0800517 configure_logger(level=logging.ERROR)
Charlie Mooneybbc05f52015-03-24 13:36:22 -0700518 args = _ParseArguments()
519
520 print '\n' + '-' * 70
521 cherrypy.log('dut machine type: %s' % args.dut_type)
522 cherrypy.log('dut\'s touch device: %s' %
523 ('touchscreen' if args.is_touchscreen else 'touchpad'))
524 cherrypy.log('dut address: %s' % args.dut_addr)
525 cherrypy.log('web server address: %s' % args.server_addr)
526 cherrypy.log('web server port: %s' % args.server_port)
Joseph Hwang59db4412015-04-09 12:43:16 +0800527 cherrypy.log('grab the touch device: %s' % args.grab)
528 if args.dut_type == 'android' and args.grab:
529 cherrypy.log('Warning: the grab option is not supported on Android devices'
530 ' yet.')
Charlie Mooneybbc05f52015-03-24 13:36:22 -0700531 cherrypy.log('touch events are saved in %s' % SAVED_FILE)
532 print '-' * 70 + '\n\n'
533
534 if args.server_port == 80:
535 url = args.server_addr
536 else:
537 url = '%s:%d' % (args.server_addr, args.server_port)
538
539 msg = 'Type "%s" in browser %s to see finger traces.\n'
540 if args.server_addr == 'localhost':
541 which_machine = 'on the webplot server machine'
542 else:
543 which_machine = 'on any machine'
544
545 print '*' * 70
546 print msg % (url, which_machine)
547 print 'Press \'q\' on the browser to quit.'
548 print '*' * 70 + '\n\n'
549
Charlie Mooneybbc05f52015-03-24 13:36:22 -0700550 # Instantiate a touch device.
Charlie Mooneyc68f9c32015-04-16 15:23:22 -0700551 if args.dut_type == 'chromeos':
552 addr = args.dut_addr if args.dut_addr else '127.0.0.1'
553 device = ChromeOSTouchDevice(addr, args.is_touchscreen, grab=args.grab)
554 else:
555 device = AndroidTouchDevice(args.dut_addr, True)
556
Charlie Mooneybbc05f52015-03-24 13:36:22 -0700557
Joseph Hwang4782a042015-04-08 17:15:50 +0800558 # Instantiate a webplot server daemon and start it.
Joseph Hwange9cfd642015-04-20 16:00:33 +0800559 webplot = Webplot(args.server_addr, args.server_port, device, logging=True)
Joseph Hwang4782a042015-04-08 17:15:50 +0800560 webplot.start()
Charlie Mooneybbc05f52015-03-24 13:36:22 -0700561
Joseph Hwang4782a042015-04-08 17:15:50 +0800562 # Get touch snapshots from the touch device and have clients plot them.
563 webplot.GetAndPlotSnapshots()
Charlie Mooneybbc05f52015-03-24 13:36:22 -0700564
565
566if __name__ == '__main__':
567 Main()