/* This program 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 3 of the License, or (at your option) any later version. This program 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 this program. If not, see . */ /* StratoBlimp simulator class */ #include "SIM_StratoBlimp.h" #if AP_SIM_STRATOBLIMP_ENABLED #include #include #include using namespace SITL; extern const AP_HAL::HAL& hal; // SITL StratoBlimp parameters const AP_Param::GroupInfo StratoBlimp::var_info[] = { // @Param: MASS // @DisplayName: mass // @Description: mass of blimp not including lifting gas // @Units: kg AP_GROUPINFO("MASS", 1, StratoBlimp, mass, 80), // @Param: HMASS // @DisplayName: helium mass // @Description: mass of lifting gas // @Units: kg AP_GROUPINFO("HMASS", 2, StratoBlimp, helium_mass, 13.54), // @Param: ARM_LEN // @DisplayName: arm length // @Description: distance from center of mass to one motor // @Units: m AP_GROUPINFO("ARM_LEN", 3, StratoBlimp, arm_length, 3.6), // @Param: MOT_THST // @DisplayName: motor thrust // @Description: thrust at max throttle for one motor // @Units: N AP_GROUPINFO("MOT_THST", 4, StratoBlimp, motor_thrust, 145), // @Param: DRAG_FWD // @DisplayName: drag in forward direction // @Description: drag on X axis AP_GROUPINFO("DRAG_FWD", 5, StratoBlimp, drag_fwd, 0.27), // @Param: DRAG_SIDE // @DisplayName: drag in sidewards direction // @Description: drag on Y axis AP_GROUPINFO("DRAG_SIDE", 16, StratoBlimp, drag_side, 0.5), // @Param: DRAG_UP // @DisplayName: drag in upward direction // @Description: drag on Z axis AP_GROUPINFO("DRAG_UP", 6, StratoBlimp, drag_up, 0.4), // @Param: MOI_YAW // @DisplayName: moment of inertia in yaw // @Description: moment of inertia in yaw AP_GROUPINFO("MOI_YAW", 7, StratoBlimp, moi_yaw, 2800), // @Param: MOI_ROLL // @DisplayName: moment of inertia in roll // @Description: moment of inertia in roll AP_GROUPINFO("MOI_ROLL", 8, StratoBlimp, moi_roll, 1400), // @Param: MOI_PITCH // @DisplayName: moment of inertia in pitch // @Description: moment of inertia in pitch AP_GROUPINFO("MOI_PITCH", 9, StratoBlimp, moi_pitch, 3050), // @Param: ALT_TARG // @DisplayName: altitude target // @Description: altitude target // @Units: m AP_GROUPINFO("ALT_TARG", 10, StratoBlimp, altitude_target, 20000), // @Param: CLMB_RT // @DisplayName: target climb rate // @Description: target climb rate // @Units: m/s AP_GROUPINFO("CLMB_RT", 11, StratoBlimp, target_climb_rate, 5), // @Param: YAW_RT // @DisplayName: yaw rate // @Description: maximum yaw rate with full left throttle at target altitude // @Units: deg/s AP_GROUPINFO("YAW_RT", 12, StratoBlimp, yaw_rate_max, 60), // @Param: MOT_ANG // @DisplayName: motor angle // @Description: maximum motor tilt angle // @Units: deg AP_GROUPINFO("MOT_ANG", 13, StratoBlimp, motor_angle, 20), // @Param: COL // @DisplayName: center of lift // @Description: center of lift position above CoG // @Units: m AP_GROUPINFO("COL", 14, StratoBlimp, center_of_lift, 2.54), // @Param: WVANE // @DisplayName: weathervaning offset // @Description: center of drag for weathervaning // @Units: m AP_GROUPINFO("WVANE", 15, StratoBlimp, center_of_drag, 0.3), // @Param: FLR // @DisplayName: free lift rate // @Description: amount of additional lift generated by the helper balloon (for the purpose of ascent), as a proportion of the 'neutral buoyancy' lift AP_GROUPINFO("FLR", 17, StratoBlimp, free_lift_rate, 0.12), AP_GROUPEND }; StratoBlimp::StratoBlimp(const char *frame_str) : Aircraft(frame_str) { AP::sitl()->models.stratoblimp_ptr = this; AP_Param::setup_object_defaults(this, var_info); } /* calculate coefficients to match parameters */ void StratoBlimp::calculate_coefficients(void) { // calculate yaw drag based on turn rate at the given altitude drag_yaw = 1.0; // get full throttle rotational accel for one motor Vector3f body_acc, rot_accel; handle_motor(1, 0, body_acc, rot_accel, -arm_length); // get rotational drag at target alt Vector3f vel_bf, g, drag_linear, drag_rotaccel; g.z = radians(yaw_rate_max); get_drag(vel_bf, g, altitude_target, drag_linear, drag_rotaccel); drag_yaw = rot_accel.z / -drag_rotaccel.z; } void StratoBlimp::handle_motor(float throttle, float tilt, Vector3f &body_acc, Vector3f &rot_accel, float lateral_position) { const float angle_rad = radians(motor_angle) * tilt; const float thrust_x = motor_thrust * throttle; const float total_mass = mass + helium_mass; const Vector3f thrust{cosf(angle_rad)*thrust_x, 0, -sinf(angle_rad)*thrust_x}; // assume constant with pressure alt and linear Vector3f accel = thrust / total_mass; Vector3f pos{0, lateral_position, 0}; Vector3f torque = (pos % thrust); rot_accel.z += torque.z / moi_yaw; body_acc += accel; } /* get body frame linear and rotational drag for a given velocity and altitude */ void StratoBlimp::get_drag(const Vector3f &velocity_linear, const Vector3f &velocity_rot, float altitude, Vector3f &drag_linear, Vector3f &drag_rotaccel) { Vector3f vel_air_bf = velocity_linear; const float drag_x_sign = vel_air_bf.x>0? -1 : 1; const float drag_y_sign = vel_air_bf.y>0? -1 : 1; const float drag_z_sign = vel_air_bf.z>0? -1 : 1; drag_linear.x = 0.5 * drag_x_sign * air_density * sq(vel_air_bf.x) * drag_fwd; drag_linear.y = 0.5 * drag_y_sign * air_density * sq(vel_air_bf.y) * drag_fwd; drag_linear.z = 0.5 * drag_z_sign * air_density * sq(vel_air_bf.z) * drag_up; drag_rotaccel = -velocity_rot * drag_yaw; /* apply torque from drag */ Vector3f drag_force = drag_linear * mass; Vector3f drag_pos{-center_of_drag, 0, -center_of_lift}; Vector3f drag_torque = (drag_pos % drag_force); drag_rotaccel += drag_torque / moi_pitch; } /* get vertical thrust from lift in Newtons */ float StratoBlimp::get_lift(float altitude) { // start with neutral buoyancy float lift_accel = GRAVITY_MSS; // add lift from helper balloon if still attached if (helper_balloon_attached) { // helper balloon additional lift amount based on Free Lift Ratio lift_accel += GRAVITY_MSS*free_lift_rate; // detach helper balloon if the target altitude has been reached if (altitude >= altitude_target) { helper_balloon_attached = false; } } return mass * lift_accel; } // calculate rotational and linear accelerations in body frame void StratoBlimp::calculate_forces(const struct sitl_input &input, Vector3f &body_acc, Vector3f &rot_accel) { //float delta_time = frame_time_us * 1.0e-6f; if (!hal.scheduler->is_system_initialized()) { return; } const float left_tilt = filtered_servo_angle(input, 0); const float right_tilt = filtered_servo_angle(input, 1); const float left_throttle = filtered_servo_range(input, 2); const float right_throttle = filtered_servo_range(input, 3); const float ground_release = filtered_servo_range(input, 4); body_acc.zero(); rot_accel.zero(); handle_motor(left_throttle, left_tilt, body_acc, rot_accel, -arm_length); handle_motor(right_throttle, right_tilt, body_acc, rot_accel, arm_length); Vector3f drag_linear, drag_rotaccel; get_drag(velocity_air_bf, gyro, location.alt*0.01, drag_linear, drag_rotaccel); body_acc += drag_linear; rot_accel += drag_rotaccel; if (ground_release > 0.9) { released = true; } if (released) { Vector3f lift_thrust_ef{0, 0, -get_lift(location.alt*0.01)}; Vector3f lift_thrust_bf = dcm.transposed() * lift_thrust_ef; body_acc += lift_thrust_bf / mass; /* apply righting moment */ Vector3f lift_pos{0, 0, -center_of_lift}; Vector3f lift_torque = (lift_pos % lift_thrust_bf); rot_accel += lift_torque / moi_roll; } } /* update the airship simulation by one time step */ void StratoBlimp::update(const struct sitl_input &input) { air_density = get_air_density(location.alt*0.01); EAS2TAS = sqrtf(SSL_AIR_DENSITY / air_density); calculate_coefficients(); float delta_time = frame_time_us * 1.0e-6f; Vector3f rot_accel = Vector3f(0,0,0); calculate_forces(input, accel_body, rot_accel); // update rotational rates in body frame gyro += rot_accel * delta_time; gyro.x = constrain_float(gyro.x, -radians(2000.0f), radians(2000.0f)); gyro.y = constrain_float(gyro.y, -radians(2000.0f), radians(2000.0f)); gyro.z = constrain_float(gyro.z, -radians(2000.0f), radians(2000.0f)); dcm.rotate(gyro * delta_time); dcm.normalize(); update_dynamics(rot_accel); update_external_payload(input); // update lat/lon/altitude update_position(); update_wind(input); time_advance(); // update magnetic field update_mag_field_bf(); } #endif // AP_SIM_STRATOBLIMP_ENABLED