blob: 294d091903f7133e5ca7303800fd069ccdfc90c6 [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
Charlie Mooneybf469942015-07-09 11:21:12 -070014import pwd
Charlie Mooneybbc05f52015-03-24 13:36:22 -070015import re
16import subprocess
Charlie Mooneyc68f9c32015-04-16 15:23:22 -070017import time
Charlie Mooneybbc05f52015-03-24 13:36:22 -070018import threading
19
20import cherrypy
21
Charlie Mooneybbc05f52015-03-24 13:36:22 -070022from ws4py import configure_logger
23from ws4py.messaging import TextMessage
24from ws4py.server.cherrypyserver import WebSocketPlugin, WebSocketTool
25from ws4py.websocket import WebSocket
26
Charlie Mooney04b41532015-04-02 12:41:37 -070027from remote import ChromeOSTouchDevice, AndroidTouchDevice
Charlie Mooneybbc05f52015-03-24 13:36:22 -070028
29
30# The WebSocket connection state object.
31state = None
32
33# The touch events are saved in this file as default.
Charlie Mooneybf469942015-07-09 11:21:12 -070034current_username = pwd.getpwuid(os.getuid()).pw_name
35SAVED_FILE = '/tmp/webplot_%s.dat' % current_username
36SAVED_IMAGE = '/tmp/webplot_%s.png' % current_username
Charlie Mooneybbc05f52015-03-24 13:36:22 -070037
38
Joseph Hwang4782a042015-04-08 17:15:50 +080039def SimpleSystem(cmd):
40 """Execute a system command."""
41 ret = subprocess.call(cmd, shell=True)
42 if ret:
43 logging.warning('Command (%s) failed (ret=%s).', cmd, ret)
44 return ret
45
46
47def SimpleSystemOutput(cmd):
48 """Execute a system command and get its output."""
49 try:
50 proc = subprocess.Popen(
51 cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
52 stdout, _ = proc.communicate()
53 except Exception, e:
54 logging.warning('Command (%s) failed (%s).', cmd, e)
55 else:
56 return None if proc.returncode else stdout.strip()
57
58
59def IsDestinationPortEnabled(port):
60 """Check if the destination port is enabled in iptables.
61
62 If port 8000 is enabled, it looks like
63 ACCEPT tcp -- 0.0.0.0/0 0.0.0.0/0 ctstate NEW tcp dpt:8000
64 """
65 pattern = re.compile('ACCEPT\s+tcp.+\s+ctstate\s+NEW\s+tcp\s+dpt:%d' % port)
66 rules = SimpleSystemOutput('sudo iptables -L INPUT -n --line-number')
67 for rule in rules.splitlines():
68 if pattern.search(rule):
69 return True
70 return False
71
72
73def EnableDestinationPort(port):
74 """Enable the destination port for input traffic in iptables."""
75 if IsDestinationPortEnabled(port):
76 cherrypy.log('Port %d has been already enabled in iptables.' % port)
77 else:
Charlie Mooneye15a5552015-07-10 13:48:03 -070078 cherrypy.log('Adding a rule to accept incoming connections on port %d in '
79 'iptables.' % port)
Joseph Hwang4782a042015-04-08 17:15:50 +080080 cmd = ('sudo iptables -A INPUT -p tcp -m conntrack --ctstate NEW '
81 '--dport %d -j ACCEPT' % port)
82 if SimpleSystem(cmd) != 0:
83 raise Error('Failed to enable port in iptables: %d.' % port)
84
85
Charlie Mooneybbc05f52015-03-24 13:36:22 -070086def InterruptHandler():
87 """An interrupt handler for both SIGINT and SIGTERM
88
89 The stop procedure triggered is as follows:
90 1. This handler sends a 'quit' message to the listening client.
91 2. The client sends the canvas image back to the server in its quit message.
92 3. WebplotWSHandler.received_message() saves the image.
93 4. WebplotWSHandler.received_message() handles the 'quit' message.
94 The cherrypy engine exits if this is the last client.
95 """
96 cherrypy.log('Cherrypy engine is sending quit message to clients.')
Joseph Hwang95bf52a2015-04-14 13:09:39 +080097 state.QuitAndShutdown()
98
99
100def _IOError(e, filename):
101 err_msg = ['\n', '!' * 60, str(e),
102 'It is likely that %s is owned by root.' % filename,
103 'Please remove the file and then run webplot again.',
104 '!' * 60, '\n']
105 cherrypy.log('\n'.join(err_msg))
Charlie Mooneybbc05f52015-03-24 13:36:22 -0700106
Charlie Mooneyc68f9c32015-04-16 15:23:22 -0700107image_lock = threading.Event()
108image_string = ''
Charlie Mooneybbc05f52015-03-24 13:36:22 -0700109
110class WebplotWSHandler(WebSocket):
111 """The web socket handler for webplot."""
112
113 def opened(self):
114 """This method is called when the handler is opened."""
115 cherrypy.log('WS handler is opened!')
116
117 def received_message(self, msg):
118 """A callback for received message."""
Charlie Mooneybbc05f52015-03-24 13:36:22 -0700119 data = msg.data.split(':', 1)
120 mtype = data[0].lower()
121 content = data[1] if len(data) == 2 else None
Joseph Hwang0c1fa7d2015-04-09 17:01:45 +0800122
123 # Do not print the image data since it is too large.
124 if mtype != 'save':
125 cherrypy.log('Received message: %s' % str(msg.data))
126
Charlie Mooneybbc05f52015-03-24 13:36:22 -0700127 if mtype == 'quit':
128 # A shutdown message requested by the user.
Charlie Mooneybbc05f52015-03-24 13:36:22 -0700129 cherrypy.log('The user requests to shutdown the cherrypy server....')
130 state.DecCount()
131 elif mtype == 'save':
Charlie Mooneyb476e892015-04-02 13:25:49 -0700132 cherrypy.log('All data saved to "%s"' % SAVED_FILE)
133 self.SaveImage(content, SAVED_IMAGE)
134 cherrypy.log('Plot image saved to "%s"' % SAVED_IMAGE)
Charlie Mooneybbc05f52015-03-24 13:36:22 -0700135 else:
136 cherrypy.log('Unknown message type: %s' % mtype)
137
138 def closed(self, code, reason="A client left the room."):
139 """This method is called when the handler is closed."""
140 cherrypy.log('A client requests to close WS.')
141 cherrypy.engine.publish('websocket-broadcast', TextMessage(reason))
142
143 @staticmethod
144 def SaveImage(image_data, image_file):
145 """Decoded the base64 image data and save it in the file."""
Charlie Mooneyc68f9c32015-04-16 15:23:22 -0700146 global image_string
147 image_string = base64.b64decode(image_data)
148 image_lock.set()
Joseph Hwangafc092c2015-04-21 11:32:45 +0800149 try:
150 with open(image_file, 'w') as f:
151 f.write(image_string)
152 except IOError as e:
153 _IOError(e, image_file)
154 state.QuitAndShutdown()
Charlie Mooneybbc05f52015-03-24 13:36:22 -0700155
156
Charlie Mooneybbc05f52015-03-24 13:36:22 -0700157class ConnectionState(object):
158 """A ws connection state object for shutting down the cherrypy server.
159
160 It shuts down the cherrypy server when the count is down to 0 and is not
161 increased before the shutdown_timer expires.
162
163 Note that when a page refreshes, it closes the WS connection first and
164 then re-connects immediately. This is why we would like to wait a while
165 before actually shutting down the server.
166 """
167 TIMEOUT = 1.0
168
169 def __init__(self):
170 self.count = 0;
171 self.lock = threading.Lock()
172 self.shutdown_timer = None
Joseph Hwang95bf52a2015-04-14 13:09:39 +0800173 self.quit_flag = False
Charlie Mooneybbc05f52015-03-24 13:36:22 -0700174
175 def IncCount(self):
176 """Increase the connection count, and cancel the shutdown timer if exists.
177 """
178 self.lock.acquire()
179 self.count += 1;
180 cherrypy.log(' WS connection count: %d' % self.count)
181 if self.shutdown_timer:
182 self.shutdown_timer.cancel()
183 self.shutdown_timer = None
184 self.lock.release()
185
186 def DecCount(self):
187 """Decrease the connection count, and start a shutdown timer if no other
188 clients are connecting to the server.
189 """
190 self.lock.acquire()
191 self.count -= 1;
192 cherrypy.log(' WS connection count: %d' % self.count)
193 if self.count == 0:
194 self.shutdown_timer = threading.Timer(self.TIMEOUT, self.Shutdown)
195 self.shutdown_timer.start()
196 self.lock.release()
197
Joseph Hwang3f561d32015-04-09 16:33:56 +0800198 def ShutdownWhenNoConnections(self):
199 """Shutdown cherrypy server when there is no client connection."""
200 self.lock.acquire()
201 if self.count == 0 and self.shutdown_timer is None:
202 self.Shutdown()
203 self.lock.release()
204
Charlie Mooneybbc05f52015-03-24 13:36:22 -0700205 def Shutdown(self):
206 """Shutdown the cherrypy server."""
207 cherrypy.log('Shutdown timer expires. Cherrypy server for Webplot exits.')
208 cherrypy.engine.exit()
209
Joseph Hwang95bf52a2015-04-14 13:09:39 +0800210 def QuitAndShutdown(self):
211 """The server notifies clients to quit and then shuts down."""
212 if not self.quit_flag:
213 self.quit_flag = True
214 cherrypy.engine.publish('websocket-broadcast', TextMessage('quit'))
215 self.ShutdownWhenNoConnections()
216
Charlie Mooneybbc05f52015-03-24 13:36:22 -0700217
218class Root(object):
219 """A class to handle requests about docroot."""
220
221 def __init__(self, ip, port, touch_min_x, touch_max_x, touch_min_y,
222 touch_max_y, touch_min_pressure, touch_max_pressure):
223 self.ip = ip
224 self.port = port
225 self.touch_min_x = touch_min_x
226 self.touch_max_x = touch_max_x
227 self.touch_min_y = touch_min_y
228 self.touch_max_y = touch_max_y
229 self.touch_min_pressure = touch_min_pressure
230 self.touch_max_pressure = touch_max_pressure
231 self.scheme = 'ws'
232 cherrypy.log('Root address: (%s, %s)' % (ip, str(port)))
233 cherrypy.log('scheme: %s' % self.scheme)
234
235 @cherrypy.expose
236 def index(self):
237 """This is the default index.html page."""
238 websocket_dict = {
239 'websocketUrl': '%s://%s:%s/ws' % (self.scheme, self.ip, self.port),
240 'touchMinX': str(self.touch_min_x),
241 'touchMaxX': str(self.touch_max_x),
242 'touchMinY': str(self.touch_min_y),
243 'touchMaxY': str(self.touch_max_y),
244 'touchMinPressure': str(self.touch_min_pressure),
245 'touchMaxPressure': str(self.touch_max_pressure),
246 }
247 root_page = os.path.join(os.path.abspath(os.path.dirname(__file__)),
248 'webplot.html')
249 with open(root_page) as f:
250 return f.read() % websocket_dict
251
252 @cherrypy.expose
253 def ws(self):
254 """This handles the request to create a new web socket per client."""
255 cherrypy.log('A new client requesting for WS')
256 cherrypy.log('WS handler created: %s' % repr(cherrypy.request.ws_handler))
257 state.IncCount()
258
259
Joseph Hwang4782a042015-04-08 17:15:50 +0800260class Webplot(threading.Thread):
261 """The server handling the Plotting of finger traces.
262
263 Use case 1: embedding Webplot as a plotter in an application
264
265 # Instantiate a webplot server and starts the daemon.
266 plot = Webplot(server_addr, server_port, device)
267 plot.start()
268
269 # Repeatedly get a snapshot and add it for plotting.
270 while True:
271 # GetSnapshot() is essentially device.NextSnapshot()
272 snapshot = plot.GetSnapshot()
273 if not snapshot:
274 break
275 # Add the snapshot to the plotter for plotting.
276 plot.AddSnapshot(snapshot)
277
278 # Save a screen dump
279 plot.Save()
280
281 # Notify the browser to clear the screen.
282 plot.Clear()
283
284 # Notify both the browser and the cherrypy engine to quit.
285 plot.Quit()
Charlie Mooneybbc05f52015-03-24 13:36:22 -0700286
287
Joseph Hwang4782a042015-04-08 17:15:50 +0800288 Use case 2: using webplot standalone
Charlie Mooneybbc05f52015-03-24 13:36:22 -0700289
Joseph Hwang4782a042015-04-08 17:15:50 +0800290 # Instantiate a webplot server and starts the daemon.
291 plot = Webplot(server_addr, server_port, device)
292 plot.start()
Charlie Mooneybbc05f52015-03-24 13:36:22 -0700293
Joseph Hwang4782a042015-04-08 17:15:50 +0800294 # Get touch snapshots from the touch device and have clients plot them.
295 webplot.GetAndPlotSnapshots()
Charlie Mooneybbc05f52015-03-24 13:36:22 -0700296 """
Charlie Mooneybbc05f52015-03-24 13:36:22 -0700297
Joseph Hwange9cfd642015-04-20 16:00:33 +0800298 def __init__(self, server_addr, server_port, device, saved_file=SAVED_FILE,
Charlie Mooneye15a5552015-07-10 13:48:03 -0700299 logging=False, is_behind_iptables_firewall=False):
Joseph Hwang4782a042015-04-08 17:15:50 +0800300 self._server_addr = server_addr
301 self._server_port = server_port
302 self._device = device
303 self._saved_file = saved_file
304 super(Webplot, self).__init__(name='webplot thread')
Charlie Mooneybbc05f52015-03-24 13:36:22 -0700305
Joseph Hwang4782a042015-04-08 17:15:50 +0800306 self.daemon = True
307 self._prev_tids = []
308
Joseph Hwange9cfd642015-04-20 16:00:33 +0800309 # The logging is turned off by default when imported as a module so that
310 # it does not mess up the screen.
311 if not logging:
312 cherrypy.log.screen = None
313
Charlie Mooneye15a5552015-07-10 13:48:03 -0700314 # Allow input traffic in iptables, if the user has specified. This setting
315 # should be used if webplot is being run directly on a chromebook, but it
316 # requires root access, so we don't want to use it all the time.
317 if is_behind_iptables_firewall:
318 EnableDestinationPort(self._server_port)
Joseph Hwang4782a042015-04-08 17:15:50 +0800319
320 # Create a ws connection state object to wait for the condition to
321 # shutdown the whole process.
322 global state
323 state = ConnectionState()
324
325 cherrypy.config.update({
326 'server.socket_host': self._server_addr,
327 'server.socket_port': self._server_port,
328 })
329
330 WebSocketPlugin(cherrypy.engine).subscribe()
331 cherrypy.tools.websocket = WebSocketTool()
332
333 # If the cherrypy server exits for whatever reason, close the device
334 # for required cleanup. Otherwise, there might exist local/remote
335 # zombie processes.
Charlie Mooneyc68f9c32015-04-16 15:23:22 -0700336 cherrypy.engine.subscribe('exit', self._device.__del__)
Joseph Hwang4782a042015-04-08 17:15:50 +0800337
338 cherrypy.engine.signal_handler.handlers['SIGINT'] = InterruptHandler
339 cherrypy.engine.signal_handler.handlers['SIGTERM'] = InterruptHandler
340
341 def run(self):
342 """Start the cherrypy engine."""
Charlie Mooneyc68f9c32015-04-16 15:23:22 -0700343 x_min, x_max = self._device.RangeX()
344 y_min, y_max = self._device.RangeY()
345 p_min, p_max = self._device.RangeP()
Joseph Hwang4782a042015-04-08 17:15:50 +0800346
347 cherrypy.quickstart(
348 Root(self._server_addr, self._server_port,
349 x_min, x_max, y_min, y_max, p_min, p_max),
350 '',
351 config={
352 '/': {
353 'tools.staticdir.root':
354 os.path.abspath(os.path.dirname(__file__)),
355 'tools.staticdir.on': True,
356 'tools.staticdir.dir': '',
357 },
358 '/ws': {
359 'tools.websocket.on': True,
360 'tools.websocket.handler_cls': WebplotWSHandler,
361 },
362 }
363 )
364
365 def _ConvertNamedtupleToDict(self, snapshot):
366 """Convert namedtuples to ordinary dictionaries and add leaving slots.
367
368 This is to make a snapshot json serializable. Otherwise, the namedtuples
369 would be transmitted as arrays which is less readable.
370
371 A snapshot looks like
372 MtSnapshot(
373 syn_time=1420524008.368854,
374 button_pressed=False,
375 fingers=[
376 MtFinger(tid=162, slot=0, syn_time=1420524008.368854, x=524,
377 y=231, pressure=45),
378 MtFinger(tid=163, slot=1, syn_time=1420524008.368854, x=677,
379 y=135, pressure=57)
380 ]
381 )
382
383 Note:
384 1. that there are two levels of namedtuples to convert.
385 2. The leaving slots are used to notify javascript that a finger is leaving
386 so that the corresponding finger color could be released for reuse.
387 """
388 # Convert MtSnapshot.
389 converted = dict(snapshot.__dict__.items())
390
391 # Convert MtFinger.
392 converted['fingers'] = [dict(finger.__dict__.items())
393 for finger in converted['fingers']]
394 converted['raw_events'] = [str(event) for event in converted['raw_events']]
395
396 # Add leaving fingers to notify js for reclaiming the finger colors.
397 curr_tids = [finger['tid'] for finger in converted['fingers']]
398 for tid in set(self._prev_tids) - set(curr_tids):
399 leaving_finger = {'tid': tid, 'leaving': True}
400 converted['fingers'].append(leaving_finger)
401 self._prev_tids = curr_tids
402
Joseph Hwang02e829c2015-04-13 17:09:07 +0800403 # Convert raw events from a list of classes to a list of its strings
404 # so that the raw_events is serializable.
405 converted['raw_events'] = [str(event) for event in converted['raw_events']]
406
Joseph Hwang4782a042015-04-08 17:15:50 +0800407 return converted
408
409 def GetSnapshot(self):
410 """Get a snapshot from the touch device."""
Charlie Mooneyc68f9c32015-04-16 15:23:22 -0700411 return self._device.NextSnapshot()
Joseph Hwang4782a042015-04-08 17:15:50 +0800412
413 def AddSnapshot(self, snapshot):
414 """Convert the snapshot to a proper format and publish it to clients."""
415 snapshot = self._ConvertNamedtupleToDict(snapshot)
416 cherrypy.engine.publish('websocket-broadcast', json.dumps(snapshot))
Joseph Hwang02e829c2015-04-13 17:09:07 +0800417 return snapshot
Joseph Hwang4782a042015-04-08 17:15:50 +0800418
419 def GetAndPlotSnapshots(self):
420 """Get and plot snapshots."""
421 cherrypy.log('Start getting the live stream snapshots....')
Joseph Hwang95bf52a2015-04-14 13:09:39 +0800422 try:
423 with open(self._saved_file, 'w') as f:
424 while True:
425 try:
426 snapshot = self.GetSnapshot()
427 if not snapshot:
428 cherrypy.log('webplot is terminated.')
429 break
430 converted_snapshot = self.AddSnapshot(snapshot)
431 f.write('\n'.join(converted_snapshot['raw_events']) + '\n')
432 f.flush()
433 except KeyboardInterrupt:
434 cherrypy.log('Keyboard Interrupt accepted')
435 cherrypy.log('webplot is being terminated...')
436 state.QuitAndShutdown()
437 except IOError as e:
438 _IOError(e, self._saved_file)
439 state.QuitAndShutdown()
Joseph Hwang4782a042015-04-08 17:15:50 +0800440
441 def Publish(self, msg):
442 """Publish a message to clients."""
443 cherrypy.engine.publish('websocket-broadcast', TextMessage(msg))
444
445 def Clear(self):
446 """Notify clients to clear the display."""
447 self.Publish('clear')
448
449 def Quit(self):
450 """Notify clients to quit.
451
452 Note that the cherrypy engine would quit accordingly.
453 """
Joseph Hwang95bf52a2015-04-14 13:09:39 +0800454 state.QuitAndShutdown()
Joseph Hwang4782a042015-04-08 17:15:50 +0800455
Charlie Mooneybf469942015-07-09 11:21:12 -0700456 def Save(self):
457 """Notify clients to save the screen, then wait for the image file to be
458 created, and return the image.
Charlie Mooneyc68f9c32015-04-16 15:23:22 -0700459 """
460 global image_lock
461 global image_string
462
463 # Trigger a save action
Joseph Hwang4782a042015-04-08 17:15:50 +0800464 self.Publish('save')
465
Charlie Mooneyc68f9c32015-04-16 15:23:22 -0700466 # Block until the server has completed saving it to disk
467 image_lock.wait()
468 image_lock.clear()
469 return image_string
470
Joseph Hwang4782a042015-04-08 17:15:50 +0800471 def Url(self):
472 """The url the server is serving at."""
473 return 'http://%s:%d' % (self._server_addr, self._server_port)
Charlie Mooneybbc05f52015-03-24 13:36:22 -0700474
475
Joseph Hwanga1c84782015-04-14 15:07:00 +0800476def _CheckLegalUser():
477 """If this program is run in chroot, it should not be run as root for security
478 reason.
479 """
480 if os.path.exists('/etc/cros_chroot_version') and os.getuid() == 0:
481 print ('You should run webplot in chroot as a regular user '
482 'instead of as root.\n')
483 exit(1)
484
485
Charlie Mooneybbc05f52015-03-24 13:36:22 -0700486def _ParseArguments():
487 """Parse the command line options."""
488 parser = argparse.ArgumentParser(description='Webplot Server')
Charlie Mooney8026f2a2015-04-02 13:01:28 -0700489 parser.add_argument('-d', '--dut_addr', default=None,
Charlie Mooneybbc05f52015-03-24 13:36:22 -0700490 help='the address of the dut')
Joseph Hwang59db4412015-04-09 12:43:16 +0800491
492 # Make an exclusive group to make the webplot.py command option
493 # consistent with the webplot.sh script command option.
494 # What is desired:
495 # When no command option specified in webplot.sh/webplot.py: grab is True
496 # When '--grab' option specified in webplot.sh/webplot.py: grab is True
497 # When '--nograb' option specified in webplot.sh/webplot.py: grab is False
498 grab_group = parser.add_mutually_exclusive_group()
499 grab_group.add_argument('--grab', help='grab the device exclusively',
500 action='store_true')
501 grab_group.add_argument('--nograb', help='do not grab the device',
502 action='store_true')
503
Charlie Mooneybbc05f52015-03-24 13:36:22 -0700504 parser.add_argument('--is_touchscreen', help='the DUT is touchscreen',
505 action='store_true')
Charlie Mooneye15a5552015-07-10 13:48:03 -0700506 parser.add_argument('-p', '--server_port', default=8080, type=int,
507 help='the port the web server listens to (default: 8080)')
508 parser.add_argument('--behind_firewall', action='store_true',
509 help=('With this flag set, you tell webplot to add a '
510 'rule to iptables to allow incoming traffic to '
511 'the webserver. If you are running webplot on '
512 'a chromebook, this is needed.'))
513 parser.add_argument('-s', '--server_addr', default='127.0.0.1',
Joseph Hwang59db4412015-04-09 12:43:16 +0800514 help='the address the webplot http server listens to')
515 parser.add_argument('-t', '--dut_type', default='chromeos', type=str.lower,
Charlie Mooneybbc05f52015-03-24 13:36:22 -0700516 help='dut type: chromeos, android')
517 args = parser.parse_args()
Joseph Hwang59db4412015-04-09 12:43:16 +0800518
519 args.grab = not args.nograb
520
Charlie Mooneybbc05f52015-03-24 13:36:22 -0700521 return args
522
523
524def Main():
525 """The main function to launch webplot service."""
Joseph Hwanga1c84782015-04-14 15:07:00 +0800526 _CheckLegalUser()
527
Joseph Hwange9cfd642015-04-20 16:00:33 +0800528 configure_logger(level=logging.ERROR)
Charlie Mooneybbc05f52015-03-24 13:36:22 -0700529 args = _ParseArguments()
530
531 print '\n' + '-' * 70
532 cherrypy.log('dut machine type: %s' % args.dut_type)
533 cherrypy.log('dut\'s touch device: %s' %
534 ('touchscreen' if args.is_touchscreen else 'touchpad'))
535 cherrypy.log('dut address: %s' % args.dut_addr)
536 cherrypy.log('web server address: %s' % args.server_addr)
537 cherrypy.log('web server port: %s' % args.server_port)
Joseph Hwang59db4412015-04-09 12:43:16 +0800538 cherrypy.log('grab the touch device: %s' % args.grab)
539 if args.dut_type == 'android' and args.grab:
540 cherrypy.log('Warning: the grab option is not supported on Android devices'
541 ' yet.')
Charlie Mooneybbc05f52015-03-24 13:36:22 -0700542 cherrypy.log('touch events are saved in %s' % SAVED_FILE)
543 print '-' * 70 + '\n\n'
544
545 if args.server_port == 80:
546 url = args.server_addr
547 else:
548 url = '%s:%d' % (args.server_addr, args.server_port)
549
550 msg = 'Type "%s" in browser %s to see finger traces.\n'
Charlie Mooneye15a5552015-07-10 13:48:03 -0700551 if args.server_addr == '127.0.0.1':
Charlie Mooneybbc05f52015-03-24 13:36:22 -0700552 which_machine = 'on the webplot server machine'
553 else:
554 which_machine = 'on any machine'
555
556 print '*' * 70
557 print msg % (url, which_machine)
558 print 'Press \'q\' on the browser to quit.'
559 print '*' * 70 + '\n\n'
560
Charlie Mooneybbc05f52015-03-24 13:36:22 -0700561 # Instantiate a touch device.
Charlie Mooneyc68f9c32015-04-16 15:23:22 -0700562 if args.dut_type == 'chromeos':
563 addr = args.dut_addr if args.dut_addr else '127.0.0.1'
564 device = ChromeOSTouchDevice(addr, args.is_touchscreen, grab=args.grab)
565 else:
566 device = AndroidTouchDevice(args.dut_addr, True)
567
Charlie Mooneybbc05f52015-03-24 13:36:22 -0700568
Joseph Hwang4782a042015-04-08 17:15:50 +0800569 # Instantiate a webplot server daemon and start it.
Charlie Mooneye15a5552015-07-10 13:48:03 -0700570 webplot = Webplot(args.server_addr, args.server_port, device, logging=True,
571 is_behind_iptables_firewall=args.behind_firewall)
Joseph Hwang4782a042015-04-08 17:15:50 +0800572 webplot.start()
Charlie Mooneybbc05f52015-03-24 13:36:22 -0700573
Joseph Hwang4782a042015-04-08 17:15:50 +0800574 # Get touch snapshots from the touch device and have clients plot them.
575 webplot.GetAndPlotSnapshots()
Charlie Mooneybbc05f52015-03-24 13:36:22 -0700576
577
578if __name__ == '__main__':
579 Main()