From c8156fa450adc2ed51af64577330990361e94a3e Mon Sep 17 00:00:00 2001 From: Adrian Del Grosso <10929341+ad3154@users.noreply.github.com> Date: Sun, 27 Aug 2023 13:02:20 -0600 Subject: [PATCH] [Core]: Added a way to get callbacks when any CF goes online or offline 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. --- .../include/isobus/isobus/can_callbacks.hpp | 13 ++++- .../isobus/isobus/can_network_manager.hpp | 29 ++++++++++-- isobus/src/can_network_manager.cpp | 47 +++++++++++++++++++ test/core_network_management_tests.cpp | 26 ++++++++++ 4 files changed, 108 insertions(+), 7 deletions(-) diff --git a/isobus/include/isobus/isobus/can_callbacks.hpp b/isobus/include/isobus/isobus/can_callbacks.hpp index 47c71086..9ec586f2 100644 --- a/isobus/include/isobus/isobus/can_callbacks.hpp +++ b/isobus/include/isobus/isobus/can_callbacks.hpp @@ -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 @@ -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, 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, diff --git a/isobus/include/isobus/isobus/can_network_manager.hpp b/isobus/include/isobus/isobus/can_network_manager.hpp index 06075305..6aeec217 100644 --- a/isobus/include/isobus/isobus/can_network_manager.hpp +++ b/isobus/include/isobus/isobus/can_network_manager.hpp @@ -137,6 +137,17 @@ namespace isobus /// @param[in] controlFunction The control function that was created void on_control_function_created(std::shared_ptr controlFunction, CANLibBadge); + /// @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, 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, 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> &get_internal_control_functions() const; @@ -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, @@ -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, @@ -260,6 +271,12 @@ namespace isobus /// @param[in] currentMessage The message to process void process_any_control_function_pgn_callbacks(const CANMessage ¤tMessage); + /// @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, 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 ¤tMessage); @@ -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, @@ -313,7 +330,7 @@ namespace isobus std::array, CAN_PORT_MAXIMUM> busloadMessageBitsHistory; ///< Stores the approximate number of bits processed on each channel over multiple previous time windows std::array currentBusloadBitAccumulator; ///< Accumulates the approximate number of bits processed on each channel during the current time window - std::array lastAddressClaimRequestTimestamp_ms; ///< Stores timestamps for when the last request for the address claim PGN was recieved. Used to prune stale CFs. + std::array lastAddressClaimRequestTimestamp_ms; ///< Stores timestamps for when the last request for the address claim PGN was received. Used to prune stale CFs. std::array, NULL_CAN_ADDRESS>, CAN_PORT_MAXIMUM> controlFunctionTable; ///< Table to maintain address to NAME mappings std::list> inactiveControlFunctions; ///< A list of the control function that currently don't have a valid address @@ -322,12 +339,14 @@ namespace isobus std::list protocolPGNCallbacks; ///< A list of PGN callback registered by CAN protocols std::list receiveMessageList; ///< A queue of Rx messages to process + std::list, ControlFunctionStateCallback>> controlFunctionStateCallbacks; ///< List of all control function state callbacks std::vector globalParameterGroupNumberCallbacks; ///< A list of all global PGN callbacks std::vector 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 diff --git a/isobus/src/can_network_manager.cpp b/isobus/src/can_network_manager.cpp index a444b523..756aca20 100644 --- a/isobus/src/can_network_manager.cpp +++ b/isobus/src/can_network_manager.cpp @@ -331,6 +331,32 @@ namespace isobus } } + void CANNetworkManager::add_control_function_status_change_callback(std::shared_ptr controlFunction, ControlFunctionStateCallback callback) + { + if ((nullptr != controlFunction) && + (nullptr != callback)) + { + const std::lock_guard lock(controlFunctionStatusCallbacksMutex); + controlFunctionStateCallbacks.emplace_back(controlFunction, callback); + } + } + + void CANNetworkManager::remove_control_function_status_change_callback(std::shared_ptr controlFunction, ControlFunctionStateCallback callback) + { + if ((nullptr != controlFunction) && + (nullptr != callback)) + { + const std::lock_guard lock(controlFunctionStatusCallbacksMutex); + std::pair, ControlFunctionStateCallback> targetCallback(controlFunction, callback); + auto callbackLocation = std::find(controlFunctionStateCallbacks.begin(), controlFunctionStateCallbacks.end(), targetCallback); + + if (controlFunctionStateCallbacks.end() != callbackLocation) + { + controlFunctionStateCallbacks.erase(callbackLocation); + } + } + } + const std::list> &CANNetworkManager::get_internal_control_functions() const { return internalControlFunctions; @@ -431,6 +457,7 @@ namespace isobus currentControlFunction->get_NAME().get_full_name(), claimedAddress, channelIndex); + process_control_function_state_change_callback(currentControlFunction, ControlFunctionState::Online); break; } } @@ -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; } @@ -663,6 +691,7 @@ namespace isobus partner->controlFunctionNAME = currentActiveControlFunction->get_NAME(); partner->initialized = true; controlFunctionTable[partner->get_can_port()][partner->address] = std::shared_ptr(partner); + process_control_function_state_change_callback(partner, ControlFunctionState::Online); break; } } @@ -770,6 +799,18 @@ namespace isobus } } + void CANNetworkManager::process_control_function_state_change_callback(std::shared_ptr controlFunction, ControlFunctionState state) + { + const std::lock_guard lock(controlFunctionStatusCallbacksMutex); + for (const auto &callback : controlFunctionStateCallbacks) + { + if (callback.first == controlFunction) + { + callback.second(callback.first, state); + } + } + } + void CANNetworkManager::process_protocol_pgn_callbacks(const CANMessage ¤tMessage) { const std::lock_guard lock(protocolPGNCallbacksMutex); @@ -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; diff --git a/test/core_network_management_tests.cpp b/test/core_network_management_tests.cpp index c75a75d7..161f86ae 100644 --- a/test/core_network_management_tests.cpp +++ b/test/core_network_management_tests.cpp @@ -13,6 +13,16 @@ using namespace isobus; +static std::shared_ptr testControlFunction = nullptr; +static ControlFunctionState testControlFunctionState = ControlFunctionState::Offline; +static bool wasTestStateCallbackHit = false; +void test_control_function_state_callback(std::shared_ptr controlFunction, ControlFunctionState state) +{ + testControlFunction = controlFunction; + testControlFunctionState = state; + wasTestStateCallbackHit = true; +} + TEST(CORE_TESTS, TestCreateAndDestroyPartners) { std::vector vtNameFilters; @@ -214,6 +224,11 @@ TEST(CORE_TESTS, InvalidatingControlFunctions) std::vector 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 @@ -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::max()); testFrame.data[1] = ((PGN >> 8) & std::numeric_limits::max()); @@ -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()); }