Application skeleton. Added controller.is_looping() to enable non-blocking loop programs

This commit is contained in:
Michael Soukup 2014-08-04 18:42:12 +02:00
parent 0bf9fe0f2f
commit 50b7f1a1ef
6 changed files with 269 additions and 15 deletions

View File

@ -2,6 +2,14 @@ import math
DEBUG = True DEBUG = True
# GUI
SCREEN_DIM = (800, 600)
FONTSIZE = 24
BG_COL = (255,255,255) #white
FONT_COL = (0,0,0) #black
MENU_FPS = 15 # enough to poll keys efficiently
# IP of the robot. # IP of the robot.
UR5_IP = "129.241.187.119" UR5_IP = "129.241.187.119"
@ -15,6 +23,8 @@ VELOCITY = 0.1
ACCELERATION = 0.5 ACCELERATION = 0.5
DECELERATION = 0.5 DECELERATION = 0.5
DEFAULT_LOOP_SPEED = 0.1 DEFAULT_LOOP_SPEED = 0.1
LOOP_SPEED_MAX = 0.7
LOOP_SPEED_MIN = 0.05
# Block parameters # Block parameters
BLOCK_DIM = 0.0995 #TODO measure BLOCK_DIM = 0.0995 #TODO measure
@ -43,6 +53,7 @@ THETA_BLOCKMOVE_OFFSET = 0 # theta start/end in place/pick
Z_BLOCKMOVE_OFFSET = BLOCK_DIM*0.5 # z start/end in place/pick Z_BLOCKMOVE_OFFSET = BLOCK_DIM*0.5 # z start/end in place/pick
# Home quadrant joint configuration. # Home quadrant joint configuration.
# We need this expressed in joint space to ensure that the robot does not # We need this expressed in joint space to ensure that the robot does not
# collide with itself when moving to the base configuration. # collide with itself when moving to the base configuration.

Binary file not shown.

221
demo.py Executable file
View File

@ -0,0 +1,221 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import pygame, sys, math, time, config
from ur5controller import DemoController
class UR5Demo(object):
def __init__(self):
# Set up pygame, GUI and controls
pygame.init()
self.screen = pygame.display.set_mode(config.SCREEN_DIM)
pygame.display.set_caption("UR5 Demo")
self.font = pygame.font.SysFont(None, config.FONTSIZE)
# move vectors are in cylinder coords
self.kb_map = {
pygame.K_UP: (-1,0,0),
pygame.K_DOWN: (1,0,0),
pygame.K_LEFT: (0,-1,0),
pygame.K_RIGHT: (0,1,0),
pygame.K_w: (0,0,1),
pygame.K_s: (0,0,-1)
}
self.clock = pygame.time.Clock()
self.sig_quit = False
self.loop_speed = config.DEFAULT_LOOP_SPEED
self.controller = None
def _disptxt(self, s_arr):
self.screen.fill(config.BG_COL)
for i,s in enumerate(s_arr):
self.screen.blit(self.font.render(s, True, config.FONT_COL), (10, 10 + i*config.FONTSIZE))
pygame.display.flip()
def quit(self, error=None):
msg = []
if error:
msg.append(error)
msg.append("Disconnecting robot...")
self._disptxt(msg)
if self.controller:
self.controller.cleanup()
time.sleep(3) # Give the UR5 threads a chance to terminate
msg.append("Exit...")
self._disptxt(msg)
pygame.quit()
if error:
return 1
else:
return 0
def go_home_pos(self):
self._disptxt(["Going to home configuration..."])
self.controller.movel(config.R_MIN, 0, config.Z_MIN)
def freedrive(self):
self.controller.set_freedrive(True)
self._disptxt(["Freedrive mode"])
running = True
while running:
pressed = pygame.key.get_pressed()
for e in pygame.event.get():
if e.type == pygame.QUIT:
self.sig_quit = True
if pressed[pygame.K_ESCAPE] or self.sig_quit:
running = False
self.clock.tick(config.MENU_FPS)
self.controller.set_freedrive(False)
def adjust_loop_speed(self):
incr = 0.05
running = True
while running:
pressed = pygame.key.get_pressed()
for e in pygame.event.get():
if e.type == pygame.QUIT:
self.sig_quit = True
if pressed[pygame.K_PLUS] or pressed[pygame.K_KP_PLUS]:
self.loop_speed += incr
self.loop_speed = min(self.loop_speed, config.LOOP_SPEED_MAX)
elif pressed[pygame.K_MINUS] or pressed[pygame.K_KP_MINUS]:
self.loop_speed -= incr
self.loop_speed = max(self.loop_speed, config.LOOP_SPEED_MIN)
elif pressed[pygame.K_ESCAPE] or self.sig_quit:
running = False
self._disptxt([
"Current speed: %.2f m/s" % self.loop_speed,
"(+) Increase speed",
"(-) Decrease speed"
])
self.clock.tick(config.MENU_FPS)
def run_loop1(self):
loopcount = 0
self._disptxt(["Running loop 1", "Loopcount: %d" % loopcount])
time.sleep(1)
# initiate loop, poll for robot move change and keypress
running = True
while running:
pressed = pygame.key.get_pressed()
for e in pygame.event.get():
if e.type == pygame.QUIT:
self.sig_quit = True
if pressed[pygame.K_ESCAPE] or self.sig_quit:
running = False
self.clock.tick(config.MENU_FPS)
# Set to default pos before return?
def run_loop2(self):
loopcount = 0
self._disptxt(["Running loop 2", "Loopcount: %d" % loopcount])
time.sleep(1)
pass
def loop_init(self, block_init_msg, loopnum):
msg = [
"Initital block configuration for loop %d:" % loopnum,
block_init_msg,
" ",
"Current loop speed is %.2f m/s" % self.loop_speed,
"Start loop? [ENTER]"
]
self._disptxt(msg)
running = True
while running:
pressed = pygame.key.get_pressed()
for e in pygame.event.get():
if e.type == pygame.QUIT:
self.sig_quit = True
if pressed[pygame.K_RETURN]:
if loopnum == 1:
self.run_loop1()
elif loopnum == 2:
self.run_loop2()
running = False
if pressed[pygame.K_ESCAPE] or self.sig_quit:
running = False
self.clock.tick(config.MENU_FPS)
def main_menu(self):
menu = [
"Go to [h]ome position",
"[f]reedrive mode",
"Adjust loop [s]peed",
"Start loop [1]",
"Start loop [2]",
"Manual [c]ontrol",
"[q]uit"
]
self._disptxt(menu)
running = True
while running:
pressed = pygame.key.get_pressed()
for e in pygame.event.get():
if e.type == pygame.QUIT:
self.sig_quit = True
if pressed[pygame.K_h]:
self.go_home_pos()
self._disptxt(menu)
elif pressed[pygame.K_f]:
self.freedrive()
self._disptxt(menu)
elif pressed[pygame.K_s]:
self.adjust_loop_speed()
self._disptxt(menu)
elif pressed[pygame.K_1]:
_instr = "2x2 blocks on left side"
self.loop_init(_instr, 1)
self._disptxt(menu)
elif pressed[pygame.K_2]:
_instr = "2x2 blocks on left side, 2x2 blocks on right side"
self.loop_init(_instr, 2)
self._disptxt(menu)
elif pressed[pygame.K_q] or self.sig_quit:
running = False
self.clock.tick(config.MENU_FPS)
return self.quit()
# break loop on quit, or return quit() on error
def run(self):
msg = []
msg.append("Connecting robot...")
self._disptxt(msg)
try:
self.controller = DemoController(config)
self.controller.set_freedrive(False)
msg.append("Calibrating cylinder coordinates...")
self._disptxt(msg)
self.controller.calibrate_csys()
except Exception, e:
return self.quit(error=("Error: %s" % e))
msg.append("Calibration done.")
self._disptxt(msg)
time.sleep(1)
return self.main_menu()
if __name__ == '__main__':
demo = UR5Demo()
sys.exit(demo.run())

View File

@ -84,24 +84,30 @@ def test_pick_place(controller):
# Initial: 1x2 left side # Initial: 1x2 left side
#speed = 0.7 #speed = 0.7
speed = 0.05 speed = 0.2
moves = [] moves = []
moves += controller.pick_block(1, config.THETA_EDGE_LEFT, 0, speed) moves += controller.pick_block(1, config.THETA_EDGE_LEFT, 0, speed)
moves += controller.place_block(0, dtheta_lvl0/2, 0, speed) moves += controller.place_block(0, dtheta_lvl0/2, 0, speed)
moves += controller.pick_block(0, config.THETA_EDGE_LEFT, 0, speed) moves += controller.pick_block(0, config.THETA_EDGE_LEFT, 0, speed)
moves += controller.place_block(0, -dtheta_lvl0/2, 0, speed) moves += controller.place_block(0, -dtheta_lvl0/2, 0, speed)
controller.movels(moves, wait=True) controller.movels(moves, wait=False)
looping = True
while looping:
looping = controller.is_looping(1.0) # blocks for 1 sec
moves.reverse() moves.reverse()
controller.movels(moves, wait=True) controller.movels(moves, wait=True)
# Shit is smooth! # Shit is smooth!
def pick_place_small_pyramid(controller): def small_pyramid(controller):
dtheta_lvl0 = config.BLOCK_DIM / config.R_LVL0 + math.pi/64 #medium gap dtheta_lvl0 = config.BLOCK_DIM / config.R_LVL0 + math.pi/64 #medium gap
#dtheta_lvl0 = config.BLOCK_DIM / config.R_LVL0 #small gap #dtheta_lvl0 = config.BLOCK_DIM / config.R_LVL0 #small gap
#dtheta_lvl0 = config.BLOCK_DIM / config.R_LVL0 - math.pi/200#no gap #dtheta_lvl0 = config.BLOCK_DIM / config.R_LVL0 - math.pi/200#no gap
speed = 0.2
# Initial: 2x2 on each side # Initial: 2x2 on each side
@ -132,6 +138,7 @@ if __name__ == '__main__':
controller = None controller = None
try: try:
controller = DemoController(config) controller = DemoController(config)
controller.calibrate_csys()
#controller.movel(config.R_LVL0, 0, config.Z_MIN + config.BLOCK_DIM) #controller.movel(config.R_LVL0, 0, config.Z_MIN + config.BLOCK_DIM)
#check_edges(controller) #check_edges(controller)
#test_movec_hax(controller) #test_movec_hax(controller)

View File

@ -1,8 +1,10 @@
import urx, math, math3d import urx, math, math3d, time
class UR5Exception(Exception): class UR5Exception(Exception):
pass pass
class DemoController(object): class DemoController(object):
"""Implements control of the UR5 in cylinder coordinates (corresponding to attached table). """Implements control of the UR5 in cylinder coordinates (corresponding to attached table).
@ -50,10 +52,12 @@ class DemoController(object):
# Home quadrant joint config # Home quadrant joint config
self.j_home = config.QUADRANT_JOINT_CONFIG self.j_home = config.QUADRANT_JOINT_CONFIG
def calibrate_csys(self):
"""Calibrate the reference cylinder coordinate system."""
# Move to reference coordinate system base # Move to reference coordinate system base
self.set_pose_home() self.set_pose_home()
# Set reference coordinate system parameters # Set reference coordinate system parameters
csys = math3d.Transform() csys = math3d.Transform()
csys.orient.rotate_zb(self.phi) csys.orient.rotate_zb(self.phi)
@ -61,7 +65,12 @@ class DemoController(object):
self.trans_base = self.robot.get_transform() self.trans_base = self.robot.get_transform()
self.cyl_ox = self.cyl_offset self.cyl_ox = self.cyl_offset
self.cyl_oy = 0 self.cyl_oy = 0
def set_freedrive(self, mode):
"""Set UR5 to freedrive mode."""
self.robot.set_freedrive(mode)
def set_pose_home(self): def set_pose_home(self):
"""Move to "home" configuration. The resulting pose has correct orientation.""" """Move to "home" configuration. The resulting pose has correct orientation."""
@ -86,6 +95,7 @@ class DemoController(object):
z) z)
return trans.pose_vector.tolist() return trans.pose_vector.tolist()
def blocklvl2pose(self, r_lvl, theta, z_lvl): def blocklvl2pose(self, r_lvl, theta, z_lvl):
return self.cylinder2pose(self.r_lvl0 - r_lvl*self.block_dim, theta, self.z_lvl0 + z_lvl*self.block_dim) return self.cylinder2pose(self.r_lvl0 - r_lvl*self.block_dim, theta, self.z_lvl0 + z_lvl*self.block_dim)
@ -135,7 +145,6 @@ class DemoController(object):
IMPORTANT: blending radius have to be smaller than the angular resolution! If not, IMPORTANT: blending radius have to be smaller than the angular resolution! If not,
the robot will not finish the last move, because it is within the target the robot will not finish the last move, because it is within the target
""" """
move_list = [] move_list = []
step = math.pi/41 step = math.pi/41
curr_r, curr_theta, curr_z = self.current_cyl curr_r, curr_theta, curr_z = self.current_cyl
@ -150,7 +159,6 @@ class DemoController(object):
move_list.append(pose + [self.acc, self.vel, 0.03]) move_list.append(pose + [self.acc, self.vel, 0.03])
move_list.append(self.cylinder2pose(r, theta, z) + [self.acc, self.vel, 0]) move_list.append(self.cylinder2pose(r, theta, z) + [self.acc, self.vel, 0])
#debug
if self.debug: if self.debug:
print "move list for movec_hax" print "move list for movec_hax"
for p in move_list: for p in move_list:
@ -178,7 +186,6 @@ class DemoController(object):
p1 = self.cylinder2pose(r_target - self.r_bmargin, theta - self.theta_bmargin, z_target - self.z_bmargin) p1 = self.cylinder2pose(r_target - self.r_bmargin, theta - self.theta_bmargin, z_target - self.z_bmargin)
p2 = self.cylinder2pose(r_target, theta, z_target) p2 = self.cylinder2pose(r_target, theta, z_target)
p3 = self.cylinder2pose(r_target + self.r_boffset, theta + self.theta_boffset, z_target + self.z_boffset) p3 = self.cylinder2pose(r_target + self.r_boffset, theta + self.theta_boffset, z_target + self.z_boffset)
# TODO take speed as arg
move_list = [ move_list = [
p1 + [self.acc, speed, 0.005], p1 + [self.acc, speed, 0.005],
p2 + [self.acc, speed, 0], p2 + [self.acc, speed, 0],
@ -188,18 +195,26 @@ class DemoController(object):
def place_block(self, r_lvl, theta, z_lvl, speed): def place_block(self, r_lvl, theta, z_lvl, speed):
"""Reverse move of pick_block.""" """Reverse move of pick_block."""
# TODO take speed as arg
moves = self.pick_block(r_lvl, theta, z_lvl, speed) moves = self.pick_block(r_lvl, theta, z_lvl, speed)
moves.reverse() moves.reverse()
return moves return moves
#r_target = self.r_lvl0 + r_lvl*self.block_dim
#_target = self.z_lvl0 + z_lvl*self.block_dim
#self.movec_hax(r_target + self.r_boffset, theta + self.theta_boffset, z_target + self.z_boffset) def construct_moves(self, task_list, speed):
#self.movel(r_target, theta, z_target) """Construct moves from a list of tasks"""
#self.movel(r_target - self.r_bmargin, theta - self.theta_bmargin, z_target - self.z_bmargin) pass # wait with implementing this
def is_looping(self, dt, threshold=0.001):
"""Polls the robot for change in pose vector. Blocks for dt seconds."""
pose0 = self.robot.getl()
time.sleep(dt)
pose1 = self.robot.getl()
v = [pose1[i]-pose0[i] for i in range(len(pose0))]
if self.debug:
_sum = sum(map(abs, v))
print "Velocity vector, dt=%.2f: %s. SUM %.3f" % (dt, str([('%.2f' % i) for i in v]), _sum)
return sum(map(abs, v)) > threshold
def cleanup(self): def cleanup(self):
self.robot.cleanup() self.robot.cleanup()

Binary file not shown.