From 049b10f785280efc5048e3108c5406fdb39bdd72 Mon Sep 17 00:00:00 2001 From: Ryan Bahl Date: Wed, 30 Sep 2026 00:17:31 +0000 Subject: [PATCH 1/3] Add socketcan adapter timestamp support --- README.md | 15 +++++++++ dbc_gen_cpp/CMakeLists.txt | 3 ++ dbc_gen_cpp/dbc_gen_cpp/templates/can.hpp.j2 | 14 +++++++++ dbc_gen_cpp/package.xml | 2 ++ test_dbc_gen_cpp/test/test_dbc_cpp.cpp | 32 ++++++++++++++++++++ 5 files changed, 66 insertions(+) diff --git a/README.md b/README.md index ca0624b..51365c4 100644 --- a/README.md +++ b/README.md @@ -39,6 +39,21 @@ my_can_socket->send(frame); ``` +### Time Stamps and `socketcan_adapter` + +Each message carries `receive_time` (`std::chrono::steady_clock`) and `bus_time` (`std::chrono::system_clock`), both default to the epoch. + +Constructing a message from a [`polymath::socketcan::CanFrame`](https://github.com/polymathrobotics/socketcan_adapter) decodes the frame and copies both time stamps. + +```c++ +polymath::socketcan::CanFrame frame = ...; +my_can_library_name::MessageName message{frame}; +message.receive_time; +message.bus_time; +``` + +This makes `socketcan_adapter` a dependency of `dbc_gen_cpp`, installed by `rosdep install`. + ### Signal Value Maps (Enums) DBC value tables (`VAL_` lines, and the global `VAL_TABLE_`) map raw signal values diff --git a/dbc_gen_cpp/CMakeLists.txt b/dbc_gen_cpp/CMakeLists.txt index fe23ed3..a197a24 100644 --- a/dbc_gen_cpp/CMakeLists.txt +++ b/dbc_gen_cpp/CMakeLists.txt @@ -13,6 +13,7 @@ endif() find_package(ament_cmake REQUIRED) find_package(ament_cmake_python REQUIRED) +find_package(socketcan_adapter REQUIRED) ament_python_install_package(${PROJECT_NAME} SCRIPTS_DESTINATION bin @@ -23,6 +24,7 @@ target_include_directories(${PROJECT_NAME} INTERFACE $ $ ) +target_link_libraries(${PROJECT_NAME} INTERFACE socketcan_adapter::socketcan_adapter) install( TARGETS ${PROJECT_NAME} @@ -50,6 +52,7 @@ if(BUILD_TESTING) endif() ament_export_targets(${PROJECT_NAME}Targets HAS_LIBRARY_TARGET) +ament_export_dependencies(socketcan_adapter) ament_package( CONFIG_EXTRAS "dbc_gen_cpp-extras.cmake" ) diff --git a/dbc_gen_cpp/dbc_gen_cpp/templates/can.hpp.j2 b/dbc_gen_cpp/dbc_gen_cpp/templates/can.hpp.j2 index 718068d..97074cd 100644 --- a/dbc_gen_cpp/dbc_gen_cpp/templates/can.hpp.j2 +++ b/dbc_gen_cpp/dbc_gen_cpp/templates/can.hpp.j2 @@ -5,9 +5,11 @@ #include +#include #include #include +#include "socketcan_adapter/can_frame.hpp" #include "{{ library_name }}/{{ c_header }}" namespace {{ library_name }} @@ -54,6 +56,10 @@ struct {{ message.name }} } {% endif %} + // Time stamps + std::chrono::steady_clock::time_point receive_time{}; + std::chrono::system_clock::time_point bus_time{}; + // Signals {% for signal in message.signals %} double {{ signal.name }}; @@ -104,6 +110,14 @@ struct {{ message.name }} {% endif %} } + // Constructor from socketcan CanFrame, keeps its time stamps + explicit {{ message.name }}(const polymath::socketcan::CanFrame & frame) + : {{ message.name }}(frame.get_frame()) + { + receive_time = frame.get_receive_time(); + bus_time = frame.get_bus_time(); + } + // Conversion operator to can_frame operator can_frame() const { diff --git a/dbc_gen_cpp/package.xml b/dbc_gen_cpp/package.xml index 3649ff8..78db7a7 100644 --- a/dbc_gen_cpp/package.xml +++ b/dbc_gen_cpp/package.xml @@ -10,6 +10,8 @@ ament_cmake ament_cmake_python + socketcan_adapter + python3-cantools-pip python3-jinja2 diff --git a/test_dbc_gen_cpp/test/test_dbc_cpp.cpp b/test_dbc_gen_cpp/test/test_dbc_cpp.cpp index 825a7fb..60e04e1 100644 --- a/test_dbc_gen_cpp/test/test_dbc_cpp.cpp +++ b/test_dbc_gen_cpp/test/test_dbc_cpp.cpp @@ -1,6 +1,7 @@ // SPDX-FileCopyrightText: 2026 Polymath Robotics, Inc. // SPDX-License-Identifier: Apache-2.0 +#include #include #include @@ -368,6 +369,37 @@ TEST_CASE("Standard CAN ID mismatch throws") REQUIRE_THROWS(fake_vehicle_can::SteeringFeedback{frame}); } +TEST_CASE("Construct from socketcan CanFrame keeps time stamps") +{ + can_frame raw; + memset(&raw, 0, sizeof(raw)); + raw.can_id = fake_vehicle_can::SteeringFeedback::Id; + raw.can_dlc = fake_vehicle_can::SteeringFeedback::DataLength; + raw.data[0] = 25; + + polymath::socketcan::CanFrame frame{raw}; + const auto receive_time = std::chrono::steady_clock::time_point{std::chrono::seconds{5}}; + const auto bus_time = std::chrono::system_clock::time_point{std::chrono::seconds{7}}; + frame.set_receive_timestamp(receive_time); + frame.set_bus_timestamp(bus_time); + + const fake_vehicle_can::SteeringFeedback msg{frame}; + + REQUIRE(msg.receive_time == receive_time); + REQUIRE(msg.bus_time == bus_time); + REQUIRE(msg.angle == 25); +} + +TEST_CASE("Construct from socketcan CanFrame with wrong ID throws") +{ + can_frame raw; + memset(&raw, 0, sizeof(raw)); + raw.can_id = 100; + raw.can_dlc = 1; + + REQUIRE_THROWS(fake_vehicle_can::SteeringFeedback{polymath::socketcan::CanFrame{raw}}); +} + // ============================================================================ // J1939 Protocol Parsing Tests // ============================================================================ From a1d68651d9825224c805b5af78192676a07c0d6c Mon Sep 17 00:00:00 2001 From: Ryan Bahl Date: Mon, 12 Oct 2026 03:28:17 +0000 Subject: [PATCH 2/3] remove SocketCanAdapter dependency --- README.md | 9 ++---- dbc_gen_cpp/CMakeLists.txt | 3 -- dbc_gen_cpp/dbc_gen_cpp/templates/can.hpp.j2 | 14 ++++++---- dbc_gen_cpp/package.xml | 2 -- test_dbc_gen_cpp/test/test_dbc_cpp.cpp | 29 +++++++++----------- 5 files changed, 24 insertions(+), 33 deletions(-) diff --git a/README.md b/README.md index 51365c4..433fb71 100644 --- a/README.md +++ b/README.md @@ -39,21 +39,18 @@ my_can_socket->send(frame); ``` -### Time Stamps and `socketcan_adapter` +### Time Stamps Each message carries `receive_time` (`std::chrono::steady_clock`) and `bus_time` (`std::chrono::system_clock`), both default to the epoch. -Constructing a message from a [`polymath::socketcan::CanFrame`](https://github.com/polymathrobotics/socketcan_adapter) decodes the frame and copies both time stamps. +Constructing a message from a `can_frame` and both time stamps decodes the frame and stores the time stamps. ```c++ -polymath::socketcan::CanFrame frame = ...; -my_can_library_name::MessageName message{frame}; +my_can_library_name::MessageName message{frame, receive_time, bus_time}; message.receive_time; message.bus_time; ``` -This makes `socketcan_adapter` a dependency of `dbc_gen_cpp`, installed by `rosdep install`. - ### Signal Value Maps (Enums) DBC value tables (`VAL_` lines, and the global `VAL_TABLE_`) map raw signal values diff --git a/dbc_gen_cpp/CMakeLists.txt b/dbc_gen_cpp/CMakeLists.txt index a197a24..fe23ed3 100644 --- a/dbc_gen_cpp/CMakeLists.txt +++ b/dbc_gen_cpp/CMakeLists.txt @@ -13,7 +13,6 @@ endif() find_package(ament_cmake REQUIRED) find_package(ament_cmake_python REQUIRED) -find_package(socketcan_adapter REQUIRED) ament_python_install_package(${PROJECT_NAME} SCRIPTS_DESTINATION bin @@ -24,7 +23,6 @@ target_include_directories(${PROJECT_NAME} INTERFACE $ $ ) -target_link_libraries(${PROJECT_NAME} INTERFACE socketcan_adapter::socketcan_adapter) install( TARGETS ${PROJECT_NAME} @@ -52,7 +50,6 @@ if(BUILD_TESTING) endif() ament_export_targets(${PROJECT_NAME}Targets HAS_LIBRARY_TARGET) -ament_export_dependencies(socketcan_adapter) ament_package( CONFIG_EXTRAS "dbc_gen_cpp-extras.cmake" ) diff --git a/dbc_gen_cpp/dbc_gen_cpp/templates/can.hpp.j2 b/dbc_gen_cpp/dbc_gen_cpp/templates/can.hpp.j2 index 97074cd..a42c707 100644 --- a/dbc_gen_cpp/dbc_gen_cpp/templates/can.hpp.j2 +++ b/dbc_gen_cpp/dbc_gen_cpp/templates/can.hpp.j2 @@ -9,7 +9,6 @@ #include #include -#include "socketcan_adapter/can_frame.hpp" #include "{{ library_name }}/{{ c_header }}" namespace {{ library_name }} @@ -110,12 +109,15 @@ struct {{ message.name }} {% endif %} } - // Constructor from socketcan CanFrame, keeps its time stamps - explicit {{ message.name }}(const polymath::socketcan::CanFrame & frame) - : {{ message.name }}(frame.get_frame()) + // Constructor from can_frame with time stamps + {{ message.name }}( + const can_frame & frame, + std::chrono::steady_clock::time_point receive_time, + std::chrono::system_clock::time_point bus_time) + : {{ message.name }}(frame) { - receive_time = frame.get_receive_time(); - bus_time = frame.get_bus_time(); + this->receive_time = receive_time; + this->bus_time = bus_time; } // Conversion operator to can_frame diff --git a/dbc_gen_cpp/package.xml b/dbc_gen_cpp/package.xml index 78db7a7..3649ff8 100644 --- a/dbc_gen_cpp/package.xml +++ b/dbc_gen_cpp/package.xml @@ -10,8 +10,6 @@ ament_cmake ament_cmake_python - socketcan_adapter - python3-cantools-pip python3-jinja2 diff --git a/test_dbc_gen_cpp/test/test_dbc_cpp.cpp b/test_dbc_gen_cpp/test/test_dbc_cpp.cpp index 60e04e1..a776ca2 100644 --- a/test_dbc_gen_cpp/test/test_dbc_cpp.cpp +++ b/test_dbc_gen_cpp/test/test_dbc_cpp.cpp @@ -369,35 +369,32 @@ TEST_CASE("Standard CAN ID mismatch throws") REQUIRE_THROWS(fake_vehicle_can::SteeringFeedback{frame}); } -TEST_CASE("Construct from socketcan CanFrame keeps time stamps") +TEST_CASE("Construct with time stamps keeps time stamps") { - can_frame raw; - memset(&raw, 0, sizeof(raw)); - raw.can_id = fake_vehicle_can::SteeringFeedback::Id; - raw.can_dlc = fake_vehicle_can::SteeringFeedback::DataLength; - raw.data[0] = 25; + can_frame frame; + memset(&frame, 0, sizeof(frame)); + frame.can_id = fake_vehicle_can::SteeringFeedback::Id; + frame.can_dlc = fake_vehicle_can::SteeringFeedback::DataLength; + frame.data[0] = 25; - polymath::socketcan::CanFrame frame{raw}; const auto receive_time = std::chrono::steady_clock::time_point{std::chrono::seconds{5}}; const auto bus_time = std::chrono::system_clock::time_point{std::chrono::seconds{7}}; - frame.set_receive_timestamp(receive_time); - frame.set_bus_timestamp(bus_time); - const fake_vehicle_can::SteeringFeedback msg{frame}; + const fake_vehicle_can::SteeringFeedback msg{frame, receive_time, bus_time}; REQUIRE(msg.receive_time == receive_time); REQUIRE(msg.bus_time == bus_time); REQUIRE(msg.angle == 25); } -TEST_CASE("Construct from socketcan CanFrame with wrong ID throws") +TEST_CASE("Construct with time stamps and wrong ID throws") { - can_frame raw; - memset(&raw, 0, sizeof(raw)); - raw.can_id = 100; - raw.can_dlc = 1; + can_frame frame; + memset(&frame, 0, sizeof(frame)); + frame.can_id = 100; + frame.can_dlc = 1; - REQUIRE_THROWS(fake_vehicle_can::SteeringFeedback{polymath::socketcan::CanFrame{raw}}); + REQUIRE_THROWS(fake_vehicle_can::SteeringFeedback{frame, {}, {}}); } // ============================================================================ From ae439e44522e980d8d8d9db555b62b86c39316c6 Mon Sep 17 00:00:00 2001 From: Ryan Bahl Date: Mon, 12 Oct 2026 04:05:00 +0000 Subject: [PATCH 3/3] add timestamp handling to can_handler --- .../include/dbc_gen_cpp/can_handler.hpp | 22 ++++++++++++++----- test_dbc_gen_cpp/test/test_dbc_cpp.cpp | 21 ++++++++++++++++++ 2 files changed, 37 insertions(+), 6 deletions(-) diff --git a/dbc_gen_cpp/include/dbc_gen_cpp/can_handler.hpp b/dbc_gen_cpp/include/dbc_gen_cpp/can_handler.hpp index 158128a..8cceaa4 100644 --- a/dbc_gen_cpp/include/dbc_gen_cpp/can_handler.hpp +++ b/dbc_gen_cpp/include/dbc_gen_cpp/can_handler.hpp @@ -5,6 +5,7 @@ #include +#include #include #include #include @@ -40,13 +41,18 @@ class CANHandler /// @brief Route a can frame to the appropriate handler, if registered /// @param frame + /// @param receive_time Copied to the message; defaults to the epoch. + /// @param bus_time Copied to the message; defaults to the epoch. /// @return true if there was a registered handler that was called - bool handle(const can_frame & frame) const + bool handle( + const can_frame & frame, + std::chrono::steady_clock::time_point receive_time = {}, + std::chrono::system_clock::time_point bus_time = {}) const { // Standard CAN: exact ID match auto it = handlers_.find(frame.can_id); if (it != handlers_.end()) { - it->second->invoke(frame); + it->second->invoke(frame, receive_time, bus_time); return true; } @@ -55,7 +61,7 @@ class CANHandler uint32_t pgn = extractPgn(frame.can_id); auto j1939_it = j1939_handlers_.find(pgn); if (j1939_it != j1939_handlers_.end()) { - j1939_it->second->invoke(frame); + j1939_it->second->invoke(frame, receive_time, bus_time); return true; } } @@ -83,7 +89,8 @@ class CANHandler struct ICanMessageHandler { virtual ~ICanMessageHandler() = default; - virtual void invoke(const can_frame &) const = 0; + virtual void invoke( + const can_frame &, std::chrono::steady_clock::time_point, std::chrono::system_clock::time_point) const = 0; }; /// @brief Concrete instantiation of the handler interface for a given message type @@ -97,9 +104,12 @@ class CANHandler : callback(std::move(cb)) {} - void invoke(const can_frame & frame) const override + void invoke( + const can_frame & frame, + std::chrono::steady_clock::time_point receive_time, + std::chrono::system_clock::time_point bus_time) const override { - callback(T(frame)); + callback(T(frame, receive_time, bus_time)); } }; diff --git a/test_dbc_gen_cpp/test/test_dbc_cpp.cpp b/test_dbc_gen_cpp/test/test_dbc_cpp.cpp index a776ca2..cd050e6 100644 --- a/test_dbc_gen_cpp/test/test_dbc_cpp.cpp +++ b/test_dbc_gen_cpp/test/test_dbc_cpp.cpp @@ -589,6 +589,27 @@ TEST_CASE("CANHandler Standard CAN - Basic routing") REQUIRE(received_value == 42); } +TEST_CASE("CANHandler Standard CAN - Passes time stamps") +{ + using fake_vehicle_can::SteeringFeedback; + + dbc_gen_cpp::CANHandler handler; + std::chrono::steady_clock::time_point received_receive_time{}; + std::chrono::system_clock::time_point received_bus_time{}; + + handler.set_handler([&](const SteeringFeedback & msg) { + received_receive_time = msg.receive_time; + received_bus_time = msg.bus_time; + }); + + const auto receive_time = std::chrono::steady_clock::time_point{std::chrono::seconds{5}}; + const auto bus_time = std::chrono::system_clock::time_point{std::chrono::seconds{7}}; + + REQUIRE(handler.handle(SteeringFeedback{42}, receive_time, bus_time)); + REQUIRE(received_receive_time == receive_time); + REQUIRE(received_bus_time == bus_time); +} + TEST_CASE("CANHandler Standard CAN - Multiple handlers") { using fake_vehicle_can::Heartbeat;