Path: blob/master/libraries/AP_ExternalAHRS/AP_ExternalAHRS_SBG.cpp
9580 views
/*1Copyright (C) 2025 Kraus Hamdani Aerospace Inc. All rights reserved.23This program is free software: you can redistribute it and/or modify4it under the terms of the GNU General Public License as published by5the Free Software Foundation, either version 3 of the License, or6(at your option) any later version.78This program is distributed in the hope that it will be useful,9but WITHOUT ANY WARRANTY; without even the implied warranty of10MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the11GNU General Public License for more details.1213You should have received a copy of the GNU General Public License14along with this program. If not, see <http://www.gnu.org/licenses/>.15*/16/*17support for serial connected SBG INS18*/1920#define AP_MATH_ALLOW_DOUBLE_FUNCTIONS 12122#include "AP_ExternalAHRS_config.h"2324#if AP_EXTERNAL_AHRS_SBG_ENABLED2526#include "AP_ExternalAHRS_SBG.h"27#include <AP_Math/AP_Math.h>28#include <AP_Math/crc.h>29#include <AP_Baro/AP_Baro.h>30#include <AP_Common/time.h>31#include <AP_Compass/AP_Compass.h>32#include <AP_InertialSensor/AP_InertialSensor.h>33#include <GCS_MAVLink/GCS.h>34#include <AP_SerialManager/AP_SerialManager.h>35#include <AP_RTC/AP_RTC.h>36#include <AP_GPS/AP_GPS.h>37#include <AP_Airspeed/AP_Airspeed.h>38#include <AP_Vehicle/AP_Vehicle_Type.h>3940extern const AP_HAL::HAL &hal;4142// constructor43AP_ExternalAHRS_SBG::AP_ExternalAHRS_SBG(AP_ExternalAHRS *_frontend,44AP_ExternalAHRS::state_t &_state) :45AP_ExternalAHRS_backend(_frontend, _state)46{47auto &sm = AP::serialmanager();48uart = sm.find_serial(AP_SerialManager::SerialProtocol_AHRS, 0);49if (uart == nullptr) {50GCS_SEND_TEXT(MAV_SEVERITY_ERROR, "SBG ExternalAHRS no UART");51return;52}53baudrate = sm.find_baudrate(AP_SerialManager::SerialProtocol_AHRS, 0);54port_num = sm.find_portnum(AP_SerialManager::SerialProtocol_AHRS, 0);555657// don't offer IMU by default, at 200Hz it is too slow for many aircraft58set_default_sensors(uint16_t(AP_ExternalAHRS::AvailableSensor::GPS) |59uint16_t(AP_ExternalAHRS::AvailableSensor::BARO) |60uint16_t(AP_ExternalAHRS::AvailableSensor::COMPASS));6162if (!hal.scheduler->thread_create(FUNCTOR_BIND_MEMBER(&AP_ExternalAHRS_SBG::update_thread, void), "SBG", 4096, AP_HAL::Scheduler::PRIORITY_SPI, 0)) {63AP_HAL::panic("SBG Failed to start ExternalAHRS update thread");64}65GCS_SEND_TEXT(MAV_SEVERITY_INFO, "SBG ExternalAHRS initialised");66}6768void AP_ExternalAHRS_SBG::update_thread()69{70hal.scheduler->delay(1000);71while (!hal.scheduler->is_system_initialized()) {72hal.scheduler->delay(100);73}74hal.scheduler->delay(1000);7576if (uart == nullptr) {77return;78}7980uart->begin(baudrate, 1024, 1024);8182setup_complete = true;8384while (true) {8586if (check_uart()) {87// we've parsed something. There might be more so lets come back quickly88hal.scheduler->delay_microseconds(100);89continue;90}9192// uart is idle, lets snooze a little more and then do some housekeeping93hal.scheduler->delay_microseconds(250);9495const uint32_t now_ms = AP_HAL::millis();96if (cached.sbg.deviceInfo.firmwareRev == 0 && now_ms - version_check_ms >= 5000) {97// request Device Info every few seconds until we get a response98version_check_ms = now_ms;99100// static uint32_t count = 1;101// GCS_SEND_TEXT(MAV_SEVERITY_INFO, "SBG Requesting DeviceInfo %u", count++);102103const sbgMessage msg = sbgMessage(SBG_ECOM_CLASS_LOG_CMD_0, SBG_ECOM_CMD_INFO);104UNUSED_RESULT(send_sbgMessage(uart, msg)); // don't care about any error, just retry at 1Hz105106#if AP_COMPASS_ENABLED107} else if (now_ms - send_MagData_ms >= 100) {108send_MagData_ms = now_ms;109110if (!send_MagData(uart)) {111// TODO: if it fails maybe we should figure out why and retry?112// possible causes:113// 1) uart == nullptr114// 2) msg.len > sizeof(data)115// 3) did not write all the bytes out the uart: zero/fail is treated same as packet_len != bytes_sent116if (now_ms - send_mag_error_last_ms >= 5000) {117// throttle the error to no faster than 5Hz because if you get one error you'll likely get a lot118send_mag_error_last_ms = now_ms;119GCS_SEND_TEXT(MAV_SEVERITY_INFO, "SBG: Error sending Mag data");120}121}122#endif // AP_COMPASS_ENABLED123124#if AP_BARO_ENABLED || AP_AIRSPEED_ENABLED125} else if (now_ms - send_AirData_ms >= 100) {126send_AirData_ms = now_ms;127128if (!send_AirData(uart)) {129// TODO: if it fails maybe we should figure out why and retry?130// possible causes:131// 1) uart == nullptr132// 2) msg.len > sizeof(data)133// 3) did not write all the bytes out the uart: zero/fail is treated same as packet_len != bytes_sent134if (now_ms - send_air_error_last_ms >= 5000) {135// throttle the error to no faster than 5Hz because if you get one error you'll likely get a lot136send_air_error_last_ms = now_ms;137GCS_SEND_TEXT(MAV_SEVERITY_INFO, "SBG: Error sending Air data");138}139}140#endif // AP_BARO_ENABLED || AP_AIRSPEED_ENABLED141}142} // while143}144145// Builds packets by looking at each individual byte, once a full packet has been read in it checks the checksum then handles the packet.146// returns true if any data was found in the UART buffer which was then processed147bool AP_ExternalAHRS_SBG::check_uart()148{149uint32_t nbytes = MIN(uart->available(), 512u);150if (nbytes == 0) {151return false;152}153while (nbytes--> 0) {154uint8_t b;155if (!uart->read(b)) {156break;157}158159if (parse_byte(b, _inbound_state.msg, _inbound_state)) {160handle_msg(_inbound_state.msg);161}162}163return true;164}165166167bool AP_ExternalAHRS_SBG::send_sbgMessage(AP_HAL::UARTDriver *_uart, const sbgMessage &msg)168{169if (_uart == nullptr || msg.len > ARRAY_SIZE(msg.data)) {170// invalid _uart or msg.len is out of range171return false;172}173174const uint16_t buffer_len = (SBG_PACKET_OVERHEAD + msg.len);175uint8_t buffer[buffer_len];176177buffer[0] = SBG_PACKET_SYNC1;178buffer[1] = SBG_PACKET_SYNC2;179buffer[2] = msg.msgid;180buffer[3] = msg.msgclass;181buffer[4] = msg.len & 0xFF; // LSB first182buffer[5] = msg.len >> 8;183184for (uint16_t i=0; i<msg.len; i++) {185buffer[6+i] = msg.data[i];186}187188const uint16_t crc = crc16_ccitt_r((const uint8_t*)&msg, msg.len+4, 0, 0);189190buffer[buffer_len-3] = crc & 0xFF; // LSB First191buffer[buffer_len-2] = crc >> 8;192buffer[buffer_len-1] = SBG_PACKET_ETX;193194const uint32_t bytes_sent = _uart->write(buffer, buffer_len);195_uart->flush();196return (bytes_sent == buffer_len);197}198199bool AP_ExternalAHRS_SBG::parse_byte(const uint8_t data, sbgMessage &msg, SBG_PACKET_INBOUND_STATE &inbound_state)200{201switch (inbound_state.parser) {202case SBG_PACKET_PARSE_STATE::SYNC1:203inbound_state.parser = (data == SBG_PACKET_SYNC1) ? SBG_PACKET_PARSE_STATE::SYNC2 : SBG_PACKET_PARSE_STATE::SYNC1;204break;205206case SBG_PACKET_PARSE_STATE::SYNC2:207inbound_state.parser = (data == SBG_PACKET_SYNC2) ? SBG_PACKET_PARSE_STATE::MSG : SBG_PACKET_PARSE_STATE::SYNC1;208break;209210case SBG_PACKET_PARSE_STATE::MSG:211msg.msgid = data;212inbound_state.parser = SBG_PACKET_PARSE_STATE::CLASS;213break;214215case SBG_PACKET_PARSE_STATE::CLASS:216msg.msgclass = data;217inbound_state.parser = SBG_PACKET_PARSE_STATE::LEN1;218break;219220case SBG_PACKET_PARSE_STATE::LEN1:221msg.len = data;222inbound_state.parser = SBG_PACKET_PARSE_STATE::LEN2;223break;224225case SBG_PACKET_PARSE_STATE::LEN2:226msg.len |= uint16_t(data) << 8;227if (msg.len > sizeof(msg.data)) {228// we can't handle this packet, it's larger than the rx buffer which is larger than the largest packet we care about229inbound_state.data_count_skip = msg.len;230inbound_state.parser = SBG_PACKET_PARSE_STATE::DROP_THIS_PACKET;231} else {232inbound_state.data_count = 0;233inbound_state.parser = (msg.len > 0) ? SBG_PACKET_PARSE_STATE::DATA : SBG_PACKET_PARSE_STATE::CRC1;234}235break;236237case SBG_PACKET_PARSE_STATE::DATA:238msg.data[inbound_state.data_count++] = data;239if (inbound_state.data_count >= sizeof(msg.data)) {240inbound_state.parser = SBG_PACKET_PARSE_STATE::SYNC1;241} else if (inbound_state.data_count >= msg.len) {242inbound_state.parser = SBG_PACKET_PARSE_STATE::CRC1;243}244break;245246case SBG_PACKET_PARSE_STATE::CRC1:247inbound_state.crc = data;248inbound_state.parser = SBG_PACKET_PARSE_STATE::CRC2;249break;250251case SBG_PACKET_PARSE_STATE::CRC2:252inbound_state.crc |= uint16_t(data) << 8;253inbound_state.parser = SBG_PACKET_PARSE_STATE::SYNC1; // skip ETX and go directly to SYNC1. Do not pass Go.254{255// CRC field is computed on [MSG(1), CLASS(1), LEN(2), DATA(msg.len)] fields256const uint16_t crc = crc16_ccitt_r((const uint8_t*)&msg, msg.len+4, 0, 0);257if (crc == inbound_state.crc) {258return true;259}260// GCS_SEND_TEXT(MAV_SEVERITY_INFO, "SBG: Failed CRC. Received 0x%04X, Expected 0x%04X", (unsigned)inbound_state.crc, (unsigned)crc);261}262break;263264case SBG_PACKET_PARSE_STATE::ETX:265// we can just skip this state and let SYNC1 fail once when it gets the ETX byte every time... same amount of work266inbound_state.parser = SBG_PACKET_PARSE_STATE::SYNC1;267break;268269default:270case SBG_PACKET_PARSE_STATE::DROP_THIS_PACKET:271// we're currently parsing a packet that is very large and is not one we care272// about. This is not the packet you're looking for... drop all those bytes273if (inbound_state.data_count_skip > 0) {274inbound_state.data_count_skip--;275}276if (inbound_state.data_count_skip == 0) {277inbound_state.parser = SBG_PACKET_PARSE_STATE::SYNC1;278}279break;280}281282return false;283}284285uint16_t AP_ExternalAHRS_SBG::make_gps_week(const SbgEComLogUtc *utc_data)286{287const struct tm tm {288.tm_sec = utc_data->second,289.tm_min = utc_data->minute,290.tm_hour = utc_data->hour,291.tm_mday = utc_data->day,292.tm_mon = utc_data->month - 1,293.tm_year = utc_data->year - 1900,294};295296297// convert from time structure to unix time298const time_t unix_time = ap_mktime(&tm);299300// convert to time since GPS epoch301const uint32_t gps_time = unix_time - ((utc_data->gpsTimeOfWeek + UNIX_OFFSET_MSEC) / 1000);302303// get GPS week304const uint16_t gps_week = gps_time / AP_SEC_PER_WEEK;305306return gps_week;307}308309void AP_ExternalAHRS_SBG::handle_msg(const sbgMessage &msg)310{311const uint32_t now_ms = AP_HAL::millis();312const bool valid_class1_msg = ((SbgEComClass)msg.msgclass == SBG_ECOM_CLASS_LOG_ECOM_1 && (_SbgEComLog1MsgId)msg.msgid == SBG_ECOM_LOG_FAST_IMU_DATA);313314if ((SbgEComClass)msg.msgclass == SBG_ECOM_CLASS_LOG_CMD_0) {315switch ((SbgEComCmd)msg.msgid) {316case SBG_ECOM_CMD_ACK: // 0317break;318319case SBG_ECOM_CMD_INFO: // 4320safe_copy_msg_to_object((uint8_t*)&cached.sbg.deviceInfo, sizeof(cached.sbg.deviceInfo), msg.data, msg.len);321322GCS_SEND_TEXT(MAV_SEVERITY_INFO, "SBG: %s", cached.sbg.deviceInfo.productCode);323// GCS_SEND_TEXT(MAV_SEVERITY_INFO, "SBG: %u, %u, %u, %u, %u, %u, %u: %u.%u.%u",324// (unsigned)cached.sbg.deviceInfo.serialNumber,325// (unsigned)cached.sbg.deviceInfo.calibationRev,326// (unsigned)cached.sbg.deviceInfo.calibrationYear,327// (unsigned)cached.sbg.deviceInfo.calibrationMonth,328// (unsigned)cached.sbg.deviceInfo.calibrationDay,329// (unsigned)cached.sbg.deviceInfo.hardwareRev,330// (unsigned)cached.sbg.deviceInfo.firmwareRev,331// (cached.sbg.deviceInfo.firmwareRev >> 22) & 0x3F,332// (cached.sbg.deviceInfo.firmwareRev >> 16) & 0x3F,333// cached.sbg.deviceInfo.firmwareRev & 0xFFFF334335// GCS_SEND_TEXT(MAV_SEVERITY_INFO, "SBG: Serial Number: %u",(unsigned)cached.sbg.deviceInfo.serialNumber);336GCS_SEND_TEXT(MAV_SEVERITY_INFO, "SBG: Version HW v%u.%u FW v%u.%u.%u",337(unsigned)(cached.sbg.deviceInfo.hardwareRev >> 24) & 0x00FF,338(unsigned)(cached.sbg.deviceInfo.hardwareRev >> 16) & 0x00FF,339340(unsigned) (cached.sbg.deviceInfo.firmwareRev >> 22) & 0x003F,341(unsigned)(cached.sbg.deviceInfo.firmwareRev >> 16) & 0x003F,342(unsigned)cached.sbg.deviceInfo.firmwareRev & 0xFFFF);343break;344345case SBG_ECOM_CMD_COMPUTE_MAG_CALIB: // 15346break;347348default:349// GCS_SEND_TEXT(MAV_SEVERITY_INFO, "SBG: Unknown ID=%u, CLASS=%u, LEN=%u", (unsigned)msg.msgid, (unsigned)msg.msgclass, (unsigned)msg.len);350break;351}352return;353}354355356357if (((SbgEComClass)msg.msgclass != SBG_ECOM_CLASS_LOG_ECOM_0) && !valid_class1_msg) {358// GCS_SEND_TEXT(MAV_SEVERITY_INFO, "SBG: Unknown ID=%u, CLASS=%u, LEN=%u", (unsigned)msg.msgid, (unsigned)msg.msgclass, (unsigned)msg.len);359return;360}361362const bool use_ekf_as_gnss = option_is_set(AP_ExternalAHRS::OPTIONS::SBG_EKF_AS_GNSS);363364bool updated_gps = false;365bool updated_baro = false;366bool updated_ins = false;367bool updated_mag = false;368bool updated_airspeed = false;369370{371WITH_SEMAPHORE(state.sem);372373switch (msg.msgid) { // (_SbgEComLog)374case SBG_ECOM_LOG_FAST_IMU_DATA: // 0375safe_copy_msg_to_object((uint8_t*)&cached.sbg.imuFastLegacy, sizeof(cached.sbg.imuFastLegacy), msg.data, msg.len);376377state.accel = Vector3f(cached.sbg.imuFastLegacy.accelerometers[0], cached.sbg.imuFastLegacy.accelerometers[1], cached.sbg.imuFastLegacy.accelerometers[2]);378state.gyro = Vector3f(cached.sbg.imuFastLegacy.gyroscopes[0], cached.sbg.imuFastLegacy.gyroscopes[1], cached.sbg.imuFastLegacy.gyroscopes[2]);379updated_ins = true;380break;381382case SBG_ECOM_LOG_UTC_TIME: // 2383safe_copy_msg_to_object((uint8_t*)&cached.sbg.utc, sizeof(cached.sbg.utc), msg.data, msg.len);384385if (sbgEComLogUtcGetClockStatus(cached.sbg.utc.status) != SBG_ECOM_CLOCK_VALID ||386sbgEComLogUtcGetClockUtcStatus(cached.sbg.utc.status) != SBG_ECOM_UTC_VALID)387{388// Data is not valid, ignore it.389// This will happen even with a GPS fix = 3. A 3D lock is not enough, a valid390// clock has a higher threshold of quiality needed. It also needs time to sync391// its internal timing and PPS outputs before valid.392break;393}394395// GCS_SEND_TEXT(MAV_SEVERITY_INFO, "------------------");396// GCS_SEND_TEXT(MAV_SEVERITY_INFO, "SBG: %u-%u-%u %02u:%02u:%02u",397// cached.sbg.utc.year,398// (unsigned)cached.sbg.utc.month,399// (unsigned)cached.sbg.utc.day,400// (unsigned)cached.sbg.utc.hour,401// (unsigned)cached.sbg.utc.minute,402// (unsigned)cached.sbg.utc.second);403404#if AP_RTC_ENABLED405{406const uint32_t utc_epoch_sec = AP::rtc().date_fields_to_clock_s(407cached.sbg.utc.year,408cached.sbg.utc.month - 1,409cached.sbg.utc.day,410cached.sbg.utc.hour,411cached.sbg.utc.minute,412cached.sbg.utc.second);413414const uint64_t utc_epoch_usec = ((uint64_t)utc_epoch_sec) * 1E6 + (cached.sbg.utc.nanoSecond * 1E-3);415AP::rtc().set_utc_usec(utc_epoch_usec, AP_RTC::SOURCE_GPS);416417}418#endif // AP_RTC_ENABLED419420cached.sensors.gps_data.ms_tow = cached.sbg.utc.gpsTimeOfWeek;421cached.sensors.gps_data.gps_week = make_gps_week(&cached.sbg.utc);422updated_gps = true;423break;424425case SBG_ECOM_LOG_IMU_SHORT: // 44426safe_copy_msg_to_object((uint8_t*)&cached.sbg.imuShort, sizeof(cached.sbg.imuShort), msg.data, msg.len);427428{429Vector3f temp;430/*!< X, Y, Z delta velocity. Unit is 1048576 LSB for 1 m.s^-2. */431temp = Vector3f(cached.sbg.imuShort.deltaVelocity[0], cached.sbg.imuShort.deltaVelocity[1], cached.sbg.imuShort.deltaVelocity[2]);432state.accel = temp / SBG_ECOM_LOG_IMU_ACCEL_SCALE_STD;433434/*!< X, Y, Z delta angle. Unit is either 67108864 LSB for 1 rad.s^-1 (standard) or 12304174 LSB for 1 rad.s^-1 (high range), managed automatically. */435temp = Vector3f(cached.sbg.imuShort.deltaAngle[0], cached.sbg.imuShort.deltaAngle[1], cached.sbg.imuShort.deltaAngle[2]);436const float scaleFactor = (cached.sbg.imuShort.status & SBG_ECOM_IMU_GYROS_USE_HIGH_SCALE) ? SBG_ECOM_LOG_IMU_GYRO_SCALE_HIGH : SBG_ECOM_LOG_IMU_GYRO_SCALE_STD;437state.gyro = temp / scaleFactor;438}439cached.sensors.ins_data.temperature = (cached.sbg.imuShort.temperature / SBG_ECOM_LOG_IMU_TEMP_SCALE_STD);440updated_ins = true;441break;442443case SBG_ECOM_LOG_IMU_DATA: // 3444safe_copy_msg_to_object((uint8_t*)&cached.sbg.imuLegacy, sizeof(cached.sbg.imuLegacy), msg.data, msg.len);445446state.accel = Vector3f(cached.sbg.imuLegacy.accelerometers[0], cached.sbg.imuLegacy.accelerometers[1], cached.sbg.imuLegacy.accelerometers[2]);447state.gyro = Vector3f(cached.sbg.imuLegacy.gyroscopes[0], cached.sbg.imuLegacy.gyroscopes[1], cached.sbg.imuLegacy.gyroscopes[2]);448cached.sensors.ins_data.temperature = cached.sbg.imuLegacy.temperature;449updated_ins = true;450break;451452case SBG_ECOM_LOG_MAG: // 4453safe_copy_msg_to_object((uint8_t*)&cached.sbg.mag, sizeof(cached.sbg.mag), msg.data, msg.len);454455state.accel = Vector3f(cached.sbg.mag.accelerometers[0], cached.sbg.mag.accelerometers[1], cached.sbg.mag.accelerometers[2]);456updated_ins = true;457458cached.sensors.mag_data.field = Vector3f(cached.sbg.mag.magnetometers[0], cached.sbg.mag.magnetometers[1], cached.sbg.mag.magnetometers[2]);459updated_mag = true;460break;461462case SBG_ECOM_LOG_EKF_EULER: // 6463safe_copy_msg_to_object((uint8_t*)&cached.sbg.ekfEuler, sizeof(cached.sbg.ekfEuler), msg.data, msg.len);464465// float euler[3]; /*!< Roll, Pitch and Yaw angles in rad. */466// float eulerStdDev[3]; /*!< Roll, Pitch and Yaw angles 1 sigma standard deviation in rad. */467state.quat.from_euler(cached.sbg.ekfEuler.euler[0], cached.sbg.ekfEuler.euler[1], cached.sbg.ekfEuler.euler[2]);468state.have_quaternion = true;469break;470471case SBG_ECOM_LOG_EKF_QUAT: // 7472safe_copy_msg_to_object((uint8_t*)&cached.sbg.ekfQuat, sizeof(cached.sbg.ekfQuat), msg.data, msg.len);473474// float quaternion[4]; /*!< Orientation quaternion stored in W, X, Y, Z form. */475// float eulerStdDev[3]; /*!< Roll, Pitch and Yaw angles 1 sigma standard deviation in rad. */476memcpy(&state.quat, cached.sbg.ekfQuat.quaternion, sizeof(state.quat));477state.have_quaternion = true;478break;479480case SBG_ECOM_LOG_EKF_NAV: // 8481safe_copy_msg_to_object((uint8_t*)&cached.sbg.ekfNav, sizeof(cached.sbg.ekfNav), msg.data, msg.len);482483state.velocity = Vector3f(cached.sbg.ekfNav.velocity[0], cached.sbg.ekfNav.velocity[1], cached.sbg.ekfNav.velocity[2]);484state.have_velocity = true;485486state.location = Location(cached.sbg.ekfNav.position[0]*1e7, cached.sbg.ekfNav.position[1]*1e7, cached.sbg.ekfNav.position[2]*1e2, Location::AltFrame::ABSOLUTE);487state.last_location_update_us = AP_HAL::micros();488489ekf_is_full_nav = SbgEkfStatus_is_fullNav(cached.sbg.ekfNav.status);490491if (!state.have_location && ekf_is_full_nav) {492state.have_location = true;493} else if (!state.have_origin && cached.sensors.gps_data.fix_type >= AP_GPS_FixType::FIX_3D && ekf_is_full_nav) {494// this is in an else so that origin doesn't get set on the very very first sample, do it on the second one just to give us a tiny bit more chance of a better origin495state.origin = state.location;496state.have_origin = true;497}498499if (ekf_is_full_nav && use_ekf_as_gnss) {500cached.sensors.gps_data.latitude = cached.sbg.ekfNav.position[0] * 1E7;501cached.sensors.gps_data.longitude = cached.sbg.ekfNav.position[1] * 1E7;502cached.sensors.gps_data.msl_altitude = cached.sbg.ekfNav.position[2] * 100;503504cached.sensors.gps_data.horizontal_pos_accuracy = Vector2f(cached.sbg.ekfNav.positionStdDev[0], cached.sbg.ekfNav.positionStdDev[1]).length();505cached.sensors.gps_data.hdop = cached.sensors.gps_data.horizontal_pos_accuracy;506cached.sensors.gps_data.vertical_pos_accuracy = cached.sbg.ekfNav.positionStdDev[2];507cached.sensors.gps_data.vdop = cached.sensors.gps_data.vertical_pos_accuracy;508509cached.sensors.gps_data.fix_type = AP_GPS_FixType::FIX_3D;510511cached.sensors.gps_data.ned_vel_north = cached.sbg.ekfNav.velocity[0];512cached.sensors.gps_data.ned_vel_east = cached.sbg.ekfNav.velocity[1];513cached.sensors.gps_data.ned_vel_down = cached.sbg.ekfNav.velocity[2];514cached.sensors.gps_data.horizontal_vel_accuracy = Vector2f(cached.sbg.ekfNav.velocityStdDev[0], cached.sbg.ekfNav.velocityStdDev[1]).length();515516updated_gps = true;517}518break;519520case SBG_ECOM_LOG_GPS1_VEL: // 13521case SBG_ECOM_LOG_GPS2_VEL: // 16522safe_copy_msg_to_object((uint8_t*)&cached.sbg.gnssVel, sizeof(cached.sbg.gnssVel), msg.data, msg.len);523524if ((!use_ekf_as_gnss) || (use_ekf_as_gnss && !ekf_is_full_nav)) {525cached.sensors.gps_data.ms_tow = cached.sbg.gnssVel.timeOfWeek;526cached.sensors.gps_data.ned_vel_north = cached.sbg.gnssVel.velocity[0];527cached.sensors.gps_data.ned_vel_east = cached.sbg.gnssVel.velocity[1];528cached.sensors.gps_data.ned_vel_down = cached.sbg.gnssVel.velocity[2];529cached.sensors.gps_data.horizontal_vel_accuracy = Vector2f(cached.sbg.gnssVel.velocityAcc[0], cached.sbg.gnssVel.velocityAcc[1]).length();530// unused - cached.sbg.gnssVel.course531// unused - cached.sbg.gnssVel.courseAcc532updated_gps = true;533}534break;535536case SBG_ECOM_LOG_GPS1_POS: // 14537case SBG_ECOM_LOG_GPS2_POS: // 17538safe_copy_msg_to_object((uint8_t*)&cached.sbg.gnssPos, sizeof(cached.sbg.gnssPos), msg.data, msg.len);539540if ((!use_ekf_as_gnss) || (use_ekf_as_gnss && !ekf_is_full_nav)) {541cached.sensors.gps_data.ms_tow = cached.sbg.gnssPos.timeOfWeek;542cached.sensors.gps_data.latitude = cached.sbg.gnssPos.latitude * 1E7;543cached.sensors.gps_data.longitude = cached.sbg.gnssPos.longitude * 1E7;544cached.sensors.gps_data.msl_altitude = cached.sbg.gnssPos.altitude * 100;545// unused - cached.sbg.gnssPos.undulation546cached.sensors.gps_data.horizontal_pos_accuracy = Vector2f(cached.sbg.gnssPos.latitudeAccuracy, cached.sbg.gnssPos.longitudeAccuracy).length();547cached.sensors.gps_data.hdop = cached.sensors.gps_data.horizontal_pos_accuracy;548cached.sensors.gps_data.vertical_pos_accuracy = cached.sbg.gnssPos.altitudeAccuracy;549cached.sensors.gps_data.vdop = cached.sensors.gps_data.vertical_pos_accuracy;550cached.sensors.gps_data.satellites_in_view = cached.sbg.gnssPos.numSvUsed;551// unused - cached.sbg.gnssPos.baseStationId552// unused - cached.sbg.gnssPos.differentialAge553cached.sensors.gps_data.fix_type = SbgGpsPosStatus_to_GpsFixType(cached.sbg.gnssPos.status);554updated_gps = true;555}556break;557558case SBG_ECOM_LOG_AIR_DATA: // 36559safe_copy_msg_to_object((uint8_t*)&cached.sbg.airData, sizeof(cached.sbg.airData), msg.data, msg.len);560561if (cached.sbg.airData.status & SBG_ECOM_AIR_DATA_PRESSURE_ABS_VALID) {562cached.sensors.baro_data.pressure_pa = cached.sbg.airData.pressureAbs;563updated_baro = true;564}565if (cached.sbg.airData.status & SBG_ECOM_AIR_DATA_ALTITUDE_VALID) {566cached.sensors.baro_height = cached.sbg.airData.altitude;567updated_baro = true;568}569if (cached.sbg.airData.status & SBG_ECOM_AIR_DATA_PRESSURE_DIFF_VALID) {570cached.sensors.airspeed_data.differential_pressure = cached.sbg.airData.pressureDiff;571updated_airspeed = true;572}573// if (cached.sbg.airData.status & SBG_ECOM_AIR_DATA_AIRPSEED_VALID) {574// // we don't accept airspeed directly, we compute it ourselves in AP_Airspeed via diff pressure575// }576577if ((cached.sbg.airData.status & SBG_ECOM_AIR_DATA_TEMPERATURE_VALID) && (updated_baro || updated_airspeed)) {578cached.sensors.airspeed_data.temperature = cached.sbg.airData.airTemperature;579cached.sensors.baro_data.temperature = cached.sbg.airData.airTemperature;580}581break;582583default:584// GCS_SEND_TEXT(MAV_SEVERITY_INFO, "SBG: unhandled ID=%u, CLASS=%u, LEN=%u", (unsigned)msg.msgid, (unsigned)msg.msgclass, (unsigned)msg.len);585return;586} // switch msgid587} // semaphore588589#if AP_GPS_ENABLED590if (updated_gps) {591cached.sensors.gps_ms = now_ms;592uint8_t instance;593if (AP::gps().get_first_external_instance(instance)) {594AP::gps().handle_external(cached.sensors.gps_data, instance);595}596}597#else598(void)updated_gps;599#endif600601#if AP_COMPASS_EXTERNALAHRS_ENABLED602if (updated_mag) {603cached.sensors.mag_ms = now_ms;604AP::compass().handle_external(cached.sensors.mag_data);605}606#else607(void)updated_mag;608#endif609610#if AP_BARO_EXTERNALAHRS_ENABLED611if (updated_baro) {612cached.sensors.baro_ms = now_ms;613cached.sensors.baro_data.instance = 0;614AP::baro().handle_external(cached.sensors.baro_data);615}616#else617(void)updated_baro;618#endif619620#if AP_AIRSPEED_EXTERNAL_ENABLED && (APM_BUILD_COPTER_OR_HELI || APM_BUILD_TYPE(APM_BUILD_ArduPlane))621if (updated_airspeed && AP::airspeed() != nullptr) {622cached.sensors.airspeed_ms = now_ms;623AP::airspeed()->handle_external(cached.sensors.airspeed_data);624}625#else626(void)updated_airspeed;627#endif628629630if (updated_ins) {631cached.sensors.ins_data.accel = state.accel;632cached.sensors.ins_data.gyro = state.gyro;633cached.sensors.ins_ms = now_ms;634AP::ins().handle_external(cached.sensors.ins_data);635}636637last_received_ms = now_ms;638}639640void AP_ExternalAHRS_SBG::safe_copy_msg_to_object(uint8_t* dest, const uint16_t dest_len, const uint8_t* src, const uint16_t src_len)641{642// To allow for future changes of the SBG protocol the protocol allows extending messages643// which can be detected by a length mismatch, usually longer. To allow for this you assume unused data is zero644// but instead of zeroing the packet every time before populating it it's best to only zero when necessary.645if (dest_len != src_len) {646memset(dest, 0, dest_len);647}648memcpy(dest, src, MIN(dest_len,src_len));649}650651bool AP_ExternalAHRS_SBG::SbgEkfStatus_is_fullNav(const uint32_t ekfStatus)652{653SbgEComSolutionMode solutionMode = (SbgEComSolutionMode)(ekfStatus & SBG_ECOM_LOG_EKF_SOLUTION_MODE_MASK);654655return (solutionMode == SBG_ECOM_SOL_MODE_NAV_POSITION);656}657658AP_GPS_FixType AP_ExternalAHRS_SBG::SbgGpsPosStatus_to_GpsFixType(const uint32_t gpsPosStatus)659{660const uint32_t fix = (gpsPosStatus >> SBG_ECOM_GPS_POS_TYPE_SHIFT) & SBG_ECOM_GPS_POS_TYPE_MASK;661switch ((SbgEComGpsPosType)fix) {662case SBG_ECOM_POS_NO_SOLUTION: /*!< No valid solution available. */663case SBG_ECOM_POS_UNKNOWN_TYPE: /*!< An unknown solution type has been computed. */664return AP_GPS_FixType::NONE;665666case SBG_ECOM_POS_SINGLE: /*!< Single point solution position. */667case SBG_ECOM_POS_FIXED: /*!< Fixed location solution position. */668return AP_GPS_FixType::FIX_3D;669670case SBG_ECOM_POS_PSRDIFF: /*!< Standard Pseudorange Differential Solution (DGPS). */671case SBG_ECOM_POS_SBAS: /*!< SBAS satellite used for differential corrections. */672return AP_GPS_FixType::DGPS;673674case SBG_ECOM_POS_RTK_FLOAT: /*!< Floating RTK ambiguity solution (20 cms RTK). */675case SBG_ECOM_POS_PPP_FLOAT: /*!< Precise Point Positioning with float ambiguities. */676case SBG_ECOM_POS_OMNISTAR: /*!< Omnistar VBS Position (L1 sub-meter). */677return AP_GPS_FixType::RTK_FLOAT;678679case SBG_ECOM_POS_RTK_INT: /*!< Integer RTK ambiguity solution (2 cms RTK). */680case SBG_ECOM_POS_PPP_INT: /*!< Precise Point Positioning with fixed ambiguities. */681return AP_GPS_FixType::RTK_FIXED;682}683return AP_GPS_FixType::NONE;684}685686void AP_ExternalAHRS_SBG::get_filter_status(nav_filter_status &status) const687{688WITH_SEMAPHORE(state.sem);689memset(&status, 0, sizeof(status));690691if (cached.sensors.ins_ms != 0 && cached.sensors.gps_ms != 0) {692status.flags.initalized = true;693}694if (!healthy()) {695return;696}697698if (state.have_location) {699status.flags.vert_pos = true;700status.flags.horiz_pos_rel = true;701status.flags.horiz_pos_abs = true;702status.flags.pred_horiz_pos_rel = true;703status.flags.pred_horiz_pos_abs = true;704status.flags.using_gps = true;705}706707if (state.have_quaternion) {708status.flags.attitude = true;709}710711if (state.have_velocity) {712status.flags.vert_vel = true;713status.flags.horiz_vel = true;714}715}716717bool AP_ExternalAHRS_SBG::pre_arm_check(char *failure_msg, uint8_t failure_msg_len) const718{719if (!setup_complete) {720hal.util->snprintf(failure_msg, failure_msg_len, "SBG setup failed");721return false;722}723if (!healthy()) {724hal.util->snprintf(failure_msg, failure_msg_len, "SBG unhealthy");725return false;726}727return true;728}729730#if AP_COMPASS_ENABLED731bool AP_ExternalAHRS_SBG::send_MagData(AP_HAL::UARTDriver *_uart)732{733SbgEComLogMag mag_data_log {};734mag_data_log.timeStamp = AP_HAL::micros();735736const auto &compass = AP::compass();737if (compass.available() || compass.healthy()) {738// TODO: consider also checking compass.last_update_usec() to only send when we have new data739740const Vector3f mag_field = compass.get_field();741mag_data_log.magnetometers[0] = mag_field[0];742mag_data_log.magnetometers[1] = mag_field[1];743mag_data_log.magnetometers[2] = mag_field[2];744745mag_data_log.status |= (SBG_ECOM_MAG_MAG_X_BIT | SBG_ECOM_MAG_MAG_Y_BIT | SBG_ECOM_MAG_MAG_Z_BIT | SBG_ECOM_MAG_MAGS_IN_RANGE | SBG_ECOM_MAG_CALIBRATION_OK);746}747748const sbgMessage msg = sbgMessage(SBG_ECOM_CLASS_LOG_ECOM_0, SBG_ECOM_LOG_MAG, (uint8_t*)&mag_data_log, sizeof(mag_data_log));749return send_sbgMessage(_uart, msg);750}751#endif // AP_COMPASS_ENABLED752753bool AP_ExternalAHRS_SBG::send_AirData(AP_HAL::UARTDriver *_uart)754{755SbgEComLogAirData air_data_log {};756air_data_log.timeStamp = 0;757air_data_log.status |= SBG_ECOM_AIR_DATA_TIME_IS_DELAY;758759#if AP_BARO_ENABLED760const auto &baro = AP::baro();761if (baro.healthy()) {762air_data_log.pressureAbs = baro.get_pressure();763air_data_log.altitude = baro.get_altitude();764air_data_log.airTemperature = baro.get_temperature();765766air_data_log.status |= (SBG_ECOM_AIR_DATA_PRESSURE_ABS_VALID | SBG_ECOM_AIR_DATA_ALTITUDE_VALID | SBG_ECOM_AIR_DATA_TEMPERATURE_VALID);767}768#endif // AP_BARO_ENABLED769770#if AP_AIRSPEED_ENABLED771auto *airspeed = AP::airspeed();772if (airspeed != nullptr && airspeed->healthy()) {773float airTemperature;774if (airspeed->get_temperature(airTemperature)) {775air_data_log.airTemperature = airTemperature;776air_data_log.status |= SBG_ECOM_AIR_DATA_TEMPERATURE_VALID;777}778779air_data_log.pressureDiff = airspeed->get_differential_pressure();780air_data_log.trueAirspeed = airspeed->get_airspeed();781air_data_log.status |= (SBG_ECOM_AIR_DATA_PRESSURE_DIFF_VALID | SBG_ECOM_AIR_DATA_AIRPSEED_VALID);782}783#endif // AP_AIRSPEED_ENABLED784785const sbgMessage msg = sbgMessage(SBG_ECOM_CLASS_LOG_ECOM_0, SBG_ECOM_LOG_AIR_DATA, (uint8_t*)&air_data_log, sizeof(air_data_log));786return send_sbgMessage(_uart, msg);787}788789// get variances790bool AP_ExternalAHRS_SBG::get_variances(float &velVar, float &posVar, float &hgtVar, Vector3f &magVar, float &tasVar) const791{792velVar = cached.sensors.gps_data.horizontal_vel_accuracy * vel_gate_scale;793posVar = cached.sensors.gps_data.horizontal_pos_accuracy * pos_gate_scale;794hgtVar = cached.sensors.gps_data.vertical_pos_accuracy * hgt_gate_scale;795magVar.zero(); // Not provided, set to 0.796tasVar = 0;797return true;798}799800#endif // AP_EXTERNAL_AHRS_SBG_ENABLED801802803804