You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
324 lines
11 KiB
324 lines
11 KiB
#ifndef SITL_GAZEBO_COMMON_H_
|
|
#define SITL_GAZEBO_COMMON_H_
|
|
/*
|
|
* Copyright 2015 Fadri Furrer, ASL, ETH Zurich, Switzerland
|
|
* Copyright 2015 Michael Burri, ASL, ETH Zurich, Switzerland
|
|
* Copyright 2015 Mina Kamel, ASL, ETH Zurich, Switzerland
|
|
* Copyright 2015 Janosch Nikolic, ASL, ETH Zurich, Switzerland
|
|
* Copyright 2015 Markus Achtelik, ASL, ETH Zurich, Switzerland
|
|
* Copyright 2015 Markus Achtelik, ASL, ETH Zurich, Switzerland
|
|
* Copyright 2019-2020 PX4 Development Team. All rights reserved.
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
|
|
#include <tinyxml.h>
|
|
#include <typeinfo>
|
|
#include <Eigen/Dense>
|
|
#include <gazebo/gazebo.hh>
|
|
#include <ignition/math.hh>
|
|
#include <gazebo/physics/physics.hh>
|
|
|
|
namespace gazebo {
|
|
|
|
/**
|
|
* \brief Obtains a parameter from sdf.
|
|
* \param[in] sdf Pointer to the sdf object.
|
|
* \param[in] name Name of the parameter.
|
|
* \param[out] param Param Variable to write the parameter to.
|
|
* \param[in] default_value Default value, if the parameter not available.
|
|
* \param[in] verbose If true, gzerror if the parameter is not available.
|
|
*/
|
|
template<class T>
|
|
bool getSdfParam(sdf::ElementPtr sdf, const std::string& name, T& param, const T& default_value, const bool& verbose =
|
|
false) {
|
|
if (sdf->HasElement(name)) {
|
|
param = sdf->GetElement(name)->Get<T>();
|
|
return true;
|
|
}
|
|
else {
|
|
param = default_value;
|
|
if (verbose)
|
|
gzerr << "[rotors_gazebo_plugins] Please specify a value for parameter \"" << name << "\".\n";
|
|
}
|
|
return false;
|
|
}
|
|
|
|
template <typename T>
|
|
void model_param(const std::string& world_name, const std::string& model_name, const std::string& param, T& param_value)
|
|
{
|
|
TiXmlElement* e_param = nullptr;
|
|
TiXmlElement* e_param_tmp = nullptr;
|
|
std::string dbg_param;
|
|
|
|
TiXmlDocument doc(world_name + ".xml");
|
|
if (doc.LoadFile())
|
|
{
|
|
TiXmlHandle h_root(doc.RootElement());
|
|
|
|
TiXmlElement* e_model = h_root.FirstChild("model").Element();
|
|
|
|
for( ; e_model != nullptr; e_model=e_model->NextSiblingElement("model") )
|
|
{
|
|
const char* attr_name = e_model->Attribute("name");
|
|
if (attr_name)
|
|
{
|
|
//specific
|
|
if (model_name.compare(attr_name) == 0)
|
|
{
|
|
e_param_tmp = e_model->FirstChildElement(param);
|
|
if (e_param_tmp)
|
|
{
|
|
e_param = e_param_tmp;
|
|
dbg_param = "";
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//common
|
|
e_param = e_model->FirstChildElement(param);
|
|
dbg_param = "common ";
|
|
}
|
|
}
|
|
|
|
if (e_param)
|
|
{
|
|
std::istringstream iss(e_param->GetText());
|
|
iss >> param_value;
|
|
|
|
gzdbg << model_name << " model: " << dbg_param << "parameter " << param << " = " << param_value << " from " << doc.Value() << "\n";
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
/**
|
|
* \brief Get a math::Angle as an angle from [0, 360)
|
|
*/
|
|
inline double GetDegrees360(const ignition::math::Angle& angle) {
|
|
double degrees = angle.Degree();
|
|
while (degrees < 0.) degrees += 360.0;
|
|
while (degrees >= 360.0) degrees -= 360.0;
|
|
return degrees;
|
|
}
|
|
|
|
|
|
} // namespace gazebo
|
|
|
|
template <typename T>
|
|
class FirstOrderFilter {
|
|
/*
|
|
This class can be used to apply a first order filter on a signal.
|
|
It allows different acceleration and deceleration time constants.
|
|
|
|
Short reveiw of discrete time implementation of firest order system:
|
|
Laplace:
|
|
X(s)/U(s) = 1/(tau*s + 1)
|
|
continous time system:
|
|
dx(t) = (-1/tau)*x(t) + (1/tau)*u(t)
|
|
discretized system (ZoH):
|
|
x(k+1) = exp(samplingTime*(-1/tau))*x(k) + (1 - exp(samplingTime*(-1/tau))) * u(k)
|
|
*/
|
|
|
|
public:
|
|
FirstOrderFilter(double timeConstantUp, double timeConstantDown, T initialState):
|
|
timeConstantUp_(timeConstantUp),
|
|
timeConstantDown_(timeConstantDown),
|
|
previousState_(initialState) {}
|
|
|
|
T updateFilter(T inputState, double samplingTime) {
|
|
/*
|
|
This method will apply a first order filter on the inputState.
|
|
*/
|
|
T outputState;
|
|
if(inputState > previousState_){
|
|
// Calcuate the outputState if accelerating.
|
|
double alphaUp = exp(- samplingTime / timeConstantUp_);
|
|
// x(k+1) = Ad*x(k) + Bd*u(k)
|
|
outputState = alphaUp * previousState_ + (1 - alphaUp) * inputState;
|
|
|
|
}else{
|
|
// Calculate the outputState if decelerating.
|
|
double alphaDown = exp(- samplingTime / timeConstantDown_);
|
|
outputState = alphaDown * previousState_ + (1 - alphaDown) * inputState;
|
|
}
|
|
previousState_ = outputState;
|
|
return outputState;
|
|
|
|
}
|
|
~FirstOrderFilter() {}
|
|
|
|
protected:
|
|
double timeConstantUp_;
|
|
double timeConstantDown_;
|
|
T previousState_;
|
|
};
|
|
|
|
/// Returns scalar value constrained by (min_val, max_val)
|
|
template<typename Scalar>
|
|
static inline constexpr const Scalar &constrain(const Scalar &val, const Scalar &min_val, const Scalar &max_val) {
|
|
return (val < min_val) ? min_val : ((val > max_val) ? max_val : val);
|
|
}
|
|
|
|
/// Computes a quaternion from the 3-element small angle approximation theta.
|
|
template<class Derived>
|
|
Eigen::Quaternion<typename Derived::Scalar> QuaternionFromSmallAngle(const Eigen::MatrixBase<Derived> & theta) {
|
|
typedef typename Derived::Scalar Scalar;
|
|
EIGEN_STATIC_ASSERT_FIXED_SIZE(Derived);
|
|
EIGEN_STATIC_ASSERT_VECTOR_SPECIFIC_SIZE(Derived, 3);
|
|
const Scalar q_squared = theta.squaredNorm() / 4.0;
|
|
|
|
if (q_squared < 1) {
|
|
return Eigen::Quaternion<Scalar>(sqrt(1 - q_squared), theta[0] * 0.5, theta[1] * 0.5, theta[2] * 0.5);
|
|
}
|
|
else {
|
|
const Scalar w = 1.0 / sqrt(1 + q_squared);
|
|
const Scalar f = w * 0.5;
|
|
return Eigen::Quaternion<Scalar>(w, theta[0] * f, theta[1] * f, theta[2] * f);
|
|
}
|
|
}
|
|
|
|
template<class In, class Out>
|
|
void copyPosition(const In& in, Out* out) {
|
|
out->x = in.x;
|
|
out->y = in.y;
|
|
out->z = in.z;
|
|
}
|
|
|
|
#if GAZEBO_MAJOR_VERSION < 9
|
|
inline ignition::math::Vector3d ignitionFromGazeboMath(const gazebo::math::Vector3 &vec_gz) {
|
|
return ignition::math::Vector3d(vec_gz.x, vec_gz.y, vec_gz.z);
|
|
}
|
|
|
|
inline ignition::math::Pose3d ignitionFromGazeboMath(const gazebo::math::Pose &pose_gz) {
|
|
|
|
return ignition::math::Pose3d(pose_gz.pos.x, pose_gz.pos.y, pose_gz.pos.z,
|
|
pose_gz.rot.w, pose_gz.rot.x, pose_gz.rot.y, pose_gz.rot.z);
|
|
}
|
|
#endif
|
|
|
|
/**
|
|
* @note Frames of reference:
|
|
* g - gazebo (ENU), east, north, up
|
|
* r - rotors imu frame (FLU), forward, left, up
|
|
* b - px4 (FRD) forward, right down
|
|
* n - px4 (NED) north, east, down
|
|
*/
|
|
|
|
/**
|
|
* @brief Quaternion for rotation between ENU and NED frames
|
|
*
|
|
* NED to ENU: +PI/2 rotation about Z (Down) followed by a +PI rotation around X (old North/new East)
|
|
* ENU to NED: +PI/2 rotation about Z (Up) followed by a +PI rotation about X (old East/new North)
|
|
* This rotation is symmetric, so q_ENU_to_NED == q_NED_to_ENU.
|
|
*/
|
|
static const auto q_ENU_to_NED = ignition::math::Quaterniond(0, 0.70711, 0.70711, 0);
|
|
|
|
/**
|
|
* @brief Quaternion for rotation between body FLU and body FRD frames
|
|
*
|
|
* +PI rotation around X (Forward) axis rotates from Forward, Right, Down (aircraft)
|
|
* to Forward, Left, Up (base_link) frames and vice-versa.
|
|
* This rotation is symmetric, so q_FLU_to_FRD == q_FRD_to_FLU.
|
|
*/
|
|
static const auto q_FLU_to_FRD = ignition::math::Quaterniond(0, 1, 0, 0);
|
|
|
|
// sensor X-axis unit vector in `base_link` frame
|
|
static const ignition::math::Vector3d kDownwardRotation = ignition::math::Vector3d(0, 0, -1);
|
|
static const ignition::math::Vector3d kUpwardRotation = ignition::math::Vector3d(0, 0, 1);
|
|
static const ignition::math::Vector3d kBackwardRotation = ignition::math::Vector3d(-1, 0, 0);
|
|
static const ignition::math::Vector3d kForwardRotation = ignition::math::Vector3d(1, 0, 0);
|
|
static const ignition::math::Vector3d kLeftRotation = ignition::math::Vector3d(0, 1, 0);
|
|
static const ignition::math::Vector3d kRightRotation = ignition::math::Vector3d(0, -1, 0);
|
|
|
|
// Zurich Irchel Park
|
|
static constexpr const double kDefaultHomeLatitude = 47.397742 * M_PI / 180.0; // rad
|
|
static constexpr const double kDefaultHomeLongitude = 8.545594 * M_PI / 180.0; // rad
|
|
static constexpr const double kDefaultHomeAltitude = 488.0; // meters
|
|
|
|
// Earth radius
|
|
static constexpr const double earth_radius = 6353000.0; // meters
|
|
|
|
/**
|
|
* @brief Get latitude and longitude coordinates from local position
|
|
* @param[in] pos position in the local frame
|
|
* @return std::pair of Latitude and Longitude
|
|
*/
|
|
inline std::pair<double, double> reproject(ignition::math::Vector3d& pos,
|
|
double& lat_home,
|
|
double& lon_home,
|
|
double& alt_home)
|
|
{
|
|
// reproject local position to gps coordinates
|
|
const double x_rad = pos.Y() / earth_radius; // north
|
|
const double y_rad = pos.X() / earth_radius; // east
|
|
const double c = sqrt(x_rad * x_rad + y_rad * y_rad);
|
|
const double sin_c = sin(c);
|
|
const double cos_c = cos(c);
|
|
|
|
double lat_rad, lon_rad;
|
|
|
|
if (c != 0.0) {
|
|
lat_rad = asin(cos_c * sin(lat_home) + (x_rad * sin_c * cos(lat_home)) / c);
|
|
lon_rad = (lon_home + atan2(y_rad * sin_c, c * cos(lat_home) * cos_c - x_rad * sin(lat_home) * sin_c));
|
|
} else {
|
|
lat_rad = lat_home;
|
|
lon_rad = lon_home;
|
|
}
|
|
|
|
return std::make_pair (lat_rad, lon_rad);
|
|
}
|
|
|
|
/**
|
|
* @brief Check if the world spherical coordinates are set and set them
|
|
* @param[in] world ptr to the world
|
|
* @return true if they exist, false otherwise
|
|
*/
|
|
inline const bool checkWorldHomePosition(gazebo::physics::WorldPtr& world,
|
|
double& world_latitude,
|
|
double& world_longitude,
|
|
double& world_altitude)
|
|
{
|
|
#if GAZEBO_MAJOR_VERSION >= 9
|
|
gazebo::common::SphericalCoordinatesPtr spherical_coords = world->SphericalCoords();
|
|
#else
|
|
gazebo::common::SphericalCoordinatesPtr spherical_coords = world->GetSphericalCoordinates();
|
|
#endif
|
|
|
|
if (!spherical_coords) {
|
|
return false;
|
|
}
|
|
world_latitude = spherical_coords->LatitudeReference().Radian();
|
|
world_longitude = spherical_coords->LongitudeReference().Radian();
|
|
world_altitude = spherical_coords->GetElevationReference();
|
|
// This logic is required given that the spherical coordinates reference call
|
|
// return 0 if the spherical coordnates are not defined in the world file
|
|
return (world_latitude && world_latitude && world_latitude) ? true : false;
|
|
}
|
|
|
|
template <typename T>
|
|
inline T degrees(T radians)
|
|
{
|
|
return radians * static_cast<T>(180.0) / static_cast<T>(M_PI);
|
|
}
|
|
|
|
template <typename T>
|
|
inline T radians(T degrees)
|
|
{
|
|
return radians / static_cast<T>(180.0) * static_cast<T>(M_PI);
|
|
}
|
|
|
|
#endif // SITL_GAZEBO_COMMON_H_
|
|
|