Source code for udaan.control.quadrotor_cspayload.payload_controller

"""Payload position + cable attitude controller.

Sreenath, Lee, Kumar (2013) https://ieeexplore.ieee.org/abstract/document/6760219
"""

import numpy as np

from ...control import Gains, PDController
from ...core.defaults import (
    DEFAULT_CABLE_LENGTH,
    DEFAULT_PAYLOAD_CABLE_KD,
    DEFAULT_PAYLOAD_CABLE_KP,
    DEFAULT_PAYLOAD_MASS,
    DEFAULT_PAYLOAD_POS_KD,
    DEFAULT_PAYLOAD_POS_KP,
    DEFAULT_QUAD_INERTIA,
    DEFAULT_QUAD_MASS,
)
from ...flatness.quadrotor_cspayload import cable_direction_jet
from ...utils import hat


[docs] class QuadCSPayloadController(PDController):
[docs] def __init__(self, **kwargs): super().__init__(**kwargs) self._gain_pos = Gains(kp=DEFAULT_PAYLOAD_POS_KP.copy(), kd=DEFAULT_PAYLOAD_POS_KD.copy()) self._gain_cable = Gains( kp=DEFAULT_PAYLOAD_CABLE_KP.copy(), kd=DEFAULT_PAYLOAD_CABLE_KD.copy() ) self._quad_mass = DEFAULT_QUAD_MASS self._quad_inertia = DEFAULT_QUAD_INERTIA.copy() self._quad_inertia_inv = np.linalg.inv(self._quad_inertia) self._payload_mass = DEFAULT_PAYLOAD_MASS self._cable_length = DEFAULT_CABLE_LENGTH
[docs] def compute(self, *args): """payload position control""" t = args[0] s = args[1] # quadrotor payload state sp = self.setpoint(t) sd, dsd, d2sd = sp[0], sp[1], sp[2] # Higher payload derivatives (jerk, snap) feed the cable-attitude # feedforward below; default to zero when the setpoint omits them # (e.g. a stationary set-point), which recovers the prior behaviour. d3sd = sp[3] if len(sp) > 3 else np.zeros(3) d4sd = sp[4] if len(sp) > 4 else np.zeros(3) ex = s.payload_position - sd ev = s.payload_velocity - dsd q = s.cable_attitude dq = s.dq() mQ = self._quad_mass mL = self._payload_mass l = self._cable_length Fff = (mQ + mL) * (d2sd + self._ge3) + mQ * l * np.dot(dq, dq) * q Fpd = -self._gain_pos.kp * ex - self._gain_pos.kd * ev A = Fff + Fpd # desired load attitude qc = -A / np.linalg.norm(A) # load-attitude ctrl qd = qc # Desired cable-attitude rates from differential flatness of the # payload trajectory: the flat cable direction is # q_des = -(a_L + g e3)/‖·‖ with payload-acceleration jet # [a_L, ȧ_L, ä_L] = [d2sd, d3sd, d4sd]; dqd, d2qd are its 1st/2nd time # derivatives, from the same flat-to-state map as # udaan.flatness.quadrotor_cspayload. These supply the cable # feedforward that was previously stubbed to zero; q_des coincides with # qc in the nominal (feedback-free) case, so qd is left as qc. try: _, q_flat = cable_direction_jet([d2sd, d3sd, d4sd], mL) dqd, d2qd = q_flat[1], q_flat[2] except ValueError: # Payload near free-fall: cable direction undefined, drop feedforward. dqd = np.zeros(3) d2qd = np.zeros(3) # calculating errors err_q = hat(q) @ hat(q) @ qd err_dq = dq - np.cross(np.cross(qd, dqd), q) Fpd = -self._gain_cable.kp * err_q - self._gain_cable.kd * err_dq Fff = (mQ * l) * np.dot(q, np.cross(qd, dqd)) * np.cross(q, dq) + (mQ * l) * np.cross( np.cross(qd, d2qd), q ) Fn = np.dot(A, q) * q thrust_force = -Fpd - Fff + Fn return thrust_force