diff --git a/README.md b/README.md index ca0624b..433fb71 100644 --- a/README.md +++ b/README.md @@ -39,6 +39,18 @@ my_can_socket->send(frame); ``` +### 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 `can_frame` and both time stamps decodes the frame and stores the time stamps. + +```c++ +my_can_library_name::MessageName message{frame, receive_time, bus_time}; +message.receive_time; +message.bus_time; +``` + ### Signal Value Maps (Enums) DBC value tables (`VAL_` lines, and the global `VAL_TABLE_`) map raw signal values 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..a42c707 100644 --- a/dbc_gen_cpp/dbc_gen_cpp/templates/can.hpp.j2 +++ b/dbc_gen_cpp/dbc_gen_cpp/templates/can.hpp.j2 @@ -5,6 +5,7 @@ #include +#include #include #include @@ -54,6 +55,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 +109,17 @@ struct {{ message.name }} {% endif %} } + // 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) + { + this->receive_time = receive_time; + this->bus_time = bus_time; + } + // Conversion operator to can_frame operator can_frame() const { 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 825a7fb..cd050e6 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,34 @@ TEST_CASE("Standard CAN ID mismatch throws") REQUIRE_THROWS(fake_vehicle_can::SteeringFeedback{frame}); } +TEST_CASE("Construct with time stamps keeps time stamps") +{ + 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; + + 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}}; + + 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 with time stamps and wrong ID throws") +{ + can_frame frame; + memset(&frame, 0, sizeof(frame)); + frame.can_id = 100; + frame.can_dlc = 1; + + REQUIRE_THROWS(fake_vehicle_can::SteeringFeedback{frame, {}, {}}); +} + // ============================================================================ // J1939 Protocol Parsing Tests // ============================================================================ @@ -560,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;