diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..6f63de9 --- /dev/null +++ b/CONTRIBUTING.md @@ -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. +~~~ diff --git a/README.md b/README.md index 2ce6e4e..1c82b3e 100644 --- a/README.md +++ b/README.md @@ -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 -#include -#include +### 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 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 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(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 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 diff --git a/CMakeLists.txt b/socketcan_adapter/CMakeLists.txt similarity index 77% rename from CMakeLists.txt rename to socketcan_adapter/CMakeLists.txt index 15448d7..674c2fd 100644 --- a/CMakeLists.txt +++ b/socketcan_adapter/CMakeLists.txt @@ -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 @@ -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 - $ - $ -) - -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 @@ -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 @@ -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) diff --git a/socketcan_adapter/README.md b/socketcan_adapter/README.md new file mode 100644 index 0000000..183b061 --- /dev/null +++ b/socketcan_adapter/README.md @@ -0,0 +1,147 @@ +# socketcan_adapter + +A C++ SocketCAN driver library for Linux-based systems. This library provides a high-level interface to Linux SocketCAN without any ROS dependencies. + +## 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. + +Example highlights: + +- 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. + +Does not implement CanFD yet. + +### 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. + +Key features: + +- 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. + +## Build + +This package can be built with the ROS2 ament toolchain or as a standalone CMake project. + +### ROS2 Build +Install dependencies: + +```bash +rosdep install -i -y --from-paths socketcan_adapter +``` + +Build: + +```bash +colcon build --packages-select socketcan_adapter +``` + +### Standalone CMake Build + +```bash +mkdir build && cd build +cmake .. +make +``` + +## Sample Usage + +```c++ +#include "socketcan_adapter/socketcan_adapter.hpp" +#include "socketcan_adapter/can_frame.hpp" +#include +#include +#include + +using namespace polymath::socketcan; + +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 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 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(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 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; +} +``` + +## Testing + +Run tests: + +```bash +colcon test --packages-select socketcan_adapter +``` + +Note: Hardware tests require `CAN_AVAILABLE=1` environment variable and a functioning CAN interface. + +### Creating Virtual CAN Interface + +For testing without hardware: + +```bash +# Load the vcan module +sudo modprobe vcan + +# Create a virtual CAN interface +sudo ip link add dev vcan0 type vcan + +# Bring up the interface +sudo ip link set up vcan0 +``` diff --git a/include/socketcan_adapter/can_frame.hpp b/socketcan_adapter/include/socketcan_adapter/can_frame.hpp similarity index 100% rename from include/socketcan_adapter/can_frame.hpp rename to socketcan_adapter/include/socketcan_adapter/can_frame.hpp diff --git a/include/socketcan_adapter/socketcan_adapter.hpp b/socketcan_adapter/include/socketcan_adapter/socketcan_adapter.hpp similarity index 100% rename from include/socketcan_adapter/socketcan_adapter.hpp rename to socketcan_adapter/include/socketcan_adapter/socketcan_adapter.hpp diff --git a/socketcan_adapter/package.xml b/socketcan_adapter/package.xml new file mode 100644 index 0000000..af211a9 --- /dev/null +++ b/socketcan_adapter/package.xml @@ -0,0 +1,17 @@ + + + + socketcan_adapter + 0.1.0 + An Adapter Library for Socketcan using Callback Patterns + Polymath Engineering + Apache-2.0 + Zeerek Ahmad + + ament_cmake_test + catch2 + + + ament_cmake + + diff --git a/src/can_frame.cpp b/socketcan_adapter/src/can_frame.cpp similarity index 100% rename from src/can_frame.cpp rename to socketcan_adapter/src/can_frame.cpp diff --git a/src/socketcan_adapter.cpp b/socketcan_adapter/src/socketcan_adapter.cpp similarity index 96% rename from src/socketcan_adapter.cpp rename to socketcan_adapter/src/socketcan_adapter.cpp index b7608d8..ca13b5e 100644 --- a/src/socketcan_adapter.cpp +++ b/socketcan_adapter/src/socketcan_adapter.cpp @@ -126,10 +126,11 @@ std::optional SocketcanAdapter::receive fds[0].fd = socket_file_descriptor_; fds[0].events = POLLIN | POLLERR; + auto poll_timeout_ms = std::chrono::duration_cast(receive_timeout_s_).count(); + /// TODO: We don't need to call duration cast every time we run this, we should store milliseconds instead /// https://gitlab.com/polymathrobotics/polymath_core/-/issues/8 - int return_value = poll( - fds, NUM_SOCKETS_IN_ADAPTER, std::chrono::duration_cast(receive_timeout_s_).count()); + int return_value = poll(fds, NUM_SOCKETS_IN_ADAPTER, poll_timeout_ms); socket_error_string_t error_string; @@ -139,7 +140,9 @@ std::optional SocketcanAdapter::receive } if (return_value == 0) { - return std::optional("poll timed out with no data"); + socket_error_string_t err_string = + socket_error_string_t("poll timed out with no data with timeout") + std::to_string(poll_timeout_ms); + return std::optional(err_string); } if (fds[0].revents & POLLERR) { diff --git a/test/can_frame_test.cpp b/socketcan_adapter/test/can_frame_test.cpp similarity index 100% rename from test/can_frame_test.cpp rename to socketcan_adapter/test/can_frame_test.cpp diff --git a/test/socketcan_adapter_test.cpp b/socketcan_adapter/test/socketcan_adapter_test.cpp similarity index 100% rename from test/socketcan_adapter_test.cpp rename to socketcan_adapter/test/socketcan_adapter_test.cpp diff --git a/socketcan_adapter_ros/CMakeLists.txt b/socketcan_adapter_ros/CMakeLists.txt new file mode 100644 index 0000000..c5fdd8d --- /dev/null +++ b/socketcan_adapter_ros/CMakeLists.txt @@ -0,0 +1,100 @@ +# Copyright (c) 2025-present Polymath Robotics, Inc. All rights reserved +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +cmake_minimum_required(VERSION 3.8) +project(socketcan_adapter_ros) + +# Project-wide language standards +if(NOT CMAKE_CXX_STANDARD) + set(CMAKE_CXX_STANDARD 17) +endif() + +# Warnings & link hygiene +if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang") + add_compile_options(-Wall -Wextra -Wpedantic) + add_link_options(-Wl,-no-undefined) +endif() + +# Enable ASAN in Debug builds +if(CMAKE_BUILD_TYPE STREQUAL "Debug") + message(STATUS "Enabling AddressSanitizer (ASAN) for Debug build") + set(ASAN_FLAGS "-fsanitize=address -fno-omit-frame-pointer") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${ASAN_FLAGS}") + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${ASAN_FLAGS}") + set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} ${ASAN_FLAGS}") + 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)") + endif() +endif() + +# Dependencies +find_package(ament_cmake REQUIRED) +find_package(rclcpp REQUIRED) +find_package(rclcpp_lifecycle REQUIRED) +find_package(can_msgs REQUIRED) +find_package(socketcan_adapter REQUIRED) + +# Primary library +add_library(socketcan_adapter_ros SHARED + src/socketcan_bridge_node.cpp +) +target_include_directories(socketcan_adapter_ros PUBLIC + $ + $ +) +target_compile_definitions(socketcan_adapter_ros PRIVATE "SOCKETCAN_ADAPTER_ROS_BUILDING_LIBRARY") + +# Executable using the library +add_executable(socketcan_bridge src/socketcan_bridge.cpp) +target_include_directories(socketcan_bridge PUBLIC + $ + $ +) + +target_link_libraries(socketcan_adapter_ros PUBLIC + ${can_msgs_TARGETS} + rclcpp::rclcpp + rclcpp_lifecycle::rclcpp_lifecycle + socketcan_adapter::socketcan_adapter +) +target_link_libraries(socketcan_bridge PUBLIC socketcan_adapter_ros) + +# Installs +install(DIRECTORY include/ DESTINATION include) +install(DIRECTORY launch/ DESTINATION share/${PROJECT_NAME}/launch) +install(TARGETS socketcan_adapter_ros + EXPORT ${PROJECT_NAME}_TARGETS + ARCHIVE DESTINATION lib + 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 +) + +# Export +ament_export_targets(${PROJECT_NAME}_TARGETS HAS_LIBRARY_TARGET) +ament_export_dependencies(socketcan_adapter rclcpp rclcpp_lifecycle can_msgs) +ament_package() diff --git a/socketcan_adapter_ros/README.md b/socketcan_adapter_ros/README.md new file mode 100644 index 0000000..41e41bd --- /dev/null +++ b/socketcan_adapter_ros/README.md @@ -0,0 +1,97 @@ +# socketcan_adapter_ros + +A ROS2 wrapper for the socketcan_adapter library, providing ROS2 nodes and launch files for easy integration with ROS2 systems. + +## Dependencies + +- [socketcan_adapter](../socketcan_adapter/README.md) - Core SocketCAN library +- rclcpp - ROS2 C++ client library +- rclcpp_lifecycle - ROS2 lifecycle node support +- can_msgs - ROS2 CAN message definitions + +## Build + +Install dependencies: + +```bash +rosdep install -i -y --from-paths socketcan_adapter_ros +``` + +Build: + +```bash +colcon build --packages-up-to socketcan_adapter_ros +``` + +## Usage + +### Launch the CAN Bridge Node + +```bash +ros2 launch socketcan_adapter_ros socketcan_bridge_launch.py +``` + +### Launch Parameters + +- `can_interface`: CAN interface to connect to (default: "can0") +- `can_error_mask`: CAN error mask (default: 0x1FFFFFFF - everything allowed) +- `can_filter_list`: CAN filters as list of ID/mask pairs (default: []) +- `join_filters`: Use joining logic for filters (default: false) +- `auto_configure`: Automatically configure the lifecycle node (default: true) +- `auto_activate`: Automatically activate the lifecycle node post configuration (default: true) + +### Example Launch with Parameters + +```bash +ros2 launch socketcan_adapter_ros socketcan_bridge_launch.py \ + can_interface:=can1 \ + can_error_mask:=0x1F \ + can_filter_list:="[{id: 0x123, mask: 0x7FF}, {id: 0x456, mask: 0x7FF}]" +``` + +### Manual Lifecycle Management + +If you prefer manual control over the lifecycle: + +```bash +# Launch without auto-activation +ros2 launch socketcan_adapter_ros socketcan_bridge_launch.py \ + auto_configure:=false \ + auto_activate:=false + +# Configure the node +ros2 lifecycle set /socketcan_bridge configure + +# Activate the node +ros2 lifecycle set /socketcan_bridge activate +``` + +## Topics + +### Published Topics + +- `/can_rx` (`can_msgs/msg/Frame`) - Received CAN frames from the bus + +### Subscribed Topics + +- `/can_tx` (`can_msgs/msg/Frame`) - CAN frames to transmit to the bus + +## Node Details + +The `socketcan_bridge` node is implemented as a ROS2 lifecycle node + +### Parameters + +The node accepts the following ROS2 parameters: + +- `can_interface` (string): Name of the CAN interface (e.g., "can0", "vcan0") +- `error_mask` (int): CAN error mask for filtering error frames +- `filters` (array): List of CAN filter objects with `id` and `mask` fields +- `join_filters` (bool): Whether to use joining logic for multiple filters + +## Requirements + +- ROS2 (Humble or later) +- Linux system with SocketCAN support +- [socketcan_adapter](../socketcan_adapter/README.md) library +- Active CAN interface (real or virtual) diff --git a/include/socketcan_adapter/socketcan_bridge_node.hpp b/socketcan_adapter_ros/include/socketcan_adapter_ros/socketcan_bridge_node.hpp similarity index 100% rename from include/socketcan_adapter/socketcan_bridge_node.hpp rename to socketcan_adapter_ros/include/socketcan_adapter_ros/socketcan_bridge_node.hpp diff --git a/launch/socketcan_bridge_launch.py b/socketcan_adapter_ros/launch/socketcan_bridge_launch.py similarity index 88% rename from launch/socketcan_bridge_launch.py rename to socketcan_adapter_ros/launch/socketcan_bridge_launch.py index 8c72920..bb0005a 100644 --- a/launch/socketcan_bridge_launch.py +++ b/socketcan_adapter_ros/launch/socketcan_bridge_launch.py @@ -28,36 +28,40 @@ # Co-developed by Tier IV, Inc. and Apex.AI, Inc. -from launch_ros.actions import LifecycleNode -from launch_ros.event_handlers import OnStateTransition -from launch_ros.events.lifecycle import ChangeState -from lifecycle_msgs.msg import Transition - from launch import LaunchDescription from launch.actions import DeclareLaunchArgument, EmitEvent, RegisterEventHandler from launch.conditions import IfCondition from launch.event_handlers import OnProcessStart from launch.events import matches_action from launch.substitutions import LaunchConfiguration +from launch_ros.actions import LifecycleNode +from launch_ros.event_handlers import OnStateTransition +from launch_ros.events.lifecycle import ChangeState +from launch_ros.parameter_descriptions import ParameterValue +from lifecycle_msgs.msg import Transition def generate_launch_description(): # Define args can_interface_arg = DeclareLaunchArgument('can_interface', default_value='can0') can_error_mask_arg = DeclareLaunchArgument('can_error_mask', default_value='0x1FFFFFFF') - can_filter_list_arg = DeclareLaunchArgument('can_filter_list', default_value=[]) + # Default filter:mask [0:0] means ALL traffic is allowed + can_filter_list_arg = DeclareLaunchArgument('can_filter_list', default_value="['0:0']") join_filters_arg = DeclareLaunchArgument('join_filters', default_value='false') + receive_timeout_arg = DeclareLaunchArgument('receive_timeout_s', default_value='1.0') socketcan_bridge_node = LifecycleNode( - package='socketcan_adapter', + package='socketcan_adapter_ros', executable='socketcan_bridge', name='socketcan_bridge', + namespace='', parameters=[ { 'can_interface': LaunchConfiguration('can_interface'), 'can_error_mask': LaunchConfiguration('can_error_mask'), - 'can_filter_list': LaunchConfiguration('can_filter_list'), + 'can_filter_list': ParameterValue(LaunchConfiguration('can_filter_list'), value_type=list[str]), 'join_filters': LaunchConfiguration('join_filters'), + 'receive_timeout_s': LaunchConfiguration('receive_timeout_s'), } ], output='screen', @@ -100,6 +104,7 @@ def generate_launch_description(): can_error_mask_arg, can_filter_list_arg, join_filters_arg, + receive_timeout_arg, DeclareLaunchArgument('auto_configure', default_value='true'), DeclareLaunchArgument('auto_activate', default_value='true'), socketcan_bridge_node, diff --git a/package.xml b/socketcan_adapter_ros/package.xml similarity index 90% rename from package.xml rename to socketcan_adapter_ros/package.xml index e945324..7831a80 100644 --- a/package.xml +++ b/socketcan_adapter_ros/package.xml @@ -1,7 +1,7 @@ - socketcan_adapter + socketcan_adapter_ros 0.1.0 An Adapter Library for Socketcan with ROS2 Polymath Engineering @@ -11,6 +11,7 @@ rclcpp rclcpp_lifecycle can_msgs + socketcan_adapter ament_cmake_test catch2 diff --git a/src/socketcan_bridge.cpp b/socketcan_adapter_ros/src/socketcan_bridge.cpp similarity index 87% rename from src/socketcan_bridge.cpp rename to socketcan_adapter_ros/src/socketcan_bridge.cpp index 2529f2f..d1bbc53 100644 --- a/src/socketcan_bridge.cpp +++ b/socketcan_adapter_ros/src/socketcan_bridge.cpp @@ -17,7 +17,7 @@ #include "rclcpp/rclcpp.hpp" #include "rclcpp_lifecycle/lifecycle_node.hpp" -#include "socketcan_adapter/socketcan_bridge_node.hpp" +#include "socketcan_adapter_ros/socketcan_bridge_node.hpp" #include "std_msgs/msg/string.hpp" int main(int argc, char ** argv) @@ -29,15 +29,8 @@ int main(int argc, char ** argv) executor.add_node(node->get_node_base_interface()); - node->configure(); - node->activate(); - executor.spin(); - node->deactivate(); - node->cleanup(); - node->shutdown(); - rclcpp::shutdown(); return 0; diff --git a/src/socketcan_bridge_node.cpp b/socketcan_adapter_ros/src/socketcan_bridge_node.cpp similarity index 96% rename from src/socketcan_bridge_node.cpp rename to socketcan_adapter_ros/src/socketcan_bridge_node.cpp index b4162f9..cce4b29 100644 --- a/src/socketcan_bridge_node.cpp +++ b/socketcan_adapter_ros/src/socketcan_bridge_node.cpp @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -#include "socketcan_adapter/socketcan_bridge_node.hpp" +#include "socketcan_adapter_ros/socketcan_bridge_node.hpp" #include #include @@ -32,7 +32,8 @@ SocketcanBridgeNode::SocketcanBridgeNode(const rclcpp::NodeOptions & options) { declare_parameter("can_interface", std::string("can0")); declare_parameter("can_error_mask", static_cast(CAN_ERR_MASK)); - declare_parameter("can_filter_list", {}); // vector of strings + // Vector of strings, default 0:0 means ALL traffic allowed + declare_parameter("can_filter_list", std::vector{"0:0"}); declare_parameter("join_filters", false); declare_parameter("receive_timeout_s", SOCKET_RECEIVE_TIMEOUT_S.count()); } @@ -101,6 +102,8 @@ SocketcanBridgeNode::rclcpp_lifecycle_callback_return SocketcanBridgeNode::on_co socketcan_adapter_->setOnErrorCallback( std::bind(&SocketcanBridgeNode::socketErrorCallback, this, std::placeholders::_1)); + RCLCPP_INFO(get_logger(), "Finished configuration!"); + return LifecycleNode::on_configure(state); } @@ -117,6 +120,7 @@ SocketcanBridgeNode::rclcpp_lifecycle_callback_return SocketcanBridgeNode::on_ac return rclcpp_lifecycle_callback_return::FAILURE; } + RCLCPP_INFO(get_logger(), "Finished activation!"); return LifecycleNode::on_activate(state); }