2011-12-02 00:13:50 -04:00
|
|
|
#!/usr/bin/env python
|
|
|
|
|
|
|
|
from aircraft import Aircraft
|
2012-03-22 08:50:47 -03:00
|
|
|
import util, time, math
|
|
|
|
from math import degrees, radians
|
|
|
|
from rotmat import Vector3, Matrix3
|
2012-01-04 04:13:15 -04:00
|
|
|
|
|
|
|
class Motor(object):
|
|
|
|
def __init__(self, angle, clockwise, servo):
|
|
|
|
self.angle = angle # angle in degrees from front
|
|
|
|
self.clockwise = clockwise # clockwise == true, anti-clockwise == false
|
|
|
|
self.servo = servo # what servo output drives this motor
|
|
|
|
|
|
|
|
|
|
|
|
def build_motors(frame):
|
|
|
|
'''build a motors list given a frame type'''
|
2012-01-04 06:45:58 -04:00
|
|
|
frame = frame.lower()
|
|
|
|
if frame in [ 'quad', '+', 'x' ]:
|
2012-01-04 04:13:15 -04:00
|
|
|
motors = [
|
|
|
|
Motor(90, False, 1),
|
|
|
|
Motor(270, False, 2),
|
|
|
|
Motor(0, True, 3),
|
|
|
|
Motor(180, True, 4),
|
|
|
|
]
|
2012-01-04 06:45:58 -04:00
|
|
|
if frame in [ 'x', 'quadx' ]:
|
2012-01-04 04:13:15 -04:00
|
|
|
for i in range(4):
|
|
|
|
motors[i].angle -= 45.0
|
2012-01-04 06:45:58 -04:00
|
|
|
elif frame in ["y6"]:
|
|
|
|
motors = [
|
|
|
|
Motor(60, False, 1),
|
|
|
|
Motor(60, True, 7),
|
|
|
|
Motor(180, True, 4),
|
|
|
|
Motor(180, False, 8),
|
|
|
|
Motor(-60, True, 2),
|
|
|
|
Motor(-60, False, 3),
|
|
|
|
]
|
|
|
|
elif frame in ["hexa", "hexa+"]:
|
|
|
|
motors = [
|
|
|
|
Motor(0, True, 1),
|
|
|
|
Motor(60, False, 4),
|
|
|
|
Motor(120, True, 8),
|
|
|
|
Motor(180, False, 2),
|
|
|
|
Motor(240, True, 3),
|
|
|
|
Motor(300, False, 7),
|
|
|
|
]
|
|
|
|
elif frame in ["hexax"]:
|
|
|
|
motors = [
|
|
|
|
Motor(30, False, 7),
|
|
|
|
Motor(90, True, 1),
|
|
|
|
Motor(150, False, 4),
|
|
|
|
Motor(210, True, 8),
|
|
|
|
Motor(270, False, 2),
|
|
|
|
Motor(330, True, 3),
|
|
|
|
]
|
2012-01-04 07:15:17 -04:00
|
|
|
elif frame in ["octa", "octa+", "octax" ]:
|
2012-01-04 04:13:15 -04:00
|
|
|
motors = [
|
|
|
|
Motor(0, True, 1),
|
|
|
|
Motor(180, True, 2),
|
|
|
|
Motor(45, False, 3),
|
|
|
|
Motor(135, False, 4),
|
2015-02-25 00:04:09 -04:00
|
|
|
Motor(-45, False, 5),
|
|
|
|
Motor(-135, False, 6),
|
|
|
|
Motor(270, True, 7),
|
|
|
|
Motor(90, True, 8),
|
2012-01-04 04:13:15 -04:00
|
|
|
]
|
2012-01-04 07:15:17 -04:00
|
|
|
if frame == 'octax':
|
|
|
|
for i in range(8):
|
|
|
|
motors[i].angle += 22.5
|
2015-02-25 00:04:09 -04:00
|
|
|
elif frame in ["octa-quad"]:
|
|
|
|
motors = [
|
|
|
|
Motor( 45, False, 1),
|
|
|
|
Motor( -45, True, 2),
|
|
|
|
Motor(-135, False, 3),
|
|
|
|
Motor( 135, True, 4),
|
|
|
|
Motor( -45, False, 5),
|
|
|
|
Motor( 45, True, 6),
|
|
|
|
Motor( 135, False, 7),
|
|
|
|
Motor(-135, True, 8),
|
|
|
|
]
|
2012-01-04 04:13:15 -04:00
|
|
|
else:
|
|
|
|
raise RuntimeError("Unknown multicopter frame type '%s'" % frame)
|
|
|
|
|
|
|
|
return motors
|
|
|
|
|
|
|
|
|
|
|
|
class MultiCopter(Aircraft):
|
|
|
|
'''a MultiCopter'''
|
|
|
|
def __init__(self, frame='+',
|
2015-04-07 20:23:59 -03:00
|
|
|
hover_throttle=0.51,
|
2013-05-03 02:52:16 -03:00
|
|
|
terminal_velocity=15.0,
|
2012-01-04 04:13:15 -04:00
|
|
|
frame_height=0.1,
|
2013-05-02 23:20:26 -03:00
|
|
|
mass=1.5):
|
2011-12-02 00:13:50 -04:00
|
|
|
Aircraft.__init__(self)
|
2012-01-04 04:13:15 -04:00
|
|
|
self.motors = build_motors(frame)
|
|
|
|
self.motor_speed = [ 0.0 ] * len(self.motors)
|
|
|
|
self.mass = mass # Kg
|
|
|
|
self.hover_throttle = hover_throttle
|
|
|
|
self.terminal_velocity = terminal_velocity
|
2012-03-22 08:50:47 -03:00
|
|
|
self.terminal_rotation_rate = 4*radians(360.0)
|
2012-01-04 04:13:15 -04:00
|
|
|
self.frame_height = frame_height
|
2011-12-02 00:13:50 -04:00
|
|
|
|
|
|
|
# scaling from total motor power to Newtons. Allows the copter
|
|
|
|
# to hover against gravity when each motor is at hover_throttle
|
2012-01-04 04:13:15 -04:00
|
|
|
self.thrust_scale = (self.mass * self.gravity) / (len(self.motors) * self.hover_throttle)
|
2011-12-02 00:13:50 -04:00
|
|
|
|
2015-03-21 11:29:28 -03:00
|
|
|
self.last_time = self.time_now
|
2011-12-02 00:13:50 -04:00
|
|
|
|
|
|
|
def update(self, servos):
|
2012-01-04 04:13:15 -04:00
|
|
|
for i in range(0, len(self.motors)):
|
|
|
|
servo = servos[self.motors[i].servo-1]
|
|
|
|
if servo <= 0.0:
|
2012-03-22 08:50:47 -03:00
|
|
|
|
2011-12-02 00:13:50 -04:00
|
|
|
self.motor_speed[i] = 0
|
|
|
|
else:
|
2012-01-04 04:13:15 -04:00
|
|
|
self.motor_speed[i] = servo
|
2011-12-02 00:13:50 -04:00
|
|
|
|
|
|
|
m = self.motor_speed
|
|
|
|
|
|
|
|
# how much time has passed?
|
2015-03-21 11:29:28 -03:00
|
|
|
t = self.time_now
|
2011-12-02 00:13:50 -04:00
|
|
|
delta_time = t - self.last_time
|
|
|
|
self.last_time = t
|
|
|
|
|
2012-03-22 08:50:47 -03:00
|
|
|
# rotational acceleration, in rad/s/s, in body frame
|
|
|
|
rot_accel = Vector3(0,0,0)
|
2012-01-04 04:13:15 -04:00
|
|
|
thrust = 0.0
|
|
|
|
for i in range(len(self.motors)):
|
2012-03-22 08:50:47 -03:00
|
|
|
rot_accel.x += -radians(5000.0) * math.sin(radians(self.motors[i].angle)) * m[i]
|
|
|
|
rot_accel.y += radians(5000.0) * math.cos(radians(self.motors[i].angle)) * m[i]
|
2012-01-04 04:13:15 -04:00
|
|
|
if self.motors[i].clockwise:
|
2012-03-22 08:50:47 -03:00
|
|
|
rot_accel.z -= m[i] * radians(400.0)
|
2012-01-04 04:13:15 -04:00
|
|
|
else:
|
2012-03-22 08:50:47 -03:00
|
|
|
rot_accel.z += m[i] * radians(400.0)
|
2012-01-04 04:13:15 -04:00
|
|
|
thrust += m[i] * self.thrust_scale # newtons
|
2011-12-02 00:13:50 -04:00
|
|
|
|
2012-03-22 08:50:47 -03:00
|
|
|
# rotational air resistance
|
|
|
|
rot_accel.x -= self.gyro.x * radians(5000.0) / self.terminal_rotation_rate
|
|
|
|
rot_accel.y -= self.gyro.y * radians(5000.0) / self.terminal_rotation_rate
|
|
|
|
rot_accel.z -= self.gyro.z * radians(400.0) / self.terminal_rotation_rate
|
2011-12-12 07:08:10 -04:00
|
|
|
|
2011-12-05 02:44:04 -04:00
|
|
|
# update rotational rates in body frame
|
2012-03-22 08:50:47 -03:00
|
|
|
self.gyro += rot_accel * delta_time
|
2011-12-02 00:13:50 -04:00
|
|
|
|
2012-03-22 08:50:47 -03:00
|
|
|
# update attitude
|
|
|
|
self.dcm.rotate(self.gyro * delta_time)
|
2012-07-04 02:59:15 -03:00
|
|
|
self.dcm.normalize()
|
2011-12-02 00:13:50 -04:00
|
|
|
|
|
|
|
# air resistance
|
|
|
|
air_resistance = - self.velocity * (self.gravity/self.terminal_velocity)
|
|
|
|
|
2012-03-22 08:50:47 -03:00
|
|
|
accel_body = Vector3(0, 0, -thrust / self.mass)
|
|
|
|
accel_earth = self.dcm * accel_body
|
|
|
|
accel_earth += Vector3(0, 0, self.gravity)
|
|
|
|
accel_earth += air_resistance
|
2012-10-30 14:36:19 -03:00
|
|
|
|
|
|
|
# add in some wind (turn force into accel by dividing by mass).
|
2013-01-14 03:02:53 -04:00
|
|
|
# NOTE: disable this drag correction until we work out
|
|
|
|
# why it is blowing up
|
|
|
|
# accel_earth += self.wind.drag(self.velocity) / self.mass
|
2011-12-12 19:11:10 -04:00
|
|
|
|
2012-03-23 02:24:52 -03:00
|
|
|
# if we're on the ground, then our vertical acceleration is limited
|
|
|
|
# to zero. This effectively adds the force of the ground on the aircraft
|
|
|
|
if self.on_ground() and accel_earth.z > 0:
|
|
|
|
accel_earth.z = 0
|
|
|
|
|
|
|
|
# work out acceleration as seen by the accelerometers. It sees the kinematic
|
|
|
|
# acceleration (ie. real movement), plus gravity
|
|
|
|
self.accel_body = self.dcm.transposed() * (accel_earth + Vector3(0, 0, -self.gravity))
|
|
|
|
|
2015-04-21 08:33:27 -03:00
|
|
|
# add some noise
|
|
|
|
self.add_noise(thrust / (self.thrust_scale * len(self.motors)))
|
|
|
|
|
2011-12-02 00:13:50 -04:00
|
|
|
# new velocity vector
|
2012-03-22 08:50:47 -03:00
|
|
|
self.velocity += accel_earth * delta_time
|
2011-12-02 00:13:50 -04:00
|
|
|
|
|
|
|
# new position vector
|
|
|
|
old_position = self.position.copy()
|
|
|
|
self.position += self.velocity * delta_time
|
|
|
|
|
|
|
|
# constrain height to the ground
|
2012-03-23 02:24:52 -03:00
|
|
|
if self.on_ground():
|
|
|
|
if not self.on_ground(old_position):
|
2012-03-22 08:50:47 -03:00
|
|
|
print("Hit ground at %f m/s" % (self.velocity.z))
|
2011-12-02 00:13:50 -04:00
|
|
|
|
2012-03-22 08:50:47 -03:00
|
|
|
self.velocity = Vector3(0, 0, 0)
|
|
|
|
# zero roll/pitch, but keep yaw
|
|
|
|
(r, p, y) = self.dcm.to_euler()
|
|
|
|
self.dcm.from_euler(0, 0, y)
|
2012-01-04 04:13:15 -04:00
|
|
|
|
2012-03-22 08:50:47 -03:00
|
|
|
self.position = Vector3(self.position.x, self.position.y,
|
|
|
|
-(self.ground_level + self.frame_height - self.home_altitude))
|
|
|
|
|
|
|
|
# update lat/lon/altitude
|
2015-04-01 13:11:25 -03:00
|
|
|
self.update_position()
|