Skip to content

Commit

Permalink
[Core]: Added a way to get callbacks when any CF goes online or offline
Browse files Browse the repository at this point in the history
This addresses issue #210 and provides a way to get callbacks from
the network manager when a control function changes state.
Overall this reduces the need to poll the state of your partners in a
consuming application.
  • Loading branch information
ad3154 committed Aug 27, 2023
1 parent 4671ec2 commit c8156fa
Show file tree
Hide file tree
Showing 4 changed files with 108 additions and 7 deletions.
13 changes: 11 additions & 2 deletions isobus/include/isobus/isobus/can_callbacks.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ namespace isobus
class InternalControlFunction;
class ControlFunction;

/// @brief The types of acknowldegement that can be sent in the Ack PGN
/// @brief The types of acknowledgement that can be sent in the Ack PGN
enum class AcknowledgementType : std::uint8_t
{
Positive = 0, ///< "ACK" Indicates that the request was completed
Expand All @@ -27,8 +27,17 @@ namespace isobus
CannotRespond = 3 ///< Signals to the requestor that we are unable to accept the request for some reason
};

/// @brief Enumerates the "online" states of a control function.
enum class ControlFunctionState
{
Offline, ///< The CF's address claim state is not valid
Online ///< The CF's address claim state is valid
};

/// @brief A callback for control functions to get CAN messages
typedef void (*CANLibCallback)(const CANMessage &message, void *parentPointer);
using CANLibCallback = void (*)(const CANMessage &message, void *parentPointer);
/// @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
using DataChunkCallback = bool (*)(std::uint32_t callbackIndex,
std::uint32_t bytesOffset,
Expand Down
29 changes: 24 additions & 5 deletions isobus/include/isobus/isobus/can_network_manager.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,17 @@ namespace isobus
/// @param[in] controlFunction The control function that was created
void on_control_function_created(std::shared_ptr<ControlFunction> controlFunction, CANLibBadge<ControlFunction>);

/// @brief Use this to get a callback when a control function goes online or offline.
/// This could be useful if you want event driven notifications for when your partners are disconnected from the bus.
/// @param[in] controlFunction The control function you want callbacks for
/// @param[in] callback The callback you want to be called when the specified control function changes state
void add_control_function_status_change_callback(std::shared_ptr<ControlFunction> controlFunction, ControlFunctionStateCallback callback);

/// @brief Used to remove callbacks added with add_control_function_status_change_callback
/// @param[in] controlFunction The control function you want to stop receiving callbacks for
/// @param[in] callback The callback you want to remove
void remove_control_function_status_change_callback(std::shared_ptr<ControlFunction> controlFunction, ControlFunctionStateCallback callback);

/// @brief Gets all the internal control functions that are currently registered in the network manager
/// @returns A list of all the internal control functions
const std::list<std::shared_ptr<InternalControlFunction>> &get_internal_control_functions() const;
Expand Down Expand Up @@ -181,7 +192,7 @@ namespace isobus
/// @param[in] parameterGroupNumber The PGN to use when sending the message
/// @param[in] priority The CAN priority of the message being sent
/// @param[in] data A pointer to the data buffer to send from
/// @param[in] size The size of the messgage to send
/// @param[in] size The size of the message to send
/// @returns `true` if the message was sent, otherwise `false`
bool send_can_message_raw(std::uint32_t portIndex,
std::uint8_t sourceAddress,
Expand Down Expand Up @@ -231,8 +242,8 @@ namespace isobus
/// @param[in] parameterGroupNumber The PGN to use when sending the message
/// @param[in] priority The CAN priority of the message being sent
/// @param[in] data A pointer to the data buffer to send from
/// @param[in] size The size of the messgage to send
/// @returns The constucted frame based on the inputs
/// @param[in] size The size of the message to send
/// @returns The constructed frame based on the inputs
CANMessageFrame construct_frame(std::uint32_t portIndex,
std::uint8_t sourceAddress,
std::uint8_t destAddress,
Expand Down Expand Up @@ -260,6 +271,12 @@ namespace isobus
/// @param[in] currentMessage The message to process
void process_any_control_function_pgn_callbacks(const CANMessage &currentMessage);

/// @brief Checks the control function state callback list to see if we need to call
/// a control function state callback.
/// @param[in] controlFunction The controlFunction whose state has changed
/// @param[in] state The new state of the control function
void process_control_function_state_change_callback(std::shared_ptr<ControlFunction> controlFunction, ControlFunctionState state);

/// @brief Processes a can message for callbacks added with add_protocol_parameter_group_number_callback
/// @param[in] currentMessage The message to process
void process_protocol_pgn_callbacks(const CANMessage &currentMessage);
Expand Down Expand Up @@ -289,7 +306,7 @@ namespace isobus
/// @param[in] parameterGroupNumber The PGN to use when sending the message
/// @param[in] priority The CAN priority of the message being sent
/// @param[in] data A pointer to the data buffer to send from
/// @param[in] size The size of the messgage to send
/// @param[in] size The size of the message to send
/// @returns `true` if the message was sent, otherwise `false`
bool send_can_message_raw(std::uint32_t portIndex,
std::uint8_t sourceAddress,
Expand All @@ -313,7 +330,7 @@ namespace isobus

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
std::array<std::uint32_t, CAN_PORT_MAXIMUM> lastAddressClaimRequestTimestamp_ms; ///< Stores timestamps for when the last request for the address claim PGN was recieved. Used to prune stale CFs.
std::array<std::uint32_t, CAN_PORT_MAXIMUM> lastAddressClaimRequestTimestamp_ms; ///< Stores timestamps for when the last request for the address claim PGN was received. Used to prune stale CFs.

std::array<std::array<std::shared_ptr<ControlFunction>, NULL_CAN_ADDRESS>, CAN_PORT_MAXIMUM> controlFunctionTable; ///< Table to maintain address to NAME mappings
std::list<std::shared_ptr<ControlFunction>> inactiveControlFunctions; ///< A list of the control function that currently don't have a valid address
Expand All @@ -322,12 +339,14 @@ namespace isobus

std::list<ParameterGroupNumberCallbackData> protocolPGNCallbacks; ///< A list of PGN callback registered by CAN protocols
std::list<CANMessage> receiveMessageList; ///< A queue of Rx messages to process
std::list<std::pair<std::shared_ptr<ControlFunction>, 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
std::mutex receiveMessageMutex; ///< A mutex for receive messages thread safety
std::mutex protocolPGNCallbacksMutex; ///< A mutex for PGN callback thread safety
std::mutex anyControlFunctionCallbacksMutex; ///< Mutex to protect the "any CF" callbacks
std::mutex busloadUpdateMutex; ///< A mutex that protects the busload metrics since we calculate it on our own thread
std::mutex controlFunctionStatusCallbacksMutex; ///< A Mutex that protects access to the control function status callback list
std::uint32_t busloadUpdateTimestamp_ms = 0; ///< Tracks a time window for determining approximate busload
std::uint32_t updateTimestamp_ms = 0; ///< Keeps track of the last time the CAN stack was update in milliseconds
bool initialized = false; ///< True if the network manager has been initialized by the update function
Expand Down
47 changes: 47 additions & 0 deletions isobus/src/can_network_manager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -331,6 +331,32 @@ namespace isobus
}
}

void CANNetworkManager::add_control_function_status_change_callback(std::shared_ptr<ControlFunction> controlFunction, ControlFunctionStateCallback callback)
{
if ((nullptr != controlFunction) &&
(nullptr != callback))
{
const std::lock_guard<std::mutex> lock(controlFunctionStatusCallbacksMutex);
controlFunctionStateCallbacks.emplace_back(controlFunction, callback);
}
}

void CANNetworkManager::remove_control_function_status_change_callback(std::shared_ptr<ControlFunction> controlFunction, ControlFunctionStateCallback callback)
{
if ((nullptr != controlFunction) &&
(nullptr != callback))
{
const std::lock_guard<std::mutex> lock(controlFunctionStatusCallbacksMutex);
std::pair<std::shared_ptr<ControlFunction>, ControlFunctionStateCallback> targetCallback(controlFunction, callback);
auto callbackLocation = std::find(controlFunctionStateCallbacks.begin(), controlFunctionStateCallbacks.end(), targetCallback);

if (controlFunctionStateCallbacks.end() != callbackLocation)
{
controlFunctionStateCallbacks.erase(callbackLocation);
}
}
}

const std::list<std::shared_ptr<InternalControlFunction>> &CANNetworkManager::get_internal_control_functions() const
{
return internalControlFunctions;
Expand Down Expand Up @@ -431,6 +457,7 @@ namespace isobus
currentControlFunction->get_NAME().get_full_name(),
claimedAddress,
channelIndex);
process_control_function_state_change_callback(currentControlFunction, ControlFunctionState::Online);
break;
}
}
Expand Down Expand Up @@ -622,6 +649,7 @@ namespace isobus
foundControlFunction->get_NAME().get_full_name(),
claimedAddress,
foundControlFunction->get_can_port());
process_control_function_state_change_callback(foundControlFunction, ControlFunctionState::Online);
}
foundControlFunction->address = claimedAddress;
}
Expand Down Expand Up @@ -663,6 +691,7 @@ namespace isobus
partner->controlFunctionNAME = currentActiveControlFunction->get_NAME();
partner->initialized = true;
controlFunctionTable[partner->get_can_port()][partner->address] = std::shared_ptr<ControlFunction>(partner);
process_control_function_state_change_callback(partner, ControlFunctionState::Online);
break;
}
}
Expand Down Expand Up @@ -770,6 +799,18 @@ namespace isobus
}
}

void CANNetworkManager::process_control_function_state_change_callback(std::shared_ptr<ControlFunction> controlFunction, ControlFunctionState state)
{
const std::lock_guard<std::mutex> lock(controlFunctionStatusCallbacksMutex);
for (const auto &callback : controlFunctionStateCallbacks)
{
if (callback.first == controlFunction)
{
callback.second(callback.first, state);
}
}
}

void CANNetworkManager::process_protocol_pgn_callbacks(const CANMessage &currentMessage)
{
const std::lock_guard<std::mutex> lock(protocolPGNCallbacksMutex);
Expand Down Expand Up @@ -883,6 +924,12 @@ namespace isobus
CANStackLogger::info("[NM]: Control function with address %u and NAME %016llx is now offline on channel %u.", controlFunction->get_address(), controlFunction->get_NAME(), channelIndex);
controlFunctionTable[channelIndex][i] = nullptr;
controlFunction->address = NULL_CAN_ADDRESS;
process_control_function_state_change_callback(controlFunction, ControlFunctionState::Offline);
}
else if ((nullptr != controlFunction) &&
(!controlFunction->claimedAddressSinceLastAddressClaimRequest))
{
process_control_function_state_change_callback(controlFunction, ControlFunctionState::Offline);
}
}
lastAddressClaimRequestTimestamp_ms.at(channelIndex) = 0;
Expand Down
26 changes: 26 additions & 0 deletions test/core_network_management_tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,16 @@

using namespace isobus;

static std::shared_ptr<ControlFunction> testControlFunction = nullptr;
static ControlFunctionState testControlFunctionState = ControlFunctionState::Offline;
static bool wasTestStateCallbackHit = false;
void test_control_function_state_callback(std::shared_ptr<ControlFunction> controlFunction, ControlFunctionState state)
{
testControlFunction = controlFunction;
testControlFunctionState = state;
wasTestStateCallbackHit = true;
}

TEST(CORE_TESTS, TestCreateAndDestroyPartners)
{
std::vector<isobus::NAMEFilter> vtNameFilters;
Expand Down Expand Up @@ -214,6 +224,11 @@ TEST(CORE_TESTS, InvalidatingControlFunctions)
std::vector<NAMEFilter> testFilter = { NAMEFilter(NAME::NAMEParameters::IdentityNumber, 967) };
auto testPartner = PartneredControlFunction::create(0, testFilter);

CANNetworkManager::CANNetwork.add_control_function_status_change_callback(testPartner, test_control_function_state_callback);
EXPECT_FALSE(wasTestStateCallbackHit);
EXPECT_EQ(testControlFunction, nullptr);
EXPECT_EQ(testControlFunctionState, ControlFunctionState::Offline);

std::uint64_t rawNAME = partnerName.get_full_name();

// Force claim some kind of partner
Expand All @@ -232,6 +247,11 @@ TEST(CORE_TESTS, InvalidatingControlFunctions)

EXPECT_TRUE(testPartner->get_address_valid());

EXPECT_TRUE(wasTestStateCallbackHit);
EXPECT_NE(testControlFunction, nullptr);
EXPECT_EQ(testControlFunctionState, ControlFunctionState::Online);
wasTestStateCallbackHit = false;

// Request the address claim PGN
testFrame.data[0] = (PGN & std::numeric_limits<std::uint8_t>::max());
testFrame.data[1] = ((PGN >> 8) & std::numeric_limits<std::uint8_t>::max());
Expand All @@ -246,5 +266,11 @@ TEST(CORE_TESTS, InvalidatingControlFunctions)
CANNetworkManager::CANNetwork.update();

EXPECT_FALSE(testPartner->get_address_valid());

EXPECT_TRUE(wasTestStateCallbackHit);
EXPECT_NE(testControlFunction, nullptr);
EXPECT_EQ(testControlFunctionState, ControlFunctionState::Offline);
CANNetworkManager::CANNetwork.remove_control_function_status_change_callback(testPartner, test_control_function_state_callback);
testControlFunction.reset();
EXPECT_TRUE(testPartner->destroy());
}

0 comments on commit c8156fa

Please sign in to comment.