Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Updated axis limit computation and added other common trajectories. #1

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
*.pyc
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you can remove this now, I already add .gitignore

model/*.pyc
test/*.pyc
utils/*.pyc


86 changes: 86 additions & 0 deletions controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@
k_p_psi = 80
k_d_psi = 5

# LQR Gains

def run(quad, des_state):
x, y, z = quad.position()
x_dot, y_dot, z_dot = quad.velocity()
Expand Down Expand Up @@ -54,4 +56,88 @@ def run(quad, des_state):
k_p_theta * (des_theta - theta) + k_d_theta * (q_des - q),
k_p_psi * (des_psi - psi) + k_d_psi * (r_des - r)]]).T

print(F)
print(M)
return F, M

def run_LQR(quad, des_state):
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you can, could you refactor this controller.py file a bit so it only includes control algoriuthm, and seperate out calibration parameters.

  • pid() - move pid specific parameters into a seperate file.
  • lqr() - could you refactor a bit so lqr specific matrix constant goes into a seperate file similar to model.params

we should create a folder called "control" and only includes controller.py and params for now.


# get drone state
x, y, z = quad.position()
x_dot, y_dot, z_dot = quad.velocity()
phi, theta, psi = quad.attitude()
p, q, r = quad.omega()

# get desired state
des_x, des_y, des_z = des_state.pos
des_x_dot, des_y_dot, des_z_dot = des_state.vel
des_x_ddot, des_y_ddot, des_z_ddot = des_state.acc
des_psi = des_state.yaw
des_psi_dot = des_state.yawdot
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it does not look like these are used, please review the input parameters, and only takes the one that is used.



# For the LQR controller, the input to the controller are the states -x-,
# and reference values -r-, and the output -u- (inputs to the drone)
# ar the Thrust and desired moments, (F,M) that should be applied to
# the drone
#
# In general u = Nu*r - K(x -Nx*r) = -K*x + (Nu + K*Nx)*r
# where K are the LQR gains, Nu and Nx are reference input matrices.
# See more at p.493 "Feedback Control of Dynamic Systems, Franklink G,
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good summery, could you write some simple description on how you tune and design lqr matrix, and their effect on the control in general?

# Powell, J. Emami-Naeini, Abbas"

# This LQR controller was designed assuming a linearized model of the drone.

# Z dynamics LQR Gains and input matrices
K_z = np.matrix([10.0, 2.04709])
Nu_z = np.matrix([0.0])
Nx_z = np.matrix([[1.0],[0.0]])

# For Z dynamics, the only coupled variables are the z and z_dot
# so make an state vector for this dynamics
state = np.matrix([[z],[z_dot]])
# Calculating thrust, note it is identical to above equation for u
F = -K_z*state +(Nu_z + K_z*Nx_z)*des_z

# X dynamics LQR Gains and input matrices
K_x = np.matrix([10.0, 10.6217, 14.762, 1.042])
Nu_x = np.matrix([0.0])
Nx_x = np.matrix([[1.0],[0.0],[0.0],[0.0]])

# For X dynamics, the only coupled variables are the x, x_dot, theta and theta_dot
# so make an state vector for this dynamics
state = np.matrix([[x],[x_dot],[theta],[q]])
# Calculating torque, note it is identical to above equation for u
L = -K_x*state +(Nu_x + K_x*Nx_x)*des_x


# Y dynamics LQR Gains and input matrices
K_y = np.matrix([-10.0, -10.6208, 14.7408, 1.039])
Nu_y = np.matrix([0.0])
Nx_y = np.matrix([[1.0],[0.0],[0.0],[0.0]])

# For X dynamics, the only coupled variables are the x, x_dot, theta and theta_dot
# so make an state vector for this dynamics
state = np.matrix([[y],[y_dot],[phi],[p]])
# Calculating torque, note it is identical to above equation for u
O = -K_y*state +(Nu_y + K_y*Nx_y)*des_y


# Yaw dynamics LQR Gains and input matrices
K_yaw = np.matrix([10.0, 1.14])
Nu_yaw = np.matrix([0.0])
Nx_yaw = np.matrix([[1.0],[0.0]])

# For Yaw dynamics, the only coupled variables are the z and z_dot
# so make an state vector for this dynamics
state = np.matrix([[psi],[r]])
# Calculating thrust, note it is identical to above equation for u
N = -K_yaw*state +(Nu_yaw + K_yaw*Nx_yaw)*des_psi

# Now create the torque vector
M = np.array([[O.item(0)], [L.item(0)], [N.item(0)]])

print(F.item(0))
print(M)

return F.item(0), M
2 changes: 1 addition & 1 deletion model/params.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@

invI = np.linalg.inv(I)
arm_length = 0.086 # meter
height = 0.05
height = 0.00
minF = 0.0
maxF = 2.0 * mass * g
L = arm_length
Expand Down
33 changes: 31 additions & 2 deletions quadPlot.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,18 @@
history = np.zeros((500,3))
count = 0

def plot_quad_3d(waypoints, args=()):
def plot_quad_3d(waypoints, init_pos, args=()):
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

see if you can adapt your change to my thread-less part, I will review this part later on. I suggest you seperate this review into two, one is general clean up and quadPlot related change, the other is lqr controller. You can do this in your branch now and submit incremental change, which it is easy for me to review.

fig = plt.figure()
ax = fig.add_axes([0, 0, 1, 1], projection='3d')
ax.plot([], [], [], '-', c='cyan')[0]
ax.plot([], [], [], '-', c='red')[0]
ax.plot([], [], [], '-', c='blue', marker='o', markevery=2)[0]
ax.plot([], [], [], '.', c='red', markersize=4)[0]
ax.plot([], [], [], '.', c='blue', markersize=2)[0]
set_limit((-0.5,0.5), (-0.5,0.5), (-0.5,8))
#set_limit((-0.5,0.5), (-0.5,0.5), (-0.5,8))
set_limit2(waypoints, init_pos)

set_ax_names("x","y","z")
plot_waypoints(waypoints)
an = animation.FuncAnimation(fig, _callback, fargs = args, init_func=None,
frames=400, interval=10, blit=False)
Expand All @@ -45,6 +48,32 @@ def set_limit(x, y, z):
ax.set_ylim(y)
ax.set_zlim(z)

def set_limit2(waypoints, pos):
"""
Set the drawing limits based on the waypoints
the quadrotor should fly and start point.

"""
ax = plt.gca()
x_min = min(pos[0], min(waypoints[:,0]))
x_max = max(pos[0], max(waypoints[:,0]))
ax.set_xlim(x_min,x_max)

y_min = min(pos[1], min(waypoints[:,1]))
y_max = max(pos[1], max(waypoints[:,1]))
ax.set_ylim(y_min,y_max)

z_min = min(pos[2], min(waypoints[:,2]))
z_max = max(pos[2], max(waypoints[:,2]))
ax.set_zlim(z_min,z_max)

def set_ax_names(x, y, z):
ax = plt.gca()
ax.set_xlabel(x)
ax.set_ylabel(y)
ax.set_zlabel(z)


def set_frame(frame):
# convert 3x6 world_frame matrix into three line_data objects which is 3x2 (row:point index, column:x,y,z)
lines_data = [frame[:,[0,2]], frame[:,[1,3]], frame[:,[4,5]]]
Expand Down
14 changes: 11 additions & 3 deletions runsim.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,26 +24,34 @@ def render(quad):

def attitudeControl(quad, time, waypoints, coeff_x, coeff_y, coeff_z):
desired_state = trajGen3D.generate_trajectory(time[0], 1.2, waypoints, coeff_x, coeff_y, coeff_z)
F, M = controller.run(quad, desired_state)
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please rename original run to run_pid() as mentioned above

F, M = controller.run_LQR(quad, desired_state)
quad.update(dt, F, M)
time[0] += dt

def main():
# define initial conditions
pos = (0.5,0,0)
attitude = (0,0,0)
# create new quadrotor
quadcopter = Quadcopter(pos, attitude)

sched = scheduler.Scheduler()
waypoints = trajGen3D.get_helix_waypoints(0, 9)
# Generate a trajectory with 9 waypoints
waypoints = trajGen3D.get_poly_waypoints(0, 9)
# Get coefficients of 8 degree polinomial that represents the
# continuos trajectory that drone will follow in space
(coeff_x, coeff_y, coeff_z) = trajGen3D.get_MST_coefficients(waypoints)
#
sched.add_task(attitudeControl, dt, (quadcopter,time,waypoints,coeff_x,coeff_y,coeff_z))
kEvent_Render = sched.add_event(render, (quadcopter,))

try:
plt.plot_quad_3d(waypoints, (sched, kEvent_Render))
plt.plot_quad_3d(waypoints, pos, (sched, kEvent_Render))
except KeyboardInterrupt:
print ("attempting to close threads.")
sched.stop()
print ("terminated.")

if __name__ == "__main__":
main()

Binary file removed sim.gif
Binary file not shown.
46 changes: 44 additions & 2 deletions trajGen3D.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,56 @@
current_heading = np.zeros(2)

def get_helix_waypoints(t, n):
""" The function generate n helix waypoints from the given time t
output waypoints shape is [n, 3]
""" The function generate n helix waypoints from the given time t.
Output waypoints shape is [n, 3]
"""
waypoints_t = np.linspace(t, t + 2*np.pi, n)
x = 0.5*np.cos(waypoints_t)
y = 0.5*np.sin(waypoints_t)
z = waypoints_t


return np.stack((x, y, z), axis=-1)

def get_poly_waypoints(t,n):
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since your get_leminiscata_waypoints does not seem to be used yet, I think we should create a folder call trajectories, and move all trajectories generation files into it and create a new one for your work and submit that change as a review.
Make sure you write some unit test in the test folder :)

""" The function generate n waypoints from a polynomial at given t.
The polynomial is
x = k1*t^2
y = k1*t^3
z = k2*t
Output waypoints shape is [n, 3]
"""
waypoints_t = np.linspace(t, t + 2*np.pi, n)
k1 = 0.1
k2 = 0.5
x = (k1*waypoints_t)**2
y = (k1*waypoints_t)**3
z = k2*waypoints_t
#x = waypoints_t
#y = waypoints_t
#z = waypoints_t


return np.stack((x, y, z), axis=-1)

def get_leminiscata_waypoints(t,n):
""" The function generate n waypoints from a leminiscata at given t.
The leminiscata is
x = k1 * cos(wt/2)
y = k1 * sin(wt)
z = k2 * t
Output waypoints shape is [n, 3]
"""
waypoints_t = np.linspace(t, t + 2*np.pi, n)

k1 = 0.5
k2 = 0.5
w = 0.1

x = k1*np.cos(w*waypoints_t/2.0)
y = k1*np.sin(w*waypoints_t)
z = k2*waypoints_t

return np.stack((x, y, z), axis=-1)

def get_MST_coefficients(waypoints):
Expand Down