mirror of https://github.com/ArduPilot/ardupilot
269 lines
12 KiB
Python
269 lines
12 KiB
Python
#!/usr/bin/env python
|
|
|
|
from aircraft import Aircraft
|
|
import util, time, math
|
|
from math import degrees, radians, cos, sin, tan
|
|
from rotmat import Vector3, Matrix3
|
|
|
|
class Gimbal3Axis(object):
|
|
'''a gimbal simulation'''
|
|
def __init__(self, vehicle):
|
|
# vehicle is the vehicle (usually a multicopter) that the gimbal is attached to
|
|
self.vehicle = vehicle
|
|
|
|
# URL of SITL 2nd telem port
|
|
self.mavlink_url = 'tcp:127.0.0.1:5762'
|
|
|
|
# rotation matrix (gimbal body -> earth)
|
|
self.dcm = None
|
|
|
|
# time of last update
|
|
self.last_update_t = time.time()
|
|
|
|
# true angular rate of gimbal in body frame (rad/s)
|
|
self.gimbal_angular_rate = Vector3()
|
|
|
|
# observed angular rate (including biases)
|
|
self.gyro = Vector3()
|
|
|
|
# joint angles, in radians. in yaw/roll/pitch order. Relative to fwd.
|
|
# So 0,0,0 points forward.
|
|
# Pi/2,0,0 means pointing right
|
|
# 0, Pi/2, 0 means pointing fwd, but rolled 90 degrees to right
|
|
# 0, 0, -Pi/2, means pointing down
|
|
self.joint_angles = Vector3()
|
|
|
|
# physical constraints on joint angles in (roll, pitch, azimuth) order
|
|
self.lower_joint_limits = Vector3(radians(-40), radians(-135), radians(-7.5))
|
|
self.upper_joint_limits = Vector3(radians(40), radians(45), radians(7.5))
|
|
self.travelLimitGain = 20
|
|
|
|
# true gyro bias
|
|
self.true_gyro_bias = Vector3()
|
|
|
|
##################################
|
|
# reporting variables. gimbal pushes these to vehicle code over MAVLink at approx 100Hz
|
|
|
|
# reporting rate in Hz
|
|
self.reporting_rate = 100
|
|
|
|
# integral of gyro vector over last time interval. In radians
|
|
self.delta_angle = Vector3()
|
|
|
|
# integral of accel vector over last time interval. In m/s
|
|
self.delta_velocity = Vector3()
|
|
# we also push joint_angles
|
|
|
|
##################################
|
|
# control variables from the vehicle
|
|
|
|
# angular rate in rad/s. In body frame of gimbal
|
|
self.demanded_angular_rate = Vector3(radians(0), radians(0), radians(0))
|
|
|
|
# gyro bias provided by EKF on vehicle. In rad/s.
|
|
# Should be subtracted from the gyro readings to get true body rotatation rates
|
|
self.supplied_gyro_bias = Vector3()
|
|
|
|
###################################
|
|
# communication variables
|
|
self.connection = None
|
|
self.last_report_t = time.time()
|
|
self.last_heartbeat_t = time.time()
|
|
self.seen_heartbeat = False
|
|
self.seen_gimbal_control = False
|
|
self.last_print_t = time.time()
|
|
self.vehicle_component_id = None
|
|
self.last_report_t = time.time()
|
|
|
|
def update(self):
|
|
'''update the gimbal state'''
|
|
|
|
# calculate delta time in seconds
|
|
now = time.time()
|
|
delta_t = now - self.last_update_t
|
|
self.last_update_t = now
|
|
|
|
if self.dcm is None:
|
|
# start with copter rotation matrix
|
|
self.dcm = self.vehicle.dcm.copy()
|
|
|
|
# take a copy of the demanded rates to bypass the limiter function for testing
|
|
demRateRaw = self.demanded_angular_rate
|
|
|
|
# 1) Rotate the copters rotation rates into the gimbals frame of reference
|
|
# copterAngRate_G = transpose(DCMgimbal)*DCMcopter*copterAngRate
|
|
copterAngRate_G = self.dcm.transposed()*self.vehicle.dcm*self.vehicle.gyro
|
|
#print("copterAngRate_G ", copterAngRate_G)
|
|
|
|
# 2) Subtract the copters body rates to obtain a copter relative rotational
|
|
# rate vector (X,Y,Z) in gimbal sensor frame
|
|
# relativeGimbalRate(X,Y,Z) = gimbalRateDemand - copterAngRate_G
|
|
relativeGimbalRate = self.demanded_angular_rate - copterAngRate_G
|
|
#print("relativeGimbalRate ", relativeGimbalRate)
|
|
|
|
# calculate joint angles (euler312 order)
|
|
# calculate copter -> gimbal rotation matrix
|
|
rotmat_copter_gimbal = self.dcm.transposed() * self.vehicle.dcm
|
|
self.joint_angles = Vector3(*rotmat_copter_gimbal.transposed().to_euler312())
|
|
#print("joint_angles ", self.joint_angles)
|
|
|
|
# 4) For each of the three joints, calculate upper and lower rate limits
|
|
# from the corresponding angle limits and current joint angles
|
|
#
|
|
# upperRatelimit = (jointAngle - lowerAngleLimit) * travelLimitGain
|
|
# lowerRatelimit = (jointAngle - upperAngleLimit) * travelLimitGain
|
|
#
|
|
# travelLimitGain is equal to the inverse of the bump stop time constant and
|
|
# should be set to something like 20 initially. If set too high it can cause
|
|
# the rates to 'ring' when they the limiter is in force, particularly given
|
|
# we are using a first order numerical integration.
|
|
upperRatelimit = -(self.joint_angles - self.upper_joint_limits) * self.travelLimitGain
|
|
lowerRatelimit = -(self.joint_angles - self.lower_joint_limits) * self.travelLimitGain
|
|
|
|
# 5) Calculate the gimbal joint rates (roll, elevation, azimuth)
|
|
#
|
|
# gimbalJointRates(roll, elev, azimuth) = Matrix*relativeGimbalRate(X,Y,Z)
|
|
#
|
|
# where matrix =
|
|
#
|
|
# +- -+
|
|
# | cos(elevAngle), 0, sin(elevAngle) |
|
|
# | |
|
|
# | sin(elevAngle) tan(rollAngle), 1, -cos(elevAngle) tan(rollAngle) |
|
|
# | |
|
|
# | sin(elevAngle) cos(elevAngle) |
|
|
# | - --------------, 0, -------------- |
|
|
# | cos(rollAngle) cos(rollAngle) |
|
|
# +- -+
|
|
rollAngle = self.joint_angles.x
|
|
elevAngle = self.joint_angles.y
|
|
matrix = Matrix3(Vector3(cos(elevAngle), 0, sin(elevAngle)),
|
|
Vector3(sin(elevAngle)*tan(rollAngle), 1, -cos(elevAngle)*tan(rollAngle)),
|
|
Vector3(-sin(elevAngle)/cos(rollAngle), 0, cos(elevAngle)/cos(rollAngle)))
|
|
gimbalJointRates = matrix * relativeGimbalRate
|
|
#print("lowerRatelimit ", lowerRatelimit)
|
|
#print("upperRatelimit ", upperRatelimit)
|
|
#print("gimbalJointRates ", gimbalJointRates)
|
|
|
|
# 6) Apply the rate limits from 4)
|
|
gimbalJointRates.x = util.constrain(gimbalJointRates.x, lowerRatelimit.x, upperRatelimit.x)
|
|
gimbalJointRates.y = util.constrain(gimbalJointRates.y, lowerRatelimit.y, upperRatelimit.y)
|
|
gimbalJointRates.z = util.constrain(gimbalJointRates.z, lowerRatelimit.z, upperRatelimit.z)
|
|
|
|
# 7) Convert the modified gimbal joint rates to body rates (still copter
|
|
# relative)
|
|
# relativeGimbalRate(X,Y,Z) = Matrix * gimbalJointRates(roll, elev, azimuth)
|
|
#
|
|
# where Matrix =
|
|
#
|
|
# +- -+
|
|
# | cos(elevAngle), 0, -cos(rollAngle) sin(elevAngle) |
|
|
# | |
|
|
# | 0, 1, sin(rollAngle) |
|
|
# | |
|
|
# | sin(elevAngle), 0, cos(elevAngle) cos(rollAngle) |
|
|
# +- -+
|
|
matrix = Matrix3(Vector3(cos(elevAngle), 0, -cos(rollAngle)*sin(elevAngle)),
|
|
Vector3(0, 1, sin(rollAngle)),
|
|
Vector3(sin(elevAngle), 0, cos(elevAngle)*cos(rollAngle)))
|
|
relativeGimbalRate = matrix * gimbalJointRates
|
|
|
|
# 8) Add to the result from step 1) to obtain the demanded gimbal body rates
|
|
# in an inertial frame of reference
|
|
# demandedGimbalRatesInertial(X,Y,Z) = relativeGimbalRate(X,Y,Z) + copterAngRate_G
|
|
demandedGimbalRatesInertial = relativeGimbalRate + copterAngRate_G
|
|
#if now - self.last_print_t >= 1.0:
|
|
# print("demandedGimbalRatesInertial ", demandedGimbalRatesInertial)
|
|
|
|
# for the moment we will set gyros equal to demanded_angular_rate
|
|
self.gimbal_angular_rate = demRateRaw #demandedGimbalRatesInertial + self.true_gyro_bias - self.supplied_gyro_bias
|
|
|
|
# update rotation of the gimbal
|
|
self.dcm.rotate(self.gimbal_angular_rate*delta_t)
|
|
self.dcm.normalize()
|
|
|
|
# calculate copter -> gimbal rotation matrix
|
|
rotmat_copter_gimbal = self.dcm.transposed() * self.vehicle.dcm
|
|
|
|
# calculate joint angles (euler312 order)
|
|
self.joint_angles = Vector3(*rotmat_copter_gimbal.transposed().to_euler312())
|
|
|
|
# update observed gyro
|
|
self.gyro = self.gimbal_angular_rate + self.true_gyro_bias
|
|
|
|
# update delta_angle (integrate)
|
|
self.delta_angle += self.gyro * delta_t
|
|
|
|
# calculate accel in gimbal body frame
|
|
copter_accel_earth = self.vehicle.dcm * self.vehicle.accel_body
|
|
accel = self.dcm.transposed() * copter_accel_earth
|
|
|
|
# integrate velocity
|
|
self.delta_velocity += accel * delta_t
|
|
|
|
# see if we should do a report
|
|
if now - self.last_report_t >= 1.0 / self.reporting_rate:
|
|
self.send_report()
|
|
self.last_report_t = now
|
|
|
|
if now - self.last_print_t >= 1.0:
|
|
self.last_print_t = now
|
|
# calculate joint angles (euler312 order)
|
|
Euler312 = Vector3(*self.dcm.to_euler312())
|
|
print("Euler Angles 312 %6.1f %6.1f %6.1f" % (degrees(Euler312.z), degrees(Euler312.x), degrees(Euler312.y)))
|
|
|
|
def send_report(self):
|
|
'''send a report to the vehicle control code over MAVLink'''
|
|
from pymavlink import mavutil
|
|
if self.connection is None:
|
|
try:
|
|
self.connection = mavutil.mavlink_connection(self.mavlink_url, retries=0)
|
|
except Exception as e:
|
|
return
|
|
print("Gimbal connected to %s" % self.mavlink_url)
|
|
|
|
# check for incoming control messages
|
|
while True:
|
|
m = self.connection.recv_match(type=['GIMBAL_CONTROL','HEARTBEAT'], blocking=False)
|
|
if m is None:
|
|
break
|
|
if m.get_type() == 'GIMBAL_CONTROL':
|
|
self.demanded_angular_rate = Vector3(m.demanded_rate_x,
|
|
m.demanded_rate_y,
|
|
m.demanded_rate_z)
|
|
# no longer supply a bias
|
|
self.supplied_gyro_bias = Vector3()
|
|
self.seen_gimbal_control = True
|
|
if m.get_type() == 'HEARTBEAT' and not self.seen_heartbeat:
|
|
self.seen_heartbeat = True
|
|
self.vehicle_component_id = m.get_srcComponent()
|
|
self.connection.mav.srcSystem = m.get_srcSystem()
|
|
self.connection.mav.srcComponent = mavutil.mavlink.MAV_COMP_ID_GIMBAL
|
|
print("Gimbal using srcSystem %u" % self.connection.mav.srcSystem)
|
|
|
|
if self.seen_heartbeat:
|
|
now = time.time()
|
|
if now - self.last_heartbeat_t >= 1:
|
|
self.connection.mav.heartbeat_send(mavutil.mavlink.MAV_TYPE_GIMBAL,
|
|
mavutil.mavlink.MAV_AUTOPILOT_ARDUPILOTMEGA,
|
|
0, 0, 0)
|
|
self.last_heartbeat_t = now
|
|
|
|
if self.seen_heartbeat:
|
|
delta_time = now - self.last_report_t
|
|
self.last_report_t = now
|
|
self.connection.mav.gimbal_report_send(self.connection.mav.srcSystem,
|
|
self.vehicle_component_id,
|
|
delta_time,
|
|
self.delta_angle.x,
|
|
self.delta_angle.y,
|
|
self.delta_angle.z,
|
|
self.delta_velocity.x,
|
|
self.delta_velocity.y,
|
|
self.delta_velocity.z,
|
|
self.joint_angles.x,
|
|
self.joint_angles.y,
|
|
self.joint_angles.z)
|
|
self.delta_velocity.zero()
|
|
self.delta_angle.zero()
|