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

Rewrite TP/ETP and update transport layer example #391

Merged
merged 9 commits into from
Jan 18, 2024
247 changes: 150 additions & 97 deletions examples/transport_layer/main.cpp
Original file line number Diff line number Diff line change
@@ -1,166 +1,219 @@
#include "isobus/hardware_integration/available_can_drivers.hpp"
#include "isobus/hardware_integration/can_hardware_interface.hpp"
#include "isobus/hardware_integration/socket_can_interface.hpp"
#include "isobus/isobus/can_general_parameter_group_numbers.hpp"
#include "isobus/isobus/can_network_configuration.hpp"
#include "isobus/hardware_integration/virtual_can_plugin.hpp"
#include "isobus/isobus/can_network_manager.hpp"
#include "isobus/isobus/can_partnered_control_function.hpp"
#include "isobus/isobus/can_transport_protocol.hpp"

#include <atomic>
#include <csignal>
#include <future>
#include <iostream>
#include <iterator>
#include <memory>
#include <thread>

static constexpr std::uint16_t MAX_TP_SIZE_BYTES = 1785;
static constexpr std::uint32_t ETP_TEST_SIZE = 2048;
static constexpr std::uint32_t PARAMETER_GROUP_NUMBER = 0xEF00; ///< The parameter group number we will use for testing
static constexpr std::uint16_t MAX_TP_MESSAGE_SIZE_BYTES = 1785; ///< The max number of bytes the Transport Protocol can handle
static constexpr std::uint32_t MAX_ETP_MESSAGE_SIZE_BYTES = 117440505; ///< The max number of bytes the Extended Transport Protocol can handle
static constexpr std::uint32_t MAX_MESSAGE_SIZE_BYTES = 1000000; ///< The max number of bytes we will test sending
static std::atomic_bool running = { true };

void signal_handler(int)
{
running = false;
}

using namespace isobus;

// Forward declarations
void check_can_message(const CANMessage &message, void *);
void print_progress_bar(const std::shared_ptr<TransportProtocolSessionBase> session);

// The example sends a series of CAN messages of incrementing length to itself and checks that the data is correct
int main()
{
std::signal(SIGINT, signal_handler);

std::shared_ptr<isobus::CANHardwarePlugin> canDriver = nullptr;
#if defined(ISOBUS_SOCKETCAN_AVAILABLE)
canDriver = std::make_shared<isobus::SocketCANInterface>("can0");
#elif defined(ISOBUS_WINDOWSPCANBASIC_AVAILABLE)
canDriver = std::make_shared<isobus::PCANBasicWindowsPlugin>(PCAN_USBBUS1);
#elif defined(ISOBUS_WINDOWSINNOMAKERUSB2CAN_AVAILABLE)
canDriver = std::make_shared<isobus::InnoMakerUSB2CANWindowsPlugin>(0); // CAN0
#elif defined(ISOBUS_MACCANPCAN_AVAILABLE)
canDriver = std::make_shared<isobus::MacCANPCANPlugin>(PCAN_USBBUS1);
#elif defined(ISOBUS_SYS_TEC_AVAILABLE)
canDriver = std::make_shared<isobus::SysTecWindowsPlugin>();
#ifndef ISOBUS_VIRTUALCAN_AVAILABLE
std::cout << "This example requires the VirtualCAN plugin to be available. If using CMake, set the `-DCAN_DRIVER=VirtualCAN`." << std::endl;
return -1;
#endif
if (nullptr == canDriver)
{
std::cout << "Unable to find a CAN driver. Please make sure you have one of the above drivers installed with the library." << std::endl;
std::cout << "If you want to use a different driver, please add it to the list above." << std::endl;
return -1;
}

isobus::CANHardwareInterface::set_number_of_can_channels(1);
isobus::CANHardwareInterface::assign_can_channel_frame_handler(0, canDriver);
std::shared_ptr<CANHardwarePlugin> originatorDriver = std::make_shared<VirtualCANPlugin>("test-channel");
std::shared_ptr<CANHardwarePlugin> recipientDriver = std::make_shared<VirtualCANPlugin>("test-channel");

CANHardwareInterface::set_number_of_can_channels(2);
CANHardwareInterface::assign_can_channel_frame_handler(0, originatorDriver);
CANHardwareInterface::assign_can_channel_frame_handler(1, recipientDriver);

if ((!isobus::CANHardwareInterface::start()) || (!canDriver->get_is_valid()))
if ((!CANHardwareInterface::start()) || (!originatorDriver->get_is_valid()) || (!recipientDriver->get_is_valid()))
{
std::cout << "Failed to start hardware interface. The CAN driver might be invalid." << std::endl;
return -2;
}

std::this_thread::sleep_for(std::chrono::milliseconds(250));

isobus::NAME TestDeviceNAME(0);

//! Make sure you change these for your device!!!!
TestDeviceNAME.set_arbitrary_address_capable(true);
TestDeviceNAME.set_industry_group(1);
TestDeviceNAME.set_device_class(0);
TestDeviceNAME.set_function_code(static_cast<std::uint8_t>(isobus::NAME::Function::SteeringControl));
TestDeviceNAME.set_identity_number(2);
TestDeviceNAME.set_ecu_instance(0);
TestDeviceNAME.set_function_instance(0);
TestDeviceNAME.set_device_class_instance(0);
TestDeviceNAME.set_manufacturer_code(1407);

const isobus::NAMEFilter filterVirtualTerminal(isobus::NAME::NAMEParameters::FunctionCode, static_cast<std::uint8_t>(isobus::NAME::Function::VirtualTerminal));

auto TestInternalECU = isobus::InternalControlFunction::create(TestDeviceNAME, 0x1C, 0);
auto TestPartner = isobus::PartneredControlFunction::create(0, { filterVirtualTerminal });

// Wait to make sure address claiming is done. The time is arbitrary.
//! @todo Check this instead of asuming it is done
std::this_thread::sleep_for(std::chrono::milliseconds(1250));

// Set up some test CAN messages
std::uint8_t TPTestBuffer[MAX_TP_SIZE_BYTES];
std::uint8_t ETPTestBuffer[ETP_TEST_SIZE];

for (uint16_t i = 0; i < MAX_TP_SIZE_BYTES; i++)
NAME originatorNAME(0);
originatorNAME.set_arbitrary_address_capable(true);
originatorNAME.set_industry_group(1);
originatorNAME.set_device_class(0);
originatorNAME.set_function_code(static_cast<std::uint8_t>(NAME::Function::SteeringControl));
originatorNAME.set_identity_number(2);
originatorNAME.set_ecu_instance(0);
originatorNAME.set_function_instance(0);
originatorNAME.set_device_class_instance(0);
originatorNAME.set_manufacturer_code(1407);

NAME recipientNAME(0);
recipientNAME.set_arbitrary_address_capable(true);
recipientNAME.set_industry_group(1);
recipientNAME.set_device_class(0);
recipientNAME.set_function_code(static_cast<std::uint8_t>(NAME::Function::VirtualTerminal));
recipientNAME.set_identity_number(1);
recipientNAME.set_ecu_instance(0);
recipientNAME.set_function_instance(0);
recipientNAME.set_device_class_instance(0);
recipientNAME.set_manufacturer_code(1407);

const NAMEFilter filterOriginator(NAME::NAMEParameters::FunctionCode, static_cast<std::uint8_t>(NAME::Function::SteeringControl));
const NAMEFilter filterRecipient(NAME::NAMEParameters::FunctionCode, static_cast<std::uint8_t>(NAME::Function::VirtualTerminal));

auto originatorECU = InternalControlFunction::create(originatorNAME, 0x1C, 0);
auto originatorPartner = PartneredControlFunction::create(1, { filterOriginator });
auto recipientECU = InternalControlFunction::create(recipientNAME, 0x1D, 1);
auto recipientPartner = PartneredControlFunction::create(0, { filterRecipient });

// We want to make sure address claiming is successful before continuing
auto addressClaimedFuture = std::async(std::launch::async, [&originatorECU, &recipientECU, &originatorPartner, &recipientPartner]() {
while ((!originatorECU->get_address_valid()) || (!recipientECU->get_address_valid()) ||
(!originatorPartner->get_address_valid()) || (!recipientPartner->get_address_valid()))
{
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}
});
if (addressClaimedFuture.wait_for(std::chrono::seconds(5)) == std::future_status::timeout)
{
TPTestBuffer[i] = (i % 0xFF); // Fill buffer with junk data
std::cout << "Address claiming failed. Please make sure that your internal control function can claim a valid address." << std::endl;
return -3;
}
for (uint32_t i = 0; i < ETP_TEST_SIZE; i++)

// Set up a message buffer for testing
std::vector<std::uint8_t> sendBuffer;
sendBuffer.reserve(MAX_MESSAGE_SIZE_BYTES);

for (uint32_t i = 0; i < MAX_MESSAGE_SIZE_BYTES; i++)
{
ETPTestBuffer[i] = (i % 0xFF); // Fill buffer with junk data
sendBuffer.push_back(i % 0xFF); // Fill buffer with incrementing values
}

// Register a callback for receiving CAN messages
CANNetworkManager::CANNetwork.add_global_parameter_group_number_callback(PARAMETER_GROUP_NUMBER, check_can_message, nullptr);
originatorPartner->add_parameter_group_number_callback(PARAMETER_GROUP_NUMBER, check_can_message, nullptr, recipientECU);

// Send a classic CAN message to a specific destination(8 bytes or less)
if (running && isobus::CANNetworkManager::CANNetwork.send_can_message(0xEF00, ETPTestBuffer, isobus::CAN_DATA_LENGTH, TestInternalECU, TestPartner))
if (running && CANNetworkManager::CANNetwork.send_can_message(PARAMETER_GROUP_NUMBER, sendBuffer.data(), CAN_DATA_LENGTH, originatorECU, recipientPartner))
{
std::cout << "Sent a normal CAN Message with length 8" << std::endl;
std::this_thread::sleep_for(std::chrono::milliseconds(4)); // Arbitrary
}

// Send a classic CAN message to global (0xFF) (8 bytes or less)
if (running && isobus::CANNetworkManager::CANNetwork.send_can_message(0xEF00, ETPTestBuffer, isobus::CAN_DATA_LENGTH, TestInternalECU))
if (running && CANNetworkManager::CANNetwork.send_can_message(PARAMETER_GROUP_NUMBER, sendBuffer.data(), CAN_DATA_LENGTH, originatorECU))
{
std::cout << "Sent a broadcast CAN Message with length 8" << std::endl;
std::this_thread::sleep_for(std::chrono::milliseconds(4)); // Arbitrary
}

// CM Tx Example
// This loop sends all possible TP CM message sizes.
// This will take a long time
for (std::uint32_t i = 9; i <= MAX_TP_SIZE_BYTES; i++)
// Send (Extended) Transport Protocol destination-destination specific messages of exponentially increasing size
// This will take a while to complete
std::uint32_t message_length = 9; // Arbitrary starting point
std::shared_ptr<TransportProtocolSessionBase> session = nullptr;
while (running && (message_length <= MAX_MESSAGE_SIZE_BYTES) && (message_length <= MAX_ETP_MESSAGE_SIZE_BYTES))
{
if (!running)
if (session == nullptr)
{
if (CANNetworkManager::CANNetwork.send_can_message(PARAMETER_GROUP_NUMBER, sendBuffer.data(), message_length, originatorECU, recipientPartner))
{
std::cout << "Sending a Transport Protocol Message with length " << message_length << std::endl;
message_length *= 2;
session = CANNetworkManager::CANNetwork.get_active_transport_protocol_sessions(0).front();
}
}
else
{
break;
print_progress_bar(session);
if (session.use_count() == 1) // We are the only ones holding a reference to the session, so it must be done/failed
{
std::cout << std::endl; // End the progress bar
session = nullptr;
}
}
std::this_thread::sleep_for(std::chrono::milliseconds(4));
}

// Send message
if (isobus::CANNetworkManager::CANNetwork.send_can_message(0xEF00, TPTestBuffer, i, TestInternalECU, TestPartner))
// Send Broadcast Transport Protocol messages (BAM) of exponentially increasing size
// This will take a while to complete
message_length = 11; // Arbitrary starting point
while (running && (message_length <= MAX_MESSAGE_SIZE_BYTES) && (message_length <= MAX_TP_MESSAGE_SIZE_BYTES))
{
if (session == nullptr)
{
std::cout << "Started TP CM Session with length " << i << std::endl;
if (CANNetworkManager::CANNetwork.send_can_message(PARAMETER_GROUP_NUMBER, sendBuffer.data(), message_length, originatorECU))
{
std::cout << "Sending a Broadcast Transport Protocol Message with length " << message_length << std::endl;
message_length *= 2;
session = CANNetworkManager::CANNetwork.get_active_transport_protocol_sessions(0).front();
}
}
else
{
std::cout << "Failed starting TP CM Session with length " << i << std::endl;
print_progress_bar(session);
if (session.use_count() == 1) // We are the only ones holding a reference to the session, so it must be done/failed
{
std::cout << std::endl; // End the progress bar
session = nullptr;
}
}
// Wait for this session to complete before starting the next
// This sleep value is arbitrary
std::this_thread::sleep_for(std::chrono::milliseconds(i * 2));
std::this_thread::sleep_for(std::chrono::milliseconds(4));
}

// BAM Tx Exmaple
// This loop sends all possible BAM message sizes
// This will take a very long time
for (std::uint32_t i = 9; i <= MAX_TP_SIZE_BYTES; i++)
CANHardwareInterface::stop();
return 0;
}

void check_can_message(const CANMessage &message, void *)
{
for (std::uint32_t i = 0; i < message.get_data_length(); i++)
{
if (!running)
if (message.get_data()[i] != (i % 0xFF))
{
break;
std::cerr << std::endl // End the progress bar
<< "Received CAN with incorrect data!!!" << std::endl;
return;
}
}
}

void print_progress_bar(const std::shared_ptr<TransportProtocolSessionBase> session)
{
constexpr std::uint8_t width = 50;
float percentage = session->get_percentage_bytes_transferred();

// Send message
if (isobus::CANNetworkManager::CANNetwork.send_can_message(0xEF00, TPTestBuffer, i, TestInternalECU))
std::cout << "[";
for (std::uint8_t i = 0; i < width; i++)
{
if (i < static_cast<std::uint8_t>(percentage / 100 * width))
{
std::cout << "=";
}
else if (i == static_cast<std::uint8_t>(percentage / 100 * width))
{
std::cout << "Started BAM Session with length " << i << std::endl;
std::cout << ">";
}
else
{
std::cout << "Failed starting BAM Session with length " << i << std::endl;
std::cout << " ";
}
// Wait for this session to complete before starting the next, or it will fail as only 1 BAM session is possible at a time
std::this_thread::sleep_for(std::chrono::milliseconds(2 * (isobus::CANNetworkManager::CANNetwork.get_configuration().get_minimum_time_between_transport_protocol_bam_frames() * ((i + 1) / 7))));
}

// ETP Example
// Send one ETP message
if (running && isobus::CANNetworkManager::CANNetwork.send_can_message(0xEF00, ETPTestBuffer, ETP_TEST_SIZE, TestInternalECU, TestPartner))
{
std::cout << "Started ETP Session with length " << ETP_TEST_SIZE << std::endl;
std::this_thread::sleep_for(std::chrono::milliseconds(2000));
}

isobus::CANHardwareInterface::stop();

return 0;
std::cout << "] " << static_cast<int>(percentage) << "% (" << session->get_total_bytes_transferred() << "/" << session->get_message_length() << " bytes)\r";
std::cout.flush();
}
8 changes: 6 additions & 2 deletions isobus/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ set(ISOBUS_SRC
"can_partnered_control_function.cpp"
"can_NAME_filter.cpp"
"can_transport_protocol.cpp"
"can_transport_protocol_base.cpp"
"can_stack_logger.cpp"
"can_network_configuration.cpp"
"can_callbacks.cpp"
Expand All @@ -40,7 +41,8 @@ set(ISOBUS_SRC
"isobus_maintain_power_interface.cpp"
"isobus_virtual_terminal_objects.cpp"
"nmea2000_message_definitions.cpp"
"nmea2000_message_interface.cpp")
"nmea2000_message_interface.cpp"
"can_message_data.cpp")

# Prepend the source directory path to all the source files
prepend(ISOBUS_SRC ${ISOBUS_SRC_DIR} ${ISOBUS_SRC})
Expand All @@ -58,6 +60,7 @@ set(ISOBUS_INCLUDE
"can_address_claim_state_machine.hpp"
"can_NAME_filter.hpp"
"can_transport_protocol.hpp"
"can_transport_protocol_base.hpp"
"can_stack_logger.hpp"
"can_network_configuration.hpp"
"can_callbacks.hpp"
Expand All @@ -82,7 +85,8 @@ set(ISOBUS_INCLUDE
"isobus_maintain_power_interface.hpp"
"isobus_virtual_terminal_objects.hpp"
"nmea2000_message_definitions.hpp"
"nmea2000_message_interface.hpp")
"nmea2000_message_interface.hpp"
"can_message_data.hpp")
# Prepend the include directory path to all the include files
prepend(ISOBUS_INCLUDE ${ISOBUS_INCLUDE_DIR} ${ISOBUS_INCLUDE})

Expand Down
9 changes: 9 additions & 0 deletions isobus/include/isobus/isobus/can_callbacks.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
#ifndef CAN_CALLBACKS_HPP
#define CAN_CALLBACKS_HPP

#include <functional>
#include "isobus/isobus/can_message.hpp"

namespace isobus
Expand All @@ -36,6 +37,14 @@ namespace isobus

/// @brief A callback for control functions to get CAN messages
using CANLibCallback = void (*)(const CANMessage &message, void *parentPointer);
/// @brief A callback for communicating CAN messages
using CANMessageCallback = std::function<void(const CANMessage &message)>;
/// @brief A callback for communicating CAN message frames
using CANMessageFrameCallback = std::function<bool(std::uint32_t parameterGroupNumber,
CANDataSpan data,
std::shared_ptr<InternalControlFunction> sourceControlFunction,
std::shared_ptr<ControlFunction> destinationControlFunction,
CANIdentifier::CANPriority priority)>; ///< A callback for sending a CAN frame
/// @brief A callback that can inform you when a control function changes state between online and offline
using ControlFunctionStateCallback = void (*)(std::shared_ptr<ControlFunction> controlFunction, ControlFunctionState state);
/// @brief A callback to get chunks of data for transfer by a protocol
Expand Down
Loading
Loading