/**************************************************************************** * * Copyright (c) 2015-2023 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. * ****************************************************************************/ #include #include "EKF2.hpp" using namespace time_literals; using math::constrain; using matrix::Eulerf; using matrix::Quatf; using matrix::Vector3f; static constexpr float kDefaultExternalPosAccuracy = 50.0f; // [m] static constexpr float kMaxDelaySecondsExternalPosMeasurement = 15.0f; // [s] pthread_mutex_t ekf2_module_mutex = PTHREAD_MUTEX_INITIALIZER; static px4::atomic _objects[EKF2_MAX_INSTANCES] {}; #if defined(CONFIG_EKF2_MULTI_INSTANCE) static px4::atomic _ekf2_selector {nullptr}; #endif // CONFIG_EKF2_MULTI_INSTANCE EKF2::EKF2(bool multi_mode, const px4::wq_config_t &config, bool replay_mode): ModuleParams(nullptr), ScheduledWorkItem(MODULE_NAME, config), _replay_mode(replay_mode && !multi_mode), _multi_mode(multi_mode), _instance(multi_mode ? -1 : 0), _attitude_pub(multi_mode ? ORB_ID(estimator_attitude) : ORB_ID(vehicle_attitude)), _local_position_pub(multi_mode ? ORB_ID(estimator_local_position) : ORB_ID(vehicle_local_position)), _global_position_pub(multi_mode ? ORB_ID(estimator_global_position) : ORB_ID(vehicle_global_position)), _odometry_pub(multi_mode ? ORB_ID(estimator_odometry) : ORB_ID(vehicle_odometry)), #if defined(CONFIG_EKF2_WIND) _wind_pub(multi_mode ? ORB_ID(estimator_wind) : ORB_ID(wind)), #endif // CONFIG_EKF2_WIND _params(_ekf.getParamHandle()), _param_ekf2_predict_us(_params->filter_update_interval_us), _param_ekf2_delay_max(_params->delay_max_ms), _param_ekf2_imu_ctrl(_params->imu_ctrl), #if defined(CONFIG_EKF2_AUXVEL) _param_ekf2_avel_delay(_params->auxvel_delay_ms), #endif // CONFIG_EKF2_AUXVEL _param_ekf2_gyr_noise(_params->gyro_noise), _param_ekf2_acc_noise(_params->accel_noise), _param_ekf2_gyr_b_noise(_params->gyro_bias_p_noise), _param_ekf2_acc_b_noise(_params->accel_bias_p_noise), #if defined(CONFIG_EKF2_WIND) _param_ekf2_wind_nsd(_params->wind_vel_nsd), #endif // CONFIG_EKF2_WIND _param_ekf2_noaid_noise(_params->pos_noaid_noise), #if defined(CONFIG_EKF2_GNSS) _param_ekf2_gps_ctrl(_params->gnss_ctrl), _param_ekf2_gps_delay(_params->gps_delay_ms), _param_ekf2_gps_pos_x(_params->gps_pos_body(0)), _param_ekf2_gps_pos_y(_params->gps_pos_body(1)), _param_ekf2_gps_pos_z(_params->gps_pos_body(2)), _param_ekf2_gps_v_noise(_params->gps_vel_noise), _param_ekf2_gps_p_noise(_params->gps_pos_noise), _param_ekf2_gps_p_gate(_params->gps_pos_innov_gate), _param_ekf2_gps_v_gate(_params->gps_vel_innov_gate), _param_ekf2_gps_check(_params->gps_check_mask), _param_ekf2_req_eph(_params->req_hacc), _param_ekf2_req_epv(_params->req_vacc), _param_ekf2_req_sacc(_params->req_sacc), _param_ekf2_req_nsats(_params->req_nsats), _param_ekf2_req_pdop(_params->req_pdop), _param_ekf2_req_hdrift(_params->req_hdrift), _param_ekf2_req_vdrift(_params->req_vdrift), _param_ekf2_gsf_tas_default(_params->EKFGSF_tas_default), #endif // CONFIG_EKF2_GNSS #if defined(CONFIG_EKF2_BAROMETER) _param_ekf2_baro_ctrl(_params->baro_ctrl), _param_ekf2_baro_delay(_params->baro_delay_ms), _param_ekf2_baro_noise(_params->baro_noise), _param_ekf2_baro_gate(_params->baro_innov_gate), _param_ekf2_gnd_eff_dz(_params->gnd_effect_deadzone), _param_ekf2_gnd_max_hgt(_params->gnd_effect_max_hgt), # if defined(CONFIG_EKF2_BARO_COMPENSATION) _param_ekf2_aspd_max(_params->max_correction_airspeed), _param_ekf2_pcoef_xp(_params->static_pressure_coef_xp), _param_ekf2_pcoef_xn(_params->static_pressure_coef_xn), _param_ekf2_pcoef_yp(_params->static_pressure_coef_yp), _param_ekf2_pcoef_yn(_params->static_pressure_coef_yn), _param_ekf2_pcoef_z(_params->static_pressure_coef_z), # endif // CONFIG_EKF2_BARO_COMPENSATION #endif // CONFIG_EKF2_BAROMETER #if defined(CONFIG_EKF2_AIRSPEED) _param_ekf2_asp_delay(_params->airspeed_delay_ms), _param_ekf2_tas_gate(_params->tas_innov_gate), _param_ekf2_eas_noise(_params->eas_noise), _param_ekf2_arsp_thr(_params->arsp_thr), #endif // CONFIG_EKF2_AIRSPEED #if defined(CONFIG_EKF2_SIDESLIP) _param_ekf2_beta_gate(_params->beta_innov_gate), _param_ekf2_beta_noise(_params->beta_noise), _param_ekf2_fuse_beta(_params->beta_fusion_enabled), #endif // CONFIG_EKF2_SIDESLIP #if defined(CONFIG_EKF2_MAGNETOMETER) _param_ekf2_mag_delay(_params->mag_delay_ms), _param_ekf2_mag_e_noise(_params->mage_p_noise), _param_ekf2_mag_b_noise(_params->magb_p_noise), _param_ekf2_head_noise(_params->mag_heading_noise), _param_ekf2_mag_noise(_params->mag_noise), _param_ekf2_mag_decl(_params->mag_declination_deg), _param_ekf2_hdg_gate(_params->heading_innov_gate), _param_ekf2_mag_gate(_params->mag_innov_gate), _param_ekf2_decl_type(_params->mag_declination_source), _param_ekf2_mag_type(_params->mag_fusion_type), _param_ekf2_mag_acclim(_params->mag_acc_gate), _param_ekf2_mag_check(_params->mag_check), _param_ekf2_mag_chk_str(_params->mag_check_strength_tolerance_gs), _param_ekf2_mag_chk_inc(_params->mag_check_inclination_tolerance_deg), _param_ekf2_synthetic_mag_z(_params->synthesize_mag_z), #endif // CONFIG_EKF2_MAGNETOMETER _param_ekf2_hgt_ref(_params->height_sensor_ref), _param_ekf2_noaid_tout(_params->valid_timeout_max), #if defined(CONFIG_EKF2_TERRAIN) || defined(CONFIG_EKF2_OPTICAL_FLOW) || defined(CONFIG_EKF2_RANGE_FINDER) _param_ekf2_min_rng(_params->rng_gnd_clearance), #endif // CONFIG_EKF2_TERRAIN || CONFIG_EKF2_OPTICAL_FLOW || CONFIG_EKF2_RANGE_FINDER #if defined(CONFIG_EKF2_TERRAIN) _param_ekf2_terr_noise(_params->terrain_p_noise), _param_ekf2_terr_grad(_params->terrain_gradient), #endif // CONFIG_EKF2_TERRAIN #if defined(CONFIG_EKF2_RANGE_FINDER) _param_ekf2_rng_ctrl(_params->rng_ctrl), _param_ekf2_rng_delay(_params->range_delay_ms), _param_ekf2_rng_noise(_params->range_noise), _param_ekf2_rng_sfe(_params->range_noise_scaler), _param_ekf2_rng_gate(_params->range_innov_gate), _param_ekf2_rng_pitch(_params->rng_sens_pitch), _param_ekf2_rng_a_vmax(_params->max_vel_for_range_aid), _param_ekf2_rng_a_hmax(_params->max_hagl_for_range_aid), _param_ekf2_rng_a_igate(_params->range_aid_innov_gate), _param_ekf2_rng_qlty_t(_params->range_valid_quality_s), _param_ekf2_rng_k_gate(_params->range_kin_consistency_gate), _param_ekf2_rng_pos_x(_params->rng_pos_body(0)), _param_ekf2_rng_pos_y(_params->rng_pos_body(1)), _param_ekf2_rng_pos_z(_params->rng_pos_body(2)), #endif // CONFIG_EKF2_RANGE_FINDER #if defined(CONFIG_EKF2_EXTERNAL_VISION) _param_ekf2_ev_delay(_params->ev_delay_ms), _param_ekf2_ev_ctrl(_params->ev_ctrl), _param_ekf2_ev_qmin(_params->ev_quality_minimum), _param_ekf2_evp_noise(_params->ev_pos_noise), _param_ekf2_evv_noise(_params->ev_vel_noise), _param_ekf2_eva_noise(_params->ev_att_noise), _param_ekf2_evv_gate(_params->ev_vel_innov_gate), _param_ekf2_evp_gate(_params->ev_pos_innov_gate), _param_ekf2_ev_pos_x(_params->ev_pos_body(0)), _param_ekf2_ev_pos_y(_params->ev_pos_body(1)), _param_ekf2_ev_pos_z(_params->ev_pos_body(2)), #endif // CONFIG_EKF2_EXTERNAL_VISION #if defined(CONFIG_EKF2_OPTICAL_FLOW) _param_ekf2_of_ctrl(_params->flow_ctrl), _param_ekf2_of_delay(_params->flow_delay_ms), _param_ekf2_of_n_min(_params->flow_noise), _param_ekf2_of_n_max(_params->flow_noise_qual_min), _param_ekf2_of_qmin(_params->flow_qual_min), _param_ekf2_of_qmin_gnd(_params->flow_qual_min_gnd), _param_ekf2_of_gate(_params->flow_innov_gate), _param_ekf2_of_pos_x(_params->flow_pos_body(0)), _param_ekf2_of_pos_y(_params->flow_pos_body(1)), _param_ekf2_of_pos_z(_params->flow_pos_body(2)), #endif // CONFIG_EKF2_OPTICAL_FLOW #if defined(CONFIG_EKF2_DRAG_FUSION) _param_ekf2_drag_ctrl(_params->drag_ctrl), _param_ekf2_drag_noise(_params->drag_noise), _param_ekf2_bcoef_x(_params->bcoef_x), _param_ekf2_bcoef_y(_params->bcoef_y), _param_ekf2_mcoef(_params->mcoef), #endif // CONFIG_EKF2_DRAG_FUSION #if defined(CONFIG_EKF2_GRAVITY_FUSION) _param_ekf2_grav_noise(_params->gravity_noise), #endif // CONFIG_EKF2_GRAVITY_FUSION _param_ekf2_imu_pos_x(_params->imu_pos_body(0)), _param_ekf2_imu_pos_y(_params->imu_pos_body(1)), _param_ekf2_imu_pos_z(_params->imu_pos_body(2)), _param_ekf2_gbias_init(_params->switch_on_gyro_bias), _param_ekf2_abias_init(_params->switch_on_accel_bias), _param_ekf2_angerr_init(_params->initial_tilt_err), _param_ekf2_abl_lim(_params->acc_bias_lim), _param_ekf2_abl_acclim(_params->acc_bias_learn_acc_lim), _param_ekf2_abl_gyrlim(_params->acc_bias_learn_gyr_lim), _param_ekf2_abl_tau(_params->acc_bias_learn_tc), _param_ekf2_gyr_b_lim(_params->gyro_bias_lim) { AdvertiseTopics(); } EKF2::~EKF2() { perf_free(_ekf_update_perf); perf_free(_msg_missed_imu_perf); } void EKF2::AdvertiseTopics() { // advertise expected minimal topic set immediately for logging _attitude_pub.advertise(); _local_position_pub.advertise(); _estimator_event_flags_pub.advertise(); _estimator_sensor_bias_pub.advertise(); _estimator_status_pub.advertise(); _estimator_status_flags_pub.advertise(); if (_multi_mode) { // only force advertise these in multi mode to ensure consistent uORB instance numbering _global_position_pub.advertise(); _odometry_pub.advertise(); #if defined(CONFIG_EKF2_WIND) _wind_pub.advertise(); #endif // CONFIG_EKF2_WIND } #if defined(CONFIG_EKF2_GNSS) if (_param_ekf2_gps_ctrl.get()) { _estimator_gps_status_pub.advertise(); _yaw_est_pub.advertise(); } #endif // CONFIG_EKF2_GNSS // verbose logging if (_param_ekf2_log_verbose.get()) { _estimator_innovation_test_ratios_pub.advertise(); _estimator_innovation_variances_pub.advertise(); _estimator_innovations_pub.advertise(); _estimator_states_pub.advertise(); #if defined(CONFIG_EKF2_AIRSPEED) if (_param_ekf2_arsp_thr.get() > 0.f) { _estimator_aid_src_airspeed_pub.advertise(); } #endif // CONFIG_EKF2_AIRSPEED #if defined(CONFIG_EKF2_BAROMETER) if (_param_ekf2_baro_ctrl.get()) { _estimator_aid_src_baro_hgt_pub.advertise(); _estimator_baro_bias_pub.advertise(); } #endif // CONFIG_EKF2_BAROMETER #if defined(CONFIG_EKF2_DRAG_FUSION) if (_param_ekf2_drag_ctrl.get()) { _estimator_aid_src_drag_pub.advertise(); } #endif // CONFIG_EKF2_DRAG_FUSION #if defined(CONFIG_EKF2_EXTERNAL_VISION) if (_param_ekf2_ev_ctrl.get() & static_cast(EvCtrl::VPOS)) { _estimator_aid_src_ev_hgt_pub.advertise(); _estimator_ev_pos_bias_pub.advertise(); } if (_param_ekf2_ev_ctrl.get() & static_cast(EvCtrl::HPOS)) { _estimator_aid_src_ev_pos_pub.advertise(); _estimator_ev_pos_bias_pub.advertise(); } if (_param_ekf2_ev_ctrl.get() & static_cast(EvCtrl::VEL)) { _estimator_aid_src_ev_vel_pub.advertise(); } if (_param_ekf2_ev_ctrl.get() & static_cast(EvCtrl::YAW)) { _estimator_aid_src_ev_yaw_pub.advertise(); } #endif // CONFIG_EKF2_EXTERNAL_VISION #if defined(CONFIG_EKF2_GNSS) if (_param_ekf2_gps_ctrl.get()) { if (_param_ekf2_gps_ctrl.get() & static_cast(GnssCtrl::VPOS)) { _estimator_aid_src_gnss_hgt_pub.advertise(); _estimator_gnss_hgt_bias_pub.advertise(); } if (_param_ekf2_gps_ctrl.get() & static_cast(GnssCtrl::HPOS)) { _estimator_aid_src_gnss_pos_pub.advertise(); } if (_param_ekf2_gps_ctrl.get() & static_cast(GnssCtrl::VEL)) { _estimator_aid_src_gnss_vel_pub.advertise(); } # if defined(CONFIG_EKF2_GNSS_YAW) if (_param_ekf2_gps_ctrl.get() & static_cast(GnssCtrl::YAW)) { _estimator_aid_src_gnss_yaw_pub.advertise(); } # endif // CONFIG_EKF2_GNSS_YAW } #endif // CONFIG_EKF2_GNSS #if defined(CONFIG_EKF2_GRAVITY_FUSION) if (_param_ekf2_imu_ctrl.get() & static_cast(ImuCtrl::GravityVector)) { _estimator_aid_src_gravity_pub.advertise(); } #endif // CONFIG_EKF2_GRAVITY_FUSION #if defined(CONFIG_EKF2_MAGNETOMETER) if (_param_ekf2_mag_type.get() != MagFuseType::NONE) { _estimator_aid_src_mag_pub.advertise(); } #endif // CONFIG_EKF2_MAGNETOMETER #if defined(CONFIG_EKF2_OPTICAL_FLOW) if (_param_ekf2_of_ctrl.get()) { _estimator_optical_flow_vel_pub.advertise(); _estimator_aid_src_optical_flow_pub.advertise(); } #endif // CONFIG_EKF2_OPTICAL_FLOW #if defined(CONFIG_EKF2_RANGE_FINDER) // RNG advertise if (_param_ekf2_rng_ctrl.get()) { _estimator_aid_src_rng_hgt_pub.advertise(); } #endif // CONFIG_EKF2_RANGE_FINDER #if defined(CONFIG_EKF2_SIDESLIP) if (_param_ekf2_fuse_beta.get()) { _estimator_aid_src_sideslip_pub.advertise(); } #endif // CONFIG_EKF2_SIDESLIP } // end verbose logging } #if defined(CONFIG_EKF2_MULTI_INSTANCE) bool EKF2::multi_init(int imu, int mag) { bool changed_instance = _vehicle_imu_sub.ChangeInstance(imu); #if defined(CONFIG_EKF2_MAGNETOMETER) if (!_magnetometer_sub.ChangeInstance(mag)) { changed_instance = false; } #endif // CONFIG_EKF2_MAGNETOMETER const int status_instance = _estimator_states_pub.get_instance(); if ((status_instance >= 0) && changed_instance && (_attitude_pub.get_instance() == status_instance) && (_local_position_pub.get_instance() == status_instance) && (_global_position_pub.get_instance() == status_instance)) { _instance = status_instance; ScheduleNow(); return true; } PX4_ERR("publication instance problem: %d att: %d lpos: %d gpos: %d", status_instance, _attitude_pub.get_instance(), _local_position_pub.get_instance(), _global_position_pub.get_instance()); return false; } #endif // CONFIG_EKF2_MULTI_INSTANCE int EKF2::print_status(bool verbose) { PX4_INFO_RAW("ekf2:%d EKF dt: %.4fs, attitude: %d, local position: %d, global position: %d\n", _instance, (double)_ekf.get_dt_ekf_avg(), _ekf.attitude_valid(), _ekf.local_position_is_valid(), _ekf.global_position_is_valid()); perf_print_counter(_ekf_update_perf); perf_print_counter(_msg_missed_imu_perf); if (verbose) { #if defined(CONFIG_EKF2_VERBOSE_STATUS) _ekf.print_status(); #endif // CONFIG_EKF2_VERBOSE_STATUS } return 0; } void EKF2::Run() { if (should_exit()) { _sensor_combined_sub.unregisterCallback(); _vehicle_imu_sub.unregisterCallback(); return; } // check for parameter updates if (_parameter_update_sub.updated() || !_callback_registered) { // clear update parameter_update_s pupdate; _parameter_update_sub.copy(&pupdate); // update parameters from storage updateParams(); VerifyParams(); // force advertise topics immediately for logging (EKF2_LOG_VERBOSE, per aid source control) AdvertiseTopics(); #if defined(CONFIG_EKF2_GNSS) _ekf.set_min_required_gps_health_time(_param_ekf2_req_gps_h.get() * 1_s); #endif // CONFIG_EKF2_GNSS const matrix::Vector3f imu_pos_body(_param_ekf2_imu_pos_x.get(), _param_ekf2_imu_pos_y.get(), _param_ekf2_imu_pos_z.get()); _ekf.output_predictor().set_imu_offset(imu_pos_body); _ekf.output_predictor().set_pos_correction_tc(_param_ekf2_tau_pos.get()); _ekf.output_predictor().set_vel_correction_tc(_param_ekf2_tau_vel.get()); #if defined(CONFIG_EKF2_AIRSPEED) // The airspeed scale factor correcton is only available via parameter as used by the airspeed module param_t param_aspd_scale = param_find("ASPD_SCALE_1"); if (param_aspd_scale != PARAM_INVALID) { param_get(param_aspd_scale, &_airspeed_scale_factor); } #endif // CONFIG_EKF2_AIRSPEED _ekf.updateParameters(); } if (!_callback_registered) { #if defined(CONFIG_EKF2_MULTI_INSTANCE) if (_multi_mode) { _callback_registered = _vehicle_imu_sub.registerCallback(); } else #endif // CONFIG_EKF2_MULTI_INSTANCE { _callback_registered = _sensor_combined_sub.registerCallback(); } if (!_callback_registered) { ScheduleDelayed(10_ms); return; } } if (_vehicle_command_sub.updated()) { vehicle_command_s vehicle_command; if (_vehicle_command_sub.update(&vehicle_command)) { vehicle_command_ack_s command_ack{}; command_ack.command = vehicle_command.command; command_ack.target_system = vehicle_command.source_system; command_ack.target_component = vehicle_command.source_component; if (vehicle_command.command == vehicle_command_s::VEHICLE_CMD_SET_GPS_GLOBAL_ORIGIN) { double latitude = vehicle_command.param5; double longitude = vehicle_command.param6; float altitude = vehicle_command.param7; if (_ekf.setEkfGlobalOrigin(latitude, longitude, altitude)) { // Validate the ekf origin status. uint64_t origin_time {}; _ekf.getEkfGlobalOrigin(origin_time, latitude, longitude, altitude); PX4_INFO("%d - New NED origin (LLA): %3.10f, %3.10f, %4.3f\n", _instance, latitude, longitude, static_cast(altitude)); command_ack.result = vehicle_command_ack_s::VEHICLE_CMD_RESULT_ACCEPTED; } else { PX4_ERR("%d - Failed to set new NED origin (LLA): %3.10f, %3.10f, %4.3f\n", _instance, latitude, longitude, static_cast(altitude)); command_ack.result = vehicle_command_ack_s::VEHICLE_CMD_RESULT_FAILED; } command_ack.timestamp = hrt_absolute_time(); _vehicle_command_ack_pub.publish(command_ack); } else if (vehicle_command.command == vehicle_command_s::VEHICLE_CMD_EXTERNAL_POSITION_ESTIMATE) { if ((_ekf.control_status_flags().wind_dead_reckoning || _ekf.control_status_flags().inertial_dead_reckoning || (!_ekf.control_status_flags().in_air && !_ekf.control_status_flags().gps)) && PX4_ISFINITE(vehicle_command.param2) && PX4_ISFINITE(vehicle_command.param5) && PX4_ISFINITE(vehicle_command.param6) ) { const float measurement_delay_seconds = math::constrain(vehicle_command.param2, 0.0f, kMaxDelaySecondsExternalPosMeasurement); const uint64_t timestamp_observation = vehicle_command.timestamp - measurement_delay_seconds * 1_s; float accuracy = kDefaultExternalPosAccuracy; if (PX4_ISFINITE(vehicle_command.param3) && vehicle_command.param3 > FLT_EPSILON) { accuracy = vehicle_command.param3; } // TODO add check for lat and long validity if (_ekf.resetGlobalPosToExternalObservation(vehicle_command.param5, vehicle_command.param6, accuracy, timestamp_observation) ) { command_ack.result = vehicle_command_ack_s::VEHICLE_CMD_RESULT_ACCEPTED; } else { command_ack.result = vehicle_command_ack_s::VEHICLE_CMD_RESULT_FAILED; } } else { command_ack.result = vehicle_command_ack_s::VEHICLE_CMD_RESULT_TEMPORARILY_REJECTED; // TODO: expand } command_ack.timestamp = hrt_absolute_time(); _vehicle_command_ack_pub.publish(command_ack); } } } bool imu_updated = false; imuSample imu_sample_new {}; hrt_abstime imu_dt = 0; // for tracking time slip later #if defined(CONFIG_EKF2_MULTI_INSTANCE) if (_multi_mode) { const unsigned last_generation = _vehicle_imu_sub.get_last_generation(); vehicle_imu_s imu; imu_updated = _vehicle_imu_sub.update(&imu); if (imu_updated && (_vehicle_imu_sub.get_last_generation() != last_generation + 1)) { perf_count(_msg_missed_imu_perf); } if (imu_updated) { imu_sample_new.time_us = imu.timestamp_sample; imu_sample_new.delta_ang_dt = imu.delta_angle_dt * 1.e-6f; imu_sample_new.delta_ang = Vector3f{imu.delta_angle}; imu_sample_new.delta_vel_dt = imu.delta_velocity_dt * 1.e-6f; imu_sample_new.delta_vel = Vector3f{imu.delta_velocity}; if (imu.delta_velocity_clipping > 0) { imu_sample_new.delta_vel_clipping[0] = imu.delta_velocity_clipping & vehicle_imu_s::CLIPPING_X; imu_sample_new.delta_vel_clipping[1] = imu.delta_velocity_clipping & vehicle_imu_s::CLIPPING_Y; imu_sample_new.delta_vel_clipping[2] = imu.delta_velocity_clipping & vehicle_imu_s::CLIPPING_Z; } imu_dt = imu.delta_angle_dt; if ((_device_id_accel == 0) || (_device_id_gyro == 0)) { _device_id_accel = imu.accel_device_id; _device_id_gyro = imu.gyro_device_id; _accel_calibration_count = imu.accel_calibration_count; _gyro_calibration_count = imu.gyro_calibration_count; } else { if ((imu.accel_calibration_count != _accel_calibration_count) || (imu.accel_device_id != _device_id_accel)) { PX4_DEBUG("%d - resetting accelerometer bias", _instance); _device_id_accel = imu.accel_device_id; _ekf.resetAccelBias(); _accel_calibration_count = imu.accel_calibration_count; // reset bias learning _accel_cal = {}; } if ((imu.gyro_calibration_count != _gyro_calibration_count) || (imu.gyro_device_id != _device_id_gyro)) { PX4_DEBUG("%d - resetting rate gyro bias", _instance); _device_id_gyro = imu.gyro_device_id; _ekf.resetGyroBias(); _gyro_calibration_count = imu.gyro_calibration_count; // reset bias learning _gyro_cal = {}; } } } } else #endif // CONFIG_EKF2_MULTI_INSTANCE { const unsigned last_generation = _sensor_combined_sub.get_last_generation(); sensor_combined_s sensor_combined; imu_updated = _sensor_combined_sub.update(&sensor_combined); if (imu_updated && (_sensor_combined_sub.get_last_generation() != last_generation + 1)) { perf_count(_msg_missed_imu_perf); } if (imu_updated) { imu_sample_new.time_us = sensor_combined.timestamp; imu_sample_new.delta_ang_dt = sensor_combined.gyro_integral_dt * 1.e-6f; imu_sample_new.delta_ang = Vector3f{sensor_combined.gyro_rad} * imu_sample_new.delta_ang_dt; imu_sample_new.delta_vel_dt = sensor_combined.accelerometer_integral_dt * 1.e-6f; imu_sample_new.delta_vel = Vector3f{sensor_combined.accelerometer_m_s2} * imu_sample_new.delta_vel_dt; if (sensor_combined.accelerometer_clipping > 0) { imu_sample_new.delta_vel_clipping[0] = sensor_combined.accelerometer_clipping & sensor_combined_s::CLIPPING_X; imu_sample_new.delta_vel_clipping[1] = sensor_combined.accelerometer_clipping & sensor_combined_s::CLIPPING_Y; imu_sample_new.delta_vel_clipping[2] = sensor_combined.accelerometer_clipping & sensor_combined_s::CLIPPING_Z; } imu_dt = sensor_combined.gyro_integral_dt; if (sensor_combined.accel_calibration_count != _accel_calibration_count) { PX4_DEBUG("%d - resetting accelerometer bias", _instance); _ekf.resetAccelBias(); _accel_calibration_count = sensor_combined.accel_calibration_count; // reset bias learning _accel_cal = {}; } if (sensor_combined.gyro_calibration_count != _gyro_calibration_count) { PX4_DEBUG("%d - resetting rate gyro bias", _instance); _ekf.resetGyroBias(); _gyro_calibration_count = sensor_combined.gyro_calibration_count; // reset bias learning _gyro_cal = {}; } } if (_sensor_selection_sub.updated() || (_device_id_accel == 0 || _device_id_gyro == 0)) { sensor_selection_s sensor_selection; if (_sensor_selection_sub.copy(&sensor_selection)) { if (_device_id_accel != sensor_selection.accel_device_id) { _device_id_accel = sensor_selection.accel_device_id; _ekf.resetAccelBias(); // reset bias learning _accel_cal = {}; } if (_device_id_gyro != sensor_selection.gyro_device_id) { _device_id_gyro = sensor_selection.gyro_device_id; _ekf.resetGyroBias(); // reset bias learning _gyro_cal = {}; } } } } if (imu_updated) { const hrt_abstime now = imu_sample_new.time_us; // push imu data into estimator _ekf.setIMUData(imu_sample_new); PublishAttitude(now); // publish attitude immediately (uses quaternion from output predictor) // integrate time to monitor time slippage if (_start_time_us > 0) { _integrated_time_us += imu_dt; _last_time_slip_us = (imu_sample_new.time_us - _start_time_us) - _integrated_time_us; } else { _start_time_us = imu_sample_new.time_us; _last_time_slip_us = 0; } // ekf2_timestamps (using 0.1 ms relative timestamps) ekf2_timestamps_s ekf2_timestamps { .timestamp = now, .airspeed_timestamp_rel = ekf2_timestamps_s::RELATIVE_TIMESTAMP_INVALID, .distance_sensor_timestamp_rel = ekf2_timestamps_s::RELATIVE_TIMESTAMP_INVALID, .optical_flow_timestamp_rel = ekf2_timestamps_s::RELATIVE_TIMESTAMP_INVALID, .vehicle_air_data_timestamp_rel = ekf2_timestamps_s::RELATIVE_TIMESTAMP_INVALID, .vehicle_magnetometer_timestamp_rel = ekf2_timestamps_s::RELATIVE_TIMESTAMP_INVALID, .visual_odometry_timestamp_rel = ekf2_timestamps_s::RELATIVE_TIMESTAMP_INVALID, }; #if defined(CONFIG_EKF2_AIRSPEED) UpdateAirspeedSample(ekf2_timestamps); #endif // CONFIG_EKF2_AIRSPEED #if defined(CONFIG_EKF2_AUXVEL) UpdateAuxVelSample(ekf2_timestamps); #endif // CONFIG_EKF2_AUXVEL #if defined(CONFIG_EKF2_BAROMETER) UpdateBaroSample(ekf2_timestamps); #endif // CONFIG_EKF2_BAROMETER #if defined(CONFIG_EKF2_EXTERNAL_VISION) UpdateExtVisionSample(ekf2_timestamps); #endif // CONFIG_EKF2_EXTERNAL_VISION #if defined(CONFIG_EKF2_OPTICAL_FLOW) UpdateFlowSample(ekf2_timestamps); #endif // CONFIG_EKF2_OPTICAL_FLOW #if defined(CONFIG_EKF2_GNSS) UpdateGpsSample(ekf2_timestamps); #endif // CONFIG_EKF2_GNSS #if defined(CONFIG_EKF2_MAGNETOMETER) UpdateMagSample(ekf2_timestamps); #endif // CONFIG_EKF2_MAGNETOMETER #if defined(CONFIG_EKF2_RANGE_FINDER) UpdateRangeSample(ekf2_timestamps); #endif // CONFIG_EKF2_RANGE_FINDER UpdateSystemFlagsSample(ekf2_timestamps); // run the EKF update and output const hrt_abstime ekf_update_start = hrt_absolute_time(); if (_ekf.update()) { perf_set_elapsed(_ekf_update_perf, hrt_elapsed_time(&ekf_update_start)); PublishLocalPosition(now); PublishOdometry(now, imu_sample_new); PublishGlobalPosition(now); PublishSensorBias(now); #if defined(CONFIG_EKF2_WIND) PublishWindEstimate(now); #endif // CONFIG_EKF2_WIND // publish status/logging messages PublishEventFlags(now); PublishStatus(now); PublishStatusFlags(now); if (_param_ekf2_log_verbose.get()) { PublishAidSourceStatus(now); PublishInnovations(now); PublishInnovationTestRatios(now); PublishInnovationVariances(now); PublishStates(now); #if defined(CONFIG_EKF2_BAROMETER) PublishBaroBias(now); #endif // CONFIG_EKF2_BAROMETER #if defined(CONFIG_EKF2_EXTERNAL_VISION) PublishEvPosBias(now); #endif // CONFIG_EKF2_EXTERNAL_VISION #if defined(CONFIG_EKF2_GNSS) PublishGnssHgtBias(now); #endif // CONFIG_EKF2_GNSS } #if defined(CONFIG_EKF2_GNSS) PublishGpsStatus(now); PublishYawEstimatorStatus(now); #endif // CONFIG_EKF2_GNSS #if defined(CONFIG_EKF2_OPTICAL_FLOW) PublishOpticalFlowVel(now); #endif // CONFIG_EKF2_OPTICAL_FLOW UpdateAccelCalibration(now); UpdateGyroCalibration(now); #if defined(CONFIG_EKF2_MAGNETOMETER) UpdateMagCalibration(now); #endif // CONFIG_EKF2_MAGNETOMETER } // publish ekf2_timestamps _ekf2_timestamps_pub.publish(ekf2_timestamps); } // re-schedule as backup timeout ScheduleDelayed(100_ms); } void EKF2::VerifyParams() { #if defined(CONFIG_EKF2_MAGNETOMETER) // EKF2_MAG_TYPE obsolete options if ((_param_ekf2_mag_type.get() != MagFuseType::AUTO) && (_param_ekf2_mag_type.get() != MagFuseType::HEADING) && (_param_ekf2_mag_type.get() != MagFuseType::NONE) && (_param_ekf2_mag_type.get() != MagFuseType::INIT) ) { mavlink_log_critical(&_mavlink_log_pub, "EKF2_MAG_TYPE invalid, resetting to default"); /* EVENT * @description EKF2_MAG_TYPE is set to {1:.0}. */ events::send(events::ID("ekf2_mag_type_invalid"), events::Log::Warning, "EKF2_MAG_TYPE invalid, resetting to default", _param_ekf2_mag_type.get()); _param_ekf2_mag_type.set(0); _param_ekf2_mag_type.commit(); } #endif // CONFIG_EKF2_MAGNETOMETER float delay_max = _param_ekf2_delay_max.get(); #if defined(CONFIG_EKF2_AUXVEL) if (_param_ekf2_avel_delay.get() > delay_max) { delay_max = _param_ekf2_avel_delay.get(); } #endif // CONFIG_EKF2_AUXVEL #if defined(CONFIG_EKF2_BAROMETER) if (_param_ekf2_baro_delay.get() > delay_max) { delay_max = _param_ekf2_baro_delay.get(); } #endif // CONFIG_EKF2_BAROMETER #if defined(CONFIG_EKF2_AIRSPEED) if (_param_ekf2_asp_delay.get() > delay_max) { delay_max = _param_ekf2_asp_delay.get(); } #endif // CONFIG_EKF2_AIRSPEED #if defined(CONFIG_EKF2_MAGNETOMETER) if (_param_ekf2_mag_delay.get() > delay_max) { delay_max = _param_ekf2_mag_delay.get(); } #endif // CONFIG_EKF2_MAGNETOMETER #if defined(CONFIG_EKF2_RANGE_FINDER) if (_param_ekf2_rng_delay.get() > delay_max) { delay_max = _param_ekf2_rng_delay.get(); } #endif // CONFIG_EKF2_RANGE_FINDER #if defined(CONFIG_EKF2_GNSS) if (_param_ekf2_gps_delay.get() > delay_max) { delay_max = _param_ekf2_gps_delay.get(); } #endif // CONFIG_EKF2_GNSS #if defined(CONFIG_EKF2_OPTICAL_FLOW) if (_param_ekf2_of_delay.get() > delay_max) { delay_max = _param_ekf2_of_delay.get(); } #endif // CONFIG_EKF2_OPTICAL_FLOW #if defined(CONFIG_EKF2_EXTERNAL_VISION) if (_param_ekf2_ev_delay.get() > delay_max) { delay_max = _param_ekf2_ev_delay.get(); } #endif // CONFIG_EKF2_EXTERNAL_VISION if (delay_max > _param_ekf2_delay_max.get()) { /* EVENT * @description EKF2_DELAY_MAX({1}ms) is too small compared to the maximum sensor delay ({2}) */ events::send(events::ID("nf_delay_max_too_small"), events::Log::Warning, "EKF2_DELAY_MAX increased to {2}ms, please reboot", _param_ekf2_delay_max.get(), delay_max); _param_ekf2_delay_max.commit_no_notification(delay_max); } } void EKF2::PublishAidSourceStatus(const hrt_abstime ×tamp) { #if defined(CONFIG_EKF2_AIRSPEED) // airspeed PublishAidSourceStatus(_ekf.aid_src_airspeed(), _status_airspeed_pub_last, _estimator_aid_src_airspeed_pub); #endif // CONFIG_EKF2_AIRSPEED #if defined(CONFIG_EKF2_SIDESLIP) // sideslip PublishAidSourceStatus(_ekf.aid_src_sideslip(), _status_sideslip_pub_last, _estimator_aid_src_sideslip_pub); #endif // CONFIG_EKF2_SIDESLIP #if defined(CONFIG_EKF2_BAROMETER) // baro height PublishAidSourceStatus(_ekf.aid_src_baro_hgt(), _status_baro_hgt_pub_last, _estimator_aid_src_baro_hgt_pub); #endif // CONFIG_EKF2_BAROMETER #if defined(CONFIG_EKF2_DRAG_FUSION) // drag PublishAidSourceStatus(_ekf.aid_src_drag(), _status_drag_pub_last, _estimator_aid_src_drag_pub); #endif // CONFIG_EKF2_DRAG_FUSION #if defined(CONFIG_EKF2_RANGE_FINDER) // RNG height PublishAidSourceStatus(_ekf.aid_src_rng_hgt(), _status_rng_hgt_pub_last, _estimator_aid_src_rng_hgt_pub); #endif // CONFIG_EKF2_RANGE_FINDER // fake position PublishAidSourceStatus(_ekf.aid_src_fake_pos(), _status_fake_pos_pub_last, _estimator_aid_src_fake_pos_pub); PublishAidSourceStatus(_ekf.aid_src_fake_hgt(), _status_fake_hgt_pub_last, _estimator_aid_src_fake_hgt_pub); #if defined(CONFIG_EKF2_EXTERNAL_VISION) // external vision (EV) hgt/pos/vel/yaw PublishAidSourceStatus(_ekf.aid_src_ev_hgt(), _status_ev_hgt_pub_last, _estimator_aid_src_ev_hgt_pub); PublishAidSourceStatus(_ekf.aid_src_ev_pos(), _status_ev_pos_pub_last, _estimator_aid_src_ev_pos_pub); PublishAidSourceStatus(_ekf.aid_src_ev_vel(), _status_ev_vel_pub_last, _estimator_aid_src_ev_vel_pub); PublishAidSourceStatus(_ekf.aid_src_ev_yaw(), _status_ev_yaw_pub_last, _estimator_aid_src_ev_yaw_pub); #endif // CONFIG_EKF2_EXTERNAL_VISION #if defined(CONFIG_EKF2_GNSS) // GNSS hgt/pos/vel/yaw PublishAidSourceStatus(_ekf.aid_src_gnss_hgt(), _status_gnss_hgt_pub_last, _estimator_aid_src_gnss_hgt_pub); PublishAidSourceStatus(_ekf.aid_src_gnss_pos(), _status_gnss_pos_pub_last, _estimator_aid_src_gnss_pos_pub); PublishAidSourceStatus(_ekf.aid_src_gnss_vel(), _status_gnss_vel_pub_last, _estimator_aid_src_gnss_vel_pub); # if defined(CONFIG_EKF2_GNSS_YAW) PublishAidSourceStatus(_ekf.aid_src_gnss_yaw(), _status_gnss_yaw_pub_last, _estimator_aid_src_gnss_yaw_pub); # endif // CONFIG_EKF2_GNSS_YAW #endif // CONFIG_EKF2_GNSS #if defined(CONFIG_EKF2_MAGNETOMETER) // mag 3d PublishAidSourceStatus(_ekf.aid_src_mag(), _status_mag_pub_last, _estimator_aid_src_mag_pub); #endif // CONFIG_EKF2_MAGNETOMETER #if defined(CONFIG_EKF2_GRAVITY_FUSION) // gravity PublishAidSourceStatus(_ekf.aid_src_gravity(), _status_gravity_pub_last, _estimator_aid_src_gravity_pub); #endif // CONFIG_EKF2_GRAVITY_FUSION #if defined(CONFIG_EKF2_AUXVEL) // aux velocity PublishAidSourceStatus(_ekf.aid_src_aux_vel(), _status_aux_vel_pub_last, _estimator_aid_src_aux_vel_pub); #endif // CONFIG_EKF2_AUXVEL #if defined(CONFIG_EKF2_OPTICAL_FLOW) // optical flow PublishAidSourceStatus(_ekf.aid_src_optical_flow(), _status_optical_flow_pub_last, _estimator_aid_src_optical_flow_pub); #endif // CONFIG_EKF2_OPTICAL_FLOW } void EKF2::PublishAttitude(const hrt_abstime ×tamp) { if (_ekf.attitude_valid()) { // generate vehicle attitude quaternion data vehicle_attitude_s att; att.timestamp_sample = timestamp; _ekf.getQuaternion().copyTo(att.q); _ekf.get_quat_reset(&att.delta_q_reset[0], &att.quat_reset_counter); att.timestamp = _replay_mode ? timestamp : hrt_absolute_time(); _attitude_pub.publish(att); } else if (_replay_mode) { // in replay mode we have to tell the replay module not to wait for an update // we do this by publishing an attitude with zero timestamp vehicle_attitude_s att{}; _attitude_pub.publish(att); } } #if defined(CONFIG_EKF2_BAROMETER) void EKF2::PublishBaroBias(const hrt_abstime ×tamp) { if (_ekf.aid_src_baro_hgt().timestamp_sample != 0) { const BiasEstimator::status &status = _ekf.getBaroBiasEstimatorStatus(); if (fabsf(status.bias - _last_baro_bias_published) > 0.001f) { _estimator_baro_bias_pub.publish(fillEstimatorBiasMsg(status, _ekf.aid_src_baro_hgt().timestamp_sample, timestamp, _device_id_baro)); _last_baro_bias_published = status.bias; } } } #endif // CONFIG_EKF2_BAROMETER #if defined(CONFIG_EKF2_GNSS) void EKF2::PublishGnssHgtBias(const hrt_abstime ×tamp) { if (_ekf.get_gps_sample_delayed().time_us != 0) { const BiasEstimator::status &status = _ekf.getGpsHgtBiasEstimatorStatus(); if (fabsf(status.bias - _last_gnss_hgt_bias_published) > 0.001f) { _estimator_gnss_hgt_bias_pub.publish(fillEstimatorBiasMsg(status, _ekf.get_gps_sample_delayed().time_us, timestamp)); _last_gnss_hgt_bias_published = status.bias; } } } #endif // CONFIG_EKF2_GNSS #if defined(CONFIG_EKF2_EXTERNAL_VISION) void EKF2::PublishEvPosBias(const hrt_abstime ×tamp) { if (_ekf.aid_src_ev_hgt().timestamp_sample) { estimator_bias3d_s bias{}; // height BiasEstimator::status bias_est_status[3]; bias_est_status[0] = _ekf.getEvPosBiasEstimatorStatus(0); bias_est_status[1] = _ekf.getEvPosBiasEstimatorStatus(1); bias_est_status[2] = _ekf.getEvHgtBiasEstimatorStatus(); for (int i = 0; i < 3; i++) { bias.bias[i] = bias_est_status[i].bias; bias.bias_var[i] = bias_est_status[i].bias_var; bias.innov[i] = bias_est_status[i].innov; bias.innov_var[i] = bias_est_status[i].innov_var; bias.innov_test_ratio[i] = bias_est_status[i].innov_test_ratio; } const Vector3f bias_vec{bias.bias}; if ((bias_vec - _last_ev_bias_published).longerThan(0.01f)) { bias.timestamp_sample = _ekf.aid_src_ev_hgt().timestamp_sample; bias.timestamp = _replay_mode ? timestamp : hrt_absolute_time(); _estimator_ev_pos_bias_pub.publish(bias); _last_ev_bias_published = Vector3f(bias.bias); } } } #endif // CONFIG_EKF2_EXTERNAL_VISION estimator_bias_s EKF2::fillEstimatorBiasMsg(const BiasEstimator::status &status, uint64_t timestamp_sample_us, uint64_t timestamp, uint32_t device_id) { estimator_bias_s bias{}; bias.timestamp_sample = timestamp_sample_us; bias.device_id = device_id; bias.bias = status.bias; bias.bias_var = status.bias_var; bias.innov = status.innov; bias.innov_var = status.innov_var; bias.innov_test_ratio = status.innov_test_ratio; bias.timestamp = _replay_mode ? timestamp : hrt_absolute_time(); return bias; } void EKF2::PublishEventFlags(const hrt_abstime ×tamp) { // information events uint32_t information_events = _ekf.information_event_status().value; bool information_event_updated = false; if (information_events != 0) { information_event_updated = true; _filter_information_event_changes++; } if (information_event_updated) { estimator_event_flags_s event_flags{}; event_flags.timestamp_sample = _ekf.time_delayed_us(); event_flags.information_event_changes = _filter_information_event_changes; event_flags.gps_checks_passed = _ekf.information_event_flags().gps_checks_passed; event_flags.reset_vel_to_gps = _ekf.information_event_flags().reset_vel_to_gps; event_flags.reset_vel_to_flow = _ekf.information_event_flags().reset_vel_to_flow; event_flags.reset_vel_to_vision = _ekf.information_event_flags().reset_vel_to_vision; event_flags.reset_vel_to_zero = _ekf.information_event_flags().reset_vel_to_zero; event_flags.reset_pos_to_last_known = _ekf.information_event_flags().reset_pos_to_last_known; event_flags.reset_pos_to_gps = _ekf.information_event_flags().reset_pos_to_gps; event_flags.reset_pos_to_vision = _ekf.information_event_flags().reset_pos_to_vision; event_flags.starting_gps_fusion = _ekf.information_event_flags().starting_gps_fusion; event_flags.starting_vision_pos_fusion = _ekf.information_event_flags().starting_vision_pos_fusion; event_flags.starting_vision_vel_fusion = _ekf.information_event_flags().starting_vision_vel_fusion; event_flags.starting_vision_yaw_fusion = _ekf.information_event_flags().starting_vision_yaw_fusion; event_flags.yaw_aligned_to_imu_gps = _ekf.information_event_flags().yaw_aligned_to_imu_gps; event_flags.reset_hgt_to_baro = _ekf.information_event_flags().reset_hgt_to_baro; event_flags.reset_hgt_to_gps = _ekf.information_event_flags().reset_hgt_to_gps; event_flags.reset_hgt_to_rng = _ekf.information_event_flags().reset_hgt_to_rng; event_flags.reset_hgt_to_ev = _ekf.information_event_flags().reset_hgt_to_ev; event_flags.timestamp = _replay_mode ? timestamp : hrt_absolute_time(); _estimator_event_flags_pub.update(event_flags); _last_event_flags_publish = event_flags.timestamp; _ekf.clear_information_events(); } else if ((_last_event_flags_publish != 0) && (timestamp >= _last_event_flags_publish + 1_s)) { // continue publishing periodically _estimator_event_flags_pub.get().timestamp = _replay_mode ? timestamp : hrt_absolute_time(); _estimator_event_flags_pub.update(); _last_event_flags_publish = _estimator_event_flags_pub.get().timestamp; } } void EKF2::PublishGlobalPosition(const hrt_abstime ×tamp) { if (_ekf.global_position_is_valid()) { const Vector3f position{_ekf.getPosition()}; // generate and publish global position data vehicle_global_position_s global_pos{}; global_pos.timestamp_sample = timestamp; // Position of local NED origin in GPS / WGS84 frame _ekf.global_origin().reproject(position(0), position(1), global_pos.lat, global_pos.lon); global_pos.alt = -position(2) + _ekf.getEkfGlobalOriginAltitude(); // Altitude AMSL in meters #if defined(CONFIG_EKF2_GNSS) global_pos.alt_ellipsoid = filter_altitude_ellipsoid(global_pos.alt); #else global_pos.alt_ellipsoid = global_pos.alt; #endif // delta_alt, alt_reset_counter // global altitude has opposite sign of local down position float delta_z = 0.f; uint8_t z_reset_counter = 0; _ekf.get_posD_reset(&delta_z, &z_reset_counter); global_pos.delta_alt = -delta_z; global_pos.alt_reset_counter = z_reset_counter; // lat_lon_reset_counter float delta_xy[2] {}; uint8_t xy_reset_counter = 0; _ekf.get_posNE_reset(delta_xy, &xy_reset_counter); global_pos.lat_lon_reset_counter = xy_reset_counter; _ekf.get_ekf_gpos_accuracy(&global_pos.eph, &global_pos.epv); global_pos.terrain_alt = NAN; global_pos.terrain_alt_valid = false; #if defined(CONFIG_EKF2_TERRAIN) if (_ekf.isTerrainEstimateValid()) { // Terrain altitude in m, WGS84 global_pos.terrain_alt = _ekf.getEkfGlobalOriginAltitude() - _ekf.getTerrainVertPos(); global_pos.terrain_alt_valid = true; } float delta_hagl = 0.f; _ekf.get_hagl_reset(&delta_hagl, &global_pos.terrain_reset_counter); global_pos.delta_terrain = -delta_z; #endif // CONFIG_EKF2_TERRAIN global_pos.dead_reckoning = _ekf.control_status_flags().inertial_dead_reckoning || _ekf.control_status_flags().wind_dead_reckoning; global_pos.timestamp = _replay_mode ? timestamp : hrt_absolute_time(); _global_position_pub.publish(global_pos); } } #if defined(CONFIG_EKF2_GNSS) void EKF2::PublishGpsStatus(const hrt_abstime ×tamp) { const hrt_abstime timestamp_sample = _ekf.get_gps_sample_delayed().time_us; if (timestamp_sample == _last_gps_status_published) { return; } estimator_gps_status_s estimator_gps_status{}; estimator_gps_status.timestamp_sample = timestamp_sample; estimator_gps_status.position_drift_rate_horizontal_m_s = _ekf.gps_horizontal_position_drift_rate_m_s(); estimator_gps_status.position_drift_rate_vertical_m_s = _ekf.gps_vertical_position_drift_rate_m_s(); estimator_gps_status.filtered_horizontal_speed_m_s = _ekf.gps_filtered_horizontal_velocity_m_s(); estimator_gps_status.checks_passed = _ekf.gps_checks_passed(); estimator_gps_status.check_fail_gps_fix = _ekf.gps_check_fail_status_flags().fix; estimator_gps_status.check_fail_min_sat_count = _ekf.gps_check_fail_status_flags().nsats; estimator_gps_status.check_fail_max_pdop = _ekf.gps_check_fail_status_flags().pdop; estimator_gps_status.check_fail_max_horz_err = _ekf.gps_check_fail_status_flags().hacc; estimator_gps_status.check_fail_max_vert_err = _ekf.gps_check_fail_status_flags().vacc; estimator_gps_status.check_fail_max_spd_err = _ekf.gps_check_fail_status_flags().sacc; estimator_gps_status.check_fail_max_horz_drift = _ekf.gps_check_fail_status_flags().hdrift; estimator_gps_status.check_fail_max_vert_drift = _ekf.gps_check_fail_status_flags().vdrift; estimator_gps_status.check_fail_max_horz_spd_err = _ekf.gps_check_fail_status_flags().hspeed; estimator_gps_status.check_fail_max_vert_spd_err = _ekf.gps_check_fail_status_flags().vspeed; estimator_gps_status.check_fail_spoofed_gps = _ekf.gps_check_fail_status_flags().spoofed; estimator_gps_status.timestamp = _replay_mode ? timestamp : hrt_absolute_time(); _estimator_gps_status_pub.publish(estimator_gps_status); _last_gps_status_published = timestamp_sample; } #endif // CONFIG_EKF2_GNSS void EKF2::PublishInnovations(const hrt_abstime ×tamp) { // publish estimator innovation data estimator_innovations_s innovations{}; innovations.timestamp_sample = _ekf.time_delayed_us(); #if defined(CONFIG_EKF2_GNSS) // GPS innovations.gps_hvel[0] = _ekf.aid_src_gnss_vel().innovation[0]; innovations.gps_hvel[1] = _ekf.aid_src_gnss_vel().innovation[1]; innovations.gps_vvel = _ekf.aid_src_gnss_vel().innovation[2]; innovations.gps_hpos[0] = _ekf.aid_src_gnss_pos().innovation[0]; innovations.gps_hpos[1] = _ekf.aid_src_gnss_pos().innovation[1]; innovations.gps_vpos = _ekf.aid_src_gnss_hgt().innovation; #endif // CONFIG_EKF2_GNSS #if defined(CONFIG_EKF2_EXTERNAL_VISION) // External Vision innovations.ev_hvel[0] = _ekf.aid_src_ev_vel().innovation[0]; innovations.ev_hvel[1] = _ekf.aid_src_ev_vel().innovation[1]; innovations.ev_vvel = _ekf.aid_src_ev_vel().innovation[2]; innovations.ev_hpos[0] = _ekf.aid_src_ev_pos().innovation[0]; innovations.ev_hpos[1] = _ekf.aid_src_ev_pos().innovation[1]; innovations.ev_vpos = _ekf.aid_src_ev_hgt().innovation; #endif // CONFIG_EKF2_EXTERNAL_VISION // Height sensors #if defined(CONFIG_EKF2_RANGE_FINDER) innovations.rng_vpos = _ekf.aid_src_rng_hgt().innovation; #endif // CONFIG_EKF2_RANGE_FINDER #if defined(CONFIG_EKF2_BAROMETER) innovations.baro_vpos = _ekf.aid_src_baro_hgt().innovation; #endif // CONFIG_EKF2_BAROMETER #if defined(CONFIG_EKF2_AUXVEL) // Auxiliary velocity innovations.aux_hvel[0] = _ekf.aid_src_aux_vel().innovation[0]; innovations.aux_hvel[1] = _ekf.aid_src_aux_vel().innovation[1]; #endif // CONFIG_EKF2_AUXVEL #if defined(CONFIG_EKF2_OPTICAL_FLOW) // Optical flow innovations.flow[0] = _ekf.aid_src_optical_flow().innovation[0]; innovations.flow[1] = _ekf.aid_src_optical_flow().innovation[1]; #endif // CONFIG_EKF2_OPTICAL_FLOW // heading innovations.heading = _ekf.getHeadingInnov(); #if defined(CONFIG_EKF2_MAGNETOMETER) // mag_field innovations.mag_field[0] = _ekf.aid_src_mag().innovation[0]; innovations.mag_field[1] = _ekf.aid_src_mag().innovation[1]; innovations.mag_field[2] = _ekf.aid_src_mag().innovation[2]; #endif // CONFIG_EKF2_MAGNETOMETER #if defined(CONFIG_EKF2_GRAVITY_FUSION) // gravity innovations.gravity[0] = _ekf.aid_src_gravity().innovation[0]; innovations.gravity[1] = _ekf.aid_src_gravity().innovation[1]; innovations.gravity[2] = _ekf.aid_src_gravity().innovation[2]; #endif // CONFIG_EKF2_GRAVITY_FUSION #if defined(CONFIG_EKF2_DRAG_FUSION) // drag innovations.drag[0] = _ekf.aid_src_drag().innovation[0]; innovations.drag[1] = _ekf.aid_src_drag().innovation[1]; #endif // CONFIG_EKF2_DRAG_FUSION #if defined(CONFIG_EKF2_AIRSPEED) // airspeed innovations.airspeed = _ekf.aid_src_airspeed().innovation; #endif // CONFIG_EKF2_AIRSPEED #if defined(CONFIG_EKF2_SIDESLIP) // beta innovations.beta = _ekf.aid_src_sideslip().innovation; #endif // CONFIG_EKF2_SIDESLIP #if defined(CONFIG_EKF2_TERRAIN) && defined(CONFIG_EKF2_RANGE_FINDER) // hagl innovations.hagl = _ekf.aid_src_rng_hgt().innovation; #endif // CONFIG_EKF2_TERRAIN && CONFIG_EKF2_RANGE_FINDER #if defined(CONFIG_EKF2_RANGE_FINDER) // hagl_rate innovations.hagl_rate = _ekf.getHaglRateInnov(); #endif // CONFIG_EKF2_RANGE_FINDER innovations.timestamp = _replay_mode ? timestamp : hrt_absolute_time(); _estimator_innovations_pub.publish(innovations); } void EKF2::PublishInnovationTestRatios(const hrt_abstime ×tamp) { // publish estimator innovation test ratio data estimator_innovations_s test_ratios{}; test_ratios.timestamp_sample = _ekf.time_delayed_us(); #if defined(CONFIG_EKF2_GNSS) // GPS test_ratios.gps_hvel[0] = _ekf.aid_src_gnss_vel().test_ratio[0]; test_ratios.gps_hvel[1] = _ekf.aid_src_gnss_vel().test_ratio[1]; test_ratios.gps_vvel = _ekf.aid_src_gnss_vel().test_ratio[2]; test_ratios.gps_hpos[0] = _ekf.aid_src_gnss_pos().test_ratio[0]; test_ratios.gps_hpos[1] = _ekf.aid_src_gnss_pos().test_ratio[1]; test_ratios.gps_vpos = _ekf.aid_src_gnss_hgt().test_ratio; #endif // CONFIG_EKF2_GNSS #if defined(CONFIG_EKF2_EXTERNAL_VISION) // External Vision test_ratios.ev_hvel[0] = _ekf.aid_src_ev_vel().test_ratio[0]; test_ratios.ev_hvel[1] = _ekf.aid_src_ev_vel().test_ratio[1]; test_ratios.ev_vvel = _ekf.aid_src_ev_vel().test_ratio[2]; test_ratios.ev_hpos[0] = _ekf.aid_src_ev_pos().test_ratio[0]; test_ratios.ev_hpos[1] = _ekf.aid_src_ev_pos().test_ratio[1]; test_ratios.ev_vpos = _ekf.aid_src_ev_hgt().test_ratio; #endif // CONFIG_EKF2_EXTERNAL_VISION // Height sensors #if defined(CONFIG_EKF2_RANGE_FINDER) test_ratios.rng_vpos = _ekf.aid_src_rng_hgt().test_ratio; #endif // CONFIG_EKF2_RANGE_FINDER #if defined(CONFIG_EKF2_BAROMETER) test_ratios.baro_vpos = _ekf.aid_src_baro_hgt().test_ratio; #endif // CONFIG_EKF2_BAROMETER #if defined(CONFIG_EKF2_AUXVEL) // Auxiliary velocity test_ratios.aux_hvel[0] = _ekf.aid_src_aux_vel().test_ratio[0]; test_ratios.aux_hvel[1] = _ekf.aid_src_aux_vel().test_ratio[1]; #endif // CONFIG_EKF2_AUXVEL #if defined(CONFIG_EKF2_OPTICAL_FLOW) // Optical flow test_ratios.flow[0] = _ekf.aid_src_optical_flow().test_ratio[0]; test_ratios.flow[1] = _ekf.aid_src_optical_flow().test_ratio[1]; #endif // CONFIG_EKF2_OPTICAL_FLOW // heading test_ratios.heading = _ekf.getHeadingInnovRatio(); #if defined(CONFIG_EKF2_MAGNETOMETER) // mag_field test_ratios.mag_field[0] = _ekf.aid_src_mag().test_ratio[0]; test_ratios.mag_field[1] = _ekf.aid_src_mag().test_ratio[1]; test_ratios.mag_field[2] = _ekf.aid_src_mag().test_ratio[2]; #endif // CONFIG_EKF2_MAGNETOMETER #if defined(CONFIG_EKF2_GRAVITY_FUSION) // gravity test_ratios.gravity[0] = _ekf.aid_src_gravity().test_ratio[0]; test_ratios.gravity[1] = _ekf.aid_src_gravity().test_ratio[1]; test_ratios.gravity[2] = _ekf.aid_src_gravity().test_ratio[2]; #endif // CONFIG_EKF2_GRAVITY_FUSION #if defined(CONFIG_EKF2_DRAG_FUSION) // drag test_ratios.drag[0] = _ekf.aid_src_drag().test_ratio[0]; test_ratios.drag[1] = _ekf.aid_src_drag().test_ratio[1]; #endif // CONFIG_EKF2_DRAG_FUSION #if defined(CONFIG_EKF2_AIRSPEED) // airspeed test_ratios.airspeed = _ekf.aid_src_airspeed().test_ratio; #endif // CONFIG_EKF2_AIRSPEED #if defined(CONFIG_EKF2_SIDESLIP) // beta test_ratios.beta = _ekf.aid_src_sideslip().test_ratio; #endif // CONFIG_EKF2_SIDESLIP #if defined(CONFIG_EKF2_TERRAIN) && defined(CONFIG_EKF2_RANGE_FINDER) // hagl test_ratios.hagl = _ekf.aid_src_rng_hgt().test_ratio; #endif // CONFIG_EKF2_TERRAIN && CONFIG_EKF2_RANGE_FINDER #if defined(CONFIG_EKF2_RANGE_FINDER) // hagl_rate test_ratios.hagl_rate = _ekf.getHaglRateInnovRatio(); #endif // CONFIG_EKF2_RANGE_FINDER test_ratios.timestamp = _replay_mode ? timestamp : hrt_absolute_time(); _estimator_innovation_test_ratios_pub.publish(test_ratios); } void EKF2::PublishInnovationVariances(const hrt_abstime ×tamp) { // publish estimator innovation variance data estimator_innovations_s variances{}; variances.timestamp_sample = _ekf.time_delayed_us(); #if defined(CONFIG_EKF2_GNSS) // GPS variances.gps_hvel[0] = _ekf.aid_src_gnss_vel().innovation_variance[0]; variances.gps_hvel[1] = _ekf.aid_src_gnss_vel().innovation_variance[1]; variances.gps_vvel = _ekf.aid_src_gnss_vel().innovation_variance[2]; variances.gps_hpos[0] = _ekf.aid_src_gnss_pos().innovation_variance[0]; variances.gps_hpos[1] = _ekf.aid_src_gnss_pos().innovation_variance[1]; variances.gps_vpos = _ekf.aid_src_gnss_hgt().innovation_variance; #endif // CONFIG_EKF2_GNSS #if defined(CONFIG_EKF2_EXTERNAL_VISION) // External Vision variances.ev_hvel[0] = _ekf.aid_src_ev_vel().innovation_variance[0]; variances.ev_hvel[1] = _ekf.aid_src_ev_vel().innovation_variance[1]; variances.ev_vvel = _ekf.aid_src_ev_vel().innovation_variance[2]; variances.ev_hpos[0] = _ekf.aid_src_ev_pos().innovation_variance[0]; variances.ev_hpos[1] = _ekf.aid_src_ev_pos().innovation_variance[1]; variances.ev_vpos = _ekf.aid_src_ev_hgt().innovation_variance; #endif // CONFIG_EKF2_EXTERNAL_VISION // Height sensors #if defined(CONFIG_EKF2_RANGE_FINDER) variances.rng_vpos = _ekf.aid_src_rng_hgt().innovation_variance; #endif // CONFIG_EKF2_RANGE_FINDER #if defined(CONFIG_EKF2_BAROMETER) variances.baro_vpos = _ekf.aid_src_baro_hgt().innovation_variance; #endif // CONFIG_EKF2_BAROMETER #if defined(CONFIG_EKF2_AUXVEL) // Auxiliary velocity variances.aux_hvel[0] = _ekf.aid_src_aux_vel().innovation_variance[0]; variances.aux_hvel[1] = _ekf.aid_src_aux_vel().innovation_variance[1]; #endif // CONFIG_EKF2_AUXVEL #if defined(CONFIG_EKF2_OPTICAL_FLOW) // Optical flow variances.flow[0] = _ekf.aid_src_optical_flow().innovation_variance[0]; variances.flow[1] = _ekf.aid_src_optical_flow().innovation_variance[1]; #endif // CONFIG_EKF2_OPTICAL_FLOW // heading variances.heading = _ekf.getHeadingInnovVar(); #if defined(CONFIG_EKF2_MAGNETOMETER) // mag_field variances.mag_field[0] = _ekf.aid_src_mag().innovation_variance[0]; variances.mag_field[1] = _ekf.aid_src_mag().innovation_variance[1]; variances.mag_field[2] = _ekf.aid_src_mag().innovation_variance[2]; #endif // CONFIG_EKF2_MAGNETOMETER #if defined(CONFIG_EKF2_GRAVITY_FUSION) // gravity variances.gravity[0] = _ekf.aid_src_gravity().innovation_variance[0]; variances.gravity[1] = _ekf.aid_src_gravity().innovation_variance[1]; variances.gravity[2] = _ekf.aid_src_gravity().innovation_variance[2]; #endif // CONFIG_EKF2_GRAVITY_FUSION #if defined(CONFIG_EKF2_DRAG_FUSION) // drag variances.drag[0] = _ekf.aid_src_drag().innovation_variance[0]; variances.drag[1] = _ekf.aid_src_drag().innovation_variance[1]; #endif // CONFIG_EKF2_DRAG_FUSION #if defined(CONFIG_EKF2_AIRSPEED) // airspeed variances.airspeed = _ekf.aid_src_airspeed().innovation_variance; #endif // CONFIG_EKF2_AIRSPEED #if defined(CONFIG_EKF2_SIDESLIP) // beta variances.beta = _ekf.aid_src_sideslip().innovation_variance; #endif // CONFIG_EKF2_SIDESLIP #if defined(CONFIG_EKF2_TERRAIN) && defined(CONFIG_EKF2_RANGE_FINDER) // hagl variances.hagl = _ekf.aid_src_rng_hgt().innovation_variance; #endif // CONFIG_EKF2_TERRAIN && CONFIG_EKF2_RANGE_FINDER #if defined(CONFIG_EKF2_RANGE_FINDER) // hagl_rate variances.hagl_rate = _ekf.getHaglRateInnovVar(); #endif // CONFIG_EKF2_RANGE_FINDER variances.timestamp = _replay_mode ? timestamp : hrt_absolute_time(); _estimator_innovation_variances_pub.publish(variances); } void EKF2::PublishLocalPosition(const hrt_abstime ×tamp) { vehicle_local_position_s lpos{}; // generate vehicle local position data lpos.timestamp_sample = timestamp; // Position of body origin in local NED frame const Vector3f position{_ekf.getPosition()}; lpos.x = position(0); lpos.y = position(1); lpos.z = position(2); // Velocity of body origin in local NED frame (m/s) const Vector3f velocity{_ekf.getVelocity()}; lpos.vx = velocity(0); lpos.vy = velocity(1); lpos.vz = velocity(2); // vertical position time derivative (m/s) lpos.z_deriv = _ekf.getVerticalPositionDerivative(); // Acceleration of body origin in local frame const Vector3f vel_deriv{_ekf.getVelocityDerivative()}; lpos.ax = vel_deriv(0); lpos.ay = vel_deriv(1); lpos.az = vel_deriv(2); lpos.xy_valid = _ekf.local_position_is_valid(); lpos.v_xy_valid = _ekf.local_position_is_valid(); // TODO: some modules (e.g.: mc_pos_control) don't handle v_z_valid != z_valid properly lpos.z_valid = _ekf.isLocalVerticalPositionValid() || _ekf.isLocalVerticalVelocityValid(); lpos.v_z_valid = _ekf.isLocalVerticalVelocityValid() || _ekf.isLocalVerticalPositionValid(); // Position of local NED origin in GPS / WGS84 frame if (_ekf.global_origin_valid()) { lpos.ref_timestamp = _ekf.global_origin().getProjectionReferenceTimestamp(); lpos.ref_lat = _ekf.global_origin().getProjectionReferenceLat(); // Reference point latitude in degrees lpos.ref_lon = _ekf.global_origin().getProjectionReferenceLon(); // Reference point longitude in degrees lpos.ref_alt = _ekf.getEkfGlobalOriginAltitude(); // Reference point in MSL altitude meters lpos.xy_global = true; lpos.z_global = true; } else { lpos.ref_timestamp = 0; lpos.ref_lat = static_cast(NAN); lpos.ref_lon = static_cast(NAN); lpos.ref_alt = NAN; lpos.xy_global = false; lpos.z_global = false; } Quatf delta_q_reset; _ekf.get_quat_reset(&delta_q_reset(0), &lpos.heading_reset_counter); lpos.heading = Eulerf(_ekf.getQuaternion()).psi(); lpos.unaided_heading = _ekf.getUnaidedYaw(); lpos.heading_var = _ekf.getYawVar(); lpos.delta_heading = Eulerf(delta_q_reset).psi(); lpos.heading_good_for_control = _ekf.isYawFinalAlignComplete(); lpos.tilt_var = _ekf.getTiltVariance(); #if defined(CONFIG_EKF2_TERRAIN) // Distance to bottom surface (ground) in meters, must be positive lpos.dist_bottom_valid = _ekf.isTerrainEstimateValid(); lpos.dist_bottom = math::max(_ekf.getHagl(), 0.f); lpos.dist_bottom_var = _ekf.getTerrainVariance(); _ekf.get_hagl_reset(&lpos.delta_dist_bottom, &lpos.dist_bottom_reset_counter); lpos.dist_bottom_sensor_bitfield = vehicle_local_position_s::DIST_BOTTOM_SENSOR_NONE; if (_ekf.control_status_flags().rng_terrain) { lpos.dist_bottom_sensor_bitfield |= vehicle_local_position_s::DIST_BOTTOM_SENSOR_RANGE; } if (_ekf.control_status_flags().opt_flow_terrain) { lpos.dist_bottom_sensor_bitfield |= vehicle_local_position_s::DIST_BOTTOM_SENSOR_FLOW; } #endif // CONFIG_EKF2_TERRAIN _ekf.get_ekf_lpos_accuracy(&lpos.eph, &lpos.epv); _ekf.get_ekf_vel_accuracy(&lpos.evh, &lpos.evv); // get state reset information of position and velocity _ekf.get_posD_reset(&lpos.delta_z, &lpos.z_reset_counter); _ekf.get_velD_reset(&lpos.delta_vz, &lpos.vz_reset_counter); _ekf.get_posNE_reset(&lpos.delta_xy[0], &lpos.xy_reset_counter); _ekf.get_velNE_reset(&lpos.delta_vxy[0], &lpos.vxy_reset_counter); lpos.dead_reckoning = _ekf.control_status_flags().inertial_dead_reckoning || _ekf.control_status_flags().wind_dead_reckoning; // get control limit information _ekf.get_ekf_ctrl_limits(&lpos.vxy_max, &lpos.vz_max, &lpos.hagl_min, &lpos.hagl_max); // convert NaN to INFINITY if (!PX4_ISFINITE(lpos.vxy_max)) { lpos.vxy_max = INFINITY; } if (!PX4_ISFINITE(lpos.vz_max)) { lpos.vz_max = INFINITY; } if (!PX4_ISFINITE(lpos.hagl_min)) { lpos.hagl_min = INFINITY; } if (!PX4_ISFINITE(lpos.hagl_max)) { lpos.hagl_max = INFINITY; } // publish vehicle local position data lpos.timestamp = _replay_mode ? timestamp : hrt_absolute_time(); _local_position_pub.publish(lpos); } void EKF2::PublishOdometry(const hrt_abstime ×tamp, const imuSample &imu_sample) { // generate vehicle odometry data vehicle_odometry_s odom; odom.timestamp_sample = imu_sample.time_us; // position odom.pose_frame = vehicle_odometry_s::POSE_FRAME_NED; _ekf.getPosition().copyTo(odom.position); // orientation quaternion _ekf.getQuaternion().copyTo(odom.q); // velocity odom.velocity_frame = vehicle_odometry_s::VELOCITY_FRAME_NED; _ekf.getVelocity().copyTo(odom.velocity); // angular_velocity const Vector3f rates{imu_sample.delta_ang / imu_sample.delta_ang_dt}; const Vector3f angular_velocity = rates - _ekf.getGyroBias(); angular_velocity.copyTo(odom.angular_velocity); // velocity covariances _ekf.getVelocityVariance().copyTo(odom.velocity_variance); // position covariances _ekf.getPositionVariance().copyTo(odom.position_variance); // orientation covariance _ekf.getRotVarBody().copyTo(odom.orientation_variance); odom.reset_counter = _ekf.get_quat_reset_count() + _ekf.get_velNE_reset_count() + _ekf.get_velD_reset_count() + _ekf.get_posNE_reset_count() + _ekf.get_posD_reset_count(); odom.quality = 0; // publish vehicle odometry data odom.timestamp = _replay_mode ? timestamp : hrt_absolute_time(); _odometry_pub.publish(odom); } void EKF2::PublishSensorBias(const hrt_abstime ×tamp) { // estimator_sensor_bias const Vector3f gyro_bias{_ekf.getGyroBias()}; const Vector3f accel_bias{_ekf.getAccelBias()}; #if defined(CONFIG_EKF2_MAGNETOMETER) const Vector3f mag_bias {_ekf.getMagBias()}; #endif // CONFIG_EKF2_MAGNETOMETER // publish at ~1 Hz, or sooner if there's a change if ((gyro_bias - _last_gyro_bias_published).longerThan(0.001f) || (accel_bias - _last_accel_bias_published).longerThan(0.001f) #if defined(CONFIG_EKF2_MAGNETOMETER) || (mag_bias - _last_mag_bias_published).longerThan(0.001f) #endif // CONFIG_EKF2_MAGNETOMETER || (timestamp >= _last_sensor_bias_published + 1_s)) { estimator_sensor_bias_s bias{}; bias.timestamp_sample = _ekf.time_delayed_us(); // take device ids from sensor_selection_s if not using specific vehicle_imu_s if ((_device_id_gyro != 0) && (_param_ekf2_imu_ctrl.get() & static_cast(ImuCtrl::GyroBias))) { const Vector3f bias_var{_ekf.getGyroBiasVariance()}; bias.gyro_device_id = _device_id_gyro; gyro_bias.copyTo(bias.gyro_bias); bias.gyro_bias_limit = _ekf.getGyroBiasLimit(); bias_var.copyTo(bias.gyro_bias_variance); bias.gyro_bias_valid = bias_var.longerThan(0.f) && !bias_var.longerThan(0.1f); bias.gyro_bias_stable = _gyro_cal.cal_available; _last_gyro_bias_published = gyro_bias; } if ((_device_id_accel != 0) && (_param_ekf2_imu_ctrl.get() & static_cast(ImuCtrl::AccelBias))) { const Vector3f bias_var{_ekf.getAccelBiasVariance()}; bias.accel_device_id = _device_id_accel; accel_bias.copyTo(bias.accel_bias); bias.accel_bias_limit = _ekf.getAccelBiasLimit(); bias_var.copyTo(bias.accel_bias_variance); bias.accel_bias_valid = bias_var.longerThan(0.f) && !bias_var.longerThan(0.1f); bias.accel_bias_stable = _accel_cal.cal_available; _last_accel_bias_published = accel_bias; } #if defined(CONFIG_EKF2_MAGNETOMETER) if (_device_id_mag != 0) { const Vector3f bias_var{_ekf.getMagBiasVariance()}; bias.mag_device_id = _device_id_mag; mag_bias.copyTo(bias.mag_bias); bias.mag_bias_limit = _ekf.getMagBiasLimit(); bias_var.copyTo(bias.mag_bias_variance); bias.mag_bias_valid = bias_var.longerThan(0.f) && !bias_var.longerThan(0.1f); bias.mag_bias_stable = _mag_cal.cal_available; _last_mag_bias_published = mag_bias; } #endif // CONFIG_EKF2_MAGNETOMETER bias.timestamp = _replay_mode ? timestamp : hrt_absolute_time(); _estimator_sensor_bias_pub.publish(bias); _last_sensor_bias_published = bias.timestamp; } } void EKF2::PublishStates(const hrt_abstime ×tamp) { // publish estimator states estimator_states_s states; states.timestamp_sample = _ekf.time_delayed_us(); const auto state_vector = _ekf.state().vector(); state_vector.copyTo(states.states); states.n_states = state_vector.size(); _ekf.covariances_diagonal().copyTo(states.covariances); states.timestamp = _replay_mode ? timestamp : hrt_absolute_time(); _estimator_states_pub.publish(states); } void EKF2::PublishStatus(const hrt_abstime ×tamp) { estimator_status_s status{}; status.timestamp_sample = _ekf.time_delayed_us(); _ekf.getOutputTrackingError().copyTo(status.output_tracking_error); #if defined(CONFIG_EKF2_GNSS) // only report enabled GPS check failures (the param indexes are shifted by 1 bit, because they don't include // the GPS Fix bit, which is always checked) status.gps_check_fail_flags = _ekf.gps_check_fail_status().value & (((uint16_t)_params->gps_check_mask << 1) | 1); #endif // CONFIG_EKF2_GNSS status.control_mode_flags = _ekf.control_status().value; status.filter_fault_flags = _ekf.fault_status().value; // vel_test_ratio float vel_xy_test_ratio = _ekf.getHorizontalVelocityInnovationTestRatio(); float vel_z_test_ratio = _ekf.getVerticalVelocityInnovationTestRatio(); if (PX4_ISFINITE(vel_xy_test_ratio) && PX4_ISFINITE(vel_z_test_ratio)) { status.vel_test_ratio = math::max(vel_xy_test_ratio, vel_z_test_ratio); } else if (PX4_ISFINITE(vel_xy_test_ratio)) { status.vel_test_ratio = vel_xy_test_ratio; } else if (PX4_ISFINITE(vel_z_test_ratio)) { status.vel_test_ratio = vel_z_test_ratio; } else { status.vel_test_ratio = NAN; } status.hdg_test_ratio = _ekf.getHeadingInnovationTestRatio(); status.pos_test_ratio = _ekf.getHorizontalPositionInnovationTestRatio(); status.hgt_test_ratio = _ekf.getVerticalPositionInnovationTestRatio(); status.tas_test_ratio = _ekf.getAirspeedInnovationTestRatio(); status.hagl_test_ratio = _ekf.getHeightAboveGroundInnovationTestRatio(); status.beta_test_ratio = _ekf.getSyntheticSideslipInnovationTestRatio(); _ekf.get_ekf_lpos_accuracy(&status.pos_horiz_accuracy, &status.pos_vert_accuracy); status.solution_status_flags = _ekf.get_ekf_soln_status(); // reset counters status.reset_count_vel_ne = _ekf.state_reset_status().reset_count.velNE; status.reset_count_vel_d = _ekf.state_reset_status().reset_count.velD; status.reset_count_pos_ne = _ekf.state_reset_status().reset_count.posNE; status.reset_count_pod_d = _ekf.state_reset_status().reset_count.posD; status.reset_count_quat = _ekf.state_reset_status().reset_count.quat; status.time_slip = _last_time_slip_us * 1e-6f; static constexpr float kMinTestRatioPreflight = 0.5f; status.pre_flt_fail_innov_heading = (kMinTestRatioPreflight < status.hdg_test_ratio); status.pre_flt_fail_innov_height = (kMinTestRatioPreflight < status.hgt_test_ratio); status.pre_flt_fail_innov_pos_horiz = (kMinTestRatioPreflight < status.pos_test_ratio); status.pre_flt_fail_innov_vel_horiz = (kMinTestRatioPreflight < vel_xy_test_ratio); status.pre_flt_fail_innov_vel_vert = (kMinTestRatioPreflight < vel_z_test_ratio); status.pre_flt_fail_mag_field_disturbed = _ekf.control_status_flags().mag_field_disturbed; status.accel_device_id = _device_id_accel; #if defined(CONFIG_EKF2_BAROMETER) status.baro_device_id = _device_id_baro; #endif // CONFIG_EKF2_BAROMETER status.gyro_device_id = _device_id_gyro; #if defined(CONFIG_EKF2_MAGNETOMETER) status.mag_device_id = _device_id_mag; _ekf.get_mag_checks(status.mag_inclination_deg, status.mag_inclination_ref_deg, status.mag_strength_gs, status.mag_strength_ref_gs); #endif // CONFIG_EKF2_MAGNETOMETER status.timestamp = _replay_mode ? timestamp : hrt_absolute_time(); _estimator_status_pub.publish(status); } void EKF2::PublishStatusFlags(const hrt_abstime ×tamp) { // publish at ~ 1 Hz (or immediately if filter control status or fault status changes) bool update = (timestamp >= _last_status_flags_publish + 1_s); // filter control status if (_ekf.control_status().value != _filter_control_status) { update = true; _filter_control_status = _ekf.control_status().value; _filter_control_status_changes++; } // filter fault status if (_ekf.fault_status().value != _filter_fault_status) { update = true; _filter_fault_status = _ekf.fault_status().value; _filter_fault_status_changes++; } // innovation check fail status if (_ekf.innov_check_fail_status().value != _innov_check_fail_status) { update = true; _innov_check_fail_status = _ekf.innov_check_fail_status().value; _innov_check_fail_status_changes++; } if (update) { estimator_status_flags_s status_flags{}; status_flags.timestamp_sample = _ekf.time_delayed_us(); status_flags.control_status_changes = _filter_control_status_changes; status_flags.cs_tilt_align = _ekf.control_status_flags().tilt_align; status_flags.cs_yaw_align = _ekf.control_status_flags().yaw_align; status_flags.cs_gps = _ekf.control_status_flags().gps; status_flags.cs_opt_flow = _ekf.control_status_flags().opt_flow; status_flags.cs_mag_hdg = _ekf.control_status_flags().mag_hdg; status_flags.cs_mag_3d = _ekf.control_status_flags().mag_3D; status_flags.cs_mag_dec = _ekf.control_status_flags().mag_dec; status_flags.cs_in_air = _ekf.control_status_flags().in_air; status_flags.cs_wind = _ekf.control_status_flags().wind; status_flags.cs_baro_hgt = _ekf.control_status_flags().baro_hgt; status_flags.cs_rng_hgt = _ekf.control_status_flags().rng_hgt; status_flags.cs_gps_hgt = _ekf.control_status_flags().gps_hgt; status_flags.cs_ev_pos = _ekf.control_status_flags().ev_pos; status_flags.cs_ev_yaw = _ekf.control_status_flags().ev_yaw; status_flags.cs_ev_hgt = _ekf.control_status_flags().ev_hgt; status_flags.cs_fuse_beta = _ekf.control_status_flags().fuse_beta; status_flags.cs_mag_field_disturbed = _ekf.control_status_flags().mag_field_disturbed; status_flags.cs_fixed_wing = _ekf.control_status_flags().fixed_wing; status_flags.cs_mag_fault = _ekf.control_status_flags().mag_fault; status_flags.cs_fuse_aspd = _ekf.control_status_flags().fuse_aspd; status_flags.cs_gnd_effect = _ekf.control_status_flags().gnd_effect; status_flags.cs_rng_stuck = _ekf.control_status_flags().rng_stuck; status_flags.cs_gnss_yaw = _ekf.control_status_flags().gnss_yaw; status_flags.cs_mag_aligned_in_flight = _ekf.control_status_flags().mag_aligned_in_flight; status_flags.cs_ev_vel = _ekf.control_status_flags().ev_vel; status_flags.cs_synthetic_mag_z = _ekf.control_status_flags().synthetic_mag_z; status_flags.cs_vehicle_at_rest = _ekf.control_status_flags().vehicle_at_rest; status_flags.cs_gnss_yaw_fault = _ekf.control_status_flags().gnss_yaw_fault; status_flags.cs_rng_fault = _ekf.control_status_flags().rng_fault; status_flags.cs_inertial_dead_reckoning = _ekf.control_status_flags().inertial_dead_reckoning; status_flags.cs_wind_dead_reckoning = _ekf.control_status_flags().wind_dead_reckoning; status_flags.cs_rng_kin_consistent = _ekf.control_status_flags().rng_kin_consistent; status_flags.cs_fake_pos = _ekf.control_status_flags().fake_pos; status_flags.cs_fake_hgt = _ekf.control_status_flags().fake_hgt; status_flags.cs_gravity_vector = _ekf.control_status_flags().gravity_vector; status_flags.cs_mag = _ekf.control_status_flags().mag; status_flags.cs_ev_yaw_fault = _ekf.control_status_flags().ev_yaw_fault; status_flags.cs_mag_heading_consistent = _ekf.control_status_flags().mag_heading_consistent; status_flags.cs_aux_gpos = _ekf.control_status_flags().aux_gpos; status_flags.cs_rng_terrain = _ekf.control_status_flags().rng_terrain; status_flags.cs_opt_flow_terrain = _ekf.control_status_flags().opt_flow_terrain; status_flags.fault_status_changes = _filter_fault_status_changes; status_flags.fs_bad_mag_x = _ekf.fault_status_flags().bad_mag_x; status_flags.fs_bad_mag_y = _ekf.fault_status_flags().bad_mag_y; status_flags.fs_bad_mag_z = _ekf.fault_status_flags().bad_mag_z; status_flags.fs_bad_hdg = _ekf.fault_status_flags().bad_hdg; status_flags.fs_bad_mag_decl = _ekf.fault_status_flags().bad_mag_decl; status_flags.fs_bad_airspeed = _ekf.fault_status_flags().bad_airspeed; status_flags.fs_bad_sideslip = _ekf.fault_status_flags().bad_sideslip; status_flags.fs_bad_optflow_x = _ekf.fault_status_flags().bad_optflow_X; status_flags.fs_bad_optflow_y = _ekf.fault_status_flags().bad_optflow_Y; status_flags.fs_bad_acc_bias = _ekf.fault_status_flags().bad_acc_bias; status_flags.fs_bad_acc_vertical = _ekf.fault_status_flags().bad_acc_vertical; status_flags.fs_bad_acc_clipping = _ekf.fault_status_flags().bad_acc_clipping; status_flags.innovation_fault_status_changes = _innov_check_fail_status_changes; status_flags.reject_hor_vel = _ekf.innov_check_fail_status_flags().reject_hor_vel; status_flags.reject_ver_vel = _ekf.innov_check_fail_status_flags().reject_ver_vel; status_flags.reject_hor_pos = _ekf.innov_check_fail_status_flags().reject_hor_pos; status_flags.reject_ver_pos = _ekf.innov_check_fail_status_flags().reject_ver_pos; status_flags.reject_yaw = _ekf.innov_check_fail_status_flags().reject_yaw; status_flags.reject_airspeed = _ekf.innov_check_fail_status_flags().reject_airspeed; status_flags.reject_sideslip = _ekf.innov_check_fail_status_flags().reject_sideslip; status_flags.reject_hagl = _ekf.innov_check_fail_status_flags().reject_hagl; status_flags.reject_optflow_x = _ekf.innov_check_fail_status_flags().reject_optflow_X; status_flags.reject_optflow_y = _ekf.innov_check_fail_status_flags().reject_optflow_Y; status_flags.timestamp = _replay_mode ? timestamp : hrt_absolute_time(); _estimator_status_flags_pub.publish(status_flags); _last_status_flags_publish = status_flags.timestamp; } } #if defined(CONFIG_EKF2_GNSS) void EKF2::PublishYawEstimatorStatus(const hrt_abstime ×tamp) { static_assert(sizeof(yaw_estimator_status_s::yaw) / sizeof(float) == N_MODELS_EKFGSF, "yaw_estimator_status_s::yaw wrong size"); yaw_estimator_status_s yaw_est_test_data; if (_ekf.getDataEKFGSF(&yaw_est_test_data.yaw_composite, &yaw_est_test_data.yaw_variance, yaw_est_test_data.yaw, yaw_est_test_data.innov_vn, yaw_est_test_data.innov_ve, yaw_est_test_data.weight)) { yaw_est_test_data.yaw_composite_valid = _ekf.isYawEmergencyEstimateAvailable(); yaw_est_test_data.timestamp_sample = _ekf.time_delayed_us(); yaw_est_test_data.timestamp = _replay_mode ? timestamp : hrt_absolute_time(); _yaw_est_pub.publish(yaw_est_test_data); } } #endif // CONFIG_EKF2_GNSS #if defined(CONFIG_EKF2_WIND) void EKF2::PublishWindEstimate(const hrt_abstime ×tamp) { if (_ekf.get_wind_status()) { // Publish wind estimate only if ekf declares them valid wind_s wind{}; wind.timestamp_sample = _ekf.time_delayed_us(); const Vector2f wind_vel = _ekf.getWindVelocity(); const Vector2f wind_vel_var = _ekf.getWindVelocityVariance(); #if defined(CONFIG_EKF2_AIRSPEED) wind.tas_innov = _ekf.aid_src_airspeed().innovation; wind.tas_innov_var = _ekf.aid_src_airspeed().innovation_variance; #endif // CONFIG_EKF2_AIRSPEED #if defined(CONFIG_EKF2_SIDESLIP) wind.beta_innov = _ekf.aid_src_sideslip().innovation; wind.beta_innov = _ekf.aid_src_sideslip().innovation_variance; #endif // CONFIG_EKF2_SIDESLIP wind.windspeed_north = wind_vel(0); wind.windspeed_east = wind_vel(1); wind.variance_north = wind_vel_var(0); wind.variance_east = wind_vel_var(1); wind.timestamp = _replay_mode ? timestamp : hrt_absolute_time(); _wind_pub.publish(wind); } } #endif // CONFIG_EKF2_WIND #if defined(CONFIG_EKF2_OPTICAL_FLOW) void EKF2::PublishOpticalFlowVel(const hrt_abstime ×tamp) { const hrt_abstime timestamp_sample = _ekf.aid_src_optical_flow().timestamp_sample; if ((timestamp_sample != 0) && (timestamp_sample > _optical_flow_vel_pub_last)) { vehicle_optical_flow_vel_s flow_vel{}; flow_vel.timestamp_sample = _ekf.aid_src_optical_flow().timestamp_sample; _ekf.getFlowVelBody().copyTo(flow_vel.vel_body); _ekf.getFlowVelNE().copyTo(flow_vel.vel_ne); _ekf.getFlowUncompensated().copyTo(flow_vel.flow_rate_uncompensated); _ekf.getFlowCompensated().copyTo(flow_vel.flow_rate_compensated); _ekf.getFlowGyro().copyTo(flow_vel.gyro_rate); _ekf.getFlowGyroBias().copyTo(flow_vel.gyro_bias); _ekf.getFlowRefBodyRate().copyTo(flow_vel.ref_gyro); flow_vel.timestamp = _replay_mode ? timestamp : hrt_absolute_time(); _estimator_optical_flow_vel_pub.publish(flow_vel); _optical_flow_vel_pub_last = timestamp_sample; } } #endif // CONFIG_EKF2_OPTICAL_FLOW #if defined(CONFIG_EKF2_GNSS) float EKF2::filter_altitude_ellipsoid(float amsl_hgt) { float height_diff = static_cast(_gps_alttitude_ellipsoid) * 1e-3f - amsl_hgt; if (_gps_alttitude_ellipsoid_previous_timestamp == 0) { _wgs84_hgt_offset = height_diff; _gps_alttitude_ellipsoid_previous_timestamp = _gps_time_usec; } else if (_gps_time_usec != _gps_alttitude_ellipsoid_previous_timestamp) { // apply a 10 second first order low pass filter to baro offset float dt = 1e-6f * (_gps_time_usec - _gps_alttitude_ellipsoid_previous_timestamp); _gps_alttitude_ellipsoid_previous_timestamp = _gps_time_usec; float offset_rate_correction = 0.1f * (height_diff - _wgs84_hgt_offset); _wgs84_hgt_offset += dt * constrain(offset_rate_correction, -0.1f, 0.1f); } return amsl_hgt + _wgs84_hgt_offset; } #endif // CONFIG_EKF2_GNSS #if defined(CONFIG_EKF2_AIRSPEED) void EKF2::UpdateAirspeedSample(ekf2_timestamps_s &ekf2_timestamps) { // EKF airspeed sample // prefer ORB_ID(airspeed_validated) if available, otherwise fallback to raw airspeed ORB_ID(airspeed) if (_airspeed_validated_sub.updated()) { airspeed_validated_s airspeed_validated; if (_airspeed_validated_sub.update(&airspeed_validated)) { if (PX4_ISFINITE(airspeed_validated.true_airspeed_m_s) && PX4_ISFINITE(airspeed_validated.calibrated_airspeed_m_s) && (airspeed_validated.calibrated_airspeed_m_s > 0.f) && (airspeed_validated.selected_airspeed_index > 0) ) { airspeedSample airspeed_sample { .time_us = airspeed_validated.timestamp, .true_airspeed = airspeed_validated.true_airspeed_m_s, .eas2tas = airspeed_validated.true_airspeed_m_s / airspeed_validated.calibrated_airspeed_m_s, }; _ekf.setAirspeedData(airspeed_sample); } _airspeed_validated_timestamp_last = airspeed_validated.timestamp; } } else if (((ekf2_timestamps.timestamp - _airspeed_validated_timestamp_last) > 3_s) && _airspeed_sub.updated()) { // use ORB_ID(airspeed) if ORB_ID(airspeed_validated) is unavailable airspeed_s airspeed; if (_airspeed_sub.update(&airspeed)) { // The airspeed measurement received via ORB_ID(airspeed) topic has not been corrected // for scale factor errors and requires the ASPD_SCALE correction to be applied. const float true_airspeed_m_s = airspeed.true_airspeed_m_s * _airspeed_scale_factor; if (PX4_ISFINITE(airspeed.true_airspeed_m_s) && PX4_ISFINITE(airspeed.indicated_airspeed_m_s) && (airspeed.indicated_airspeed_m_s > 0.f) ) { airspeedSample airspeed_sample { .time_us = airspeed.timestamp_sample, .true_airspeed = true_airspeed_m_s, .eas2tas = airspeed.true_airspeed_m_s / airspeed.indicated_airspeed_m_s, }; _ekf.setAirspeedData(airspeed_sample); } ekf2_timestamps.airspeed_timestamp_rel = (int16_t)((int64_t)airspeed.timestamp / 100 - (int64_t)ekf2_timestamps.timestamp / 100); } } } #endif // CONFIG_EKF2_AIRSPEED #if defined(CONFIG_EKF2_AUXVEL) void EKF2::UpdateAuxVelSample(ekf2_timestamps_s &ekf2_timestamps) { // EKF auxiliary velocity sample // - use the landing target pose estimate as another source of velocity data landing_target_pose_s landing_target_pose; if (_landing_target_pose_sub.update(&landing_target_pose)) { // we can only use the landing target if it has a fixed position and a valid velocity estimate if (landing_target_pose.is_static && landing_target_pose.rel_vel_valid) { // velocity of vehicle relative to target has opposite sign to target relative to vehicle auxVelSample auxvel_sample{ .time_us = landing_target_pose.timestamp, .vel = Vector2f{-landing_target_pose.vx_rel, -landing_target_pose.vy_rel}, .velVar = Vector2f{landing_target_pose.cov_vx_rel, landing_target_pose.cov_vy_rel}, }; _ekf.setAuxVelData(auxvel_sample); } } } #endif // CONFIG_EKF2_AUXVEL #if defined(CONFIG_EKF2_BAROMETER) void EKF2::UpdateBaroSample(ekf2_timestamps_s &ekf2_timestamps) { // EKF baro sample vehicle_air_data_s airdata; if (_airdata_sub.update(&airdata)) { bool reset = false; // check if barometer has changed if (airdata.baro_device_id != _device_id_baro) { if (_device_id_baro != 0) { PX4_DEBUG("%d - baro sensor ID changed %" PRIu32 " -> %" PRIu32, _instance, _device_id_baro, airdata.baro_device_id); } reset = true; } else if (airdata.calibration_count != _baro_calibration_count) { // existing calibration has changed, reset saved baro bias PX4_DEBUG("%d - baro %" PRIu32 " calibration updated, resetting bias", _instance, _device_id_baro); reset = true; } if (reset) { _device_id_baro = airdata.baro_device_id; _baro_calibration_count = airdata.calibration_count; } _ekf.set_air_density(airdata.rho); _ekf.setBaroData(baroSample{airdata.timestamp_sample, airdata.baro_alt_meter, reset}); ekf2_timestamps.vehicle_air_data_timestamp_rel = (int16_t)((int64_t)airdata.timestamp / 100 - (int64_t)ekf2_timestamps.timestamp / 100); } } #endif // CONFIG_EKF2_BAROMETER #if defined(CONFIG_EKF2_EXTERNAL_VISION) bool EKF2::UpdateExtVisionSample(ekf2_timestamps_s &ekf2_timestamps) { // EKF external vision sample bool new_ev_odom = false; vehicle_odometry_s ev_odom; if (_ev_odom_sub.update(&ev_odom)) { extVisionSample ev_data{}; ev_data.pos.setNaN(); ev_data.vel.setNaN(); ev_data.quat.setNaN(); // check for valid velocity data const Vector3f ev_odom_vel(ev_odom.velocity); const Vector3f ev_odom_vel_var(ev_odom.velocity_variance); if (ev_odom_vel.isAllFinite()) { bool velocity_frame_valid = false; switch (ev_odom.velocity_frame) { case vehicle_odometry_s::VELOCITY_FRAME_NED: ev_data.vel_frame = VelocityFrame::LOCAL_FRAME_NED; velocity_frame_valid = true; break; case vehicle_odometry_s::VELOCITY_FRAME_FRD: ev_data.vel_frame = VelocityFrame::LOCAL_FRAME_FRD; velocity_frame_valid = true; break; case vehicle_odometry_s::VELOCITY_FRAME_BODY_FRD: ev_data.vel_frame = VelocityFrame::BODY_FRAME_FRD; velocity_frame_valid = true; break; } if (velocity_frame_valid) { ev_data.vel = ev_odom_vel; const float evv_noise_var = sq(_param_ekf2_evv_noise.get()); // velocity measurement error from ev_data or parameters if ((_param_ekf2_ev_noise_md.get() == 0) && ev_odom_vel_var.isAllFinite()) { ev_data.velocity_var(0) = fmaxf(evv_noise_var, ev_odom_vel_var(0)); ev_data.velocity_var(1) = fmaxf(evv_noise_var, ev_odom_vel_var(1)); ev_data.velocity_var(2) = fmaxf(evv_noise_var, ev_odom_vel_var(2)); } else { ev_data.velocity_var.setAll(evv_noise_var); } new_ev_odom = true; } } // check for valid position data const Vector3f ev_odom_pos(ev_odom.position); const Vector3f ev_odom_pos_var(ev_odom.position_variance); if (ev_odom_pos.isAllFinite()) { bool position_frame_valid = false; switch (ev_odom.pose_frame) { case vehicle_odometry_s::POSE_FRAME_NED: ev_data.pos_frame = PositionFrame::LOCAL_FRAME_NED; position_frame_valid = true; break; case vehicle_odometry_s::POSE_FRAME_FRD: ev_data.pos_frame = PositionFrame::LOCAL_FRAME_FRD; position_frame_valid = true; break; } if (position_frame_valid) { ev_data.pos = ev_odom_pos; const float evp_noise_var = sq(_param_ekf2_evp_noise.get()); // position measurement error from ev_data or parameters if ((_param_ekf2_ev_noise_md.get() == 0) && ev_odom_pos_var.isAllFinite()) { ev_data.position_var(0) = fmaxf(evp_noise_var, ev_odom_pos_var(0)); ev_data.position_var(1) = fmaxf(evp_noise_var, ev_odom_pos_var(1)); ev_data.position_var(2) = fmaxf(evp_noise_var, ev_odom_pos_var(2)); } else { ev_data.position_var.setAll(evp_noise_var); } new_ev_odom = true; } } // check for valid orientation data const Quatf ev_odom_q(ev_odom.q); const Vector3f ev_odom_q_var(ev_odom.orientation_variance); const bool non_zero = (fabsf(ev_odom_q(0)) > 0.f) || (fabsf(ev_odom_q(1)) > 0.f) || (fabsf(ev_odom_q(2)) > 0.f) || (fabsf(ev_odom_q(3)) > 0.f); const float eps = 1e-5f; const bool no_element_larger_than_one = (fabsf(ev_odom_q(0)) <= 1.f + eps) && (fabsf(ev_odom_q(1)) <= 1.f + eps) && (fabsf(ev_odom_q(2)) <= 1.f + eps) && (fabsf(ev_odom_q(3)) <= 1.f + eps); const bool norm_in_tolerance = fabsf(1.f - ev_odom_q.norm()) <= eps; const bool orientation_valid = ev_odom_q.isAllFinite() && non_zero && no_element_larger_than_one && norm_in_tolerance; if (orientation_valid) { ev_data.quat = ev_odom_q; ev_data.quat.normalize(); // orientation measurement error from ev_data or parameters const float eva_noise_var = sq(_param_ekf2_eva_noise.get()); if ((_param_ekf2_ev_noise_md.get() == 0) && ev_odom_q_var.isAllFinite()) { ev_data.orientation_var(0) = fmaxf(eva_noise_var, ev_odom_q_var(0)); ev_data.orientation_var(1) = fmaxf(eva_noise_var, ev_odom_q_var(1)); ev_data.orientation_var(2) = fmaxf(eva_noise_var, ev_odom_q_var(2)); } else { ev_data.orientation_var.setAll(eva_noise_var); } new_ev_odom = true; } // use timestamp from external computer, clocks are synchronized when using MAVROS ev_data.time_us = ev_odom.timestamp_sample; ev_data.reset_counter = ev_odom.reset_counter; ev_data.quality = ev_odom.quality; if (new_ev_odom) { _ekf.setExtVisionData(ev_data); } ekf2_timestamps.visual_odometry_timestamp_rel = (int16_t)((int64_t)ev_odom.timestamp / 100 - (int64_t)ekf2_timestamps.timestamp / 100); } return new_ev_odom; } #endif // CONFIG_EKF2_EXTERNAL_VISION #if defined(CONFIG_EKF2_OPTICAL_FLOW) bool EKF2::UpdateFlowSample(ekf2_timestamps_s &ekf2_timestamps) { // EKF flow sample bool new_optical_flow = false; vehicle_optical_flow_s optical_flow; if (_vehicle_optical_flow_sub.update(&optical_flow)) { const float dt = 1e-6f * (float)optical_flow.integration_timespan_us; Vector2f flow_rate; Vector3f gyro_rate; if (dt > FLT_EPSILON) { // NOTE: the EKF uses the reverse sign convention to the flow sensor. EKF assumes positive LOS rate // is produced by a RH rotation of the image about the sensor axis. flow_rate = Vector2f(-optical_flow.pixel_flow[0], -optical_flow.pixel_flow[1]) / dt; gyro_rate = Vector3f(-optical_flow.delta_angle[0], -optical_flow.delta_angle[1], -optical_flow.delta_angle[2]) / dt; } else if (optical_flow.quality == 0) { // handle special case of SITL and PX4Flow where dt is forced to zero when the quaity is 0 flow_rate.zero(); gyro_rate.zero(); } flowSample flow { .time_us = optical_flow.timestamp_sample - optical_flow.integration_timespan_us / 2, // correct timestamp to midpoint of integration interval as the data is converted to rates .flow_rate = flow_rate, .gyro_rate = gyro_rate, .quality = optical_flow.quality }; if (Vector2f(optical_flow.pixel_flow).isAllFinite() && optical_flow.integration_timespan_us < 1e6) { // Save sensor limits reported by the optical flow sensor _ekf.set_optical_flow_limits(optical_flow.max_flow_rate, optical_flow.min_ground_distance, optical_flow.max_ground_distance); _ekf.setOpticalFlowData(flow); new_optical_flow = true; } #if defined(CONFIG_EKF2_RANGE_FINDER) // use optical_flow distance as range sample if distance_sensor unavailable if (PX4_ISFINITE(optical_flow.distance_m) && (ekf2_timestamps.timestamp > _last_range_sensor_update + 1_s)) { int8_t quality = static_cast(optical_flow.quality) / static_cast(UINT8_MAX) * 100.f; estimator::sensor::rangeSample range_sample { .time_us = optical_flow.timestamp_sample, .rng = optical_flow.distance_m, .quality = quality, }; _ekf.setRangeData(range_sample); // set sensor limits _ekf.set_rangefinder_limits(optical_flow.min_ground_distance, optical_flow.max_ground_distance); } #endif // CONFIG_EKF2_RANGE_FINDER ekf2_timestamps.optical_flow_timestamp_rel = (int16_t)((int64_t)optical_flow.timestamp / 100 - (int64_t)ekf2_timestamps.timestamp / 100); } return new_optical_flow; } #endif // CONFIG_EKF2_OPTICAL_FLOW #if defined(CONFIG_EKF2_GNSS) void EKF2::UpdateGpsSample(ekf2_timestamps_s &ekf2_timestamps) { // EKF GPS message sensor_gps_s vehicle_gps_position; if (_vehicle_gps_position_sub.update(&vehicle_gps_position)) { Vector3f vel_ned; if (vehicle_gps_position.vel_ned_valid) { vel_ned = Vector3f(vehicle_gps_position.vel_n_m_s, vehicle_gps_position.vel_e_m_s, vehicle_gps_position.vel_d_m_s); } else { return; //TODO: change and set to NAN } gnssSample gnss_sample{ .time_us = vehicle_gps_position.timestamp, .lat = vehicle_gps_position.latitude_deg, .lon = vehicle_gps_position.longitude_deg, .alt = static_cast(vehicle_gps_position.altitude_msl_m), .vel = vel_ned, .hacc = vehicle_gps_position.eph, .vacc = vehicle_gps_position.epv, .sacc = vehicle_gps_position.s_variance_m_s, .fix_type = vehicle_gps_position.fix_type, .nsats = vehicle_gps_position.satellites_used, .pdop = sqrtf(vehicle_gps_position.hdop *vehicle_gps_position.hdop + vehicle_gps_position.vdop * vehicle_gps_position.vdop), .yaw = vehicle_gps_position.heading, //TODO: move to different message .yaw_acc = vehicle_gps_position.heading_accuracy, .yaw_offset = vehicle_gps_position.heading_offset, .spoofed = vehicle_gps_position.spoofing_state == sensor_gps_s::SPOOFING_STATE_MULTIPLE, }; _ekf.setGpsData(gnss_sample); _gps_time_usec = gnss_sample.time_us; _gps_alttitude_ellipsoid = static_cast(round(vehicle_gps_position.altitude_ellipsoid_m * 1e3)); } } #endif // CONFIG_EKF2_GNSS #if defined(CONFIG_EKF2_MAGNETOMETER) void EKF2::UpdateMagSample(ekf2_timestamps_s &ekf2_timestamps) { vehicle_magnetometer_s magnetometer; if (_magnetometer_sub.update(&magnetometer)) { bool reset = false; // check if magnetometer has changed if (magnetometer.device_id != _device_id_mag) { if (_device_id_mag != 0) { PX4_DEBUG("%d - mag sensor ID changed %" PRIu32 " -> %" PRIu32, _instance, _device_id_mag, magnetometer.device_id); } reset = true; } else if (magnetometer.calibration_count != _mag_calibration_count) { // existing calibration has changed, reset saved mag bias PX4_DEBUG("%d - mag %" PRIu32 " calibration updated, resetting bias", _instance, _device_id_mag); reset = true; } if (reset) { _device_id_mag = magnetometer.device_id; _mag_calibration_count = magnetometer.calibration_count; // reset magnetometer bias learning _mag_cal = {}; } _ekf.setMagData(magSample{magnetometer.timestamp_sample, Vector3f{magnetometer.magnetometer_ga}, reset}); ekf2_timestamps.vehicle_magnetometer_timestamp_rel = (int16_t)((int64_t)magnetometer.timestamp / 100 - (int64_t)ekf2_timestamps.timestamp / 100); } } #endif // CONFIG_EKF2_MAGNETOMETER #if defined(CONFIG_EKF2_RANGE_FINDER) void EKF2::UpdateRangeSample(ekf2_timestamps_s &ekf2_timestamps) { distance_sensor_s distance_sensor; if (_distance_sensor_selected < 0) { // only consider distance sensors that have updated within the last 0.1s const hrt_abstime timestamp_stale = math::max(ekf2_timestamps.timestamp, 100_ms) - 100_ms; if (_distance_sensor_subs.advertised()) { for (unsigned i = 0; i < _distance_sensor_subs.size(); i++) { if (_distance_sensor_subs[i].update(&distance_sensor)) { // only use the first instace which has the correct orientation if ((distance_sensor.timestamp != 0) && (distance_sensor.timestamp > timestamp_stale) && (distance_sensor.orientation == distance_sensor_s::ROTATION_DOWNWARD_FACING)) { int ndist = orb_group_count(ORB_ID(distance_sensor)); if (ndist > 1) { PX4_INFO("%d - selected distance_sensor:%d (%d advertised)", _instance, i, ndist); } _distance_sensor_selected = i; _last_range_sensor_update = distance_sensor.timestamp; break; } } } } } if (_distance_sensor_selected >= 0 && _distance_sensor_subs[_distance_sensor_selected].update(&distance_sensor)) { // EKF range sample if (distance_sensor.orientation == distance_sensor_s::ROTATION_DOWNWARD_FACING) { estimator::sensor::rangeSample range_sample { .time_us = distance_sensor.timestamp, .rng = distance_sensor.current_distance, .quality = distance_sensor.signal_quality, }; _ekf.setRangeData(range_sample); // Save sensor limits reported by the rangefinder _ekf.set_rangefinder_limits(distance_sensor.min_distance, distance_sensor.max_distance); _last_range_sensor_update = ekf2_timestamps.timestamp; } ekf2_timestamps.distance_sensor_timestamp_rel = (int16_t)((int64_t)distance_sensor.timestamp / 100 - (int64_t)ekf2_timestamps.timestamp / 100); } if (_last_range_sensor_update < ekf2_timestamps.timestamp - 1_s) { // force reselection after timeout _distance_sensor_selected = -1; } } #endif // CONFIG_EKF2_RANGE_FINDER void EKF2::UpdateSystemFlagsSample(ekf2_timestamps_s &ekf2_timestamps) { // EKF system flags if (_status_sub.updated() || _vehicle_land_detected_sub.updated()) { systemFlagUpdate flags{}; flags.time_us = ekf2_timestamps.timestamp; // vehicle_status vehicle_status_s vehicle_status; if (_status_sub.copy(&vehicle_status) && (ekf2_timestamps.timestamp < vehicle_status.timestamp + 3_s)) { // initially set in_air from arming_state (will be overridden if land detector is available) flags.in_air = (vehicle_status.arming_state == vehicle_status_s::ARMING_STATE_ARMED); // let the EKF know if the vehicle motion is that of a fixed wing (forward flight only relative to wind) flags.is_fixed_wing = (vehicle_status.vehicle_type == vehicle_status_s::VEHICLE_TYPE_FIXED_WING); #if defined(CONFIG_EKF2_SIDESLIP) if (vehicle_status.is_vtol_tailsitter && _params->beta_fusion_enabled) { PX4_WARN("Disable EKF beta fusion as unsupported for tailsitter"); _param_ekf2_fuse_beta.set(0); _param_ekf2_fuse_beta.commit_no_notification(); } #endif // CONFIG_EKF2_SIDESLIP } // vehicle_land_detected vehicle_land_detected_s vehicle_land_detected; if (_vehicle_land_detected_sub.copy(&vehicle_land_detected) && (ekf2_timestamps.timestamp < vehicle_land_detected.timestamp + 3_s)) { flags.at_rest = vehicle_land_detected.at_rest; flags.in_air = !vehicle_land_detected.landed; flags.gnd_effect = vehicle_land_detected.in_ground_effect; } _ekf.setSystemFlagData(flags); } } void EKF2::UpdateCalibration(const hrt_abstime ×tamp, InFlightCalibration &cal, const matrix::Vector3f &bias, const matrix::Vector3f &bias_variance, float bias_limit, bool bias_valid, bool learning_valid) { // reset existing cal on takeoff if (!_ekf.control_status_prev_flags().in_air && _ekf.control_status_flags().in_air) { cal = {}; } // Check if conditions are OK for learning of accelerometer bias values // the EKF is operating in the correct mode and there are no filter faults static constexpr float max_var_allowed = 1e-3f; static constexpr float max_var_ratio = 1e2f; const bool valid = bias_valid && (bias_variance.max() < max_var_allowed) && (bias_variance.max() < max_var_ratio * bias_variance.min()); if (valid && learning_valid) { // consider bias estimates stable when all checks pass consistently and bias hasn't changed more than 10% of the limit const float bias_change_limit = 0.1f * bias_limit; if (!(cal.bias - bias).longerThan(bias_change_limit)) { if (cal.last_us != 0) { cal.total_time_us += timestamp - cal.last_us; } if (cal.total_time_us > 10_s) { cal.cal_available = true; } } else { cal.total_time_us = 0; cal.bias = bias; cal.cal_available = false; } cal.last_us = timestamp; } else { // conditions are NOT OK for learning bias, reset timestamp // but keep the accumulated calibration time cal.last_us = 0; if (!valid && (cal.total_time_us != 0)) { // if a filter fault has occurred, assume previous learning was invalid and do not // count it towards total learning time. cal = {}; } } } void EKF2::UpdateAccelCalibration(const hrt_abstime ×tamp) { // the EKF is operating in the correct mode and there are no filter faults const bool bias_valid = (_param_ekf2_imu_ctrl.get() & static_cast(ImuCtrl::AccelBias)) && _ekf.control_status_flags().tilt_align && (_ekf.fault_status().value == 0) && !_ekf.fault_status_flags().bad_acc_bias && !_ekf.fault_status_flags().bad_acc_clipping && !_ekf.fault_status_flags().bad_acc_vertical; const bool learning_valid = bias_valid && !_ekf.accel_bias_inhibited(); UpdateCalibration(timestamp, _accel_cal, _ekf.getAccelBias(), _ekf.getAccelBiasVariance(), _ekf.getAccelBiasLimit(), bias_valid, learning_valid); } void EKF2::UpdateGyroCalibration(const hrt_abstime ×tamp) { // the EKF is operating in the correct mode and there are no filter faults const bool bias_valid = (_param_ekf2_imu_ctrl.get() & static_cast(ImuCtrl::GyroBias)) && _ekf.control_status_flags().tilt_align && (_ekf.fault_status().value == 0); const bool learning_valid = bias_valid && !_ekf.gyro_bias_inhibited(); UpdateCalibration(timestamp, _gyro_cal, _ekf.getGyroBias(), _ekf.getGyroBiasVariance(), _ekf.getGyroBiasLimit(), bias_valid, learning_valid); } #if defined(CONFIG_EKF2_MAGNETOMETER) void EKF2::UpdateMagCalibration(const hrt_abstime ×tamp) { const Vector3f mag_bias = _ekf.getMagBias(); const Vector3f mag_bias_var = _ekf.getMagBiasVariance(); const bool bias_valid = (_ekf.fault_status().value == 0) && _ekf.control_status_flags().yaw_align && mag_bias_var.longerThan(0.f) && !mag_bias_var.longerThan(0.02f); const bool learning_valid = bias_valid && _ekf.control_status_flags().mag; UpdateCalibration(timestamp, _mag_cal, mag_bias, mag_bias_var, _ekf.getMagBiasLimit(), bias_valid, learning_valid); // update stored declination value if (!_mag_decl_saved) { float declination_deg; if (_ekf.get_mag_decl_deg(declination_deg)) { _param_ekf2_mag_decl.update(); if (PX4_ISFINITE(declination_deg) && (fabsf(declination_deg - _param_ekf2_mag_decl.get()) > 0.1f)) { _param_ekf2_mag_decl.set(declination_deg); _param_ekf2_mag_decl.commit_no_notification(); } _mag_decl_saved = true; } } } #endif // CONFIG_EKF2_MAGNETOMETER int EKF2::custom_command(int argc, char *argv[]) { return print_usage("unknown command"); } int EKF2::task_spawn(int argc, char *argv[]) { bool success = false; bool replay_mode = false; if (argc > 1 && !strcmp(argv[1], "-r")) { PX4_INFO("replay mode enabled"); replay_mode = true; } #if defined(CONFIG_EKF2_MULTI_INSTANCE) bool multi_mode = false; int32_t imu_instances = 0; int32_t mag_instances = 0; int32_t sens_imu_mode = 1; param_get(param_find("SENS_IMU_MODE"), &sens_imu_mode); if (sens_imu_mode == 0) { // ekf selector requires SENS_IMU_MODE = 0 multi_mode = true; // IMUs (1 - MAX_NUM_IMUS supported) param_get(param_find("EKF2_MULTI_IMU"), &imu_instances); if (imu_instances < 1 || imu_instances > MAX_NUM_IMUS) { const int32_t imu_instances_limited = math::constrain(imu_instances, static_cast(1), static_cast(MAX_NUM_IMUS)); PX4_WARN("EKF2_MULTI_IMU limited %" PRId32 " -> %" PRId32, imu_instances, imu_instances_limited); param_set_no_notification(param_find("EKF2_MULTI_IMU"), &imu_instances_limited); imu_instances = imu_instances_limited; } #if defined(CONFIG_EKF2_MAGNETOMETER) int32_t sens_mag_mode = 1; const param_t param_sens_mag_mode = param_find("SENS_MAG_MODE"); param_get(param_sens_mag_mode, &sens_mag_mode); if (sens_mag_mode == 0) { const param_t param_ekf2_mult_mag = param_find("EKF2_MULTI_MAG"); param_get(param_ekf2_mult_mag, &mag_instances); // Mags (1 - MAX_NUM_MAGS supported) if (mag_instances > MAX_NUM_MAGS) { const int32_t mag_instances_limited = math::constrain(mag_instances, static_cast(1), static_cast(MAX_NUM_MAGS)); PX4_WARN("EKF2_MULTI_MAG limited %" PRId32 " -> %" PRId32, mag_instances, mag_instances_limited); param_set_no_notification(param_ekf2_mult_mag, &mag_instances_limited); mag_instances = mag_instances_limited; } else if (mag_instances <= 1) { // properly disable multi-magnetometer at sensors hub level PX4_WARN("EKF2_MULTI_MAG disabled, resetting SENS_MAG_MODE"); // re-enable at sensors level sens_mag_mode = 1; param_set(param_sens_mag_mode, &sens_mag_mode); mag_instances = 1; } } else { mag_instances = 1; } #endif // CONFIG_EKF2_MAGNETOMETER } if (multi_mode && !replay_mode) { // Start EKF2Selector if it's not already running if (_ekf2_selector.load() == nullptr) { EKF2Selector *inst = new EKF2Selector(); if (inst) { _ekf2_selector.store(inst); } else { PX4_ERR("Failed to create EKF2 selector"); return PX4_ERROR; } } const hrt_abstime time_started = hrt_absolute_time(); const int multi_instances = math::min(imu_instances * mag_instances, static_cast(EKF2_MAX_INSTANCES)); int multi_instances_allocated = 0; // allocate EKF2 instances until all found or arming uORB::SubscriptionData vehicle_status_sub{ORB_ID(vehicle_status)}; bool ekf2_instance_created[MAX_NUM_IMUS][MAX_NUM_MAGS] {}; // IMUs * mags while ((multi_instances_allocated < multi_instances) && (vehicle_status_sub.get().arming_state != vehicle_status_s::ARMING_STATE_ARMED) && ((hrt_elapsed_time(&time_started) < 30_s) || (vehicle_status_sub.get().hil_state == vehicle_status_s::HIL_STATE_ON))) { vehicle_status_sub.update(); for (uint8_t mag = 0; mag < mag_instances; mag++) { uORB::SubscriptionData vehicle_mag_sub{ORB_ID(vehicle_magnetometer), mag}; for (uint8_t imu = 0; imu < imu_instances; imu++) { uORB::SubscriptionData vehicle_imu_sub{ORB_ID(vehicle_imu), imu}; vehicle_mag_sub.update(); // Mag & IMU data must be valid, first mag can be ignored initially if ((vehicle_mag_sub.advertised() || mag == 0) && (vehicle_imu_sub.advertised())) { if (!ekf2_instance_created[imu][mag]) { EKF2 *ekf2_inst = new EKF2(true, px4::ins_instance_to_wq(imu), false); if (ekf2_inst && ekf2_inst->multi_init(imu, mag)) { int actual_instance = ekf2_inst->instance(); // match uORB instance numbering if ((actual_instance >= 0) && (_objects[actual_instance].load() == nullptr)) { _objects[actual_instance].store(ekf2_inst); success = true; multi_instances_allocated++; ekf2_instance_created[imu][mag] = true; PX4_DEBUG("starting instance %d, IMU:%" PRIu8 " (%" PRIu32 "), MAG:%" PRIu8 " (%" PRIu32 ")", actual_instance, imu, vehicle_imu_sub.get().accel_device_id, mag, vehicle_mag_sub.get().device_id); _ekf2_selector.load()->ScheduleNow(); } else { PX4_ERR("instance numbering problem instance: %d", actual_instance); delete ekf2_inst; break; } } else { PX4_ERR("alloc and init failed imu: %" PRIu8 " mag:%" PRIu8, imu, mag); px4_usleep(100000); break; } } } else { px4_usleep(1000); // give the sensors extra time to start break; } } } if (multi_instances_allocated < multi_instances) { px4_usleep(10000); } } } else #endif // CONFIG_EKF2_MULTI_INSTANCE { // otherwise launch regular EKF2 *ekf2_inst = new EKF2(false, px4::wq_configurations::INS0, replay_mode); if (ekf2_inst) { _objects[0].store(ekf2_inst); ekf2_inst->ScheduleNow(); success = true; } } return success ? PX4_OK : PX4_ERROR; } int EKF2::print_usage(const char *reason) { if (reason) { PX4_WARN("%s\n", reason); } PRINT_MODULE_DESCRIPTION( R"DESCR_STR( ### Description Attitude and position estimator using an Extended Kalman Filter. It is used for Multirotors and Fixed-Wing. The documentation can be found on the [ECL/EKF Overview & Tuning](https://docs.px4.io/main/en/advanced_config/tuning_the_ecl_ekf.html) page. ekf2 can be started in replay mode (`-r`): in this mode, it does not access the system time, but only uses the timestamps from the sensor topics. )DESCR_STR"); PRINT_MODULE_USAGE_NAME("ekf2", "estimator"); PRINT_MODULE_USAGE_COMMAND("start"); PRINT_MODULE_USAGE_PARAM_FLAG('r', "Enable replay mode", true); PRINT_MODULE_USAGE_COMMAND("stop"); PRINT_MODULE_USAGE_COMMAND_DESCR("status", "print status info"); #if defined(CONFIG_EKF2_VERBOSE_STATUS) PRINT_MODULE_USAGE_ARG("-v", "verbose (print all states and full covariance matrix)", true); #endif // CONFIG_EKF2_VERBOSE_STATUS #if defined(CONFIG_EKF2_MULTI_INSTANCE) PRINT_MODULE_USAGE_COMMAND_DESCR("select_instance", "Request switch to new estimator instance"); PRINT_MODULE_USAGE_ARG("", "Specify desired estimator instance", false); #endif // CONFIG_EKF2_MULTI_INSTANCE return 0; } extern "C" __EXPORT int ekf2_main(int argc, char *argv[]) { if (argc <= 1 || strcmp(argv[1], "-h") == 0) { return EKF2::print_usage(); } if (strcmp(argv[1], "start") == 0) { int ret = 0; EKF2::lock_module(); ret = EKF2::task_spawn(argc - 1, argv + 1); if (ret < 0) { PX4_ERR("start failed (%i)", ret); } EKF2::unlock_module(); return ret; #if defined(CONFIG_EKF2_MULTI_INSTANCE) } else if (strcmp(argv[1], "select_instance") == 0) { if (EKF2::trylock_module()) { if (_ekf2_selector.load()) { if (argc > 2) { int instance = atoi(argv[2]); _ekf2_selector.load()->RequestInstance(instance); } else { EKF2::unlock_module(); return EKF2::print_usage("instance required"); } } else { PX4_ERR("multi-EKF not active, unable to select instance"); } EKF2::unlock_module(); } else { PX4_WARN("module locked, try again later"); } return 0; #endif // CONFIG_EKF2_MULTI_INSTANCE } else if (strcmp(argv[1], "status") == 0) { if (EKF2::trylock_module()) { #if defined(CONFIG_EKF2_MULTI_INSTANCE) if (_ekf2_selector.load()) { _ekf2_selector.load()->PrintStatus(); } #endif // CONFIG_EKF2_MULTI_INSTANCE bool verbose_status = false; #if defined(CONFIG_EKF2_VERBOSE_STATUS) if (argc > 2 && (strcmp(argv[2], "-v") == 0)) { verbose_status = true; } #endif // CONFIG_EKF2_VERBOSE_STATUS for (int i = 0; i < EKF2_MAX_INSTANCES; i++) { if (_objects[i].load()) { PX4_INFO_RAW("\n"); _objects[i].load()->print_status(verbose_status); } } EKF2::unlock_module(); } else { PX4_WARN("module locked, try again later"); } return 0; } else if (strcmp(argv[1], "stop") == 0) { EKF2::lock_module(); if (argc > 2) { int instance = atoi(argv[2]); if (instance >= 0 && instance < EKF2_MAX_INSTANCES) { PX4_INFO("stopping instance %d", instance); EKF2 *inst = _objects[instance].load(); if (inst) { inst->request_stop(); px4_usleep(20000); // 20 ms delete inst; _objects[instance].store(nullptr); } } else { PX4_ERR("invalid instance %d", instance); } } else { // otherwise stop everything bool was_running = false; #if defined(CONFIG_EKF2_MULTI_INSTANCE) if (_ekf2_selector.load()) { PX4_INFO("stopping ekf2 selector"); _ekf2_selector.load()->Stop(); delete _ekf2_selector.load(); _ekf2_selector.store(nullptr); was_running = true; } #endif // CONFIG_EKF2_MULTI_INSTANCE for (int i = 0; i < EKF2_MAX_INSTANCES; i++) { EKF2 *inst = _objects[i].load(); if (inst) { PX4_INFO("stopping ekf2 instance %d", i); was_running = true; inst->request_stop(); px4_usleep(20000); // 20 ms delete inst; _objects[i].store(nullptr); } } if (!was_running) { PX4_WARN("not running"); } } EKF2::unlock_module(); return PX4_OK; } EKF2::lock_module(); // Lock here, as the method could access _object. int ret = EKF2::custom_command(argc - 1, argv + 1); EKF2::unlock_module(); return ret; }