Files
PX4-Autopilot/src/modules/uavcan/sensors/gnss.cpp
T

463 lines
13 KiB
C++

/****************************************************************************
*
* Copyright (c) 2014, 2015 PX4 Development Team. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
* 3. Neither the name PX4 nor the names of its contributors may be
* used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
* ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
****************************************************************************/
/**
* @file gnss.cpp
*
* @author Pavel Kirienko <pavel.kirienko@gmail.com>
* @author Andrew Chambers <achamber@gmail.com>
*
*/
#include "gnss.hpp"
#include <drivers/drv_hrt.h>
#include <systemlib/err.h>
#include <mathlib/mathlib.h>
const char *const UavcanGnssBridge::NAME = "gnss";
UavcanGnssBridge::UavcanGnssBridge(uavcan::INode &node) :
_node(node),
_sub_fix(node),
_sub_fix2(node),
_pub_fix2(node),
_orb_to_uavcan_pub_timer(node, TimerCbBinder(this, &UavcanGnssBridge::broadcast_from_orb)),
_report_pub(nullptr)
{
}
UavcanGnssBridge::~UavcanGnssBridge()
{
(void) orb_unsubscribe(_orb_sub_gnss);
}
int UavcanGnssBridge::init()
{
int res = _pub_fix2.init(uavcan::TransferPriority::MiddleLower);
if (res < 0) {
PX4_WARN("GNSS fix2 pub failed %i", res);
return res;
}
res = _sub_fix.start(FixCbBinder(this, &UavcanGnssBridge::gnss_fix_sub_cb));
if (res < 0) {
PX4_WARN("GNSS fix sub failed %i", res);
return res;
}
res = _sub_fix2.start(Fix2CbBinder(this, &UavcanGnssBridge::gnss_fix2_sub_cb));
if (res < 0) {
PX4_WARN("GNSS fix2 sub failed %i", res);
return res;
}
_orb_to_uavcan_pub_timer.startPeriodic(
uavcan::MonotonicDuration::fromUSec(1000000U / ORB_TO_UAVCAN_FREQUENCY_HZ));
return res;
}
unsigned UavcanGnssBridge::get_num_redundant_channels() const
{
return (_receiver_node_id < 0) ? 0 : 1;
}
void UavcanGnssBridge::print_status() const
{
printf("RX errors: %d, using old Fix: %d, receiver node id: ",
_sub_fix.getFailureCount(), int(_old_fix_subscriber_active));
if (_receiver_node_id < 0) {
printf("N/A\n");
} else {
printf("%d\n", _receiver_node_id);
}
}
void UavcanGnssBridge::gnss_fix_sub_cb(const uavcan::ReceivedDataStructure<uavcan::equipment::gnss::Fix> &msg)
{
const bool valid_pos_cov = !msg.position_covariance.empty();
const bool valid_vel_cov = !msg.velocity_covariance.empty();
float pos_cov[9];
msg.position_covariance.unpackSquareMatrix(pos_cov);
float vel_cov[9];
msg.velocity_covariance.unpackSquareMatrix(vel_cov);
process_fixx(msg, pos_cov, vel_cov, valid_pos_cov, valid_vel_cov);
}
void UavcanGnssBridge::gnss_fix2_sub_cb(const uavcan::ReceivedDataStructure<uavcan::equipment::gnss::Fix2> &msg)
{
if (_old_fix_subscriber_active) {
PX4_WARN("GNSS Fix2 message detected, disabling support for the old Fix message");
_sub_fix.stop();
_old_fix_subscriber_active = false;
_receiver_node_id = -1;
}
float pos_cov[9] {};
float vel_cov[9] {};
bool valid_covariances = true;
switch (msg.covariance.size()) {
// Scalar matrix
case 1: {
const auto x = msg.covariance[0];
pos_cov[0] = x;
pos_cov[4] = x;
pos_cov[8] = x;
vel_cov[0] = x;
vel_cov[4] = x;
vel_cov[8] = x;
break;
}
// Diagonal matrix (the most common case)
case 6: {
pos_cov[0] = msg.covariance[0];
pos_cov[4] = msg.covariance[1];
pos_cov[8] = msg.covariance[2];
vel_cov[0] = msg.covariance[3];
vel_cov[4] = msg.covariance[4];
vel_cov[8] = msg.covariance[5];
break;
}
// Upper triangular matrix.
// This code has been carefully optimized by hand. We could use unpackSquareMatrix(), but it's slow.
// Sub-matrix indexes (empty squares contain velocity-position covariance data):
// 0 1 2
// 1 6 7
// 2 7 11
// 15 16 17
// 16 18 19
// 17 19 20
case 21: {
pos_cov[0] = msg.covariance[0];
pos_cov[1] = msg.covariance[1];
pos_cov[2] = msg.covariance[2];
pos_cov[3] = msg.covariance[1];
pos_cov[4] = msg.covariance[6];
pos_cov[5] = msg.covariance[7];
pos_cov[6] = msg.covariance[2];
pos_cov[7] = msg.covariance[7];
pos_cov[8] = msg.covariance[11];
vel_cov[0] = msg.covariance[15];
vel_cov[1] = msg.covariance[16];
vel_cov[2] = msg.covariance[17];
vel_cov[3] = msg.covariance[16];
vel_cov[4] = msg.covariance[18];
vel_cov[5] = msg.covariance[19];
vel_cov[6] = msg.covariance[17];
vel_cov[7] = msg.covariance[19];
vel_cov[8] = msg.covariance[20];
}
/* FALLTHROUGH */
// Full matrix 6x6.
// This code has been carefully optimized by hand. We could use unpackSquareMatrix(), but it's slow.
// Sub-matrix indexes (empty squares contain velocity-position covariance data):
// 0 1 2
// 6 7 8
// 12 13 14
// 21 22 23
// 27 28 29
// 33 34 35
case 36: {
pos_cov[0] = msg.covariance[0];
pos_cov[1] = msg.covariance[1];
pos_cov[2] = msg.covariance[2];
pos_cov[3] = msg.covariance[6];
pos_cov[4] = msg.covariance[7];
pos_cov[5] = msg.covariance[8];
pos_cov[6] = msg.covariance[12];
pos_cov[7] = msg.covariance[13];
pos_cov[8] = msg.covariance[14];
vel_cov[0] = msg.covariance[21];
vel_cov[1] = msg.covariance[22];
vel_cov[2] = msg.covariance[23];
vel_cov[3] = msg.covariance[27];
vel_cov[4] = msg.covariance[28];
vel_cov[5] = msg.covariance[29];
vel_cov[6] = msg.covariance[33];
vel_cov[7] = msg.covariance[34];
vel_cov[8] = msg.covariance[35];
}
/* FALLTHROUGH */
// Either empty or invalid sized, interpret as zero matrix
default: {
valid_covariances = false;
break; // Nothing to do
}
}
process_fixx(msg, pos_cov, vel_cov, valid_covariances, valid_covariances);
}
template <typename FixType>
void UavcanGnssBridge::process_fixx(const uavcan::ReceivedDataStructure<FixType> &msg,
const float (&pos_cov)[9],
const float (&vel_cov)[9],
const bool valid_pos_cov,
const bool valid_vel_cov)
{
// This bridge does not support redundant GNSS receivers yet.
if (_receiver_node_id < 0) {
_receiver_node_id = msg.getSrcNodeID().get();
PX4_WARN("GNSS receiver node ID: %d", _receiver_node_id);
} else {
if (_receiver_node_id != msg.getSrcNodeID().get()) {
return; // This GNSS receiver is the redundant one, ignore it.
}
}
auto report = ::vehicle_gps_position_s();
/*
* FIXME HACK
* There used to be the following line of code:
* report.timestamp_position = msg.getMonotonicTimestamp().toUSec();
* It stopped working when the time sync feature has been introduced, because it caused libuavcan
* to use an independent time source (based on hardware TIM5) instead of HRT.
* The proper solution is to be developed.
*/
report.timestamp = hrt_absolute_time();
report.lat = msg.latitude_deg_1e8 / 10;
report.lon = msg.longitude_deg_1e8 / 10;
report.alt = msg.height_msl_mm;
report.alt_ellipsoid = msg.height_ellipsoid_mm;
if (valid_pos_cov) {
// Horizontal position uncertainty
const float horizontal_pos_variance = math::max(pos_cov[0], pos_cov[4]);
report.eph = (horizontal_pos_variance > 0) ? sqrtf(horizontal_pos_variance) : -1.0F;
// Vertical position uncertainty
report.epv = (pos_cov[8] > 0) ? sqrtf(pos_cov[8]) : -1.0F;
} else {
report.eph = -1.0F;
report.epv = -1.0F;
}
if (valid_vel_cov) {
report.s_variance_m_s = math::max(math::max(vel_cov[0], vel_cov[4]), vel_cov[8]);
/* There is a nonlinear relationship between the velocity vector and the heading.
* Use Jacobian to transform velocity covariance to heading covariance
*
* Nonlinear equation:
* heading = atan2(vel_e_m_s, vel_n_m_s)
* For math, see http://en.wikipedia.org/wiki/Atan2#Derivative
*
* To calculate the variance of heading from the variance of velocity,
* cov(heading) = J(velocity)*cov(velocity)*J(velocity)^T
*/
float vel_n = msg.ned_velocity[0];
float vel_e = msg.ned_velocity[1];
float vel_n_sq = vel_n * vel_n;
float vel_e_sq = vel_e * vel_e;
report.c_variance_rad =
(vel_e_sq * vel_cov[0] +
-2 * vel_n * vel_e * vel_cov[1] + // Covariance matrix is symmetric
vel_n_sq * vel_cov[4]) / ((vel_n_sq + vel_e_sq) * (vel_n_sq + vel_e_sq));
} else {
report.s_variance_m_s = -1.0F;
report.c_variance_rad = -1.0F;
}
report.fix_type = msg.status;
report.vel_n_m_s = msg.ned_velocity[0];
report.vel_e_m_s = msg.ned_velocity[1];
report.vel_d_m_s = msg.ned_velocity[2];
report.vel_m_s = sqrtf(report.vel_n_m_s * report.vel_n_m_s +
report.vel_e_m_s * report.vel_e_m_s +
report.vel_d_m_s * report.vel_d_m_s);
report.cog_rad = atan2f(report.vel_e_m_s, report.vel_n_m_s);
report.vel_ned_valid = true;
report.timestamp_time_relative = 0;
const std::uint64_t gnss_ts_usec = uavcan::UtcTime(msg.gnss_timestamp).toUSec();
switch (msg.gnss_time_standard) {
case FixType::GNSS_TIME_STANDARD_UTC: {
report.time_utc_usec = gnss_ts_usec;
break;
}
case FixType::GNSS_TIME_STANDARD_GPS: {
if (msg.num_leap_seconds > 0) {
report.time_utc_usec = gnss_ts_usec - msg.num_leap_seconds + 9;
}
break;
}
case FixType::GNSS_TIME_STANDARD_TAI: {
if (msg.num_leap_seconds > 0) {
report.time_utc_usec = gnss_ts_usec - msg.num_leap_seconds - 10;
}
break;
}
default: {
break;
}
}
//TODO px4_clock_settime does nothing on the Snapdragon platform
#ifndef __PX4_QURT
// If we haven't already done so, set the system clock using GPS data
if (valid_pos_cov && !_system_clock_set) {
timespec ts;
memset(&ts, 0, sizeof(ts));
// get the whole microseconds
ts.tv_sec = report.time_utc_usec / 1000000ULL;
// get the remainder microseconds and convert to nanoseconds
ts.tv_nsec = (report.time_utc_usec % 1000000ULL) * 1000;
px4_clock_settime(CLOCK_REALTIME, &ts);
_system_clock_set = true;
}
#endif
report.satellites_used = msg.sats_used;
// Using PDOP for HDOP and VDOP
// Relevant discussion: https://github.com/PX4/Firmware/issues/5153
report.hdop = msg.pdop;
report.vdop = msg.pdop;
report.heading = NAN;
// Publish to a multi-topic
int32_t gps_orb_instance;
orb_publish_auto(ORB_ID(vehicle_gps_position), &_report_pub, &report, &gps_orb_instance,
ORB_PRIO_HIGH);
// Doing less time critical stuff here
if (_orb_to_uavcan_pub_timer.isRunning()) {
_orb_to_uavcan_pub_timer.stop();
PX4_WARN("GNSS ORB->UAVCAN bridge stopped, because there are other GNSS publishers");
}
}
void UavcanGnssBridge::broadcast_from_orb(const uavcan::TimerEvent &)
{
if (_orb_sub_gnss < 0) {
// ORB subscription stops working if this is relocated into init()
_orb_sub_gnss = orb_subscribe(ORB_ID(vehicle_gps_position));
if (_orb_sub_gnss < 0) {
PX4_WARN("GNSS ORB sub errno %d", errno);
return;
}
PX4_WARN("GNSS ORB fd %d", _orb_sub_gnss);
}
{
bool updated = false;
const int res = orb_check(_orb_sub_gnss, &updated);
if (res < 0) {
PX4_WARN("GNSS ORB check err %d %d", res, errno);
return;
}
if (!updated) {
return;
}
}
auto orb_msg = ::vehicle_gps_position_s();
const int res = orb_copy(ORB_ID(vehicle_gps_position), _orb_sub_gnss, &orb_msg);
if (res < 0) {
PX4_WARN("GNSS ORB read errno %d", errno);
return;
}
// Convert to UAVCAN
using uavcan::equipment::gnss::Fix2;
Fix2 msg;
msg.gnss_timestamp = uavcan::UtcTime::fromUSec(orb_msg.time_utc_usec);
msg.gnss_time_standard = Fix2::GNSS_TIME_STANDARD_UTC;
msg.longitude_deg_1e8 = std::int64_t(orb_msg.lon) * 10LL;
msg.latitude_deg_1e8 = std::int64_t(orb_msg.lat) * 10LL;
msg.height_ellipsoid_mm = orb_msg.alt_ellipsoid;
msg.height_msl_mm = orb_msg.alt;
msg.ned_velocity[0] = orb_msg.vel_n_m_s;
msg.ned_velocity[1] = orb_msg.vel_e_m_s;
msg.ned_velocity[2] = orb_msg.vel_d_m_s;
msg.sats_used = orb_msg.satellites_used;
msg.status = orb_msg.fix_type;
// mode skipped
// sub mode skipped
// diagonal covariance matrix
msg.covariance.resize(2, orb_msg.eph * orb_msg.eph);
msg.covariance.resize(3, orb_msg.epv * orb_msg.epv);
msg.covariance.resize(6, orb_msg.s_variance_m_s * orb_msg.s_variance_m_s);
msg.pdop = (orb_msg.hdop > orb_msg.vdop) ? orb_msg.hdop : orb_msg.vdop; // this is a hack :(
// Publishing now
(void) _pub_fix2.broadcast(msg);
}