From d5b5dcef26cd422a0e6f2d1be5e43775e11e3cd2 Mon Sep 17 00:00:00 2001 From: Adrian Del Grosso <10929341+ad3154@users.noreply.github.com> Date: Sun, 20 Oct 2024 11:54:32 -0500 Subject: [PATCH] Update CAN Stack to latest AgIsoStack Updated AgIsoStack to 29dab887a48bb204aae983b06052d52b0f2314d5 --- examples/VirtualTerminal/VirtualTerminal.ino | 10 +- src/AgIsoStack.hpp | 93 +-- src/can_NAME.cpp | 16 +- src/can_NAME.hpp | 83 +- src/can_NAME_filter.cpp | 2 +- src/can_NAME_filter.hpp | 2 +- src/can_address_claim_state_machine.cpp | 387 --------- src/can_address_claim_state_machine.hpp | 116 --- src/can_badge.hpp | 2 +- src/can_callbacks.cpp | 43 +- src/can_callbacks.hpp | 26 +- src/can_constants.hpp | 2 +- src/can_control_function.cpp | 26 +- src/can_control_function.hpp | 39 +- src/can_extended_transport_protocol.cpp | 82 +- src/can_general_parameter_group_numbers.hpp | 4 +- src/can_hardware_abstraction.hpp | 2 +- src/can_hardware_plugin.hpp | 2 +- src/can_identifier.cpp | 2 +- src/can_identifier.hpp | 2 +- src/can_internal_control_function.cpp | 394 +++++++++- src/can_internal_control_function.hpp | 111 ++- src/can_message.cpp | 54 +- src/can_message.hpp | 13 +- src/can_message_data.cpp | 2 +- src/can_message_frame.cpp | 2 +- src/can_message_frame.hpp | 2 +- src/can_network_configuration.cpp | 2 +- src/can_network_configuration.hpp | 2 +- src/can_network_manager.cpp | 365 ++++----- src/can_network_manager.hpp | 117 +-- ...arameter_group_number_request_protocol.cpp | 39 +- ...arameter_group_number_request_protocol.hpp | 8 +- src/can_partnered_control_function.cpp | 18 +- src/can_partnered_control_function.hpp | 18 +- src/can_protocol.cpp | 60 -- src/can_protocol.hpp | 95 --- src/can_stack_logger.cpp | 13 +- src/can_stack_logger.hpp | 57 +- src/can_transport_protocol.cpp | 117 +-- src/can_transport_protocol.hpp | 2 +- src/flex_can_t4_plugin.cpp | 4 +- src/flex_can_t4_plugin.hpp | 2 +- src/isobus_data_dictionary.cpp | 4 + src/isobus_data_dictionary.hpp | 2 + src/isobus_device_descriptor_object_pool.cpp | 296 +++---- src/isobus_device_descriptor_object_pool.hpp | 7 +- src/isobus_diagnostic_protocol.cpp | 14 +- src/isobus_diagnostic_protocol.hpp | 3 +- src/isobus_functionalities.cpp | 140 +--- src/isobus_functionalities.hpp | 12 +- src/isobus_guidance_interface.cpp | 18 +- src/isobus_guidance_interface.hpp | 2 +- src/isobus_heartbeat.cpp | 240 ++++++ src/isobus_heartbeat.hpp | 154 ++++ src/isobus_language_command_interface.cpp | 42 +- src/isobus_language_command_interface.hpp | 8 +- src/isobus_maintain_power_interface.cpp | 25 +- src/isobus_maintain_power_interface.hpp | 2 +- src/isobus_shortcut_button_interface.cpp | 20 +- src/isobus_shortcut_button_interface.hpp | 3 +- src/isobus_speed_distance_messages.cpp | 14 +- src/isobus_speed_distance_messages.hpp | 6 +- ...obus_standard_data_description_indices.hpp | 5 +- src/isobus_task_controller_client.cpp | 456 +++++------ src/isobus_task_controller_client.hpp | 28 +- src/isobus_task_controller_client_objects.cpp | 6 +- src/isobus_task_controller_client_objects.hpp | 8 +- src/isobus_time_date_interface.cpp | 217 ++++++ src/isobus_time_date_interface.hpp | 135 ++++ src/isobus_virtual_terminal_base.hpp | 385 +++++++++ src/isobus_virtual_terminal_client.cpp | 234 +++--- src/isobus_virtual_terminal_client.hpp | 8 +- ..._virtual_terminal_client_state_tracker.cpp | 26 +- ..._virtual_terminal_client_state_tracker.hpp | 2 +- ..._virtual_terminal_client_update_helper.cpp | 18 +- src/isobus_virtual_terminal_objects.cpp | 20 +- src/isobus_virtual_terminal_objects.hpp | 9 +- src/nmea2000_fast_packet_protocol.cpp | 733 +++++++++--------- src/nmea2000_fast_packet_protocol.hpp | 262 ++++--- src/nmea2000_message_definitions.cpp | 18 +- src/nmea2000_message_definitions.hpp | 2 +- src/nmea2000_message_interface.cpp | 52 +- src/nmea2000_message_interface.hpp | 2 +- src/platform_endianness.cpp | 2 +- src/platform_endianness.hpp | 2 +- src/processing_flags.cpp | 2 +- src/processing_flags.hpp | 2 +- src/system_timing.cpp | 2 +- src/system_timing.hpp | 2 +- src/thread_synchronization.hpp | 167 +++- src/to_string.hpp | 2 +- 92 files changed, 3654 insertions(+), 2603 deletions(-) delete mode 100644 src/can_address_claim_state_machine.cpp delete mode 100644 src/can_address_claim_state_machine.hpp delete mode 100644 src/can_protocol.cpp delete mode 100644 src/can_protocol.hpp create mode 100644 src/isobus_heartbeat.cpp create mode 100644 src/isobus_heartbeat.hpp create mode 100644 src/isobus_time_date_interface.cpp create mode 100644 src/isobus_time_date_interface.hpp create mode 100644 src/isobus_virtual_terminal_base.hpp diff --git a/examples/VirtualTerminal/VirtualTerminal.ino b/examples/VirtualTerminal/VirtualTerminal.ino index 49acd6c..7a3e264 100644 --- a/examples/VirtualTerminal/VirtualTerminal.ino +++ b/examples/VirtualTerminal/VirtualTerminal.ino @@ -121,9 +121,9 @@ void setup() { deviceNAME.set_ecu_instance(0); deviceNAME.set_function_instance(0); deviceNAME.set_device_class_instance(0); - deviceNAME.set_manufacturer_code(64); - // Change 0x81 to be your preferred address, but 0x81 is the base arbitrary address - ISOBUSControlFunction = InternalControlFunction::create(deviceNAME, 0x81, 0); + deviceNAME.set_manufacturer_code(1407); // This is the Open-Agriculture manufacturer code. You are welcome to use it if you want. + // If you want to set a preferred address, you can add another parameter below, like (deviceNAME, 0, 0x81), otherwise the CAN stack will choose one automatically. + ISOBUSControlFunction = CANNetworkManager::CANNetwork.create_internal_control_function(deviceNAME, 0); ISOBUSDiagnostics = std::make_shared(ISOBUSControlFunction); ISOBUSDiagnostics->initialize(); @@ -142,11 +142,11 @@ void setup() { // Set up virtual terminal client const NAMEFilter filterVirtualTerminal(NAME::NAMEParameters::FunctionCode, static_cast(NAME::Function::VirtualTerminal)); const std::vector vtNameFilters = { filterVirtualTerminal }; - auto TestPartnerVT = PartneredControlFunction::create(0, vtNameFilters); + auto TestPartnerVT = CANNetworkManager::CANNetwork.create_partnered_control_function(0, vtNameFilters); ExampleVirtualTerminalClient = std::make_shared(TestPartnerVT, ISOBUSControlFunction); ExampleVirtualTerminalClient->set_object_pool(0, VT3TestPool, sizeof(VT3TestPool), "AIS1"); ExampleVirtualTerminalClient->get_vt_button_event_dispatcher().add_listener(handleVTKeyEvents); - ExampleVirtualTerminalClient->get_vt_button_event_dispatcher().add_listener(handleVTKeyEvents); + ExampleVirtualTerminalClient->get_vt_soft_key_event_dispatcher().add_listener(handleVTKeyEvents); ExampleVirtualTerminalClient->initialize(false); } diff --git a/src/AgIsoStack.hpp b/src/AgIsoStack.hpp index 25f560e..9edd65b 100644 --- a/src/AgIsoStack.hpp +++ b/src/AgIsoStack.hpp @@ -1,7 +1,7 @@ /******************************************************************************* ** @file AgIsoStack.hpp ** @author Automatic Code Generation -** @date February 08, 2024 at 19:36:28 +** @date October 19, 2024 at 23:45:04 ** @brief Includes all important files in the AgIsoStack library. ** ** Copyright 2024 The AgIsoStack++ Developers @@ -10,62 +10,63 @@ #ifndef AG_ISO_STACK_HPP #define AG_ISO_STACK_HPP -#include -#include +#include +#include #include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include -#include #include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include #include -#include +#include +#include #include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include #include -#include -#include -#include +#include +#include +#include #include #include -#include +#include #include -#include +#include #include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include #include -#include -#include -#include -#include -#include -#include -#include +#include #include -#include -#include +#include +#include +#include +#include +#include +#include +#include #endif // AG_ISO_STACK_HPP diff --git a/src/can_NAME.cpp b/src/can_NAME.cpp index 82fc806..1e0a583 100644 --- a/src/can_NAME.cpp +++ b/src/can_NAME.cpp @@ -4,7 +4,7 @@ /// @brief A class that represents a control function's NAME /// @author Adrian Del Grosso /// -/// @copyright 2022 Adrian Del Grosso +/// @copyright 2022 The Open-Agriculture Developers //================================================================================================ #include "can_NAME.hpp" #include "can_stack_logger.hpp" @@ -41,7 +41,7 @@ namespace isobus { if (value > 0x07) { - CANStackLogger::error("[NAME]: Industry group out of range, must be between 0 and 7"); + LOG_ERROR("[NAME]: Industry group out of range, must be between 0 and 7"); } rawName &= ~static_cast(0x7000000000000000); rawName |= (static_cast(value & 0x07) << 60); @@ -56,7 +56,7 @@ namespace isobus { if (value > 0x0F) { - CANStackLogger::error("[NAME]: Device class instance out of range, must be between 0 and 15"); + LOG_ERROR("[NAME]: Device class instance out of range, must be between 0 and 15"); } rawName &= ~static_cast(0xF00000000000000); rawName |= (static_cast(value & 0x0F) << 56); @@ -71,7 +71,7 @@ namespace isobus { if (value > 0x7F) { - CANStackLogger::error("[NAME]: Device class out of range, must be between 0 and 127"); + LOG_ERROR("[NAME]: Device class out of range, must be between 0 and 127"); } rawName &= ~static_cast(0xFE000000000000); rawName |= (static_cast(value & 0x7F) << 49); @@ -97,7 +97,7 @@ namespace isobus { if (value > 0x1F) { - CANStackLogger::error("[NAME]: Function instance out of range, must be between 0 and 31"); + LOG_ERROR("[NAME]: Function instance out of range, must be between 0 and 31"); } rawName &= ~static_cast(0xF800000000); rawName |= (static_cast(value & 0x1F) << 35); @@ -112,7 +112,7 @@ namespace isobus { if (value > 0x07) { - CANStackLogger::error("[NAME]: ECU instance out of range, must be between 0 and 7"); + LOG_ERROR("[NAME]: ECU instance out of range, must be between 0 and 7"); } rawName &= ~static_cast(0x700000000); rawName |= (static_cast(value & 0x07) << 32); @@ -127,7 +127,7 @@ namespace isobus { if (value > 0x07FF) { - CANStackLogger::error("[NAME]: Manufacturer code out of range, must be between 0 and 2047"); + LOG_ERROR("[NAME]: Manufacturer code out of range, must be between 0 and 2047"); } rawName &= ~static_cast(0xFFE00000); rawName |= (static_cast(value & 0x07FF) << 21); @@ -142,7 +142,7 @@ namespace isobus { if (value > 0x001FFFFF) { - CANStackLogger::error("[NAME]: Identity number out of range, must be between 0 and 2097151"); + LOG_ERROR("[NAME]: Identity number out of range, must be between 0 and 2097151"); } rawName &= ~static_cast(0x1FFFFF); rawName |= static_cast(value & 0x1FFFFF); diff --git a/src/can_NAME.hpp b/src/can_NAME.hpp index 365eb6b..c775eb5 100644 --- a/src/can_NAME.hpp +++ b/src/can_NAME.hpp @@ -4,7 +4,7 @@ /// @brief A class that represents a control function's NAME /// @author Adrian Del Grosso /// -/// @copyright 2022 Adrian Del Grosso +/// @copyright 2022 The Open-Agriculture Developers //================================================================================================ #ifndef CAN_NAME_HPP @@ -37,6 +37,21 @@ namespace isobus ArbitraryAddressCapable ///< Defines if this ECU supports address arbitration }; + /// @brief The industry group is part of the ISO NAME. + /// It allocates devices and their functions by a specific industry. + /// Function codes' meanings are defined in relation to industry group and device class. + enum class IndustryGroup : std::uint8_t + { + Global = 0, ///< Global applies to all industries + OnHighwayEquipment = 1, + AgriculturalAndForestryEquipment = 2, + ConstructionEquipment = 3, + Marine = 4, + IndustrialOrProcessControl = 5, + Reserved1 = 6, ///< Reserved for future assignment by SAE. Should not be used. + Reserved2 = 7 ///< Reserved for future assignment by SAE. Should not be used. + }; + /// @brief See ISO11783-1 and www.isobus.net For complete descriptions of the ISO NAME function codes /// @note Functions are defined in relation to industry group and device class. See www.isobus.net for more info. enum class Function : std::uint8_t @@ -341,6 +356,72 @@ namespace isobus MaxFunctionCode = 255 ///< Max allocated function code }; + /// @brief The device class is part of the ISO NAME and is known in J1939 as the "vehicle system". + /// This is a 7-bit field defined and assigned by SAE. + /// Device class provides a common name for a group of functions + /// within a connected network. + enum class DeviceClass + { + NonSpecific = 0, + Tractor = 1, ///< Industry Group 1 and 2 + SkidSteerLoader = 1, ///< Industry Group 3 + Trailer = 2, ///< Industry group 1 and 2 + ArticulatedDumpTruck = 2, ///< Industry group 3 + SecondaryTillage = 3, ///< Industry group 2 + Backhoe = 3, ///< Industry group 3 + PlanterSeeder = 4, ///< Industry group 2 + Crawler = 4, ///< Industry group 3 + Fertilizer = 5, ///< Industry group 2 + Excavator = 5, ///< Industry group 3 + Sprayer = 6, ///< Industry group 2 + Forklift = 6, ///< Industry group 3 + Harvester = 7, ///< Industry group 2 + FourWheelDriveLoader = 7, ///< Industry group 3 + RootHarvester = 8, ///< Industry group 2 + Grader = 8, ///< Industry group 3 + Forage = 9, ///< Industry group 2 + MillingMachine = 9, ///< Industry group 3 + Irrigation = 10, ///< Industry group 2 + RecyclerAndSoilStabilizer = 10, ///< Industry group 3 + SystemTools = 10, ///< Industry group 4 + TransportTrailer = 11, ///< Industry group 2 + BindingAgentSpreader = 11, ///< Industry group 3 + FarmYardOperations = 12, ///< Industry group 2 + Paver = 12, ///< Industry group 3 + PoweredAuxiliaryDevices = 13, ///< Industry group 2 + Feeder = 13, ///< Industry group 3 + SpecialCrops = 14, ///< Industry group 2 + ScreeningPlant = 14, ///< Industry group 3 + Earthwork = 15, ///< Industry group 2 + Stacker = 15, ///< Industry group 3 + Skidder = 16, ///< Industry group 2 + Roller = 16, ///< Industry group 3 + SensorSystems = 17, ///< Industry group 2 + Crusher = 17, ///< Industry group 3 + TimberHarvester = 19, ///< Industry group 2 + Forwarder = 20, ///< Industry group 2 + SafetySystems = 20, ///< Industry group 4 + TimberLoader = 21, ///< Industry group 2 + TimberProcessor = 22, ///< Industry group 2 + Mulcher = 23, ///< Industry group 2 + UtilityVehicle = 24, ///< Industry group 2 + SlurryManureApplicator = 25, ///< Industry group 2 + Gateway = 25, ///< Industry group 4 + FeederMixer = 26, ///< Industry group 2 + WeederNonChemical = 27, ///< Industry group 2 + TurfOrLawnCareMower = 28, ///< Industry group 2 + ProductMaterialHandling = 29, ///< Industry group 2 + PowerManagementAndLightingSystem = 30, ///< Industry group 4 + SteeringSystems = 40, ///< Industry group 4 + PropulsionSystems = 50, ///< Industry group 4 + NavigationSystems = 60, ///< Industry group 4 + CommunicationsSystems = 70, ///< Industry group 4 + InstrumentationOrGeneral = 80, ///< Industry group 4 + EnvironmentalHVACSystem = 90, ///< Industry group 4 + DeckCargoOrFishingEquipment = 100, ///< Industry group 4 + NotAvailable = 127 //< Applicable to all IGs + }; + /// @brief A useful way to compare session objects to each other for equality /// @param[in] obj The rhs of the operator /// @returns `true` if the objects are "equal" diff --git a/src/can_NAME_filter.cpp b/src/can_NAME_filter.cpp index 3b38f43..0894a2f 100644 --- a/src/can_NAME_filter.cpp +++ b/src/can_NAME_filter.cpp @@ -5,7 +5,7 @@ /// ECU you want to talk to when creating a partnered control function. /// @author Adrian Del Grosso /// -/// @copyright 2022 Adrian Del Grosso +/// @copyright 2022 The Open-Agriculture Developers //================================================================================================ #include "can_NAME_filter.hpp" diff --git a/src/can_NAME_filter.hpp b/src/can_NAME_filter.hpp index 2858107..43cd0c7 100644 --- a/src/can_NAME_filter.hpp +++ b/src/can_NAME_filter.hpp @@ -5,7 +5,7 @@ /// ECU you want to talk to when creating a partnered control function. /// @author Adrian Del Grosso /// -/// @copyright 2022 Adrian Del Grosso +/// @copyright 2022 The Open-Agriculture Developers //================================================================================================ #ifndef CAN_NAME_FILTER_HPP diff --git a/src/can_address_claim_state_machine.cpp b/src/can_address_claim_state_machine.cpp deleted file mode 100644 index 8342127..0000000 --- a/src/can_address_claim_state_machine.cpp +++ /dev/null @@ -1,387 +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 "can_address_claim_state_machine.hpp" -#include "can_general_parameter_group_numbers.hpp" -#include "can_network_manager.hpp" -#include "can_stack_logger.hpp" -#include "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(m_preferredAddress != NULL_CAN_ADDRESS); - assert(portIndex < CAN_PORT_MAXIMUM); - 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()) - { - CANStackLogger::warn("[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()) - { - CANStackLogger::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); - CANStackLogger::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); - CANStackLogger::info("[AC]: Our address was commanded to a new value of %u, and an ECU at the target address is being evicted.", commandedAddress); - } - else - { - CANStackLogger::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 = CANNetworkManager::CANNetwork.get_control_function(m_portIndex, m_preferredAddress, {}); - // Time to find a free address - if (nullptr == deviceAtOurPreferredAddress) - { - // 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 (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)) - { - CANStackLogger::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 <= 247; i++) - { - if ((nullptr == CANNetworkManager::CANNetwork.get_control_function(m_portIndex, i, {})) && (send_address_claim(i))) - { - addressFound = true; - CANStackLogger::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) - { - CANStackLogger::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; - CANStackLogger::warn("[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/src/can_address_claim_state_machine.hpp b/src/can_address_claim_state_machine.hpp deleted file mode 100644 index 959bbad..0000000 --- a/src/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 "can_NAME.hpp" -#include "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/src/can_badge.hpp b/src/can_badge.hpp index 0c67939..763e746 100644 --- a/src/can_badge.hpp +++ b/src/can_badge.hpp @@ -5,7 +5,7 @@ /// at compile time. A neat trick from Serenity OS :^) /// @author Adrian Del Grosso /// -/// @copyright 2022 Adrian Del Grosso +/// @copyright 2022 The Open-Agriculture Developers //================================================================================================ #ifndef CAN_BADGE_HPP diff --git a/src/can_callbacks.cpp b/src/can_callbacks.cpp index 5dc2dc6..53cf39f 100644 --- a/src/can_callbacks.cpp +++ b/src/can_callbacks.cpp @@ -4,62 +4,45 @@ /// @brief An object to represent common callbacks used within this CAN stack. /// @author Adrian Del Grosso /// -/// @copyright 2022 Adrian Del Grosso +/// @copyright 2022 The Open-Agriculture Developers //================================================================================================ #include "can_callbacks.hpp" namespace isobus { ParameterGroupNumberCallbackData::ParameterGroupNumberCallbackData(std::uint32_t parameterGroupNumber, CANLibCallback callback, void *parentPointer, std::shared_ptr internalControlFunction) : - mCallback(callback), - mParameterGroupNumber(parameterGroupNumber), - mParent(parentPointer), - mInternalControlFunctionFilter(internalControlFunction) - { - } - - ParameterGroupNumberCallbackData::ParameterGroupNumberCallbackData(const ParameterGroupNumberCallbackData &oldObj) : - mCallback(oldObj.mCallback), - mParameterGroupNumber(oldObj.mParameterGroupNumber), - mParent(oldObj.mParent), - mInternalControlFunctionFilter(oldObj.mInternalControlFunctionFilter) + callback(callback), + parameterGroupNumber(parameterGroupNumber), + parent(parentPointer), + internalControlFunctionFilter(internalControlFunction) { } bool ParameterGroupNumberCallbackData::operator==(const ParameterGroupNumberCallbackData &obj) const { - return ((obj.mCallback == this->mCallback) && - (obj.mParameterGroupNumber == this->mParameterGroupNumber) && - (obj.mParent == this->mParent) && - (obj.mInternalControlFunctionFilter == this->mInternalControlFunctionFilter)); - } - - ParameterGroupNumberCallbackData &ParameterGroupNumberCallbackData::operator=(const ParameterGroupNumberCallbackData &obj) - { - mCallback = obj.mCallback; - mParameterGroupNumber = obj.mParameterGroupNumber; - mParent = obj.mParent; - mInternalControlFunctionFilter = obj.mInternalControlFunctionFilter; - return *this; + return ((obj.callback == this->callback) && + (obj.parameterGroupNumber == this->parameterGroupNumber) && + (obj.parent == this->parent) && + (obj.internalControlFunctionFilter == this->internalControlFunctionFilter)); } std::uint32_t ParameterGroupNumberCallbackData::get_parameter_group_number() const { - return mParameterGroupNumber; + return parameterGroupNumber; } CANLibCallback ParameterGroupNumberCallbackData::get_callback() const { - return mCallback; + return callback; } void *ParameterGroupNumberCallbackData::get_parent() const { - return mParent; + return parent; } std::shared_ptr ParameterGroupNumberCallbackData::get_internal_control_function() const { - return mInternalControlFunctionFilter; + return internalControlFunctionFilter; } } // namespace isobus diff --git a/src/can_callbacks.hpp b/src/can_callbacks.hpp index 87fbc1a..de6b4bf 100644 --- a/src/can_callbacks.hpp +++ b/src/can_callbacks.hpp @@ -4,7 +4,7 @@ /// @brief An object to represent common callbacks used within this CAN stack. /// @author Adrian Del Grosso /// -/// @copyright 2022 Adrian Del Grosso +/// @copyright 2022 The Open-Agriculture Developers //================================================================================================ #ifndef CAN_CALLBACKS_HPP @@ -69,14 +69,11 @@ namespace isobus /// @brief A callback for handling a request for repetition rate for a specific PGN using PGNRequestForRepetitionRateCallback = bool (*)(std::uint32_t parameterGroupNumber, std::shared_ptr requestingControlFunction, + std::shared_ptr targetControlFunction, std::uint32_t repetitionRate, void *parentPointer); - //================================================================================================ - /// @class ParameterGroupNumberCallbackData - /// - /// @brief A storage class to hold data about PGN callbacks. - //================================================================================================ + /// @brief A storage class to hold data about callbacks for a specific PGN class ParameterGroupNumberCallbackData { public: @@ -87,20 +84,11 @@ namespace isobus /// @param[in] internalControlFunction An internal control function to use as an additional filter for the callback ParameterGroupNumberCallbackData(std::uint32_t parameterGroupNumber, CANLibCallback callback, void *parentPointer, std::shared_ptr internalControlFunction); - /// @brief A copy constructor for holding callback data - /// @param[in] oldObj The object to copy from - ParameterGroupNumberCallbackData(const ParameterGroupNumberCallbackData &oldObj); - /// @brief Equality operator for this class /// @param[in] obj The object to check equality against /// @returns true if the objects have equivalent data bool operator==(const ParameterGroupNumberCallbackData &obj) const; - /// @brief Assignment operator for this class - /// @param[in] obj The object to assign data from - /// @returns The lhs of the operator - ParameterGroupNumberCallbackData &operator=(const ParameterGroupNumberCallbackData &obj); - /// @brief Returns the PGN associated with this callback data /// @returns The PGN associated with this callback data std::uint32_t get_parameter_group_number() const; @@ -118,10 +106,10 @@ namespace isobus std::shared_ptr get_internal_control_function() const; private: - CANLibCallback mCallback; ///< The callback that will get called when a matching PGN is received - std::uint32_t mParameterGroupNumber; ///< The PGN assocuiated with this callback - void *mParent; ///< A generic variable that can provide context to which object the callback was meant for - std::shared_ptr mInternalControlFunctionFilter; ///< An optional way to filter callbacks based on the destination of messages from the partner + CANLibCallback callback; ///< The callback that will get called when a matching PGN is received + std::uint32_t parameterGroupNumber; ///< The PGN assocuiated with this callback + void *parent; ///< A generic variable that can provide context to which object the callback was meant for + std::shared_ptr internalControlFunctionFilter; ///< An optional way to filter callbacks based on the destination of messages from the partner }; } // namespace isobus diff --git a/src/can_constants.hpp b/src/can_constants.hpp index d1b4772..9ac4ce9 100644 --- a/src/can_constants.hpp +++ b/src/can_constants.hpp @@ -4,7 +4,7 @@ /// @brief General constants used throughout this library /// @author Adrian Del Grosso /// -/// @copyright 2022 Adrian Del Grosso +/// @copyright 2022 The Open-Agriculture Developers //================================================================================================ #ifndef CAN_CONSTANTS_HPP #define CAN_CONSTANTS_HPP diff --git a/src/can_control_function.cpp b/src/can_control_function.cpp index 8ebc076..d205be1 100644 --- a/src/can_control_function.cpp +++ b/src/can_control_function.cpp @@ -5,21 +5,18 @@ /// @author Adrian Del Grosso /// @author Daan Steenbergen /// -/// @copyright 2022 Adrian Del Grosso +/// @copyright 2022 The Open-Agriculture Developers //================================================================================================ #include "can_control_function.hpp" #include "can_constants.hpp" -#include "can_network_manager.hpp" #include namespace isobus { -#if !defined CAN_STACK_DISABLE_THREADS && !defined ARDUINO - std::mutex ControlFunction::controlFunctionProcessingMutex; -#endif + Mutex ControlFunction::controlFunctionProcessingMutex; isobus::ControlFunction::ControlFunction(NAME NAMEValue, std::uint8_t addressValue, std::uint8_t CANPort, Type type) : controlFunctionType(type), @@ -29,25 +26,6 @@ namespace isobus { } - std::shared_ptr ControlFunction::create(NAME NAMEValue, std::uint8_t addressValue, std::uint8_t CANPort) - { - // Unfortunately, we can't use `std::make_shared` here because the constructor is private - auto controlFunction = std::shared_ptr(new ControlFunction(NAMEValue, addressValue, CANPort)); - CANNetworkManager::CANNetwork.on_control_function_created(controlFunction, CANLibBadge()); - return controlFunction; - } - - bool ControlFunction::destroy(std::uint32_t expectedRefCount) - { -#if !defined CAN_STACK_DISABLE_THREADS && !defined ARDUINO - std::lock_guard lock(controlFunctionProcessingMutex); -#endif - - CANNetworkManager::CANNetwork.on_control_function_destroyed(shared_from_this(), {}); - - return static_cast(shared_from_this().use_count()) == expectedRefCount + 1; - } - std::uint8_t ControlFunction::get_address() const { return address; diff --git a/src/can_control_function.hpp b/src/can_control_function.hpp index aaa2f7f..474ed41 100644 --- a/src/can_control_function.hpp +++ b/src/can_control_function.hpp @@ -5,28 +5,22 @@ /// @author Adrian Del Grosso /// @author Daan Steenbergen /// -/// @copyright 2022 Adrian Del Grosso +/// @copyright 2024 The Open-Agriculture Developers //================================================================================================ #ifndef CAN_CONTROL_FUNCTION_HPP #define CAN_CONTROL_FUNCTION_HPP #include "can_NAME.hpp" +#include "thread_synchronization.hpp" #include - -#if !defined CAN_STACK_DISABLE_THREADS && !defined ARDUINO -#include -#endif +#include namespace isobus { - //================================================================================================ - /// @class ControlFunction - /// /// @brief A class that describes an ISO11783 control function, which includes a NAME and address. - //================================================================================================ - class ControlFunction : public std::enable_shared_from_this + class ControlFunction { public: /// @brief The type of the control function @@ -37,19 +31,15 @@ namespace isobus Partnered ///< An external control function that you explicitly want to talk to }; - virtual ~ControlFunction() = default; - - /// @brief The factory function to construct a control function + /// @brief The constructor of a control function. In most cases use `CANNetworkManager::create_internal_control_function()` or + /// `CANNetworkManager::create_partnered_control_function()` instead, only use this constructor if you have advanced needs. /// @param[in] NAMEValue The NAME of the control function /// @param[in] addressValue The current address of the control function /// @param[in] CANPort The CAN channel index that the control function communicates on - /// @returns A shared pointer to a ControlFunction object created with the parameters passed in - static std::shared_ptr create(NAME NAMEValue, std::uint8_t addressValue, std::uint8_t CANPort); + /// @param[in] type The 'Type' of control function to create + ControlFunction(NAME NAMEValue, std::uint8_t addressValue, std::uint8_t CANPort, Type type = Type::External); - /// @brief Destroys this control function, by removing it from the network manager - /// @param[in] expectedRefCount The expected number of shared pointers to this control function after removal - /// @returns true if the control function was successfully removed from everywhere in the stack, otherwise false - virtual bool destroy(std::uint32_t expectedRefCount = 1); + virtual ~ControlFunction() = default; /// @brief Returns the current address of the control function /// @returns The current address of the control function @@ -76,17 +66,8 @@ namespace isobus std::string get_type_string() const; protected: - /// @brief The protected constructor for the control function, which is called by the (inherited) factory function - /// @param[in] NAMEValue The NAME of the control function - /// @param[in] addressValue The current address of the control function - /// @param[in] CANPort The CAN channel index that the control function communicates on - /// @param[in] type The 'Type' of control function to create - ControlFunction(NAME NAMEValue, std::uint8_t addressValue, std::uint8_t CANPort, Type type = Type::External); - friend class CANNetworkManager; ///< The network manager needs access to the control function's internals -#if !defined CAN_STACK_DISABLE_THREADS && !defined ARDUINO - static std::mutex controlFunctionProcessingMutex; ///< Protects the control function tables -#endif + static Mutex controlFunctionProcessingMutex; ///< Protects the control function tables const Type controlFunctionType; ///< The Type of the control function NAME controlFunctionNAME; ///< The NAME of the control function bool claimedAddressSinceLastAddressClaimRequest = false; ///< Used to mark CFs as stale if they don't claim within a certain time diff --git a/src/can_extended_transport_protocol.cpp b/src/can_extended_transport_protocol.cpp index b541e7e..f919710 100644 --- a/src/can_extended_transport_protocol.cpp +++ b/src/can_extended_transport_protocol.cpp @@ -136,7 +136,8 @@ namespace isobus if (activeSessions.size() >= configuration->get_max_number_transport_protocol_sessions()) { // TODO: consider using maximum memory instead of maximum number of sessions - CANStackLogger::warn("[ETP]: Replying with abort to Request To Send (RTS) for 0x%05X, configured maximum number of sessions reached.", parameterGroupNumber); + LOG_WARNING("[ETP]: Replying with abort to Request To Send (RTS) for 0x%05X, configured maximum number of sessions reached.", + parameterGroupNumber); send_abort(std::static_pointer_cast(destination), source, parameterGroupNumber, ConnectionAbortReason::AlreadyInCMSession); } else @@ -146,12 +147,14 @@ namespace isobus { if (oldSession->get_parameter_group_number() != parameterGroupNumber) { - CANStackLogger::error("[ETP]: Received Request To Send (RTS) while a session already existed for this source and destination, aborting for 0x%05X...", parameterGroupNumber); + LOG_ERROR("[ETP]: Received Request To Send (RTS) while a session already existed for this source and destination, aborting for 0x%05X...", + parameterGroupNumber); abort_session(oldSession, ConnectionAbortReason::AlreadyInCMSession); } else { - CANStackLogger::warn("[ETP]: Received Request To Send (RTS) while a session already existed for this source and destination and parameterGroupNumber, overwriting for 0x%05X...", parameterGroupNumber); + LOG_WARNING("[ETP]: Received Request To Send (RTS) while a session already existed for this source and destination and parameterGroupNumber, overwriting for 0x%05X...", + parameterGroupNumber); close_session(oldSession, false); } } @@ -170,7 +173,7 @@ namespace isobus newSession->set_state(StateMachineState::SendClearToSend); activeSessions.push_back(newSession); - CANStackLogger::debug("[ETP]: New rx session for 0x%05X. Source: %hu, destination: %hu", parameterGroupNumber, source->get_address(), destination->get_address()); + LOG_DEBUG("[ETP]: New rx session for 0x%05X. Source: %hu, destination: %hu", parameterGroupNumber, source->get_address(), destination->get_address()); update_state_machine(newSession); } } @@ -186,19 +189,19 @@ namespace isobus { if (session->get_parameter_group_number() != parameterGroupNumber) { - CANStackLogger::error("[ETP]: Received a Clear To Send (CTS) message for 0x%05X while a session already existed for this source and destination, sending abort for both...", parameterGroupNumber); + LOG_ERROR("[ETP]: Received a Clear To Send (CTS) message for 0x%05X while a session already existed for this source and destination, sending abort for both...", parameterGroupNumber); abort_session(session, ConnectionAbortReason::ClearToSendReceivedWhileTransferInProgress); send_abort(std::static_pointer_cast(destination), source, parameterGroupNumber, ConnectionAbortReason::ClearToSendReceivedWhileTransferInProgress); } else if (nextPacketNumber > session->get_total_number_of_packets()) { - CANStackLogger::error("[ETP]: Received a Clear To Send (CTS) message for 0x%05X with a bad sequence number, aborting...", parameterGroupNumber); + LOG_ERROR("[ETP]: Received a Clear To Send (CTS) message for 0x%05X with a bad sequence number, aborting...", parameterGroupNumber); abort_session(session, ConnectionAbortReason::NumberOfClearToSendPacketsExceedsMessage); } else if (StateMachineState::WaitForClearToSend != session->state) { // The session exists, but we're not in the right state to receive a CTS, so we must abort - CANStackLogger::warn("[ETP]: Received a Clear To Send (CTS) message for 0x%05X, but not expecting one, aborting session->", parameterGroupNumber); + LOG_WARNING("[ETP]: Received a Clear To Send (CTS) message for 0x%05X, but not expecting one, aborting session->", parameterGroupNumber); abort_session(session, ConnectionAbortReason::ClearToSendReceivedWhileTransferInProgress); } else @@ -217,7 +220,7 @@ namespace isobus else { // We got a CTS but no session exists, by the standard we must ignore it - CANStackLogger::warn("[ETP]: Received Clear To Send (CTS) for 0x%05X while no session existed for this source and destination, ignoring...", parameterGroupNumber); + LOG_WARNING("[ETP]: Received Clear To Send (CTS) for 0x%05X while no session existed for this source and destination, ignoring...", parameterGroupNumber); } } @@ -232,24 +235,24 @@ namespace isobus { if (session->get_parameter_group_number() != parameterGroupNumber) { - CANStackLogger::error("[ETP]: Received a Data Packet Offset message for 0x%05X while a session already existed for this source and destination with a different PGN, sending abort for both...", parameterGroupNumber); + LOG_ERROR("[ETP]: Received a Data Packet Offset message for 0x%05X while a session already existed for this source and destination with a different PGN, sending abort for both...", parameterGroupNumber); abort_session(session, ConnectionAbortReason::UnexpectedDataPacketOffsetPGN); send_abort(std::static_pointer_cast(destination), source, parameterGroupNumber, ConnectionAbortReason::UnexpectedDataPacketOffsetPGN); } else if (StateMachineState::WaitForDataPacketOffset != session->state) { // The session exists, but we're not in the right state to receive a DPO, so we must abort - CANStackLogger::warn("[ETP]: Received a Data Packet Offset message for 0x%05X, but not expecting one, aborting session->", parameterGroupNumber); + LOG_WARNING("[ETP]: Received a Data Packet Offset message for 0x%05X, but not expecting one, aborting session->", parameterGroupNumber); abort_session(session, ConnectionAbortReason::UnexpectedDataPacketOffsetReceived); } else if (numberOfPackets > session->get_cts_number_of_packet_limit()) { - CANStackLogger::error("[ETP]: Received a Data Packet Offset message for 0x%05X with an higher number of packets than our CTS, aborting...", parameterGroupNumber); + LOG_ERROR("[ETP]: Received a Data Packet Offset message for 0x%05X with an higher number of packets than our CTS, aborting...", parameterGroupNumber); abort_session(session, ConnectionAbortReason::DataPacketOffsetExceedsClearToSend); } else if (packetOffset != session->get_last_acknowledged_packet_number()) { - CANStackLogger::error("[ETP]: Received a Data Packet Offset message for 0x%05X with a bad sequence number, aborting...", parameterGroupNumber); + LOG_ERROR("[ETP]: Received a Data Packet Offset message for 0x%05X with a bad sequence number, aborting...", parameterGroupNumber); abort_session(session, ConnectionAbortReason::BadDataPacketOffset); } else @@ -269,7 +272,7 @@ namespace isobus else { // We got a CTS but no session exists, by the standard we must ignore it - CANStackLogger::warn("[ETP]: Received Data Packet Offset for 0x%05X while no session existed for this source and destination, ignoring...", parameterGroupNumber); + LOG_WARNING("[ETP]: Received Data Packet Offset for 0x%05X while no session existed for this source and destination, ignoring...", parameterGroupNumber); } } @@ -286,17 +289,17 @@ namespace isobus session->state = StateMachineState::None; bool successful = (numberOfBytesTransferred == session->get_message_length()); close_session(session, successful); - CANStackLogger::debug("[ETP]: Completed tx session for 0x%05X from %hu", parameterGroupNumber, source->get_address()); + LOG_DEBUG("[ETP]: Completed tx session for 0x%05X from %hu (successful=%s)", parameterGroupNumber, source->get_address(), successful ? "true" : "false"); } else { // The session exists, but we're not in the right state to receive an EOM, by the standard we must ignore it - CANStackLogger::warn("[ETP]: Received an End Of Message Acknowledgement message for 0x%05X, but not expecting one, ignoring.", parameterGroupNumber); + LOG_WARNING("[ETP]: Received an End Of Message Acknowledgement message for 0x%05X, but not expecting one, ignoring.", parameterGroupNumber); } } else { - CANStackLogger::warn("[ETP]: Received End Of Message Acknowledgement for 0x%05X while no session existed for this source and destination, ignoring.", parameterGroupNumber); + LOG_WARNING("[ETP]: Received End Of Message Acknowledgement for 0x%05X while no session existed for this source and destination, ignoring.", parameterGroupNumber); } } @@ -311,20 +314,20 @@ namespace isobus if ((nullptr != session) && (session->get_parameter_group_number() == parameterGroupNumber)) { foundSession = true; - CANStackLogger::error("[ETP]: Received an abort (reason=%hu) for an rx session for parameterGroupNumber 0x%05X", static_cast(reason), parameterGroupNumber); + LOG_ERROR("[ETP]: Received an abort (reason=%hu) for an rx session for parameterGroupNumber 0x%05X", static_cast(reason), parameterGroupNumber); close_session(session, false); } session = get_session(destination, source); if ((nullptr != session) && (session->get_parameter_group_number() == parameterGroupNumber)) { foundSession = true; - CANStackLogger::error("[ETP]: Received an abort (reason=%hu) for a tx session for parameterGroupNumber 0x%05X", static_cast(reason), parameterGroupNumber); + LOG_ERROR("[ETP]: Received an abort (reason=%hu) for a tx session for parameterGroupNumber 0x%05X", static_cast(reason), parameterGroupNumber); close_session(session, false); } if (!foundSession) { - CANStackLogger::warn("[ETP]: Received an abort (reason=%hu) with no matching session for parameterGroupNumber 0x%05X", static_cast(reason), parameterGroupNumber); + LOG_WARNING("[ETP]: Received an abort (reason=%hu) with no matching session for parameterGroupNumber 0x%05X", static_cast(reason), parameterGroupNumber); } } @@ -332,7 +335,7 @@ namespace isobus { if (CAN_DATA_LENGTH != message.get_data_length()) { - CANStackLogger::warn("[ETP]: Received a Connection Management message of invalid length %hu", message.get_data_length()); + LOG_WARNING("[ETP]: Received a Connection Management message of invalid length %hu", message.get_data_length()); return; } @@ -396,7 +399,7 @@ namespace isobus default: { - CANStackLogger::warn("[ETP]: Bad Mux in Transport Protocol Connection Management message"); + LOG_WARNING("[ETP]: Bad Mux in Transport Protocol Connection Management message"); } break; } @@ -406,7 +409,7 @@ namespace isobus { if (CAN_DATA_LENGTH != message.get_data_length()) { - CANStackLogger::warn("[ETP]: Received a Data Transfer message of invalid length %hu", message.get_data_length()); + LOG_WARNING("[ETP]: Received a Data Transfer message of invalid length %hu", message.get_data_length()); return; } @@ -420,12 +423,12 @@ namespace isobus { if (StateMachineState::WaitForDataTransferPacket != session->state) { - CANStackLogger::warn("[ETP]: Received a Data Transfer message from %hu while not expecting one, sending abort", source->get_address()); + LOG_WARNING("[ETP]: Received a Data Transfer message from %hu while not expecting one, sending abort", source->get_address()); abort_session(session, ConnectionAbortReason::UnexpectedDataTransferPacketReceived); } else if (sequenceNumber == session->get_last_sequence_number()) { - CANStackLogger::error("[ETP]: Aborting rx session for 0x%05X due to duplicate sequence number", session->get_parameter_group_number()); + LOG_ERROR("[ETP]: Aborting rx session for 0x%05X due to duplicate sequence number", session->get_parameter_group_number()); abort_session(session, ConnectionAbortReason::DuplicateSequenceNumber); } else if (sequenceNumber == (session->get_last_sequence_number() + 1)) @@ -469,7 +472,7 @@ namespace isobus canMessageReceivedCallback(completedMessage); close_session(session, true); - CANStackLogger::debug("[ETP]: Completed rx session for 0x%05X from %hu", session->get_parameter_group_number(), source->get_address()); + LOG_DEBUG("[ETP]: Completed rx session for 0x%05X from %hu", session->get_parameter_group_number(), source->get_address()); } else if (session->get_dpo_number_of_packets_remaining() == 0) { @@ -479,7 +482,7 @@ namespace isobus } else { - CANStackLogger::error("[ETP]: Aborting rx session for 0x%05X due to bad sequence number", session->get_parameter_group_number()); + LOG_ERROR("[ETP]: Aborting rx session for 0x%05X due to bad sequence number", session->get_parameter_group_number()); abort_session(session, ConnectionAbortReason::BadSequenceNumber); } } @@ -545,10 +548,10 @@ namespace isobus sessionCompleteCallback, parentPointer); session->set_state(StateMachineState::SendRequestToSend); - CANStackLogger::debug("[ETP]: New tx session for 0x%05X. Source: %hu, destination: %hu", - parameterGroupNumber, - source->get_address(), - destination->get_address()); + LOG_DEBUG("[ETP]: New tx session for 0x%05X. Source: %hu, destination: %hu", + parameterGroupNumber, + source->get_address(), + destination->get_address()); activeSessions.push_back(session); update_state_machine(session); @@ -563,12 +566,12 @@ namespace isobus auto session = activeSessions.at(i - 1); if (!session->get_source()->get_address_valid()) { - CANStackLogger::warn("[ETP]: Closing active session as the source control function is no longer valid"); + LOG_WARNING("[ETP]: Closing active session as the source control function is no longer valid"); close_session(session, false); } else if (!session->get_destination()->get_address_valid()) { - CANStackLogger::warn("[ETP]: Closing active session as the destination control function is no longer valid"); + LOG_WARNING("[ETP]: Closing active session as the destination control function is no longer valid"); close_session(session, false); } else if (StateMachineState::None != session->state) @@ -651,7 +654,7 @@ namespace isobus { if (session->get_time_since_last_update() > T2_T3_TIMEOUT_MS) { - CANStackLogger::error("[ETP]: Timeout tx session for 0x%05X (expected CTS)", session->get_parameter_group_number()); + LOG_ERROR("[ETP]: Timeout tx session for 0x%05X (expected CTS)", session->get_parameter_group_number()); if (session->get_cts_number_of_packet_limit() > 0) { // A connection is only considered established if we've received at least one CTS before @@ -679,7 +682,7 @@ namespace isobus { if (session->get_time_since_last_update() > T2_T3_TIMEOUT_MS) { - CANStackLogger::error("[ETP]: Timeout rx session for 0x%05X (expected DPO)", session->get_parameter_group_number()); + LOG_ERROR("[ETP]: Timeout rx session for 0x%05X (expected DPO)", session->get_parameter_group_number()); abort_session(session, ConnectionAbortReason::Timeout); } } @@ -704,7 +707,7 @@ namespace isobus { if (session->get_time_since_last_update() > T1_TIMEOUT_MS) { - CANStackLogger::error("[ETP]: Timeout for destination-specific rx session (expected sequential data frame)"); + LOG_ERROR("[ETP]: Timeout for destination-specific rx session (expected sequential data frame)"); abort_session(session, ConnectionAbortReason::Timeout); } } @@ -714,7 +717,7 @@ namespace isobus { if (session->get_time_since_last_update() > T2_T3_TIMEOUT_MS) { - CANStackLogger::error("[ETP]: Timeout tx session for 0x%05X (expected EOMA)", session->get_parameter_group_number()); + LOG_ERROR("[ETP]: Timeout tx session for 0x%05X (expected EOMA)", session->get_parameter_group_number()); abort_session(session, ConnectionAbortReason::Timeout); } } @@ -775,7 +778,7 @@ namespace isobus if (activeSessions.end() != sessionLocation) { activeSessions.erase(sessionLocation); - CANStackLogger::debug("[ETP]: Session Closed"); + LOG_DEBUG("[ETP]: Session Closed"); } } @@ -844,7 +847,9 @@ namespace isobus } if (packetsThisSegment > configuration->get_number_of_packets_per_dpo_message()) { - CANStackLogger::debug("[TP]: Received Request To Send (RTS) with a CTS packet count of %hu, which is greater than the configured maximum of %hu, using the configured maximum instead.", packetsThisSegment, configuration->get_number_of_packets_per_dpo_message()); + LOG_DEBUG("[TP]: Received Request To Send (RTS) with a CTS packet count of %hu, which is greater than the configured maximum of %hu, using the configured maximum instead.", + packetsThisSegment, + configuration->get_number_of_packets_per_dpo_message()); packetsThisSegment = configuration->get_number_of_packets_per_dpo_message(); } @@ -908,7 +913,6 @@ namespace isobus auto result = std::find_if(activeSessions.begin(), activeSessions.end(), [&](const std::shared_ptr &session) { return session->matches(source, destination); }); - // Instead of returning a pointer, we return by reference to indicate it should not be deleted or stored return (activeSessions.end() != result) ? (*result) : nullptr; } diff --git a/src/can_general_parameter_group_numbers.hpp b/src/can_general_parameter_group_numbers.hpp index ebbfb38..2f23869 100644 --- a/src/can_general_parameter_group_numbers.hpp +++ b/src/can_general_parameter_group_numbers.hpp @@ -4,7 +4,7 @@ /// @brief Defines some PGNs that are used in the library or are very common /// @author Adrian Del Grosso /// -/// @copyright 2022 Adrian Del Grosso +/// @copyright 2022 The Open-Agriculture Developers //================================================================================================ #ifndef CAN_GENERAL_PARAMETER_GROUP_NUMBERS_HPP @@ -33,6 +33,7 @@ namespace isobus AddressClaim = 0xEE00, ProprietaryA = 0xEF00, MachineSelectedSpeed = 0xF022, + HeartbeatMessage = 0xF0E4, ProductIdentification = 0xFC8D, ControlFunctionFunctionalities = 0xFC8E, DiagnosticProtocolIdentification = 0xFD32, @@ -50,6 +51,7 @@ namespace isobus CommandedAddress = 0xFED8, SoftwareIdentification = 0xFEDA, AllImplementsStopOperationsSwitchState = 0xFD02, + TimeDate = 0xFEE6, VesselHeading = 0x1F112, RateOfTurn = 0x1F113, PositionRapidUpdate = 0x1F801, diff --git a/src/can_hardware_abstraction.hpp b/src/can_hardware_abstraction.hpp index ff620f6..7839e88 100644 --- a/src/can_hardware_abstraction.hpp +++ b/src/can_hardware_abstraction.hpp @@ -4,7 +4,7 @@ /// @brief An abstraction between this CAN stack and any hardware layer /// @author Adrian Del Grosso /// -/// @copyright 2022 Adrian Del Grosso +/// @copyright 2022 The Open-Agriculture Developers //================================================================================================ #ifndef CAN_HARDWARE_ABSTRACTION_HPP diff --git a/src/can_hardware_plugin.hpp b/src/can_hardware_plugin.hpp index 47ea94b..4f466d5 100644 --- a/src/can_hardware_plugin.hpp +++ b/src/can_hardware_plugin.hpp @@ -4,7 +4,7 @@ /// @brief A base class for a CAN driver. Can be derived into your platform's required interface. /// @author Adrian Del Grosso /// -/// @copyright 2022 Adrian Del Grosso +/// @copyright 2022 The Open-Agriculture Developers //================================================================================================ #ifndef CAN_HARDEWARE_PLUGIN_HPP #define CAN_HARDEWARE_PLUGIN_HPP diff --git a/src/can_identifier.cpp b/src/can_identifier.cpp index 3e91893..ed3cdf8 100644 --- a/src/can_identifier.cpp +++ b/src/can_identifier.cpp @@ -5,7 +5,7 @@ /// values that are encoded inside, along with some helpful constants. /// @author Adrian Del Grosso /// -/// @copyright 2022 Adrian Del Grosso +/// @copyright 2022 The Open-Agriculture Developers //================================================================================================ #include "can_identifier.hpp" diff --git a/src/can_identifier.hpp b/src/can_identifier.hpp index bfcda1b..6e2f238 100644 --- a/src/can_identifier.hpp +++ b/src/can_identifier.hpp @@ -5,7 +5,7 @@ /// values that are encoded inside, along with some helpful constants. /// @author Adrian Del Grosso /// -/// @copyright 2022 Adrian Del Grosso +/// @copyright 2022 The Open-Agriculture Developers //================================================================================================ #ifndef CAN_IDENTIFIER_HPP diff --git a/src/can_internal_control_function.cpp b/src/can_internal_control_function.cpp index ab3fe12..0b1593d 100644 --- a/src/can_internal_control_function.cpp +++ b/src/can_internal_control_function.cpp @@ -6,59 +6,298 @@ /// @author Adrian Del Grosso /// @author Daan Steenbergen /// -/// @copyright 2022 Adrian Del Grosso +/// @copyright 2022 The Open-Agriculture Developers //================================================================================================ #include "can_internal_control_function.hpp" #include "can_constants.hpp" -#include "can_network_manager.hpp" #include "can_parameter_group_number_request_protocol.hpp" +#include "can_stack_logger.hpp" +#include "system_timing.hpp" #include +#include +#include namespace isobus { - InternalControlFunction::InternalControlFunction(NAME desiredName, std::uint8_t preferredAddress, std::uint8_t CANPort, CANLibBadge) : + 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 } - std::shared_ptr InternalControlFunction::create(NAME desiredName, std::uint8_t preferredAddress, std::uint8_t CANPort) + InternalControlFunction::State InternalControlFunction::get_current_state() const { - // Unfortunately, we can't use `std::make_shared` here because the constructor is private - CANLibBadge badge; // This badge is used to allow creation of the PGN request protocol only from within this class - auto controlFunction = std::shared_ptr(new InternalControlFunction(desiredName, preferredAddress, CANPort, badge)); - controlFunction->pgnRequestProtocol.reset(new ParameterGroupNumberRequestProtocol(controlFunction, badge)); - CANNetworkManager::CANNetwork.on_control_function_created(controlFunction, badge); - return controlFunction; + return state; } - bool InternalControlFunction::destroy(std::uint32_t expectedRefCount) + void InternalControlFunction::set_current_state(State value) { - // We need to destroy the PGN request protocol before we destroy the control function - pgnRequestProtocol.reset(); - - return ControlFunction::destroy(expectedRefCount); + stateChangeTimestamp_ms = SystemTiming::get_timestamp_ms(); + state = value; } - void InternalControlFunction::on_address_violation(CANLibBadge) + void InternalControlFunction::process_rx_message_for_address_claiming(const CANMessage &message) { - stateMachine.on_address_violation(); + 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 available dynamic addresses based on industry group + // Ref: https://www.isobus.net/isobus/sourceAddress + static constexpr std::uint8_t START_ADDRESS = 128; + std::uint8_t endAddress = START_ADDRESS; + switch (static_cast(get_NAME().get_industry_group())) + { + case NAME::IndustryGroup::Global: + endAddress = 247; + break; + case NAME::IndustryGroup::OnHighwayEquipment: + endAddress = 158; + break; + case NAME::IndustryGroup::AgriculturalAndForestryEquipment: + endAddress = 235; + break; + case NAME::IndustryGroup::ConstructionEquipment: + case NAME::IndustryGroup::Marine: + case NAME::IndustryGroup::IndustrialOrProcessControl: + endAddress = 207; + break; + + default: + break; + } + + for (std::uint8_t i = START_ADDRESS; i <= endAddress; 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 @@ -66,4 +305,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/src/can_internal_control_function.hpp b/src/can_internal_control_function.hpp index 74a4696..7882c56 100644 --- a/src/can_internal_control_function.hpp +++ b/src/can_internal_control_function.hpp @@ -6,72 +6,119 @@ /// @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 "can_address_claim_state_machine.hpp" -#include "can_badge.hpp" #include "can_control_function.hpp" +#include "can_message.hpp" +#include "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 The factory function to construct an internal control function + /// @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. /// @param[in] desiredName The NAME for this control function to claim as /// @param[in] preferredAddress The preferred NAME for this control function /// @param[in] CANPort The CAN channel index for this control function to use - /// @returns A shared pointer to an InternalControlFunction object created with the parameters passed in - static std::shared_ptr create(NAME desiredName, std::uint8_t preferredAddress, std::uint8_t CANPort); + InternalControlFunction(NAME desiredName, std::uint8_t preferredAddress, std::uint8_t CANPort); - /// @brief Destroys this control function, by removing it from the network manager - /// @param[in] expectedRefCount The expected number of shared pointers to this control function after removal - /// @returns true if the control function was successfully removed from everywhere in the stack, otherwise false - bool destroy(std::uint32_t expectedRefCount = 1) override; + /// @brief Returns the current state of the internal control function + /// @returns The current state + State get_current_state() const; - /// @brief The protected constructor for the internal control function, which is called by the (inherited) factory function - /// @param[in] desiredName The NAME for this control function to claim as - /// @param[in] preferredAddress The preferred NAME for this control function - /// @param[in] CANPort The CAN channel index for this control function to use - InternalControlFunction(NAME desiredName, std::uint8_t preferredAddress, std::uint8_t CANPort, CANLibBadge); + /// @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 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 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 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 Returns the preferred address of the internal control function + /// @returns The preferred address + std::uint8_t get_preferred_address() const; - /// @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 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; - private: - AddressClaimStateMachine stateMachine; ///< The address claimer for this ICF + /// @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: + /// @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 + /// @param[in] value The new state + void set_current_state(State value); + + static constexpr std::uint32_t ADDRESS_CONTENTION_TIME_MS = 250; ///< The time in milliseconds to wait for address contention + + 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/src/can_message.cpp b/src/can_message.cpp index 09d49f5..b00d91f 100644 --- a/src/can_message.cpp +++ b/src/can_message.cpp @@ -5,7 +5,7 @@ /// @author Adrian Del Grosso /// @author Daan Steenbergen /// -/// @copyright 2022 Adrian Del Grosso +/// @copyright 2022 The Open-Agriculture Developers //================================================================================================ #include "can_message.hpp" #include "can_stack_logger.hpp" @@ -62,7 +62,7 @@ namespace isobus std::uint32_t CANMessage::get_data_length() const { - return data.size(); + return static_cast(data.size()); } std::shared_ptr CANMessage::get_source_control_function() const @@ -326,4 +326,54 @@ namespace isobus return (get_uint8_at(byteIndex) & mask) == mask; } + std::uint64_t CANMessage::get_data_custom_length(const std::uint32_t startBitIndex, const std::uint32_t length, const isobus::CANMessage::ByteFormat format) const + { + std::uint64_t retVal = 0; + std::uint8_t currentByte = 0; + std::uint32_t endBitIndex = startBitIndex + length - 1; + std::uint32_t bitCounter = 0; + std::uint32_t amountOfBytesLeft = (length + 8 - 1) / 8; + std::uint32_t startAmountOfBytes = amountOfBytesLeft; + std::uint8_t indexOfFinalByteBit = 7; + + if (endBitIndex > 8 * data.size() || length < 1 || startBitIndex >= 8 * data.size()) + { + LOG_ERROR("End bit index is greater than length or startBitIndex is wrong or startBitIndex is greater than endBitIndex"); + return retVal; + } + + for (auto i = startBitIndex; i <= endBitIndex; i++) + { + auto byteIndex = i / 8; + auto bitIndexWithinByte = i % 8; + auto bit = (data.at(byteIndex) >> (indexOfFinalByteBit - bitIndexWithinByte)) & 1; + if (length - bitCounter < 8) + { + currentByte |= static_cast(bit) << (length - 1 - bitCounter); + } + else + { + currentByte |= static_cast(bit) << (indexOfFinalByteBit - bitIndexWithinByte); + } + + if ((bitCounter + 1) % 8 == 0 || i == endBitIndex) + { + if (ByteFormat::LittleEndian == format) + { + retVal |= (static_cast(currentByte) << (startAmountOfBytes - amountOfBytesLeft) * 8); + } + else + { + retVal |= (static_cast(currentByte) << ((amountOfBytesLeft * 8) - 8)); + } + currentByte = 0; + amountOfBytesLeft--; + } + + bitCounter++; + } + + return retVal; + } + } // namespace isobus diff --git a/src/can_message.hpp b/src/can_message.hpp index a00966f..1e51875 100644 --- a/src/can_message.hpp +++ b/src/can_message.hpp @@ -5,12 +5,13 @@ /// @author Adrian Del Grosso /// @author Daan Steenbergen /// -/// @copyright 2022 Adrian Del Grosso +/// @copyright 2022 The Open-Agriculture Developers //================================================================================================ #ifndef CAN_MESSAGE_HPP #define CAN_MESSAGE_HPP +#include "can_constants.hpp" #include "can_control_function.hpp" #include "can_general_parameter_group_numbers.hpp" #include "can_identifier.hpp" @@ -248,6 +249,16 @@ namespace isobus /// @return True if (all) the bit(s) are set, false otherwise bool get_bool_at(const std::uint32_t byteIndex, const std::uint8_t bitIndex, const std::uint8_t length = 1) const; + /// @brief Get a 64-bit unsinged integer from the buffer at a specific index but custom length + /// Why 64 bit? Because we do not know the length and it could be 10 bits or 54 so better to convert everything into 64 bit + /// @details This function will return 8 bytes at a specified index in the buffer but custom bit length + /// We are iterating by full bytes (assembling a full byte) and shifting it into the final 64-bit value to return + /// @param[in] startBitIndex The index to get the 64-bit unsigned integer from + /// @param[in] length The length of bits to extract from the buffer + /// @param[in] format The byte format to use when reading the integer + /// @return The 64-bit unsigned integer + std::uint64_t get_data_custom_length(const std::uint32_t startBitIndex, const std::uint32_t length, const ByteFormat format = ByteFormat::LittleEndian) const; + private: Type messageType; ///< The internal message type associated with the message CANIdentifier identifier; ///< The CAN ID of the message diff --git a/src/can_message_data.cpp b/src/can_message_data.cpp index 214918d..fad4684 100644 --- a/src/can_message_data.cpp +++ b/src/can_message_data.cpp @@ -1,5 +1,5 @@ //================================================================================================ -/// @file can_message_data.hpp +/// @file can_message_data.cpp /// /// @brief An class that represents a CAN message of arbitrary length being transported. /// @author Daan Steenbergen diff --git a/src/can_message_frame.cpp b/src/can_message_frame.cpp index b5516e3..de18710 100644 --- a/src/can_message_frame.cpp +++ b/src/can_message_frame.cpp @@ -4,7 +4,7 @@ /// @brief Implements helper functions for CANMessageFrame /// @author Adrian Del Grosso /// -/// @copyright 2023 Adrian Del Grosso +/// @copyright 2023 The Open-Agriculture Developers //================================================================================================ #include "can_message_frame.hpp" #include "can_constants.hpp" diff --git a/src/can_message_frame.hpp b/src/can_message_frame.hpp index f03710b..9a1e8a9 100644 --- a/src/can_message_frame.hpp +++ b/src/can_message_frame.hpp @@ -5,7 +5,7 @@ /// @author Adrian Del Grosso /// @author Daan Steenbergen /// -/// @copyright 2022 Adrian Del Grosso +/// @copyright 2022 The Open-Agriculture Developers //================================================================================================ #ifndef CAN_MESSAGE_FRAME_HPP diff --git a/src/can_network_configuration.cpp b/src/can_network_configuration.cpp index 0fcc172..f1b4e2f 100644 --- a/src/can_network_configuration.cpp +++ b/src/can_network_configuration.cpp @@ -4,7 +4,7 @@ /// @brief This is a class for changing stack settings. /// @author Adrian Del Grosso /// -/// @copyright 2022 Adrian Del Grosso +/// @copyright 2022 The Open-Agriculture Developers //================================================================================================ #include "can_network_configuration.hpp" diff --git a/src/can_network_configuration.hpp b/src/can_network_configuration.hpp index e0a710f..0d13781 100644 --- a/src/can_network_configuration.hpp +++ b/src/can_network_configuration.hpp @@ -4,7 +4,7 @@ /// @brief This is a class for changing stack settings. /// @author Adrian Del Grosso /// -/// @copyright 2022 Adrian Del Grosso +/// @copyright 2022 The Open-Agriculture Developers //================================================================================================ #ifndef CAN_NETWORK_CONFIGURATION_HPP diff --git a/src/can_network_manager.cpp b/src/can_network_manager.cpp index 23e13b5..dedd489 100644 --- a/src/can_network_manager.cpp +++ b/src/can_network_manager.cpp @@ -6,7 +6,7 @@ /// @author Adrian Del Grosso /// @author Daan Steenbergen /// -/// @copyright 2022 Adrian Del Grosso +/// @copyright 2022 The Open-Agriculture Developers //================================================================================================ #include "can_network_manager.hpp" @@ -14,8 +14,8 @@ #include "can_general_parameter_group_numbers.hpp" #include "can_hardware_abstraction.hpp" #include "can_message.hpp" +#include "can_parameter_group_number_request_protocol.hpp" #include "can_partnered_control_function.hpp" -#include "can_protocol.hpp" #include "can_stack_logger.hpp" #include "system_timing.hpp" #include "to_string.hpp" @@ -43,9 +43,35 @@ namespace isobus initialized = true; } - std::shared_ptr CANNetworkManager::get_control_function(std::uint8_t channelIndex, std::uint8_t address, CANLibBadge) const + std::shared_ptr CANNetworkManager::create_internal_control_function(NAME desiredName, std::uint8_t CANPort, std::uint8_t preferredAddress) { - return get_control_function(channelIndex, address); + auto controlFunction = std::make_shared(desiredName, preferredAddress, CANPort); + controlFunction->pgnRequestProtocol.reset(new ParameterGroupNumberRequestProtocol(controlFunction)); + internalControlFunctions.push_back(controlFunction); + heartBeatInterfaces.at(CANPort)->on_new_internal_control_function(controlFunction); + return controlFunction; + } + + std::shared_ptr CANNetworkManager::create_partnered_control_function(std::uint8_t CANPort, const std::vector NAMEFilters) + { + auto controlFunction = std::make_shared(CANPort, NAMEFilters); + partneredControlFunctions.push_back(controlFunction); + return controlFunction; + } + + void CANNetworkManager::deactivate_control_function(std::shared_ptr controlFunction) + { + // We need to unregister the control function from the interfaces managed by the network manager first. + controlFunction->pgnRequestProtocol.reset(); + heartBeatInterfaces.at(controlFunction->get_can_port())->on_destroyed_internal_control_function(controlFunction); + internalControlFunctions.erase(std::remove(internalControlFunctions.begin(), internalControlFunctions.end(), controlFunction), internalControlFunctions.end()); + deactivate_control_function(std::static_pointer_cast(controlFunction)); + } + + void CANNetworkManager::deactivate_control_function(std::shared_ptr controlFunction) + { + partneredControlFunctions.erase(std::remove(partneredControlFunctions.begin(), partneredControlFunctions.end(), controlFunction), partneredControlFunctions.end()); + deactivate_control_function(std::static_pointer_cast(controlFunction)); } void CANNetworkManager::add_global_parameter_group_number_callback(std::uint32_t parameterGroupNumber, CANLibCallback callback, void *parent) @@ -70,18 +96,14 @@ namespace isobus void CANNetworkManager::add_any_control_function_parameter_group_number_callback(std::uint32_t parameterGroupNumber, CANLibCallback callback, void *parent) { -#if !defined CAN_STACK_DISABLE_THREADS && !defined ARDUINO - std::lock_guard lock(anyControlFunctionCallbacksMutex); -#endif + LOCK_GUARD(Mutex, anyControlFunctionCallbacksMutex); anyControlFunctionParameterGroupNumberCallbacks.emplace_back(parameterGroupNumber, callback, parent, nullptr); } void CANNetworkManager::remove_any_control_function_parameter_group_number_callback(std::uint32_t parameterGroupNumber, CANLibCallback callback, void *parent) { ParameterGroupNumberCallbackData tempObject(parameterGroupNumber, callback, parent, nullptr); -#if !defined CAN_STACK_DISABLE_THREADS && !defined ARDUINO - std::lock_guard lock(anyControlFunctionCallbacksMutex); -#endif + LOCK_GUARD(Mutex, anyControlFunctionCallbacksMutex); auto callbackLocation = std::find(anyControlFunctionParameterGroupNumberCallbacks.begin(), anyControlFunctionParameterGroupNumberCallbacks.end(), tempObject); if (anyControlFunctionParameterGroupNumberCallbacks.end() != callbackLocation) { @@ -108,9 +130,7 @@ namespace isobus float CANNetworkManager::get_estimated_busload(std::uint8_t canChannel) { -#if !defined CAN_STACK_DISABLE_THREADS && !defined ARDUINO - const std::lock_guard lock(busloadUpdateMutex); -#endif + LOCK_GUARD(Mutex, busloadUpdateMutex); constexpr float ISOBUS_BAUD_RATE_BPS = 250000.0f; float retVal = 0.0f; @@ -172,31 +192,6 @@ namespace isobus // Successfully sent via the extended transport protocol retVal = true; } - else - { - //! @todo convert the other protocols to stop using the abstract protocollib class - CANLibProtocol *currentProtocol; - // See if any transport layer protocol can handle this message - for (std::uint32_t i = 0; i < CANLibProtocol::get_number_protocols(); i++) - { - if (CANLibProtocol::get_protocol(i, currentProtocol)) - { - retVal = currentProtocol->protocol_transmit_message(parameterGroupNumber, - dataBuffer, - dataLength, - sourceControlFunction, - destinationControlFunction, - transmitCompleteCallback, - parentPointer, - frameChunkCallback); - - if (retVal) - { - break; - } - } - } - } //! @todo Allow sending 8 byte message with the frameChunkCallback if ((!retVal) && @@ -225,9 +220,8 @@ namespace isobus void CANNetworkManager::update() { -#if !defined CAN_STACK_DISABLE_THREADS && !defined ARDUINO - const std::lock_guard lock(ControlFunction::controlFunctionProcessingMutex); -#endif + auto &processingMutex = ControlFunction::controlFunctionProcessingMutex; + LOCK_GUARD(Mutex, processingMutex); if (!initialized) { @@ -237,6 +231,14 @@ namespace isobus update_new_partners(); process_rx_messages(); + + // Update ISOBUS heartbeats (should be done before process_tx_messages + // to minimize latency in safety critical paths) + for (std::uint32_t i = 0; i < CAN_PORT_MAXIMUM; i++) + { + heartBeatInterfaces.at(i)->update(); + } + process_tx_messages(); update_internal_cfs(); @@ -248,20 +250,7 @@ namespace isobus { transportProtocols[i]->update(); extendedTransportProtocols[i]->update(); - } - - for (std::size_t i = 0; i < CANLibProtocol::get_number_protocols(); i++) - { - CANLibProtocol *currentProtocol = nullptr; - - if (CANLibProtocol::get_protocol(i, currentProtocol)) - { - if (!currentProtocol->get_is_initialized()) - { - currentProtocol->initialize({}); - } - currentProtocol->update({}); - } + fastPacketProtocol[i]->update(); } update_busload_history(); updateTimestamp_ms = SystemTiming::get_timestamp_ms(); @@ -274,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); } @@ -322,9 +311,7 @@ namespace isobus if (initialized) { -#if !defined CAN_STACK_DISABLE_THREADS && !defined ARDUINO - std::lock_guard lock(receivedMessageQueueMutex); -#endif + LOCK_GUARD(Mutex, receivedMessageQueueMutex); receivedMessageQueue.push(std::move(message)); } } @@ -349,17 +336,8 @@ namespace isobus } } - void CANNetworkManager::on_control_function_destroyed(std::shared_ptr controlFunction, CANLibBadge) + void CANNetworkManager::deactivate_control_function(std::shared_ptr controlFunction) { - if (ControlFunction::Type::Internal == controlFunction->get_type()) - { - internalControlFunctions.erase(std::remove(internalControlFunctions.begin(), internalControlFunctions.end(), controlFunction), internalControlFunctions.end()); - } - else if (ControlFunction::Type::Partnered == controlFunction->get_type()) - { - partneredControlFunctions.erase(std::remove(partneredControlFunctions.begin(), partneredControlFunctions.end(), controlFunction), partneredControlFunctions.end()); - } - auto result = std::find(inactiveControlFunctions.begin(), inactiveControlFunctions.end(), controlFunction); if (result != inactiveControlFunctions.end()) { @@ -372,49 +350,31 @@ namespace isobus { if (i != controlFunction->get_address()) { - CANStackLogger::warn("[NM]: %s control function with address '%d' was at incorrect address '%d' in the lookup table prior to deletion.", controlFunction->get_type_string().c_str(), controlFunction->get_address(), i); + LOG_WARNING("[NM]: %s control function with address '%d' was at incorrect address '%d' in the lookup table prior to deactivation.", + controlFunction->get_type_string().c_str(), + controlFunction->get_address(), + i); } - if (controlFunction->get_address() < NULL_CAN_ADDRESS) + controlFunctionTable[controlFunction->get_can_port()][i] = nullptr; + + if (controlFunction->get_address_valid() && (ControlFunction::Type::Partnered == controlFunction->get_type())) { - if (initialized) - { - // The control function was active, replace it with an new external control function - controlFunctionTable[controlFunction->get_can_port()][controlFunction->address] = ControlFunction::create(controlFunction->get_NAME(), controlFunction->get_address(), controlFunction->get_can_port()); - } - else - { - // The network manager is not initialized yet, just remove the control function from the table - controlFunctionTable[controlFunction->get_can_port()][i] = nullptr; - } + // The control function was an active partner when deleted, so we replace it with an new external control function instead + create_external_control_function(controlFunction->get_NAME(), controlFunction->get_address(), controlFunction->get_can_port()); } } } - CANStackLogger::info("[NM]: %s control function with address '%d' is deleted.", controlFunction->get_type_string().c_str(), controlFunction->get_address()); - } - - void CANNetworkManager::on_control_function_created(std::shared_ptr controlFunction, CANLibBadge) - { - on_control_function_created(controlFunction); - } - - void CANNetworkManager::on_control_function_created(std::shared_ptr controlFunction, CANLibBadge) - { - on_control_function_created(controlFunction); - } - - void CANNetworkManager::on_control_function_created(std::shared_ptr controlFunction, CANLibBadge) - { - on_control_function_created(controlFunction); + LOG_DEBUG("[NM]: %s control function at address '%d' is deactivated.", + controlFunction->get_type_string().c_str(), + controlFunction->get_address()); } void CANNetworkManager::add_control_function_status_change_callback(ControlFunctionStateCallback callback) { if (nullptr != callback) { -#if !defined CAN_STACK_DISABLE_THREADS && !defined ARDUINO - const std::lock_guard lock(controlFunctionStatusCallbacksMutex); -#endif + LOCK_GUARD(Mutex, controlFunctionStatusCallbacksMutex); controlFunctionStateCallbacks.emplace_back(callback); } } @@ -423,9 +383,7 @@ namespace isobus { if (nullptr != callback) { -#if !defined CAN_STACK_DISABLE_THREADS && !defined ARDUINO - const std::lock_guard lock(controlFunctionStatusCallbacksMutex); -#endif + LOCK_GUARD(Mutex, controlFunctionStatusCallbacksMutex); ControlFunctionStateCallback targetCallback(callback); auto callbackLocation = std::find(controlFunctionStateCallbacks.begin(), controlFunctionStateCallbacks.end(), targetCallback); @@ -477,9 +435,15 @@ namespace isobus return retVal; } - FastPacketProtocol &CANNetworkManager::get_fast_packet_protocol() + std::unique_ptr &CANNetworkManager::get_fast_packet_protocol(std::uint8_t canPortIndex) { - return fastPacketProtocol; + return fastPacketProtocol[canPortIndex]; + } + + HeartbeatInterface &CANNetworkManager::get_heartbeat_interface(std::uint8_t canPortIndex) + { + assert(canPortIndex < CAN_PORT_MAXIMUM); // You passed in an out of range index! + return *heartBeatInterfaces.at(canPortIndex); } CANNetworkConfiguration &CANNetworkManager::get_configuration() @@ -496,9 +460,7 @@ namespace isobus { bool retVal = false; ParameterGroupNumberCallbackData callbackInfo(parameterGroupNumber, callback, parentPointer, nullptr); -#if !defined CAN_STACK_DISABLE_THREADS && !defined ARDUINO - const std::lock_guard lock(protocolPGNCallbacksMutex); -#endif + LOCK_GUARD(Mutex, protocolPGNCallbacksMutex); if ((nullptr != callback) && (protocolPGNCallbacks.end() == find(protocolPGNCallbacks.begin(), protocolPGNCallbacks.end(), callbackInfo))) { protocolPGNCallbacks.push_back(callbackInfo); @@ -511,9 +473,7 @@ namespace isobus { bool retVal = false; ParameterGroupNumberCallbackData callbackInfo(parameterGroupNumber, callback, parentPointer, nullptr); -#if !defined CAN_STACK_DISABLE_THREADS && !defined ARDUINO - const std::lock_guard lock(protocolPGNCallbacksMutex); -#endif + LOCK_GUARD(Mutex, protocolPGNCallbacksMutex); if (nullptr != callback) { std::list::iterator callbackLocation; @@ -552,9 +512,21 @@ namespace isobus i); this->protocol_message_callback(message); }; - transportProtocols[i].reset(new TransportProtocolManager(send_frame_callback, receive_message_callback, &configuration)); - extendedTransportProtocols[i].reset(new ExtendedTransportProtocolManager(send_frame_callback, receive_message_callback, &configuration)); + transportProtocols.at(i).reset(new TransportProtocolManager(send_frame_callback, receive_message_callback, &configuration)); + extendedTransportProtocols.at(i).reset(new ExtendedTransportProtocolManager(send_frame_callback, receive_message_callback, &configuration)); + fastPacketProtocol.at(i).reset(new FastPacketProtocol(send_frame_callback)); + heartBeatInterfaces.at(i).reset(new HeartbeatInterface(send_frame_callback)); + } + } + + std::shared_ptr CANNetworkManager::create_external_control_function(NAME desiredName, std::uint8_t address, std::uint8_t CANPort) + { + auto controlFunction = std::make_shared(desiredName, address, CANPort, ControlFunction::Type::External); + if ((CANPort < CAN_PORT_MAXIMUM) && (address < NULL_CAN_ADDRESS)) + { + controlFunctionTable[CANPort][address] = controlFunction; } + return controlFunction; } void CANNetworkManager::update_address_table(const CANMessage &message) @@ -573,11 +545,11 @@ namespace isobus // Need to evict them from the table and move them to the inactive list targetControlFunction->address = NULL_CAN_ADDRESS; inactiveControlFunctions.push_back(targetControlFunction); - CANStackLogger::info("[NM]: %s CF '%016llx' is evicted from address '%d' on channel '%d', as their address is probably stolen.", - targetControlFunction->get_type_string().c_str(), - targetControlFunction->get_NAME().get_full_name(), - claimedAddress, - channelIndex); + LOG_INFO("[NM]: %s CF '%016llx' is evicted from address '%d' on channel '%d', as their address is probably stolen.", + targetControlFunction->get_type_string().c_str(), + targetControlFunction->get_NAME().get_full_name(), + claimedAddress, + channelIndex); targetControlFunction = nullptr; } @@ -594,11 +566,11 @@ namespace isobus (currentControlFunction->get_can_port() == channelIndex)) { controlFunctionTable[channelIndex][claimedAddress] = currentControlFunction; - CANStackLogger::debug("[NM]: %s CF '%016llx' is now active at address '%d' on channel '%d'.", - currentControlFunction->get_type_string().c_str(), - currentControlFunction->get_NAME().get_full_name(), - claimedAddress, - channelIndex); + LOG_DEBUG("[NM]: %s CF '%016llx' is now active at address '%d' on channel '%d'.", + currentControlFunction->get_type_string().c_str(), + currentControlFunction->get_NAME().get_full_name(), + claimedAddress, + channelIndex); process_control_function_state_change_callback(currentControlFunction, ControlFunctionState::Online); break; } @@ -636,7 +608,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(); @@ -665,19 +637,23 @@ 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) { -#if !defined CAN_STACK_DISABLE_THREADS && !defined ARDUINO - const std::lock_guard lock(CANNetworkManager::CANNetwork.busloadUpdateMutex); -#endif + LOCK_GUARD(Mutex, busloadUpdateMutex); currentBusloadBitAccumulator.at(channelIndex) += numberOfBitsProcessed; } void CANNetworkManager::update_busload_history() { -#if !defined CAN_STACK_DISABLE_THREADS && !defined ARDUINO - const std::lock_guard lock(busloadUpdateMutex); -#endif + LOCK_GUARD(Mutex, busloadUpdateMutex); if (SystemTiming::time_expired_ms(busloadUpdateTimestamp_ms, BUSLOAD_UPDATE_FREQUENCY_MS)) { for (std::size_t i = 0; i < busloadMessageBitsHistory.size(); i++) @@ -717,7 +693,7 @@ namespace isobus auto activeResult = std::find_if(controlFunctionTable[rxFrame.channel].begin(), controlFunctionTable[rxFrame.channel].end(), [claimedNAME](const std::shared_ptr &cf) { - return (nullptr != cf) && (cf->controlFunctionNAME.get_full_name() == claimedNAME); + return (nullptr != cf) && (cf->get_NAME().get_full_name() == claimedNAME); }); if (activeResult != controlFunctionTable[rxFrame.channel].end()) { @@ -728,7 +704,7 @@ namespace isobus auto inActiveResult = std::find_if(inactiveControlFunctions.begin(), inactiveControlFunctions.end(), [claimedNAME, &rxFrame](const std::shared_ptr &cf) { - return (cf->controlFunctionNAME.get_full_name() == claimedNAME) && (cf->get_can_port() == rxFrame.channel); + return (cf->get_NAME().get_full_name() == claimedNAME) && (cf->get_can_port() == rxFrame.channel); }); if (inActiveResult != inactiveControlFunctions.end()) { @@ -757,43 +733,42 @@ namespace isobus std::for_each(controlFunctionTable[rxFrame.channel].begin(), controlFunctionTable[rxFrame.channel].end(), [&foundControlFunction, &claimedAddress](const std::shared_ptr &cf) { - if ((nullptr != cf) && (foundControlFunction != cf) && (cf->address == claimedAddress)) + if ((nullptr != cf) && (foundControlFunction != cf) && (cf->get_address() == claimedAddress)) cf->address = CANIdentifier::NULL_ADDRESS; }); std::for_each(inactiveControlFunctions.begin(), inactiveControlFunctions.end(), [&rxFrame, &foundControlFunction, &claimedAddress](const std::shared_ptr &cf) { - if ((foundControlFunction != cf) && (cf->address == claimedAddress) && (cf->get_can_port() == rxFrame.channel)) + if ((foundControlFunction != cf) && (cf->get_address() == claimedAddress) && (cf->get_can_port() == rxFrame.channel)) cf->address = CANIdentifier::NULL_ADDRESS; }); if (nullptr == foundControlFunction) { // New device, need to start keeping track of it - foundControlFunction = ControlFunction::create(NAME(claimedNAME), claimedAddress, rxFrame.channel); - controlFunctionTable[rxFrame.channel][foundControlFunction->get_address()] = foundControlFunction; - CANStackLogger::debug("[NM]: A control function claimed address %u on channel %u", foundControlFunction->get_address(), foundControlFunction->get_can_port()); + foundControlFunction = create_external_control_function(NAME(claimedNAME), claimedAddress, rxFrame.channel); + LOG_DEBUG("[NM]: A control function claimed address %u on channel %u", foundControlFunction->get_address(), foundControlFunction->get_can_port()); } - else if (foundControlFunction->address != claimedAddress) + else if (foundControlFunction->get_address() != claimedAddress) { if (foundControlFunction->get_address_valid()) { controlFunctionTable[rxFrame.channel][claimedAddress] = foundControlFunction; controlFunctionTable[rxFrame.channel][foundControlFunction->get_address()] = nullptr; - CANStackLogger::info("[NM]: The %s control function at address %d changed it's address to %d on channel %u.", - foundControlFunction->get_type_string().c_str(), - foundControlFunction->get_address(), - claimedAddress, - foundControlFunction->get_can_port()); + LOG_INFO("[NM]: The %s control function at address %d changed it's address to %d on channel %u.", + foundControlFunction->get_type_string().c_str(), + foundControlFunction->get_address(), + claimedAddress, + foundControlFunction->get_can_port()); } else { - CANStackLogger::info("[NM]: %s control function with name %016llx has claimed address %u on channel %u.", - foundControlFunction->get_type_string().c_str(), - foundControlFunction->get_NAME().get_full_name(), - claimedAddress, - foundControlFunction->get_can_port()); + LOG_INFO("[NM]: %s control function with name %016llx has claimed address %u on channel %u.", + foundControlFunction->get_type_string().c_str(), + foundControlFunction->get_NAME().get_full_name(), + claimedAddress, + foundControlFunction->get_can_port()); process_control_function_state_change_callback(foundControlFunction, ControlFunctionState::Online); } foundControlFunction->address = claimedAddress; @@ -826,17 +801,17 @@ namespace isobus (ControlFunction::Type::External == currentActiveControlFunction->get_type())) { // This CF matches the filter and is not an internal or already partnered CF - CANStackLogger::info("[NM]: A partner with name %016llx has claimed address %u on channel %u.", - partner->get_NAME().get_full_name(), - partner->get_address(), - partner->get_can_port()); - // Populate the partner's data partner->address = currentActiveControlFunction->get_address(); 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); + + LOG_INFO("[NM]: A partner with name %016llx has claimed address %u on channel %u.", + partner->get_NAME().get_full_name(), + partner->get_address(), + partner->get_can_port()); break; } } @@ -885,10 +860,10 @@ namespace isobus } else { - CANStackLogger::warn("[NM]: Cannot send a message with PGN " + - isobus::to_string(static_cast(parameterGroupNumber)) + - " as a destination specific message. " + - "Try resending it using nullptr as your destination control function."); + LOG_WARNING("[NM]: Cannot send a message with PGN " + + isobus::to_string(static_cast(parameterGroupNumber)) + + " as a destination specific message. " + + "Try resending it using nullptr as your destination control function."); identifier = DEFAULT_IDENTIFIER; } } @@ -918,9 +893,7 @@ namespace isobus CANMessage CANNetworkManager::get_next_can_message_from_rx_queue() { -#if !defined CAN_STACK_DISABLE_THREADS && !defined ARDUINO - std::lock_guard lock(receivedMessageQueueMutex); -#endif + LOCK_GUARD(Mutex, receivedMessageQueueMutex); if (!receivedMessageQueue.empty()) { CANMessage retVal = std::move(receivedMessageQueue.front()); @@ -942,23 +915,9 @@ namespace isobus return CANMessage::create_invalid_message(); } - void CANNetworkManager::on_control_function_created(std::shared_ptr controlFunction) - { - if (ControlFunction::Type::Internal == controlFunction->get_type()) - { - internalControlFunctions.push_back(std::static_pointer_cast(controlFunction)); - } - else if (ControlFunction::Type::Partnered == controlFunction->get_type()) - { - partneredControlFunctions.push_back(std::static_pointer_cast(controlFunction)); - } - } - void CANNetworkManager::process_any_control_function_pgn_callbacks(const CANMessage ¤tMessage) { -#if !defined CAN_STACK_DISABLE_THREADS && !defined ARDUINO - const std::lock_guard lock(anyControlFunctionCallbacksMutex); -#endif + LOCK_GUARD(Mutex, anyControlFunctionCallbacksMutex); for (const auto ¤tCallback : anyControlFunctionParameterGroupNumberCallbacks) { if ((currentCallback.get_parameter_group_number() == currentMessage.get_identifier().get_parameter_group_number()) && @@ -972,29 +931,19 @@ 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); } } } void CANNetworkManager::process_control_function_state_change_callback(std::shared_ptr controlFunction, ControlFunctionState state) { -#if !defined CAN_STACK_DISABLE_THREADS && !defined ARDUINO - const std::lock_guard lock(controlFunctionStatusCallbacksMutex); -#endif + LOCK_GUARD(Mutex, controlFunctionStatusCallbacksMutex); for (const auto &callback : controlFunctionStateCallbacks) { callback(controlFunction, state); @@ -1003,9 +952,7 @@ namespace isobus void CANNetworkManager::process_protocol_pgn_callbacks(const CANMessage ¤tMessage) { -#if !defined CAN_STACK_DISABLE_THREADS && !defined ARDUINO - const std::lock_guard lock(protocolPGNCallbacksMutex); -#endif + LOCK_GUARD(Mutex, protocolPGNCallbacksMutex); for (const auto ¤tCallback : protocolPGNCallbacks) { if (currentCallback.get_parameter_group_number() == currentMessage.get_identifier().get_parameter_group_number()) @@ -1059,27 +1006,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 @@ -1089,10 +1015,13 @@ 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[currentMessage.get_can_port_index()]->process_message(currentMessage); - extendedTransportProtocols[currentMessage.get_can_port_index()]->process_message(currentMessage); + transportProtocols.at(currentMessage.get_can_port_index())->process_message(currentMessage); + extendedTransportProtocols.at(currentMessage.get_can_port_index())->process_message(currentMessage); + fastPacketProtocol.at(currentMessage.get_can_port_index())->process_message(currentMessage); + heartBeatInterfaces.at(currentMessage.get_can_port_index())->process_rx_message(currentMessage); process_protocol_pgn_callbacks(currentMessage); process_any_control_function_pgn_callbacks(currentMessage); @@ -1129,7 +1058,7 @@ namespace isobus (ControlFunction::Type::Internal != controlFunction->get_type())) { inactiveControlFunctions.push_back(controlFunction); - CANStackLogger::info("[NM]: Control function with address %u and NAME %016llx is now offline on channel %u.", controlFunction->get_address(), controlFunction->get_NAME(), channelIndex); + LOG_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); @@ -1162,7 +1091,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 diff --git a/src/can_network_manager.hpp b/src/can_network_manager.hpp index 92e1589..5da28d4 100644 --- a/src/can_network_manager.hpp +++ b/src/can_network_manager.hpp @@ -6,25 +6,28 @@ /// @author Adrian Del Grosso /// @author Daan Steenbergen /// -/// @copyright 2022 Adrian Del Grosso +/// @copyright 2022 The Open-Agriculture Developers //================================================================================================ #ifndef CAN_NETWORK_MANAGER_HPP #define CAN_NETWORK_MANAGER_HPP -#include "can_address_claim_state_machine.hpp" #include "can_badge.hpp" #include "can_callbacks.hpp" #include "can_constants.hpp" +#include "can_control_function.hpp" #include "can_extended_transport_protocol.hpp" #include "can_identifier.hpp" #include "can_internal_control_function.hpp" #include "can_message.hpp" #include "can_message_frame.hpp" #include "can_network_configuration.hpp" +#include "can_partnered_control_function.hpp" #include "can_transport_protocol.hpp" +#include "isobus_heartbeat.hpp" #include "nmea2000_fast_packet_protocol.hpp" #include "event_dispatcher.hpp" +#include "thread_synchronization.hpp" #include #include @@ -32,14 +35,9 @@ #include #include -#if !defined CAN_STACK_DISABLE_THREADS && !defined ARDUINO -#include -#endif - /// @brief This namespace encompasses all of the ISO11783 stack's functionality to reduce global namespace pollution namespace isobus { - class PartneredControlFunction; //================================================================================================ /// @class CANNetworkManager @@ -55,11 +53,34 @@ namespace isobus /// @brief Initializer function for the network manager void initialize(); - /// @brief Called only by the stack, returns a control function based on certain port and address + /// @brief The factory function to construct an internal control function, also automatically initializes it to be functional + /// @param[in] desiredName The NAME for this control function to claim as + /// @param[in] CANPort The CAN channel index for this control function to use + /// @param[in] preferredAddress Optionally, the preferred address for this control function to claim as, if not provided or NULL address, + /// it will fallback onto a random preferred address somewhere in the arbitrary address range, which means your NAME must have the arbitrary address bit set. + /// @returns A shared pointer to an InternalControlFunction object created with the parameters passed in + std::shared_ptr create_internal_control_function(NAME desiredName, std::uint8_t CANPort, std::uint8_t preferredAddress = NULL_CAN_ADDRESS); + + /// @brief The factory function to construct a partnered control function, also automatically initializes it to be functional + /// @param[in] CANPort The CAN channel associated with this control function definition + /// @param[in] NAMEFilters A list of filters that describe the identity of the CF based on NAME components + /// @returns A shared pointer to a PartneredControlFunction object created with the parameters passed in + std::shared_ptr create_partnered_control_function(std::uint8_t CANPort, const std::vector NAMEFilters); + + /// @brief Removes an internal control function from the network manager, making it inactive + /// @param[in] controlFunction The control function to deactivate + void deactivate_control_function(std::shared_ptr controlFunction); + + /// @brief Removes a partnered control function from the network manager, making it inactive + /// @param[in] controlFunction The control function to deactivate + void deactivate_control_function(std::shared_ptr controlFunction); + + /// @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 @@ -144,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 @@ -190,8 +195,14 @@ namespace isobus /// @brief Returns the class instance of the NMEA2k fast packet protocol. /// Use this to register for FP multipacket messages + /// @param[in] canPortIndex The CAN channel index to get the fast packet protocol for /// @returns The class instance of the NMEA2k fast packet protocol. - FastPacketProtocol &get_fast_packet_protocol(); + std::unique_ptr &get_fast_packet_protocol(std::uint8_t canPortIndex); + + /// @brief Returns an interface which can be used to manage ISO11783-7 heartbeat messages. + /// @param[in] canPortIndex The index of the CAN channel associated to the interface you're requesting + /// @returns ISO11783-7 heartbeat interface + HeartbeatInterface &get_heartbeat_interface(std::uint8_t canPortIndex); /// @brief Returns the configuration of this network manager /// @returns The configuration class for this network manager @@ -204,13 +215,12 @@ 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 friend class ParameterGroupNumberRequestProtocol; ///< Allows the PGN request protocol to access the network manager protected functions friend class FastPacketProtocol; ///< Allows the FP protocol to access the network manager protected functions - friend class CANLibProtocol; ///< Allows the CANLib protocol base class functions to access the network manager protected functions /// @brief Adds a PGN callback for a protocol class /// @param[in] parameterGroupNumber The PGN to register for @@ -242,18 +252,27 @@ 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 void protocol_message_callback(const CANMessage &message); - std::vector protocolList; ///< A list of all created protocol classes - private: /// @brief Constructor for the network manager. Sets default values for members CANNetworkManager(); + /// @brief Factory function to create an external control function, also automatically assigns it to the lookup table. + /// @param[in] desiredName The NAME of the control function + /// @param[in] address The address of the control function + /// @param[in] CANPort The CAN channel index of the control function + /// @returns A shared pointer to the control function created + std::shared_ptr create_external_control_function(NAME desiredName, std::uint8_t address, std::uint8_t CANPort); + + /// @brief Removes a control function from the network manager, making it inactive + /// @param[in] controlFunction The control function to remove + void deactivate_control_function(std::shared_ptr controlFunction); + /// @brief Updates the internal address table based on a received CAN message /// @param[in] message A message being received by the stack void update_address_table(const CANMessage &message); @@ -261,6 +280,10 @@ 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 + /// @param[in] message The message to process + 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 @@ -293,12 +316,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 @@ -309,10 +326,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); @@ -338,13 +351,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(); @@ -383,7 +389,8 @@ namespace isobus CANNetworkConfiguration configuration; ///< The configuration for this network manager std::array, CAN_PORT_MAXIMUM> transportProtocols; ///< One instance of the transport protocol manager for each channel std::array, CAN_PORT_MAXIMUM> extendedTransportProtocols; ///< One instance of the extended transport protocol manager for each channel - FastPacketProtocol fastPacketProtocol; ///< Instance of the fast packet protocol + std::array, CAN_PORT_MAXIMUM> fastPacketProtocol; ///< One instance of the fast packet protocol for each channel + std::array, CAN_PORT_MAXIMUM> heartBeatInterfaces; ///< Manages ISOBUS heartbeat requests, one per channel 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 @@ -402,13 +409,11 @@ namespace isobus std::vector anyControlFunctionParameterGroupNumberCallbacks; ///< A list of all global PGN callbacks EventDispatcher messageTransmittedEventDispatcher; ///< An event dispatcher for notifying consumers about transmitted messages by our application EventDispatcher> addressViolationEventDispatcher; ///< An event dispatcher for notifying consumers about address violations -#if !defined CAN_STACK_DISABLE_THREADS && !defined ARDUINO - std::mutex receivedMessageQueueMutex; ///< 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 -#endif + Mutex receivedMessageQueueMutex; ///< A mutex for receive messages thread safety + Mutex protocolPGNCallbacksMutex; ///< A mutex for PGN callback thread safety + Mutex anyControlFunctionCallbacksMutex; ///< Mutex to protect the "any CF" callbacks + Mutex busloadUpdateMutex; ///< A mutex that protects the busload metrics since we calculate it on our own thread + Mutex controlFunctionStatusCallbacksMutex; ///< A Mutex that protects access to the control function status callback list Mutex transmittedMessageQueueMutex; ///< A mutex for protecting the transmitted message queue 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 diff --git a/src/can_parameter_group_number_request_protocol.cpp b/src/can_parameter_group_number_request_protocol.cpp index 97de49a..203d810 100644 --- a/src/can_parameter_group_number_request_protocol.cpp +++ b/src/can_parameter_group_number_request_protocol.cpp @@ -6,7 +6,7 @@ /// are made and responded to. /// @author Adrian Del Grosso /// -/// @copyright 2022 Adrian Del Grosso +/// @copyright 2022 The Open-Agriculture Developers //================================================================================================ #include "can_parameter_group_number_request_protocol.hpp" #include "can_general_parameter_group_numbers.hpp" @@ -19,7 +19,7 @@ namespace isobus { - ParameterGroupNumberRequestProtocol::ParameterGroupNumberRequestProtocol(std::shared_ptr internalControlFunction, CANLibBadge) : + ParameterGroupNumberRequestProtocol::ParameterGroupNumberRequestProtocol(std::shared_ptr internalControlFunction) : myControlFunction(internalControlFunction) { assert(nullptr != myControlFunction && "ParameterGroupNumberRequestProtocol::ParameterGroupNumberRequestProtocol() called with nullptr internalControlFunction"); @@ -72,9 +72,7 @@ namespace isobus { PGNRequestCallbackInfo pgnCallback(callback, pgn, parentPointer); bool retVal = false; -#if !defined CAN_STACK_DISABLE_THREADS && !defined ARDUINO - const std::lock_guard lock(pgnRequestMutex); -#endif + LOCK_GUARD(Mutex, pgnRequestMutex); if ((nullptr != callback) && (pgnRequestCallbacks.end() == std::find(pgnRequestCallbacks.begin(), pgnRequestCallbacks.end(), pgnCallback))) { @@ -88,9 +86,7 @@ namespace isobus { PGNRequestForRepetitionRateCallbackInfo repetitionRateCallback(callback, pgn, parentPointer); bool retVal = false; -#if !defined CAN_STACK_DISABLE_THREADS && !defined ARDUINO - const std::lock_guard lock(pgnRequestMutex); -#endif + LOCK_GUARD(Mutex, pgnRequestMutex); if ((nullptr != callback) && (repetitionRateCallbacks.end() == std::find(repetitionRateCallbacks.begin(), repetitionRateCallbacks.end(), repetitionRateCallback))) { @@ -104,9 +100,7 @@ namespace isobus { PGNRequestCallbackInfo repetitionRateCallback(callback, pgn, parentPointer); bool retVal = false; -#if !defined CAN_STACK_DISABLE_THREADS && !defined ARDUINO - const std::lock_guard lock(pgnRequestMutex); -#endif + LOCK_GUARD(Mutex, pgnRequestMutex); auto callbackLocation = find(pgnRequestCallbacks.begin(), pgnRequestCallbacks.end(), repetitionRateCallback); @@ -122,9 +116,7 @@ namespace isobus { PGNRequestForRepetitionRateCallbackInfo repetitionRateCallback(callback, pgn, parentPointer); bool retVal = false; -#if !defined CAN_STACK_DISABLE_THREADS && !defined ARDUINO - const std::lock_guard lock(pgnRequestMutex); -#endif + LOCK_GUARD(Mutex, pgnRequestMutex); auto callbackLocation = find(repetitionRateCallbacks.begin(), repetitionRateCallbacks.end(), repetitionRateCallback); @@ -185,15 +177,12 @@ namespace isobus { std::uint32_t requestedPGN = message.get_uint24_at(0); std::uint16_t requestedRate = message.get_uint16_at(3); - -#if !defined CAN_STACK_DISABLE_THREADS && !defined ARDUINO - const std::lock_guard lock(pgnRequestMutex); -#endif + LOCK_GUARD(Mutex, pgnRequestMutex); for (const auto &repetitionRateCallback : repetitionRateCallbacks) { if (((repetitionRateCallback.pgn == requestedPGN) || (static_cast(isobus::CANLibParameterGroupNumber::Any) == repetitionRateCallback.pgn)) && - (repetitionRateCallback.callbackFunction(requestedPGN, message.get_source_control_function(), requestedRate, repetitionRateCallback.parent))) + (repetitionRateCallback.callbackFunction(requestedPGN, message.get_source_control_function(), message.get_destination_control_function(), requestedRate, repetitionRateCallback.parent))) { // If the callback was able to process the PGN request, stop processing more. break; @@ -207,7 +196,7 @@ namespace isobus } else { - CANStackLogger::warn("[PR]: Received a malformed or broadcast request for repetition rate message. The message will not be processed."); + LOG_WARNING("[PR]: Received a malformed or broadcast request for repetition rate message. The message will not be processed."); } } break; @@ -222,9 +211,7 @@ namespace isobus std::uint32_t requestedPGN = message.get_uint24_at(0); -#if !defined CAN_STACK_DISABLE_THREADS && !defined ARDUINO - const std::lock_guard lock(pgnRequestMutex); -#endif + LOCK_GUARD(Mutex, pgnRequestMutex); for (const auto &pgnRequestCallback : pgnRequestCallbacks) { if (((pgnRequestCallback.pgn == requestedPGN) || @@ -253,12 +240,14 @@ namespace isobus send_acknowledgement(AcknowledgementType::Negative, requestedPGN, message.get_source_control_function()); - CANStackLogger::warn("[PR]: NACK-ing PGN request for PGN " + isobus::to_string(requestedPGN) + " because no callback could handle it."); + LOG_WARNING("[PR]: NACK-ing PGN request for PGN " + + isobus::to_string(requestedPGN) + + " because no callback could handle it."); } } else { - CANStackLogger::warn("[PR]: Received a malformed PGN request message. The message will not be processed."); + LOG_WARNING("[PR]: Received a malformed PGN request message. The message will not be processed."); } } break; diff --git a/src/can_parameter_group_number_request_protocol.hpp b/src/can_parameter_group_number_request_protocol.hpp index 8eee2a0..29fdea6 100644 --- a/src/can_parameter_group_number_request_protocol.hpp +++ b/src/can_parameter_group_number_request_protocol.hpp @@ -6,7 +6,7 @@ /// are made and responded to. /// @author Adrian Del Grosso /// -/// @copyright 2022 Adrian Del Grosso +/// @copyright 2022 The Open-Agriculture Developers //================================================================================================ #ifndef CAN_PARAMETER_GROUP_NUMBER_REQUEST_PROTOCOL_HPP #define CAN_PARAMETER_GROUP_NUMBER_REQUEST_PROTOCOL_HPP @@ -32,7 +32,7 @@ namespace isobus public: /// @brief The constructor for this protocol /// @param[in] internalControlFunction The internal control function that owns this protocol and will be used to send messages - ParameterGroupNumberRequestProtocol(std::shared_ptr internalControlFunction, CANLibBadge); + explicit ParameterGroupNumberRequestProtocol(std::shared_ptr internalControlFunction); /// @brief The destructor for this protocol ~ParameterGroupNumberRequestProtocol(); @@ -159,9 +159,7 @@ namespace isobus std::shared_ptr myControlFunction; ///< The internal control function that this protocol will send from std::vector pgnRequestCallbacks; ///< A list of all registered PGN callbacks and the PGN associated with each callback std::vector repetitionRateCallbacks; ///< A list of all registered request for repetition rate callbacks and the PGN associated with the callback -#if !defined CAN_STACK_DISABLE_THREADS && !defined ARDUINO - std::mutex pgnRequestMutex; ///< A mutex to protect the callback lists -#endif + Mutex pgnRequestMutex; ///< A mutex to protect the callback lists }; } diff --git a/src/can_partnered_control_function.cpp b/src/can_partnered_control_function.cpp index 4d3d359..6957d9f 100644 --- a/src/can_partnered_control_function.cpp +++ b/src/can_partnered_control_function.cpp @@ -6,33 +6,23 @@ /// @author Adrian Del Grosso /// @author Daan Steenbergen /// -/// @copyright 2022 Adrian Del Grosso +/// @copyright 2022 The Open-Agriculture Developers //================================================================================================ #include "can_partnered_control_function.hpp" #include "can_constants.hpp" -#include "can_network_manager.hpp" #include #include namespace isobus { - PartneredControlFunction::PartneredControlFunction(std::uint8_t CANPort, const std::vector NAMEFilters, CANLibBadge) : + PartneredControlFunction::PartneredControlFunction(std::uint8_t CANPort, const std::vector NAMEFilters) : ControlFunction(NAME(0), NULL_CAN_ADDRESS, CANPort, Type::Partnered), NAMEFilterList(NAMEFilters) { -#if !defined CAN_STACK_DISABLE_THREADS && !defined ARDUINO - const std::lock_guard lock(ControlFunction::controlFunctionProcessingMutex); -#endif - } - - std::shared_ptr PartneredControlFunction::create(std::uint8_t CANPort, const std::vector NAMEFilters) - { - // Unfortunately, we can't use `std::make_shared` here because the constructor is meant to be protected - auto controlFunction = std::shared_ptr(new PartneredControlFunction(CANPort, NAMEFilters, {})); - CANNetworkManager::CANNetwork.on_control_function_created(controlFunction, CANLibBadge()); - return controlFunction; + auto &processingMutex = ControlFunction::controlFunctionProcessingMutex; + LOCK_GUARD(Mutex, processingMutex); } void PartneredControlFunction::add_parameter_group_number_callback(std::uint32_t parameterGroupNumber, CANLibCallback callback, void *parent, std::shared_ptr internalControlFunction) diff --git a/src/can_partnered_control_function.hpp b/src/can_partnered_control_function.hpp index e991b96..18a9566 100644 --- a/src/can_partnered_control_function.hpp +++ b/src/can_partnered_control_function.hpp @@ -6,14 +6,13 @@ /// @author Adrian Del Grosso /// @author Daan Steenbergen /// -/// @copyright 2022 Adrian Del Grosso +/// @copyright 2024 The Open-Agriculture Developers //================================================================================================ #ifndef CAN_PARTNERED_CONTROL_FUNCTION_HPP #define CAN_PARTNERED_CONTROL_FUNCTION_HPP #include "can_NAME_filter.hpp" -#include "can_address_claim_state_machine.hpp" #include "can_badge.hpp" #include "can_callbacks.hpp" #include "can_control_function.hpp" @@ -25,29 +24,19 @@ namespace isobus class CANNetworkManager; class InternalControlFunction; - //================================================================================================ - /// @class PartneredControlFunction - /// /// @brief This represents any device on the bus you want to talk to. /// @details To communicate with a device on the bus, create one of these objects and tell it /// via the constructor what the identity of that device is using NAME fields like /// manufacturer code, function, and device class. The stack will take care of locating the /// device on the bus that matches that description, and will allow you to talk to it through /// passing this object to the appropriate send function in the network manager. - //================================================================================================ class PartneredControlFunction : public ControlFunction { public: - /// @brief The factory function to construct a partnered control function - /// @param[in] CANPort The CAN channel associated with this control function definition - /// @param[in] NAMEFilters A list of filters that describe the identity of the CF based on NAME components - /// @returns A shared pointer to a PartneredControlFunction object created with the parameters passed in - static std::shared_ptr create(std::uint8_t CANPort, const std::vector NAMEFilters); - /// @brief the constructor for a PartneredControlFunction, which is called by the factory function /// @param[in] CANPort The CAN channel associated with this control function definition /// @param[in] NAMEFilters A list of filters that describe the identity of the CF based on NAME components - PartneredControlFunction(std::uint8_t CANPort, const std::vector NAMEFilters, CANLibBadge); + PartneredControlFunction(std::uint8_t CANPort, const std::vector NAMEFilters); /// @brief Deleted copy constructor for PartneredControlFunction to avoid slicing PartneredControlFunction(PartneredControlFunction &) = delete; @@ -102,9 +91,6 @@ namespace isobus private: friend class CANNetworkManager; ///< Allows the network manager to use get_parameter_group_number_callback - /// @brief Make inherited factory function private so that it can't be called - static std::shared_ptr create(NAME, std::uint8_t, std::uint8_t) = delete; - /// @brief Returns a parameter group number associated with this control function by index /// @param[in] index The index from which to get the PGN callback data object /// @returns A reference to the PGN callback data object at the index specified diff --git a/src/can_protocol.cpp b/src/can_protocol.cpp deleted file mode 100644 index 1392677..0000000 --- a/src/can_protocol.cpp +++ /dev/null @@ -1,60 +0,0 @@ -//================================================================================================ -/// @file can_protocol.cpp -/// -/// @brief A base class for all protocol classes. Allows the network manager to update them -/// in a generic, dynamic way. -/// @author Adrian Del Grosso -/// -/// @copyright 2022 Adrian Del Grosso -//================================================================================================ -#include "can_protocol.hpp" - -#include "can_network_manager.hpp" - -#include - -namespace isobus -{ - CANLibProtocol::CANLibProtocol() : - initialized(false) - { - CANNetworkManager::CANNetwork.protocolList.push_back(this); - } - - CANLibProtocol::~CANLibProtocol() - { - auto protocolLocation = find(CANNetworkManager::CANNetwork.protocolList.begin(), CANNetworkManager::CANNetwork.protocolList.end(), this); - - if (CANNetworkManager::CANNetwork.protocolList.end() != protocolLocation) - { - CANNetworkManager::CANNetwork.protocolList.erase(protocolLocation); - } - } - - bool CANLibProtocol::get_is_initialized() const - { - return initialized; - } - - bool CANLibProtocol::get_protocol(std::uint32_t index, CANLibProtocol *&returnedProtocol) - { - returnedProtocol = nullptr; - - if (index < CANNetworkManager::CANNetwork.protocolList.size()) - { - returnedProtocol = CANNetworkManager::CANNetwork.protocolList[index]; - } - return (nullptr != returnedProtocol); - } - - std::uint32_t CANLibProtocol::get_number_protocols() - { - return CANNetworkManager::CANNetwork.protocolList.size(); - } - - void CANLibProtocol::initialize(CANLibBadge) - { - initialized = true; - } - -} // namespace isobus diff --git a/src/can_protocol.hpp b/src/can_protocol.hpp deleted file mode 100644 index 066f380..0000000 --- a/src/can_protocol.hpp +++ /dev/null @@ -1,95 +0,0 @@ -//================================================================================================ -/// @file can_protocol.hpp -/// -/// @brief A base class for all protocol classes. Allows the network manager to update them -/// in a generic, dynamic way. -/// @author Adrian Del Grosso -/// -/// @copyright 2022 Adrian Del Grosso -//================================================================================================ - -#ifndef CAN_PROTOCOL_HPP -#define CAN_PROTOCOL_HPP - -#include "can_badge.hpp" -#include "can_callbacks.hpp" -#include "can_control_function.hpp" -#include "can_message.hpp" - -#include - -namespace isobus -{ - class CANNetworkManager; - - //================================================================================================ - /// @class CANLibProtocol - /// - /// @brief A base class for a CAN protocol - /// @details CANLibProtocols are objects that manage different statful CAN protocols defined by - /// ISO11783 and/or J1939. They could also be used for abitrary processing inside the CAN stack. - //================================================================================================ - class CANLibProtocol - { - public: - /// @brief The base class constructor for a CANLibProtocol - CANLibProtocol(); - - /// @brief Deleted copy constructor for a CANLibProtocol - CANLibProtocol(CANLibProtocol &) = delete; - - /// @brief The base class destructor for a CANLibProtocol - virtual ~CANLibProtocol(); - - /// @brief Returns whether or not the protocol has been initialized by the network manager - /// @returns true if the protocol has been initialized by the network manager - bool get_is_initialized() const; - - /// @brief Gets a CAN protocol by index from the list of all protocols - /// @param[in] index The index of the protocol to get from the list of protocols - /// @param[out] returnedProtocol The returned protocol - /// @returns true if a protocol was successfully returned, false if index was out of range - static bool get_protocol(std::uint32_t index, CANLibProtocol *&returnedProtocol); - - /// @brief Returns the number of all created protocols - /// @returns The number of all created protocols - static std::uint32_t get_number_protocols(); - - /// @brief A generic way to initialize a protocol - /// @details The network manager will call a protocol's initialize function - /// when it is first updated, if it has yet to be initialized. - virtual void initialize(CANLibBadge); - - /// @brief A generic way for a protocol to process a received message - /// @param[in] message A received CAN message - virtual void process_message(const CANMessage &message) = 0; - - /// @brief The network manager calls this to see if the protocol can accept a non-raw CAN message for processing - /// @param[in] parameterGroupNumber The PGN of the message - /// @param[in] data The data to be sent - /// @param[in] messageLength The length of the data to be sent - /// @param[in] source The source control function - /// @param[in] destination The destination control function - /// @param[in] transmitCompleteCallback A callback for when the protocol completes its work - /// @param[in] parentPointer A generic context object for the tx complete and chunk callbacks - /// @param[in] frameChunkCallback A callback to get some data to send - /// @returns true if the message was accepted by the protocol for processing - virtual bool protocol_transmit_message(std::uint32_t parameterGroupNumber, - const std::uint8_t *data, - std::uint32_t messageLength, - std::shared_ptr source, - std::shared_ptr destination, - TransmitCompleteCallback transmitCompleteCallback, - void *parentPointer, - DataChunkCallback frameChunkCallback) = 0; - - /// @brief This will be called by the network manager on every cyclic update of the stack - virtual void update(CANLibBadge) = 0; - - protected: - bool initialized; ///< Keeps track of if the protocol has been initialized by the network manager - }; - -} // namespace isobus - -#endif // CAN_PROTOCOL_HPP diff --git a/src/can_stack_logger.cpp b/src/can_stack_logger.cpp index e20617e..ef9304b 100644 --- a/src/can_stack_logger.cpp +++ b/src/can_stack_logger.cpp @@ -5,7 +5,7 @@ /// derived class of logger and inject it into the CAN stack to get helpful debug logging. /// @author Adrian Del Grosso /// -/// @copyright 2022 Adrian Del Grosso +/// @copyright 2022 The Open-Agriculture Developers //================================================================================================ #include "can_stack_logger.hpp" @@ -15,15 +15,12 @@ namespace isobus { CANStackLogger *CANStackLogger::logger = nullptr; CANStackLogger::LoggingLevel CANStackLogger::currentLogLevel = LoggingLevel::Info; -#if !defined CAN_STACK_DISABLE_THREADS && !defined ARDUINO - std::mutex CANStackLogger::loggerMutex; -#endif + Mutex CANStackLogger::loggerMutex; +#ifndef DISABLE_CAN_STACK_LOGGER void CANStackLogger::CAN_stack_log(LoggingLevel level, const std::string &logText) { -#if !defined CAN_STACK_DISABLE_THREADS && !defined ARDUINO - const std::lock_guard lock(loggerMutex); -#endif + LOCK_GUARD(Mutex, loggerMutex); CANStackLogger *canStackLogger = nullptr; if ((get_can_stack_logger(canStackLogger)) && @@ -58,6 +55,8 @@ namespace isobus CAN_stack_log(LoggingLevel::Critical, logText); } +#endif // DISABLE_CAN_STACK_LOGGER + void CANStackLogger::set_can_stack_logger_sink(CANStackLogger *logSink) { logger = logSink; diff --git a/src/can_stack_logger.hpp b/src/can_stack_logger.hpp index addf572..b539f90 100644 --- a/src/can_stack_logger.hpp +++ b/src/can_stack_logger.hpp @@ -5,18 +5,16 @@ /// derived class of logger and inject it into the CAN stack to get helpful debug logging. /// @author Adrian Del Grosso /// -/// @copyright 2022 Adrian Del Grosso +/// @copyright 2022 The Open-Agriculture Developers //================================================================================================ #ifndef CAN_STACK_LOGGER_HPP #define CAN_STACK_LOGGER_HPP +#include "thread_synchronization.hpp" + #include #include -#if !defined CAN_STACK_DISABLE_THREADS && !defined ARDUINO -#include -#endif - namespace isobus { //================================================================================================ @@ -46,6 +44,8 @@ namespace isobus /// @brief The destructor for a CANStackLogger ~CANStackLogger() = default; +#ifndef DISABLE_CAN_STACK_LOGGER + /// @brief Gets called from the CAN stack to log information. Wraps sink_CAN_stack_log. /// @param[in] level The log level for this text /// @param[in] logText The text to be logged @@ -137,6 +137,8 @@ namespace isobus CAN_stack_log(LoggingLevel::Critical, format, args...); } +#endif + /// @brief Assigns a derived logger class to be used as the log sink /// @param[in] logSink A pointer to a derived CANStackLogger class static void set_can_stack_logger_sink(CANStackLogger *logSink); @@ -166,10 +168,49 @@ namespace isobus static CANStackLogger *logger; ///< A static pointer to an instance of a logger static LoggingLevel currentLogLevel; ///< The current log level. Logs for levels below the current one will be dropped. -#if !defined CAN_STACK_DISABLE_THREADS && !defined ARDUINO - static std::mutex loggerMutex; ///< A mutex that protects the logger so it can be used from multiple threads -#endif + static Mutex loggerMutex; ///< A mutex that protects the logger so it can be used from multiple threads }; } // namespace isobus +//! @cond Doxygen_Suppress +#ifdef DISABLE_CAN_STACK_LOGGER +/// @brief A macro which removes a "critical" log statement depending on the state of DISABLE_CAN_STACK_LOGGER +/// @param logString A log statement +#define LOG_CRITICAL(...) +/// @brief A macro which removes a "error" log statement depending on the state of DISABLE_CAN_STACK_LOGGER +/// @param logString A log statement +#define LOG_ERROR(...) +/// @brief A macro which removes a "warning" log statement depending on the state of DISABLE_CAN_STACK_LOGGER +/// @param logString A log statement +#define LOG_WARNING(...) +/// @brief A macro which removes a "info" log statement depending on the state of DISABLE_CAN_STACK_LOGGER +/// @param logString A log statement +#define LOG_INFO(...) +/// @brief A macro which removes a "debug" log statement depending on the state of DISABLE_CAN_STACK_LOGGER +/// @param logString A log statement +#define LOG_DEBUG(...) +#else +/// @brief A macro which logs a string at "critical" logging level +/// @param[in] logString A log statement +/// @param[in] args (optional) A list of printf style arguments to format the logString with +#define LOG_CRITICAL(...) isobus::CANStackLogger::critical(__VA_ARGS__) +/// @brief A macro which logs a string at "error" logging level +/// @param[in] logString A log statement +/// @param[in] args (optional) A list of printf style arguments to format the logString with +#define LOG_ERROR(...) isobus::CANStackLogger::error(__VA_ARGS__) +/// @brief A macro which logs a string at "warning" logging level +/// @param[in] logString A log statement +/// @param[in] args (optional) A list of printf style arguments to format the logString with +#define LOG_WARNING(...) isobus::CANStackLogger::warn(__VA_ARGS__) +/// @brief A macro which logs a string at "info" logging level +/// @param[in] logString A log statement +/// @param[in] args (optional) A list of printf style arguments to format the logString with +#define LOG_INFO(...) isobus::CANStackLogger::info(__VA_ARGS__) +/// @brief A macro which logs a string at "debug" logging level +/// @param[in] logString A log statement +/// @param[in] args (optional) A list of printf style arguments to format the logString with +#define LOG_DEBUG(...) isobus::CANStackLogger::debug(__VA_ARGS__) +#endif +//! @endcond + #endif // CAN_STACK_LOGGER_HPP diff --git a/src/can_transport_protocol.cpp b/src/can_transport_protocol.cpp index e34e01d..d56bfcc 100644 --- a/src/can_transport_protocol.cpp +++ b/src/can_transport_protocol.cpp @@ -138,18 +138,24 @@ namespace isobus if (activeSessions.size() >= configuration->get_max_number_transport_protocol_sessions()) { // TODO: consider using maximum memory instead of maximum number of sessions - CANStackLogger::warn("[TP]: Ignoring Broadcast Announcement Message (BAM) for 0x%05X, configured maximum number of sessions reached.", parameterGroupNumber); + LOG_WARNING("[TP]: Ignoring Broadcast Announcement Message (BAM) for 0x%05X, configured maximum number of sessions reached.", + parameterGroupNumber); } else if (totalMessageSize > MAX_PROTOCOL_DATA_LENGTH) { - CANStackLogger::warn("[TP]: Ignoring Broadcast Announcement Message (BAM) for 0x%05X, message size (%hu) is greater than the maximum (%hu).", parameterGroupNumber, totalMessageSize, MAX_PROTOCOL_DATA_LENGTH); + LOG_WARNING("[TP]: Ignoring Broadcast Announcement Message (BAM) for 0x%05X, message size (%hu) is greater than the maximum (%hu).", + parameterGroupNumber, + totalMessageSize, + MAX_PROTOCOL_DATA_LENGTH); } else { auto oldSession = get_session(source, nullptr); if (nullptr != oldSession) { - CANStackLogger::warn("[TP]: Received Broadcast Announcement Message (BAM) while a session already existed for this source (%hu), overwriting for 0x%05X...", source->get_address(), parameterGroupNumber); + LOG_WARNING("[TP]: Received Broadcast Announcement Message (BAM) while a session already existed for this source (%hu), overwriting for 0x%05X...", + source->get_address(), + parameterGroupNumber); close_session(oldSession, false); } @@ -165,14 +171,14 @@ namespace isobus if (newSession->get_total_number_of_packets() != totalNumberOfPackets) { - CANStackLogger::warn("[TP]: Received Broadcast Announcement Message (BAM) for 0x%05X with a bad number of packets, aborting...", parameterGroupNumber); + LOG_WARNING("[TP]: Received Broadcast Announcement Message (BAM) for 0x%05X with a bad number of packets, aborting...", parameterGroupNumber); } else { newSession->set_state(StateMachineState::WaitForDataTransferPacket); activeSessions.push_back(newSession); update_state_machine(newSession); - CANStackLogger::debug("[TP]: New rx broadcast message session for 0x%05X. Source: %hu", parameterGroupNumber, source->get_address()); + LOG_DEBUG("[TP]: New rx broadcast message session for 0x%05X. Source: %hu", parameterGroupNumber, source->get_address()); } } } @@ -187,7 +193,7 @@ namespace isobus if (activeSessions.size() >= configuration->get_max_number_transport_protocol_sessions()) { // TODO: consider using maximum memory instead of maximum number of sessions - CANStackLogger::warn("[TP]: Replying with abort to Request To Send (RTS) for 0x%05X, configured maximum number of sessions reached.", parameterGroupNumber); + LOG_WARNING("[TP]: Replying with abort to Request To Send (RTS) for 0x%05X, configured maximum number of sessions reached.", parameterGroupNumber); send_abort(std::static_pointer_cast(destination), source, parameterGroupNumber, ConnectionAbortReason::AlreadyInCMSession); } else @@ -197,12 +203,14 @@ namespace isobus { if (oldSession->get_parameter_group_number() != parameterGroupNumber) { - CANStackLogger::error("[TP]: Received Request To Send (RTS) while a session already existed for this source and destination, aborting for 0x%05X...", parameterGroupNumber); + LOG_ERROR("[TP]: Received Request To Send (RTS) while a session already existed for this source and destination, aborting for 0x%05X...", + parameterGroupNumber); abort_session(oldSession, ConnectionAbortReason::AlreadyInCMSession); } else { - CANStackLogger::warn("[TP]: Received Request To Send (RTS) while a session already existed for this source and destination and parameterGroupNumber, overwriting for 0x%05X...", parameterGroupNumber); + LOG_WARNING("[TP]: Received Request To Send (RTS) while a session already existed for this source and destination and parameterGroupNumber, overwriting for 0x%05X...", + parameterGroupNumber); close_session(oldSession, false); } } @@ -211,7 +219,9 @@ namespace isobus if (clearToSendPacketMax > configuration->get_number_of_packets_per_cts_message()) { - CANStackLogger::debug("[TP]: Received Request To Send (RTS) with a CTS packet count of %hu, which is greater than the configured maximum of %hu, using the configured maximum instead.", clearToSendPacketMax, configuration->get_number_of_packets_per_cts_message()); + LOG_DEBUG("[TP]: Received Request To Send (RTS) with a CTS packet count of %hu, which is greater than the configured maximum of %hu, using the configured maximum instead.", + clearToSendPacketMax, + configuration->get_number_of_packets_per_cts_message()); clearToSendPacketMax = configuration->get_number_of_packets_per_cts_message(); } @@ -227,14 +237,14 @@ namespace isobus if (newSession->get_total_number_of_packets() != totalNumberOfPackets) { - CANStackLogger::error("[TP]: Received Request To Send (RTS) for 0x%05X with a bad number of packets, aborting...", parameterGroupNumber); + LOG_ERROR("[TP]: Received Request To Send (RTS) for 0x%05X with a bad number of packets, aborting...", parameterGroupNumber); abort_session(newSession, ConnectionAbortReason::AnyOtherError); } else { newSession->set_state(StateMachineState::SendClearToSend); activeSessions.push_back(newSession); - CANStackLogger::debug("[TP]: New rx session for 0x%05X. Source: %hu, destination: %hu", parameterGroupNumber, source->get_address(), destination->get_address()); + LOG_DEBUG("[TP]: New rx session for 0x%05X. Source: %hu, destination: %hu", parameterGroupNumber, source->get_address(), destination->get_address()); update_state_machine(newSession); } } @@ -251,19 +261,19 @@ namespace isobus { if (session->get_parameter_group_number() != parameterGroupNumber) { - CANStackLogger::error("[TP]: Received a Clear To Send (CTS) message for 0x%05X while a session already existed for this source and destination, sending abort for both...", parameterGroupNumber); + LOG_ERROR("[TP]: Received a Clear To Send (CTS) message for 0x%05X while a session already existed for this source and destination, sending abort for both...", parameterGroupNumber); abort_session(session, ConnectionAbortReason::ClearToSendReceivedWhileTransferInProgress); send_abort(std::static_pointer_cast(destination), source, parameterGroupNumber, ConnectionAbortReason::ClearToSendReceivedWhileTransferInProgress); } else if (nextPacketNumber > session->get_total_number_of_packets()) { - CANStackLogger::error("[TP]: Received a Clear To Send (CTS) message for 0x%05X with a bad sequence number, aborting...", parameterGroupNumber); + LOG_ERROR("[TP]: Received a Clear To Send (CTS) message for 0x%05X with a bad sequence number, aborting...", parameterGroupNumber); abort_session(session, ConnectionAbortReason::BadSequenceNumber); } else if (StateMachineState::WaitForClearToSend != session->state) { // The session exists, but we're not in the right state to receive a CTS, so we must abort - CANStackLogger::warn("[TP]: Received a Clear To Send (CTS) message for 0x%05X, but not expecting one, aborting session.", parameterGroupNumber); + LOG_WARNING("[TP]: Received a Clear To Send (CTS) message for 0x%05X, but not expecting one, aborting session.", parameterGroupNumber); abort_session(session, ConnectionAbortReason::ClearToSendReceivedWhileTransferInProgress); } else @@ -282,7 +292,7 @@ namespace isobus else { // We got a CTS but no session exists, by the standard we must ignore it - CANStackLogger::warn("[TP]: Received Clear To Send (CTS) for 0x%05X while no session existed for this source and destination, ignoring...", parameterGroupNumber); + LOG_WARNING("[TP]: Received Clear To Send (CTS) for 0x%05X while no session existed for this source and destination, ignoring...", parameterGroupNumber); } } @@ -297,17 +307,17 @@ namespace isobus { session->state = StateMachineState::None; close_session(session, true); - CANStackLogger::debug("[TP]: Completed tx session for 0x%05X from %hu", parameterGroupNumber, source->get_address()); + LOG_DEBUG("[TP]: Completed tx session for 0x%05X from %hu", parameterGroupNumber, source->get_address()); } else { // The session exists, but we're not in the right state to receive an EOM, by the standard we must ignore it - CANStackLogger::warn("[TP]: Received an End Of Message Acknowledgement message for 0x%05X, but not expecting one, ignoring.", parameterGroupNumber); + LOG_WARNING("[TP]: Received an End Of Message Acknowledgement message for 0x%05X, but not expecting one, ignoring.", parameterGroupNumber); } } else { - CANStackLogger::warn("[TP]: Received End Of Message Acknowledgement for 0x%05X while no session existed for this source and destination, ignoring.", parameterGroupNumber); + LOG_WARNING("[TP]: Received End Of Message Acknowledgement for 0x%05X while no session existed for this source and destination, ignoring.", parameterGroupNumber); } } @@ -322,20 +332,26 @@ namespace isobus if ((nullptr != session) && (session->get_parameter_group_number() == parameterGroupNumber)) { foundSession = true; - CANStackLogger::error("[TP]: Received an abort (reason=%hu) for an rx session for parameterGroupNumber 0x%05X", static_cast(reason), parameterGroupNumber); + LOG_ERROR("[TP]: Received an abort (reason=%hu) for an rx session for parameterGroupNumber 0x%05X", + static_cast(reason), + parameterGroupNumber); close_session(session, false); } session = get_session(destination, source); if ((nullptr != session) && (session->get_parameter_group_number() == parameterGroupNumber)) { foundSession = true; - CANStackLogger::error("[TP]: Received an abort (reason=%hu) for a tx session for parameterGroupNumber 0x%05X", static_cast(reason), parameterGroupNumber); + LOG_ERROR("[TP]: Received an abort (reason=%hu) for a tx session for parameterGroupNumber 0x%05X", + static_cast(reason), + parameterGroupNumber); close_session(session, false); } if (!foundSession) { - CANStackLogger::warn("[TP]: Received an abort (reason=%hu) with no matching session for parameterGroupNumber 0x%05X", static_cast(reason), parameterGroupNumber); + LOG_WARNING("[TP]: Received an abort (reason=%hu) with no matching session for parameterGroupNumber 0x%05X", + static_cast(reason), + parameterGroupNumber); } } @@ -343,7 +359,7 @@ namespace isobus { if (CAN_DATA_LENGTH != message.get_data_length()) { - CANStackLogger::warn("[TP]: Received a Connection Management message of invalid length %hu", message.get_data_length()); + LOG_WARNING("[TP]: Received a Connection Management message of invalid length %hu", message.get_data_length()); return; } @@ -364,7 +380,7 @@ namespace isobus } else { - CANStackLogger::warn("[TP]: Received a Broadcast Announcement Message (BAM) with a non-global destination, ignoring"); + LOG_WARNING("[TP]: Received a Broadcast Announcement Message (BAM) with a non-global destination, ignoring"); } } break; @@ -373,7 +389,7 @@ namespace isobus { if (message.is_broadcast()) { - CANStackLogger::warn("[TP]: Received a Request to Send (RTS) message with a global destination, ignoring"); + LOG_WARNING("[TP]: Received a Request to Send (RTS) message with a global destination, ignoring"); } else { @@ -394,7 +410,7 @@ namespace isobus { if (message.is_broadcast()) { - CANStackLogger::warn("[TP]: Received a Clear to Send (CTS) message with a global destination, ignoring"); + LOG_WARNING("[TP]: Received a Clear to Send (CTS) message with a global destination, ignoring"); } else { @@ -413,7 +429,7 @@ namespace isobus { if (message.is_broadcast()) { - CANStackLogger::warn("[TP]: Received an End of Message Acknowledge message with a global destination, ignoring"); + LOG_WARNING("[TP]: Received an End of Message Acknowledge message with a global destination, ignoring"); } else { @@ -428,7 +444,7 @@ namespace isobus { if (message.is_broadcast()) { - CANStackLogger::warn("[TP]: Received an Abort message with a global destination, ignoring"); + LOG_WARNING("[TP]: Received an Abort message with a global destination, ignoring"); } else { @@ -443,7 +459,7 @@ namespace isobus default: { - CANStackLogger::warn("[TP]: Bad Mux in Transport Protocol Connection Management message"); + LOG_WARNING("[TP]: Bad Mux in Transport Protocol Connection Management message"); } break; } @@ -453,7 +469,7 @@ namespace isobus { if (CAN_DATA_LENGTH != message.get_data_length()) { - CANStackLogger::warn("[TP]: Received a Data Transfer message of invalid length %hu", message.get_data_length()); + LOG_WARNING("[TP]: Received a Data Transfer message of invalid length %hu", message.get_data_length()); return; } @@ -467,12 +483,12 @@ namespace isobus { if (StateMachineState::WaitForDataTransferPacket != session->state) { - CANStackLogger::warn("[TP]: Received a Data Transfer message from %hu while not expecting one, sending abort", source->get_address()); + LOG_WARNING("[TP]: Received a Data Transfer message from %hu while not expecting one, sending abort", source->get_address()); abort_session(session, ConnectionAbortReason::UnexpectedDataTransferPacketReceived); } else if (sequenceNumber == session->get_last_sequence_number()) { - CANStackLogger::error("[TP]: Aborting rx session for 0x%05X due to duplicate sequence number", session->get_parameter_group_number()); + LOG_ERROR("[TP]: Aborting rx session for 0x%05X due to duplicate sequence number", session->get_parameter_group_number()); abort_session(session, ConnectionAbortReason::DuplicateSequenceNumber); } else if (sequenceNumber == (session->get_last_sequence_number() + 1)) @@ -519,7 +535,7 @@ namespace isobus canMessageReceivedCallback(completedMessage); close_session(session, true); - CANStackLogger::debug("[TP]: Completed rx session for 0x%05X from %hu", session->get_parameter_group_number(), source->get_address()); + LOG_DEBUG("[TP]: Completed rx session for 0x%05X from %hu", session->get_parameter_group_number(), source->get_address()); } else if (session->get_cts_number_of_packets_remaining() == 0) { @@ -528,13 +544,13 @@ namespace isobus } else { - CANStackLogger::error("[TP]: Aborting rx session for 0x%05X due to bad sequence number", session->get_parameter_group_number()); + LOG_ERROR("[TP]: Aborting rx session for 0x%05X due to bad sequence number", session->get_parameter_group_number()); abort_session(session, ConnectionAbortReason::BadSequenceNumber); } } else if (!message.is_broadcast()) { - CANStackLogger::warn("[TP]: Received a Data Transfer message from %hu with no matching session, ignoring...", source->get_address()); + LOG_WARNING("[TP]: Received a Data Transfer message from %hu with no matching session, ignoring...", source->get_address()); } } @@ -600,18 +616,18 @@ namespace isobus { // Broadcast message session->set_state(StateMachineState::SendBroadcastAnnounce); - CANStackLogger::debug("[TP]: New broadcast tx session for 0x%05X. Source: %hu", - parameterGroupNumber, - source->get_address()); + LOG_DEBUG("[TP]: New broadcast tx session for 0x%05X. Source: %hu", + parameterGroupNumber, + source->get_address()); } else { // Destination specific message session->set_state(StateMachineState::SendRequestToSend); - CANStackLogger::debug("[TP]: New tx session for 0x%05X. Source: %hu, destination: %hu", - parameterGroupNumber, - source->get_address(), - destination->get_address()); + LOG_DEBUG("[TP]: New tx session for 0x%05X. Source: %hu, destination: %hu", + parameterGroupNumber, + source->get_address(), + destination->get_address()); } activeSessions.push_back(session); update_state_machine(session); @@ -626,12 +642,12 @@ namespace isobus auto session = activeSessions.at(i - 1); if (!session->get_source()->get_address_valid()) { - CANStackLogger::warn("[TP]: Closing active session as the source control function is no longer valid"); + LOG_WARNING("[TP]: Closing active session as the source control function is no longer valid"); close_session(session, false); } else if (!session->is_broadcast() && !session->get_destination()->get_address_valid()) { - CANStackLogger::warn("[TP]: Closing active session as the destination control function is no longer valid"); + LOG_WARNING("[TP]: Closing active session as the destination control function is no longer valid"); close_session(session, false); } else if (StateMachineState::None != session->state) @@ -692,7 +708,7 @@ namespace isobus { if (session->is_broadcast()) { - CANStackLogger::debug("[TP]: Completed broadcast tx session for 0x%05X", session->get_parameter_group_number()); + LOG_DEBUG("[TP]: Completed broadcast tx session for 0x%05X", session->get_parameter_group_number()); close_session(session, true); } else @@ -735,7 +751,7 @@ namespace isobus { if (session->get_time_since_last_update() > T2_T3_TIMEOUT_MS) { - CANStackLogger::error("[TP]: Timeout tx session for 0x%05X (expected CTS)", session->get_parameter_group_number()); + LOG_ERROR("[TP]: Timeout tx session for 0x%05X (expected CTS)", session->get_parameter_group_number()); if (session->get_cts_number_of_packets() > 0) { // A connection is only considered established if we've received at least one CTS before @@ -779,7 +795,7 @@ namespace isobus // Broadcast message timeout check if (session->get_time_since_last_update() > T1_TIMEOUT_MS) { - CANStackLogger::warn("[TP]: Broadcast rx session timeout"); + LOG_WARNING("[TP]: Broadcast rx session timeout"); close_session(session, false); } } @@ -788,7 +804,7 @@ namespace isobus // Waiting to receive the first data frame after CTS if (session->get_time_since_last_update() > T2_T3_TIMEOUT_MS) { - CANStackLogger::error("[TP]: Timeout for destination-specific rx session (expected first data frame)"); + LOG_ERROR("[TP]: Timeout for destination-specific rx session (expected first data frame)"); abort_session(session, ConnectionAbortReason::Timeout); } } @@ -797,7 +813,7 @@ namespace isobus // Waiting on sequential data frames if (session->get_time_since_last_update() > T1_TIMEOUT_MS) { - CANStackLogger::error("[TP]: Timeout for destination-specific rx session (expected sequential data frame)"); + LOG_ERROR("[TP]: Timeout for destination-specific rx session (expected sequential data frame)"); abort_session(session, ConnectionAbortReason::Timeout); } } @@ -808,7 +824,7 @@ namespace isobus { if (session->get_time_since_last_update() > T2_T3_TIMEOUT_MS) { - CANStackLogger::error("[TP]: Timeout tx session for 0x%05X (expected EOMA)", session->get_parameter_group_number()); + LOG_ERROR("[TP]: Timeout tx session for 0x%05X (expected EOMA)", session->get_parameter_group_number()); abort_session(session, ConnectionAbortReason::Timeout); } } @@ -870,7 +886,7 @@ namespace isobus if (activeSessions.end() != sessionLocation) { activeSessions.erase(sessionLocation); - CANStackLogger::debug("[TP]: Session Closed"); + LOG_DEBUG("[TP]: Session Closed"); } } @@ -987,7 +1003,6 @@ namespace isobus auto result = std::find_if(activeSessions.begin(), activeSessions.end(), [&](const std::shared_ptr &session) { return session->matches(source, destination); }); - // Instead of returning a pointer, we return by reference to indicate it should not be deleted or stored return (activeSessions.end() != result) ? (*result) : nullptr; } diff --git a/src/can_transport_protocol.hpp b/src/can_transport_protocol.hpp index 3fd81bf..adcd0b6 100644 --- a/src/can_transport_protocol.hpp +++ b/src/can_transport_protocol.hpp @@ -304,7 +304,7 @@ namespace isobus /// @param[in] message The CAN message to be processed. void process_data_transfer_message(const CANMessage &message); - /// @brief Gets a TP session from the passed in source and destination and PGN combination + /// @brief Gets a TP session from the passed in source and destination combination /// @param[in] source The source control function for the session /// @param[in] destination The destination control function for the session /// @returns a matching session, or nullptr if no session matched the supplied parameters diff --git a/src/flex_can_t4_plugin.cpp b/src/flex_can_t4_plugin.cpp index ede267b..fce970e 100644 --- a/src/flex_can_t4_plugin.cpp +++ b/src/flex_can_t4_plugin.cpp @@ -4,7 +4,7 @@ /// @brief An interface for using Teensy4/4.1 CAN hardware /// @author Adrian Del Grosso /// -/// @copyright 2023 Adrian Del Grosso +/// @copyright 2023 The Open-Agriculture Developers //================================================================================================ #include "flex_can_t4_plugin.hpp" @@ -62,7 +62,7 @@ namespace isobus #endif else { - isobus::CANStackLogger::critical("[FlexCAN]: Invalid Channel Selected"); + LOG_CRITICAL("[FlexCAN]: Invalid Channel Selected"); } } diff --git a/src/flex_can_t4_plugin.hpp b/src/flex_can_t4_plugin.hpp index 1623cee..daf9ed2 100644 --- a/src/flex_can_t4_plugin.hpp +++ b/src/flex_can_t4_plugin.hpp @@ -4,7 +4,7 @@ /// @brief An interface for using FlexCAN_T4 on a Teensy4/4.1 device. /// @author Adrian Del Grosso /// -/// @copyright 2023 Adrian Del Grosso +/// @copyright 2023 The Open-Agriculture Developers //================================================================================================ #ifndef FLEX_CAN_T4_PLUGIN_HPP #define FLEX_CAN_T4_PLUGIN_HPP diff --git a/src/isobus_data_dictionary.cpp b/src/isobus_data_dictionary.cpp index 6f4212a..9d8a774 100644 --- a/src/isobus_data_dictionary.cpp +++ b/src/isobus_data_dictionary.cpp @@ -13,6 +13,7 @@ namespace isobus { const DataDictionary::Entry &DataDictionary::get_entry(std::uint16_t dataDictionaryIdentifier) { +#ifndef DISABLE_ISOBUS_DATA_DICTIONARY for (std::uint_fast16_t i = 0; i < sizeof(DDI_ENTRIES) / sizeof(DataDictionary::Entry); i++) { if (DDI_ENTRIES[i].ddi == dataDictionaryIdentifier) @@ -20,11 +21,13 @@ namespace isobus return DDI_ENTRIES[i]; } } +#endif return DEFAULT_ENTRY; } const DataDictionary::Entry DataDictionary::DEFAULT_ENTRY = { 65535, "Unknown", "Unknown", 0.0f }; +#ifndef DISABLE_ISOBUS_DATA_DICTIONARY // The table below is auto-generated, and is not to be edited manually. const DataDictionary::Entry DataDictionary::DDI_ENTRIES[] = { { 0, "Internal Data Base DDI", "None", 1.0f }, @@ -743,5 +746,6 @@ namespace isobus { 57344, "65534 Proprietary DDI Range", "None", 0.0f }, { 65535, "Reserved", "None", 0.0f }, }; +#endif } // namespace isobus diff --git a/src/isobus_data_dictionary.hpp b/src/isobus_data_dictionary.hpp index 5fb1100..91bfd21 100644 --- a/src/isobus_data_dictionary.hpp +++ b/src/isobus_data_dictionary.hpp @@ -37,7 +37,9 @@ namespace isobus static const Entry &get_entry(std::uint16_t dataDictionaryIdentifier); private: +#ifndef DISABLE_ISOBUS_DATA_DICTIONARY static const Entry DDI_ENTRIES[715]; ///< A lookup table of all DDI entries in ISO11783-11 +#endif static const Entry DEFAULT_ENTRY; ///< A default "unknown" DDI to return if a DDI is not in the database }; } // namespace isobus diff --git a/src/isobus_device_descriptor_object_pool.cpp b/src/isobus_device_descriptor_object_pool.cpp index 2223f0b..d836e64 100644 --- a/src/isobus_device_descriptor_object_pool.cpp +++ b/src/isobus_device_descriptor_object_pool.cpp @@ -4,7 +4,7 @@ /// @brief Implements an interface for creating a Task Controller DDOP. /// @author Adrian Del Grosso /// -/// @copyright 2023 Adrian Del Grosso +/// @copyright 2023 The Open-Agriculture Developers //================================================================================================ #include "isobus_device_descriptor_object_pool.hpp" @@ -51,71 +51,71 @@ namespace isobus if ((taskControllerCompatibilityLevel < MAX_TC_VERSION_SUPPORTED) && (deviceDesignator.size() > task_controller_object::Object::MAX_DESIGNATOR_LEGACY_LENGTH)) { - CANStackLogger::warn("[DDOP]: Device designator " + - deviceDesignator + - " is greater than the max byte length of 32. Value will be truncated."); + LOG_WARNING("[DDOP]: Device designator " + + deviceDesignator + + " is greater than the max byte length of 32. Value will be truncated."); deviceDesignator.resize(task_controller_object::Object::MAX_DESIGNATOR_LEGACY_LENGTH); } else if ((taskControllerCompatibilityLevel == MAX_TC_VERSION_SUPPORTED) && (deviceDesignator.size() > task_controller_object::Object::MAX_DESIGNATOR_LENGTH)) { - CANStackLogger::warn("[DDOP]: Device designator " + - deviceDesignator + - " is greater than the max byte length of 128. Value will be truncated."); + LOG_WARNING("[DDOP]: Device designator " + + deviceDesignator + + " is greater than the max byte length of 128. Value will be truncated."); deviceDesignator.resize(task_controller_object::Object::MAX_DESIGNATOR_LENGTH); } else if ((taskControllerCompatibilityLevel == MAX_TC_VERSION_SUPPORTED) && (deviceDesignator.size() > task_controller_object::Object::MAX_DESIGNATOR_LEGACY_LENGTH)) { - CANStackLogger::info("[DDOP]: Device designator " + - deviceDesignator + - " byte length is greater than the max character count of 32. " + - "This is only acceptable if you have 32 or fewer UTF-8 characters!" + - " Please verify your DDOP configuration meets this requirement."); + LOG_INFO("[DDOP]: Device designator " + + deviceDesignator + + " byte length is greater than the max character count of 32. " + + "This is only acceptable if you have 32 or fewer UTF-8 characters!" + + " Please verify your DDOP configuration meets this requirement."); } if ((taskControllerCompatibilityLevel < MAX_TC_VERSION_SUPPORTED) && (deviceSerialNumber.size() > task_controller_object::Object::MAX_DESIGNATOR_LEGACY_LENGTH)) { - CANStackLogger::warn("[DDOP]: Device serial number " + - deviceSerialNumber + - " is greater than the max byte length of 32. Value will be truncated."); + LOG_WARNING("[DDOP]: Device serial number " + + deviceSerialNumber + + " is greater than the max byte length of 32. Value will be truncated."); deviceSerialNumber.resize(task_controller_object::Object::MAX_DESIGNATOR_LEGACY_LENGTH); } else if ((taskControllerCompatibilityLevel == MAX_TC_VERSION_SUPPORTED) && (deviceSerialNumber.size() > task_controller_object::Object::MAX_DESIGNATOR_LENGTH)) { - CANStackLogger::warn("[DDOP]: Device serial number " + - deviceSerialNumber + - " is greater than the max byte length of 128. Value will be truncated."); + LOG_WARNING("[DDOP]: Device serial number " + + deviceSerialNumber + + " is greater than the max byte length of 128. Value will be truncated."); deviceSerialNumber.resize(task_controller_object::Object::MAX_DESIGNATOR_LENGTH); } else if ((taskControllerCompatibilityLevel == MAX_TC_VERSION_SUPPORTED) && (deviceSerialNumber.size() > task_controller_object::Object::MAX_DESIGNATOR_LEGACY_LENGTH)) { - CANStackLogger::info("[DDOP]: Device serial number " + - deviceSerialNumber + - " byte length is greater than the max character count of 32. " + - "This is only acceptable if you have 32 or fewer UTF-8 characters!" + - " Please verify your DDOP configuration meets this requirement."); + LOG_INFO("[DDOP]: Device serial number " + + deviceSerialNumber + + " byte length is greater than the max character count of 32. " + + "This is only acceptable if you have 32 or fewer UTF-8 characters!" + + " Please verify your DDOP configuration meets this requirement."); } if (deviceStructureLabel.size() > task_controller_object::DeviceObject::MAX_STRUCTURE_AND_LOCALIZATION_LABEL_LENGTH) { - CANStackLogger::warn("[DDOP]: Device structure label " + - deviceStructureLabel + - " is greater than the max length of 7. Value will be truncated."); + LOG_WARNING("[DDOP]: Device structure label " + + deviceStructureLabel + + " is greater than the max length of 7. Value will be truncated."); deviceStructureLabel.resize(task_controller_object::DeviceObject::MAX_STRUCTURE_AND_LOCALIZATION_LABEL_LENGTH); } if (deviceExtendedStructureLabel.size() > task_controller_object::DeviceObject::MAX_EXTENDED_STRUCTURE_LABEL_LENGTH) { - CANStackLogger::warn("[DDOP]: Device extended structure label is greater than the max length of 32. Value will be truncated."); + LOG_WARNING("[DDOP]: Device extended structure label is greater than the max length of 32. Value will be truncated."); deviceExtendedStructureLabel.resize(task_controller_object::DeviceObject::MAX_EXTENDED_STRUCTURE_LABEL_LENGTH); } if (deviceLocalizationLabel[6] != 0xFF) { - CANStackLogger::warn("[DDOP]: Device localization label byte 7 must be the reserved value 0xFF. This value will be enforced when DDOP binary is generated."); + LOG_WARNING("[DDOP]: Device localization label byte 7 must be the reserved value 0xFF. This value will be enforced when DDOP binary is generated."); } objectList.emplace_back(new task_controller_object::DeviceObject(deviceDesignator, deviceSoftwareVersion, @@ -128,7 +128,7 @@ namespace isobus } else { - CANStackLogger::error("[DDOP]: Cannot add more than 1 Device object to a DDOP."); + LOG_ERROR("[DDOP]: Cannot add more than 1 Device object to a DDOP."); } return retVal; } @@ -148,17 +148,17 @@ namespace isobus if ((taskControllerCompatibilityLevel < MAX_TC_VERSION_SUPPORTED) && (deviceElementDesignator.size() > task_controller_object::Object::MAX_DESIGNATOR_LEGACY_LENGTH)) { - CANStackLogger::warn("[DDOP]: Device element designator " + - deviceElementDesignator + - " is greater than the max length of 32. Value will be truncated."); + LOG_WARNING("[DDOP]: Device element designator " + + deviceElementDesignator + + " is greater than the max length of 32. Value will be truncated."); deviceElementDesignator.resize(task_controller_object::Object::MAX_DESIGNATOR_LEGACY_LENGTH); } if ((taskControllerCompatibilityLevel == MAX_TC_VERSION_SUPPORTED) && (deviceElementDesignator.size() > task_controller_object::Object::MAX_DESIGNATOR_LENGTH)) { - CANStackLogger::warn("[DDOP]: Device element designator " + - deviceElementDesignator + - " is greater than the max length of 128. Value will be truncated."); + LOG_WARNING("[DDOP]: Device element designator " + + deviceElementDesignator + + " is greater than the max length of 128. Value will be truncated."); deviceElementDesignator.resize(task_controller_object::Object::MAX_DESIGNATOR_LENGTH); } @@ -170,9 +170,9 @@ namespace isobus } else { - CANStackLogger::error("[DDOP]: Device element ID " + - isobus::to_string(static_cast(uniqueID)) + - " is not unique. Object will not be added to the DDOP."); + LOG_ERROR("[DDOP]: Device element ID " + + isobus::to_string(static_cast(uniqueID)) + + " is not unique. Object will not be added to the DDOP."); } return retVal; } @@ -192,35 +192,35 @@ namespace isobus // Check for warnings if ((processDataProperties & 0x02) && (processDataProperties & 0x04)) { - CANStackLogger::warn("[DDOP]: Process data object " + - isobus::to_string(static_cast(uniqueID)) + - " has mutually exclusive options 'settable' and 'control source' set."); + LOG_WARNING("[DDOP]: Process data object " + + isobus::to_string(static_cast(uniqueID)) + + " has mutually exclusive options 'settable' and 'control source' set."); } if ((taskControllerCompatibilityLevel < MAX_TC_VERSION_SUPPORTED) && (processDataDesignator.size() > task_controller_object::Object::MAX_DESIGNATOR_LEGACY_LENGTH)) { - CANStackLogger::warn("[DDOP]: Device process data designator " + - processDataDesignator + - " is greater than the max byte length of 32. Value will be truncated."); + LOG_WARNING("[DDOP]: Device process data designator " + + processDataDesignator + + " is greater than the max byte length of 32. Value will be truncated."); processDataDesignator.resize(task_controller_object::Object::MAX_DESIGNATOR_LEGACY_LENGTH); } else if ((taskControllerCompatibilityLevel == MAX_TC_VERSION_SUPPORTED) && (processDataDesignator.size() > task_controller_object::Object::MAX_DESIGNATOR_LENGTH)) { - CANStackLogger::warn("[DDOP]: Device process data designator " + - processDataDesignator + - " is greater than the max byte length of 128. Value will be truncated."); + LOG_WARNING("[DDOP]: Device process data designator " + + processDataDesignator + + " is greater than the max byte length of 128. Value will be truncated."); processDataDesignator.resize(task_controller_object::Object::MAX_DESIGNATOR_LENGTH); } else if ((taskControllerCompatibilityLevel == MAX_TC_VERSION_SUPPORTED) && (processDataDesignator.size() > task_controller_object::Object::MAX_DESIGNATOR_LEGACY_LENGTH)) { - CANStackLogger::info("[DDOP]: Device process data designator " + - processDataDesignator + - " byte length is greater than the max character count of 32. " + - "This is only acceptable if you have 32 or fewer UTF-8 characters!" + - " Please verify your DDOP configuration meets this requirement."); + LOG_INFO("[DDOP]: Device process data designator " + + processDataDesignator + + " byte length is greater than the max character count of 32. " + + "This is only acceptable if you have 32 or fewer UTF-8 characters!" + + " Please verify your DDOP configuration meets this requirement."); } objectList.emplace_back(new task_controller_object::DeviceProcessDataObject(processDataDesignator, @@ -232,9 +232,9 @@ namespace isobus } else { - CANStackLogger::error("[DDOP]: Device process data ID " + - isobus::to_string(static_cast(uniqueID)) + - " is not unique. Object will not be added to the DDOP."); + LOG_ERROR("[DDOP]: Device process data ID " + + isobus::to_string(static_cast(uniqueID)) + + " is not unique. Object will not be added to the DDOP."); } return retVal; } @@ -254,27 +254,27 @@ namespace isobus if ((taskControllerCompatibilityLevel < MAX_TC_VERSION_SUPPORTED) && (propertyDesignator.size() > task_controller_object::Object::MAX_DESIGNATOR_LEGACY_LENGTH)) { - CANStackLogger::warn("[DDOP]: Device property designator " + - propertyDesignator + - " is greater than the max byte length of 32. Value will be truncated."); + LOG_WARNING("[DDOP]: Device property designator " + + propertyDesignator + + " is greater than the max byte length of 32. Value will be truncated."); propertyDesignator.resize(task_controller_object::Object::MAX_DESIGNATOR_LEGACY_LENGTH); } else if ((taskControllerCompatibilityLevel == MAX_TC_VERSION_SUPPORTED) && (propertyDesignator.size() > task_controller_object::Object::MAX_DESIGNATOR_LENGTH)) { - CANStackLogger::warn("[DDOP]: Device property designator " + - propertyDesignator + - " is greater than the max byte length of 128. Value will be truncated."); + LOG_WARNING("[DDOP]: Device property designator " + + propertyDesignator + + " is greater than the max byte length of 128. Value will be truncated."); propertyDesignator.resize(task_controller_object::Object::MAX_DESIGNATOR_LENGTH); } else if ((taskControllerCompatibilityLevel == MAX_TC_VERSION_SUPPORTED) && (propertyDesignator.size() > task_controller_object::Object::MAX_DESIGNATOR_LEGACY_LENGTH)) { - CANStackLogger::info("[DDOP]: Device property designator " + - propertyDesignator + - " byte length is greater than the max character count of 32. " + - "This is only acceptable if you have 32 or fewer UTF-8 characters!" + - " Please verify your DDOP configuration meets this requirement."); + LOG_INFO("[DDOP]: Device property designator " + + propertyDesignator + + " byte length is greater than the max character count of 32. " + + "This is only acceptable if you have 32 or fewer UTF-8 characters!" + + " Please verify your DDOP configuration meets this requirement."); } objectList.emplace_back(new task_controller_object::DevicePropertyObject(propertyDesignator, @@ -285,9 +285,9 @@ namespace isobus } else { - CANStackLogger::error("[DDOP]: Device property ID " + - isobus::to_string(static_cast(uniqueID)) + - " is not unique. Object will not be added to the DDOP."); + LOG_ERROR("[DDOP]: Device property ID " + + isobus::to_string(static_cast(uniqueID)) + + " is not unique. Object will not be added to the DDOP."); } return retVal; } @@ -307,27 +307,27 @@ namespace isobus if ((taskControllerCompatibilityLevel < MAX_TC_VERSION_SUPPORTED) && (unitDesignator.size() > task_controller_object::Object::MAX_DESIGNATOR_LEGACY_LENGTH)) { - CANStackLogger::warn("[DDOP]: Device value presentation unit designator " + - unitDesignator + - " is greater than the max byte length of 32. Value will be truncated."); + LOG_WARNING("[DDOP]: Device value presentation unit designator " + + unitDesignator + + " is greater than the max byte length of 32. Value will be truncated."); unitDesignator.resize(task_controller_object::Object::MAX_DESIGNATOR_LEGACY_LENGTH); } else if ((taskControllerCompatibilityLevel == MAX_TC_VERSION_SUPPORTED) && (unitDesignator.size() > task_controller_object::Object::MAX_DESIGNATOR_LENGTH)) { - CANStackLogger::warn("[DDOP]: Device value presentation unit designator " + - unitDesignator + - " is greater than the max byte length of 128. Value will be truncated."); + LOG_WARNING("[DDOP]: Device value presentation unit designator " + + unitDesignator + + " is greater than the max byte length of 128. Value will be truncated."); unitDesignator.resize(task_controller_object::Object::MAX_DESIGNATOR_LENGTH); } else if ((taskControllerCompatibilityLevel == MAX_TC_VERSION_SUPPORTED) && (unitDesignator.size() > task_controller_object::Object::MAX_DESIGNATOR_LEGACY_LENGTH)) { - CANStackLogger::info("[DDOP]: Device value presentation unit designator " + - unitDesignator + - " byte length is greater than the max character count of 32. " + - "This is only acceptable if you have 32 or fewer UTF-8 characters!" + - " Please verify your DDOP configuration meets this requirement."); + LOG_INFO("[DDOP]: Device value presentation unit designator " + + unitDesignator + + " byte length is greater than the max character count of 32. " + + "This is only acceptable if you have 32 or fewer UTF-8 characters!" + + " Please verify your DDOP configuration meets this requirement."); } objectList.emplace_back(new task_controller_object::DeviceValuePresentationObject(unitDesignator, @@ -338,9 +338,9 @@ namespace isobus } else { - CANStackLogger::error("[DDOP]: Device value presentation object ID " + - isobus::to_string(static_cast(uniqueID)) + - " is not unique. Object will not be added to the DDOP."); + LOG_ERROR("[DDOP]: Device value presentation object ID " + + isobus::to_string(static_cast(uniqueID)) + + " is not unique. Object will not be added to the DDOP."); } return retVal; } @@ -356,7 +356,7 @@ namespace isobus if ((nullptr != binaryPool) && (0 != binaryPoolSizeBytes)) { - CANStackLogger::debug("[DDOP]: Attempting to deserialize a binary object pool with size %u.", binaryPoolSizeBytes); + LOG_DEBUG("[DDOP]: Attempting to deserialize a binary object pool with size %u.", binaryPoolSizeBytes); clear(); // Iterate over the DDOP and convert to objects. @@ -386,7 +386,7 @@ namespace isobus } else { - CANStackLogger::error("[DDOP]: Binary device object designator has invalid length."); + LOG_ERROR("[DDOP]: Binary device object designator has invalid length."); retVal = false; } @@ -397,7 +397,7 @@ namespace isobus } else { - CANStackLogger::error("[DDOP]: Binary device object software version has invalid length."); + LOG_ERROR("[DDOP]: Binary device object software version has invalid length."); retVal = false; } @@ -408,7 +408,7 @@ namespace isobus } else { - CANStackLogger::error("[DDOP]: Binary device object serial number has invalid length."); + LOG_ERROR("[DDOP]: Binary device object serial number has invalid length."); retVal = false; } @@ -421,7 +421,7 @@ namespace isobus } else { - CANStackLogger::error("[DDOP]: Binary device object with version 4 contains invalid extended structure label length."); + LOG_ERROR("[DDOP]: Binary device object with version 4 contains invalid extended structure label length."); retVal = false; } } @@ -461,7 +461,7 @@ namespace isobus if ((0 != clientNAME.get_full_name()) && (ddopClientNAME != clientNAME.get_full_name())) { - CANStackLogger::error("[DDOP]: Failed adding deserialized device object. DDOP NAME doesn't match client's actual NAME."); + LOG_ERROR("[DDOP]: Failed adding deserialized device object. DDOP NAME doesn't match client's actual NAME."); retVal = false; } else if (0 == clientNAME.get_full_name()) @@ -496,13 +496,13 @@ namespace isobus } else { - CANStackLogger::error("[DDOP]: Failed adding deserialized device object. DDOP schema is not valid."); + LOG_ERROR("[DDOP]: Failed adding deserialized device object. DDOP schema is not valid."); retVal = false; } } else { - CANStackLogger::error("[DDOP]: Not enough binary DDOP data left to parse device object. DDOP schema is not valid"); + LOG_ERROR("[DDOP]: Not enough binary DDOP data left to parse device object. DDOP schema is not valid"); retVal = false; } } @@ -518,7 +518,7 @@ namespace isobus } else { - CANStackLogger::error("[DDOP]: Binary device element object has invalid length."); + LOG_ERROR("[DDOP]: Binary device element object has invalid length."); retVal = false; } @@ -528,13 +528,13 @@ namespace isobus } else { - CANStackLogger::error("[DDOP]: Binary device element object has invalid length to process referenced object IDs."); + LOG_ERROR("[DDOP]: Binary device element object has invalid length to process referenced object IDs."); retVal = false; } if (binaryPool[5] > static_cast(task_controller_object::DeviceElementObject::Type::NavigationReference)) { - CANStackLogger::error("[DDOP]: Binary device element object has invalid element type."); + LOG_ERROR("[DDOP]: Binary device element object has invalid element type."); retVal = false; } @@ -571,13 +571,13 @@ namespace isobus } else { - CANStackLogger::error("[DDOP]: Failed adding deserialized device element object. DDOP schema is not valid."); + LOG_ERROR("[DDOP]: Failed adding deserialized device element object. DDOP schema is not valid."); retVal = false; } } else { - CANStackLogger::error("[DDOP]: Not enough binary DDOP data left to parse device element object. DDOP schema is not valid"); + LOG_ERROR("[DDOP]: Not enough binary DDOP data left to parse device element object. DDOP schema is not valid"); retVal = false; } } @@ -593,7 +593,7 @@ namespace isobus } else { - CANStackLogger::error("[DDOP]: Binary device process data object has invalid length."); + LOG_ERROR("[DDOP]: Binary device process data object has invalid length."); retVal = false; } @@ -618,13 +618,13 @@ namespace isobus } else { - CANStackLogger::error("[DDOP]: Failed adding deserialized device process data object. DDOP schema is not valid."); + LOG_ERROR("[DDOP]: Failed adding deserialized device process data object. DDOP schema is not valid."); retVal = false; } } else { - CANStackLogger::error("[DDOP]: Not enough binary DDOP data left to parse device process data object. DDOP schema is not valid"); + LOG_ERROR("[DDOP]: Not enough binary DDOP data left to parse device process data object. DDOP schema is not valid"); retVal = false; } } @@ -639,7 +639,7 @@ namespace isobus } else { - CANStackLogger::error("[DDOP]: Binary device property object has invalid length."); + LOG_ERROR("[DDOP]: Binary device property object has invalid length."); retVal = false; } @@ -668,13 +668,13 @@ namespace isobus } else { - CANStackLogger::error("[DDOP]: Failed adding deserialized device property object. DDOP schema is not valid."); + LOG_ERROR("[DDOP]: Failed adding deserialized device property object. DDOP schema is not valid."); retVal = false; } } else { - CANStackLogger::error("[DDOP]: Not enough binary DDOP data left to parse device property object. DDOP schema is not valid"); + LOG_ERROR("[DDOP]: Not enough binary DDOP data left to parse device property object. DDOP schema is not valid"); retVal = false; } } @@ -689,7 +689,7 @@ namespace isobus } else { - CANStackLogger::error("[DDOP]: Binary device value presentation object has invalid length."); + LOG_ERROR("[DDOP]: Binary device value presentation object has invalid length."); retVal = false; } @@ -730,19 +730,19 @@ namespace isobus } else { - CANStackLogger::error("[DDOP]: Failed adding deserialized device value presentation object. DDOP schema is not valid."); + LOG_ERROR("[DDOP]: Failed adding deserialized device value presentation object. DDOP schema is not valid."); retVal = false; } } else { - CANStackLogger::error("[DDOP]: Not enough binary DDOP data left to parse device value presentation object. DDOP schema is not valid"); + LOG_ERROR("[DDOP]: Not enough binary DDOP data left to parse device value presentation object. DDOP schema is not valid"); retVal = false; } } else { - CANStackLogger::error("[DDOP]: Cannot process an unknown XML namespace from binary DDOP. DDOP schema is invalid."); + LOG_ERROR("[DDOP]: Cannot process an unknown XML namespace from binary DDOP. DDOP schema is invalid."); retVal = false; } } @@ -753,7 +753,7 @@ namespace isobus if (!retVal) { - CANStackLogger::error("[DDOP]: Binary DDOP deserialization aborted."); + LOG_ERROR("[DDOP]: Binary DDOP deserialization aborted."); break; } } @@ -761,7 +761,7 @@ namespace isobus else { retVal = false; - CANStackLogger::error("[DDOP]: Cannot deserialize a DDOP with zero length."); + LOG_ERROR("[DDOP]: Cannot deserialize a DDOP with zero length."); } return retVal; } @@ -774,7 +774,7 @@ namespace isobus if (taskControllerCompatibilityLevel > MAX_TC_VERSION_SUPPORTED) { - CANStackLogger::warn("[DDOP]: A DDOP is being generated for a TC version that is unsupported. This may cause issues."); + LOG_WARNING("[DDOP]: A DDOP is being generated for a TC version that is unsupported. This may cause issues."); } if (resolve_parent_ids_to_objects()) @@ -790,7 +790,7 @@ namespace isobus } else { - CANStackLogger::error("[DDOP]: Failed to create all object binaries. Your DDOP is invalid."); + LOG_ERROR("[DDOP]: Failed to create all object binaries. Your DDOP is invalid."); retVal = false; break; } @@ -798,7 +798,7 @@ namespace isobus } else { - CANStackLogger::error("[DDOP]: Failed to resolve all object IDs in DDOP. Your DDOP contains invalid object references."); + LOG_ERROR("[DDOP]: Failed to resolve all object IDs in DDOP. Your DDOP contains invalid object references."); retVal = false; } return retVal; @@ -812,7 +812,7 @@ namespace isobus if (taskControllerCompatibilityLevel > MAX_TC_VERSION_SUPPORTED) { - CANStackLogger::warn("[DDOP]: An XML DDOP is being generated for a TC version that is unsupported. This may cause issues."); + LOG_WARNING("[DDOP]: An XML DDOP is being generated for a TC version that is unsupported. This may cause issues."); } if (resolve_parent_ids_to_objects()) @@ -885,7 +885,7 @@ namespace isobus xmlOutput << "\">" << std::endl; // Process a list of all device object references - for (std::size_t k = 0; k < deviceElement->get_number_child_objects(); k++) + for (std::uint16_t k = 0; k < deviceElement->get_number_child_objects(); k++) { xmlOutput << "\t\t(deviceElement->get_child_object_id(k)) << "\"/>" << std::endl; } @@ -969,14 +969,14 @@ namespace isobus xmlOutput << "" << std::endl; xmlOutput << "" << std::endl; resultantString = xmlOutput.str(); - CANStackLogger::debug("[DDOP]: Generated ISO XML DDOP data OK"); + LOG_DEBUG("[DDOP]: Generated ISO XML DDOP data OK"); break; } } } else { - CANStackLogger::error("[DDOP]: Failed to resolve all object IDs in DDOP. Your DDOP contains invalid object references."); + LOG_ERROR("[DDOP]: Failed to resolve all object IDs in DDOP. Your DDOP contains invalid object references."); retVal = false; } return retVal; @@ -1053,9 +1053,9 @@ namespace isobus objectList.clear(); } - std::size_t DeviceDescriptorObjectPool::size() const + std::uint16_t DeviceDescriptorObjectPool::size() const { - return objectList.size(); + return static_cast(objectList.size()); } bool DeviceDescriptorObjectPool::resolve_parent_ids_to_objects() @@ -1087,9 +1087,9 @@ namespace isobus default: { - CANStackLogger::error("[DDOP]: Object " + - isobus::to_string(static_cast(currentObject->get_object_id())) + - " has an invalid parent object type. Only device element objects or device objects may be its parent."); + LOG_ERROR("[DDOP]: Object " + + isobus::to_string(static_cast(currentObject->get_object_id())) + + " has an invalid parent object type. Only device element objects or device objects may be its parent."); retVal = false; } break; @@ -1097,41 +1097,41 @@ namespace isobus } else { - CANStackLogger::error("[DDOP]: Object " + - isobus::to_string(static_cast(currentDeviceElement->get_parent_object())) + - " is not found."); + LOG_ERROR("[DDOP]: Object " + + isobus::to_string(static_cast(currentDeviceElement->get_parent_object())) + + " is not found."); retVal = false; } } else { - CANStackLogger::error("[DDOP]: Object " + - isobus::to_string(static_cast(currentObject->get_object_id())) + - " is an orphan. It's parent is 0xFFFF!"); + LOG_ERROR("[DDOP]: Object " + + isobus::to_string(static_cast(currentObject->get_object_id())) + + " is an orphan. It's parent is 0xFFFF!"); retVal = false; } if (retVal) { // Process children now that parent has been validated - for (std::size_t i = 0; i < currentDeviceElement->get_number_child_objects(); i++) + for (std::uint16_t i = 0; i < currentDeviceElement->get_number_child_objects(); i++) { auto child = get_object_by_id(currentDeviceElement->get_child_object_id(i)); if (nullptr == child.get()) { - CANStackLogger::error("[DDOP]: Object " + - isobus::to_string(static_cast(currentDeviceElement->get_child_object_id(i))) + - " is not found."); + LOG_ERROR("[DDOP]: Object " + + isobus::to_string(static_cast(currentDeviceElement->get_child_object_id(i))) + + " is not found."); retVal = false; break; } else if ((task_controller_object::ObjectTypes::DeviceProcessData != child->get_object_type()) && (task_controller_object::ObjectTypes::DeviceProperty != child->get_object_type())) { - CANStackLogger::error("[DDOP]: Object %u has child %u which is an object type that is not allowed.", - currentDeviceElement->get_child_object_id(i), - child->get_object_id()); - CANStackLogger::error("[DDOP]: A DET object may only have DPD and DPT children."); + LOG_ERROR("[DDOP]: Object %u has child %u which is an object type that is not allowed.", + currentDeviceElement->get_child_object_id(i), + child->get_object_id()); + LOG_ERROR("[DDOP]: A DET object may only have DPD and DPT children."); retVal = false; break; } @@ -1149,18 +1149,18 @@ namespace isobus auto child = get_object_by_id(currentProcessData->get_device_value_presentation_object_id()); if (nullptr == child.get()) { - CANStackLogger::error("[DDOP]: Object " + - isobus::to_string(static_cast(currentProcessData->get_device_value_presentation_object_id())) + - " is not found."); + LOG_ERROR("[DDOP]: Object " + + isobus::to_string(static_cast(currentProcessData->get_device_value_presentation_object_id())) + + " is not found."); retVal = false; break; } else if (task_controller_object::ObjectTypes::DeviceValuePresentation != child->get_object_type()) { - CANStackLogger::error("[DDOP]: Object %u has a child %u with an object type that is not allowed.", - currentProcessData->get_device_value_presentation_object_id(), - child->get_object_id()); - CANStackLogger::error("[DDOP]: A DPD object may only have DVP children."); + LOG_ERROR("[DDOP]: Object %u has a child %u with an object type that is not allowed.", + currentProcessData->get_device_value_presentation_object_id(), + child->get_object_id()); + LOG_ERROR("[DDOP]: A DPD object may only have DVP children."); retVal = false; break; } @@ -1177,18 +1177,18 @@ namespace isobus auto child = get_object_by_id(currentProperty->get_device_value_presentation_object_id()); if (nullptr == child.get()) { - CANStackLogger::error("[DDOP]: Object " + - isobus::to_string(static_cast(currentProperty->get_device_value_presentation_object_id())) + - " is not found."); + LOG_ERROR("[DDOP]: Object " + + isobus::to_string(static_cast(currentProperty->get_device_value_presentation_object_id())) + + " is not found."); retVal = false; break; } else if (task_controller_object::ObjectTypes::DeviceValuePresentation != child->get_object_type()) { - CANStackLogger::error("[DDOP]: Object %u has a child %u with an object type that is not allowed.", - currentProperty->get_device_value_presentation_object_id(), - child->get_object_id()); - CANStackLogger::error("[DDOP]: A DPT object may only have DVP children."); + LOG_ERROR("[DDOP]: Object %u has a child %u with an object type that is not allowed.", + currentProperty->get_device_value_presentation_object_id(), + child->get_object_id()); + LOG_ERROR("[DDOP]: A DPT object may only have DVP children."); retVal = false; break; } diff --git a/src/isobus_device_descriptor_object_pool.hpp b/src/isobus_device_descriptor_object_pool.hpp index f48178c..4675676 100644 --- a/src/isobus_device_descriptor_object_pool.hpp +++ b/src/isobus_device_descriptor_object_pool.hpp @@ -4,7 +4,7 @@ /// @brief Defines an interface for creating a Task Controller DDOP. /// @author Adrian Del Grosso /// -/// @copyright 2023 Adrian Del Grosso +/// @copyright 2023 The Open-Agriculture Developers //================================================================================================ #ifndef ISOBUS_DEVICE_DESCRIPTOR_OBJECT_POOL_HPP @@ -174,9 +174,10 @@ namespace isobus /// @brief Clears the DDOP back to an empty state void clear(); - /// @brief Returns the number of objects in the DDOP + /// @brief Returns the number of objects in the DDOP. + /// @note The number of objects in the DDOP is limited to 65535. /// @returns The number of objects in the DDOP - std::size_t size() const; + std::uint16_t size() const; private: /// @brief Checks to see that all parent object IDs correspond to an object in this DDOP diff --git a/src/isobus_diagnostic_protocol.cpp b/src/isobus_diagnostic_protocol.cpp index 11c37dc..3ee5a6d 100644 --- a/src/isobus_diagnostic_protocol.cpp +++ b/src/isobus_diagnostic_protocol.cpp @@ -30,7 +30,7 @@ /// /// @author Adrian Del Grosso /// -/// @copyright 2022 Adrian Del Grosso +/// @copyright 2022 The Open-Agriculture Developers //================================================================================================ #include "isobus_diagnostic_protocol.hpp" @@ -119,7 +119,7 @@ namespace isobus } else { - CANStackLogger::warn("[DP] DiagnosticProtocol's initialize() called when already initialized"); + LOG_WARNING("[DP] DiagnosticProtocol's initialize() called when already initialized"); } return retVal; } @@ -833,7 +833,7 @@ namespace isobus }; return CANNetworkManager::CANNetwork.send_can_message(static_cast(CANLibParameterGroupNumber::DiagnosticMessage13), buffer.data(), - buffer.size(), + static_cast(buffer.size()), myControlFunction); } @@ -850,7 +850,7 @@ namespace isobus std::vector buffer(ecuIdString.begin(), ecuIdString.end()); return CANNetworkManager::CANNetwork.send_can_message(static_cast(CANLibParameterGroupNumber::ECUIdentificationInformation), buffer.data(), - buffer.size(), + static_cast(buffer.size()), myControlFunction); } @@ -861,7 +861,7 @@ namespace isobus return CANNetworkManager::CANNetwork.send_can_message(static_cast(CANLibParameterGroupNumber::ProductIdentification), buffer.data(), - buffer.size(), + static_cast(buffer.size()), myControlFunction); } @@ -883,7 +883,7 @@ namespace isobus std::vector buffer(softIDString.begin(), softIDString.end()); retVal = CANNetworkManager::CANNetwork.send_can_message(static_cast(CANLibParameterGroupNumber::SoftwareIdentification), buffer.data(), - buffer.size(), + static_cast(buffer.size()), myControlFunction); } return retVal; @@ -939,7 +939,7 @@ namespace isobus retVal = CANNetworkManager::CANNetwork.send_can_message(static_cast(CANLibParameterGroupNumber::DiagnosticMessage22), buffer.data(), - buffer.size(), + static_cast(buffer.size()), myControlFunction, currentMessageData.destination); if (retVal) diff --git a/src/isobus_diagnostic_protocol.hpp b/src/isobus_diagnostic_protocol.hpp index 5430dd7..b9db82a 100644 --- a/src/isobus_diagnostic_protocol.hpp +++ b/src/isobus_diagnostic_protocol.hpp @@ -30,14 +30,13 @@ /// /// @author Adrian Del Grosso /// -/// @copyright 2022 Adrian Del Grosso +/// @copyright 2022 The Open-Agriculture Developers //================================================================================================ #ifndef ISOBUS_DIAGNOSTIC_PROTOCOL_HPP #define ISOBUS_DIAGNOSTIC_PROTOCOL_HPP #include "can_internal_control_function.hpp" -#include "can_protocol.hpp" #include "isobus_functionalities.hpp" #include "processing_flags.hpp" diff --git a/src/isobus_functionalities.cpp b/src/isobus_functionalities.cpp index b597a31..3c5b125 100644 --- a/src/isobus_functionalities.cpp +++ b/src/isobus_functionalities.cpp @@ -4,7 +4,7 @@ /// @brief Implements the management of the ISOBUS control function functionalities message. /// @author Adrian Del Grosso /// -/// @copyright 2023 Adrian Del Grosso +/// @copyright 2023 The Open-Agriculture Developers //================================================================================================ #include "isobus_functionalities.hpp" #include "can_general_parameter_group_numbers.hpp" @@ -28,7 +28,7 @@ namespace isobus } else { - CANStackLogger::error("[DP]: Failed to register PGN request callback for ControlFunctionFunctionalities due to the protocol being expired"); + LOG_ERROR("[DP]: Failed to register PGN request callback for ControlFunctionFunctionalities due to the protocol being expired"); } } @@ -42,9 +42,7 @@ namespace isobus void ControlFunctionFunctionalities::set_functionality_is_supported(Functionalities functionality, std::uint8_t functionalityGeneration, bool isSupported) { -#if !defined CAN_STACK_DISABLE_THREADS && !defined ARDUINO - const std::lock_guard lock(functionalitiesMutex); -#endif + LOCK_GUARD(Mutex, functionalitiesMutex); auto existingFunctionality = get_functionality(functionality); @@ -59,7 +57,7 @@ namespace isobus { if (Functionalities::MinimumControlFunction == functionality) { - CANStackLogger::warn("[DP]: You are disabling minimum control function functionality reporting! This is not recommended."); + LOG_WARNING("[DP]: You are disabling minimum control function functionality reporting! This is not recommended."); } supportedFunctionalities.erase(existingFunctionality); } @@ -68,9 +66,7 @@ namespace isobus bool ControlFunctionFunctionalities::get_functionality_is_supported(Functionalities functionality) { bool retVal = false; -#if !defined CAN_STACK_DISABLE_THREADS && !defined ARDUINO - const std::lock_guard lock(functionalitiesMutex); -#endif + LOCK_GUARD(Mutex, functionalitiesMutex); auto existingFunctionality = get_functionality(functionality); @@ -96,9 +92,7 @@ namespace isobus void ControlFunctionFunctionalities::set_minimum_control_function_option_state(MinimumControlFunctionOptions option, bool optionState) { -#if !defined CAN_STACK_DISABLE_THREADS && !defined ARDUINO - const std::lock_guard lock(functionalitiesMutex); -#endif + LOCK_GUARD(Mutex, functionalitiesMutex); auto existingFunctionality = get_functionality(Functionalities::MinimumControlFunction); @@ -116,9 +110,7 @@ namespace isobus void ControlFunctionFunctionalities::set_aux_O_inputs_option_state(AuxOOptions option, bool optionState) { -#if !defined CAN_STACK_DISABLE_THREADS && !defined ARDUINO - const std::lock_guard lock(functionalitiesMutex); -#endif + LOCK_GUARD(Mutex, functionalitiesMutex); auto existingFunctionality = get_functionality(Functionalities::AuxOInputs); @@ -136,9 +128,7 @@ namespace isobus void ControlFunctionFunctionalities::set_aux_O_functions_option_state(AuxOOptions option, bool optionState) { -#if !defined CAN_STACK_DISABLE_THREADS && !defined ARDUINO - const std::lock_guard lock(functionalitiesMutex); -#endif + LOCK_GUARD(Mutex, functionalitiesMutex); auto existingFunctionality = get_functionality(Functionalities::AuxOFunctions); @@ -156,9 +146,7 @@ namespace isobus void ControlFunctionFunctionalities::set_aux_N_inputs_option_state(AuxNOptions option, bool optionState) { -#if !defined CAN_STACK_DISABLE_THREADS && !defined ARDUINO - const std::lock_guard lock(functionalitiesMutex); -#endif + LOCK_GUARD(Mutex, functionalitiesMutex); auto existingFunctionality = get_functionality(Functionalities::AuxNInputs); @@ -178,9 +166,7 @@ namespace isobus void ControlFunctionFunctionalities::set_aux_N_functions_option_state(AuxNOptions option, bool optionState) { -#if !defined CAN_STACK_DISABLE_THREADS && !defined ARDUINO - const std::lock_guard lock(functionalitiesMutex); -#endif + LOCK_GUARD(Mutex, functionalitiesMutex); auto existingFunctionality = get_functionality(Functionalities::AuxNFunctions); @@ -200,9 +186,7 @@ namespace isobus void ControlFunctionFunctionalities::set_task_controller_geo_server_option_state(TaskControllerGeoServerOptions option, bool optionState) { -#if !defined CAN_STACK_DISABLE_THREADS && !defined ARDUINO - const std::lock_guard lock(functionalitiesMutex); -#endif + LOCK_GUARD(Mutex, functionalitiesMutex); auto existingFunctionality = get_functionality(Functionalities::TaskControllerGeoServer); @@ -220,9 +204,7 @@ namespace isobus void ControlFunctionFunctionalities::set_task_controller_geo_client_option(std::uint8_t numberOfControlChannels) { -#if !defined CAN_STACK_DISABLE_THREADS && !defined ARDUINO - const std::lock_guard lock(functionalitiesMutex); -#endif + LOCK_GUARD(Mutex, functionalitiesMutex); auto existingFunctionality = get_functionality(Functionalities::TaskControllerGeoClient); @@ -235,9 +217,7 @@ namespace isobus std::uint8_t ControlFunctionFunctionalities::get_task_controller_geo_client_option() { -#if !defined CAN_STACK_DISABLE_THREADS && !defined ARDUINO - const std::lock_guard lock(functionalitiesMutex); -#endif + LOCK_GUARD(Mutex, functionalitiesMutex); auto existingFunctionality = get_functionality(Functionalities::TaskControllerGeoClient); std::uint8_t retVal = 0; @@ -250,9 +230,7 @@ namespace isobus void ControlFunctionFunctionalities::set_task_controller_section_control_server_option_state(std::uint8_t numberOfSupportedBooms, std::uint8_t numberOfSupportedSections) { -#if !defined CAN_STACK_DISABLE_THREADS && !defined ARDUINO - const std::lock_guard lock(functionalitiesMutex); -#endif + LOCK_GUARD(Mutex, functionalitiesMutex); auto existingFunctionality = get_functionality(Functionalities::TaskControllerSectionControlServer); @@ -267,9 +245,7 @@ namespace isobus std::uint8_t ControlFunctionFunctionalities::get_task_controller_section_control_server_number_supported_booms() { -#if !defined CAN_STACK_DISABLE_THREADS && !defined ARDUINO - const std::lock_guard lock(functionalitiesMutex); -#endif + LOCK_GUARD(Mutex, functionalitiesMutex); auto existingFunctionality = get_functionality(Functionalities::TaskControllerSectionControlServer); std::uint8_t retVal = 0; @@ -282,9 +258,7 @@ namespace isobus std::uint8_t ControlFunctionFunctionalities::get_task_controller_section_control_server_number_supported_sections() { -#if !defined CAN_STACK_DISABLE_THREADS && !defined ARDUINO - const std::lock_guard lock(functionalitiesMutex); -#endif + LOCK_GUARD(Mutex, functionalitiesMutex); auto existingFunctionality = get_functionality(Functionalities::TaskControllerSectionControlServer); std::uint8_t retVal = 0; @@ -297,9 +271,7 @@ namespace isobus void ControlFunctionFunctionalities::set_task_controller_section_control_client_option_state(std::uint8_t numberOfSupportedBooms, std::uint8_t numberOfSupportedSections) { -#if !defined CAN_STACK_DISABLE_THREADS && !defined ARDUINO - const std::lock_guard lock(functionalitiesMutex); -#endif + LOCK_GUARD(Mutex, functionalitiesMutex); auto existingFunctionality = get_functionality(Functionalities::TaskControllerSectionControlClient); @@ -314,9 +286,7 @@ namespace isobus std::uint8_t ControlFunctionFunctionalities::get_task_controller_section_control_client_number_supported_booms() { -#if !defined CAN_STACK_DISABLE_THREADS && !defined ARDUINO - const std::lock_guard lock(functionalitiesMutex); -#endif + LOCK_GUARD(Mutex, functionalitiesMutex); auto existingFunctionality = get_functionality(Functionalities::TaskControllerSectionControlClient); std::uint8_t retVal = 0; @@ -329,9 +299,7 @@ namespace isobus std::uint8_t ControlFunctionFunctionalities::get_task_controller_section_control_client_number_supported_sections() { -#if !defined CAN_STACK_DISABLE_THREADS && !defined ARDUINO - const std::lock_guard lock(functionalitiesMutex); -#endif + LOCK_GUARD(Mutex, functionalitiesMutex); auto existingFunctionality = get_functionality(Functionalities::TaskControllerSectionControlClient); std::uint8_t retVal = 0; @@ -344,9 +312,7 @@ namespace isobus void ControlFunctionFunctionalities::set_basic_tractor_ECU_server_option_state(BasicTractorECUOptions option, bool optionState) { -#if !defined CAN_STACK_DISABLE_THREADS && !defined ARDUINO - const std::lock_guard lock(functionalitiesMutex); -#endif + LOCK_GUARD(Mutex, functionalitiesMutex); auto existingFunctionality = get_functionality(Functionalities::BasicTractorECUServer); @@ -364,9 +330,7 @@ namespace isobus // This one is handled differently to handle the 0 value if (BasicTractorECUOptions::TECUNotMeetingCompleteClass1Requirements == option) { -#if !defined CAN_STACK_DISABLE_THREADS && !defined ARDUINO - const std::lock_guard lock(functionalitiesMutex); -#endif + LOCK_GUARD(Mutex, functionalitiesMutex); auto existingFunctionality = get_functionality(Functionalities::BasicTractorECUServer); if (supportedFunctionalities.end() != existingFunctionality) @@ -383,9 +347,7 @@ namespace isobus void ControlFunctionFunctionalities::set_basic_tractor_ECU_implement_client_option_state(BasicTractorECUOptions option, bool optionState) { -#if !defined CAN_STACK_DISABLE_THREADS && !defined ARDUINO - const std::lock_guard lock(functionalitiesMutex); -#endif + LOCK_GUARD(Mutex, functionalitiesMutex); auto existingFunctionality = get_functionality(Functionalities::BasicTractorECUImplementClient); @@ -403,9 +365,7 @@ namespace isobus // This one is handled differently to handle the 0 value if (BasicTractorECUOptions::TECUNotMeetingCompleteClass1Requirements == option) { -#if !defined CAN_STACK_DISABLE_THREADS && !defined ARDUINO - const std::lock_guard lock(functionalitiesMutex); -#endif + LOCK_GUARD(Mutex, functionalitiesMutex); auto existingFunctionality = get_functionality(Functionalities::BasicTractorECUImplementClient); if (supportedFunctionalities.end() != existingFunctionality) @@ -422,9 +382,7 @@ namespace isobus void ControlFunctionFunctionalities::set_tractor_implement_management_server_option_state(TractorImplementManagementOptions option, bool optionState) { -#if !defined CAN_STACK_DISABLE_THREADS && !defined ARDUINO - const std::lock_guard lock(functionalitiesMutex); -#endif + LOCK_GUARD(Mutex, functionalitiesMutex); auto existingFunctionality = get_functionality(Functionalities::TractorImplementManagementServer); @@ -436,7 +394,7 @@ namespace isobus } else { - CANStackLogger::debug("[DP]: Can't set the No Options TIM option, disable the other ones instead."); + LOG_DEBUG("[DP]: Can't set the No Options TIM option, disable the other ones instead."); } } } @@ -447,9 +405,7 @@ namespace isobus if (TractorImplementManagementOptions::NoOptions == option) { -#if !defined CAN_STACK_DISABLE_THREADS && !defined ARDUINO - const std::lock_guard lock(functionalitiesMutex); -#endif + LOCK_GUARD(Mutex, functionalitiesMutex); auto existingFunctionality = get_functionality(Functionalities::TractorImplementManagementServer); if (supportedFunctionalities.end() != existingFunctionality) @@ -469,9 +425,7 @@ namespace isobus void ControlFunctionFunctionalities::set_tractor_implement_management_server_aux_valve_option(std::uint8_t auxValveIndex, bool stateSupported, bool flowSupported) { -#if !defined CAN_STACK_DISABLE_THREADS && !defined ARDUINO - const std::lock_guard lock(functionalitiesMutex); -#endif + LOCK_GUARD(Mutex, functionalitiesMutex); auto existingFunctionality = get_functionality(Functionalities::TractorImplementManagementServer); @@ -484,9 +438,7 @@ namespace isobus bool ControlFunctionFunctionalities::get_tractor_implement_management_server_aux_valve_state_supported(std::uint8_t auxValveIndex) { -#if !defined CAN_STACK_DISABLE_THREADS && !defined ARDUINO - const std::lock_guard lock(functionalitiesMutex); -#endif + LOCK_GUARD(Mutex, functionalitiesMutex); bool retVal = false; auto existingFunctionality = get_functionality(Functionalities::TractorImplementManagementServer); @@ -500,9 +452,7 @@ namespace isobus bool ControlFunctionFunctionalities::get_tractor_implement_management_server_aux_valve_flow_supported(std::uint8_t auxValveIndex) { -#if !defined CAN_STACK_DISABLE_THREADS && !defined ARDUINO - const std::lock_guard lock(functionalitiesMutex); -#endif + LOCK_GUARD(Mutex, functionalitiesMutex); bool retVal = false; auto existingFunctionality = get_functionality(Functionalities::TractorImplementManagementServer); @@ -516,9 +466,7 @@ namespace isobus void ControlFunctionFunctionalities::set_tractor_implement_management_client_option_state(TractorImplementManagementOptions option, bool optionState) { -#if !defined CAN_STACK_DISABLE_THREADS && !defined ARDUINO - const std::lock_guard lock(functionalitiesMutex); -#endif + LOCK_GUARD(Mutex, functionalitiesMutex); auto existingFunctionality = get_functionality(Functionalities::TractorImplementManagementClient); @@ -534,9 +482,7 @@ namespace isobus if (TractorImplementManagementOptions::NoOptions == option) { -#if !defined CAN_STACK_DISABLE_THREADS && !defined ARDUINO - const std::lock_guard lock(functionalitiesMutex); -#endif + LOCK_GUARD(Mutex, functionalitiesMutex); auto existingFunctionality = get_functionality(Functionalities::TractorImplementManagementClient); if (supportedFunctionalities.end() != existingFunctionality) @@ -568,9 +514,7 @@ namespace isobus void ControlFunctionFunctionalities::set_tractor_implement_management_client_aux_valve_option(std::uint8_t auxValveIndex, bool stateSupported, bool flowSupported) { -#if !defined CAN_STACK_DISABLE_THREADS && !defined ARDUINO - const std::lock_guard lock(functionalitiesMutex); -#endif + LOCK_GUARD(Mutex, functionalitiesMutex); auto existingFunctionality = get_functionality(Functionalities::TractorImplementManagementClient); if ((supportedFunctionalities.end() != existingFunctionality) && (auxValveIndex < NUMBER_TIM_AUX_VALVES)) @@ -582,9 +526,7 @@ namespace isobus bool ControlFunctionFunctionalities::get_tractor_implement_management_client_aux_valve_state_supported(std::uint8_t auxValveIndex) { -#if !defined CAN_STACK_DISABLE_THREADS && !defined ARDUINO - const std::lock_guard lock(functionalitiesMutex); -#endif + LOCK_GUARD(Mutex, functionalitiesMutex); bool retVal = false; auto existingFunctionality = get_functionality(Functionalities::TractorImplementManagementClient); @@ -598,9 +540,7 @@ namespace isobus bool ControlFunctionFunctionalities::get_tractor_implement_management_client_aux_valve_flow_supported(std::uint8_t auxValveIndex) { -#if !defined CAN_STACK_DISABLE_THREADS && !defined ARDUINO - const std::lock_guard lock(functionalitiesMutex); -#endif + LOCK_GUARD(Mutex, functionalitiesMutex); bool retVal = false; auto existingFunctionality = get_functionality(Functionalities::TractorImplementManagementClient); @@ -666,7 +606,7 @@ namespace isobus case Functionalities::TractorImplementManagementServer: case Functionalities::TractorImplementManagementClient: { - CANStackLogger::warn("[DP]: You have configured TIM as a CF functionality, but the library doesn't support TIM at this time. Do you have an external TIM implementation?"); + LOG_WARNING("[DP]: You have configured TIM as a CF functionality, but the library doesn't support TIM at this time. Do you have an external TIM implementation?"); serializedValue.resize(15); // TIM has a lot of options. https://www.isobus.net/isobus/option std::fill(serializedValue.begin(), serializedValue.end(), 0x00); // Support nothing by default } @@ -674,7 +614,7 @@ namespace isobus default: { - CANStackLogger::error("[DP]: An invalid control function functionality was added. It's values will be ignored."); + LOG_ERROR("[DP]: An invalid control function functionality was added. It's values will be ignored."); } break; } @@ -713,9 +653,7 @@ namespace isobus bool ControlFunctionFunctionalities::get_functionality_byte_option(Functionalities functionality, std::uint8_t byteIndex, std::uint8_t option) { -#if !defined CAN_STACK_DISABLE_THREADS && !defined ARDUINO - const std::lock_guard lock(functionalitiesMutex); -#endif + LOCK_GUARD(Mutex, functionalitiesMutex); bool retVal = false; auto existingFunctionality = get_functionality(functionality); @@ -729,9 +667,7 @@ namespace isobus void ControlFunctionFunctionalities::get_message_content(std::vector &messageData) { -#if !defined CAN_STACK_DISABLE_THREADS && !defined ARDUINO - const std::lock_guard lock(functionalitiesMutex); -#endif + LOCK_GUARD(Mutex, functionalitiesMutex); messageData.clear(); messageData.reserve(supportedFunctionalities.size() * 4); // Approximate, but pretty close unless you have TIM. messageData.push_back(0xFF); // Each control function shall respond with byte 1 set to FF diff --git a/src/isobus_functionalities.hpp b/src/isobus_functionalities.hpp index e200796..c025be3 100644 --- a/src/isobus_functionalities.hpp +++ b/src/isobus_functionalities.hpp @@ -8,23 +8,19 @@ /// /// @author Adrian Del Grosso /// -/// @copyright 2023 Adrian Del Grosso +/// @copyright 2023 The Open-Agriculture Developers //================================================================================================ #ifndef ISOBUS_FUNCTIONALITIES_HPP #define ISOBUS_FUNCTIONALITIES_HPP #include "can_internal_control_function.hpp" #include "can_parameter_group_number_request_protocol.hpp" -#include "can_protocol.hpp" #include "processing_flags.hpp" +#include "thread_synchronization.hpp" #include #include -#if !defined CAN_STACK_DISABLE_THREADS && !defined ARDUINO -#include -#endif - namespace isobus { class DiagnosticProtocol; // Forward declaration @@ -473,9 +469,7 @@ namespace isobus std::shared_ptr myControlFunction; ///< The control function to send messages as std::list supportedFunctionalities; ///< A list of all configured functionalities and their data ProcessingFlags txFlags; ///< Handles retries for sending the CF functionalities message -#if !defined CAN_STACK_DISABLE_THREADS && !defined ARDUINO - std::mutex functionalitiesMutex; ///< Since messages come in on a different thread than the main app (probably), this mutex protects the functionality data -#endif + Mutex functionalitiesMutex; ///< Since messages come in on a different thread than the main app (probably), this mutex protects the functionality data }; } // namespace isobus #endif // ISOBUS_FUNCTIONALITIES_HPP diff --git a/src/isobus_guidance_interface.cpp b/src/isobus_guidance_interface.cpp index 6519a98..199b8ac 100644 --- a/src/isobus_guidance_interface.cpp +++ b/src/isobus_guidance_interface.cpp @@ -17,7 +17,7 @@ /// /// @author Adrian Del Grosso /// -/// @copyright 2023 Adrian Del Grosso +/// @copyright 2023 The Open-Agriculture Developers //================================================================================================ #include "isobus_guidance_interface.hpp" #include "can_general_parameter_group_numbers.hpp" @@ -251,7 +251,7 @@ namespace isobus if ((nullptr != guidanceSystemCommandTransmitData.get_sender_control_function()) || (nullptr != guidanceMachineInfoTransmitData.get_sender_control_function())) { // Make sure you know what you are doing... consider reviewing the guidance messaging in ISO 11783-7 if you haven't already. - CANStackLogger::warn("[Guidance]: Use extreme caution! You have configured the ISOBUS guidance interface with the ability to steer a machine."); + LOG_WARNING("[Guidance]: Use extreme caution! You have configured the ISOBUS guidance interface with the ability to steer a machine."); } CANNetworkManager::CANNetwork.add_any_control_function_parameter_group_number_callback(static_cast(CANLibParameterGroupNumber::AgriculturalGuidanceMachineInfo), process_rx_message, this); CANNetworkManager::CANNetwork.add_any_control_function_parameter_group_number_callback(static_cast(CANLibParameterGroupNumber::AgriculturalGuidanceSystemCommand), process_rx_message, this); @@ -318,12 +318,12 @@ namespace isobus if (guidanceMachineInfoTransmitData.get_estimated_curvature() > CURVATURE_COMMAND_MAX_INVERSE_KM) { encodedCurvature = 32127 + ZERO_CURVATURE_INVERSE_KM; // Clamp to maximum value - CANStackLogger::warn("[Guidance]: Transmitting a commanded curvature clamped to maximum value. Verify guidance calculations are accurate!"); + LOG_WARNING("[Guidance]: Transmitting a commanded curvature clamped to maximum value. Verify guidance calculations are accurate!"); } else if (scaledCurvature < 0) // 0 In this case is -8032 km-1 due to the addition of the offset earlier { encodedCurvature = 0; // Clamp to minimum value - CANStackLogger::warn("[Guidance]: Transmitting a commanded curvature clamped to minimum value. Verify guidance calculations are accurate!"); + LOG_WARNING("[Guidance]: Transmitting a commanded curvature clamped to minimum value. Verify guidance calculations are accurate!"); } else { @@ -361,12 +361,12 @@ namespace isobus if (guidanceMachineInfoTransmitData.get_estimated_curvature() > CURVATURE_COMMAND_MAX_INVERSE_KM) { encodedCurvature = 32127 + ZERO_CURVATURE_INVERSE_KM; // Clamp to maximum value - CANStackLogger::warn("[Guidance]: Transmitting an estimated curvature clamped to maximum value. Verify guidance calculations are accurate!"); + LOG_WARNING("[Guidance]: Transmitting an estimated curvature clamped to maximum value. Verify guidance calculations are accurate!"); } else if (scaledCurvature < 0) // 0 In this case is -8032 km-1 due to the addition of the offset earlier { encodedCurvature = 0; // Clamp to minimum value - CANStackLogger::warn("[Guidance]: Transmitting an estimated curvature clamped to minimum value. Verify guidance calculations are accurate!"); + LOG_WARNING("[Guidance]: Transmitting an estimated curvature clamped to minimum value. Verify guidance calculations are accurate!"); } else { @@ -431,7 +431,7 @@ namespace isobus } else { - CANStackLogger::error("[Guidance]: Guidance interface has not been initialized yet."); + LOG_ERROR("[Guidance]: Guidance interface has not been initialized yet."); } } @@ -505,7 +505,7 @@ namespace isobus } else { - CANStackLogger::warn("[Guidance]: Received a malformed guidance system command message. DLC must be 8."); + LOG_WARNING("[Guidance]: Received a malformed guidance system command message. DLC must be 8."); } } break; @@ -547,7 +547,7 @@ namespace isobus } else { - CANStackLogger::warn("[Guidance]: Received a malformed guidance machine info message. DLC must be 8."); + LOG_WARNING("[Guidance]: Received a malformed guidance machine info message. DLC must be 8."); } } break; diff --git a/src/isobus_guidance_interface.hpp b/src/isobus_guidance_interface.hpp index 24d9c5b..bb72861 100644 --- a/src/isobus_guidance_interface.hpp +++ b/src/isobus_guidance_interface.hpp @@ -17,7 +17,7 @@ /// /// @author Adrian Del Grosso /// -/// @copyright 2023 Adrian Del Grosso +/// @copyright 2023 The Open-Agriculture Developers //================================================================================================ #ifndef ISOBUS_GUIDANCE_INTERFACE_HPP #define ISOBUS_GUIDANCE_INTERFACE_HPP diff --git a/src/isobus_heartbeat.cpp b/src/isobus_heartbeat.cpp new file mode 100644 index 0000000..914f95d --- /dev/null +++ b/src/isobus_heartbeat.cpp @@ -0,0 +1,240 @@ +//================================================================================================ +/// @file isobus_heartbeat.cpp +/// +/// @brief Implements 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 +//================================================================================================ +#include "isobus_heartbeat.hpp" + +#include "can_general_parameter_group_numbers.hpp" +#include "can_parameter_group_number_request_protocol.hpp" +#include "can_stack_logger.hpp" +#include "system_timing.hpp" + +namespace isobus +{ + HeartbeatInterface::HeartbeatInterface(const CANMessageFrameCallback &sendCANFrameCallback) : + sendCANFrameCallback(sendCANFrameCallback) + { + } + + void HeartbeatInterface::set_enabled(bool enable) + { + if ((!enable) && (enable != enabled)) + { + LOG_DEBUG("[HB]: Disabling ISOBUS heartbeat interface."); + } + enabled = enable; + } + + bool HeartbeatInterface::is_enabled() const + { + return enabled; + } + + bool HeartbeatInterface::request_heartbeat(std::shared_ptr sourceControlFunction, + std::shared_ptr destinationControlFunction) const + { + bool retVal = false; + + if ((nullptr != sourceControlFunction) && + (nullptr != destinationControlFunction) && + enabled) + { + retVal = ParameterGroupNumberRequestProtocol::request_repetition_rate(static_cast(CANLibParameterGroupNumber::HeartbeatMessage), + SEQUENCE_REPETITION_RATE_MS, + sourceControlFunction, + destinationControlFunction); + } + return retVal; + } + + void HeartbeatInterface::on_new_internal_control_function(std::shared_ptr newControlFunction) + { + auto pgnRequestProtocol = newControlFunction->get_pgn_request_protocol().lock(); + + if (nullptr != pgnRequestProtocol) + { + pgnRequestProtocol->register_request_for_repetition_rate_callback(static_cast(CANLibParameterGroupNumber::HeartbeatMessage), process_request_for_heartbeat, this); + } + } + + void HeartbeatInterface::on_destroyed_internal_control_function(std::shared_ptr destroyedControlFunction) + { + auto pgnRequestProtocol = destroyedControlFunction->get_pgn_request_protocol().lock(); + + if (nullptr != pgnRequestProtocol) + { + pgnRequestProtocol->remove_request_for_repetition_rate_callback(static_cast(CANLibParameterGroupNumber::HeartbeatMessage), process_request_for_heartbeat, this); + } + } + + EventDispatcher> &HeartbeatInterface::get_heartbeat_error_event_dispatcher() + { + return heartbeatErrorEventDispatcher; + } + + EventDispatcher> &HeartbeatInterface::get_new_tracked_heartbeat_event_dispatcher() + { + return newTrackedHeartbeatEventDispatcher; + } + + void HeartbeatInterface::update() + { + if (enabled) + { + trackedHeartbeats.erase(std::remove_if(trackedHeartbeats.begin(), trackedHeartbeats.end(), [this](Heartbeat &heartbeat) { + bool retVal = false; + + if (nullptr != heartbeat.controlFunction) + { + if (ControlFunction::Type::Internal == heartbeat.controlFunction->get_type()) + { + if ((SystemTiming::time_expired_ms(heartbeat.timestamp_ms, heartbeat.repetitionRate_ms)) && + heartbeat.send(*this)) + { + heartbeat.sequenceCounter++; + + if (heartbeat.sequenceCounter > 250) + { + heartbeat.sequenceCounter = 0; + } + } + } + else if (SystemTiming::time_expired_ms(heartbeat.timestamp_ms, SEQUENCE_TIMEOUT_MS)) + { + retVal = true; // External heartbeat is timed-out + LOG_ERROR("[HB]: Heartbeat from control function at address 0x%02X timed out.", heartbeat.controlFunction->get_address()); + heartbeatErrorEventDispatcher.call(HeartBeatError::TimedOut, heartbeat.controlFunction); + } + } + else + { + retVal = true; // Invalid state + } + return retVal; + }), + trackedHeartbeats.end()); + } + } + + HeartbeatInterface::Heartbeat::Heartbeat(std::shared_ptr sendingControlFunction) : + controlFunction(sendingControlFunction), + timestamp_ms(SystemTiming::get_timestamp_ms()) + { + } + + bool HeartbeatInterface::Heartbeat::send(const HeartbeatInterface &parent) + { + bool retVal = false; + const std::array buffer = { sequenceCounter }; + + retVal = parent.sendCANFrameCallback(static_cast(CANLibParameterGroupNumber::HeartbeatMessage), + CANDataSpan(buffer.data(), buffer.size()), + CANNetworkManager::CANNetwork.get_internal_control_function(controlFunction), + nullptr, + CANIdentifier::CANPriority::Priority3); + if (retVal) + { + timestamp_ms = SystemTiming::get_timestamp_ms(); // Sent OK + } + return retVal; + } + + void HeartbeatInterface::process_rx_message(const CANMessage &message) + { + if (enabled && + (static_cast(CANLibParameterGroupNumber::HeartbeatMessage) == message.get_identifier().get_parameter_group_number()) && + (nullptr != message.get_source_control_function()) && + (message.get_data_length() >= 1)) + { + auto managedHeartbeat = std::find_if(trackedHeartbeats.begin(), + trackedHeartbeats.end(), + [&message](const Heartbeat &hb) { + return (message.get_source_control_function() == hb.controlFunction); + }); + + if (managedHeartbeat != trackedHeartbeats.end()) + { + managedHeartbeat->timestamp_ms = SystemTiming::get_timestamp_ms(); + + if (message.get_uint8_at(0) == managedHeartbeat->sequenceCounter) + { + LOG_ERROR("[HB]: Duplicate sequence counter received in heartbeat."); + heartbeatErrorEventDispatcher.call(HeartBeatError::InvalidSequenceCounter, message.get_source_control_function()); + } + else if (message.get_uint8_at(0) != ((managedHeartbeat->sequenceCounter + 1) % 250)) + { + LOG_ERROR("[HB]: Invalid sequence counter received in heartbeat."); + heartbeatErrorEventDispatcher.call(HeartBeatError::InvalidSequenceCounter, message.get_source_control_function()); + } + trackedHeartbeats.back().sequenceCounter = message.get_uint8_at(0); + } + else + { + LOG_DEBUG("[HB]: Tracking new heartbeat from control function at address 0x%02X.", message.get_source_control_function()->get_address()); + + if (message.get_uint8_at(0) != static_cast(HeartbeatInterface::SequenceCounterSpecialValue::Initial)) + { + LOG_WARNING("[HB]: Initial heartbeat sequence counter not received from control function at address 0x%02X.", message.get_source_control_function()->get_address()); + } + + trackedHeartbeats.emplace_back(message.get_source_control_function()); + trackedHeartbeats.back().timestamp_ms = SystemTiming::get_timestamp_ms(); + trackedHeartbeats.back().sequenceCounter = message.get_uint8_at(0); + newTrackedHeartbeatEventDispatcher.call(message.get_source_control_function()); + } + } + } + + bool HeartbeatInterface::process_request_for_heartbeat(std::uint32_t parameterGroupNumber, + std::shared_ptr requestingControlFunction, + std::shared_ptr targetControlFunction, + std::uint32_t repetitionRate, + void *parentPointer) + { + bool retVal = false; + + if (nullptr != parentPointer) + { + auto interface = static_cast(parentPointer); + + if ((interface->is_enabled()) && + (static_cast(CANLibParameterGroupNumber::HeartbeatMessage) == parameterGroupNumber)) + { + retVal = true; + + if (SEQUENCE_REPETITION_RATE_MS != repetitionRate) + { + LOG_WARNING("[HB]: Control function at address 0x%02X requested the ISOBUS heartbeat at non-compliant interval. Interval should be 100ms.", requestingControlFunction->get_address()); + } + else + { + LOG_DEBUG("[HB]: Control function at address 0x%02X requested the ISOBUS heartbeat from control function at address 0x%02X.", requestingControlFunction->get_address(), targetControlFunction->get_address()); + } + + auto managedHeartbeat = std::find_if(interface->trackedHeartbeats.begin(), + interface->trackedHeartbeats.end(), + [targetControlFunction](const Heartbeat &hb) { + return (targetControlFunction == hb.controlFunction); + }); + + if (managedHeartbeat == interface->trackedHeartbeats.end()) + { + interface->trackedHeartbeats.emplace_back(targetControlFunction); // Heartbeat will be sent on next update + } + } + } + return retVal; + } +} // namespace isobus diff --git a/src/isobus_heartbeat.hpp b/src/isobus_heartbeat.hpp new file mode 100644 index 0000000..a8126bd --- /dev/null +++ b/src/isobus_heartbeat.hpp @@ -0,0 +1,154 @@ +//================================================================================================ +/// @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 "can_callbacks.hpp" +#include "can_internal_control_function.hpp" +#include "can_message.hpp" +#include "event_dispatcher.hpp" + +#include + +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 Constructor for a HeartbeatInterface + /// @param[in] sendCANFrameCallback A callback used to send CAN frames + HeartbeatInterface(const CANMessageFrameCallback &sendCANFrameCallback); + + /// @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 is_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 sourceControlFunction, + std::shared_ptr destinationControlFunction) const; + + /// @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 newControlFunction); + + /// @brief Called when an internal control function is deleted. Cleans up stale registrations with + /// PGN request protocol. + /// @param[in] destroyedControlFunction The destroyed internal control function + void on_destroyed_internal_control_function(std::shared_ptr destroyedControlFunction); + + /// @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> &get_heartbeat_error_event_dispatcher(); + + /// @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> &get_new_tracked_heartbeat_event_dispatcher(); + + /// @brief Processes a CAN message, called by the network manager. + /// @param[in] message The CAN message being received + void process_rx_message(const CANMessage &message); + + /// @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 + explicit Heartbeat(std::shared_ptr sendingControlFunction); + + /// @brief Transmits a heartbeat message (for internal control functions only). + /// Updates the sequence counter and timestamp if needed. + /// @param[in] parent The parent interface + /// @returns True if the message is sent, otherwise false. + bool send(const HeartbeatInterface &parent); + + std::shared_ptr 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(SequenceCounterSpecialValue::Initial); ///< The sequence counter used to validate the heartbeat. Counts from 0-250 normally. + }; + + /// @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 requestingControlFunction, + std::shared_ptr targetControlFunction, + std::uint32_t repetitionRate, + void *parentPointer); + + const CANMessageFrameCallback sendCANFrameCallback; ///< A callback for sending a CAN frame + EventDispatcher> heartbeatErrorEventDispatcher; ///< Event dispatcher for heartbeat errors + EventDispatcher> newTrackedHeartbeatEventDispatcher; ///< Event dispatcher for when a heartbeat message from another control function becomes tracked by this interface + std::list trackedHeartbeats; ///< Store tracked heartbeat data, per CF + bool enabled = true; ///< Attribute that specifies if this interface is enabled. When false, the interface does nothing. + }; +} // namespace isobus + +#endif diff --git a/src/isobus_language_command_interface.cpp b/src/isobus_language_command_interface.cpp index a61e1a0..49a7f7d 100644 --- a/src/isobus_language_command_interface.cpp +++ b/src/isobus_language_command_interface.cpp @@ -5,7 +5,7 @@ /// ISO11783-7 commonly used in VT and TC communication /// @author Adrian Del Grosso /// -/// @copyright 2023 Adrian Del Grosso +/// @copyright 2023 The Open-Agriculture Developers //================================================================================================ #include "isobus_language_command_interface.hpp" @@ -63,12 +63,12 @@ namespace isobus } else { - CANStackLogger::error("[VT/TC]: Language command interface is missing an internal control function, and will not be functional."); + LOG_ERROR("[VT/TC]: Language command interface is missing an internal control function, and will not be functional."); } } else { - CANStackLogger::warn("[VT/TC]: Language command interface has been initialized, but is being initialized again."); + LOG_WARNING("[VT/TC]: Language command interface has been initialized, but is being initialized again."); } } @@ -77,6 +77,11 @@ namespace isobus myPartner = filteredControlFunction; } + std::shared_ptr LanguageCommandInterface::get_partner() const + { + return myPartner; + } + bool LanguageCommandInterface::get_initialized() const { return initialized; @@ -93,13 +98,18 @@ namespace isobus else { // Make sure you call initialize first! - CANStackLogger::error("[VT/TC]: Language command interface is being used without being initialized!"); + LOG_ERROR("[VT/TC]: Language command interface is being used without being initialized!"); } return retVal; } - bool LanguageCommandInterface::send_language_command() const + bool LanguageCommandInterface::send_language_command() { + if ((languageCode.length() < 2) || (countryCode.length() < 2)) + { + LOG_ERROR("[VT/TC]: Language command interface is missing language or country code, and will not send a language command."); + return false; + } std::array buffer{ static_cast(languageCode[0]), static_cast(languageCode[1]), @@ -119,7 +129,7 @@ namespace isobus }; return CANNetworkManager::CANNetwork.send_can_message(static_cast(CANLibParameterGroupNumber::LanguageCommand), buffer.data(), - buffer.size(), + static_cast(buffer.size()), myControlFunction, nullptr); } @@ -133,11 +143,11 @@ namespace isobus { if (country.length() > 2) { - CANStackLogger::warn("[VT/TC]: Language command country code should not be more than 2 characters! It will be truncated."); + LOG_WARNING("[VT/TC]: Language command country code should not be more than 2 characters! It will be truncated."); } else if (country.length() < 2) { - CANStackLogger::warn("[VT/TC]: Language command country code should not be less than 2 characters! It will be padded."); + LOG_WARNING("[VT/TC]: Language command country code should not be less than 2 characters! It will be padded."); } while (country.length() < 2) @@ -156,11 +166,11 @@ namespace isobus { if (language.length() > 2) { - CANStackLogger::warn("[VT/TC]: Language command language code should not be more than 2 characters! It will be truncated."); + LOG_WARNING("[VT/TC]: Language command language code should not be more than 2 characters! It will be truncated."); } else if (language.length() < 2) { - CANStackLogger::warn("[VT/TC]: Language command language code should not be less than 2 characters! It will be padded."); + LOG_WARNING("[VT/TC]: Language command language code should not be less than 2 characters! It will be padded."); } while (language.length() < 2) @@ -348,12 +358,12 @@ namespace isobus parentInterface->countryCode.push_back(static_cast(data.at(7))); } - CANStackLogger::debug("[VT/TC]: Language and unit data received from control function " + - isobus::to_string(static_cast(message.get_identifier().get_source_address())) + - " language is: " + - parentInterface->languageCode.c_str(), - " and country code is ", - parentInterface->countryCode.empty() ? "unknown." : parentInterface->countryCode.c_str()); + LOG_DEBUG("[VT/TC]: Language and unit data received from control function " + + isobus::to_string(static_cast(message.get_identifier().get_source_address())) + + " language is: " + + parentInterface->languageCode.c_str(), + " and country code is ", + parentInterface->countryCode.empty() ? "unknown." : parentInterface->countryCode.c_str()); } } diff --git a/src/isobus_language_command_interface.hpp b/src/isobus_language_command_interface.hpp index 0e8cae2..be6b691 100644 --- a/src/isobus_language_command_interface.hpp +++ b/src/isobus_language_command_interface.hpp @@ -5,7 +5,7 @@ /// ISO11783-7 commonly used in VT and TC communication /// @author Adrian Del Grosso /// -/// @copyright 2023 Adrian Del Grosso +/// @copyright 2023 The Open-Agriculture Developers //================================================================================================ #ifndef ISOBUS_LANGUAGE_COMMAND_INTERFACE_HPP #define ISOBUS_LANGUAGE_COMMAND_INTERFACE_HPP @@ -166,6 +166,10 @@ namespace isobus /// @param[in] filteredControlFunction The new partner to communicate with void set_partner(std::shared_ptr filteredControlFunction); + /// @brief Returns the current partner being used by the interface + /// @return The current partner being used by the interface, or nullptr if none + std::shared_ptr get_partner() const; + /// @brief Returns if initialize has been called yet /// @return `true` if initialize has been called, otherwise false bool get_initialized() const; @@ -178,7 +182,7 @@ namespace isobus /// @brief Sends a language command based on the current content of this class as a broadcast. /// @note This is only meant to be used by a VT server or TC/DL server /// @return `true` if the message was sent, otherwise `false` - bool send_language_command() const; + bool send_language_command(); /// @brief Returns the commanded country code parsed from the last language command specifying the operator's desired language dialect. /// @note ISO 11783 networks shall use the alpha-2 country codes in accordance with ISO 3166-1. diff --git a/src/isobus_maintain_power_interface.cpp b/src/isobus_maintain_power_interface.cpp index 7a08855..1313f27 100644 --- a/src/isobus_maintain_power_interface.cpp +++ b/src/isobus_maintain_power_interface.cpp @@ -1,3 +1,12 @@ +//================================================================================================ +/// @file isobus_maintain_power_interface.cpp +/// +/// @brief Implements an interface for sending and receiving the maintain power message (PGN 65095). +/// @author Adrian Del Grosso +/// +/// @copyright 2023 The Open-Agriculture Developers +//================================================================================================ + #include "isobus_maintain_power_interface.hpp" #include "can_general_parameter_group_numbers.hpp" #include "can_network_manager.hpp" @@ -57,7 +66,7 @@ namespace isobus } else { - CANStackLogger::error("[Maintain Power]: Interface has not been initialized yet."); + LOG_ERROR("[Maintain Power]: Interface has not been initialized yet."); } } @@ -252,7 +261,7 @@ namespace isobus { if (0 != targetInterface->keyNotOffTimestamp) { - CANStackLogger::info("[Maintain Power]: The key switch state has transitioned from NOT OFF to OFF."); + LOG_INFO("[Maintain Power]: The key switch state has transitioned from NOT OFF to OFF."); targetInterface->keyNotOffTimestamp = 0; // Send the maintain power message based on the key state transition @@ -263,7 +272,7 @@ namespace isobus } else if (0 == targetInterface->keyOffTimestamp) { - CANStackLogger::info("[Maintain Power]: The key switch state is detected as OFF."); + LOG_INFO("[Maintain Power]: The key switch state is detected as OFF."); targetInterface->keyOffTimestamp = SystemTiming::get_timestamp_ms(); } } @@ -273,13 +282,13 @@ namespace isobus { if (0 != targetInterface->keyOffTimestamp) { - CANStackLogger::info("[Maintain Power]: The key switch state has transitioned from OFF to NOT OFF."); + LOG_INFO("[Maintain Power]: The key switch state has transitioned from OFF to NOT OFF."); targetInterface->keyOffTimestamp = 0; targetInterface->keyNotOffTimestamp = SystemTiming::get_timestamp_ms(); } else if (0 == targetInterface->keyNotOffTimestamp) { - CANStackLogger::info("[Maintain Power]: The key switch state is detected as NOT OFF."); + LOG_INFO("[Maintain Power]: The key switch state is detected as NOT OFF."); targetInterface->keyNotOffTimestamp = SystemTiming::get_timestamp_ms(); } targetInterface->maintainPowerTransmitTimestamp_ms = 0; @@ -288,7 +297,7 @@ namespace isobus case KeySwitchState::Error: { - CANStackLogger::warn("[Maintain Power]: The key switch is in an error state."); + LOG_WARNING("[Maintain Power]: The key switch is in an error state."); targetInterface->keyOffTimestamp = 0; targetInterface->keyNotOffTimestamp = 0; targetInterface->maintainPowerTransmitTimestamp_ms = 0; @@ -305,7 +314,7 @@ namespace isobus } else { - CANStackLogger::warn("[Maintain Power]: Received malformed wheel based speed PGN. DLC must be 8."); + LOG_WARNING("[Maintain Power]: Received malformed wheel based speed PGN. DLC must be 8."); } } else if (static_cast(CANLibParameterGroupNumber::MaintainPower) == message.get_identifier().get_parameter_group_number()) @@ -343,7 +352,7 @@ namespace isobus } else { - CANStackLogger::warn("[Maintain Power]: Received malformed maintain power PGN. DLC must be 8."); + LOG_WARNING("[Maintain Power]: Received malformed maintain power PGN. DLC must be 8."); } } } diff --git a/src/isobus_maintain_power_interface.hpp b/src/isobus_maintain_power_interface.hpp index 224dba5..a627817 100644 --- a/src/isobus_maintain_power_interface.hpp +++ b/src/isobus_maintain_power_interface.hpp @@ -11,7 +11,7 @@ /// You might want to maintain actuator power to ensure your section valves close when keyed off. /// @author Adrian Del Grosso /// -/// @copyright 2023 Adrian Del Grosso +/// @copyright 2023 The Open-Agriculture Developers //================================================================================================ #ifndef ISOBUS_MAINTAIN_POWER_INTERFACE_HPP #define ISOBUS_MAINTAIN_POWER_INTERFACE_HPP diff --git a/src/isobus_shortcut_button_interface.cpp b/src/isobus_shortcut_button_interface.cpp index 08656a3..3239b40 100644 --- a/src/isobus_shortcut_button_interface.cpp +++ b/src/isobus_shortcut_button_interface.cpp @@ -4,7 +4,7 @@ /// @brief Implements the interface for an ISOBUS shortcut button /// @author Adrian Del Grosso /// -/// @copyright 2023 Adrian Del Grosso +/// @copyright 2023 The Open-Agriculture Developers //================================================================================================ #include "isobus_shortcut_button_interface.hpp" @@ -66,18 +66,18 @@ namespace isobus if (StopAllImplementOperationsState::StopImplementOperations == newState) { - CANStackLogger::error("[ISB]: All implement operations must stop. (Triggered internally)"); + LOG_ERROR("[ISB]: All implement operations must stop. (Triggered internally)"); } else { - CANStackLogger::info("[ISB]: Internal ISB state is now permitted."); + LOG_INFO("[ISB]: Internal ISB state is now permitted."); } txFlags.set_flag(static_cast(TransmitFlags::SendStopAllImplementOperationsSwitchState)); } } else { - CANStackLogger::error("[ISB]: You are attempting to set the internal ISB state but the ISB interface is not configured as a server!"); + LOG_ERROR("[ISB]: You are attempting to set the internal ISB state but the ISB interface is not configured as a server!"); } } @@ -164,7 +164,7 @@ namespace isobus { ISBServerData newISB; - CANStackLogger::debug("[ISB]: New ISB detected at address %u", message.get_identifier().get_source_address()); + LOG_DEBUG("[ISB]: New ISB detected at address %u", message.get_identifier().get_source_address()); newISB.ISONAME = messageNAME; isobusShorcutButtonList.emplace_back(newISB); ISB = std::prev(isobusShorcutButtonList.end()); @@ -182,7 +182,7 @@ namespace isobus // A Working Set shall consider an increase in the transitions without detecting a corresponding // transition of the Stop all implement operations state as an error and react accordingly. ISB->commandedState = StopAllImplementOperationsState::StopImplementOperations; - CANStackLogger::error("[ISB]: Missed an ISB transition from ISB at address %u", message.get_identifier().get_source_address()); + LOG_ERROR("[ISB]: Missed an ISB transition from ISB at address %u", message.get_identifier().get_source_address()); } else { @@ -196,11 +196,11 @@ namespace isobus { if (StopAllImplementOperationsState::StopImplementOperations == newState) { - CANStackLogger::error("[ISB]: All implement operations must stop. (ISB at address %u has commanded it)", message.get_identifier().get_source_address()); + LOG_ERROR("[ISB]: All implement operations must stop. (ISB at address %u has commanded it)", message.get_identifier().get_source_address()); } else { - CANStackLogger::info("[ISB]: Implement operations now permitted."); + LOG_INFO("[ISB]: Implement operations now permitted."); } ISBEventDispatcher.call(newState); } @@ -208,7 +208,7 @@ namespace isobus } else { - CANStackLogger::warn("[ISB]: Received malformed All Implements Stop Operations Switch State. DLC must be 8."); + LOG_WARNING("[ISB]: Received malformed All Implements Stop Operations Switch State. DLC must be 8."); } } } @@ -228,7 +228,7 @@ namespace isobus return CANNetworkManager::CANNetwork.send_can_message(static_cast(CANLibParameterGroupNumber::AllImplementsStopOperationsSwitchState), buffer.data(), - buffer.size(), + static_cast(buffer.size()), sourceControlFunction, nullptr, CANIdentifier::CANPriority::Priority3); diff --git a/src/isobus_shortcut_button_interface.hpp b/src/isobus_shortcut_button_interface.hpp index 6f090ca..b701ff2 100644 --- a/src/isobus_shortcut_button_interface.hpp +++ b/src/isobus_shortcut_button_interface.hpp @@ -18,7 +18,7 @@ /// /// @author Adrian Del Grosso /// -/// @copyright 2023 Adrian Del Grosso +/// @copyright 2023 The Open-Agriculture Developers //================================================================================================ #ifndef ISOBUS_SHORTCUT_BUTTON_INTERFACE_HPP #define ISOBUS_SHORTCUT_BUTTON_INTERFACE_HPP @@ -26,7 +26,6 @@ #include "can_NAME.hpp" #include "can_internal_control_function.hpp" #include "can_message.hpp" -#include "can_protocol.hpp" #include "event_dispatcher.hpp" #include "processing_flags.hpp" diff --git a/src/isobus_speed_distance_messages.cpp b/src/isobus_speed_distance_messages.cpp index 6673883..2fff4f2 100644 --- a/src/isobus_speed_distance_messages.cpp +++ b/src/isobus_speed_distance_messages.cpp @@ -16,7 +16,7 @@ /// /// @author Adrian Del Grosso /// -/// @copyright 2023 Adrian Del Grosso +/// @copyright 2023 The Open-Agriculture Developers //================================================================================================ #include "isobus_speed_distance_messages.hpp" #include "can_general_parameter_group_numbers.hpp" @@ -419,7 +419,7 @@ namespace isobus { if (nullptr != machineSelectedSpeedCommandTransmitData.get_sender_control_function()) { - CANStackLogger::warn("[Speed/Distance]: Use extreme cation! You have configured an interface to command the speed of the machine. The machine may move without warning!"); + LOG_WARNING("[Speed/Distance]: Use extreme cation! You have configured an interface to command the speed of the machine. The machine may move without warning!"); } CANNetworkManager::CANNetwork.add_any_control_function_parameter_group_number_callback(static_cast(CANLibParameterGroupNumber::MachineSelectedSpeed), process_rx_message, this); CANNetworkManager::CANNetwork.add_any_control_function_parameter_group_number_callback(static_cast(CANLibParameterGroupNumber::WheelBasedSpeedAndDistance), process_rx_message, this); @@ -575,7 +575,7 @@ namespace isobus } else { - CANStackLogger::error("[Speed/Distance]: ISOBUS speed messages interface has not been initialized yet."); + LOG_ERROR("[Speed/Distance]: ISOBUS speed messages interface has not been initialized yet."); } } @@ -665,7 +665,7 @@ namespace isobus } else { - CANStackLogger::error("[Speed/Distance]: Received a malformed machine selected speed. DLC must be 8."); + LOG_ERROR("[Speed/Distance]: Received a malformed machine selected speed. DLC must be 8."); } } break; @@ -706,7 +706,7 @@ namespace isobus } else { - CANStackLogger::error("[Speed/Distance]: Received a malformed wheel-based speed and distance message. DLC must be 8."); + LOG_ERROR("[Speed/Distance]: Received a malformed wheel-based speed and distance message. DLC must be 8."); } } break; @@ -743,7 +743,7 @@ namespace isobus } else { - CANStackLogger::error("[Speed/Distance]: Received a malformed ground-based speed and distance message. DLC must be 8."); + LOG_ERROR("[Speed/Distance]: Received a malformed ground-based speed and distance message. DLC must be 8."); } } break; @@ -780,7 +780,7 @@ namespace isobus } else { - CANStackLogger::error("[Speed/Distance]: Received a malformed machine selected speed command message. DLC must be 8."); + LOG_ERROR("[Speed/Distance]: Received a malformed machine selected speed command message. DLC must be 8."); } } break; diff --git a/src/isobus_speed_distance_messages.hpp b/src/isobus_speed_distance_messages.hpp index 046bfa8..2e1e889 100644 --- a/src/isobus_speed_distance_messages.hpp +++ b/src/isobus_speed_distance_messages.hpp @@ -7,7 +7,7 @@ /// @note The full list of standardized speeds can be found at "isobus.net" /// @author Adrian Del Grosso /// -/// @copyright 2023 Adrian Del Grosso +/// @copyright 2023 The Open-Agriculture Developers //================================================================================================ #ifndef ISOBUS_SPEED_MESSAGES_HPP #define ISOBUS_SPEED_MESSAGES_HPP @@ -46,8 +46,8 @@ namespace isobus /// @brief Enumerates the values of the direction of travel for the machine. enum class MachineDirection : std::uint8_t { - Forward = 0, - Reverse = 1, + Reverse = 0, + Forward = 1, Error = 2, NotAvailable = 3 }; diff --git a/src/isobus_standard_data_description_indices.hpp b/src/isobus_standard_data_description_indices.hpp index 3fae628..e4eb6a3 100644 --- a/src/isobus_standard_data_description_indices.hpp +++ b/src/isobus_standard_data_description_indices.hpp @@ -6,7 +6,7 @@ /// @note The full list of standardized DDIs can be found at "isobus.net" /// @author Adrian Del Grosso /// -/// @copyright 2023 Adrian Del Grosso +/// @copyright 2023 The Open-Agriculture Developers //================================================================================================ #ifndef ISOBUS_STANDARD_DATA_DESCRIPTION_INDICES_HPP #define ISOBUS_STANDARD_DATA_DESCRIPTION_INDICES_HPP @@ -541,7 +541,8 @@ namespace isobus ActualCoolingFluidTemperature = 0x020E, ///< The actual temperature of the cooling fluid for the machine. LastBaleCapacity = 0x0210, ///< The capacity of the bale that leaves the machine. PGNBasedData = 0xDFFE, ///< This DDI is used in the XML files to identify PGN based data. - RequestDefaultProcessData = 0xDFFF ///< Request Default Process Data. This DDE is the highest ISO assigned entity. The range above this number is reserved for manufacture specific DDE's. + RequestDefaultProcessData = 0xDFFF, ///< Request Default Process Data. This DDE is the highest ISO assigned entity. The range above this number is reserved for manufacture specific DDE's. + Reserved = 0xFFFF }; } diff --git a/src/isobus_task_controller_client.cpp b/src/isobus_task_controller_client.cpp index 44e459a..41da291 100644 --- a/src/isobus_task_controller_client.cpp +++ b/src/isobus_task_controller_client.cpp @@ -4,14 +4,13 @@ /// @brief A class to manage a client connection to a ISOBUS field computer's task controller /// @author Adrian Del Grosso /// -/// @copyright 2023 Adrian Del Grosso +/// @copyright 2023 The Open-Agriculture Developers //================================================================================================ #include "isobus_task_controller_client.hpp" #include "can_general_parameter_group_numbers.hpp" #include "can_network_manager.hpp" #include "can_stack_logger.hpp" -#include "isobus_virtual_terminal_client.hpp" #include "system_timing.hpp" #include "to_string.hpp" @@ -19,11 +18,13 @@ #include #include #include +#if !defined CAN_STACK_DISABLE_THREADS && !defined ARDUINO #include +#endif namespace isobus { - TaskControllerClient::TaskControllerClient(std::shared_ptr partner, std::shared_ptr clientSource, std::shared_ptr primaryVT) : + TaskControllerClient::TaskControllerClient(std::shared_ptr partner, std::shared_ptr clientSource, std::shared_ptr primaryVT) : languageCommandInterface(clientSource, partner), partnerControlFunction(partner), myControlFunction(clientSource), @@ -71,9 +72,7 @@ namespace isobus void TaskControllerClient::add_request_value_callback(RequestValueCommandCallback callback, void *parentPointer) { -#if !defined CAN_STACK_DISABLE_THREADS && !defined ARDUINO - const std::lock_guard lock(clientMutex); -#endif + LOCK_GUARD(Mutex, clientMutex); RequestValueCommandCallbackInfo callbackData = { callback, parentPointer }; requestValueCallbacks.push_back(callbackData); @@ -81,9 +80,7 @@ namespace isobus void TaskControllerClient::add_value_command_callback(ValueCommandCallback callback, void *parentPointer) { -#if !defined CAN_STACK_DISABLE_THREADS && !defined ARDUINO - const std::lock_guard lock(clientMutex); -#endif + LOCK_GUARD(Mutex, clientMutex); ValueCommandCallbackInfo callbackData = { callback, parentPointer }; valueCommandsCallbacks.push_back(callbackData); @@ -91,9 +88,7 @@ namespace isobus void TaskControllerClient::remove_request_value_callback(RequestValueCommandCallback callback, void *parentPointer) { -#if !defined CAN_STACK_DISABLE_THREADS && !defined ARDUINO - const std::lock_guard lock(clientMutex); -#endif + LOCK_GUARD(Mutex, clientMutex); RequestValueCommandCallbackInfo callbackData = { callback, parentPointer }; auto callbackLocation = std::find(requestValueCallbacks.begin(), requestValueCallbacks.end(), callbackData); @@ -106,9 +101,7 @@ namespace isobus void TaskControllerClient::remove_value_command_callback(ValueCommandCallback callback, void *parentPointer) { -#if !defined CAN_STACK_DISABLE_THREADS && !defined ARDUINO - const std::lock_guard lock(clientMutex); -#endif + LOCK_GUARD(Mutex, clientMutex); ValueCommandCallbackInfo callbackData = { callback, parentPointer }; auto callbackLocation = std::find(valueCommandsCallbacks.begin(), valueCommandsCallbacks.end(), callbackData); @@ -152,7 +145,7 @@ namespace isobus else { // We don't want someone to erase our object pool or something while it is being used. - CANStackLogger::error("[TC]: Cannot reconfigure TC client while it is running!"); + LOG_ERROR("[TC]: Cannot reconfigure TC client while it is running!"); } } @@ -190,7 +183,7 @@ namespace isobus else { // We don't want someone to erase our object pool or something while it is being used. - CANStackLogger::error("[TC]: Cannot reconfigure TC client while it is running!"); + LOG_ERROR("[TC]: Cannot reconfigure TC client while it is running!"); } } @@ -226,7 +219,7 @@ namespace isobus else { // We don't want someone to erase our object pool or something while it is being used. - CANStackLogger::error("[TC]: Cannot reconfigure TC client while it is running!"); + LOG_ERROR("[TC]: Cannot reconfigure TC client while it is running!"); } } @@ -234,9 +227,7 @@ namespace isobus { if (initialized) { -#if !defined CAN_STACK_DISABLE_THREADS && !defined ARDUINO - const std::lock_guard lock(clientMutex); -#endif + LOCK_GUARD(Mutex, clientMutex); set_state(StateMachineState::Disconnected); } } @@ -333,9 +324,7 @@ namespace isobus bool TaskControllerClient::reupload_device_descriptor_object_pool(std::shared_ptr> binaryDDOP) { bool retVal = false; -#if !defined CAN_STACK_DISABLE_THREADS && !defined ARDUINO - const std::lock_guard lock(clientMutex); -#endif + LOCK_GUARD(Mutex, clientMutex); if (StateMachineState::Connected == get_state()) { @@ -352,7 +341,7 @@ namespace isobus set_state(StateMachineState::DeactivateObjectPool); clear_queues(); retVal = true; - CANStackLogger::info("[TC]: Requested to change the DDOP. Object pool will be deactivated for a little while."); + LOG_INFO("[TC]: Requested to change the DDOP. Object pool will be deactivated for a little while."); } return retVal; } @@ -360,9 +349,7 @@ namespace isobus bool TaskControllerClient::reupload_device_descriptor_object_pool(std::uint8_t const *binaryDDOP, std::uint32_t DDOPSize) { bool retVal = false; -#if !defined CAN_STACK_DISABLE_THREADS && !defined ARDUINO - const std::lock_guard lock(clientMutex); -#endif + LOCK_GUARD(Mutex, clientMutex); if (StateMachineState::Connected == get_state()) { @@ -379,7 +366,7 @@ namespace isobus set_state(StateMachineState::DeactivateObjectPool); clear_queues(); retVal = true; - CANStackLogger::info("[TC]: Requested to change the DDOP. Object pool will be deactivated for a little while."); + LOG_INFO("[TC]: Requested to change the DDOP. Object pool will be deactivated for a little while."); } return retVal; } @@ -387,9 +374,7 @@ namespace isobus bool TaskControllerClient::reupload_device_descriptor_object_pool(std::shared_ptr DDOP) { bool retVal = false; -#if !defined CAN_STACK_DISABLE_THREADS && !defined ARDUINO - const std::lock_guard lock(clientMutex); -#endif + LOCK_GUARD(Mutex, clientMutex); if (StateMachineState::Connected == get_state()) { @@ -406,7 +391,7 @@ namespace isobus set_state(StateMachineState::DeactivateObjectPool); clear_queues(); retVal = true; - CANStackLogger::info("[TC]: Requested to change the DDOP. Object pool will be deactivated for a little while."); + LOG_INFO("[TC]: Requested to change the DDOP. Object pool will be deactivated for a little while."); } return retVal; } @@ -431,7 +416,7 @@ namespace isobus { if (SystemTiming::time_expired_ms(stateMachineTimestamp_ms, SIX_SECOND_TIMEOUT_MS)) { - CANStackLogger::debug("[TC]: Startup delay complete, waiting for TC server status message."); + LOG_DEBUG("[TC]: Startup delay complete, waiting for TC server status message."); set_state(StateMachineState::WaitForServerStatusMessage); } } @@ -445,7 +430,7 @@ namespace isobus } else if (SystemTiming::time_expired_ms(stateMachineTimestamp_ms, TWO_SECOND_TIMEOUT_MS)) { - CANStackLogger::error("[TC]: Timeout sending working set master message. Resetting client connection."); + LOG_ERROR("[TC]: Timeout sending working set master message. Resetting client connection."); set_state(StateMachineState::Disconnected); } } @@ -462,7 +447,7 @@ namespace isobus } else if (SystemTiming::time_expired_ms(stateMachineTimestamp_ms, TWO_SECOND_TIMEOUT_MS)) { - CANStackLogger::error("[TC]: Timeout sending first status message. Resetting client connection."); + LOG_ERROR("[TC]: Timeout sending first status message. Resetting client connection."); set_state(StateMachineState::Disconnected); } } @@ -476,7 +461,7 @@ namespace isobus } else if (SystemTiming::time_expired_ms(stateMachineTimestamp_ms, TWO_SECOND_TIMEOUT_MS)) { - CANStackLogger::error("[TC]: Timeout sending version request message. Resetting client connection."); + LOG_ERROR("[TC]: Timeout sending version request message. Resetting client connection."); set_state(StateMachineState::Disconnected); } } @@ -486,7 +471,7 @@ namespace isobus { if (SystemTiming::time_expired_ms(stateMachineTimestamp_ms, TWO_SECOND_TIMEOUT_MS)) { - CANStackLogger::error("[TC]: Timeout waiting for version request response. Resetting client connection."); + LOG_ERROR("[TC]: Timeout waiting for version request response. Resetting client connection."); set_state(StateMachineState::Disconnected); } } @@ -496,7 +481,8 @@ namespace isobus { if (SystemTiming::time_expired_ms(stateMachineTimestamp_ms, SIX_SECOND_TIMEOUT_MS)) { - CANStackLogger::warn("[TC]: Timeout waiting for version request from TC. This is not required, so proceeding anways."); + LOG_WARNING("[TC]: Timeout waiting for version request from TC. This is not required, so proceeding anways."); + select_language_command_partner(); set_state(StateMachineState::RequestLanguage); } } @@ -506,11 +492,12 @@ namespace isobus { if (send_request_version_response()) { + select_language_command_partner(); set_state(StateMachineState::RequestLanguage); } else if (SystemTiming::time_expired_ms(stateMachineTimestamp_ms, TWO_SECOND_TIMEOUT_MS)) { - CANStackLogger::error("[TC]: Timeout sending version request response. Resetting client connection."); + LOG_ERROR("[TC]: Timeout sending version request response. Resetting client connection."); set_state(StateMachineState::Disconnected); } } @@ -518,26 +505,14 @@ namespace isobus case StateMachineState::RequestLanguage: { - if ((serverVersion < static_cast(Version::SecondPublishedEdition)) && - (nullptr == primaryVirtualTerminal)) - { - languageCommandInterface.set_partner(nullptr); // TC might not reply and no VT specified, so just see if anyone knows. - CANStackLogger::warn("[TC]: The TC is < version 4 but no VT was provided. Language data will be requested globally, which might not be ideal."); - } - - if ((serverVersion < static_cast(Version::SecondPublishedEdition)) && - (nullptr != primaryVirtualTerminal) && - (primaryVirtualTerminal->languageCommandInterface.send_request_language_command())) - { - set_state(StateMachineState::WaitForLanguageResponse); - } - else if (languageCommandInterface.send_request_language_command()) + if (languageCommandInterface.send_request_language_command()) { set_state(StateMachineState::WaitForLanguageResponse); + languageCommandWaitingTimestamp_ms = SystemTiming::get_timestamp_ms(); } else if (SystemTiming::time_expired_ms(stateMachineTimestamp_ms, SIX_SECOND_TIMEOUT_MS)) { - CANStackLogger::error("[TC]: Timeout trying to send request for language command message. Resetting client connection."); + LOG_ERROR("[TC]: Timeout trying to send request for language command message. Resetting client connection."); set_state(StateMachineState::Disconnected); } } @@ -545,11 +520,36 @@ namespace isobus case StateMachineState::WaitForLanguageResponse: { - if ((SystemTiming::get_time_elapsed_ms(languageCommandInterface.get_language_command_timestamp()) < SIX_SECOND_TIMEOUT_MS) && + if ((SystemTiming::get_time_elapsed_ms(languageCommandInterface.get_language_command_timestamp()) < TWO_SECOND_TIMEOUT_MS) && ("" != languageCommandInterface.get_language_code())) { set_state(StateMachineState::ProcessDDOP); } + else if (SystemTiming::time_expired_ms(languageCommandWaitingTimestamp_ms, SIX_SECOND_TIMEOUT_MS)) + { + LOG_WARNING("[TC]: Timeout waiting for language response. Moving on to processing the DDOP anyways."); + set_state(StateMachineState::ProcessDDOP); + } + else if ((SystemTiming::time_expired_ms(stateMachineTimestamp_ms, TWO_SECOND_TIMEOUT_MS)) && + (nullptr != languageCommandInterface.get_partner())) + { + LOG_WARNING("[TC]: No response to our request for the language command data, which is unusual."); + + if (nullptr != primaryVirtualTerminal) + { + LOG_WARNING("[TC]: Falling back to VT for language data."); + languageCommandInterface.set_partner(primaryVirtualTerminal); + languageCommandInterface.send_request_language_command(); + stateMachineTimestamp_ms = SystemTiming::get_timestamp_ms(); + } + else + { + LOG_WARNING("[TC]: Since no VT was specified, falling back to a global request for language data."); + languageCommandInterface.set_partner(nullptr); + languageCommandInterface.send_request_language_command(); + stateMachineTimestamp_ms = SystemTiming::get_timestamp_ms(); + } + } } break; @@ -562,8 +562,8 @@ namespace isobus if (serverVersion < clientDDOP->get_task_controller_compatibility_level()) { clientDDOP->set_task_controller_compatibility_level(serverVersion); // Manipulate the DDOP slightly if needed to upload a version compatible DDOP - CANStackLogger::info("[TC]: DDOP will be generated using the server's version instead of the specified version. New version: " + - isobus::to_string(static_cast(serverVersion))); + LOG_INFO("[TC]: DDOP will be generated using the server's version instead of the specified version. New version: " + + isobus::to_string(static_cast(serverVersion))); } if (generatedBinaryDDOP.empty()) @@ -572,11 +572,11 @@ namespace isobus if (clientDDOP->generate_binary_object_pool(generatedBinaryDDOP)) { process_labels_from_ddop(); - CANStackLogger::debug("[TC]: DDOP Generated, size: " + isobus::to_string(static_cast(generatedBinaryDDOP.size()))); + LOG_DEBUG("[TC]: DDOP Generated, size: " + isobus::to_string(static_cast(generatedBinaryDDOP.size()))); if ((!previousStructureLabel.empty()) && (ddopStructureLabel == previousStructureLabel)) { - CANStackLogger::error("[TC]: You didn't properly update your new DDOP's structure label. ISO11783-10 states that an update to an object pool must include an updated structure label."); + LOG_ERROR("[TC]: You didn't properly update your new DDOP's structure label. ISO11783-10 states that an update to an object pool must include an updated structure label."); } previousStructureLabel = ddopStructureLabel; @@ -584,13 +584,13 @@ namespace isobus } else { - CANStackLogger::error("[TC]: Cannot proceed with connection to TC due to invalid DDOP. Check log for [DDOP] events. TC client will now terminate."); + LOG_ERROR("[TC]: Cannot proceed with connection to TC due to invalid DDOP. Check log for [DDOP] events. TC client will now terminate."); terminate(); } } else { - CANStackLogger::debug("[TC]: Using previously generated DDOP binary"); + LOG_DEBUG("[TC]: Using previously generated DDOP binary"); set_state(StateMachineState::RequestStructureLabel); } } @@ -599,19 +599,19 @@ namespace isobus if ((ddopLocalizationLabel.empty()) || (ddopStructureLabel.empty())) { - CANStackLogger::debug("[TC]: Beginning a search of pre-serialized DDOP for device structure and localization labels."); + LOG_DEBUG("[TC]: Beginning a search of pre-serialized DDOP for device structure and localization labels."); process_labels_from_ddop(); if ((ddopLocalizationLabel.empty()) || (ddopStructureLabel.empty())) { - CANStackLogger::error("[TC]: Failed to parse the DDOP. Ensure you provided a valid device object. TC client will now terminate."); + LOG_ERROR("[TC]: Failed to parse the DDOP. Ensure you provided a valid device object. TC client will now terminate."); terminate(); } } else { - CANStackLogger::debug("[TC]: Reusing previously located device labels."); + LOG_DEBUG("[TC]: Reusing previously located device labels."); } set_state(StateMachineState::RequestStructureLabel); } @@ -626,7 +626,7 @@ namespace isobus } else if (SystemTiming::time_expired_ms(stateMachineTimestamp_ms, TWO_SECOND_TIMEOUT_MS)) { - CANStackLogger::error("[TC]: Timeout trying to send request for TC structure label. Resetting client connection."); + LOG_ERROR("[TC]: Timeout trying to send request for TC structure label. Resetting client connection."); set_state(StateMachineState::Disconnected); } } @@ -636,7 +636,7 @@ namespace isobus { if (SystemTiming::time_expired_ms(stateMachineTimestamp_ms, TWO_SECOND_TIMEOUT_MS)) { - CANStackLogger::error("[TC]: Timeout waiting for TC structure label. Resetting client connection."); + LOG_ERROR("[TC]: Timeout waiting for TC structure label. Resetting client connection."); set_state(StateMachineState::Disconnected); } } @@ -650,7 +650,7 @@ namespace isobus } else if (SystemTiming::time_expired_ms(stateMachineTimestamp_ms, TWO_SECOND_TIMEOUT_MS)) { - CANStackLogger::error("[TC]: Timeout trying to send request for TC localization label. Resetting client connection."); + LOG_ERROR("[TC]: Timeout trying to send request for TC localization label. Resetting client connection."); set_state(StateMachineState::Disconnected); } } @@ -660,7 +660,7 @@ namespace isobus { if (SystemTiming::time_expired_ms(stateMachineTimestamp_ms, TWO_SECOND_TIMEOUT_MS)) { - CANStackLogger::error("[TC]: Timeout waiting for TC localization label. Resetting client connection."); + LOG_ERROR("[TC]: Timeout waiting for TC localization label. Resetting client connection."); set_state(StateMachineState::Disconnected); } } @@ -674,7 +674,7 @@ namespace isobus } else if (SystemTiming::time_expired_ms(stateMachineTimestamp_ms, TWO_SECOND_TIMEOUT_MS)) { - CANStackLogger::error("[TC]: Timeout trying to send delete object pool message. Resetting client connection."); + LOG_ERROR("[TC]: Timeout trying to send delete object pool message. Resetting client connection."); set_state(StateMachineState::Disconnected); } } @@ -684,7 +684,7 @@ namespace isobus { if (SystemTiming::time_expired_ms(stateMachineTimestamp_ms, TWO_SECOND_TIMEOUT_MS)) { - CANStackLogger::error("[TC]: Timeout waiting for delete object pool response. Resetting client connection."); + LOG_ERROR("[TC]: Timeout waiting for delete object pool response. Resetting client connection."); set_state(StateMachineState::Disconnected); } } @@ -698,7 +698,7 @@ namespace isobus } else if (SystemTiming::time_expired_ms(stateMachineTimestamp_ms, TWO_SECOND_TIMEOUT_MS)) { - CANStackLogger::error("[TC]: Timeout trying to send request to transfer object pool. Resetting client connection."); + LOG_ERROR("[TC]: Timeout trying to send request to transfer object pool. Resetting client connection."); set_state(StateMachineState::Disconnected); } } @@ -708,7 +708,7 @@ namespace isobus { if (SystemTiming::time_expired_ms(stateMachineTimestamp_ms, TWO_SECOND_TIMEOUT_MS)) { - CANStackLogger::error("[TC]: Timeout waiting for request transfer object pool response. Resetting client connection."); + LOG_ERROR("[TC]: Timeout waiting for request transfer object pool response. Resetting client connection."); set_state(StateMachineState::Disconnected); } } @@ -759,7 +759,7 @@ namespace isobus } else if (SystemTiming::time_expired_ms(stateMachineTimestamp_ms, TWO_SECOND_TIMEOUT_MS)) { - CANStackLogger::error("[TC]: Timeout trying to begin the object pool upload. Resetting client connection."); + LOG_ERROR("[TC]: Timeout trying to begin the object pool upload. Resetting client connection."); set_state(StateMachineState::Disconnected); } } @@ -776,7 +776,7 @@ namespace isobus { if (SystemTiming::time_expired_ms(stateMachineTimestamp_ms, TWO_SECOND_TIMEOUT_MS)) { - CANStackLogger::error("[TC]: Timeout waiting for object pool transfer response. Resetting client connection."); + LOG_ERROR("[TC]: Timeout waiting for object pool transfer response. Resetting client connection."); set_state(StateMachineState::Disconnected); } } @@ -790,7 +790,7 @@ namespace isobus } else if (SystemTiming::time_expired_ms(stateMachineTimestamp_ms, TWO_SECOND_TIMEOUT_MS)) { - CANStackLogger::error("[TC]: Timeout trying to activate object pool. Resetting client connection."); + LOG_ERROR("[TC]: Timeout trying to activate object pool. Resetting client connection."); set_state(StateMachineState::Disconnected); } } @@ -800,7 +800,7 @@ namespace isobus { if (SystemTiming::time_expired_ms(stateMachineTimestamp_ms, TWO_SECOND_TIMEOUT_MS)) { - CANStackLogger::error("[TC]: Timeout waiting for activate object pool response. Resetting client connection."); + LOG_ERROR("[TC]: Timeout waiting for activate object pool response. Resetting client connection."); set_state(StateMachineState::Disconnected); } } @@ -810,7 +810,7 @@ namespace isobus { if (SystemTiming::time_expired_ms(serverStatusMessageTimestamp_ms, SIX_SECOND_TIMEOUT_MS)) { - CANStackLogger::error("[TC]: Server Status Message Timeout. The TC may be offline."); + LOG_ERROR("[TC]: Server Status Message Timeout. The TC may be offline."); set_state(StateMachineState::Disconnected); } else @@ -829,7 +829,7 @@ namespace isobus } else if (SystemTiming::time_expired_ms(stateMachineTimestamp_ms, TWO_SECOND_TIMEOUT_MS)) { - CANStackLogger::error("[TC]: Timeout sending object pool deactivate. Client terminated."); + LOG_ERROR("[TC]: Timeout sending object pool deactivate. Client terminated."); set_state(StateMachineState::Disconnected); terminate(); } @@ -842,13 +842,13 @@ namespace isobus { if (shouldReuploadAfterDDOPDeletion) { - CANStackLogger::warn("[TC]: Timeout waiting for deactivate object pool response. This is unusual, but we're just going to reconnect anyways."); + LOG_WARNING("[TC]: Timeout waiting for deactivate object pool response. This is unusual, but we're just going to reconnect anyways."); shouldReuploadAfterDDOPDeletion = false; set_state(StateMachineState::ProcessDDOP); } else { - CANStackLogger::error("[TC]: Timeout waiting for deactivate object pool response. Client terminated."); + LOG_ERROR("[TC]: Timeout waiting for deactivate object pool response. Client terminated."); set_state(StateMachineState::Disconnected); terminate(); } @@ -1031,9 +1031,7 @@ namespace isobus void TaskControllerClient::process_queued_commands() { -#if !defined CAN_STACK_DISABLE_THREADS && !defined ARDUINO - const std::lock_guard lock(clientMutex); -#endif + LOCK_GUARD(Mutex, clientMutex); bool transmitSuccessful = true; while (!queuedValueRequests.empty() && transmitSuccessful) @@ -1042,7 +1040,7 @@ namespace isobus for (auto ¤tCallback : requestValueCallbacks) { - std::uint32_t newValue = 0; + std::int32_t newValue = 0; if (currentCallback.callback(currentRequest.elementNumber, currentRequest.ddi, newValue, currentCallback.parent)) { transmitSuccessful = send_value_command(currentRequest.elementNumber, currentRequest.ddi, newValue); @@ -1078,13 +1076,13 @@ namespace isobus for (auto &measurementTimeCommand : measurementTimeIntervalCommands) { - if (SystemTiming::time_expired_ms(measurementTimeCommand.lastValue, measurementTimeCommand.processDataValue)) + if (SystemTiming::time_expired_ms(static_cast(measurementTimeCommand.lastValue), static_cast(measurementTimeCommand.processDataValue))) { // Time to update this time interval variable transmitSuccessful = false; for (auto ¤tCallback : requestValueCallbacks) { - std::uint32_t newValue = 0; + std::int32_t newValue = 0; if (currentCallback.callback(measurementTimeCommand.elementNumber, measurementTimeCommand.ddi, newValue, currentCallback.parent)) { transmitSuccessful = send_value_command(measurementTimeCommand.elementNumber, measurementTimeCommand.ddi, newValue); @@ -1094,14 +1092,14 @@ namespace isobus if (transmitSuccessful) { - measurementTimeCommand.lastValue = SystemTiming::get_timestamp_ms(); + measurementTimeCommand.lastValue = static_cast(SystemTiming::get_timestamp_ms()); } } } for (auto &measurementMaxCommand : measurementMaximumThresholdCommands) { // Get the current process data value - std::uint32_t newValue = 0; + std::int32_t newValue = 0; for (auto ¤tCallback : requestValueCallbacks) { if (currentCallback.callback(measurementMaxCommand.elementNumber, measurementMaxCommand.ddi, newValue, currentCallback.parent)) @@ -1129,7 +1127,7 @@ namespace isobus for (auto &measurementMinCommand : measurementMinimumThresholdCommands) { // Get the current process data value - std::uint32_t newValue = 0; + std::int32_t newValue = 0; for (auto ¤tCallback : requestValueCallbacks) { if (currentCallback.callback(measurementMinCommand.elementNumber, measurementMinCommand.ddi, newValue, currentCallback.parent)) @@ -1157,7 +1155,7 @@ namespace isobus for (auto &measurementChangeCommand : measurementOnChangeThresholdCommands) { // Get the current process data value - std::uint32_t newValue = 0; + std::int32_t newValue = 0; for (auto ¤tCallback : requestValueCallbacks) { if (currentCallback.callback(measurementChangeCommand.elementNumber, measurementChangeCommand.ddi, newValue, currentCallback.parent)) @@ -1191,6 +1189,7 @@ namespace isobus (nullptr != message.get_source_control_function())) { auto parentTC = static_cast(parentPointer); + auto &clientMutex = parentTC->clientMutex; const auto &messageData = message.get_data(); switch (message.get_identifier().get_parameter_group_number()) @@ -1202,7 +1201,7 @@ namespace isobus std::uint32_t targetParameterGroupNumber = message.get_uint24_at(5); if (static_cast(CANLibParameterGroupNumber::ProcessData) == targetParameterGroupNumber) { - CANStackLogger::error("[TC]: The TC Server is NACK-ing our messages. Disconnecting."); + LOG_ERROR("[TC]: The TC Server is NACK-ing our messages. Disconnecting."); parentTC->set_state(StateMachineState::Disconnected); } } @@ -1225,7 +1224,7 @@ namespace isobus } else { - CANStackLogger::warn("[TC]: Server requested version information at a strange time."); + LOG_WARNING("[TC]: Server requested version information at a strange time."); } } break; @@ -1242,13 +1241,13 @@ namespace isobus if (messageData[1] > static_cast(Version::SecondPublishedEdition)) { - CANStackLogger::warn("[TC]: Server version is newer than client's maximum supported version."); + LOG_WARNING("[TC]: Server version is newer than client's maximum supported version."); } - CANStackLogger::debug("[TC]: TC Server supports version %u with %u booms, %u sections, and %u position based control channels.", - messageData[1], - messageData[5], - messageData[6], - messageData[7]); + LOG_DEBUG("[TC]: TC Server supports version %u with %u booms, %u sections, and %u position based control channels.", + messageData[1], + messageData[5], + messageData[6], + messageData[7]); if (StateMachineState::WaitForRequestVersionResponse == parentTC->get_state()) { @@ -1259,7 +1258,7 @@ namespace isobus default: { - CANStackLogger::warn("[TC]: Unsupported process data technical data message received. Message will be dropped."); + LOG_WARNING("[TC]: Unsupported process data technical data message received. Message will be dropped."); } break; } @@ -1297,26 +1296,26 @@ namespace isobus if (tcStructure.size() > 40) { - CANStackLogger::warn("[TC]: Structure Label from TC exceeds the max length allowed by ISO11783-10"); + LOG_WARNING("[TC]: Structure Label from TC exceeds the max length allowed by ISO11783-10"); } if (parentTC->ddopStructureLabel == tcStructure) { // Structure label matched. No upload needed yet. - CANStackLogger::debug("[TC]: Task controller structure labels match"); + LOG_DEBUG("[TC]: Task controller structure labels match"); parentTC->set_state(StateMachineState::RequestLocalizationLabel); } else { // Structure label did not match. Need to delete current DDOP and re-upload. - CANStackLogger::info("[TC]: Task controller structure labels do not match. DDOP will be deleted and reuploaded."); + LOG_INFO("[TC]: Task controller structure labels do not match. DDOP will be deleted and reuploaded."); parentTC->set_state(StateMachineState::SendDeleteObjectPool); } } } else { - CANStackLogger::warn("[TC]: Structure label message received, but ignored due to current state machine state."); + LOG_WARNING("[TC]: Structure label message received, but ignored due to current state machine state."); } } break; @@ -1357,20 +1356,20 @@ namespace isobus if (labelsMatch) { // DDOP labels all matched - CANStackLogger::debug("[TC]: Task controller localization labels match"); + LOG_DEBUG("[TC]: Task controller localization labels match"); parentTC->set_state(StateMachineState::SendObjectPoolActivate); } else { // Labels didn't match. Reupload - CANStackLogger::info("[TC]: Task controller localization labels do not match. DDOP will be deleted and reuploaded."); + LOG_INFO("[TC]: Task controller localization labels do not match. DDOP will be deleted and reuploaded."); parentTC->set_state(StateMachineState::SendDeleteObjectPool); } } } else { - CANStackLogger::warn("[TC]: Localization label message received, but ignored due to current state machine state."); + LOG_WARNING("[TC]: Localization label message received, but ignored due to current state machine state."); } } break; @@ -1382,18 +1381,18 @@ namespace isobus if (0 == messageData[1]) { // Because there is overhead associated with object storage, it is impossible to predict whether there is enough memory available, technically. - CANStackLogger::debug("[TC]: Server indicates there may be enough memory available."); + LOG_DEBUG("[TC]: Server indicates there may be enough memory available."); parentTC->set_state(StateMachineState::BeginTransferDDOP); } else { - CANStackLogger::error("[TC]: Server states that there is not enough memory available for our DDOP. Client will terminate."); + LOG_ERROR("[TC]: Server states that there is not enough memory available for our DDOP. Client will terminate."); parentTC->terminate(); } } else { - CANStackLogger::warn("[TC]: Request Object-pool Transfer Response message received, but ignored due to current state machine state."); + LOG_WARNING("[TC]: Request Object-pool Transfer Response message received, but ignored due to current state machine state."); } } break; @@ -1404,59 +1403,59 @@ namespace isobus { if (0 == messageData[1]) { - CANStackLogger::info("[TC]: DDOP Activated without error."); + LOG_INFO("[TC]: DDOP Activated without error."); parentTC->set_state(StateMachineState::Connected); } else { - CANStackLogger::error("[TC]: DDOP was not activated."); + LOG_ERROR("[TC]: DDOP was not activated."); if (0x01 & messageData[1]) { - CANStackLogger::error("[TC]: There are errors in the DDOP. Faulting parent ID: " + - isobus::to_string(static_cast(static_cast(messageData[2]) | - static_cast(messageData[3] << 8))) + - " Faulting object: " + - isobus::to_string(static_cast(static_cast(messageData[4]) | - static_cast(messageData[5] << 8)))); + LOG_ERROR("[TC]: There are errors in the DDOP. Faulting parent ID: " + + isobus::to_string(static_cast(static_cast(messageData[2]) | + static_cast(messageData[3] << 8))) + + " Faulting object: " + + isobus::to_string(static_cast(static_cast(messageData[4]) | + static_cast(messageData[5] << 8)))); if (0x01 & messageData[6]) { - CANStackLogger::error("[TC]: Method or attribute not supported by the TC"); + LOG_ERROR("[TC]: Method or attribute not supported by the TC"); } if (0x02 & messageData[6]) { - CANStackLogger::error("[TC]: Unknown object reference (missing object)"); + LOG_ERROR("[TC]: Unknown object reference (missing object)"); } if (0x04 & messageData[6]) { - CANStackLogger::error("[TC]: Unknown error (Any other error)"); + LOG_ERROR("[TC]: Unknown error (Any other error)"); } if (0x08 & messageData[6]) { - CANStackLogger::error("[TC]: Device descriptor object pool was deleted from volatile memory"); + LOG_ERROR("[TC]: Device descriptor object pool was deleted from volatile memory"); } if (0xF0 & messageData[6]) { - CANStackLogger::warn("[TC]: The TC sent illegal errors in the reserved bits of the response."); + LOG_WARNING("[TC]: The TC sent illegal errors in the reserved bits of the response."); } } if (0x02 & messageData[1]) { - CANStackLogger::error("[TC]: Task Controller ran out of memory during activation."); + LOG_ERROR("[TC]: Task Controller ran out of memory during activation."); } if (0x04 & messageData[1]) { - CANStackLogger::error("[TC]: Task Controller indicates an unknown error occurred."); + LOG_ERROR("[TC]: Task Controller indicates an unknown error occurred."); } if (0x08 & messageData[1]) { - CANStackLogger::error("[TC]: A different DDOP with the same structure label already exists in the TC."); + LOG_ERROR("[TC]: A different DDOP with the same structure label already exists in the TC."); } if (0xF0 & messageData[1]) { - CANStackLogger::warn("[TC]: The TC sent illegal errors in the reserved bits of the response."); + LOG_WARNING("[TC]: The TC sent illegal errors in the reserved bits of the response."); } parentTC->set_state(StateMachineState::Disconnected); - CANStackLogger::error("[TC]: Client terminated."); + LOG_ERROR("[TC]: Client terminated."); parentTC->terminate(); } } @@ -1464,7 +1463,7 @@ namespace isobus { if (0 == messageData[1]) { - CANStackLogger::info("[TC]: Object pool deactivated OK."); + LOG_INFO("[TC]: Object pool deactivated OK."); if (parentTC->shouldReuploadAfterDDOPDeletion) { @@ -1473,12 +1472,12 @@ namespace isobus } else { - CANStackLogger::error("[TC]: Object pool deactivation error."); + LOG_ERROR("[TC]: Object pool deactivation error."); } } else { - CANStackLogger::warn("[TC]: Object pool activate/deactivate response received at a strange time. Message dropped."); + LOG_WARNING("[TC]: Object pool activate/deactivate response received at a strange time. Message dropped."); } } break; @@ -1500,33 +1499,33 @@ namespace isobus { if (0 == messageData[1]) { - CANStackLogger::debug("[TC]: DDOP upload completed with no errors."); + LOG_DEBUG("[TC]: DDOP upload completed with no errors."); parentTC->set_state(StateMachineState::SendObjectPoolActivate); } else { if (0x01 == messageData[1]) { - CANStackLogger::error("[TC]: DDOP upload completed but TC ran out of memory during transfer."); + LOG_ERROR("[TC]: DDOP upload completed but TC ran out of memory during transfer."); } else { - CANStackLogger::error("[TC]: DDOP upload completed but TC had some unknown error."); + LOG_ERROR("[TC]: DDOP upload completed but TC had some unknown error."); } - CANStackLogger::error("[TC]: Client terminated."); + LOG_ERROR("[TC]: Client terminated."); parentTC->terminate(); } } else { - CANStackLogger::warn("[TC]: Recieved unexpected object pool transfer response"); + LOG_WARNING("[TC]: Recieved unexpected object pool transfer response"); } } break; default: { - CANStackLogger::warn("[TC]: Unsupported device descriptor command message received. Message will be dropped."); + LOG_WARNING("[TC]: Unsupported device descriptor command message received. Message will be dropped."); } break; } @@ -1554,25 +1553,23 @@ namespace isobus case ProcessDataCommands::ClientTask: { - CANStackLogger::warn("[TC]: Server sent the client task message, which is not meant to be sent by servers."); + LOG_WARNING("[TC]: Server sent the client task message, which is not meant to be sent by servers."); } break; case ProcessDataCommands::RequestValue: { ProcessDataCallbackInfo requestData = { 0, 0, 0, 0, false, false }; -#if !defined CAN_STACK_DISABLE_THREADS && !defined ARDUINO - const std::lock_guard lock(parentTC->clientMutex); -#endif + LOCK_GUARD(Mutex, clientMutex); requestData.ackRequested = false; requestData.elementNumber = (static_cast(messageData[0] >> 4) | (static_cast(messageData[1]) << 4)); requestData.ddi = static_cast(messageData[2]) | (static_cast(messageData[3]) << 8); - requestData.processDataValue = (static_cast(messageData[4]) | - (static_cast(messageData[5]) << 8) | - (static_cast(messageData[6]) << 16) | - (static_cast(messageData[7]) << 24)); + requestData.processDataValue = (static_cast(messageData[4]) | + (static_cast(messageData[5]) << 8) | + (static_cast(messageData[6]) << 16) | + (static_cast(messageData[7]) << 24)); parentTC->queuedValueRequests.push_back(requestData); } break; @@ -1580,18 +1577,16 @@ namespace isobus case ProcessDataCommands::Value: { ProcessDataCallbackInfo requestData = { 0, 0, 0, 0, false, false }; -#if !defined CAN_STACK_DISABLE_THREADS && !defined ARDUINO - const std::lock_guard lock(parentTC->clientMutex); -#endif + LOCK_GUARD(Mutex, clientMutex); requestData.ackRequested = false; requestData.elementNumber = (static_cast(messageData[0] >> 4) | (static_cast(messageData[1]) << 4)); requestData.ddi = static_cast(messageData[2]) | (static_cast(messageData[3]) << 8); - requestData.processDataValue = (static_cast(messageData[4]) | - (static_cast(messageData[5]) << 8) | - (static_cast(messageData[6]) << 16) | - (static_cast(messageData[7]) << 24)); + requestData.processDataValue = (static_cast(messageData[4]) | + (static_cast(messageData[5]) << 8) | + (static_cast(messageData[6]) << 16) | + (static_cast(messageData[7]) << 24)); parentTC->queuedValueCommands.push_back(requestData); } break; @@ -1599,18 +1594,16 @@ namespace isobus case ProcessDataCommands::SetValueAndAcknowledge: { ProcessDataCallbackInfo requestData = { 0, 0, 0, 0, false, false }; -#if !defined CAN_STACK_DISABLE_THREADS && !defined ARDUINO - const std::lock_guard lock(parentTC->clientMutex); -#endif + LOCK_GUARD(Mutex, clientMutex); requestData.ackRequested = true; requestData.elementNumber = (static_cast(messageData[0] >> 4) | (static_cast(messageData[1]) << 4)); requestData.ddi = static_cast(messageData[2]) | (static_cast(messageData[3]) << 8); - requestData.processDataValue = (static_cast(messageData[4]) | - (static_cast(messageData[5]) << 8) | - (static_cast(messageData[6]) << 16) | - (static_cast(messageData[7]) << 24)); + requestData.processDataValue = (static_cast(messageData[4]) | + (static_cast(messageData[5]) << 8) | + (static_cast(messageData[6]) << 16) | + (static_cast(messageData[7]) << 24)); parentTC->queuedValueCommands.push_back(requestData); } break; @@ -1618,42 +1611,40 @@ namespace isobus case ProcessDataCommands::MeasurementTimeInterval: { ProcessDataCallbackInfo commandData = { 0, 0, 0, 0, false, false }; -#if !defined CAN_STACK_DISABLE_THREADS && !defined ARDUINO - const std::lock_guard lock(parentTC->clientMutex); -#endif + LOCK_GUARD(Mutex, clientMutex); commandData.elementNumber = (static_cast(messageData[0] >> 4) | (static_cast(messageData[1]) << 4)); commandData.ddi = static_cast(messageData[2]) | (static_cast(messageData[3]) << 8); - commandData.processDataValue = (static_cast(messageData[4]) | - (static_cast(messageData[5]) << 8) | - (static_cast(messageData[6]) << 16) | - (static_cast(messageData[7]) << 24)); - commandData.lastValue = SystemTiming::get_timestamp_ms(); + commandData.processDataValue = (static_cast(messageData[4]) | + (static_cast(messageData[5]) << 8) | + (static_cast(messageData[6]) << 16) | + (static_cast(messageData[7]) << 24)); + commandData.lastValue = static_cast(SystemTiming::get_timestamp_ms()); auto previousCommand = std::find(parentTC->measurementTimeIntervalCommands.begin(), parentTC->measurementTimeIntervalCommands.end(), commandData); if (parentTC->measurementTimeIntervalCommands.end() == previousCommand) { parentTC->measurementTimeIntervalCommands.push_back(commandData); - CANStackLogger::debug("[TC]: TC Requests element: " + - isobus::to_string(static_cast(commandData.elementNumber)) + - " DDI: " + - isobus::to_string(static_cast(commandData.ddi)) + - " every: " + - isobus::to_string(static_cast(commandData.processDataValue)) + - " milliseconds."); + LOG_DEBUG("[TC]: TC Requests element: " + + isobus::to_string(static_cast(commandData.elementNumber)) + + " DDI: " + + isobus::to_string(static_cast(commandData.ddi)) + + " every: " + + isobus::to_string(static_cast(commandData.processDataValue)) + + " milliseconds."); } else { // Use the existing one and update the value previousCommand->processDataValue = commandData.processDataValue; - CANStackLogger::debug("[TC]: TC Altered time interval request for element: " + - isobus::to_string(static_cast(commandData.elementNumber)) + - " DDI: " + - isobus::to_string(static_cast(commandData.ddi)) + - " every: " + - isobus::to_string(static_cast(commandData.processDataValue)) + - " milliseconds."); + LOG_DEBUG("[TC]: TC Altered time interval request for element: " + + isobus::to_string(static_cast(commandData.elementNumber)) + + " DDI: " + + isobus::to_string(static_cast(commandData.ddi)) + + " every: " + + isobus::to_string(static_cast(commandData.processDataValue)) + + " milliseconds."); } } break; @@ -1661,28 +1652,26 @@ namespace isobus case ProcessDataCommands::MeasurementMaximumWithinThreshold: { ProcessDataCallbackInfo commandData = { 0, 0, 0, 0, false, false }; -#if !defined CAN_STACK_DISABLE_THREADS && !defined ARDUINO - const std::lock_guard lock(parentTC->clientMutex); -#endif + LOCK_GUARD(Mutex, clientMutex); commandData.elementNumber = (static_cast(messageData[0] >> 4) | (static_cast(messageData[1]) << 4)); commandData.ddi = static_cast(messageData[2]) | (static_cast(messageData[3]) << 8); - commandData.processDataValue = (static_cast(messageData[4]) | - (static_cast(messageData[5]) << 8) | - (static_cast(messageData[6]) << 16) | - (static_cast(messageData[7]) << 24)); + commandData.processDataValue = (static_cast(messageData[4]) | + (static_cast(messageData[5]) << 8) | + (static_cast(messageData[6]) << 16) | + (static_cast(messageData[7]) << 24)); auto previousCommand = std::find(parentTC->measurementMaximumThresholdCommands.begin(), parentTC->measurementMaximumThresholdCommands.end(), commandData); if (parentTC->measurementMaximumThresholdCommands.end() == previousCommand) { parentTC->measurementMaximumThresholdCommands.push_back(commandData); - CANStackLogger::debug("[TC]: TC Requests element: " + - isobus::to_string(static_cast(commandData.elementNumber)) + - " DDI: " + - isobus::to_string(static_cast(commandData.ddi)) + - " when it is above the raw value: " + - isobus::to_string(static_cast(commandData.processDataValue))); + LOG_DEBUG("[TC]: TC Requests element: " + + isobus::to_string(static_cast(commandData.elementNumber)) + + " DDI: " + + isobus::to_string(static_cast(commandData.ddi)) + + " when it is above the raw value: " + + isobus::to_string(static_cast(commandData.processDataValue))); } else { @@ -1696,28 +1685,26 @@ namespace isobus case ProcessDataCommands::MeasurementMinimumWithinThreshold: { ProcessDataCallbackInfo commandData = { 0, 0, 0, 0, false, false }; -#if !defined CAN_STACK_DISABLE_THREADS && !defined ARDUINO - const std::lock_guard lock(parentTC->clientMutex); -#endif + LOCK_GUARD(Mutex, clientMutex); commandData.elementNumber = (static_cast(messageData[0] >> 4) | (static_cast(messageData[1]) << 4)); commandData.ddi = static_cast(messageData[2]) | (static_cast(messageData[3]) << 8); - commandData.processDataValue = (static_cast(messageData[4]) | - (static_cast(messageData[5]) << 8) | - (static_cast(messageData[6]) << 16) | - (static_cast(messageData[7]) << 24)); + commandData.processDataValue = (static_cast(messageData[4]) | + (static_cast(messageData[5]) << 8) | + (static_cast(messageData[6]) << 16) | + (static_cast(messageData[7]) << 24)); auto previousCommand = std::find(parentTC->measurementMinimumThresholdCommands.begin(), parentTC->measurementMinimumThresholdCommands.end(), commandData); if (parentTC->measurementMinimumThresholdCommands.end() == previousCommand) { parentTC->measurementMinimumThresholdCommands.push_back(commandData); - CANStackLogger::debug("[TC]: TC Requests Element " + - isobus::to_string(static_cast(commandData.elementNumber)) + - " DDI: " + - isobus::to_string(static_cast(commandData.ddi)) + - " when it is below the raw value: " + - isobus::to_string(static_cast(commandData.processDataValue))); + LOG_DEBUG("[TC]: TC Requests Element " + + isobus::to_string(static_cast(commandData.elementNumber)) + + " DDI: " + + isobus::to_string(static_cast(commandData.ddi)) + + " when it is below the raw value: " + + isobus::to_string(static_cast(commandData.processDataValue))); } else { @@ -1731,28 +1718,26 @@ namespace isobus case ProcessDataCommands::MeasurementChangeThreshold: { ProcessDataCallbackInfo commandData = { 0, 0, 0, 0, false, false }; -#if !defined CAN_STACK_DISABLE_THREADS && !defined ARDUINO - const std::lock_guard lock(parentTC->clientMutex); -#endif + LOCK_GUARD(Mutex, clientMutex); commandData.elementNumber = (static_cast(messageData[0] >> 4) | (static_cast(messageData[1]) << 4)); commandData.ddi = static_cast(messageData[2]) | (static_cast(messageData[3]) << 8); - commandData.processDataValue = (static_cast(messageData[4]) | - (static_cast(messageData[5]) << 8) | - (static_cast(messageData[6]) << 16) | - (static_cast(messageData[7]) << 24)); + commandData.processDataValue = (static_cast(messageData[4]) | + (static_cast(messageData[5]) << 8) | + (static_cast(messageData[6]) << 16) | + (static_cast(messageData[7]) << 24)); auto previousCommand = std::find(parentTC->measurementOnChangeThresholdCommands.begin(), parentTC->measurementOnChangeThresholdCommands.end(), commandData); if (parentTC->measurementOnChangeThresholdCommands.end() == previousCommand) { parentTC->measurementOnChangeThresholdCommands.push_back(commandData); - CANStackLogger::debug("[TC]: TC Requests element " + - isobus::to_string(static_cast(commandData.elementNumber)) + - " DDI: " + - isobus::to_string(static_cast(commandData.ddi)) + - " on change by at least: " + - isobus::to_string(static_cast(commandData.processDataValue))); + LOG_DEBUG("[TC]: TC Requests element " + + isobus::to_string(static_cast(commandData.elementNumber)) + + " DDI: " + + isobus::to_string(static_cast(commandData.ddi)) + + " on change by at least: " + + isobus::to_string(static_cast(commandData.processDataValue))); } else { @@ -1767,14 +1752,14 @@ namespace isobus { if (0 != messageData[4]) { - CANStackLogger::warn("[TC]: TC sent us a PDNACK"); + LOG_WARNING("[TC]: TC sent us a PDNACK"); } } break; default: { - CANStackLogger::warn("[TC]: Unhandled process data message!"); + LOG_WARNING("[TC]: Unhandled process data message!"); } break; } @@ -1838,7 +1823,7 @@ namespace isobus } else { - CANStackLogger::error("[TC]: DDOP internal data callback received out of range request."); + LOG_ERROR("[TC]: DDOP internal data callback received out of range request."); } return retVal; } @@ -1864,7 +1849,7 @@ namespace isobus } else { - CANStackLogger::error("[TC]: DDOP upload did not complete. Resetting."); + LOG_ERROR("[TC]: DDOP upload did not complete. Resetting."); parent->set_state(StateMachineState::Disconnected); } } @@ -2017,7 +2002,7 @@ namespace isobus partnerControlFunction); } - bool TaskControllerClient::send_value_command(std::uint16_t elementNumber, std::uint16_t ddi, std::uint32_t value) const + bool TaskControllerClient::send_value_command(std::uint16_t elementNumber, std::uint16_t ddi, std::int32_t value) const { const std::array buffer = { static_cast(static_cast(ProcessDataCommands::Value) | (static_cast(elementNumber & 0x0F) << 4)), @@ -2090,6 +2075,23 @@ namespace isobus currentState = newState; } + void TaskControllerClient::select_language_command_partner() + { + if (serverVersion < static_cast(Version::SecondPublishedEdition)) + { + if (nullptr == primaryVirtualTerminal) + { + languageCommandInterface.set_partner(nullptr); // TC might not reply and no VT specified, so just see if anyone knows. + LOG_WARNING("[TC]: The TC is < version 4 but no VT was provided. Language data will be requested globally, which might not be ideal."); + } + else + { + languageCommandInterface.set_partner(primaryVirtualTerminal); + LOG_DEBUG("[TC]: Using VT as the partner for language data, because the TC's version is less than 4."); + } + } + } + void TaskControllerClient::worker_thread_function() { #if !defined CAN_STACK_DISABLE_THREADS && !defined ARDUINO @@ -2149,9 +2151,7 @@ namespace isobus void TaskControllerClient::on_value_changed_trigger(std::uint16_t elementNumber, std::uint16_t DDI) { ProcessDataCallbackInfo requestData = { 0, 0, 0, 0, false, false }; -#if !defined CAN_STACK_DISABLE_THREADS && !defined ARDUINO - const std::lock_guard lock(clientMutex); -#endif + LOCK_GUARD(Mutex, clientMutex); requestData.ackRequested = false; requestData.elementNumber = elementNumber; diff --git a/src/isobus_task_controller_client.hpp b/src/isobus_task_controller_client.hpp index 2b1beb1..c8b7765 100644 --- a/src/isobus_task_controller_client.hpp +++ b/src/isobus_task_controller_client.hpp @@ -4,7 +4,7 @@ /// @brief A class to manage a client connection to a ISOBUS field computer's task controller /// @author Adrian Del Grosso /// -/// @copyright 2022 Adrian Del Grosso +/// @copyright 2022 The Open-Agriculture Developers //================================================================================================ #ifndef ISOBUS_TASK_CONTROLLER_CLIENT_HPP #define ISOBUS_TASK_CONTROLLER_CLIENT_HPP @@ -16,12 +16,12 @@ #include "processing_flags.hpp" #include +#if !defined CAN_STACK_DISABLE_THREADS && !defined ARDUINO #include +#endif namespace isobus { - class VirtualTerminalClient; // Forward declaring VT client - /// @brief A class to manage a client connection to a ISOBUS field computer's task controller or data logger class TaskControllerClient { @@ -86,20 +86,20 @@ namespace isobus /// @brief A callback for handling a value request command from the TC using RequestValueCommandCallback = bool (*)(std::uint16_t elementNumber, std::uint16_t DDI, - std::uint32_t &processVariableValue, + std::int32_t &processVariableValue, void *parentPointer); /// @brief A callback for handling a set value command from the TC using ValueCommandCallback = bool (*)(std::uint16_t elementNumber, std::uint16_t DDI, - std::uint32_t processVariableValue, + std::int32_t processVariableValue, void *parentPointer); /// @brief The constructor for a TaskControllerClient /// @param[in] partner The TC server control function /// @param[in] clientSource The internal control function to communicate from /// @param[in] primaryVT Pointer to our primary VT. This is optional (can be nullptr), but should be provided if possible to provide the best compatibility to TC < version 4. - TaskControllerClient(std::shared_ptr partner, std::shared_ptr clientSource, std::shared_ptr primaryVT); + TaskControllerClient(std::shared_ptr partner, std::shared_ptr clientSource, std::shared_ptr primaryVT); /// @brief Destructor for the client ~TaskControllerClient(); @@ -112,7 +112,7 @@ namespace isobus /// @brief This adds a callback that will be called when the TC requests the value of one of your variables. /// @details The task controller will often send a request for the value of a process data variable. - /// When the stack recieves those messages, it will call this callback to request the value from your + /// When the stack receives those messages, it will call this callback to request the value from your /// application. You must provide the value at that time for the associated process data variable identified /// by its element number and DDI. /// @param[in] callback The callback to add @@ -520,7 +520,7 @@ namespace isobus /// @param[in] ddi The DDI for the command /// @param[in] value The value to send /// @returns `true` if the message was sent, otherwise `false` - bool send_value_command(std::uint16_t elementNumber, std::uint16_t ddi, std::uint32_t value) const; + bool send_value_command(std::uint16_t elementNumber, std::uint16_t ddi, std::int32_t value) const; /// @brief Sends the version request message to the TC /// @returns `true` if the message was sent, otherwise `false` @@ -558,6 +558,9 @@ namespace isobus /// @param[in] timestamp The new value for the state machine timestamp (in milliseconds) void set_state(StateMachineState newState, std::uint32_t timestamp); + /// @brief Sets the behavior of the language command interface based on the TC's reported version information + void select_language_command_partner(); + /// @brief The worker thread will execute this function when it runs, if applicable void worker_thread_function(); @@ -572,8 +575,8 @@ namespace isobus /// @param obj the object to compare against /// @returns true if the ddi and element numbers of the provided objects match, otherwise false bool operator==(const ProcessDataCallbackInfo &obj) const; - std::uint32_t processDataValue; ///< The value of the value set command - std::uint32_t lastValue; ///< Used for measurement commands to store timestamp or previous values + std::int32_t processDataValue; ///< The value of the value set command + std::int32_t lastValue; ///< Used for measurement commands to store timestamp or previous values std::uint16_t elementNumber; ///< The element number for the command std::uint16_t ddi; ///< The DDI for the command bool ackRequested; ///< Stores if the TC used the mux that also requires a PDACK @@ -612,7 +615,7 @@ namespace isobus std::shared_ptr partnerControlFunction; ///< The partner control function this client will send to std::shared_ptr myControlFunction; ///< The internal control function the client uses to send from - std::shared_ptr primaryVirtualTerminal; ///< A pointer to the primary VT. Used for TCs < version 4 + std::shared_ptr primaryVirtualTerminal; ///< A pointer to the primary VT's control function. Used for TCs < version 4 and language command compatibility std::shared_ptr clientDDOP; ///< Stores the DDOP for upload to the TC (if needed) std::uint8_t const *userSuppliedBinaryDDOP = nullptr; ///< Stores a client-provided DDOP if one was provided std::shared_ptr> userSuppliedVectorDDOP; ///< Stores a client-provided DDOP if one was provided @@ -625,8 +628,8 @@ namespace isobus std::list measurementMinimumThresholdCommands; ///< A list of measurement commands that will be processed when the value drops below a threshold std::list measurementMaximumThresholdCommands; ///< A list of measurement commands that will be processed when the value above a threshold std::list measurementOnChangeThresholdCommands; ///< A list of measurement commands that will be processed when the value changes by the specified amount + Mutex clientMutex; ///< A general mutex to protect data in the worker thread against data accessed by the app or the network manager #if !defined CAN_STACK_DISABLE_THREADS && !defined ARDUINO - std::mutex clientMutex; ///< A general mutex to protect data in the worker thread against data accessed by the app or the network manager std::thread *workerThread = nullptr; ///< The worker thread that updates this interface #endif std::string ddopStructureLabel; ///< Stores a pre-parsed structure label, helps to avoid processing the whole DDOP during a CAN message callback @@ -638,6 +641,7 @@ namespace isobus std::uint32_t statusMessageTimestamp_ms = 0; ///< Timestamp corresponding to the last time we sent a status message to the TC std::uint32_t serverStatusMessageTimestamp_ms = 0; ///< Timestamp corresponding to the last time we received a status message from the TC std::uint32_t userSuppliedBinaryDDOPSize_bytes = 0; ///< The number of bytes in the user provided binary DDOP (if one was provided) + std::uint32_t languageCommandWaitingTimestamp_ms = 0; ///< Timestamp used to determine when to give up on waiting for a language command response std::uint8_t numberOfWorkingSetMembers = 1; ///< The number of working set members that will be reported in the working set master message std::uint8_t tcStatusBitfield = 0; ///< The last received TC/DL status from the status message std::uint8_t sourceAddressOfCommandBeingExecuted = 0; ///< Source address of client for which the current command is being executed diff --git a/src/isobus_task_controller_client_objects.cpp b/src/isobus_task_controller_client_objects.cpp index 9f0d200..0b9bf0d 100644 --- a/src/isobus_task_controller_client_objects.cpp +++ b/src/isobus_task_controller_client_objects.cpp @@ -4,7 +4,7 @@ /// @brief Implements the base functionality of the basic task controller objects. /// @author Adrian Del Grosso /// -/// @copyright 2023 Adrian Del Grosso +/// @copyright 2023 The Open-Agriculture Developers //================================================================================================ #include "isobus_task_controller_client_objects.hpp" @@ -318,9 +318,9 @@ namespace isobus return retVal; } - std::size_t DeviceElementObject::get_number_child_objects() const + std::uint16_t DeviceElementObject::get_number_child_objects() const { - return referenceList.size(); + return static_cast(referenceList.size()); } std::uint16_t DeviceElementObject::get_child_object_id(std::size_t index) diff --git a/src/isobus_task_controller_client_objects.hpp b/src/isobus_task_controller_client_objects.hpp index 8133beb..fde28a9 100644 --- a/src/isobus_task_controller_client_objects.hpp +++ b/src/isobus_task_controller_client_objects.hpp @@ -4,7 +4,7 @@ /// @brief Defines a set of C++ objects that represent a DDOP /// @author Adrian Del Grosso /// -/// @copyright 2023 Adrian Del Grosso +/// @copyright 2023 The Open-Agriculture Developers //================================================================================================ #ifndef ISOBUS_TASK_CONTROLLER_CLIENT_OBJECTS_HPP #define ISOBUS_TASK_CONTROLLER_CLIENT_OBJECTS_HPP @@ -272,9 +272,11 @@ namespace isobus /// @returns true if the child object ID was found and removed, otherwise false bool remove_reference_to_child_object(std::uint16_t childID); - /// @brief Returns the number of child objects added with `add_reference_to_child_object` + /// @brief Returns the number of child objects added with `add_reference_to_child_object`. + /// @note The maximum number of child objects is technically 65535 because the serialized + /// form of the value uses a 16-bit integer to store the count. /// @returns The number of child objects added with `add_reference_to_child_object` - std::size_t get_number_child_objects() const; + std::uint16_t get_number_child_objects() const; /// @brief Returns a child object ID by index /// @param[in] index The index of the child object ID to return diff --git a/src/isobus_time_date_interface.cpp b/src/isobus_time_date_interface.cpp new file mode 100644 index 0000000..0f95ff7 --- /dev/null +++ b/src/isobus_time_date_interface.cpp @@ -0,0 +1,217 @@ +//================================================================================================ +/// @file isobus_time_date_interface.cpp +/// +/// @brief Implements an interface to handle to transmit the time and date information using the +/// Time/Date (TD) PGN. +/// +/// @author Adrian Del Grosso +/// +/// @copyright 2024 The Open-Agriculture Developers +//================================================================================================ +#include "isobus_time_date_interface.hpp" + +#include "can_general_parameter_group_numbers.hpp" +#include "can_parameter_group_number_request_protocol.hpp" +#include "can_stack_logger.hpp" +#include "to_string.hpp" + +#include + +#ifndef DISABLE_CAN_STACK_LOGGER +#include +#include +#endif + +namespace isobus +{ + TimeDateInterface::TimeDateInterface(std::shared_ptr sourceControlFunction, std::function timeAndDateCallback) : + myControlFunction(sourceControlFunction), + userTimeDateCallback(timeAndDateCallback) + { + if (nullptr != sourceControlFunction) + { + assert(nullptr != timeAndDateCallback); // You need a callback to populate the time and date information... the interface needs to know the time and date to send it out on the bus! + } + } + + TimeDateInterface::~TimeDateInterface() + { + if (initialized && (nullptr != myControlFunction)) + { + auto pgnRequestProtocol = myControlFunction->get_pgn_request_protocol().lock(); + + if (nullptr != pgnRequestProtocol) + { + pgnRequestProtocol->remove_pgn_request_callback(static_cast(CANLibParameterGroupNumber::TimeDate), process_request_for_time_date, this); + } + } + } + + void TimeDateInterface::initialize() + { + if (!initialized) + { + CANNetworkManager::CANNetwork.add_global_parameter_group_number_callback(static_cast(CANLibParameterGroupNumber::TimeDate), + process_rx_message, + this); + + if (nullptr != myControlFunction) + { + auto pgnRequestProtocol = myControlFunction->get_pgn_request_protocol().lock(); + + if (nullptr != pgnRequestProtocol) + { + pgnRequestProtocol->register_pgn_request_callback(static_cast(CANLibParameterGroupNumber::TimeDate), process_request_for_time_date, this); + } + } + initialized = true; + } + } + + bool TimeDateInterface::is_initialized() const + { + return initialized; + } + + EventDispatcher &TimeDateInterface::get_event_dispatcher() + { + return timeAndDateEventDispatcher; + } + + bool TimeDateInterface::send_time_and_date(const TimeAndDate &timeAndDateToSend) const + { + // If you hit any of these assertions, it's because you are trying to send an invalid time and date. + // Sending invalid values on the network is bad. + // Please check the values you are trying to send and make sure they are within the valid ranges noted below. + // These values can also be found in the ISO11783-7 standard (on isobus.net), or in the J1939 standard. + // Also, please only send the time and date if you have a good RTC or GPS source. Sending bad time and date information can cause issues for other devices on the network. + assert(timeAndDateToSend.year >= 1985 && timeAndDateToSend.year <= 2235); // The year must be between 1985 and 2235 + assert(timeAndDateToSend.month >= 1 && timeAndDateToSend.month <= 12); // The month must be between 1 and 12 + assert(timeAndDateToSend.day <= 31); // The day must be between 0 and 31 + assert(timeAndDateToSend.hours <= 23); // The hours must be between 0 and 23 + assert(timeAndDateToSend.minutes <= 59); // The minutes must be between 0 and 59 + assert(timeAndDateToSend.seconds <= 59); // The seconds must be between 0 and 59 + assert(timeAndDateToSend.quarterDays <= 3); // The quarter days must be between 0 and 3 + assert(timeAndDateToSend.milliseconds == 0 || timeAndDateToSend.milliseconds == 250 || timeAndDateToSend.milliseconds == 500 || timeAndDateToSend.milliseconds == 750); // The milliseconds must be 0, 250, 500, or 750 + assert(timeAndDateToSend.localHourOffset >= -23 && timeAndDateToSend.localHourOffset <= 23); // The local hour offset must be between -23 and 23 + assert(timeAndDateToSend.localMinuteOffset >= -59 && timeAndDateToSend.localMinuteOffset <= 59); // The local minute offset must be between -59 and 59 + + const std::array buffer = { + static_cast(timeAndDateToSend.seconds * 4 + (timeAndDateToSend.milliseconds / 250)), + timeAndDateToSend.minutes, + timeAndDateToSend.hours, + timeAndDateToSend.month, + static_cast(timeAndDateToSend.day * 4 + timeAndDateToSend.quarterDays), + static_cast(timeAndDateToSend.year - 1985), + static_cast(timeAndDateToSend.localMinuteOffset + 125), + static_cast(timeAndDateToSend.localHourOffset + 125) + }; + return CANNetworkManager::CANNetwork.send_can_message(static_cast(CANLibParameterGroupNumber::TimeDate), + buffer.data(), + buffer.size(), + myControlFunction, + nullptr, + CANIdentifier::CANPriority::PriorityDefault6); + } + + bool TimeDateInterface::request_time_and_date(std::shared_ptr requestingControlFunction, std::shared_ptr optionalDestination) const + { + bool retVal = false; + + if (nullptr != requestingControlFunction) + { + retVal = ParameterGroupNumberRequestProtocol::request_parameter_group_number(static_cast(CANLibParameterGroupNumber::TimeDate), + requestingControlFunction, + optionalDestination); + } + return retVal; + } + + std::shared_ptr TimeDateInterface::get_control_function() const + { + return myControlFunction; + } + + void TimeDateInterface::process_rx_message(const CANMessage &message, void *parentPointer) + { + auto timeDateInterface = static_cast(parentPointer); + + if ((nullptr != timeDateInterface) && + (static_cast(CANLibParameterGroupNumber::TimeDate) == message.get_identifier().get_parameter_group_number()) && + (nullptr != message.get_source_control_function())) + { + if (CAN_DATA_LENGTH == message.get_data_length()) + { + TimeAndDateInformation timeAndDateInformation; + + timeAndDateInformation.controlFunction = message.get_source_control_function(); + timeAndDateInformation.timeAndDate.seconds = message.get_uint8_at(0) / 4; // This is SPN 959 + timeAndDateInformation.timeAndDate.milliseconds = static_cast((message.get_uint8_at(0) % 4) * 250); // This is also part of SPN 959 + timeAndDateInformation.timeAndDate.minutes = message.get_uint8_at(1); // This is SPN 960 + timeAndDateInformation.timeAndDate.hours = message.get_uint8_at(2); // This is SPN 961 + timeAndDateInformation.timeAndDate.month = message.get_uint8_at(3); // This is SPN 963 + timeAndDateInformation.timeAndDate.day = message.get_uint8_at(4) / 4; // This is SPN 962 + timeAndDateInformation.timeAndDate.quarterDays = message.get_uint8_at(4) % 4; // This is also part of SPN 962 + timeAndDateInformation.timeAndDate.year = static_cast(message.get_uint8_at(5) + 1985); // This is SPN 964 + timeAndDateInformation.timeAndDate.localMinuteOffset = static_cast(message.get_uint8_at(6) - 125); // This is SPN 1601 + timeAndDateInformation.timeAndDate.localHourOffset = static_cast(message.get_int8_at(7) - 125); // This is SPN 1602 + +#ifndef DISABLE_CAN_STACK_LOGGER + if (CANStackLogger::get_log_level() == CANStackLogger::LoggingLevel::Debug) // This is a very heavy log statement, so only do it if we are logging at debug level + { + std::ostringstream oss; + oss << "[Time/Date]: Control Function 0x"; + oss << std::setfill('0') << std::setw(16) << std::hex << message.get_source_control_function()->get_NAME().get_full_name(); + oss << " at address " << static_cast(message.get_source_control_function()->get_address()); + oss << " reports it is: " << isobus::to_string(static_cast(timeAndDateInformation.timeAndDate.hours)) << ":"; + oss << isobus::to_string(static_cast(timeAndDateInformation.timeAndDate.minutes)) << ":"; + oss << isobus::to_string(static_cast(timeAndDateInformation.timeAndDate.seconds)); + oss << " on day " << isobus::to_string(static_cast(timeAndDateInformation.timeAndDate.day)); + oss << " of month " << isobus::to_string(static_cast(timeAndDateInformation.timeAndDate.month)); + oss << " in the year " << isobus::to_string(static_cast(timeAndDateInformation.timeAndDate.year)); + oss << " with a local offset of " << isobus::to_string(static_cast(timeAndDateInformation.timeAndDate.localHourOffset)); + oss << " hours and " << isobus::to_string(static_cast(timeAndDateInformation.timeAndDate.localMinuteOffset)) << " minutes."; + CANStackLogger::debug(oss.str()); + } +#endif + timeDateInterface->timeAndDateEventDispatcher.invoke(std::move(timeAndDateInformation)); + } + else + { + CANStackLogger::warn("[Time/Date]: Received a Time/Date message with an invalid data length. DLC must be 8."); + } + } + } + + bool TimeDateInterface::process_request_for_time_date(std::uint32_t parameterGroupNumber, + std::shared_ptr, + bool &acknowledge, + AcknowledgementType &, + void *parentPointer) + { + bool retVal = false; + + if ((nullptr != parentPointer) && + (static_cast(CANLibParameterGroupNumber::TimeDate) == parameterGroupNumber)) + { + auto interface = static_cast(parentPointer); + + if ((nullptr != interface->myControlFunction) && + (nullptr != interface->userTimeDateCallback)) + { + TimeAndDate timeAndDateInformation; + if (interface->userTimeDateCallback(timeAndDateInformation)) // Getting the time and date information from the user callback + { + CANStackLogger::debug("[Time/Date]: Received a request for Time/Date information and interface is configured to reply. Sending Time/Date."); + retVal = interface->send_time_and_date(timeAndDateInformation); + acknowledge = false; + } + else + { + CANStackLogger::error("[Time/Date]: Your application failed to provide Time/Date information when requested! You are probably doing something wrong. The request may be NACKed as a result."); + } + } + } + return retVal; + } +} // namespace isobus diff --git a/src/isobus_time_date_interface.hpp b/src/isobus_time_date_interface.hpp new file mode 100644 index 0000000..8f6c90c --- /dev/null +++ b/src/isobus_time_date_interface.hpp @@ -0,0 +1,135 @@ +//================================================================================================ +/// @file isobus_time_date_interface.hpp +/// +/// @brief Defines an interface for accessing or sending time and date information using +/// the Time/Date (TD) PGN. Can be useful for interacting with an ISOBUS file server, +/// or just for keeping track of time and date information as provided by some authoritative +/// control function on the bus. Control functions which provide the message this interface +/// manages are expected to have a real-time clock (RTC) or GPS time source. +/// @author Adrian Del Grosso +/// +/// @copyright 2024 The Open-Agriculture Developers +//================================================================================================ +#ifndef ISOBUS_TIME_DATE_INTERFACE_HPP +#define ISOBUS_TIME_DATE_INTERFACE_HPP + +#include "can_callbacks.hpp" +#include "can_internal_control_function.hpp" +#include "can_message.hpp" +#include "event_dispatcher.hpp" + +namespace isobus +{ + /// @brief An interface for sending and receiving time and date information using the Time/Date (TD) PGN, 0xFEE6. + /// You may hear this time referred to as "ISOBUS Time" in some cases. It is normally provided by control functions with a + /// real-time clock (RTC) or GPS source. This is not the same thing as the NMEA2000 time and date, which is PGN 129033 (0x1F809), and is + /// backwards compatible with J1939 which uses this same PGN and message structure. + class TimeDateInterface + { + public: + /// @brief A struct to hold time and date information. + /// This will generally be a UTC time and date, unless the local hour offset is 0, + /// in which case it will be a local time and date. + /// We store it slightly differently than the PGN to make it easier to work with. + struct TimeAndDate + { + std::uint16_t milliseconds = 0; ///< Number of milliseconds. This has resolution of 0.25s, so it will be either 0, 250, 500, or 750 + std::uint8_t seconds = 0; ///< Number of seconds, range: 0 to 59s + std::uint8_t minutes = 0; ///< Number of minutes, range: 0 to 59m + std::uint8_t hours = 0; ///< Number of hours, range: 0 to 23h + std::uint8_t quarterDays = 0; ///< Number of quarter days. This is a less precise version of "hours" that is used in some cases. Range: 0 to 3. 0 is midnight, 1 is 6am, 2 is noon, 3 is 6pm + std::uint8_t day = 0; ///< Number of days, range 0 to 31 + std::uint8_t month = 0; ///< Number of months, range 1 to 12 + std::uint16_t year = 1985; ///< The year. Range: 1985 to 2235 + std::int8_t localMinuteOffset = 0; ///< Local minute offset is the number of minutes between the UTC time and date and a local time and date. This value is added to UTC time and date to determine the local time and date. The local offset is a positive value for times east of the Prime Meridian to the International Date Line. + std::int8_t localHourOffset = 0; ///< Local hour offset is the number of hours between the UTC time and date and a local time and date. This value is added to UTC time and date to determine the local time and date. The local offset is a positive value for times east of the Prime Meridian to the International Date Line. + }; + + /// @brief A struct to hold time and date information and the control function that sent it. + /// Used by the event dispatcher to provide event driven access to time and date information. + struct TimeAndDateInformation + { + TimeAndDate timeAndDate; ///< The time and date information + std::shared_ptr controlFunction; ///< The control function that sent the time and date information + }; + + /// @brief Constructor for the TimeDateInterface class, with no source control function. + /// Receives time and date information from the bus, and does not transmit. + /// This is generally the normal use case for this class. + TimeDateInterface() = default; + + /// @brief Constructor for the TimeDateInterface class, used for when you want to also transmit the time/date. + /// @param sourceControlFunction If you want to transmit the time and date information, you + /// can pass a control function in this parameter to be used as the source of the information. + /// @param timeAndDateCallback A callback that will be called when the interface needs you to tell it the current time and date. + /// This is used to populate the time and date information that will be sent out on the bus. The function you use for this callback + /// should be relatively quick as it will be called from the CAN stack's thread, and you don't want to delay the stack's update thread. + /// The function should return "true" if the time and date information was successfully populated, and "false" if it was not. + /// Note that if it returns false, the request will probably be NACKed, which is not ideal. + TimeDateInterface(std::shared_ptr sourceControlFunction, std::function timeAndDateCallback); + + /// @brief Destructor for the TimeDateInterface class. + ~TimeDateInterface(); + + /// @brief Deleted copy constructor for TimeDateInterface + TimeDateInterface(TimeDateInterface &) = delete; + + /// @brief Initializes the interface. + /// @details This needs to be called before the interface is usable. + /// It registers its PGN callback and sets up the PGN request interface + /// if needed. + void initialize(); + + /// @brief Returns if initialize has been called yet + /// @return `true` if initialize has been called, otherwise false + bool is_initialized() const; + + /// @brief Returns the event dispatcher for time and date information. + /// Use this to subscribe to event-driven time and date information events. + /// @return The event dispatcher for time and date information + EventDispatcher &get_event_dispatcher(); + + /// @brief Sends a time and date message (a broadcast message) as long as the interface + /// has been initialized and a control function has been set. + /// @param timeAndDateToSend The time and date information to send + /// @return `true` if the message was sent, otherwise `false` + bool send_time_and_date(const TimeAndDate &timeAndDateToSend) const; + + /// @brief Requests time and date information from a specific control function, or from all control functions to see if any respond. + /// Responses can be monitored by using the event dispatcher. See get_event_dispatcher. + /// This is really just a very thin wrapper around the PGN request interface for convenience. + /// @param requestingControlFunction This control function will be used to send the request. + /// @param optionalDestination If you want to request time and date information from a specific control function, you can pass it here, otherwise pass an empty pointer. + /// @return `true` if the request was sent, otherwise `false` + bool request_time_and_date(std::shared_ptr requestingControlFunction, std::shared_ptr optionalDestination = nullptr) const; + + /// @brief Returns the control function that is being used as the source of the time and date information if one was set. + /// @return The control function that is being used as the source of the time and date information, or an empty pointer if one was not set. + std::shared_ptr get_control_function() const; + + private: + /// @brief Parses incoming CAN messages into usable unit and language settings + /// @param message The CAN message to parse + /// @param parentPointer A generic context variable, usually the `this` pointer for this interface instance + static void process_rx_message(const CANMessage &message, void *parentPointer); + + /// @brief Processes a PGN request + /// @param[in] parameterGroupNumber The PGN being requested + /// @param[in] requestingControlFunction The control function that is requesting the PGN + /// @param[in] acknowledge If the request should be acknowledged (will always be false for this interface) + /// @param[in] acknowledgeType How to acknowledge the request (will always be NACK for this interface) + /// @param[in] parentPointer A context variable to find the relevant instance of this class + /// @returns True if the request was serviced, otherwise false. + static bool process_request_for_time_date(std::uint32_t parameterGroupNumber, + std::shared_ptr requestingControlFunction, + bool &acknowledge, + AcknowledgementType &acknowledgeType, + void *parentPointer); + + std::shared_ptr myControlFunction; ///< The control function to send messages as, or an empty pointer if not sending + std::function userTimeDateCallback; ///< The callback the user provided to get the time and date information at runtime to be transmitted + EventDispatcher timeAndDateEventDispatcher; ///< The event dispatcher for time and date information + bool initialized = false; ///< If the interface has been initialized yet + }; +} // namespace isobus +#endif // ISOBUS_TIME_DATE_INTERFACE_HPP diff --git a/src/isobus_virtual_terminal_base.hpp b/src/isobus_virtual_terminal_base.hpp new file mode 100644 index 0000000..2e7e194 --- /dev/null +++ b/src/isobus_virtual_terminal_base.hpp @@ -0,0 +1,385 @@ +//================================================================================================ +/// @file isobus_virtual_terminal_base.hpp +/// +/// @brief A base class that stores definitions common between the VT client and the VT server. +/// @author Adrian Del Grosso +/// +/// @copyright 2023 Adrian Del Grosso +//================================================================================================ +#ifndef ISOBUS_VIRTUAL_TERMINAL_BASE_HPP +#define ISOBUS_VIRTUAL_TERMINAL_BASE_HPP + +#include + +namespace isobus +{ + /// @brief A base class for the VT client and VT server that stores common definitions + class VirtualTerminalBase + { + public: + /// @brief Enumerates the states that can be sent with a hide/show object command + enum class HideShowObjectCommand : std::uint8_t + { + HideObject = 0, ///< Hides the object + ShowObject = 1 ///< Shows an object + }; + + /// @brief Enumerates the states that can be sent with an enable/disable object command + enum class EnableDisableObjectCommand : std::uint8_t + { + DisableObject = 0, ///< Disables a compatible object + EnableObject = 1 ///< Enables a compatible object + }; + + /// @brief Enumerates the states that can be sent with a select input object options command + enum class SelectInputObjectOptions : std::uint8_t + { + ActivateObjectForDataInput = 0x00, ///< Activates an object for data input + SetFocusToObject = 0xFF ///< Focuses the object (usually this draws a temporary box around it) + }; + + /// @brief The different VT versions that a client or server might support + enum class VTVersion + { + Version2OrOlder, ///< Client or server supports VT version 2 or lower + Version3, ///< Client or server supports all of VT version 3 + Version4, ///< Client or server supports all of VT version 4 + Version5, ///< Client or server supports all of VT version 5 + Version6, ///< Client or server supports all of VT version 6 + ReservedOrUnknown, ///< Reserved value, not to be used + }; + + /// @brief Enumerates the different line directions that can be used when changing an endpoint of an object + enum class LineDirection : std::uint8_t + { + TopLeftToBottomRightOfEnclosingVirtualRectangle = 0, ///< Draws the line from top left to bottom right of the enclosing virtual rectangle + BottomLeftToTopRightOfEnclosingVirtualRectangle = 1 ///< Draws the line from bottom left to top right of the enclosing virtual rectangle + }; + + /// @brief Enumerates the different font sizes + enum class FontSize : std::uint8_t + { + Size6x8 = 0, ///< 6x8 Font size + Size8x8 = 1, ///< 8x8 Font size + Size8x12 = 2, ///< 8x12 Font size + Size12x16 = 3, ///< 12x16 Font size + Size16x16 = 4, ///< 16x16 Font size + Size16x24 = 5, ///< 16x24 Font size + Size24x32 = 6, ///< 24x32 Font size + Size32x32 = 7, ///< 32x32 Font size + Size32x48 = 8, ///< 32x48 Font size + Size48x64 = 9, ///< 48x64 Font size + Size64x64 = 10, ///< 64x64 Font size + Size64x96 = 11, ///< 64x96 Font size + Size96x128 = 12, ///< 96x128 Font size + Size128x128 = 13, ///< 128x128 Font size + Size128x192 = 14 ///< 128x192 Font size + }; + + /// @brief Enumerates the font style options that can be encoded in a font style bitfield + enum class FontStyleBits : std::uint8_t + { + Bold = 0, ///< Bold font style + CrossedOut = 1, ///< Crossed-out font style (strikethrough) + Underlined = 2, ///< Underlined font style + Italic = 3, ///< Italic font style + Inverted = 4, ///< Inverted font style (upside down) + Flashing = 5, ///< Flashing font style + FlashingHidden = 6, ///< Flashing between hidden and shown font style + ProportionalFontRendering = 7 ///< Enables proportional font rendering if supported by the server + }; + + /// @brief Enumerates the different font types + enum class FontType : std::uint8_t + { + ISO8859_1 = 0, ///< ISO Latin 1 + ISO8859_15 = 1, ///< ISO Latin 9 + ISO8859_2 = 2, ///< ISO Latin 2 + Reserved_1 = 3, ///< Reserved + ISO8859_4 = 4, ///< ISO Latin 4 + ISO8859_5 = 5, ///< Cyrillic + Reserved_2 = 6, ///< Reserved + ISO8859_7 = 7, ///< Greek + ReservedEnd = 239, ///< Reserved from ISO8859_7 to this value + ProprietaryBegin = 240, ///< The beginning of the proprietary range + ProprietaryEnd = 255 ///< The end of the proprietary region + }; + + /// @brief Enumerates the different fill types for an object + enum class FillType : std::uint8_t + { + NoFill = 0, ///< No fill will be applied + FillWithLineColour = 1, ///< Fill with the colour of the outline of the shape + FillWithSpecifiedColourInFillColourAttribute = 2, ///< Fill with the colour specified by a fill attribute + FillWithPatternGivenByFillPatternAttribute = 3 ///< Fill with a patter provided by a fill pattern attribute + }; + + /// @brief The types of object pool masks + enum class MaskType : std::uint8_t + { + DataMask = 1, ///< A data mask, used in normal circumstances + AlarmMask = 2 ///< An alarm mask, which has different metadata related to popping up alarms, like priority + }; + + /// @brief The allowable priorities of an alarm mask + enum class AlarmMaskPriority : std::uint8_t + { + High = 0, ///< Overrides lower priority alarm masks + Medium = 1, ///< Overrides low priority alarm masks + Low = 2 ///< Overrides data masks + }; + + /// @brief Denotes the lock/unlock state of a mask. Used to freeze/unfreeze rendering of a mask. + enum class MaskLockState : std::uint8_t + { + UnlockMask = 0, ///< Renders the mask normally + LockMask = 1 ///< Locks the mask so rendering of it is not updated until it is unlocked or a timeout occurs + }; + + /// @brief The different key activation codes that a button press can generate + enum class KeyActivationCode : std::uint8_t + { + ButtonUnlatchedOrReleased = 0, ///< Button is released + ButtonPressedOrLatched = 1, ///< Button is pressed + ButtonStillHeld = 2, ///< Button is being held down (sent cyclically) + ButtonPressAborted = 3 ///< Press was aborted (user navigated away from the button and did not release it) + }; + + /// @brief Enumerates the errors that can be present in an ESC message + enum class ESCMessageErrorCode : std::uint8_t + { + NoError = 0, ///< No error occurred + NoInputFieldOpen = 1, ///< No input field is open + OtherError = 5 ///< Error is not one of the above + }; + + /// @brief Enumerates the different events that can be associated with a macro + enum class MacroEventID : std::uint8_t + { + Reserved = 0, ///< Reserved + OnActivate = 1, ///< Event on activation of an object (such as for data input) + OnDeactivate = 2, ///< Event on deactivation of an object + OnShow = 3, ///< Event on an object being shown + OnHide = 4, ///< Event on an object being hidden + OnEnable = 5, ///< Event on enable of an object + OnDisable = 6, ///< Event on disabling an object + OnChangeActiveMask = 7, ///< Event on changing the active mask + OnChangeSoftKeyMask = 8, ///< Event on change of the soft key mask + OnChangeAttribute = 9, ///< Event on change of an attribute value + OnChangeBackgroundColour = 10, ///< Event on change of a background colour + OnChangeFontAttributes = 11, ///< Event on change of a font attribute + OnChangeLineAttributes = 12, ///< Event on change of a line attribute + OnChangeFillAttributes = 13, ///< Event on change of a fill attribute + OnChangeChildLocation = 14, ///< Event on change of a child objects location + OnChangeSize = 15, ///< Event on change of an object size + OnChangeValue = 16, ///< Event on change of an object value (like via `change numeric value`) + OnChangePriority = 17, ///< Event on change of a mask's priority + OnChangeEndPoint = 18, ///< Event on change of an object endpoint + OnInputFieldSelection = 19, ///< Event when an input field is selected + OnInputFieldDeselection = 20, ///< Event on deselection of an input field + OnESC = 21, ///< Event on ESC (escape) + OnEntryOfValue = 22, ///< Event on entry of a value + OnEntryOfNewValue = 23, ///< Event on entry of a *new* value + OnKeyPress = 24, ///< Event on the press of a key + OnKeyRelease = 25, ///< Event on the release of a key + OnChangeChildPosition = 26, ///< Event on changing a child object's position + OnPointingEventPress = 27, ///< Event on a pointing event press + OnPointingEventRelease = 28, ///< Event on a pointing event release + ReservedBegin = 29, ///< Beginning of the reserved range + ReservedEnd = 254, ///< End of the reserved range + UseExtendedMacroReference = 255 ///< Use extended macro reference + }; + + /// @brief Enumerates the various VT server graphics modes + enum class GraphicMode : std::uint8_t + { + Monochrome = 0, ///< Monochromatic graphics mode (1 bit) + SixteenColour = 1, ///< 16 Colour mode (4 bit) + TwoHundredFiftySixColour = 2 ///< 256 Colour mode (8 bit) + }; + + /// @brief Enumerates the various auxiliary input function types + enum class AuxiliaryTypeTwoFunctionType : std::uint8_t + { + BooleanLatching = 0, ///< Two-position switch (maintains position) (Single Pole, Double Throw) + AnalogueLatching = 1, ///< Two-way analogue (Maintains position setting) + BooleanMomentary = 2, ///< Two-position switch (returns to off) (Momentary Single Pole, Single Throw) + AnalogueMomentaryTwoWay = 3, ///< Two-way analogue (returns to centre position - 50%) + AnalogueMomentaryOneWay = 4, ///< One-way analogue (returns to 0%) + DualBooleanLatching = 5, ///< Three-position switch (maintains position) (Single Pole, Three Positions, Centre Off) + DualBooleanMomentary = 6, ///< Three-position switch (returns to off/centre position) (Momentary Single Pole, Three Positions, Centre Off) + DualBooleanLatchingUpOnly = 7, ///< Three-position switch (maintains position only in up position) (Single Pole, Three Positions, Centre Off) + DualBooleanLatchingDownpOnly = 8, ///< Three-position switch (maintains position only in down position) (Momentary Single Pole, Three Positions, Centre Off) + AnalogueMomentaryBooleanLatching = 9, ///< two-way analogue (returns to centre position) with latching Boolean at 0% and 100% positions + AnalogueLatchingBooleanLatching = 10, ///< two-way analogue (maintains position setting) with momentary Boolean at 0% and 100% positions + QuadratureBooleanMomentary = 11, ///< Two Quadrature mounted Three-position switches (returns to centre position) (Momentary Single Pole, Three Position Single Throw, Centre Off) + QuadratureAnalogueLatching = 12, ///< Two Quadrature mounted Two-way analogue (maintains position) + QuadratureAnalogueMomentary = 13, ///< Two Quadrature mounted Two-way analogue (returns to centre position - 50%) + BidirectionalEncoder = 14, ///< Count increases when turning in the encoders "increase" direction, and decreases when turning in the opposite direction + Reserved = 30, ///< 15-30 Reserved + ReservedRemoveAssignment = 31 ///< Used for Remove assignment command + }; + + /// @brief The internal state machine state of the VT client, mostly just public so tests can access it + enum class StateMachineState : std::uint8_t + { + Disconnected, ///< VT is not connected, and is not trying to connect yet + WaitForPartnerVTStatusMessage, ///< VT client is initialized, waiting for a VT server to come online + SendWorkingSetMasterMessage, ///< Client is sending the working state master message + ReadyForObjectPool, ///< Client needs an object pool before connection can continue + SendGetMemory, ///< Client is sending the "get memory" message to see if VT has enough memory available + WaitForGetMemoryResponse, ///< Client is waiting for a response to the "get memory" message + SendGetNumberSoftkeys, ///< Client is sending the "get number of soft keys" message + WaitForGetNumberSoftKeysResponse, ///< Client is waiting for a response to the "get number of soft keys" message + SendGetTextFontData, ///< Client is sending the "get text font data" message + WaitForGetTextFontDataResponse, ///< Client is waiting for a response to the "get text font data" message + SendGetHardware, ///< Client is sending the "get hardware" message + WaitForGetHardwareResponse, ///< Client is waiting for a response to the "get hardware" message + SendGetVersions, ///< If a version label was specified, check to see if the VT has that version already + WaitForGetVersionsResponse, ///< Client is waiting for a response to the "get versions" message + SendStoreVersion, ///< Sending the store version command + WaitForStoreVersionResponse, ///< Client is waiting for a response to the store version command + SendLoadVersion, ///< Sending the load version command + WaitForLoadVersionResponse, ///< Client is waiting for the VT to respond to the "Load Version" command + UploadObjectPool, ///< Client is uploading the object pool + SendEndOfObjectPool, ///< Client is sending the end of object pool message + WaitForEndOfObjectPoolResponse, ///< Client is waiting for the end of object pool response message + Connected, ///< Client is connected to the VT server and the application layer is in control + Failed ///< Client could not connect to the VT due to an error + }; + + /// @brief Enumerates the errors that can occur when requesting the supported wide chars from a VT + enum class SupportedWideCharsErrorCode + { + TooManyRanges = 0x01, ///< Too many ranges (more than 255 sub-ranges in the requested range) + ErrorInCodePlane = 0x02, + AnyOtherError = 0x08 + }; + + /// @brief A struct for storing information of a function assigned to an auxiliary input + class AssignedAuxiliaryFunction + { + public: + /// @brief Constructs a `AssignedAuxiliaryFunction`, sets default values + /// @param[in] functionObjectID the object ID of the function present in our object pool + /// @param[in] inputObjectID the object ID assigned on the auxiliary inputs end + /// @param[in] functionType the type of function + AssignedAuxiliaryFunction(std::uint16_t functionObjectID, std::uint16_t inputObjectID, AuxiliaryTypeTwoFunctionType functionType); + + /// @brief Allows easy comparison of two `AssignedAuxiliaryFunction` objects + /// @param[in] other the object to compare against + /// @returns true if the two objects are equal, otherwise false + bool operator==(const AssignedAuxiliaryFunction &other) const; + + std::uint16_t functionObjectID; ///< The object ID of the function present in our object pool + std::uint16_t inputObjectID; ///< The object ID assigned on the auxiliary inputs end + AuxiliaryTypeTwoFunctionType functionType; ///< The type of function + }; + + protected: + /// @brief Enumerates the multiplexor byte values for VT commands + enum class Function : std::uint8_t + { + SoftKeyActivationMessage = 0x00, + ButtonActivationMessage = 0x01, + PointingEventMessage = 0x02, + VTSelectInputObjectMessage = 0x03, + VTESCMessage = 0x04, + VTChangeNumericValueMessage = 0x05, + VTChangeActiveMaskMessage = 0x06, + VTChangeSoftKeyMaskMessage = 0x07, + VTChangeStringValueMessage = 0x08, + VTOnUserLayoutHideShowMessage = 0x09, + VTControlAudioSignalTerminationMessage = 0x0A, + ObjectPoolTransferMessage = 0x11, + EndOfObjectPoolMessage = 0x12, + AuxiliaryAssignmentTypeOneCommand = 0x20, + AuxiliaryInputTypeOneStatus = 0x21, + PreferredAssignmentCommand = 0x22, + AuxiliaryInputTypeTwoMaintenanceMessage = 0x23, + AuxiliaryAssignmentTypeTwoCommand = 0x24, + AuxiliaryInputStatusTypeTwoEnableCommand = 0x25, + AuxiliaryInputTypeTwoStatusMessage = 0x26, + AuxiliaryCapabilitiesRequest = 0x27, + SelectActiveWorkingSet = 0x90, + ESCCommand = 0x92, + HideShowObjectCommand = 0xA0, + EnableDisableObjectCommand = 0xA1, + SelectInputObjectCommand = 0xA2, + ControlAudioSignalCommand = 0xA3, + SetAudioVolumeCommand = 0xA4, + ChangeChildLocationCommand = 0xA5, + ChangeSizeCommand = 0xA6, + ChangeBackgroundColourCommand = 0xA7, + ChangeNumericValueCommand = 0xA8, + ChangeEndPointCommand = 0xA9, + ChangeFontAttributesCommand = 0xAA, + ChangeLineAttributesCommand = 0xAB, + ChangeFillAttributesCommand = 0xAC, + ChangeActiveMaskCommand = 0xAD, + ChangeSoftKeyMaskCommand = 0xAE, + ChangeAttributeCommand = 0xAF, + ChangePriorityCommand = 0xB0, + ChangeListItemCommand = 0xB1, + DeleteObjectPoolCommand = 0xB2, + ChangeStringValueCommand = 0xB3, + ChangeChildPositionCommand = 0xB4, + ChangeObjectLabelCommand = 0xB5, + ChangePolygonPointCommand = 0xB6, + ChangePolygonScaleCommand = 0xB7, + GraphicsContextCommand = 0xB8, + GetAttributeValueMessage = 0xB9, + SelectColourMapCommand = 0xBA, + IdentifyVTMessage = 0xBB, + ExecuteExtendedMacroCommand = 0xBC, + LockUnlockMaskCommand = 0xBD, + ExecuteMacroCommand = 0xBE, + GetMemoryMessage = 0xC0, + GetSupportedWidecharsMessage = 0xC1, + GetNumberOfSoftKeysMessage = 0xC2, + GetTextFontDataMessage = 0xC3, + GetWindowMaskDataMessage = 0xC4, + GetSupportedObjectsMessage = 0xC5, + GetHardwareMessage = 0xC7, + StoreVersionCommand = 0xD0, + LoadVersionCommand = 0xD1, + DeleteVersionCommand = 0xD2, + ExtendedGetVersionsMessage = 0xD3, + ExtendedStoreVersionCommand = 0xD4, + ExtendedLoadVersionCommand = 0xD5, + ExtendedDeleteVersionCommand = 0xD6, + GetVersionsMessage = 0xDF, + GetVersionsResponse = 0xE0, + UnsupportedVTFunctionMessage = 0xFD, + VTStatusMessage = 0xFE, + WorkingSetMaintenanceMessage = 0xFF + }; + + /// @brief Enumerates the command types for graphics context objects + enum class GraphicsContextSubCommandID : std::uint8_t + { + SetGraphicsCursor = 0x00, ///< Sets the graphics cursor x/y attributes + MoveGraphicsCursor = 0x01, ///< Moves the cursor relative to current location + SetForegroundColour = 0x02, ///< Sets the foreground colour + SetBackgroundColour = 0x03, ///< Sets the background colour + SetLineAttributesObjectID = 0x04, ///< Sets the line attribute object ID + SetFillAttributesObjectID = 0x05, ///< Sets the fill attribute object ID + SetFontAttributesObjectID = 0x06, ///< Sets the font attribute object ID + EraseRectangle = 0x07, ///< Erases a rectangle + DrawPoint = 0x08, ///< Draws a point + DrawLine = 0x09, ///< Draws a line + DrawRectangle = 0x0A, ///< Draws a rectangle + DrawClosedEllipse = 0x0B, ///< Draws a closed ellipse + DrawPolygon = 0x0C, ///< Draws polygon + DrawText = 0x0D, ///< Draws text + PanViewport = 0x0E, ///< Pans viewport + ZoomViewport = 0x0F, ///< Zooms the viewport + PanAndZoomViewport = 0x10, ///< Pan and zooms the viewport + ChangeViewportSize = 0x11, ///< Changes the viewport size + DrawVTObject = 0x12, ///< Draws a VT object + CopyCanvasToPictureGraphic = 0x13, ///< Copies the canvas to picture graphic object + CopyViewportToPictureGraphic = 0x14 ///< Copies the viewport to picture graphic object + }; + }; +} // namespace isobus +#endif // ISOBUS_VIRTUAL_TERMINAL_BASE_HPP diff --git a/src/isobus_virtual_terminal_client.cpp b/src/isobus_virtual_terminal_client.cpp index 569782f..c887c6e 100644 --- a/src/isobus_virtual_terminal_client.cpp +++ b/src/isobus_virtual_terminal_client.cpp @@ -4,7 +4,7 @@ /// @brief Implements the client for a virtual terminal /// @author Adrian Del Grosso /// -/// @copyright 2022 Adrian Del Grosso +/// @copyright 2022 The Open-Agriculture Developers //================================================================================================ #include "isobus_virtual_terminal_client.hpp" @@ -89,7 +89,7 @@ namespace isobus if ((StateMachineState::Connected == state) && (send_delete_object_pool())) { - CANStackLogger::debug("[VT]: Requested object pool deletion from volatile VT memory."); + LOG_DEBUG("[VT]: Requested object pool deletion from volatile VT memory."); } partnerControlFunction->remove_parameter_group_number_callback(static_cast(CANLibParameterGroupNumber::VirtualTerminalToECU), process_rx_message, this); partnerControlFunction->remove_parameter_group_number_callback(static_cast(CANLibParameterGroupNumber::Acknowledge), process_rx_message, this); @@ -108,13 +108,13 @@ namespace isobus #endif initialized = false; set_state(StateMachineState::Disconnected); - CANStackLogger::info("[VT]: VT Client connection has been terminated."); + LOG_INFO("[VT]: VT Client connection has been terminated."); } } void VirtualTerminalClient::restart_communication() { - CANStackLogger::info("[VT]:VT Client connection restart requested. Client will now terminate and reinitialize."); + LOG_INFO("[VT]:VT Client connection restart requested. Client will now terminate and reinitialize."); #if !defined CAN_STACK_DISABLE_THREADS && !defined ARDUINO bool workerNeeded = (nullptr != workerThread); #else @@ -225,7 +225,8 @@ namespace isobus if (ourAuxiliaryInputs.count(auxiliaryInputID)) { ourAuxiliaryInputs.erase(auxiliaryInputID); - CANStackLogger::debug("[AUX-N] Removed auxiliary input with ID: " + isobus::to_string(static_cast(auxiliaryInputID))); + LOG_DEBUG("[AUX-N] Removed auxiliary input with ID: " + + isobus::to_string(static_cast(auxiliaryInputID))); } } @@ -233,7 +234,9 @@ namespace isobus { if (!ourAuxiliaryInputs.count(auxiliaryInputID)) { - CANStackLogger::warn("[AUX-N] Auxiliary input with ID '" + isobus::to_string(static_cast(auxiliaryInputID)) + "' has not been registered. Ignoring update"); + LOG_WARNING("[AUX-N] Auxiliary input with ID '" + + isobus::to_string(static_cast(auxiliaryInputID)) + + "' has not been registered. Ignoring update"); return; } @@ -332,7 +335,7 @@ namespace isobus if (volume_percent > MAX_VOLUME_PERCENT) { volume_percent = MAX_VOLUME_PERCENT; - CANStackLogger::warn("[VT]: Cannot try to set audio volume greater than 100 percent. Value will be capped at 100."); + LOG_WARNING("[VT]: Cannot try to set audio volume greater than 100 percent. Value will be capped at 100."); } const std::vector buffer = { static_cast(Function::SetAudioVolumeCommand), @@ -445,7 +448,8 @@ namespace isobus bool VirtualTerminalClient::send_change_string_value(std::uint16_t objectID, const std::string &value) { - return send_change_string_value(objectID, value.size(), value.c_str()); + assert(value.length() < std::numeric_limits::max()); // You can't send more than 65535 characters! + return send_change_string_value(objectID, static_cast(value.size()), value.c_str()); } bool VirtualTerminalClient::send_change_endpoint(std::uint16_t objectID, std::uint16_t width_px, std::uint16_t height_px, LineDirection direction) @@ -1258,7 +1262,7 @@ namespace isobus tempData.objectPoolDataPointer = nullptr; tempData.objectPoolVectorPointer = pool; tempData.dataCallback = nullptr; - tempData.objectPoolSize = pool->size(); + tempData.objectPoolSize = static_cast(pool->size()); tempData.autoScaleDataMaskOriginalDimension = 0; tempData.autoScaleSoftKeyDesignatorOriginalHeight = 0; tempData.useDataCallback = false; @@ -1362,7 +1366,7 @@ namespace isobus // so the state machine cannot progress. if (SystemTiming::time_expired_ms(lastVTStatusTimestamp_ms, VT_STATUS_TIMEOUT_MS)) { - CANStackLogger::error("[VT]: Ready to upload pool, but VT server has timed out. Disconnecting."); + LOG_ERROR("[VT]: Ready to upload pool, but VT server has timed out. Disconnecting."); set_state(StateMachineState::Disconnected); } @@ -1398,7 +1402,7 @@ namespace isobus if (SystemTiming::time_expired_ms(stateMachineTimestamp_ms, VT_STATUS_TIMEOUT_MS)) { set_state(StateMachineState::Failed); - CANStackLogger::error("[VT]: Get Memory Response Timeout"); + LOG_ERROR("[VT]: Get Memory Response Timeout"); } } break; @@ -1417,7 +1421,7 @@ namespace isobus if (SystemTiming::time_expired_ms(stateMachineTimestamp_ms, VT_STATUS_TIMEOUT_MS)) { set_state(StateMachineState::Failed); - CANStackLogger::error("[VT]: Get Number Softkeys Response Timeout"); + LOG_ERROR("[VT]: Get Number Softkeys Response Timeout"); } } break; @@ -1436,7 +1440,7 @@ namespace isobus if (SystemTiming::time_expired_ms(stateMachineTimestamp_ms, VT_STATUS_TIMEOUT_MS)) { set_state(StateMachineState::Failed); - CANStackLogger::error("[VT]: Get Text Font Data Response Timeout"); + LOG_ERROR("[VT]: Get Text Font Data Response Timeout"); } } break; @@ -1455,7 +1459,7 @@ namespace isobus if (SystemTiming::time_expired_ms(stateMachineTimestamp_ms, VT_STATUS_TIMEOUT_MS)) { set_state(StateMachineState::Failed); - CANStackLogger::error("[VT]: Get Hardware Response Timeout"); + LOG_ERROR("[VT]: Get Hardware Response Timeout"); } } break; @@ -1465,7 +1469,7 @@ namespace isobus if (SystemTiming::time_expired_ms(stateMachineTimestamp_ms, VT_STATUS_TIMEOUT_MS)) { set_state(StateMachineState::Failed); - CANStackLogger::error("[VT]: Get Versions Timeout"); + LOG_ERROR("[VT]: Get Versions Timeout"); } else if ((!objectPools.empty()) && (!objectPools[0].versionLabel.empty()) && @@ -1481,7 +1485,7 @@ namespace isobus if (SystemTiming::time_expired_ms(stateMachineTimestamp_ms, VT_STATUS_TIMEOUT_MS)) { set_state(StateMachineState::Failed); - CANStackLogger::error("[VT]: Get Versions Response Timeout"); + LOG_ERROR("[VT]: Get Versions Response Timeout"); } } break; @@ -1491,7 +1495,7 @@ namespace isobus if (SystemTiming::time_expired_ms(stateMachineTimestamp_ms, VT_STATUS_TIMEOUT_MS)) { set_state(StateMachineState::Failed); - CANStackLogger::error("[VT]: Send Load Version Timeout"); + LOG_ERROR("[VT]: Send Load Version Timeout"); } else { @@ -1525,7 +1529,7 @@ namespace isobus if (SystemTiming::time_expired_ms(stateMachineTimestamp_ms, VT_STATUS_TIMEOUT_MS)) { set_state(StateMachineState::Failed); - CANStackLogger::error("[VT]: Load Version Response Timeout"); + LOG_ERROR("[VT]: Load Version Response Timeout"); } } break; @@ -1535,7 +1539,7 @@ namespace isobus if (SystemTiming::time_expired_ms(stateMachineTimestamp_ms, VT_STATUS_TIMEOUT_MS)) { set_state(StateMachineState::Failed); - CANStackLogger::error("[VT]: Send Store Version Timeout"); + LOG_ERROR("[VT]: Send Store Version Timeout"); } else { @@ -1569,7 +1573,7 @@ namespace isobus if (SystemTiming::time_expired_ms(stateMachineTimestamp_ms, VT_STATUS_TIMEOUT_MS)) { set_state(StateMachineState::Failed); - CANStackLogger::error("[VT]: Store Version Response Timeout"); + LOG_ERROR("[VT]: Store Version Response Timeout"); } } break; @@ -1630,14 +1634,14 @@ namespace isobus if (false == objectPools[i].uploaded) { objectPools[i].uploaded = true; - CANStackLogger::debug("[VT]: Object pool %u uploaded.", i + 1); + LOG_DEBUG("[VT]: Object pool %u uploaded.", i + 1); currentObjectPoolState = CurrentObjectPoolUploadState::Uninitialized; } } else if (CurrentObjectPoolUploadState::Failed == currentObjectPoolState) { currentObjectPoolState = CurrentObjectPoolUploadState::Uninitialized; - CANStackLogger::error("[VT]: An object pool failed to upload. Resetting connection to VT."); + LOG_ERROR("[VT]: An object pool failed to upload. Resetting connection to VT."); set_state(StateMachineState::Disconnected); } else @@ -1649,7 +1653,7 @@ namespace isobus } else { - CANStackLogger::warn("[VT]: An object pool was supplied with an invalid size or pointer. Ignoring it."); + LOG_WARNING("[VT]: An object pool was supplied with an invalid size or pointer. Ignoring it."); objectPools[i].uploaded = true; } } @@ -1675,7 +1679,7 @@ namespace isobus if (SystemTiming::time_expired_ms(stateMachineTimestamp_ms, VT_STATUS_TIMEOUT_MS)) { set_state(StateMachineState::Failed); - CANStackLogger::error("[VT]: Get End of Object Pool Response Timeout"); + LOG_ERROR("[VT]: Get End of Object Pool Response Timeout"); } } break; @@ -1686,7 +1690,7 @@ namespace isobus if (SystemTiming::time_expired_ms(lastVTStatusTimestamp_ms, VT_STATUS_TIMEOUT_MS)) { set_state(StateMachineState::Disconnected); - CANStackLogger::error("[VT]: Status Timeout"); + LOG_ERROR("[VT]: Status Timeout"); } update_auxiliary_input_status(); } @@ -1701,7 +1705,7 @@ namespace isobus // Retry connecting after a while if (SystemTiming::time_expired_ms(stateMachineTimestamp_ms, VT_STATE_MACHINE_RETRY_TIMEOUT_MS)) { - CANStackLogger::info("[VT]: Resetting Failed VT Connection"); + LOG_INFO("[VT]: Resetting Failed VT Connection"); set_state(StateMachineState::Disconnected); } } @@ -2207,7 +2211,7 @@ namespace isobus std::uint32_t targetParameterGroupNumber = message.get_uint24_at(5); if (static_cast(CANLibParameterGroupNumber::ECUtoVirtualTerminal) == targetParameterGroupNumber) { - CANStackLogger::error("[VT]: The VT Server is NACK-ing our VT messages. Disconnecting."); + LOG_ERROR("[VT]: The VT Server is NACK-ing our VT messages. Disconnecting."); parentVT->set_state(StateMachineState::Disconnected); } } @@ -2604,33 +2608,33 @@ namespace isobus { if (message.get_bool_at(1, 0)) { - CANStackLogger::error("[AUX-N]: Preferred Assignment Error - Auxiliary Input Unit(s) (NAME or Model Identification Code) not valid"); + LOG_ERROR("[AUX-N]: Preferred Assignment Error - Auxiliary Input Unit(s) (NAME or Model Identification Code) not valid"); } if (message.get_bool_at(1, 1)) { - CANStackLogger::error("[AUX-N]: Preferred Assignment Error - Function Object ID(S) not valid"); + LOG_ERROR("[AUX-N]: Preferred Assignment Error - Function Object ID(S) not valid"); } if (message.get_bool_at(1, 2)) { - CANStackLogger::error("[AUX-N]: Preferred Assignment Error - Input Object ID(s) not valid"); + LOG_ERROR("[AUX-N]: Preferred Assignment Error - Input Object ID(s) not valid"); } if (message.get_bool_at(1, 3)) { - CANStackLogger::error("[AUX-N]: Preferred Assignment Error - Duplicate Object ID of Auxiliary Function"); + LOG_ERROR("[AUX-N]: Preferred Assignment Error - Duplicate Object ID of Auxiliary Function"); } if (message.get_bool_at(1, 4)) { - CANStackLogger::error("[AUX-N]: Preferred Assignment Error - Other"); + LOG_ERROR("[AUX-N]: Preferred Assignment Error - Other"); } if (0 != message.get_uint8_at(1)) { std::uint16_t faultyObjectID = message.get_uint16_at(2); - CANStackLogger::error("[AUX-N]: Auxiliary Function Object ID of faulty assignment: " + isobus::to_string(faultyObjectID)); + LOG_ERROR("[AUX-N]: Auxiliary Function Object ID of faulty assignment: " + isobus::to_string(faultyObjectID)); } else { - CANStackLogger::debug("[AUX-N]: Preferred Assignment OK"); + LOG_DEBUG("[AUX-N]: Preferred Assignment OK"); //! @todo load the preferred assignment into parentVT->assignedAuxiliaryInputDevices } } @@ -2656,7 +2660,7 @@ namespace isobus { aux.functions.clear(); } - CANStackLogger::info("[AUX-N] Unassigned all functions"); + LOG_INFO("[AUX-N] Unassigned all functions"); } else if (NULL_OBJECT_ID == inputObjectID) { @@ -2671,7 +2675,7 @@ namespace isobus { //! @todo save preferred assignment to persistent configuration } - CANStackLogger::info("[AUX-N] Unassigned function " + isobus::to_string(static_cast(functionObjectID)) + " from input " + isobus::to_string(static_cast(inputObjectID))); + LOG_INFO("[AUX-N] Unassigned function " + isobus::to_string(static_cast(functionObjectID)) + " from input " + isobus::to_string(static_cast(inputObjectID))); } else { @@ -2699,32 +2703,32 @@ namespace isobus { //! @todo save preferred assignment to persistent configuration } - CANStackLogger::info("[AUX-N]: Assigned function " + isobus::to_string(static_cast(functionObjectID)) + " to input " + isobus::to_string(static_cast(inputObjectID))); + LOG_INFO("[AUX-N]: Assigned function " + isobus::to_string(static_cast(functionObjectID)) + " to input " + isobus::to_string(static_cast(inputObjectID))); } else { hasError = true; isAlreadyAssigned = true; - CANStackLogger::warn("[AUX-N]: Unable to store preferred assignment due to missing auxiliary input device with name: " + isobus::to_string(isoName)); + LOG_WARNING("[AUX-N]: Unable to store preferred assignment due to missing auxiliary input device with name: " + isobus::to_string(isoName)); } } else { hasError = true; - CANStackLogger::warn("[AUX-N]: Unable to store preferred assignment due to unsupported function type: " + isobus::to_string(functionType)); + LOG_WARNING("[AUX-N]: Unable to store preferred assignment due to unsupported function type: " + isobus::to_string(functionType)); } } else { hasError = true; - CANStackLogger::warn("[AUX-N]: Unable to store preferred assignment due to missing auxiliary input device with name: " + isobus::to_string(isoName)); + LOG_WARNING("[AUX-N]: Unable to store preferred assignment due to missing auxiliary input device with name: " + isobus::to_string(isoName)); } } parentVT->send_auxiliary_function_assignment_response(functionObjectID, hasError, isAlreadyAssigned); } else { - CANStackLogger::warn("[AUX-N]: Received AuxiliaryAssignmentTypeTwoCommand with wrong data length: " + isobus::to_string(message.get_data_length()) + " but expected 14."); + LOG_WARNING("[AUX-N]: Received AuxiliaryAssignmentTypeTwoCommand with wrong data length: " + isobus::to_string(message.get_data_length()) + " but expected 14."); } } break; @@ -2799,7 +2803,7 @@ namespace isobus else { parentVT->set_state(StateMachineState::Failed); - CANStackLogger::error("[VT]: Connection Failed Not Enough Memory"); + LOG_ERROR("[VT]: Connection Failed Not Enough Memory"); } } } @@ -2903,12 +2907,12 @@ namespace isobus { labelMatched = true; parentVT->set_state(StateMachineState::SendLoadVersion); - CANStackLogger::info("[VT]: VT Server has a matching label for " + isobus::to_string(labelDecoded) + ". It will be loaded and upload will be skipped."); + LOG_INFO("[VT]: VT Server has a matching label for " + isobus::to_string(labelDecoded) + ". It will be loaded and upload will be skipped."); break; } else { - CANStackLogger::info("[VT]: VT Server has a label for " + isobus::to_string(labelDecoded) + ". This version will be deleted."); + LOG_INFO("[VT]: VT Server has a label for " + isobus::to_string(labelDecoded) + ". This version will be deleted."); const std::array deleteBuffer = { static_cast(labelDecoded[0]), static_cast(labelDecoded[1]), @@ -2920,30 +2924,30 @@ namespace isobus }; if (!parentVT->send_delete_version(deleteBuffer)) { - CANStackLogger::warn("[VT]: Failed to send the delete version message for label " + isobus::to_string(labelDecoded)); + LOG_WARNING("[VT]: Failed to send the delete version message for label " + isobus::to_string(labelDecoded)); } } } if (!labelMatched) { - CANStackLogger::info("[VT]: No version label from the VT matched. Client will upload the pool and store it instead."); + LOG_INFO("[VT]: No version label from the VT matched. Client will upload the pool and store it instead."); parentVT->set_state(StateMachineState::UploadObjectPool); } } else { - CANStackLogger::warn("[VT]: Get Versions Response length is not long enough. Message ignored."); + LOG_WARNING("[VT]: Get Versions Response length is not long enough. Message ignored."); } } else { - CANStackLogger::info("[VT]: No version label from the VT matched. Client will upload the pool and store it instead."); + LOG_INFO("[VT]: No version label from the VT matched. Client will upload the pool and store it instead."); parentVT->set_state(StateMachineState::UploadObjectPool); } } else { - CANStackLogger::warn("[VT]: Get Versions Response ignored!"); + LOG_WARNING("[VT]: Get Versions Response ignored!"); } } break; @@ -2954,7 +2958,7 @@ namespace isobus { if (0 == message.get_uint8_at(5)) { - CANStackLogger::info("[VT]: Loaded object pool version from VT non-volatile memory with no errors."); + LOG_INFO("[VT]: Loaded object pool version from VT non-volatile memory with no errors."); parentVT->set_state(StateMachineState::Connected); //! @todo maybe a better way available than relying on aux function callbacks registered? @@ -2962,11 +2966,11 @@ namespace isobus { if (parentVT->send_auxiliary_functions_preferred_assignment()) { - CANStackLogger::debug("[AUX-N]: Sent preferred assignments after LoadVersionCommand."); + LOG_DEBUG("[AUX-N]: Sent preferred assignments after LoadVersionCommand."); } else { - CANStackLogger::warn("[AUX-N]: Failed to send preferred assignments after LoadVersionCommand."); + LOG_WARNING("[AUX-N]: Failed to send preferred assignments after LoadVersionCommand."); } } } @@ -2975,25 +2979,25 @@ namespace isobus // At least one error is set if (message.get_bool_at(5, 0)) { - CANStackLogger::warn("[VT]: Load Versions Response error: File system error or corruption."); + LOG_WARNING("[VT]: Load Versions Response error: File system error or corruption."); } if (message.get_bool_at(5, 1)) { - CANStackLogger::warn("[VT]: Load Versions Response error: Insufficient memory."); + LOG_WARNING("[VT]: Load Versions Response error: Insufficient memory."); } if (message.get_bool_at(5, 2)) { - CANStackLogger::warn("[VT]: Load Versions Response error: Any other error."); + LOG_WARNING("[VT]: Load Versions Response error: Any other error."); } // Not sure what happened here... should be mostly impossible. Try to upload instead. - CANStackLogger::warn("[VT]: Switching to pool upload instead."); + LOG_WARNING("[VT]: Switching to pool upload instead."); parentVT->set_state(StateMachineState::UploadObjectPool); } } else { - CANStackLogger::warn("[VT]: Load Versions Response ignored!"); + LOG_WARNING("[VT]: Load Versions Response ignored!"); } } break; @@ -3006,28 +3010,28 @@ namespace isobus { // Stored with no error parentVT->set_state(StateMachineState::Connected); - CANStackLogger::info("[VT]: Stored object pool with no error."); + LOG_INFO("[VT]: Stored object pool with no error."); } else { // At least one error is set if (message.get_bool_at(5, 0)) { - CANStackLogger::warn("[VT]: Store Versions Response error: Version label is not correct."); + LOG_WARNING("[VT]: Store Versions Response error: Version label is not correct."); } if (message.get_bool_at(5, 1)) { - CANStackLogger::warn("[VT]: Store Versions Response error: Insufficient memory."); + LOG_WARNING("[VT]: Store Versions Response error: Insufficient memory."); } if (message.get_bool_at(5, 2)) { - CANStackLogger::warn("[VT]: Store Versions Response error: Any other error."); + LOG_WARNING("[VT]: Store Versions Response error: Any other error."); } } } else { - CANStackLogger::warn("[VT]: Store Versions Response ignored!"); + LOG_WARNING("[VT]: Store Versions Response ignored!"); } } break; @@ -3036,17 +3040,17 @@ namespace isobus { if (0 == message.get_uint8_at(5)) { - CANStackLogger::info("[VT]: Delete Version Response OK!"); + LOG_INFO("[VT]: Delete Version Response OK!"); } else { if (message.get_bool_at(5, 1)) { - CANStackLogger::warn("[VT]: Delete Version Response error: Version label is not correct, or unknown."); + LOG_WARNING("[VT]: Delete Version Response error: Version label is not correct, or unknown."); } if (message.get_bool_at(5, 3)) { - CANStackLogger::warn("[VT]: Delete Version Response error: Any other error."); + LOG_WARNING("[VT]: Delete Version Response error: Any other error."); } } } @@ -3086,25 +3090,31 @@ namespace isobus { if (parentVT->send_auxiliary_functions_preferred_assignment()) { - CANStackLogger::debug("[AUX-N]: Sent preferred assignments after EndOfObjectPoolMessage."); + LOG_DEBUG("[AUX-N]: Sent preferred assignments after EndOfObjectPoolMessage."); } else { - CANStackLogger::warn("[AUX-N]: Failed to send preferred assignments after EndOfObjectPoolMessage."); + LOG_WARNING("[AUX-N]: Failed to send preferred assignments after EndOfObjectPoolMessage."); } } } else { parentVT->set_state(StateMachineState::Failed); - CANStackLogger::error("[VT]: Error in end of object pool message." + std::string("Faulty Object ") + isobus::to_string(static_cast(objectIDOfFaultyObject)) + std::string(" Faulty Object Parent ") + isobus::to_string(static_cast(parentObjectIDOfFaultyObject)) + std::string(" Pool error bitmask value ") + isobus::to_string(static_cast(objectPoolErrorBitmask))); + LOG_ERROR("[VT]: Error in end of object pool message." + + std::string("Faulty Object ") + + isobus::to_string(static_cast(objectIDOfFaultyObject)) + + std::string(" Faulty Object Parent ") + + isobus::to_string(static_cast(parentObjectIDOfFaultyObject)) + + std::string(" Pool error bitmask value ") + + isobus::to_string(static_cast(objectPoolErrorBitmask))); if (vtRanOutOfMemory) { - CANStackLogger::error("[VT]: Ran out of memory"); + LOG_ERROR("[VT]: Ran out of memory"); } if (otherErrors) { - CANStackLogger::error("[VT]: Reported other errors in EOM response"); + LOG_ERROR("[VT]: Reported other errors in EOM response"); } } } @@ -3161,13 +3171,13 @@ namespace isobus { parentVT->unsupportedFunctions.push_back(unsupportedFunction); } - CANStackLogger::warn("[VT]: Server indicated VT Function '%llu' is unsupported, caching it", unsupportedFunction); + LOG_WARNING("[VT]: Server indicated VT Function '%hu' is unsupported, caching it", unsupportedFunction); } break; default: { std::uint8_t unsupportedFunction = message.get_uint8_at(0); - CANStackLogger::warn("[VT]: Server sent function '%llu' which we do not support", unsupportedFunction); + LOG_WARNING("[VT]: Server sent function '%hu' which we do not support", unsupportedFunction); std::array buffer{ static_cast(Function::UnsupportedVTFunctionMessage), unsupportedFunction, @@ -3203,7 +3213,10 @@ namespace isobus { AssignedAuxiliaryInputDevice inputDevice{ message.get_source_control_function()->get_NAME().get_full_name(), modelIdentificationCode, {} }; parentVT->assignedAuxiliaryInputDevices.push_back(inputDevice); - CANStackLogger::info("[AUX-N]: New auxiliary input device with name: " + isobus::to_string(inputDevice.name) + " and model identification code: " + isobus::to_string(modelIdentificationCode)); + LOG_INFO("[AUX-N]: New auxiliary input device with name: " + + isobus::to_string(inputDevice.name) + + " and model identification code: " + + isobus::to_string(modelIdentificationCode)); } } } @@ -3214,14 +3227,14 @@ namespace isobus default: { - CANStackLogger::warn("[VT]: Client unknown message: " + isobus::to_string(static_cast(message.get_identifier().get_parameter_group_number()))); + LOG_WARNING("[VT]: Client unknown message: " + isobus::to_string(static_cast(message.get_identifier().get_parameter_group_number()))); } break; } } else { - CANStackLogger::warn("[VT]: VT-ECU Client message invalid"); + LOG_WARNING("[VT]: VT-ECU Client message invalid"); } } @@ -3408,22 +3421,22 @@ namespace isobus { if (get_is_object_scalable(static_cast(*(poolIterator + 2)))) { - CANStackLogger::debug("[VT]: Resized an object: " + - isobus::to_string(static_cast((*poolIterator)) | (static_cast((*poolIterator + 1))) << 8) + - " with type " + - isobus::to_string(static_cast((*(poolIterator + 2)))) + - " with size " + - isobus::to_string(static_cast(objectSize))); + LOG_DEBUG("[VT]: Resized an object: " + + isobus::to_string(static_cast((*poolIterator)) | (static_cast((*poolIterator + 1))) << 8) + + " with type " + + isobus::to_string(static_cast((*(poolIterator + 2)))) + + " with size " + + isobus::to_string(static_cast(objectSize))); } } else { - CANStackLogger::error("[VT]: Failed to resize an object: " + - isobus::to_string(static_cast((*poolIterator)) | (static_cast((*poolIterator + 1))) << 8) + - " with type " + - isobus::to_string(static_cast((*poolIterator + 2))) + - " with size " + - isobus::to_string(static_cast(objectSize))); + LOG_ERROR("[VT]: Failed to resize an object: " + + isobus::to_string(static_cast((*poolIterator)) | (static_cast((*poolIterator + 1))) << 8) + + " with type " + + isobus::to_string(static_cast((*poolIterator + 2))) + + " with size " + + isobus::to_string(static_cast(objectSize))); } poolIterator += objectSize; } @@ -3700,8 +3713,8 @@ namespace isobus if (retVal == originalFont) { // Unknown font? Newer version than we support of the ISO standard? Or scaling factor out of range? - CANStackLogger::error("[VT]: Unable to scale font type " + isobus::to_string(static_cast(originalFont)) + - " with scale factor " + isobus::to_string(scaleFactor) + ". Returning original font."); + LOG_ERROR("[VT]: Unable to scale font type " + isobus::to_string(static_cast(originalFont)) + + " with scale factor " + isobus::to_string(scaleFactor) + ". Returning original font."); } } return retVal; @@ -3858,9 +3871,23 @@ namespace isobus } break; + case VirtualTerminalObjectType::AuxiliaryFunctionType1: + case VirtualTerminalObjectType::AuxiliaryFunctionType2: + case VirtualTerminalObjectType::AuxiliaryInputType2: + { + retVal = 6; + } + break; + + case VirtualTerminalObjectType::AuxiliaryInputType1: + { + retVal = 7; + } + break; + default: { - CANStackLogger::error("[VT]: Cannot autoscale object pool due to unknown object minimum length - type " + isobus::to_string(static_cast(type))); + LOG_ERROR("[VT]: Cannot autoscale object pool due to unknown object minimum length - type " + isobus::to_string(static_cast(type))); } break; } @@ -4151,7 +4178,7 @@ namespace isobus default: { - CANStackLogger::error("[VT]: Cannot autoscale object pool due to unknown object total length - type " + isobus::to_string(static_cast(buffer[2]))); + LOG_ERROR("[VT]: Cannot autoscale object pool due to unknown object total length - type " + isobus::to_string(static_cast(buffer[2]))); } break; } @@ -4420,8 +4447,8 @@ namespace isobus default: { - CANStackLogger::debug("[VT]: Skipping resize of non-resizable object type " + - isobus::to_string(static_cast(type))); + LOG_DEBUG("[VT]: Skipping resize of non-resizable object type " + + isobus::to_string(static_cast(type))); retVal = false; } break; @@ -4429,8 +4456,8 @@ namespace isobus } else { - CANStackLogger::debug("[VT]: Skipping resize of non-resizable object type " + - isobus::to_string(static_cast(type))); + LOG_DEBUG("[VT]: Skipping resize of non-resizable object type " + + isobus::to_string(static_cast(type))); retVal = true; } return retVal; @@ -4452,7 +4479,7 @@ namespace isobus { if (SystemTiming::time_expired_ms(lastCommandTimestamp_ms, 1500)) { - CANStackLogger::warn("[VT]: Server response to a command timed out"); + LOG_WARNING("[VT]: Server response to a command timed out"); commandAwaitingResponse = false; } else @@ -4464,7 +4491,7 @@ namespace isobus if (!get_is_connected()) { - CANStackLogger::warn("[VT]: Cannot send command, not connected"); + LOG_ERROR("[VT]: Cannot send command, not connected"); return false; } @@ -4491,14 +4518,11 @@ namespace isobus return true; } + LOCK_GUARD(Mutex, commandQueueMutex); if (replace && replace_command(data)) { return true; } - -#if !defined CAN_STACK_DISABLE_THREADS && !defined ARDUINO - std::lock_guard lock(commandQueueMutex); -#endif commandQueue.emplace_back(data); return true; } @@ -4522,6 +4546,10 @@ namespace isobus it = commandQueue.erase(it); } } + else + { + it++; + } } return alreadyReplaced; } @@ -4532,9 +4560,7 @@ namespace isobus { return; } -#if !defined CAN_STACK_DISABLE_THREADS && !defined ARDUINO - std::lock_guard lock(commandQueueMutex); -#endif + LOCK_GUARD(Mutex, commandQueueMutex); for (auto it = commandQueue.begin(); it != commandQueue.end();) { if (send_command(*it)) diff --git a/src/isobus_virtual_terminal_client.hpp b/src/isobus_virtual_terminal_client.hpp index b32c553..1756ef5 100644 --- a/src/isobus_virtual_terminal_client.hpp +++ b/src/isobus_virtual_terminal_client.hpp @@ -4,7 +4,7 @@ /// @brief A class to manage a client connection to a ISOBUS virtual terminal display /// @author Adrian Del Grosso /// -/// @copyright 2022 Adrian Del Grosso +/// @copyright 2022 The Open-Agriculture Developers //================================================================================================ #ifndef ISOBUS_VIRTUAL_TERMINAL_CLIENT_HPP #define ISOBUS_VIRTUAL_TERMINAL_CLIENT_HPP @@ -15,6 +15,7 @@ #include "isobus_virtual_terminal_objects.hpp" #include "event_dispatcher.hpp" #include "processing_flags.hpp" +#include "thread_synchronization.hpp" #include #include @@ -23,7 +24,6 @@ #include #if !defined CAN_STACK_DISABLE_THREADS && !defined ARDUINO -#include #include #endif @@ -1648,9 +1648,7 @@ namespace isobus std::vector> commandQueue; ///< A queue of commands to send to the VT server bool commandAwaitingResponse = false; ///< Determines if we are currently waiting for a response to a command std::uint32_t lastCommandTimestamp_ms = 0; ///< The timestamp of the last command sent -#if !defined CAN_STACK_DISABLE_THREADS && !defined ARDUINO - std::mutex commandQueueMutex; ///< A mutex to protect the command queue -#endif + Mutex commandQueueMutex; ///< A mutex to protect the command queue // Activation event callbacks EventDispatcher softKeyEventDispatcher; ///< A list of all soft key event callbacks diff --git a/src/isobus_virtual_terminal_client_state_tracker.cpp b/src/isobus_virtual_terminal_client_state_tracker.cpp index 183aff0..5251b5f 100644 --- a/src/isobus_virtual_terminal_client_state_tracker.cpp +++ b/src/isobus_virtual_terminal_client_state_tracker.cpp @@ -37,7 +37,7 @@ namespace isobus void VirtualTerminalClientStateTracker::terminate() { CANNetworkManager::CANNetwork.remove_any_control_function_parameter_group_number_callback(static_cast(CANLibParameterGroupNumber::VirtualTerminalToECU), process_rx_or_tx_message, this); - CANNetworkManager::CANNetwork.add_any_control_function_parameter_group_number_callback(static_cast(CANLibParameterGroupNumber::ECUtoVirtualTerminal), process_rx_or_tx_message, this); + CANNetworkManager::CANNetwork.remove_any_control_function_parameter_group_number_callback(static_cast(CANLibParameterGroupNumber::ECUtoVirtualTerminal), process_rx_or_tx_message, this); CANNetworkManager::CANNetwork.remove_global_parameter_group_number_callback(static_cast(CANLibParameterGroupNumber::VirtualTerminalToECU), process_rx_or_tx_message, this); } @@ -45,7 +45,7 @@ namespace isobus { if (numericValueStates.find(objectId) != numericValueStates.end()) { - CANStackLogger::warn("[VTStateHelper] add_tracked_numeric_value: objectId '%lu' already tracked", objectId); + LOG_WARNING("[VTStateHelper] add_tracked_numeric_value: objectId '%lu' already tracked", objectId); return; } @@ -56,7 +56,7 @@ namespace isobus { if (numericValueStates.find(objectId) == numericValueStates.end()) { - CANStackLogger::warn("[VTStateHelper] remove_tracked_numeric_value: objectId '%lu' was not tracked", objectId); + LOG_WARNING("[VTStateHelper] remove_tracked_numeric_value: objectId '%lu' was not tracked", objectId); return; } @@ -67,7 +67,7 @@ namespace isobus { if (numericValueStates.find(objectId) == numericValueStates.end()) { - CANStackLogger::warn("[VTStateHelper] get_numeric_value: objectId '%lu' not tracked", objectId); + LOG_WARNING("[VTStateHelper] get_numeric_value: objectId '%lu' not tracked", objectId); return 0; } @@ -98,7 +98,7 @@ namespace isobus { if (softKeyMasks.find(dataOrAlarmMaskId) != softKeyMasks.end()) { - CANStackLogger::warn("[VTStateHelper] add_tracked_soft_key_mask: data/alarm mask '%lu' already tracked", dataOrAlarmMaskId); + LOG_WARNING("[VTStateHelper] add_tracked_soft_key_mask: data/alarm mask '%lu' already tracked", dataOrAlarmMaskId); return; } @@ -109,7 +109,7 @@ namespace isobus { if (softKeyMasks.find(dataOrAlarmMaskId) == softKeyMasks.end()) { - CANStackLogger::warn("[VTStateHelper] remove_tracked_soft_key_mask: data/alarm mask '%lu' was not tracked", dataOrAlarmMaskId); + LOG_WARNING("[VTStateHelper] remove_tracked_soft_key_mask: data/alarm mask '%lu' was not tracked", dataOrAlarmMaskId); return; } @@ -120,7 +120,7 @@ namespace isobus { if (softKeyMasks.find(activeDataOrAlarmMask) == softKeyMasks.end()) { - CANStackLogger::warn("[VTStateHelper] get_active_soft_key_mask: the currently active data/alarm mask '%lu' is not tracked", activeDataOrAlarmMask); + LOG_WARNING("[VTStateHelper] get_active_soft_key_mask: the currently active data/alarm mask '%lu' is not tracked", activeDataOrAlarmMask); return NULL_OBJECT_ID; } @@ -131,7 +131,7 @@ namespace isobus { if (softKeyMasks.find(dataOrAlarmMaskId) == softKeyMasks.end()) { - CANStackLogger::warn("[VTStateHelper] get_soft_key_mask: data/alarm mask '%lu' is not tracked", activeDataOrAlarmMask); + LOG_WARNING("[VTStateHelper] get_soft_key_mask: data/alarm mask '%lu' is not tracked", activeDataOrAlarmMask); return NULL_OBJECT_ID; } @@ -153,7 +153,7 @@ namespace isobus auto &attributeMap = attributeStates.at(objectId); if (attributeMap.find(attribute) != attributeMap.end()) { - CANStackLogger::warn("[VTStateHelper] add_tracked_attribute: attribute '%lu' of objectId '%lu' already tracked", attribute, objectId); + LOG_WARNING("[VTStateHelper] add_tracked_attribute: attribute '%lu' of objectId '%lu' already tracked", attribute, objectId); return; } @@ -164,14 +164,14 @@ namespace isobus { if (attributeStates.find(objectId) == attributeStates.end()) { - CANStackLogger::warn("[VTStateHelper] remove_tracked_attribute: objectId '%lu' was not tracked", objectId); + LOG_WARNING("[VTStateHelper] remove_tracked_attribute: objectId '%lu' was not tracked", objectId); return; } auto &attributeMap = attributeStates.at(objectId); if (attributeMap.find(attribute) == attributeMap.end()) { - CANStackLogger::warn("[VTStateHelper] remove_tracked_attribute: attribute '%lu' of objectId '%lu' was not tracked", attribute, objectId); + LOG_WARNING("[VTStateHelper] remove_tracked_attribute: attribute '%lu' of objectId '%lu' was not tracked", attribute, objectId); return; } @@ -182,14 +182,14 @@ namespace isobus { if (attributeStates.find(objectId) == attributeStates.end()) { - CANStackLogger::warn("[VTStateHelper] get_attribute: objectId '%lu' not tracked", objectId); + LOG_WARNING("[VTStateHelper] get_attribute: objectId '%lu' not tracked", objectId); return 0; } const auto &attributeMap = attributeStates.at(objectId); if (attributeMap.find(attribute) == attributeMap.end()) { - CANStackLogger::warn("[VTStateHelper] get_attribute: attribute '%lu' of objectId '%lu' not tracked", attribute, objectId); + LOG_WARNING("[VTStateHelper] get_attribute: attribute '%lu' of objectId '%lu' not tracked", attribute, objectId); return 0; } diff --git a/src/isobus_virtual_terminal_client_state_tracker.hpp b/src/isobus_virtual_terminal_client_state_tracker.hpp index d2f5e5b..bf89736 100644 --- a/src/isobus_virtual_terminal_client_state_tracker.hpp +++ b/src/isobus_virtual_terminal_client_state_tracker.hpp @@ -80,7 +80,7 @@ namespace isobus /// @param[in] dataOrAlarmMaskId The data/alarm mask to remove the soft key mask from tracking for. void remove_tracked_soft_key_mask(std::uint16_t dataOrAlarmMaskId); - /// @brief Get the soft key mask currently active on thse server for this client. It may not be displayed if the working set is not active. + /// @brief Get the soft key mask currently active on the server for this client. It may not be displayed if the working set is not active. /// @return The soft key mask currently active on the server for this client. std::uint16_t get_active_soft_key_mask() const; diff --git a/src/isobus_virtual_terminal_client_update_helper.cpp b/src/isobus_virtual_terminal_client_update_helper.cpp index 642a4ed..7a17a63 100644 --- a/src/isobus_virtual_terminal_client_update_helper.cpp +++ b/src/isobus_virtual_terminal_client_update_helper.cpp @@ -19,7 +19,7 @@ namespace isobus { if (nullptr == client) { - CANStackLogger::error("[VTStateHelper] constructor: client is nullptr"); + LOG_ERROR("[VTStateHelper] constructor: client is nullptr"); return; } numericValueChangeEventHandle = client->get_vt_change_numeric_value_event_dispatcher().add_listener( @@ -38,12 +38,12 @@ namespace isobus { if (nullptr == client) { - CANStackLogger::error("[VTStateHelper] set_numeric_value: client is nullptr"); + LOG_ERROR("[VTStateHelper] set_numeric_value: client is nullptr"); return false; } if (numericValueStates.find(object_id) == numericValueStates.end()) { - CANStackLogger::warn("[VTStateHelper] set_numeric_value: objectId %lu not tracked", object_id); + LOG_WARNING("[VTStateHelper] set_numeric_value: objectId %lu not tracked", object_id); return false; } if (numericValueStates.at(object_id) == value) @@ -101,7 +101,7 @@ namespace isobus { if (nullptr == client) { - CANStackLogger::error("[VTStateHelper] set_active_data_or_alarm_mask: client is nullptr"); + LOG_ERROR("[VTStateHelper] set_active_data_or_alarm_mask: client is nullptr"); return false; } if (activeDataOrAlarmMask == dataOrAlarmMaskId) @@ -121,12 +121,12 @@ namespace isobus { if (nullptr == client) { - CANStackLogger::error("[VTStateHelper] set_active_soft_key_mask: client is nullptr"); + LOG_ERROR("[VTStateHelper] set_active_soft_key_mask: client is nullptr"); return false; } if (softKeyMasks.find(maskId) == softKeyMasks.end()) { - CANStackLogger::warn("[VTStateHelper] set_active_soft_key_mask: data/alarm mask '%lu' not tracked", maskId); + LOG_WARNING("[VTStateHelper] set_active_soft_key_mask: data/alarm mask '%lu' not tracked", maskId); return false; } if (softKeyMasks.at(maskId) == softKeyMaskId) @@ -146,17 +146,17 @@ namespace isobus { if (nullptr == client) { - CANStackLogger::error("[VTStateHelper] set_attribute: client is nullptr"); + LOG_ERROR("[VTStateHelper] set_attribute: client is nullptr"); return false; } if (attributeStates.find(objectId) == attributeStates.end()) { - CANStackLogger::warn("[VTStateHelper] set_attribute: objectId %lu not tracked", objectId); + LOG_ERROR("[VTStateHelper] set_attribute: objectId %lu not tracked", objectId); return false; } if (attributeStates.at(objectId).find(attribute) == attributeStates.at(objectId).end()) { - CANStackLogger::warn("[VTStateHelper] set_attribute: attribute %lu of objectId %lu not tracked", attribute, objectId); + LOG_WARNING("[VTStateHelper] set_attribute: attribute %lu of objectId %lu not tracked", attribute, objectId); return false; } if (attributeStates.at(objectId).at(attribute) == value) diff --git a/src/isobus_virtual_terminal_objects.cpp b/src/isobus_virtual_terminal_objects.cpp index 0e8a728..54180c6 100644 --- a/src/isobus_virtual_terminal_objects.cpp +++ b/src/isobus_virtual_terminal_objects.cpp @@ -4,7 +4,7 @@ /// @brief Implements VT server object pool objects. /// @author Adrian Del Grosso /// -/// @copyright 2023 Adrian Del Grosso +/// @copyright 2023 The Open-Agriculture Developers //================================================================================================ #include "isobus_virtual_terminal_objects.hpp" @@ -4933,6 +4933,19 @@ namespace isobus return retVal; } + bool OutputPolygon::change_point(std::uint8_t index, std::uint16_t x, std::uint16_t y) + { + bool retVal = false; + + if (index < pointList.size()) + { + pointList.at(index).xValue = x; + pointList.at(index).yValue = y; + retVal = true; + } + return retVal; + } + OutputPolygon::PolygonType OutputPolygon::get_type() const { return static_cast(polygonType); @@ -6338,7 +6351,10 @@ namespace isobus void PictureGraphic::add_raw_data(std::uint8_t dataByte) { - rawData.push_back(dataByte); + if (rawData.size() < (get_actual_width() * get_actual_height())) + { + rawData.push_back(dataByte); + } } std::uint32_t PictureGraphic::get_number_of_bytes_in_raw_data() const diff --git a/src/isobus_virtual_terminal_objects.hpp b/src/isobus_virtual_terminal_objects.hpp index a50c930..77f1715 100644 --- a/src/isobus_virtual_terminal_objects.hpp +++ b/src/isobus_virtual_terminal_objects.hpp @@ -4,7 +4,7 @@ /// @brief Defines the different VT object types that can comprise a VT object pool. /// @author Adrian Del Grosso /// -/// @copyright 2023 Adrian Del Grosso +/// @copyright 2023 The Open-Agriculture Developers //================================================================================================ #ifndef ISOBUS_VIRTUAL_TERMINAL_OBJECTS_HPP #define ISOBUS_VIRTUAL_TERMINAL_OBJECTS_HPP @@ -2506,6 +2506,13 @@ namespace isobus /// @returns A point in the polygon by index, or zeros if the index is out of range. PolygonPoint get_point(std::uint8_t index); + /// @brief Changes a polygon point by index + /// @param[in] index The point index to modify + /// @param[in] x The new X position of the point, relative to the top left corner of the polygon + /// @param[in] y The new Y position of the point, relative to the top left corner of the polygon + /// @returns True if the point was modified, false if the index was out of range + bool change_point(std::uint8_t index, std::uint16_t x, std::uint16_t y); + /// @brief Returns the polygon type of this object /// @returns The polygon type of this object PolygonType get_type() const; diff --git a/src/nmea2000_fast_packet_protocol.cpp b/src/nmea2000_fast_packet_protocol.cpp index 9349bba..a070673 100644 --- a/src/nmea2000_fast_packet_protocol.cpp +++ b/src/nmea2000_fast_packet_protocol.cpp @@ -3,9 +3,13 @@ /// /// @brief A protocol that handles the NMEA 2000 fast packet protocol. /// +/// @note This library and its authors are not affiliated with the National Marine +/// Electronics Association in any way. +/// /// @author Adrian Del Grosso +/// @author Daan Steenbergen /// -/// @copyright 2022 Adrian Del Grosso +/// @copyright 2024 The Open-Agriculture Developers //================================================================================================ #include "nmea2000_fast_packet_protocol.hpp" @@ -19,52 +23,93 @@ namespace isobus { - FastPacketProtocol::FastPacketProtocolSession::FastPacketProtocolSession(Direction sessionDirection) : - sessionMessage(CANMessage::create_invalid_message()), - sessionCompleteCallback(nullptr), - frameChunkCallback(nullptr), - parent(nullptr), - timestamp_ms(0), - lastPacketNumber(0), - packetCount(0), - processedPacketsThisSession(0), - sequenceNumber(0), - sessionDirection(sessionDirection) + FastPacketProtocol::FastPacketProtocolSession::FastPacketProtocolSession(TransportProtocolSessionBase::Direction direction, + std::unique_ptr data, + std::uint32_t parameterGroupNumber, + std::uint16_t totalMessageSize, + std::uint8_t sequenceNumber, + CANIdentifier::CANPriority priority, + std::shared_ptr source, + std::shared_ptr destination, + TransmitCompleteCallback sessionCompleteCallback, + void *parentPointer) : + TransportProtocolSessionBase(direction, std::move(data), parameterGroupNumber, totalMessageSize, source, destination, sessionCompleteCallback, parentPointer), + sequenceNumber(sequenceNumber), + priority(priority) { } - bool FastPacketProtocol::FastPacketProtocolSession::operator==(const FastPacketProtocolSession &obj) + std::uint8_t FastPacketProtocol::FastPacketProtocolSession::get_message_length() const { - return ((sessionMessage.get_source_control_function() == obj.sessionMessage.get_source_control_function()) && - (sessionMessage.get_destination_control_function() == obj.sessionMessage.get_destination_control_function()) && - (sessionMessage.get_identifier().get_parameter_group_number() == obj.sessionMessage.get_identifier().get_parameter_group_number())); + // We know that this session can only be used to transfer 223 bytes of data, so we can safely cast to a uint8_t + return static_cast(TransportProtocolSessionBase::get_message_length()); } - std::uint32_t FastPacketProtocol::FastPacketProtocolSession::get_message_data_length() const + bool FastPacketProtocol::FastPacketProtocolSession::is_broadcast() const { - if (nullptr != frameChunkCallback) + return (nullptr == get_destination()); + } + + std::uint32_t FastPacketProtocol::FastPacketProtocolSession::get_total_bytes_transferred() const + { + return numberOfBytesTransferred; + } + + std::uint8_t FastPacketProtocol::FastPacketProtocolSession::get_last_packet_number() const + { + // We know that this session can only be used to transfer 223 bytes of data, so we can safely cast to a uint8_t + std::uint8_t numberOfFrames = calculate_number_of_frames(static_cast(get_total_bytes_transferred())); + if (numberOfFrames > 0) + { + return numberOfFrames; + } + else { - return frameChunkCallbackMessageLength; + return 0; } - return sessionMessage.get_data_length(); } - FastPacketProtocol::FastPacketProtocolSession::~FastPacketProtocolSession() + std::uint8_t FastPacketProtocol::FastPacketProtocolSession::get_number_of_remaining_packets() const { + return get_total_number_of_packets() - get_last_packet_number(); } - void FastPacketProtocol::initialize(CANLibBadge) + std::uint8_t FastPacketProtocol::FastPacketProtocolSession::get_total_number_of_packets() const { - if (!initialized) + return calculate_number_of_frames(get_message_length()); + } + + void FastPacketProtocol::FastPacketProtocolSession::add_number_of_bytes_transferred(std::uint8_t bytes) + { + numberOfBytesTransferred += bytes; + update_timestamp(); + } + + std::uint8_t FastPacketProtocol::calculate_number_of_frames(std::uint8_t messageLength) + { + std::uint8_t numberOfFrames = 0; + // Account for the 6 bytes of data in the first frame + if (messageLength > 6) + { + messageLength -= 6; + numberOfFrames++; + } + numberOfFrames += (messageLength / PROTOCOL_BYTES_PER_FRAME); + if (0 != (messageLength % PROTOCOL_BYTES_PER_FRAME)) { - initialized = true; + numberOfFrames++; } + return numberOfFrames; + } + + FastPacketProtocol::FastPacketProtocol(const CANMessageFrameCallback &sendCANFrameCallback) : + sendCANFrameCallback(sendCANFrameCallback) + { } void FastPacketProtocol::register_multipacket_message_callback(std::uint32_t parameterGroupNumber, CANLibCallback callback, void *parent, std::shared_ptr internalControlFunction) { - parameterGroupNumberCallbacks.push_back(ParameterGroupNumberCallbackData(parameterGroupNumber, callback, parent, internalControlFunction)); - CANNetworkManager::CANNetwork.add_protocol_parameter_group_number_callback(parameterGroupNumber, process_message, this); + parameterGroupNumberCallbacks.emplace_back(parameterGroupNumber, callback, parent, internalControlFunction); } void FastPacketProtocol::remove_multipacket_message_callback(std::uint32_t parameterGroupNumber, CANLibCallback callback, void *parent, std::shared_ptr internalControlFunction) @@ -75,11 +120,15 @@ namespace isobus { parameterGroupNumberCallbacks.erase(callbackLocation); } - CANNetworkManager::CANNetwork.remove_protocol_parameter_group_number_callback(parameterGroupNumber, process_message, this); + } + + void FastPacketProtocol::allow_any_control_function(bool allow) + { + allowAnyControlFunction = allow; } bool FastPacketProtocol::send_multipacket_message(std::uint32_t parameterGroupNumber, - const std::uint8_t *data, + const std::uint8_t *messageData, std::uint8_t messageLength, std::shared_ptr source, std::shared_ptr destination, @@ -88,86 +137,89 @@ namespace isobus void *parentPointer, DataChunkCallback frameChunkCallback) { - bool retVal = false; - - if ((nullptr != source) && - (source->get_address_valid()) && - (parameterGroupNumber >= FP_MIN_PARAMETER_GROUP_NUMBER) && - (parameterGroupNumber <= FP_MAX_PARAMETER_GROUP_NUMBER) && - (messageLength <= MAX_PROTOCOL_MESSAGE_LENGTH) && - ((nullptr != data) || - (nullptr != frameChunkCallback))) + std::unique_ptr data; + if (nullptr != frameChunkCallback) { - FastPacketProtocolSession *tempSession = nullptr; - - if (!get_session(tempSession, parameterGroupNumber, source, destination)) - { - tempSession = new FastPacketProtocolSession(FastPacketProtocolSession::Direction::Transmit); - CANIdentifier identifier(CANIdentifier::Type::Extended, parameterGroupNumber, priority, (destination == nullptr ? 0xFF : destination->get_address()), source->get_address()); - tempSession->sessionMessage = CANMessage(CANMessage::Type::Transmit, - identifier, - data, - messageLength, - source, - destination, - source->get_can_port()); - - tempSession->parent = parentPointer; - tempSession->packetCount = ((messageLength - 6) / PROTOCOL_BYTES_PER_FRAME); - tempSession->timestamp_ms = SystemTiming::get_timestamp_ms(); - tempSession->processedPacketsThisSession = 0; - tempSession->sessionCompleteCallback = txCompleteCallback; - tempSession->sequenceNumber = get_new_sequence_number(tempSession); - - if ((messageLength > 6) && - (0 != ((messageLength - 6) % PROTOCOL_BYTES_PER_FRAME))) - { - tempSession->packetCount++; - } -#if !defined CAN_STACK_DISABLE_THREADS && !defined ARDUINO - std::unique_lock lock(sessionMutex); -#endif + data.reset(new CANMessageDataCallback(messageLength, frameChunkCallback, parentPointer)); + } + else if (nullptr != messageData) + { + // Make a copy of the data as it could go out of scope + data.reset(new CANMessageDataVector(messageData, messageLength)); + } - activeSessions.push_back(tempSession); - retVal = true; - } - else - { - // Already in a matching session, can't start another. - CANStackLogger::warn("[FP]: Can't send fast packet message, already in matching session."); - } + // Return false early if we can't send the message + if ((nullptr == data) || (data->size() <= CAN_DATA_LENGTH) || (data->size() > MAX_PROTOCOL_MESSAGE_LENGTH)) + { + LOG_ERROR("[FP]: Unable to send multipacket message, data is invalid or has invalid length."); + return false; } - else + else if ((nullptr == source) || (!source->get_address_valid()) || has_session(parameterGroupNumber, source, destination)) + { + LOG_ERROR("[FP]: Unable to send multipacket message, source is invalid or already in a session for the PGN."); + return false; + } + else if ((parameterGroupNumber < FP_MIN_PARAMETER_GROUP_NUMBER) || (parameterGroupNumber > FP_MAX_PARAMETER_GROUP_NUMBER)) { - CANStackLogger::error("[FP]: Can't send fast packet message, bad parameters or ICF is invalid"); + LOG_ERROR("[FP]: Unable to send multipacket message, PGN is unsupported by this protocol."); + return false; } - return retVal; + else if ((nullptr != destination) && (!destination->get_address_valid())) + { + LOG_ERROR("[FP]: Unable to send multipacket message, destination is invalid."); + return false; + } + + std::uint8_t sequenceNumber = get_new_sequence_number(source->get_NAME(), parameterGroupNumber); + auto session = std::make_shared(FastPacketProtocolSession::Direction::Transmit, + std::move(data), + parameterGroupNumber, + messageLength, + sequenceNumber, + priority, + source, + destination, + txCompleteCallback, + parentPointer); + + LOCK_GUARD(Mutex, sessionMutex); + activeSessions.push_back(session); + return true; } - void FastPacketProtocol::update(CANLibBadge) + void FastPacketProtocol::update() { -#if !defined CAN_STACK_DISABLE_THREADS && !defined ARDUINO - std::unique_lock lock(sessionMutex); -#endif - - for (auto i : activeSessions) + LOCK_GUARD(Mutex, sessionMutex); + // We use a fancy for loop here to allow us to remove sessions from the list while iterating + for (std::size_t i = activeSessions.size(); i > 0; i--) { - update_state_machine(i); + auto session = activeSessions.at(i - 1); + if (!session->get_source()->get_address_valid()) + { + LOG_WARNING("[FP]: Closing active session as the source control function is no longer valid"); + close_session(session, false); + } + else if (!session->is_broadcast() && !session->get_destination()->get_address_valid()) + { + LOG_WARNING("[FP]: Closing active session as the destination control function is no longer valid"); + close_session(session, false); + } + update_session(session); } } - void FastPacketProtocol::add_session_history(FastPacketProtocolSession *session) + void FastPacketProtocol::add_session_history(const std::shared_ptr &session) { if (nullptr != session) { bool formerSessionMatched = false; - for (std::size_t i = 0; i < sessionHistory.size(); i++) + for (auto &formerSessions : sessionHistory) { - if ((sessionHistory[i].isoName == session->sessionMessage.get_source_control_function()->get_NAME()) && - (sessionHistory[i].parameterGroupNumber == session->sessionMessage.get_identifier().get_parameter_group_number())) + if ((formerSessions.isoName == session->get_source()->get_NAME()) && + (formerSessions.parameterGroupNumber == session->get_parameter_group_number())) { - sessionHistory[i].sequenceNumber++; + formerSessions.sequenceNumber = session->sequenceNumber; formerSessionMatched = true; break; } @@ -176,367 +228,286 @@ namespace isobus if (!formerSessionMatched) { FastPacketHistory history{ - session->sessionMessage.get_source_control_function()->get_NAME(), - session->sessionMessage.get_identifier().get_parameter_group_number(), + session->get_source()->get_NAME(), + session->get_parameter_group_number(), session->sequenceNumber }; - history.sequenceNumber++; sessionHistory.push_back(history); } } } - void FastPacketProtocol::close_session(FastPacketProtocolSession *session, bool successful) + void FastPacketProtocol::close_session(std::shared_ptr session, bool successful) { if (nullptr != session) { - process_session_complete_callback(session, successful); - for (auto currentSession = activeSessions.begin(); currentSession != activeSessions.end(); currentSession++) + session->complete(successful); + add_session_history(session); + + auto sessionLocation = std::find(activeSessions.begin(), activeSessions.end(), session); + if (activeSessions.end() != sessionLocation) { - if (session == *currentSession) - { - activeSessions.erase(currentSession); - delete session; - break; - } + activeSessions.erase(sessionLocation); } } } - std::uint8_t FastPacketProtocol::get_new_sequence_number(FastPacketProtocolSession *session) + std::uint8_t FastPacketProtocol::get_new_sequence_number(NAME name, std::uint32_t parameterGroupNumber) const { - std::uint8_t retVal = 0; - - if (nullptr != session) + std::uint8_t sequenceNumber = 0; + for (const auto &formerSessions : sessionHistory) { - for (auto &formerSessions : sessionHistory) + if ((formerSessions.isoName == name) && (formerSessions.parameterGroupNumber == parameterGroupNumber)) { - if ((formerSessions.isoName == session->sessionMessage.get_source_control_function()->get_NAME()) && - (formerSessions.parameterGroupNumber == session->sessionMessage.get_identifier().get_parameter_group_number())) - { - retVal = formerSessions.sequenceNumber; - break; - } + sequenceNumber = formerSessions.sequenceNumber + 1; + break; } } - return retVal; + return sequenceNumber; } - bool FastPacketProtocol::get_session(FastPacketProtocolSession *&returnedSession, std::uint32_t parameterGroupNumber, std::shared_ptr source, std::shared_ptr destination) + void FastPacketProtocol::process_message(const CANMessage &message) { - returnedSession = nullptr; -#if !defined CAN_STACK_DISABLE_THREADS && !defined ARDUINO - std::unique_lock lock(sessionMutex); -#endif + if ((CAN_DATA_LENGTH != message.get_data_length()) || + (message.get_source_control_function() == nullptr) || + (message.get_identifier().get_parameter_group_number() < FP_MIN_PARAMETER_GROUP_NUMBER) || + (message.get_identifier().get_parameter_group_number() > FP_MAX_PARAMETER_GROUP_NUMBER)) + { + // Not a valid message for this protocol + return; + } + + if (parameterGroupNumberCallbacks.empty()) + { + // No listeners, no need to process the message + return; + } - for (auto session : activeSessions) + if ((!message.is_destination_our_device()) && (!allowAnyControlFunction) && (!message.is_broadcast())) { - if ((session->sessionMessage.get_identifier().get_parameter_group_number() == parameterGroupNumber) && - (session->sessionMessage.get_source_control_function() == source) && - (session->sessionMessage.get_destination_control_function() == destination)) + // Destined for someone else, no need to process the message + return; + } + + // Check if we have any callbacks for this PGN + bool pgnNeedsParsing = false; + for (const auto &callback : parameterGroupNumberCallbacks) + { + if ((callback.get_parameter_group_number() == message.get_identifier().get_parameter_group_number()) && + ((nullptr == callback.get_internal_control_function()) || + (callback.get_internal_control_function() == message.get_destination_control_function()))) { - returnedSession = session; + pgnNeedsParsing = true; break; } } - return (nullptr != returnedSession); - } - void FastPacketProtocol::process_message(const CANMessage &message, void *parent) - { - if (nullptr != parent) + if (!pgnNeedsParsing) { - static_cast(parent)->process_message(message); + // No callbacks for this PGN, no need to process the message + return; } - } - void FastPacketProtocol::process_message(const CANMessage &message) - { - if ((CAN_DATA_LENGTH == message.get_data_length()) && - (message.get_identifier().get_parameter_group_number() >= FP_MIN_PARAMETER_GROUP_NUMBER) && - (message.get_identifier().get_parameter_group_number() <= FP_MAX_PARAMETER_GROUP_NUMBER)) + std::shared_ptr session = get_session(message.get_identifier().get_parameter_group_number(), + message.get_source_control_function(), + message.get_destination_control_function()); + std::uint8_t actualFrameCount = (message.get_uint8_at(0) & FRAME_COUNTER_BIT_MASK); + + if (nullptr != session) { - // See if we care about parsing this message - if (parameterGroupNumberCallbacks.size() > 0) + // We have a matching active session + if (0 == actualFrameCount) { - bool pgnNeedsParsing = false; - - for (auto &callback : parameterGroupNumberCallbacks) + // This is the beginning of a new message, but we already have a session + LOG_ERROR("[FP]: Existing session matched new frame counter, aborting the matching session."); + close_session(session, false); + } + else + { + // Correct sequence number, copy the data + // Convert data type to a vector to allow for manipulation + auto &data = static_cast(session->get_data()); + for (std::uint8_t i = 0; i < PROTOCOL_BYTES_PER_FRAME; i++) { - if ((callback.get_parameter_group_number() == message.get_identifier().get_parameter_group_number()) && - ((nullptr == callback.get_internal_control_function()) || - (callback.get_internal_control_function()->get_address() == message.get_destination_control_function()->get_address()))) + if (session->numberOfBytesTransferred < session->get_message_length()) { - pgnNeedsParsing = true; + data.set_byte(session->numberOfBytesTransferred, message.get_uint8_at(1 + i)); + session->add_number_of_bytes_transferred(1); + } + else + { + // Reached the end of the message, no need to copy any more data break; } } - if (pgnNeedsParsing) + if (session->numberOfBytesTransferred >= session->get_message_length()) { - FastPacketProtocolSession *currentSession = nullptr; - std::vector messageData = message.get_data(); - std::uint8_t frameCount = (messageData[0] & FRAME_COUNTER_BIT_MASK); - - // Check for a valid session - if (get_session(currentSession, message.get_identifier().get_parameter_group_number(), message.get_source_control_function(), message.get_destination_control_function())) + // Complete + CANMessage completedMessage(CANMessage::Type::Receive, + message.get_identifier(), + std::move(data), + message.get_source_control_function(), + message.get_destination_control_function(), + message.get_can_port_index()); + + // Find the appropriate callback and let them know + for (const auto &callback : parameterGroupNumberCallbacks) { - // Matched a session - if (0 != frameCount) - { - // Continue processing the message - for (std::uint8_t i = 0; i < PROTOCOL_BYTES_PER_FRAME; i++) - { - if (static_cast(i + (currentSession->processedPacketsThisSession * PROTOCOL_BYTES_PER_FRAME) - 1) < currentSession->sessionMessage.get_data_length()) - { - currentSession->sessionMessage.set_data(messageData[1 + i], i + (currentSession->processedPacketsThisSession * PROTOCOL_BYTES_PER_FRAME) - 1); - } - else - { - break; - } - } - currentSession->processedPacketsThisSession++; - - if (static_cast((currentSession->processedPacketsThisSession * PROTOCOL_BYTES_PER_FRAME) - 1) >= currentSession->sessionMessage.get_data_length()) - { - // Complete - // Find the appropriate callback and let them know - for (auto &callback : parameterGroupNumberCallbacks) - { - if (callback.get_parameter_group_number() == currentSession->sessionMessage.get_identifier().get_parameter_group_number()) - { - callback.get_callback()(currentSession->sessionMessage, callback.get_parent()); - } - } - close_session(currentSession, true); // All done - } - } - else + if ((callback.get_parameter_group_number() == message.get_identifier().get_parameter_group_number()) && + ((nullptr == callback.get_internal_control_function()) || + (callback.get_internal_control_function() == message.get_destination_control_function()))) { - CANStackLogger::error("[FP]: Existing session matched new frame counter, aborting the matching session."); - close_session(currentSession, false); - } - } - else - { - // No matching session. See if we need to start a new session - if (0 == frameCount) - { - if (messageData[1] <= MAX_PROTOCOL_MESSAGE_LENGTH) - { - // This is the beginning of a new message - currentSession = new FastPacketProtocolSession(FastPacketProtocolSession::Direction::Receive); - currentSession->frameChunkCallback = nullptr; - if (messageData[1] >= PROTOCOL_BYTES_PER_FRAME - 1) - { - currentSession->packetCount = ((messageData[1] - 6) / PROTOCOL_BYTES_PER_FRAME); - } - else - { - currentSession->packetCount = 1; - } - currentSession->lastPacketNumber = ((messageData[0] >> SEQUENCE_NUMBER_BIT_OFFSET) & SEQUENCE_NUMBER_BIT_MASK); - currentSession->processedPacketsThisSession = 1; - - currentSession->sessionMessage = CANMessage(CANMessage::Type::Receive, - message.get_identifier(), - nullptr, - 0, - message.get_source_control_function(), - message.get_destination_control_function(), - message.get_can_port_index()); - currentSession->sessionMessage.set_data_size(messageData[1]); - currentSession->timestamp_ms = SystemTiming::get_timestamp_ms(); - - if (0 != (messageData[1] % PROTOCOL_BYTES_PER_FRAME)) - { - currentSession->packetCount++; - } - - // Save the 6 bytes of payload in this first message - for (std::uint8_t i = 0; i < (PROTOCOL_BYTES_PER_FRAME - 1); i++) - { - currentSession->sessionMessage.set_data(messageData[2 + i], i); - } -#if !defined CAN_STACK_DISABLE_THREADS && !defined ARDUINO - std::unique_lock lock(sessionMutex); -#endif - - activeSessions.push_back(currentSession); - } - else - { - CANStackLogger::warn("[FP]: Ignoring possible new FP session with advertised length > 233."); - } - } - else - { - // This is the middle of some message that we have no context for. - // Ignore the message for now until we receive it with a fresh packet counter. - CANStackLogger::warn("[FP]: Ignoring FP message with PGN %u, no context available. The message may be processed when packet count returns to zero.", message.get_identifier().get_parameter_group_number()); + callback.get_callback()(completedMessage, callback.get_parent()); } } + close_session(session, true); } } } - } - - void FastPacketProtocol::process_session_complete_callback(FastPacketProtocolSession *session, bool success) - { - if ((nullptr != session) && - (nullptr != session->sessionCompleteCallback) && - (nullptr != session->sessionMessage.get_source_control_function()) && - (ControlFunction::Type::Internal == session->sessionMessage.get_source_control_function()->get_type())) + else { - session->sessionCompleteCallback(session->sessionMessage.get_identifier().get_parameter_group_number(), - session->get_message_data_length(), - std::static_pointer_cast(session->sessionMessage.get_source_control_function()), - session->sessionMessage.get_destination_control_function(), - success, - session->parent); + // No matching session. See if we need to start a new session + if (0 != actualFrameCount) + { + // This is the middle of some message that we have no context for. + // Ignore the message for now until we receive it with a fresh packet counter. + LOG_WARNING("[FP]: Ignoring FP message with PGN %u, no context available. The message may be processed when packet count returns to zero.", + message.get_identifier().get_parameter_group_number()); + } + else + { + // This is the beginning of a new message + std::uint8_t messageLength = message.get_uint8_at(1); + if (messageLength > MAX_PROTOCOL_MESSAGE_LENGTH) + { + LOG_WARNING("[FP]: Ignoring possible new FP session with advertised length > 233."); + return; + } + else if (messageLength <= CAN_DATA_LENGTH) + { + LOG_WARNING("[FP]: Ignoring possible new FP session with advertised length <= 8."); + return; + } + + // Create a new session + session = std::make_shared(FastPacketProtocolSession::Direction::Receive, + std::unique_ptr(new CANMessageDataVector(messageLength)), + message.get_identifier().get_parameter_group_number(), + messageLength, + (message.get_uint8_at(0) & SEQUENCE_NUMBER_BIT_MASK), + message.get_identifier().get_priority(), + message.get_source_control_function(), + message.get_destination_control_function(), + nullptr, // No callback + nullptr); + + // Save the 6 bytes of payload in this first message + // Convert data type to a vector to allow for manipulation + auto &data = static_cast(session->get_data()); + for (std::uint8_t i = 0; i < (PROTOCOL_BYTES_PER_FRAME - 1); i++) + { + data.set_byte(session->numberOfBytesTransferred, message.get_uint8_at(2 + i)); + session->add_number_of_bytes_transferred(1); + } + + LOCK_GUARD(Mutex, sessionMutex); + activeSessions.push_back(session); + } } } - bool FastPacketProtocol::protocol_transmit_message(std::uint32_t, - const std::uint8_t *, - std::uint32_t, - std::shared_ptr, - std::shared_ptr, - TransmitCompleteCallback, - void *, - DataChunkCallback) + void FastPacketProtocol::update_session(const std::shared_ptr &session) { - return false; - } + if (nullptr == session) + { + return; + } - void FastPacketProtocol::update_state_machine(FastPacketProtocolSession *session) - { - if (nullptr != session) + if (session->get_direction() == FastPacketProtocolSession::Direction::Receive) + { + // We are receiving a message, only need to check for timeouts + if (session->get_time_since_last_update() > FP_TIMEOUT_MS) + { + LOG_ERROR("[FP]: Rx session timed out."); + close_session(session, false); + } + } + else { - switch (session->sessionDirection) + std::array buffer; + // We are transmitting a message, let's try and send remaining packets + for (std::uint8_t i = 0; i < session->get_number_of_remaining_packets(); i++) { - case FastPacketProtocolSession::Direction::Receive: + buffer[0] = session->get_last_packet_number(); + buffer[0] |= (session->sequenceNumber << SEQUENCE_NUMBER_BIT_OFFSET); + + std::uint8_t startIndex = 1; + std::uint8_t bytesThisFrame = PROTOCOL_BYTES_PER_FRAME; + if (0 == session->get_total_bytes_transferred()) { - if (SystemTiming::time_expired_ms(session->timestamp_ms, FP_TIMEOUT_MS)) - { - CANStackLogger::error("[FP]: Rx session timed out."); - close_session(session, false); - } + // This is the first frame, so we need to send the message length + buffer[1] = session->get_message_length(); + startIndex++; + bytesThisFrame--; } - break; - case FastPacketProtocolSession::Direction::Transmit: + for (std::uint8_t j = 0; j < bytesThisFrame; j++) { - std::array dataBuffer; - std::vector messageData; - bool txSessionCancelled = false; - - for (std::uint8_t i = session->processedPacketsThisSession; i <= session->packetCount; i++) + std::uint8_t index = static_cast(session->get_total_bytes_transferred()) + j; + if (index < session->get_message_length()) { - std::uint8_t bytesProcessedSoFar = (session->processedPacketsThisSession > 0 ? 6 : 0); - - if (0 != bytesProcessedSoFar) - { - bytesProcessedSoFar += (PROTOCOL_BYTES_PER_FRAME * (session->processedPacketsThisSession - 1)); - } - - std::uint16_t numberBytesLeft = (session->get_message_data_length() - bytesProcessedSoFar); - - if (numberBytesLeft > PROTOCOL_BYTES_PER_FRAME) - { - numberBytesLeft = PROTOCOL_BYTES_PER_FRAME; - } - - if (nullptr != session->frameChunkCallback) - { - std::uint8_t callbackBuffer[CAN_DATA_LENGTH] = { 0 }; // Only need 7 but give them 8 in case they make a mistake - bool callbackSuccessful = session->frameChunkCallback(dataBuffer[0], (PROTOCOL_BYTES_PER_FRAME * session->processedPacketsThisSession), numberBytesLeft, callbackBuffer, session->parent); - - if (callbackSuccessful) - { - for (std::uint8_t j = 0; j < PROTOCOL_BYTES_PER_FRAME; j++) - { - dataBuffer[1 + j] = callbackBuffer[j]; - } - } - else - { - close_session(session, false); - break; - } - } - else - { - messageData = session->sessionMessage.get_data(); - if (0 == session->processedPacketsThisSession) - { - dataBuffer[0] = session->processedPacketsThisSession; - dataBuffer[0] |= (session->sequenceNumber << SEQUENCE_NUMBER_BIT_OFFSET); - dataBuffer[1] = session->get_message_data_length(); - dataBuffer[2] = messageData[0]; - dataBuffer[3] = messageData[1]; - dataBuffer[4] = messageData[2]; - dataBuffer[5] = messageData[3]; - dataBuffer[6] = messageData[4]; - dataBuffer[7] = messageData[5]; - } - else - { - dataBuffer[0] = session->processedPacketsThisSession; - dataBuffer[0] |= (session->sequenceNumber << SEQUENCE_NUMBER_BIT_OFFSET); - - if (numberBytesLeft < PROTOCOL_BYTES_PER_FRAME) - { - dataBuffer[1] = 0xFF; - dataBuffer[2] = 0xFF; - dataBuffer[3] = 0xFF; - dataBuffer[4] = 0xFF; - dataBuffer[5] = 0xFF; - dataBuffer[6] = 0xFF; - dataBuffer[7] = 0xFF; - } - - for (std::uint8_t j = 0; j < numberBytesLeft; j++) - { - dataBuffer[1 + j] = messageData[6 + ((i - 1) * PROTOCOL_BYTES_PER_FRAME) + j]; - } - } - } - if (CANNetworkManager::CANNetwork.send_can_message(session->sessionMessage.get_identifier().get_parameter_group_number(), - dataBuffer.data(), - CAN_DATA_LENGTH, - std::static_pointer_cast(session->sessionMessage.get_source_control_function()), - session->sessionMessage.get_destination_control_function(), - session->sessionMessage.get_identifier().get_priority(), - nullptr, - nullptr)) - { - session->processedPacketsThisSession++; - session->timestamp_ms = SystemTiming::get_timestamp_ms(); - } - else - { - if (SystemTiming::time_expired_ms(session->timestamp_ms, FP_TIMEOUT_MS)) - { - CANStackLogger::error("[FP]: Tx session timed out."); - close_session(session, false); - txSessionCancelled = true; - } - break; - } + buffer[startIndex + j] = session->get_data().get_byte(index); } + else + { + buffer[startIndex + j] = 0xFF; + } + } - if ((!txSessionCancelled) && - (session->processedPacketsThisSession >= session->packetCount)) + if (sendCANFrameCallback(session->get_parameter_group_number(), + CANDataSpan(buffer.data(), buffer.size()), + std::static_pointer_cast(session->get_source()), + session->get_destination(), + session->priority)) + { + session->add_number_of_bytes_transferred(bytesThisFrame); + } + else + { + if (session->get_time_since_last_update() > FP_TIMEOUT_MS) { - add_session_history(session); - close_session(session, true); // Session is done! + LOG_ERROR("[FP]: Tx session timed out."); + close_session(session, false); } + break; } - break; + } + + if (session->get_number_of_remaining_packets() == 0) + { + close_session(session, true); } } } + bool FastPacketProtocol::has_session(std::uint32_t parameterGroupNumber, std::shared_ptr source, std::shared_ptr destination) + { + LOCK_GUARD(Mutex, sessionMutex); + return std::any_of(activeSessions.begin(), activeSessions.end(), [&](const std::shared_ptr &session) { + return session->matches(source, destination) && (session->get_parameter_group_number() == parameterGroupNumber); + }); + } + + std::shared_ptr FastPacketProtocol::get_session(std::uint32_t parameterGroupNumber, + std::shared_ptr source, + std::shared_ptr destination) + { + LOCK_GUARD(Mutex, sessionMutex); + auto result = std::find_if(activeSessions.begin(), activeSessions.end(), [&](const std::shared_ptr &session) { + return session->matches(source, destination) && (session->get_parameter_group_number() == parameterGroupNumber); + }); + return (activeSessions.end() != result) ? (*result) : nullptr; + } + } // namespace isobus diff --git a/src/nmea2000_fast_packet_protocol.hpp b/src/nmea2000_fast_packet_protocol.hpp index 39f20f0..555aaff 100644 --- a/src/nmea2000_fast_packet_protocol.hpp +++ b/src/nmea2000_fast_packet_protocol.hpp @@ -19,44 +19,118 @@ /// Electronics Association in any way. /// /// @author Adrian Del Grosso +/// @author Daan Steenbergen /// -/// @copyright 2022 Adrian Del Grosso +/// @copyright 2024 The Open-Agriculture Developers //================================================================================================ #ifndef NMEA2000_FAST_PACKET_PROTOCOL_HPP #define NMEA2000_FAST_PACKET_PROTOCOL_HPP -#include "can_internal_control_function.hpp" -#include "can_protocol.hpp" - -#if !defined CAN_STACK_DISABLE_THREADS && !defined ARDUINO -#include -#endif +#include "can_transport_protocol_base.hpp" +#include "event_dispatcher.hpp" +#include "thread_synchronization.hpp" namespace isobus { /// @brief A protocol that handles the NMEA 2000 fast packet protocol. - class FastPacketProtocol : public CANLibProtocol + class FastPacketProtocol { public: - /// @brief A generic way to initialize a protocol - /// @details The network manager will call a protocol's initialize function - /// when it is first updated, if it has yet to be initialized. - void initialize(CANLibBadge) override; + /// @brief An object for tracking fast packet session state + class FastPacketProtocolSession : public TransportProtocolSessionBase + { + public: + /// @brief The constructor for a session, for advanced use only. + /// In most cases, you should use the CANNetworkManager::get_fast_packet_protocol().send_message() function to transmit messages. + /// @param[in] direction The direction of the session + /// @param[in] data Data buffer (will be moved into the session) + /// @param[in] parameterGroupNumber The PGN of the message + /// @param[in] totalMessageSize The total size of the message in bytes + /// @param[in] sequenceNumber The sequence number for this PGN + /// @param[in] priority The priority to encode in the IDs of the component CAN messages + /// @param[in] source The source control function + /// @param[in] destination The destination control function + /// @param[in] sessionCompleteCallback A callback for when the session completes + /// @param[in] parentPointer A generic context object for the tx complete and chunk callbacks + FastPacketProtocolSession(TransportProtocolSessionBase::Direction direction, + std::unique_ptr data, + std::uint32_t parameterGroupNumber, + std::uint16_t totalMessageSize, + std::uint8_t sequenceNumber, + CANIdentifier::CANPriority priority, + std::shared_ptr source, + std::shared_ptr destination, + TransmitCompleteCallback sessionCompleteCallback, + void *parentPointer); + + /// @brief Get the total number of bytes that will be sent or received in this session + /// @note The maximum number of bytes that can be sent in a single session is 6 + 31 * 7 = 223 + /// @return The length of the message in number of bytes + std::uint8_t get_message_length() const; + + /// @brief Get the number of bytes that have been sent or received in this session + /// @return The number of bytes that have been sent or received + std::uint32_t get_total_bytes_transferred() const override; + + /// @brief Get whether or not this session is a broadcast session (BAM) + /// @return True if this session is a broadcast session, false if not + bool is_broadcast() const; + + protected: + friend class FastPacketProtocol; ///< Allows the TP manager full access + + /// @brief Get the last packet number that was sent or received in this session + /// @return The last packet number that was sent or received in this session + std::uint8_t get_last_packet_number() const; + + /// @brief Get the number of packets that remain to be sent or received in this session + /// @return The number of packets that remain to be sent or received in this session + std::uint8_t get_number_of_remaining_packets() const; + + /// @brief Get the total number of packets that will be sent or received in this session + /// @return The total number of packets that will be sent or received in this session + std::uint8_t get_total_number_of_packets() const; + + /// @brief Add number of bytes to the total number of bytes that have been sent or received in this session + /// @param[in] bytes The number of bytes to add to the total + void add_number_of_bytes_transferred(std::uint8_t bytes); + + private: + std::uint8_t numberOfBytesTransferred = 0; ///< The total number of bytes that have been processed in this session + std::uint8_t sequenceNumber; ///< The sequence number for this PGN + CANIdentifier::CANPriority priority; ///< The priority to encode in the IDs of the component CAN messages + }; - /// @brief Similar to add_parameter_group_number_callback but tells the stack to parse those PGNs as Fast Packet + /// @brief A structure for keeping track of past sessions so we can resume with the right session number + struct FastPacketHistory + { + NAME isoName; ///< The ISO name of the internal control function used in a session + std::uint32_t parameterGroupNumber; ///< The PGN of the session being saved + std::uint8_t sequenceNumber; ///< The sequence number to use in the next matching session + }; + + /// @brief The constructor for the FastPacketProtocol, for advanced use only. + /// In most cases, you should use the CANNetworkManager::get_fast_packet_protocol().send_message() function to transmit messages. + /// @param[in] sendCANFrameCallback A callback for sending a CAN frame to hardware + explicit FastPacketProtocol(const CANMessageFrameCallback &sendCANFrameCallback); + + /// @brief Add a callback to be called when a message is received by the Fast Packet protocol /// @param[in] parameterGroupNumber The PGN to parse as fast packet /// @param[in] callback The callback that the stack will call when a matching message is received - /// @param[in] parent Generic context variable + /// @param[in] parent Generic context variable for the callback /// @param[in] internalControlFunction An internal control function to use as an additional filter for the callback. - /// Only messages destined for the specified ICF will generate a callback. Use nullptr to receive all messages. + /// Only messages destined for the specified ICF will generate a callback. Use nullptr to receive messages for + /// destined for any ICF and broadcast messages. + /// @note You can also sniff all messages by allowing messages for messages destined to non-internal control functions + /// to be parsed by this protocol, use the "allow_any_control_function()" function to enable this. void register_multipacket_message_callback(std::uint32_t parameterGroupNumber, CANLibCallback callback, void *parent, std::shared_ptr internalControlFunction = nullptr); // @brief Removes a callback previously added with register_multipacket_message_callback /// @param[in] parameterGroupNumber The PGN to parse as fast packet /// @param[in] callback The callback that the stack will call when a matching message is received /// @param[in] parent Generic context variable - /// @param[in] internalControlFunction An internal control function to use as an additional filter for the callback + /// @param[in] internalControlFunction An internal control function used as an additional filter for the callback void remove_multipacket_message_callback(std::uint32_t parameterGroupNumber, CANLibCallback callback, void *parent, std::shared_ptr internalControlFunction = nullptr); /// @brief Used to send CAN messages using fast packet @@ -83,119 +157,55 @@ namespace isobus void *parentPointer = nullptr, DataChunkCallback frameChunkCallback = nullptr); - /// @brief This will be called by the network manager on every cyclic update of the stack - void update(CANLibBadge) override; + /// @brief Set whether or not to allow messages for non-internal control functions to be parsed by this protocol + /// @param[in] allow Denotes if messages for non-internal control functions should be parsed by this protocol + void allow_any_control_function(bool allow); - private: - /// @brief An object for tracking fast packet session state - class FastPacketProtocolSession - { - public: - /// @brief Enumerates the possible session directions, Rx or Tx - enum class Direction - { - Transmit, ///< We are transmitting a message - Receive ///< We are receiving a message - }; - - /// @brief A useful way to compare session objects to each other for equality - /// @param[in] obj The object to compare against - /// @returns true if the objects are equal, false otherwise - bool operator==(const FastPacketProtocolSession &obj); + /// @brief Updates all sessions managed by this protocol manager instance. + void update(); - /// @brief Get the total number of bytes that will be sent or received in this session - /// @return The length of the message in number of bytes - std::uint32_t get_message_data_length() const; - - private: - friend class FastPacketProtocol; ///< Allows the TP manager full access + /// @brief A generic way for a protocol to process a received message + /// @param[in] message A received CAN message + void process_message(const CANMessage &message); - /// @brief The constructor for a TP session - /// @param[in] sessionDirection Tx or Rx - explicit FastPacketProtocolSession(Direction sessionDirection); - - /// @brief The destructor for a TP session - ~FastPacketProtocolSession(); - - CANMessage sessionMessage; ///< A CAN message is used in the session to represent and store data like PGN - TransmitCompleteCallback sessionCompleteCallback; ///< A callback that is to be called when the session is completed - DataChunkCallback frameChunkCallback; ///< A callback that might be used to get chunks of data to send - std::uint32_t frameChunkCallbackMessageLength; ///< The length of the message that is being sent in chunks - void *parent; ///< A generic context variable that helps identify what object callbacks are destined for. Can be nullptr - std::uint32_t timestamp_ms; ///< A timestamp used to track session timeouts - std::uint16_t lastPacketNumber; ///< The last processed sequence number for this set of packets - std::uint8_t packetCount; ///< The total number of packets to receive or send in this session - std::uint8_t processedPacketsThisSession; ///< The total processed packet count for the whole session so far - std::uint8_t sequenceNumber; ///< The sequence number for this PGN - const Direction sessionDirection; ///< Represents Tx or Rx session - }; - - /// @brief A structure for keeping track of past sessions so we can resume with the right session number - struct FastPacketHistory - { - NAME isoName; ///< The ISO name of the internal control function used in a session - std::uint32_t parameterGroupNumber; ///< The PGN of the session being saved - std::uint8_t sequenceNumber; ///< The sequence number to use in the next matching session - }; + /// @brief Calculates the number of frames needed for a message + /// @param[in] messageLength The length of the message in bytes + /// @returns The number of frames needed for the message + static std::uint8_t calculate_number_of_frames(std::uint8_t messageLength); + private: /// @brief Adds a session's info to the history so that we can continue the sequence number later /// @param[in] session The session to add to the history - void add_session_history(FastPacketProtocolSession *session); + void add_session_history(const std::shared_ptr &session); - /// @brief Ends a session and cleans up the memory associated with its metadata + /// @brief Gracefully closes a session to prepare for a new session /// @param[in] session The session to close - /// @param[in] successful `true` if the session was closed successfully, otherwise `false` - void close_session(FastPacketProtocolSession *session, bool successful); - - /// @brief Gets the sequence number to use for a new session based on the history - /// @param[in] session The new session we're starting - /// @returns The new sequence number to use - std::uint8_t get_new_sequence_number(FastPacketProtocolSession *session); - - /// @brief Returns a session that matches the parameters, if one exists - /// @param[in,out] returnedSession The returned session - /// @param[in] parameterGroupNumber The PGN - /// @param[in] source The session source control function - /// @param[in] destination The session destination control function - /// @returns `true` if a session was found that matches, otherwise `false` - bool get_session(FastPacketProtocolSession *&returnedSession, std::uint32_t parameterGroupNumber, std::shared_ptr source, std::shared_ptr destination); - - /// @brief A generic way for a protocol to process a received message - /// @param[in] message A received CAN message - void process_message(const CANMessage &message) override; - - /// @brief A generic way for a protocol to process a received message - /// @param[in] message A received CAN message - /// @param[in] parent Provides the context to the actual FP manager object - static void process_message(const CANMessage &message, void *parent); - - /// @brief Processes end of session callbacks - /// @param[in] session The session we've just completed - /// @param[in] success Denotes if the session was successful - void process_session_complete_callback(FastPacketProtocolSession *session, bool success); - - /// @brief The network manager calls this to see if the protocol can accept a non-raw CAN message for processing - /// @param[in] parameterGroupNumber The PGN of the message - /// @param[in] data The data to be sent - /// @param[in] messageLength The length of the data to be sent - /// @param[in] source The source control function - /// @param[in] destination The destination control function - /// @param[in] transmitCompleteCallback A callback for when the protocol completes its work - /// @param[in] parentPointer A generic context object for the tx complete and chunk callbacks - /// @param[in] frameChunkCallback A callback to get some data to send - /// @returns true if the message was accepted by the protocol for processing - bool protocol_transmit_message(std::uint32_t parameterGroupNumber, - const std::uint8_t *data, - std::uint32_t messageLength, - std::shared_ptr source, - std::shared_ptr destination, - TransmitCompleteCallback transmitCompleteCallback, - void *parentPointer, - DataChunkCallback frameChunkCallback) override; - - /// @brief Updates in-progress sessions - /// @param[in] session The session to process - void update_state_machine(FastPacketProtocolSession *session); + /// @param[in] successful Denotes if the session was successful + void close_session(std::shared_ptr session, bool successful); + + /// @brief Get the sequence number to use for a new session based on the history of past sessions + /// @param[in] name The ISO name of the internal control function used in a session + /// @param[in] parameterGroupNumber The PGN of the session being started + /// @returns The sequence number to use for the new session + std::uint8_t get_new_sequence_number(NAME name, std::uint32_t parameterGroupNumber) const; + + /// @brief Gets a FP session from the passed in source and destination and PGN combination + /// @param[in] parameterGroupNumber The PGN of the session + /// @param[in] source The source control function for the session + /// @param[in] destination The destination control function for the session + /// @returns a matching session, or nullptr if no session matched the supplied parameters + std::shared_ptr get_session(std::uint32_t parameterGroupNumber, std::shared_ptr source, std::shared_ptr destination); + + /// @brief Checks if a session by the passed in source and destination and PGN combination exists + /// @param[in] parameterGroupNumber The PGN of the session + /// @param[in] source The source control function for the session + /// @param[in] destination The destination control function for the session + /// @returns true if a matching session exists, false if not + bool has_session(std::uint32_t parameterGroupNumber, std::shared_ptr source, std::shared_ptr destination); + + /// @brief Update a single session + /// @param[in] session The session to update + void update_session(const std::shared_ptr &session); static constexpr std::uint32_t FP_MIN_PARAMETER_GROUP_NUMBER = 0x1F000; ///< Start of PGNs that can be received via Fast Packet static constexpr std::uint32_t FP_MAX_PARAMETER_GROUP_NUMBER = 0x1FFFF; ///< End of PGNs that can be received via Fast Packet @@ -203,15 +213,15 @@ namespace isobus static constexpr std::uint8_t MAX_PROTOCOL_MESSAGE_LENGTH = 223; ///< Max message length based on there being 5 bits of sequence data static constexpr std::uint8_t FRAME_COUNTER_BIT_MASK = 0x1F; ///< Bit mask for masking out the frame counter static constexpr std::uint8_t SEQUENCE_NUMBER_BIT_MASK = 0x07; ///< Bit mask for masking out the sequence number bits - static constexpr std::uint8_t SEQUENCE_NUMBER_BIT_OFFSET = 0x05; ///< The bit offset into the first byte of data to get the seq number + static constexpr std::uint8_t SEQUENCE_NUMBER_BIT_OFFSET = 5; ///< The bit offset into the first byte of data to get the seq number static constexpr std::uint8_t PROTOCOL_BYTES_PER_FRAME = 7; ///< The number of payload bytes per frame for all but the first message, which has 6 - std::vector activeSessions; ///< A list of all active TP sessions + std::vector> activeSessions; ///< A list of all active TP sessions + Mutex sessionMutex; ///< A mutex to lock the sessions list in case someone starts a Tx while the stack is processing sessions std::vector sessionHistory; ///< Used to keep track of sequence numbers for future sessions std::vector parameterGroupNumberCallbacks; ///< A list of all parameter group number callbacks that will be parsed as fast packet messages -#if !defined CAN_STACK_DISABLE_THREADS && !defined ARDUINO - std::mutex sessionMutex; ///< A mutex to lock the sessions list in case someone starts a Tx while the stack is processing sessions -#endif + bool allowAnyControlFunction = false; ///< Denotes if messages for non-internal control functions should be parsed by this protocol + const CANMessageFrameCallback sendCANFrameCallback; ///< A callback for sending a CAN frame }; } // namespace isobus diff --git a/src/nmea2000_message_definitions.cpp b/src/nmea2000_message_definitions.cpp index 7e8f002..ad74bab 100644 --- a/src/nmea2000_message_definitions.cpp +++ b/src/nmea2000_message_definitions.cpp @@ -10,7 +10,7 @@ /// /// @author Adrian Del Grosso /// -/// @copyright 2023 Adrian Del Grosso +/// @copyright 2023 The Open-Agriculture Developers //================================================================================================ #include "nmea2000_message_definitions.hpp" #include "can_message.hpp" @@ -147,7 +147,7 @@ namespace isobus } else { - CANStackLogger::warn("[NMEA2K]: Can't deserialize vessel heading. DLC must be 8."); + LOG_WARNING("[NMEA2K]: Can't deserialize vessel heading. DLC must be 8."); } return retVal; } @@ -238,7 +238,7 @@ namespace isobus } else { - CANStackLogger::warn("[NMEA2K]: Can't deserialize rate of turn. DLC must be 8."); + LOG_WARNING("[NMEA2K]: Can't deserialize rate of turn. DLC must be 8."); } return retVal; } @@ -338,7 +338,7 @@ namespace isobus } else { - CANStackLogger::warn("[NMEA2K]: Can't deserialize position rapid update. DLC must be 8."); + LOG_WARNING("[NMEA2K]: Can't deserialize position rapid update. DLC must be 8."); } return retVal; } @@ -456,7 +456,7 @@ namespace isobus } else { - CANStackLogger::warn("[NMEA2K]: Can't deserialize COG/SOG rapid update. DLC must be 8."); + LOG_WARNING("[NMEA2K]: Can't deserialize COG/SOG rapid update. DLC must be 8."); } return retVal; } @@ -579,7 +579,7 @@ namespace isobus } else { - CANStackLogger::warn("[NMEA2K]: Cannot deserialize position delta high precision rapid update. DLC must be 8 bytes."); + LOG_WARNING("[NMEA2K]: Cannot deserialize position delta high precision rapid update. DLC must be 8 bytes."); } return retVal; } @@ -958,14 +958,14 @@ namespace isobus } else { - CANStackLogger::warn("[NMEA2K]: Can't fully parse GNSS position data reference station info because message length is not long enough."); + LOG_WARNING("[NMEA2K]: Can't fully parse GNSS position data reference station info because message length is not long enough."); break; } } } else { - CANStackLogger::warn("[NMEA2K]: Cannot deserialize GNSS position data. DLC must be >= 43 bytes."); + LOG_WARNING("[NMEA2K]: Cannot deserialize GNSS position data. DLC must be >= 43 bytes."); } return retVal; } @@ -1141,7 +1141,7 @@ namespace isobus } else { - CANStackLogger::warn("[NMEA2K]: Can't deserialize Datum message. Message length must be at least 20 bytes."); + LOG_WARNING("[NMEA2K]: Can't deserialize Datum message. Message length must be at least 20 bytes."); } return retVal; } diff --git a/src/nmea2000_message_definitions.hpp b/src/nmea2000_message_definitions.hpp index ed95c82..6e0a1ed 100644 --- a/src/nmea2000_message_definitions.hpp +++ b/src/nmea2000_message_definitions.hpp @@ -10,7 +10,7 @@ /// /// @author Adrian Del Grosso /// -/// @copyright 2023 Adrian Del Grosso +/// @copyright 2023 The Open-Agriculture Developers //================================================================================================ #ifndef NMEA2000_MESSAGE_DEFINITIONS_HPP #define NMEA2000_MESSAGE_DEFINITIONS_HPP diff --git a/src/nmea2000_message_interface.cpp b/src/nmea2000_message_interface.cpp index b5b7f24..7982718 100644 --- a/src/nmea2000_message_interface.cpp +++ b/src/nmea2000_message_interface.cpp @@ -14,7 +14,7 @@ /// /// @author Adrian Del Grosso /// -/// @copyright 2023 Adrian Del Grosso +/// @copyright 2023 The Open-Agriculture Developers //================================================================================================ #include "nmea2000_message_interface.hpp" #include "can_general_parameter_group_numbers.hpp" @@ -316,8 +316,9 @@ namespace isobus { if (!initialized) { - CANNetworkManager::CANNetwork.get_fast_packet_protocol().register_multipacket_message_callback(static_cast(CANLibParameterGroupNumber::Datum), process_rx_message, this); - CANNetworkManager::CANNetwork.get_fast_packet_protocol().register_multipacket_message_callback(static_cast(CANLibParameterGroupNumber::GNSSPositionData), process_rx_message, this); + const auto &fastPacketProtocol = CANNetworkManager::CANNetwork.get_fast_packet_protocol(0); // TODO: This should be a configurable can index (will be solved with the new CAN network manager) + fastPacketProtocol->register_multipacket_message_callback(static_cast(CANLibParameterGroupNumber::Datum), process_rx_message, this); + fastPacketProtocol->register_multipacket_message_callback(static_cast(CANLibParameterGroupNumber::GNSSPositionData), process_rx_message, this); CANNetworkManager::CANNetwork.add_any_control_function_parameter_group_number_callback(static_cast(CANLibParameterGroupNumber::CourseOverGroundSpeedOverGroundRapidUpdate), process_rx_message, this); CANNetworkManager::CANNetwork.add_any_control_function_parameter_group_number_callback(static_cast(CANLibParameterGroupNumber::PositionDeltaHighPrecisionRapidUpdate), process_rx_message, this); CANNetworkManager::CANNetwork.add_any_control_function_parameter_group_number_callback(static_cast(CANLibParameterGroupNumber::PositionRapidUpdate), process_rx_message, this); @@ -336,8 +337,9 @@ namespace isobus { if (initialized) { - CANNetworkManager::CANNetwork.get_fast_packet_protocol().remove_multipacket_message_callback(static_cast(CANLibParameterGroupNumber::Datum), process_rx_message, this); - CANNetworkManager::CANNetwork.get_fast_packet_protocol().remove_multipacket_message_callback(static_cast(CANLibParameterGroupNumber::GNSSPositionData), process_rx_message, this); + const auto &fastPacketProtocol = CANNetworkManager::CANNetwork.get_fast_packet_protocol(0); // TODO: This should be a configurable can index (will be solved with the new CAN network manager) + fastPacketProtocol->remove_multipacket_message_callback(static_cast(CANLibParameterGroupNumber::Datum), process_rx_message, this); + fastPacketProtocol->remove_multipacket_message_callback(static_cast(CANLibParameterGroupNumber::GNSSPositionData), process_rx_message, this); CANNetworkManager::CANNetwork.remove_any_control_function_parameter_group_number_callback(static_cast(CANLibParameterGroupNumber::CourseOverGroundSpeedOverGroundRapidUpdate), process_rx_message, this); CANNetworkManager::CANNetwork.remove_any_control_function_parameter_group_number_callback(static_cast(CANLibParameterGroupNumber::PositionDeltaHighPrecisionRapidUpdate), process_rx_message, this); CANNetworkManager::CANNetwork.remove_any_control_function_parameter_group_number_callback(static_cast(CANLibParameterGroupNumber::PositionRapidUpdate), process_rx_message, this); @@ -357,7 +359,7 @@ namespace isobus } else { - CANStackLogger::error("[NMEA2K]: Interface not initialized!"); + LOG_ERROR("[NMEA2K]: Interface not initialized!"); } } @@ -392,12 +394,12 @@ namespace isobus if (nullptr != targetInterface->datumTransmitMessage.get_control_function()) { targetInterface->datumTransmitMessage.serialize(messageBuffer); - transmitSuccessful = CANNetworkManager::CANNetwork.get_fast_packet_protocol().send_multipacket_message(static_cast(CANLibParameterGroupNumber::Datum), - messageBuffer.data(), - messageBuffer.size(), - std::static_pointer_cast(targetInterface->datumTransmitMessage.get_control_function()), - nullptr, - CANIdentifier::CANPriority::PriorityDefault6); + transmitSuccessful = CANNetworkManager::CANNetwork.get_fast_packet_protocol(0)->send_multipacket_message(static_cast(CANLibParameterGroupNumber::Datum), + messageBuffer.data(), + messageBuffer.size(), + std::static_pointer_cast(targetInterface->datumTransmitMessage.get_control_function()), + nullptr, + CANIdentifier::CANPriority::PriorityDefault6); } } break; @@ -407,12 +409,12 @@ namespace isobus if (nullptr != targetInterface->gnssPositionDataTransmitMessage.get_control_function()) { targetInterface->gnssPositionDataTransmitMessage.serialize(messageBuffer); - transmitSuccessful = CANNetworkManager::CANNetwork.get_fast_packet_protocol().send_multipacket_message(static_cast(CANLibParameterGroupNumber::GNSSPositionData), - messageBuffer.data(), - messageBuffer.size(), - std::static_pointer_cast(targetInterface->gnssPositionDataTransmitMessage.get_control_function()), - nullptr, - CANIdentifier::CANPriority::Priority3); + transmitSuccessful = CANNetworkManager::CANNetwork.get_fast_packet_protocol(0)->send_multipacket_message(static_cast(CANLibParameterGroupNumber::GNSSPositionData), + messageBuffer.data(), + messageBuffer.size(), + std::static_pointer_cast(targetInterface->gnssPositionDataTransmitMessage.get_control_function()), + nullptr, + CANIdentifier::CANPriority::Priority3); } } break; @@ -672,7 +674,7 @@ namespace isobus [](std::shared_ptr message) { if (SystemTiming::time_expired_ms(message->get_timestamp(), 3 * CourseOverGroundSpeedOverGroundRapidUpdate::get_timeout())) { - CANStackLogger::warn("[NMEA2K]: COG & SOG message Rx timeout."); + LOG_WARNING("[NMEA2K]: COG & SOG message Rx timeout."); return true; } return false; @@ -683,7 +685,7 @@ namespace isobus [](std::shared_ptr message) { if (SystemTiming::time_expired_ms(message->get_timestamp(), 3 * Datum::get_timeout())) { - CANStackLogger::warn("[NMEA2K]: Datum message Rx timeout."); + LOG_WARNING("[NMEA2K]: Datum message Rx timeout."); return true; } return false; @@ -694,7 +696,7 @@ namespace isobus [](std::shared_ptr message) { if (SystemTiming::time_expired_ms(message->get_timestamp(), 3 * GNSSPositionData::get_timeout())) { - CANStackLogger::warn("[NMEA2K]: GNSS position data message Rx timeout."); + LOG_WARNING("[NMEA2K]: GNSS position data message Rx timeout."); return true; } return false; @@ -705,7 +707,7 @@ namespace isobus [](std::shared_ptr message) { if (SystemTiming::time_expired_ms(message->get_timestamp(), 3 * PositionDeltaHighPrecisionRapidUpdate::get_timeout())) { - CANStackLogger::warn("[NMEA2K]: Position Delta High Precision Rapid Update Rx timeout."); + LOG_WARNING("[NMEA2K]: Position Delta High Precision Rapid Update Rx timeout."); return true; } return false; @@ -716,7 +718,7 @@ namespace isobus [](std::shared_ptr message) { if (SystemTiming::time_expired_ms(message->get_timestamp(), 3 * PositionRapidUpdate::get_timeout())) { - CANStackLogger::warn("[NMEA2K]: Position delta high precision rapid update message Rx timeout."); + LOG_WARNING("[NMEA2K]: Position delta high precision rapid update message Rx timeout."); return true; } return false; @@ -727,7 +729,7 @@ namespace isobus [](std::shared_ptr message) { if (SystemTiming::time_expired_ms(message->get_timestamp(), 3 * RateOfTurn::get_timeout())) { - CANStackLogger::warn("[NMEA2K]: Rate of turn message Rx timeout."); + LOG_WARNING("[NMEA2K]: Rate of turn message Rx timeout."); return true; } return false; @@ -738,7 +740,7 @@ namespace isobus [](std::shared_ptr message) { if (SystemTiming::time_expired_ms(message->get_timestamp(), 3 * VesselHeading::get_timeout())) { - CANStackLogger::warn("[NMEA2K]: Vessel heading message Rx timeout."); + LOG_WARNING("[NMEA2K]: Vessel heading message Rx timeout."); return true; } return false; diff --git a/src/nmea2000_message_interface.hpp b/src/nmea2000_message_interface.hpp index 75b0a21..ce20510 100644 --- a/src/nmea2000_message_interface.hpp +++ b/src/nmea2000_message_interface.hpp @@ -14,7 +14,7 @@ /// /// @author Adrian Del Grosso /// -/// @copyright 2023 Adrian Del Grosso +/// @copyright 2023 The Open-Agriculture Developers //================================================================================================ #ifndef NMEA2000_MESSAGE_INTERFACE_HPP #define NMEA2000_MESSAGE_INTERFACE_HPP diff --git a/src/platform_endianness.cpp b/src/platform_endianness.cpp index 77add5f..706465f 100644 --- a/src/platform_endianness.cpp +++ b/src/platform_endianness.cpp @@ -5,7 +5,7 @@ /// Useful when trying to convert bytes in memory (like float*) to a specific endianness. /// @author Adrian Del Grosso /// -/// @copyright 2023 Adrian Del Grosso +/// @copyright 2023 The Open-Agriculture Developers //================================================================================================ #include "platform_endianness.hpp" diff --git a/src/platform_endianness.hpp b/src/platform_endianness.hpp index 359d3b9..7c33f3b 100644 --- a/src/platform_endianness.hpp +++ b/src/platform_endianness.hpp @@ -5,7 +5,7 @@ /// Useful when trying to convert bytes in memory (like float*) to a specific endianness. /// @author Adrian Del Grosso /// -/// @copyright 2023 Adrian Del Grosso +/// @copyright 2023 The Open-Agriculture Developers //================================================================================================ #ifndef PLATFORM_ENDIANNESS_HPP #define PLATFORM_ENDIANNESS_HPP diff --git a/src/processing_flags.cpp b/src/processing_flags.cpp index 70f48df..5d5b5fb 100644 --- a/src/processing_flags.cpp +++ b/src/processing_flags.cpp @@ -4,7 +4,7 @@ /// @brief A class that manages 1 bit flags. Handy as a retry machanism for sending CAN messages. /// @author Adrian Del Grosso /// -/// @copyright 2022 Adrian Del Grosso +/// @copyright 2022 The Open-Agriculture Developers //================================================================================================ #include "processing_flags.hpp" diff --git a/src/processing_flags.hpp b/src/processing_flags.hpp index a6d54c1..081a25b 100644 --- a/src/processing_flags.hpp +++ b/src/processing_flags.hpp @@ -4,7 +4,7 @@ /// @brief A class that manages 1 bit flags. Handy as a retry machanism for sending CAN messages. /// @author Adrian Del Grosso /// -/// @copyright 2022 Adrian Del Grosso +/// @copyright 2022 The Open-Agriculture Developers //================================================================================================ #ifndef PROCESSING_FLAGS_HPP #define PROCESSING_FLAGS_HPP diff --git a/src/system_timing.cpp b/src/system_timing.cpp index 228a443..8cce474 100644 --- a/src/system_timing.cpp +++ b/src/system_timing.cpp @@ -4,7 +4,7 @@ /// @brief Utility class for getting system time and handling u32 time rollover /// @author Adrian Del Grosso /// -/// @copyright 2022 Adrian Del Grosso +/// @copyright 2022 The Open-Agriculture Developers //================================================================================================ #include "system_timing.hpp" diff --git a/src/system_timing.hpp b/src/system_timing.hpp index 962c3df..450dbef 100644 --- a/src/system_timing.hpp +++ b/src/system_timing.hpp @@ -4,7 +4,7 @@ /// @brief Utility class for getting system time and handling u32 time rollover /// @author Adrian Del Grosso /// -/// @copyright 2022 Adrian Del Grosso +/// @copyright 2022 The Open-Agriculture Developers //================================================================================================ #include diff --git a/src/thread_synchronization.hpp b/src/thread_synchronization.hpp index 2de9dd0..bae1e7b 100644 --- a/src/thread_synchronization.hpp +++ b/src/thread_synchronization.hpp @@ -9,9 +9,8 @@ #ifndef THREAD_SYNCHRONIZATION_HPP #define THREAD_SYNCHRONIZATION_HPP -#include "event_dispatcher.hpp" - #if defined CAN_STACK_DISABLE_THREADS || defined ARDUINO +#include namespace isobus { @@ -27,19 +26,181 @@ namespace isobus /// @brief Disabled LOCK_GUARD macro since threads are disabled. #define LOCK_GUARD(type, x) +/// @brief A template class for a queue, since threads are disabled this is a simple queue. +/// @tparam T The item type for the queue. +template +class LockFreeQueue +{ +public: + /// @brief Constructor for the lock free queue. + explicit LockFreeQueue(std::size_t) {} + + /// @brief Push an item to the queue. + /// @param item The item to push to the queue. + /// @return Simply returns true, since this version of the queue is not limited in size. + bool push(const T &item) + { + queue.push(item); + return true; + } + + /// @brief Peek at the next item in the queue. + /// @param item The item to peek at in the queue. + /// @return True if the item was peeked at in the queue, false if the queue is empty. + bool peek(T &item) + { + if (queue.empty()) + { + return false; + } + + item = queue.front(); + return true; + } + + /// @brief Pop an item from the queue. + /// @return True if the item was popped from the queue, false if the queue is empty. + bool pop() + { + if (queue.empty()) + { + return false; + } + + queue.pop(); + return true; + } + + /// @brief Check if the queue is full. + /// @return Always returns false, since this version of the queue is not limited in size. + bool is_full() const + { + return false; + } + + /// @brief Clear the queue. + void clear() + { + queue = {}; + } + +private: + std::queue queue; ///< The queue +}; + #else +#include +#include #include +#include namespace isobus { using Mutex = std::mutex; using RecursiveMutex = std::recursive_mutex; -}; +} /// @brief A macro to automatically lock a mutex and unlock it when the scope ends. /// @param type The type of the mutex. /// @param x The mutex to lock. #define LOCK_GUARD(type, x) const std::lock_guard x##Lock(x) +/// @brief A template class for a lock free queue. +/// @tparam T The item type for the queue. +template +class LockFreeQueue +{ +public: + /// @brief Constructor for the lock free queue. + explicit LockFreeQueue(std::size_t size) : + buffer(size), capacity(size) + { + // Validate the size of the queue, if assertion is disabled, set the size to 1. + assert(size > 0 && "The size of the queue must be greater than 0."); + if (size == 0) + { + size = 1; + } + } + + /// @brief Push an item to the queue. + /// @param item The item to push to the queue. + /// @return True if the item was pushed to the queue, false if the queue is full. + bool push(const T &item) + { + const auto currentWriteIndex = writeIndex.load(std::memory_order_relaxed); + const auto nextWriteIndex = nextIndex(currentWriteIndex); + + if (nextWriteIndex == readIndex.load(std::memory_order_acquire)) + { + // The buffer is full. + return false; + } + + buffer[currentWriteIndex] = item; + writeIndex.store(nextWriteIndex, std::memory_order_release); + return true; + } + + /// @brief Peek at the next item in the queue. + /// @param item The item to peek at in the queue. + /// @return True if the item was peeked at in the queue, false if the queue is empty. + bool peek(T &item) + { + const auto currentReadIndex = readIndex.load(std::memory_order_relaxed); + if (currentReadIndex == writeIndex.load(std::memory_order_acquire)) + { + // The buffer is empty. + return false; + } + + item = buffer[currentReadIndex]; + return true; + } + + /// @brief Pop an item from the queue. + /// @return True if the item was popped from the queue, false if the queue is empty. + bool pop() + { + const auto currentReadIndex = readIndex.load(std::memory_order_relaxed); + if (currentReadIndex == writeIndex.load(std::memory_order_acquire)) + { + // The buffer is empty. + return false; + } + + readIndex.store(nextIndex(currentReadIndex), std::memory_order_release); + return true; + } + + /// @brief Check if the queue is full. + /// @return True if the queue is full, false if the queue is not full. + bool is_full() const + { + return nextIndex(writeIndex.load(std::memory_order_acquire)) == readIndex.load(std::memory_order_acquire); + } + + /// @brief Clear the queue. + void clear() + { + // Simply move the read index to the write index. + readIndex.store(writeIndex.load(std::memory_order_acquire), std::memory_order_release); + } + +private: + std::vector buffer; ///< The buffer for the circular buffer. + std::atomic readIndex = { 0 }; ///< The read index for the circular buffer. + std::atomic writeIndex = { 0 }; ///< The write index for the circular buffer. + const std::size_t capacity; ///< The capacity of the circular buffer. + + /// @brief Get the next index in the circular buffer. + /// @param current The current index. + /// @return The next index in the circular buffer. + std::size_t nextIndex(std::size_t current) const + { + return (current + 1) % capacity; + } +}; + #endif #endif // THREAD_SYNCHRONIZATION_HPP diff --git a/src/to_string.hpp b/src/to_string.hpp index 404d597..27abb1e 100644 --- a/src/to_string.hpp +++ b/src/to_string.hpp @@ -7,7 +7,7 @@ /// implementation. /// @author Adrian Del Grosso /// -/// @copyright 2022 Adrian Del Grosso +/// @copyright 2022 The Open-Agriculture Developers //================================================================================================ #ifndef TO_STRING_HPP #define TO_STRING_HPP