diff --git a/conf/flight_plans/demo_gvf.xml b/conf/flight_plans/demo_gvf.xml index 7c065a8a95..be8a3a1233 100644 --- a/conf/flight_plans/demo_gvf.xml +++ b/conf/flight_plans/demo_gvf.xml @@ -29,10 +29,12 @@ + + @@ -78,6 +80,18 @@ + + + + + + + + + + + + diff --git a/conf/modules/gvf_parametric.xml b/conf/modules/gvf_parametric.xml new file mode 100644 index 0000000000..cb8ccf974f --- /dev/null +++ b/conf/modules/gvf_parametric.xml @@ -0,0 +1,87 @@ + + + + + Guidance algorithm for tracking 2D and 3D smooth trajectories that are defined by a parametric equation. + + For more details we refer to https://wiki.paparazziuav.org/wiki/Module/guidance_vector_field . + This algorithm needs the Eigen library, be sure you have it installed, e.g., git submodule update --init --recursive sw/ext/eigen + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+ + + + + + + + + + + + + + + + + + + + +
diff --git a/conf/telemetry/default_fixedwing_gvf.xml b/conf/telemetry/default_fixedwing_gvf.xml index 16c05b6129..ac6a9155fe 100644 --- a/conf/telemetry/default_fixedwing_gvf.xml +++ b/conf/telemetry/default_fixedwing_gvf.xml @@ -32,6 +32,7 @@ + diff --git a/sw/airborne/modules/guidance/gvf/gvf.c b/sw/airborne/modules/guidance/gvf/gvf.c index 5e10467d1d..d6e0dbd1ef 100644 --- a/sw/airborne/modules/guidance/gvf/gvf.c +++ b/sw/airborne/modules/guidance/gvf/gvf.c @@ -40,6 +40,9 @@ gvf_con gvf_control; gvf_tra gvf_trajectory; gvf_seg gvf_segment; +// Time variables to check if GVF is active +uint32_t gvf_t0 = 0; + #if PERIODIC_TELEMETRY #include "subsystems/datalink/telemetry.h" static void send_gvf(struct transport_tx *trans, struct link_device *dev) @@ -65,27 +68,39 @@ static void send_gvf(struct transport_tx *trans, struct link_device *dev) uint8_t traj_type = (uint8_t)gvf_trajectory.type; - pprz_msg_send_GVF(trans, dev, AC_ID, &gvf_control.error, &traj_type, - &gvf_control.s, &gvf_control.ke, plen, gvf_trajectory.p); + uint32_t now = get_sys_time_msec(); + uint32_t delta_T = now - gvf_t0; + + if (delta_T < 200) + pprz_msg_send_GVF(trans, dev, AC_ID, &gvf_control.error, &traj_type, + &gvf_control.s, &gvf_control.ke, plen, gvf_trajectory.p); } static void send_circle(struct transport_tx *trans, struct link_device *dev) { - if (gvf_trajectory.type == ELLIPSE && - (gvf_trajectory.p[2] == gvf_trajectory.p[3])) { - pprz_msg_send_CIRCLE(trans, dev, AC_ID, - &gvf_trajectory.p[0], &gvf_trajectory.p[1], - &gvf_trajectory.p[2]); - } + uint32_t now = get_sys_time_msec(); + uint32_t delta_T = now - gvf_t0; + + if (delta_T < 200) + if (gvf_trajectory.type == ELLIPSE && + (gvf_trajectory.p[2] == gvf_trajectory.p[3])) { + pprz_msg_send_CIRCLE(trans, dev, AC_ID, + &gvf_trajectory.p[0], &gvf_trajectory.p[1], + &gvf_trajectory.p[2]); + } } static void send_segment(struct transport_tx *trans, struct link_device *dev) { - if (gvf_trajectory.type == LINE && gvf_segment.seg == 1) { - pprz_msg_send_SEGMENT(trans, dev, AC_ID, - &gvf_segment.x1, &gvf_segment.y1, - &gvf_segment.x2, &gvf_segment.y2); - } + uint32_t now = get_sys_time_msec(); + uint32_t delta_T = now - gvf_t0; + + if (delta_T < 200) + if (gvf_trajectory.type == LINE && gvf_segment.seg == 1) { + pprz_msg_send_SEGMENT(trans, dev, AC_ID, + &gvf_segment.x1, &gvf_segment.y1, + &gvf_segment.x2, &gvf_segment.y2); + } } #endif @@ -139,6 +154,8 @@ void gvf_init(void) void gvf_control_2D(float ke, float kn, float e, struct gvf_grad *grad, struct gvf_Hess *hess) { + gvf_t0 = get_sys_time_msec(); + struct FloatEulers *att = stateGetNedToBodyEulers_f(); float ground_speed = stateGetHorizontalSpeedNorm_f(); float course = stateGetHorizontalSpeedDir_f(); diff --git a/sw/airborne/modules/guidance/gvf/gvf.h b/sw/airborne/modules/guidance/gvf/gvf.h index 63f79f223f..be824a9a27 100644 --- a/sw/airborne/modules/guidance/gvf/gvf.h +++ b/sw/airborne/modules/guidance/gvf/gvf.h @@ -32,8 +32,8 @@ #include "std.h" -/** @typedef gvf_conf -* @brief Parameters for the GVF +/** @typedef gvf_con +* @brief Control parameters for the GVF * @param ke Gain defining how agressive is the vector field * @param kn Gain for making converge the vehile to the vector field * @param error Error signal. It does not have any specific units. It depends on how the trajectory has been implemented. Check the specific wiki entry for each trajectory. diff --git a/sw/airborne/modules/guidance/gvf_parametric/gvf_parametric.cpp b/sw/airborne/modules/guidance/gvf_parametric/gvf_parametric.cpp new file mode 100644 index 0000000000..2f3b6fda63 --- /dev/null +++ b/sw/airborne/modules/guidance/gvf_parametric/gvf_parametric.cpp @@ -0,0 +1,440 @@ +/* + * Copyright (C) 2020 Hector Garcia de Marina + * + * This file is part of paparazzi. + * + * paparazzi is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * paparazzi is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with paparazzi; see the file COPYING. If not, see + * . + */ + +/** + * @file modules/guidance/gvf_parametric/gvf_parametric.cpp + * + * Guiding vector field algorithm for 2D and 3D complex trajectories. + */ + +#include +#include + +#include "gvf_parametric.h" +#include "gvf_parametric_low_level_control.h" +#include "./trajectories/gvf_parametric_3d_ellipse.h" +#include "./trajectories/gvf_parametric_3d_lissajous.h" +#include "./trajectories/gvf_parametric_2d_trefoil.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#include "autopilot.h" + +// Control +uint32_t gvf_parametric_t0 = 0; // We need it for calculting the time lapse delta_T +gvf_parametric_con gvf_parametric_control; + +// Trajectory +gvf_parametric_tra gvf_parametric_trajectory; + +#if PERIODIC_TELEMETRY +#include "subsystems/datalink/telemetry.h" +static void send_gvf_parametric(struct transport_tx *trans, struct link_device *dev) +{ + // Do not know whether is a good idea to do this check here or to include + // this plen in gvf_trajectory + int plen; + int elen; + + switch (gvf_parametric_trajectory.type) { + case TREFOIL_2D: + plen = 7; + elen = 2; + break; + case ELLIPSE_3D: + plen = 6; + elen = 3; + break; + case LISSAJOUS_3D: + plen = 13; + elen = 3; + break; + default: + plen = 1; + elen = 3; + } + + uint8_t traj_type = (uint8_t)gvf_parametric_trajectory.type; + + uint32_t now = get_sys_time_msec(); + uint32_t delta_T = now - gvf_parametric_t0; + + float wb = gvf_parametric_control.w * gvf_parametric_control.beta; + + if (delta_T < 200) { + pprz_msg_send_GVF_PARAMETRIC(trans, dev, AC_ID, &traj_type, &gvf_parametric_control.s, &wb, plen, + gvf_parametric_trajectory.p_parametric, elen, gvf_parametric_trajectory.phi_errors); + } +} + +static void send_circle_parametric(struct transport_tx *trans, struct link_device *dev) +{ + uint32_t now = get_sys_time_msec(); + uint32_t delta_T = now - gvf_parametric_t0; + + if (delta_T < 200) + if (gvf_parametric_trajectory.type == ELLIPSE_3D) { + pprz_msg_send_CIRCLE(trans, dev, AC_ID, &gvf_parametric_trajectory.p_parametric[0], + &gvf_parametric_trajectory.p_parametric[1], &gvf_parametric_trajectory.p_parametric[2]); + } +} + +#endif // PERIODIC TELEMETRY + +#ifdef __cplusplus +} +#endif + +void gvf_parametric_init(void) +{ + gvf_parametric_control.w = 0; + gvf_parametric_control.delta_T = 0; + gvf_parametric_control.s = 1; + gvf_parametric_control.k_roll = GVF_PARAMETRIC_CONTROL_KROLL; + gvf_parametric_control.k_climb = GVF_PARAMETRIC_CONTROL_KCLIMB; + gvf_parametric_control.k_psi = GVF_PARAMETRIC_CONTROL_KPSI; + gvf_parametric_control.L = GVF_PARAMETRIC_CONTROL_L; + gvf_parametric_control.beta = GVF_PARAMETRIC_CONTROL_BETA; + +#if PERIODIC_TELEMETRY + register_periodic_telemetry(DefaultPeriodic, PPRZ_MSG_ID_GVF_PARAMETRIC, send_gvf_parametric); + register_periodic_telemetry(DefaultPeriodic, PPRZ_MSG_ID_CIRCLE, send_circle_parametric); +#endif + +} + +void gvf_parametric_set_direction(int8_t s) +{ + gvf_parametric_control.s = s; +} + +void gvf_parametric_control_2D(float kx, float ky, float f1, float f2, float f1d, float f2d, float f1dd, float f2dd) +{ + + uint32_t now = get_sys_time_msec(); + gvf_parametric_control.delta_T = now - gvf_parametric_t0; + gvf_parametric_t0 = now; + + if (gvf_parametric_control.delta_T > 300) { // We need at least two iterations for Delta_T + gvf_parametric_control.w = 0; // Reset w since we assume the algorithm starts + return; + } + + float L = gvf_parametric_control.L; + float beta = gvf_parametric_control.beta * gvf_parametric_control.s; + + Eigen::Vector3f X; + Eigen::Matrix3f J; + + // Error signals phi_x and phi_y + struct EnuCoor_f *pos_enu = stateGetPositionEnu_f(); + float x = pos_enu->x; + float y = pos_enu->y; + + float phi1 = L * (x - f1); + float phi2 = L * (y - f2); + + gvf_parametric_trajectory.phi_errors[0] = phi1; // Error signals for the telemetry + gvf_parametric_trajectory.phi_errors[1] = phi2; + + // Chi + X(0) = L * beta * f1d - kx * phi1; + X(1) = L * beta * f2d - ky * phi2; + X(2) = L + beta * (kx * phi1 * f1d + ky * phi2 * f2d); + X *= L; + + // Jacobian + J.setZero(); + J(0, 0) = -kx * L; + J(1, 1) = -ky * L; + J(2, 0) = (beta * L) * (beta * f1dd + kx * f1d); + J(2, 1) = (beta * L) * (beta * f2dd + ky * f2d); + J(2, 2) = beta * beta * (kx * (phi1 * f1dd - L * f1d * f1d) + ky * (phi2 * f2dd - L * f2d * f2d)); + J *= L; + + // Guidance algorithm + float ground_speed = stateGetHorizontalSpeedNorm_f(); + float w_dot = (ground_speed * X(2)) / sqrtf(X(0) * X(0) + X(1) * X(1)); + + Eigen::Vector3f xi_dot; + struct EnuCoor_f *vel_enu = stateGetSpeedEnu_f(); + float course = stateGetHorizontalSpeedDir_f(); + + xi_dot << vel_enu->x, vel_enu->y, w_dot; + + Eigen::Matrix3f G; + Eigen::Matrix3f Gp; + Eigen::Matrix Fp; + Eigen::Vector2f h; + Eigen::Matrix ht; + + G << 1, 0, 0, + 0, 1, 0, + 0, 0, 0; + Fp << 0, -1, 0, + 1, 0, 0; + Gp << 0, -1, 0, + 1, 0, 0, + 0, 0, 0; + + h << sinf(course), cosf(course); + ht = h.transpose(); + + Eigen::Matrix Xt = X.transpose(); + Eigen::Vector3f Xh = X / X.norm(); + Eigen::Matrix Xht = Xh.transpose(); + Eigen::Matrix3f I; + I.setIdentity(); + + float aux = ht * Fp * X; + float heading_rate = -1 / (Xt * G * X) * Xt * Gp * (I - Xh * Xht) * J * xi_dot - (gvf_parametric_control.k_psi * aux / + sqrtf(Xt * G * X)); + + // Virtual coordinate update, even if the vehicle is not in autonomous mode, the parameter w will get "closer" to + // the vehicle. So it is not only okei but advisable to update it. + gvf_parametric_control.w += w_dot * gvf_parametric_control.delta_T * 1e-3; + + gvf_parametric_low_level_control_2D(heading_rate); +} + +void gvf_parametric_control_3D(float kx, float ky, float kz, float f1, float f2, float f3, float f1d, float f2d, + float f3d, float f1dd, float f2dd, float f3dd) +{ + uint32_t now = get_sys_time_msec(); + gvf_parametric_control.delta_T = now - gvf_parametric_t0; + gvf_parametric_t0 = now; + + if (gvf_parametric_control.delta_T > 300) { // We need at least two iterations for Delta_T + gvf_parametric_control.w = 0; // Reset w since we assume the algorithm starts + return; + } + + float L = gvf_parametric_control.L; + float beta = gvf_parametric_control.beta * gvf_parametric_control.s; + + Eigen::Vector4f X; + Eigen::Matrix4f J; + + // Error signals phi_x phi_y and phi_z + struct EnuCoor_f *pos_enu = stateGetPositionEnu_f(); + float x = pos_enu->x; + float y = pos_enu->y; + float z = pos_enu->z; + + float phi1 = L * (x - f1); + float phi2 = L * (y - f2); + float phi3 = L * (z - f3); + + gvf_parametric_trajectory.phi_errors[0] = phi1 / L; // Error signals in meters for the telemetry + gvf_parametric_trajectory.phi_errors[1] = phi2 / L; + gvf_parametric_trajectory.phi_errors[2] = phi3 / L; + + // Chi + X(0) = -f1d * L * L * beta - kx * phi1; + X(1) = -f2d * L * L * beta - ky * phi2; + X(2) = -f3d * L * L * beta - kz * phi3; + X(3) = -L * L + beta * (kx * phi1 * f1d + ky * phi2 * f2d + kz * phi3 * f3d); + X *= L; + + // Jacobian + J.setZero(); + J(0, 0) = -kx * L; + J(1, 1) = -ky * L; + J(2, 2) = -kz * L; + J(3, 0) = kx * f1d * beta * L; + J(3, 1) = ky * f2d * beta * L; + J(3, 2) = kz * f3d * beta * L; + J(0, 3) = -(beta * L) * (beta * L * f1dd - kx * f1d); + J(1, 3) = -(beta * L) * (beta * L * f2dd - ky * f2d); + J(2, 3) = -(beta * L) * (beta * L * f3dd - kz * f3d); + J(3, 3) = beta * beta * (kx * (phi1 * f1dd - L * f1d * f1d) + ky * (phi2 * f2dd - L * f2d * f2d) + + kz * (phi3 * f3dd - L * f3d * f3d)); + J *= L; + + // Guidance algorithm + float ground_speed = stateGetHorizontalSpeedNorm_f(); + float w_dot = (ground_speed * X(3)) / sqrtf(X(0) * X(0) + X(1) * X(1)); + + Eigen::Vector4f xi_dot; + struct EnuCoor_f *vel_enu = stateGetSpeedEnu_f(); + float course = stateGetHorizontalSpeedDir_f(); + + xi_dot << vel_enu->x, vel_enu->y, vel_enu->z, w_dot; + + Eigen::Matrix2f E; + Eigen::Matrix F; + Eigen::Matrix Fp; + Eigen::Matrix4f G; + Eigen::Matrix4f Gp; + Eigen::Matrix4f I; + Eigen::Vector2f h; + Eigen::Matrix ht; + + h << sinf(course), cosf(course); + ht = h.transpose(); + I.setIdentity(); + F << 1.0, 0.0, 0.0, 0.0, + 0.0, 1.0, 0.0, 0.0; + E << 0.0, -1.0, + 1.0, 0.0; + G = F.transpose() * F; + Fp = E * F; + Gp = F.transpose() * E * F; + + Eigen::Matrix Xt = X.transpose(); + Eigen::Vector4f Xh = X / X.norm(); + Eigen::Matrix Xht = Xh.transpose(); + + float aux = ht * Fp * X; + + float heading_rate = -1 / (Xt * G * X) * Xt * Gp * (I - Xh * Xht) * J * xi_dot - (gvf_parametric_control.k_psi * aux / + sqrtf(Xt * G * X)); + float climbing_rate = (ground_speed * X(2)) / sqrtf(X(0) * X(0) + X(1) * X(1)); + + // Virtual coordinate update, even if the vehicle is not in autonomous mode, the parameter w will get "closer" to + // the vehicle. So it is not only okei but advisable to update it. + gvf_parametric_control.w += w_dot * gvf_parametric_control.delta_T * 1e-3; + + gvf_parametric_low_level_control_3D(heading_rate, climbing_rate); +} + +/** 2D TRAJECTORIES **/ +// 2D TREFOIL KNOT + +bool gvf_parametric_2D_trefoil_XY(float xo, float yo, float w1, float w2, float ratio, float r, float alpha) +{ + gvf_parametric_trajectory.type = TREFOIL_2D; + gvf_parametric_trajectory.p_parametric[0] = xo; + gvf_parametric_trajectory.p_parametric[1] = yo; + gvf_parametric_trajectory.p_parametric[2] = w1; + gvf_parametric_trajectory.p_parametric[3] = w2; + gvf_parametric_trajectory.p_parametric[4] = ratio; + gvf_parametric_trajectory.p_parametric[5] = r; + gvf_parametric_trajectory.p_parametric[6] = alpha; + + float f1, f2, f1d, f2d, f1dd, f2dd; + + gvf_parametric_2d_trefoil_info(&f1, &f2, &f1d, &f2d, &f1dd, &f2dd); + gvf_parametric_control_2D(gvf_parametric_2d_trefoil_par.kx, gvf_parametric_2d_trefoil_par.ky, f1, f2, f1d, f2d, f1dd, + f2dd); + + return true; +} + +bool gvf_parametric_2D_trefoil_wp(uint8_t wp, float w1, float w2, float ratio, float r, float alpha) +{ + gvf_parametric_2D_trefoil_XY(waypoints[wp].x, waypoints[wp].y, w1, w2, ratio, r, alpha); + return true; +} + +/** 3D TRAJECTORIES **/ +// 3D ELLIPSE + +bool gvf_parametric_3D_ellipse_XYZ(float xo, float yo, float r, float zl, float zh, float alpha) +{ + horizontal_mode = HORIZONTAL_MODE_CIRCLE; // Circle for the 2D GCS + + // Safety first! If the asked altitude is low + if (zl > zh) { + zl = zh; + } + if (zl < 1 || zh < 1) { + zl = 10; + zh = 10; + } + if (r < 1) { + r = 60; + } + + gvf_parametric_trajectory.type = ELLIPSE_3D; + gvf_parametric_trajectory.p_parametric[0] = xo; + gvf_parametric_trajectory.p_parametric[1] = yo; + gvf_parametric_trajectory.p_parametric[2] = r; + gvf_parametric_trajectory.p_parametric[3] = zl; + gvf_parametric_trajectory.p_parametric[4] = zh; + gvf_parametric_trajectory.p_parametric[5] = alpha; + + float f1, f2, f3, f1d, f2d, f3d, f1dd, f2dd, f3dd; + + gvf_parametric_3d_ellipse_info(&f1, &f2, &f3, &f1d, &f2d, &f3d, &f1dd, &f2dd, &f3dd); + gvf_parametric_control_3D(gvf_parametric_3d_ellipse_par.kx, gvf_parametric_3d_ellipse_par.ky, + gvf_parametric_3d_ellipse_par.kz, f1, f2, f3, f1d, f2d, f3d, f1dd, f2dd, f3dd); + + return true; +} + +bool gvf_parametric_3D_ellipse_wp(uint8_t wp, float r, float zl, float zh, float alpha) +{ + gvf_parametric_3D_ellipse_XYZ(waypoints[wp].x, waypoints[wp].y, r, zl, zh, alpha); + return true; +} + +bool gvf_parametric_3D_ellipse_wp_delta(uint8_t wp, float r, float alt_center, float delta, float alpha) +{ + float zl = alt_center - delta; + float zh = alt_center + delta; + + gvf_parametric_3D_ellipse_XYZ(waypoints[wp].x, waypoints[wp].y, r, zl, zh, alpha); + return true; +} + +// 3D Lissajous + +bool gvf_parametric_3D_lissajous_XYZ(float xo, float yo, float zo, float cx, float cy, float cz, float wx, float wy, + float wz, float dx, float dy, float dz, float alpha) +{ + // Safety first! If the asked altitude is low + if ((zo - cz) < 1) { + zo = 10; + cz = 0; + } + + gvf_parametric_trajectory.type = LISSAJOUS_3D; + gvf_parametric_trajectory.p_parametric[0] = xo; + gvf_parametric_trajectory.p_parametric[1] = yo; + gvf_parametric_trajectory.p_parametric[2] = zo; + gvf_parametric_trajectory.p_parametric[3] = cx; + gvf_parametric_trajectory.p_parametric[4] = cy; + gvf_parametric_trajectory.p_parametric[5] = cz; + gvf_parametric_trajectory.p_parametric[6] = wx; + gvf_parametric_trajectory.p_parametric[7] = wy; + gvf_parametric_trajectory.p_parametric[8] = wz; + gvf_parametric_trajectory.p_parametric[9] = dx; + gvf_parametric_trajectory.p_parametric[10] = dy; + gvf_parametric_trajectory.p_parametric[11] = dz; + gvf_parametric_trajectory.p_parametric[12] = alpha; + + float f1, f2, f3, f1d, f2d, f3d, f1dd, f2dd, f3dd; + + gvf_parametric_3d_lissajous_info(&f1, &f2, &f3, &f1d, &f2d, &f3d, &f1dd, &f2dd, &f3dd); + gvf_parametric_control_3D(gvf_parametric_3d_lissajous_par.kx, gvf_parametric_3d_lissajous_par.ky, + gvf_parametric_3d_lissajous_par.kz, f1, f2, f3, f1d, f2d, f3d, f1dd, f2dd, f3dd); + + return true; +} + +bool gvf_parametric_3D_lissajous_wp_center(uint8_t wp, float zo, float cx, float cy, float cz, float wx, float wy, + float wz, float dx, float dy, float dz, float alpha) +{ + gvf_parametric_3D_lissajous_XYZ(waypoints[wp].x, waypoints[wp].y, zo, cx, cy, cz, wx, wy, wz, dx, dy, dz, alpha); + return true; +} diff --git a/sw/airborne/modules/guidance/gvf_parametric/gvf_parametric.h b/sw/airborne/modules/guidance/gvf_parametric/gvf_parametric.h new file mode 100644 index 0000000000..7bcb611164 --- /dev/null +++ b/sw/airborne/modules/guidance/gvf_parametric/gvf_parametric.h @@ -0,0 +1,132 @@ +/* + * Copyright (C) 2020 Hector Garcia de Marina + * + * This file is part of paparazzi. + * + * paparazzi is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * paparazzi is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with paparazzi; see the file COPYING. If not, see + * . + */ + +/** + * @file modules/guidance/gvf_parametric/gvf_parametric.h + * + * Guiding vector field algorithm for 2D and 3D parametric trajectories. + */ + +#ifndef GVF_PARAMETRIC_H +#define GVF_PARAMETRIC_H + +#define GVF_PARAMETRIC_GRAVITY 9.806 + +/*! Default gain kroll for tuning the "coordinated turn" */ +#ifndef GVF_PARAMETRIC_CONTROL_KROLL +#define GVF_PARAMETRIC_CONTROL_KROLL 1 +#endif + +/*! Default gain kclimb for tuning the climbing setting point */ +#ifndef GVF_PARAMETRIC_CONTROL_KCLIMB +#define GVF_PARAMETRIC_CONTROL_KCLIMB 1 +#endif + +/*! Default scale for the error signals */ +#ifndef GVF_PARAMETRIC_CONTROL_L +#define GVF_PARAMETRIC_CONTROL_L 0.1 +#endif + +/*! Default scale for w */ +#ifndef GVF_PARAMETRIC_CONTROL_BETA +#define GVF_PARAMETRIC_CONTROL_BETA 0.01 +#endif + +/*! Default gain kpsi for tuning the alignment of the vehicle with the vector field */ +#ifndef GVF_PARAMETRIC_CONTROL_KPSI +#define GVF_PARAMETRIC_CONTROL_KPSI 1 +#endif + + +#ifdef __cplusplus +extern "C" { +#endif + +#include "modules/guidance/gvf_parametric/trajectories/gvf_parametric_3d_ellipse.h" +#include "modules/guidance/gvf_parametric/trajectories/gvf_parametric_3d_lissajous.h" +#include "modules/guidance/gvf_parametric/trajectories/gvf_parametric_2d_trefoil.h" + +/** @typedef gvf_parametric_con +* @brief Control parameters for the GVF_PARAMETRIC +* @param w Virtual coordinate from the parametrization of the trajectory +* @param delta_T Time between iterations needed for integrating w +* @param s Defines the direction to be tracked. It takes the values -1 or 1. +* @param k_roll Gain for tuning the coordinated turn. +* @param k_climb Gain for tuning the climbing setting point. +*/ +typedef struct { + float w; + float delta_T; + int8_t s; + float k_roll; + float k_climb; + float k_psi; + float L; + float beta; +} gvf_parametric_con; + +extern gvf_parametric_con gvf_parametric_control; + +// Parameters for the trajectories +enum trajectories_parametric { + TREFOIL_2D = 0, + ELLIPSE_3D = 1, + LISSAJOUS_3D = 2, + NONE_PARAMETRIC = 255, +}; + +typedef struct { + enum trajectories_parametric type; + float p_parametric[16]; + float phi_errors[3]; +} gvf_parametric_tra; + +extern gvf_parametric_tra gvf_parametric_trajectory; + +// Init function +extern void gvf_parametric_init(void); + +// Control functions +extern void gvf_parametric_set_direction(int8_t s); +extern void gvf_parametric_control_2D(float, float, float, float, float, float, float, float); +extern void gvf_parametric_control_3D(float, float, float, float, float, float, float, float, float, + float, float, float); + +// 2D Trefoil +extern bool gvf_parametric_2D_trefoil_XY(float, float, float, float, float, float, float); +extern bool gvf_parametric_2D_trefoil_wp(uint8_t, float, float, float, float, float); + +// 3D Ellipse +extern bool gvf_parametric_3D_ellipse_XYZ(float, float, float, float, float, float); +extern bool gvf_parametric_3D_ellipse_wp(uint8_t, float, float, float, float); +extern bool gvf_parametric_3D_ellipse_wp_delta(uint8_t, float, float, float, float); + +// 3D Lissajous +extern bool gvf_parametric_3D_lissajous_XYZ(float, float, float, float, float, float, float, float, float, float, float, + float, float); +extern bool gvf_parametric_3D_lissajous_wp_center(uint8_t, float, float, float, float, float, float, float, float, + float, float, float); + +#ifdef __cplusplus +} +#endif + + +#endif // GVF_PARAMETRIC_H diff --git a/sw/airborne/modules/guidance/gvf_parametric/gvf_parametric_low_level_control.c b/sw/airborne/modules/guidance/gvf_parametric/gvf_parametric_low_level_control.c new file mode 100644 index 0000000000..ed83bb52d8 --- /dev/null +++ b/sw/airborne/modules/guidance/gvf_parametric/gvf_parametric_low_level_control.c @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2020 Hector Garcia de Marina + * + * This file is part of paparazzi. + * + * paparazzi is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * paparazzi is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with paparazzi; see the file COPYING. If not, see + * . + */ + +/** + * @file modules/guidance/gvf_parametric/gvf_parametric_low_level_control.c + * + * Firmware dependent file for the guiding vector field algorithm for 2D and 3D parametric trajectories. + */ + +#include "autopilot.h" +#include "gvf_parametric_low_level_control.h" +#include "gvf_parametric.h" + +#if defined(FIXEDWING_FIRMWARE) +#include "firmwares/fixedwing/stabilization/stabilization_attitude.h" +#include "firmwares/fixedwing/guidance/guidance_v_n.h" // gvf_parametric is only compatible with the new pprz controller! +#endif + +void gvf_parametric_low_level_control_2D(float heading_rate) +{ +#if defined(FIXEDWING_FIRMWARE) + if (autopilot_get_mode() == AP_MODE_AUTO2) { + // Lateral XY coordinates + lateral_mode = LATERAL_MODE_ROLL; + + struct FloatEulers *att = stateGetNedToBodyEulers_f(); + float ground_speed = stateGetHorizontalSpeedNorm_f(); + + h_ctl_roll_setpoint = + -gvf_parametric_control.k_roll * atanf(heading_rate * ground_speed / GVF_PARAMETRIC_GRAVITY / cosf(att->theta)); + BoundAbs(h_ctl_roll_setpoint, h_ctl_roll_max_setpoint); // Setting point for roll angle + } +#else +#error gvf_parametric does not support your firmware yet +#endif +} + +void gvf_parametric_low_level_control_3D(float heading_rate, float climbing_rate) +{ +#if defined(FIXEDWING_FIRMWARE) + if (autopilot_get_mode() == AP_MODE_AUTO2) { + // Vertical Z coordinate + v_ctl_mode = V_CTL_MODE_AUTO_CLIMB; + v_ctl_speed_mode = V_CTL_SPEED_THROTTLE; + + v_ctl_climb_setpoint = gvf_parametric_control.k_climb * climbing_rate; // Setting point for vertical speed + + // Lateral XY coordinates + lateral_mode = LATERAL_MODE_ROLL; + + struct FloatEulers *att = stateGetNedToBodyEulers_f(); + float ground_speed = stateGetHorizontalSpeedNorm_f(); + + h_ctl_roll_setpoint = + -gvf_parametric_control.k_roll * atanf(heading_rate * ground_speed / GVF_PARAMETRIC_GRAVITY / cosf(att->theta)); + BoundAbs(h_ctl_roll_setpoint, h_ctl_roll_max_setpoint); // Setting point for roll angle + } +#else +#error gvf_parametric does not support your firmware yet +#endif +} diff --git a/sw/airborne/modules/guidance/gvf_parametric/gvf_parametric_low_level_control.h b/sw/airborne/modules/guidance/gvf_parametric/gvf_parametric_low_level_control.h new file mode 100644 index 0000000000..dbed98ffe1 --- /dev/null +++ b/sw/airborne/modules/guidance/gvf_parametric/gvf_parametric_low_level_control.h @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2020 Hector Garcia de Marina + * + * This file is part of paparazzi. + * + * paparazzi is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * paparazzi is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with paparazzi; see the file COPYING. If not, see + * . + */ + +/** + * @file modules/guidance/gvf_parametric/gvf_parametric_low_level_control.h + * + * Firmware dependent file for the guiding vector field algorithm for 2D and 3D parametric trajectories. + */ + +#ifndef GVF_PARAMETRIC_LOW_LEVEL_CONTROL_H +#define GVF_PARAMETRIC_LOW_LEVEL_CONTROL_H + +#ifdef __cplusplus +extern "C" { +#endif + +// Low level control functions +extern void gvf_parametric_low_level_control_2D(float); +extern void gvf_parametric_low_level_control_3D(float, float); + +#ifdef __cplusplus +} +#endif + + +#endif // GVF_PARAMETRIC_LOW_LEVEL_CONTROL_H + diff --git a/sw/airborne/modules/guidance/gvf_parametric/trajectories/gvf_parametric_2d_trefoil.c b/sw/airborne/modules/guidance/gvf_parametric/trajectories/gvf_parametric_2d_trefoil.c new file mode 100644 index 0000000000..7628231b91 --- /dev/null +++ b/sw/airborne/modules/guidance/gvf_parametric/trajectories/gvf_parametric_2d_trefoil.c @@ -0,0 +1,102 @@ +/* + * Copyright (C) 2020 Hector Garcia de Marina + * + * This file is part of paparazzi. + * + * paparazzi is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * paparazzi is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with paparazzi; see the file COPYING. If not, see + * . + */ + +/** + * @file modules/guidance/gvf_parametric/trajectories/gvf_parametric_2d_trefoil.c + * + * Guiding vector field algorithm for 2D and 3D complex trajectories. + * + * 2D trefoil know + */ + +#include "subsystems/navigation/common_nav.h" +#include "modules/guidance/gvf_parametric/gvf_parametric.h" +#include "gvf_parametric_2d_trefoil.h" + +/*! Default gain kx for the 2d trefoil knot trajectory */ +#ifndef GVF_PARAMETRIC_2D_TREFOIL_KX +#define GVF_PARAMETRIC_2D_TREFOIL_KX 0.001 +#endif + +/*! Default gain ky for the 2d trefoil knot trajectory */ +#ifndef GVF_PARAMETRIC_2D_TREFOIL_KY +#define GVF_PARAMETRIC_2D_TREFOIL_KY 0.001 +#endif + +/*! Default 1st frequency for the 2d trefoil trajectory*/ +#ifndef GVF_PARAMETRIC_2D_TREFOIL_W1 +#define GVF_PARAMETRIC_2D_TREFOIL_W1 0.02 +#endif + +/*! Default 2nd frequency for the 2d trefoil trajectory*/ +#ifndef GVF_PARAMETRIC_2D_TREFOIL_W2 +#define GVF_PARAMETRIC_2D_TREFOIL_W2 0.03 +#endif + +/*! Default ratio for the 2d trefoil trajectory*/ +#ifndef GVF_PARAMETRIC_2D_TREFOIL_RATIO +#define GVF_PARAMETRIC_2D_TREFOIL_RATIO 160 +#endif + +/*! Default radius of the circles for the 2d trefoil trajectory*/ +#ifndef GVF_PARAMETRIC_2D_TREFOIL_R +#define GVF_PARAMETRIC_2D_TREFOIL_R 80 +#endif + +/*! Default orientation for the 2d trefoil trajectory*/ +#ifndef GVF_PARAMETRIC_2D_TREFOIL_ALPHA +#define GVF_PARAMETRIC_2D_TREFOIL_ALPHA 0 +#endif + +gvf_par_2d_tre_par gvf_parametric_2d_trefoil_par = {GVF_PARAMETRIC_2D_TREFOIL_KX, GVF_PARAMETRIC_2D_TREFOIL_KY, GVF_PARAMETRIC_2D_TREFOIL_W1, GVF_PARAMETRIC_2D_TREFOIL_W2, GVF_PARAMETRIC_2D_TREFOIL_RATIO, GVF_PARAMETRIC_2D_TREFOIL_R, GVF_PARAMETRIC_2D_TREFOIL_ALPHA}; + +void gvf_parametric_2d_trefoil_info(float *f1, float *f2, float *f1d, float *f2d, float *f1dd, float *f2dd) +{ + float xo = gvf_parametric_trajectory.p_parametric[0]; + float yo = gvf_parametric_trajectory.p_parametric[1]; + float w1 = gvf_parametric_trajectory.p_parametric[2]; + float w2 = gvf_parametric_trajectory.p_parametric[3]; + float ratio = gvf_parametric_trajectory.p_parametric[4]; + float r = gvf_parametric_trajectory.p_parametric[5]; + float alpha_rad = gvf_parametric_trajectory.p_parametric[6]*M_PI/180; + + float w = gvf_parametric_control.w; + float wb = w * gvf_parametric_control.beta * gvf_parametric_control.s; + + // Parametric equations of the trajectory and the partial derivatives w.r.t. 'w' + float nrf1 = cosf(wb*w1)*(r*cosf(wb*w2) + ratio); + float nrf2 = sinf(wb*w1)*(r*cosf(wb*w2) + ratio); + + float nrf1d = -w1*sinf(wb*w1)*(r*cosf(wb*w2) + ratio) - cosf(wb*w1)*r*w2*sinf(wb*w2); + float nrf2d = w1*cosf(wb*w1)*(r*cosf(wb*w2) + ratio) - sinf(wb*w1)*r*w2*sinf(wb*w2); + + float nrf1dd = -w1*w1*cosf(wb*w1)*(r*cosf(wb*w2) + ratio) + w1*sinf(wb*w1)*r*w2*sinf(wb*w2) + w1*sinf(wb*w1)*r*w2*sinf(wb*w2) - cosf(wb*w1)*r*w2*w2*cosf(wb*w2); + float nrf2dd = -w1*w1*sinf(wb*w1)*(r*cosf(wb*w2) + ratio) - w1*cosf(wb*w1)*r*w2*sinf(wb*w2) - w1*cosf(wb*w1)*r*w2*sinf(wb*w2) - sinf(wb*w1)*r*w2*w2*cosf(wb*w2); + + *f1 = cosf(alpha_rad)*nrf1 - sinf(alpha_rad)*nrf2 + xo; + *f2 = sinf(alpha_rad)*nrf1 + cosf(alpha_rad)*nrf2 + yo; + + *f1d = cosf(alpha_rad)*nrf1d - sinf(alpha_rad)*nrf2d; + *f2d = sinf(alpha_rad)*nrf1d + cosf(alpha_rad)*nrf2d; + + *f1dd = cosf(alpha_rad)*nrf1dd - sinf(alpha_rad)*nrf2dd; + *f2dd = sinf(alpha_rad)*nrf1dd + cosf(alpha_rad)*nrf2dd; +} + diff --git a/sw/airborne/modules/guidance/gvf_parametric/trajectories/gvf_parametric_2d_trefoil.h b/sw/airborne/modules/guidance/gvf_parametric/trajectories/gvf_parametric_2d_trefoil.h new file mode 100644 index 0000000000..227576f307 --- /dev/null +++ b/sw/airborne/modules/guidance/gvf_parametric/trajectories/gvf_parametric_2d_trefoil.h @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2020 Hector Garcia de Marina + * + * This file is part of paparazzi. + * + * paparazzi is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * paparazzi is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with paparazzi; see the file COPYING. If not, see + * . + */ + +/** + * @file modules/guidance/gvf_parametric/trajectories/gvf_parametric_2d_trefoil.h + * + * Guiding vector field algorithm for 2D and 3D complex trajectories. + * + * 2D trefoil knot + */ + +#ifndef GVF_PARAMETRIC_2D_TREFOIL_H +#define GVF_PARAMETRIC_2D_TREFOIL_H + +#ifdef __cplusplus +extern "C" { +#endif + +/** @typedef gvf_2d_tre_par +* @brief Parameters for the GVF parametric 2D trefoil knot +* @param kx Gain defining how agressive is the vector field in x coordinate +* @param ky Gain defining how agressive is the vector field in y coordinate +* @param w1 1st frequency +* @param w2 2nd frequency +* @param off Off-phase +* @param r Radius of the "circles" +* @param alpha Orientation/rotation of the trajectory in the XY plane +*/ +typedef struct { + float kx; + float ky; + float w1; + float w2; + float ratio; + float r; + float alpha; +} gvf_par_2d_tre_par; + +extern gvf_par_2d_tre_par gvf_parametric_2d_trefoil_par; + +extern void gvf_parametric_2d_trefoil_info(float *f1, float *f2, float *f1d, float *f2d, float *f1dd, float *f2dd); + +#ifdef __cplusplus +} +#endif + +#endif // GVF_PARAMETRIC_2D_TREFOIL_H diff --git a/sw/airborne/modules/guidance/gvf_parametric/trajectories/gvf_parametric_3d_ellipse.c b/sw/airborne/modules/guidance/gvf_parametric/trajectories/gvf_parametric_3d_ellipse.c new file mode 100644 index 0000000000..4f9e079184 --- /dev/null +++ b/sw/airborne/modules/guidance/gvf_parametric/trajectories/gvf_parametric_3d_ellipse.c @@ -0,0 +1,98 @@ +/* + * Copyright (C) 2020 Hector Garcia de Marina + * + * This file is part of paparazzi. + * + * paparazzi is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * paparazzi is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with paparazzi; see the file COPYING. If not, see + * . + */ + +/** + * @file modules/guidance/gvf_parametric/trajectories/gvf_parametric_3d_ellipse.c + * + * Guiding vector field algorithm for 2D and 3D complex trajectories. + * + * 3D ellipse (intersection between a cylinder and a tilted plane) + */ + +#include "subsystems/navigation/common_nav.h" +#include "modules/guidance/gvf_parametric/gvf_parametric.h" +#include "gvf_parametric_3d_ellipse.h" + +/*! Default gain kx for the 3d ellipse trajectory */ +#ifndef GVF_PARAMETRIC_3D_ELLIPSE_KX +#define GVF_PARAMETRIC_3D_ELLIPSE_KX 0.001 +#endif + +/*! Default gain ky for the 3d ellipse trajectory */ +#ifndef GVF_PARAMETRIC_3D_ELLIPSE_KY +#define GVF_PARAMETRIC_3D_ELLIPSE_KY 0.001 +#endif + +/*! Default gain kz for the 3d ellipse trajectory */ +#ifndef GVF_PARAMETRIC_3D_ELLIPSE_KZ +#define GVF_PARAMETRIC_3D_ELLIPSE_KZ 0.001 +#endif + +/*! Default radius of the cylinder */ +#ifndef GVF_PARAMETRIC_3D_ELLIPSE_R +#define GVF_PARAMETRIC_3D_ELLIPSE_R 80 +#endif + +/*! Default highest point for the ellipse trajectory */ +#ifndef GVF_PARAMETRIC_3D_ELLIPSE_ZL +#define GVF_PARAMETRIC_3D_ELLIPSE_ZL 40 +#endif + +/*! Default highest point for the ellipse trajectory */ +#ifndef GVF_PARAMETRIC_3D_ELLIPSE_ZH +#define GVF_PARAMETRIC_3D_ELLIPSE_ZH 40 +#endif + +/*! Default orientation in degrees for the lowest point of the ellipse */ +#ifndef GVF_PARAMETRIC_3D_ELLIPSE_ALPHA +#define GVF_PARAMETRIC_3D_ELLIPSE_ALPHA 0 +#endif + +gvf_par_3d_ell_par gvf_parametric_3d_ellipse_par = {GVF_PARAMETRIC_3D_ELLIPSE_KX, + GVF_PARAMETRIC_3D_ELLIPSE_KY, GVF_PARAMETRIC_3D_ELLIPSE_KZ, GVF_PARAMETRIC_3D_ELLIPSE_R, GVF_PARAMETRIC_3D_ELLIPSE_ZL, GVF_PARAMETRIC_3D_ELLIPSE_ZH, GVF_PARAMETRIC_3D_ELLIPSE_ALPHA + }; + +void gvf_parametric_3d_ellipse_info(float *f1, float *f2, float *f3, float *f1d, float *f2d, float *f3d, + float *f1dd, float *f2dd, float *f3dd) +{ + float xo = gvf_parametric_trajectory.p_parametric[0]; + float yo = gvf_parametric_trajectory.p_parametric[1]; + float r = gvf_parametric_trajectory.p_parametric[2]; + float zl = gvf_parametric_trajectory.p_parametric[3]; + float zh = gvf_parametric_trajectory.p_parametric[4]; + float alpha_rad = gvf_parametric_trajectory.p_parametric[5]*M_PI/180; + + float w = gvf_parametric_control.w; + float wb = w * gvf_parametric_control.beta * gvf_parametric_control.s; + + // Parametric equations of the trajectory and the partial derivatives w.r.t. 'w' + *f1 = r * cosf(wb) + xo; + *f2 = r * sinf(wb) + yo; + *f3 = 0.5 * (zh + zl + (zl - zh) * sinf(alpha_rad - wb)); + + *f1d = -r * sinf(wb); + *f2d = r * cosf(wb); + *f3d = -0.5 * (zl - zh) * cosf(alpha_rad - wb); + + *f1dd = -r * cosf(wb); + *f2dd = -r * sinf(wb); + *f3dd = -0.5 * (zl - zh) * sinf(alpha_rad - wb); +} + diff --git a/sw/airborne/modules/guidance/gvf_parametric/trajectories/gvf_parametric_3d_ellipse.h b/sw/airborne/modules/guidance/gvf_parametric/trajectories/gvf_parametric_3d_ellipse.h new file mode 100644 index 0000000000..c7edce3f85 --- /dev/null +++ b/sw/airborne/modules/guidance/gvf_parametric/trajectories/gvf_parametric_3d_ellipse.h @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2020 Hector Garcia de Marina + * + * This file is part of paparazzi. + * + * paparazzi is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * paparazzi is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with paparazzi; see the file COPYING. If not, see + * . + */ + +/** + * @file modules/guidance/gvf_parametric/trajectories/gvf_parametric_3d_ellipse.h + * + * Guiding vector field algorithm for 2D and 3D complex trajectories. + * + * 3D ellipse (intersection between a cylinder and a tilted plane) + */ + +#ifndef GVF_PARAMETRIC_3D_ELLIPSE_H +#define GVF_PARAMETRIC_3D_ELLIPSE_H + +#ifdef __cplusplus +extern "C" { +#endif + +/** @typedef gvf_3d_ell_par +* @brief Parameters for the GVF parametric 3D ellipse +* @param kx Gain defining how agressive is the vector field in x coordinate +* @param ky Gain defining how agressive is the vector field in y coordinate +* @param kz Gain defining how agressive is the vector field in z coordinate +* @param r Radius of the cylinder in meters +* @param zl Altitude of the lowest point of the ellipse +* @param zh Altitude of the highest point of the ellipse +* @param alpha Heading of the lowest point zl in rads +*/ +typedef struct { + float kx; + float ky; + float kz; + float r; + float zl; + float zh; + float alpha; +} gvf_par_3d_ell_par; + +extern gvf_par_3d_ell_par gvf_parametric_3d_ellipse_par; + +extern void gvf_parametric_3d_ellipse_info(float *f1, float *f2, float *f3, float *f1d, float *f2d, float *f3d, + float *f1dd, float *f2dd, float *f3dd); + +#ifdef __cplusplus +} +#endif + +#endif // GVF_PARAMETRIC_3D_ELLIPSE_H diff --git a/sw/airborne/modules/guidance/gvf_parametric/trajectories/gvf_parametric_3d_lissajous.c b/sw/airborne/modules/guidance/gvf_parametric/trajectories/gvf_parametric_3d_lissajous.c new file mode 100644 index 0000000000..f51010e15d --- /dev/null +++ b/sw/airborne/modules/guidance/gvf_parametric/trajectories/gvf_parametric_3d_lissajous.c @@ -0,0 +1,143 @@ +/* + * Copyright (C) 2020 Hector Garcia de Marina + * + * This file is part of paparazzi. + * + * paparazzi is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * paparazzi is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with paparazzi; see the file COPYING. If not, see + * . + */ + +/** + * @file modules/guidance/gvf_parametric/trajectories/gvf_parametric_3d_lissajous.c + * + * Guiding vector field algorithm for 2D and 3D complex trajectories. + * + * 3D lissajous + */ + +#include "subsystems/navigation/common_nav.h" +#include "modules/guidance/gvf_parametric/gvf_parametric.h" +#include "gvf_parametric_3d_lissajous.h" + +/*! Default gain kx for the 3d lissajous trajectory */ +#ifndef GVF_PARAMETRIC_3D_LISSAJOUS_KX +#define GVF_PARAMETRIC_3D_LISSAJOUS_KX 0.001 +#endif + +/*! Default gain ky for the 3d lissajous trajectory */ +#ifndef GVF_PARAMETRIC_3D_LISSAJOUS_KY +#define GVF_PARAMETRIC_3D_LISSAJOUS_KY 0.001 +#endif + +/*! Default gain kz for the 3d lissajous trajectory */ +#ifndef GVF_PARAMETRIC_3D_LISSAJOUS_KZ +#define GVF_PARAMETRIC_3D_LISSAJOUS_KZ 0.001 +#endif + +/*! Default amplitude of the trajectory in the X plane */ +#ifndef GVF_PARAMETRIC_3D_LISSAJOUS_CX +#define GVF_PARAMETRIC_3D_LISSAJOUS_CX 80 +#endif + +/*! Default amplitude of the trajectory in the Y plane */ +#ifndef GVF_PARAMETRIC_3D_LISSAJOUS_CY +#define GVF_PARAMETRIC_3D_LISSAJOUS_CY 80 +#endif + +/*! Default amplitude of the trajectory in the Z plane */ +#ifndef GVF_PARAMETRIC_3D_LISSAJOUS_CZ +#define GVF_PARAMETRIC_3D_LISSAJOUS_CZ 10 +#endif + +/*! Default frequency of the trajectory in the X plane */ +#ifndef GVF_PARAMETRIC_3D_LISSAJOUS_WX +#define GVF_PARAMETRIC_3D_LISSAJOUS_WX 1 +#endif + +/*! Default frequency of the trajectory in the Y plane */ +#ifndef GVF_PARAMETRIC_3D_LISSAJOUS_WY +#define GVF_PARAMETRIC_3D_LISSAJOUS_WY 1 +#endif + +/*! Default frequency of the trajectory in the Z plane */ +#ifndef GVF_PARAMETRIC_3D_LISSAJOUS_WZ +#define GVF_PARAMETRIC_3D_LISSAJOUS_WZ 1 +#endif + +/*! Default offphase of the trajectory in the X plane */ +#ifndef GVF_PARAMETRIC_3D_LISSAJOUS_DX +#define GVF_PARAMETRIC_3D_LISSAJOUS_DX 0 +#endif + +/*! Default offphase of the trajectory in the Y plane */ +#ifndef GVF_PARAMETRIC_3D_LISSAJOUS_DY +#define GVF_PARAMETRIC_3D_LISSAJOUS_DY 0 +#endif + +/*! Default offphase of the trajectory in the Z plane */ +#ifndef GVF_PARAMETRIC_3D_LISSAJOUS_DZ +#define GVF_PARAMETRIC_3D_LISSAJOUS_DZ 0 +#endif + +/*! Default orientation in degrees for the trajectory in the XY plane */ +#ifndef GVF_PARAMETRIC_3D_LISSAJOUS_ALPHA +#define GVF_PARAMETRIC_3D_LISSAJOUS_ALPHA 0 +#endif + +gvf_par_3d_lis_par gvf_parametric_3d_lissajous_par = {GVF_PARAMETRIC_3D_LISSAJOUS_KX, GVF_PARAMETRIC_3D_LISSAJOUS_KY, GVF_PARAMETRIC_3D_LISSAJOUS_KZ, GVF_PARAMETRIC_3D_LISSAJOUS_CX, GVF_PARAMETRIC_3D_LISSAJOUS_CY, GVF_PARAMETRIC_3D_LISSAJOUS_CZ, GVF_PARAMETRIC_3D_LISSAJOUS_WX, GVF_PARAMETRIC_3D_LISSAJOUS_WY, GVF_PARAMETRIC_3D_LISSAJOUS_WZ, GVF_PARAMETRIC_3D_LISSAJOUS_DX, GVF_PARAMETRIC_3D_LISSAJOUS_DY, GVF_PARAMETRIC_3D_LISSAJOUS_DZ, GVF_PARAMETRIC_3D_LISSAJOUS_ALPHA}; + +void gvf_parametric_3d_lissajous_info(float *f1, float *f2, float *f3, float *f1d, float *f2d, float *f3d, + float *f1dd, float *f2dd, float *f3dd) +{ + float xo = gvf_parametric_trajectory.p_parametric[0]; + float yo = gvf_parametric_trajectory.p_parametric[1]; + float zo = gvf_parametric_trajectory.p_parametric[2]; + float cx = gvf_parametric_trajectory.p_parametric[3]; + float cy = gvf_parametric_trajectory.p_parametric[4]; + float cz = gvf_parametric_trajectory.p_parametric[5]; + float wx = gvf_parametric_trajectory.p_parametric[6]; + float wy = gvf_parametric_trajectory.p_parametric[7]; + float wz = gvf_parametric_trajectory.p_parametric[8]; + float deltax_rad = gvf_parametric_trajectory.p_parametric[9]*M_PI/180; + float deltay_rad = gvf_parametric_trajectory.p_parametric[10]*M_PI/180; + float deltaz_rad = gvf_parametric_trajectory.p_parametric[11]*M_PI/180; + float alpha_rad = gvf_parametric_trajectory.p_parametric[12]*M_PI/180; + + float w = gvf_parametric_control.w; + float wb = w * gvf_parametric_control.beta * gvf_parametric_control.s; + + // Parametric equations of the trajectory and the partial derivatives w.r.t. 'w' + + float nrf1 = cx*cosf(wx*wb + deltax_rad); + float nrf2 = cy*cosf(wy*wb + deltay_rad); + + *f1 = cosf(alpha_rad)*nrf1 - sinf(alpha_rad)*nrf2 + xo; + *f2 = sinf(alpha_rad)*nrf1 + cosf(alpha_rad)*nrf2 + yo; + *f3 = cz*cosf(wz*wb + deltaz_rad) + zo; + + float nrf1d = -wx*cx*sinf(wx*wb + deltax_rad); + float nrf2d = -wy*cy*sinf(wy*wb + deltay_rad); + + *f1d = cosf(alpha_rad)*nrf1d - sinf(alpha_rad)*nrf2d; + *f2d = sinf(alpha_rad)*nrf1d + cosf(alpha_rad)*nrf2d; + *f3d = -wz*cz*sinf(wz*wb + deltaz_rad); + + float nrf1dd = -wx*wx*cx*cosf(wx*wb + deltax_rad); + float nrf2dd = -wy*wy*cy*cosf(wy*wb + deltay_rad); + + *f1dd = cosf(alpha_rad)*nrf1dd - sinf(alpha_rad)*nrf2dd; + *f2dd = sinf(alpha_rad)*nrf1dd + cosf(alpha_rad)*nrf2dd; + *f3dd = -wz*wz*cz*cosf(wz*wb + deltaz_rad); +} + diff --git a/sw/airborne/modules/guidance/gvf_parametric/trajectories/gvf_parametric_3d_lissajous.h b/sw/airborne/modules/guidance/gvf_parametric/trajectories/gvf_parametric_3d_lissajous.h new file mode 100644 index 0000000000..eaaf337633 --- /dev/null +++ b/sw/airborne/modules/guidance/gvf_parametric/trajectories/gvf_parametric_3d_lissajous.h @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2020 Hector Garcia de Marina + * + * This file is part of paparazzi. + * + * paparazzi is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * paparazzi is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with paparazzi; see the file COPYING. If not, see + * . + */ + +/** + * @file modules/guidance/gvf_parametric/trajectories/gvf_parametric_3d_lissajous.h + * + * Guiding vector field algorithm for 2D and 3D complex trajectories. + * + * 3D lissajous figures + */ + +#ifndef GVF_PARAMETRIC_3D_LISSAJOUS_H +#define GVF_PARAMETRIC_3D_LISSAJOUS_H + +#ifdef __cplusplus +extern "C" { +#endif + +/** @typedef gvf_3d_lis_par +* @brief Parameters for the GVF parametric 3D lissajous +* @param kx Gain defining how agressive is the vector field in x coordinate +* @param ky Gain defining how agressive is the vector field in y coordinate +* @param kz Gain defining how agressive is the vector field in z coordinate +* @param cx Amplitude of the trajectory in the X plane +* @param cy Amplitude of the trajectory in the Y plane +* @param cz Amplitude of the trajectory in the Z plane +* @param wx Frequency of the trajectory in the X plane +* @param wy Frequency of the trajectory in the Y plane +* @param wz Frequency of the trajectory in the Z plane +* @param dx Offphase of the trajectory in the X plane +* @param dy Offphase of the trajectory in the Y plane +* @param dz Offphase of the trajectory in the Z plane +* @param alpha Orientation/rotation of the trajectory in the XY plane +*/ +typedef struct { + float kx; + float ky; + float kz; + float cx; + float cy; + float cz; + float wx; + float wy; + float wz; + float dx; + float dy; + float dz; + float alpha; +} gvf_par_3d_lis_par; + +extern gvf_par_3d_lis_par gvf_parametric_3d_lissajous_par; + +extern void gvf_parametric_3d_lissajous_info(float *f1, float *f2, float *f3, float *f1d, float *f2d, float *f3d, + float *f1dd, float *f2dd, float *f3dd); + +#ifdef __cplusplus +} +#endif + +#endif // GVF_PARAMETRIC_3D_LISSAJOUS_H diff --git a/sw/ground_segment/python/gvf/gvf_parametric_3d_lissa_sim.py b/sw/ground_segment/python/gvf/gvf_parametric_3d_lissa_sim.py new file mode 100644 index 0000000000..18e59586f0 --- /dev/null +++ b/sw/ground_segment/python/gvf/gvf_parametric_3d_lissa_sim.py @@ -0,0 +1,189 @@ +# Simulator for the gvf_parametric algorithm in Paparazzi (fixed-wing). +# Here you can check the demanded climbing and heading rates for your aircraft +# and the expected trajectories as well. + +import numpy as np +from scipy import linalg as la + +import matplotlib.pyplot as plt +import matplotlib +from mpl_toolkits.mplot3d import Axes3D + +# Happy pdf for a happy submission without complains in paperplaza, arxiv, etc +font = {'size' : 20} + +matplotlib.rc('font', **font) + +matplotlib.rcParams['ps.useafm'] = True +matplotlib.rcParams['pdf.use14corefonts'] = True +matplotlib.rcParams['text.usetex'] = True + +# Simulation parameters +tf = 500 +dt = 5e-2 +time = np.linspace(0, tf, tf/dt) +it = 1 + +# Data log +X_h = np.zeros((time.size, 4)) +theta_h = np.zeros((time.size, 1)) +e1_h = np.zeros((time.size, 1)) +e2_h = np.zeros((time.size, 1)) +e3_h = np.zeros((time.size, 1)) +u_theta_h = np.zeros((time.size, 1)) +u_z_h = np.zeros((time.size, 1)) +u_w_h = np.zeros((time.size, 1)) + +# Initial conditions +X = np.array([[100], [-53], [40], [0]]) +X_dot = np.array([[0], [0], [0], [0]]) +theta = np.pi/4 + +X_h[0, :] = X.transpose() +theta_h[0] = theta + +# Desired trajectory +xo = 0 +yo = 0 +zo = 100 +cx = 150 +cy = 150 +cz = -10 +deltax = 0 +deltay = np.pi/2 +deltaz = 0 +wx = 1 +wy = 1 +wz = 1 + +alpha = np.pi/4 + +# Controller +L = 1e-1 +beta = 1e-2 +k1 = 1e-3 +k2 = 1e-3 +k3 = 1e-3 +ktheta = 0.5 + +# Vehicle +s = 15 + +for t in time[:-1]: + + x = X[0][0] + y = X[1][0] + z = X[2][0] + w = X[3][0] + + wb = w*beta + + #f + nrf1 = cx*np.cos(wx*wb + deltax) + nrf2 = cy*np.cos(wy*wb + deltay) + f3 = cz*np.cos(wz*wb + deltaz) + zo + + nrf1d = -wx*cx*np.sin(wx*wb + deltax) + nrf2d = -wy*cy*np.sin(wy*wb + deltay) + f3d = -wz*cz*np.sin(wz*wb + deltaz) + + nrf1dd = -wx*wx*cx*np.cos(wx*wb + deltax) + nrf2dd = -wy*wy*cy*np.cos(wy*wb + deltay) + f3dd = -wz*wz*cz*np.cos(wz*wb + deltaz) + + f1 = np.cos(alpha)*nrf1 - np.sin(alpha)*nrf2 + xo + f2 = np.sin(alpha)*nrf1 + np.cos(alpha)*nrf2 + yo + + f1d = np.cos(alpha)*nrf1d - np.sin(alpha)*nrf2d + f2d = np.sin(alpha)*nrf1d + np.cos(alpha)*nrf2d + + f1dd = np.cos(alpha)*nrf1dd - np.sin(alpha)*nrf2dd + f2dd = np.sin(alpha)*nrf1dd + np.cos(alpha)*nrf2dd + + #phi + phi1 = L*(x - f1) + phi2 = L*(y - f2) + phi3 = L*(z - f3) + + #Chi, J + Chi = L*np.array([[-f1d*L*L*beta -k1*phi1], + [-f2d*L*L*beta -k2*phi2], + [-f3d*L*L*beta -k3*phi3], + [-L*L + beta*(k1*phi1*f1d + k2*phi2*f2d + k3*phi3*f3d)]]) + + j44 = beta*beta*(k1*(phi1*f1dd-L*f1d*f1d) + k2*(phi2*f2dd-L*f2d*f2d) + k3*(phi3*f3dd-L*f3d*f3d)) + J = L*np.array([[-k1*L, 0, 0, -(beta*L)*(beta*L*f1dd-k1*f1d)], + [ 0, -k2*L, 0, -(beta*L)*(beta*L*f2dd-k2*f2d)], + [ 0, 0, -k3*L, -(beta*L)*(beta*L*f3dd-k3*f3d)], + [beta*L*k1*f1d, beta*L*k2*f2d, beta*L*k3*f3d, j44]]) + + #G, Fp, Gp + G = np.array([[1,0,0,0], + [0,1,0,0], + [0,0,0,0], + [0,0,0,0]]) + + Fp = np.array([[0, -1, 0, 0], + [1, 0, 0, 0]]) + + Gp = np.array([[0, -1, 0, 0], + [1, 0, 0, 0], + [0, 0, 0, 0], + [0, 0, 0, 0]]) + + h = np.array([[np.cos(theta)],[np.sin(theta)]]) + ht = h.transpose() + + Chit = Chi.transpose() + Chinorm = np.sqrt(Chi.transpose().dot(Chi))[0][0] + Chih = Chi / Chinorm + + u_theta = (-(1/(Chit.dot(G).dot(Chi))*Chit.dot(Gp).dot(np.eye(4) - Chih.dot(Chih.transpose())).dot(J).dot(X_dot)) - ktheta*ht.dot(Fp).dot(Chi) / np.sqrt(Chit.dot(G).dot(Chi)))[0][0] + + u_zeta = Chi[2][0]*s / np.sqrt(Chi[0][0]*Chi[0][0] + Chi[1][0]*Chi[1][0]) + u_w = Chi[3][0]*s / np.sqrt(Chi[0][0]*Chi[0][0] + Chi[1][0]*Chi[1][0]) + + # Euler integration + theta = theta + u_theta*dt + X_dot = np.array([[s*np.cos(theta)],[s*np.sin(theta)], [u_zeta], [u_w]]) + X = X + X_dot*dt + + + # Log + X_h[it, :] = X.transpose() + theta_h[it] = theta + e1_h[it] = phi1 + e2_h[it] = phi2 + e3_h[it] = phi3 + u_theta_h[it] = u_theta + u_z_h[it] = u_zeta + u_w_h[it] = u_w + + it = it + 1 + +# Plots + +fig3d = plt.figure() +ax3d = fig3d.gca(projection='3d') +ax3d.plot(X_h[:,0], X_h[:,1], X_h[:,2]) + +f, (axe1,axe2,axe3) = plt.subplots(3,1) +axe1.plot(time[1:], e1_h[1:]) +axe2.plot(time[1:], e2_h[1:]) +axe3.plot(time[1:], e3_h[1:]) +axe3.set_xlabel("Time [s]") +axe1.set_xlabel("error X") +axe2.set_xlabel("error Y") +axe3.set_xlabel("error Z") + +f, (axut,axuz,axuw) = plt.subplots(3,1) +axut.plot(time[1:], u_theta_h[1:]) +axuz.plot(time[1:], u_z_h[1:]) +axuw.plot(time[1:], u_w_h[1:]) +axuw.set_xlabel("Time [s]") +axut.set_ylabel("Heading rate [rad/s]") +axuz.set_ylabel("Climbing rate [m/s]") +axuw.set_ylabel("Virual coord rate") + +plt.show() + diff --git a/sw/ground_segment/python/gvf/gvfframe.py b/sw/ground_segment/python/gvf/gvfframe.py index 4b3bbf66a3..00eb36d625 100644 --- a/sw/ground_segment/python/gvf/gvfframe.py +++ b/sw/ground_segment/python/gvf/gvfframe.py @@ -4,6 +4,7 @@ import time from scipy import linalg as la from matplotlib.path import Path from matplotlib.backends.backend_wxagg import FigureCanvasWxAgg as FigureCanvas +from mpl_toolkits.mplot3d import Axes3D import matplotlib.pyplot as pl import matplotlib.patches as patches import numpy as np @@ -33,12 +34,13 @@ class GVFFrame(wx.Frame): self.course = 0 self.yaw = 0 self.XY = np.array([0, 0]) + self.altitude = 0 + self.ground_altitude = -1 # Desired trajectory - self.timer_traj = 0 # We do not update the traj every time we receive a msg - self.timer_traj_lim = 7 # (7+1) * 0.25secs self.s = 0 self.ke = 0 + self.gvf_error = 0 self.map_gvf = map2d(np.array([0, 0]), 150000) self.traj = None @@ -59,16 +61,18 @@ class GVFFrame(wx.Frame): if int(ac_id) == self.ac_id: if msg.name == 'GPS': self.course = int(msg.get_field(3))*np.pi/1800 + self.altitude = float(msg.get_field(4))/1000 if msg.name == 'NAVIGATION': self.XY[0] = float(msg.get_field(2)) self.XY[1] = float(msg.get_field(3)) + if msg.name == 'NAVIGATION_REF': + self.ground_altitude = float(msg.get_field(3)) if msg.name == 'ATTITUDE': self.yaw = float(msg.get_field(1)) if msg.name == 'GVF': self.gvf_error = float(msg.get_field(0)) # Straight line - if int(msg.get_field(1)) == 0 \ - and self.timer_traj == self.timer_traj_lim: + if int(msg.get_field(1)) == 0: self.s = int(msg.get_field(2)) self.ke = float(msg.get_field(3)) param = [float(x) for x in msg.get_field(4)] @@ -81,8 +85,7 @@ class GVFFrame(wx.Frame): self.s, self.ke) # Ellipse - if int(msg.get_field(1)) == 1 \ - and self.timer_traj == self.timer_traj_lim: + elif int(msg.get_field(1)) == 1: self.s = int(msg.get_field(2)) self.ke = float(msg.get_field(3)) param = [float(x) for x in msg.get_field(4)] @@ -96,8 +99,7 @@ class GVFFrame(wx.Frame): self.map_gvf.area, self.s, self.ke) # Sin - if int(msg.get_field(1)) == 2 \ - and self.timer_traj == self.timer_traj_lim: + elif int(msg.get_field(1)) == 2: self.s = int(msg.get_field(2)) self.ke = float(msg.get_field(3)) param = [float(x) for x in msg.get_field(4)] @@ -112,34 +114,83 @@ class GVFFrame(wx.Frame): self.traj.vector_field(self.traj.XYoff, \ self.map_gvf.area, self.s, self.ke) - self.timer_traj = self.timer_traj + 1 - if self.timer_traj > self.timer_traj_lim: - self.timer_traj = 0 + if msg.name == 'GVF_PARAMETRIC': + # Trefoil 2D + if int(msg.get_field(0)) == 0: + self.s = int(msg.get_field(1)) + self.wb = float(msg.get_field(2)) + param = [float(x) for x in msg.get_field(3)] + xo = param[0] + yo = param[1] + w1 = param[2] + w2 = param[3] + ratio = param[4] + r = param[5] + alpha = param[6] + phi = [float(x) for x in msg.get_field(4)] + phi_x = phi[0] + phi_y = phi[1] + self.traj = traj_param_trefoil_2D(np.array([xo, yo]), w1, w2, ratio, r, alpha, self.wb) - def draw_gvf(self, XY, yaw, course): + # Ellipse 3D + elif int(msg.get_field(0)) == 1: + self.s = int(msg.get_field(1)) + self.wb = float(msg.get_field(2)) + param = [float(x) for x in msg.get_field(3)] + xo = param[0] + yo = param[1] + r = param[2] + zl = param[3] + zh = param[4] + alpha = param[5] + phi = [float(x) for x in msg.get_field(4)] + phi_x = phi[0] + phi_y = phi[1] + phi_z = phi[2] + self.traj = traj_param_ellipse_3D(np.array([xo,yo]), r, zl, zh, alpha, self.wb) + + # Lissajous 3D + elif int(msg.get_field(0)) == 2: + self.s = int(msg.get_field(1)) + self.wb = float(msg.get_field(2)) + param = [float(x) for x in msg.get_field(3)] + xo = param[0] + yo = param[1] + zo = param[2] + cx = param[3] + cy = param[4] + cz = param[5] + wx = param[6] + wy = param[7] + wz = param[8] + dx = param[9] + dy = param[10] + dz = param[11] + alpha = param[12] + phi = [float(x) for x in msg.get_field(4)] + phi_x = phi[0] + phi_y = phi[1] + phi_z = phi[2] + self.traj = traj_param_lissajous_3D(np.array([xo,yo]), zo, cx, cy, cz, \ + wx, wy, wz, dx, dy, dz, alpha, self.wb) + + def draw_gvf(self, XY, yaw, course, altitude): if self.traj is not None: - self.map_gvf.draw(XY, yaw, course, self.traj) + self.map_gvf.draw(XY, yaw, course, altitude, self.traj) def OnClose(self, event): self.interface.shutdown() self.Destroy() def OnRedrawTimer(self, event): - self.draw_gvf(self.XY, self.yaw, self.course) + self.draw_gvf(self.XY, self.yaw, self.course, self.altitude-self.ground_altitude) self.canvas.draw() class map2d: def __init__(self, XYoff, area): self.XYoff = XYoff self.area = area - self.fig, self.ax = pl.subplots() - self.ax.set_xlabel('South [m]') - self.ax.set_ylabel('West [m]') - self.ax.set_title('2D Map') - self.ax.annotate('HOME', xy = (0, 0)) - self.ax.set_xlim(XYoff[0]-0.5*np.sqrt(area), XYoff[0]+0.5*np.sqrt(area)) - self.ax.set_ylim(XYoff[1]-0.5*np.sqrt(area), XYoff[1]+0.5*np.sqrt(area)) - self.ax.axis('equal') + self.fig = pl.figure() def vehicle_patch(self, XY, yaw): Rot = np.array([[np.cos(yaw), np.sin(yaw)],[-np.sin(yaw), np.cos(yaw)]]) @@ -167,42 +218,113 @@ class map2d: return patches.PathPatch(path, facecolor='red', lw=2) - def draw(self, XY, yaw, course, traj): - self.ax.clear() - self.ax.plot(traj.traj_points[0, :], traj.traj_points[1, :]) - self.ax.quiver(traj.mapgrad_X, traj.mapgrad_Y, \ - traj.mapgrad_U, traj.mapgrad_V, color='Teal', \ - pivot='mid', width=0.002) - self.ax.add_patch(self.vehicle_patch(XY, yaw)) # In radians - apex = 45*np.pi/180 # 30 degrees apex angle - b = np.sqrt(2*(self.area/2000) / np.sin(apex)) - h = b*np.cos(apex/2) - self.ax.arrow(XY[0], XY[1], \ - h*np.sin(course), h*np.cos(course),\ - head_width=5, head_length=10, fc='k', ec='k') - self.ax.annotate('HOME', xy = (0, 0)) - if isinstance(traj, traj_ellipse): - self.ax.annotate('ELLIPSE', xy = (traj.XYoff[0], traj.XYoff[1])) - self.ax.plot(0, 0, 'kx', ms=10, mew=2) - self.ax.plot(traj.XYoff[0], traj.XYoff[1], 'kx', ms=10, mew=2) - elif isinstance(traj, traj_sin): - self.ax.annotate('SIN', xy = (traj.XYoff[0], traj.XYoff[1])) - self.ax.plot(0, 0, 'kx', ms=10, mew=2) - self.ax.plot(traj.XYoff[0], traj.XYoff[1], 'kx', ms=10, mew=2) - elif isinstance(traj, traj_line): - self.ax.annotate('LINE', xy = (traj.XYoff[0], traj.XYoff[1])) - self.ax.plot(0, 0, 'kx', ms=10, mew=2) - self.ax.plot(traj.XYoff[0], traj.XYoff[1], 'kx', ms=10, mew=2) + def draw(self, XY, yaw, course, altitude, traj): + self.fig.clf() + if traj.dim == 2: + ax = self.fig.add_subplot(111) + ax.plot(traj.traj_points[0, :], traj.traj_points[1, :]) + ax.quiver(traj.mapgrad_X, traj.mapgrad_Y, \ + traj.mapgrad_U, traj.mapgrad_V, color='Teal', \ + pivot='mid', width=0.002) + ax.add_patch(self.vehicle_patch(XY, yaw)) # In radians + apex = 45*np.pi/180 # 30 degrees apex angle + b = np.sqrt(2*(self.area/2000) / np.sin(apex)) + h = b*np.cos(apex/2) + ax.arrow(XY[0], XY[1], \ + h*np.sin(course), h*np.cos(course),\ + head_width=5, head_length=10, fc='k', ec='k') + ax.annotate('HOME', xy = (0, 0)) + if isinstance(traj, traj_ellipse): + ax.annotate('ELLIPSE', xy = (traj.XYoff[0], traj.XYoff[1])) + ax.plot(0, 0, 'kx', ms=10, mew=2) + ax.plot(traj.XYoff[0], traj.XYoff[1], 'kx', ms=10, mew=2) + elif isinstance(traj, traj_sin): + ax.annotate('SIN', xy = (traj.XYoff[0], traj.XYoff[1])) + ax.plot(0, 0, 'kx', ms=10, mew=2) + ax.plot(traj.XYoff[0], traj.XYoff[1], 'kx', ms=10, mew=2) + elif isinstance(traj, traj_line): + ax.annotate('LINE', xy = (traj.XYoff[0], traj.XYoff[1])) + ax.plot(0, 0, 'kx', ms=10, mew=2) + ax.plot(traj.XYoff[0], traj.XYoff[1], 'kx', ms=10, mew=2) + elif isinstance(traj, traj_param_trefoil_2D): + ax.annotate('TREFOIL', xy = (traj.XYoff[0], traj.XYoff[1])) + ax.plot(0, 0, 'kx', ms=10, mew=2) + ax.plot(traj.XYoff[0], traj.XYoff[1], 'kx', ms=10, mew=2) + ax.plot(traj.wpoint[0], traj.wpoint[1], 'rx', ms=10, mew=2) - self.ax.set_xlabel('South [m]') - self.ax.set_ylabel('West [m]') - self.ax.set_title('2D Map') - self.ax.set_xlim(self.XYoff[0]-0.5*np.sqrt(self.area), \ - self.XYoff[0]+0.5*np.sqrt(self.area)) - self.ax.set_ylim(self.XYoff[1]-0.5*np.sqrt(self.area), \ - self.XYoff[1]+0.5*np.sqrt(self.area)) - self.ax.axis('equal') - self.ax.grid() + ax.set_xlabel('South [m]') + ax.set_ylabel('West [m]') + ax.set_title('2D Map') + ax.set_xlim(self.XYoff[0]-0.5*np.sqrt(self.area), \ + self.XYoff[0]+0.5*np.sqrt(self.area)) + ax.set_ylim(self.XYoff[1]-0.5*np.sqrt(self.area), \ + self.XYoff[1]+0.5*np.sqrt(self.area)) + ax.axis('equal') + ax.grid() + + if traj.dim == 3: + a3d = self.fig.add_subplot(2,2,1, projection='3d') + axy = self.fig.add_subplot(2,2,2) + axz = self.fig.add_subplot(2,2,3) + ayz = self.fig.add_subplot(2,2,4) + + a3d.set_title('3D Map') + axy.set_title('XY Map') + axz.set_title('XZ Map') + ayz.set_title('YZ Map') + + # 3D + a3d.plot(traj.traj_points[0, :], traj.traj_points[1, :], traj.traj_points[2, :]) + if altitude != -1: + a3d.plot([XY[0]], [XY[1]], [altitude], marker='o', markerfacecolor='r', markeredgecolor='r') + a3d.plot([traj.wpoint[0]], [traj.wpoint[1]], [traj.wpoint[2]], marker='x', markerfacecolor='r', markeredgecolor='r') + + a3d.axis('equal') + if traj.deltaz < 0: + a3d.set_zlim(traj.zo+1.5*traj.deltaz, traj.zo-1.5*traj.deltaz) + else: + a3d.set_zlim(traj.zo-1.5*traj.deltaz, traj.zo+1.5*traj.deltaz) + + # XY + axy.plot(traj.traj_points[0, :], traj.traj_points[1, :]) + axy.add_patch(self.vehicle_patch(XY, yaw)) # In radians + apex = 45*np.pi/180 # 30 degrees apex angle + b = np.sqrt(2*(self.area/2000) / np.sin(apex)) + h = b*np.cos(apex/2) + axy.arrow(XY[0], XY[1], \ + h*np.sin(course), h*np.cos(course),\ + head_width=5, head_length=10, fc='k', ec='k') + axy.annotate('HOME', xy = (0, 0)) + axy.plot(traj.wpoint[0], traj.wpoint[1], 'rx', ms=10, mew=2) + if isinstance(traj, traj_param_ellipse_3D): + axy.annotate('ELLIPSE_3D', xy = (traj.XYoff[0], traj.XYoff[1])) + axy.plot(0, 0, 'kx', ms=10, mew=2) + axy.plot(traj.XYoff[0], traj.XYoff[1], 'kx', ms=10, mew=2) + elif isinstance(traj, traj_param_lissajous_3D): + axy.annotate('LISSA_3D', xy = (traj.XYoff[0], traj.XYoff[1])) + axy.plot(0, 0, 'kx', ms=10, mew=2) + axy.plot(traj.XYoff[0], traj.XYoff[1], 'kx', ms=10, mew=2) + + axy.axis('equal') + + # XZ + axz.plot(traj.traj_points[0, :], traj.traj_points[2, :]) + if altitude != -1: + axz.plot([XY[0]], [altitude], 'ro') + axz.plot(traj.wpoint[0], traj.wpoint[2], 'rx', ms=10, mew=2) + if traj.deltaz < 0: + axz.set_ylim(traj.zo+1.5*traj.deltaz, traj.zo-1.5*traj.deltaz) + else: + axz.set_ylim(traj.zo-1.5*traj.deltaz, traj.zo+1.5*traj.deltaz) + # YZ + ayz.plot(traj.traj_points[1, :], traj.traj_points[2, :]) + if altitude != -1: + ayz.plot([XY[1]], [altitude], 'ro') + ayz.plot(traj.wpoint[1], traj.wpoint[2], 'rx', ms=10, mew=2) + if traj.deltaz < 0: + axz.set_ylim(traj.zo+1.5*traj.deltaz, traj.zo-1.5*traj.deltaz) + else: + ayz.set_ylim(traj.zo-1.5*traj.deltaz, traj.zo+1.5*traj.deltaz) class traj_line: def float_range(self, start, end, step): @@ -211,6 +333,7 @@ class traj_line: start += step def __init__(self, Xminmax, a, b, alpha): + self.dim = 2 self.XYoff = np.array([a, b]) self.Xminmax = Xminmax self.a, self.b, self.alpha = a, b, alpha @@ -265,6 +388,7 @@ class traj_ellipse: start += step def __init__(self, XYoff, rot, a, b): + self.dim = 2 self.XYoff = XYoff self.a, self.b = a, b self.rot = rot @@ -324,6 +448,7 @@ class traj_sin: start += step def __init__(self, Xminmax, a, b, alpha, w, off, A): + self.dim = 2 self.XYoff = np.array([a, b]) self.Xminmax = Xminmax self.a, self.b, self.alpha, self.w, self.off, self.A = \ @@ -382,3 +507,125 @@ class traj_sin: self.mapgrad_U = self.mapgrad_U/norm self.mapgrad_V = self.mapgrad_V/norm + +class traj_param_trefoil_2D: + def float_range(self, start, end, step): + while start <= end: + yield start + start += step + + def __init__(self, XYoff, w1, w2, ratio, r, alpha, wb): + self.dim = 2 + self.XYoff, self.w1, self.w2, self.ratio, self.r, self.alpha, self.wb = XYoff, w1, w2, ratio, r, alpha, wb + self.mapgrad_X = [] + self.mapgrad_Y = [] + self.mapgrad_U = [] + self.mapgrad_V = [] + + self.alpha = alpha*np.pi/180 + + self.wpoint = self.param_point(self.wb) + + num_points = 1000 + self.traj_points = np.zeros((2, num_points)) + + i = 0 + range_points = 1000.0 + for t in self.float_range(0, range_points, range_points/num_points): + self.traj_points[:, i] = self.param_point(t) + i = i + 1 + if i >= num_points: + break + + def param_point(self, t): + xnr = np.cos(t*self.w1)*(self.r*np.cos(t*self.w2) + self.ratio) + ynr = np.sin(t*self.w1)*(self.r*np.cos(t*self.w2) + self.ratio) + + x = np.cos(self.alpha)*xnr - np.sin(self.alpha)*ynr + y = np.sin(self.alpha)*xnr + np.cos(self.alpha)*ynr + + return self.XYoff + np.array([x,y]) + +class traj_param_ellipse_3D: + def float_range(self, start, end, step): + while start <= end: + yield start + start += step + + def __init__(self, XYoff, r, zl, zh, alpha, wb): + self.dim = 3 + self.XYoff, self.r, self.zl, self.zh, self.alpha, self.wb = XYoff, r, zl, zh, alpha, wb + self.mapgrad_X = [] + self.mapgrad_Y = [] + self.mapgrad_U = [] + self.mapgrad_V = [] + + self.deltaz = self.zh - self.zl # For the 3D plot + self.zo = self.zl + self.deltaz # For the 3D plot + self.alpha = self.alpha*np.pi/180 + + self.wpoint = self.param_point(self.wb) + + num_points = 100 + self.traj_points = np.zeros((3, num_points)) + + i = 0 + range_points = 2*np.pi + 0.1 + for t in self.float_range(0, range_points, range_points/num_points): + self.traj_points[:, i] = self.param_point(t) + i = i + 1 + if i >= num_points: + break + + def param_point(self, t): + x = self.r * np.cos(t) + self.XYoff[0] + y = self.r * np.sin(t) + self.XYoff[1] + z = 0.5 * (self.zh + self.zl + (self.zl - self.zh) * np.sin(self.alpha - t)) + + return np.array([x,y,z]) + +class traj_param_lissajous_3D: + def float_range(self, start, end, step): + while start <= end: + yield start + start += step + + def __init__(self, XYoff, zo, cx, cy, cz, wx, wy, wz, dx, dy, dz, alpha, wb): + self.dim = 3 + self.XYoff, self.zo, self.cx, self.cy, self.cz, self.wx, self.wy, self.wz, self.dx, self.dy, self.dz, \ + self.alpha, self.wb = XYoff, zo, cx, cy, cz, wx, wy, wz, dx, dy, dz, alpha, wb + self.mapgrad_X = [] + self.mapgrad_Y = [] + self.mapgrad_U = [] + self.mapgrad_V = [] + + self.deltaz = self.cz # For the 3D plot + self.alpha = self.alpha*np.pi/180 + self.dx = self.dx*np.pi/180 + self.dy = self.dy*np.pi/180 + self.dz = self.dz*np.pi/180 + + self.wpoint = self.param_point(self.wb) + + smallest_w = min([self.wx, self.wy, self.wz]) + + num_points = 100 + self.traj_points = np.zeros((3, num_points)) + + i = 0 + range_points = 3*np.pi / smallest_w + for t in self.float_range(0, range_points, range_points/num_points): + self.traj_points[:, i] = self.param_point(t) + i = i + 1 + if i >= num_points: + break + + def param_point(self, t): + xnr = self.cx*np.cos(self.wx*t + self.dx) + ynr = self.cy*np.cos(self.wy*t + self.dy) + z = self.cz*np.cos(self.wz*t + self.dz) + self.zo + + x = np.cos(self.alpha)*xnr - np.sin(self.alpha)*ynr + self.XYoff[0] + y = np.sin(self.alpha)*xnr + np.cos(self.alpha)*ynr + self.XYoff[1] + + return np.array([x,y,z])