diff --git a/socketcan_adapter/include/socketcan_adapter/can_frame.hpp b/socketcan_adapter/include/socketcan_adapter/can_frame.hpp index 911fd85..2d479a3 100644 --- a/socketcan_adapter/include/socketcan_adapter/can_frame.hpp +++ b/socketcan_adapter/include/socketcan_adapter/can_frame.hpp @@ -18,6 +18,7 @@ #include #include +#include #include #include #include @@ -62,7 +63,7 @@ class CanFrame CanFrame( const canid_t raw_id, const std::array & data, - const uint64_t & timestamp, + const uint64_t & bus_timestamp, uint8_t len = CAN_MAX_DLC); /// @brief Initialize CanFrame with a partial ID and data defined in std::array @@ -74,7 +75,7 @@ class CanFrame CanFrame( const canid_t id, const std::array & data, - const uint64_t & timestamp, + const uint64_t & bus_timestamp, FrameType & frame_type, IdType & frame_id_type, uint8_t len = CAN_MAX_DLC); @@ -113,9 +114,29 @@ class CanFrame /// @param data INPUT raw data to pass through the socket void set_data(const std::array & data); - /// @brief Set timestamp + /// @brief Set bus timestamp + /// @param timestamp INPUT time as uint64_t from the bus + void set_bus_timestamp(const uint64_t & timestamp); + + /// @brief Set receive timestamp /// @param timestamp INPUT time as uint64_t from the bus - void set_timestamp(const uint64_t & timestamp); + void set_receive_timestamp(const uint64_t & timestamp); + + /// @brief Set timestamp + /// @param timestamp INPUT time as std::chrono::system_clock::time_point from the bus + void set_bus_timestamp(const std::chrono::system_clock::time_point & timestamp); + + /// @brief Set timestamp + /// @param timestamp INPUT time as std::chrono::steady_clock::time_point from the adapter + void set_receive_timestamp(const std::chrono::steady_clock::time_point & timestamp); + + /// @brief Get bus time + /// @return returns std::chrono::system_clock::time_point for when the frame was received on the bus + std::chrono::system_clock::time_point get_bus_time() const; + + /// @brief Get receive time + /// @return returns std::chrono::steady_clock::time_point for when the frame was received by the adapter + std::chrono::steady_clock::time_point get_receive_time() const; /// @brief Get frame type /// @return returns polymath::socketcan::FrameType @@ -156,7 +177,8 @@ class CanFrame private: struct can_frame frame_{}; - uint64_t timestamp_{}; + std::chrono::steady_clock::time_point receive_time_{}; + std::chrono::system_clock::time_point bus_time_{}; }; } // namespace polymath::socketcan diff --git a/socketcan_adapter/src/can_frame.cpp b/socketcan_adapter/src/can_frame.cpp index 57e3c0a..91ff99a 100644 --- a/socketcan_adapter/src/can_frame.cpp +++ b/socketcan_adapter/src/can_frame.cpp @@ -32,8 +32,12 @@ CanFrame::CanFrame(const struct can_frame & frame) {} CanFrame::CanFrame( - const canid_t raw_id, const std::array & data, const uint64_t & timestamp, uint8_t len) -: timestamp_(timestamp) + const canid_t raw_id, + const std::array & data, + const uint64_t & bus_timestamp, + uint8_t len) +: receive_time_(std::chrono::steady_clock::now()) +, bus_time_(std::chrono::system_clock::time_point(std::chrono::microseconds(bus_timestamp))) { set_can_id(raw_id); std::copy(data.begin(), data.end(), frame_.data); @@ -43,11 +47,12 @@ CanFrame::CanFrame( CanFrame::CanFrame( const canid_t id, const std::array & data, - const uint64_t & timestamp, + const uint64_t & bus_timestamp, FrameType & frame_type, IdType & frame_id_type, uint8_t len) -: timestamp_(timestamp) +: receive_time_(std::chrono::steady_clock::now()) +, bus_time_(std::chrono::system_clock::time_point(std::chrono::microseconds(bus_timestamp))) { set_can_id(id); set_data(data); @@ -130,9 +135,34 @@ void CanFrame::set_data(const std::array & data) std::copy(data.begin(), data.end(), frame_.data); } -void CanFrame::set_timestamp(const uint64_t & timestamp) +void CanFrame::set_bus_timestamp(const uint64_t & timestamp) +{ + bus_time_ = std::chrono::system_clock::time_point(std::chrono::microseconds(timestamp)); +} + +void CanFrame::set_receive_timestamp(const uint64_t & timestamp) +{ + receive_time_ = std::chrono::steady_clock::time_point(std::chrono::microseconds(timestamp)); +} + +void CanFrame::set_bus_timestamp(const std::chrono::system_clock::time_point & timestamp) +{ + bus_time_ = timestamp; +} + +void CanFrame::set_receive_timestamp(const std::chrono::steady_clock::time_point & timestamp) +{ + receive_time_ = timestamp; +} + +std::chrono::system_clock::time_point CanFrame::get_bus_time() const +{ + return bus_time_; +} + +std::chrono::steady_clock::time_point CanFrame::get_receive_time() const { - timestamp_ = timestamp; + return receive_time_; } IdType CanFrame::get_id_type() const diff --git a/socketcan_adapter/src/socketcan_adapter.cpp b/socketcan_adapter/src/socketcan_adapter.cpp index ca13b5e..445ae5a 100644 --- a/socketcan_adapter/src/socketcan_adapter.cpp +++ b/socketcan_adapter/src/socketcan_adapter.cpp @@ -24,6 +24,7 @@ #include #include +#include #include #include #include @@ -172,9 +173,12 @@ std::optional SocketcanAdapter::receive struct timeval tv; ioctl(socket_file_descriptor_, SIOCGSTAMP, &tv); - uint64_t timestamp_uint64 = static_cast(tv.tv_sec) * 1e6 + tv.tv_usec; + // uint64_t timestamp_uint64 = static_cast(tv.tv_sec) * 1e6 + tv.tv_usec; + std::chrono::system_clock::time_point timestamp{ + std::chrono::seconds{tv.tv_sec} + std::chrono::microseconds{tv.tv_usec}}; polymath_can_frame.set_frame(frame); - polymath_can_frame.set_timestamp(timestamp_uint64); + polymath_can_frame.set_bus_timestamp(timestamp); + polymath_can_frame.set_receive_timestamp(std::chrono::steady_clock::now()); } return error_string.empty() ? std::nullopt : std::optional(error_string); diff --git a/socketcan_adapter/test/can_frame_test.cpp b/socketcan_adapter/test/can_frame_test.cpp index 311a9d1..a630d6e 100644 --- a/socketcan_adapter/test/can_frame_test.cpp +++ b/socketcan_adapter/test/can_frame_test.cpp @@ -14,6 +14,7 @@ #include #include +#include #include #include @@ -168,3 +169,103 @@ TEST_CASE("Get error as string", "[CanFrame]") std::string error = frame.get_error(); REQUIRE(!error.empty()); } + +TEST_CASE("Set and get bus timestamp with uint64_t", "[CanFrame]") +{ + polymath::socketcan::CanFrame frame; + std::uint64_t timestamp_us = 1000000; // 1 second in microseconds + + frame.set_bus_timestamp(timestamp_us); + auto bus_time = frame.get_bus_time(); + + auto expected = std::chrono::system_clock::time_point(std::chrono::microseconds(timestamp_us)); + REQUIRE(bus_time == expected); +} + +TEST_CASE("Set and get receive timestamp with uint64_t", "[CanFrame]") +{ + polymath::socketcan::CanFrame frame; + std::uint64_t timestamp_us = 2000000; // 2 seconds in microseconds + + frame.set_receive_timestamp(timestamp_us); + auto receive_time = frame.get_receive_time(); + + auto expected = std::chrono::steady_clock::time_point(std::chrono::microseconds(timestamp_us)); + REQUIRE(receive_time == expected); +} + +TEST_CASE("Set and get bus timestamp with time_point", "[CanFrame]") +{ + polymath::socketcan::CanFrame frame; + auto timestamp = std::chrono::system_clock::now(); + + frame.set_bus_timestamp(timestamp); + auto bus_time = frame.get_bus_time(); + + REQUIRE(bus_time == timestamp); +} + +TEST_CASE("Set and get receive timestamp with time_point", "[CanFrame]") +{ + polymath::socketcan::CanFrame frame; + auto timestamp = std::chrono::steady_clock::now(); + + frame.set_receive_timestamp(timestamp); + auto receive_time = frame.get_receive_time(); + + REQUIRE(receive_time == timestamp); +} + +TEST_CASE("Constructor initializes receive_time to now", "[CanFrame]") +{ + auto before = std::chrono::steady_clock::now(); + + canid_t raw_id = 0x123; + std::array data = {1, 2, 3, 4, 5, 6, 7, 8}; + std::uint64_t bus_timestamp = 123456789; + polymath::socketcan::CanFrame frame(raw_id, data, bus_timestamp); + + auto after = std::chrono::steady_clock::now(); + auto receive_time = frame.get_receive_time(); + + REQUIRE(receive_time >= before); + REQUIRE(receive_time <= after); +} + +TEST_CASE("Constructor sets bus_time from timestamp parameter", "[CanFrame]") +{ + canid_t raw_id = 0x123; + std::array data = {1, 2, 3, 4, 5, 6, 7, 8}; + std::uint64_t bus_timestamp = 5000000; // 5 seconds in microseconds + + polymath::socketcan::CanFrame frame(raw_id, data, bus_timestamp); + auto bus_time = frame.get_bus_time(); + + auto expected = std::chrono::system_clock::time_point(std::chrono::microseconds(bus_timestamp)); + REQUIRE(bus_time == expected); +} + +TEST_CASE("Extended constructor initializes timestamps correctly", "[CanFrame]") +{ + auto before = std::chrono::steady_clock::now(); + + canid_t raw_id = 0x789; + std::array data = {9, 8, 7, 6, 5, 4, 3, 2}; + std::uint64_t bus_timestamp = 10000000; // 10 seconds in microseconds + auto frame_type = polymath::socketcan::FrameType::REMOTE; + auto frame_id_type = polymath::socketcan::IdType::EXTENDED; + + polymath::socketcan::CanFrame frame(raw_id, data, bus_timestamp, frame_type, frame_id_type); + + auto after = std::chrono::steady_clock::now(); + + // Check receive_time was set to approximately now + auto receive_time = frame.get_receive_time(); + REQUIRE(receive_time >= before); + REQUIRE(receive_time <= after); + + // Check bus_time was set from the parameter + auto bus_time = frame.get_bus_time(); + auto expected_bus_time = std::chrono::system_clock::time_point(std::chrono::microseconds(bus_timestamp)); + REQUIRE(bus_time == expected_bus_time); +} diff --git a/socketcan_adapter/test/socketcan_adapter_test.cpp b/socketcan_adapter/test/socketcan_adapter_test.cpp index 8b0fc86..047700e 100644 --- a/socketcan_adapter/test/socketcan_adapter_test.cpp +++ b/socketcan_adapter/test/socketcan_adapter_test.cpp @@ -16,8 +16,13 @@ #include #include +#include #include +#include +#include #include +#include +#include #if __has_include() #include // v3 @@ -168,3 +173,118 @@ TEST_CASE("Get error as string", "[CanFrame]") std::string error = frame.get_error(); REQUIRE(!error.empty()); } + +TEST_CASE("SocketcanAdapter receive sets timestamps", "[SocketcanAdapter]") +{ + // Create two adapters - one to send, one to receive + polymath::socketcan::SocketcanAdapter sender("vcan0"); + polymath::socketcan::SocketcanAdapter receiver("vcan0"); + + REQUIRE(sender.openSocket()); + REQUIRE(receiver.openSocket()); + + // Set up promise/future for synchronization - no sleep needed + std::promise frame_promise; + std::future frame_future = frame_promise.get_future(); + + // Register callback that fulfills the promise when frame is received + receiver.setOnReceiveCallback( + [&frame_promise](std::unique_ptr frame) { frame_promise.set_value(*frame); }); + + REQUIRE(receiver.startReceptionThread()); + + // Create a frame to send + polymath::socketcan::CanFrame tx_frame; + tx_frame.set_can_id(0x123); + std::array data = {0xDE, 0xAD, 0xBE, 0xEF, 0x00, 0x00, 0x00, 0x00}; + tx_frame.set_data(data); + tx_frame.set_len(4); + + // Record time before send + auto before_send = std::chrono::steady_clock::now(); + + // Send the frame + auto send_result = sender.send(tx_frame); + REQUIRE_FALSE(send_result.has_value()); + + // Wait for frame with explicit timeout (1 second is generous for vcan loopback) + auto status = frame_future.wait_for(std::chrono::seconds(1)); + REQUIRE(status == std::future_status::ready); + + auto rx_frame = frame_future.get(); + auto after_receive = std::chrono::steady_clock::now(); + + // Verify frame data matches + REQUIRE(rx_frame.get_id() == 0x123); + + // Verify receive_time is set and within expected bounds + auto receive_time = rx_frame.get_receive_time(); + REQUIRE(receive_time >= before_send); + REQUIRE(receive_time <= after_receive); + + // Verify bus_time is set (non-default) + auto bus_time = rx_frame.get_bus_time(); + auto epoch = std::chrono::system_clock::time_point{}; + REQUIRE(bus_time != epoch); + + receiver.joinReceptionThread(); + sender.closeSocket(); + receiver.closeSocket(); +} + +TEST_CASE("SocketcanAdapter receive timestamps are monotonic", "[SocketcanAdapter]") +{ + polymath::socketcan::SocketcanAdapter sender("vcan0"); + polymath::socketcan::SocketcanAdapter receiver("vcan0"); + + REQUIRE(sender.openSocket()); + REQUIRE(receiver.openSocket()); + + // Thread-safe frame collection using mutex-protected vector and a promise for completion + constexpr size_t FRAME_COUNT = 3; + std::mutex mtx; + std::vector received_frames; + std::promise all_received_promise; + std::future all_received_future = all_received_promise.get_future(); + + receiver.setOnReceiveCallback([&](std::unique_ptr frame) { + std::lock_guard lock(mtx); + received_frames.push_back(*frame); + if (received_frames.size() == FRAME_COUNT) { + all_received_promise.set_value(); + } + }); + + REQUIRE(receiver.startReceptionThread()); + + // Send all frames + for (size_t i = 0; i < FRAME_COUNT; ++i) { + polymath::socketcan::CanFrame tx_frame; + tx_frame.set_can_id(0x200 + static_cast(i)); + std::array data = {static_cast(i), 0, 0, 0, 0, 0, 0, 0}; + tx_frame.set_data(data); + tx_frame.set_len(1); + + auto send_result = sender.send(tx_frame); + REQUIRE_FALSE(send_result.has_value()); + } + + // Wait for all frames with explicit timeout + auto status = all_received_future.wait_for(std::chrono::seconds(2)); + REQUIRE(status == std::future_status::ready); + + // Verify we received all frames + { + std::lock_guard lock(mtx); + REQUIRE(received_frames.size() == FRAME_COUNT); + + // Verify receive times are monotonically increasing + for (size_t i = 1; i < received_frames.size(); ++i) { + REQUIRE(received_frames[i].get_receive_time() >= received_frames[i - 1].get_receive_time()); + } + } + + receiver.joinReceptionThread(); + sender.closeSocket(); + receiver.closeSocket(); +}