Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- import matplotlib.pyplot as plt
- import numpy as np
- import time
- from scipy.optimize import fsolve
- class Link():
- def __init__(self, M, L):
- self.M = M
- self.L = L
- class Foot():
- def __init__(self, M, height, foot_length=0.1, isLeft=True):
- self.M = M
- self.foot_length = foot_length
- self.height = height
- self.angle = 0
- self.coord = (0,0)
- self.isLeft = isLeft
- self._tip = self.coord
- @property
- def tip(self):
- if self.isLeft:
- self._tip = (self.coord[0]+self.foot_length*np.cos(self.angle),
- self.coord[1]-self.foot_length*np.sin(self.angle))
- else:
- self._tip = (self.coord[0]-self.foot_length*np.cos(self.angle),
- self.coord[1]+self.foot_length*np.sin(self.angle))
- # self._tip = self.coord
- return self._tip
- @tip.setter
- def tip(self, val):
- self._tip = tuple(val)
- class Actuator():
- def __init__(self, link1, link2):
- self.angle = 0 # rad
- self.link1 = link1
- self.link2 = link2
- self.coord = (0,0)
- self.speed = 0 # rad/s
- self.time_seriesx = []
- self.time_seriesy = []
- def update(self, dt):
- self.angle += self.speed*dt
- class Robot():
- def __init__(self, world=None, can_topple=False, animate=False):
- self.left_planted = True
- self.right_planted = False
- self.build()
- if self.left_planted:
- self.l_L.coord = (0, 0)
- self.l_L.angle = 0
- elif self.right_planted:
- self.r_L.coord = (0, 0)
- self.r_L.angle = 0
- else:
- raise ValueError("Need initial conditions")
- if world is not None:
- self.world = world
- if can_topple:
- self.can_topple = True
- if animate:
- self.prepare_plot_for_animation()
- @property
- def is_stable(self):
- if not hasattr(self, 'can_topple'):
- return True
- if self.left_planted:
- _off = self.l_L.coord[0]
- sum_com = 0*self.l_L.M
- sum_com += self.l_mid.M*(self.servo2.coord[0]-_off)/2
- sum_com += self.r_mid.M*(self.servo2.coord[0]+(self.servo3.coord[0]-self.servo2.coord[0])/2-_off)
- sum_com += self.r_L.M*(self.servo3.coord[0]+(self.r_L.coord[0]-self.servo3.coord[0])/2-_off)
- sum_com /= self.l_L.M + self.l_mid.M + self.r_mid.M + self.r_L.M
- if sum_com > self.l_L.foot_length or sum_com < 0:
- return False
- else:
- return True
- elif self.right_planted:
- _off = self.r_L.coord[0]
- sum_com = 0*self.r_L.M
- sum_com += self.r_mid.M*(self.servo2.coord[0]-_off)/2
- sum_com += self.l_mid.M*(self.servo2.coord[0]-(self.servo1.coord[0]-self.servo2.coord[0])/2-_off)
- sum_com += self.l_L.M*(self.servo1.coord[0]-(self.l_L.coord[0]-self.servo1.coord[0])/2-_off)
- sum_com /= self.l_L.M + self.l_mid.M + self.r_mid.M + self.r_L.M
- if sum_com < -self.l_L.foot_length or sum_com > 0:
- return False
- else:
- return True
- def build(self):
- self.l_L = Foot(0.1, 0.1, isLeft=True)
- self.l_mid = Link(0.1, 0.1)
- self.r_mid = Link(0.1, 0.1)
- self.r_L = Foot(0.1, 0.1, isLeft=False)
- self.servo1 = Actuator(self.l_L,self.l_mid)
- self.servo2 = Actuator(self.l_mid,self.r_mid)
- self.servo3 = Actuator(self.r_mid, self.r_L)
- def goto(self, angs):
- self.servo1.angle = np.deg2rad(angs[0])
- self.servo2.angle = np.deg2rad(angs[1])
- self.servo3.angle = np.deg2rad(angs[2])
- self.forwards_kin()
- def forwards_kin(self):
- if not self.is_stable:
- raise Warning("Robot is unstable")
- if self.left_planted and not self.right_planted:
- # start building from left foot corner
- _ang = self.l_L.angle
- self.servo1.coord = (self.l_L.coord[0]+self.l_L.height*np.sin(_ang),
- self.l_L.coord[1]+self.l_L.height*np.cos(_ang))
- _ang += self.servo1.angle
- self.servo2.coord = (self.servo1.coord[0]+self.l_mid.L*np.sin(_ang),
- self.servo1.coord[1]+self.l_mid.L*np.cos(_ang))
- _ang += self.servo2.angle
- self.servo3.coord = (self.servo2.coord[0]+self.r_mid.L*np.sin(_ang),
- self.servo2.coord[1]+self.r_mid.L*np.cos(_ang))
- _ang += self.servo3.angle
- self.r_L.coord = (self.servo3.coord[0]+self.r_L.height*np.sin(_ang),
- self.servo3.coord[1]+self.r_L.height*np.cos(_ang))
- self.r_L.angle = _ang-np.pi
- if hasattr(self, 'world'):
- def ison(point, obst):
- if point[0] < obst.top[1][0] and point[0] > obst.top[0][0]:
- return True
- else:
- return False
- for obstacle in self.world.obstacles:
- if ison(self.r_L.coord, obstacle) or ison(self.r_L.tip, obstacle):
- if self.r_L.coord[1] < obstacle.top[0][1]:
- self.right_planted = True
- self.left_planted = False
- if self.r_L.coord[1] < obstacle.top[0][1]:
- self.translate(0, obstacle.top[0][1]-self.r_L.coord[1])
- hit_ground = (self.r_L.coord[1] < -0) and abs(self.r_L.angle) < np.deg2rad(10)
- if hit_ground:
- self.right_planted = True
- self.left_planted = False
- lowest_point = min(self.r_L.coord[1],self.r_L.coord[1])
- if lowest_point < -0:
- self.translate(0, -lowest_point)
- elif self.r_L.coord[1] < -0 or self.r_L.tip[1] < -0:
- raise Exception("Collision with ground at angle > 10 degrees")
- elif self.right_planted and not self.left_planted:
- # start building from right foot corner
- _ang = self.r_L.angle
- self.servo3.coord = (self.r_L.coord[0]+self.r_L.height*np.sin(_ang),
- self.r_L.coord[1]+self.r_L.height*np.cos(_ang))
- _ang -= self.servo3.angle
- self.servo2.coord = (self.servo3.coord[0]+self.r_mid.L*np.sin(_ang),
- self.servo3.coord[1]+self.r_mid.L*np.cos(_ang))
- _ang -= self.servo2.angle
- self.servo1.coord = (self.servo2.coord[0]+self.r_mid.L*np.sin(_ang),
- self.servo2.coord[1]+self.r_mid.L*np.cos(_ang))
- _ang -= self.servo1.angle
- self.l_L.coord = (self.servo1.coord[0]+self.l_L.height*np.sin(_ang),
- self.servo1.coord[1]+self.l_L.height*np.cos(_ang))
- self.l_L.angle = _ang+np.pi
- if hasattr(self, 'world'):
- def ison(point, obst):
- if point[0] < obst.top[1][0] and point[0] > obst.top[0][0]:
- return True
- else:
- return False
- for obstacle in self.world.obstacles:
- if ison(self.l_L.coord, obstacle) or ison(self.l_L.tip, obstacle):
- if self.l_L.coord[1] < obstacle.top[0][1]:
- self.left_planted = True
- self.right_planted = False
- if self.l_L.coord[1] < obstacle.top[0][1]:
- self.translate(0, obstacle.top[0][1]-self.l_L.coord[1])
- hit_ground = (self.l_L.coord[1] < -0) and abs(self.l_L.angle) < np.deg2rad(10)
- if hit_ground:
- self.left_planted = True
- self.right_planted = False
- lowest_point = min(self.l_L.coord[1],self.r_L.coord[1])
- if lowest_point < -0:
- self.translate(0, -lowest_point)
- elif self.l_L.coord[1] < -0 or self.l_L.tip[1] < -0:
- raise Exception("Collision with ground at angle > 10 degrees")
- # self.servo1.time_seriesx.append(self.servo1.coord[0])
- # self.servo1.time_seriesy.append(self.servo1.coord[1])
- # self.servo3.time_seriesx.append(self.servo3.coord[0])
- # self.servo3.time_seriesy.append(self.servo3.coord[1])
- def translate(self, x, y):
- objects = [self.l_L, self.r_L, self.servo1, self.servo2, self.servo3]
- for i in objects:
- tmp = list(i.coord)
- tmp[0] += x
- tmp[1] += y
- i.coord = tuple(tmp)
- tmp = list(self.l_L.tip)
- tmp[0] += x
- tmp[1] += y
- self.l_L.tip = tmp
- tmp = list(self.r_L.tip)
- tmp[0] += x
- tmp[1] += y
- self.r_L.tip = tmp
- def set_servo_speeds(self, speeds, deg=True):
- if deg:
- self.servo1.speed = np.deg2rad(speeds[0])
- self.servo2.speed = np.deg2rad(speeds[1])
- self.servo3.speed = np.deg2rad(speeds[2])
- else:
- self.servo1.speed = speeds[0]
- self.servo2.speed = speeds[1]
- self.servo3.speed = speeds[2]
- def get_angles(self, deg=True):
- if deg:
- return [np.rad2deg(self.servo1.angle),
- np.rad2deg(self.servo2.angle),
- np.rad2deg(self.servo3.angle)]
- else:
- return [self.servo1.angle,
- self.servo2.angle,
- self.servo3.angle]
- def prepare_plot_for_animation(self):
- plt.ion()
- self.l_L.plot = plt.plot(0, 0, 'r', linewidth=3)[0] # create plot handle
- self.l_mid.plot = plt.plot(0, 0, 'b', linewidth=3)[0] # create plot handle
- self.r_mid.plot = plt.plot(0, 0, 'm', linewidth=3)[0] # create plot handle
- self.r_L.plot = plt.plot(0, 0, 'y', linewidth=3)[0] # create plot handle
- plt.axhspan(-0.1,0)
- def plot(self):
- if hasattr(self.l_L, 'plot'):
- # animation
- self.l_L.plot.set_xdata([self.l_L.tip[0], self.l_L.coord[0], self.servo1.coord[0]])
- self.l_L.plot.set_ydata([self.l_L.tip[1], self.l_L.coord[1], self.servo1.coord[1]])
- self.l_mid.plot.set_xdata([self.servo1.coord[0], self.servo2.coord[0]])
- self.l_mid.plot.set_ydata([self.servo1.coord[1], self.servo2.coord[1]])
- self.r_mid.plot.set_xdata([self.servo2.coord[0], self.servo3.coord[0]])
- self.r_mid.plot.set_ydata([self.servo2.coord[1], self.servo3.coord[1]])
- self.r_L.plot.set_xdata([self.servo3.coord[0], self.r_L.coord[0], self.r_L.tip[0]])
- self.r_L.plot.set_ydata([self.servo3.coord[1], self.r_L.coord[1], self.r_L.tip[1]])
- plt.axis('equal')
- ax = plt.gca()
- ax.relim()
- ax.autoscale_view()
- plt.draw()
- else:
- #left foot
- plt.plot([self.l_L.tip[0], self.l_L.coord[0], self.servo1.coord[0]],
- [self.l_L.tip[1], self.l_L.coord[1], self.servo1.coord[1]], c='r')
- #mid left
- plt.plot([self.servo1.coord[0], self.servo2.coord[0]],
- [self.servo1.coord[1], self.servo2.coord[1]], c='b')
- #mid right
- plt.plot([self.servo2.coord[0], self.servo3.coord[0]],
- [self.servo2.coord[1], self.servo3.coord[1]], c='m')
- #right foot
- plt.plot([self.servo3.coord[0], self.r_L.coord[0], self.r_L.tip[0]],
- [self.servo3.coord[1], self.r_L.coord[1], self.r_L.tip[1]], c='y')
- plt.axis('equal')
- plt.show()
- def time_step(self, dt):
- self.servo1.update(dt)
- self.servo2.update(dt)
- self.servo3.update(dt)
- self.forwards_kin()
- if hasattr(self.l_L, 'plot'):
- self.plot()
- plt.pause(dt/2)
- class Obstacle():
- def __init__(self, height, dist, width=0.15):
- self.top = [(dist, height), (dist+width, height)]
- from matplotlib.patches import Rectangle
- rect = Rectangle((dist,0), width, height, color='k')
- plt.gca().add_patch(rect)
- class World():
- def __init__(self):
- self.g = 9.81
- self.obstacles = []
- def add_object(self, obj):
- self.obstacles.append(obj)
- class Planner():
- def __init__(self, robot, dt=0.01):
- self.r = robot
- self.next_obstacle = self.get_next_obstacle()
- self.start_recording()
- self.dt = dt
- ######## POSITION APPROACH ##########
- def move_elbow_to_pos(self, pos):
- def _equn(vec):
- a,b = vec
- return (self.r.l_mid.L*np.sin(a) + self.r.r_mid.L*np.sin(a+b) - pos[0],
- self.r.l_mid.L*np.cos(a) + self.r.r_mid.L*np.cos(a+b) - pos[1])
- theta1,theta2 = fsolve(_equn, (np.deg2rad(60), np.deg2rad(30)))
- out = [theta1, theta2, np.pi-(theta1+theta2)]
- return list(np.rad2deg(out))
- ######### VELOCITY APPROACH #########
- # not accurate!!!!
- def move_feet_rel_parallel(self, v):
- angles = self.r.get_angles(deg=False)
- def _equn(vec):
- a,b = vec
- return (a*self.r.l_mid.L*np.cos(angles[0]) - b*self.r.r_mid.L*np.sin(angles[0]+angles[1]-np.pi/2) - v[0],
- -a*self.r.l_mid.L*np.sin(angles[0]) - b*self.r.r_mid.L*np.cos(angles[0]+angles[1]-np.pi/2) - v[1])
- w1,w2 = fsolve(_equn, (0., 0.), factor=5, maxfev=1000)
- out = [w1, w2, -w1-w2] ##### ??? wtf
- return out
- def vel_ctrl(self, times, vels):
- if type(times) == int or type(times) == float:
- times = np.diff(np.arange(0,times, self.dt))
- elif type(times) == list:
- times = np.diff(times)
- if type(vels) == int or type(vels) == float:
- for i in times:
- omegas = self.move_feet_rel_parallel(vels)
- self.r.set_servo_speeds(omegas, deg=False)
- self.r.time_step(i)
- elif type(vels) == list:
- for i, vel in zip(times,vels):
- omegas = self.move_feet_rel_parallel(vel)
- self.r.set_servo_speeds(omegas, deg=False)
- self.r.time_step(i)
- def pos_ctrl(self, times, posns):
- if type(times) == int or type(times) == float:
- times = np.diff(np.arange(0,times, self.dt))
- elif type(times) == list:
- times = np.diff(times)
- for i, posn in zip(times, posns):
- angs = self.move_elbow_to_pos(posn)
- self.r.goto(angs)
- self.r.time_step(i)
- self.joint_trajs.append(angs)
- def take_step(self, length, T=1, model='circle', height=False):
- if height is False:
- height = length
- max_reach = self.r.l_mid.L + self.r.r_mid.L
- min_reach = min(self.r.l_L.foot_length, self.r.r_L.foot_length)
- if abs(length) > max_reach - min_reach:
- raise ValueError("Max step size is {0:.3f}m".format(max_reach-min_reach))
- if self.r.left_planted and self.r.right_planted:
- self.r.right_planted = False
- resting = (min_reach+max_reach)/2
- t = np.arange(0, T+self.dt, step=self.dt)
- if model == 'circle':
- w = 2*np.pi/T
- if length < 0:
- w *= -1
- x = resting - length*np.cos(w*t)/2
- y = 0 + height*np.sin(w*t)/2
- elif model == 'rect':
- vel = (2*height+2*length)/T
- ts = np.array_split(t,8) # TODO: Split this into non-equal sections
- x1 = resting - length*np.ones_like(ts[0])/2
- x2 = resting - length/2 + (ts[1]-ts[1][0])*vel
- x3 = resting + (ts[2]-ts[2][0])*vel
- x4 = resting + length*np.ones_like(ts[3])/2
- x5 = resting + length*np.ones_like(ts[4])/2
- x6 = resting + length/2 - (ts[5]-ts[5][0])*vel
- x7 = resting - (ts[6]-ts[6][0])*vel
- x8 = resting - length*np.ones_like(ts[7])/2
- y1 = ts[0]*vel
- y2 = height*np.ones_like(ts[1])/2
- y3 = height*np.ones_like(ts[2])/2
- y4 = height/2 - (ts[3]-ts[3][0])*vel
- y5 = -(ts[4]-ts[4][0])*vel
- y6 = -height*np.ones_like(ts[5])/2
- y7 = -height*np.ones_like(ts[6])/2
- y8 = -height/2 + (ts[7]-ts[7][0])*vel
- x = np.concatenate((x1,x2,x3,x4,x5,x6,x7,x8), axis=-1)
- y = np.concatenate((y1,y2,y3,y4,y5,y6,y7,y8), axis=-1)
- posn = np.vstack((x, y)).T
- self.pos_ctrl(list(t), list([list(i) for i in posn]))
- def circle_walk(self, N=10, normal_stride_length=0.09):
- for _ in range(N):
- dist = self.get_next_obstacle()
- if dist == np.inf:
- self.take_step(normal_stride_length, height=normal_stride_length/2)
- continue
- N_normal_steps = dist // normal_stride_length
- if N_normal_steps > 0:
- self.take_step(normal_stride_length, height=normal_stride_length/2)
- elif dist % normal_stride_length < 0.006: # step up or down
- self.take_step(normal_stride_length, height=normal_stride_length*1.5)
- else: # get close
- self.take_step(dist % normal_stride_length - 0.005)
- def rect_walk(self, N=10, normal_stride_length=0.09):
- for _ in range(N):
- dist = self.get_next_obstacle()
- if dist == np.inf:
- self.take_step(normal_stride_length, model='rect')
- continue
- N_normal_steps = dist // normal_stride_length
- if N_normal_steps > 0:
- self.take_step(normal_stride_length, model='rect')
- elif dist % normal_stride_length < 0.001:
- self.take_step(normal_stride_length, model='rect')
- else:
- self.take_step(dist % normal_stride_length - 0.0005, model='rect')
- def get_next_obstacle(self):
- prev = np.inf
- for obs in self.r.world.obstacles:
- dist_front = obs.top[0][0] - self.r.r_L.coord[0]
- dist_back = obs.top[1][0] - self.r.r_L.coord[0]
- if dist_front < 0:
- if dist_back < 0:
- continue # past the object
- else:
- prev = dist_back # on the object
- else:
- if dist_front < prev:
- prev = dist_front
- return prev
- def start_recording(self):
- self.joint_trajs = []
- def plot_joint_trajs(self):
- plt.ioff()
- plt.figure()
- plt.subplot(111)
- trajs = np.asarray(self.joint_trajs)
- t = np.arange(0, trajs.shape[0]*self.dt, self.dt)
- plt.plot(t, trajs[:,0], label='Joint 1')
- plt.plot(t, trajs[:,1], label='Joint 2')
- plt.plot(t, trajs[:,2], label='Joint 3')
- plt.ylabel('Joint angle /degrees')
- plt.xlabel('Time /s')
- plt.legend()
- plt.show()
- plt.ion()
- def plot_joint_vels(self):
- plt.ioff()
- plt.figure()
- plt.subplot(111)
- trajs = np.asarray(self.joint_trajs)
- t = np.arange(0, trajs.shape[0]*self.dt, self.dt)[:-1]
- plt.plot(t, np.diff(trajs[:,0]), label='Joint 1')
- plt.plot(t, np.diff(trajs[:,1]), label='Joint 2')
- plt.plot(t, np.diff(trajs[:,2]), label='Joint 3')
- plt.ylabel('Joint speed / degrees/s')
- plt.xlabel('Time /s')
- plt.legend()
- plt.show()
- plt.ion()
- def plot_joint_accels(self):
- plt.ioff()
- plt.figure()
- plt.subplot(111)
- trajs = np.asarray(self.joint_trajs)
- t = np.arange(0, trajs.shape[0]*self.dt, self.dt)[1:-1]
- plt.plot(t, np.diff(np.diff(trajs[:,0])), label='Joint 1')
- plt.plot(t, np.diff(np.diff(trajs[:,1])), label='Joint 2')
- plt.plot(t, np.diff(np.diff(trajs[:,2])), label='Joint 3')
- plt.ylabel('Joint acceleration / degrees/s^2')
- plt.xlabel('Time /s')
- plt.legend()
- plt.show()
- plt.ion()
- world = World()
- # world.add_object(Obstacle(0.03, 0.3))
- # world.add_object(Obstacle(0.06, 0.45))
- robot = Robot(world=world, can_topple=False, animate=False)
- robot.goto([60,60,60])
- planner = Planner(robot)
- planner.circle_walk(6)
- import pyfirmata
- board = pyfirmata.Arduino('/dev/ttyACM0')
- s1 = board.get_pin('d:9:s')
- s2 = board.get_pin('d:10:s')
- s3 = board.get_pin('d:11:s')
- print("starting")
- off = [10,40,80]
- time.sleep(5)
- # for [a1,a2,a3] in planner.joint_trajs:
- # s1.write(a1+off[0])
- # s2.write(a2+off[1])
- # s3.write(a3-off[2])
- # time.sleep(0.02)
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement