/**************************************************************************** * * Copyright (c) 2013-2019 PX4 Development Team. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the * distribution. * 3. Neither the name PX4 nor the names of its contributors may be * used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. * ****************************************************************************/ /** * @file ControlAllocator.cpp * * Control allocator. * * @author Julien Lecoeur */ #include "ControlAllocator.hpp" #include #include #include #include using namespace matrix; using namespace time_literals; ControlAllocator::ControlAllocator() : ModuleParams(nullptr), ScheduledWorkItem(MODULE_NAME, px4::wq_configurations::rate_ctrl), _loop_perf(perf_alloc(PC_ELAPSED, MODULE_NAME": cycle")) { _control_allocator_status_pub[0].advertise(); _control_allocator_status_pub[1].advertise(); _actuator_motors_pub.advertise(); _actuator_servos_pub.advertise(); _actuator_servos_trim_pub.advertise(); for (int i = 0; i < MAX_NUM_MOTORS; ++i) { char buffer[17]; snprintf(buffer, sizeof(buffer), "CA_R%u_SLEW", i); _param_handles.slew_rate_motors[i] = param_find(buffer); } for (int i = 0; i < MAX_NUM_SERVOS; ++i) { char buffer[17]; snprintf(buffer, sizeof(buffer), "CA_SV%u_SLEW", i); _param_handles.slew_rate_servos[i] = param_find(buffer); } parameters_updated(); } ControlAllocator::~ControlAllocator() { for (int i = 0; i < ActuatorEffectiveness::MAX_NUM_MATRICES; ++i) { delete _control_allocation[i]; } delete _actuator_effectiveness; perf_free(_loop_perf); } bool ControlAllocator::init() { if (!_vehicle_torque_setpoint_sub.registerCallback()) { PX4_ERR("callback registration failed"); return false; } if (!_vehicle_thrust_setpoint_sub.registerCallback()) { PX4_ERR("callback registration failed"); return false; } #ifndef ENABLE_LOCKSTEP_SCHEDULER // Backup schedule would interfere with lockstep ScheduleDelayed(50_ms); #endif return true; } void ControlAllocator::parameters_updated() { _has_slew_rate = false; for (int i = 0; i < MAX_NUM_MOTORS; ++i) { param_get(_param_handles.slew_rate_motors[i], &_params.slew_rate_motors[i]); _has_slew_rate |= _params.slew_rate_motors[i] > FLT_EPSILON; } for (int i = 0; i < MAX_NUM_SERVOS; ++i) { param_get(_param_handles.slew_rate_servos[i], &_params.slew_rate_servos[i]); _has_slew_rate |= _params.slew_rate_servos[i] > FLT_EPSILON; } // Allocation method & effectiveness source // Do this first: in case a new method is loaded, it will be configured below bool updated = update_effectiveness_source(); update_allocation_method(updated); // must be called after update_effectiveness_source() if (_num_control_allocation == 0) { return; } for (int i = 0; i < _num_control_allocation; ++i) { _control_allocation[i]->updateParameters(); } update_effectiveness_matrix_if_needed(EffectivenessUpdateReason::CONFIGURATION_UPDATE); } void ControlAllocator::update_allocation_method(bool force) { AllocationMethod configured_method = (AllocationMethod)_param_ca_method.get(); if (!_actuator_effectiveness) { PX4_ERR("_actuator_effectiveness null"); return; } if (_allocation_method_id != configured_method || force) { matrix::Vector actuator_sp[ActuatorEffectiveness::MAX_NUM_MATRICES]; // Cleanup first for (int i = 0; i < ActuatorEffectiveness::MAX_NUM_MATRICES; ++i) { // Save current state if (_control_allocation[i] != nullptr) { actuator_sp[i] = _control_allocation[i]->getActuatorSetpoint(); } delete _control_allocation[i]; _control_allocation[i] = nullptr; } _num_control_allocation = _actuator_effectiveness->numMatrices(); AllocationMethod desired_methods[ActuatorEffectiveness::MAX_NUM_MATRICES]; _actuator_effectiveness->getDesiredAllocationMethod(desired_methods); bool normalize_as_planar_mc[ActuatorEffectiveness::MAX_NUM_MATRICES]; _actuator_effectiveness->getNormalizeAsPlanarMC(normalize_as_planar_mc); for (int i = 0; i < _num_control_allocation; ++i) { AllocationMethod method = configured_method; if (configured_method == AllocationMethod::AUTO) { method = desired_methods[i]; } switch (method) { case AllocationMethod::PSEUDO_INVERSE: _control_allocation[i] = new ControlAllocationPseudoInverse(); break; case AllocationMethod::SEQUENTIAL_DESATURATION: _control_allocation[i] = new ControlAllocationSequentialDesaturation(); break; default: PX4_ERR("Unknown allocation method"); break; } if (_control_allocation[i] == nullptr) { PX4_ERR("alloc failed"); _num_control_allocation = 0; } else { _control_allocation[i]->setNormalizeAsPlanarMC(normalize_as_planar_mc[i]); _control_allocation[i]->setActuatorSetpoint(actuator_sp[i]); } } _allocation_method_id = configured_method; } } bool ControlAllocator::update_effectiveness_source() { const EffectivenessSource source = (EffectivenessSource)_param_ca_airframe.get(); if (_effectiveness_source_id != source) { // try to instanciate new effectiveness source ActuatorEffectiveness *tmp = nullptr; switch (source) { case EffectivenessSource::NONE: case EffectivenessSource::MULTIROTOR: tmp = new ActuatorEffectivenessMultirotor(this); break; case EffectivenessSource::STANDARD_VTOL: tmp = new ActuatorEffectivenessStandardVTOL(this); break; case EffectivenessSource::TILTROTOR_VTOL: tmp = new ActuatorEffectivenessTiltrotorVTOL(this); break; case EffectivenessSource::TAILSITTER_VTOL: tmp = new ActuatorEffectivenessTailsitterVTOL(this); break; case EffectivenessSource::ROVER_ACKERMANN: tmp = new ActuatorEffectivenessRoverAckermann(); break; case EffectivenessSource::ROVER_DIFFERENTIAL: // differential_drive_control does allocation and publishes directly to actuator_motors topic break; case EffectivenessSource::FIXED_WING: tmp = new ActuatorEffectivenessFixedWing(this); break; case EffectivenessSource::MOTORS_6DOF: // just a different UI from MULTIROTOR tmp = new ActuatorEffectivenessUUV(this); break; case EffectivenessSource::MULTIROTOR_WITH_TILT: tmp = new ActuatorEffectivenessMCTilt(this); break; case EffectivenessSource::CUSTOM: tmp = new ActuatorEffectivenessCustom(this); break; case EffectivenessSource::HELICOPTER_TAIL_ESC: tmp = new ActuatorEffectivenessHelicopter(this, ActuatorType::MOTORS); break; case EffectivenessSource::HELICOPTER_TAIL_SERVO: tmp = new ActuatorEffectivenessHelicopter(this, ActuatorType::SERVOS); break; case EffectivenessSource::HELICOPTER_COAXIAL: tmp = new ActuatorEffectivenessHelicopterCoaxial(this); break; default: PX4_ERR("Unknown airframe"); break; } // Replace previous source with new one if (tmp == nullptr) { // It did not work, forget about it PX4_ERR("Actuator effectiveness init failed"); _param_ca_airframe.set((int)_effectiveness_source_id); } else { // Swap effectiveness sources delete _actuator_effectiveness; _actuator_effectiveness = tmp; // Save source id _effectiveness_source_id = source; } return true; } return false; } void ControlAllocator::Run() { if (should_exit()) { _vehicle_torque_setpoint_sub.unregisterCallback(); _vehicle_thrust_setpoint_sub.unregisterCallback(); exit_and_cleanup(); return; } perf_begin(_loop_perf); #ifndef ENABLE_LOCKSTEP_SCHEDULER // Backup schedule would interfere with lockstep // Push backup schedule ScheduleDelayed(50_ms); #endif // Check if parameters have changed if (_parameter_update_sub.updated()) { // clear update parameter_update_s param_update; _parameter_update_sub.copy(¶m_update); if (_handled_motor_failure_bitmask == 0) { // We don't update the geometry after an actuator failure, as it could lead to unexpected results // (e.g. a user could add/remove motors, such that the bitmask isn't correct anymore) updateParams(); parameters_updated(); } } if (_num_control_allocation == 0 || _actuator_effectiveness == nullptr) { return; } { vehicle_status_s vehicle_status; if (_vehicle_status_sub.update(&vehicle_status)) { _armed = vehicle_status.arming_state == vehicle_status_s::ARMING_STATE_ARMED; // Check if the current flight phase is HOVER or FIXED_WING if (vehicle_status.vehicle_type == vehicle_status_s::VEHICLE_TYPE_ROTARY_WING) { _flight_phase = ActuatorEffectiveness::FlightPhase::HOVER_FLIGHT; } else { _flight_phase = ActuatorEffectiveness::FlightPhase::FORWARD_FLIGHT; } // Special cases for VTOL in transition if (vehicle_status.is_vtol && vehicle_status.in_transition_mode) { if (vehicle_status.in_transition_to_fw) { _flight_phase = ActuatorEffectiveness::FlightPhase::TRANSITION_HF_TO_FF; } else { _flight_phase = ActuatorEffectiveness::FlightPhase::TRANSITION_FF_TO_HF; } } // Forward to effectiveness source _actuator_effectiveness->setFlightPhase(_flight_phase); } } { vehicle_control_mode_s vehicle_control_mode; if (_vehicle_control_mode_sub.update(&vehicle_control_mode)) { _publish_controls = vehicle_control_mode.flag_control_allocation_enabled; } } // Guard against too small (< 0.2ms) and too large (> 20ms) dt's. const hrt_abstime now = hrt_absolute_time(); const float dt = math::constrain(((now - _last_run) / 1e6f), 0.0002f, 0.02f); bool do_update = false; vehicle_torque_setpoint_s vehicle_torque_setpoint; vehicle_thrust_setpoint_s vehicle_thrust_setpoint; // Run allocator on torque changes if (_vehicle_torque_setpoint_sub.update(&vehicle_torque_setpoint)) { _torque_sp = matrix::Vector3f(vehicle_torque_setpoint.xyz); do_update = true; _timestamp_sample = vehicle_torque_setpoint.timestamp_sample; } // Also run allocator on thrust setpoint changes if the torque setpoint // has not been updated for more than 5ms if (_vehicle_thrust_setpoint_sub.update(&vehicle_thrust_setpoint)) { _thrust_sp = matrix::Vector3f(vehicle_thrust_setpoint.xyz); if (dt > 0.005f) { do_update = true; _timestamp_sample = vehicle_thrust_setpoint.timestamp_sample; } } if (do_update) { _last_run = now; // update matrix if there was an actuator activation change (due to detected failure or actuator activation due to VTOL mode change) if (check_for_actuator_activation_update()) { update_effectiveness_matrix_if_needed(EffectivenessUpdateReason::ACTUATOR_ACTIVATION_UPDATE); } else { update_effectiveness_matrix_if_needed(EffectivenessUpdateReason::NO_EXTERNAL_UPDATE); } float fw_dthr_weight[3] = {_param_ca_fw_dthr_wgt_r.get(), _param_ca_fw_dthr_wgt_p.get(), _param_ca_fw_dthr_wgt_y.get()}; float fw_dthr_scale[3] = {_param_ca_fw_dthr_sc_r.get(), _param_ca_fw_dthr_sc_p.get(), _param_ca_fw_dthr_sc_y.get()}; // VTOL Tailsitters have roll/yaw axis swaped in fixed-wing mode if (_effectiveness_source_id == EffectivenessSource::TAILSITTER_VTOL) { fw_dthr_weight[2] = fw_dthr_weight[0]; fw_dthr_weight[0] = fw_dthr_weight[2]; fw_dthr_scale[2] = fw_dthr_scale[0]; fw_dthr_scale[0] = fw_dthr_scale[2]; } for (int i = 0; i < 3; i++) { if (_handled_servo_failure_bitmask && _param_ca_fw_dthr_fb_en.get() && !_has_control_authority[1][i]) { // If fallback is enabled and it is discovered that an axis doesn't have any // control authority, then the differential thrust weight for that axis is set to 1. fw_dthr_weight[i] = 1.f; } } // Set control setpoint vector(s) matrix::Vector c[ActuatorEffectiveness::MAX_NUM_MATRICES]; c[0](0) = _torque_sp(0); c[0](1) = _torque_sp(1); c[0](2) = _torque_sp(2); c[0](3) = _thrust_sp(0); c[0](4) = _thrust_sp(1); c[0](5) = _thrust_sp(2); const int airmode = _flight_phase != ActuatorEffectiveness::FlightPhase::FORWARD_FLIGHT ? _param_mc_airmode.get() : _param_ca_fw_dthr_airmd.get(); if (_num_control_allocation > 1) { matrix::Vector vehicle_torque_setpoint_matrix_1; if (_vehicle_torque_setpoint1_sub.copy(&vehicle_torque_setpoint)) { vehicle_torque_setpoint_matrix_1(0) = vehicle_torque_setpoint.xyz[0]; vehicle_torque_setpoint_matrix_1(1) = vehicle_torque_setpoint.xyz[1]; vehicle_torque_setpoint_matrix_1(2) = vehicle_torque_setpoint.xyz[2]; // reduce outputs on servos (matrix 1) if differential thrust with motors (matrix 0) is enabled c[1](0) = vehicle_torque_setpoint_matrix_1(0) * (1.f - fw_dthr_weight[0]); c[1](1) = vehicle_torque_setpoint_matrix_1(1) * (1.f - fw_dthr_weight[1]); c[1](2) = vehicle_torque_setpoint_matrix_1(2) * (1.f - fw_dthr_weight[2]); } if (_vehicle_thrust_setpoint1_sub.copy(&vehicle_thrust_setpoint)) { c[1](3) = vehicle_thrust_setpoint.xyz[0]; c[1](4) = vehicle_thrust_setpoint.xyz[1]; c[1](5) = vehicle_thrust_setpoint.xyz[2]; } // VTOL differential thrust in FW if (_flight_phase == ActuatorEffectiveness::FlightPhase::FORWARD_FLIGHT) { /* CA_FW_DTHR_SC_R etc are scales to tune response of differential thrust around each axis. The scaling factor is applied to the (normalized) torque setpoint from the rate controller going to the motors for rate control in fixed-wing flight using differential thrust instead of aerodynamic control surfaces. Set this parameter such that when the control surfaces are disabled, the systems rate tracking is maintained as best as possible through differential thrust. */ c[0](0) = vehicle_torque_setpoint_matrix_1(0) * fw_dthr_scale[0] * fw_dthr_weight[0]; c[0](1) = vehicle_torque_setpoint_matrix_1(1) * fw_dthr_scale[1] * fw_dthr_weight[1]; c[0](2) = vehicle_torque_setpoint_matrix_1(2) * fw_dthr_scale[2] * fw_dthr_weight[2]; const bool has_non_zero_dthr_weight = fw_dthr_weight[0] > FLT_EPSILON || fw_dthr_weight[1] > FLT_EPSILON || fw_dthr_weight[2] > FLT_EPSILON; if (has_non_zero_dthr_weight) { _actuator_effectiveness->setEnableAuxiliaryMotors(true); // Currently only does something for Standard VTOL } else { _actuator_effectiveness->setEnableAuxiliaryMotors(false); } } } for (int i = 0; i < _num_control_allocation; ++i) { _control_allocation[i]->setAirmode(airmode); _control_allocation[i]->setControlSetpoint(c[i]); // Do allocation _control_allocation[i]->allocate(); _actuator_effectiveness->allocateAuxilaryControls(dt, i, _control_allocation[i]->_actuator_sp); //flaps and spoilers _actuator_effectiveness->updateSetpoint(c[i], i, _control_allocation[i]->_actuator_sp, _control_allocation[i]->getActuatorMin(), _control_allocation[i]->getActuatorMax()); if (_has_slew_rate) { _control_allocation[i]->applySlewRateLimit(dt); } _control_allocation[i]->clipActuatorSetpoint(); } } // Publish actuator setpoint and allocator status publish_actuator_controls(); // Publish status at limited rate, as it's somewhat expensive and we use it for slower dynamics // (i.e. anti-integrator windup) if (now - _last_status_pub >= 5_ms) { publish_control_allocator_status(0); if (_num_control_allocation > 1) { publish_control_allocator_status(1); } _last_status_pub = now; } perf_end(_loop_perf); } void ControlAllocator::update_effectiveness_matrix_if_needed(EffectivenessUpdateReason reason) { ActuatorEffectiveness::Configuration config{}; if (reason == EffectivenessUpdateReason::NO_EXTERNAL_UPDATE && hrt_elapsed_time(&_last_effectiveness_update) < 100_ms) { // rate-limit updates return; } if (_actuator_effectiveness->getEffectivenessMatrix(config, reason)) { _last_effectiveness_update = hrt_absolute_time(); memcpy(_control_allocation_selection_indexes, config.matrix_selection_indexes, sizeof(_control_allocation_selection_indexes)); // Get the minimum and maximum depending on type and configuration ActuatorEffectiveness::ActuatorVector minimum[ActuatorEffectiveness::MAX_NUM_MATRICES]; ActuatorEffectiveness::ActuatorVector maximum[ActuatorEffectiveness::MAX_NUM_MATRICES]; ActuatorEffectiveness::ActuatorVector slew_rate[ActuatorEffectiveness::MAX_NUM_MATRICES]; int actuator_idx = 0; int actuator_idx_matrix[ActuatorEffectiveness::MAX_NUM_MATRICES] {}; actuator_servos_trim_s trims{}; static_assert(actuator_servos_trim_s::NUM_CONTROLS == actuator_servos_s::NUM_CONTROLS, "size mismatch"); for (int actuator_type = 0; actuator_type < (int)ActuatorType::COUNT; ++actuator_type) { _num_actuators[actuator_type] = config.num_actuators[actuator_type]; for (int actuator_type_idx = 0; actuator_type_idx < config.num_actuators[actuator_type]; ++actuator_type_idx) { if (actuator_idx >= NUM_ACTUATORS) { _num_actuators[actuator_type] = 0; PX4_ERR("Too many actuators"); break; } int selected_matrix = _control_allocation_selection_indexes[actuator_idx]; if ((ActuatorType)actuator_type == ActuatorType::MOTORS) { if (actuator_type_idx >= MAX_NUM_MOTORS) { PX4_ERR("Too many motors"); _num_actuators[actuator_type] = 0; break; } if (_param_r_rev.get() & (1u << actuator_type_idx)) { minimum[selected_matrix](actuator_idx_matrix[selected_matrix]) = -1.f; } else { minimum[selected_matrix](actuator_idx_matrix[selected_matrix]) = 0.f; } slew_rate[selected_matrix](actuator_idx_matrix[selected_matrix]) = _params.slew_rate_motors[actuator_type_idx]; } else if ((ActuatorType)actuator_type == ActuatorType::SERVOS) { if (actuator_type_idx >= MAX_NUM_SERVOS) { PX4_ERR("Too many servos"); _num_actuators[actuator_type] = 0; break; } minimum[selected_matrix](actuator_idx_matrix[selected_matrix]) = -1.f; slew_rate[selected_matrix](actuator_idx_matrix[selected_matrix]) = _params.slew_rate_servos[actuator_type_idx]; trims.trim[actuator_type_idx] = config.trim[selected_matrix](actuator_idx_matrix[selected_matrix]); } else { minimum[selected_matrix](actuator_idx_matrix[selected_matrix]) = -1.f; } maximum[selected_matrix](actuator_idx_matrix[selected_matrix]) = 1.f; ++actuator_idx_matrix[selected_matrix]; ++actuator_idx; } } const int16_t motor_failed_disabled_bitmask = _handled_motor_failure_bitmask | _handled_motor_disabled_bitmask; // Handle failed/disabled motors if (motor_failed_disabled_bitmask) { actuator_idx = 0; memset(&actuator_idx_matrix, 0, sizeof(actuator_idx_matrix)); for (int motors_idx = 0; motors_idx < _num_actuators[0] && motors_idx < actuator_motors_s::NUM_CONTROLS; motors_idx++) { int selected_matrix = _control_allocation_selection_indexes[actuator_idx]; if (motor_failed_disabled_bitmask & (1 << motors_idx)) { ActuatorEffectiveness::EffectivenessMatrix &matrix = config.effectiveness_matrices[selected_matrix]; for (int i = 0; i < NUM_AXES; i++) { matrix(i, actuator_idx_matrix[selected_matrix]) = 0.0f; } } ++actuator_idx_matrix[selected_matrix]; ++actuator_idx; } } // Handle failed servos if (_handled_servo_failure_bitmask) { actuator_idx = 0; memset(&actuator_idx_matrix, 0, sizeof(actuator_idx_matrix)); for (int servos_idx = 0; servos_idx < _num_actuators[0] && servos_idx < actuator_servos_s::NUM_CONTROLS; servos_idx++) { const int selected_matrix = 1; // matrix 1 (only works for VTOL atm) if (_handled_servo_failure_bitmask & (1 << servos_idx)) { ActuatorEffectiveness::EffectivenessMatrix &matrix = config.effectiveness_matrices[selected_matrix]; for (int i = 0; i < NUM_AXES; i++) { matrix(i, actuator_idx_matrix[selected_matrix]) = 0.f; } } ++actuator_idx_matrix[selected_matrix]; ++actuator_idx; } } for (int i = 0; i < _num_control_allocation; ++i) { _control_allocation[i]->setActuatorMin(minimum[i]); _control_allocation[i]->setActuatorMax(maximum[i]); _control_allocation[i]->setSlewRateLimit(slew_rate[i]); // Set all the elements of a row to 0 if that row has weak authority. // That ensures that the algorithm doesn't try to control axes with only marginal control authority, // which in turn would degrade the control of the main axes that actually should and can be controlled. ActuatorEffectiveness::EffectivenessMatrix &matrix = config.effectiveness_matrices[i]; for (int n = 0; n < NUM_AXES; n++) { bool all_entries_small = true; for (int m = 0; m < config.num_actuators_matrix[i]; m++) { if (fabsf(matrix(n, m)) > 0.05f) { all_entries_small = false; } } if (all_entries_small) { matrix.row(n) = 0.f; _has_control_authority[i][n] = false; } else { _has_control_authority[i][n] = true; } } // Assign control effectiveness matrix int total_num_actuators = config.num_actuators_matrix[i]; _control_allocation[i]->setEffectivenessMatrix(config.effectiveness_matrices[i], config.trim[i], config.linearization_point[i], total_num_actuators, reason == EffectivenessUpdateReason::CONFIGURATION_UPDATE); } trims.timestamp = hrt_absolute_time(); _actuator_servos_trim_pub.publish(trims); } } void ControlAllocator::publish_control_allocator_status(int matrix_index) { control_allocator_status_s control_allocator_status{}; control_allocator_status.timestamp = hrt_absolute_time(); // TODO: disabled motors (?) // Allocated control const matrix::Vector &allocated_control = _control_allocation[matrix_index]->getAllocatedControl(); // Unallocated control const matrix::Vector unallocated_control = _control_allocation[matrix_index]->getControlSetpoint() - allocated_control; control_allocator_status.unallocated_torque[0] = unallocated_control(0); control_allocator_status.unallocated_torque[1] = unallocated_control(1); control_allocator_status.unallocated_torque[2] = unallocated_control(2); control_allocator_status.unallocated_thrust[0] = unallocated_control(3); control_allocator_status.unallocated_thrust[1] = unallocated_control(4); control_allocator_status.unallocated_thrust[2] = unallocated_control(5); control_allocator_status.torque_setpoint[0] = _control_allocation[matrix_index]->getControlSetpoint()(0); control_allocator_status.torque_setpoint[1] = _control_allocation[matrix_index]->getControlSetpoint()(1); control_allocator_status.torque_setpoint[2] = _control_allocation[matrix_index]->getControlSetpoint()(2); control_allocator_status.thrust_setpoint[0] = _control_allocation[matrix_index]->getControlSetpoint()(3); control_allocator_status.thrust_setpoint[1] = _control_allocation[matrix_index]->getControlSetpoint()(4); control_allocator_status.thrust_setpoint[2] = _control_allocation[matrix_index]->getControlSetpoint()(5); // override control_allocator_status in customized saturation logic for certain effectiveness types _actuator_effectiveness->getUnallocatedControl(matrix_index, control_allocator_status); // Allocation success flags control_allocator_status.torque_setpoint_achieved = (Vector3f(control_allocator_status.unallocated_torque[0], control_allocator_status.unallocated_torque[1], control_allocator_status.unallocated_torque[2]).norm_squared() < 1e-6f); control_allocator_status.thrust_setpoint_achieved = (Vector3f(control_allocator_status.unallocated_thrust[0], control_allocator_status.unallocated_thrust[1], control_allocator_status.unallocated_thrust[2]).norm_squared() < 1e-6f); // Actuator saturation const matrix::Vector &actuator_sp = _control_allocation[matrix_index]->getActuatorSetpoint(); const matrix::Vector &actuator_min = _control_allocation[matrix_index]->getActuatorMin(); const matrix::Vector &actuator_max = _control_allocation[matrix_index]->getActuatorMax(); for (int i = 0; i < NUM_ACTUATORS; i++) { if (actuator_sp(i) > (actuator_max(i) - FLT_EPSILON)) { control_allocator_status.actuator_saturation[i] = control_allocator_status_s::ACTUATOR_SATURATION_UPPER; } else if (actuator_sp(i) < (actuator_min(i) + FLT_EPSILON)) { control_allocator_status.actuator_saturation[i] = control_allocator_status_s::ACTUATOR_SATURATION_LOWER; } } // Handled motor failures control_allocator_status.handled_motor_failure_mask = _handled_motor_failure_bitmask; _control_allocator_status_pub[matrix_index].publish(control_allocator_status); } void ControlAllocator::publish_actuator_controls() { if (!_publish_controls) { return; } actuator_motors_s actuator_motors; actuator_motors.timestamp = hrt_absolute_time(); actuator_motors.timestamp_sample = _timestamp_sample; actuator_servos_s actuator_servos; actuator_servos.timestamp = actuator_motors.timestamp; actuator_servos.timestamp_sample = _timestamp_sample; actuator_motors.reversible_flags = _param_r_rev.get(); int actuator_idx = 0; int actuator_idx_matrix[ActuatorEffectiveness::MAX_NUM_MATRICES] {}; // set output to NAN of motors that are stopped or disabled const uint32_t stopped_or_disabled_motors = _actuator_effectiveness->getStoppedMotors() | _actuator_effectiveness->getDisabledMotors(); // motors int motors_idx; for (motors_idx = 0; motors_idx < _num_actuators[0] && motors_idx < actuator_motors_s::NUM_CONTROLS; motors_idx++) { int selected_matrix = _control_allocation_selection_indexes[actuator_idx]; float actuator_sp = _control_allocation[selected_matrix]->getActuatorSetpoint()(actuator_idx_matrix[selected_matrix]); actuator_motors.control[motors_idx] = PX4_ISFINITE(actuator_sp) ? actuator_sp : NAN; if (stopped_or_disabled_motors & (1u << motors_idx)) { actuator_motors.control[motors_idx] = NAN; } ++actuator_idx_matrix[selected_matrix]; ++actuator_idx; } for (int i = motors_idx; i < actuator_motors_s::NUM_CONTROLS; i++) { actuator_motors.control[i] = NAN; } _actuator_motors_pub.publish(actuator_motors); // servos if (_num_actuators[1] > 0) { int servos_idx; for (servos_idx = 0; servos_idx < _num_actuators[1] && servos_idx < actuator_servos_s::NUM_CONTROLS; servos_idx++) { int selected_matrix = _control_allocation_selection_indexes[actuator_idx]; float actuator_sp = _control_allocation[selected_matrix]->getActuatorSetpoint()(actuator_idx_matrix[selected_matrix]); actuator_servos.control[servos_idx] = PX4_ISFINITE(actuator_sp) ? actuator_sp : NAN; ++actuator_idx_matrix[selected_matrix]; ++actuator_idx; } for (int i = servos_idx; i < actuator_servos_s::NUM_CONTROLS; i++) { actuator_servos.control[i] = NAN; } _actuator_servos_pub.publish(actuator_servos); } } bool ControlAllocator::check_for_actuator_activation_update() { bool activation_updated = false; failure_detector_status_s failure_detector_status; const bool status_updated = _failure_detector_status_sub.update(&failure_detector_status); if ((FailureMode)_param_ca_failure_mode.get() > FailureMode::IGNORE && status_updated) { if (failure_detector_status.fd_motor) { if (_handled_motor_failure_bitmask != failure_detector_status.motor_failure_mask) { // motor failure bitmask changed switch ((FailureMode)_param_ca_failure_mode.get()) { case FailureMode::REMOVE_FIRST_FAILING_MOTOR: { // Count number of failed motors const int num_motors_failed = math::countSetBits(failure_detector_status.motor_failure_mask); // Only handle if it is the first failure if (_handled_motor_failure_bitmask == 0 && num_motors_failed == 1) { _handled_motor_failure_bitmask = failure_detector_status.motor_failure_mask; PX4_WARN("Removing motor from allocation (0x%x)", _handled_motor_failure_bitmask); activation_updated = true; } } break; default: break; } } } else if (_handled_motor_failure_bitmask != 0) { // Clear bitmask completely PX4_INFO("Restoring all motors"); _handled_motor_failure_bitmask = 0; activation_updated = true; } if (failure_detector_status.fd_servo) { if (_handled_servo_failure_bitmask != failure_detector_status.servo_failure_mask) { // servo failure bitmask changed _handled_servo_failure_bitmask = failure_detector_status.servo_failure_mask; PX4_WARN("Removing servo nr. %d from allocation", _handled_servo_failure_bitmask); activation_updated = true; } } else if (_handled_servo_failure_bitmask != 0) { // Clear bitmask completely PX4_INFO("Restoring all servos"); _handled_servo_failure_bitmask = 0; activation_updated = true; } } // handle disabled actuators (currently only VTOL motors) if (_actuator_effectiveness->getDisabledMotors() != _handled_motor_disabled_bitmask) { _handled_motor_disabled_bitmask = _actuator_effectiveness->getDisabledMotors(); activation_updated = true; } return activation_updated; } int ControlAllocator::task_spawn(int argc, char *argv[]) { ControlAllocator *instance = new ControlAllocator(); if (instance) { _object.store(instance); _task_id = task_id_is_work_queue; if (instance->init()) { return PX4_OK; } } else { PX4_ERR("alloc failed"); } delete instance; _object.store(nullptr); _task_id = -1; return PX4_ERROR; } int ControlAllocator::print_status() { PX4_INFO("Running"); // Print current allocation method switch (_allocation_method_id) { case AllocationMethod::NONE: PX4_INFO("Method: None"); break; case AllocationMethod::PSEUDO_INVERSE: PX4_INFO("Method: Pseudo-inverse"); break; case AllocationMethod::SEQUENTIAL_DESATURATION: PX4_INFO("Method: Sequential desaturation"); break; case AllocationMethod::AUTO: PX4_INFO("Method: Auto"); break; } // Print current airframe if (_actuator_effectiveness != nullptr) { PX4_INFO("Effectiveness Source: %s", _actuator_effectiveness->name()); } // Print current effectiveness matrix for (int i = 0; i < _num_control_allocation; ++i) { const ActuatorEffectiveness::EffectivenessMatrix &effectiveness = _control_allocation[i]->getEffectivenessMatrixAllocation(); if (_num_control_allocation > 1) { PX4_INFO("Instance: %i", i); } // const size_t num_actuators = _control_allocation[i]->numConfiguredActuators(); const size_t num_actuators = 6; matrix::Matrix effectiveness_sliced_transposed( effectiveness.T().slice(0, 0)); const matrix::Matrix mix_raw = _control_allocation[i]->getMixMatrix(); const matrix::Matrix mix_raw_sliced(mix_raw.slice(0, 0)); const matrix::Matrix mix_normalized = _control_allocation[i]->getNormalizedMixMatrix(); const matrix::Matrix mix_normalized_sliced( mix_normalized.slice(0, 0)); PX4_INFO(" Effectiveness.T (sliced) ="); effectiveness_sliced_transposed.print(); PX4_INFO(" Mixer matrix raw (sliced) ="); mix_raw_sliced.print(); PX4_INFO(" Mixer matrix normalized (sliced) ="); mix_normalized_sliced.print(); PX4_INFO(" minimum ="); _control_allocation[i]->getActuatorMin().T().print(); PX4_INFO(" maximum ="); _control_allocation[i]->getActuatorMax().T().print(); PX4_INFO(" Configured actuators: %i", _control_allocation[i]->numConfiguredActuators()); } if (_handled_motor_failure_bitmask) { PX4_INFO("Failed motors: %i (0x%x)", math::countSetBits(_handled_motor_failure_bitmask), _handled_motor_failure_bitmask); } // Print perf perf_print_counter(_loop_perf); return 0; } int ControlAllocator::custom_command(int argc, char *argv[]) { return print_usage("unknown command"); } int ControlAllocator::print_usage(const char *reason) { if (reason) { PX4_WARN("%s\n", reason); } PRINT_MODULE_DESCRIPTION( R"DESCR_STR( ### Description This implements control allocation. It takes torque and thrust setpoints as inputs and outputs actuator setpoint messages. )DESCR_STR"); PRINT_MODULE_USAGE_NAME("control_allocator", "controller"); PRINT_MODULE_USAGE_COMMAND("start"); PRINT_MODULE_USAGE_DEFAULT_COMMANDS(); return 0; } /** * Control Allocator app start / stop handling function */ extern "C" __EXPORT int control_allocator_main(int argc, char *argv[]); int control_allocator_main(int argc, char *argv[]) { return ControlAllocator::main(argc, argv); }