Skip to content

Commit

Permalink
[TECU]: Add ISOBUS Heartbeat Message
Browse files Browse the repository at this point in the history
Added an interface which manages sending and receiving the ISOBUS heartbeat.
  • Loading branch information
ad3154 committed Feb 28, 2024
1 parent aacebde commit 072aba4
Show file tree
Hide file tree
Showing 9 changed files with 601 additions and 1 deletion.
2 changes: 2 additions & 0 deletions isobus/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ set(ISOBUS_SRC
"isobus_virtual_terminal_objects.cpp"
"isobus_virtual_terminal_client_state_tracker.cpp"
"isobus_virtual_terminal_client_update_helper.cpp"
"isobus_heartbeat.cpp"
"nmea2000_message_definitions.cpp"
"nmea2000_message_interface.cpp"
"can_message_data.cpp")
Expand Down Expand Up @@ -88,6 +89,7 @@ set(ISOBUS_INCLUDE
"isobus_virtual_terminal_objects.hpp"
"isobus_virtual_terminal_client_state_tracker.hpp"
"isobus_virtual_terminal_client_update_helper.hpp"
"isobus_heartbeat.hpp"
"nmea2000_message_definitions.hpp"
"nmea2000_message_interface.hpp"
"isobus_preferred_addresses.hpp"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ namespace isobus
AddressClaim = 0xEE00,
ProprietaryA = 0xEF00,
MachineSelectedSpeed = 0xF022,
HeartbeatMessage = 0xF0E4,
ProductIdentification = 0xFC8D,
ControlFunctionFunctionalities = 0xFC8E,
DiagnosticProtocolIdentification = 0xFD32,
Expand Down
6 changes: 6 additions & 0 deletions isobus/include/isobus/isobus/can_network_manager.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
#include "isobus/isobus/can_message_frame.hpp"
#include "isobus/isobus/can_network_configuration.hpp"
#include "isobus/isobus/can_transport_protocol.hpp"
#include "isobus/isobus/isobus_heartbeat.hpp"
#include "isobus/isobus/nmea2000_fast_packet_protocol.hpp"
#include "isobus/utility/event_dispatcher.hpp"
#include "isobus/utility/thread_synchronization.hpp"
Expand Down Expand Up @@ -191,6 +192,10 @@ namespace isobus
/// @returns The class instance of the NMEA2k fast packet protocol.
std::unique_ptr<FastPacketProtocol> &get_fast_packet_protocol(std::uint8_t canPortIndex);

/// @brief Returns an interface which can be used to manage ISO11783-7 heartbeat messages.
/// @returns ISO11783-7 heartbeat interface
HeartbeatInterface &get_heartbeat_interface();

/// @brief Returns the configuration of this network manager
/// @returns The configuration class for this network manager
CANNetworkConfiguration &get_configuration();
Expand Down Expand Up @@ -379,6 +384,7 @@ namespace isobus
std::array<std::unique_ptr<TransportProtocolManager>, CAN_PORT_MAXIMUM> transportProtocols; ///< One instance of the transport protocol manager for each channel
std::array<std::unique_ptr<ExtendedTransportProtocolManager>, CAN_PORT_MAXIMUM> extendedTransportProtocols; ///< One instance of the extended transport protocol manager for each channel
std::array<std::unique_ptr<FastPacketProtocol>, CAN_PORT_MAXIMUM> fastPacketProtocol; ///< One instance of the fast packet protocol for each channel
HeartbeatInterface heartBeatInterface; ///< Manages ISOBUS heartbeat requests

std::array<std::deque<std::uint32_t>, CAN_PORT_MAXIMUM> busloadMessageBitsHistory; ///< Stores the approximate number of bits processed on each channel over multiple previous time windows
std::array<std::uint32_t, CAN_PORT_MAXIMUM> currentBusloadBitAccumulator; ///< Accumulates the approximate number of bits processed on each channel during the current time window
Expand Down
158 changes: 158 additions & 0 deletions isobus/include/isobus/isobus/isobus_heartbeat.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
//================================================================================================
/// @file isobus_heartbeat.hpp
///
/// @brief Defines an interface for sending and receiving ISOBUS heartbeats.
/// The heartbeat message is used to determine the integrity of the communication of messages and
/// parameters being transmitted by a control function. There may be multiple instances of the
/// heartbeat message on the network, and CFs are required transmit the message on request.
/// As long as the heartbeat message is transmitted at the regular
/// time interval and the sequence number increases through the valid range, then the
/// heartbeat message indicates that the data source CF is operational and provides
/// correct data in all its messages.
///
/// @author Adrian Del Grosso
///
/// @copyright 2024 The Open-Agriculture Developers
//================================================================================================
#ifndef ISOBUS_HEARTBEAT_HPP
#define ISOBUS_HEARTBEAT_HPP

#include "isobus/isobus/can_internal_control_function.hpp"
#include "isobus/isobus/can_message.hpp"
#include "isobus/utility/event_dispatcher.hpp"

#include <list>

namespace isobus
{
/// @brief This class is used to send and receive ISOBUS heartbeats.
class HeartbeatInterface
{
public:
/// @brief This enum is used to define the possible errors that can occur when receiving a heartbeat.
enum class HeartBeatError
{
InvalidSequenceCounter, ///< The sequence counter is not valid
TimedOut ///< The heartbeat message has not been received within the repetition rate
};

/// @brief Default constructor for a HeartbeatInterface
HeartbeatInterface() = default;

/// @brief Initializes the interface. This will be called by the network manager, so there is
/// no need for you to call it in your application.
void initialize();

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

/// @brief This can be used to disable or enable this heartbeat functionality.
/// It's probably best to leave it enabled for most applications, but it's not
/// strictly needed.
/// @note The interface is enabled by default.
/// @param[in] enable Set to true to enable the interface, or false to disable it.
void set_enabled(bool enable);

/// @brief Returns if the interface is currently enabled or not.
/// @note The interface is enabled by default.
/// @returns true if the interface is enabled, false if the interface is disabled
bool get_enabled() const;

/// @brief This method can be used to request that another control function on the bus
/// start sending the heartbeat message. This does not mean the request will be honored.
/// In order to know if your request was accepted, you will need to either
/// register for timeout events, register for heartbeat events, or check to see if your
/// destination control function ever responded at some later time using the various methods
/// available to you on this class' public interface.
/// @note CFs may take up to 250ms to begin sending the heartbeat.
/// @param[in] sourceControlFunction The internal control function to use when sending the request
/// @param[in] destinationControlFunction The destination for the request
/// @returns true if the request was transmitted, otherwise false.
bool request_heartbeat(std::shared_ptr<InternalControlFunction> sourceControlFunction,
std::shared_ptr<ControlFunction> destinationControlFunction);

/// @brief Called by the internal control function class when a new internal control function is added.
/// This allows us to respond to requests for heartbeats from other control functions.
/// @param[in] newControlFunction The new internal control function
void on_new_internal_control_function(std::shared_ptr<InternalControlFunction> newControlFunction);

/// @brief Returns an event dispatcher which can be used to register for heartbeat errors.
/// Heartbeat errors are generated when a heartbeat message is not received within the
/// repetition rate, or when the sequence counter is not valid.
/// The control function that generated the error is passed as an argument to the event.
/// @returns An event dispatcher for heartbeat errors
EventDispatcher<HeartBeatError, std::shared_ptr<ControlFunction>> &GetHeartbeatErrorEventDispatcher();

/// @brief Returns an event dispatcher which can be used to register for new tracked heartbeat events.
/// An event will be generated when a new control function is added to the list of CFs sending heartbeats.
/// The control function that generated the error is passed as an argument to the event.
/// @returns An event dispatcher for new tracked heartbeat events
EventDispatcher<std::shared_ptr<ControlFunction>> &GetNewTrackedHeartbeatEventDispatcher();

/// @brief Updates the interface. Called by the network manager,
/// so there is no need for you to call it in your application.
void update();

private:
/// @brief This enum is used to define special values for the sequence counter.
enum class SequenceCounterSpecialValue : std::uint8_t
{
Initial = 251, ///< The heartbeat sequence number value shall be set to 251 once upon initialization of a CF.
Error = 254, ///< Sequence Number value 254 indicates an error condition.
NotAvailable = 255 ///< This value shall be used when the transmitted CF is in a shutdown status and is gracefully disconnecting from the network.
};

static constexpr std::uint32_t SEQUENCE_TIMEOUT_MS = 300; ///< If the repetition rate exceeds 300 ms an error in the communication is detected.
static constexpr std::uint32_t SEQUENCE_INITIAL_RESPONSE_TIMEOUT_MS = 250; ///< When requesting a heartbeat from another device, If no response for the repetition rate has been received after 250 ms, the requester shall assume that the request was not accepted
static constexpr std::uint32_t SEQUENCE_REPETITION_RATE_MS = 100; ///< A consuming CF shall send a Request for Repetition rate for the heart beat message with a repetition rate of 100 ms

/// @brief This class is used to store information about a tracked heartbeat.
class Heartbeat
{
public:
/// @brief Constructor for a Heartbeat
/// @param[in] sendingControlFunction The control function that is sending the heartbeat
Heartbeat(std::shared_ptr<ControlFunction> sendingControlFunction);

/// @brief Transmits a heartbeat message (for internal control functions only).
/// Updates the sequence counter and timestamp if needed.
/// @returns True if the message is sent, otherwise false.
bool send();

std::shared_ptr<ControlFunction> controlFunction; ///< The CF that is sending the message
std::uint32_t timestamp_ms; ///< The last time the message was sent by the associated control function
std::uint32_t repetitionRate_ms = SEQUENCE_REPETITION_RATE_MS; ///< For internal control functions, this controls how often the heartbeat is sent. This should really stay at the standard 100ms defined in ISO11783-7.
std::uint8_t sequenceCounter = static_cast<std::uint8_t>(SequenceCounterSpecialValue::Initial); ///< The sequence counter used to validate the heartbeat. Counts from 0-250 normally.
};

/// @brief Processes a CAN message
/// @param[in] message The CAN message being received
/// @param[in] parentPointer A context variable to find the relevant instance of this class
static void process_rx_message(const CANMessage &message, void *parentPointer);

/// @brief Processes a PGN request for a heartbeat.
/// @param[in] parameterGroupNumber The PGN being requested
/// @param[in] requestingControlFunction The control function that is requesting the heartbeat
/// @param[in] targetControlFunction The control function that is being requested to send the heartbeat
/// @param[in] repetitionRate The repetition rate for the heartbeat
/// @param[in] parentPointer A context variable to find the relevant instance of this class
/// @returns True if the request was transmitted, otherwise false.
static bool process_request_for_heartbeat(std::uint32_t parameterGroupNumber,
std::shared_ptr<ControlFunction> requestingControlFunction,
std::shared_ptr<ControlFunction> targetControlFunction,
std::uint32_t repetitionRate,
void *parentPointer);

/// @brief Cleans up and shuts down the interface.
void disable();

EventDispatcher<HeartBeatError, std::shared_ptr<ControlFunction>> heartbeatErrorEventDispatcher; ///< Event dispatcher for heartbeat errors
EventDispatcher<std::shared_ptr<ControlFunction>> newTrackedHeartbeatEventDispatcher; ///< Event dispatcher for heartbeat errors
std::list<Heartbeat> trackedHeartbeats; ///< Store tracked heartbeat data, per CF
bool enabled = true; ///< Attribute that specifies if this interface is enabled. When false, the interface does nothing.
bool initialized = false; ///< Attribute that tracks if the interface has been initialized yet
};
} // namespace isobus

#endif
10 changes: 10 additions & 0 deletions isobus/src/can_network_manager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ namespace isobus
{
get_next_can_message_from_tx_queue();
}
heartBeatInterface.initialize();
initialized = true;
}

Expand Down Expand Up @@ -204,6 +205,9 @@ namespace isobus
update_new_partners();

process_rx_messages();

heartBeatInterface.update();

process_tx_messages();

update_internal_cfs();
Expand Down Expand Up @@ -358,6 +362,7 @@ namespace isobus
void CANNetworkManager::on_control_function_created(std::shared_ptr<ControlFunction> controlFunction, CANLibBadge<InternalControlFunction>)
{
on_control_function_created(controlFunction);
heartBeatInterface.on_new_internal_control_function(std::static_pointer_cast<InternalControlFunction>(controlFunction));
}

void CANNetworkManager::on_control_function_created(std::shared_ptr<ControlFunction> controlFunction, CANLibBadge<PartneredControlFunction>)
Expand Down Expand Up @@ -435,6 +440,11 @@ namespace isobus
return fastPacketProtocol[canPortIndex];
}

HeartbeatInterface &CANNetworkManager::get_heartbeat_interface()
{
return heartBeatInterface;
}

CANNetworkConfiguration &CANNetworkManager::get_configuration()
{
return configuration;
Expand Down
Loading

0 comments on commit 072aba4

Please sign in to comment.