1# Copyright (c) 2013 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"""A wrapper for robot manipulation with Touchbot II.""" 6 7import os 8import re 9import shutil 10import sys 11import time 12 13import common_util 14import test_conf as conf 15 16from firmware_constants import GV, MODE, OPTIONS 17 18 19# Define the robot control script names. 20SCRIPT_NOISE = 'generate_noise.py' 21SCRIPT_LINE = 'line.py' 22SCRIPT_TAP = 'tap.py' 23SCRIPT_CLICK = 'click.py' 24SCRIPT_ONE_STATIONARY_FINGER = 'one_stationary_finger.py' 25SCRIPT_STATIONARY_WITH_TAPS = 'stationary_finger_with_taps_around_it.py' 26SCRIPT_QUICKSTEP = 'quickstep.py' 27 28# A script to "reset" the robot by having it move safely to a spot just above 29# the center of the touchpad. 30SCRIPT_RESET = 'reset.py' 31 32# Define constants for coordinates. 33# Normally, a gesture is performed within [START, END]. 34# For tests involved with RangeValidator which intends to verify 35# the min/max reported coordinates, use [OFF_START, OFF_END] instead 36# so that the gestures are performed off the edge. 37START = 0.1 38CENTER = 0.5 39END = 0.9 40OFF_START = -0.05 41OFF_END = 1.05 42ABOVE_CENTER = 0.25 43BELOW_CENTER = 0.75 44LEFT_TO_CENTER = 0.25 45RIGHT_TO_CENTER = 0.75 46 47OUTER_PINCH_SPACING = 70 48INNER_PINCH_SPACING = 25 49 50PHYSICAL_CLICK_SPACING = 20 51PHYSICAL_CLICK_FINGER_SIZE = 2 52 53TWO_CLOSE_FINGER_SPACING = 17 54TWO_FINGER_SPACING = 25 55 56class RobotWrapperError(Exception): 57 """An exception class for the robot_wrapper module.""" 58 pass 59 60 61class RobotWrapper: 62 """A class to wrap and manipulate the robot library.""" 63 64 def __init__(self, board, options): 65 self._board = board 66 self._mode = options[OPTIONS.MODE] 67 self._is_touchscreen = options[OPTIONS.TOUCHSCREEN] 68 self._fngenerator_only = options[OPTIONS.FNGENERATOR] 69 self._robot_script_dir = self._get_robot_script_dir() 70 self._noise_script_dir = os.path.join(self._get_robot_script_dir(), 71 SCRIPT_NOISE) 72 73 # Each get_control_command method maps to a script name. 74 self._robot_script_name_dict = { 75 self._get_control_command_line: SCRIPT_LINE, 76 self._get_control_command_rapid_taps: SCRIPT_TAP, 77 self._get_control_command_single_tap: SCRIPT_TAP, 78 self._get_control_command_drumroll: SCRIPT_TAP, 79 self._get_control_command_click: SCRIPT_CLICK, 80 self._get_control_command_one_stationary_finger: 81 SCRIPT_ONE_STATIONARY_FINGER, 82 self._get_control_command_pinch: SCRIPT_LINE, 83 self._get_control_command_stationary_with_taps: 84 SCRIPT_STATIONARY_WITH_TAPS, 85 self._get_control_command_quickstep: SCRIPT_QUICKSTEP, 86 } 87 88 # Each gesture maps to a get_control_command method 89 self._method_of_control_command_dict = { 90 conf.NOISE_LINE: self._get_control_command_line, 91 conf.NOISE_STATIONARY: self._get_control_command_single_tap, 92 conf.NOISE_STATIONARY_EXTENDED: self._get_control_command_single_tap, 93 conf.ONE_FINGER_TRACKING: self._get_control_command_line, 94 conf.ONE_FINGER_TO_EDGE: self._get_control_command_line, 95 conf.ONE_FINGER_SWIPE: self._get_control_command_line, 96 conf.ONE_FINGER_TAP: self._get_control_command_single_tap, 97 conf.ONE_FINGER_TRACKING_FROM_CENTER: 98 self._get_control_command_line, 99 conf.RAPID_TAPS: self._get_control_command_rapid_taps, 100 conf.TWO_FINGER_TRACKING: self._get_control_command_line, 101 conf.TWO_FINGER_SWIPE: self._get_control_command_line, 102 conf.TWO_FINGER_TAP: self._get_control_command_single_tap, 103 conf.TWO_CLOSE_FINGERS_TRACKING: self._get_control_command_line, 104 conf.RESTING_FINGER_PLUS_2ND_FINGER_MOVE: 105 self._get_control_command_one_stationary_finger, 106 conf.FIRST_FINGER_TRACKING_AND_SECOND_FINGER_TAPS: 107 self._get_control_command_one_stationary_finger, 108 conf.FINGER_CROSSING: 109 self._get_control_command_one_stationary_finger, 110 conf.PINCH_TO_ZOOM: self._get_control_command_pinch, 111 conf.DRUMROLL: self._get_control_command_drumroll, 112 conf.TWO_FAT_FINGERS_TRACKING: self._get_control_command_line, 113 conf.ONE_FINGER_PHYSICAL_CLICK: self._get_control_command_click, 114 conf.TWO_FINGER_PHYSICAL_CLICK: self._get_control_command_click, 115 conf.THREE_FINGER_PHYSICAL_CLICK: self._get_control_command_click, 116 conf.FOUR_FINGER_PHYSICAL_CLICK: self._get_control_command_click, 117 conf.STATIONARY_FINGER_NOT_AFFECTED_BY_2ND_FINGER_TAPS: 118 self._get_control_command_stationary_with_taps, 119 conf.FAT_FINGER_MOVE_WITH_RESTING_FINGER: 120 self._get_control_command_one_stationary_finger, 121 conf.DRAG_LATENCY: 122 self._get_control_command_quickstep, 123 } 124 125 self._line_dict = { 126 GV.LR: (START, CENTER, END, CENTER), 127 GV.RL: (END, CENTER, START, CENTER), 128 GV.TB: (CENTER, START, CENTER, END), 129 GV.BT: (CENTER, END, CENTER, START), 130 GV.BLTR: (START, END, END, START), 131 GV.TRBL: (END, START, START, END), 132 GV.BRTL: (END, END, START, START), 133 GV.TLBR: (START, START, END, END), 134 GV.CUR: (CENTER, CENTER, END, START), 135 GV.CUL: (CENTER, CENTER, START, START), 136 GV.CLR: (CENTER, CENTER, END, END), 137 GV.CLL: (CENTER, CENTER, START, END), 138 139 # Overshoot for this one-finger gesture only: ONE_FINGER_TO_EDGE 140 GV.CL: (CENTER, CENTER, OFF_START, CENTER), 141 GV.CR: (CENTER, CENTER, OFF_END, CENTER), 142 GV.CT: (CENTER, CENTER, CENTER, OFF_START), 143 GV.CB: (CENTER, CENTER, CENTER, OFF_END), 144 } 145 146 # The angle wrt the pad that the fingers should take when doing a 2f 147 # gesture along these lines, or doing 2f taps at different angles. 148 self._angle_dict = { 149 GV.LR: -45, 150 GV.RL: -45, 151 GV.TB: 45, 152 GV.BT: 45, 153 GV.TLBR: 90, 154 GV.BLTR: 0, 155 GV.TRBL: 0, 156 GV.HORIZONTAL: -45, 157 GV.VERTICAL: 45, 158 GV.DIAGONAL: 0, 159 } 160 161 # The stationary finger locations corresponding the line specifications 162 # for the finger crossing tests 163 self._stationary_finger_dict = { 164 GV.LR: (CENTER, ABOVE_CENTER), 165 GV.RL: (CENTER, BELOW_CENTER), 166 GV.TB: (RIGHT_TO_CENTER, CENTER), 167 GV.BT: (LEFT_TO_CENTER, CENTER), 168 GV.BLTR: (RIGHT_TO_CENTER, BELOW_CENTER), 169 GV.TRBL: (LEFT_TO_CENTER, ABOVE_CENTER), 170 } 171 172 self._speed_dict = { 173 GV.SLOW: 10, 174 GV.NORMAL: 20, 175 GV.FAST: 30, 176 GV.FULL_SPEED: 100, 177 } 178 179 # The frequencies of noise in Hz. 180 self._frequency_dict = { 181 GV.LOW_FREQUENCY: 5000, # 5kHz 182 GV.MED_FREQUENCY: 500000, # 500kHz 183 GV.HIGH_FREQUENCY: 1000000, # 1MHz 184 } 185 # Add the list of extended frequency values to the dict. 186 freq_value_dict = {freq: int(freq.replace('Hz', '')) for freq in GV.EXTENDED_FREQUENCIES} 187 self._frequency_dict = dict(self._frequency_dict.items() + freq_value_dict.items()) 188 189 # The waveforms of noise. 190 self._waveform_dict = { 191 GV.SQUARE_WAVE: 'SQUARE', 192 GV.SINE_WAVE: 'SINE', 193 } 194 195 # The amplitude of noise in Vpp. 196 self._amplitude_dict = { 197 GV.HALF_AMPLITUDE: 10, 198 GV.MAX_AMPLITUDE: 20, 199 } 200 201 self._location_dict = { 202 # location parameters for one-finger taps 203 GV.TL: (START, START), 204 GV.TR: (END, START), 205 GV.BL: (START, END), 206 GV.BR: (END, END), 207 GV.TS: (CENTER, START), 208 GV.BS: (CENTER, END), 209 GV.LS: (START, CENTER), 210 GV.RS: (END, CENTER), 211 GV.CENTER: (CENTER, CENTER), 212 } 213 214 self.fingertips = [None, None, None, None] 215 216 self._build_robot_script_paths() 217 218 self._get_device_spec() 219 220 def _get_device_spec(self): 221 if not self.is_robot_action_mode(): 222 return 223 224 # First check if there is already a device spec in this directory 225 if (os.path.isfile('%s.p' % self._board) and 226 os.path.isfile('%s_min.p' % self._board)): 227 return 228 229 # Next, check if maybe there is a spec in the touchbotII directory 230 spec_path = os.path.join(self._robot_script_dir, 231 'device_specs/%s.p' % self._board) 232 spec_min_path = os.path.join(self._robot_script_dir, 233 'device_specs/%s_min.p' % self._board) 234 if os.path.isfile(spec_path) and os.path.isfile(spec_min_path): 235 shutil.copy(spec_path, '.') 236 shutil.copy(spec_min_path, '.') 237 return 238 239 # If both of those fail, then generate a new device spec 240 self._calibrate_device(self._board) 241 242 def _get_fingertips(self, tips_to_get): 243 if self.fingertips != [None, None, None, None]: 244 print 'Error, there are still fingertips on' 245 sys.exit(1) 246 247 for size in conf.ALL_FINGERTIP_SIZES: 248 fingers = [1 if f == size else 0 for f in tips_to_get] 249 print 'size: %d\tfingers: %s' % (size, str(fingers)) 250 if fingers == [0, 0, 0, 0]: 251 continue 252 253 script = os.path.join(self._robot_script_dir, 254 'manipulate_fingertips.py') 255 para = (script, fingers[0], fingers[1], fingers[2], fingers[3], 256 size, str(True)) 257 cmd = 'python %s %d %d %d %d %d %s' % para 258 259 if self._execute_control_command(cmd): 260 print 'Error getting the figertips!' 261 print 'Please make sure all the fingertips are in the correct' 262 print 'positions before continuing.' 263 print 'WARNING: failure to do this correctly could cause' 264 print ' permanent damage to the robot!' 265 print 266 print 'The fingers it was attempting to get were as follows:' 267 print tips_to_get 268 print '(Starting with the front right finger and going counter' 269 print 'clockwise from there. Finger sizes are 0-3)' 270 self._wait_for_user_input('GO', 'ABORT') 271 272 self.fingertips = tips_to_get 273 274 275 def _return_fingertips(self): 276 """ Return all the fingertips to the nest, one size at a time. 277 This function uses the self.fingertips member variable to know which 278 finger has which tip size, then returns them all to the nest 279 """ 280 for size in conf.ALL_FINGERTIP_SIZES: 281 # See which (if any) of the fingers currently have this size tip 282 fingers = [1 if f == size else 0 for f in self.fingertips] 283 if fingers == [0, 0, 0, 0]: 284 continue 285 286 script = os.path.join(self._robot_script_dir, 287 'manipulate_fingertips.py') 288 para = (script, fingers[0], fingers[1], fingers[2], fingers[3], 289 size, str(False)) 290 cmd = 'python %s %d %d %d %d %d %s' % para 291 292 if self._execute_control_command(cmd): 293 print 'Error returning the figertips!' 294 print 'Please make sure all the fingertips are in their correct' 295 print 'spots in the nest before continuing.' 296 print 'WARNING: failure to do this correctly could cause' 297 print ' permanent damage to the robot!' 298 self._wait_for_user_input('GO', 'ABORT') 299 300 self.fingertips = [None, None, None, None] 301 302 def _wait_for_user_input(self, continue_cmd, stop_cmd): 303 user_input = None 304 while user_input not in [continue_cmd, stop_cmd]: 305 if user_input: 306 print 'Sorry, but "%s" was incorrect' % user_input 307 user_input = raw_input('Type "%s" to continue or "%s" to stop: ' % 308 (continue_cmd, stop_cmd)) 309 if user_input == stop_cmd: 310 raise RobotWrapperError('Operator aborted after error') 311 312 def _calibrate_device(self, board): 313 """ Have the operator show the robot where the device is.""" 314 calib_script = os.path.join(self._robot_script_dir, 315 'calibrate_for_new_device.py') 316 calib_cmd = 'python %s %s' % (calib_script, board) 317 self._execute_control_command(calib_cmd) 318 319 def is_manual_noise_test_mode(self): 320 return (self._mode in [MODE.NOISE, MODE.COMPLETE] 321 and self._fngenerator_only) 322 323 def is_robot_action_mode(self): 324 """Is it in robot action mode? 325 326 In the robot action mode, it actually invokes the robot control script. 327 """ 328 if self.is_manual_noise_test_mode(): 329 return False 330 331 return self._mode in [MODE.ROBOT, MODE.QUICKSTEP, MODE.NOISE] 332 333 def _raise_error(self, msg): 334 """Only raise an error if it is in the robot action mode.""" 335 if self.is_robot_action_mode(): 336 raise RobotWrapperError(msg) 337 338 def _get_robot_script_dir(self): 339 """Get the directory of the robot control scripts.""" 340 for lib_path in [conf.robot_lib_path, conf.robot_lib_path_local]: 341 cmd = ('find %s -maxdepth 1 -type d -name %s' % 342 (lib_path, conf.python_package)) 343 path = common_util.simple_system_output(cmd) 344 if path: 345 robot_script_dir = os.path.join(path, conf.gestures_sub_path) 346 if os.path.isdir(robot_script_dir): 347 return robot_script_dir 348 return '' 349 350 def _get_num_taps(self, gesture): 351 """Determine the number of times to tap.""" 352 matches = re.match('[^0-9]*([0-9]*)[^0-9]*', gesture) 353 return int(matches.group(1)) if matches else None 354 355 def _reverse_coord_if_is_touchscreen(self, coordinates): 356 """Reverse the coordinates if the device is a touchscreen. 357 358 E.g., the original coordinates = (0.1, 0.9) 359 After reverse, the coordinates = (1 - 0.1, 1 - 0.9) = (0.9, 0.1) 360 361 @param coordinates: a tuple of coordinates 362 """ 363 return (tuple(1.0 - c for c in coordinates) if self._is_touchscreen else 364 coordinates) 365 366 def _get_control_command_pinch(self, robot_script, gesture, variation): 367 # Depending on which direction you're zooming, change the order of the 368 # finger spacings. 369 if GV.ZOOM_IN in variation: 370 starting_spacing = INNER_PINCH_SPACING 371 ending_spacing = OUTER_PINCH_SPACING 372 else: 373 starting_spacing = OUTER_PINCH_SPACING 374 ending_spacing = INNER_PINCH_SPACING 375 376 # Keep the hand centered on the pad, and make the fingers move 377 # in or out with only two opposing fingers on the pad. 378 para = (robot_script, self._board, 379 CENTER, CENTER, 45, starting_spacing, 380 CENTER, CENTER, 45, ending_spacing, 381 0, 1, 0, 1, self._speed_dict[GV.SLOW], 'basic') 382 return 'python %s %s_min.p %f %f %d %d %f %f %d %d %d %d %d %d %f %s' % para 383 384 def _get_control_command_quickstep(self, robot_script, gesture, variation): 385 """have the robot do the zig-zag gesture for latency testing.""" 386 para = (robot_script, self._board, self._speed_dict[GV.FULL_SPEED]) 387 return 'python %s %s_min.p %d' % para 388 389 390 def _dimensions(self, device_spec): 391 device_script = os.path.join(self._robot_script_dir, 'device_size.py') 392 cmd = 'python %s %s' % (device_script, device_spec) 393 results = common_util.simple_system_output(cmd) 394 dimensions = results.split() 395 return float(dimensions[0]), float(dimensions[1]) 396 397 def _adjust_for_target_distance(self, line, stationary_finger, distance): 398 """ Given a point and a line in relative coordinates move the point 399 so that is exactly 'distance' millimeters away from the line for the 400 current board. Sometimes the robot needs to move two fingers very 401 close and this is problematic in relative coordinates as the board 402 dimensions change. This function allows the test to compensate and 403 get a more accurate test. 404 """ 405 # First convert all the values into absolute coordinates by using 406 # the calibrated device spec to see the dimensions of the pad 407 h, w = self._dimensions("%s_min.p" % self._board) 408 abs_line = (line[0] * w, line[1] * h, line[2] * w, line[3] * h) 409 x, y = (stationary_finger[0] * w, stationary_finger[1] * h) 410 411 # Find a point near the stationary finger that is distance 412 # away from the line at the closest point 413 dx = abs_line[2] - abs_line[0] 414 dy = abs_line[3] - abs_line[1] 415 if dx == 0: # vertical line 416 if x > abs_line[0]: 417 x = abs_line[0] + distance 418 else: 419 x = abs_line[0] - distance 420 elif dy == 0: # horizontal line 421 if y > abs_line[1]: 422 y = abs_line[1] + distance 423 else: 424 y = abs_line[1] - distance 425 else: 426 # First, find the closest point on the line to the point 427 m = dy / dx 428 b = abs_line[1] - m * abs_line[0] 429 m_perp = -1.0 / m 430 b_perp = y - m_perp * x 431 x_intersect = (b_perp - b) / (m - m_perp) 432 y_intersect = m * x_intersect + b 433 434 current_distance_sq = ((x_intersect - x) ** 2 + 435 (y_intersect - y) ** 2) 436 scale = distance / (current_distance_sq ** 0.5) 437 x = x_intersect - (x_intersect - x) * scale 438 y = y_intersect - (y_intersect - y) * scale 439 440 #Convert this absolute point back into relative coordinates 441 x_rel = x / w 442 y_rel = y / h 443 444 return (x_rel, y_rel) 445 446 def _get_control_command_one_stationary_finger(self, robot_script, gesture, 447 variation): 448 line = speed = finger_gap_mm = None 449 num_taps = 0 450 # The stationary finger should be in the bottom left corner for resting 451 # finger tests, and various locations for finger crossing tests 452 stationary_finger = (START, END) 453 454 for element in variation: 455 if element in GV.GESTURE_DIRECTIONS: 456 line = self._line_dict[element] 457 if 'finger_crossing' in gesture: 458 stationary_finger = self._stationary_finger_dict[element] 459 finger_gap_mm = conf.FINGER_CROSSING_GAP_MM 460 elif 'second_finger_taps' in gesture: 461 stationary_finger = self._location_dict[GV.BL] 462 speed = self._speed_dict[GV.SLOW] 463 num_taps = 3 464 elif 'fat_finger' in gesture: 465 stationary_finger = self._stationary_finger_dict[element] 466 speed = self._speed_dict[GV.SLOW] 467 finger_gap_mm = conf.FAT_FINGER_AND_STATIONARY_FINGER_GAP_MM 468 elif element in GV.GESTURE_SPEED: 469 speed = self._speed_dict[element] 470 471 if line is None or speed is None: 472 msg = 'Cannot derive the line/speed parameters from %s %s.' 473 self._raise_error(msg % (gesture, variation)) 474 475 # Adjust the positioning to get a precise gap between finger tips 476 # if this gesture requires it 477 if finger_gap_mm: 478 min_space_mm = (conf.FINGERTIP_DIAMETER_MM[self.fingertips[0]] + 479 conf.FINGERTIP_DIAMETER_MM[self.fingertips[2]]) / 2 480 stationary_finger = self._adjust_for_target_distance( 481 line, stationary_finger, 482 min_space_mm + finger_gap_mm) 483 484 line = self._reverse_coord_if_is_touchscreen(line) 485 start_x, start_y, end_x, end_y = line 486 stationary_x, stationary_y = stationary_finger 487 488 para = (robot_script, self._board, stationary_x, stationary_y, 489 start_x, start_y, end_x, end_y, speed, num_taps) 490 cmd = 'python %s %s_min.p %f %f %f %f %f %f %s --taps=%d' % para 491 return cmd 492 493 def _get_control_command_line(self, robot_script, gesture, variation): 494 """Get robot control command for gestures using robot line script.""" 495 line_type = 'swipe' if bool('swipe' in gesture) else 'basic' 496 line = speed = finger_angle = None 497 for element in variation: 498 if element in GV.GESTURE_DIRECTIONS: 499 line = self._line_dict[element] 500 finger_angle = self._angle_dict.get(element, None) 501 elif element in GV.GESTURE_SPEED: 502 speed = self._speed_dict[element] 503 504 if not speed: 505 if line_type is 'swipe': 506 speed = self._speed_dict[GV.FAST] 507 if 'two_close_fingers' in gesture or 'fat' in gesture: 508 speed = self._speed_dict[GV.NORMAL] 509 510 if line is None or speed is None: 511 msg = 'Cannot derive the line/speed parameters from %s %s.' 512 self._raise_error(msg % (gesture, variation)) 513 514 line = self._reverse_coord_if_is_touchscreen(line) 515 start_x, start_y, end_x, end_y = line 516 517 if 'two' in gesture: 518 if 'close_fingers' in gesture: 519 finger_spacing = TWO_CLOSE_FINGER_SPACING 520 finger_angle += 45 521 fingers = (1, 1, 0, 0) 522 else: 523 finger_spacing = TWO_FINGER_SPACING 524 fingers = (0, 1, 0, 1) 525 526 if finger_angle is None: 527 msg = 'Unable to determine finger angle for %s %s.' 528 self._raise_error(msg % (gesture, variation)) 529 else: 530 finger_spacing = 17 531 fingers = (0, 1, 0, 0) 532 finger_angle = 0 533 534 # Generate the CLI command 535 para = (robot_script, self._board, 536 start_x, start_y, finger_angle, finger_spacing, 537 end_x, end_y, finger_angle, finger_spacing, 538 fingers[0], fingers[1], fingers[2], fingers[3], 539 speed, line_type) 540 cmd = 'python %s %s.p %f %f %d %d %f %f %d %d %d %d %d %d %f %s' % para 541 542 if self._get_noise_command(gesture, variation): 543 # A one second pause to give the touchpad time to calibrate 544 DELAY = 1 # 1 seconds 545 cmd = '%s %d' % (cmd, DELAY) 546 547 return cmd 548 549 def _get_control_command_stationary_with_taps(self, robot_script, gesture, 550 variation): 551 """ There is only one variant of this gesture, so there is only one 552 command to generate. This is the command for tapping around a 553 stationary finger on the pad. 554 """ 555 return 'python %s %s.p 0.5 0.5' % (robot_script, self._board) 556 557 def _get_control_command_rapid_taps(self, robot_script, gesture, variation): 558 num_taps = self._get_num_taps(gesture) 559 return self._get_control_command_taps(robot_script, gesture, 560 variation, num_taps) 561 562 def _get_control_command_single_tap(self, robot_script, gesture, variation): 563 # Get the single tap command 564 cmd = self._get_control_command_taps(robot_script, gesture, variation, 1) 565 566 if self._get_noise_command(gesture, variation): 567 # Add the noise command and a pause to the tap 568 TEST_DURATION = 3 # 3 seconds 569 cmd = '%s %d' % (cmd, TEST_DURATION) 570 571 return cmd 572 573 def _get_control_command_drumroll(self, robot_script, gesture, variation): 574 """Get robot control command for the drumroll gesture. There is only 575 one so there is no need for complicated parsing here. 576 """ 577 return ('python %s %s.p 0.5 0.5 45 20 0 1 0 1 50 drumroll' % 578 (robot_script, self._board)) 579 580 def _get_control_command_taps(self, robot_script, gesture, 581 variation, num_taps): 582 """Get robot control command for tap gestures. This includes rapid tap 583 tests as well as 1 and 2 finger taps at various locations on the pad. 584 """ 585 if num_taps is None: 586 msg = 'Cannot determine the number of taps to do from %s.' 587 self._raise_error(msg % gesture) 588 589 # The tap commands have identical arguments as the click except with 590 # two additional arguments at the end. As such we generate the 'click' 591 # command and add these on to make it work as a tap. 592 cmd = self._get_control_command_click_tap(robot_script, gesture, 593 variation) 594 control_cmd = '%s %d tap' % (cmd, num_taps) 595 return control_cmd 596 597 def _get_control_command_click(self, robot_script, gesture, variation): 598 """Get the robot control command for a physical click gesture""" 599 cmd = self._get_control_command_click_tap(robot_script, gesture, 600 variation) 601 control_cmd = '%s %d' % (cmd, PHYSICAL_CLICK_FINGER_SIZE) 602 return control_cmd 603 604 def _get_control_command_click_tap(self, robot_script, gesture, variation): 605 """Get robot control arguments that are common between pysical click 606 and tapping gestures 607 """ 608 location = None 609 angle = 45 610 for element in variation: 611 if element in self._location_dict: 612 location = self._location_dict[element] 613 if 'two' in gesture and element in self._angle_dict: 614 angle = self._angle_dict[element] 615 616 # All non-one finger taps are simply perfomed in the middle of the pad 617 if 'one' not in gesture and location is None: 618 location = self._location_dict[GV.CENTER] 619 620 if location is None: 621 msg = 'Cannot determine the location parameters from %s %s.' 622 self._raise_error(msg % (gesture, variation)) 623 624 fingers = [1, 0, 0, 0] 625 if 'two' in gesture: 626 fingers = [0, 1, 0, 1] 627 elif 'three' in gesture: 628 fingers = [0, 1, 1, 1] 629 elif 'four' in gesture: 630 fingers = [1, 1, 1, 1] 631 632 location_str = ' '.join( 633 map(str, self._reverse_coord_if_is_touchscreen(location))) 634 635 para = (robot_script, self._board, location_str, angle, 636 PHYSICAL_CLICK_SPACING, 637 fingers[0], fingers[1], fingers[2], fingers[3]) 638 control_cmd = 'python %s %s.p %s %d %d %d %d %d %d' % para 639 return control_cmd 640 641 def _build_robot_script_paths(self): 642 """Build the robot script paths.""" 643 # Check if the robot script dir could be found. 644 if not self._robot_script_dir: 645 script_path = os.path.join(conf.robot_lib_path, conf.python_package, 646 conf.gestures_sub_path) 647 msg = 'Cannot find robot script directory in "%s".' 648 self._raise_error(msg % script_path) 649 650 # Build the robot script path dictionary 651 self._robot_script_dict = {} 652 for method in self._robot_script_name_dict: 653 script_name = self._robot_script_name_dict.get(method) 654 655 # Check if the control script actually exists. 656 robot_script = os.path.join(self._robot_script_dir, script_name) 657 if not os.path.isfile(robot_script): 658 msg = 'Cannot find the robot control script: %s' 659 self._raise_error(msg % robot_script) 660 661 self._robot_script_dict[method] = robot_script 662 663 def _get_control_command(self, gesture, variation): 664 """Get robot control command based on the gesture and variation.""" 665 script_method = self._method_of_control_command_dict.get(gesture) 666 if not script_method: 667 self._raise_error('Cannot find "%s" gesture in ' 668 '_method_of_control_command_dict.' % gesture) 669 670 robot_script = self._robot_script_dict.get(script_method) 671 if not robot_script: 672 msg = 'Cannot find "%s" method in _robot_script_dict.' 673 self._raise_error(msg % script_method) 674 675 return script_method(robot_script, gesture, variation) 676 677 def _execute_control_command(self, control_cmd): 678 """Execute a control command.""" 679 print 'Executing: "%s"' % control_cmd 680 if self.is_robot_action_mode(): 681 # Pausing to give everything time to settle 682 time.sleep(0.5) 683 return common_util.simple_system(control_cmd) 684 return 0 685 686 def _reset_with_safety_clearance(self, destination): 687 reset_script = os.path.join(self._robot_script_dir, SCRIPT_RESET) 688 para = (reset_script, self._board, destination, 689 self._speed_dict[GV.FAST]) 690 self._execute_control_command('python %s %s.p %s %d' % para) 691 692 def turn_off_noise(self): 693 off_cmd = 'python %s OFF' % self._noise_script_dir 694 common_util.simple_system(off_cmd) 695 696 def _execute_noise_command(self, noise_cmd): 697 if self.is_robot_action_mode() or self.is_manual_noise_test_mode(): 698 return common_util.simple_system(noise_cmd) 699 700 def _get_noise_command(self, gesture, variation): 701 if not gesture or not variation: 702 return None 703 704 waveform = frequency = amplitude = None 705 for element in variation: 706 if element.endswith('Hz'): 707 frequency = int(element[:-2]) 708 elif element in GV.NOISE_FREQUENCY: 709 frequency = self._frequency_dict[element] 710 elif element in GV.NOISE_WAVEFORM: 711 waveform = self._waveform_dict[element] 712 elif element in GV.NOISE_AMPLITUDE: 713 amplitude = self._amplitude_dict[element] 714 715 if waveform and frequency and amplitude: 716 return 'python %s %s %d %d' % (self._noise_script_dir, 717 waveform, frequency, amplitude) 718 return None 719 720 def configure_noise(self, gesture, variation): 721 if not gesture or not variation: 722 return None 723 724 if not isinstance(variation, tuple): 725 variation = (variation,) 726 727 noise_cmd = self._get_noise_command(gesture.name, variation) 728 if noise_cmd: 729 self._execute_noise_command(noise_cmd) 730 731 def control(self, gesture, variation): 732 """Have the robot perform the gesture variation.""" 733 tips_needed = conf.finger_tips_required[gesture.name] 734 if self.fingertips != tips_needed: 735 self._reset_with_safety_clearance('nest') 736 self._return_fingertips() 737 self._get_fingertips(tips_needed) 738 self._reset_with_safety_clearance('pad') 739 740 if not isinstance(variation, tuple): 741 variation = (variation,) 742 try: 743 print gesture.name, variation 744 control_cmd = self._get_control_command(gesture.name, variation) 745 self._execute_control_command(control_cmd) 746 747 except RobotWrapperError as e: 748 print gesture.name, variation 749 print 'RobotWrapperError: %s' % str(e) 750 sys.exit(1) 751