/*
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 AHRS systems
*/
#include "AP_ExternalAHRS_config.h"
#if HAL_EXTERNAL_AHRS_ENABLED
#include "AP_ExternalAHRS.h"
#include "AP_ExternalAHRS_backend.h"
#include "AP_ExternalAHRS_VectorNav.h"
#include "AP_ExternalAHRS_MicroStrain5.h"
#include "AP_ExternalAHRS_MicroStrain7.h"
#include "AP_ExternalAHRS_InertialLabs.h"
#include
#include
#include
#include
extern const AP_HAL::HAL &hal;
AP_ExternalAHRS *AP_ExternalAHRS::_singleton;
// constructor
AP_ExternalAHRS::AP_ExternalAHRS()
{
AP_Param::setup_object_defaults(this, var_info);
_singleton = this;
if (rate.get() < 50) {
// min 50Hz
rate.set(50);
}
}
#ifndef HAL_EXTERNAL_AHRS_DEFAULT
#define HAL_EXTERNAL_AHRS_DEFAULT 0
#endif
// table of user settable parameters
const AP_Param::GroupInfo AP_ExternalAHRS::var_info[] = {
// @Param: _TYPE
// @DisplayName: AHRS type
// @Description: Type of AHRS device
// @Values: 0:None,1:VectorNav,2:MicroStrain5,5:InertialLabs,7:MicroStrain7
// @User: Standard
AP_GROUPINFO_FLAGS("_TYPE", 1, AP_ExternalAHRS, devtype, HAL_EXTERNAL_AHRS_DEFAULT, AP_PARAM_FLAG_ENABLE),
// @Param: _RATE
// @DisplayName: AHRS data rate
// @Description: Requested rate for AHRS device
// @Units: Hz
// @User: Standard
AP_GROUPINFO("_RATE", 2, AP_ExternalAHRS, rate, 50),
// @Param: _OPTIONS
// @DisplayName: External AHRS options
// @Description: External AHRS options bitmask
// @Bitmask: 0:Vector Nav use uncompensated values for accel gyro and mag.
// @User: Standard
AP_GROUPINFO("_OPTIONS", 3, AP_ExternalAHRS, options, 0),
// @Param: _SENSORS
// @DisplayName: External AHRS sensors
// @Description: External AHRS sensors bitmask
// @Bitmask: 0:GPS,1:IMU,2:Baro,3:Compass
// @User: Advanced
AP_GROUPINFO("_SENSORS", 4, AP_ExternalAHRS, sensors, 0xF),
// @Param: _LOG_RATE
// @DisplayName: AHRS logging rate
// @Description: Logging rate for EARHS devices
// @Units: Hz
// @User: Standard
AP_GROUPINFO("_LOG_RATE", 5, AP_ExternalAHRS, log_rate, 10),
AP_GROUPEND
};
void AP_ExternalAHRS::init(void)
{
if (rate.get() < 50) {
// min 50Hz
rate.set(50);
}
switch (DevType(devtype)) {
case DevType::None:
// nothing to do
return;
#if AP_EXTERNAL_AHRS_VECTORNAV_ENABLED
case DevType::VecNav:
backend = NEW_NOTHROW AP_ExternalAHRS_VectorNav(this, state);
return;
#endif
#if AP_EXTERNAL_AHRS_MICROSTRAIN5_ENABLED
case DevType::MicroStrain5:
backend = NEW_NOTHROW AP_ExternalAHRS_MicroStrain5(this, state);
return;
#endif
#if AP_EXTERNAL_AHRS_MICROSTRAIN7_ENABLED
case DevType::MicroStrain7:
backend = NEW_NOTHROW AP_ExternalAHRS_MicroStrain7(this, state);
return;
#endif
#if AP_EXTERNAL_AHRS_INERTIALLABS_ENABLED
case DevType::InertialLabs:
backend = NEW_NOTHROW AP_ExternalAHRS_InertialLabs(this, state);
return;
#endif
}
GCS_SEND_TEXT(MAV_SEVERITY_INFO, "Unsupported ExternalAHRS type %u", unsigned(devtype));
}
bool AP_ExternalAHRS::enabled() const
{
return DevType(devtype) != DevType::None;
}
// get serial port number for the uart, or -1 if not applicable
int8_t AP_ExternalAHRS::get_port(AvailableSensor sensor) const
{
if (!backend || !has_sensor(sensor)) {
return -1;
}
return backend->get_port();
};
// accessors for AP_AHRS
bool AP_ExternalAHRS::healthy(void) const
{
return backend && backend->healthy();
}
bool AP_ExternalAHRS::initialised(void) const
{
return backend && backend->initialised();
}
bool AP_ExternalAHRS::get_quaternion(Quaternion &quat)
{
if (state.have_quaternion) {
WITH_SEMAPHORE(state.sem);
quat = state.quat;
return true;
}
return false;
}
bool AP_ExternalAHRS::get_origin(Location &loc)
{
if (state.have_origin) {
WITH_SEMAPHORE(state.sem);
loc = state.origin;
return true;
}
return false;
}
bool AP_ExternalAHRS::set_origin(const Location &loc)
{
WITH_SEMAPHORE(state.sem);
if (state.have_origin) {
return false;
}
state.origin = loc;
state.have_origin = true;
return true;
}
bool AP_ExternalAHRS::get_location(Location &loc)
{
if (!state.have_location) {
return false;
}
WITH_SEMAPHORE(state.sem);
loc = state.location;
if (state.last_location_update_us != 0 &&
state.have_velocity) {
// extrapolate position based on velocity to cope with slow backends
const float dt = (AP_HAL::micros() - state.last_location_update_us)*1.0e-6;
if (dt < 1) {
// only extrapolate for 1s max
Vector3p ofs = state.velocity.topostype();
ofs *= dt;
loc.offset(ofs);
}
}
return true;
}
Vector2f AP_ExternalAHRS::get_groundspeed_vector()
{
WITH_SEMAPHORE(state.sem);
Vector2f vec{state.velocity.x, state.velocity.y};
return vec;
}
bool AP_ExternalAHRS::get_velocity_NED(Vector3f &vel)
{
if (!state.have_velocity) {
return false;
}
WITH_SEMAPHORE(state.sem);
vel = state.velocity;
return true;
}
bool AP_ExternalAHRS::get_speed_down(float &speedD)
{
if (!state.have_velocity) {
return false;
}
WITH_SEMAPHORE(state.sem);
speedD = state.velocity.z;
return true;
}
bool AP_ExternalAHRS::pre_arm_check(char *failure_msg, uint8_t failure_msg_len) const
{
if (backend == nullptr) {
hal.util->snprintf(failure_msg, failure_msg_len, "ExternalAHRS: Invalid backend");
return false;
}
if (!backend->pre_arm_check(failure_msg, failure_msg_len)) {
return false;
}
// Verify the user has configured the GPS to accept EAHRS data.
if (has_sensor(AvailableSensor::GPS)) {
const auto eahrs_gps_sensors = backend->num_gps_sensors();
const auto &gps = AP::gps();
uint8_t n_configured_eahrs_gps = 0;
for (uint8_t i = 0; i < GPS_MAX_INSTANCES; ++i) {
const auto gps_type = gps.get_type(i);
if (gps_type == AP_GPS::GPS_TYPE_EXTERNAL_AHRS) {
n_configured_eahrs_gps++;
}
}
// Once AP supports at least 3 GPS's, change to == and remove the second condition.
// At that point, enforce that all GPS's in EAHRS can report to AP_GPS.
if (n_configured_eahrs_gps < 1 && eahrs_gps_sensors >= 1) {
hal.util->snprintf(failure_msg, failure_msg_len, "ExternalAHRS: Incorrect number of GPS sensors configured for EAHRS");
return false;
}
}
if (!state.have_origin) {
hal.util->snprintf(failure_msg, failure_msg_len, "ExternalAHRS: No origin");
return false;
}
return true;
}
/*
get filter status
*/
void AP_ExternalAHRS::get_filter_status(nav_filter_status &status) const
{
status = {};
if (backend) {
backend->get_filter_status(status);
}
}
/*
get estimated variances, return false if not implemented
*/
bool AP_ExternalAHRS::get_variances(float &velVar, float &posVar, float &hgtVar, Vector3f &magVar, float &tasVar) const
{
if (backend != nullptr) {
return backend->get_variances(velVar, posVar, hgtVar, magVar, tasVar);
}
return false;
}
bool AP_ExternalAHRS::get_gyro(Vector3f &gyro)
{
WITH_SEMAPHORE(state.sem);
if (!has_sensor(AvailableSensor::IMU)) {
return false;
}
gyro = state.gyro;
return true;
}
bool AP_ExternalAHRS::get_accel(Vector3f &accel)
{
WITH_SEMAPHORE(state.sem);
if (!has_sensor(AvailableSensor::IMU)) {
return false;
}
accel = state.accel;
return true;
}
// send an EKF_STATUS message to GCS
void AP_ExternalAHRS::send_status_report(GCS_MAVLINK &link) const
{
float velVar, posVar, hgtVar, tasVar;
Vector3f magVar;
if (backend == nullptr || !backend->get_variances(velVar, posVar, hgtVar, magVar, tasVar)) {
return;
}
uint16_t flags = 0;
nav_filter_status filterStatus {};
get_filter_status(filterStatus);
if (filterStatus.flags.attitude) {
flags |= EKF_ATTITUDE;
}
if (filterStatus.flags.horiz_vel) {
flags |= EKF_VELOCITY_HORIZ;
}
if (filterStatus.flags.vert_vel) {
flags |= EKF_VELOCITY_VERT;
}
if (filterStatus.flags.horiz_pos_rel) {
flags |= EKF_POS_HORIZ_REL;
}
if (filterStatus.flags.horiz_pos_abs) {
flags |= EKF_POS_HORIZ_ABS;
}
if (filterStatus.flags.vert_pos) {
flags |= EKF_POS_VERT_ABS;
}
if (filterStatus.flags.terrain_alt) {
flags |= EKF_POS_VERT_AGL;
}
if (filterStatus.flags.const_pos_mode) {
flags |= EKF_CONST_POS_MODE;
}
if (filterStatus.flags.pred_horiz_pos_rel) {
flags |= EKF_PRED_POS_HORIZ_REL;
}
if (filterStatus.flags.pred_horiz_pos_abs) {
flags |= EKF_PRED_POS_HORIZ_ABS;
}
if (!filterStatus.flags.initalized) {
flags |= EKF_UNINITIALIZED;
}
const float mag_var = MAX(magVar.x, MAX(magVar.y, magVar.z));
mavlink_msg_ekf_status_report_send(link.get_chan(), flags,
velVar,
posVar,
hgtVar,
mag_var, 0, 0);
}
void AP_ExternalAHRS::update(void)
{
if (backend) {
backend->update();
}
WITH_SEMAPHORE(state.sem);
#if HAL_LOGGING_ENABLED
const uint32_t now_ms = AP_HAL::millis();
if (log_rate.get() > 0 && now_ms - last_log_ms >= uint32_t(1000U/log_rate.get())) {
last_log_ms = now_ms;
// @LoggerMessage: EAHR
// @Description: External AHRS data
// @Field: TimeUS: Time since system startup
// @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: Lon: longitude
// @Field: Alt: altitude AMSL
// @Field: Flg: nav status flags
float roll, pitch, yaw;
state.quat.to_euler(roll, pitch, yaw);
nav_filter_status filterStatus {};
get_filter_status(filterStatus);
AP::logger().WriteStreaming("EAHR", "TimeUS,Roll,Pitch,Yaw,VN,VE,VD,Lat,Lon,Alt,Flg",
"sdddnnnDUm-",
"F000000GG0-",
"QffffffLLfI",
AP_HAL::micros64(),
degrees(roll), degrees(pitch), degrees(yaw),
state.velocity.x, state.velocity.y, state.velocity.z,
state.location.lat, state.location.lng, state.location.alt*0.01,
filterStatus.value);
// @LoggerMessage: EAHV
// @Description: External AHRS variances
// @Field: TimeUS: Time since system startup
// @Field: Vel: velocity variance
// @Field: Pos: position variance
// @Field: Hgt: height variance
// @Field: MagX: magnetic variance, X
// @Field: MagY: magnetic variance, Y
// @Field: MagZ: magnetic variance, Z
// @Field: TAS: true airspeed variance
float velVar, posVar, hgtVar, tasVar;
Vector3f magVar;
if (backend != nullptr && backend->get_variances(velVar, posVar, hgtVar, magVar, tasVar)) {
AP::logger().WriteStreaming("EAHV", "TimeUS,Vel,Pos,Hgt,MagX,MagY,MagZ,TAS",
"Qfffffff",
AP_HAL::micros64(),
velVar, posVar, hgtVar,
magVar.x, magVar.y, magVar.z,
tasVar);
}
}
#endif // HAL_LOGGING_ENABLED
}
// Get model/type name
const char* AP_ExternalAHRS::get_name() const
{
if (backend) {
return backend->get_name();
}
return nullptr;
}
namespace AP {
AP_ExternalAHRS &externalAHRS()
{
return *AP_ExternalAHRS::get_singleton();
}
};
#endif // HAL_EXTERNAL_AHRS_ENABLED