Home | History | Annotate | Download | only in firmware_TouchMTB
      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 
      7 import os
      8 import re
      9 import shutil
     10 import sys
     11 import time
     12 
     13 import common_util
     14 import test_conf as conf
     15 
     16 from firmware_constants import GV, MODE, OPTIONS
     17 
     18 
     19 # Define the robot control script names.
     20 SCRIPT_NOISE = 'generate_noise.py'
     21 SCRIPT_LINE = 'line.py'
     22 SCRIPT_TAP = 'tap.py'
     23 SCRIPT_CLICK = 'click.py'
     24 SCRIPT_ONE_STATIONARY_FINGER = 'one_stationary_finger.py'
     25 SCRIPT_STATIONARY_WITH_TAPS = 'stationary_finger_with_taps_around_it.py'
     26 SCRIPT_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.
     30 SCRIPT_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.
     37 START = 0.1
     38 CENTER = 0.5
     39 END = 0.9
     40 OFF_START = -0.05
     41 OFF_END = 1.05
     42 ABOVE_CENTER = 0.25
     43 BELOW_CENTER = 0.75
     44 LEFT_TO_CENTER = 0.25
     45 RIGHT_TO_CENTER = 0.75
     46 
     47 OUTER_PINCH_SPACING = 70
     48 INNER_PINCH_SPACING = 25
     49 
     50 PHYSICAL_CLICK_SPACING = 20
     51 PHYSICAL_CLICK_FINGER_SIZE = 2
     52 
     53 TWO_CLOSE_FINGER_SPACING = 17
     54 TWO_FINGER_SPACING = 25
     55 
     56 class RobotWrapperError(Exception):
     57     """An exception class for the robot_wrapper module."""
     58     pass
     59 
     60 
     61 class 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