Skip to content

Commit 706a3fb

Browse files
committed
add gprmc_publisher_node
1 parent e9ac1df commit 706a3fb

File tree

2 files changed

+194
-5
lines changed

2 files changed

+194
-5
lines changed

example/ros2_example/CMakeLists.txt

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ find_package(ament_cmake REQUIRED)
66
find_package(rclcpp REQUIRED)
77
find_package(rclcpp_components REQUIRED)
88
find_package(std_msgs REQUIRED)
9+
find_package(sensor_msgs REQUIRED)
910

1011
# === InfiniteSense SDK ===
1112
set(INFINITE_SENSE_INCLUDE_DIR "${CMAKE_SOURCE_DIR}/infinite_sense_core/include")
@@ -34,29 +35,31 @@ ament_target_dependencies(synchronizer_component
3435

3536
rclcpp_components_register_nodes(synchronizer_component SynchronizerComponent)
3637

38+
## === ROS2 GPRMC Publisher Node ===
39+
add_executable(gprmc_publisher_node gprmc_publisher_node.cpp)
40+
ament_target_dependencies(gprmc_publisher_node rclcpp std_msgs sensor_msgs)
41+
3742
# === Standalone ROS 2 Node (executable) ===
3843
add_executable(synchronizer_node
3944
synchronizer_node.cpp
4045
)
41-
4246
target_include_directories(synchronizer_node
4347
PUBLIC
4448
${INFINITE_SENSE_INCLUDE_DIR}
4549
)
46-
4750
target_link_libraries(synchronizer_node
4851
${INFINITE_SENSE_LIB_NAME}
4952
)
50-
5153
ament_target_dependencies(synchronizer_node
5254
rclcpp
5355
std_msgs
5456
)
5557

5658
# === Installation ===
5759
install(TARGETS
58-
synchronizer_component
59-
synchronizer_node
60+
synchronizer_component
61+
synchronizer_node
62+
gprmc_publisher_node
6063
ARCHIVE DESTINATION lib
6164
LIBRARY DESTINATION lib
6265
RUNTIME DESTINATION lib/${PROJECT_NAME}
Lines changed: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,186 @@
1+
#include <fstream>
2+
#include <string>
3+
#include <sstream>
4+
#include <iomanip>
5+
#include <termios.h>
6+
#include <fcntl.h>
7+
#include <unistd.h>
8+
9+
#include <rclcpp/rclcpp.hpp>
10+
#include <std_msgs/msg/string.hpp>
11+
#include <sensor_msgs/msg/time_reference.hpp>
12+
13+
class GprmcPublisher : public rclcpp::Node {
14+
public:
15+
GprmcPublisher() : Node("gprmc_publisher") {
16+
this->declare_parameter<std::string>("port", "/dev/ttyUSB0");
17+
this->declare_parameter<int>("baud", 115200);
18+
19+
std::string port;
20+
int baud;
21+
this->get_parameter("port", port);
22+
this->get_parameter("baud", baud);
23+
24+
publisher_ = this->create_publisher<std_msgs::msg::String>("/gps/gprmc", 10);
25+
time_pub_ = this->create_publisher<sensor_msgs::msg::TimeReference>("/gps/time_reference", 10);
26+
27+
fd_ = open(port.c_str(), O_RDWR | O_NOCTTY | O_SYNC);
28+
if (fd_ < 0) {
29+
RCLCPP_FATAL(this->get_logger(), "Failed to open port: %s", port.c_str());
30+
rclcpp::shutdown();
31+
return;
32+
}
33+
34+
configure_serial(fd_, baud);
35+
timer_ =
36+
this->create_wall_timer(std::chrono::milliseconds(100), std::bind(&GprmcPublisher::read_and_publish, this));
37+
38+
RCLCPP_INFO(this->get_logger(), "Opened %s @ %d baud", port.c_str(), baud);
39+
}
40+
41+
~GprmcPublisher() {
42+
if (fd_ >= 0) close(fd_);
43+
}
44+
45+
private:
46+
void configure_serial(int fd, int baudrate) {
47+
struct termios tty;
48+
tcgetattr(fd, &tty);
49+
cfsetospeed(&tty, baudrate_to_constant(baudrate));
50+
cfsetispeed(&tty, baudrate_to_constant(baudrate));
51+
tty.c_cflag = (tty.c_cflag & ~CSIZE) | CS8;
52+
tty.c_iflag = IGNBRK;
53+
tty.c_lflag = 0;
54+
tty.c_oflag = 0;
55+
tty.c_cc[VMIN] = 0;
56+
tty.c_cc[VTIME] = 10;
57+
tty.c_cflag |= (CLOCAL | CREAD);
58+
tty.c_cflag &= ~(PARENB | PARODD);
59+
tty.c_cflag &= ~CSTOPB;
60+
tty.c_cflag &= ~CRTSCTS;
61+
tcsetattr(fd, TCSANOW, &tty);
62+
}
63+
64+
speed_t baudrate_to_constant(int baud) {
65+
switch (baud) {
66+
case 9600:
67+
return B9600;
68+
case 19200:
69+
return B19200;
70+
case 38400:
71+
return B38400;
72+
case 57600:
73+
return B57600;
74+
case 115200:
75+
return B115200;
76+
default:
77+
RCLCPP_FATAL(this->get_logger(), "Unsupported baudrate: %d", baud);
78+
rclcpp::shutdown();
79+
return B9600;
80+
}
81+
}
82+
83+
void read_and_publish() {
84+
char buf[256];
85+
ssize_t n = read(fd_, buf, sizeof(buf));
86+
if (n <= 0) return;
87+
88+
for (ssize_t i = 0; i < n; ++i) {
89+
if (buf[i] == '$') {
90+
line_.clear();
91+
}
92+
line_ += buf[i];
93+
if (buf[i] == '\n') {
94+
if (is_valid_checksum(line_)) {
95+
// ---> Publish GPRMC as String
96+
std_msgs::msg::String msg;
97+
msg.data = line_;
98+
std::cout << "Data: " << line_ << std::endl;
99+
publisher_->publish(msg);
100+
101+
// ---> Publish GPRMC as TimeReference
102+
auto maybe_time = parse_gprmc_utc(line_);
103+
if (maybe_time.has_value()) {
104+
sensor_msgs::msg::TimeReference tref;
105+
tref.header.stamp = this->now();
106+
tref.header.frame_id = "infinite_sense";
107+
tref.source = "infinite_sense";
108+
tref.time_ref = maybe_time.value();
109+
time_pub_->publish(tref);
110+
}
111+
}
112+
line_.clear();
113+
}
114+
}
115+
}
116+
117+
bool is_valid_checksum(const std::string &line) {
118+
auto asterisk = line.find('*');
119+
if (asterisk == std::string::npos || asterisk + 2 >= line.size()) return false;
120+
121+
uint8_t checksum = 0;
122+
for (size_t i = 1; i < asterisk; ++i) {
123+
checksum ^= line[i];
124+
}
125+
126+
std::istringstream iss(line.substr(asterisk + 1, 2));
127+
int sent_checksum;
128+
iss >> std::hex >> sent_checksum;
129+
return checksum == sent_checksum;
130+
}
131+
132+
std::optional<builtin_interfaces::msg::Time> parse_gprmc_utc(const std::string &nmea_line) {
133+
std::vector<std::string> fields;
134+
std::istringstream ss(nmea_line);
135+
std::string token;
136+
while (std::getline(ss, token, ',')) {
137+
fields.push_back(token);
138+
}
139+
140+
if (fields.size() > 9 && fields[1].length() >= 6 && fields[9].length() >= 6) {
141+
try {
142+
std::string hh = fields[1].substr(0, 2);
143+
std::string mm = fields[1].substr(2, 2);
144+
std::string ss = fields[1].substr(4, 2);
145+
146+
std::string dd = fields[9].substr(0, 2);
147+
std::string mo = fields[9].substr(2, 2);
148+
std::string yy = fields[9].substr(4, 2);
149+
150+
std::tm timeinfo = {};
151+
timeinfo.tm_year = std::stoi(yy) + 100;
152+
timeinfo.tm_mon = std::stoi(mo) - 1;
153+
timeinfo.tm_mday = std::stoi(dd);
154+
timeinfo.tm_hour = std::stoi(hh);
155+
timeinfo.tm_min = std::stoi(mm);
156+
timeinfo.tm_sec = std::stoi(ss);
157+
timeinfo.tm_isdst = 0;
158+
159+
time_t utc_sec = timegm(&timeinfo);
160+
if (utc_sec == -1) return std::nullopt;
161+
162+
builtin_interfaces::msg::Time t;
163+
t.sec = static_cast<int32_t>(utc_sec);
164+
t.nanosec = 0;
165+
return t;
166+
} catch (...) {
167+
return std::nullopt;
168+
}
169+
}
170+
171+
return std::nullopt;
172+
}
173+
174+
int fd_;
175+
std::string line_;
176+
rclcpp::TimerBase::SharedPtr timer_;
177+
rclcpp::Publisher<std_msgs::msg::String>::SharedPtr publisher_;
178+
rclcpp::Publisher<sensor_msgs::msg::TimeReference>::SharedPtr time_pub_;
179+
};
180+
181+
int main(int argc, char **argv) {
182+
rclcpp::init(argc, argv);
183+
rclcpp::spin(std::make_shared<GprmcPublisher>());
184+
rclcpp::shutdown();
185+
return 0;
186+
}

0 commit comments

Comments
 (0)