LCOV - code coverage report
Current view: top level - AP_ExternalAHRS - AP_ExternalAHRS_SensAItion.cpp (source / functions) Hit Total Coverage
Test: lcov.info Lines: 193 207 93.2 %
Date: 2026-09-09 11:02:08 Functions: 13 14 92.9 %

          Line data    Source code
       1             : /*
       2             :    This program is free software: you can redistribute it and/or modify
       3             :    it under the terms of the GNU General Public License as published by
       4             :    the Free Software Foundation, either version 3 of the License, or
       5             :    (at your option) any later version.
       6             : 
       7             :    This program is distributed in the hope that it will be useful,
       8             :    but WITHOUT ANY WARRANTY; without even the implied warranty of
       9             :    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      10             :    GNU General Public License for more details.
      11             : 
      12             :    You should have received a copy of the GNU General Public License
      13             :    along with this program.  If not, see <http://www.gnu.org/licenses/>.
      14             : */
      15             : /*
      16             :   Support for SensAItion serial connected INS and IMU
      17             :   Implements SensAItion protocol with ArduPilot-specific adaptations
      18             : */
      19             : 
      20             : #include "AP_ExternalAHRS_SensAItion.h"
      21             : 
      22             : #if AP_EXTERNAL_AHRS_SENSAITION_ENABLED
      23             : 
      24             : #include <float.h>
      25             : #include <math.h>
      26             : 
      27             : #include <AP_SerialManager/AP_SerialManager.h>
      28             : #include <GCS_MAVLink/GCS.h>
      29             : #include <AP_HAL/AP_HAL.h>
      30             : #include <AP_GPS/AP_GPS.h>
      31             : #include <AP_Baro/AP_Baro.h>
      32             : #include <AP_Compass/AP_Compass.h>
      33             : #include <AP_InertialSensor/AP_InertialSensor.h>
      34             : 
      35             : namespace
      36             : {
      37             : const float MINIMUM_INTERESTING_BAROMETER_CHANGE_p = 1.0f;
      38             : const float MINIMUM_INTERESTING_TEMP_CHANGE_degc = 0.1f;
      39             : const uint32_t BARO_UPDATE_TIMEOUT_ms = 100U;
      40             : }
      41             : 
      42             : extern const AP_HAL::HAL &hal;
      43             : 
      44           3 : AP_ExternalAHRS_SensAItion::AP_ExternalAHRS_SensAItion(AP_ExternalAHRS *_frontend, AP_ExternalAHRS::state_t &_state) :
      45             :     AP_ExternalAHRS_backend(_frontend, _state),
      46           3 :     parser(AP_ExternalAHRS_SensAItion_Parser::ConfigMode::IMU)
      47             : {
      48           3 :     ins_mode_enabled = option_is_set(AP_ExternalAHRS::OPTIONS::SENSAITION_INS);
      49             : 
      50           3 :     auto mode = ins_mode_enabled ?
      51             :                 AP_ExternalAHRS_SensAItion_Parser::ConfigMode::INTERLEAVED_INS :
      52             :                 AP_ExternalAHRS_SensAItion_Parser::ConfigMode::IMU;
      53             : 
      54           3 :     if (ins_mode_enabled) {
      55           1 :         parser = AP_ExternalAHRS_SensAItion_Parser(mode);
      56             :     }
      57             : 
      58           3 :     auto &sm = AP::serialmanager();
      59           3 :     uart = sm.find_serial(AP_SerialManager::SerialProtocol_AHRS, 0);
      60           3 :     baudrate = sm.find_baudrate(AP_SerialManager::SerialProtocol_AHRS, 0);
      61           3 :     port_num = sm.find_portnum(AP_SerialManager::SerialProtocol_AHRS, 0);
      62           3 :     if (!uart || baudrate == 0 || port_num == -1) {
      63           0 :         GCS_SEND_TEXT(MAV_SEVERITY_ERROR, "KEBNI: Serial Port Not Found!");
      64           0 :         return;
      65             :     }
      66             : 
      67           3 :     if (ins_mode_enabled) {
      68           1 :         set_default_sensors(uint16_t(AP_ExternalAHRS::AvailableSensor::IMU) |
      69             :                             uint16_t(AP_ExternalAHRS::AvailableSensor::GPS) |
      70             :                             uint16_t(AP_ExternalAHRS::AvailableSensor::BARO) |
      71             :                             uint16_t(AP_ExternalAHRS::AvailableSensor::COMPASS));
      72             :     } else {
      73           2 :         set_default_sensors(uint16_t(AP_ExternalAHRS::AvailableSensor::IMU));
      74             :     }
      75             : 
      76           3 :     if (!hal.scheduler->thread_create(
      77             :             FUNCTOR_BIND_MEMBER(&AP_ExternalAHRS_SensAItion::update_thread, void),
      78             :             "AHRS_SensAItion", 2048, AP_HAL::Scheduler::PRIORITY_SPI, 0)) {
      79           0 :         GCS_SEND_TEXT(MAV_SEVERITY_ERROR, "KEBNI: Failed to create thread!");
      80             :     }
      81             : }
      82             : 
      83          10 : int8_t AP_ExternalAHRS_SensAItion::get_port() const
      84             : {
      85          10 :     return uart ? port_num : -1;
      86             : }
      87             : 
      88           0 : const char* AP_ExternalAHRS_SensAItion::get_name() const
      89             : {
      90           0 :     return "Kebni SensAItion";
      91             : }
      92             : 
      93      161837 : bool AP_ExternalAHRS_SensAItion::healthy() const
      94             : {
      95      161837 :     const uint32_t now_ms = AP_HAL::millis();
      96             : 
      97      161837 :     WITH_SEMAPHORE(driver_state.semaphore);
      98             : 
      99      161837 :     if ((now_ms - driver_state.last_imu_pkt_ms) > 160) {
     100             :         // IMU is required at high rate for health
     101          10 :         return false;
     102             :     }
     103             : 
     104      161827 :     if (ins_mode_enabled) {
     105       11403 :         if ((now_ms - driver_state.last_ins_pkt_ms) > 400) {
     106             :             // INS packets must also have high enough rate
     107           0 :             return false;
     108             :         }
     109             : 
     110       11403 :         if (!(driver_state.last_sensor_valid & 0x01)) {
     111             :             // IMU not available
     112           1 :             return false;
     113             :         }
     114             : 
     115       11402 :         if (driver_state.last_gnss1_fix < 3) {
     116             :             // No 3D satellite fix
     117           0 :             return false;
     118             :         }
     119             :     }
     120             : 
     121      161826 :     return true;
     122      161837 : }
     123             : 
     124       28840 : bool AP_ExternalAHRS_SensAItion::initialised() const
     125             : {
     126       28840 :     return setup_complete;
     127             : }
     128             : 
     129      132997 : bool AP_ExternalAHRS_SensAItion::pre_arm_check(char *failure_msg, uint8_t failure_msg_len) const
     130             : {
     131      132997 :     if (!healthy()) {
     132           0 :         hal.util->snprintf(failure_msg, failure_msg_len, "SensAItion Unhealthy");
     133           0 :         return false;
     134             :     }
     135             : 
     136      132997 :     if (ins_mode_enabled) {
     137             :         uint8_t last_alignment_status;
     138             :         {
     139          10 :             WITH_SEMAPHORE(driver_state.semaphore);
     140          10 :             last_alignment_status = driver_state.last_alignment_status;
     141          10 :         }
     142          10 :         if (last_alignment_status != 1) {
     143           0 :             hal.util->snprintf(failure_msg, failure_msg_len, "SensAItion Aligning");
     144           0 :             return false;
     145             :         }
     146             :     }
     147             : 
     148      132997 :     return true;
     149             : }
     150             : 
     151        8009 : void AP_ExternalAHRS_SensAItion::get_filter_status(nav_filter_status &status) const
     152             : {
     153        8009 :     memset(&status, 0, sizeof(status));
     154             : 
     155        8009 :     status.flags.initalized = initialised();
     156             : 
     157        8009 :     if (healthy()) {
     158        7998 :         WITH_SEMAPHORE(driver_state.semaphore);
     159        7998 :         if (ins_mode_enabled && driver_state.last_alignment_status == 1) {
     160        6216 :             status.flags.attitude = true;
     161        6216 :             status.flags.horiz_pos_abs = true;
     162        6216 :             status.flags.vert_pos = true;
     163        6216 :             status.flags.horiz_vel = true;
     164        6216 :             status.flags.vert_vel = true;
     165        6216 :             status.flags.using_gps = true;
     166        6216 :             status.flags.horiz_pos_rel = true;
     167        6216 :             status.flags.pred_horiz_pos_abs = true;
     168        6216 :             status.flags.pred_horiz_pos_rel = true;
     169             :         }
     170        7998 :     }
     171        8009 : }
     172             : 
     173        8009 : bool AP_ExternalAHRS_SensAItion::get_variances(float &velVar, float &posVar, float &hgtVar, Vector3f &magVar, float &tasVar) const
     174             : {
     175        8009 :     WITH_SEMAPHORE(driver_state.semaphore);
     176        8009 :     if (ins_mode_enabled && driver_state.last_alignment_status == 1) {
     177        6216 :         posVar = driver_state.last_h_pos_quality * pos_gate_scale;
     178        6216 :         velVar = driver_state.last_vel_quality * vel_gate_scale;
     179        6216 :         hgtVar = driver_state.last_v_pos_quality * hgt_gate_scale;
     180        6216 :         tasVar = 0; //not used
     181        6216 :         return true;
     182             :     }
     183             : 
     184        1793 :     return false;
     185        8009 : }
     186             : 
     187          10 : uint8_t AP_ExternalAHRS_SensAItion::num_gps_sensors() const
     188             : {
     189          10 :     return ins_mode_enabled ? 1 : 0;
     190             : }
     191             : 
     192             : // ---------------------------------------------------------------------------
     193             : // THREAD & PROFILER
     194             : // ---------------------------------------------------------------------------
     195      103928 : void AP_ExternalAHRS_SensAItion::update_thread()
     196             : {
     197             :     while (true) {
     198      103928 :         if (!check_uart()) {
     199       70719 :             hal.scheduler->delay_microseconds(500);
     200             :         }
     201             :     }
     202             : }
     203             : 
     204      191076 : bool AP_ExternalAHRS_SensAItion::check_uart()
     205             : {
     206      191076 :     WITH_SEMAPHORE(sem_handle);
     207             : 
     208      191093 :     if (!uart) {
     209           0 :         return false;
     210             :     }
     211             : 
     212      191093 :     if (!setup_complete) {
     213           3 :         uart->begin(baudrate);
     214           3 :         setup_complete = true;
     215           3 :         GCS_SEND_TEXT(MAV_SEVERITY_INFO, "KEBNI: INIT. Mode:%d Baud:%u",
     216             :                       (int)ins_mode_enabled, (unsigned)baudrate);
     217             :     }
     218             : 
     219      191093 :     const auto nread = uart->read(buffer, sizeof(buffer));
     220      191093 :     if (nread <= 0) {
     221      139853 :         return false;
     222             :     }
     223             : 
     224       51240 :     const uint32_t now_ms = AP_HAL::millis();
     225             : 
     226       51240 :     AP_ExternalAHRS_SensAItion_Parser::Measurement meas;
     227       51240 :     bool found_measurement = false;
     228       51240 :     ssize_t parsed_bytes = 0;
     229      111719 :     while (parsed_bytes < nread) {
     230       60479 :         const size_t max_bytes_to_parse = nread - parsed_bytes;
     231       60479 :         parsed_bytes += parser.parse_stream(&buffer[parsed_bytes], max_bytes_to_parse, meas);
     232             : 
     233       60479 :         switch (meas.type) {
     234           0 :         case AP_ExternalAHRS_SensAItion_Parser::MeasurementType::UNINITIALIZED:
     235             :             // The parser is done parsing the whole buffer, but didn't find any
     236             :             // more complete message. (We might be in the middle of a message
     237             :             // that will be finished later.)
     238           0 :             break;
     239       58328 :         case AP_ExternalAHRS_SensAItion_Parser::MeasurementType::IMU:
     240       58328 :             handle_imu(meas, now_ms);
     241       58328 :             found_measurement = true;
     242       58328 :             break;
     243        1075 :         case AP_ExternalAHRS_SensAItion_Parser::MeasurementType::AHRS:
     244        1075 :             handle_ahrs(meas, now_ms);
     245        1075 :             found_measurement = true;
     246        1075 :             break;
     247        1076 :         case AP_ExternalAHRS_SensAItion_Parser::MeasurementType::INS:
     248        1076 :             handle_ins(meas, now_ms);
     249        1076 :             found_measurement = true;
     250        1076 :             break;
     251             :         }
     252             :     }
     253             : 
     254       51240 :     return found_measurement;
     255      191093 : }
     256             : 
     257       58328 : void AP_ExternalAHRS_SensAItion::handle_imu(const AP_ExternalAHRS_SensAItion_Parser::Measurement& meas, uint32_t now_ms)
     258             : {
     259             :     {
     260       58328 :         WITH_SEMAPHORE(state.sem);
     261       58328 :         state.accel = meas.acceleration_mss;
     262       58328 :         state.gyro = meas.angular_velocity_rads;
     263       58328 :     }
     264             : 
     265             :     // INS
     266       58328 :     ins.accel = meas.acceleration_mss;
     267       58328 :     ins.gyro = meas.angular_velocity_rads;
     268       58328 :     ins.temperature = meas.temperature_degc;
     269       58328 :     AP::ins().handle_external(ins);
     270             : 
     271             : #if AP_COMPASS_EXTERNALAHRS_ENABLED
     272             :     // COMPASS
     273       58328 :     mag.field = meas.magnetic_field_mgauss;
     274       58328 :     AP::compass().handle_external(mag);
     275             : #endif
     276             : 
     277             : #if AP_BARO_EXTERNALAHRS_ENABLED
     278       58328 :     bool baro_updated = false;
     279             : #endif
     280             : 
     281             :     {
     282       58328 :         WITH_SEMAPHORE(driver_state.semaphore);
     283       58328 :         driver_state.last_imu_pkt_ms = now_ms;
     284             : 
     285             : #if AP_BARO_EXTERNALAHRS_ENABLED
     286             :         // BARO
     287             :         // ArduPlane has an internal check that triggers an error if there are too many barometer
     288             :         // readings with the same value. At high sampling rates, we triggered that check because
     289             :         // the barometer is internally sampled at a lower rate. To avoid that, we only update the value
     290             :         // if it changes OR a certain minimum time has passed.
     291       58328 :         const bool pressure_changed = fabsf(baro.pressure_pa - meas.air_pressure_p) > MINIMUM_INTERESTING_BAROMETER_CHANGE_p;
     292       58328 :         const bool temp_changed = fabsf(baro.temperature - meas.temperature_degc) > MINIMUM_INTERESTING_TEMP_CHANGE_degc;
     293       58328 :         const bool timeout = now_ms > driver_state.last_baro_update_ms + BARO_UPDATE_TIMEOUT_ms;
     294             : 
     295       58328 :         if (pressure_changed || temp_changed || timeout) {
     296       52428 :             driver_state.last_baro_update_ms = now_ms;
     297       52428 :             baro.instance = 0;
     298       52428 :             baro.pressure_pa = meas.air_pressure_p;
     299       52428 :             baro.temperature = meas.temperature_degc;
     300       52428 :             baro_updated = true;
     301             :         }
     302             : #endif
     303             : 
     304       58328 :     }
     305             : 
     306             : #if AP_BARO_EXTERNALAHRS_ENABLED
     307             :     // Do the update after releasing the semaphore, for speed
     308       58328 :     if (baro_updated) {
     309       52428 :         AP::baro().handle_external(baro);
     310             :     }
     311             : #endif
     312       58328 : }
     313             : 
     314        1075 : void AP_ExternalAHRS_SensAItion::handle_ahrs(const AP_ExternalAHRS_SensAItion_Parser::Measurement& meas, uint32_t now_ms)
     315             : {
     316             :     {
     317        1075 :         WITH_SEMAPHORE(driver_state.semaphore);
     318        1075 :         driver_state.last_quat_pkt_ms = now_ms;
     319        1075 :     }
     320             : 
     321             :     {
     322        1075 :         WITH_SEMAPHORE(state.sem);
     323        1075 :         state.quat = meas.orientation;
     324        1075 :         state.have_quaternion = true;
     325        1075 :     }
     326        1075 : }
     327             : 
     328        1076 : void AP_ExternalAHRS_SensAItion::handle_ins(const AP_ExternalAHRS_SensAItion_Parser::Measurement& meas, uint32_t now_ms)
     329             : {
     330             :     // STATE
     331             :     {
     332        1076 :         WITH_SEMAPHORE(state.sem);
     333        2152 :         state.location = Location(
     334        1076 :                              meas.location.lat,
     335        1076 :                              meas.location.lng,
     336        1076 :                              meas.location.alt,
     337             :                              Location::AltFrame::ABSOLUTE
     338             :                          );
     339        1076 :         state.velocity = meas.velocity_ned;
     340        1076 :         state.have_location = true;
     341        1076 :         state.have_velocity = true;
     342        1076 :         state.last_location_update_us = AP_HAL::micros();
     343             : 
     344        1076 :         if (!state.have_origin && meas.alignment_status) {
     345           2 :             state.origin = Location(
     346           1 :                                meas.location.lat,
     347           1 :                                meas.location.lng,
     348           1 :                                meas.location.alt,
     349             :                                Location::AltFrame::ABSOLUTE
     350             :                            );
     351           1 :             state.have_origin = true;
     352           1 :             GCS_SEND_TEXT(MAV_SEVERITY_NOTICE, "KEBNI: Origin Set.");
     353             :         }
     354        1076 :     }
     355             : 
     356             :     {
     357        1076 :         WITH_SEMAPHORE(driver_state.semaphore);
     358             : 
     359             :         // Local data
     360        1076 :         driver_state.last_ins_pkt_ms = now_ms;
     361        1076 :         driver_state.last_alignment_status = meas.alignment_status;
     362        1076 :         driver_state.last_sensor_valid = meas.sensor_valid;
     363        1076 :         driver_state.last_gnss1_fix = meas.gnss1_fix;
     364        1076 :         driver_state.last_gnss2_fix = meas.gnss2_fix;
     365        1076 :         driver_state.last_error_flags = meas.error_flags;
     366        1076 :         driver_state.last_h_pos_quality = meas.pos_accuracy.xy().length();
     367        1076 :         driver_state.last_v_pos_quality = meas.pos_accuracy.z;
     368        1076 :         driver_state.last_vel_quality = meas.vel_accuracy.length();
     369             : 
     370             :         // GPS
     371        1076 :         gps.horizontal_pos_accuracy = driver_state.last_h_pos_quality;
     372        1076 :         gps.vertical_pos_accuracy = driver_state.last_v_pos_quality;
     373        1076 :     }
     374             : 
     375             :     // GPS - continued
     376        1076 :     gps.gps_week = meas.gps_week;
     377        1076 :     gps.ms_tow = meas.time_itow_ms;
     378        1076 :     gps.fix_type = AP_GPS_FixType(meas.gnss1_fix);
     379        1076 :     gps.satellites_in_view = meas.num_sats_gnss1;
     380        1076 :     gps.horizontal_vel_accuracy = meas.vel_accuracy.xy().length();
     381        1076 :     gps.latitude = meas.location.lat;
     382        1076 :     gps.longitude = meas.location.lng;
     383             : 
     384             :     // Note: SensAItion reports altitude relative to WGS84, not MSL.
     385             :     // But we expect the user to reset the altitude to 0 at start,
     386             :     // so the absolute reference should not matter.
     387        1076 :     gps.msl_altitude = meas.location.alt;
     388        1076 :     gps.ned_vel_north = meas.velocity_ned.x;
     389        1076 :     gps.ned_vel_east = meas.velocity_ned.y;
     390        1076 :     gps.ned_vel_down = meas.velocity_ned.z;
     391             : 
     392             :     // 3. Estimate DOPs (Unitless) using assumed UERE of 3.0m
     393             :     // This answers "What is HDOP/VDOP?"
     394        1076 :     const float ASSUMED_UERE = 3.0f;
     395             : 
     396        1076 :     float est_hdop = gps.horizontal_pos_accuracy / ASSUMED_UERE;
     397        1076 :     float est_vdop = gps.vertical_pos_accuracy / ASSUMED_UERE;
     398             : 
     399             :     // 4. Sanity Clamping (DOP cannot be 0, and rarely < 0.6)
     400        1076 :     if (est_hdop < 0.7f) {
     401        1076 :         est_hdop = 0.7f;
     402             :     }
     403        1076 :     if (est_vdop < 0.7f) {
     404        1076 :         est_vdop = 0.7f;
     405             :     }
     406        1076 :     gps.hdop = est_hdop;
     407        1076 :     gps.vdop = est_vdop;
     408             : 
     409             :     // Handle
     410             :     uint8_t instance;
     411        1076 :     if (AP::gps().get_first_external_instance(instance)) {
     412        1051 :         AP::gps().handle_external(gps, instance);
     413             :     }
     414        1076 : }
     415             : 
     416             : #endif // AP_EXTERNAL_AHRS_SENSAITION_ENABLED

Generated by: LCOV version 1.14