diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 0000000..6a117f8
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,40 @@
+# Start from ROS Jazzy + CUDA base
+FROM dtcpronto/ros-jazzy:cuda
+
+USER root
+
+RUN apt-get update && apt-get install -y --no-install-recommends \
+ vim \
+ tmux \
+ cmake \
+ gcc \
+ g++ \
+ git \
+ build-essential \
+ sudo \
+ wget \
+ curl \
+ zip \
+ unzip \
+ ros-jazzy-gtsam \
+ ros-jazzy-gps-msgs \
+ python3-colcon-common-extensions \
+ libgoogle-glog-dev \
+ && rm -rf /var/lib/apt/lists/*
+
+# Switch to dtc user
+USER dtc
+WORKDIR /home/dtc/ws
+
+# Clone glider
+COPY --chown=dtc:dtc ./glider /home/dtc/ws/src/glider
+COPY --chown=dtc:dtc ./dgps_msgs /home/dtc/ws/src/dgps_msgs
+
+# Build workspace
+RUN /bin/bash -c "source /opt/ros/jazzy/setup.bash && \
+ colcon build --symlink-install"
+
+COPY --chown=dtc:dtc ./entrypoint.bash /home/dtc/entrypoint.bash
+RUN chmod +x /home/dtc/entrypoint.bash
+
+ENTRYPOINT ["/home/dtc/entrypoint.bash"]
diff --git a/README.md b/README.md
index 054bc48..72937ea 100644
--- a/README.md
+++ b/README.md
@@ -3,8 +3,8 @@

-Glider is a G-INS system built on [GTSAM](https://github.com/borglab/gtsam). It currently takes in GPS and 9-DOF IMU and provides a full
-state estimate up to the rate of you IMU. Glider is designed to be configured to your system.
+Glider is a G-INS system built on [GTSAM](https://github.com/borglab/gtsam). It accepts GPS, a 9-DOF IMU, and optional local odometry, and provides a full
+state estimate at up to the IMU rate. Glider is designed to be configured for a specific sensor suite.
## Building Glider
To run glider you can use the provided docker images, ROS2 jazzy and humble are both supported, simply use the `build.bash` and `run.bash` files. You can mounted volumes in the run files if necessary. If you want to inlcude this in another ROS2 workspace, you may need to install the following dependencies:
@@ -18,7 +18,7 @@ colcon build --packages-select glider
If you only want the api you can build with:
```
cmake -S . -B build -DBUILD_ROS=OFF
-cmake --build build
+cmake --build build
```
## Running Glider
@@ -40,7 +40,13 @@ the parameters mean:
- `publishers.viz.use`: if true will publish an `Odometry` topic for visualization centered around the origin.
- `publishers.viz.origin_easting`: the easting value you want to viz odometry to center around.
- `publishers.viz.origin_northing`: the northing value you want the viz odometry to center around.
- - `subscribers.use_odom`: Still under development.
+ - `subscribers.imu_topic`, `gps_topic`, `dgps_topic`, `odom_topic`: input topic names.
+ - `subscribers.use_gps`, `use_dgps`, `use_odom`: enable each aiding source. Avoid enabling both GPS inputs when they represent the same receiver fix.
+ - `subscribers.gps_rejection_variance`: hard GPS variance ceiling in m². Measurements above it are rejected while local odometry remains active.
+
+The checked-in ROS parameters use a VectorNav IMU on `/vectornav/imu`, LIO on
+`/rko_lio/odometry`, and the ENU DGPS fix on `/sept/enu/dfix`. LIO remains active
+while GPS is absent or rejected.
## Glider Setup
You can configure glider itself in `config/glider-params.yaml`, this is where you can specify the parameters for the factor graph. Here's more detail on each parameter:
@@ -61,19 +67,18 @@ You can configure glider itself in `config/glider-params.yaml`, this is where yo
- `logging.stdout`: output log statements to terminal in addition to the logfile
- `optimizer.smooth`: if true the factor graph will optimize using a fixed lag smoother, otherwise it will use iSAM2.
- `optimizer.lag_time`: period of time the fixed lag smoother should look at in seconds.
- - `gps_to_imu`: the relative transformation from your gps to your imu in the FLU frame.
+ - `extrinsics`: the single hardware-calibration section. Sensor poses are entered relative to the LiDAR using ROS FLU axes (+X forward, +Y left, +Z up), metres, and XYZ roll/pitch/yaw degrees. Glider converts them to its body frame internally.
### Building and Running Unit Tests
-We use GTest to run unit tests. You can build the tests with
-```
+We use GTest to run unit tests. You can build the tests with
+```
cd glider
cmake -S . -B build -DBUILD_TESTS=ON
cmake --build build
```
and run with:
```
-cd build
+cd build
ctest
```
Note these tests are run on PR's and pushes to the `ros2` branch.
-
diff --git a/build.bash b/build.bash
new file mode 100755
index 0000000..41bea5b
--- /dev/null
+++ b/build.bash
@@ -0,0 +1,2 @@
+#!/bin/bash
+docker build --build-arg user_id=$(id -u) --build-arg USER=$(whoami) --build-arg NAME=glider --rm -t dtc-jackal-`hostname`:glider .
\ No newline at end of file
diff --git a/dgps_msgs/CMakeLists.txt b/dgps_msgs/CMakeLists.txt
new file mode 100644
index 0000000..e8ef3fe
--- /dev/null
+++ b/dgps_msgs/CMakeLists.txt
@@ -0,0 +1,14 @@
+cmake_minimum_required(VERSION 3.8)
+project(dgps_msgs)
+
+find_package(ament_cmake REQUIRED)
+find_package(rosidl_default_generators REQUIRED)
+find_package(sensor_msgs REQUIRED)
+
+rosidl_generate_interfaces(${PROJECT_NAME}
+ "msg/DifferentialNavSatFix.msg"
+ DEPENDENCIES sensor_msgs
+)
+
+ament_export_dependencies(rosidl_default_runtime)
+ament_package()
diff --git a/dgps_msgs/msg/DifferentialNavSatFix.msg b/dgps_msgs/msg/DifferentialNavSatFix.msg
new file mode 100644
index 0000000..4f62c7c
--- /dev/null
+++ b/dgps_msgs/msg/DifferentialNavSatFix.msg
@@ -0,0 +1,11 @@
+# Center/primary antenna navigation fix.
+sensor_msgs/NavSatFix nmea
+
+# Frame-corrected ENU yaw, normalized to [0, 2*pi), in radians.
+float32 heading
+
+# The same heading expressed in degrees, normalized to [0, 360).
+float32 heading_deg
+
+# Heading variance in rad^2.
+float32 heading_covariance
diff --git a/dgps_msgs/package.xml b/dgps_msgs/package.xml
new file mode 100644
index 0000000..cdca467
--- /dev/null
+++ b/dgps_msgs/package.xml
@@ -0,0 +1,18 @@
+
+
+ dgps_msgs
+ 0.1.0
+ Differential GPS fix and dual-antenna heading message
+ Shenbax
+ BSD-3-Clause
+
+ ament_cmake
+ rosidl_default_generators
+ sensor_msgs
+ rosidl_default_runtime
+ rosidl_interface_packages
+
+
+ ament_cmake
+
+
diff --git a/entrypoint.bash b/entrypoint.bash
new file mode 100755
index 0000000..a5cd1ba
--- /dev/null
+++ b/entrypoint.bash
@@ -0,0 +1,13 @@
+#!/bin/bash
+
+source /opt/ros/jazzy/setup.bash
+source /home/dtc/ws/install/setup.bash
+
+if [ "$RUN" = "true" ]; then
+ echo "[GLIDER] Launching glider..."
+ ros2 launch glider glider-node.launch.py use_sim_time:="${USE_SIM_TIME:-false}"
+else
+ echo "[GLIDER] RUN=false, keeping container alive..."
+fi
+
+exec "$@"
diff --git a/glider/CMakeLists.txt b/glider/CMakeLists.txt
index f0e1550..d824bd8 100644
--- a/glider/CMakeLists.txt
+++ b/glider/CMakeLists.txt
@@ -44,6 +44,9 @@ if (BUILD_ROS)
find_package(std_msgs REQUIRED)
find_package(nav_msgs REQUIRED)
find_package(gps_msgs REQUIRED)
+ find_package(dgps_msgs REQUIRED)
+ find_package(geometry_msgs REQUIRED)
+ find_package(tf2_ros REQUIRED)
set(node_plugins "")
endif()
@@ -97,6 +100,8 @@ if (BUILD_ROS)
geometry_msgs
nav_msgs
gps_msgs
+ dgps_msgs
+ tf2_ros
)
add_executable(${PROJECT_NAME}_node
@@ -146,6 +151,7 @@ if (BUILD_ROS)
sensor_msgs
geometry_msgs
nav_msgs
+ dgps_msgs
Eigen3
)
diff --git a/glider/config/glider-params.yaml b/glider/config/glider-params.yaml
index 1e77d8a..27d7ae9 100644
--- a/glider/config/glider-params.yaml
+++ b/glider/config/glider-params.yaml
@@ -3,12 +3,14 @@ imu:
accelerometer: 0.00001
gyroscope: 0.00001
integration: 0.001
- heading: 0.09
+ heading: 0.09
roll_pitch: 0.001
bias: 0.001
frame: "enu"
-gps:
+gps:
covariance: 2.0
+odom:
+ covariance: 0.1
dgps:
enable: true
covariance: 0.03
@@ -24,9 +26,30 @@ logging:
stdout: true
directory: "/tmp/glider"
optimizer:
- smooth: true
+ # Set true to use the incremental fixed-lag smoother instead of iSAM2.
+ smooth: false
lag_time: 5.0
-gps_to_imu:
- x: 0.0
- y: 0.0
- z: 0.0
+# All sensor locations are expressed relative to the LiDAR using ROS FLU axes:
+# +X forward, +Y left, +Z up. Thus right/down/behind are negative Y/Z/X.
+# Translations are metres; rotations are XYZ roll/pitch/yaw in degrees.
+extrinsics:
+ reference_frame: "os_sensor"
+ body:
+ frame: "rko_base_link"
+ translation: {x: -0.2, y: 0.0, z: -0.338}
+ rotation_rpy_deg: {roll: 0.0, pitch: 0.0, yaw: 0.0}
+ lidar:
+ frame: "os_sensor"
+ translation: {x: 0.0, y: 0.0, z: 0.0}
+ rotation_rpy_deg: {roll: 0.0, pitch: 0.0, yaw: 0.0}
+ imu:
+ frame: "vectornav"
+ # 12 cm right and 17 cm down from the LiDAR.
+ translation: {x: 0.0, y: -0.12, z: -0.17}
+ rotation_rpy_deg: {roll: 90.6107, pitch: 3.1974, yaw: -148.5656}
+ gps:
+ frame: "navsat_link"
+ # 25 cm directly behind the LiDAR along the X axis.
+ translation: {x: -0.25, y: 0.0, z: 0.0}
+ rotation_rpy_deg: {roll: 0.0, pitch: 0.0, yaw: 0.0}
+ heading_offset_deg: 0.0
diff --git a/glider/config/ros-params.yaml b/glider/config/ros-params.yaml
index 0a06d4c..549b273 100644
--- a/glider/config/ros-params.yaml
+++ b/glider/config/ros-params.yaml
@@ -2,10 +2,23 @@ glider_node:
ros__parameters:
publishers:
rate: 0.0
- nav_sat_fix: false
+ nav_sat_fix: true
+ utm_zone: "18S"
+ map_frame: "map"
+ base_link_frame: "base_link"
viz:
use: true
origin_easting: 753912.0063845584
origin_northing: 3385461.6073698294
subscribers:
- use_odom: false
+ use_odom: true
+ use_gps: false
+ use_dgps: true
+ imu_topic: "/vectornav/imu"
+ odom_topic: "/rko_lio/odometry"
+ gps_topic: "/ublox/fix"
+ dgps_topic: "/sept/enu/dfix"
+ # Reject fixes when any reported position variance exceeds this value.
+ gps_rejection_variance: 1.0
+ max_stamp_skew_sec: 1.0
+ gps_loss_timeout_sec: 3.0
diff --git a/glider/include/glider/core/factor_manager.hpp b/glider/include/glider/core/factor_manager.hpp
index 521aa58..8f3f7d1 100644
--- a/glider/include/glider/core/factor_manager.hpp
+++ b/glider/include/glider/core/factor_manager.hpp
@@ -2,7 +2,7 @@
* Jason Hughes
* April 2025
*
- * This manages everything with the factor graph. It adds measurements,
+ * This manages everything with the factor graph. It adds measurements,
* runs the optimization with the smoother or isam and predicts with the pim
*/
@@ -49,7 +49,7 @@ using gtsam::symbol_shorthand::B; // Bias
using gtsam::symbol_shorthand::V; // Velocity
using gtsam::symbol_shorthand::X; // Pose
-namespace Glider
+namespace Glider
{
class FactorManager
@@ -58,16 +58,19 @@ class FactorManager
// Constructos
/*! @brief default constructor */
FactorManager() = default;
- /*! @brief constructor that initalizes all parameters in the
+ /*! @brief constructor that initalizes all parameters in the
* factor manager
* @param params: the parameters loaded from the yaml file*/
- FactorManager(const Parameters& params);
-
+ FactorManager(const Parameters& params);
+ /*! @brief initializes all parameters in the factor manager
+ * @param params: the parameters loaded from the yaml file*/
+ void initialize(const Parameters& params);
+
// state predictors
/*! @brief calls the pim predict method
* @param timestamp: the time at which this method is being called
* @return the odometry from the pim prediction */
- Odometry predict(int64_t timestamp);
+ Odometry predict(int64_t timestamp);
/*! @brief the runner takes care of everything with the optimization, it calls the
* optimizer, and resets everything after optimization is done
* @param timestamp: time at which the runner is called
@@ -79,21 +82,30 @@ class FactorManager
/*! @brief adds the gps measurement and pim to the factor graph
* @param timestamp: time of the gps measurement
* @param gps: GPS measurement in the UTM frame */
- void addGpsFactor(int64_t timestamp, const Eigen::Vector3d& gps);
+ void addGpsFactor(int64_t timestamp, const Eigen::Vector3d& gps, const double sigma = 0.0);
/*! @brief adds the gps measurement and a heading from dgps
- * @param timestamp: time of the gps measurement
+ * @param timestamp: time of the gps measurement
* @param gps: GPS measurement in the UTM frame
- * @param heading: heading from dgpsfm in the ENU frame
+ * @param heading: heading from dgpsfm in the ENU frame
* @param fuse: whether or not to add the heading measurement
- * to the factor graph */
- void addGpsFactor(int64_t timestamp, const Eigen::Vector3d& gps, const double& heading, const bool fuse);
+ * to the factor graph
+ * @param sigma: the standard deviation of the gps measurement, if 0 use param */
+ void addGpsFactor(int64_t timestamp, const Eigen::Vector3d& gps, const double& heading, const bool fuse,
+ const double sigma = 0.0, const double heading_sigma = 0.0);
/*! @brief adds the imu measurements to the pim and saves the orientation
- * @param timestamp: time of the imu measurement
+ * @param timestamp: time of the imu measurement
* @param accel: the accelerometer reading
* @param gyro: gyroscopre reading
* @param orient: orientation in quaternion (w,x,y,z) format */
void addImuFactor(int64_t timestamp, const Eigen::Vector3d& accel, const Eigen::Vector3d& gyro, const Eigen::Vector4d& orient);
- /*! @brief adds a landmark factor for an estimated utm point and covariance
+ /*! @brief adds an odometry measurement
+ * @param timestamp: time of the odometry
+ * @param odom: estimated odometry pose
+ * @return true if a graph node was created */
+ bool addOdomFactor(int64_t timestamp, const Eigen::Isometry3d& odom,
+ const Eigen::Vector3d& velocity = Eigen::Vector3d::Zero(),
+ double velocity_sigma = 1.0);
+ /*! @brief adds a landmark factor for an estimated utm point and covariance
* @param timestamp: time of the landmark measurements
* @param landmark_id: a unique id for the landmark
* @param utm: the estimated utm coordinate of the landmark
@@ -103,59 +115,71 @@ class FactorManager
// getters and checkers
/*! @brief gets the estimated landmark utm coordinate and covariance
- * @param landmark_id: the uinque id for the landmark
+ * @param landmark_id: the uinque id for the landmark
* @return the estimated utm point and covariance */
PointWithCovariance getLandmarkPoint(size_t landmark_id) const;
/*! @brief gets the complete factor graph */
gtsam::ExpressionFactorGraph getGraph();
- /*! @brief checks if the imu has been initialized
+ /*! @brief checks if the imu has been initialized
* @return true if imu bias calibration is complete else false*/
bool isImuInitialized() const;
/*! @brief checks if the gps is initialized
* @return true if the gps reading has been added to the graph else false */
bool isGpsInitialized() const;
- /*! @brief checks if the odometry system is initialized
+ /*! @brief checks if the odometry system is initialized
* @return true if graph has been optimized more than specified
* number of times else false */
bool isSystemInitialized() const;
- /*! @brief gets the matrix used for bias estimation
+ /*! @brief gets the matrix used for bias estimation
* @return 6-by-bias_num_measurements matrix */
Eigen::MatrixXd getBiasEstimate() const;
- /*! @brief gets the current pim object
+ /*! @brief gets the current pim object
* @return the current pim object dereferenced */
gtsam::PreintegratedCombinedMeasurements getPim() const;
- /*! @brief gets the key index
+ /*! @brief gets the key index
* @return the current key index */
gtsam::Key getKeyIndex() const;
+ /*! @brief gets the gps offset
+ * @return the 3D gps offset */
+ Eigen::Vector3d getGpsOffset() const;
+ /*! @brief checks if the gps offset has been initialized
+ * @return true if the gps offset has been initialized else false */
+ bool isGpsOffsetInitialized() const;
+
+ /*! @brief gets the current parameters
+ * @return the current parameters */
+ const Parameters& params() const { return params_; }
- private:
- /*! @brief handles the optimization call with the specified
- * optimizer, either isam or fixed lag smoother
+ private:
+ /*! @brief handles the optimization call with the specified
+ * optimizer, either isam or fixed lag smoother
* @return the output estimates from the optimization */
gtsam::Values optimize();
-
- /*! @brief initializes all the parameters for the pim
+
+ /*! @brief initializes all the parameters for the pim
* @param g: gravity as defined in the yaml config
* @return pim parameters as a shared_ptr */
boost::shared_ptr defaultImuParams(double g);
-
+
/*! @brief helper function that sets initial values in the graph */
void initializeGraph();
- /*! @brief estiamtes the bias using the specified number of measurements
+ /*! @brief estiamtes the bias using the specified number of measurements
* up initialization, and saves the orientation as the initial orientation
- * @param accel_meas: accelerometer measurement
+ * @param accel_meas: accelerometer measurement
* @param gytro_meas: gytroscop measurement
* @param orient: the 3D orientation of the robot as a quaternion from the imu*/
void initializeImu(const Eigen::Vector3d& accel_meas, const Eigen::Vector3d& gyro_meas, const Eigen::Vector4d& orient);
- // @brief a mutex to use accross function that access the pim
+ // @brief a mutex to use accross function that access the pim
// as the pim could be accessd by multiple threads
static std::mutex mutex_;
+ // @brief a mutex to protect the factor graph and its variables
+ mutable std::mutex graph_mutex_;
// parameters
- // @brief parameters for the isam2 optimizer
+ // @brief parameters for the isam2 optimizer
gtsam::ISAM2Params isam_params_;
- // @brief parameters for the pim
+ // @brief parameters for the pim
boost::shared_ptr imu_params_;
// @brief parameters set in the config file
Parameters params_;
@@ -171,14 +195,14 @@ class FactorManager
// @brief 6-by-bias_num_measurements matrix to store measurements
// from accel and gyro to measurem bias
Eigen::MatrixXd bias_estimate_vec_;
-
+
// @brief saves the bias estimate from gtsam optimization
gtsam::imuBias::ConstantBias bias_;
// @brief the pim for imu measurements
std::shared_ptr pim_;
// noise
- // @brief noise on the prior estimate
+ // @brief noise on the prior estimate
gtsam::noiseModel::Isotropic::shared_ptr prior_noise_;
// @brief noise on the gps position estimate
gtsam::noiseModel::Isotropic::shared_ptr gps_noise_;
@@ -186,6 +210,8 @@ class FactorManager
gtsam::noiseModel::Base::shared_ptr orient_noise_;
// @brief noise in the heading estimate of differential gps
gtsam::noiseModel::Base::shared_ptr dgpsfm_noise_;
+ // @brief noise for the odometry constraints
+ gtsam::noiseModel::Diagonal::shared_ptr odom_noise_;
// factor graph
// @brief tracks the number of times the optimizer has been called
@@ -222,6 +248,17 @@ class FactorManager
bool imu_initialized_;
// @param tracks if a gps measurement has been received
bool gps_initialized_;
+ // @param tracks if an odom measurement has been received
+ bool odom_initialized_;
+ // @param true if the first graph node originated from Odometry (LIO), making it a local origin
+ bool using_local_origin_;
+ bool gps_offset_initialized_;
+ Eigen::Vector3d gps_offset_;
+ double last_node_time_;
+ // @param previous odometry measurement for relative constraints
+ Eigen::Isometry3d last_odom_meas_;
+ // @param accumulated odometry transform between nodes
+ Eigen::Isometry3d accumulated_odom_delta_;
// landmark variables
std::unordered_map landmark_info_;
diff --git a/glider/include/glider/core/glider.hpp b/glider/include/glider/core/glider.hpp
index deea476..1c08b19 100644
--- a/glider/include/glider/core/glider.hpp
+++ b/glider/include/glider/core/glider.hpp
@@ -31,12 +31,23 @@ class Glider
/*! @brief converts the gps measurement from lat, lon to UTM
* and passes that to the factor manager
* @param timestamp: time that the gps measurement was taken
- * @param gps: gps measurement in (lat, lon, alt) format,
+ * @param gps: gps measurement in (lat, lon, alt) format,
* should be in degree decimal and altitude in meters. Altitude
* frame does not matter */
- void addGps(int64_t timestamp, Eigen::Vector3d& gps);
- void addGpsWithHeading(int64_t timestamp, Eigen::Vector3d& gps);
- void addGpsWithHeading(int64_t timestamp, Eigen::Vector3d& gps, Eigen::Vector2d& heading);
+ void addGps(int64_t timestamp, Eigen::Vector3d& gps, const double sigma = 0.0);
+ /*! @brief adds the gps measurement and heading info to the factor
+ * graph
+ * @param timestamp: time of measurement
+ * @param gps: lat, lon, alt coordinates
+ * @param heading: track, error track
+ * @param sigma: standard deviation of the gps position measurement */
+ void addGpsWithHeading(int64_t timestamp, Eigen::Vector3d& gps, Eigen::Vector2d& heading, const double sigma = 0.0);
+ /*! @brief adds the gps measurement and calculates a heading based on previous
+ * GPS measurements
+ * @param timestamp: time of measurement
+ * @param gps: lat, lon, alt coordinates
+ * @param sigma: standard deviation of the gps position measurement */
+ void addGpsWithHeading(int64_t timestamp, Eigen::Vector3d& gps, const double sigma = 0.0);
/*! @brief converts the imu measurements into the ENU frame if
* they are not in that frame already.
* @param timestamp: time the imu measurement was taken
@@ -44,13 +55,26 @@ class Glider
* @param gyro: gyroscope measurement in the imu's frame
* @param quat: the orientation measurement in the imu's frame */
void addImu(int64_t timestamp, Eigen::Vector3d& accel, Eigen::Vector3d& gyro, Eigen::Vector4d& quat);
+ bool addOdom(int64_t timestamp, const Eigen::Isometry3d& pose);
+ bool addOdom(int64_t timestamp, const Eigen::Isometry3d& pose,
+ const Eigen::Vector3d& velocity, double velocity_sigma);
void addLandmark(int64_t timestamp, size_t lid, const Eigen::Vector3d& utm, const Eigen::Matrix3d& cov);
PointWithCovariance getLandmark(size_t lid);
-
+ Eigen::Vector3d getGpsOffset() const;
+ /*! @brief gets the auto-detected UTM zone
+ * @return the UTM zone string */
+ std::string getUtmZone() const { return utm_zone_; }
- /*! @brief calls the factor manager to interpolate between GPS
+ const Parameters& params() const { return factor_manager_.params(); }
+
+ bool isGpsInitialized() const { return factor_manager_.isGpsInitialized(); }
+ bool isGpsOffsetInitialized() const { return factor_manager_.isGpsOffsetInitialized(); }
+ bool isSystemInitialized() const { return factor_manager_.isSystemInitialized(); }
+
+
+ /*! @brief calls the factor manager to interpolate between GPS
* measurements using the pim
- * @param timestamp: time at which you want to interpolate
+ * @param timestamp: time at which you want to interpolate
* @return odometry object tracking the predicted navstate
* from the pim */
Odometry interpolate(int64_t timestamp);
@@ -59,7 +83,7 @@ class Glider
* @return the full odometry estimate with covariance from the results
* of the gtsam optimization */
OdometryWithCovariance optimize(int64_t timestamp);
-
+
private:
/*! @brief initializes glog with the specified logging
* parameters
@@ -80,17 +104,21 @@ class Glider
std::string frame_;
// @brief the relative translation from the gps to
// the imu
- Eigen::Vector3d t_imu_gps_;
+ Eigen::Vector3d t_body_imu_;
+ Eigen::Matrix3d r_body_imu_;
+ Eigen::Vector3d t_body_gps_;
+ double gps_heading_offset_;
// @brief the rotation matrix from ned to enu frame
Eigen::Matrix3d r_enu_ned_;
// @brief whether or not to use differential gps
// from motion for heading
bool use_dgpsfm_;
- // @brief object to handle differential gps
+ // @brief object to handle differential gps
// from motion
Geodetics::DifferentialGpsFromMotion dgps_;
- // @brief save the state estimate from
+ // @brief save the state estimate from
// the optimizer
OdometryWithCovariance current_odom_;
+ std::string utm_zone_;
};
}
diff --git a/glider/include/glider/core/odometry.hpp b/glider/include/glider/core/odometry.hpp
index d3d3d84..a0900fd 100644
--- a/glider/include/glider/core/odometry.hpp
+++ b/glider/include/glider/core/odometry.hpp
@@ -3,10 +3,10 @@
* Jason Hughes
* May 2025
*
- * Struct to keep track of the odometry output
+ * Struct to keep track of the odometry output
* from the factor graph. This keeps track of
* everything the gtsam NavState does but adds
- * a timestamp, gyroscope reading and initilization
+ * a timestamp, gyroscope reading and initilization
* status.
*/
@@ -35,17 +35,17 @@ namespace Glider
class Odometry
{
public:
- /*! @brief default constructor of Odometry object, note that this
+ /*! @brief default constructor of Odometry object, note that this
* sets initialized_ to false */
Odometry() = default;
- /*! @brief initialize the Odometry from the result of optimization, designed to used
+ /*! @brief initialize the Odometry from the result of optimization, designed to used
* by child class upon inheritance
* @param val: results from gtsam optimization
* @param timestamp: timestamp passed to the optimizer
* @param key: current key_index to get current results from val
- * @param init: should this constuctor call initialize the odometry */
+ * @param init: should this constuctor call initialize the odometry */
Odometry(gtsam::Values& val, int64_t timestamp, gtsam::Key key, bool init = true);
- /*! @brief initialize the Odometry from a NavState, likely from calling the the pim
+ /*! @brief initialize the Odometry from a NavState, likely from calling the the pim
* predict
* @param ns: the current NavState from gtsam
* @param timestamp: the current timestamp
@@ -60,7 +60,7 @@ class Odometry
* @type T: std::pair */
template
T getPose() const;
- /*! @brief gets the 3D positon from odometry
+ /*! @brief gets the 3D positon from odometry
* @return position in 3D in UTM ENU frame (easting, northing, altitude)
* @type T: gtsam::Point3
* @type T: Eigen::Vector3d*/
@@ -71,7 +71,7 @@ class Odometry
* @type T: gtsam::Rot3
* @type T: gtsam::Quaternion
* @type T: Eigen::Vector4d
- * @type T: Eigen::Quaterniond */
+ * @type T: Eigen::Quaterniond */
template
T getOrientation() const;
/* @brief gets the 3D velocity from odometry
@@ -102,6 +102,10 @@ class Odometry
* @return true if odometry is initialized otherwise false */
bool isInitialized() const;
+ /*! @brief check if the gps offset is initialized
+ * @return true if it is otherwise false */
+ bool isGpsOffsetInitialized() const;
+
/*! @brief get the latitude of the current position
* @param zone: the utm zone ex "18S"
* @return the latitude in degrees decimal from the UTM position */
@@ -112,9 +116,9 @@ class Odometry
double getLongitude(const char* zone);
/*! @brief get the latitude and longitude as a pair
* @param zone: the utm zone, ex "18S"
- * @return latitude and longitude in degrees decimal as a pair
+ * @return latitude and longitude in degrees decimal as a pair
* where lat is first and lon is second */
- std::pair getLatLon(const char* zone);
+ std::pair getLatLon(const char* zone, const Eigen::Vector3d& offset = Eigen::Vector3d::Zero());
/*! @brief get the timestamp of the odometry
* @return nanosec time in integer format */
int64_t getTimestamp() const;
@@ -124,10 +128,15 @@ class Odometry
* otherwise false */
void setInitializedStatus(bool init);
+ /*! @brief set the gps offset initalization status
+ * @param init: true if you want the gps offset to be initialized
+ * otherwise false */
+ void setGpsOffsetInitialized(bool init);
+
protected:
/*! @brief a helper function to convert gtsam Pose3 to a pair
- * of Eigen objects
- * @return a pair of Eigen objects where first is position and second in orientation as a quaternion
+ * of Eigen objects
+ * @return a pair of Eigen objects where first is position and second in orientation as a quaternion
* @type TF: Eigen::Vector3d
* @type TS: Eigen::Vector4d
* @type TS: Eigen::Quaterniond */
@@ -138,7 +147,7 @@ class Odometry
double latitude_;
// @brief the longitude from the input UTM pose
double longitude_;
-
+
// @brief the 3D velocity in m/s
gtsam::Point3 velocity_;
// @brief the 3D position in UTM and ENU frames
@@ -157,5 +166,7 @@ class Odometry
int64_t timestamp_;
// @brief is this initialized, default to false
bool initialized_{false};
+ // @brief is the gps offset initialized, default to false
+ bool is_gps_offset_initialized_{false};
};
} // namespace glider
diff --git a/glider/include/glider/utils/parameters.hpp b/glider/include/glider/utils/parameters.hpp
index 3160331..8b9a4f3 100644
--- a/glider/include/glider/utils/parameters.hpp
+++ b/glider/include/glider/utils/parameters.hpp
@@ -44,8 +44,10 @@ struct Parameters
// @brief covariance of the IMU's bias estimate
double bias_cov;
// @brief covariance of the GPS position estimate
- // TODO make this gps_cov to match
+ // TODO make this gps_cov to match
double gps_noise;
+ // @brief covariance of the odometry position estimate
+ double odom_cov;
// @brief gravity as read from your IMU
double gravity;
@@ -61,7 +63,7 @@ struct Parameters
// @brief if true this logs to stdout and log file otherwise it logs
// just to a file
bool log;
- // @brief the directory to save the log file, glog needs an
+ // @brief the directory to save the log file, glog needs an
// absolute path
std::string log_dir;
@@ -70,7 +72,7 @@ struct Parameters
bool smooth;
// @brief amount of time in seconds for the fixed lag smoother to
// smooth over.
- double lag_time;
+ double lag_time;
// @brief wheather or not to integrate differential gps from motion heading,
// if false orientation from the IMU will be integrated
@@ -78,13 +80,16 @@ struct Parameters
// @brief velocity in m/s that the robot should be moving at to integrate
// dgpsfm
double dgpsfm_threshold;
- // @brief heading noise for differential gps from motion
+ // @brief heading noise for differential gps from motion
double dgpsfm_cov;
bool use_dgps;
double dgps_cov;
-
- // @brief translation from the GPS to the IMU
- Eigen::Vector3d t_imu_gps;
+ // Sensor poses transformed from the configured reference frame to body.
+ Eigen::Vector3d t_body_imu;
+ Eigen::Matrix3d r_body_imu;
+ Eigen::Vector3d t_body_gps;
+ double gps_heading_offset;
+ std::string body_frame;
};
}
diff --git a/glider/include/ros/conversions.hpp b/glider/include/ros/conversions.hpp
index 26a1262..50ed501 100644
--- a/glider/include/ros/conversions.hpp
+++ b/glider/include/ros/conversions.hpp
@@ -37,10 +37,10 @@ class Conversions
static Output eigenToRos(const Input& vec);
template
- static Output odomToRos(Glider::Odometry& odom, const char* zone = nullptr);
+ static Output odomToRos(Glider::Odometry& odom, std::string frame_id = "enu", const char* zone = nullptr, const Eigen::Vector3d& offset = Eigen::Vector3d::Zero());
template
- static Output odomToRos(Glider::OdometryWithCovariance& odom_wc, const char* zone = nullptr);
+ static Output odomToRos(Glider::OdometryWithCovariance& odom_wc, std::string frame_id = "enu", const char* zone = nullptr, const Eigen::Vector3d& offset = Eigen::Vector3d::Zero());
template
static void addCovariance(const Glider::OdometryWithCovariance& odom_wc, T& msg);
@@ -48,7 +48,7 @@ class Conversions
static std::chrono::milliseconds hzToDuration(const double freq);
private:
-
+
struct RosToEigen
{
static Eigen::Vector3d vector3Convert(const geometry_msgs::msg::Vector3& vec);
diff --git a/glider/include/ros/glider_node.hpp b/glider/include/ros/glider_node.hpp
index 121d5f1..7729951 100644
--- a/glider/include/ros/glider_node.hpp
+++ b/glider/include/ros/glider_node.hpp
@@ -14,7 +14,11 @@
#include
#include
#include
+#include
#include
+#include
+#include
+#include
#include "glider/core/glider.hpp"
#include "glider/core/odometry.hpp"
@@ -33,19 +37,25 @@ class GliderNode : public rclcpp::Node
private:
std::unique_ptr glider_;
- // timer callbacks
+ // timer callbacks
void interpolationCallback();
// subscriber callbacks
- void dgpsCallback(const gps_msgs::msg::GPSFix::ConstSharedPtr msg);
+ void dgpsCallback(const dgps_msgs::msg::DifferentialNavSatFix::ConstSharedPtr msg);
void gpsCallback(const sensor_msgs::msg::NavSatFix::ConstSharedPtr msg);
void imuCallback(const sensor_msgs::msg::Imu::ConstSharedPtr msg);
void magCallback(const sensor_msgs::msg::MagneticField::ConstSharedPtr msg);
void odomCallback(const nav_msgs::msg::Odometry::ConstSharedPtr msg);
void poseCallback(const geometry_msgs::msg::PoseStamped::ConstSharedPtr msg);
+ void gpsGoalCallback(const sensor_msgs::msg::NavSatFix::ConstSharedPtr msg);
// utility functions
int64_t getTime(const builtin_interfaces::msg::Time& stamp) const;
+ int64_t getSynchronizedTime(const builtin_interfaces::msg::Time& stamp, const char* source,
+ std::optional& clock_offset);
+ void updateEnvironmentState(int64_t timestamp);
+ void markGpsAccepted(int64_t timestamp);
+ void markGpsUnavailable();
void publishOdometry(Glider::OdometryWithCovariance& state) const;
void publishOdometry(Glider::Odometry& odom) const;
void publishNavSatFix(Glider::OdometryWithCovariance& state) const;
@@ -53,35 +63,53 @@ class GliderNode : public rclcpp::Node
void publishOdometryViz(nav_msgs::msg::Odometry viz_msg) const;
// subscriptions
- rclcpp::Subscription::ConstSharedPtr dgps_sub_;
+ rclcpp::Subscription::ConstSharedPtr dgps_sub_;
rclcpp::Subscription::ConstSharedPtr gps_sub_;
rclcpp::Subscription::ConstSharedPtr imu_sub_;
rclcpp::Subscription::ConstSharedPtr mag_sub_;
rclcpp::Subscription::ConstSharedPtr odom_sub_;
rclcpp::Subscription::ConstSharedPtr pose_sub_;
+ rclcpp::Subscription::SharedPtr gps_goal_sub_;
// groups
rclcpp::CallbackGroup::SharedPtr imu_group_;
rclcpp::CallbackGroup::SharedPtr gps_group_;
-
- // publishers
+
+ // publishers
rclcpp::Publisher::SharedPtr odom_pub_;
rclcpp::Publisher::SharedPtr odom_viz_pub_;
rclcpp::Publisher::SharedPtr gps_pub_;
+ rclcpp::Publisher::SharedPtr goal_pub_;
+
+ std::unique_ptr tf_broadcaster_;
// timers
rclcpp::TimerBase::SharedPtr timer_;
// parameters
- bool initialized_;
bool publish_nsf_;
bool viz_;
+ bool use_odom_;
+ bool use_gps_;
+ bool use_dgps_;
+ double gps_rejection_variance_;
+ double max_stamp_skew_sec_;
+ double gps_loss_timeout_sec_;
std::string utm_zone_;
+ std::string map_frame_;
+ std::string base_link_frame_;
double origin_easting_;
double origin_northing_;
double freq_;
// tracker
Glider::OdometryWithCovariance current_state_;
+ std::optional imu_clock_offset_;
+ std::optional gps_clock_offset_;
+ std::optional dgps_clock_offset_;
+ std::optional odom_clock_offset_;
+ enum class EnvironmentState { Unknown, Outdoor, Indoor };
+ EnvironmentState environment_state_{EnvironmentState::Unknown};
+ std::optional last_accepted_gps_time_;
};
}
diff --git a/glider/launch/glider-node.launch.py b/glider/launch/glider-node.launch.py
index 7e05987..6a8167d 100644
--- a/glider/launch/glider-node.launch.py
+++ b/glider/launch/glider-node.launch.py
@@ -23,10 +23,10 @@ def generate_launch_description():
default_value='false',
description='Use simulation time'
)
-
+
# Get launch configurations
use_sim_time = LaunchConfiguration('use_sim_time')
-
+
# Find package share directory
glider_share = FindPackageShare('glider')
glider_share_dir = get_package_share_directory('glider')
@@ -37,13 +37,13 @@ def generate_launch_description():
'config',
'ros-params.yaml'
])
-
+
graph_params_file = PathJoinSubstitution([
glider_share,
'config',
'glider-params.yaml'
])
-
+
# create logging directory
with open(os.path.join(glider_share_dir, "config", "glider-params.yaml")) as f:
config = yaml.safe_load(f)
@@ -58,14 +58,8 @@ def generate_launch_description():
parameters=[
ros_params_file,
{'path': graph_params_file,
- 'use_sim_time': use_sim_time,
- 'use_odom': False}
- ],
- remappings=[
- ('/dgps', '/dgps/fix'),
- ('/imu', '/vectornav/imu'),
- ('/odom', '/Odometry'),
+ 'use_sim_time': use_sim_time}
]
)
-
+
return LaunchDescription([use_sim_time_arg, glider_node])
diff --git a/glider/package.xml b/glider/package.xml
index 849bb37..0fa16b0 100644
--- a/glider/package.xml
+++ b/glider/package.xml
@@ -17,6 +17,8 @@
sensor_msgs
nav_msgs
gps_msgs
+ dgps_msgs
+ tf2_ros
tf2_eigen
message_filters
diff --git a/glider/ros/conversions.cpp b/glider/ros/conversions.cpp
index 0034c04..c589080 100644
--- a/glider/ros/conversions.cpp
+++ b/glider/ros/conversions.cpp
@@ -1,12 +1,14 @@
/*
* Jason Hughes
-* July 2025
+* July 2025
*
* convert between ros and eigen
*/
#include "ros/conversions.hpp"
+#include
+
using namespace GliderROS;
template
@@ -84,7 +86,7 @@ Eigen::Isometry3d Conversions::RosToEigen::poseConvert(const geometry_msgs::msg:
Eigen::Vector3d Conversions::RosToEigen::vector3Convert(const geometry_msgs::msg::Vector3& msg)
{
return Eigen::Vector3d(msg.x, msg.y, msg.z);
-}
+}
Eigen::Vector4d Conversions::RosToEigen::orientConvert(const geometry_msgs::msg::Quaternion& msg)
{
@@ -105,7 +107,7 @@ std::pair Conversions::RosToEigen::dgpsConvert
}
Eigen::Isometry3d Conversions::RosToEigen::odomConvert(const nav_msgs::msg::Odometry& msg)
-{
+{
Eigen::Quaterniond quat(msg.pose.pose.orientation.w, msg.pose.pose.orientation.x, msg.pose.pose.orientation.y, msg.pose.pose.orientation.z);
Eigen::Vector3d trans(msg.pose.pose.position.x, msg.pose.pose.position.y, msg.pose.pose.position.z);
@@ -175,29 +177,42 @@ std_msgs::msg::Header Conversions::getHeader(int64_t timestamp, std::string fram
}
template
-Output Conversions::odomToRos(Glider::Odometry& odom, const char* zone)
+Output Conversions::odomToRos(Glider::Odometry& odom, std::string frame_id, const char* zone, const Eigen::Vector3d& offset)
{
if constexpr (std::is_same_v