-
Notifications
You must be signed in to change notification settings - Fork 1
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Initial implementation #1
Merged
Merged
Changes from 3 commits
Commits
Show all changes
27 commits
Select commit
Hold shift + click to select a range
169742e
Scaffold a SocketCan read loop
Rayman 3cedb12
Finish ROS topics
Rayman e767ec0
Cleanup printing
Rayman c64f8b1
Add troughput benchmark
Rayman ba27b0f
Address review comments
Rayman 028c029
Add some linters
Rayman 64b79ca
Add industrial CI
Rayman 86acc8e
Change to ROS2 CI
Rayman e9ae481
Fix package dependencies
Rayman c273724
Rule of 5
Rayman 6b6e9cc
Convert to rclcpp component
Rayman 89fd0e7
Cleanup includes
Rayman 507477b
Disable fail_fast
Rayman ed7d53a
Add README
Rayman e9130bb
Add read_timeout parameter
Rayman c88e713
Reorganize the package
Rayman 2509a7c
Fix component launching
Rayman 6aa2a2f
Add Apache-2 license
Rayman db7c23c
Remove perf data
Rayman 3d7bcd4
Add `EventsExecutor` support
Rayman 4e3c3ff
Fix tests
Rayman 3c2a008
Disable EventsExecutor support in humble
Rayman 12f384c
Add a comment
Rayman 6f2f79b
Refactor to use ostream for readability
Rayman 8aecaa9
Rename package to ros2_socketcan_bridge
Rayman fd1d634
Finishing up the README
Rayman 3da7292
Use send/receive instead of write/read
Rayman File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
cmake_minimum_required(VERSION 3.8) | ||
project(nobleo_socketcan_bridge) | ||
|
||
set(CMAKE_CXX_STANDARD_REQUIRED ON) | ||
set(CMAKE_CXX_EXTENSIONS OFF) | ||
set(CMAKE_CXX_STANDARD 20) | ||
|
||
if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang") | ||
add_compile_options(-Wall -Wextra -Wpedantic) | ||
endif() | ||
|
||
# find dependencies | ||
find_package(ament_cmake REQUIRED) | ||
find_package(ament_cmake_ros REQUIRED) | ||
find_package(can_msgs REQUIRED) | ||
find_package(rclcpp REQUIRED) | ||
find_package(fmt REQUIRED) | ||
|
||
add_executable(socketcan_bridge src/socketcan_bridge.cpp) | ||
target_include_directories(socketcan_bridge PUBLIC | ||
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include> | ||
$<INSTALL_INTERFACE:include>) | ||
ament_target_dependencies(socketcan_bridge | ||
can_msgs | ||
rclcpp | ||
) | ||
target_link_libraries(socketcan_bridge fmt::fmt) | ||
|
||
install(TARGETS socketcan_bridge | ||
DESTINATION lib/${PROJECT_NAME}) | ||
|
||
if(BUILD_TESTING) | ||
find_package(ament_lint_auto REQUIRED) | ||
ament_lint_auto_find_test_dependencies() | ||
endif() | ||
|
||
ament_package() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
<?xml version="1.0"?> | ||
<?xml-model href="http://download.ros.org/schema/package_format3.xsd" schematypens="http://www.w3.org/2001/XMLSchema"?> | ||
<package format="3"> | ||
<name>nobleo_socketcan_bridge</name> | ||
<version>0.0.0</version> | ||
<description>Simple wrapper around SocketCAN</description> | ||
<author email="[email protected]">Ramon Wijnands</author> | ||
<maintainer email="[email protected]">Ramon Wijnands</maintainer> | ||
<license>TODO: License declaration</license> | ||
|
||
<buildtool_depend>ament_cmake</buildtool_depend> | ||
|
||
<test_depend>ament_lint_auto</test_depend> | ||
<test_depend>ament_lint_common</test_depend> | ||
|
||
<export> | ||
<build_type>ament_cmake</build_type> | ||
</export> | ||
</package> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,141 @@ | ||
#include <fmt/core.h> | ||
#include <linux/can/raw.h> | ||
#include <net/if.h> | ||
#include <sys/ioctl.h> | ||
#include <sys/socket.h> | ||
|
||
#include <can_msgs/msg/frame.hpp> | ||
#include <rclcpp/experimental/executors/events_executor/events_executor.hpp> | ||
#include <thread> | ||
|
||
std::string to_string(const can_msgs::msg::Frame & msg) | ||
{ | ||
auto result = fmt::format("{:0>3X} [{}]", msg.id, msg.dlc); | ||
for (auto i = 0; i < msg.dlc; ++i) { | ||
result += fmt::format(" {:0>2X}", msg.data[i]); | ||
} | ||
return result; | ||
} | ||
|
||
class SocketCanBridge | ||
{ | ||
public: | ||
using CanCallback = std::function<void(const can_msgs::msg::Frame &)>; | ||
|
||
SocketCanBridge( | ||
rclcpp::Logger logger, rclcpp::Clock::SharedPtr clock, const std::string & interface, | ||
Timple marked this conversation as resolved.
Show resolved
Hide resolved
|
||
const CanCallback & receive_callback) | ||
: logger_(logger), clock_(clock), receive_callback_(receive_callback) | ||
{ | ||
using std::placeholders::_1; | ||
|
||
if ((socket_ = socket(PF_CAN, SOCK_RAW, CAN_RAW)) < 0) { | ||
throw std::system_error(errno, std::generic_category()); | ||
} | ||
|
||
timeval tv; | ||
Rayman marked this conversation as resolved.
Show resolved
Hide resolved
|
||
tv.tv_sec = 1; | ||
tv.tv_usec = 0; | ||
setsockopt(socket_, SOL_SOCKET, SO_RCVTIMEO, (const char *)&tv, sizeof tv); | ||
|
||
ifreq ifr; | ||
strcpy(ifr.ifr_name, interface.c_str()); | ||
ioctl(socket_, SIOCGIFINDEX, &ifr); | ||
|
||
sockaddr_can addr; | ||
memset(&addr, 0, sizeof(addr)); | ||
addr.can_family = AF_CAN; | ||
addr.can_ifindex = ifr.ifr_ifindex; | ||
|
||
if (bind(socket_, (sockaddr *)&addr, sizeof(addr)) < 0) { | ||
throw std::system_error(errno, std::generic_category()); | ||
} | ||
|
||
read_thread_ = std::jthread{std::bind(&SocketCanBridge::read_loop, this, _1)}; | ||
} | ||
|
||
~SocketCanBridge() { close(); } | ||
Timple marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
void write(const can_msgs::msg::Frame & msg) | ||
{ | ||
can_frame frame; | ||
frame.can_id = msg.id; | ||
frame.len = msg.dlc; | ||
std::copy(msg.data.begin(), msg.data.end(), frame.data); | ||
RCLCPP_INFO(logger_, "Sending %s", to_string(msg).c_str()); | ||
auto n = ::write(socket_, &frame, sizeof(frame)); | ||
if (n < 0) { | ||
throw std::system_error(errno, std::generic_category()); | ||
} | ||
RCLCPP_INFO(logger_, "Wrote %zd bytes to the socket", n); | ||
} | ||
|
||
void close() | ||
{ | ||
RCLCPP_INFO(logger_, "Stopping the read thread"); | ||
read_thread_.request_stop(); | ||
|
||
RCLCPP_INFO(logger_, "Waiting for the read thread to stop"); | ||
read_thread_.join(); | ||
|
||
RCLCPP_INFO(logger_, "Closing the socket"); | ||
if (::close(socket_) < 0) { | ||
throw std::system_error(errno, std::generic_category()); | ||
} | ||
} | ||
|
||
private: | ||
void read_loop(std::stop_token stoken) | ||
{ | ||
RCLCPP_INFO(logger_, "Read loop started"); | ||
while (!stoken.stop_requested()) { | ||
can_frame frame; | ||
auto nbytes = read(socket_, &frame, sizeof(can_frame)); | ||
if (nbytes < 0) { | ||
if (errno == EAGAIN) continue; | ||
throw std::system_error(errno, std::generic_category()); | ||
} | ||
if (nbytes != sizeof(frame)) { | ||
throw std::runtime_error("Partial CAN frame received"); | ||
} | ||
|
||
can_msgs::msg::Frame msg; | ||
msg.header.stamp = clock_->now(); | ||
msg.id = frame.can_id; | ||
msg.dlc = frame.len; | ||
std::copy_n(frame.data, sizeof(frame.data), msg.data.begin()); | ||
|
||
RCLCPP_INFO(logger_, "Received %s", to_string(msg).c_str()); | ||
receive_callback_(msg); | ||
} | ||
RCLCPP_INFO(logger_, "Read loop stopped"); | ||
} | ||
|
||
rclcpp::Logger logger_; | ||
rclcpp::Clock::SharedPtr clock_; | ||
int socket_; | ||
CanCallback receive_callback_; | ||
std::jthread read_thread_; | ||
}; | ||
|
||
int main(int argc, char ** argv) | ||
{ | ||
rclcpp::init(argc, argv); | ||
|
||
auto node = std::make_shared<rclcpp::Node>("socketcan_bridge"); | ||
|
||
auto can_pub = node->create_publisher<can_msgs::msg::Frame>("~/rx", 100); | ||
SocketCanBridge::CanCallback cb = [&can_pub](const can_msgs::msg::Frame & msg) { | ||
can_pub->publish(msg); | ||
}; | ||
|
||
SocketCanBridge bridge{node->get_logger(), node->get_clock(), "vcan0", cb}; | ||
auto can_sub = node->create_subscription<can_msgs::msg::Frame>( | ||
"~/tx", 100, [&bridge](can_msgs::msg::Frame::ConstSharedPtr msg) { bridge.write(*msg); }); | ||
|
||
rclcpp::experimental::executors::EventsExecutor executor; | ||
executor.add_node(node); | ||
Timple marked this conversation as resolved.
Show resolved
Hide resolved
|
||
executor.spin(); | ||
|
||
return EXIT_SUCCESS; | ||
} |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can simply specify a formatter for
can_msgs::msg::Frame
. That way you can do:There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wouldn't this require you to do this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, good point. Actually one of these two I think:
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The first is dangerous because use data could contain a string formatting. Also you should use c_str because it should be null terminated.
So the second option seems more clean, but then I could also just add a stream operator and not use fmt at all...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
.data()
is the same as.c_str()
but works onstd::string_view
andstd::string
.At least, that was my understanding.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It seems that since C++11 they are required to be the same. But indeed
.data()
is available forstring_view
s.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm now using
ostream
because its a bit more readable