Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

✨ Add can_message_finder #52

Merged
merged 1 commit into from
Jan 3, 2025
Merged
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
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
Loading