blob: 4cfd04a932f008875b88b0752501e63342687ecb [file] [log] [blame]
# Copyright (c) 2012 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."""
import os
import re
import common_util
import test_conf as conf
from firmware_constants import GV, MODE
# Define the robot control script names.
SCRIPT_LINE = 'line.py'
SCRIPT_CLICK = 'click.py'
SCRIPT_RAPID_TAPS = 'rapid_taps.py'
SCRIPT_REPLAY = 'run_program.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.3
BELOW_CENTER = 0.7
LEFT_TO_CENTER = 0.3
RIGHT_TO_CENTER = 0.7
# Define constants used to determine the types of gestures.
TRACKING = 'tracking'
CROSSING = 'crossing'
MOVE = 'move'
# Define constants determining which dictionary to use for a gesture
THROUGH_CENTER = 'through_center'
OFF_CENTER = 'off_center'
# Define constants used as command options of robot control scripts.
# For line gesture: [basic | swipe]
# For click gesture: [click | tap]
BASIC = 'basic'
SWIPE = 'swipe'
TAP = 'tap'
CLICK = 'click'
CALIBRATION_FILE = 'calibrated_dimensions.py'
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, mode, is_touchscreen, should_calibrate=True):
self._board = board
self._mode = mode
self.is_touchscreen = is_touchscreen
self._robot_script_dir = self._get_robot_script_dir()
self._gesture_variation = None
# Each get_contorol_command method maps to a script name.
self._robot_script_name_dict = {
self._get_control_command_line: SCRIPT_LINE,
self._get_control_command_click: SCRIPT_CLICK,
self._get_control_command_rapid_taps: SCRIPT_RAPID_TAPS,
self._get_control_command_replay: SCRIPT_REPLAY,
}
# Each gesture maps to a get_contorol_command method
self._method_of_control_command_dict = {
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_click,
conf.ONE_FINGER_PHYSICAL_CLICK: self._get_control_command_click,
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_click,
conf.TWO_FINGER_PHYSICAL_CLICK: self._get_control_command_click,
conf.FINGER_CROSSING: self._get_control_command_line,
conf.STATIONARY_FINGER_NOT_AFFECTED_BY_2ND_FINGER_TAPS:
self._get_control_command_click,
conf.RESTING_FINGER_PLUS_2ND_FINGER_MOVE:
self._get_control_command_line,
conf.RAPID_TAPS: self._get_control_command_rapid_taps,
}
self._line_dict = {
# These are the tracking lines that will go through the center.
THROUGH_CENTER: {
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),
# 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),
},
# These are the tracking lines that will not go through the center.
OFF_CENTER: {
GV.LR: (START, ABOVE_CENTER, END, ABOVE_CENTER),
GV.RL: (END, BELOW_CENTER, START, BELOW_CENTER),
GV.TB: (RIGHT_TO_CENTER, START, RIGHT_TO_CENTER, END),
GV.BT: (LEFT_TO_CENTER, END, LEFT_TO_CENTER, START),
GV.BLTR: (START, CENTER, RIGHT_TO_CENTER, START),
GV.TRBL: (RIGHT_TO_CENTER, START, START, CENTER),
GV.BRTL: (END, CENTER, LEFT_TO_CENTER, START),
GV.TLBR: (LEFT_TO_CENTER, START, END, CENTER),
},
}
self._speed_dict = {
GV.SLOW: 33,
GV.NORMAL: 100,
GV.FAST: 300,
}
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),
# location parameters for
# stationary_finger_not_affected_by_2nd_finger_taps
GV.AROUND: (
CENTER, START,
RIGHT_TO_CENTER, ABOVE_CENTER,
END, CENTER,
RIGHT_TO_CENTER, BELOW_CENTER,
CENTER, END,
),
# location parameters for two-finger taps
# In the manual mode:
# The original meanings of the following gesture variations:
# HORIZONTAL: two fingers aligned horizontally
# VERTICAL: two fingers aligned vertically
# DIAGONAL: two fingers aligned diagonally
#
# In the robot mode:
# The robot fingers cannot rotate automatically. Have the robot
# perform taps on distinct locations instead for convenience.
# Note: the location is specified by the first finger, and the
# second finger is on the left. Choose the tap locations
# that guarantee both fingers on the touch surface.
GV.HORIZONTAL: (CENTER, CENTER),
GV.VERTICAL: (END, CENTER),
GV.DIAGONAL: (CENTER, END),
# location parameters for one_finger_click and two_finger_click
None: (CENTER, CENTER),
}
self._build_robot_script_paths()
# If the robot is actually connected, we should calibrate the Z height
# for this device immediately. This generates a file that over-rides
# the values found in the device description
self._calibrate_z(should_calibrate)
def _calibrate_z(self, should_calibrate):
""" Clear any old calibration files and possibly generate a new one """
if os.path.isfile(CALIBRATION_FILE):
os.remove(CALIBRATION_FILE)
if self._is_robot_action_mode() and should_calibrate:
calibrate_script = os.path.join(self._robot_script_dir,
'calibrate_z.py')
calibrate_cmd = 'python %s %s' % (calibrate_script, self._board)
common_util.simple_system(calibrate_cmd)
if not os.path.isfile(CALIBRATION_FILE):
self._raise_error('Z calibration failed')
def _is_robot_action_mode(self):
"""Is it in robot action mode?
In the robot action mode, it actually invokes the robot control script.
"""
return self._mode in [MODE.ROBOT, MODE.ROBOT_INT]
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."""
cmd = 'find %s -type d -name %s' % (conf.robot_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_basic_tracking_or_swipe(self, gesture):
"""Determine whether the gesture is a basic tracking or a swipe."""
if SWIPE in gesture:
return SWIPE
else:
return BASIC
def _get_tap_or_click(self, gesture):
"""Determine whether the gesture is a tap or a click."""
if TAP in gesture:
return TAP
elif CLICK in gesture:
return CLICK
else:
return None
def _get_num_taps(self, gesture):
"""Determine the number of times to tap."""
matches = re.match('[^0-9]*([0-9]*)[^0-9]*', gesture)
return 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_line(self, robot_script, gesture, variation):
"""Get robot control command for gestures using robot line script."""
# Determine whether this is a basic tracking gesture or a swipe.
basic_tracking_or_swipe = self._get_basic_tracking_or_swipe(gesture)
if not basic_tracking_or_swipe:
msg = 'Cannot determine whether "%s" is basic tracking or swipe.'
self._raise_error(msg % gesture)
# Determine which line dictionary to use.
is_finger_crossing = (gesture == conf.FINGER_CROSSING)
dict_choice = OFF_CENTER if is_finger_crossing else THROUGH_CENTER
line_dict = self._line_dict[dict_choice]
line = speed = None
for element in variation:
if element in GV.GESTURE_DIRECTIONS:
line = line_dict[element]
elif element in GV.GESTURE_SPEED:
speed = self._speed_dict[element]
# The speed is assigned in the one_finger_tracking gesture.
# For the swipe gesture, no speed is specified in test_conf.
# Hence, need to assign FAST to the speed of the swipe gesture.
if basic_tracking_or_swipe == SWIPE:
speed = self._speed_dict[GV.FAST]
if line is None or speed is None:
msg = 'Cannot derive the line/speed parameters from %s %s.'
self._raise_error(msg % (gesture, variation))
start_x, start_y, end_x, end_y = self._reverse_coord_if_is_touchscreen(
line)
para = (robot_script, self._board, start_x, start_y, end_x, end_y,
speed, basic_tracking_or_swipe)
control_cmd = 'python %s %s %f %f %f %f %f %s' % para
return control_cmd
def _get_control_command_click(self, robot_script, gesture, variation):
"""Get robot control command for gestures using robot click script."""
location = None
for element in variation:
location = self._location_dict.get(element)
if location:
location_str = ' '.join(
map(str, self._reverse_coord_if_is_touchscreen(location)))
break
if location is None:
msg = 'Cannot derive the location parameters from %s %s.'
self._raise_error(msg % (gesture, variation))
tap_or_click = self._get_tap_or_click(gesture)
if not tap_or_click:
msg = 'Cannot determine whether "%s" is a tap or a click.'
self._raise_error(msg % gesture)
para = (robot_script, self._board, location_str, tap_or_click)
control_cmd = 'python %s %s %s %s' % para
return control_cmd
def _get_control_command_rapid_taps(self, robot_script, gesture, variation):
"""Get robot control command for the rapid tap gestures."""
location = None
for element in variation:
location = self._location_dict.get(element)
if location:
location_str = ' '.join(
map(str, self._reverse_coord_if_is_touchscreen(location)))
break
if location is None:
msg = 'Cannot determine the location parameters from %s %s.'
self._raise_error(msg % (gesture, variation))
num_taps = self._get_num_taps(gesture)
if not num_taps:
msg = 'Cannot determine the number of taps to do from %s.'
self._raise_error(msg % gesture)
para = (robot_script, self._board, location_str, num_taps)
control_cmd = 'python %s %s %s %s' % para
return control_cmd
def _get_control_command_replay(self, robot_script, gesture, variation):
"""Get robot control command for replaying the existent gestures."""
control_cmd = 'python %s' % robot_script
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."""
# If the (gesture, variation) is the same as the previous one, use
# the replay script (run_program.py)
if self._gesture_variation == (gesture, variation):
script_method = self._get_control_command_replay
else:
# Check if there exists a method to derive the robot script command
# for this gesture.
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)
self._gesture_variation = (gesture, variation)
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():
common_util.simple_system(control_cmd)
def control(self, gesture, variation):
"""Have the robot perform the gesture variation."""
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)