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
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,10 @@ __INITIAL RELEASE__
- Ability to send and receive CanFrame types
- Built in Threading to receive data
- SocketcanBridgeNode to run a ros2 passthrough node

## v0.2.1

### Features
- Now with `axiomatic_adapter` package!
- Introduces an additional interface between Axiomatic's CAN-ETH converter and CAN messages
- Has a wrapper to put ETH CAN traffic onto a socketcan interface using `SocketcanAdapter`
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,12 @@ ROS2 wrapper providing nodes and launch files.
- Launch files with parameter support
- Depends on the core `socketcan_adapter` library

### [axiomatic_adapter](./axiomatic_adapter/README.md)
C++ library to connect CAN data to Axiomatic CAN-ETH adapter
- Core Axiomatic ETH-CAN bridge functionality
- Modeled after the socketcan_adapter
- Has an additional bridge to leverage the `socketcan_adapter` library to put eth traffic on socketcan

## Quick Start

### Build Both Packages
Expand Down
3 changes: 3 additions & 0 deletions axiomatic_adapter/CHANGELOG.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Changelog for package axiomatic_adapter
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
120 changes: 120 additions & 0 deletions axiomatic_adapter/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
# 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(axiomatic_adapter)

if(NOT CMAKE_CXX_STANDARD)
set(CMAKE_CXX_STANDARD 17)
endif()
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()

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)
string(TOLOWER "${CMAKE_MATCH_1}" _UBUNTU_CODENAME)
if(_UBUNTU_CODENAME STREQUAL "jammy")
set(BUILD_JAMMY ON)
endif()
endif()
endif()
endif()

# Find dependencies
find_package(ament_cmake REQUIRED)
find_package(ament_cmake_ros REQUIRED)
find_package(Boost REQUIRED COMPONENTS system)
find_package(CLI11 REQUIRED)
find_package(socketcan_adapter REQUIRED)

# Axiomatic Adapter Library
add_library(axiomatic_adapter SHARED src/axiomatic_adapter.cpp)

target_compile_features(axiomatic_adapter PUBLIC c_std_99 cxx_std_17)
target_include_directories(axiomatic_adapter PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
$<INSTALL_INTERFACE:include>
)
set_target_properties(axiomatic_adapter PROPERTIES LINK_FLAGS "-Wl,--no-undefined")
target_link_libraries(axiomatic_adapter
PUBLIC socketcan_adapter::socketcan_adapter
PRIVATE
Boost::system
)

install(DIRECTORY include/ DESTINATION include)

install(TARGETS axiomatic_adapter
EXPORT export_${PROJECT_NAME}
ARCHIVE DESTINATION lib
LIBRARY DESTINATION lib
RUNTIME DESTINATION bin
)

# Axiomatic SocketCAN Bridge
add_executable(axiomatic_socketcan_bridge
src/axiomatic_socketcan_bridge.cpp
src/axiomatic_socketcan_bridge_node.cpp
)

target_include_directories(axiomatic_socketcan_bridge PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
$<INSTALL_INTERFACE:include>
)
set_target_properties(axiomatic_socketcan_bridge PROPERTIES LINK_FLAGS "-Wl,--no-undefined")
target_link_libraries(axiomatic_socketcan_bridge
axiomatic_adapter
CLI11::CLI11
socketcan_adapter::socketcan_adapter
)

# Install the bridge executable
install(TARGETS axiomatic_socketcan_bridge
DESTINATION lib/${PROJECT_NAME}
)

if(BUILD_TESTING)
if(BUILD_JAMMY)
find_package(Catch2 2 REQUIRED)
else()
find_package(Catch2 3 REQUIRED)
endif()
include(Catch OPTIONAL)

option(HARDWARE_CONNECTED "Enable tests that require a connected Axiomatic device and CAN interface" OFF)

if(HARDWARE_CONNECTED)
add_executable(test_axiomatic_adapter test/axiomatic_adapter_test.cpp)
target_link_libraries(test_axiomatic_adapter PRIVATE
axiomatic_adapter
socketcan_adapter::socketcan_adapter
Catch2::Catch2WithMain
)
if(COMMAND catch_discover_tests)
catch_discover_tests(test_axiomatic_adapter)
else()
add_test(NAME test_axiomatic_adapter COMMAND test_axiomatic_adapter)
endif()
endif()
endif()

ament_export_targets(export_${PROJECT_NAME} HAS_LIBRARY_TARGET)

ament_package()
55 changes: 55 additions & 0 deletions axiomatic_adapter/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# Axiomatic Adapter
Library and Adapter for the Axiomatic CAN-ETH converter.

For more information on the decoding/endcoding and product, see the links below:

https://www.notion.so/polymathrobotics/Axiomatic-CAN-to-Ethernet-Converter-08e078d8914f40d6b7cd99ebf39fe1b0

https://products.axiomatic.com/viewitems/connectivity/ethernet-can-converters

## Usage
### Socketcan-Axiomatic Bridge

```bash
ros2 run axiomatic_adapter axiomatic_socketcan_bridge [CAN_INTERFACE_NAME] [IP_ADDRESS] [PORT] [OPTIONAL]--retry-connection[-r] [OPTIONAL]--max-retry-attempts [OPTIONAL]--verbose[-v]

# Examples
# generic example to bridge vcan0 with axiomatic using 192.168.50.34:4000
ros2 run axiomatic_adapter axiomatic_socketcan_bridge vcan0 192.168.50.34 4000

# this will continue retrying to connect forever and not exit on first failure. It will also print more detailed logs
ros2 run axiomatic_adapter axiomatic_socketcan_bridge vcan0 192.168.50.34 4000 -r -v

# this will attempt to reconnect a max number of 100 times before failing
ros2 run axiomatic_adapter axiomatic_socketcan_bridge vcan0 192.168.50.34 4000 -r --max-retry-attempts 100
```

### Library

```cpp
// construct the adapter
std::string ip_address = "192.168.0.34";
std::string port = "4000";
std::chrono::milliseconds receive_timeout_ms(100);

// the two functions passed in are the receive and error callback functions, receive timeout has a default
polymath::can::AxiomaticAdapter adapter(
ip_address,
port,
[](std::unique_ptr<const CanFrame> /*frame*/) { /* No-op */ },
[](polymath::can::AxiomaticAdapter::socket_error_string_t /*error*/) { /*do nothing*/ },
receive_timeout_ms
);

// open the socket
adapter.openSocket();

// start the reception thread. At this point, it's running
adapter.startReceptionThread();

// on shutdown/destruction, thread will join and socket will close
```

## KNOWN ISSUES
1. There seems to be an issue with massive amounts of CAN data causing timing inconsistencies through the adapter library
1. This has not been fully diagnosed
123 changes: 123 additions & 0 deletions axiomatic_adapter/include/axiomatic_adapter/axiomatic_adapter.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
// 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.

#ifndef AXIOMATIC_ADAPTER__AXIOMATIC_ADAPTER_HPP_
#define AXIOMATIC_ADAPTER__AXIOMATIC_ADAPTER_HPP_

#include <linux/can.h>
#include <poll.h>

#include <chrono>
#include <functional>
#include <memory>
#include <optional>
#include <string>

#include "socketcan_adapter/can_frame.hpp"

namespace polymath
{
namespace can
{

/// @brief State of TCP socket, error, open or closed
enum class TCPSocketState
{
ERROR = -1,
OPEN = 0,
CLOSED = 1,
};

/// @class polymath::can::AxiomaticAdapter
/// @brief Creates and manages a tcp connection and simplifies the interface.
/// Generally does not throw, but returns booleans to tell you success
class AxiomaticAdapter : public std::enable_shared_from_this<AxiomaticAdapter>
{
public:
/// @brief Mapped to std lib, but should be remapped to Polymath Safety compatible versions
using socket_error_string_t = std::string;

static constexpr std::chrono::milliseconds DEFAULT_SOCKET_RECEIVE_TIMEOUT_MS{100};
static constexpr std::chrono::milliseconds JOIN_RECEPTION_TIMEOUT_MS{100};

/// @brief AxiomaticAdapter Class Init
/// @param ip_address Axiomatic Device IP address to connect
/// @param port Axiomatic Device Port to connect to
/// @param receive_timeout_ms receive timeout in milliseconds
AxiomaticAdapter(
const std::string & ip_address,
const std::string & port,
const std::function<void(std::unique_ptr<const polymath::socketcan::CanFrame> frame)> && receive_callback_function =
[](std::unique_ptr<const polymath::socketcan::CanFrame> /*frame*/) { /*do nothing*/ },
const std::function<void(socket_error_string_t error)> && error_callback_function =
[](socket_error_string_t /*error*/) { /*do nothing*/ },
const std::chrono::milliseconds & receive_timeout_ms = AxiomaticAdapter::DEFAULT_SOCKET_RECEIVE_TIMEOUT_MS);

/// @brief Destructor for AxiomaticAdapter
virtual ~AxiomaticAdapter();

/// @brief Open TCP Socket
/// @return bool success for opening socket
bool openSocket();

/// @brief Close TCP Socket
/// @return bool success for closing socket
bool closeSocket();

/// @brief Receive with a reference to a CanFrame to fill
/// @param frame OUTPUT CanFrame to fill
/// @return optional error string filled with an error message if any
std::optional<socket_error_string_t> receive(polymath::socketcan::CanFrame & can_frame);

/// @brief Receive returns the received CanFrame
/// @return optional CanFrame
/// nullopt is returned if no frame received, acts like null
std::optional<const polymath::socketcan::CanFrame> receive();

/// @brief Start a reception thread (calls callback)
/// @return success on started
bool startReceptionThread();

/// @brief Stop and join reception thread
/// @param timeout_s INPUT timeout in seconds, <=0 means no timeout
/// @return success on closed and joined thread
bool joinReceptionThread(const std::chrono::milliseconds & timeout_s = AxiomaticAdapter::JOIN_RECEPTION_TIMEOUT_MS);

/// @brief Transmit a can frame via socket
/// @param frame INPUT const reference to the frame
/// @return optional error string filled with an error message if any
std::optional<socket_error_string_t> send(const polymath::socketcan::CanFrame & frame);

/// @brief Transmit a can frame via socket
/// @param frame Linux CAN frame to send
/// @return optional error string filled with an error message if any
std::optional<socket_error_string_t> send(const can_frame & frame);

/// @brief Get state of socket
/// @return TCPSocketState data type detailing OPEN or CLOSED
TCPSocketState get_socket_state();

/// @brief Checks if the receive thread is running
/// @return True if the thread is running, false otherwise
bool is_thread_running();

private:
/// @brief use Implemention (pimpl) to avoid including boost/asio.hpp in header + linking in CMake
class AxiomaticAdapterImpl;
std::unique_ptr<AxiomaticAdapterImpl> pimpl_;
};
} // namespace can
} // namespace polymath

#endif // AXIOMATIC_ADAPTER__AXIOMATIC_ADAPTER_HPP_
Loading
Loading