Path: blob/master/libraries/AP_ExternalAHRS/AP_ExternalAHRS_SBG.cpp
6351 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_Compass/AP_Compass.h>31#include <AP_InertialSensor/AP_InertialSensor.h>32#include <GCS_MAVLink/GCS.h>33#include <AP_SerialManager/AP_SerialManager.h>34#include <AP_RTC/AP_RTC.h>35#include <AP_GPS/AP_GPS.h>36#include <AP_Airspeed/AP_Airspeed.h>37#include <AP_Vehicle/AP_Vehicle_Type.h>3839extern const AP_HAL::HAL &hal;4041// constructor42AP_ExternalAHRS_SBG::AP_ExternalAHRS_SBG(AP_ExternalAHRS *_frontend,43AP_ExternalAHRS::state_t &_state) :44AP_ExternalAHRS_backend(_frontend, _state)45{46auto &sm = AP::serialmanager();47uart = sm.find_serial(AP_SerialManager::SerialProtocol_AHRS, 0);48if (uart == nullptr) {49GCS_SEND_TEXT(MAV_SEVERITY_ERROR, "SBG ExternalAHRS no UART");50return;51}52baudrate = sm.find_baudrate(AP_SerialManager::SerialProtocol_AHRS, 0);53port_num = sm.find_portnum(AP_SerialManager::SerialProtocol_AHRS, 0);545556// don't offer IMU by default, at 200Hz it is too slow for many aircraft57set_default_sensors(uint16_t(AP_ExternalAHRS::AvailableSensor::GPS) |58uint16_t(AP_ExternalAHRS::AvailableSensor::BARO) |59uint16_t(AP_ExternalAHRS::AvailableSensor::COMPASS));6061if (!hal.scheduler->thread_create(FUNCTOR_BIND_MEMBER(&AP_ExternalAHRS_SBG::update_thread, void), "SBG", 4096, AP_HAL::Scheduler::PRIORITY_SPI, 0)) {62AP_HAL::panic("SBG Failed to start ExternalAHRS update thread");63}64GCS_SEND_TEXT(MAV_SEVERITY_INFO, "SBG ExternalAHRS initialised");65}6667void AP_ExternalAHRS_SBG::update_thread()68{69hal.scheduler->delay(1000);70while (!hal.scheduler->is_system_initialized()) {71hal.scheduler->delay(100);72}73hal.scheduler->delay(1000);7475if (uart == nullptr) {76return;77}7879uart->begin(baudrate, 1024, 1024);8081setup_complete = true;8283while (true) {8485if (check_uart()) {86// we've parsed something. There might be more so lets come back quickly87hal.scheduler->delay_microseconds(100);88continue;89}9091// uart is idle, lets snooze a little more and then do some housekeeping92hal.scheduler->delay_microseconds(250);9394const uint32_t now_ms = AP_HAL::millis();95if (cached.sbg.sbgEComDeviceInfo.firmwareRev == 0 && now_ms - version_check_ms >= 5000) {96// request Device Info every few seconds until we get a response97version_check_ms = now_ms;9899// static uint32_t count = 1;100// GCS_SEND_TEXT(MAV_SEVERITY_INFO, "SBG Requesting DeviceInfo %u", count++);101102const sbgMessage msg = sbgMessage(SBG_ECOM_CLASS_LOG_CMD_0, SBG_ECOM_CMD_INFO);103UNUSED_RESULT(send_sbgMessage(uart, msg)); // don't care about any error, just retry at 1Hz104105#if AP_COMPASS_ENABLED106} else if (now_ms - send_MagData_ms >= 100) {107send_MagData_ms = now_ms;108109if (!send_MagData(uart)) {110// TODO: if it fails maybe we should figure out why and retry?111// possible causes:112// 1) uart == nullptr113// 2) msg.len > sizeof(data)114// 3) did not write all the bytes out the uart: zero/fail is treated same as packet_len != bytes_sent115if (now_ms - send_mag_error_last_ms >= 5000) {116// throttle the error to no faster than 5Hz because if you get one error you'll likely get a lot117send_mag_error_last_ms = now_ms;118GCS_SEND_TEXT(MAV_SEVERITY_INFO, "SBG: Error sending Mag data");119}120}121#endif // AP_COMPASS_ENABLED122123#if AP_BARO_ENABLED || AP_AIRSPEED_ENABLED124} else if (now_ms - send_AirData_ms >= 100) {125send_AirData_ms = now_ms;126127if (!send_AirData(uart)) {128// TODO: if it fails maybe we should figure out why and retry?129// possible causes:130// 1) uart == nullptr131// 2) msg.len > sizeof(data)132// 3) did not write all the bytes out the uart: zero/fail is treated same as packet_len != bytes_sent133if (now_ms - send_air_error_last_ms >= 5000) {134// throttle the error to no faster than 5Hz because if you get one error you'll likely get a lot135send_air_error_last_ms = now_ms;136GCS_SEND_TEXT(MAV_SEVERITY_INFO, "SBG: Error sending Air data");137}138}139#endif // AP_BARO_ENABLED || AP_AIRSPEED_ENABLED140}141} // while142}143144// Builds packets by looking at each individual byte, once a full packet has been read in it checks the checksum then handles the packet.145// returns true if any data was found in the UART buffer which was then processed146bool AP_ExternalAHRS_SBG::check_uart()147{148uint32_t nbytes = MIN(uart->available(), 512u);149if (nbytes == 0) {150return false;151}152while (nbytes--> 0) {153uint8_t b;154if (!uart->read(b)) {155break;156}157158if (parse_byte(b, _inbound_state.msg, _inbound_state)) {159handle_msg(_inbound_state.msg);160}161}162return true;163}164165166bool AP_ExternalAHRS_SBG::send_sbgMessage(AP_HAL::UARTDriver *_uart, const sbgMessage &msg)167{168if (_uart == nullptr || msg.len > ARRAY_SIZE(msg.data)) {169// invalid _uart or msg.len is out of range170return false;171}172173const uint16_t buffer_len = (SBG_PACKET_OVERHEAD + msg.len);174uint8_t buffer[buffer_len];175176buffer[0] = SBG_PACKET_SYNC1;177buffer[1] = SBG_PACKET_SYNC2;178buffer[2] = msg.msgid;179buffer[3] = msg.msgclass;180buffer[4] = msg.len & 0xFF; // LSB first181buffer[5] = msg.len >> 8;182183for (uint16_t i=0; i<msg.len; i++) {184buffer[6+i] = msg.data[i];185}186187const uint16_t crc = crc16_ccitt_r((const uint8_t*)&msg, msg.len+4, 0, 0);188189buffer[buffer_len-3] = crc & 0xFF; // LSB First190buffer[buffer_len-2] = crc >> 8;191buffer[buffer_len-1] = SBG_PACKET_ETX;192193const uint32_t bytes_sent = _uart->write(buffer, buffer_len);194_uart->flush();195return (bytes_sent == buffer_len);196}197198bool AP_ExternalAHRS_SBG::parse_byte(const uint8_t data, sbgMessage &msg, SBG_PACKET_INBOUND_STATE &inbound_state)199{200switch (inbound_state.parser) {201case SBG_PACKET_PARSE_STATE::SYNC1:202inbound_state.parser = (data == SBG_PACKET_SYNC1) ? SBG_PACKET_PARSE_STATE::SYNC2 : SBG_PACKET_PARSE_STATE::SYNC1;203break;204205case SBG_PACKET_PARSE_STATE::SYNC2:206inbound_state.parser = (data == SBG_PACKET_SYNC2) ? SBG_PACKET_PARSE_STATE::MSG : SBG_PACKET_PARSE_STATE::SYNC1;207break;208209case SBG_PACKET_PARSE_STATE::MSG:210msg.msgid = data;211inbound_state.parser = SBG_PACKET_PARSE_STATE::CLASS;212break;213214case SBG_PACKET_PARSE_STATE::CLASS:215msg.msgclass = data;216inbound_state.parser = SBG_PACKET_PARSE_STATE::LEN1;217break;218219case SBG_PACKET_PARSE_STATE::LEN1:220msg.len = data;221inbound_state.parser = SBG_PACKET_PARSE_STATE::LEN2;222break;223224case SBG_PACKET_PARSE_STATE::LEN2:225msg.len |= uint16_t(data) << 8;226if (msg.len > sizeof(msg.data)) {227// we can't handle this packet, it's larger than the rx buffer which is larger than the largest packet we care about228inbound_state.data_count_skip = msg.len;229inbound_state.parser = SBG_PACKET_PARSE_STATE::DROP_THIS_PACKET;230} else {231inbound_state.data_count = 0;232inbound_state.parser = (msg.len > 0) ? SBG_PACKET_PARSE_STATE::DATA : SBG_PACKET_PARSE_STATE::CRC1;233}234break;235236case SBG_PACKET_PARSE_STATE::DATA:237msg.data[inbound_state.data_count++] = data;238if (inbound_state.data_count >= sizeof(msg.data)) {239inbound_state.parser = SBG_PACKET_PARSE_STATE::SYNC1;240} else if (inbound_state.data_count >= msg.len) {241inbound_state.parser = SBG_PACKET_PARSE_STATE::CRC1;242}243break;244245case SBG_PACKET_PARSE_STATE::CRC1:246inbound_state.crc = data;247inbound_state.parser = SBG_PACKET_PARSE_STATE::CRC2;248break;249250case SBG_PACKET_PARSE_STATE::CRC2:251inbound_state.crc |= uint16_t(data) << 8;252inbound_state.parser = SBG_PACKET_PARSE_STATE::SYNC1; // skip ETX and go directly to SYNC1. Do not pass Go.253{254// CRC field is computed on [MSG(1), CLASS(1), LEN(2), DATA(msg.len)] fields255const uint16_t crc = crc16_ccitt_r((const uint8_t*)&msg, msg.len+4, 0, 0);256if (crc == inbound_state.crc) {257return true;258}259// GCS_SEND_TEXT(MAV_SEVERITY_INFO, "SBG: Failed CRC. Received 0x%04X, Expected 0x%04X", (unsigned)inbound_state.crc, (unsigned)crc);260}261break;262263case SBG_PACKET_PARSE_STATE::ETX:264// we can just skip this state and let SYNC1 fail once when it gets the ETX byte every time... same amount of work265inbound_state.parser = SBG_PACKET_PARSE_STATE::SYNC1;266break;267268default:269case SBG_PACKET_PARSE_STATE::DROP_THIS_PACKET:270// we're currently parsing a packet that is very large and is not one we care271// about. This is not the packet you're looking for... drop all those bytes272if (inbound_state.data_count_skip > 0) {273inbound_state.data_count_skip--;274}275if (inbound_state.data_count_skip == 0) {276inbound_state.parser = SBG_PACKET_PARSE_STATE::SYNC1;277}278break;279}280281return false;282}283284void AP_ExternalAHRS_SBG::handle_msg(const sbgMessage &msg)285{286const uint32_t now_ms = AP_HAL::millis();287const bool valid_class1_msg = ((SbgEComClass)msg.msgclass == SBG_ECOM_CLASS_LOG_ECOM_1 && (_SbgEComLog1MsgId)msg.msgid == SBG_ECOM_LOG_FAST_IMU_DATA);288289if ((SbgEComClass)msg.msgclass == SBG_ECOM_CLASS_LOG_CMD_0) {290switch ((SbgEComCmd)msg.msgid) {291case SBG_ECOM_CMD_ACK: // 0292break;293294case SBG_ECOM_CMD_INFO: // 4295safe_copy_msg_to_object((uint8_t*)&cached.sbg.sbgEComDeviceInfo, sizeof(cached.sbg.sbgEComDeviceInfo), msg.data, msg.len);296297GCS_SEND_TEXT(MAV_SEVERITY_INFO, "SBG: %s", cached.sbg.sbgEComDeviceInfo.productCode);298// GCS_SEND_TEXT(MAV_SEVERITY_INFO, "SBG: %u, %u, %u, %u, %u, %u, %u: %u.%u.%u",299// (unsigned)cached.sbg.sbgEComDeviceInfo.serialNumber,300// (unsigned)cached.sbg.sbgEComDeviceInfo.calibationRev,301// (unsigned)cached.sbg.sbgEComDeviceInfo.calibrationYear,302// (unsigned)cached.sbg.sbgEComDeviceInfo.calibrationMonth,303// (unsigned)cached.sbg.sbgEComDeviceInfo.calibrationDay,304// (unsigned)cached.sbg.sbgEComDeviceInfo.hardwareRev,305// (unsigned)cached.sbg.sbgEComDeviceInfo.firmwareRev,306// (cached.sbg.sbgEComDeviceInfo.firmwareRev >> 22) & 0x3F,307// (cached.sbg.sbgEComDeviceInfo.firmwareRev >> 16) & 0x3F,308// cached.sbg.sbgEComDeviceInfo.firmwareRev & 0xFFFF309310// GCS_SEND_TEXT(MAV_SEVERITY_INFO, "SBG: Serial Number: %u",(unsigned)cached.sbg.sbgEComDeviceInfo.serialNumber);311GCS_SEND_TEXT(MAV_SEVERITY_INFO, "SBG: Version HW v%u.%u FW v%u.%u.%u",312(unsigned)(cached.sbg.sbgEComDeviceInfo.hardwareRev >> 24) & 0x00FF,313(unsigned)(cached.sbg.sbgEComDeviceInfo.hardwareRev >> 16) & 0x00FF,314315(unsigned) (cached.sbg.sbgEComDeviceInfo.firmwareRev >> 22) & 0x003F,316(unsigned)(cached.sbg.sbgEComDeviceInfo.firmwareRev >> 16) & 0x003F,317(unsigned)cached.sbg.sbgEComDeviceInfo.firmwareRev & 0xFFFF);318break;319320case SBG_ECOM_CMD_COMPUTE_MAG_CALIB: // 15321break;322323default:324// GCS_SEND_TEXT(MAV_SEVERITY_INFO, "SBG: Unknown ID=%u, CLASS=%u, LEN=%u", (unsigned)msg.msgid, (unsigned)msg.msgclass, (unsigned)msg.len);325break;326}327return;328}329330331332if (((SbgEComClass)msg.msgclass != SBG_ECOM_CLASS_LOG_ECOM_0) && !valid_class1_msg) {333// GCS_SEND_TEXT(MAV_SEVERITY_INFO, "SBG: Unknown ID=%u, CLASS=%u, LEN=%u", (unsigned)msg.msgid, (unsigned)msg.msgclass, (unsigned)msg.len);334return;335}336337bool updated_gps = false;338bool updated_baro = false;339bool updated_ins = false;340bool updated_mag = false;341bool updated_airspeed = false;342343{344WITH_SEMAPHORE(state.sem);345346switch (msg.msgid) { // (_SbgEComLog)347case SBG_ECOM_LOG_FAST_IMU_DATA: // 0348safe_copy_msg_to_object((uint8_t*)&cached.sbg.sbgLogFastImuData, sizeof(cached.sbg.sbgLogFastImuData), msg.data, msg.len);349350state.accel = Vector3f(cached.sbg.sbgLogFastImuData.accelerometers[0], cached.sbg.sbgLogFastImuData.accelerometers[1], cached.sbg.sbgLogFastImuData.accelerometers[2]);351state.gyro = Vector3f(cached.sbg.sbgLogFastImuData.gyroscopes[0], cached.sbg.sbgLogFastImuData.gyroscopes[1], cached.sbg.sbgLogFastImuData.gyroscopes[2]);352updated_ins = true;353break;354355case SBG_ECOM_LOG_UTC_TIME: // 2356safe_copy_msg_to_object((uint8_t*)&cached.sbg.sbgLogUtcData, sizeof(cached.sbg.sbgLogUtcData), msg.data, msg.len);357358if (sbgEComLogUtcGetClockStatus(cached.sbg.sbgLogUtcData.status) != SBG_ECOM_CLOCK_VALID ||359sbgEComLogUtcGetClockUtcStatus(cached.sbg.sbgLogUtcData.status) != SBG_ECOM_UTC_VALID)360{361// Data is not valid, ignore it.362// This will happen even with a GPS fix = 3. A 3D lock is not enough, a valid363// clock has a higher threshold of quiality needed. It also needs time to sync364// its internal timing and PPS outputs before valid.365break;366}367368// GCS_SEND_TEXT(MAV_SEVERITY_INFO, "------------------");369// GCS_SEND_TEXT(MAV_SEVERITY_INFO, "SBG: %u-%u-%u %02u:%02u:%02u",370// cached.sbg.sbgLogUtcData.year,371// (unsigned)cached.sbg.sbgLogUtcData.month,372// (unsigned)cached.sbg.sbgLogUtcData.day,373// (unsigned)cached.sbg.sbgLogUtcData.hour,374// (unsigned)cached.sbg.sbgLogUtcData.minute,375// (unsigned)cached.sbg.sbgLogUtcData.second);376377#if AP_RTC_ENABLED378{379const uint32_t utc_epoch_sec = AP::rtc().date_fields_to_clock_s(380cached.sbg.sbgLogUtcData.year,381cached.sbg.sbgLogUtcData.month,382cached.sbg.sbgLogUtcData.day,383cached.sbg.sbgLogUtcData.hour,384cached.sbg.sbgLogUtcData.minute,385cached.sbg.sbgLogUtcData.second);386387const uint64_t utc_epoch_usec = ((uint64_t)utc_epoch_sec) * 1E6 + (cached.sbg.sbgLogUtcData.nanoSecond * 1E-3);388AP::rtc().set_utc_usec(utc_epoch_usec, AP_RTC::SOURCE_GPS);389390}391#endif // AP_RTC_ENABLED392393cached.sensors.gps_data.ms_tow = cached.sbg.sbgLogUtcData.gpsTimeOfWeek;394cached.sensors.gps_data.gps_week = cached.sbg.sbgLogUtcData.gpsTimeOfWeek / AP_MSEC_PER_WEEK;395updated_gps = true;396break;397398case SBG_ECOM_LOG_IMU_SHORT: // 44399safe_copy_msg_to_object((uint8_t*)&cached.sbg.sbgEComLogImuShort, sizeof(cached.sbg.sbgEComLogImuShort), msg.data, msg.len);400401{402Vector3f temp;403/*!< X, Y, Z delta velocity. Unit is 1048576 LSB for 1 m.s^-2. */404temp = Vector3f(cached.sbg.sbgEComLogImuShort.deltaVelocity[0], cached.sbg.sbgEComLogImuShort.deltaVelocity[1], cached.sbg.sbgEComLogImuShort.deltaVelocity[2]);405state.accel = temp / SBG_ECOM_LOG_IMU_ACCEL_SCALE_STD;406407/*!< 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. */408temp = Vector3f(cached.sbg.sbgEComLogImuShort.deltaAngle[0], cached.sbg.sbgEComLogImuShort.deltaAngle[1], cached.sbg.sbgEComLogImuShort.deltaAngle[2]);409const float scaleFactor = (cached.sbg.sbgEComLogImuShort.status & SBG_ECOM_IMU_GYROS_USE_HIGH_SCALE) ? SBG_ECOM_LOG_IMU_GYRO_SCALE_HIGH : SBG_ECOM_LOG_IMU_GYRO_SCALE_STD;410state.gyro = temp / scaleFactor;411}412cached.sensors.ins_data.temperature = (cached.sbg.sbgEComLogImuShort.temperature / SBG_ECOM_LOG_IMU_TEMP_SCALE_STD);413updated_ins = true;414break;415416case SBG_ECOM_LOG_IMU_DATA: // 3417safe_copy_msg_to_object((uint8_t*)&cached.sbg.sbgLogImuData, sizeof(cached.sbg.sbgLogImuData), msg.data, msg.len);418419state.accel = Vector3f(cached.sbg.sbgLogImuData.accelerometers[0], cached.sbg.sbgLogImuData.accelerometers[1], cached.sbg.sbgLogImuData.accelerometers[2]);420state.gyro = Vector3f(cached.sbg.sbgLogImuData.gyroscopes[0], cached.sbg.sbgLogImuData.gyroscopes[1], cached.sbg.sbgLogImuData.gyroscopes[2]);421cached.sensors.ins_data.temperature = cached.sbg.sbgLogImuData.temperature;422updated_ins = true;423break;424425case SBG_ECOM_LOG_MAG: // 4426safe_copy_msg_to_object((uint8_t*)&cached.sbg.sbgLogMag, sizeof(cached.sbg.sbgLogMag), msg.data, msg.len);427428state.accel = Vector3f(cached.sbg.sbgLogMag.accelerometers[0], cached.sbg.sbgLogMag.accelerometers[1], cached.sbg.sbgLogMag.accelerometers[2]);429updated_ins = true;430431cached.sensors.mag_data.field = Vector3f(cached.sbg.sbgLogMag.magnetometers[0], cached.sbg.sbgLogMag.magnetometers[1], cached.sbg.sbgLogMag.magnetometers[2]);432updated_mag = true;433break;434435case SBG_ECOM_LOG_EKF_EULER: // 6436safe_copy_msg_to_object((uint8_t*)&cached.sbg.sbgLogEkfEulerData, sizeof(cached.sbg.sbgLogEkfEulerData), msg.data, msg.len);437438// float euler[3]; /*!< Roll, Pitch and Yaw angles in rad. */439// float eulerStdDev[3]; /*!< Roll, Pitch and Yaw angles 1 sigma standard deviation in rad. */440state.quat.from_euler(cached.sbg.sbgLogEkfEulerData.euler[0], cached.sbg.sbgLogEkfEulerData.euler[1], cached.sbg.sbgLogEkfEulerData.euler[2]);441state.have_quaternion = true;442break;443444case SBG_ECOM_LOG_EKF_QUAT: // 7445safe_copy_msg_to_object((uint8_t*)&cached.sbg.sbgLogEkfQuatData, sizeof(cached.sbg.sbgLogEkfQuatData), msg.data, msg.len);446447// float quaternion[4]; /*!< Orientation quaternion stored in W, X, Y, Z form. */448// float eulerStdDev[3]; /*!< Roll, Pitch and Yaw angles 1 sigma standard deviation in rad. */449memcpy(&state.quat, cached.sbg.sbgLogEkfQuatData.quaternion, sizeof(state.quat));450state.have_quaternion = true;451break;452453case SBG_ECOM_LOG_EKF_NAV: // 8454safe_copy_msg_to_object((uint8_t*)&cached.sbg.sbgLogEkfNavData, sizeof(cached.sbg.sbgLogEkfNavData), msg.data, msg.len);455456state.velocity = Vector3f(cached.sbg.sbgLogEkfNavData.velocity[0], cached.sbg.sbgLogEkfNavData.velocity[1], cached.sbg.sbgLogEkfNavData.velocity[2]);457state.have_velocity = true;458459state.location = Location(cached.sbg.sbgLogEkfNavData.position[0]*1e7, cached.sbg.sbgLogEkfNavData.position[1]*1e7, cached.sbg.sbgLogEkfNavData.position[2]*1e2, Location::AltFrame::ABSOLUTE);460state.last_location_update_us = AP_HAL::micros();461if (!state.have_location) {462state.have_location = true;463} else if (!state.have_origin && cached.sensors.gps_data.fix_type >= AP_GPS_FixType::FIX_3D && SbgEkfStatus_is_fullNav(cached.sbg.sbgLogEkfNavData.status)) {464// 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 origin465state.origin = state.location;466state.have_origin = true;467}468break;469470case SBG_ECOM_LOG_GPS1_VEL: // 13471case SBG_ECOM_LOG_GPS2_VEL: // 16472safe_copy_msg_to_object((uint8_t*)&cached.sbg.sbgLogGpsVel, sizeof(cached.sbg.sbgLogGpsVel), msg.data, msg.len);473474cached.sensors.gps_data.ms_tow = cached.sbg.sbgLogGpsVel.timeOfWeek;475cached.sensors.gps_data.ned_vel_north = cached.sbg.sbgLogGpsVel.velocity[0];476cached.sensors.gps_data.ned_vel_east = cached.sbg.sbgLogGpsVel.velocity[1];477cached.sensors.gps_data.ned_vel_down = cached.sbg.sbgLogGpsVel.velocity[2];478cached.sensors.gps_data.horizontal_vel_accuracy = Vector2f(cached.sensors.gps_data.ned_vel_north, cached.sensors.gps_data.ned_vel_east).length();479// unused - cached.sbg.sbgLogGpsVel.course480// unused - cached.sbg.sbgLogGpsVel.courseAcc481updated_gps = true;482break;483484case SBG_ECOM_LOG_GPS1_POS: // 14485case SBG_ECOM_LOG_GPS2_POS: // 17486safe_copy_msg_to_object((uint8_t*)&cached.sbg.sbgLogGpsPos, sizeof(cached.sbg.sbgLogGpsPos), msg.data, msg.len);487488cached.sensors.gps_data.ms_tow = cached.sbg.sbgLogGpsPos.timeOfWeek;489cached.sensors.gps_data.gps_week = cached.sbg.sbgLogGpsPos.timeOfWeek / AP_MSEC_PER_WEEK;490cached.sensors.gps_data.latitude = cached.sbg.sbgLogGpsPos.latitude * 1E7;491cached.sensors.gps_data.longitude = cached.sbg.sbgLogGpsPos.longitude * 1E7;492cached.sensors.gps_data.msl_altitude = cached.sbg.sbgLogGpsPos.altitude * 100;493// unused - cached.sbg.sbgLogGpsPos.undulation494cached.sensors.gps_data.horizontal_pos_accuracy = Vector2f(cached.sbg.sbgLogGpsPos.latitudeAccuracy, cached.sbg.sbgLogGpsPos.longitudeAccuracy).length();495cached.sensors.gps_data.hdop = cached.sensors.gps_data.horizontal_pos_accuracy;496cached.sensors.gps_data.vertical_pos_accuracy = cached.sbg.sbgLogGpsPos.altitudeAccuracy;497cached.sensors.gps_data.vdop = cached.sensors.gps_data.vertical_pos_accuracy;498cached.sensors.gps_data.satellites_in_view = cached.sbg.sbgLogGpsPos.numSvUsed;499// unused - cached.sbg.sbgLogGpsPos.baseStationId500// unused - cached.sbg.sbgLogGpsPos.differentialAge501cached.sensors.gps_data.fix_type = SbgGpsPosStatus_to_GpsFixType(cached.sbg.sbgLogGpsPos.status);502updated_gps = true;503break;504505case SBG_ECOM_LOG_AIR_DATA: // 36506safe_copy_msg_to_object((uint8_t*)&cached.sbg.sbgLogAirData, sizeof(cached.sbg.sbgLogAirData), msg.data, msg.len);507508if (cached.sbg.sbgLogAirData.status & SBG_ECOM_AIR_DATA_PRESSURE_ABS_VALID) {509cached.sensors.baro_data.pressure_pa = cached.sbg.sbgLogAirData.pressureAbs;510updated_baro = true;511}512if (cached.sbg.sbgLogAirData.status & SBG_ECOM_AIR_DATA_ALTITUDE_VALID) {513cached.sensors.baro_height = cached.sbg.sbgLogAirData.altitude;514updated_baro = true;515}516if (cached.sbg.sbgLogAirData.status & SBG_ECOM_AIR_DATA_PRESSURE_DIFF_VALID) {517cached.sensors.airspeed_data.differential_pressure = cached.sbg.sbgLogAirData.pressureDiff;518updated_airspeed = true;519}520// if (cached.sbg.sbgLogAirData.status & SBG_ECOM_AIR_DATA_AIRPSEED_VALID) {521// // we don't accept airspeed directly, we compute it ourselves in AP_Airspeed via diff pressure522// }523524if ((cached.sbg.sbgLogAirData.status & SBG_ECOM_AIR_DATA_TEMPERATURE_VALID) && (updated_baro || updated_airspeed)) {525cached.sensors.airspeed_data.temperature = cached.sbg.sbgLogAirData.airTemperature;526cached.sensors.baro_data.temperature = cached.sbg.sbgLogAirData.airTemperature;527}528break;529530default:531// GCS_SEND_TEXT(MAV_SEVERITY_INFO, "SBG: unhandled ID=%u, CLASS=%u, LEN=%u", (unsigned)msg.msgid, (unsigned)msg.msgclass, (unsigned)msg.len);532return;533} // switch msgid534} // semaphore535536#if AP_GPS_ENABLED537if (updated_gps) {538cached.sensors.gps_ms = now_ms;539uint8_t instance;540if (AP::gps().get_first_external_instance(instance)) {541AP::gps().handle_external(cached.sensors.gps_data, instance);542}543}544#else545(void)updated_gps;546#endif547548#if AP_COMPASS_EXTERNALAHRS_ENABLED549if (updated_mag) {550cached.sensors.mag_ms = now_ms;551AP::compass().handle_external(cached.sensors.mag_data);552}553#else554(void)updated_mag;555#endif556557#if AP_BARO_EXTERNALAHRS_ENABLED558if (updated_baro) {559cached.sensors.baro_ms = now_ms;560cached.sensors.baro_data.instance = 0;561AP::baro().handle_external(cached.sensors.baro_data);562}563#else564(void)updated_baro;565#endif566567#if AP_AIRSPEED_EXTERNAL_ENABLED && (APM_BUILD_COPTER_OR_HELI || APM_BUILD_TYPE(APM_BUILD_ArduPlane))568if (updated_airspeed && AP::airspeed() != nullptr) {569cached.sensors.airspeed_ms = now_ms;570AP::airspeed()->handle_external(cached.sensors.airspeed_data);571}572#else573(void)updated_airspeed;574#endif575576577if (updated_ins) {578cached.sensors.ins_data.accel = state.accel;579cached.sensors.ins_data.gyro = state.gyro;580cached.sensors.ins_ms = now_ms;581AP::ins().handle_external(cached.sensors.ins_data);582}583584last_received_ms = now_ms;585}586587void 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)588{589// To allow for future changes of the SBG protocol the protocol allows extending messages590// which can be detected by a length mismatch, usually longer. To allow for this you assume unused data is zero591// but instead of zeroing the packet every time before populating it it's best to only zero when necessary.592if (dest_len != src_len) {593memset(dest, 0, dest_len);594}595memcpy(dest, src, MIN(dest_len,src_len));596}597598bool AP_ExternalAHRS_SBG::SbgEkfStatus_is_fullNav(const uint32_t ekfStatus)599{600SbgEComSolutionMode solutionMode = (SbgEComSolutionMode)(ekfStatus & SBG_ECOM_LOG_EKF_SOLUTION_MODE_MASK);601602return (solutionMode == SBG_ECOM_SOL_MODE_NAV_POSITION);603}604605AP_GPS_FixType AP_ExternalAHRS_SBG::SbgGpsPosStatus_to_GpsFixType(const uint32_t gpsPosStatus)606{607const uint32_t fix = (gpsPosStatus >> SBG_ECOM_GPS_POS_TYPE_SHIFT) & SBG_ECOM_GPS_POS_TYPE_MASK;608switch ((SbgEComGpsPosType)fix) {609case SBG_ECOM_POS_NO_SOLUTION: /*!< No valid solution available. */610case SBG_ECOM_POS_UNKNOWN_TYPE: /*!< An unknown solution type has been computed. */611return AP_GPS_FixType::NONE;612613case SBG_ECOM_POS_SINGLE: /*!< Single point solution position. */614return AP_GPS_FixType::FIX_3D;615616case SBG_ECOM_POS_PSRDIFF: /*!< Standard Pseudorange Differential Solution (DGPS). */617case SBG_ECOM_POS_SBAS: /*!< SBAS satellite used for differential corrections. */618case SBG_ECOM_POS_OMNISTAR: /*!< Omnistar VBS Position (L1 sub-meter). */619return AP_GPS_FixType::DGPS;620621case SBG_ECOM_POS_RTK_FLOAT: /*!< Floating RTK ambiguity solution (20 cms RTK). */622case SBG_ECOM_POS_PPP_FLOAT: /*!< Precise Point Positioning with float ambiguities. */623return AP_GPS_FixType::RTK_FLOAT;624625case SBG_ECOM_POS_RTK_INT: /*!< Integer RTK ambiguity solution (2 cms RTK). */626case SBG_ECOM_POS_PPP_INT: /*!< Precise Point Positioning with fixed ambiguities. */627case SBG_ECOM_POS_FIXED: /*!< Fixed location solution position. */628return AP_GPS_FixType::RTK_FIXED;629}630return AP_GPS_FixType::NONE;631}632633void AP_ExternalAHRS_SBG::get_filter_status(nav_filter_status &status) const634{635WITH_SEMAPHORE(state.sem);636memset(&status, 0, sizeof(status));637638if (cached.sensors.ins_ms != 0 && cached.sensors.gps_ms != 0) {639status.flags.initalized = true;640}641if (!healthy()) {642return;643}644645if (state.have_location) {646status.flags.vert_pos = true;647status.flags.horiz_pos_rel = true;648status.flags.horiz_pos_abs = true;649status.flags.pred_horiz_pos_rel = true;650status.flags.pred_horiz_pos_abs = true;651status.flags.using_gps = true;652}653654if (state.have_quaternion) {655status.flags.attitude = true;656}657658if (state.have_velocity) {659status.flags.vert_vel = true;660status.flags.horiz_vel = true;661}662}663664bool AP_ExternalAHRS_SBG::pre_arm_check(char *failure_msg, uint8_t failure_msg_len) const665{666if (!setup_complete) {667hal.util->snprintf(failure_msg, failure_msg_len, "SBG setup failed");668return false;669}670if (!healthy()) {671hal.util->snprintf(failure_msg, failure_msg_len, "SBG unhealthy");672return false;673}674return true;675}676677#if AP_COMPASS_ENABLED678bool AP_ExternalAHRS_SBG::send_MagData(AP_HAL::UARTDriver *_uart)679{680SbgLogMag mag_data_log {};681mag_data_log.timeStamp = AP_HAL::micros();682683const auto &compass = AP::compass();684if (compass.available() || compass.healthy()) {685// TODO: consider also checking compass.last_update_usec() to only send when we have new data686687const Vector3f mag_field = compass.get_field();688mag_data_log.magnetometers[0] = mag_field[0];689mag_data_log.magnetometers[1] = mag_field[1];690mag_data_log.magnetometers[2] = mag_field[2];691692mag_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);693}694695const sbgMessage msg = sbgMessage(SBG_ECOM_CLASS_LOG_ECOM_0, SBG_ECOM_LOG_MAG, (uint8_t*)&mag_data_log, sizeof(mag_data_log));696return send_sbgMessage(_uart, msg);697}698#endif // AP_COMPASS_ENABLED699700bool AP_ExternalAHRS_SBG::send_AirData(AP_HAL::UARTDriver *_uart)701{702SbgLogAirData air_data_log {};703air_data_log.timeStamp = 0;704air_data_log.status |= SBG_ECOM_AIR_DATA_TIME_IS_DELAY;705706#if AP_BARO_ENABLED707const auto &baro = AP::baro();708if (baro.healthy()) {709air_data_log.pressureAbs = baro.get_pressure();710air_data_log.altitude = baro.get_altitude();711air_data_log.airTemperature = baro.get_temperature();712713air_data_log.status |= (SBG_ECOM_AIR_DATA_PRESSURE_ABS_VALID | SBG_ECOM_AIR_DATA_ALTITUDE_VALID | SBG_ECOM_AIR_DATA_TEMPERATURE_VALID);714}715#endif // AP_BARO_ENABLED716717#if AP_AIRSPEED_ENABLED718auto *airspeed = AP::airspeed();719if (airspeed != nullptr && airspeed->healthy()) {720float airTemperature;721if (airspeed->get_temperature(airTemperature)) {722air_data_log.airTemperature = airTemperature;723air_data_log.status |= SBG_ECOM_AIR_DATA_TEMPERATURE_VALID;724}725726air_data_log.pressureDiff = airspeed->get_differential_pressure();727air_data_log.trueAirspeed = airspeed->get_airspeed();728air_data_log.status |= (SBG_ECOM_AIR_DATA_PRESSURE_DIFF_VALID | SBG_ECOM_AIR_DATA_AIRPSEED_VALID);729}730#endif // AP_AIRSPEED_ENABLED731732const sbgMessage msg = sbgMessage(SBG_ECOM_CLASS_LOG_ECOM_0, SBG_ECOM_LOG_AIR_DATA, (uint8_t*)&air_data_log, sizeof(air_data_log));733return send_sbgMessage(_uart, msg);734}735736#endif // AP_EXTERNAL_AHRS_SBG_ENABLED737738739740