• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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