1#!/usr/bin/env python 2# 3# Copyright 2016 The Chromium OS Authors. All rights reserved. 4# 5# Licensed under the Apache License, Version 2.0 (the "License"); 6# you may not use this file except in compliance with the License. 7# You may obtain a copy of the License at 8# 9# http://www.apache.org/licenses/LICENSE-2.0 10# 11# Unless required by applicable law or agreed to in writing, software 12# distributed under the License is distributed on an "AS IS" BASIS, 13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14# See the License for the specific language governing permissions and 15# limitations under the License. 16# 17 18""" 19Runs the touchpad drag latency test using WALT Latency Timer 20Usage example: 21 $ python walt.py 11 22 Input device : /dev/input/event11 23 Serial device : /dev/ttyACM1 24 Laser log file : /tmp/WALT_2016_06_23__1714_51_laser.log 25 evtest log file: /tmp/WALT_2016_06_23__1714_51_evtest.log 26 Clock zeroed at 1466716492 (rt 0.284ms) 27 ........................................ 28 Processing data, may take a minute or two... 29 Drag latency (min method) = 15.37 ms 30 31Note, before running this script, check that evtest can grab the device. 32On some systems it requires running as root. 33""" 34 35import argparse 36import contextlib 37import glob 38import os 39import random 40import re 41import socket 42import subprocess 43import sys 44import tempfile 45import threading 46import time 47 48import serial 49import numpy 50 51import evparser 52import minimization 53import screen_stats 54 55 56# Time units 57MS = 1e-3 # MS = 0.001 seconds 58US = 1e-6 # US = 10^-6 seconds 59 60# Globals 61debug_mode = True 62 63 64def log(msg): 65 if debug_mode: 66 print(msg) 67 68 69class Walt(object): 70 """ A class for communicating with Walt device 71 72 Usage: 73 with Walt('/dev/ttyUSB0') as walt: 74 body.... 75 76 77 """ 78 79 # Teensy commands, always singe char. Defined in WALT.ino 80 # github.com/google/walt/blob/master/arduino/walt/walt.ino 81 CMD_RESET = 'F' 82 CMD_PING = 'P' 83 CMD_SYNC_ZERO = 'Z' 84 CMD_SYNC_SEND = 'I' 85 CMD_SYNC_READOUT = 'R' 86 CMD_TIME_NOW = 'T' 87 CMD_AUTO_LASER_ON = 'L' 88 CMD_AUTO_LASER_OFF = 'l' 89 CMD_AUTO_SCREEN_ON = 'C' 90 CMD_AUTO_SCREEN_OFF = 'c' 91 CMD_GSHOCK = 'G' 92 CMD_VERSION = 'V' 93 CMD_SAMPLE_ALL = 'Q' 94 CMD_BRIGHTNESS_CURVE = 'U' 95 CMD_AUDIO = 'A' 96 97 98 def __init__(self, serial_dev, timeout=None, encoding='utf-8'): 99 self.encoding = encoding 100 self.serial_dev = serial_dev 101 self.ser = serial.Serial(serial_dev, baudrate=115200, timeout=timeout) 102 self.base_time = None 103 self.min_lag = None 104 self.max_lag = None 105 self.median_latency = None 106 107 def __enter__(self): 108 return self 109 110 def __exit__(self, exc_type, exc_value, traceback): 111 try: 112 self.ser.close() 113 except: 114 pass 115 116 def close(self): 117 self.ser.close() 118 119 def readline(self): 120 return self.ser.readline().decode(self.encoding) 121 122 def sndrcv(self, data): 123 """ Send a 1-char command. 124 Return the reply and how long it took. 125 126 """ 127 t0 = time.time() 128 self.ser.write(data.encode(self.encoding)) 129 reply = self.ser.readline() 130 reply = reply.decode(self.encoding) 131 t1 = time.time() 132 dt = (t1 - t0) 133 log('sndrcv(): round trip %.3fms, reply=%s' % (dt / MS, reply.strip())) 134 return dt, reply 135 136 def read_shock_time(self): 137 dt, s = self.sndrcv(Walt.CMD_GSHOCK) 138 t_us = int(s.strip()) 139 return t_us 140 141 142 def run_comm_stats(self, N=100): 143 """ 144 Measure the USB serial round trip time. 145 Send CMD_TIME_NOW to the Teensy N times measuring the round trip each time. 146 Prints out stats (min, median, max). 147 148 """ 149 log('Running USB comm stats...') 150 self.ser.flushInput() 151 self.sndrcv(Walt.CMD_SYNC_ZERO) 152 tstart = time.time() 153 times = numpy.zeros((N, 1)) 154 for i in range(N): 155 dt, _ = self.sndrcv(Walt.CMD_TIME_NOW) 156 times[i] = dt 157 t_total = time.time() - tstart 158 159 median = numpy.median(times) 160 stats = (times.min() / MS, median / MS, times.max() / MS, N) 161 self.median_latency = median 162 log('USB comm round trip stats:') 163 log('min=%.2fms, median=%.2fms, max=%.2fms N=%d' % stats) 164 if (median > 2): 165 print('ERROR: the median round trip is too high: %.2f ms' % (median / MS) ) 166 sys.exit(2) 167 168 def zero_clock(self, max_delay=0.001, retries=10): 169 """ 170 Tell the TeensyUSB to zero its clock (CMD_SYNC_ZERO). 171 Returns the time when the command was sent. 172 Verify that the response arrived within max_delay seconds. 173 174 This is the simple zeroing used when the round trip is fast. 175 It does not employ the same method as Android clock sync. 176 """ 177 178 # Check that we get reasonable ping time with Teensy 179 # this also 'warms up' the comms, first msg is often slower 180 self.run_comm_stats(N=10) 181 182 self.ser.flushInput() 183 184 for i in range(retries): 185 t0 = time.time() 186 dt, _ = self.sndrcv(Walt.CMD_SYNC_ZERO) 187 if dt < max_delay: 188 print('Clock zeroed at %.0f (rt %.3f ms)' % (t0, dt / MS)) 189 self.base_time = t0 190 self.max_lag = dt 191 self.min_lag = 0 192 return t0 193 print('Error, failed to zero the clock after %d retries') 194 return -1 195 196 def read_remote_times(self): 197 """ Helper func, see doc string in estimate_lage() 198 Read out the timestamps taken recorded by the Teensy. 199 """ 200 times = numpy.zeros(9) 201 for i in range(9): 202 dt, reply = self.sndrcv(Walt.CMD_SYNC_READOUT) 203 num, tstamp = reply.strip().split(':') 204 # TODO: verify that num is what we expect it to be 205 log('read_remote_times() CMD_SYNC_READOUT > w > = %s' % reply) 206 t = float(tstamp) * US # WALT sends timestamps in microseconds 207 times[i] = t 208 return times 209 210 def estimate_lag(self): 211 """ Estimate the difference between local and remote clocks 212 213 This is based on: 214 github.com/google/walt/blob/master/android/WALT/app/src/main/jni/README.md 215 216 self.base_time needs to be set using self.zero_clock() before running 217 this function. 218 219 The result is saved as self.min_lag and self.max_lag. Assume that the 220 remote clock lags behind the local by `lag` That is, at a given moment 221 local_time = remote_time + lag 222 where local_time = time.time() - self.base_time 223 224 Immediately after this function completes the lag is guaranteed to be 225 between min_lag and max_lag. But the lag change (drift) away with time. 226 """ 227 self.ser.flushInput() 228 229 # remote -> local 230 times_local_received = numpy.zeros(9) 231 self.ser.write(Walt.CMD_SYNC_SEND) 232 for i in range(9): 233 reply = self.ser.readline() 234 times_local_received[i] = time.time() - self.base_time 235 236 times_remote_sent = self.read_remote_times() 237 max_lag = (times_local_received - times_remote_sent).min() 238 239 # local -> remote 240 times_local_sent = numpy.zeros(9) 241 for i in range(9): 242 s = '%d' % (i + 1) 243 # Sleep between the messages to combat buffering 244 t_sleep = US * random.randint(70, 700) 245 time.sleep(t_sleep) 246 times_local_sent[i] = time.time() - self.base_time 247 self.ser.write(s) 248 249 times_remote_received = self.read_remote_times() 250 min_lag = (times_local_sent - times_remote_received).max() 251 252 self.min_lag = min_lag 253 self.max_lag = max_lag 254 255 def parse_trigger(self, trigger_line): 256 """ Parse a trigger line from WALT. 257 258 Trigger events look like this: "G L 12902345 1 1" 259 The parts: 260 * G - common for all trigger events 261 * L - means laser 262 * 12902345 is timestamp in us since zeroed 263 * 1st 1 or 0 is trigger value. 0 = changed to dark, 1 = changed to light, 264 * 2nd 1 is counter of how many times this trigger happened since last 265 readout, should always be 1 in our case 266 267 """ 268 269 parts = trigger_line.strip().split() 270 if len(parts) != 5: 271 raise Exception('Malformed trigger line: "%s"\n' % trigger_line) 272 t_us = int(parts[2]) 273 val = int(parts[3]) 274 return (t_us / 1e6, val) 275 276 277def array2str(a): 278 a_strs = ['%0.2f' % x for x in a] 279 s = ', '.join(a_strs) 280 return '[' + s + ']' 281 282 283def parse_args(argv): 284 temp_dir = tempfile.gettempdir() 285 serial = '/dev/ttyACM0' 286 287 # Try to autodetect the WALT serial port 288 ls_ttyACM = glob.glob('/dev/ttyACM*') 289 if len(ls_ttyACM) > 0: 290 serial = ls_ttyACM[0] 291 292 description = "Run a latency test using WALT Latency Timer" 293 parser = argparse.ArgumentParser( 294 description=description, 295 formatter_class=argparse.ArgumentDefaultsHelpFormatter) 296 297 parser.add_argument('-i', '--input', 298 help='input device, e.g: 6 or /dev/input/event6') 299 parser.add_argument('-s', '--serial', default=serial, 300 help='WALT serial port') 301 parser.add_argument('-t', '--type', 302 help='Test type: drag|tap|screen|sanity|curve|bridge|' 303 'tapaudio|tapblink') 304 parser.add_argument('-l', '--logdir', default=temp_dir, 305 help='where to store logs') 306 parser.add_argument('-n', default=40, type=int, 307 help='Number of laser toggles to read') 308 parser.add_argument('-p', '--port', default=50007, type=int, 309 help='port to listen on for the TCP bridge') 310 parser.add_argument('-d', '--debug', action='store_true', 311 help='talk more') 312 args = parser.parse_args(argv) 313 314 if not args.type: 315 parser.print_usage() 316 sys.exit(0) 317 318 global debug_mode 319 debug_mode = args.debug 320 321 if args.input and args.input.isalnum(): 322 args.input = '/dev/input/event' + args.input 323 324 return args 325 326 327def run_drag_latency_test(args): 328 329 if not args.input: 330 print('Error: --input argument is required for drag latency test') 331 sys.exit(1) 332 333 # Create names for log files 334 prefix = time.strftime('WALT_%Y_%m_%d__%H%M_%S') 335 laser_file_name = os.path.join(args.logdir, prefix + '_laser.log') 336 evtest_file_name = os.path.join(args.logdir, prefix + '_evtest.log') 337 338 print('Starting drag latency test') 339 print('Input device : ' + args.input) 340 print('Serial device : ' + args.serial) 341 print('Laser log file : ' + laser_file_name) 342 print('evtest log file: ' + evtest_file_name) 343 344 with Walt(args.serial) as walt: 345 walt.sndrcv(Walt.CMD_RESET) 346 tstart = time.time() 347 t_zero = walt.zero_clock() 348 if t_zero < 0: 349 print('Error: Couldn\'t zero clock, exiting') 350 sys.exit(1) 351 352 # Fire up the evtest process 353 cmd = 'evtest %s > %s' % (args.input, evtest_file_name) 354 evtest = subprocess.Popen(cmd, shell=True) 355 356 # Turn on laser trigger auto-sending 357 walt.sndrcv(Walt.CMD_AUTO_LASER_ON) 358 trigger_count = 0 359 while trigger_count < args.n: 360 # The following line blocks until a message from WALT arrives 361 trigger_line = walt.readline() 362 trigger_count += 1 363 log('#%d/%d - ' % (trigger_count, args.n) + 364 trigger_line.strip()) 365 366 if not debug_mode: 367 sys.stdout.write('.') 368 sys.stdout.flush() 369 370 t, val = walt.parse_trigger(trigger_line) 371 t += t_zero 372 with open(laser_file_name, 'at') as flaser: 373 flaser.write('%.3f %d\n' % (t, val)) 374 walt.sndrcv(Walt.CMD_AUTO_LASER_OFF) 375 376 # Send SIGTERM to evtest process 377 evtest.terminate() 378 379 print("\nProcessing data, may take a minute or two...") 380 # lm.main(evtest_file_name, laser_file_name) 381 minimization.minimize(evtest_file_name, laser_file_name) 382 383 384def run_screen_curve(args): 385 386 with Walt(args.serial, timeout=1) as walt: 387 walt.sndrcv(Walt.CMD_RESET) 388 389 t_zero = walt.zero_clock() 390 if t_zero < 0: 391 print('Error: Couldn\'t zero clock, exiting') 392 sys.exit(1) 393 394 # Fire up the walt_blinker process 395 cmd = 'blink_test 1' 396 blinker = subprocess.Popen(cmd, shell=True) 397 398 # Request screen brightness data 399 walt.sndrcv(Walt.CMD_BRIGHTNESS_CURVE) 400 s = 'dummy' 401 while s: 402 s = walt.readline() 403 print(s.strip()) 404 405 406def run_screen_latency_test(args): 407 408 # Create names for log files 409 prefix = time.strftime('WALT_%Y_%m_%d__%H%M_%S') 410 sensor_file_name = os.path.join(args.logdir, prefix + '_screen_sensor.log') 411 blinker_file_name = os.path.join(args.logdir, prefix + '_blinker.log') 412 413 print('Starting screen latency test') 414 print('Serial device : ' + args.serial) 415 print('Sensor log file : ' + sensor_file_name) 416 print('Blinker log file: ' + blinker_file_name) 417 418 with Walt(args.serial, timeout=1) as walt: 419 walt.sndrcv(Walt.CMD_RESET) 420 421 t_zero = walt.zero_clock() 422 if t_zero < 0: 423 print('Error: Couldn\'t zero clock, exiting') 424 sys.exit(1) 425 426 # Fire up the walt_blinker process 427 cmd = 'blink_test %d > %s' % (args.n, blinker_file_name, ) 428 blinker = subprocess.Popen(cmd, shell=True) 429 430 # Turn on screen trigger auto-sending 431 walt.sndrcv(Walt.CMD_AUTO_SCREEN_ON) 432 trigger_count = 0 433 434 # Iterate while the blinker process is alive 435 # TODO: re-sync clocks every once in a while 436 while blinker.poll() is None: 437 # The following line blocks until a message from WALT arrives 438 trigger_line = walt.readline() 439 if not trigger_line: 440 # This usually happens when readline timeouts on last iteration 441 continue 442 trigger_count += 1 443 log('#%d/%d - ' % (trigger_count, args.n) + 444 trigger_line.strip()) 445 446 if not debug_mode: 447 sys.stdout.write('.') 448 sys.stdout.flush() 449 450 t, val = walt.parse_trigger(trigger_line) 451 t += t_zero 452 with open(sensor_file_name, 'at') as flaser: 453 flaser.write('%.3f %d\n' % (t, val)) 454 walt.sndrcv(Walt.CMD_AUTO_SCREEN_OFF) 455 screen_stats.screen_stats(blinker_file_name, sensor_file_name) 456 457 458def run_tap_audio_test(args): 459 print('Starting tap-to-audio latency test') 460 with Walt(args.serial) as walt: 461 walt.sndrcv(Walt.CMD_RESET) 462 t_zero = walt.zero_clock() 463 if t_zero < 0: 464 print('Error: Couldn\'t zero clock, exiting') 465 sys.exit(1) 466 467 walt.sndrcv(Walt.CMD_GSHOCK) 468 deltas = [] 469 while len(deltas) < args.n: 470 sys.stdout.write('\rWAIT ') 471 sys.stdout.flush() 472 time.sleep(1) # Wait for previous beep to stop playing 473 while walt.read_shock_time() != 0: 474 pass # skip shocks during sleep 475 sys.stdout.write('\rTAP NOW') 476 sys.stdout.flush() 477 walt.sndrcv(Walt.CMD_AUDIO) 478 trigger_line = walt.readline() 479 beep_time_seconds, val = walt.parse_trigger(trigger_line) 480 beep_time_ms = beep_time_seconds * 1e3 481 shock_time_ms = walt.read_shock_time() / 1e3 482 if shock_time_ms == 0: 483 print("\rNo shock detected, skipping this event") 484 continue 485 dt = beep_time_ms - shock_time_ms 486 deltas.append(dt) 487 print("\rdt=%0.1f ms" % dt) 488 print('Median tap-to-audio latency: %0.1f ms' % numpy.median(deltas)) 489 490 491def run_tap_blink_test(args): 492 print('Starting tap-to-blink latency test') 493 with Walt(args.serial) as walt: 494 walt.sndrcv(Walt.CMD_RESET) 495 t_zero = walt.zero_clock() 496 if t_zero < 0: 497 print('Error: Couldn\'t zero clock, exiting') 498 sys.exit(1) 499 500 walt.sndrcv(Walt.CMD_GSHOCK) 501 walt.sndrcv(Walt.CMD_AUTO_SCREEN_ON) 502 deltas = [] 503 while len(deltas) < args.n: 504 trigger_line = walt.readline() 505 blink_time_seconds, val = walt.parse_trigger(trigger_line) 506 blink_time_ms = blink_time_seconds * 1e3 507 shock_time_ms = walt.read_shock_time() / 1e3 508 if shock_time_ms == 0: 509 print("No shock detected, skipping this event") 510 continue 511 dt = blink_time_ms - shock_time_ms 512 deltas.append(dt) 513 print("dt=%0.1f ms" % dt) 514 print('Median tap-to-blink latency: %0.1f ms' % numpy.median(deltas)) 515 516 517def run_tap_latency_test(args): 518 519 if not args.input: 520 print('Error: --input argument is required for tap latency test') 521 sys.exit(1) 522 523 print('Starting tap latency test') 524 525 with Walt(args.serial) as walt: 526 walt.sndrcv(Walt.CMD_RESET) 527 t_zero = walt.zero_clock() 528 if t_zero < 0: 529 print('Error: Couldn\'t zero clock, exiting') 530 sys.exit(1) 531 532 # Fire up the evtest process 533 cmd = 'evtest ' + args.input 534 evtest = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, bufsize=1, universal_newlines=True) 535 walt.sndrcv(Walt.CMD_GSHOCK) 536 537 taps_detected = 0 538 taps = [] 539 while taps_detected < args.n: 540 ev_line = evtest.stdout.readline() 541 tap_info = evparser.parse_tap_line(ev_line) 542 if not tap_info: 543 continue 544 545 # Just received a tap event from evtest 546 taps_detected += 1 547 548 t_tap_epoch, direction = tap_info 549 shock_time_us = walt.read_shock_time() 550 dt_tap_us = 1e6 * (t_tap_epoch - t_zero) - shock_time_us 551 552 print(ev_line.strip()) 553 print("shock t %d, tap t %f, tap val %d. dt=%0.1f" % (shock_time_us, t_tap_epoch, direction, dt_tap_us)) 554 555 if shock_time_us == 0: 556 print("No shock detected, skipping this event") 557 continue 558 559 taps.append((dt_tap_us, direction)) 560 561 evtest.terminate() 562 563 # Process data 564 print("\nProcessing data...") 565 dt_down = numpy.array([t[0] for t in taps if t[1] == 1]) / 1e3 566 dt_up = numpy.array([t[0] for t in taps if t[1] == 0]) / 1e3 567 568 print('dt_down = ' + array2str(dt_down)) 569 print('dt_up = ' + array2str(dt_up)) 570 571 median_down_ms = numpy.median(dt_down) 572 median_up_ms = numpy.median(dt_up) 573 574 print('Median latency, down: %0.1f, up: %0.1f' % (median_down_ms, median_up_ms)) 575 576 577def run_walt_sanity_test(args): 578 print('Starting sanity test') 579 580 with Walt(args.serial) as walt: 581 walt.sndrcv(Walt.CMD_RESET) 582 583 not_digit = re.compile('\D+') 584 lows = numpy.zeros(3) + 1024 585 highs = numpy.zeros(3) 586 while True: 587 t, s = walt.sndrcv(Walt.CMD_SAMPLE_ALL) 588 nums = not_digit.sub(' ', s).strip().split() 589 if not nums: 590 continue 591 ints = numpy.array([int(x) for x in nums]) 592 lows = numpy.array([lows, ints]).min(axis=0) 593 highs = numpy.array([highs, ints]).max(axis=0) 594 595 minmax = ' '.join(['%d-%d' % (lows[i], highs[i]) for i in range(3)]) 596 print(s.strip() + '\tmin-max: ' + minmax) 597 time.sleep(0.1) 598 599 600class TcpServer: 601 """ 602 603 604 """ 605 def __init__(self, walt, port=50007, host=''): 606 self.running = threading.Event() 607 self.paused = threading.Event() 608 self.net = None 609 self.walt = walt 610 self.port = port 611 self.host = host 612 self.last_zero = 0. 613 614 def ser2net(self, data): 615 print('w>: ' + repr(data)) 616 return data 617 618 def net2ser(self, data): 619 print('w<: ' + repr(data)) 620 # Discard any empty data 621 if not data or len(data) == 0: 622 print('o<: discarded empty data') 623 return 624 625 # Get a string version of the data for checking longer commands 626 s = data.decode(self.walt.encoding) 627 bridge_command = None 628 while len(s) > 0: 629 if not bridge_command: 630 bridge_command = re.search(r'bridge (sync|update)', s) 631 # If a "bridge" command does not exist, send everything to the WALT 632 if not bridge_command: 633 self.walt.ser.write(s.encode(self.walt.encoding)) 634 break 635 # If a "bridge" command is preceded by WALT commands, send those 636 # first 637 if bridge_command.start() > 0: 638 before_command = s[:bridge_command.start()] 639 log('found bridge command after "%s"' % before_command) 640 s = s[bridge_command.start():] 641 self.walt.ser.write(before_command.encode(self.walt.encoding)) 642 continue 643 # Otherwise, reply directly to the command 644 log('bridge command: %s, pausing ser2net thread...' % 645 bridge_command.group(0)) 646 self.pause() 647 is_sync = bridge_command.group(1) == 'sync' or not self.walt.base_time 648 if is_sync: 649 self.walt.zero_clock() 650 651 self.walt.estimate_lag() 652 if is_sync: 653 # shift the base so that min_lag is 0 654 self.walt.base_time += self.walt.min_lag 655 self.walt.max_lag -= self.walt.min_lag 656 self.walt.min_lag = 0 657 658 min_lag = self.walt.min_lag * 1e6 659 max_lag = self.walt.max_lag * 1e6 660 # Send the time difference between now and when the clock was zeroed 661 dt0 = (time.time() - self.walt.base_time) * 1e6 662 reply = 'clock %d %d %d\n' % (dt0, min_lag, max_lag) 663 self.net.sendall(reply) 664 print('|custom-reply>: ' + repr(reply)) 665 self.resume() 666 s = s[bridge_command.end():] 667 bridge_command = None 668 669 def connections_loop(self): 670 with contextlib.closing(socket.socket( 671 socket.AF_INET, socket.SOCK_STREAM)) as sock: 672 self.sock = sock 673 # SO_REUSEADDR is supposed to prevent the "Address already in use" error 674 sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) 675 sock.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1) 676 sock.bind((self.host, self.port)) 677 sock.listen(1) 678 while True: 679 print('Listening on port %d' % self.port) 680 net, addr = sock.accept() 681 self.net = net 682 try: 683 print('Connected by: ' + str(addr)) 684 self.net2ser_loop() 685 except socket.error as e: 686 # IO errors with the socket, not sure what they are 687 print('Error: %s' % e) 688 break 689 finally: 690 net.close() 691 self.net = None 692 693 def net2ser_loop(self): 694 while True: 695 data = self.net.recv(1024) 696 if not data: 697 break # got disconnected 698 self.net2ser(data) 699 700 def ser2net_loop(self): 701 while True: 702 self.running.wait() 703 data = self.walt.readline() 704 if self.net and self.running.is_set(): 705 data = self.ser2net(data) 706 data = data.encode(self.walt.encoding) 707 self.net.sendall(data) 708 if not self.running.is_set(): 709 self.paused.set() 710 711 def serve(self): 712 t = self.ser2net_thread = threading.Thread( 713 target=self.ser2net_loop, 714 name='ser2net_thread' 715 ) 716 t.daemon = True 717 t.start() 718 self.paused.clear() 719 self.running.set() 720 self.connections_loop() 721 722 def pause(self): 723 """ Pause serial -> net forwarding 724 725 The ser2net_thread stays running, but won't read any incoming data 726 from the serial port. 727 """ 728 729 self.running.clear() 730 # Send a ping to break out of the blocking read on serial port and get 731 # blocked on running.wait() instead. The ping response is discarded. 732 self.walt.ser.write(Walt.CMD_PING) 733 # Wait until the ping response comes in and we are sure we are no longer 734 # blocked on ser.read() 735 self.paused.wait() 736 print("Paused ser2net thread") 737 738 def resume(self): 739 self.running.set() 740 self.paused.clear() 741 print("Resuming ser2net thread") 742 743 def close(self): 744 try: 745 self.sock.close() 746 except: 747 pass 748 749 try: 750 self.walt.close() 751 except: 752 pass 753 754 def __exit__(self, exc_type, exc_value, traceback): 755 self.close() 756 757 def __enter__(self): 758 return self 759 760 761def run_tcp_bridge(args): 762 763 print('Starting TCP bridge') 764 print('You may need to run the following to allow traffic from the android container:') 765 print('iptables -A INPUT -p tcp --dport %d -j ACCEPT' % args.port) 766 767 try: 768 with Walt(args.serial) as walt: 769 with TcpServer(walt, port=args.port) as srv: 770 walt.sndrcv(Walt.CMD_RESET) 771 srv.serve() 772 except KeyboardInterrupt: 773 print(' KeyboardInterrupt, exiting...') 774 775 776def main(argv=sys.argv[1:]): 777 args = parse_args(argv) 778 if args.type == 'drag': 779 run_drag_latency_test(args) 780 if args.type == 'tap': 781 run_tap_latency_test(args) 782 elif args.type == 'screen': 783 run_screen_latency_test(args) 784 elif args.type == 'sanity': 785 run_walt_sanity_test(args) 786 elif args.type == 'curve': 787 run_screen_curve(args) 788 elif args.type == 'bridge': 789 run_tcp_bridge(args) 790 elif args.type == 'tapaudio': 791 run_tap_audio_test(args) 792 elif args.type == 'tapblink': 793 run_tap_blink_test(args) 794 else: 795 print('Unknown test type: "%s"' % args.type) 796 797 798if __name__ == '__main__': 799 main() 800