diff --git a/isobus/CMakeLists.txt b/isobus/CMakeLists.txt index 8e79ed22..ca8b0cd0 100644 --- a/isobus/CMakeLists.txt +++ b/isobus/CMakeLists.txt @@ -14,7 +14,6 @@ set(ISOBUS_SRC "can_control_function.cpp" "can_message.cpp" "can_network_manager.cpp" - "can_address_claim_state_machine.cpp" "can_internal_control_function.cpp" "can_partnered_control_function.cpp" "can_NAME_filter.cpp" @@ -59,7 +58,6 @@ set(ISOBUS_INCLUDE "can_message.hpp" "can_general_parameter_group_numbers.hpp" "can_network_manager.hpp" - "can_address_claim_state_machine.hpp" "can_NAME_filter.hpp" "can_transport_protocol.hpp" "can_transport_protocol_base.hpp" diff --git a/isobus/include/isobus/isobus/can_address_claim_state_machine.hpp b/isobus/include/isobus/isobus/can_address_claim_state_machine.hpp deleted file mode 100644 index 073ebb6c..00000000 --- a/isobus/include/isobus/isobus/can_address_claim_state_machine.hpp +++ /dev/null @@ -1,116 +0,0 @@ -//================================================================================================ -/// @file can_address_claim_state_machine.hpp -/// -/// @brief Defines a class for managing the address claiming process -/// @author Adrian Del Grosso -/// -/// @copyright 2022 Adrian Del Grosso -//================================================================================================ - -#ifndef CAN_ADDRESS_CLAIM_STATE_MACHINE_HPP -#define CAN_ADDRESS_CLAIM_STATE_MACHINE_HPP - -#include "isobus/isobus/can_NAME.hpp" -#include "isobus/isobus/can_constants.hpp" - -namespace isobus -{ - class CANMessage; ///< Forward declare CANMessage - - //================================================================================================ - /// @class AddressClaimStateMachine - /// - /// @brief State machine for managing the J1939/ISO11783 address claim process - /// - /// @details This class manages address claiming for internal control functions - /// and keeps track of things like requests for address claim. - //================================================================================================ - class AddressClaimStateMachine - { - public: - /// @brief Defines the state machine states for address claiming - enum class State - { - None, ///< Address claiming is uninitialized - WaitForClaim, ///< State machine is waiting for the random delay time - SendRequestForClaim, ///< State machine is sending the request for address claim - WaitForRequestContentionPeriod, ///< State machine is waiting for the address claim contention period - SendPreferredAddressClaim, ///< State machine is claiming the preferred address - ContendForPreferredAddress, ///< State machine is contending the preferred address - SendArbitraryAddressClaim, ///< State machine is claiming an address - SendReclaimAddressOnRequest, ///< An ECU requested address claim, inform the bus of our current address - UnableToClaim, ///< State machine could not claim an address - AddressClaimingComplete ///< Address claiming is complete and we have an address - }; - - /// @brief The constructor of the state machine class - /// @param[in] preferredAddressValue The address you prefer to claim - /// @param[in] ControlFunctionNAME The NAME you want to claim - /// @param[in] portIndex The CAN channel index to claim on - AddressClaimStateMachine(std::uint8_t preferredAddressValue, NAME ControlFunctionNAME, std::uint8_t portIndex); - - /// @brief The destructor for the address claim state machine - ~AddressClaimStateMachine(); - - /// @brief Returns the current state of the state machine - /// @returns The current state of the state machine - State get_current_state() const; - - /// @brief Used to inform the address claim state machine that two CFs are using the same source address. - /// This function may cause the state machine to emit an address claim depending on its state, as is - /// required by ISO11783-5. - void on_address_violation(); - - /// @brief Attempts to process a commanded address. - /// @details If the state machine has claimed successfully before, - /// this will attempt to move a NAME from the claimed address to the new, specified address. - /// @param[in] commandedAddress The address to attempt to claim - void process_commanded_address(std::uint8_t commandedAddress); - - /// @brief Enables or disables the address claimer - /// @param[in] value true if you want the class to claim, false if you want to be a sniffer only - void set_is_enabled(bool value); - - /// @brief Returns if the address claimer is enabled - /// @returns true if the class will address claim, false if in sniffing mode - bool get_enabled() const; - - /// @brief Returns the address claimed by the state machine or 0xFE if none claimed - /// @returns The address claimed by the state machine or 0xFE if no address has been claimed - std::uint8_t get_claimed_address() const; - - /// @brief Updates the state machine, should be called periodically - void update(); - - private: - /// @brief Processes a CAN message - /// @param[in] message The CAN message being received - /// @param[in] parentPointer A context variable to find the relevant address claimer - static void process_rx_message(const CANMessage &message, void *parentPointer); - - /// @brief Sets the current state machine state - /// @param[in] value The state to set the state machine to - void set_current_state(State value); - - /// @brief Sends the PGN request for the address claim PGN - /// @returns true if the message was sent, otherwise false - bool send_request_to_claim() const; - - /// @brief Sends the address claim message - /// @param[in] address The address to claim - /// @returns true if the message was sent, otherwise false - bool send_address_claim(std::uint8_t address); - - NAME m_isoname; ///< The ISO NAME to claim as - State m_currentState = State::None; ///< The address claim state machine state - std::uint32_t m_timestamp_ms = 0; ///< A generic timestamp in milliseconds used to find timeouts - std::uint8_t m_portIndex; ///< The CAN channel index to claim on - std::uint8_t m_preferredAddress; ///< The address we'd prefer to claim as (we may not get it) - std::uint8_t m_randomClaimDelay_ms; ///< The random delay as required by the ISO11783 standard - std::uint8_t m_claimedAddress = NULL_CAN_ADDRESS; ///< The actual address we ended up claiming - bool m_enabled = true; ///< Enable/disable state for this state machine - }; - -} // namespace isobus - -#endif // CAN_ADDRESS_CLAIM_STATE_MACHINE_HPP diff --git a/isobus/include/isobus/isobus/can_internal_control_function.hpp b/isobus/include/isobus/isobus/can_internal_control_function.hpp index a28b902f..d409da9e 100644 --- a/isobus/include/isobus/isobus/can_internal_control_function.hpp +++ b/isobus/include/isobus/isobus/can_internal_control_function.hpp @@ -6,34 +6,44 @@ /// @author Adrian Del Grosso /// @author Daan Steenbergen /// -/// @copyright 2022 Adrian Del Grosso +/// @copyright 2024 The Open-Agriculture Developers //================================================================================================ #ifndef CAN_INTERNAL_CONTROL_FUNCTION_HPP #define CAN_INTERNAL_CONTROL_FUNCTION_HPP -#include "isobus/isobus/can_address_claim_state_machine.hpp" -#include "isobus/isobus/can_badge.hpp" #include "isobus/isobus/can_control_function.hpp" +#include "isobus/isobus/can_message.hpp" +#include "isobus/utility/event_dispatcher.hpp" #include namespace isobus { - class CANNetworkManager; class ParameterGroupNumberRequestProtocol; - //================================================================================================ - /// @class InternalControlFunction - /// /// @brief Describes an internal ECU's NAME and address data. Used to send CAN messages. /// @details This class is used to define your own ECU's NAME, and is used to transmit messages. /// Each instance of this class will claim a unique address on the bus, and can be used to /// send messages. - //================================================================================================ class InternalControlFunction : public ControlFunction { public: + /// @brief Defines the states the internal control function can be in + enum class State + { + None, ///< Initial state + WaitForClaim, ///< Waiting for the random delay time to expire + SendRequestForClaim, ///< Sending the request for address claim to the bus + WaitForRequestContentionPeriod, ///< Waiting for the address claim contention period to expire + SendPreferredAddressClaim, ///< Claiming the preferred address as our own + ContendForPreferredAddress, ///< Contending the preferred address with another ECU + SendArbitraryAddressClaim, ///< Claiming an arbitrary (not our preferred) address as our own + SendReclaimAddressOnRequest, ///< An ECU requested address claim, inform the bus of our current address + UnableToClaim, ///< Unable to claim an address + AddressClaimingComplete ///< Address claiming is complete and we have an address + }; + /// @brief The constructor of an internal control function. /// In most cases use `CANNetworkManager::create_internal_control_function()` instead, /// only use this constructor if you have advanced needs. @@ -42,29 +52,72 @@ namespace isobus /// @param[in] CANPort The CAN channel index for this control function to use InternalControlFunction(NAME desiredName, std::uint8_t preferredAddress, std::uint8_t CANPort); - /// @brief Used to inform the member address claim state machine that two CFs are using the same source address. - /// @note Address violation occurs when two CFs are using the same source address. - void on_address_violation(CANLibBadge); + /// @brief Returns the current state of the internal control function + /// @returns The current state + State get_current_state() const; + + /// @brief Processes a CAN message for address claiming purposes + /// @param[in] message The CAN message being received + void process_rx_message_for_address_claiming(const CANMessage &message); - /// @brief Used by the network manager to tell the ICF that the address claim state machine needs to process - /// a J1939 command to move address. - /// @param[in] commandedAddress The address that the ICF has been commanded to move to - void process_commanded_address(std::uint8_t commandedAddress, CANLibBadge); + /// @brief Updates the internal control function address claiming, will be called periodically by + /// the network manager if the ICF is registered there. + /// @returns true if the address of internal control function has changed, otherwise false + bool update_address_claiming(); - /// @brief Updates the internal control function together with it's associated address claim state machine - /// @returns Wether the control function has changed address by the end of the update - bool update_address_claiming(CANLibBadge); + /// @brief Returns the preferred address of the internal control function + /// @returns The preferred address + std::uint8_t get_preferred_address() const; + + /// @brief Returns the event dispatcher for when an address is claimed. Use this to register a callback + /// for when an address is claimed. + /// @returns The event dispatcher for when an address is claimed + EventDispatcher &get_address_claimed_event_dispatcher(); /// @brief Gets the PGN request protocol for this ICF /// @returns The PGN request protocol for this ICF std::weak_ptr get_pgn_request_protocol() const; + /// @brief Validates that a CAN message has not caused an address violation for this ICF. + /// If a violation is found, a re-claim will be executed for as is required by ISO 11783-5, + /// and will attempt to activate a DTC that is defined in ISO 11783-5. + /// This function is for advanced use cases only. Normally, the network manager will call this + /// for every message received. + /// @note Address violation occurs when two CFs are using the same source address. + /// @param[in] message The message to process + /// @returns true if the message caused an address violation, otherwise false + bool process_rx_message_for_address_violation(const CANMessage &message); + protected: friend class CANNetworkManager; ///< Allow the network manager to access the pgn request protocol std::shared_ptr pgnRequestProtocol; ///< The PGN request protocol for this ICF private: - AddressClaimStateMachine stateMachine; ///< The address claimer for this ICF + /// @brief Sends the PGN request for the address claim PGN + /// @returns true if the message was sent, otherwise false + bool send_request_to_claim() const; + + /// @brief Sends the address claim message to the bus + /// @param[in] address The address to claim + /// @returns true if the message was sent, otherwise false + bool send_address_claim(std::uint8_t address); + + /// @brief Attempts to process a commanded address. + /// @details If the state machine has claimed successfully before, + /// this will attempt to move a NAME from the claimed address to the new, specified address. + /// @param[in] commandedAddress The address to attempt to claim + void process_commanded_address(std::uint8_t commandedAddress); + + /// @brief Setter for the state + void set_current_state(State value); + + static constexpr std::uint32_t ADDRESS_CONTENTION_TIME_MS = 250; + + State state = State::None; ///< The current state of the internal control function + std::uint32_t stateChangeTimestamp_ms = 0; ///< A timestamp in milliseconds used for timing the address claiming process + std::uint8_t preferredAddress; ///< The address we'd prefer to claim as (we may not get it) + std::uint8_t randomClaimDelay_ms; ///< The random delay before claiming an address as required by the ISO11783 standard + EventDispatcher addressClaimedDispatcher; ///< The event dispatcher for when an address is claimed }; } // namespace isobus diff --git a/isobus/include/isobus/isobus/can_message.hpp b/isobus/include/isobus/isobus/can_message.hpp index 0730e173..6fd63035 100644 --- a/isobus/include/isobus/isobus/can_message.hpp +++ b/isobus/include/isobus/isobus/can_message.hpp @@ -11,6 +11,7 @@ #ifndef CAN_MESSAGE_HPP #define CAN_MESSAGE_HPP +#include "isobus/isobus/can_constants.hpp" #include "isobus/isobus/can_control_function.hpp" #include "isobus/isobus/can_general_parameter_group_numbers.hpp" #include "isobus/isobus/can_identifier.hpp" diff --git a/isobus/include/isobus/isobus/can_network_manager.hpp b/isobus/include/isobus/isobus/can_network_manager.hpp index e8a05de1..3d680adb 100644 --- a/isobus/include/isobus/isobus/can_network_manager.hpp +++ b/isobus/include/isobus/isobus/can_network_manager.hpp @@ -12,7 +12,6 @@ #ifndef CAN_NETWORK_MANAGER_HPP #define CAN_NETWORK_MANAGER_HPP -#include "isobus/isobus/can_address_claim_state_machine.hpp" #include "isobus/isobus/can_badge.hpp" #include "isobus/isobus/can_callbacks.hpp" #include "isobus/isobus/can_constants.hpp" @@ -76,11 +75,12 @@ namespace isobus /// @param[in] controlFunction The control function to deactivate void deactivate_control_function(std::shared_ptr controlFunction); - /// @brief Called only by the stack, returns a control function based on certain port and address + /// @brief Getter for a control function based on certain port and address, normally only used internaly. + /// You should try to refrain from using addresses directly, instead try keeping a reference to the control function. /// @param[in] channelIndex CAN Channel index of the control function /// @param[in] address Address of the control function /// @returns A control function that matches the parameters, or nullptr if no match was found - std::shared_ptr get_control_function(std::uint8_t channelIndex, std::uint8_t address, CANLibBadge) const; + std::shared_ptr get_control_function(std::uint8_t channelIndex, std::uint8_t address) const; /// @brief This is how you register a callback for any PGN destined for the global address (0xFF) /// @param[in] parameterGroupNumber The PGN you want to register for @@ -165,22 +165,6 @@ namespace isobus /// @param[in] txFrame The frame that was just emitted onto the bus void process_transmitted_can_message_frame(const CANMessageFrame &txFrame); - /// @brief Informs the network manager that a control function object has been destroyed, so that it can be purged from the network manager - /// @param[in] controlFunction The control function that was destroyed - void on_control_function_destroyed(std::shared_ptr controlFunction, CANLibBadge); - - /// @brief Informs the network manager that a control function object has been created, so that it can be added to the network manager - /// @param[in] controlFunction The control function that was created - void on_control_function_created(std::shared_ptr controlFunction, CANLibBadge); - - /// @brief Informs the network manager that a control function object has been created, so that it can be added to the network manager - /// @param[in] controlFunction The control function that was created - void on_control_function_created(std::shared_ptr controlFunction, CANLibBadge); - - /// @brief Informs the network manager that a control function object has been created, so that it can be added to the network manager - /// @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] callback The callback you want to be called when the any control function changes state @@ -231,7 +215,7 @@ namespace isobus protected: // Using protected region to allow protocols use of special functions from the network manager - friend class AddressClaimStateMachine; ///< Allows the network manager to work closely with the address claiming process + friend class InternalControlFunction; ///< Allows the network manager to work closely with the address claiming process friend class ExtendedTransportProtocolManager; ///< Allows the network manager to access the ETP manager friend class TransportProtocolManager; ///< Allows the network manager to work closely with the transport protocol manager object friend class DiagnosticProtocol; ///< Allows the diagnostic protocol to access the protected functions on the network manager @@ -268,7 +252,7 @@ namespace isobus std::uint8_t priority, const void *data, std::uint32_t size, - CANLibBadge) const; + CANLibBadge) const; /// @brief Processes completed protocol messages. Causes PGN callbacks to trigger. /// @param[in] message The completed protocol message @@ -296,6 +280,9 @@ namespace isobus /// @brief Updates the internal address table based on updates to internal cfs addresses void update_internal_cfs(); + /// @brief Processes a message for each internal control function for address claiming + void process_rx_message_for_address_claiming(const CANMessage &message); + /// @brief Processes a CAN message's contribution to the current busload /// @param[in] channelIndex The CAN channel index associated to the message being processed /// @param[in] numberOfBitsProcessed The number of bits to add to the busload calculation @@ -328,12 +315,6 @@ namespace isobus const void *data, std::uint32_t size) const; - /// @brief Returns a control function based on a CAN address and channel index - /// @param[in] channelIndex The CAN channel index of the CAN message being processed - /// @param[in] address The CAN address associated with a control function - /// @returns A control function matching the address and CAN port passed in - std::shared_ptr get_control_function(std::uint8_t channelIndex, std::uint8_t address) const; - /// @brief Get the next CAN message from the received message queue, and remove it from the queue. /// @note This will only ever get an 8 byte message because they are directly translated from CAN frames. /// @returns The message that was at the front of the queue, or an invalid message if the queue is empty @@ -344,10 +325,6 @@ namespace isobus /// @returns The message that was at the front of the queue, or an invalid message if the queue is empty CANMessage get_next_can_message_from_tx_queue(); - /// @brief Informs the network manager that a control function object has been created - /// @param[in] controlFunction The control function that was created - void on_control_function_created(std::shared_ptr controlFunction); - /// @brief Processes a can message for callbacks added with add_any_control_function_parameter_group_number_callback /// @param[in] currentMessage The message to process void process_any_control_function_pgn_callbacks(const CANMessage ¤tMessage); @@ -373,13 +350,6 @@ namespace isobus /// @param[in] message A pointer to a CAN message to be processed void process_can_message_for_global_and_partner_callbacks(const CANMessage &message); - /// @brief Processes a CAN message to see if it's a commanded address message, and - /// if it is, it attempts to set the relevant CF's address to the new value. - /// @note Changing the address will resend the address claim message if - /// the target was an internal control function. - /// @param[in] message The message to process - void process_can_message_for_commanded_address(const CANMessage &message); - /// @brief Processes the internal received message queue void process_rx_messages(); diff --git a/isobus/include/isobus/isobus/can_partnered_control_function.hpp b/isobus/include/isobus/isobus/can_partnered_control_function.hpp index bcedaf9f..d345bc94 100644 --- a/isobus/include/isobus/isobus/can_partnered_control_function.hpp +++ b/isobus/include/isobus/isobus/can_partnered_control_function.hpp @@ -12,7 +12,6 @@ #define CAN_PARTNERED_CONTROL_FUNCTION_HPP #include "isobus/isobus/can_NAME_filter.hpp" -#include "isobus/isobus/can_address_claim_state_machine.hpp" #include "isobus/isobus/can_badge.hpp" #include "isobus/isobus/can_callbacks.hpp" #include "isobus/isobus/can_control_function.hpp" diff --git a/isobus/src/can_address_claim_state_machine.cpp b/isobus/src/can_address_claim_state_machine.cpp deleted file mode 100644 index 9f0d8e1f..00000000 --- a/isobus/src/can_address_claim_state_machine.cpp +++ /dev/null @@ -1,405 +0,0 @@ -//================================================================================================ -/// @file can_address_claim_state_machine.cpp -/// -/// @brief Defines a class for managing the address claiming process -/// @author Adrian Del Grosso -/// -/// @copyright 2022 Adrian Del Grosso -//================================================================================================ -#include "isobus/isobus/can_address_claim_state_machine.hpp" -#include "isobus/isobus/can_general_parameter_group_numbers.hpp" -#include "isobus/isobus/can_network_manager.hpp" -#include "isobus/isobus/can_stack_logger.hpp" -#include "isobus/utility/system_timing.hpp" - -#include -#include -#include - -namespace isobus -{ - AddressClaimStateMachine::AddressClaimStateMachine(std::uint8_t preferredAddressValue, NAME ControlFunctionNAME, std::uint8_t portIndex) : - m_isoname(ControlFunctionNAME), - m_portIndex(portIndex), - m_preferredAddress(preferredAddressValue) - { - assert(m_preferredAddress != BROADCAST_CAN_ADDRESS); - assert(portIndex < CAN_PORT_MAXIMUM); - - if (NULL_CAN_ADDRESS == m_preferredAddress) - { - // If we don't have a preferred address, your NAME must be arbitrary address capable! - assert(m_isoname.get_arbitrary_address_capable()); - } - std::default_random_engine generator; - std::uniform_int_distribution distribution(0, 255); - m_randomClaimDelay_ms = distribution(generator) * 0.6f; // Defined by ISO part 5 - CANNetworkManager::CANNetwork.add_global_parameter_group_number_callback(static_cast(CANLibParameterGroupNumber::ParameterGroupNumberRequest), process_rx_message, this); - CANNetworkManager::CANNetwork.add_global_parameter_group_number_callback(static_cast(CANLibParameterGroupNumber::AddressClaim), process_rx_message, this); - } - - AddressClaimStateMachine ::~AddressClaimStateMachine() - { - CANNetworkManager::CANNetwork.remove_global_parameter_group_number_callback(static_cast(CANLibParameterGroupNumber::ParameterGroupNumberRequest), process_rx_message, this); - CANNetworkManager::CANNetwork.remove_global_parameter_group_number_callback(static_cast(CANLibParameterGroupNumber::AddressClaim), process_rx_message, this); - } - - AddressClaimStateMachine::State AddressClaimStateMachine::get_current_state() const - { - return m_currentState; - } - - void AddressClaimStateMachine::on_address_violation() - { - if (State::AddressClaimingComplete == get_current_state()) - { - LOG_WARNING("[AC]: Address violation for address %u", - get_claimed_address()); - - set_current_state(State::SendReclaimAddressOnRequest); - } - } - - void AddressClaimStateMachine::process_commanded_address(std::uint8_t commandedAddress) - { - if (State::AddressClaimingComplete == get_current_state()) - { - if (!m_isoname.get_arbitrary_address_capable()) - { - LOG_ERROR("[AC]: Our address was commanded to a new value, but our ISO NAME doesn't support changing our address."); - } - else - { - std::shared_ptr deviceAtOurPreferredAddress = CANNetworkManager::CANNetwork.get_control_function(m_portIndex, commandedAddress, {}); - m_preferredAddress = commandedAddress; - - if (nullptr == deviceAtOurPreferredAddress) - { - // Commanded address is free. We'll claim it. - set_current_state(State::SendPreferredAddressClaim); - LOG_INFO("[AC]: Our address was commanded to a new value of %u", commandedAddress); - } - else if (deviceAtOurPreferredAddress->get_NAME().get_full_name() < m_isoname.get_full_name()) - { - // We can steal the address of the device at our commanded address and force it to move - set_current_state(State::SendArbitraryAddressClaim); - LOG_INFO("[AC]: Our address was commanded to a new value of %u, and an ECU at the target address is being evicted.", commandedAddress); - } - else - { - LOG_ERROR("[AC]: Our address was commanded to a new value of %u, but we cannot move to the target address.", commandedAddress); - } - } - } - } - - void AddressClaimStateMachine::set_is_enabled(bool value) - { - m_enabled = value; - } - - bool AddressClaimStateMachine::get_enabled() const - { - return m_enabled; - } - - std::uint8_t AddressClaimStateMachine::get_claimed_address() const - { - return m_claimedAddress; - } - - void AddressClaimStateMachine::update() - { - if (get_enabled()) - { - switch (get_current_state()) - { - case State::None: - { - set_current_state(State::WaitForClaim); - } - break; - - case State::WaitForClaim: - { - if (0 == m_timestamp_ms) - { - m_timestamp_ms = SystemTiming::get_timestamp_ms(); - } - if (SystemTiming::time_expired_ms(m_timestamp_ms, m_randomClaimDelay_ms)) - { - set_current_state(State::SendRequestForClaim); - } - } - break; - - case State::SendRequestForClaim: - { - if (send_request_to_claim()) - { - set_current_state(State::WaitForRequestContentionPeriod); - } - } - break; - - case State::WaitForRequestContentionPeriod: - { - const std::uint32_t addressContentionTime_ms = 250; - - if (SystemTiming::time_expired_ms(m_timestamp_ms, addressContentionTime_ms + m_randomClaimDelay_ms)) - { - std::shared_ptr deviceAtOurPreferredAddress; - - if (NULL_CAN_ADDRESS != m_preferredAddress) - { - deviceAtOurPreferredAddress = CANNetworkManager::CANNetwork.get_control_function(m_portIndex, m_preferredAddress, {}); - } - - // Time to find a free address - if ((nullptr == deviceAtOurPreferredAddress) && - (NULL_CAN_ADDRESS != m_preferredAddress)) - { - // Our address is free. This is the best outcome. Claim it. - set_current_state(State::SendPreferredAddressClaim); - } - else if ((!m_isoname.get_arbitrary_address_capable()) && - (deviceAtOurPreferredAddress->get_NAME().get_full_name() > m_isoname.get_full_name())) - { - // Our address is not free, we cannot be at an arbitrary address, and address is contendable - set_current_state(State::ContendForPreferredAddress); - } - else if (!m_isoname.get_arbitrary_address_capable()) - { - // Can't claim because we cannot tolerate an arbitrary address, and the CF at that spot wins contention - set_current_state(State::UnableToClaim); - } - else - { - // We will move to another address if whoever is in our spot has a lower NAME - if ((nullptr == deviceAtOurPreferredAddress) || - (deviceAtOurPreferredAddress->get_NAME().get_full_name() < m_isoname.get_full_name())) - { - set_current_state(State::SendArbitraryAddressClaim); - } - else - { - set_current_state(State::SendPreferredAddressClaim); - } - } - } - } - break; - - case State::SendPreferredAddressClaim: - { - if (send_address_claim(m_preferredAddress)) - { - LOG_DEBUG("[AC]: Internal control function %016llx has claimed address %u on channel %u", - m_isoname.get_full_name(), - m_preferredAddress, - m_portIndex); - set_current_state(State::AddressClaimingComplete); - } - else - { - set_current_state(State::None); - } - } - break; - - case State::SendArbitraryAddressClaim: - { - // Search the range of generally available addresses - bool addressFound = false; - - for (std::uint8_t i = 128; i <= 235; i++) - { - if ((nullptr == CANNetworkManager::CANNetwork.get_control_function(m_portIndex, i, {})) && (send_address_claim(i))) - { - addressFound = true; - - if (NULL_CAN_ADDRESS == m_preferredAddress) - { - m_preferredAddress = i; - } - LOG_DEBUG("[AC]: Internal control function %016llx could not use the preferred address, but has claimed address %u on channel %u", - m_isoname.get_full_name(), - i, - m_portIndex); - set_current_state(State::AddressClaimingComplete); - break; - } - } - - if (!addressFound) - { - LOG_CRITICAL("[AC]: Internal control function %016llx failed to claim an address on channel %u", - m_isoname.get_full_name(), - m_portIndex); - set_current_state(State::UnableToClaim); - } - } - break; - - case State::SendReclaimAddressOnRequest: - { - if (send_address_claim(m_claimedAddress)) - { - set_current_state(State::AddressClaimingComplete); - } - } - break; - - case State::ContendForPreferredAddress: - { - /// @todo Non-arbitratable address contention (there is not a good reason to use this, but we should add support anyways) - } - break; - - default: - { - } - break; - } - } - else - { - set_current_state(State::None); - } - } - - void AddressClaimStateMachine::process_rx_message(const CANMessage &message, void *parentPointer) - { - if (nullptr != parentPointer) - { - AddressClaimStateMachine *parent = reinterpret_cast(parentPointer); - - if ((message.get_can_port_index() == parent->m_portIndex) && - (parent->get_enabled())) - { - switch (message.get_identifier().get_parameter_group_number()) - { - case static_cast(CANLibParameterGroupNumber::ParameterGroupNumberRequest): - { - const auto &messageData = message.get_data(); - std::uint32_t requestedPGN = messageData.at(0); - requestedPGN |= (static_cast(messageData.at(1)) << 8); - requestedPGN |= (static_cast(messageData.at(2)) << 16); - - if ((static_cast(CANLibParameterGroupNumber::AddressClaim) == requestedPGN) && - (State::AddressClaimingComplete == parent->get_current_state())) - { - parent->set_current_state(State::SendReclaimAddressOnRequest); - } - } - break; - - case static_cast(CANLibParameterGroupNumber::AddressClaim): - { - if (parent->m_claimedAddress == message.get_identifier().get_source_address()) - { - const auto &messageData = message.get_data(); - std::uint64_t NAMEClaimed = messageData.at(0); - NAMEClaimed |= (static_cast(messageData.at(1)) << 8); - NAMEClaimed |= (static_cast(messageData.at(2)) << 16); - NAMEClaimed |= (static_cast(messageData.at(3)) << 24); - NAMEClaimed |= (static_cast(messageData.at(4)) << 32); - NAMEClaimed |= (static_cast(messageData.at(5)) << 40); - NAMEClaimed |= (static_cast(messageData.at(6)) << 48); - NAMEClaimed |= (static_cast(messageData.at(7)) << 56); - - // Check to see if another ECU is hijacking our address - // This is not really a needed check, as we can be pretty sure that our address - // has been stolen if we're running this logic. But, you never know, someone could be - // spoofing us I guess, or we could be getting an echo? CAN Bridge from another channel? - // Seemed safest to just confirm. - if (NAMEClaimed != parent->m_isoname.get_full_name()) - { - // Wait for things to shake out a bit, then claim a new address. - parent->set_current_state(State::WaitForRequestContentionPeriod); - parent->m_claimedAddress = NULL_CAN_ADDRESS; - LOG_WARNING("[AC]: Internal control function %016llx on channel %u must re-arbitrate its address because it was stolen by another ECU with NAME %016llx.", - parent->m_isoname.get_full_name(), - parent->m_portIndex, - NAMEClaimed); - } - } - } - break; - - default: - { - } - break; - } - } - } - } - - void AddressClaimStateMachine::set_current_state(State value) - { - m_currentState = value; - } - - bool AddressClaimStateMachine::send_request_to_claim() const - { - bool retVal = false; - - if (get_enabled()) - { - const std::uint8_t addressClaimRequestLength = 3; - const auto PGN = static_cast(CANLibParameterGroupNumber::AddressClaim); - std::uint8_t dataBuffer[addressClaimRequestLength]; - - dataBuffer[0] = (PGN & std::numeric_limits::max()); - dataBuffer[1] = ((PGN >> 8) & std::numeric_limits::max()); - dataBuffer[2] = ((PGN >> 16) & std::numeric_limits::max()); - - retVal = CANNetworkManager::CANNetwork.send_can_message_raw(m_portIndex, - NULL_CAN_ADDRESS, - BROADCAST_CAN_ADDRESS, - static_cast(CANLibParameterGroupNumber::ParameterGroupNumberRequest), - static_cast(CANIdentifier::CANPriority::PriorityDefault6), - dataBuffer, - 3, - {}); - } - return retVal; - } - - bool AddressClaimStateMachine::send_address_claim(std::uint8_t address) - { - bool retVal = false; - - assert(address < BROADCAST_CAN_ADDRESS); - - if (get_enabled()) - { - std::uint64_t isoNAME = m_isoname.get_full_name(); - std::uint8_t dataBuffer[CAN_DATA_LENGTH]; - - dataBuffer[0] = static_cast(isoNAME); - dataBuffer[1] = static_cast(isoNAME >> 8); - dataBuffer[2] = static_cast(isoNAME >> 16); - dataBuffer[3] = static_cast(isoNAME >> 24); - dataBuffer[4] = static_cast(isoNAME >> 32); - dataBuffer[5] = static_cast(isoNAME >> 40); - dataBuffer[6] = static_cast(isoNAME >> 48); - dataBuffer[7] = static_cast(isoNAME >> 56); - retVal = CANNetworkManager::CANNetwork.send_can_message_raw(m_portIndex, - address, - BROADCAST_CAN_ADDRESS, - static_cast(CANLibParameterGroupNumber::AddressClaim), - static_cast(CANIdentifier::CANPriority::PriorityDefault6), - dataBuffer, - CAN_DATA_LENGTH, - {}); - if (retVal) - { - m_claimedAddress = address; - } - } - return retVal; - } - -} // namespace isobus diff --git a/isobus/src/can_internal_control_function.cpp b/isobus/src/can_internal_control_function.cpp index e383851a..4d70684b 100644 --- a/isobus/src/can_internal_control_function.cpp +++ b/isobus/src/can_internal_control_function.cpp @@ -12,34 +12,267 @@ #include "isobus/isobus/can_constants.hpp" #include "isobus/isobus/can_parameter_group_number_request_protocol.hpp" +#include "isobus/isobus/can_stack_logger.hpp" +#include "isobus/utility/system_timing.hpp" #include +#include namespace isobus { InternalControlFunction::InternalControlFunction(NAME desiredName, std::uint8_t preferredAddress, std::uint8_t CANPort) : ControlFunction(desiredName, NULL_CAN_ADDRESS, CANPort, Type::Internal), - stateMachine(preferredAddress, desiredName, CANPort) + preferredAddress(preferredAddress) { + assert(preferredAddress != BROADCAST_CAN_ADDRESS); + assert(CANPort < CAN_PORT_MAXIMUM); + + if (NULL_CAN_ADDRESS == preferredAddress) + { + // If we don't have a preferred address, your NAME must be arbitrary address capable! + assert(desiredName.get_arbitrary_address_capable()); + } + + std::default_random_engine generator; + std::uniform_int_distribution distribution(0, 255); + randomClaimDelay_ms = static_cast(distribution(generator) * 0.6); // Defined by ISO part 5 + } + + InternalControlFunction::State InternalControlFunction::get_current_state() const + { + return state; } - void InternalControlFunction::on_address_violation(CANLibBadge) + void InternalControlFunction::set_current_state(State value) { - stateMachine.on_address_violation(); + stateChangeTimestamp_ms = SystemTiming::get_timestamp_ms(); + state = value; + } + + void InternalControlFunction::process_rx_message_for_address_claiming(const CANMessage &message) + { + if (message.get_can_port_index() != canPortIndex) + { + return; + } + + switch (message.get_identifier().get_parameter_group_number()) + { + case static_cast(CANLibParameterGroupNumber::ParameterGroupNumberRequest): + { + std::uint32_t requestedPGN = message.get_uint24_at(0); + + if ((static_cast(CANLibParameterGroupNumber::AddressClaim) == requestedPGN) && + (State::AddressClaimingComplete == get_current_state())) + { + set_current_state(State::SendReclaimAddressOnRequest); + } + } + break; + + case static_cast(CANLibParameterGroupNumber::AddressClaim): + { + if (get_address() == message.get_identifier().get_source_address()) + { + std::uint64_t NAMEClaimed = message.get_uint64_at(0); + + // Check to see if another ECU is hijacking our address + // This is not really a needed check, as we can be pretty sure that our address + // has been stolen if we're running this logic. But, you never know, someone could be + // spoofing us I guess, or we could be getting an echo? CAN Bridge from another channel? + // Seemed safest to just confirm. + if (NAMEClaimed != get_NAME().get_full_name()) + { + // Wait for things to shake out a bit, then claim a new address. + set_current_state(State::WaitForRequestContentionPeriod); + address = NULL_CAN_ADDRESS; + LOG_WARNING("[AC]: Internal control function %016llx on channel %u must re-arbitrate its address because it was stolen by another ECU with NAME %016llx.", + get_NAME().get_full_name(), + get_can_port(), + NAMEClaimed); + } + } + } + break; + + case static_cast(CANLibParameterGroupNumber::CommandedAddress): + { + constexpr std::uint8_t COMMANDED_ADDRESS_LENGTH = 9; + + if ((nullptr == message.get_destination_control_function()) && + (COMMANDED_ADDRESS_LENGTH == message.get_data_length()) && + (message.get_can_port_index() == get_can_port())) + { + std::uint64_t targetNAME = message.get_uint64_at(0); + if (get_NAME().get_full_name() == targetNAME) + { + process_commanded_address(message.get_uint8_at(8)); + } + } + } + + default: + break; + } } - void InternalControlFunction::process_commanded_address(std::uint8_t commandedAddress, CANLibBadge) + bool InternalControlFunction::update_address_claiming() { - stateMachine.process_commanded_address(commandedAddress); + bool hasClaimedAddress = false; + switch (get_current_state()) + { + case State::None: + { + set_current_state(State::WaitForClaim); + } + break; + + case State::WaitForClaim: + { + if (SystemTiming::time_expired_ms(stateChangeTimestamp_ms, randomClaimDelay_ms)) + { + set_current_state(State::SendRequestForClaim); + } + } + break; + + case State::SendRequestForClaim: + { + if (send_request_to_claim()) + { + set_current_state(State::WaitForRequestContentionPeriod); + } + } + break; + + case State::WaitForRequestContentionPeriod: + { + if (SystemTiming::time_expired_ms(stateChangeTimestamp_ms, ADDRESS_CONTENTION_TIME_MS)) + { + std::shared_ptr deviceAtOurPreferredAddress; + if (NULL_CAN_ADDRESS != preferredAddress) + { + deviceAtOurPreferredAddress = CANNetworkManager::CANNetwork.get_control_function(get_can_port(), preferredAddress); + } + + // Time to find a free address + if ((nullptr == deviceAtOurPreferredAddress) && (NULL_CAN_ADDRESS != preferredAddress)) + { + // Our address is free. This is the best outcome. Claim it. + set_current_state(State::SendPreferredAddressClaim); + } + else if (get_NAME().get_arbitrary_address_capable()) + { + // Our address is not free, but we can tolerate an arbitrary address + set_current_state(State::SendArbitraryAddressClaim); + } + else if ((!get_NAME().get_arbitrary_address_capable()) && + (nullptr != deviceAtOurPreferredAddress) && + (deviceAtOurPreferredAddress->get_NAME().get_full_name() > get_NAME().get_full_name())) + { + // Our address is not free, we cannot be at an arbitrary address, but address is contendable + set_current_state(State::ContendForPreferredAddress); + } + else + { + // Can't claim because we cannot tolerate an arbitrary address, and the CF at that spot wins contention + set_current_state(State::UnableToClaim); + LOG_ERROR("[AC]: Internal control function %016llx failed to claim its preferred address %u on channel %u, as it cannot tolerate for an arbitrary address and there is already a CF at the preferred address that wins contention.", + get_NAME().get_full_name(), + preferredAddress, + get_can_port()); + } + } + } + break; + + case State::SendPreferredAddressClaim: + { + if (send_address_claim(preferredAddress)) + { + LOG_DEBUG("[AC]: Internal control function %016llx has claimed address %u on channel %u", + get_NAME().get_full_name(), + get_preferred_address(), + get_can_port()); + hasClaimedAddress = true; + set_current_state(State::AddressClaimingComplete); + } + else + { + set_current_state(State::None); + } + } + break; + + case State::SendArbitraryAddressClaim: + { + // Search the range of generally available addresses + for (std::uint8_t i = 128; i <= 235; i++) + { + if ((nullptr == CANNetworkManager::CANNetwork.get_control_function(get_can_port(), i)) && (send_address_claim(i))) + { + hasClaimedAddress = true; + + if (NULL_CAN_ADDRESS == get_preferred_address()) + { + preferredAddress = i; + LOG_DEBUG("[AC]: Internal control function %016llx has arbitrarily claimed address %u on channel %u", + get_NAME().get_full_name(), + i, + get_can_port()); + } + else + { + LOG_DEBUG("[AC]: Internal control function %016llx could not use the preferred address, but has arbitrarily claimed address %u on channel %u", + get_NAME().get_full_name(), + i, + get_can_port()); + } + set_current_state(State::AddressClaimingComplete); + break; + } + } + + if (!hasClaimedAddress) + { + LOG_CRITICAL("[AC]: Internal control function %016llx failed to claim an address on channel %u", + get_NAME().get_full_name(), + get_can_port()); + set_current_state(State::UnableToClaim); + } + } + break; + + case State::SendReclaimAddressOnRequest: + { + if (send_address_claim(get_address())) + { + hasClaimedAddress = true; + set_current_state(State::AddressClaimingComplete); + } + } + break; + + case State::ContendForPreferredAddress: + { + /// @todo Non-arbitratable address contention (there is not a good reason to use this, but we should add support anyways) + } + break; + + default: + break; + } + return hasClaimedAddress; } - bool InternalControlFunction::update_address_claiming(CANLibBadge) + std::uint8_t InternalControlFunction::get_preferred_address() const { - std::uint8_t previousAddress = address; - stateMachine.update(); - address = stateMachine.get_claimed_address(); + return preferredAddress; + } - return previousAddress != address; + EventDispatcher &InternalControlFunction::get_address_claimed_event_dispatcher() + { + return addressClaimedDispatcher; } std::weak_ptr InternalControlFunction::get_pgn_request_protocol() const @@ -47,4 +280,109 @@ namespace isobus return pgnRequestProtocol; } + bool InternalControlFunction::send_request_to_claim() const + { + const auto parameterGroupNumber = static_cast(CANLibParameterGroupNumber::AddressClaim); + static const std::array dataBuffer{ + static_cast(parameterGroupNumber), + static_cast(parameterGroupNumber >> 8), + static_cast(parameterGroupNumber >> 16) + }; + + return CANNetworkManager::CANNetwork.send_can_message_raw(canPortIndex, + NULL_CAN_ADDRESS, + BROADCAST_CAN_ADDRESS, + static_cast(CANLibParameterGroupNumber::ParameterGroupNumberRequest), + static_cast(CANIdentifier::CANPriority::PriorityDefault6), + dataBuffer.data(), + dataBuffer.size(), + {}); + } + + bool InternalControlFunction::send_address_claim(std::uint8_t addressToClaim) + { + assert(addressToClaim < BROADCAST_CAN_ADDRESS); + + std::uint64_t isoNAME = controlFunctionNAME.get_full_name(); + std::array dataBuffer{ + static_cast(isoNAME), + static_cast(isoNAME >> 8), + static_cast(isoNAME >> 16), + static_cast(isoNAME >> 24), + static_cast(isoNAME >> 32), + static_cast(isoNAME >> 40), + static_cast(isoNAME >> 48), + static_cast(isoNAME >> 56) + }; + bool retVal = CANNetworkManager::CANNetwork.send_can_message_raw(canPortIndex, + addressToClaim, + BROADCAST_CAN_ADDRESS, + static_cast(CANLibParameterGroupNumber::AddressClaim), + static_cast(CANIdentifier::CANPriority::PriorityDefault6), + dataBuffer.data(), + dataBuffer.size(), + {}); + if (retVal) + { + address = addressToClaim; + } + return retVal; + } + + void InternalControlFunction::process_commanded_address(std::uint8_t commandedAddress) + { + if (State::AddressClaimingComplete == get_current_state()) + { + if (!controlFunctionNAME.get_arbitrary_address_capable()) + { + LOG_ERROR("[AC]: Our address was commanded to a new value, but our ISO NAME doesn't support changing our address."); + } + else + { + std::shared_ptr deviceAtOurPreferredAddress = CANNetworkManager::CANNetwork.get_control_function(canPortIndex, commandedAddress); + preferredAddress = commandedAddress; + + if (nullptr == deviceAtOurPreferredAddress) + { + // Commanded address is free. We'll claim it. + set_current_state(State::SendPreferredAddressClaim); + LOG_INFO("[AC]: Our address was commanded to a new value of %u", commandedAddress); + } + else if (deviceAtOurPreferredAddress->get_NAME().get_full_name() < controlFunctionNAME.get_full_name()) + { + // We can steal the address of the device at our commanded address and force it to move + set_current_state(State::SendArbitraryAddressClaim); + LOG_INFO("[AC]: Our address was commanded to a new value of %u, and an ECU at the target address is being evicted.", commandedAddress); + } + else + { + // We can't steal the address of the device at our commanded address, so we'll just ignore the command + // and log an error. + LOG_ERROR("[AC]: Our address was commanded to a new value of %u, but we cannot move to the target address.", commandedAddress); + } + } + } + else + { + LOG_WARNING("[AC]: Our address was commanded to a new value, but we are not in a state to change our address."); + } + } + + bool InternalControlFunction::process_rx_message_for_address_violation(const CANMessage &message) + { + auto sourceAddress = message.get_identifier().get_source_address(); + + if ((BROADCAST_CAN_ADDRESS != sourceAddress) && + (NULL_CAN_ADDRESS != sourceAddress) && + (get_address() == sourceAddress) && + (message.get_can_port_index() == get_can_port()) && + (State::AddressClaimingComplete == get_current_state())) + { + LOG_WARNING("[AC]: Address violation for address %u", get_address()); + + set_current_state(State::SendReclaimAddressOnRequest); + return true; + } + return false; + } } // namespace isobus diff --git a/isobus/src/can_network_manager.cpp b/isobus/src/can_network_manager.cpp index b9e03465..a69805fc 100644 --- a/isobus/src/can_network_manager.cpp +++ b/isobus/src/can_network_manager.cpp @@ -74,11 +74,6 @@ namespace isobus deactivate_control_function(std::static_pointer_cast(controlFunction)); } - std::shared_ptr CANNetworkManager::get_control_function(std::uint8_t channelIndex, std::uint8_t address, CANLibBadge) const - { - return get_control_function(channelIndex, address); - } - void CANNetworkManager::add_global_parameter_group_number_callback(std::uint32_t parameterGroupNumber, CANLibCallback callback, void *parent) { globalParameterGroupNumberCallbacks.emplace_back(parameterGroupNumber, callback, parent, nullptr); @@ -268,7 +263,7 @@ namespace isobus std::uint8_t priority, const void *data, std::uint32_t size, - CANLibBadge) const + CANLibBadge) const { return send_can_message_raw(portIndex, sourceAddress, destAddress, parameterGroupNumber, priority, data, size); } @@ -610,7 +605,7 @@ namespace isobus { for (const auto ¤tInternalControlFunction : internalControlFunctions) { - if (currentInternalControlFunction->update_address_claiming({})) + if (currentInternalControlFunction->update_address_claiming()) { std::uint8_t channelIndex = currentInternalControlFunction->get_can_port(); std::uint8_t claimedAddress = currentInternalControlFunction->get_address(); @@ -639,6 +634,14 @@ namespace isobus } } + void CANNetworkManager::process_rx_message_for_address_claiming(const CANMessage &message) + { + for (const auto &internalCF : internalControlFunctions) + { + internalCF->process_rx_message_for_address_claiming(message); + } + } + void CANNetworkManager::update_busload(std::uint8_t channelIndex, std::uint32_t numberOfBitsProcessed) { LOCK_GUARD(Mutex, busloadUpdateMutex); @@ -925,20 +928,12 @@ namespace isobus void CANNetworkManager::process_can_message_for_address_violations(const CANMessage ¤tMessage) { - auto sourceAddress = currentMessage.get_identifier().get_source_address(); - - if ((BROADCAST_CAN_ADDRESS != sourceAddress) && - (NULL_CAN_ADDRESS != sourceAddress)) + for (const auto &internalCF : internalControlFunctions) { - for (auto &internalCF : internalControlFunctions) + if ((nullptr != internalCF) && + internalCF->process_rx_message_for_address_violation(currentMessage)) { - if ((nullptr != internalCF) && - (internalCF->get_address() == sourceAddress) && - (currentMessage.get_can_port_index() == internalCF->get_can_port())) - { - internalCF->on_address_violation({}); - addressViolationEventDispatcher.call(internalCF); - } + addressViolationEventDispatcher.call(internalCF); } } } @@ -1008,27 +1003,6 @@ namespace isobus } } - void CANNetworkManager::process_can_message_for_commanded_address(const CANMessage &message) - { - constexpr std::uint8_t COMMANDED_ADDRESS_LENGTH = 9; - - if ((nullptr == message.get_destination_control_function()) && - (static_cast(CANLibParameterGroupNumber::CommandedAddress) == message.get_identifier().get_parameter_group_number()) && - (COMMANDED_ADDRESS_LENGTH == message.get_data_length())) - { - std::uint64_t targetNAME = message.get_uint64_at(0); - - for (const auto ¤tICF : internalControlFunctions) - { - if ((message.get_can_port_index() == currentICF->get_can_port()) && - (currentICF->get_NAME().get_full_name() == targetNAME)) - { - currentICF->process_commanded_address(message.get_uint8_at(8), {}); - } - } - } - } - void CANNetworkManager::process_rx_messages() { // We may miss a message without locking the mutex when checking if empty, but that's okay. It will be picked up on the next iteration @@ -1038,6 +1012,7 @@ namespace isobus update_address_table(currentMessage); process_can_message_for_address_violations(currentMessage); + process_rx_message_for_address_claiming(currentMessage); // Update Special Callbacks, like protocols and non-cf specific ones transportProtocols.at(currentMessage.get_can_port_index())->process_message(currentMessage); @@ -1113,7 +1088,7 @@ namespace isobus { process_can_message_for_global_and_partner_callbacks(message); process_any_control_function_pgn_callbacks(message); - process_can_message_for_commanded_address(message); + process_rx_message_for_address_claiming(message); } } // namespace isobus