| # 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. |
| |
| """ This is a gesture that draws a line with one finger while holding |
| another steady in a fixed spot. |
| |
| The script takes in three points in relative coordinates. The first specifies |
| where the stationary finger should be placed. The other two specify the start |
| and end coordinates for the mobile finger. Finally it takes a speed paramater |
| between 0 and 100. |
| |
| There is one optional flag to indicate how many times to tap the stationary |
| finger during the gesture. If this is omitted, the finger will just rest on |
| the pad. |
| |
| Usage: |
| python one_stationary_finger.py device.p stationary_pos start_pos |
| end_pos speed [--taps=n] |
| |
| Example: |
| # Slowly move one finger from the top left to bottom right corner while |
| # keeping a finger stationary in the bottom left |
| python one_stationary_finger.py link.p 0.1 0.9 0.1 0.1 0.9 0.9 10 |
| |
| # The same as above but tapping in the bottom left corner 3 times |
| python one_stationary_finger.py link.p 0.1 0.9 0.1 0.1 0.9 0.9 10 --taps=3 |
| """ |
| |
| import math |
| import sys |
| import time |
| |
| from collections import namedtuple |
| |
| from touchbotII import Touchbot, Device, PositionArg |
| |
| NUM_STEPS = 15 |
| YAW_BUFFER = 20 |
| |
| try: |
| device = Device(sys.argv[1]) |
| stationary_x, stationary_y = (float(sys.argv[2]), float(sys.argv[3])) |
| start_x, start_y = (float(sys.argv[4]), float(sys.argv[5])) |
| end_x, end_y = (float(sys.argv[6]), float(sys.argv[7])) |
| speed = float(sys.argv[8]) |
| |
| num_taps = 0 |
| if len(sys.argv) > 9: |
| for arg in sys.argv[9:len(sys.argv)]: |
| if arg.startswith('--taps='): |
| num_taps = int(arg.split('=')[1]) |
| break |
| |
| except: |
| print ('Usage: python %s device.p stationary_pos start_pos end_pos speed' % |
| __file__) |
| print 'Example: python %s link.p 0.1 0.9 0.1 0.1 0.9 0.9 10' % __file__ |
| sys.exit(1) |
| |
| # Connect to the robot and configure the profile |
| bot = Touchbot() |
| prof = bot.GetCurrentProfile() |
| prof.speed = speed |
| prof.straight = Touchbot.STRAIGHT_INTERPOLATION |
| prof.inRange = Touchbot.BLEND_MOVEMENTS |
| bot.SetProfileData(prof) |
| |
| if not (bot.IsLegalRelativeCoordinate((stationary_x, stationary_y)) and |
| bot.IsLegalRelativeCoordinate((start_x, start_y)) and |
| bot.IsLegalRelativeCoordinate((end_x, end_y))): |
| print ('All coordinates must fall in the range of %s' % |
| str(Touchbot.RELATIVE_COORDINATE_RANGE)) |
| sys.exit(-1) |
| if not bot.IsLegalSpeed(speed): |
| print 'The speed %f is not acceptable for this robot' % speed |
| sys.exit(-1) |
| if num_taps < 0: |
| print 'The number of taps to execute (%d) must be >= 0' % num_taps |
| sys.exit(-1) |
| |
| |
| abs_stationary = device.RelativePosToAbsolutePos((stationary_x, stationary_y)) |
| |
| # Split the movement up into a bunch of small steps to increase precision |
| # in the middle of the gesture |
| last_angle = None |
| positions = [] |
| for step in range(NUM_STEPS): |
| # Compute where the moving finger should be at this point |
| percent = float(step) / float(NUM_STEPS - 1) |
| moving_finger_x = percent * end_x + (1.0 - percent) * start_x |
| moving_finger_y = percent * end_y + (1.0 - percent) * start_y |
| |
| # Compute the hand position required to put the fingers on these spots |
| mid_x = (stationary_x + moving_finger_x) / 2.0 |
| mid_y = (stationary_y + moving_finger_y) / 2.0 |
| abs_mid = device.RelativePosToAbsolutePos((mid_x, mid_y)) |
| |
| # Compute the angle the hand needs to make |
| abs_stationary = device.RelativePosToAbsolutePos( |
| (stationary_x, stationary_y)) |
| abs_moving = device.RelativePosToAbsolutePos( |
| (moving_finger_x, moving_finger_y)) |
| dx = abs_stationary.x - abs_moving.x |
| dy = abs_stationary.y - abs_moving.y |
| angle = math.degrees(math.atan2(dy, dx)) |
| # The fingers are offset by 45 degrees |
| angle += 45 |
| |
| # Check to see if the angle aliased onto another configuration |
| # This can cause the robot to spontaneously switch the role of the fingers |
| # if we don't check for it and compensate. |
| if last_angle: |
| curr_change = abs(angle - last_angle) |
| aliased_change_pos = abs((angle + 360) - last_angle) |
| aliased_change_neg = abs((angle - 360) - last_angle) |
| min_change = min([curr_change, aliased_change_pos, aliased_change_neg]) |
| if aliased_change_pos == min_change: |
| angle = angle + 360 |
| elif aliased_change_neg == min_change: |
| angle = angle - 360 |
| last_angle = angle |
| abs_mid.yaw = angle |
| |
| # Find the distance between the two points to spread the fingers |
| dx = abs_stationary.x - abs_moving.x |
| dy = abs_stationary.y - abs_moving.y |
| dist = (dx ** 2 + dy ** 2) ** 0.5 |
| |
| # Store this position |
| positions.append((abs_mid, dist)) |
| |
| |
| # Check to make sure the wrist won't alias around to the other side |
| # If it does, determine if it can be fixed with a 180 or 360 deg offset |
| min_yaw, max_yaw = bot.NonBindingYawRange(positions[0][0], YAW_BUFFER) |
| chosen_yaw_shift = None |
| for yaw_shift in [-360, -180, 0, 180, 360]: |
| legal = True |
| for abs_pos, _ in positions: |
| if (abs_pos.yaw + yaw_shift > max_yaw or |
| abs_pos.yaw + yaw_shift < min_yaw): |
| legal = False |
| break |
| if legal: |
| chosen_yaw_shift = yaw_shift |
| break |
| |
| if chosen_yaw_shift is None: |
| print 'ERROR: Unable to perform this gesture with wrapping the wrist' |
| sys.exit(1) |
| |
| for i in range(len(positions)): |
| positions[i][0].yaw += chosen_yaw_shift |
| |
| # Nudge the wrist into the right direction before starting. If the wrist |
| # begins the gesture way on the other side of its rotation sometimes it will |
| # go the wrong direction and result in problems when it tries to over-extend. |
| # By nudging the wrist closer to the starting position, this shouldn't happen. |
| dist_min = abs(positions[0][0].yaw - min_yaw) |
| dist_max = abs(positions[0][0].yaw - max_yaw) |
| nudge_pos = bot.GetCurrentPosition() |
| nudge_pos.ax_4 = bot.AX_4_MAX if dist_min > dist_max else bot.AX_4_MIN |
| bot.SetAngles(nudge_pos) |
| |
| # Compute which steps (if any) the taps will be executed on |
| tap_steps = [] |
| if num_taps > 0: |
| tap_steps = [(i + 1) * len(positions) / (num_taps + 1) |
| for i in range(num_taps)] |
| lifted_fingers = [1, 0, 0, 0] if chosen_yaw_shift % 360 == 0 else [0, 0, 1, 0] |
| |
| # Execute the move |
| for step, (abs_pos, finger_distance) in enumerate(positions): |
| bot.SetCartesian(abs_pos, finger_distance=finger_distance, blocking=False) |
| |
| # If the stationary finger should tap here, do it. The non-blocking nature |
| # of the SetCartesian command should allow this to work without issues |
| if step in tap_steps: |
| bot.SetFingerStates([1, 0, 1, 0]) |
| time.sleep(bot.MINIMUM_FINGER_EXTENSION_TIME) |
| bot.SetFingerStates(lifted_fingers) |
| |
| # If this is the first or last step, block then raise/lower the fingertips |
| if step == 0: |
| bot.Wait() |
| bot.SetFingerStates([1, 0, 1, 0] if num_taps is 0 else lifted_fingers) |
| elif step == (len(positions) - 1): |
| bot.Wait() |
| bot.SetFingerStates([0, 0, 0, 0]) |