| # Copyright (c) 2013 The Chromium OS Authors. All rights reserved. |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| |
| """A wrapper for robot manipulation with Touchbot II.""" |
| |
| import os |
| import re |
| import shutil |
| import sys |
| import time |
| |
| import common_util |
| import test_conf as conf |
| |
| from firmware_constants import GV, MODE, OPTIONS |
| |
| |
| # Define the robot control script names. |
| SCRIPT_NOISE = 'generate_noise.py' |
| SCRIPT_LINE = 'line.py' |
| SCRIPT_TAP = 'tap.py' |
| SCRIPT_CLICK = 'click.py' |
| SCRIPT_ONE_STATIONARY_FINGER = 'one_stationary_finger.py' |
| SCRIPT_STATIONARY_WITH_TAPS = 'stationary_finger_with_taps_around_it.py' |
| SCRIPT_QUICKSTEP = 'quickstep.py' |
| |
| # A script to "reset" the robot by having it move safely to a spot just above |
| # the center of the touchpad. |
| SCRIPT_RESET = 'reset.py' |
| |
| # Define constants for coordinates. |
| # Normally, a gesture is performed within [START, END]. |
| # For tests involved with RangeValidator which intends to verify |
| # the min/max reported coordinates, use [OFF_START, OFF_END] instead |
| # so that the gestures are performed off the edge. |
| START = 0.1 |
| CENTER = 0.5 |
| END = 0.9 |
| OFF_START = -0.05 |
| OFF_END = 1.05 |
| ABOVE_CENTER = 0.25 |
| BELOW_CENTER = 0.75 |
| LEFT_TO_CENTER = 0.25 |
| RIGHT_TO_CENTER = 0.75 |
| |
| OUTER_PINCH_SPACING = 70 |
| INNER_PINCH_SPACING = 25 |
| |
| PHYSICAL_CLICK_SPACING = 20 |
| PHYSICAL_CLICK_FINGER_SIZE = 2 |
| |
| TWO_CLOSE_FINGER_SPACING = 17 |
| TWO_FINGER_SPACING = 25 |
| |
| class RobotWrapperError(Exception): |
| """An exception class for the robot_wrapper module.""" |
| pass |
| |
| |
| class RobotWrapper: |
| """A class to wrap and manipulate the robot library.""" |
| |
| def __init__(self, board, options): |
| self._board = board |
| self._mode = options[OPTIONS.MODE] |
| self._is_touchscreen = options[OPTIONS.TOUCHSCREEN] |
| self._fngenerator_only = options[OPTIONS.FNGENERATOR] |
| self._robot_script_dir = self._get_robot_script_dir() |
| self._noise_script_dir = os.path.join(self._get_robot_script_dir(), |
| SCRIPT_NOISE) |
| |
| # Each get_control_command method maps to a script name. |
| self._robot_script_name_dict = { |
| self._get_control_command_line: SCRIPT_LINE, |
| self._get_control_command_rapid_taps: SCRIPT_TAP, |
| self._get_control_command_single_tap: SCRIPT_TAP, |
| self._get_control_command_drumroll: SCRIPT_TAP, |
| self._get_control_command_click: SCRIPT_CLICK, |
| self._get_control_command_one_stationary_finger: |
| SCRIPT_ONE_STATIONARY_FINGER, |
| self._get_control_command_pinch: SCRIPT_LINE, |
| self._get_control_command_stationary_with_taps: |
| SCRIPT_STATIONARY_WITH_TAPS, |
| self._get_control_command_quickstep: SCRIPT_QUICKSTEP, |
| } |
| |
| # Each gesture maps to a get_control_command method |
| self._method_of_control_command_dict = { |
| conf.NOISE_LINE: self._get_control_command_line, |
| conf.NOISE_STATIONARY: self._get_control_command_single_tap, |
| conf.NOISE_STATIONARY_EXTENDED: self._get_control_command_single_tap, |
| conf.ONE_FINGER_TRACKING: self._get_control_command_line, |
| conf.ONE_FINGER_TO_EDGE: self._get_control_command_line, |
| conf.ONE_FINGER_SWIPE: self._get_control_command_line, |
| conf.ONE_FINGER_TAP: self._get_control_command_single_tap, |
| conf.ONE_FINGER_TRACKING_FROM_CENTER: |
| self._get_control_command_line, |
| conf.RAPID_TAPS: self._get_control_command_rapid_taps, |
| conf.TWO_FINGER_TRACKING: self._get_control_command_line, |
| conf.TWO_FINGER_SWIPE: self._get_control_command_line, |
| conf.TWO_FINGER_TAP: self._get_control_command_single_tap, |
| conf.TWO_CLOSE_FINGERS_TRACKING: self._get_control_command_line, |
| conf.RESTING_FINGER_PLUS_2ND_FINGER_MOVE: |
| self._get_control_command_one_stationary_finger, |
| conf.FIRST_FINGER_TRACKING_AND_SECOND_FINGER_TAPS: |
| self._get_control_command_one_stationary_finger, |
| conf.FINGER_CROSSING: |
| self._get_control_command_one_stationary_finger, |
| conf.PINCH_TO_ZOOM: self._get_control_command_pinch, |
| conf.DRUMROLL: self._get_control_command_drumroll, |
| conf.TWO_FAT_FINGERS_TRACKING: self._get_control_command_line, |
| conf.ONE_FINGER_PHYSICAL_CLICK: self._get_control_command_click, |
| conf.TWO_FINGER_PHYSICAL_CLICK: self._get_control_command_click, |
| conf.THREE_FINGER_PHYSICAL_CLICK: self._get_control_command_click, |
| conf.FOUR_FINGER_PHYSICAL_CLICK: self._get_control_command_click, |
| conf.STATIONARY_FINGER_NOT_AFFECTED_BY_2ND_FINGER_TAPS: |
| self._get_control_command_stationary_with_taps, |
| conf.FAT_FINGER_MOVE_WITH_RESTING_FINGER: |
| self._get_control_command_one_stationary_finger, |
| conf.DRAG_LATENCY: |
| self._get_control_command_quickstep, |
| } |
| |
| self._line_dict = { |
| GV.LR: (START, CENTER, END, CENTER), |
| GV.RL: (END, CENTER, START, CENTER), |
| GV.TB: (CENTER, START, CENTER, END), |
| GV.BT: (CENTER, END, CENTER, START), |
| GV.BLTR: (START, END, END, START), |
| GV.TRBL: (END, START, START, END), |
| GV.BRTL: (END, END, START, START), |
| GV.TLBR: (START, START, END, END), |
| GV.CUR: (CENTER, CENTER, END, START), |
| GV.CUL: (CENTER, CENTER, START, START), |
| GV.CLR: (CENTER, CENTER, END, END), |
| GV.CLL: (CENTER, CENTER, START, END), |
| |
| # Overshoot for this one-finger gesture only: ONE_FINGER_TO_EDGE |
| GV.CL: (CENTER, CENTER, OFF_START, CENTER), |
| GV.CR: (CENTER, CENTER, OFF_END, CENTER), |
| GV.CT: (CENTER, CENTER, CENTER, OFF_START), |
| GV.CB: (CENTER, CENTER, CENTER, OFF_END), |
| } |
| |
| # The angle wrt the pad that the fingers should take when doing a 2f |
| # gesture along these lines, or doing 2f taps at different angles. |
| self._angle_dict = { |
| GV.LR: -45, |
| GV.RL: -45, |
| GV.TB: 45, |
| GV.BT: 45, |
| GV.TLBR: 90, |
| GV.BLTR: 0, |
| GV.TRBL: 0, |
| GV.HORIZONTAL: -45, |
| GV.VERTICAL: 45, |
| GV.DIAGONAL: 0, |
| } |
| |
| # The stationary finger locations corresponding the line specifications |
| # for the finger crossing tests |
| self._stationary_finger_dict = { |
| GV.LR: (CENTER, ABOVE_CENTER), |
| GV.RL: (CENTER, BELOW_CENTER), |
| GV.TB: (RIGHT_TO_CENTER, CENTER), |
| GV.BT: (LEFT_TO_CENTER, CENTER), |
| GV.BLTR: (RIGHT_TO_CENTER, BELOW_CENTER), |
| GV.TRBL: (LEFT_TO_CENTER, ABOVE_CENTER), |
| } |
| |
| self._speed_dict = { |
| GV.SLOW: 10, |
| GV.NORMAL: 20, |
| GV.FAST: 30, |
| GV.FULL_SPEED: 100, |
| } |
| |
| # The frequencies of noise in Hz. |
| self._frequency_dict = { |
| GV.LOW_FREQUENCY: 5000, # 5kHz |
| GV.MED_FREQUENCY: 500000, # 500kHz |
| GV.HIGH_FREQUENCY: 1000000, # 1MHz |
| } |
| # Add the list of extended frequency values to the dict. |
| freq_value_dict = {freq: int(freq.replace('Hz', '')) for freq in GV.EXTENDED_FREQUENCIES} |
| self._frequency_dict = dict(self._frequency_dict.items() + freq_value_dict.items()) |
| |
| # The waveforms of noise. |
| self._waveform_dict = { |
| GV.SQUARE_WAVE: 'SQUARE', |
| GV.SINE_WAVE: 'SINE', |
| } |
| |
| # The amplitude of noise in Vpp. |
| self._amplitude_dict = { |
| GV.HALF_AMPLITUDE: 10, |
| GV.MAX_AMPLITUDE: 20, |
| } |
| |
| self._location_dict = { |
| # location parameters for one-finger taps |
| GV.TL: (START, START), |
| GV.TR: (END, START), |
| GV.BL: (START, END), |
| GV.BR: (END, END), |
| GV.TS: (CENTER, START), |
| GV.BS: (CENTER, END), |
| GV.LS: (START, CENTER), |
| GV.RS: (END, CENTER), |
| GV.CENTER: (CENTER, CENTER), |
| } |
| |
| self.fingertips = [None, None, None, None] |
| |
| self._build_robot_script_paths() |
| |
| self._get_device_spec() |
| |
| def _get_device_spec(self): |
| if not self.is_robot_action_mode(): |
| return |
| |
| # First check if there is already a device spec in this directory |
| if (os.path.isfile('%s.p' % self._board) and |
| os.path.isfile('%s_min.p' % self._board)): |
| return |
| |
| # Next, check if maybe there is a spec in the touchbotII directory |
| spec_path = os.path.join(self._robot_script_dir, |
| 'device_specs/%s.p' % self._board) |
| spec_min_path = os.path.join(self._robot_script_dir, |
| 'device_specs/%s_min.p' % self._board) |
| if os.path.isfile(spec_path) and os.path.isfile(spec_min_path): |
| shutil.copy(spec_path, '.') |
| shutil.copy(spec_min_path, '.') |
| return |
| |
| # If both of those fail, then generate a new device spec |
| self._calibrate_device(self._board) |
| |
| def _get_fingertips(self, tips_to_get): |
| if self.fingertips != [None, None, None, None]: |
| print 'Error, there are still fingertips on' |
| sys.exit(1) |
| |
| for size in conf.ALL_FINGERTIP_SIZES: |
| fingers = [1 if f == size else 0 for f in tips_to_get] |
| print 'size: %d\tfingers: %s' % (size, str(fingers)) |
| if fingers == [0, 0, 0, 0]: |
| continue |
| |
| script = os.path.join(self._robot_script_dir, |
| 'manipulate_fingertips.py') |
| para = (script, fingers[0], fingers[1], fingers[2], fingers[3], |
| size, str(True)) |
| cmd = 'python %s %d %d %d %d %d %s' % para |
| |
| if self._execute_control_command(cmd): |
| print 'Error getting the figertips!' |
| print 'Please make sure all the fingertips are in the correct' |
| print 'positions before continuing.' |
| print 'WARNING: failure to do this correctly could cause' |
| print ' permanent damage to the robot!' |
| print |
| print 'The fingers it was attempting to get were as follows:' |
| print tips_to_get |
| print '(Starting with the front right finger and going counter' |
| print 'clockwise from there. Finger sizes are 0-3)' |
| self._wait_for_user_input('GO', 'ABORT') |
| |
| self.fingertips = tips_to_get |
| |
| |
| def _return_fingertips(self): |
| """ Return all the fingertips to the nest, one size at a time. |
| This function uses the self.fingertips member variable to know which |
| finger has which tip size, then returns them all to the nest |
| """ |
| for size in conf.ALL_FINGERTIP_SIZES: |
| # See which (if any) of the fingers currently have this size tip |
| fingers = [1 if f == size else 0 for f in self.fingertips] |
| if fingers == [0, 0, 0, 0]: |
| continue |
| |
| script = os.path.join(self._robot_script_dir, |
| 'manipulate_fingertips.py') |
| para = (script, fingers[0], fingers[1], fingers[2], fingers[3], |
| size, str(False)) |
| cmd = 'python %s %d %d %d %d %d %s' % para |
| |
| if self._execute_control_command(cmd): |
| print 'Error returning the figertips!' |
| print 'Please make sure all the fingertips are in their correct' |
| print 'spots in the nest before continuing.' |
| print 'WARNING: failure to do this correctly could cause' |
| print ' permanent damage to the robot!' |
| self._wait_for_user_input('GO', 'ABORT') |
| |
| self.fingertips = [None, None, None, None] |
| |
| def _wait_for_user_input(self, continue_cmd, stop_cmd): |
| user_input = None |
| while user_input not in [continue_cmd, stop_cmd]: |
| if user_input: |
| print 'Sorry, but "%s" was incorrect' % user_input |
| user_input = raw_input('Type "%s" to continue or "%s" to stop: ' % |
| (continue_cmd, stop_cmd)) |
| if user_input == stop_cmd: |
| raise RobotWrapperError('Operator aborted after error') |
| |
| def _calibrate_device(self, board): |
| """ Have the operator show the robot where the device is.""" |
| calib_script = os.path.join(self._robot_script_dir, |
| 'calibrate_for_new_device.py') |
| calib_cmd = 'python %s %s' % (calib_script, board) |
| self._execute_control_command(calib_cmd) |
| |
| def is_manual_noise_test_mode(self): |
| return (self._mode in [MODE.NOISE, MODE.COMPLETE] |
| and self._fngenerator_only) |
| |
| def is_robot_action_mode(self): |
| """Is it in robot action mode? |
| |
| In the robot action mode, it actually invokes the robot control script. |
| """ |
| if self.is_manual_noise_test_mode(): |
| return False |
| |
| return self._mode in [MODE.ROBOT, MODE.QUICKSTEP, MODE.NOISE] |
| |
| def _raise_error(self, msg): |
| """Only raise an error if it is in the robot action mode.""" |
| if self.is_robot_action_mode(): |
| raise RobotWrapperError(msg) |
| |
| def _get_robot_script_dir(self): |
| """Get the directory of the robot control scripts.""" |
| for lib_path in [conf.robot_lib_path, conf.robot_lib_path_local]: |
| cmd = ('find %s -maxdepth 1 -type d -name %s' % |
| (lib_path, conf.python_package)) |
| path = common_util.simple_system_output(cmd) |
| if path: |
| robot_script_dir = os.path.join(path, conf.gestures_sub_path) |
| if os.path.isdir(robot_script_dir): |
| return robot_script_dir |
| return '' |
| |
| def _get_num_taps(self, gesture): |
| """Determine the number of times to tap.""" |
| matches = re.match('[^0-9]*([0-9]*)[^0-9]*', gesture) |
| return int(matches.group(1)) if matches else None |
| |
| def _reverse_coord_if_is_touchscreen(self, coordinates): |
| """Reverse the coordinates if the device is a touchscreen. |
| |
| E.g., the original coordinates = (0.1, 0.9) |
| After reverse, the coordinates = (1 - 0.1, 1 - 0.9) = (0.9, 0.1) |
| |
| @param coordinates: a tuple of coordinates |
| """ |
| return (tuple(1.0 - c for c in coordinates) if self._is_touchscreen else |
| coordinates) |
| |
| def _get_control_command_pinch(self, robot_script, gesture, variation): |
| # Depending on which direction you're zooming, change the order of the |
| # finger spacings. |
| if GV.ZOOM_IN in variation: |
| starting_spacing = INNER_PINCH_SPACING |
| ending_spacing = OUTER_PINCH_SPACING |
| else: |
| starting_spacing = OUTER_PINCH_SPACING |
| ending_spacing = INNER_PINCH_SPACING |
| |
| # Keep the hand centered on the pad, and make the fingers move |
| # in or out with only two opposing fingers on the pad. |
| para = (robot_script, self._board, |
| CENTER, CENTER, 45, starting_spacing, |
| CENTER, CENTER, 45, ending_spacing, |
| 0, 1, 0, 1, self._speed_dict[GV.SLOW], 'basic') |
| return 'python %s %s_min.p %f %f %d %d %f %f %d %d %d %d %d %d %f %s' % para |
| |
| def _get_control_command_quickstep(self, robot_script, gesture, variation): |
| """have the robot do the zig-zag gesture for latency testing.""" |
| para = (robot_script, self._board, self._speed_dict[GV.FULL_SPEED]) |
| return 'python %s %s_min.p %d' % para |
| |
| |
| def _dimensions(self, device_spec): |
| device_script = os.path.join(self._robot_script_dir, 'device_size.py') |
| cmd = 'python %s %s' % (device_script, device_spec) |
| results = common_util.simple_system_output(cmd) |
| dimensions = results.split() |
| return float(dimensions[0]), float(dimensions[1]) |
| |
| def _adjust_for_target_distance(self, line, stationary_finger, distance): |
| """ Given a point and a line in relative coordinates move the point |
| so that is exactly 'distance' millimeters away from the line for the |
| current board. Sometimes the robot needs to move two fingers very |
| close and this is problematic in relative coordinates as the board |
| dimensions change. This function allows the test to compensate and |
| get a more accurate test. |
| """ |
| # First convert all the values into absolute coordinates by using |
| # the calibrated device spec to see the dimensions of the pad |
| h, w = self._dimensions("%s_min.p" % self._board) |
| abs_line = (line[0] * w, line[1] * h, line[2] * w, line[3] * h) |
| x, y = (stationary_finger[0] * w, stationary_finger[1] * h) |
| |
| # Find a point near the stationary finger that is distance |
| # away from the line at the closest point |
| dx = abs_line[2] - abs_line[0] |
| dy = abs_line[3] - abs_line[1] |
| if dx == 0: # vertical line |
| if x > abs_line[0]: |
| x = abs_line[0] + distance |
| else: |
| x = abs_line[0] - distance |
| elif dy == 0: # horizontal line |
| if y > abs_line[1]: |
| y = abs_line[1] + distance |
| else: |
| y = abs_line[1] - distance |
| else: |
| # First, find the closest point on the line to the point |
| m = dy / dx |
| b = abs_line[1] - m * abs_line[0] |
| m_perp = -1.0 / m |
| b_perp = y - m_perp * x |
| x_intersect = (b_perp - b) / (m - m_perp) |
| y_intersect = m * x_intersect + b |
| |
| current_distance_sq = ((x_intersect - x) ** 2 + |
| (y_intersect - y) ** 2) |
| scale = distance / (current_distance_sq ** 0.5) |
| x = x_intersect - (x_intersect - x) * scale |
| y = y_intersect - (y_intersect - y) * scale |
| |
| #Convert this absolute point back into relative coordinates |
| x_rel = x / w |
| y_rel = y / h |
| |
| return (x_rel, y_rel) |
| |
| def _get_control_command_one_stationary_finger(self, robot_script, gesture, |
| variation): |
| line = speed = finger_gap_mm = None |
| num_taps = 0 |
| # The stationary finger should be in the bottom left corner for resting |
| # finger tests, and various locations for finger crossing tests |
| stationary_finger = (START, END) |
| |
| for element in variation: |
| if element in GV.GESTURE_DIRECTIONS: |
| line = self._line_dict[element] |
| if 'finger_crossing' in gesture: |
| stationary_finger = self._stationary_finger_dict[element] |
| finger_gap_mm = conf.FINGER_CROSSING_GAP_MM |
| elif 'second_finger_taps' in gesture: |
| stationary_finger = self._location_dict[GV.BL] |
| speed = self._speed_dict[GV.SLOW] |
| num_taps = 3 |
| elif 'fat_finger' in gesture: |
| stationary_finger = self._stationary_finger_dict[element] |
| speed = self._speed_dict[GV.SLOW] |
| finger_gap_mm = conf.FAT_FINGER_AND_STATIONARY_FINGER_GAP_MM |
| elif element in GV.GESTURE_SPEED: |
| speed = self._speed_dict[element] |
| |
| if line is None or speed is None: |
| msg = 'Cannot derive the line/speed parameters from %s %s.' |
| self._raise_error(msg % (gesture, variation)) |
| |
| # Adjust the positioning to get a precise gap between finger tips |
| # if this gesture requires it |
| if finger_gap_mm: |
| min_space_mm = (conf.FINGERTIP_DIAMETER_MM[self.fingertips[0]] + |
| conf.FINGERTIP_DIAMETER_MM[self.fingertips[2]]) / 2 |
| stationary_finger = self._adjust_for_target_distance( |
| line, stationary_finger, |
| min_space_mm + finger_gap_mm) |
| |
| line = self._reverse_coord_if_is_touchscreen(line) |
| start_x, start_y, end_x, end_y = line |
| stationary_x, stationary_y = stationary_finger |
| |
| para = (robot_script, self._board, stationary_x, stationary_y, |
| start_x, start_y, end_x, end_y, speed, num_taps) |
| cmd = 'python %s %s_min.p %f %f %f %f %f %f %s --taps=%d' % para |
| return cmd |
| |
| def _get_control_command_line(self, robot_script, gesture, variation): |
| """Get robot control command for gestures using robot line script.""" |
| line_type = 'swipe' if bool('swipe' in gesture) else 'basic' |
| line = speed = finger_angle = None |
| for element in variation: |
| if element in GV.GESTURE_DIRECTIONS: |
| line = self._line_dict[element] |
| finger_angle = self._angle_dict.get(element, None) |
| elif element in GV.GESTURE_SPEED: |
| speed = self._speed_dict[element] |
| |
| if not speed: |
| if line_type is 'swipe': |
| speed = self._speed_dict[GV.FAST] |
| if 'two_close_fingers' in gesture or 'fat' in gesture: |
| speed = self._speed_dict[GV.NORMAL] |
| |
| if line is None or speed is None: |
| msg = 'Cannot derive the line/speed parameters from %s %s.' |
| self._raise_error(msg % (gesture, variation)) |
| |
| line = self._reverse_coord_if_is_touchscreen(line) |
| start_x, start_y, end_x, end_y = line |
| |
| if 'two' in gesture: |
| if 'close_fingers' in gesture: |
| finger_spacing = TWO_CLOSE_FINGER_SPACING |
| finger_angle += 45 |
| fingers = (1, 1, 0, 0) |
| else: |
| finger_spacing = TWO_FINGER_SPACING |
| fingers = (0, 1, 0, 1) |
| |
| if finger_angle is None: |
| msg = 'Unable to determine finger angle for %s %s.' |
| self._raise_error(msg % (gesture, variation)) |
| else: |
| finger_spacing = 17 |
| fingers = (0, 1, 0, 0) |
| finger_angle = 0 |
| |
| # Generate the CLI command |
| para = (robot_script, self._board, |
| start_x, start_y, finger_angle, finger_spacing, |
| end_x, end_y, finger_angle, finger_spacing, |
| fingers[0], fingers[1], fingers[2], fingers[3], |
| speed, line_type) |
| cmd = 'python %s %s.p %f %f %d %d %f %f %d %d %d %d %d %d %f %s' % para |
| |
| if self._get_noise_command(gesture, variation): |
| # A one second pause to give the touchpad time to calibrate |
| DELAY = 1 # 1 seconds |
| cmd = '%s %d' % (cmd, DELAY) |
| |
| return cmd |
| |
| def _get_control_command_stationary_with_taps(self, robot_script, gesture, |
| variation): |
| """ There is only one variant of this gesture, so there is only one |
| command to generate. This is the command for tapping around a |
| stationary finger on the pad. |
| """ |
| return 'python %s %s.p 0.5 0.5' % (robot_script, self._board) |
| |
| def _get_control_command_rapid_taps(self, robot_script, gesture, variation): |
| num_taps = self._get_num_taps(gesture) |
| return self._get_control_command_taps(robot_script, gesture, |
| variation, num_taps) |
| |
| def _get_control_command_single_tap(self, robot_script, gesture, variation): |
| # Get the single tap command |
| cmd = self._get_control_command_taps(robot_script, gesture, variation, 1) |
| |
| if self._get_noise_command(gesture, variation): |
| # Add the noise command and a pause to the tap |
| TEST_DURATION = 3 # 3 seconds |
| cmd = '%s %d' % (cmd, TEST_DURATION) |
| |
| return cmd |
| |
| def _get_control_command_drumroll(self, robot_script, gesture, variation): |
| """Get robot control command for the drumroll gesture. There is only |
| one so there is no need for complicated parsing here. |
| """ |
| return ('python %s %s.p 0.5 0.5 45 20 0 1 0 1 50 drumroll' % |
| (robot_script, self._board)) |
| |
| def _get_control_command_taps(self, robot_script, gesture, |
| variation, num_taps): |
| """Get robot control command for tap gestures. This includes rapid tap |
| tests as well as 1 and 2 finger taps at various locations on the pad. |
| """ |
| if num_taps is None: |
| msg = 'Cannot determine the number of taps to do from %s.' |
| self._raise_error(msg % gesture) |
| |
| # The tap commands have identical arguments as the click except with |
| # two additional arguments at the end. As such we generate the 'click' |
| # command and add these on to make it work as a tap. |
| cmd = self._get_control_command_click_tap(robot_script, gesture, |
| variation) |
| control_cmd = '%s %d tap' % (cmd, num_taps) |
| return control_cmd |
| |
| def _get_control_command_click(self, robot_script, gesture, variation): |
| """Get the robot control command for a physical click gesture""" |
| cmd = self._get_control_command_click_tap(robot_script, gesture, |
| variation) |
| control_cmd = '%s %d' % (cmd, PHYSICAL_CLICK_FINGER_SIZE) |
| return control_cmd |
| |
| def _get_control_command_click_tap(self, robot_script, gesture, variation): |
| """Get robot control arguments that are common between pysical click |
| and tapping gestures |
| """ |
| location = None |
| angle = 45 |
| for element in variation: |
| if element in self._location_dict: |
| location = self._location_dict[element] |
| if 'two' in gesture and element in self._angle_dict: |
| angle = self._angle_dict[element] |
| |
| # All non-one finger taps are simply perfomed in the middle of the pad |
| if 'one' not in gesture and location is None: |
| location = self._location_dict[GV.CENTER] |
| |
| if location is None: |
| msg = 'Cannot determine the location parameters from %s %s.' |
| self._raise_error(msg % (gesture, variation)) |
| |
| fingers = [1, 0, 0, 0] |
| if 'two' in gesture: |
| fingers = [0, 1, 0, 1] |
| elif 'three' in gesture: |
| fingers = [0, 1, 1, 1] |
| elif 'four' in gesture: |
| fingers = [1, 1, 1, 1] |
| |
| location_str = ' '.join( |
| map(str, self._reverse_coord_if_is_touchscreen(location))) |
| |
| para = (robot_script, self._board, location_str, angle, |
| PHYSICAL_CLICK_SPACING, |
| fingers[0], fingers[1], fingers[2], fingers[3]) |
| control_cmd = 'python %s %s.p %s %d %d %d %d %d %d' % para |
| return control_cmd |
| |
| def _build_robot_script_paths(self): |
| """Build the robot script paths.""" |
| # Check if the robot script dir could be found. |
| if not self._robot_script_dir: |
| script_path = os.path.join(conf.robot_lib_path, conf.python_package, |
| conf.gestures_sub_path) |
| msg = 'Cannot find robot script directory in "%s".' |
| self._raise_error(msg % script_path) |
| |
| # Build the robot script path dictionary |
| self._robot_script_dict = {} |
| for method in self._robot_script_name_dict: |
| script_name = self._robot_script_name_dict.get(method) |
| |
| # Check if the control script actually exists. |
| robot_script = os.path.join(self._robot_script_dir, script_name) |
| if not os.path.isfile(robot_script): |
| msg = 'Cannot find the robot control script: %s' |
| self._raise_error(msg % robot_script) |
| |
| self._robot_script_dict[method] = robot_script |
| |
| def _get_control_command(self, gesture, variation): |
| """Get robot control command based on the gesture and variation.""" |
| script_method = self._method_of_control_command_dict.get(gesture) |
| if not script_method: |
| self._raise_error('Cannot find "%s" gesture in ' |
| '_method_of_control_command_dict.' % gesture) |
| |
| robot_script = self._robot_script_dict.get(script_method) |
| if not robot_script: |
| msg = 'Cannot find "%s" method in _robot_script_dict.' |
| self._raise_error(msg % script_method) |
| |
| return script_method(robot_script, gesture, variation) |
| |
| def _execute_control_command(self, control_cmd): |
| """Execute a control command.""" |
| print 'Executing: "%s"' % control_cmd |
| if self.is_robot_action_mode(): |
| # Pausing to give everything time to settle |
| time.sleep(0.5) |
| return common_util.simple_system(control_cmd) |
| return 0 |
| |
| def _reset_with_safety_clearance(self, destination): |
| reset_script = os.path.join(self._robot_script_dir, SCRIPT_RESET) |
| para = (reset_script, self._board, destination, |
| self._speed_dict[GV.FAST]) |
| self._execute_control_command('python %s %s.p %s %d' % para) |
| |
| def turn_off_noise(self): |
| off_cmd = 'python %s OFF' % self._noise_script_dir |
| common_util.simple_system(off_cmd) |
| |
| def _execute_noise_command(self, noise_cmd): |
| if self.is_robot_action_mode() or self.is_manual_noise_test_mode(): |
| return common_util.simple_system(noise_cmd) |
| |
| def _get_noise_command(self, gesture, variation): |
| if not gesture or not variation: |
| return None |
| |
| waveform = frequency = amplitude = None |
| for element in variation: |
| if element.endswith('Hz'): |
| frequency = int(element[:-2]) |
| elif element in GV.NOISE_FREQUENCY: |
| frequency = self._frequency_dict[element] |
| elif element in GV.NOISE_WAVEFORM: |
| waveform = self._waveform_dict[element] |
| elif element in GV.NOISE_AMPLITUDE: |
| amplitude = self._amplitude_dict[element] |
| |
| if waveform and frequency and amplitude: |
| return 'python %s %s %d %d' % (self._noise_script_dir, |
| waveform, frequency, amplitude) |
| return None |
| |
| def configure_noise(self, gesture, variation): |
| if not gesture or not variation: |
| return None |
| |
| if not isinstance(variation, tuple): |
| variation = (variation,) |
| |
| noise_cmd = self._get_noise_command(gesture.name, variation) |
| if noise_cmd: |
| self._execute_noise_command(noise_cmd) |
| |
| def control(self, gesture, variation): |
| """Have the robot perform the gesture variation.""" |
| tips_needed = conf.finger_tips_required[gesture.name] |
| if self.fingertips != tips_needed: |
| self._reset_with_safety_clearance('nest') |
| self._return_fingertips() |
| self._get_fingertips(tips_needed) |
| self._reset_with_safety_clearance('pad') |
| |
| if not isinstance(variation, tuple): |
| variation = (variation,) |
| try: |
| print gesture.name, variation |
| control_cmd = self._get_control_command(gesture.name, variation) |
| self._execute_control_command(control_cmd) |
| |
| except RobotWrapperError as e: |
| print gesture.name, variation |
| print 'RobotWrapperError: %s' % str(e) |
| sys.exit(1) |