Skip to content

Commit

Permalink
feat: add middleman for message handling (only implemented for ISB)
Browse files Browse the repository at this point in the history
  • Loading branch information
GwnDaan committed Jun 4, 2024
1 parent 1d239eb commit ce31ac5
Show file tree
Hide file tree
Showing 8 changed files with 314 additions and 91 deletions.
6 changes: 4 additions & 2 deletions isobus/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,8 @@ set(ISOBUS_SRC
"nmea2000_message_definitions.cpp"
"nmea2000_message_interface.cpp"
"isobus_device_descriptor_object_pool_helpers.cpp"
"can_message_data.cpp")
"can_message_data.cpp"
"can_message_handling.cpp")

# Prepend the source directory path to all the source files
prepend(ISOBUS_SRC ${ISOBUS_SRC_DIR} ${ISOBUS_SRC})
Expand Down Expand Up @@ -96,7 +97,8 @@ set(ISOBUS_INCLUDE
"nmea2000_message_interface.hpp"
"isobus_preferred_addresses.hpp"
"isobus_device_descriptor_object_pool_helpers.hpp"
"can_message_data.hpp")
"can_message_data.hpp"
"can_message_handling.hpp")
# Prepend the include directory path to all the include files
prepend(ISOBUS_INCLUDE ${ISOBUS_INCLUDE_DIR} ${ISOBUS_INCLUDE})

Expand Down
143 changes: 143 additions & 0 deletions isobus/include/isobus/isobus/can_message_handling.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
//================================================================================================
/// @file can_message_handling.hpp
///
/// @brief Defines an interface for interacting with incoming and outgoing CAN messages. This is
/// used to abstract the CAN messaging layer from the rest of the application. This allows for
/// easy testing and swapping out of the CAN messaging layer. Furthermore, it ensures that the
/// implementing class is not intertwined with the CAN messaging layer.
///
/// @details The interfaces are more generic than raw CAN messaging, and is designed to be used
/// with J1939 and ISOBUS protocols.
///
///
/// @author Daan Steenbergen
///
/// @copyright 2023 The Open-Agriculture Developers
//================================================================================================
#ifndef CAN_MESSAGE_HANDLER_HPP
#define CAN_MESSAGE_HANDLER_HPP

#include "isobus/isobus/can_callbacks.hpp"
#include "isobus/isobus/can_internal_control_function.hpp"
#include "isobus/isobus/can_message.hpp"

namespace isobus
{
/// @brief An interface that provides a way to send CAN messages to the bus.
/// @note This is a pure virtual interface, and must be implemented by a concrete class.
class CANMessagingProvider
{
public:
/// @brief Default destructor
virtual ~CANMessagingProvider() = default;

/// @brief This is the main way to send a CAN message of any length.
/// @details This function will automatically choose an appropriate transport protocol if needed.
/// If you don't specify a destination (or use nullptr) you message will be sent as a broadcast
/// if it is valid to do so.
/// You can also get a callback on success or failure of the transmit.
/// @param[in] parameterGroupNumber The PGN to use when sending the message
/// @param[in] dataBuffer A pointer to the data buffer to send from
/// @param[in] dataLength The size of the message to send
/// @param[in] sourceControlFunction The control function that is sending the message
/// @param[in] destinationControlFunction The control function that the message is destined for or nullptr if broadcast
/// @param[in] priority The CAN priority of the message being sent
/// @param[in] txCompleteCallback A callback to be called when the message is sent or fails to send
/// @param[in] parentPointer A generic context variable that helps identify what object the callback is destined for
/// @param[in] frameChunkCallback A callback which can be supplied to have the tack call you back to get chunks of the message as they are sent
/// @returns `true` if the message was sent, otherwise `false`
virtual bool send_can_message(std::uint32_t parameterGroupNumber,
const std::uint8_t *dataBuffer,
std::uint32_t dataLength,
std::shared_ptr<InternalControlFunction> sourceControlFunction,
std::shared_ptr<ControlFunction> destinationControlFunction = nullptr,
CANIdentifier::CANPriority priority = CANIdentifier::CANPriority::PriorityDefault6,
TransmitCompleteCallback txCompleteCallback = nullptr,
void *parentPointer = nullptr,
DataChunkCallback frameChunkCallback = nullptr) = 0;
};

/// @brief A class that provides a way to interact with incoming and outgoing CAN messages.
/// @details This should be extended by a class that wants to interact with incoming and outgoing
/// CAN messages. It provides a way to process incoming and outgoing messages, and send messages
/// to the bus.
class CANMessagingConsumer
{
public:
/// @brief Default destructor
virtual ~CANMessagingConsumer() = default;

/// @brief Processes incoming CAN messages
/// @param message The incoming CAN message to process
virtual void process_rx_message(const CANMessage &)
{
// Override this function in the derived class, if you want to process incoming messages
}

/// @brief Processes outgoing CAN messages
/// @param message The outgoing CAN message to process
virtual void process_tx_message(const CANMessage &)
{
// Override this function in the derived class, if you want to process outgoing messages
}

protected:
/// @brief This is the main way to send a CAN message of any length.
/// @details This function will automatically choose an appropriate transport protocol if needed.
/// If you don't specify a destination (or use nullptr) you message will be sent as a broadcast
/// if it is valid to do so.
/// You can also get a callback on success or failure of the transmit.
/// @param[in] parameterGroupNumber The PGN to use when sending the message
/// @param[in] dataBuffer A pointer to the data buffer to send from
/// @param[in] dataLength The size of the message to send
/// @param[in] sourceControlFunction The control function that is sending the message
/// @param[in] destinationControlFunction The control function that the message is destined for or nullptr if broadcast
/// @param[in] priority The CAN priority of the message being sent
/// @param[in] txCompleteCallback A callback to be called when the message is sent or fails to send
/// @param[in] parentPointer A generic context variable that helps identify what object the callback is destined for
/// @param[in] frameChunkCallback A callback which can be supplied to have the tack call you back to get chunks of the message as they are sent
/// @returns `true` if the message was sent, otherwise `false`
bool send_can_message(std::uint32_t parameterGroupNumber,
const std::uint8_t *dataBuffer,
std::uint32_t dataLength,
std::shared_ptr<InternalControlFunction> sourceControlFunction,
std::shared_ptr<ControlFunction> destinationControlFunction = nullptr,
CANIdentifier::CANPriority priority = CANIdentifier::CANPriority::PriorityDefault6,
TransmitCompleteCallback txCompleteCallback = nullptr,
void *parentPointer = nullptr,
DataChunkCallback frameChunkCallback = nullptr) const;

friend class CANMessageHandler; ///< Allow the CANMessageHandler to modify the messaging provider
std::shared_ptr<CANMessagingProvider> messagingProvider; ///< The messaging provider to use for sending messages
};

/// @brief A class for managing the routing of incoming and outgoing CAN messages
class CANMessageHandler : public CANMessagingConsumer
{
public:
/// @brief Processes incoming CAN messages
/// @param message The incoming CAN message to process
void process_rx_message(const CANMessage &message) override;

/// @brief Processes outgoing CAN messages
/// @param message The outgoing CAN message to process
void process_tx_message(const CANMessage &message) override;

/// @brief Adds a consumer to the list of consumers
/// @param consumer The consumer to add
void add_consumer(std::shared_ptr<CANMessagingConsumer> consumer);

/// @brief Removes a consumer from the list of consumers
/// @param consumer The consumer to remove
void remove_consumer(std::shared_ptr<CANMessagingConsumer> consumer);

/// @brief Sets the messaging provider to use for sending messages
/// @param provider The messaging provider to use for sending messages
void set_messaging_provider(std::shared_ptr<CANMessagingProvider> provider);

private:
std::vector<std::weak_ptr<CANMessagingConsumer>> consumers; ///< The list of consumers to route messages to
};
} // namespace isobus

#endif // CAN_MESSAGE_HANDLER_HPP
10 changes: 8 additions & 2 deletions isobus/include/isobus/isobus/can_network_manager.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
#include "isobus/isobus/can_internal_control_function.hpp"
#include "isobus/isobus/can_message.hpp"
#include "isobus/isobus/can_message_frame.hpp"
#include "isobus/isobus/can_message_handling.hpp"
#include "isobus/isobus/can_network_configuration.hpp"
#include "isobus/isobus/can_partnered_control_function.hpp"
#include "isobus/isobus/can_transport_protocol.hpp"
Expand All @@ -45,7 +46,7 @@ namespace isobus
/// @brief The main CAN network manager object, handles protocol management and updating other
/// stack components. Provides an interface for sending CAN messages.
//================================================================================================
class CANNetworkManager
class CANNetworkManager : public CANMessagingProvider
{
public:
static CANNetworkManager CANNetwork; ///< Static singleton of the one network manager. Use this to access stack functionality.
Expand Down Expand Up @@ -152,7 +153,7 @@ namespace isobus
CANIdentifier::CANPriority priority = CANIdentifier::CANPriority::PriorityDefault6,
TransmitCompleteCallback txCompleteCallback = nullptr,
void *parentPointer = nullptr,
DataChunkCallback frameChunkCallback = nullptr);
DataChunkCallback frameChunkCallback = nullptr) override;

/// @brief The main update function for the network manager. Updates all protocols.
void update();
Expand Down Expand Up @@ -213,6 +214,10 @@ namespace isobus
/// @returns An event dispatcher which can be used to get notified about address violations
EventDispatcher<std::shared_ptr<InternalControlFunction>> &get_address_violation_event_dispatcher();

/// @brief Returns the message handler to subscribe to incoming and outgoing messages from the network manager
/// @returns The message handler
CANMessageHandler &get_can_message_handler();

protected:
// Using protected region to allow protocols use of special functions from the network manager
friend class InternalControlFunction; ///< Allows the network manager to work closely with the address claiming process
Expand Down Expand Up @@ -407,6 +412,7 @@ namespace isobus
std::list<ControlFunctionStateCallback> controlFunctionStateCallbacks; ///< List of all control function state callbacks
std::vector<ParameterGroupNumberCallbackData> globalParameterGroupNumberCallbacks; ///< A list of all global PGN callbacks
std::vector<ParameterGroupNumberCallbackData> anyControlFunctionParameterGroupNumberCallbacks; ///< A list of all global PGN callbacks
CANMessageHandler messageHandler; ///< The message handler driven by the network manager
EventDispatcher<CANMessage> messageTransmittedEventDispatcher; ///< An event dispatcher for notifying consumers about transmitted messages by our application
EventDispatcher<std::shared_ptr<InternalControlFunction>> addressViolationEventDispatcher; ///< An event dispatcher for notifying consumers about address violations
Mutex receivedMessageQueueMutex; ///< A mutex for receive messages thread safety
Expand Down
26 changes: 5 additions & 21 deletions isobus/include/isobus/isobus/isobus_shortcut_button_interface.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@

#include "isobus/isobus/can_NAME.hpp"
#include "isobus/isobus/can_internal_control_function.hpp"
#include "isobus/isobus/can_message.hpp"
#include "isobus/isobus/can_message_handling.hpp"
#include "isobus/utility/event_dispatcher.hpp"
#include "isobus/utility/processing_flags.hpp"

Expand Down Expand Up @@ -60,7 +60,7 @@ namespace isobus
/// The Working Set shall monitor the number of transitions for each ISB server upon receiving first
/// the message from a given ISB server. A Working Set shall consider an increase in the transitions
/// without detecting a corresponding transition of the Stop all implement operations state as an error and react accordingly.
class ShortcutButtonInterface
class ShortcutButtonInterface : public CANMessagingConsumer
{
public:
/// @brief Enumerates the states that can be sent in the main ISB message (pgn 64770, 0xFD02)
Expand All @@ -77,16 +77,6 @@ namespace isobus
/// @param[in] serverEnabled Enables the interface's transmission of the "Stop all implement operations" message.
ShortcutButtonInterface(std::shared_ptr<InternalControlFunction> internalControlFunction, bool serverEnabled = false);

/// @brief Destructor for a ShortcutButtonInterface
virtual ~ShortcutButtonInterface();

/// @brief Used to initialize the interface. Registers for PGNs with the network manager.
void initialize();

/// @brief Returns if the interface has been initialized
/// @returns true if the interface has been initialized
bool get_is_initialized() const;

/// @brief Gets the event dispatcher for when the assigned bus' ISB state changes.
/// The assigned bus is determined by which internal control function you pass into the constructor.
/// @returns The event dispatcher which can be used to register callbacks/listeners to
Expand Down Expand Up @@ -128,19 +118,14 @@ namespace isobus
NumberOfFlags ///< The number of flags defined in this enumeration
};

/// @brief Parses incoming CAN messages for the interface
/// @param message The CAN message to parse
/// @param parentPointer A generic context variable, usually the `this` pointer for this interface instance
static void process_rx_message(const CANMessage &message, void *parentPointer);

/// @brief Processes the internal Tx flags
/// @param[in] flag The flag to process
/// @param[in] parent A context variable to find the relevant interface class
static void process_flags(std::uint32_t flag, void *parent);

/// @brief A generic way for a protocol to process a received message
/// @param[in] message A received CAN message
void process_message(const CANMessage &message);
/// @brief Processes incoming CAN messages
/// @param message The incoming CAN message to process
void process_rx_message(const CANMessage &message) override;

/// @brief Sends the Stop all implement operations switch state message
/// @returns true if the message was sent, otherwise false
Expand All @@ -157,7 +142,6 @@ namespace isobus
std::uint8_t stopAllImplementOperationsTransitionNumber = 0; ///< A counter used to track our transitions from "stop" to "permit" when acting as a server
StopAllImplementOperationsState commandedState = StopAllImplementOperationsState::NotAvailable; ///< The state set by the user to transmit if we're acting as a server
bool actAsISBServer = false; ///< A setting that enables sending the ISB messages rather than just receiving them
bool initialized = false; ///< Stores if the interface has been initialized
};
} // namespace isobus
#endif // ISOBUS_SHORTCUT_BUTTON_INTERFACE_HPP
110 changes: 110 additions & 0 deletions isobus/src/can_message_handling.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
/// @file can_message_handling.hpp
///
/// @brief Defines interfaces for interacting with incoming and outgoing CAN messages.
///
/// @author Daan Steenbergen
///
/// @copyright 2023 The Open-Agriculture Developers
//================================================================================================
#include "isobus/isobus/can_message_handling.hpp"

#include <algorithm>

namespace isobus
{
bool CANMessagingConsumer::send_can_message(std::uint32_t parameterGroupNumber,
const std::uint8_t *dataBuffer,
std::uint32_t dataLength,
std::shared_ptr<InternalControlFunction> sourceControlFunction,
std::shared_ptr<ControlFunction> destinationControlFunction,
CANIdentifier::CANPriority priority,
TransmitCompleteCallback txCompleteCallback,
void *parentPointer,
DataChunkCallback frameChunkCallback) const
{
if (nullptr != messagingProvider)
{
return messagingProvider->send_can_message(parameterGroupNumber,
dataBuffer,
dataLength,
sourceControlFunction,
destinationControlFunction,
priority,
txCompleteCallback,
parentPointer,
frameChunkCallback);
}
return false;
}

void CANMessageHandler::process_rx_message(const CANMessage &message)
{
for (auto it = consumers.begin(); it != consumers.end();)
{
const auto consumer = (*it).lock();
if (nullptr != consumer)
{
consumer->process_rx_message(message);
++it;
}
else
{
it = consumers.erase(it);
}
}
}

void CANMessageHandler::process_tx_message(const CANMessage &message)
{
for (auto it = consumers.begin(); it != consumers.end();)
{
const auto consumer = (*it).lock();
if (nullptr != consumer)
{
consumer->process_tx_message(message);
++it;
}
else
{
it = consumers.erase(it);
}
}
}

void CANMessageHandler::add_consumer(std::shared_ptr<CANMessagingConsumer> consumer)
{
// Ensure the consumer is not already in the list
remove_consumer(consumer);
consumer->messagingProvider = messagingProvider;
consumers.push_back(consumer);
}

void CANMessageHandler::remove_consumer(std::shared_ptr<CANMessagingConsumer> consumer)
{
for (auto it = consumers.begin(); it != consumers.end();)
{
const auto consumerPtr = (*it).lock();
if (consumerPtr == consumer)
{
it = consumers.erase(it);
}
else
{
++it;
}
}
}

void CANMessageHandler::set_messaging_provider(std::shared_ptr<CANMessagingProvider> provider)
{
messagingProvider = provider;
for (const auto &consumer : consumers)
{
const auto consumerPtr = consumer.lock();
if (nullptr != consumerPtr)
{
consumerPtr->messagingProvider = provider;
}
}
}
}; // namespace isobus
Loading

0 comments on commit ce31ac5

Please sign in to comment.