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
36 changes: 32 additions & 4 deletions axiomatic_adapter/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ https://products.axiomatic.com/viewitems/connectivity/ethernet-can-converters
### 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]
ros2 run axiomatic_adapter axiomatic_socketcan_bridge [CAN_INTERFACE_NAME] [IP_ADDRESS] [PORT] [OPTIONAL]--retry-connection[-r] [OPTIONAL]--max-retry-attempts [OPTIONAL]--verbose[-v] [OPTIONAL]--no-tcp-nodelay

# Examples
# generic example to bridge vcan0 with axiomatic using 192.168.50.34:4000
Expand All @@ -22,6 +22,11 @@ ros2 run axiomatic_adapter axiomatic_socketcan_bridge vcan0 192.168.50.34 4000 -

# 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

# disable TCP_NODELAY (re-enable Nagle's algorithm). Default is on; only use if
# you are pushing bulk traffic where throughput matters more than per-frame latency.
# See the Library section below for the full tradeoff discussion.
ros2 run axiomatic_adapter axiomatic_socketcan_bridge vcan0 192.168.50.34 4000 --no-tcp-nodelay
```

### Library
Expand All @@ -38,7 +43,8 @@ polymath::can::AxiomaticAdapter adapter(
port,
[](std::unique_ptr<const CanFrame> /*frame*/) { /* No-op */ },
[](polymath::can::AxiomaticAdapter::socket_error_string_t /*error*/) { /*do nothing*/ },
receive_timeout_ms
receive_timeout_ms,
/*tcp_nodelay=*/true // optional, defaults to true; see below
);

// open the socket
Expand All @@ -50,6 +56,28 @@ adapter.startReceptionThread();
// on shutdown/destruction, thread will join and socket will close
```

#### `tcp_nodelay` parameter

Controls whether `TCP_NODELAY` is set on the socket after a successful connect.
Defaults to `true`.

- **`true` (default)**: Nagle's algorithm is disabled. Each CAN frame leaves the
host as its own TCP segment. The converter's delayed-ACK behaviour otherwise
interacts with Nagle to produce 30–450 ms inter-segment stalls under sustained
CAN traffic, which breaks UDS-style request/response timing (flash sessions,
control loops). This is the right choice for any latency-sensitive workload.
- **`false`**: Nagle's algorithm stays enabled. The kernel may coalesce many
small CAN frames into fewer, larger TCP segments. This lowers
packets-per-second on the network and reduces per-segment header overhead
(~40 bytes IP+TCP per ~24 byte CAN frame payload), at the cost of much higher
worst-case latency per individual frame. Useful only if you are pushing bulk
data where overall throughput matters more than per-frame latency, or if the
network path / receiving device cannot keep up at high PPS.

If you have any doubt, leave it at the default.

## 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
1. Axiomatic-specific heartbeat messages are not handled and are deliberately skipped. Using TCP, this does not cause issues. In future updates heartbeat messages should be consumed and sent as needed
2. Axiomatic-specific status messages are ignored and deliberately skipped. In future revisions this should be handled and reported as necessary.
3. CAN FD is not supported
4. Only TCP mode is supported; no UDP support (heartbeats are required for UDP support)
Original file line number Diff line number Diff line change
Expand Up @@ -54,15 +54,27 @@ class AxiomaticAdapter : public std::enable_shared_from_this<AxiomaticAdapter>
/// @brief AxiomaticAdapter Class Init
/// @param ip_address Axiomatic Device IP address to connect
/// @param port Axiomatic Device Port to connect to
/// @param receive_callback_function called for each CAN frame received from the converter
/// @param error_callback_function called for socket errors and unrecoverable parser conditions
/// @param receive_timeout_ms receive timeout in milliseconds
/// @param tcp_nodelay when true (default), sets TCP_NODELAY on the socket after connect to
/// disable Nagle's algorithm. This eliminates the device's delayed-ACK +
/// Nagle interaction that produces 30-450 ms inter-segment stalls under
/// sustained CAN traffic, at the cost of higher packet-per-second rate
/// on the network (each small CAN frame becomes its own TCP segment with
/// ~40 bytes of IP+TCP header overhead — meaning more interrupts/syscalls
/// on both sides, more switch/NIC PPS load, and worse bytes-on-wire
/// efficiency for bulk transfers). Better for real-time, but can, in constrained
/// resource environments, cause issues
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);
const std::chrono::milliseconds & receive_timeout_ms = AxiomaticAdapter::DEFAULT_SOCKET_RECEIVE_TIMEOUT_MS,
bool tcp_nodelay = true);

/// @brief Destructor for AxiomaticAdapter
virtual ~AxiomaticAdapter();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,15 @@ class AxiomaticSocketcanBridge
/// @param ip IP address of the ethernet can device
/// @param port port for the ethernet can device
/// @param verbose enables printing of debug logs if true. Defaults to false
/// @param tcp_nodelay when true (default), sets TCP_NODELAY on the underlying
/// AxiomaticAdapter socket. See AxiomaticAdapter for the
/// full tradeoff discussion.
AxiomaticSocketcanBridge(
const std::string & can_interface_name, const std::string & ip, const std::string & port, bool verbose = false);
const std::string & can_interface_name,
const std::string & ip,
const std::string & port,
bool verbose = false,
bool tcp_nodelay = true);

/// @brief Destruct axiomatic socketcan bridge
~AxiomaticSocketcanBridge();
Expand Down
Loading
Loading