Skip to content

Commit

Permalink
✨ Add can_message_finder (#52)
Browse files Browse the repository at this point in the history
  • Loading branch information
kammce authored Jan 3, 2025
1 parent 54bf502 commit 2fe551f
Show file tree
Hide file tree
Showing 3 changed files with 305 additions and 1 deletion.
2 changes: 1 addition & 1 deletion conanfile.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ def build_requirements(self):
self.test_requires("boost-ext-ut/2.1.0")

def requirements(self):
self.requires("libhal/[^4.7.0]", transitive_headers=True)
self.requires("libhal/[^4.9.0]", transitive_headers=True)

def layout(self):
cmake_layout(self)
Expand Down
102 changes: 102 additions & 0 deletions include/libhal-util/can.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -190,4 +190,106 @@ struct can_bus_divider_t

return timing;
}

/**
* @brief A hal::can_transceiver wrapper class that makes finding message via ID
* easier with a find API
*
* If your driver plans to use this wrapper, rather than storing both the
* pointer to the hal::can_transceiver and this object, simply construct this
* object with the hal::can_transceiver object's address and use this object's
* transceiver() API to get access to the underlying transceiver implementation.
* This will ensure that the driver doesn't store the implementation's address
* twice.
*
*/
class can_message_finder
{
public:
/**
* @brief Construct a new can reader object
*
* @param p_transceiver - the transceiver to read messages from
* @param p_id - the message ID to search for
*/
can_message_finder(hal::can_transceiver& p_transceiver, hal::u32 p_id)
: m_transceiver(&p_transceiver)
, m_id(p_id)
{
}

/**
* @brief Find a message within the receive buffer matching the input message
* ID.
*
* This API performs a double copy of the can message in order to confirm that
* the message did not get modified by the driver during the copy.
*
* @return std::optional<hal::can_message> - a copy of the can message within
* the buffer. The return is set to std::nullopt if the message could not be
* found OR was modified during the copy.
*/
[[nodiscard]] std::optional<hal::can_message> find()
{
auto const buffer = m_transceiver->receive_buffer();
auto cursor = m_transceiver->receive_cursor();
hal::can_message message = {};

// Run through the circular buffer until the cursor is reached or until an
// ID match is found.
while (m_receive_cursor != cursor) {
if (m_id == buffer[m_receive_cursor].id) {
message = buffer[m_receive_cursor];
// Can message buffers are typically updated via interrupt which can
// happen at any time. If the cursor has moved past the received message
// and changed its bits mid way To ensure that an interrupt has not
// occurred and modified the bits of the buffer message, a second copy
// is created in order to perform a stability check.
hal::can_message const copy = buffer[m_receive_cursor];
bool const message_is_stable = copy == message;
bool const copy_id_matches = copy.id == m_id;
if (message_is_stable and copy_id_matches) {
// Perform circular increment of the m_receive_cursor
m_receive_cursor = (m_receive_cursor + 1) % buffer.size();
return message;
}
}
// Acquire latest cursor
cursor = m_transceiver->receive_cursor();
// Perform circular increment of the m_receive_cursor
m_receive_cursor = (m_receive_cursor + 1) % buffer.size();
}

return std::nullopt;
}

/**
* @brief Returns the underlying can hal::can_transceiver allowing
*
* This API makes it possible for drivers and applications using this wrapper
* to fully access the hal::can_transceiver without needing to store the
* pointer twice.
*
* @return hal::can_transceiver& - the underlying hal::can_transceiver
*/
[[nodiscard]] inline hal::can_transceiver& transceiver() const
{
return *m_transceiver;
}

/**
* @brief Returns the ID to being searched for
*
* @return auto - the ID being searched for
*/
[[nodiscard]] inline auto id() const
{
return m_id;
}

private:
hal::can_transceiver* m_transceiver;
hal::u32 m_id;
std::size_t m_receive_cursor = 0;
};
} // namespace hal
202 changes: 202 additions & 0 deletions tests/can.test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
#include <iostream>

#include <boost/ut.hpp>
#include <libhal/can.hpp>
#include <ratio>

namespace hal {

Expand Down Expand Up @@ -71,6 +73,40 @@ void check_validity(hal::hertz p_operating_frequency,
static_cast<std::uint32_t>(p_target_baud_rate))
<< "Failure to get the expected baud rate with " << test_subject;
}

struct test_can_transceiver : hal::can_transceiver
{
test_can_transceiver() = default;
hal::u32 driver_baud_rate() override
{
using namespace hal::literals;
return 100_kHz;
}

void driver_send(hal::can_message const& p_message) override
{
last_sent_message = p_message;
}

std::span<hal::can_message const> driver_receive_buffer() override
{
return m_buffer;
}

std::size_t driver_receive_cursor() override
{
return m_cursor % m_buffer.size();
};

void add_to_received_messages(hal::can_message const& p_message)
{
m_buffer[m_cursor++ % m_buffer.size()] = p_message;
}

hal::can_message last_sent_message{};
std::array<hal::can_message, 12> m_buffer{};
std::size_t m_cursor = 0;
};
} // namespace

namespace hal {
Expand Down Expand Up @@ -159,5 +195,171 @@ boost::ut::suite<"can_test"> can_test = [] {
expect(that % not fail2.has_value());
expect(that % not fail4.has_value());
};

"hal::can_message_finder"_test = []() {
"hal::can_message_finder::find() two messages"_test = []() {
// Setup
constexpr hal::u32 search_id = 0x115;
test_can_transceiver test_transceiver;
hal::can_message_finder reader(test_transceiver, search_id);
hal::can_message findable_message0{
.id = search_id,
.extended = false,
.remote_request = false,
.length = 3,
.payload = { 0xAB, 0xCD, 0xEF },
};

hal::can_message findable_message1 = {
.id = search_id,
.extended = false,
.remote_request = false,
.length = 2,
.payload = { 0xDE, 0xAD },
};

hal::can_message skipped_message = {
.id = search_id + 5,
.extended = false,
.remote_request = false,
.length = 1,
.payload = { 0xCC },
};

// Ensure
auto const ensure_message = reader.find();
expect(not ensure_message.has_value());

// Exercise
test_transceiver.add_to_received_messages(skipped_message);
test_transceiver.add_to_received_messages(findable_message0);
test_transceiver.add_to_received_messages(skipped_message);
test_transceiver.add_to_received_messages(skipped_message);
test_transceiver.add_to_received_messages(findable_message1);
test_transceiver.add_to_received_messages(findable_message1);
test_transceiver.add_to_received_messages(skipped_message);
test_transceiver.add_to_received_messages(skipped_message);
test_transceiver.add_to_received_messages(skipped_message);
test_transceiver.add_to_received_messages(findable_message0);
test_transceiver.add_to_received_messages(skipped_message);
auto const found_message0 = reader.find();
auto const found_message1 = reader.find();
auto const found_message2 = reader.find();
auto const found_message3 = reader.find();
auto const no_message_found0 = reader.find();
auto const no_message_found1 = reader.find();

test_transceiver.add_to_received_messages(skipped_message);
test_transceiver.add_to_received_messages(skipped_message);
test_transceiver.add_to_received_messages(skipped_message);
test_transceiver.add_to_received_messages(findable_message1);
test_transceiver.add_to_received_messages(skipped_message);

auto const found_message4 = reader.find();

test_transceiver.add_to_received_messages(skipped_message);
test_transceiver.add_to_received_messages(skipped_message);
test_transceiver.add_to_received_messages(skipped_message);
test_transceiver.add_to_received_messages(skipped_message);
test_transceiver.add_to_received_messages(skipped_message);
test_transceiver.add_to_received_messages(skipped_message);
test_transceiver.add_to_received_messages(skipped_message);

auto const no_message_found2 = reader.find();

// Verify
expect(found_message0.has_value());
expect(found_message0.value() == findable_message0);
expect(found_message1.has_value());
expect(found_message1.value() == findable_message1);
expect(found_message2.has_value());
expect(found_message2.value() == findable_message1);
expect(found_message3.has_value());
expect(found_message3.value() == findable_message0);
expect(found_message4.has_value());
expect(found_message4.value() == findable_message1);
expect(not no_message_found0.has_value());
expect(not no_message_found1.has_value());
expect(not no_message_found2.has_value());
};

"hal::can_message_finder::find() overflow cursor"_test = []() {
// Setup
constexpr hal::u32 expected_id = 0x115;
test_can_transceiver test_transceiver;
hal::can_message_finder reader(test_transceiver, expected_id);
hal::can_message desired_message{
.id = expected_id,
.extended = false,
.remote_request = false,
.length = 3,
.payload = { 0xAB, 0xCD, 0xEF },
};

hal::can_message undesired_message = desired_message;
undesired_message.id = expected_id + 1;
// Setup: With 8 messages to fill the buffer, the cursor will land back at
// 0. Meaning that a find will come up with nothing. This is to be
// expected with a circular buffer if the receive cursor gets lapped.

// Ensure
auto const ensure_message0 = reader.find();
expect(not ensure_message0.has_value());
auto const ensure_message1 = reader.find();
expect(not ensure_message1.has_value());

// Exercise
test_transceiver.add_to_received_messages(desired_message);
for (std::size_t i = 0; i < test_transceiver.m_buffer.size(); i++) {
test_transceiver.add_to_received_messages(undesired_message);
}

auto const found_message0 = reader.find();

// Verify
expect(not found_message0.has_value());
};

"hal::can_message_finder::find() std::nullopt from stability failure"_test =
[]() {
// This is empty because we haven't come up with a practical cross
// platform way to verify this check.
};

"hal::can_message_finder::transceiver() should work"_test = []() {
// Setup
test_can_transceiver test_transceiver;
hal::can_message_finder reader(test_transceiver, 0x111);
constexpr hal::can_message expected_message{
.id = 0x111,
.extended = false,
.remote_request = false,
.length = 3,
.payload = { 0xAB, 0xCD, 0xEF },
};
// Setup: With 8 messages to fill the buffer, the cursor will land back at
// 0. Meaning that a find will come up with nothing. This is to be
// expected with a circular buffer if the receive cursor gets lapped.

// Exercise
reader.transceiver().send(expected_message);

// Verify
expect(expected_message == test_transceiver.last_sent_message);
};

"hal::can_message_finder::id()"_test = []() {
// Setup
constexpr auto desired_id = 0x087;
test_can_transceiver test_transceiver;
hal::can_message_finder reader(test_transceiver, desired_id);

// Exercise
auto const actual_id = reader.id();

// Verify
expect(desired_id == actual_id);
};
};
};
} // namespace hal

0 comments on commit 2fe551f

Please sign in to comment.