Initial commit

This commit is contained in:
2026-07-27 13:51:19 +08:00
commit 7bec56ca51
5408 changed files with 1126933 additions and 0 deletions
+65
View File
@@ -0,0 +1,65 @@
cmake_minimum_required(VERSION 3.0.2)
project(rc_receiver)
# 查找必要的catkin组件
find_package(catkin REQUIRED COMPONENTS
roscpp
serial
std_msgs
sensor_msgs
message_generation
)
# 声明消息文件(位于msg/rc.msg
add_message_files(
FILES
rc.msg
)
# 生成消息文件,依赖std_msgs
generate_messages(
DEPENDENCIES
std_msgs
)
# 配置catkin包
catkin_package(
INCLUDE_DIRS include
LIBRARIES rc_receiver
CATKIN_DEPENDS roscpp serial std_msgs message_runtime sensor_msgs
DEPENDS system_lib
)
# 包含头文件路径
include_directories(
include
${catkin_INCLUDE_DIRS}
)
# 编译可执行文件
add_executable(rc_receiver_node
src/rc_receiver_node.cpp
)
# 添加消息生成依赖,确保先生成消息再编译可执行文件
add_dependencies(rc_receiver_node
${${PROJECT_NAME}_EXPORTED_TARGETS}
${catkin_EXPORTED_TARGETS}
)
# 链接依赖库
target_link_libraries(rc_receiver_node
${catkin_LIBRARIES}
)
# 安装可执行文件
install(TARGETS rc_receiver_node
RUNTIME DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION}
)
# 安装头文件
install(DIRECTORY include/${PROJECT_NAME}/
DESTINATION ${CATKIN_PACKAGE_INCLUDE_DESTINATION}
FILES_MATCHING PATTERN "*.h"
PATTERN ".svn" EXCLUDE
)
+48
View File
@@ -0,0 +1,48 @@
#ifndef RC_RECEIVER_H
#define RC_RECEIVER_H
#include <ros/ros.h>
#include <serial/serial.h>
#include <rc_receiver/rc.h>
#include <vector>
#include "sensor_msgs/BatteryState.h"
class RCReceiver {
public:
RCReceiver();
void run();
private:
ros::Subscriber battery_state_sub_;
std::string input_battery_state_topic_;
void readSerialData();
void parseFrameData();
bool parseModbusProtocol(const std::vector<uint8_t> &frame);
void battery_status_callback(const sensor_msgs::BatteryState::ConstPtr &msg);
void checkTimeout(const ros::TimerEvent& event);
ros::NodeHandle nh_;
ros::NodeHandle private_nh_;
serial::Serial ser_;
ros::Publisher control_pub_;
ros::Timer timeoutTimer_;
std::vector<uint8_t> serial_buffer_;
ros::Time lastReceivedTime_;
ros::Time lastReconnectAttempt_; // 记录上一次尝试重连的时间
const uint8_t AUTO_DRIVING;
const uint8_t REMOTE_CONTROL;
size_t frames_received_;
size_t frames_discarded_;
bool debug_;
// 新增:串口配置参数(用于重连)
std::string port_; // 串口设备路径
int baudrate_; // 波特率
};
#endif // RC_RECEIVER_H
@@ -0,0 +1,10 @@
<launch>
<node name="rc_receiver_node" pkg="rc_receiver" type="rc_receiver_node" output="screen">
<!-- 直接设置参数,无需通过arg中转 -->
<param name="port" value="/dev/stm32_rc"/>
<param name="baudrate" value="115200"/>
<param name="output_remote_ctrl_topic_" value="/remote_ctrl"/>
<param name="input_battery_state_topic_" value="/sensor/battery_state"/>
<param name="debug" value="false"/>
</node>
</launch>
+12
View File
@@ -0,0 +1,12 @@
# 线性速度(单位:m/s
float32 linear_x
float32 linear_y
float32 linear_z
# 转向速度(角速度,单位:rad/s)
float32 angular_x
float32 angular_y
float32 angular_z
# 控制模式(0: auto_driving1: remote_control
int8 control_mode
+72
View File
@@ -0,0 +1,72 @@
<?xml version="1.0"?>
<package format="2">
<name>rc_receiver</name>
<version>0.0.0</version>
<description>The rc_receiver package</description>
<!-- One maintainer tag required, multiple allowed, one person per tag -->
<!-- Example: -->
<!-- <maintainer email="jane.doe@example.com">Jane Doe</maintainer> -->
<maintainer email="dreamdeck@todo.todo">dreamdeck</maintainer>
<!-- One license tag required, multiple allowed, one license per tag -->
<!-- Commonly used license strings: -->
<!-- BSD, MIT, Boost Software License, GPLv2, GPLv3, LGPLv2.1, LGPLv3 -->
<license>TODO</license>
<!-- Url tags are optional, but multiple are allowed, one per tag -->
<!-- Optional attribute type can be: website, bugtracker, or repository -->
<!-- Example: -->
<!-- <url type="website">http://wiki.ros.org/rc_receiver</url> -->
<!-- Author tags are optional, multiple are allowed, one per tag -->
<!-- Authors do not have to be maintainers, but could be -->
<!-- Example: -->
<!-- <author email="jane.doe@example.com">Jane Doe</author> -->
<!-- The *depend tags are used to specify dependencies -->
<!-- Dependencies can be catkin packages or system dependencies -->
<!-- Examples: -->
<!-- Use depend as a shortcut for packages that are both build and exec dependencies -->
<!-- <depend>roscpp</depend> -->
<!-- Note that this is equivalent to the following: -->
<!-- <build_depend>roscpp</build_depend> -->
<!-- <exec_depend>roscpp</exec_depend> -->
<!-- Use build_depend for packages you need at compile time: -->
<!-- <build_depend>message_generation</build_depend> -->
<!-- Use build_export_depend for packages you need in order to build against this package: -->
<!-- <build_export_depend>message_generation</build_export_depend> -->
<!-- Use buildtool_depend for build tool packages: -->
<!-- <buildtool_depend>catkin</buildtool_depend> -->
<!-- Use exec_depend for packages you need at runtime: -->
<!-- <exec_depend>message_runtime</exec_depend> -->
<!-- Use test_depend for packages you need only for testing: -->
<!-- <test_depend>gtest</test_depend> -->
<!-- Use doc_depend for packages you need only for building documentation: -->
<!-- <doc_depend>doxygen</doc_depend> -->
<buildtool_depend>catkin</buildtool_depend>
<build_depend>roscpp</build_depend>
<build_depend>serial</build_depend>
<build_depend>std_msgs</build_depend>
<build_depend>sensor_msgs</build_depend>
<build_export_depend>roscpp</build_export_depend>
<build_export_depend>serial</build_export_depend>
<build_export_depend>std_msgs</build_export_depend>
<build_export_depend>sensor_msgs</build_export_depend>
<build_depend>message_generation</build_depend>
<exec_depend>message_runtime</exec_depend>
<exec_depend>roscpp</exec_depend>
<exec_depend>serial</exec_depend>
<exec_depend>std_msgs</exec_depend>
<!-- The export tag contains other, unspecified, tags -->
<export>
<!-- Other tools can request additional information be placed here -->
</export>
</package>
@@ -0,0 +1,481 @@
#include "rc_receiver.h"
#include <sstream>
#include <vector>
#include <algorithm>
#include <string>
#include <cmath>
#include <iomanip>
using namespace std;
RCReceiver::RCReceiver()
: AUTO_DRIVING(0),
REMOTE_CONTROL(1),
frames_received_(0),
frames_discarded_(0),
debug_(false),
private_nh_("~"),
nh_("~"),
lastReceivedTime_(ros::Time::now())
{
// 从参数服务器获取串口配置(默认设为/dev/stm32_rc
private_nh_.param<std::string>("port", port_, "/dev/stm32_rc"); // 默认设备路径
private_nh_.param<int>("baudrate", baudrate_, 115200);
// debug参数可以有默认值(可选参数)
nh_.param<bool>("debug", debug_, false);
private_nh_.param<std::string>("input_battery_state_topic_", input_battery_state_topic_, "");
ROS_INFO("Configuration: port=%s, baudrate=%d, debug=%s",
port_.c_str(), baudrate_, debug_ ? "true" : "false");
lastReconnectAttempt_ = ros::Time(0); // 初始化为0时刻(远早于当前时间)
// 设置日志级别
if (debug_)
{
if (ros::console::set_logger_level(ROSCONSOLE_DEFAULT_NAME,
ros::console::levels::Debug))
{
ros::console::notifyLoggerLevelsChanged();
}
}
// 初始化串口(首次连接)
try
{
ser_.setPort(port_);
ser_.setBaudrate(baudrate_);
// 创建超时对象
serial::Timeout timeout = serial::Timeout::simpleTimeout(100);
ser_.setTimeout(timeout);
ser_.open();
// 清空启动时的垃圾数据
ser_.flushInput();
ros::Duration(0.1).sleep();
ROS_INFO("Serial port %s opened successfully at %d baud",
port_.c_str(), baudrate_);
ROS_INFO("Serial settings: timeout=%dms",
timeout.read_timeout_constant);
}
catch (const serial::IOException &e)
{
ROS_ERROR("Initial failed to open serial port %s: %s",
port_.c_str(), e.what());
// 不立即退出,留待后续重连
}
// 初始化发布者
string topic_name;
if (!nh_.getParam("output_remote_ctrl_topic_", topic_name))
{
ROS_FATAL("Missing required parameter: output_remote_ctrl_topic_");
ros::shutdown();
return;
}
control_pub_ = nh_.advertise<rc_receiver::rc>(topic_name, 10);
ROS_INFO("Publisher created for topic: %s", topic_name.c_str());
// 初始化超时定时器(每0.5秒检查一次连接状态)
timeoutTimer_ = nh_.createTimer(ros::Duration(0.5), &RCReceiver::checkTimeout, this);
}
void RCReceiver::run()
{
ros::Rate rate(20);
ROS_INFO("Starting main loop (20Hz)");
battery_state_sub_ = nh_.subscribe<sensor_msgs::BatteryState>(
input_battery_state_topic_, 1, &RCReceiver::battery_status_callback, this);
short loopCount = 0;
while (ros::ok())
{
loopCount++;
ROS_DEBUG("Loop %d start", loopCount);
readSerialData(); // 这里会处理重连逻辑
ros::spinOnce();
// 每10秒报告一次状态
if (loopCount % 200 == 0)
{
ROS_INFO("Still running. Serial buffer: %zu bytes, Frames: %zu good, %zu bad",
serial_buffer_.size(), frames_received_, frames_discarded_);
loopCount = 0;
}
rate.sleep();
}
// 退出时关闭串口
if (ser_.isOpen())
{
ser_.close();
ROS_INFO("Serial port %s closed", port_.c_str());
}
}
// 电池状态回调函数(保持不变)
uint8_t battery_info[6] = {0x5F, 0x00, 0x00, 0x00, 0x00, 0X9B};
void RCReceiver::battery_status_callback(const sensor_msgs::BatteryState::ConstPtr &msg)
{
if (!ser_.isOpen())
return; // 串口未打开时不发送数据
uint16_t soc_ = msg->percentage * 10;
battery_info[1] = (msg->power_supply_status == 1) ? 1 : 0;
battery_info[2] = (soc_ & 0xFFFF) >> 8;
battery_info[3] = soc_ & 0xFF;
uint8_t checksum = 0;
for (size_t i = 0; i < 4; ++i)
{
checksum ^= battery_info[i];
}
battery_info[4] = checksum;
try
{
ser_.write(battery_info, sizeof(battery_info));
}
catch (const serial::IOException &e)
{
ROS_ERROR("Failed to write battery info: %s", e.what());
ser_.close(); // 写失败时主动关闭,触发重连
}
}
void RCReceiver::readSerialData()
{
if (!ser_.isOpen())
{
// 计算当前时间与上次重连尝试的时间差(秒)
double timeSinceLastAttempt = (ros::Time::now() - lastReconnectAttempt_).toSec();
// 仅当间隔≥2秒时,才尝试重连
if (timeSinceLastAttempt >= 2.0)
{
ROS_WARN("Serial port %s is closed, attempting to reopen...", port_.c_str()); // 无需THROTTLE,因为本身已间隔2秒
try
{
// 重新配置串口参数(重连核心逻辑)
ser_.setPort(port_);
ser_.setBaudrate(baudrate_);
serial::Timeout timeout = serial::Timeout::simpleTimeout(100);
ser_.setTimeout(timeout);
ser_.open();
ser_.flushInput(); // 清空输入缓冲区
serial_buffer_.clear(); // 清除历史缓冲数据
lastReceivedTime_ = ros::Time::now(); // 重置接收时间
ROS_INFO("Serial port %s reopened successfully at %d baud",
port_.c_str(), baudrate_);
}
catch (const serial::IOException &e)
{
ROS_ERROR("Failed to reopen serial port %s: %s",
port_.c_str(), e.what());
}
catch (const serial::PortNotOpenedException &e)
{
ROS_ERROR("Port %s not open: %s",
port_.c_str(), e.what());
}
// 更新上次重连尝试时间(无论成功与否,都间隔2秒再试)
lastReconnectAttempt_ = ros::Time::now();
}
// 若间隔不足2秒,则不执行任何操作,等待下次主循环检查
}
// 读取串口数据(仅在串口打开状态下执行)
try
{
const size_t available = ser_.available();
ROS_DEBUG("Serial available: %zu bytes", available);
if (available > 0)
{
vector<uint8_t> buffer(available);
size_t bytes_read = ser_.read(buffer.data(), available);
ROS_DEBUG("Read %zu bytes", bytes_read);
serial_buffer_.insert(serial_buffer_.end(), buffer.begin(), buffer.end());
lastReceivedTime_ = ros::Time::now();
if (debug_)
{
ROS_DEBUG("Buffer now: %zu bytes", serial_buffer_.size());
stringstream ss;
for (size_t i = 0; i < min(bytes_read, size_t(20)); ++i)
{
ss << "0x" << hex << setw(2) << setfill('0')
<< static_cast<int>(buffer[i]) << " ";
}
ROS_DEBUG("Raw data (first 20): %s", ss.str().c_str());
}
}
else
{
ROS_DEBUG_THROTTLE(5, "No data available on %s", port_.c_str());
}
}
catch (const serial::IOException &e)
{
ROS_ERROR("Read error on %s: %s", port_.c_str(), e.what());
ser_.close(); // 读失败时主动关闭,触发下次重连
}
catch (const serial::PortNotOpenedException &e)
{
ROS_ERROR("Port %s not open during read: %s", port_.c_str(), e.what());
}
parseFrameData();
}
void RCReceiver::parseFrameData()
{
const uint8_t FRAME_HEADER = 0x5F;
const uint8_t FRAME_TAIL = 0x9B;
const size_t FRAME_LENGTH = 10;
const size_t MAX_BUFFER_SIZE = 1000;
ROS_DEBUG("Enter parseFrameData(). Buffer size: %zu", serial_buffer_.size());
// 安全:限制缓冲区大小
if (serial_buffer_.size() > MAX_BUFFER_SIZE)
{
ROS_WARN("Buffer overflow (%zu > %zu), clearing buffer",
serial_buffer_.size(), MAX_BUFFER_SIZE);
serial_buffer_.clear();
frames_discarded_++;
return;
}
// 当缓冲区长度不足时直接返回
if (serial_buffer_.size() < FRAME_LENGTH)
{
ROS_DEBUG("Buffer too small (%zu < %zu)", serial_buffer_.size(), FRAME_LENGTH);
return;
}
// 帧定位主循环
size_t validFramePos = -1;
for (size_t pos = 0; pos <= serial_buffer_.size() - FRAME_LENGTH; pos++)
{
ROS_DEBUG("Checking position %zu", pos);
// 检查帧头
uint8_t header = static_cast<uint8_t>(serial_buffer_[pos]);
if (header != FRAME_HEADER)
{
ROS_DEBUG("No header at %zu (0x%02X)", pos, header);
continue;
}
// 检查帧尾
size_t tail_pos = pos + FRAME_LENGTH - 1;
uint8_t tail = static_cast<uint8_t>(serial_buffer_[tail_pos]);
if (tail != FRAME_TAIL)
{
ROS_DEBUG("Bad frame tail at %zu: expected 0x%02X, got 0x%02X",
tail_pos, FRAME_TAIL, tail);
frames_discarded_++;
continue;
}
ROS_DEBUG("Valid frame found at position %zu", pos);
validFramePos = pos;
break;
}
if (validFramePos == (size_t)-1)
{
ROS_DEBUG("No valid frame found in buffer");
// 如果找不到有效帧,但缓冲区足够大,清除非帧头数据
if (!serial_buffer_.empty())
{
auto firstHeader = find(serial_buffer_.begin(), serial_buffer_.end(), FRAME_HEADER);
if (firstHeader != serial_buffer_.begin())
{
size_t discardSize = distance(serial_buffer_.begin(), firstHeader);
ROS_DEBUG("Discarding %zu bytes before first header", discardSize);
serial_buffer_.erase(serial_buffer_.begin(), firstHeader);
}
else
{
// 如果没有找到帧头,清除所有数据
ROS_DEBUG("No header found, clearing entire buffer");
serial_buffer_.clear();
}
}
return;
}
// 提取有效帧
vector<uint8_t> frame(
serial_buffer_.begin() + validFramePos,
serial_buffer_.begin() + validFramePos + FRAME_LENGTH);
ROS_DEBUG("Processing frame at pos %zu", validFramePos);
bool parsed = parseModbusProtocol(frame);
if (parsed)
{
frames_received_++;
ROS_DEBUG("Frame parsed successfully (#%zu)", frames_received_);
}
else
{
frames_discarded_++;
ROS_DEBUG("Frame parsing failed (#%zu)", frames_discarded_);
}
// 移除已处理帧
size_t newStart = validFramePos + FRAME_LENGTH;
serial_buffer_.erase(serial_buffer_.begin(), serial_buffer_.begin() + newStart);
ROS_DEBUG("Buffer after erase: %zu bytes", serial_buffer_.size());
}
bool RCReceiver::parseModbusProtocol(const vector<uint8_t> &frame)
{
// 基本完整性检查
if (frame.size() != 10)
{
ROS_WARN_THROTTLE(1, "Invalid frame size: expected 10, got %zu", frame.size());
return false;
}
rc_receiver::rc msg;
// 帧头帧尾校验
if (frame[0] != 0x5F || frame[9] != 0x9B)
{
ROS_WARN_THROTTLE(1, "Invalid frame: header=0x%02X, tail=0x%02X",
static_cast<uint8_t>(frame[0]),
static_cast<uint8_t>(frame[9]));
return false;
}
// 校验和计算(前8字节异或和)
uint8_t checksum = 0;
for (size_t i = 0; i < 8; ++i)
{
checksum ^= frame[i];
}
if (checksum != static_cast<uint8_t>(frame[8]))
{
ROS_WARN_THROTTLE(1, "Checksum error: calculated 0x%02X vs received 0x%02X",
checksum, static_cast<uint8_t>(frame[8]));
return false;
}
// 解析控制模式
msg.control_mode = static_cast<uint8_t>(frame[1]);
if (msg.control_mode != AUTO_DRIVING && msg.control_mode != REMOTE_CONTROL)
{
ROS_WARN_THROTTLE(1, "Invalid control mode: %d", msg.control_mode);
return false;
}
// 解析速度数据(使用安全类型转换)
int16_t linear_raw = static_cast<int16_t>((frame[2] << 8) | frame[3]);
int16_t angular_raw = static_cast<int16_t>((frame[6] << 8) | frame[7]);
// 设置合理范围限制(±2m/s和±2rad/s)
const float MAX_SPEED = 2.0f;
msg.linear_x = linear_raw * 0.01f;
msg.angular_z = angular_raw * 0.01f;
if (fabs(msg.linear_x) > MAX_SPEED)
{
ROS_WARN_THROTTLE(1, "Unreasonable linear speed: %.2f m/s", msg.linear_x);
msg.linear_x = 0.0f;
}
if (fabs(msg.angular_z) > MAX_SPEED)
{
ROS_WARN_THROTTLE(1, "Unreasonable angular speed: %.2f rad/s", msg.angular_z);
msg.angular_z = 0.0f;
}
// 发布消息
control_pub_.publish(msg);
// ROS_INFO("Received frame #%zu: mode=%s, linear=%.2f m/s, angular=%.2f rad/s",
// frames_received_,
// msg.control_mode == AUTO_DRIVING ? "AUTO" : "REMOTE",
// msg.linear_x, msg.angular_z);
return true;
}
// 超时检查:更安全的发布逻辑
void RCReceiver::checkTimeout(const ros::TimerEvent &event)
{
ros::Duration delta = ros::Time::now() - lastReceivedTime_;
if (delta >= ros::Duration(1.0))
{
// 安全获取节点句柄状态
if (!ros::ok())
return;
// 在发布前检查订阅者是否存在
if (control_pub_.getNumSubscribers() > 0)
{
rc_receiver::rc msg;
msg.linear_x = 0.0f;
msg.angular_z = 0.0f;
try
{
control_pub_.publish(msg);
ROS_WARN_THROTTLE(2, "No data for %.1fs, speed zeroed", delta.toSec());
}
catch (const ros::Exception &e)
{
ROS_ERROR("Failed to publish timeout message: %s", e.what());
}
}
// // 定期打印统计信息
// ROS_INFO_THROTTLE(30, "Received stats: good=%zu, bad=%zu",
// frames_received_, frames_discarded_);
}
}
int main(int argc, char **argv)
{
ros::init(argc, argv, "rc_receiver_node");
ROS_INFO("RC Receiver node starting...");
try
{
RCReceiver rcReceiver;
ROS_INFO("Initialization completed, starting run loop");
rcReceiver.run();
}
catch (const std::exception &e)
{
ROS_FATAL("Unhandled exception: %s", e.what());
return 1;
}
catch (...)
{
ROS_FATAL("Unknown exception occurred");
return 1;
}
ROS_INFO("RC Receiver node stopped");
return 0;
}