Skip to content
Draft
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
32 changes: 32 additions & 0 deletions socketcan_adapter/include/socketcan_adapter/socketcan_adapter.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,38 @@ constexpr std::chrono::duration<float> JOIN_RECEPTION_TIMEOUT_S = std::chrono::d
constexpr int32_t CLOSED_SOCKET_VALUE = -1;
constexpr nfds_t NUM_SOCKETS_IN_ADAPTER = 1;

/// @brief J1939 CAN ID bit layout constants
constexpr uint32_t J1939_PGN_SHIFT = 8;
constexpr uint32_t J1939_PF_SHIFT = 16;
constexpr uint32_t J1939_PF_MASK = 0xFF;
constexpr uint32_t J1939_PDU2_THRESHOLD = 0xF0;
/// @brief Masks priority (3 bits) and source address (8 bits), matches full PGN (18 bits)
constexpr uint32_t J1939_PGN_FULL_MASK = 0x03FFFF00;
/// @brief Masks priority, source address, and PDU Specific (destination addr in PDU1)
constexpr uint32_t J1939_PGN_PDU1_MASK = 0x03FF0000;

/// @brief Convert a J1939 PGN to a CAN filter suitable for use with setFilters()
/// Handles PDU1 (PF < 0xF0) vs PDU2 (PF >= 0xF0) masking automatically:
/// - PDU2: PS is Group Extension, part of the PGN — all 18 PGN bits are matched
/// - PDU1: PS is destination address, not part of the PGN — only 10 bits are matched
/// @param pgn The PGN value (18-bit, e.g. 0xFEF1)
/// @return can_filter with appropriate can_id and can_mask set
static inline struct can_filter j1939PgnToFilter(const uint32_t pgn)
{
const uint8_t pf = static_cast<uint8_t>((pgn >> J1939_PGN_SHIFT) & J1939_PF_MASK);

struct can_filter filter{};
filter.can_id = (pgn << J1939_PGN_SHIFT) | CAN_EFF_FLAG;

if (J1939_PDU2_THRESHOLD <= pf) {
filter.can_mask = J1939_PGN_FULL_MASK | CAN_EFF_FLAG;
} else {
filter.can_mask = J1939_PGN_PDU1_MASK | CAN_EFF_FLAG;
}

return filter;
}

/// @class polymath::socketcan::SocketcanAdapter
/// @brief Creates and manages a socketcan instance and simplifies the interface.
/// Generally does not throw, but returns booleans to tell you success
Expand Down
35 changes: 35 additions & 0 deletions socketcan_adapter/test/can_frame_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -269,3 +269,38 @@ TEST_CASE("Extended constructor initializes timestamps correctly", "[CanFrame]")
auto expected_bus_time = std::chrono::system_clock::time_point(std::chrono::microseconds(bus_timestamp));
REQUIRE(bus_time == expected_bus_time);
}

TEST_CASE("j1939PgnToFilter PDU2 matches full PGN", "[J1939]")
{
// PGN 0xFEF1 (Engine Coolant Temperature) — PF=0xFE >= 0xF0, so PDU2
const auto filter = polymath::socketcan::j1939PgnToFilter(0xFEF1);

// can_id should be PGN shifted left 8 bits with EFF flag
REQUIRE(filter.can_id == ((0xFEF1U << 8) | CAN_EFF_FLAG));
// PDU2: full 18-bit PGN mask + EFF flag
REQUIRE(filter.can_mask == (polymath::socketcan::J1939_PGN_FULL_MASK | CAN_EFF_FLAG));
}

TEST_CASE("j1939PgnToFilter PDU1 masks out destination address", "[J1939]")
{
// PGN 0xEA00 (Request PGN) — PF=0xEA < 0xF0, so PDU1
const auto filter = polymath::socketcan::j1939PgnToFilter(0xEA00);

REQUIRE(filter.can_id == ((0xEA00U << 8) | CAN_EFF_FLAG));
// PDU1: only upper 10 bits of PGN (EDP+DP+PF), PS bits unmasked
REQUIRE(filter.can_mask == (polymath::socketcan::J1939_PGN_PDU1_MASK | CAN_EFF_FLAG));
}

TEST_CASE("j1939PgnToFilter PDU1 boundary at 0xEF", "[J1939]")
{
// PF=0xEF is the last PDU1 value (< 0xF0)
const auto filter = polymath::socketcan::j1939PgnToFilter(0xEF00);
REQUIRE(filter.can_mask == (polymath::socketcan::J1939_PGN_PDU1_MASK | CAN_EFF_FLAG));
}

TEST_CASE("j1939PgnToFilter PDU2 boundary at 0xF0", "[J1939]")
{
// PF=0xF0 is the first PDU2 value (>= 0xF0)
const auto filter = polymath::socketcan::j1939PgnToFilter(0xF000);
REQUIRE(filter.can_mask == (polymath::socketcan::J1939_PGN_FULL_MASK | CAN_EFF_FLAG));
}
Loading