Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
Any contribution that you make to this repository will
be under the Apache 2 License, as dictated by that
[license](http://www.apache.org/licenses/LICENSE-2.0.html):

~~~
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
~~~
170 changes: 69 additions & 101 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,129 +1,97 @@
# socketcan_adapter
Socketcan Driver Library for Linux based PCs and ROS2 nodes
# SocketCAN Adapter

# Build
Socketcan adapter can be built with the ros2 ament toolchain. All requirements can be installed via rosdep
A modular SocketCAN driver library and ROS2 integration for Linux-based systems. This repository provides both a standalone C++ library and ROS2 wrapper for easy integration with robotics applications.

Install the dependencies!
## Architecture

This package is split into two complementary components:

### [socketcan_adapter](./socketcan_adapter/README.md)
Pure C++ SocketCAN library with no ROS dependencies.
- Core SocketCAN functionality
- Thread-safe operations
- Callback-based asynchronous processing
- Configurable filters and error handling
- Can be used in non-ROS projects

### [socketcan_adapter_ros](./socketcan_adapter_ros/README.md)
ROS2 wrapper providing nodes and launch files.
- ROS2 lifecycle node
- Integration with `can_msgs`
- Launch files with parameter support
- Depends on the core `socketcan_adapter` library

## Quick Start

### Build Both Packages

```bash
rosdep install -i -y --from-paths socketcan_adapter
# Install dependencies
rosdep install -i -y --from-paths .

# Build everything
colcon build --packages-up-to socketcan_adapter_ros
```

Build it!
### Launch ROS2 CAN Bridge

```bash
colcon build --packages-up-to socketcan_adapter
ros2 launch socketcan_adapter_ros socketcan_bridge_launch.py
```

# Library
## Classes of Note
### CanFrame
`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.
### Use Core Library in C++

Example highlights:
```c++
#include "socketcan_adapter/socketcan_adapter.hpp"

- Flexible constructors for `can_frame` struct and raw data inputs.
- Functions to modify frame type, ID type (standard/extended), and length.
- Helper methods to access CAN frame data, ID, and timestamp.
using namespace polymath::socketcan;

Does not implement CanFD yet.
SocketcanAdapter adapter("can0");
adapter.openSocket();
// ... see socketcan_adapter README for full example
```

### SocketcanAdapter
`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.
## Requirements

Key features:
### System Requirements
- Linux with SocketCAN support
- C++17 compatible compiler
- CMake 3.8+

- Configurable receive timeout and threading for reception.
- `setFilters` and setErrorMaskOverwrite to apply CAN filters and error masks.
- A callback-based system for handling received frames and errors asynchronously.
- Supports multiple send and receive methods, including `std::shared_ptr` for efficient memory management.
- 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.
### ROS2 Requirements (for ROS package only)
- ROS2 Humble or later
- can_msgs package

## Sample Usage
## Testing

```c++
#include "socketcan_adapter/socketcan_adapter.hpp"
#include "socketcan_adapter/can_frame.hpp"
#include <iostream>
#include <thread>
#include <vector>
### Core Library Tests

using namespace polymath::socketcan;
```bash
colcon test --packages-select socketcan_adapter
```

int main() {
// Initialize SocketcanAdapter with the CAN interface name (e.g., "can0")
SocketcanAdapter adapter("can0");

// Open the CAN socket
if (!adapter.openSocket()) {
std::cerr << "Failed to open CAN socket!" << std::endl;
return -1;
}

// Step 1: Set up a filter to allow only messages with ID 0x123
std::vector<struct can_filter> filters = {{0x123, CAN_SFF_MASK}};
if (auto error = adapter.setFilters(filters)) {
std::cerr << "Error setting filters: " << *error << std::endl;
return -1;
}

// Step 2: Set up a callback function to handle received CAN frames
adapter.setOnReceiveCallback([](std::unique_ptr<const CanFrame> frame) {
std::cout << "Received CAN frame with ID: " << std::hex << frame->get_id() << std::endl;
auto data = frame->get_data();
std::cout << "Data: ";
for (const auto& byte : data) {
std::cout << std::hex << static_cast<int>(byte) << " ";
}
std::cout << std::endl;
});

// Step 3: Start the reception thread
if (!adapter.startReceptionThread()) {
std::cerr << "Failed to start reception thread!" << std::endl;
adapter.closeSocket();
return -1;
}

// Step 4: Prepare a CAN frame to send
canid_t raw_id = 0x123;
std::array<unsigned char, CAN_MAX_DLC> data = {0x11, 0x22, 0x33, 0x44};
uint64_t timestamp = 0; // Placeholder timestamp
CanFrame frame(raw_id, data, timestamp);

// Step 5: Send the CAN frame
if (auto error = adapter.send(frame)) {
std::cerr << "Failed to send CAN frame: " << *error << std::endl;
} else {
std::cout << "Sent CAN frame with ID: " << std::hex << raw_id << std::endl;
}

// Keep the application running for 10 seconds to allow for frame reception
std::this_thread::sleep_for(std::chrono::seconds(10));

// Step 5: Clean up - close the socket and stop the reception thread
adapter.joinReceptionThread();
adapter.closeSocket();

return 0;
}
### Hardware Tests
Set `CAN_AVAILABLE=1` to enable hardware-dependent tests:

```bash
CAN_AVAILABLE=1 colcon test --packages-select socketcan_adapter
```

# ROS2 Node
To make usage even easier, this package comes with a ROS2 node with default settings!
## Virtual CAN Setup

## Launch
For testing without hardware:

```bash
ros2 launch socketcan_adapter socketcan_bridge_launch.py
sudo modprobe vcan
sudo ip link add dev vcan0 type vcan
sudo ip link set up vcan0
```

launch args:
- `can_interface`: can interface to connect to (default: 0)
- `can_error_mask`: can error mask (default: 0x1FFFFFFF aka everything allowed)
- `can_filter_list`: can filters (default: [])
- `join_filters`: use joining logic for filters (default: false)
- `auto_configure`: automatically configure the lifecycle node
- `auto_activate`: automatically activate the lifecycle node post configuration
## License

Licensed under the Apache License, Version 2.0. See [LICENSE](LICENSE) for the full license text.

## Authors & Maintainers

- **Zeerek Ahmad** - Original author - zeerekahmad@hotmail.com
- **Polymath Robotics Engineering Team** - Maintainers - engineering@polymathrobotics.com
46 changes: 18 additions & 28 deletions CMakeLists.txt → socketcan_adapter/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -36,27 +36,32 @@ if(CMAKE_BUILD_TYPE STREQUAL "Debug")
set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} ${ASAN_FLAGS}")
endif()

# Distro detection (override with -BUILD_HUMBLE=ON/OFF)
if(NOT DEFINED BUILD_HUMBLE)
if(DEFINED ENV{ROS_DISTRO} AND "$ENV{ROS_DISTRO}" STREQUAL "humble")
set(BUILD_HUMBLE TRUE)
message(STATUS "ROS_DISTRO=humble -> using Catch2 v2")
else()
set(BUILD_HUMBLE FALSE)
message(STATUS "ROS_DISTRO>humble -> Catch2 v3 (Jazzy/Rolling)")
# Ubuntu detection (override with -DBUILD_JAMMY=ON/OFF)
if(NOT DEFINED BUILD_JAMMY)
set(BUILD_JAMMY OFF)
if(EXISTS "/etc/os-release")
file(READ "/etc/os-release" _OS_RELEASE)
string(REGEX MATCH "VERSION_CODENAME=([^\n\r]+)" _match "${_OS_RELEASE}")
if(CMAKE_MATCH_1)
set(_UBUNTU_CODENAME "${CMAKE_MATCH_1}")
string(TOLOWER "${_UBUNTU_CODENAME}" _UBUNTU_CODENAME)
if(_UBUNTU_CODENAME STREQUAL "jammy")
set(BUILD_JAMMY ON)
message(STATUS "Ubuntu=jammy (22.04) -> Catch2 v2")
else()
set(BUILD_JAMMY OFF)
message(STATUS "Ubuntu codename='${_UBUNTU_CODENAME}' -> defaulting to Catch2 v3")
endif()
endif()
endif()
endif()

# Dependencies
find_package(ament_cmake REQUIRED)
find_package(rclcpp REQUIRED)
find_package(rclcpp_lifecycle REQUIRED)
find_package(can_msgs REQUIRED)

# Primary library
add_library(socketcan_adapter SHARED
src/socketcan_adapter.cpp
src/socketcan_bridge_node.cpp
src/can_frame.cpp
)
target_include_directories(socketcan_adapter PUBLIC
Expand All @@ -65,20 +70,6 @@ target_include_directories(socketcan_adapter PUBLIC
)
target_compile_definitions(socketcan_adapter PRIVATE "SOCKETCAN_ADAPTER_BUILDING_LIBRARY")

# Executable using the library
add_executable(socketcan_bridge src/socketcan_bridge.cpp)
target_include_directories(socketcan_bridge PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
$<INSTALL_INTERFACE:include>
)

target_link_libraries(socketcan_adapter PUBLIC
${can_msgs_TARGETS}
rclcpp::rclcpp
rclcpp_lifecycle::rclcpp_lifecycle
)
target_link_libraries(socketcan_bridge PUBLIC socketcan_adapter)

# Installs
install(DIRECTORY include/ DESTINATION include)
install(TARGETS socketcan_adapter
Expand All @@ -87,7 +78,6 @@ install(TARGETS socketcan_adapter
LIBRARY DESTINATION lib
RUNTIME DESTINATION bin
)
install(TARGETS socketcan_bridge DESTINATION lib/${PROJECT_NAME})
install(EXPORT ${PROJECT_NAME}_TARGETS
NAMESPACE ${PROJECT_NAME}::
DESTINATION share/${PROJECT_NAME}/cmake
Expand All @@ -97,7 +87,7 @@ install(EXPORT ${PROJECT_NAME}_TARGETS
if(BUILD_TESTING)
include(CTest)
# Find correct Catch2 major version per distro
if(BUILD_HUMBLE)
if(BUILD_JAMMY)
find_package(Catch2 2 REQUIRED)
else()
find_package(Catch2 3 REQUIRED)
Expand Down
Loading