Skip to content

Commit ed05b8c

Browse files
Merge branch '1-port-socketcan-adapter-to-publishable-repository' into 'main'
Resolve "Port Socketcan Adapter to Publishable Repository" Closes #1 See merge request polymathrobotics/socketcan_adapter!1
2 parents 6b998b0 + 0733666 commit ed05b8c

16 files changed

+2372
-0
lines changed

.gitlab-ci.yml

+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
include:
2+
- project: "polymathrobotics/ci/ci_templates"
3+
ref: main
4+
file: "/ros/ros2_package.impl.yml"
5+
# Enables checking out the current branch on build and test jobs
6+
- project: "polymathrobotics/ci/ci_templates"
7+
ref: main
8+
file: "/common/rules.yml"
9+
10+
build_and_test_socketcan_adapter:
11+
variables:
12+
PACKAGE_NAME: socketcan_adapter
13+
PACKAGE_DIRECTORY: $PACKAGE_NAME
14+
extends: .ros2_build_and_test
15+
16+
eval_socketcan_adapter:
17+
extends: .ros2_evaluate
18+
variables:
19+
PACKAGE_NAME: socketcan_adapter
20+
needs:
21+
- job: build_and_test_socketcan_adapter
22+
artifacts: true
23+
artifacts:
24+
reports:
25+
junit: $ARTIFACTS_PATH/test_results/test_results/$PACKAGE_NAME/*.xml

CHANGELOG.md

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
# Release Notes
2+
3+
## v0.1.0
4+
__INITIAL RELEASE__
5+
6+
### Features
7+
8+
- CanFrame wrapper around struct can_frame
9+
- SocketcanAdapter wrapper around socketcan sockets
10+
- Ability to send and receive CanFrame types
11+
- Built in Threading to receive data
12+
- SocketcanBridgeNode to run a ros2 passthrough node

CMakeLists.txt

+128
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
cmake_minimum_required(VERSION 3.8)
2+
project(socketcan_adapter)
3+
4+
if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang")
5+
add_compile_options(-Wall -Wextra -Wpedantic)
6+
endif()
7+
8+
# find dependencies
9+
find_package(ament_cmake REQUIRED)
10+
find_package(ament_cmake_ros REQUIRED)
11+
find_package(rclcpp REQUIRED)
12+
find_package(rclcpp_lifecycle REQUIRED)
13+
find_package(rclcpp_components REQUIRED)
14+
find_package(can_msgs REQUIRED)
15+
# uncomment the following section in order to fill in
16+
# further dependencies manually.
17+
# find_package(<dependency> REQUIRED)
18+
19+
set(dependencies
20+
rclcpp
21+
rclcpp_lifecycle
22+
rclcpp_components
23+
can_msgs
24+
)
25+
26+
add_library(socketcan_adapter SHARED
27+
src/socketcan_adapter.cpp
28+
src/socketcan_bridge_node.cpp
29+
src/can_frame.cpp)
30+
31+
target_compile_features(socketcan_adapter PUBLIC c_std_99 cxx_std_17) # Require C99 and C++17
32+
target_include_directories(socketcan_adapter PUBLIC
33+
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
34+
$<INSTALL_INTERFACE:include>)
35+
36+
# Causes the visibility macros to use dllexport rather than dllimport,
37+
# which is appropriate when building the dll but not consuming it.
38+
target_compile_definitions(socketcan_adapter PRIVATE "SOCKETCAN_ADAPTER_BUILDING_LIBRARY")
39+
40+
ament_target_dependencies(socketcan_adapter ${dependencies})
41+
42+
install(
43+
DIRECTORY include/
44+
DESTINATION include
45+
)
46+
install(
47+
TARGETS socketcan_adapter
48+
EXPORT export_${PROJECT_NAME}
49+
ARCHIVE DESTINATION lib
50+
LIBRARY DESTINATION lib
51+
RUNTIME DESTINATION bin
52+
)
53+
54+
add_executable(socketcan_bridge src/socketcan_bridge.cpp)
55+
target_include_directories(socketcan_bridge PUBLIC
56+
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
57+
$<INSTALL_INTERFACE:include>)
58+
target_link_libraries(socketcan_bridge socketcan_adapter)
59+
60+
ament_target_dependencies(socketcan_bridge ${dependencies})
61+
62+
install(TARGETS socketcan_bridge
63+
DESTINATION lib/${PROJECT_NAME})
64+
65+
if(BUILD_TESTING)
66+
find_package(ament_lint_auto REQUIRED)
67+
find_package(ament_cmake_gtest REQUIRED)
68+
find_package(ament_cmake_test REQUIRED)
69+
# the following line skips the linter which checks for copyrights
70+
# comment the line when a copyright and license is added to all source files
71+
set(ament_cmake_copyright_FOUND TRUE)
72+
# the following line skips cpplint (only works in a git repo)
73+
# comment the line when this package is in a git repo and when
74+
# a copyright and license is added to all source files
75+
set(ament_cmake_cpplint_FOUND TRUE)
76+
ament_lint_auto_find_test_dependencies()
77+
78+
find_package(Catch2 2 REQUIRED)
79+
set(test_dependencies
80+
Catch2
81+
)
82+
83+
add_executable(can_frame_test test/can_frame_test.cpp)
84+
target_link_libraries(can_frame_test Catch2::Catch2 socketcan_adapter)
85+
ament_target_dependencies(
86+
can_frame_test
87+
${test_dependencies}
88+
${dependencies}
89+
)
90+
91+
ament_add_test(
92+
can_frame_test
93+
GENERATE_RESULT_FOR_RETURN_CODE_ZERO
94+
COMMAND ${CMAKE_CURRENT_BINARY_DIR}/can_frame_test -r junit -o test_results/${PROJECT_NAME}/can_frame_test_output.xml
95+
ENV CATCH_CONFIG_CONSOLE_WIDTH=120
96+
WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}"
97+
)
98+
99+
# Don't try to run vcan-based tests in CI just yet
100+
if(NOT DEFINED ENV{CI})
101+
add_executable(socketcan_adapter_test test/socketcan_adapter_test.cpp)
102+
target_link_libraries(socketcan_adapter_test Catch2::Catch2 socketcan_adapter)
103+
ament_target_dependencies(socketcan_adapter_test
104+
${test_dependencies}
105+
${dependencies}
106+
)
107+
ament_add_test(
108+
socketcan_adapter_test
109+
GENERATE_RESULT_FOR_RETURN_CODE_ZERO
110+
COMMAND ${CMAKE_CURRENT_BINARY_DIR}/socketcan_adapter_test -r junit -o test_results/${PROJECT_NAME}/socketcan_adapter_test_output.xml
111+
ENV CATCH_CONFIG_CONSOLE_WIDTH=120
112+
WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}"
113+
)
114+
endif()
115+
116+
endif()
117+
118+
ament_export_include_directories(
119+
include
120+
)
121+
ament_export_libraries(
122+
socketcan_adapter
123+
)
124+
ament_export_targets(
125+
export_${PROJECT_NAME}
126+
)
127+
128+
ament_package()

README.md

+124
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,126 @@
11
# socketcan_adapter
22
Socketcan Driver Library for Linux based PCs and ROS2 nodes
3+
4+
# Build
5+
Socketcan adapter can be built with the ros2 ament toolchain. All requirements can be installed via rosdep
6+
7+
Install the dependencies!
8+
```bash
9+
rosdep install -i -y --from-paths socketcan_adapter
10+
```
11+
12+
Build it!
13+
```bash
14+
colcon build --packages-up-to socketcan_adapter
15+
```
16+
17+
# Library
18+
## Classes of Note
19+
### CanFrame
20+
`CanFrame` Class - This class wraps the C-level `can_frame` structure, encapsulating CAN message details like the CAN ID, data, timestamp, and frame type (DATA, ERROR, or REMOTE). By providing a robust API for creating and managing CAN frames, CanFrame simplifies interaction with raw CAN data and offers utilities like ID masking, setting error types, and timestamp management.
21+
22+
Example highlights:
23+
24+
- Flexible constructors for `can_frame` struct and raw data inputs.
25+
- Functions to modify frame type, ID type (standard/extended), and length.
26+
- Helper methods to access CAN frame data, ID, and timestamp.
27+
28+
Does not implement CanFD yet.
29+
30+
### SocketcanAdapter
31+
`SocketcanAdapter` Class - The `SocketcanAdapter` abstracts and manages socket operations for CAN communication. It initializes and configures the socket, applies filters, and handles CAN frame transmission and reception. The adapter offers error handling, thread-safe operations, and optional callback functions for asynchronous frame and error processing.
32+
33+
Key features:
34+
35+
- Configurable receive timeout and threading for reception.
36+
- `setFilters` and setErrorMaskOverwrite to apply CAN filters and error masks.
37+
- A callback-based system for handling received frames and errors asynchronously.
38+
- Supports multiple send and receive methods, including `std::shared_ptr` for efficient memory management.
39+
- Together, `CanFrame` and `SocketcanAdapter` simplify interaction with CAN networks, allowing developers to focus on - high-level application logic instead of low-level socket and data handling.
40+
41+
## Sample Usage
42+
43+
```c++
44+
#include "socketcan_adapter/socketcan_adapter.hpp"
45+
#include "socketcan_adapter/can_frame.hpp"
46+
#include <iostream>
47+
#include <thread>
48+
#include <vector>
49+
50+
using namespace polymath::socketcan;
51+
52+
int main() {
53+
// Initialize SocketcanAdapter with the CAN interface name (e.g., "can0")
54+
SocketcanAdapter adapter("can0");
55+
56+
// Open the CAN socket
57+
if (!adapter.openSocket()) {
58+
std::cerr << "Failed to open CAN socket!" << std::endl;
59+
return -1;
60+
}
61+
62+
// Step 1: Set up a filter to allow only messages with ID 0x123
63+
std::vector<struct can_filter> filters = {{0x123, CAN_SFF_MASK}};
64+
if (auto error = adapter.setFilters(filters)) {
65+
std::cerr << "Error setting filters: " << *error << std::endl;
66+
return -1;
67+
}
68+
69+
// Step 2: Set up a callback function to handle received CAN frames
70+
adapter.setOnReceiveCallback([](std::unique_ptr<const CanFrame> frame) {
71+
std::cout << "Received CAN frame with ID: " << std::hex << frame->get_id() << std::endl;
72+
auto data = frame->get_data();
73+
std::cout << "Data: ";
74+
for (const auto& byte : data) {
75+
std::cout << std::hex << static_cast<int>(byte) << " ";
76+
}
77+
std::cout << std::endl;
78+
});
79+
80+
// Step 3: Start the reception thread
81+
if (!adapter.startReceptionThread()) {
82+
std::cerr << "Failed to start reception thread!" << std::endl;
83+
adapter.closeSocket();
84+
return -1;
85+
}
86+
87+
// Step 4: Prepare a CAN frame to send
88+
canid_t raw_id = 0x123;
89+
std::array<unsigned char, CAN_MAX_DLC> data = {0x11, 0x22, 0x33, 0x44};
90+
uint64_t timestamp = 0; // Placeholder timestamp
91+
CanFrame frame(raw_id, data, timestamp);
92+
93+
// Step 5: Send the CAN frame
94+
if (auto error = adapter.send(frame)) {
95+
std::cerr << "Failed to send CAN frame: " << *error << std::endl;
96+
} else {
97+
std::cout << "Sent CAN frame with ID: " << std::hex << raw_id << std::endl;
98+
}
99+
100+
// Keep the application running for 10 seconds to allow for frame reception
101+
std::this_thread::sleep_for(std::chrono::seconds(10));
102+
103+
// Step 5: Clean up - close the socket and stop the reception thread
104+
adapter.joinReceptionThread();
105+
adapter.closeSocket();
106+
107+
return 0;
108+
}
109+
110+
```
111+
112+
# ROS2 Node
113+
To make usage even easier, this package comes with a ROS2 node with default settings!
114+
115+
## Launch
116+
```bash
117+
ros2 launch socketcan_adapter socketcan_bridge_launch.py
118+
```
119+
120+
launch args:
121+
- `can_interface`: can interface to connect to (default: 0)
122+
- `can_error_mask`: can error mask (default: 0x1FFFFFFF aka everything allowed)
123+
- `can_filter_list`: can filters (default: [])
124+
- `join_filters`: use joining logic for filters (default: false)
125+
- `auto_configure`: automatically configure the lifecycle node
126+
- `auto_activate`: automatically activate the lifecycle node post configuration

0 commit comments

Comments
 (0)