/* This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ /* support for serial connected InertialLabs INS */ #include "AP_ExternalAHRS_config.h" #if AP_EXTERNAL_AHRS_INERTIALLABS_ENABLED #include "AP_ExternalAHRS_InertialLabs.h" #include #include #include #include #include #include #include #include #include #include #include #include #include extern const AP_HAL::HAL &hal; // unit status bits #define ILABS_UNIT_STATUS_ALIGNMENT_FAIL 0x0001 #define ILABS_UNIT_STATUS_OPERATION_FAIL 0x0002 #define ILABS_UNIT_STATUS_GYRO_FAIL 0x0004 #define ILABS_UNIT_STATUS_ACCEL_FAIL 0x0008 #define ILABS_UNIT_STATUS_MAG_FAIL 0x0010 #define ILABS_UNIT_STATUS_ELECTRONICS_FAIL 0x0020 #define ILABS_UNIT_STATUS_GNSS_FAIL 0x0040 #define ILABS_UNIT_STATUS_RUNTIME_CAL 0x0080 #define ILABS_UNIT_STATUS_VOLTAGE_LOW 0x0100 #define ILABS_UNIT_STATUS_VOLTAGE_HIGH 0x0200 #define ILABS_UNIT_STATUS_X_RATE_HIGH 0x0400 #define ILABS_UNIT_STATUS_Y_RATE_HIGH 0x0800 #define ILABS_UNIT_STATUS_Z_RATE_HIGH 0x1000 #define ILABS_UNIT_STATUS_MAG_FIELD_HIGH 0x2000 #define ILABS_UNIT_STATUS_TEMP_RANGE_ERR 0x4000 #define ILABS_UNIT_STATUS_RUNTIME_CAL2 0x8000 // unit status2 bits #define ILABS_UNIT_STATUS2_ACCEL_X_HIGH 0x0001 #define ILABS_UNIT_STATUS2_ACCEL_Y_HIGH 0x0002 #define ILABS_UNIT_STATUS2_ACCEL_Z_HIGH 0x0004 #define ILABS_UNIT_STATUS2_BARO_FAIL 0x0008 #define ILABS_UNIT_STATUS2_DIFF_PRESS_FAIL 0x0010 #define ILABS_UNIT_STATUS2_MAGCAL_2D_ACT 0x0020 #define ILABS_UNIT_STATUS2_MAGCAL_3D_ACT 0x0040 #define ILABS_UNIT_STATUS2_GNSS_FUSION_OFF 0x0080 #define ILABS_UNIT_STATUS2_DIFF_PRESS_FUSION_OFF 0x0100 #define ILABS_UNIT_STATUS2_MAG_FUSION_OFF 0x0200 #define ILABS_UNIT_STATUS2_GNSS_POS_VALID 0x0400 // air data status bits #define ILABS_AIRDATA_INIT_FAIL 0x0001 #define ILABS_AIRDATA_DIFF_PRESS_INIT_FAIL 0x0002 #define ILABS_AIRDATA_STATIC_PRESS_FAIL 0x0004 #define ILABS_AIRDATA_DIFF_PRESS_FAIL 0x0008 #define ILABS_AIRDATA_STATIC_PRESS_RANGE_ERR 0x0010 #define ILABS_AIRDATA_DIFF_PRESS_RANGE_ERR 0x0020 #define ILABS_AIRDATA_PRESS_ALT_FAIL 0x0100 #define ILABS_AIRDATA_AIRSPEED_FAIL 0x0200 #define ILABS_AIRDATA_BELOW_THRESHOLD 0x0400 // constructor AP_ExternalAHRS_InertialLabs::AP_ExternalAHRS_InertialLabs(AP_ExternalAHRS *_frontend, AP_ExternalAHRS::state_t &_state) : AP_ExternalAHRS_backend(_frontend, _state) { auto &sm = AP::serialmanager(); uart = sm.find_serial(AP_SerialManager::SerialProtocol_AHRS, 0); if (!uart) { GCS_SEND_TEXT(MAV_SEVERITY_ERROR, "InertialLabs ExternalAHRS no UART"); return; } baudrate = sm.find_baudrate(AP_SerialManager::SerialProtocol_AHRS, 0); port_num = sm.find_portnum(AP_SerialManager::SerialProtocol_AHRS, 0); // don't offer IMU by default, at 200Hz it is too slow for many aircraft set_default_sensors(uint16_t(AP_ExternalAHRS::AvailableSensor::GPS) | uint16_t(AP_ExternalAHRS::AvailableSensor::BARO) | uint16_t(AP_ExternalAHRS::AvailableSensor::COMPASS)); if (!hal.scheduler->thread_create(FUNCTOR_BIND_MEMBER(&AP_ExternalAHRS_InertialLabs::update_thread, void), "ILabs", 2048, AP_HAL::Scheduler::PRIORITY_SPI, 0)) { AP_HAL::panic("InertialLabs Failed to start ExternalAHRS update thread"); } GCS_SEND_TEXT(MAV_SEVERITY_INFO, "InertialLabs ExternalAHRS initialised"); } /* re-sync buffer on parse failure */ void AP_ExternalAHRS_InertialLabs::re_sync(void) { if (buffer_ofs > 3) { /* look for the 2 byte header and try to sync to that */ const uint16_t header = 0x55AA; const uint8_t *p = (const uint8_t *)memmem(&buffer[1], buffer_ofs-3, &header, sizeof(header)); if (p != nullptr) { const uint16_t n = p - &buffer[0]; memmove(&buffer[0], p, buffer_ofs - n); buffer_ofs -= n; } else { buffer_ofs = 0; } } else { buffer_ofs = 0; } } // macro for checking we don't run past end of message buffer #define CHECK_SIZE(d) need_re_sync = (message_ofs + (msg_len=sizeof(d)) > buffer_end); if (need_re_sync) break // lookup a message in the msg_types bitmask to see if we received it in this packet #define GOT_MSG(mtype) msg_types.get(unsigned(MessageType::mtype)) /* check header is valid */ bool AP_ExternalAHRS_InertialLabs::check_header(const ILabsHeader *h) const { return h->magic == 0x55AA && h->msg_type == 1 && h->msg_id == 0x95 && h->msg_len <= sizeof(buffer)-2; } /* check the UART for more data returns true if we have consumed potentially valid bytes */ bool AP_ExternalAHRS_InertialLabs::check_uart() { WITH_SEMAPHORE(state.sem); if (!setup_complete) { return false; } // ensure we own the uart uart->begin(0); uint32_t n = uart->available(); if (n == 0) { return false; } if (n + buffer_ofs > sizeof(buffer)) { n = sizeof(buffer) - buffer_ofs; } const ILabsHeader *h = (ILabsHeader *)&buffer[0]; if (buffer_ofs < sizeof(ILabsHeader)) { n = MIN(n, sizeof(ILabsHeader)-buffer_ofs); } else { if (!check_header(h)) { re_sync(); return false; } if (buffer_ofs > h->msg_len+8) { re_sync(); return false; } n = MIN(n, uint32_t(h->msg_len + 2 - buffer_ofs)); } const ssize_t nread = uart->read(&buffer[buffer_ofs], n); if (nread != ssize_t(n)) { re_sync(); return false; } buffer_ofs += n; if (buffer_ofs < sizeof(ILabsHeader)) { return true; } if (!check_header(h)) { re_sync(); return false; } if (buffer_ofs < h->msg_len+2) { /* see if we can read the rest immediately */ const uint16_t needed = h->msg_len+2 - buffer_ofs; if (uart->available() < needed) { // need more data return true; } const ssize_t nread2 = uart->read(&buffer[buffer_ofs], needed); if (nread2 != needed) { re_sync(); return false; } buffer_ofs += nread2; } // check checksum const uint16_t crc1 = crc_sum_of_bytes_16(&buffer[2], buffer_ofs-4); const uint16_t crc2 = le16toh_ptr(&buffer[buffer_ofs-2]); if (crc1 != crc2) { re_sync(); return false; } const uint8_t *buffer_end = &buffer[buffer_ofs]; const uint16_t payload_size = h->msg_len - 6; const uint8_t *payload = &buffer[6]; if (payload_size < 3) { re_sync(); return false; } const uint8_t num_messages = payload[0]; if (num_messages == 0 || num_messages > payload_size-1) { re_sync(); return false; } const uint8_t *message_ofs = &payload[num_messages+1]; bool need_re_sync = false; // bitmask for what types we get Bitmask<256> msg_types; uint32_t now_ms = AP_HAL::millis(); for (uint8_t i=0; i= buffer_end) { re_sync(); return false; } MessageType mtype = (MessageType)payload[1+i]; ILabsData &u = *(ILabsData*)message_ofs; uint8_t msg_len = 0; msg_types.set(unsigned(mtype)); switch (mtype) { case MessageType::GPS_INS_TIME_MS: { // this is the GPS tow timestamp in ms for when the IMU data was sampled CHECK_SIZE(u.gnss_time_ms); gps_data.ms_tow = u.gnss_time_ms; break; } case MessageType::GPS_WEEK: { CHECK_SIZE(u.gnss_week); gps_data.gps_week = u.gnss_week; break; } case MessageType::ACCEL_DATA_HR: { CHECK_SIZE(u.accel_data_hr); // should use 9.8106 instead of GRAVITY_MSS-constant in accordance with the device-documentation ins_data.accel = u.accel_data_hr.tofloat().rfu_to_frd()*9.8106f*1.0e-6; // m/s^2 break; } case MessageType::GYRO_DATA_HR: { CHECK_SIZE(u.gyro_data_hr); ins_data.gyro = u.gyro_data_hr.tofloat().rfu_to_frd()*DEG_TO_RAD*1.0e-5; // rad/s break; } case MessageType::BARO_DATA: { CHECK_SIZE(u.baro_data); baro_data.pressure_pa = u.baro_data.pressure_pa2*2; // Pa state2.baro_alt = u.baro_data.baro_alt*0.01; // m break; } case MessageType::MAG_DATA: { CHECK_SIZE(u.mag_data); mag_data.field = u.mag_data.tofloat().rfu_to_frd()*(10*NTESLA_TO_MGAUSS); // milligauss break; } case MessageType::ORIENTATION_ANGLES: { CHECK_SIZE(u.orientation_angles); state.quat.from_euler(radians(u.orientation_angles.roll*0.01), radians(u.orientation_angles.pitch*0.01), radians(u.orientation_angles.yaw*0.01)); state.have_quaternion = true; if (last_att_ms == 0) { GCS_SEND_TEXT(MAV_SEVERITY_INFO, "InertialLabs: got link"); } last_att_ms = now_ms; break; } case MessageType::VELOCITIES: { CHECK_SIZE(u.velocity); state.velocity = u.velocity.tofloat().rfu_to_frd()*0.01; gps_data.ned_vel_north = state.velocity.x; // m/s gps_data.ned_vel_east = state.velocity.y; // m/s gps_data.ned_vel_down = state.velocity.z; // m/s state.have_velocity = true; last_vel_ms = now_ms; break; } case MessageType::POSITION: { CHECK_SIZE(u.position); state.location.lat = u.position.lat; // deg*1.0e7 state.location.lng = u.position.lon; // deg*1.0e7 state.location.alt = u.position.alt; // m*100 gps_data.latitude = u.position.lat; // deg*1.0e7 gps_data.longitude = u.position.lon; // deg*1.0e7 gps_data.msl_altitude = u.position.alt; // m*100 state.have_location = true; state.last_location_update_us = AP_HAL::micros(); last_pos_ms = now_ms; break; } case MessageType::KF_VEL_COVARIANCE: { CHECK_SIZE(u.kf_vel_covariance); state2.kf_vel_covariance = u.kf_vel_covariance.tofloat().rfu_to_frd(); // mm/s break; } case MessageType::KF_POS_COVARIANCE: { CHECK_SIZE(u.kf_pos_covariance); state2.kf_pos_covariance = u.kf_pos_covariance.tofloat(); // mm break; } case MessageType::UNIT_STATUS: { CHECK_SIZE(u.unit_status); state2.unit_status = u.unit_status; break; } case MessageType::GNSS_EXTENDED_INFO: { CHECK_SIZE(u.gnss_extended_info); gps_data.fix_type = AP_GPS_FixType(u.gnss_extended_info.fix_type+1); gnss_data.spoof_status = u.gnss_extended_info.spoofing_status; break; } case MessageType::NUM_SATS: { CHECK_SIZE(u.num_sats); gps_data.satellites_in_view = u.num_sats; break; } case MessageType::GNSS_POSITION: { CHECK_SIZE(u.gnss_position); gnss_data.lat = u.gnss_position.lat; // deg*1.0e7 gnss_data.lng = u.gnss_position.lon; // deg*1.0e7 gnss_data.alt = u.gnss_position.alt; // mm break; } case MessageType::GNSS_VEL_TRACK: { CHECK_SIZE(u.gnss_vel_track); gnss_data.hor_speed = u.gnss_vel_track.hor_speed*0.01; // m/s gnss_data.ver_speed = u.gnss_vel_track.ver_speed*0.01; // m/s gnss_data.track_over_ground = u.gnss_vel_track.track_over_ground*0.01; // deg break; } case MessageType::GNSS_POS_TIMESTAMP: { CHECK_SIZE(u.gnss_pos_timestamp); gnss_data.pos_timestamp = u.gnss_pos_timestamp; break; } case MessageType::GNSS_INFO_SHORT: { CHECK_SIZE(u.gnss_info_short); gnss_data.info_short = u.gnss_info_short; break; } case MessageType::GNSS_NEW_DATA: { CHECK_SIZE(u.gnss_new_data); gnss_data.new_data = u.gnss_new_data; break; } case MessageType::GNSS_JAM_STATUS: { CHECK_SIZE(u.gnss_jam_status); gnss_data.jam_status = u.gnss_jam_status; break; } case MessageType::DIFFERENTIAL_PRESSURE: { CHECK_SIZE(u.differential_pressure); airspeed_data.differential_pressure = u.differential_pressure*1.0e-4*100; // 100: mbar to Pa break; } case MessageType::TRUE_AIRSPEED: { CHECK_SIZE(u.true_airspeed); state2.true_airspeed = u.true_airspeed*0.01; // m/s break; } case MessageType::WIND_SPEED: { CHECK_SIZE(u.wind_speed); state2.wind_speed = u.wind_speed.tofloat().rfu_to_frd()*0.01; // m/s break; } case MessageType::AIR_DATA_STATUS: { CHECK_SIZE(u.air_data_status); state2.air_data_status = u.air_data_status; break; } case MessageType::SUPPLY_VOLTAGE: { CHECK_SIZE(u.supply_voltage); state2.supply_voltage = u.supply_voltage*0.01; // V break; } case MessageType::TEMPERATURE: { CHECK_SIZE(u.temperature); // assume same temperature for baro and airspeed baro_data.temperature = u.temperature*0.1; // degC airspeed_data.temperature = u.temperature*0.1; // degC ins_data.temperature = u.temperature*0.1; break; } case MessageType::UNIT_STATUS2: { CHECK_SIZE(u.unit_status2); state2.unit_status2 = u.unit_status2; break; } case MessageType::GNSS_ANGLES: { CHECK_SIZE(u.gnss_angles); gnss_data.heading = u.gnss_angles.heading*0.01; // deg gnss_data.pitch = u.gnss_angles.pitch*0.01; // deg break; } case MessageType::GNSS_ANGLE_POS_TYPE: { CHECK_SIZE(u.gnss_angle_pos_type); gnss_data.angle_pos_type = u.gnss_angle_pos_type; break; } case MessageType::GNSS_HEADING_TIMESTAMP: { CHECK_SIZE(u.gnss_heading_timestamp); gnss_data.heading_timestamp = u.gnss_heading_timestamp; break; } case MessageType::GNSS_DOP: { CHECK_SIZE(u.gnss_dop); gnss_data.gdop = u.gnss_dop.gdop*0.1; gnss_data.pdop = u.gnss_dop.pdop*0.1; gnss_data.tdop = u.gnss_dop.tdop*0.1; gps_data.hdop = u.gnss_dop.hdop*0.1; gps_data.vdop = u.gnss_dop.vdop*0.1; break; } case MessageType::INS_SOLUTION_STATUS: { CHECK_SIZE(u.ins_sol_status); state2.ins_sol_status = u.ins_sol_status; break; } } if (msg_len == 0) { // got an unknown message GCS_SEND_TEXT(MAV_SEVERITY_INFO, "InertialLabs: unknown msg 0x%02x", unsigned(mtype)); buffer_ofs = 0; return false; } message_ofs += msg_len; if (msg_len == 0 || need_re_sync) { re_sync(); return false; } } if (h->msg_len != message_ofs-buffer) { // we had stray bytes at the end of the message re_sync(); return false; } if (GOT_MSG(ACCEL_DATA_HR) && GOT_MSG(GYRO_DATA_HR)) { AP::ins().handle_external(ins_data); state.accel = ins_data.accel; state.gyro = ins_data.gyro; #if HAL_LOGGING_ENABLED uint64_t now_us = AP_HAL::micros64(); // @LoggerMessage: ILB1 // @Description: InertialLabs AHRS data1 // @Field: TimeUS: Time since system startup // @Field: GMS: GPS INS time (round) // @Field: GyrX: Gyro X // @Field: GyrY: Gyro Y // @Field: GyrZ: Gyro z // @Field: AccX: Accelerometer X // @Field: AccY: Accelerometer Y // @Field: AccZ: Accelerometer Z AP::logger().WriteStreaming("ILB1", "TimeUS,GMS,GyrX,GyrY,GyrZ,AccX,AccY,AccZ", "s-kkkooo", "F-------", "QIffffff", now_us, gps_data.ms_tow, ins_data.gyro.x, ins_data.gyro.y, ins_data.gyro.z, ins_data.accel.x, ins_data.accel.y, ins_data.accel.z); #endif // HAL_LOGGING_ENABLED } if (GOT_MSG(GPS_INS_TIME_MS) && GOT_MSG(NUM_SATS) && GOT_MSG(GNSS_POSITION) && GOT_MSG(GNSS_NEW_DATA) && GOT_MSG(GNSS_EXTENDED_INFO) && gnss_data.new_data != 0) { uint8_t instance; if (AP::gps().get_first_external_instance(instance)) { AP::gps().handle_external(gps_data, instance); } if (gps_data.satellites_in_view > 3) { if (last_gps_ms == 0) { GCS_SEND_TEXT(MAV_SEVERITY_INFO, "InertialLabs: got GPS lock"); if (!state.have_origin) { state.origin = Location{ gps_data.latitude, gps_data.longitude, gps_data.msl_altitude, Location::AltFrame::ABSOLUTE}; state.have_origin = true; } } last_gps_ms = now_ms; } #if HAL_LOGGING_ENABLED uint64_t now_us = AP_HAL::micros64(); // @LoggerMessage: ILB4 // @Description: InertialLabs AHRS data4 // @Field: TimeUS: Time since system startup // @Field: GMS: GNSS Position timestamp // @Field: GWk: GPS Week // @Field: NSat: Number of satellites // @Field: NewGPS: Indicator of new update of GPS data // @Field: Lat: GNSS Latitude // @Field: Lng: GNSS Longitude // @Field: Alt: GNSS Altitude // @Field: GCrs: GNSS Track over ground // @Field: Spd: GNSS Horizontal speed // @Field: VZ: GNSS Vertical speed AP::logger().WriteStreaming("ILB4", "TimeUS,GMS,GWk,NSat,NewGPS,Lat,Lng,Alt,GCrs,Spd,VZ", "s----DUmhnn", "F----------", "QIHBBffffff", now_us, gnss_data.pos_timestamp, gps_data.gps_week, gps_data.satellites_in_view, gnss_data.new_data, gnss_data.lat*1.0e-7, gnss_data.lng*1.0e-7, gnss_data.alt*0.01, gnss_data.track_over_ground, gnss_data.hor_speed, gnss_data.ver_speed ); // @LoggerMessage: ILB5 // @Description: InertialLabs AHRS data5 // @Field: TimeUS: Time since system startup // @Field: GMS: GNSS Position timestamp // @Field: FType: fix type // @Field: GSS: GNSS spoofing status // @Field: GJS: GNSS jamming status // @Field: GI1: GNSS Info1 // @Field: GI2: GNSS Info2 // @Field: GAPS: GNSS Angles position type AP::logger().WriteStreaming("ILB5", "TimeUS,GMS,FType,GSS,GJS,GI1,GI2,GAPS", "s-------", "F-------", "QIBBBBBB", now_us, gnss_data.pos_timestamp, gps_data.fix_type, gnss_data.spoof_status, gnss_data.jam_status, gnss_data.info_short.info1, gnss_data.info_short.info2, gnss_data.angle_pos_type); // @LoggerMessage: ILB6 // @Description: InertialLabs AHRS data6 // @Field: TimeUS: Time since system startup // @Field: GMS: GNSS Position timestamp // @Field: GpsHTS: GNSS Heading timestamp // @Field: GpsYaw: GNSS Heading // @Field: GpsPitch: GNSS Pitch // @Field: GDOP: GNSS GDOP // @Field: PDOP: GNSS PDOP // @Field: HDOP: GNSS HDOP // @Field: VDOP: GNSS VDOP // @Field: TDOP: GNSS TDOP AP::logger().WriteStreaming("ILB6", "TimeUS,GMS,GpsHTS,GpsYaw,GpsPitch,GDOP,PDOP,HDOP,VDOP,TDOP", "s--hd-----", "F---------", "QIIfffffff", now_us, gnss_data.pos_timestamp, gnss_data.heading_timestamp, gnss_data.heading, gnss_data.pitch, gnss_data.gdop, gnss_data.pdop, gps_data.hdop, gps_data.vdop, gnss_data.tdop); #endif // HAL_LOGGING_ENABLED } #if AP_BARO_EXTERNALAHRS_ENABLED if (GOT_MSG(BARO_DATA) && GOT_MSG(TEMPERATURE)) { AP::baro().handle_external(baro_data); #if HAL_LOGGING_ENABLED uint64_t now_us = AP_HAL::micros64(); // @LoggerMessage: ILB3 // @Description: InertialLabs AHRS data3 // @Field: TimeUS: Time since system startup // @Field: GMS: GPS INS time (round) // @Field: Press: Static pressure // @Field: Diff: Differential pressure // @Field: Temp: Temperature // @Field: Alt: Baro altitude // @Field: TAS: true airspeed // @Field: VWN: Wind velocity north // @Field: VWE: Wind velocity east // @Field: VWD: Wind velocity down // @Field: ADU: Air Data Unit status AP::logger().WriteStreaming("ILB3", "TimeUS,GMS,Press,Diff,Temp,Alt,TAS,VWN,VWE,VWD,ADU", "s-PPOmnnnn-", "F----------", "QIffffffffH", now_us, gps_data.ms_tow, baro_data.pressure_pa, airspeed_data.differential_pressure, baro_data.temperature, state2.baro_alt, state2.true_airspeed, state2.wind_speed.x, state2.wind_speed.y, state2.wind_speed.z, state2.air_data_status); #endif // HAL_LOGGING_ENABLED } #endif // AP_BARO_EXTERNALAHRS_ENABLED #if AP_COMPASS_EXTERNALAHRS_ENABLED if (GOT_MSG(MAG_DATA)) { AP::compass().handle_external(mag_data); #if HAL_LOGGING_ENABLED uint64_t now_us = AP_HAL::micros64(); // @LoggerMessage: ILB2 // @Description: InertialLabs AHRS data2 // @Field: TimeUS: Time since system startup // @Field: GMS: GPS INS time (round) // @Field: MagX: Magnetometer X // @Field: MagY: Magnetometer Y // @Field: MagZ: Magnetometer Z AP::logger().WriteStreaming("ILB2", "TimeUS,GMS,MagX,MagY,MagZ", "s----", "F----", "QIfff", now_us, gps_data.ms_tow, mag_data.field.x, mag_data.field.y, mag_data.field.z); #endif // HAL_LOGGING_ENABLED } #endif // AP_COMPASS_EXTERNALAHRS_ENABLED #if AP_AIRSPEED_EXTERNAL_ENABLED && (APM_BUILD_COPTER_OR_HELI || APM_BUILD_TYPE(APM_BUILD_ArduPlane)) // only on plane and copter as others do not link AP_Airspeed if (GOT_MSG(DIFFERENTIAL_PRESSURE) && GOT_MSG(TEMPERATURE)) { auto *arsp = AP::airspeed(); if (arsp != nullptr) { arsp->handle_external(airspeed_data); } } #endif // AP_AIRSPEED_EXTERNAL_ENABLED buffer_ofs = 0; if (GOT_MSG(POSITION) && GOT_MSG(ORIENTATION_ANGLES) && GOT_MSG(VELOCITIES)) { float roll, pitch, yaw_deg; state.quat.to_euler(roll, pitch, yaw_deg); yaw_deg = fmodf(degrees(yaw_deg), 360.0f); if (yaw_deg < 0.0f) { yaw_deg += 360.0f; } #if HAL_LOGGING_ENABLED uint64_t now_us = AP_HAL::micros64(); // @LoggerMessage: ILB7 // @Description: InertialLabs AHRS data7 // @Field: TimeUS: Time since system startup // @Field: GMS: GPS INS time (round) // @Field: Roll: euler roll // @Field: Pitch: euler pitch // @Field: Yaw: euler yaw // @Field: VN: velocity north // @Field: VE: velocity east // @Field: VD: velocity down // @Field: Lat: latitude // @Field: Lng: longitude // @Field: Alt: altitude MSL // @Field: USW: USW1 // @Field: USW2: USW2 // @Field: Vdc: Supply voltage AP::logger().WriteStreaming("ILB7", "TimeUS,GMS,Roll,Pitch,Yaw,VN,VE,VD,Lat,Lng,Alt,USW,USW2,Vdc", "s-dddnnnDUm--v", "F-------------", "QIfffffffffHHf", now_us, gps_data.ms_tow, degrees(roll), degrees(pitch), yaw_deg, state.velocity.x, state.velocity.y, state.velocity.z, state.location.lat*1.0e-7, state.location.lng*1.0e-7, state.location.alt*0.01, state2.unit_status, state2.unit_status2, state2.supply_voltage); // @LoggerMessage: ILB8 // @Description: InertialLabs AHRS data8 // @Field: TimeUS: Time since system startup // @Field: GMS: GPS INS time (round) // @Field: PVN: position variance north // @Field: PVE: position variance east // @Field: PVD: position variance down // @Field: VVN: velocity variance north // @Field: VVE: velocity variance east // @Field: VVD: velocity variance down AP::logger().WriteStreaming("ILB8", "TimeUS,GMS,PVN,PVE,PVD,VVN,VVE,VVD", "s-mmmnnn", "F-------", "QIffffff", now_us, gps_data.ms_tow, state2.kf_pos_covariance.x, state2.kf_pos_covariance.y, state2.kf_pos_covariance.z, state2.kf_vel_covariance.x, state2.kf_vel_covariance.y, state2.kf_vel_covariance.z); #endif // HAL_LOGGING_ENABLED } const uint32_t dt_critical_usw = 10000; uint32_t now_usw = AP_HAL::millis(); // InertialLabs critical messages to GCS (sending messages once every 10 seconds) if ((last_unit_status != state2.unit_status) || (last_unit_status2 != state2.unit_status2) || (last_air_data_status != state2.air_data_status) || (now_usw - last_critical_msg_ms > dt_critical_usw)) { // Critical USW message if (state2.unit_status & ILABS_UNIT_STATUS_ALIGNMENT_FAIL) { GCS_SEND_TEXT(MAV_SEVERITY_CRITICAL, "ILAB: Unsuccessful initial alignment"); } if (state2.unit_status & ILABS_UNIT_STATUS_OPERATION_FAIL) { GCS_SEND_TEXT(MAV_SEVERITY_CRITICAL, "ILAB: IMU data are incorrect"); } if (state2.unit_status & ILABS_UNIT_STATUS_GYRO_FAIL) { GCS_SEND_TEXT(MAV_SEVERITY_CRITICAL, "ILAB: Gyros failure"); } if (state2.unit_status & ILABS_UNIT_STATUS_ACCEL_FAIL) { GCS_SEND_TEXT(MAV_SEVERITY_CRITICAL, "ILAB: Accelerometers failure"); } if (state2.unit_status & ILABS_UNIT_STATUS_MAG_FAIL) { GCS_SEND_TEXT(MAV_SEVERITY_CRITICAL, "ILAB: Magnetometers failure"); } if (state2.unit_status & ILABS_UNIT_STATUS_ELECTRONICS_FAIL) { GCS_SEND_TEXT(MAV_SEVERITY_CRITICAL, "ILAB: Electronics failure"); } if (state2.unit_status & ILABS_UNIT_STATUS_GNSS_FAIL) { GCS_SEND_TEXT(MAV_SEVERITY_CRITICAL, "ILAB: GNSS receiver failure"); } // Critical USW2 message if (state2.unit_status2 & ILABS_UNIT_STATUS2_BARO_FAIL) { GCS_SEND_TEXT(MAV_SEVERITY_CRITICAL, "ILAB: Baro altimeter failure"); } if (state2.unit_status2 & ILABS_UNIT_STATUS2_DIFF_PRESS_FAIL) { GCS_SEND_TEXT(MAV_SEVERITY_CRITICAL, "ILAB: Diff. pressure sensor failure"); } // Critical ADU message if (state2.air_data_status & ILABS_AIRDATA_INIT_FAIL) { GCS_SEND_TEXT(MAV_SEVERITY_CRITICAL, "ILAB: Static pressure sensor unsuccessful initialization"); } if (state2.air_data_status & ILABS_AIRDATA_DIFF_PRESS_INIT_FAIL) { GCS_SEND_TEXT(MAV_SEVERITY_CRITICAL, "ILAB: Diff. pressure sensor unsuccessful initialization"); } if (state2.air_data_status & ILABS_AIRDATA_STATIC_PRESS_FAIL) { GCS_SEND_TEXT(MAV_SEVERITY_ERROR, "ILAB: Static pressure sensor failure is detect"); } if (state2.air_data_status & ILABS_AIRDATA_DIFF_PRESS_FAIL) { GCS_SEND_TEXT(MAV_SEVERITY_ERROR, "ILAB: Diff. pressure sensor failure is detect"); } last_critical_msg_ms = AP_HAL::millis(); } if (last_unit_status != state2.unit_status) { if (state2.unit_status & ILABS_UNIT_STATUS_RUNTIME_CAL) { GCS_SEND_TEXT(MAV_SEVERITY_INFO, "ILAB: On-the-fly calibration is in progress"); } if (state2.unit_status & ILABS_UNIT_STATUS_VOLTAGE_LOW) { GCS_SEND_TEXT(MAV_SEVERITY_WARNING, "ILAB: Low input voltage"); } else { if (last_unit_status & ILABS_UNIT_STATUS_VOLTAGE_LOW) { GCS_SEND_TEXT(MAV_SEVERITY_INFO, "ILAB: Input voltage is in range"); } } if (state2.unit_status & ILABS_UNIT_STATUS_VOLTAGE_HIGH) { GCS_SEND_TEXT(MAV_SEVERITY_WARNING, "ILAB: High input voltage"); } else { if (last_unit_status & ILABS_UNIT_STATUS_VOLTAGE_HIGH) { GCS_SEND_TEXT(MAV_SEVERITY_INFO, "ILAB: Input voltage is in range"); } } if (state2.unit_status & ILABS_UNIT_STATUS_X_RATE_HIGH) { GCS_SEND_TEXT(MAV_SEVERITY_WARNING, "ILAB: Y-axis angular rate is exceeded"); } else { if (last_unit_status & ILABS_UNIT_STATUS_X_RATE_HIGH) { GCS_SEND_TEXT(MAV_SEVERITY_INFO, "ILAB: Y-axis angular rate is in range"); } } if (state2.unit_status & ILABS_UNIT_STATUS_Y_RATE_HIGH) { GCS_SEND_TEXT(MAV_SEVERITY_WARNING, "ILAB: X-axis angular rate is exceeded"); } else { if (last_unit_status & ILABS_UNIT_STATUS_Y_RATE_HIGH) { GCS_SEND_TEXT(MAV_SEVERITY_INFO, "ILAB: X-axis angular rate is in range"); } } if (state2.unit_status & ILABS_UNIT_STATUS_Z_RATE_HIGH) { GCS_SEND_TEXT(MAV_SEVERITY_WARNING, "ILAB: Z-axis angular rate is exceeded"); } else { if (last_unit_status & ILABS_UNIT_STATUS_Z_RATE_HIGH) { GCS_SEND_TEXT(MAV_SEVERITY_INFO, "ILAB: Z-axis angular rate is in range"); } } if (state2.unit_status & ILABS_UNIT_STATUS_MAG_FIELD_HIGH) { GCS_SEND_TEXT(MAV_SEVERITY_WARNING, "ILAB: Large total magnetic field"); } else { if (last_unit_status & ILABS_UNIT_STATUS_MAG_FIELD_HIGH) { GCS_SEND_TEXT(MAV_SEVERITY_INFO, "ILAB: Total magnetic field is in range"); } } if (state2.unit_status & ILABS_UNIT_STATUS_TEMP_RANGE_ERR) { GCS_SEND_TEXT(MAV_SEVERITY_WARNING, "ILAB: Temperature is out of range"); } else { if (last_unit_status & ILABS_UNIT_STATUS_TEMP_RANGE_ERR) { GCS_SEND_TEXT(MAV_SEVERITY_INFO, "ILAB: Temperature is in range"); } } if (state2.unit_status & ILABS_UNIT_STATUS_RUNTIME_CAL2) { GCS_SEND_TEXT(MAV_SEVERITY_INFO, "ILAB: On-the-fly calibration successful"); } last_unit_status = state2.unit_status; } // InertialLabs INS Unit Status Word 2 (USW2) messages to GCS if (last_unit_status2 != state2.unit_status2) { if (state2.unit_status2 & ILABS_UNIT_STATUS2_ACCEL_X_HIGH) { GCS_SEND_TEXT(MAV_SEVERITY_WARNING, "ILAB: Y-acceleration is out of range"); } else { if (last_unit_status2 & ILABS_UNIT_STATUS2_ACCEL_X_HIGH) { GCS_SEND_TEXT(MAV_SEVERITY_INFO, "ILAB: Y-acceleration is in range"); } } if (state2.unit_status2 & ILABS_UNIT_STATUS2_ACCEL_Y_HIGH) { GCS_SEND_TEXT(MAV_SEVERITY_WARNING, "ILAB: X-acceleration is out of range"); } else { if (last_unit_status2 & ILABS_UNIT_STATUS2_ACCEL_Y_HIGH) { GCS_SEND_TEXT(MAV_SEVERITY_INFO, "ILAB: X-acceleration is in range"); } } if (state2.unit_status2 & ILABS_UNIT_STATUS2_ACCEL_Z_HIGH) { GCS_SEND_TEXT(MAV_SEVERITY_WARNING, "ILAB: Z-acceleration is out of range"); } else { if (last_unit_status2 & ILABS_UNIT_STATUS2_ACCEL_Z_HIGH) { GCS_SEND_TEXT(MAV_SEVERITY_INFO, "ILAB: Z-acceleration is in range"); } } if (state2.unit_status2 & ILABS_UNIT_STATUS2_MAGCAL_2D_ACT) { GCS_SEND_TEXT(MAV_SEVERITY_INFO, "ILAB: Automatic 2D calibration is in progress"); } if (state2.unit_status2 & ILABS_UNIT_STATUS2_MAGCAL_3D_ACT) { GCS_SEND_TEXT(MAV_SEVERITY_INFO, "ILAB: Automatic 3D calibration is in progress"); } if (state2.unit_status2 & ILABS_UNIT_STATUS2_GNSS_FUSION_OFF) { GCS_SEND_TEXT(MAV_SEVERITY_INFO, "ILAB: GNSS input switched off"); } else { if (last_unit_status2 & ILABS_UNIT_STATUS2_GNSS_FUSION_OFF) { GCS_SEND_TEXT(MAV_SEVERITY_INFO, "ILAB: GNSS input switched on"); } } if (state2.unit_status2 & ILABS_UNIT_STATUS2_DIFF_PRESS_FUSION_OFF) { GCS_SEND_TEXT(MAV_SEVERITY_INFO, "ILAB: Diff. pressure input switched off"); } else { if (last_unit_status2 & ILABS_UNIT_STATUS2_DIFF_PRESS_FUSION_OFF) { GCS_SEND_TEXT(MAV_SEVERITY_INFO, "ILAB: Diff. pressure input switched on"); } } if (state2.unit_status2 & ILABS_UNIT_STATUS2_MAG_FUSION_OFF) { GCS_SEND_TEXT(MAV_SEVERITY_INFO, "ILAB: Magnetometer input switched off"); } else { if (last_unit_status2 & ILABS_UNIT_STATUS2_MAG_FUSION_OFF) { GCS_SEND_TEXT(MAV_SEVERITY_INFO, "ILAB: Magnetometer input switched on"); } } if (state2.unit_status2 & ILABS_UNIT_STATUS2_GNSS_POS_VALID) { GCS_SEND_TEXT(MAV_SEVERITY_WARNING, "ILAB: Incorrect GNSS position"); } else { if (last_unit_status2 & ILABS_UNIT_STATUS2_GNSS_POS_VALID) { GCS_SEND_TEXT(MAV_SEVERITY_INFO, "ILAB: GNSS position is correct"); } } last_unit_status2 = state2.unit_status2; } // InertialLabs INS Air Data Unit (ADU) status messages to GCS if (last_air_data_status != state2.air_data_status) { if (state2.air_data_status & ILABS_AIRDATA_STATIC_PRESS_RANGE_ERR) { GCS_SEND_TEXT(MAV_SEVERITY_WARNING, "ILAB: Static pressure is out of range"); } else { if (last_air_data_status & ILABS_AIRDATA_STATIC_PRESS_RANGE_ERR) { GCS_SEND_TEXT(MAV_SEVERITY_INFO, "ILAB: Static pressure is in range"); } } if (state2.air_data_status & ILABS_AIRDATA_DIFF_PRESS_RANGE_ERR) { GCS_SEND_TEXT(MAV_SEVERITY_WARNING, "ILAB: Diff. pressure is out of range"); } else { if (last_air_data_status & ILABS_AIRDATA_DIFF_PRESS_RANGE_ERR) { GCS_SEND_TEXT(MAV_SEVERITY_INFO, "ILAB: Diff. pressure is in range"); } } if (state2.air_data_status & ILABS_AIRDATA_PRESS_ALT_FAIL) { GCS_SEND_TEXT(MAV_SEVERITY_WARNING, "ILAB: Pressure altitude is incorrect"); } else { if (last_air_data_status & ILABS_AIRDATA_PRESS_ALT_FAIL) { GCS_SEND_TEXT(MAV_SEVERITY_INFO, "Pressure altitude is correct"); } } if (state2.air_data_status & ILABS_AIRDATA_AIRSPEED_FAIL) { GCS_SEND_TEXT(MAV_SEVERITY_WARNING, "ILAB: Air speed is incorrect"); } else { if (last_air_data_status & ILABS_AIRDATA_AIRSPEED_FAIL) { GCS_SEND_TEXT(MAV_SEVERITY_INFO, "ILAB: Air speed is correct"); } } if (state2.air_data_status & ILABS_AIRDATA_AIRSPEED_FAIL) { GCS_SEND_TEXT(MAV_SEVERITY_WARNING, "ILAB: Air speed is below the threshold"); } else { if (last_air_data_status & ILABS_AIRDATA_AIRSPEED_FAIL) { GCS_SEND_TEXT(MAV_SEVERITY_INFO, "ILAB: Air speed is above the threshold"); } } last_air_data_status = state2.air_data_status; } // InertialLabs INS spoofing detection messages to GCS if (last_spoof_status != gnss_data.spoof_status) { if ((last_spoof_status == 2 || last_spoof_status == 3) && (gnss_data.spoof_status == 1)) { GCS_SEND_TEXT(MAV_SEVERITY_INFO, "ILAB: GNSS no spoofing"); } if (last_spoof_status == 2) { GCS_SEND_TEXT(MAV_SEVERITY_WARNING, "ILAB: GNSS spoofing indicated"); } if (last_spoof_status == 3) { GCS_SEND_TEXT(MAV_SEVERITY_WARNING, "ILAB: GNSS multiple spoofing indicated"); } last_spoof_status = gnss_data.spoof_status; } // InertialLabs INS jamming detection messages to GCS if (last_jam_status != gnss_data.jam_status) { if ((last_jam_status == 2 || last_jam_status == 3) && (gnss_data.jam_status == 1)) { GCS_SEND_TEXT(MAV_SEVERITY_INFO, "ILAB: GNSS no jamming"); } if (gnss_data.jam_status == 3) { GCS_SEND_TEXT(MAV_SEVERITY_WARNING, "ILAB: GNSS jamming indicated and no fix"); } last_jam_status = gnss_data.jam_status; } return true; } void AP_ExternalAHRS_InertialLabs::update_thread() { // Open port in the thread uart->begin(baudrate, 1024, 512); /* we assume the user has already configured the device */ setup_complete = true; while (true) { if (!check_uart()) { hal.scheduler->delay_microseconds(250); } } } // get serial port number for the uart int8_t AP_ExternalAHRS_InertialLabs::get_port(void) const { if (!uart) { return -1; } return port_num; }; // accessors for AP_AHRS bool AP_ExternalAHRS_InertialLabs::healthy(void) const { WITH_SEMAPHORE(state.sem); return AP_HAL::millis() - last_att_ms < 100; } bool AP_ExternalAHRS_InertialLabs::initialised(void) const { if (!setup_complete) { return false; } return true; } bool AP_ExternalAHRS_InertialLabs::pre_arm_check(char *failure_msg, uint8_t failure_msg_len) const { if (!setup_complete) { hal.util->snprintf(failure_msg, failure_msg_len, "InertialLabs setup failed"); return false; } if (!healthy()) { hal.util->snprintf(failure_msg, failure_msg_len, "InertialLabs unhealthy"); return false; } WITH_SEMAPHORE(state.sem); uint32_t now = AP_HAL::millis(); if (now - last_att_ms > 10 || now - last_pos_ms > 10 || now - last_vel_ms > 10) { hal.util->snprintf(failure_msg, failure_msg_len, "InertialLabs not up to date"); return false; } return true; } /* get filter status. We don't know the meaning of the status bits yet, so assume all OK if we have GPS lock */ void AP_ExternalAHRS_InertialLabs::get_filter_status(nav_filter_status &status) const { WITH_SEMAPHORE(state.sem); uint32_t now = AP_HAL::millis(); const uint32_t dt_limit = 200; const uint32_t dt_limit_gps = 500; memset(&status, 0, sizeof(status)); const bool init_ok = (state2.unit_status & (ILABS_UNIT_STATUS_ALIGNMENT_FAIL|ILABS_UNIT_STATUS_OPERATION_FAIL))==0; status.flags.initalized = init_ok; status.flags.attitude = init_ok && (now - last_att_ms < dt_limit) && init_ok; status.flags.vert_vel = init_ok && (now - last_vel_ms < dt_limit); status.flags.vert_pos = init_ok && (now - last_pos_ms < dt_limit); status.flags.horiz_vel = status.flags.vert_vel; status.flags.horiz_pos_abs = status.flags.vert_pos; status.flags.horiz_pos_rel = status.flags.horiz_pos_abs; status.flags.pred_horiz_pos_rel = status.flags.horiz_pos_abs; status.flags.pred_horiz_pos_abs = status.flags.horiz_pos_abs; status.flags.using_gps = (now - last_gps_ms < dt_limit_gps) && ((state2.unit_status & ILABS_UNIT_STATUS_GNSS_FAIL) | (state2.unit_status2 & ILABS_UNIT_STATUS2_GNSS_FUSION_OFF)) == 0; status.flags.gps_quality_good = (now - last_gps_ms < dt_limit_gps) && (state2.unit_status2 & ILABS_UNIT_STATUS2_GNSS_POS_VALID) != 0 && (state2.unit_status & ILABS_UNIT_STATUS_GNSS_FAIL) == 0; status.flags.rejecting_airspeed = (state2.air_data_status & ILABS_AIRDATA_AIRSPEED_FAIL); } // get variances bool AP_ExternalAHRS_InertialLabs::get_variances(float &velVar, float &posVar, float &hgtVar, Vector3f &magVar, float &tasVar) const { velVar = state2.kf_vel_covariance.length() * vel_gate_scale; posVar = state2.kf_pos_covariance.xy().length() * pos_gate_scale; hgtVar = state2.kf_pos_covariance.z * hgt_gate_scale; tasVar = 0; return true; } #endif // AP_EXTERNAL_AHRS_INERTIALLABS_ENABLED