|
@@ -11,13 +11,17 @@
|
|
|
#include <deque>
|
|
#include <deque>
|
|
|
#include <thread>
|
|
#include <thread>
|
|
|
#include <atomic>
|
|
#include <atomic>
|
|
|
|
|
+#include <mutex>
|
|
|
#include <sstream>
|
|
#include <sstream>
|
|
|
#include <iomanip>
|
|
#include <iomanip>
|
|
|
#include <signal.h>
|
|
#include <signal.h>
|
|
|
|
|
+#include <chrono>
|
|
|
|
|
|
|
|
#include "rclcpp/rclcpp.hpp"
|
|
#include "rclcpp/rclcpp.hpp"
|
|
|
#include "nmea_msgs/msg/sentence.hpp"
|
|
#include "nmea_msgs/msg/sentence.hpp"
|
|
|
|
|
+#include "cruise_msgs/msg/location_status.hpp"
|
|
|
#include "geometry_msgs/msg/twist_stamped.hpp"
|
|
#include "geometry_msgs/msg/twist_stamped.hpp"
|
|
|
|
|
+#include "eagleye_msgs/msg/heading.hpp"
|
|
|
#include <cmath>
|
|
#include <cmath>
|
|
|
|
|
|
|
|
class SerialNtripNmea : public rclcpp::Node
|
|
class SerialNtripNmea : public rclcpp::Node
|
|
@@ -49,6 +53,10 @@ public:
|
|
|
|
|
|
|
|
// 创建NMEA消息发布器
|
|
// 创建NMEA消息发布器
|
|
|
nmea_pub_ = this->create_publisher<nmea_msgs::msg::Sentence>(nmea_topic_, 10);
|
|
nmea_pub_ = this->create_publisher<nmea_msgs::msg::Sentence>(nmea_topic_, 10);
|
|
|
|
|
+ nmea_status_pub_ = this->create_publisher<cruise_msgs::msg::LocationStatus>("/cruise/LocationStatus", 10);
|
|
|
|
|
+ nmea_status_timer_ = this->create_wall_timer(
|
|
|
|
|
+ std::chrono::seconds(1),
|
|
|
|
|
+ std::bind(&SerialNtripNmea::publish_nmea_status, this));
|
|
|
// 额外创建一个带 RMC 航向修正的 NMEA 话题,保持原有话题行为不变
|
|
// 额外创建一个带 RMC 航向修正的 NMEA 话题,保持原有话题行为不变
|
|
|
nmea_fix_pub_ = this->create_publisher<nmea_msgs::msg::Sentence>("navsat/nmea_sentence_fix", 10);
|
|
nmea_fix_pub_ = this->create_publisher<nmea_msgs::msg::Sentence>("navsat/nmea_sentence_fix", 10);
|
|
|
|
|
|
|
@@ -56,6 +64,10 @@ public:
|
|
|
twist_sub_ = this->create_subscription<geometry_msgs::msg::TwistStamped>(
|
|
twist_sub_ = this->create_subscription<geometry_msgs::msg::TwistStamped>(
|
|
|
"/cruise/wheel/twist", 10,
|
|
"/cruise/wheel/twist", 10,
|
|
|
std::bind(&SerialNtripNmea::twist_callback, this, std::placeholders::_1));
|
|
std::bind(&SerialNtripNmea::twist_callback, this, std::placeholders::_1));
|
|
|
|
|
+ // 订阅 /eagleye/heading_3rd,首次收到数据时启用静止航向修正
|
|
|
|
|
+ heading_3rd_sub_ = this->create_subscription<eagleye_msgs::msg::Heading>(
|
|
|
|
|
+ "/eagleye/heading_3rd", 10,
|
|
|
|
|
+ std::bind(&SerialNtripNmea::heading_3rd_callback, this, std::placeholders::_1));
|
|
|
|
|
|
|
|
RCLCPP_INFO(this->get_logger(), "SerialNtripNmea初始化:");
|
|
RCLCPP_INFO(this->get_logger(), "SerialNtripNmea初始化:");
|
|
|
RCLCPP_INFO(this->get_logger(), " 串口: %s @ %d baud", serial_port_.c_str(), baud_rate_);
|
|
RCLCPP_INFO(this->get_logger(), " 串口: %s @ %d baud", serial_port_.c_str(), baud_rate_);
|
|
@@ -91,16 +103,65 @@ private:
|
|
|
int serial_fd_{-1};
|
|
int serial_fd_{-1};
|
|
|
int ntrip_socket_{-1};
|
|
int ntrip_socket_{-1};
|
|
|
rclcpp::Publisher<nmea_msgs::msg::Sentence>::SharedPtr nmea_pub_;
|
|
rclcpp::Publisher<nmea_msgs::msg::Sentence>::SharedPtr nmea_pub_;
|
|
|
|
|
+ rclcpp::Publisher<cruise_msgs::msg::LocationStatus>::SharedPtr nmea_status_pub_;
|
|
|
|
|
+ rclcpp::TimerBase::SharedPtr nmea_status_timer_;
|
|
|
rclcpp::Publisher<nmea_msgs::msg::Sentence>::SharedPtr nmea_fix_pub_;
|
|
rclcpp::Publisher<nmea_msgs::msg::Sentence>::SharedPtr nmea_fix_pub_;
|
|
|
rclcpp::Subscription<geometry_msgs::msg::TwistStamped>::SharedPtr twist_sub_;
|
|
rclcpp::Subscription<geometry_msgs::msg::TwistStamped>::SharedPtr twist_sub_;
|
|
|
|
|
+ rclcpp::Subscription<eagleye_msgs::msg::Heading>::SharedPtr heading_3rd_sub_;
|
|
|
rclcpp::TimerBase::SharedPtr status_timer_;
|
|
rclcpp::TimerBase::SharedPtr status_timer_;
|
|
|
std::atomic<bool> running_{false};
|
|
std::atomic<bool> running_{false};
|
|
|
std::atomic<bool> ntrip_connected_{false};
|
|
std::atomic<bool> ntrip_connected_{false};
|
|
|
|
|
+ std::atomic<int64_t> last_serial_rx_ms_{0};
|
|
|
|
|
+ std::atomic<int64_t> last_gga_valid_ms_{0};
|
|
|
|
|
+
|
|
|
|
|
+ static int64_t now_ms()
|
|
|
|
|
+ {
|
|
|
|
|
+ return std::chrono::duration_cast<std::chrono::milliseconds>(
|
|
|
|
|
+ std::chrono::steady_clock::now().time_since_epoch())
|
|
|
|
|
+ .count();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ bool gga_has_lat_lon(const std::string & nmea_sentence)
|
|
|
|
|
+ {
|
|
|
|
|
+ // GGA格式中: field[2]=latitude, field[4]=longitude
|
|
|
|
|
+ std::vector<std::string> fields;
|
|
|
|
|
+ std::stringstream ss(nmea_sentence);
|
|
|
|
|
+ std::string item;
|
|
|
|
|
+ while (std::getline(ss, item, ',')) {
|
|
|
|
|
+ fields.push_back(item);
|
|
|
|
|
+ }
|
|
|
|
|
+ if (fields.size() < 5) {
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+ const std::string & lat = fields[2];
|
|
|
|
|
+ const std::string & lon = fields[4];
|
|
|
|
|
+ return !lat.empty() && !lon.empty() && lat != "0" && lon != "0";
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ void publish_nmea_status()
|
|
|
|
|
+ {
|
|
|
|
|
+ cruise_msgs::msg::LocationStatus status_msg;
|
|
|
|
|
+ status_msg.header.stamp = this->now();
|
|
|
|
|
+ status_msg.header.frame_id = frame_id_;
|
|
|
|
|
+
|
|
|
|
|
+ const int64_t now = now_ms();
|
|
|
|
|
+ const int64_t serial_gap_ms = now - last_serial_rx_ms_.load();
|
|
|
|
|
+ const int64_t gga_gap_ms = now - last_gga_valid_ms_.load();
|
|
|
|
|
+
|
|
|
|
|
+ status_msg.ntrip_account_ok = ntrip_connected_.load() ? 1 : 0;
|
|
|
|
|
+ status_msg.serial_rx_ok =
|
|
|
|
|
+ (last_serial_rx_ms_.load() > 0 && serial_gap_ms <= 3000) ? 1 : 0;
|
|
|
|
|
+ status_msg.gga_lat_lon_ok =
|
|
|
|
|
+ (last_gga_valid_ms_.load() > 0 && gga_gap_ms <= 3000) ? 1 : 0;
|
|
|
|
|
+
|
|
|
|
|
+ nmea_status_pub_->publish(status_msg);
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
// 静止判定:从 /cruise/wheel/twist 获取,linear.x==0 且 angular.z==0 为静止
|
|
// 静止判定:从 /cruise/wheel/twist 获取,linear.x==0 且 angular.z==0 为静止
|
|
|
std::atomic<bool> is_stationary_{false};
|
|
std::atomic<bool> is_stationary_{false};
|
|
|
|
|
|
|
|
// RMC 静止航向修正相关状态(仅用于 navsat/nmea_sentence_fix)
|
|
// RMC 静止航向修正相关状态(仅用于 navsat/nmea_sentence_fix)
|
|
|
|
|
+ std::atomic<bool> stationary_heading_enabled_{false}; // 默认不启用,首次收到 /eagleye/heading_3rd 时启用
|
|
|
std::deque<double> rmc_heading_history_;
|
|
std::deque<double> rmc_heading_history_;
|
|
|
bool in_stationary_mode_{false};
|
|
bool in_stationary_mode_{false};
|
|
|
bool stationary_heading_locked_{false};
|
|
bool stationary_heading_locked_{false};
|
|
@@ -215,9 +276,11 @@ private:
|
|
|
RCLCPP_INFO(this->get_logger(), "NTRIP连接成功: %s", mountpoint_.c_str());
|
|
RCLCPP_INFO(this->get_logger(), "NTRIP连接成功: %s", mountpoint_.c_str());
|
|
|
return true;
|
|
return true;
|
|
|
} else {
|
|
} else {
|
|
|
|
|
+ ntrip_connected_ = false;
|
|
|
RCLCPP_ERROR(this->get_logger(), "NTRIP连接失败: %s", response.c_str());
|
|
RCLCPP_ERROR(this->get_logger(), "NTRIP连接失败: %s", response.c_str());
|
|
|
}
|
|
}
|
|
|
} else {
|
|
} else {
|
|
|
|
|
+ ntrip_connected_ = false;
|
|
|
RCLCPP_ERROR(this->get_logger(), "未收到NTRIP服务器响应");
|
|
RCLCPP_ERROR(this->get_logger(), "未收到NTRIP服务器响应");
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -232,6 +295,13 @@ private:
|
|
|
is_stationary_.store(stationary);
|
|
is_stationary_.store(stationary);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ void heading_3rd_callback(const eagleye_msgs::msg::Heading::ConstSharedPtr /*msg*/)
|
|
|
|
|
+ {
|
|
|
|
|
+ if (!stationary_heading_enabled_.exchange(true)) {
|
|
|
|
|
+ RCLCPP_INFO(this->get_logger(), "收到 /eagleye/heading_3rd,已启用静止航向修正");
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
void start_server()
|
|
void start_server()
|
|
|
{
|
|
{
|
|
|
// 启动串口数据读取和NTRIP发送线程
|
|
// 启动串口数据读取和NTRIP发送线程
|
|
@@ -242,6 +312,13 @@ private:
|
|
|
std::thread ntrip_thread(&SerialNtripNmea::read_ntrip_and_write_serial, this);
|
|
std::thread ntrip_thread(&SerialNtripNmea::read_ntrip_and_write_serial, this);
|
|
|
ntrip_thread.detach();
|
|
ntrip_thread.detach();
|
|
|
|
|
|
|
|
|
|
+ // 主循环 - 显示状态信息
|
|
|
|
|
+ rclcpp::Rate rate(1); // 1Hz
|
|
|
|
|
+ while (rclcpp::ok() && running_) {
|
|
|
|
|
+ publish_nmea_status();
|
|
|
|
|
+ RCLCPP_INFO(this->get_logger(), "运行状态 - NTRIP连接: %s", ntrip_connected_ ? "是" : "否");
|
|
|
|
|
+ rate.sleep();
|
|
|
|
|
+ }
|
|
|
// 使用定时器显示状态,使 spin() 能处理 twist 订阅回调
|
|
// 使用定时器显示状态,使 spin() 能处理 twist 订阅回调
|
|
|
status_timer_ = this->create_wall_timer(
|
|
status_timer_ = this->create_wall_timer(
|
|
|
std::chrono::seconds(1),
|
|
std::chrono::seconds(1),
|
|
@@ -262,6 +339,7 @@ private:
|
|
|
while (rclcpp::ok() && running_) {
|
|
while (rclcpp::ok() && running_) {
|
|
|
ssize_t bytes_read = read(serial_fd_, buffer.data(), buffer.size());
|
|
ssize_t bytes_read = read(serial_fd_, buffer.data(), buffer.size());
|
|
|
if (bytes_read > 0) {
|
|
if (bytes_read > 0) {
|
|
|
|
|
+ last_serial_rx_ms_ = now_ms();
|
|
|
// 处理接收到的数据
|
|
// 处理接收到的数据
|
|
|
for (ssize_t i = 0; i < bytes_read; ++i) {
|
|
for (ssize_t i = 0; i < bytes_read; ++i) {
|
|
|
char c = buffer[i];
|
|
char c = buffer[i];
|
|
@@ -324,6 +402,10 @@ private:
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
|
|
+ if (is_gga && gga_has_lat_lon(nmea_buffer)) {
|
|
|
|
|
+ last_gga_valid_ms_ = now_ms();
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
nmea_buffer.clear();
|
|
nmea_buffer.clear();
|
|
@@ -520,14 +602,14 @@ private:
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
double output_heading = heading_deg;
|
|
double output_heading = heading_deg;
|
|
|
- if (in_stationary_mode_ && stationary_heading_locked_) {
|
|
|
|
|
|
|
+ if (stationary_heading_enabled_.load() && in_stationary_mode_ && stationary_heading_locked_) {
|
|
|
output_heading = stationary_locked_heading_;
|
|
output_heading = stationary_locked_heading_;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// 更新航向字段
|
|
// 更新航向字段
|
|
|
{
|
|
{
|
|
|
std::ostringstream oss;
|
|
std::ostringstream oss;
|
|
|
- oss << std::fixed << std::setprecision(2) << output_heading;
|
|
|
|
|
|
|
+ oss << std::fixed << std::setprecision(1) << output_heading;
|
|
|
fields[8] = oss.str();
|
|
fields[8] = oss.str();
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -566,4 +648,4 @@ int main(int argc, char** argv)
|
|
|
rclcpp::spin(node);
|
|
rclcpp::spin(node);
|
|
|
rclcpp::shutdown();
|
|
rclcpp::shutdown();
|
|
|
return 0;
|
|
return 0;
|
|
|
-}
|
|
|
|
|
|
|
+}
|