blob: 901766b1537d5b468124d8e2268aa90b0be29e40 [file] [log] [blame]
# 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.
import copy
import math
import time
from numpy import clip
from position import Position
from profile import Profile
from touchbotcomm import TouchbotComm
class Touchbot:
""" High level Touchbot control class
This class hides the internals of communicating with the robot and
offers high level robot control commands.
"""
# Network connection parameters for the robot
TOUCHBOT_IP_ADDR = '192.168.0.1'
TOUCHBOT_PORT = 10100
# How high above to approach from to be safe
SAFETY_CLEARANCE = 220
# Useful speeds for movement
SPEED_VERY_SLOW = 1.0
SPEED_SLOW = 5.0
SPEED_MEDIUM = 10.0
SPEED_FAST = 15.0
SPEED_VERY_FAST = 20.0
MAX_SPEED = 100.0
# Limits on how far the robot hand can move the fingers in and out
MIN_FINGER_DISTANCE = 15
MAX_FINGER_DISTANCE = 140
# How long it takes for the fingertip pistons to extend (in seconds)
MINIMUM_FINGER_EXTENSION_TIME = 0.09
# Setting for the accuracy (InRange) value of a profile
BLEND_MOVEMENTS = -1
ALWAYS_STOP = 0
MAX_ACCURACY = 1
# Settings for how to interpolate points (straight or curved)
STRAIGHT_INTERPOLATION = -1
JOINT_INTERPOLATION = 0
# Indicies for adressing specific axes of the robot's arm
AXIS_Z = 1
AXIS_SHOULDER = 2
AXIS_ELBOW = 3
AXIS_WRIST = 4
AXIS_HAND = 5
ALL_AXES = [AXIS_Z, AXIS_SHOULDER, AXIS_ELBOW, AXIS_WRIST, AXIS_HAND]
# GPIO addresses for the robot's fingers
FINGER_BR = 16
FINGER_FR = 15
FINGER_FL = 14
FINGER_BL = 13
ALL_FINGERS = [FINGER_FR, FINGER_FL, FINGER_BL, FINGER_BR]
# GPIO addresses for the nest close and open valves
NEST_CLOSE_VALVE = 800013
NEST_OPEN_VALVE = 800015
# GPIO address for the fingertip detection sensor
FINGERTIP_DETECT = 810005
FINGERTIP_DETECTION_REPITITIONS = 10
# Paths to pre-recorded Positions hovering above the nest
NEST_POSITION_FILES = ['nest/size0.p', 'nest/size1.p',
'nest/size2.p', 'nest/size3.p']
# Paths to pre-recorded Positions hovering above the fingertip detector
FINGERTIP_DETECT_POSITION_FILES = ['detector/fingerfr.p',
'detector/fingerfl.p',
'detector/fingerbl.p',
'detector/fingerbr.p']
# It appears that the units are not exactly mm, but are scaled slightly
# By scaling distances by this value the accuracy goes up dramatically
DISTANCE_SCALING_FACTOR = 0.75
# The range of legal relative coordinates.
RELATIVE_COORDINATE_RANGE = (-0.05, 1.05)
# The range of non-binding values for ax_4
AX_4_MIN = 1
AX_4_MAX = 359
def __init__(self, ip_address=TOUCHBOT_IP_ADDR, port=TOUCHBOT_PORT):
self.speed_stack = []
self.comm = TouchbotComm(ip_address, port)
self.nest_positions = [Position.FromPickledFile(filename)
for filename in Touchbot.NEST_POSITION_FILES]
if not all(self.nest_positions):
print 'WARNING: Some nest positions loaded incorrectly'
self.fingertip_detect_positions = [Position.FromPickledFile(filename)
for filename in Touchbot.FINGERTIP_DETECT_POSITION_FILES]
if not all(self.fingertip_detect_positions):
print 'WARNING: Some fingertip detector position loaded incorrectly'
self.SetFingerStates([0, 0, 0, 0])
self.OpenNest()
def CalibratePosition(self):
""" Make the robot go limp and record its position after the operator
moves it into the desired configuration.
Returns a Position object of the recorded posisition.
"""
successful = True
response = self.FreeAxes()
if not response or response[0] != 0:
print 'There was an error entering free mode.'
return None
print 'Move the robot CAREFULLY to where you want it.'
raw_input('Press "enter" to record that position.')
response = self.UnFreeAxes()
if not response or response[0] != 0:
print 'There was an error leaving free mode.'
return None
return self.GetCurrentPosition()
def SetSpeed(self, speed):
prof = self.GetCurrentProfile()
prof.speed = speed
self.SetProfileData(prof)
def GetSpeed(self):
prof = self.GetCurrentProfile()
return prof.speed
def FreeAxes(self):
""" Set all axis into free mode, ie: make them go limp. """
return self.comm.SendCmd('freemode', 0)
def UnFreeAxes(self):
""" Return all axes to computer control. """
return self.comm.SendCmd('freemode', -1)
def GetCurrentPosition(self):
""" Record the current position as a Position object. """
response = self.comm.SendCmd('where')
if not response:
return None
error_code, data = response
if error_code != 0:
return None
return Position.FromTouchbotResponse(data)
def GetCurrentProfile(self):
""" Get a copy of the current movement profile. """
error_code, data = self.comm.SendCmd('profile')
return Profile.FromTouchbotResponse(data)
def SetProfileData(self, profile):
""" Replace the robot's current profile with this one. """
return self.comm.SendCmd('profile', str(profile))
def GetGPIO(self, gpio_num):
""" Get the value of a GPIO input from the robot. """
error_code, raw_data = self.comm.SendCmd('sig', gpio_num)
if error_code == 0:
return int(raw_data.split()[1])
else:
return None
def SetGPIO(self, gpio_num, value):
""" Set the value of a GPIO output on the robot. """
return self.comm.SendCmd('sig', gpio_num, value)
def SetAngles(self, pos, blocking=True):
""" Move the robot to pos by the raw joint angles. """
response = self.comm.SendCmd('movej', pos.ax_1, pos.ax_2, pos.ax_3,
pos.ax_4, pos.ax_5)
if blocking:
self.Wait()
return response
def SetCartesian(self, pos, finger_distance=None, blocking=True):
""" Move the robot to pos by Cartesian coordinates. """
# If a finger spacing has been specified add that to the movement
# command. The finger spacing motors were added as an additional axis
# so are not controlled by the 'movec' command by default. They must
# be specified before with the 'moveExtraAxis' command, and then are
# moved when the robot receives the next 'movec.'
if finger_distance:
finger_distance = clip(finger_distance,
Touchbot.MIN_FINGER_DISTANCE,
Touchbot.MAX_FINGER_DISTANCE)
adjusted_dist = finger_distance * Touchbot.DISTANCE_SCALING_FACTOR
self.comm.SendCmd('moveExtraAxis', adjusted_dist)
response = self.comm.SendCmd('movec', pos.x, pos.y, pos.z,
pos.yaw, pos.pitch, pos.roll)
if blocking:
self.Wait()
return response
def Wait(self):
return self.comm.SendCmd('waitForEom')
def AddSafetyClearance(self, pos):
new_pos = copy.deepcopy(pos)
new_pos.ax_1 = Touchbot.SAFETY_CLEARANCE
new_pos.z = Touchbot.SAFETY_CLEARANCE
return new_pos
def ManipulateFingertips(self, fingers, size, should_keep_fingertips):
""" Either attach or detach the selected fingers. """
if size < 0 or size > len(self.nest_positions) or len(fingers) != 4:
return False
curr_pos = self.GetCurrentPosition()
above_curr_pos = self.AddSafetyClearance(curr_pos)
nest_pos = self.nest_positions[size]
above_nest_pos = self.AddSafetyClearance(nest_pos)
response = self.SetAngles(above_curr_pos)
self.OpenNest()
response = self.SetAngles(above_nest_pos)
if not response or response[0] != 0:
print 'Error getting into position for size %d fingertips' % size
return False
self.SetAngles(nest_pos)
self.SetFingerStates(fingers)
if not should_keep_fingertips:
time.sleep(1)
self.CloseNest()
time.sleep(1)
self.SetFingerStates([0, 0, 0, 0])
self.SetAngles(above_nest_pos)
self.OpenNest()
return True
def DetectFingertips(self):
""" Check the state of the fingertips -- see if they're connected. """
states = []
for pos in self.fingertip_detect_positions:
self.SetAngles(pos)
readings = []
for i in range(Touchbot.FINGERTIP_DETECTION_REPITITIONS):
time.sleep(0.1)
readings.append(self.GetGPIO(Touchbot.FINGERTIP_DETECT))
num_present = sum([1 for reading in readings if reading != 0])
states.append(num_present >=
(Touchbot.FINGERTIP_DETECTION_REPITITIONS / 2.0))
print 'Finger readings %s -> %d' % (str(readings), states[-1])
return states
def SetFingerStates(self, states):
""" Control which fingers are extended and which are retracted. """
for finger, state in zip(Touchbot.ALL_FINGERS, states):
self.SetGPIO(finger, state)
def FingerStates(self):
""" See which fingers are extended and which are retracted. """
return [self.GetGPIO(finger) for finger in Touchbot.ALL_FINGERS]
def OpenNest(self):
""" Open the nest to allow fingertips to pass in and out. """
self.SetGPIO(Touchbot.NEST_CLOSE_VALVE, 0)
self.SetGPIO(Touchbot.NEST_OPEN_VALVE, 1)
def CloseNest(self):
""" Close the nest to keep fingertips from leaving. """
self.SetGPIO(Touchbot.NEST_CLOSE_VALVE, 1)
self.SetGPIO(Touchbot.NEST_OPEN_VALVE, 0)
def IsLegalSpeed(self, speed):
""" Is a speed an acceptable value for this robot """
return speed > 0.0 and speed <= Touchbot.MAX_SPEED
def IsLegalRelativeCoordinate(self, coords):
""" Are the x and y values between 0 and 1 """
if len(coords) != 2:
return False
x, y = coords
min_val, max_val = Touchbot.RELATIVE_COORDINATE_RANGE
return min_val <= x <= max_val and min_val <= y <= max_val
def IsLegalFingerList(self, fingers):
""" Check that a finger selection list is legal """
return (len(fingers) == len(Touchbot.ALL_FINGERS) and
all([bool(f in [0, 1]) for f in fingers]))
def CenterIfSingleFinger(self, fingers, abs_pos, finger_distance):
""" Offset them if only 1 finger is down (you're on your own for >1
finger) This way the *finger* itself will be centered at the point,
not the point where all the fingers meet.
"""
if len(fingers) == 4 and sum(fingers) == 1:
# Find which finger is down and how many degrees off center it is
finger_index = fingers.index(1)
finger_angle_offset = (90 * finger_index) - 45
# Shift the x and y accordingly for both points in the line
angle = math.radians(abs_pos.yaw + finger_angle_offset)
abs_pos.x += math.cos(angle) * finger_distance / 2.0
abs_pos.y += math.sin(angle) * finger_distance / 2.0
return abs_pos
def NonBindingYawRange(self, pos=None, safety_buffer=0):
""" Compute the range of legal yaw values for a given arm configuration
If no position is given, the robot's current position will be used.
"""
self.PushSpeed(Touchbot.MAX_SPEED)
pos = self.GetCurrentPosition() if not pos else pos
pos.ax_4 = self.AX_4_MIN
self.SetAngles(pos)
pos = self.GetCurrentPosition()
yaw_min = pos.yaw
pos.ax_4 = self.AX_4_MAX
self.SetAngles(pos)
pos = self.GetCurrentPosition()
yaw_max = pos.yaw
if yaw_max - yaw_min < 360:
yaw_max += 360
self.PopSpeed()
return (yaw_min + safety_buffer, yaw_max - safety_buffer)
def PushSpeed(self, new_speed):
self.speed_stack.append(self.GetSpeed())
self.SetSpeed(new_speed)
def PopSpeed(self):
if len(self.speed_stack) >= 1:
self.SetSpeed(self.speed_stack.pop())