From b48c2621ad291ef6e82164d887b6822f60f8eddc Mon Sep 17 00:00:00 2001 From: Adrian Del Grosso <10929341+ad3154@users.noreply.github.com> Date: Sun, 28 Jan 2024 18:32:02 -0700 Subject: [PATCH] [TC]: TC Server Cleanup and Fixes Add TC server options helper class. Continued improving unit tests. Replaced some manual bit shifting with helper functions. Cleaned up some sonar warnings. Added helper function for sending process data messages. Add condition variable for when messages are received by the TC. Improved thread safety of TC server. --- .../task_controller_server/console_logger.cpp | 3 + examples/task_controller_server/main.cpp | 47 +- isobus/CMakeLists.txt | 2 + .../isobus/isobus_task_controller_server.hpp | 306 +++--- .../isobus_task_controller_server_options.hpp | 83 ++ isobus/src/isobus_task_controller_server.cpp | 267 +++--- .../isobus_task_controller_server_options.cpp | 70 ++ test/tc_server_tests.cpp | 885 +++++++++++++++--- 8 files changed, 1229 insertions(+), 434 deletions(-) create mode 100644 isobus/include/isobus/isobus/isobus_task_controller_server_options.hpp create mode 100644 isobus/src/isobus_task_controller_server_options.cpp diff --git a/examples/task_controller_server/console_logger.cpp b/examples/task_controller_server/console_logger.cpp index 2eb71263..a9aac5c5 100644 --- a/examples/task_controller_server/console_logger.cpp +++ b/examples/task_controller_server/console_logger.cpp @@ -6,6 +6,9 @@ class CustomLogger : public isobus::CANStackLogger { public: + /// @brief Destructor for the custom logger. + virtual ~CustomLogger() = default; + void sink_CAN_stack_log(CANStackLogger::LoggingLevel level, const std::string &text) override { switch (level) diff --git a/examples/task_controller_server/main.cpp b/examples/task_controller_server/main.cpp index 0a681ad2..7a90414e 100644 --- a/examples/task_controller_server/main.cpp +++ b/examples/task_controller_server/main.cpp @@ -4,6 +4,7 @@ #include "isobus/isobus/can_network_manager.hpp" #include "isobus/isobus/can_partnered_control_function.hpp" #include "isobus/isobus/can_stack_logger.hpp" +#include "isobus/isobus/isobus_preferred_addresses.hpp" #include "isobus/isobus/isobus_task_controller_server.hpp" #include "console_logger.cpp" @@ -31,12 +32,12 @@ class MyTCServer : public isobus::TaskControllerServer std::uint8_t numberBoomsSupported, std::uint8_t numberSectionsSupported, std::uint8_t numberChannelsSupportedForPositionBasedControl, - std::uint8_t optionsBitfield) : + const isobus::TaskControllerOptions &options) : TaskControllerServer(internalControlFunction, numberBoomsSupported, numberSectionsSupported, numberChannelsSupportedForPositionBasedControl, - optionsBitfield) + options) { } @@ -45,59 +46,58 @@ class MyTCServer : public isobus::TaskControllerServer return true; } - bool change_designator(std::shared_ptr, std::uint16_t, const std::vector &) + bool change_designator(std::shared_ptr, std::uint16_t, const std::vector &) override { return true; } - bool deactivate_object_pool(std::shared_ptr) + bool deactivate_object_pool(std::shared_ptr) override { return true; } - bool delete_device_descriptor_object_pool(std::shared_ptr, ObjectPoolDeletionErrors &) + bool delete_device_descriptor_object_pool(std::shared_ptr, ObjectPoolDeletionErrors &) override { return true; } - bool get_is_stored_device_descriptor_object_pool_by_structure_label(std::shared_ptr, const std::vector &, const std::vector &) + bool get_is_stored_device_descriptor_object_pool_by_structure_label(std::shared_ptr, const std::vector &, const std::vector &) override { return false; } - bool get_is_stored_device_descriptor_object_pool_by_localization_label(std::shared_ptr, const std::array &) + bool get_is_stored_device_descriptor_object_pool_by_localization_label(std::shared_ptr, const std::array &) override { return false; } - bool get_is_enough_memory_available(std::uint32_t) + bool get_is_enough_memory_available(std::uint32_t) override { return true; } - std::uint32_t get_number_of_complete_object_pools_stored_for_client(std::shared_ptr) + void identify_task_controller(std::uint8_t) override { - return 0; + // When this is called, the TC is supposed to display its TC number for 3 seconds if possible (which is passed into this function). + // Your TC's number is your function code + 1, in the range of 1-32. } - void identify_task_controller(std::uint8_t) + void on_client_timeout(std::shared_ptr) override { + // You can use this function to handle when a client times out (6 Seconds) } - void on_client_timeout(std::shared_ptr) + void on_process_data_acknowledge(std::shared_ptr, std::uint16_t, std::uint16_t, std::uint8_t, ProcessDataCommands) override { + // This callback lets you know when a client sends a process data acknowledge (PDACK) message to you } - void on_process_data_acknowledge(std::shared_ptr, std::uint16_t, std::uint16_t, std::uint8_t, ProcessDataCommands) - { - } - - bool on_value_command(std::shared_ptr, std::uint16_t, std::uint16_t, std::int32_t, std::uint8_t &) + bool on_value_command(std::shared_ptr, std::uint16_t, std::uint16_t, std::int32_t, std::uint8_t &) override { return true; } - bool store_device_descriptor_object_pool(std::shared_ptr, const std::vector &, bool) + bool store_device_descriptor_object_pool(std::shared_ptr, const std::vector &, bool) override { return true; } @@ -152,8 +152,15 @@ int main() TestDeviceNAME.set_device_class_instance(0); TestDeviceNAME.set_manufacturer_code(1407); - auto TestInternalECU = isobus::InternalControlFunction::create(TestDeviceNAME, 247, 0); // The preferred address for a TC is defined in ISO 11783 - MyTCServer server(TestInternalECU, 4, 255, 16, 0x17); // 4 booms, 255 sections, 16 channels, some options configured using isobus::TaskControllerServer::ServerOptions, such as "Supports Documentation" + auto TestInternalECU = isobus::InternalControlFunction::create(TestDeviceNAME, isobus::preferred_addresses::IndustryGroup2::TaskController_MappingComputer, 0); + MyTCServer server(TestInternalECU, + 4, // Booms + 255, // Sections + 16, // Channels + isobus::TaskControllerOptions() + .with_documentation() + .with_implement_section_control() + .with_tc_geo_with_position_based_control()); auto &languageInterface = server.get_language_command_interface(); languageInterface.set_language_code("en"); // This is the default, but you can change it if you want languageInterface.set_country_code("US"); // This is the default, but you can change it if you want diff --git a/isobus/CMakeLists.txt b/isobus/CMakeLists.txt index a1a69e43..9e16ef87 100644 --- a/isobus/CMakeLists.txt +++ b/isobus/CMakeLists.txt @@ -44,6 +44,7 @@ set(ISOBUS_SRC "isobus_virtual_terminal_client_state_tracker.cpp" "isobus_virtual_terminal_client_update_helper.cpp" "isobus_task_controller_server.cpp" + "isobus_task_controller_server_options.cpp" "nmea2000_message_definitions.cpp" "nmea2000_message_interface.cpp" "can_message_data.cpp") @@ -91,6 +92,7 @@ set(ISOBUS_INCLUDE "isobus_virtual_terminal_client_state_tracker.hpp" "isobus_virtual_terminal_client_update_helper.hpp" "isobus_task_controller_server.hpp" + "isobus_task_controller_server_options.hpp" "nmea2000_message_definitions.hpp" "nmea2000_message_interface.hpp" "isobus_preferred_addresses.hpp" diff --git a/isobus/include/isobus/isobus/isobus_task_controller_server.hpp b/isobus/include/isobus/isobus/isobus_task_controller_server.hpp index cc78d295..3161a15f 100644 --- a/isobus/include/isobus/isobus/isobus_task_controller_server.hpp +++ b/isobus/include/isobus/isobus/isobus_task_controller_server.hpp @@ -5,16 +5,19 @@ /// the pure virtual functions to create your own task controller or data logger server. /// @author Adrian Del Grosso /// -/// @copyright 2023 Adrian Del Grosso +/// @copyright 2024 The Open-Agriculture Developers //================================================================================================ #ifndef ISOBUS_TASK_CONTROLLER_SERVER_HPP #define ISOBUS_TASK_CONTROLLER_SERVER_HPP #include "isobus/isobus/can_constants.hpp" #include "isobus/isobus/isobus_language_command_interface.hpp" +#include "isobus/isobus/isobus_task_controller_server_options.hpp" #include +#include + namespace isobus { /// @brief An ISO11783-10 task controller (or data logger) server. @@ -103,17 +106,30 @@ namespace isobus ProcessDataValueIsOutOfOperationalRangeOfThisDevice = 0x80 }; + /// @brief Enumerates the different versions of the task controller standard. + enum class TaskControllerVersion : std::uint8_t + { + DraftInternationalStandard = 0, ///< The version of the DIS (draft International Standard). + FinalDraftInternationalStandardFirstEdition = 1, ///< The version of the FDIS.1 (final draft International Standard, first edition). + FirstPublishedEdition = 2, ///< The version of the FDIS.2 and the first edition published ss an International Standard. + SecondEditionDraft = 3, ///< The version of the second edition published as a draft International Standard(E2.DIS). + SecondPublishedEdition = 4, ///< The version of the second edition published as the final draft International Standard(E2.FDIS) and as the International Standard(E2.IS) + Unknown = 0xFF + }; + /// @brief Constructor for a TC server. - /// @param internalControlFunction The control function to use to communicate with the clients. - /// @param numberBoomsSupported The number of booms to report as supported by the TC. - /// @param numberSectionsSupported The number of sections to report as supported by the TC. - /// @param numberChannelsSupportedForPositionBasedControl The number of channels to report as supported by the TC. - /// @param optionsBitfield The options bitfield to report as supported by the TC. See ServerOptions enum for the bit definitions. + /// @param[in] internalControlFunction The control function to use to communicate with the clients. + /// @param[in] numberBoomsSupported The number of booms to report as supported by the TC. + /// @param[in] numberSectionsSupported The number of sections to report as supported by the TC. + /// @param[in] numberChannelsSupportedForPositionBasedControl The number of channels to report as supported by the TC. + /// @param[in] options The options to report as supported by the TC. See the TaskControllerOptions object for more info. + /// @param[in] versionToReport The version of the task controller standard to report as supported by the TC. Generally you should leave this as 4 (SecondPublishedEdition). TaskControllerServer(std::shared_ptr internalControlFunction, std::uint8_t numberBoomsSupported, std::uint8_t numberSectionsSupported, std::uint8_t numberChannelsSupportedForPositionBasedControl, - std::uint8_t optionsBitfield); + const TaskControllerOptions &options, + TaskControllerVersion versionToReport = TaskControllerVersion::SecondPublishedEdition); /// @brief Destructor for a TC server. virtual ~TaskControllerServer(); @@ -124,67 +140,64 @@ namespace isobus /// @brief Deleted assignment operator TaskControllerServer &operator=(const TaskControllerServer &) = delete; - /// @brief Shuts down the TC server, unregisters PGN callbacks - void terminate(); - // **** Functions to be implemented by the consumer of the library **** /// @brief This function will be called by the server when the client wants to activate its DDOP. /// You should implement this function to activate the DDOP and return whether or not it was successful. /// Generally this means that you will want to parse the pool, and make sure its schema is valid at this time. /// You can use our DeviceDescriptorObjectPool class to help you with this. - /// @param clientControlFunction The control function which is requesting the activation. - /// @param activationError The error code to return if the activation fails. - /// @param objectPoolError This error code tells the client if there was an error in the DDOP. - /// @param parentObjectIDOfFaultyObject If there was an error in the DDOP, this is the parent object ID of the object that caused the error. Otherwise you should return 0xFFFF - /// @param faultyObjectID If there was an error in the DDOP, this is the object ID of the object that caused the error. Otherwise you should return 0xFFFF + /// @param[in] clientControlFunction The control function which is requesting the activation. + /// @param[out] activationError The error code to return if the activation fails. + /// @param[out] objectPoolError This error code tells the client if there was an error in the DDOP. + /// @param[out] parentObjectIDOfFaultyObject If there was an error in the DDOP, this is the parent object ID of the object that caused the error. Otherwise you should return 0xFFFF + /// @param[out] faultyObjectID If there was an error in the DDOP, this is the object ID of the object that caused the error. Otherwise you should return 0xFFFF /// @returns Whether or not the activation was successful. virtual bool activate_object_pool(std::shared_ptr clientControlFunction, ObjectPoolActivationError &activationError, ObjectPoolErrorCodes &objectPoolError, std::uint16_t &parentObjectIDOfFaultyObject, std::uint16_t &faultyObjectID) = 0; /// @brief This function will be called by the server when the client wants to change the designator of an object. /// This could be called because the client wants to change the name of an implement, or the name of a section, or change the active language being used in the DDOP's designators. /// You should implement this function to change the designator of the object and return whether or not it was successful. - /// @param clientControlFunction The control function which is requesting the designator change. - /// @param objectIDToAlter The object ID of the object to change the designator of. - /// @param designator The new designator to set for the object. + /// @param[in] clientControlFunction The control function which is requesting the designator change. + /// @param[in] objectIDToAlter The object ID of the object to change the designator of. + /// @param[in] designator The new designator to set for the object. /// @returns Whether or not the designator change was successful. virtual bool change_designator(std::shared_ptr clientControlFunction, std::uint16_t objectIDToAlter, const std::vector &designator) = 0; /// @brief This function will be called by the server when the client wants to deactivate its DDOP. /// You should implement this function to deactivate the DDOP and return whether or not it was successful. - /// @param clientControlFunction The control function which is requesting the deactivation. + /// @param[in] clientControlFunction The control function which is requesting the deactivation. /// @returns Whether or not the deactivation was successful. virtual bool deactivate_object_pool(std::shared_ptr clientControlFunction) = 0; /// @brief This function will be called by the server when the client wants to delete its DDOP. /// Each client is allowed to have one DDOP, so deletion is not required, but a client might be kind and delete its DDOP when it is no longer needed. /// You should implement this function to delete the DDOP and return whether or not it was successful. - /// @param clientControlFunction The control function which is requesting the deletion. - /// @param returnedErrorCode The error code to return if the deletion fails. + /// @param[in] clientControlFunction The control function which is requesting the deletion. + /// @param[out] returnedErrorCode The error code to return if the deletion fails. /// @returns Whether or not the deletion was successful. virtual bool delete_device_descriptor_object_pool(std::shared_ptr clientControlFunction, ObjectPoolDeletionErrors &returnedErrorCode) = 0; /// @brief This function will be called by the server when the server needs to know if it has previously saved to non volatile memory (NVM) /// a DDOP which is identified by the provided structure label, and optionally also the provided extended structure label. /// You should implement this function to return whether or not the DDOP is stored in NVM. - /// @param clientControlFunction The control function which is requesting the information. - /// @param structureLabel The structure label of the DDOP to check for. (always 7 bytes) - /// @param extendedStructureLabel The extended structure label of the DDOP to check for. (up to 32 bytes) + /// @param[in] clientControlFunction The control function which is requesting the information. + /// @param[in] structureLabel The structure label of the DDOP to check for. (always 7 bytes) + /// @param[in] extendedStructureLabel The extended structure label of the DDOP to check for. (up to 32 bytes) /// @returns Whether or not the DDOP is stored in NVM. virtual bool get_is_stored_device_descriptor_object_pool_by_structure_label(std::shared_ptr clientControlFunction, const std::vector &structureLabel, const std::vector &extendedStructureLabel) = 0; /// @brief This function will be called by the server when the server needs to know if it has previously saved to non volatile memory (NVM) /// a DDOP which is identified by the provided localization label. /// You should implement this function to return whether or not the DDOP is stored in NVM. - /// @param clientControlFunction The control function which is requesting the information. - /// @param localizationLabel The localization label of the DDOP to check for. + /// @param[in] clientControlFunction The control function which is requesting the information. + /// @param[in] localizationLabel The localization label of the DDOP to check for. /// @returns Whether or not the DDOP is stored in NVM. virtual bool get_is_stored_device_descriptor_object_pool_by_localization_label(std::shared_ptr clientControlFunction, const std::array &localizationLabel) = 0; /// @brief This function will be called by the server when the client wants to transfer its DDOP to the server and needs to know /// if the server has enough memory available to store the DDOP. /// You should implement this function to return whether or not the server has enough memory available to store the DDOP. - /// @param numberBytesRequired The number of bytes required to store the DDOP. + /// @param[in] numberBytesRequired The number of bytes required to store the DDOP. /// @returns Whether or not the server has enough memory available to store the DDOP. A value of true indicates: "There may be enough memory available. However, /// because there is overhead associated with object storage,it is impossible to predict whether there is enough memory available." and false indicates: /// "There is not enough memory available. Do not transmit device descriptor object pool." @@ -192,23 +205,23 @@ namespace isobus /// @brief This function will be called if someone requests that the TC identify itself. /// If this gets called, you should display the TC number for 3 seconds if your TC has a visual interface. - /// @param taskControllerNumber The task controller number to display. + /// @param[in] taskControllerNumber The task controller number to display. virtual void identify_task_controller(std::uint8_t taskControllerNumber) = 0; /// @brief This function will be called by the server when a connected client times out. /// You should implement this function to do whatever you want to do when a client times out. /// Generally this means you will want to also deactivate the DDOP for that client. - /// @param clientControlFunction The control function which timed out. + /// @param[in] clientControlFunction The control function which timed out. virtual void on_client_timeout(std::shared_ptr clientControlFunction) = 0; /// @brief This function will be called by the server when a client sends an acknowledgement for a /// process data command that was sent to it. /// This can be useful to know if the client received the command or not when using the set_value_and_acknowledge command. - /// @param clientControlFunction The control function which sent the acknowledgement. - /// @param dataDescriptionIndex The data description index of the data element that was acknowledged. - /// @param elementNumber The element number of the data element that was acknowledged. - /// @param errorCodesFromClient The error codes that the client sent in the acknowledgement. This will be a bitfield defined by the ProcessDataAcknowledgeErrorCodes enum. - /// @param processDataCommand The process data command that was acknowledged. + /// @param[in] clientControlFunction The control function which sent the acknowledgement. + /// @param[in] dataDescriptionIndex The data description index of the data element that was acknowledged. + /// @param[in] elementNumber The element number of the data element that was acknowledged. + /// @param[in] errorCodesFromClient The error codes that the client sent in the acknowledgement. This will be a bitfield defined by the ProcessDataAcknowledgeErrorCodes enum. + /// @param[in] processDataCommand The process data command that was acknowledged. virtual void on_process_data_acknowledge(std::shared_ptr clientControlFunction, std::uint16_t dataDescriptionIndex, std::uint16_t elementNumber, std::uint8_t errorCodesFromClient, ProcessDataCommands processDataCommand) = 0; /// @brief This function will be called by the server when a client sends a value command to the TC. @@ -217,11 +230,11 @@ namespace isobus /// The client could be telling you that a section's state changed, or that a boom's position changed, etc. Therefore /// this is probably the most important function to implement to get your TC "working". /// Use the ISOBUS data dictionary to determine what the dataDescriptionIndex and elementNumber mean. - /// @param clientControlFunction The control function which sent the value command. - /// @param dataDescriptionIndex The data description index of the data element that was sent. - /// @param elementNumber The element number of the data element that was sent. - /// @param processDataValue The process data value that was sent. - /// @param errorCodes You should return any errors that occurred while processing the value command in this variable as defined by the ProcessDataAcknowledgeErrorCodes enum. + /// @param[in] clientControlFunction The control function which sent the value command. + /// @param[in] dataDescriptionIndex The data description index of the data element that was sent. + /// @param[in] elementNumber The element number of the data element that was sent. + /// @param[in] processDataValue The process data value that was sent. + /// @param[out] errorCodes You should return any errors that occurred while processing the value command in this variable as defined by the ProcessDataAcknowledgeErrorCodes enum. /// This will be sent back to the client if an acknowledgement is requested. /// @returns Whether or not the value command was processed successfully. virtual bool on_value_command(std::shared_ptr clientControlFunction, std::uint16_t dataDescriptionIndex, std::uint16_t elementNumber, std::int32_t processDataValue, std::uint8_t &errorCodes) = 0; @@ -229,18 +242,18 @@ namespace isobus /// @brief This function is called when the server wants you to save a DDOP to non volatile memory (NVM). /// You should implement this function to save the DDOP to NVM. /// If appendToPool is true, you should append the DDOP to the existing DDOP in NVM. - /// @param clientControlFunction The control function which is requesting the save. - /// @param objectPoolData The DDOP itself as a binary blob. - /// @param appendToPool Whether or not to append the DDOP to the existing DDOP in NVM, or overwrite it. + /// @param[in] clientControlFunction The control function which is requesting the save. + /// @param[in] objectPoolData The DDOP itself as a binary blob. + /// @param[in] appendToPool Whether or not to append the DDOP to the existing DDOP in NVM, or overwrite it. /// @returns Whether or not the save was successful. virtual bool store_device_descriptor_object_pool(std::shared_ptr clientControlFunction, const std::vector &objectPoolData, bool appendToPool) = 0; // **** Functions used to communicate with the client **** /// @brief Sends a request to a client for an element's value of a particular DDI. - /// @param clientControlFunction The control function to send the message to - /// @param dataDescriptionIndex The Data Description Index being requested - /// @param elementNumber The element number being requested + /// @param[in] clientControlFunction The control function to send the message to + /// @param[in] dataDescriptionIndex The Data Description Index being requested + /// @param[in] elementNumber The element number being requested /// @returns true if the message was sent, otherwise false bool send_request_value(std::shared_ptr clientControlFunction, std::uint16_t dataDescriptionIndex, std::uint16_t elementNumber) const; @@ -248,10 +261,10 @@ namespace isobus /// The process data value for this command is the time interval for sending the data element /// specified by the data dictionary identifier.The client has to send the value of this data /// element to the TC or DL cyclic with this time interval. - /// @param clientControlFunction The control function to send the message to - /// @param dataDescriptionIndex The data description index of the data element to send the command for - /// @param elementNumber The element number of the data element to send the command for - /// @param timeInterval The time interval for sending the data element specified by the data dictionary identifier. + /// @param[in] clientControlFunction The control function to send the message to + /// @param[in] dataDescriptionIndex The data description index of the data element to send the command for + /// @param[in] elementNumber The element number of the data element to send the command for + /// @param[in] timeInterval The time interval for sending the data element specified by the data dictionary identifier. /// @returns true if the message was sent, otherwise false bool send_time_interval_measurement_command(std::shared_ptr clientControlFunction, std::uint16_t dataDescriptionIndex, std::uint16_t elementNumber, std::uint32_t timeInterval) const; @@ -259,10 +272,10 @@ namespace isobus /// The process data value for this command is the distance interval for sending the data element /// specified by the data dictionary identifier.The client has to send the value of this data /// element to the TC or DL cyclic with this distance interval. - /// @param clientControlFunction The control function to send the message to - /// @param dataDescriptionIndex The data description index of the data element to send the command for - /// @param elementNumber The element number of the data element to send the command for - /// @param distanceInterval The distance interval for sending the data element specified by the data dictionary identifier. + /// @param[in] clientControlFunction The control function to send the message to + /// @param[in] dataDescriptionIndex The data description index of the data element to send the command for + /// @param[in] elementNumber The element number of the data element to send the command for + /// @param[in] distanceInterval The distance interval for sending the data element specified by the data dictionary identifier. /// @returns true if the message was sent, otherwise false bool send_distance_interval_measurement_command(std::shared_ptr clientControlFunction, std::uint16_t dataDescriptionIndex, std::uint16_t elementNumber, std::uint32_t distanceInterval) const; @@ -270,10 +283,10 @@ namespace isobus /// The process data value for this command is the minimum threshold for sending the data element /// specified by the data dictionary identifier.The client has to send the value of this data /// element to the TC or DL when the value is higher than the threshold value. - /// @param clientControlFunction The control function to send the message to - /// @param dataDescriptionIndex The data description index of the data element to send the command for - /// @param elementNumber The element number of the data element to send the command for - /// @param minimum The minimum threshold for sending the data element specified by the data dictionary identifier. + /// @param[in] clientControlFunction The control function to send the message to + /// @param[in] dataDescriptionIndex The data description index of the data element to send the command for + /// @param[in] elementNumber The element number of the data element to send the command for + /// @param[in] minimum The minimum threshold for sending the data element specified by the data dictionary identifier. /// @returns true if the message was sent, otherwise false bool send_minimum_threshold_measurement_command(std::shared_ptr clientControlFunction, std::uint16_t dataDescriptionIndex, std::uint16_t elementNumber, std::uint32_t minimum) const; @@ -281,10 +294,10 @@ namespace isobus /// The process data value for this command is the maximum threshold for sending the data element /// specified by the data dictionary identifier.The client has to send the value of this data /// element to the TC or DL when the value is lower than the threshold value. - /// @param clientControlFunction The control function to send the message to - /// @param dataDescriptionIndex The data description index of the data element to send the command for - /// @param elementNumber The element number of the data element to send the command for - /// @param maximum The maximum threshold for sending the data element specified by the data dictionary identifier. + /// @param[in] clientControlFunction The control function to send the message to + /// @param[in] dataDescriptionIndex The data description index of the data element to send the command for + /// @param[in] elementNumber The element number of the data element to send the command for + /// @param[in] maximum The maximum threshold for sending the data element specified by the data dictionary identifier. /// @returns true if the message was sent, otherwise false bool send_maximum_threshold_measurement_command(std::shared_ptr clientControlFunction, std::uint16_t dataDescriptionIndex, std::uint16_t elementNumber, std::uint32_t maximum) const; @@ -292,27 +305,37 @@ namespace isobus /// The process data value for this command is the change threshold for sending the data element /// specified by the data dictionary identifier.The client has to send the value of this data /// element to the TC or DL when the value change is higher than or equal to the change threshold since last transmission. - /// @param clientControlFunction The control function to send the message to - /// @param dataDescriptionIndex The data description index of the data element to send the command for - /// @param elementNumber The element number of the data element to send the command for - /// @param threshold The change threshold for sending the data element specified by the data dictionary identifier. + /// @param[in] clientControlFunction The control function to send the message to + /// @param[in] dataDescriptionIndex The data description index of the data element to send the command for + /// @param[in] elementNumber The element number of the data element to send the command for + /// @param[in] threshold The change threshold for sending the data element specified by the data dictionary identifier. /// @returns true if the message was sent, otherwise false bool send_change_threshold_measurement_command(std::shared_ptr clientControlFunction, std::uint16_t dataDescriptionIndex, std::uint16_t elementNumber, std::uint32_t threshold) const; /// @brief Sends a set value and acknowledge command. /// This command is used to set the value of a process data entity and request a reception acknowledgement from the recipient. /// The set value command process data value is the value of the data entity specified by the data dictionary identifier. - /// @param clientControlFunction The control function to send the message to - /// @param dataDescriptionIndex The data description index of the data element to send the command for - /// @param elementNumber The element number of the data element to send the command for - /// @param processDataValue The process data value to send + /// @param[in] clientControlFunction The control function to send the message to + /// @param[in] dataDescriptionIndex The data description index of the data element to send the command for + /// @param[in] elementNumber The element number of the data element to send the command for + /// @param[in] processDataValue The process data value to send /// @returns true if the message was sent, otherwise false bool send_set_value_and_acknowledge(std::shared_ptr clientControlFunction, std::uint16_t dataDescriptionIndex, std::uint16_t elementNumber, std::uint32_t processDataValue) const; + /// @brief Sends a set value command without requesting an acknowledgement. + /// This command is used to set the value of a process data entity. + /// The set value command process data value is the value of the data entity specified by the data dictionary identifier. + /// @param[in] clientControlFunction The control function to send the message to + /// @param[in] dataDescriptionIndex The data description index of the data element to send the command for + /// @param[in] elementNumber The element number of the data element to send the command for + /// @param[in] processDataValue The process data value to send + /// @returns true if the message was sent, otherwise false + bool send_set_value(std::shared_ptr clientControlFunction, std::uint16_t dataDescriptionIndex, std::uint16_t elementNumber, std::uint32_t processDataValue) const; + /// @brief Use this to set the reported task state in the status message. /// Basically, this should be set to true when the user starts a job, and false when the user stops a job. /// @note Don't be like some terminals which set this to true all the time, that's very annoying for the client. - /// @param isTaskActive Whether a task is currently active or not. + /// @param[in] isTaskActive Whether a task is currently active or not. void set_task_totals_active(bool isTaskActive); /// @brief Returns whether a task is currently active or not. @@ -325,6 +348,13 @@ namespace isobus /// @returns The language command interface used to communicate with the client which language/units are in use. LanguageCommandInterface &get_language_command_interface(); +#if !defined CAN_STACK_DISABLE_THREADS && !defined ARDUINO + /// @brief Returns a condition variable which you can optionally use to wake up your server's thread + /// when messages are received from the client. + /// @returns A condition variable which you can optionally use to wake up your server's thread + std::condition_variable &get_condition_variable(); +#endif + // **** Functions used to initialize and run the server **** /// @brief Initializes the task controller server. @@ -334,24 +364,20 @@ namespace isobus /// @returns Whether or not the task controller server has been initialized. bool get_initialized() const; - /// @brief This must be called cyclically for the interface to operate correctly. - /// You can run this in a separate thread or in the main loop of your program, but - /// it must be called at least around 2-3x as fast as your fastest triggered message. - /// 50ms may be a good value to start with. - void update(); + /// @brief Shuts down the TC server, unregisters PGN callbacks + void terminate(); - private: - /// @brief Enumerates the different versions of the task controller standard. - enum class TaskControllerVersion : std::uint8_t - { - DraftInternationalStandard = 0, ///< The version of the DIS (draft International Standard). - FinalDraftInternationalStandardFirstEdition = 1, ///< The version of the FDIS.1 (final draft International Standard, first edition). - FirstPublishedEdition = 2, ///< The version of the FDIS.2 and the first edition published ss an International Standard. - SecondEditionDraft = 3, ///< The version of the second edition published as a draft International Standard(E2.DIS). - SecondPublishedEdition = 4, ///< The version of the second edition published as the final draft International Standard(E2.FDIS) and as the International Standard(E2.IS) - Unknown = 0xFF - }; + /// @brief This must be called periodically for the interface to operate correctly. + /// @details This function must be called periodically. You have some choices on how to do this: + /// First, you could poll it at a high rate in your main thread, at least 2-3x as fast as your fastest triggered message. + /// Second, you could call it at a slower rate (something like 250-500 ms), and update it when the condition variable is notified. + /// You can get the condition variable by calling get_condition_variable() if threading is enabled in the CAN stack. + /// Third, you could run this in a separate thread, but again, you should call it at least 2-3x as fast as your fastest triggered message. + /// Calling this often ensures timed out clients are pruned, and messages are processed in a timely fashion, which + /// is important for the TC to function correctly and for agronomic/implement performance. + void update(); + protected: /// @brief Enumerates the different status bits that can be sent in the status message. enum class ServerStatusBit : std::uint8_t { @@ -406,26 +432,35 @@ namespace isobus }; /// @brief Stores messages received from task controller clients for processing later. - /// @param message The message received from the client. - /// @param parentPointer A context variable that can be used to find this class's instance. + /// @details This is used to avoid processing messages on the CAN stack's thread. + /// Messages are actually processed in process_rx_messages which is called by update(). + /// Because update is called by your application, this means that messages are processed on your application's thread, + /// which avoids a bunch of mutexing in your app. + /// @param[in] message The message received from the client. + /// @param[in] parentPointer A context variable that can be used to find this class's instance. static void store_rx_message(const CANMessage &message, void *parentPointer); /// @brief Processes messages received from task controller clients. + /// @details This is called by update() and processes messages that were received from clients. + /// Because update is called by your application, this means that messages are processed on your application's thread, + /// rather than on the CAN stack's thread, which avoids a bunch of mutexing in your app. + /// You can get a condition variable from get_condition_variable() which you can use to wake up your application's thread + /// to process messages if you want to avoid polling the interface at a high rate. void process_rx_messages(); /// @brief This sends a process data message with all FFs in the payload except for the command byte. /// Useful for avoiding a lot of boilerplate code when sending process data messages. - /// @param multiplexer The multiplexer value to send in the message. - /// @param destination The control function to send the message to. + /// @param[in] multiplexer The multiplexer value to send in the message. + /// @param[in] destination The control function to send the message to. /// @returns true if the message was sent, otherwise false bool send_generic_process_data_default_payload(std::uint8_t multiplexer, std::shared_ptr destination) const; /// @brief Sends a measurement command to the client. - /// @param clientControlFunction The control function to send the message to - /// @param commandValue The command value to send - /// @param dataDescriptionIndex The data description index of the data element to send the command for - /// @param elementNumber The element number of the data element to send the command for - /// @param processDataValue The process data value to send + /// @param[in] clientControlFunction The control function to send the message to + /// @param[in] commandValue The command value to send + /// @param[in] dataDescriptionIndex The data description index of the data element to send the command for + /// @param[in] elementNumber The element number of the data element to send the command for + /// @param[in] processDataValue The process data value to send /// @returns true if the message was sent, otherwise false bool send_measurement_command(std::shared_ptr clientControlFunction, std::uint8_t commandValue, std::uint16_t dataDescriptionIndex, std::uint16_t elementNumber, std::uint32_t processDataValue) const; @@ -434,54 +469,54 @@ namespace isobus bool send_status_message() const; /// @brief Sends the version message to a client - /// @param clientControlFunction The control function to send the message to + /// @param[in] clientControlFunction The control function to send the message to /// @returns true if the message was sent, otherwise false bool send_version(std::shared_ptr clientControlFunction) const; /// @brief Checks to see if we are communicating with a control function that is already in our list of active clients. /// If we are, it returns a pointer to our active client object for that control function. - /// @param clientControlFunction The control function to check for. + /// @param[in] clientControlFunction The control function to check for. /// @returns A pointer to our active client object for that control function, or nullptr if we are not communicating with that control function. std::shared_ptr get_active_client(std::shared_ptr clientControlFunction) const; /// @brief Sends a negative acknowledge for a the process data PGN which indicates to clients /// that we aren't listening to them because they aren't following the protocol. - /// @param clientControlFunction The control function to send the message to + /// @param[in] clientControlFunction The control function to send the message to /// @returns true if the message was sent, otherwise false bool nack_process_data_command(std::shared_ptr clientControlFunction) const; /// @brief Sends a response to a request structure label command. - /// @param clientControlFunction The control function to send the message to - /// @param structureLabel The structure label to send - /// @param extendedStructureLabel The extended structure label to send + /// @param[in] clientControlFunction The control function to send the message to + /// @param[in] structureLabel The structure label to send + /// @param[in] extendedStructureLabel The extended structure label to send /// @returns true if the message was sent, otherwise false - bool send_structure_label(std::shared_ptr clientControlFunction, std::vector &structureLabel, std::vector &extendedStructureLabel); + bool send_structure_label(std::shared_ptr clientControlFunction, std::vector &structureLabel, const std::vector &extendedStructureLabel) const; /// @brief Sends a response to a request localization label command. - /// @param clientControlFunction The control function to send the message to - /// @param localizationLabel The localization label to send + /// @param[in] clientControlFunction The control function to send the message to + /// @param[in] localizationLabel The localization label to send /// @returns true if the message was sent, otherwise false - bool send_localization_label(std::shared_ptr clientControlFunction, std::array localizationLabel); + bool send_localization_label(std::shared_ptr clientControlFunction, const std::array &localizationLabel) const; /// @brief Sends a response to a request object pool transfer command. - /// @param clientControlFunction The control function to send the message to - /// @param isEnoughMemory Whether or not there is enough memory available to transfer the object pool. + /// @param[in] clientControlFunction The control function to send the message to + /// @param[in] isEnoughMemory Whether or not there is enough memory available to transfer the object pool. /// @returns true if the message was sent, otherwise false - bool send_request_object_pool_transfer_response(std::shared_ptr clientControlFunction, bool isEnoughMemory); + bool send_request_object_pool_transfer_response(std::shared_ptr clientControlFunction, bool isEnoughMemory) const; /// @brief Sends a response to an object pool transfer - /// @param clientControlFunction The control function to send the message to - /// @param errorBitfield The error bitfield to send - /// @param sizeBytes The size of the object pool in bytes + /// @param[in] clientControlFunction The control function to send the message to + /// @param[in] errorBitfield The error bitfield to send + /// @param[in] sizeBytes The size of the object pool in bytes /// @returns true if the message was sent, otherwise false bool send_object_pool_transfer_response(std::shared_ptr clientControlFunction, std::uint8_t errorBitfield, std::uint32_t sizeBytes) const; /// @brief Sends a response to an object pool activate/deactivate command - /// @param clientControlFunction The control function to send the message to - /// @param activationErrorBitfield The error bitfield to send for the activation - /// @param objectPoolErrorBitfield The error bitfield to send for the object pool - /// @param parentOfFaultingObject The parent of the object that caused the error (or 0xFFFF if there is no error or it's unknown) - /// @param faultingObject The object that caused the error (or 0xFFFF if there is no error or it's unknown) + /// @param[in] clientControlFunction The control function to send the message to + /// @param[in] activationErrorBitfield The error bitfield to send for the activation + /// @param[in] objectPoolErrorBitfield The error bitfield to send for the object pool + /// @param[in] parentOfFaultingObject The parent of the object that caused the error (or 0xFFFF if there is no error or it's unknown) + /// @param[in] faultingObject The object that caused the error (or 0xFFFF if there is no error or it's unknown) /// @returns true if the message was sent, otherwise false bool send_object_pool_activate_deactivate_response(std::shared_ptr clientControlFunction, std::uint8_t activationErrorBitfield, @@ -490,36 +525,51 @@ namespace isobus std::uint16_t faultingObject) const; /// @brief Sends a response to a delete object pool command - /// @param clientControlFunction The control function to send the message to - /// @param deletionResult Whether or not the object pool was deleted - /// @param errorCode The error code to send + /// @param[in] clientControlFunction The control function to send the message to + /// @param[in] deletionResult Whether or not the object pool was deleted + /// @param[in] errorCode The error code to send /// @returns true if the message was sent, otherwise false bool send_delete_object_pool_response(std::shared_ptr clientControlFunction, bool deletionResult, std::uint8_t errorCode) const; /// @brief Sends a response to a change designator command - /// @param clientControlFunction The control function to send the message to - /// @param objectID The object ID that was changed - /// @param errorCode The error code to send + /// @param[in] clientControlFunction The control function to send the message to + /// @param[in] objectID The object ID that was changed + /// @param[in] errorCode The error code to send /// @returns true if the message was sent, otherwise false bool send_change_designator_response(std::shared_ptr clientControlFunction, std::uint16_t objectID, std::uint8_t errorCode) const; /// @brief Sends a process data acknowledge message to the client - /// @param clientControlFunction The control function to send the message to - /// @param dataDescriptionIndex The data description index of the data element that was acknowledged. - /// @param elementNumber The element number of the data element that was acknowledged. - /// @param errorBitfield The error bitfield to send (see ProcessDataAcknowledgeErrorCodes enum) - /// @param processDataCommand The process data command that was acknowledged. (or 0x0F if N/A) + /// @param[in] clientControlFunction The control function to send the message to + /// @param[in] dataDescriptionIndex The data description index of the data element that was acknowledged. + /// @param[in] elementNumber The element number of the data element that was acknowledged. + /// @param[in] errorBitfield The error bitfield to send (see ProcessDataAcknowledgeErrorCodes enum) + /// @param[in] processDataCommand The process data command that was acknowledged. (or 0x0F if N/A) /// @returns true if the message was sent, otherwise false bool send_process_data_acknowledge(std::shared_ptr clientControlFunction, std::uint16_t dataDescriptionIndex, std::uint16_t elementNumber, std::uint8_t errorBitfield, ProcessDataCommands processDataCommand) const; + /// @brief Sends a process data message to a client with a slightly shorter signature than calling send_can_message. + /// @param[in] clientControlFunction The control function to send the message to + /// @param[in] dataBuffer The data to send + /// @param[in] dataLength The data buffer length + /// @param[in] priority The priority of the message, normally 5 + /// @returns true if the message was sent, otherwise false + bool send_process_data_to_client(std::shared_ptr clientControlFunction, + const std::uint8_t *dataBuffer, + std::uint32_t dataLength, + CANIdentifier::CANPriority priority = CANIdentifier::CANPriority::Priority5) const; + static constexpr std::uint32_t STATUS_MESSAGE_RATE_MS = 2000; ///< The rate at which status messages are sent to the clients in milliseconds. LanguageCommandInterface languageCommandInterface; ///< The language command interface used to communicate with the client which language/units are in use. std::shared_ptr serverControlFunction; ///< The control function used to communicate with the clients. std::deque rxMessageQueue; ///< A queue of messages received from the clients which will be processed when update is called. std::deque> activeClients; ///< A list of clients that are currently being communicated with. - std::mutex taskControllerMutex; ///< A mutex used to protect the task controller server from concurrent access. +#if !defined CAN_STACK_DISABLE_THREADS && !defined ARDUINO + std::condition_variable updateWakeupCondition; ///< A condition variable you can optionally use to update the interface when messages are received + std::mutex messagesMutex; ///< A mutex used to protect the rxMessageQueue. +#endif std::uint32_t lastStatusMessageTimestamp_ms = 0; ///< The timestamp of the last status message sent on the bus + const TaskControllerVersion reportedVersion; ///< The version of the TC that will be reported to the clients. const std::uint8_t numberBoomsSupportedToReport; ///< The number of booms that will be reported as supported by the TC. const std::uint8_t numberSectionsSupportedToReport; ///< The number of sections that will be reported as supported by the TC. const std::uint8_t numberChannelsSupportedForPositionBasedControlToReport; ///< The number of channels that will be reported as supported by the TC. diff --git a/isobus/include/isobus/isobus/isobus_task_controller_server_options.hpp b/isobus/include/isobus/isobus/isobus_task_controller_server_options.hpp new file mode 100644 index 00000000..5b256883 --- /dev/null +++ b/isobus/include/isobus/isobus/isobus_task_controller_server_options.hpp @@ -0,0 +1,83 @@ +//================================================================================================ +/// @file isobus_task_controller_server_options.hpp +/// +/// @brief Defines a helper class to assign TC server options. +/// @author Adrian Del Grosso +/// +/// @copyright 2024 The Open-Agriculture Developers +//================================================================================================ +#ifndef ISOBUS_TASK_CONTROLLER_SERVER_OPTIONS_HPP +#define ISOBUS_TASK_CONTROLLER_SERVER_OPTIONS_HPP + +#include + +namespace isobus +{ + /// @brief A helper class to assign TC server options. + /// You can use this by doing something like this: + /// TaskControllerServer::TaskControllerOptions().with_documentation().with_tc_geo_with_position_based_control(); + /// Or you can set the settings yourself, manually. + class TaskControllerOptions + { + public: + /// @brief Constructor for a TC server options helper class. + TaskControllerOptions() = default; + + /// @brief Copy constructor for a TC server options helper class. + TaskControllerOptions(const TaskControllerOptions &) = default; + + /// @brief Assignment operator for a TC server options helper class. + /// @returns A reference to the current object. + TaskControllerOptions &operator=(const TaskControllerOptions &) = default; + + /// @brief Alters the settings object to indicate you want to support documentation. + /// @param[in] supported Whether or not the TC supports documentation. + /// @returns An updated settings object. + TaskControllerOptions with_documentation(bool supported = true); + + /// @brief Alters the settings object to indicate you want to support tc-geo without position based control. + /// @param[in] supported Whether or not the TC supports tc-geo without position based control. + /// @returns An updated settings object. + TaskControllerOptions with_tc_geo_without_position_based_control(bool supported = true) const; + + /// @brief Alters the settings object to indicate you want to support tc-geo with position based control. + /// @param[in] supported Whether or not the TC supports tc-geo with position based control. + /// @returns An updated settings object. + TaskControllerOptions with_tc_geo_with_position_based_control(bool supported = true) const; + + /// @brief Alters the settings object to indicate you want to support peer control assignment. + /// @param[in] supported Whether or not the TC supports peer control assignment. + /// @returns An updated settings object. + TaskControllerOptions with_peer_control_assignment(bool supported = true) const; + + /// @brief Alters the settings object to indicate you want to support implement section control. + /// @param[in] supported Whether or not the TC supports implement section control. + /// @returns An updated settings object. + TaskControllerOptions with_implement_section_control(bool supported = true) const; + + /// @brief Sets the settings object to the provided options bitfield. + /// @param[in] supportsDocumentation Whether or not the TC supports documentation. + /// @param[in] supportsTCGEOWithoutPositionBasedControl Whether or not the TC supports tc-geo without position based control. + /// @param[in] supportsTCGEOWithPositionBasedControl Whether or not the TC supports tc-geo with position based control. + /// @param[in] supportsPeerControlAssignment Whether or not the TC supports peer control assignment. + /// @param[in] supportsImplementSectionControl Whether or not the TC supports implement section control. + void set_settings( + bool supportsDocumentation, + bool supportsTCGEOWithoutPositionBasedControl, + bool supportsTCGEOWithPositionBasedControl, + bool supportsPeerControlAssignment, + bool supportsImplementSectionControl); + + /// @brief Gets the options bitfield that was set by the user. + /// @returns The options bitfield that was set by the user. + std::uint8_t get_bitfield() const; + + private: + bool optionDocumentation = false; ///< Bit 0, defines whether or not the TC supports documentation. + bool optionTCGEOWithoutPositionBasedControl = false; ///< Bit 1, defines whether or not the TC supports tc-geo without position based control. + bool optionTCGEOWithPositionBasedControl = false; ///< Bit 2, defines whether or not the TC supports tc-geo with position based control. + bool optionPeerControlAssignment = false; ///< Bit 3, defines whether or not the TC supports peer control assignment. + bool optionImplementSectionControl = false; ///< Bit 4, defines whether or not the TC supports implement section control. + }; +} // namespace isobus +#endif // ISOBUS_TASK_CONTROLLER_SERVER_OPTIONS_HPP diff --git a/isobus/src/isobus_task_controller_server.cpp b/isobus/src/isobus_task_controller_server.cpp index 5cd228c0..5a96d974 100644 --- a/isobus/src/isobus_task_controller_server.cpp +++ b/isobus/src/isobus_task_controller_server.cpp @@ -6,7 +6,7 @@ /// task controller or data logger server. /// @author Adrian Del Grosso /// -/// @copyright 2023 Adrian Del Grosso +/// @copyright 2024 The Open-Agriculture Developers //================================================================================================ #include "isobus/isobus/isobus_task_controller_server.hpp" @@ -23,13 +23,15 @@ namespace isobus std::uint8_t numberBoomsSupported, std::uint8_t numberSectionsSupported, std::uint8_t numberChannelsSupportedForPositionBasedControl, - std::uint8_t optionsBitfield) : + const TaskControllerOptions &options, + TaskControllerVersion versionToReport) : languageCommandInterface(internalControlFunction, true), serverControlFunction(internalControlFunction), + reportedVersion(versionToReport), numberBoomsSupportedToReport(numberBoomsSupported), numberSectionsSupportedToReport(numberSectionsSupported), numberChannelsSupportedForPositionBasedControlToReport(numberChannelsSupportedForPositionBasedControl), - optionsBitfieldToReport(optionsBitfield) + optionsBitfieldToReport(options.get_bitfield()) { } @@ -38,35 +40,22 @@ namespace isobus terminate(); } - void TaskControllerServer::terminate() - { - if (initialized) - { - initialized = false; - CANNetworkManager::CANNetwork.remove_any_control_function_parameter_group_number_callback(static_cast(CANLibParameterGroupNumber::ProcessData), store_rx_message, this); - CANNetworkManager::CANNetwork.remove_any_control_function_parameter_group_number_callback(static_cast(CANLibParameterGroupNumber::WorkingSetMaster), store_rx_message, this); - } - } - bool TaskControllerServer::send_request_value(std::shared_ptr clientControlFunction, std::uint16_t dataDescriptionIndex, std::uint16_t elementNumber) const { std::array payload = { 0 }; payload[0] = (static_cast(ProcessDataCommands::RequestValue) & 0x0F); - payload[0] |= (elementNumber && 0x0F) << 4; - payload[1] = static_cast((elementNumber >> 4) & 0xFF); - payload[2] = static_cast(dataDescriptionIndex & 0xFF); - payload[3] = static_cast((dataDescriptionIndex >> 8) & 0xFF); + payload[0] |= (elementNumber & 0x0F) << 4; + payload[1] = static_cast(elementNumber >> 4); + payload[2] = static_cast(dataDescriptionIndex); + payload[3] = static_cast(dataDescriptionIndex >> 8); payload[4] = 0xFF; payload[5] = 0xFF; payload[6] = 0xFF; payload[7] = 0xFF; - return CANNetworkManager::CANNetwork.send_can_message(static_cast(CANLibParameterGroupNumber::ProcessData), - payload.data(), - payload.size(), - serverControlFunction, - clientControlFunction, - CANIdentifier::CANPriority::Priority5); + return send_process_data_to_client(clientControlFunction, + payload.data(), + payload.size()); } bool TaskControllerServer::send_time_interval_measurement_command(std::shared_ptr clientControlFunction, std::uint16_t dataDescriptionIndex, std::uint16_t elementNumber, std::uint32_t timeInterval) const @@ -99,6 +88,11 @@ namespace isobus return send_measurement_command(clientControlFunction, static_cast(ProcessDataCommands::SetValueAndAcknowledge), dataDescriptionIndex, elementNumber, processDataValue); } + bool TaskControllerServer::send_set_value(std::shared_ptr clientControlFunction, std::uint16_t dataDescriptionIndex, std::uint16_t elementNumber, std::uint32_t processDataValue) const + { + return send_measurement_command(clientControlFunction, static_cast(ProcessDataCommands::Value), dataDescriptionIndex, elementNumber, processDataValue); + } + void TaskControllerServer::set_task_totals_active(bool isTaskActive) { if (isTaskActive != get_task_totals_active()) @@ -131,11 +125,28 @@ namespace isobus return initialized; } + void TaskControllerServer::terminate() + { + if (initialized) + { + initialized = false; + CANNetworkManager::CANNetwork.remove_any_control_function_parameter_group_number_callback(static_cast(CANLibParameterGroupNumber::ProcessData), store_rx_message, this); + CANNetworkManager::CANNetwork.remove_any_control_function_parameter_group_number_callback(static_cast(CANLibParameterGroupNumber::WorkingSetMaster), store_rx_message, this); + } + } + LanguageCommandInterface &TaskControllerServer::get_language_command_interface() { return languageCommandInterface; } +#if !defined CAN_STACK_DISABLE_THREADS && !defined ARDUINO + std::condition_variable &TaskControllerServer::get_condition_variable() + { + return updateWakeupCondition; + } +#endif + void TaskControllerServer::update() { process_rx_messages(); @@ -170,15 +181,25 @@ namespace isobus { if (nullptr != parentPointer) { - static_cast(parentPointer)->rxMessageQueue.push_back(message); + auto server = static_cast(parentPointer); +#if !defined CAN_STACK_DISABLE_THREADS && !defined ARDUINO + const std::lock_guard lock(server->messagesMutex); + server->rxMessageQueue.push_back(message); + server->updateWakeupCondition.notify_all(); +#else + server->rxMessageQueue.push_back(message); +#endif } } void TaskControllerServer::process_rx_messages() { +#if !defined CAN_STACK_DISABLE_THREADS && !defined ARDUINO + const std::lock_guard lock(messagesMutex); +#endif while (!rxMessageQueue.empty()) { - auto &rxMessage = rxMessageQueue.front(); + const auto &rxMessage = rxMessageQueue.front(); auto &rxData = rxMessage.get_data(); switch (rxMessage.get_identifier().get_parameter_group_number()) @@ -315,10 +336,7 @@ namespace isobus { if (nullptr != get_active_client(rxMessage.get_source_control_function())) { - std::uint32_t requestedSize = static_cast(rxData[1]) | - (static_cast(rxData[2]) << 8) | - (static_cast(rxData[3]) << 16) | - (static_cast(rxData[4]) << 24); + std::uint32_t requestedSize = rxMessage.get_uint32_at(1); if ((requestedSize <= CANMessage::ABSOLUTE_MAX_MESSAGE_LENGTH) && (get_is_enough_memory_available(requestedSize))) @@ -356,12 +374,12 @@ namespace isobus if (store_device_descriptor_object_pool(rxMessage.get_source_control_function(), objectPool, 0 != get_active_client(rxMessage.get_source_control_function())->numberOfObjectPoolSegments)) { CANStackLogger::info("[TC Server]: Stored DDOP segment for client 0x%02X", rxMessage.get_source_control_function()->get_address()); - send_object_pool_transfer_response(rxMessage.get_source_control_function(), 0, objectPool.size()); // No error, transfer OK + send_object_pool_transfer_response(rxMessage.get_source_control_function(), 0, static_cast(objectPool.size())); // No error, transfer OK } else { CANStackLogger::error("[TC Server]: Failed to store DDOP segment for client 0x%02X. Reporting to the client as \"Any other error\"", rxMessage.get_source_control_function()->get_address()); - send_object_pool_transfer_response(rxMessage.get_source_control_function(), 2, objectPool.size()); + send_object_pool_transfer_response(rxMessage.get_source_control_function(), 2, static_cast(objectPool.size())); } } else @@ -391,7 +409,7 @@ namespace isobus { CANStackLogger::info("[TC Server]: Object pool activated for client 0x%02X", rxMessage.get_source_control_function()->get_address()); client->isDDOPActive = true; - send_object_pool_activate_deactivate_response(rxMessage.get_source_control_function(), static_cast(activationError), static_cast(errorCode), faultingParentObject, faultingObject); + send_object_pool_activate_deactivate_response(rxMessage.get_source_control_function(), 0, 0, 0xFFFF, 0xFFFF); } else { @@ -457,7 +475,7 @@ namespace isobus { if (get_active_client(rxMessage.get_source_control_function())->isDDOPActive) { - std::uint16_t objectID = static_cast(rxData[1]) | (static_cast(rxData[2]) << 8); + std::uint16_t objectID = rxMessage.get_uint16_at(1); std::vector newDesignatorUTF8Bytes; for (std::size_t i = 0; i < rxData.size() - 3; i++) @@ -521,12 +539,9 @@ namespace isobus { if (get_active_client(rxMessage.get_source_control_function())->isDDOPActive) { - std::uint16_t DDI = static_cast(rxData[2]) | (static_cast(rxData[3]) << 8); + std::uint16_t DDI = rxMessage.get_uint16_at(2); std::uint16_t elementNumber = static_cast(rxData[0] >> 4) | (static_cast(rxData[1]) << 4); - std::int32_t processVariableValue = static_cast(static_cast(rxData[4]) | - (static_cast(rxData[5]) << 8) | - (static_cast(rxData[6]) << 16) | - (static_cast(rxData[7]) << 24)); + std::int32_t processVariableValue = rxMessage.get_int32_at(4); std::uint8_t errorCodes = 0; if (on_value_command(rxMessage.get_source_control_function(), DDI, elementNumber, processVariableValue, errorCodes)) @@ -567,7 +582,7 @@ namespace isobus { if (nullptr != get_active_client(rxMessage.get_source_control_function())) { - std::uint16_t DDI = static_cast(rxData[2]) | (static_cast(rxData[3]) << 8); + std::uint16_t DDI = rxMessage.get_uint16_at(2); std::uint16_t elementNumber = static_cast(rxData[0] >> 4) | (static_cast(rxData[1]) << 4); if (get_active_client(rxMessage.get_source_control_function())->isDDOPActive) @@ -618,7 +633,7 @@ namespace isobus { if (CAN_DATA_LENGTH == rxMessage.get_data_length()) { - for (auto &activeClient : activeClients) + for (const auto &activeClient : activeClients) { if ((nullptr != activeClient) && (activeClient->clientControlFunction == rxMessage.get_source_control_function())) @@ -720,7 +735,10 @@ namespace isobus default: break; } - return CANNetworkManager::CANNetwork.send_can_message(static_cast(CANLibParameterGroupNumber::ProcessData), payload.data(), payload.size(), serverControlFunction, destination, priority); + return send_process_data_to_client(destination, + payload.data(), + payload.size(), + priority); } bool TaskControllerServer::send_measurement_command(std::shared_ptr clientControlFunction, std::uint8_t commandValue, std::uint16_t dataDescriptionIndex, std::uint16_t elementNumber, std::uint32_t processDataValue) const @@ -731,21 +749,19 @@ namespace isobus { std::array payload = { 0 }; payload[0] = (commandValue & 0x0F); - payload[0] |= (elementNumber && 0x0F) << 4; - payload[1] = static_cast((elementNumber >> 4) & 0xFF); - payload[2] = static_cast(dataDescriptionIndex & 0xFF); - payload[3] = static_cast((dataDescriptionIndex >> 8) & 0xFF); - payload[4] = static_cast(processDataValue & 0xFF); - payload[5] = static_cast((processDataValue >> 8) & 0xFF); - payload[6] = static_cast((processDataValue >> 16) & 0xFF); - payload[7] = static_cast((processDataValue >> 24) & 0xFF); - - retVal = CANNetworkManager::CANNetwork.send_can_message(static_cast(CANLibParameterGroupNumber::ProcessData), - payload.data(), - payload.size(), - serverControlFunction, - clientControlFunction, - static_cast(ProcessDataCommands::SetValueAndAcknowledge) == commandValue ? CANIdentifier::CANPriority::Priority3 : CANIdentifier::CANPriority::Priority5); + payload[0] |= (elementNumber & 0x0F) << 4; + payload[1] = static_cast(elementNumber >> 4); + payload[2] = static_cast(dataDescriptionIndex); + payload[3] = static_cast(dataDescriptionIndex >> 8); + payload[4] = static_cast(processDataValue); + payload[5] = static_cast(processDataValue >> 8); + payload[6] = static_cast(processDataValue >> 16); + payload[7] = static_cast(processDataValue >> 24); + + retVal = send_process_data_to_client(clientControlFunction, + payload.data(), + payload.size(), + static_cast(ProcessDataCommands::SetValueAndAcknowledge) == commandValue ? CANIdentifier::CANPriority::Priority3 : CANIdentifier::CANPriority::Priority5); } return retVal; } @@ -763,19 +779,17 @@ namespace isobus 0xFF }; - return CANNetworkManager::CANNetwork.send_can_message(static_cast(CANLibParameterGroupNumber::ProcessData), - payload.data(), - payload.size(), - serverControlFunction, - nullptr, - CANIdentifier::CANPriority::Priority3); + return send_process_data_to_client(nullptr, + payload.data(), + payload.size(), + CANIdentifier::CANPriority::Priority3); } bool TaskControllerServer::send_version(std::shared_ptr clientControlFunction) const { std::array payload = { static_cast(TechnicalDataCommandParameters::ParameterVersion) << 4, - static_cast(TaskControllerVersion::SecondPublishedEdition), + static_cast(reportedVersion), 0xFF, optionsBitfieldToReport, 0x00, @@ -783,12 +797,9 @@ namespace isobus numberSectionsSupportedToReport, numberChannelsSupportedForPositionBasedControlToReport }; - return CANNetworkManager::CANNetwork.send_can_message(static_cast(CANLibParameterGroupNumber::ProcessData), - payload.data(), - payload.size(), - serverControlFunction, - clientControlFunction, - CANIdentifier::CANPriority::Priority5); + return send_process_data_to_client(clientControlFunction, + payload.data(), + payload.size()); } std::shared_ptr TaskControllerServer::get_active_client(std::shared_ptr clientControlFunction) const @@ -834,7 +845,7 @@ namespace isobus return retVal; } - bool TaskControllerServer::send_structure_label(std::shared_ptr clientControlFunction, std::vector &structureLabel, std::vector &extendedStructureLabel) + bool TaskControllerServer::send_structure_label(std::shared_ptr clientControlFunction, std::vector &structureLabel, const std::vector &extendedStructureLabel) const { bool retVal = false; @@ -856,17 +867,14 @@ namespace isobus { payload.push_back(extendedLabelByte); } - retVal = CANNetworkManager::CANNetwork.send_can_message(static_cast(CANLibParameterGroupNumber::ProcessData), - payload.data(), - payload.size(), - serverControlFunction, - clientControlFunction, - CANIdentifier::CANPriority::Priority5); + retVal = send_process_data_to_client(clientControlFunction, + payload.data(), + payload.size()); } return retVal; } - bool TaskControllerServer::send_localization_label(std::shared_ptr clientControlFunction, std::array localizationLabel) + bool TaskControllerServer::send_localization_label(std::shared_ptr clientControlFunction, const std::array &localizationLabel) const { bool retVal = false; @@ -883,17 +891,14 @@ namespace isobus localizationLabel[6] }; - retVal = CANNetworkManager::CANNetwork.send_can_message(static_cast(CANLibParameterGroupNumber::ProcessData), - payload.data(), - payload.size(), - serverControlFunction, - clientControlFunction, - CANIdentifier::CANPriority::Priority5); + retVal = send_process_data_to_client(clientControlFunction, + payload.data(), + payload.size()); } return retVal; } - bool TaskControllerServer::send_request_object_pool_transfer_response(std::shared_ptr clientControlFunction, bool isEnoughMemory) + bool TaskControllerServer::send_request_object_pool_transfer_response(std::shared_ptr clientControlFunction, bool isEnoughMemory) const { bool retVal = false; @@ -910,12 +915,9 @@ namespace isobus 0xFF }; - retVal = CANNetworkManager::CANNetwork.send_can_message(static_cast(CANLibParameterGroupNumber::ProcessData), - payload.data(), - payload.size(), - serverControlFunction, - clientControlFunction, - CANIdentifier::CANPriority::Priority5); + retVal = send_process_data_to_client(clientControlFunction, + payload.data(), + payload.size()); } return retVal; } @@ -929,20 +931,17 @@ namespace isobus const std::array payload = { static_cast(ProcessDataCommands::DeviceDescriptor) | static_cast(DeviceDescriptorCommandParameters::ObjectPoolTransferResponse) << 4, errorBitfield, - static_cast(sizeBytes & 0xFF), - static_cast((sizeBytes >> 8) & 0xFF), - static_cast((sizeBytes >> 16) & 0xFF), - static_cast((sizeBytes >> 24) & 0xFF), + static_cast(sizeBytes), + static_cast(sizeBytes >> 8), + static_cast(sizeBytes >> 16), + static_cast(sizeBytes >> 24), 0xFF, 0xFF }; - retVal = CANNetworkManager::CANNetwork.send_can_message(static_cast(CANLibParameterGroupNumber::ProcessData), - payload.data(), - payload.size(), - serverControlFunction, - clientControlFunction, - CANIdentifier::CANPriority::Priority5); + retVal = send_process_data_to_client(clientControlFunction, + payload.data(), + payload.size()); } return retVal; } @@ -960,19 +959,16 @@ namespace isobus const std::array payload = { static_cast(ProcessDataCommands::DeviceDescriptor) | static_cast(DeviceDescriptorCommandParameters::ObjectPoolActivateDeactivateResponse) << 4, activationErrorBitfield, - static_cast(parentOfFaultingObject & 0xFF), - static_cast((parentOfFaultingObject >> 8) & 0xFF), - static_cast(faultingObject & 0xFF), - static_cast((faultingObject >> 8) & 0xFF), + static_cast(parentOfFaultingObject), + static_cast(parentOfFaultingObject >> 8), + static_cast(faultingObject), + static_cast(faultingObject >> 8), objectPoolErrorBitfield, 0xFF }; - retVal = CANNetworkManager::CANNetwork.send_can_message(static_cast(CANLibParameterGroupNumber::ProcessData), - payload.data(), - payload.size(), - serverControlFunction, - clientControlFunction, - CANIdentifier::CANPriority::Priority5); + retVal = send_process_data_to_client(clientControlFunction, + payload.data(), + payload.size()); } return retVal; } @@ -985,7 +981,7 @@ namespace isobus { const std::array payload = { static_cast(ProcessDataCommands::DeviceDescriptor) | static_cast(DeviceDescriptorCommandParameters::DeleteObjectPoolResponse) << 4, - static_cast(deletionResult), + static_cast(!deletionResult), // 0 = No errors, 1 = Error errorCode, 0xFF, 0xFF, @@ -993,12 +989,9 @@ namespace isobus 0xFF, 0xFF }; - retVal = CANNetworkManager::CANNetwork.send_can_message(static_cast(CANLibParameterGroupNumber::ProcessData), - payload.data(), - payload.size(), - serverControlFunction, - clientControlFunction, - CANIdentifier::CANPriority::Priority5); + retVal = send_process_data_to_client(clientControlFunction, + payload.data(), + payload.size()); } return retVal; } @@ -1011,20 +1004,17 @@ namespace isobus { const std::array payload = { static_cast(ProcessDataCommands::DeviceDescriptor) | static_cast(DeviceDescriptorCommandParameters::ChangeDesignatorResponse) << 4, - static_cast(objectID & 0xFF), - static_cast((objectID >> 8) & 0xFF), + static_cast(objectID), + static_cast(objectID >> 8), errorCode, 0xFF, 0xFF, 0xFF, 0xFF }; - retVal = CANNetworkManager::CANNetwork.send_can_message(static_cast(CANLibParameterGroupNumber::ProcessData), - payload.data(), - payload.size(), - serverControlFunction, - clientControlFunction, - CANIdentifier::CANPriority::Priority5); + retVal = send_process_data_to_client(clientControlFunction, + payload.data(), + payload.size()); } return retVal; } @@ -1038,19 +1028,36 @@ namespace isobus const std::array payload = { static_cast(static_cast(ProcessDataCommands::Acknowledge) | (static_cast(elementNumber & 0x0F) << 4)), static_cast(elementNumber >> 4), - static_cast(dataDescriptionIndex & 0xFF), - static_cast((dataDescriptionIndex >> 8) & 0xFF), + static_cast(dataDescriptionIndex), + static_cast(dataDescriptionIndex >> 8), errorBitfield, static_cast(0xF0 | static_cast(processDataCommand)), 0xFF, 0xFF }; + retVal = send_process_data_to_client(clientControlFunction, + payload.data(), + payload.size(), + CANIdentifier::CANPriority::Priority4); + } + return retVal; + } + + bool TaskControllerServer::send_process_data_to_client(std::shared_ptr clientControlFunction, + const std::uint8_t *dataBuffer, + std::uint32_t dataLength, + CANIdentifier::CANPriority priority) const + { + bool retVal = false; + + if ((nullptr != dataBuffer) && (dataLength > 0)) + { retVal = CANNetworkManager::CANNetwork.send_can_message(static_cast(CANLibParameterGroupNumber::ProcessData), - payload.data(), - payload.size(), + dataBuffer, + dataLength, serverControlFunction, clientControlFunction, - CANIdentifier::CANPriority::Priority4); + priority); } return retVal; } diff --git a/isobus/src/isobus_task_controller_server_options.cpp b/isobus/src/isobus_task_controller_server_options.cpp new file mode 100644 index 00000000..a1c4ca78 --- /dev/null +++ b/isobus/src/isobus_task_controller_server_options.cpp @@ -0,0 +1,70 @@ +//================================================================================================ +/// @file isobus_task_controller_server_options.cpp +/// +/// @brief Implements a helper class to assign TC server options. +/// @author Adrian Del Grosso +/// +/// @copyright 2024 The Open-Agriculture Developers +//================================================================================================ +#include "isobus/isobus/isobus_task_controller_server_options.hpp" + +namespace isobus +{ + TaskControllerOptions TaskControllerOptions::with_documentation(bool supported) + { + TaskControllerOptions copy = *this; + copy.optionDocumentation = supported; + return copy; + } + + TaskControllerOptions TaskControllerOptions::with_tc_geo_without_position_based_control(bool supported) const + { + TaskControllerOptions copy = *this; + copy.optionTCGEOWithoutPositionBasedControl = supported; + return copy; + } + + TaskControllerOptions TaskControllerOptions::with_tc_geo_with_position_based_control(bool supported) const + { + TaskControllerOptions copy = *this; + copy.optionTCGEOWithPositionBasedControl = supported; + return copy; + } + + TaskControllerOptions TaskControllerOptions::with_peer_control_assignment(bool supported) const + { + TaskControllerOptions copy = *this; + copy.optionPeerControlAssignment = supported; + return copy; + } + + TaskControllerOptions TaskControllerOptions::with_implement_section_control(bool supported) const + { + TaskControllerOptions copy = *this; + copy.optionImplementSectionControl = supported; + return copy; + } + + void TaskControllerOptions::set_settings( + bool supportsDocumentation, + bool supportsTCGEOWithoutPositionBasedControl, + bool supportsTCGEOWithPositionBasedControl, + bool supportsPeerControlAssignment, + bool supportsImplementSectionControl) + { + optionDocumentation = supportsDocumentation; + optionTCGEOWithoutPositionBasedControl = supportsTCGEOWithoutPositionBasedControl; + optionTCGEOWithPositionBasedControl = supportsTCGEOWithPositionBasedControl; + optionPeerControlAssignment = supportsPeerControlAssignment; + optionImplementSectionControl = supportsImplementSectionControl; + } + + std::uint8_t TaskControllerOptions::get_bitfield() const + { + return static_cast(optionDocumentation) | + static_cast(optionTCGEOWithoutPositionBasedControl << 1) | + static_cast(optionTCGEOWithPositionBasedControl << 2) | + static_cast(optionPeerControlAssignment << 3) | + static_cast(optionImplementSectionControl << 4); + } +} // namespace isobus diff --git a/test/tc_server_tests.cpp b/test/tc_server_tests.cpp index a801936e..38affcb8 100644 --- a/test/tc_server_tests.cpp +++ b/test/tc_server_tests.cpp @@ -1,3 +1,11 @@ +//================================================================================================ +/// @file tc_server_tests.cpp +/// +/// @brief Unit tests for the TaskControllerServer class. +/// @author Adrian Del Grosso +/// +/// @copyright 2024 The Open-Agriculture Developers +//================================================================================================ #include #include "isobus/hardware_integration/can_hardware_interface.hpp" @@ -174,18 +182,25 @@ class DerivedTcServer : public TaskControllerServer std::uint8_t numberBoomsSupported, std::uint8_t numberSectionsSupported, std::uint8_t numberChannelsSupportedForPositionBasedControl, - std::uint8_t optionsBitfield) : + const TaskControllerOptions &options) : TaskControllerServer(internalControlFunction, numberBoomsSupported, numberSectionsSupported, numberChannelsSupportedForPositionBasedControl, - optionsBitfield) + options) { } - bool activate_object_pool(std::shared_ptr, ObjectPoolActivationError &, ObjectPoolErrorCodes &, std::uint16_t &, std::uint16_t &) override + bool activate_object_pool(std::shared_ptr, ObjectPoolActivationError &activationError, ObjectPoolErrorCodes &poolError, std::uint16_t &parentObject, std::uint16_t &faultyObject) override { - return true; + if (failActivations) + { + activationError = ObjectPoolActivationError::ThereAreErrorsInTheDDOP; + poolError = ObjectPoolErrorCodes::UnknownObjectReference; + parentObject = 1234; + faultyObject = 789; + } + return !failActivations; } bool change_designator(std::shared_ptr, std::uint16_t, const std::vector &) @@ -215,16 +230,12 @@ class DerivedTcServer : public TaskControllerServer bool get_is_enough_memory_available(std::uint32_t) { - return true; - } - - std::uint32_t get_number_of_complete_object_pools_stored_for_client(std::shared_ptr) - { - return 0; + return enoughMemory; } - void identify_task_controller(std::uint8_t) + void identify_task_controller(std::uint8_t tcNumber) { + identifyTC = tcNumber; } void on_client_timeout(std::shared_ptr) @@ -245,8 +256,33 @@ class DerivedTcServer : public TaskControllerServer return true; } + void test_receive_message(const CANMessage &message, void *parent) + { + TaskControllerServer::store_rx_message(message, parent); + } + + std::uint32_t get_client_status() const + { + std::uint32_t retVal = 0; + + EXPECT_FALSE(activeClients.empty()); + if (!activeClients.empty()) + { + retVal = activeClients.back()->statusBitfield; + } + return retVal; + } + + bool send_status() const + { + return send_status_message(); + } + std::vector testStructureLabel; std::array testLocalizationLabel = { 0 }; + std::uint8_t identifyTC = 0xFF; + bool failActivations = false; + bool enoughMemory = true; }; void isNack(const CANMessageFrame &frame) @@ -288,21 +324,21 @@ void testNackWrapper(VirtualCANPlugin &plugin, std::shared_ptr icf, std::shared_ptr partner) { - CANNetworkManager::CANNetwork.receive_can_message(test_helpers::create_message(5, - 0xCB00, - icf, - partner, - { - mux, - 0xFF, - 0xFF, - 0xFF, - 0xFF, - 0xFF, - 0xFF, - 0xFF, - - })); + CANNetworkManager::CANNetwork.process_receive_can_message_frame(test_helpers::create_message_frame(5, + 0xCB00, + icf, + partner, + { + mux, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + + })); CANNetworkManager::CANNetwork.update(); server.update(); EXPECT_TRUE(readFrameFilterStatus(plugin, frame)); @@ -316,21 +352,21 @@ void testPDNackWrapper(VirtualCANPlugin &plugin, std::shared_ptr icf, std::shared_ptr partner) { - CANNetworkManager::CANNetwork.receive_can_message(test_helpers::create_message(5, - 0xCB00, - icf, - partner, - { - mux, - 0xFF, - 0xFF, - 0xFF, - 0xFF, - 0xFF, - 0xFF, - 0xFF, - - })); + CANNetworkManager::CANNetwork.process_receive_can_message_frame(test_helpers::create_message_frame(5, + 0xCB00, + icf, + partner, + { + mux, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + + })); CANNetworkManager::CANNetwork.update(); server.update(); EXPECT_TRUE(readFrameFilterStatus(plugin, frame)); @@ -352,26 +388,39 @@ TEST(TASK_CONTROLLER_SERVER_TESTS, MessageEncoding) auto internalECU = test_helpers::claim_internal_control_function(0x87, 0); auto partnerClient = test_helpers::force_claim_partnered_control_function(0x88, 0); - DerivedTcServer server(internalECU, 4, 255, 16, 0x17); + DerivedTcServer server(internalECU, + 4, + 255, + 16, + TaskControllerOptions() + .with_documentation() + .with_implement_section_control() + .with_tc_geo_with_position_based_control()); + EXPECT_FALSE(server.get_initialized()); server.initialize(); + EXPECT_TRUE(server.get_initialized()); + + // Test language command interface was initialized + auto &languageCommand = server.get_language_command_interface(); + EXPECT_TRUE(languageCommand.get_initialized()); testPlugin.clear_queue(); // Test that the server responds to requests for version information - CANNetworkManager::CANNetwork.receive_can_message(test_helpers::create_message(5, - 0xCB00, - internalECU, - partnerClient, - { - 0x00, - 0xFF, - 0xFF, - 0xFF, - 0xFF, - 0xFF, - 0xFF, - 0xFF, - })); + CANNetworkManager::CANNetwork.process_receive_can_message_frame(test_helpers::create_message_frame(5, + 0xCB00, + internalECU, + partnerClient, + { + 0x00, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + })); CANNetworkManager::CANNetwork.update(); server.update(); CANMessageFrame testFrame = {}; @@ -381,7 +430,7 @@ TEST(TASK_CONTROLLER_SERVER_TESTS, MessageEncoding) EXPECT_EQ(0x10, testFrame.data[0]); EXPECT_EQ(0x04, testFrame.data[1]); // version EXPECT_EQ(0xFF, testFrame.data[2]); // boot time - EXPECT_EQ(0x17, testFrame.data[3]); // options + EXPECT_EQ(0x15, testFrame.data[3]); // options EXPECT_EQ(0x00, testFrame.data[4]); // options 2 (reserved) EXPECT_EQ(0x04, testFrame.data[5]); // booms EXPECT_EQ(0xFF, testFrame.data[6]); // sections @@ -405,6 +454,13 @@ TEST(TASK_CONTROLLER_SERVER_TESTS, MessageEncoding) testNackWrapper(testPlugin, server, testFrame, 0x80 | static_cast(TaskControllerServer::ProcessDataCommands::DeviceDescriptor), internalECU, partnerClient); // activate pool testNackWrapper(testPlugin, server, testFrame, static_cast(TaskControllerServer::ProcessDataCommands::Acknowledge), internalECU, partnerClient); testNackWrapper(testPlugin, server, testFrame, 0x0A, internalECU, partnerClient); // set and ack + testNackWrapper(testPlugin, server, testFrame, 0x10 | static_cast(TaskControllerServer::ProcessDataCommands::DeviceDescriptor), internalECU, partnerClient); // Server message + testNackWrapper(testPlugin, server, testFrame, 0x30 | static_cast(TaskControllerServer::ProcessDataCommands::DeviceDescriptor), internalECU, partnerClient); // Server message + testNackWrapper(testPlugin, server, testFrame, 0x50 | static_cast(TaskControllerServer::ProcessDataCommands::DeviceDescriptor), internalECU, partnerClient); // Server message + testNackWrapper(testPlugin, server, testFrame, 0x70 | static_cast(TaskControllerServer::ProcessDataCommands::DeviceDescriptor), internalECU, partnerClient); // Server message + testNackWrapper(testPlugin, server, testFrame, 0x90 | static_cast(TaskControllerServer::ProcessDataCommands::DeviceDescriptor), internalECU, partnerClient); // Server message + testNackWrapper(testPlugin, server, testFrame, 0xB0 | static_cast(TaskControllerServer::ProcessDataCommands::DeviceDescriptor), internalECU, partnerClient); // Server message + testNackWrapper(testPlugin, server, testFrame, 0xD0 | static_cast(TaskControllerServer::ProcessDataCommands::DeviceDescriptor), internalECU, partnerClient); // Server message // Test PDNACKs testPDNackWrapper(testPlugin, server, testFrame, static_cast(TaskControllerServer::ProcessDataCommands::MeasurementTimeInterval), internalECU, partnerClient); @@ -414,37 +470,37 @@ TEST(TASK_CONTROLLER_SERVER_TESTS, MessageEncoding) testPDNackWrapper(testPlugin, server, testFrame, static_cast(TaskControllerServer::ProcessDataCommands::MeasurementChangeThreshold), internalECU, partnerClient); // Send working set master - CANNetworkManager::CANNetwork.receive_can_message(test_helpers::create_message_broadcast(6, - 0xFE0D, - partnerClient, - { - 0x01, - 0xFF, - 0xFF, - 0xFF, - 0xFF, - 0xFF, - 0xFF, - 0xFF, - })); + CANNetworkManager::CANNetwork.process_receive_can_message_frame(test_helpers::create_message_frame_broadcast(6, + 0xFE0D, + partnerClient, + { + 0x01, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + })); CANNetworkManager::CANNetwork.update(); server.update(); // Request structure label - CANNetworkManager::CANNetwork.receive_can_message(test_helpers::create_message(5, - 0xCB00, - internalECU, - partnerClient, - { - 0x01, - 0xFF, - 0xFF, - 0xFF, - 0xFF, - 0xFF, - 0xFF, - 0xFF, - })); + CANNetworkManager::CANNetwork.process_receive_can_message_frame(test_helpers::create_message_frame(5, + 0xCB00, + internalECU, + partnerClient, + { + 0x01, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + })); CANNetworkManager::CANNetwork.update(); server.update(); EXPECT_TRUE(readFrameFilterStatus(testPlugin, testFrame)); @@ -461,20 +517,20 @@ TEST(TASK_CONTROLLER_SERVER_TESTS, MessageEncoding) // Make sure a valid label is echoed back server.testStructureLabel = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07 }; - CANNetworkManager::CANNetwork.receive_can_message(test_helpers::create_message(5, - 0xCB00, - internalECU, - partnerClient, - { - 0x01, - 0x01, - 0x02, - 0x03, - 0x04, - 0x05, - 0x06, - 0x07, - })); + CANNetworkManager::CANNetwork.process_receive_can_message_frame(test_helpers::create_message_frame(5, + 0xCB00, + internalECU, + partnerClient, + { + 0x01, + 0x01, + 0x02, + 0x03, + 0x04, + 0x05, + 0x06, + 0x07, + })); CANNetworkManager::CANNetwork.update(); server.update(); EXPECT_TRUE(readFrameFilterStatus(testPlugin, testFrame)); @@ -490,18 +546,18 @@ TEST(TASK_CONTROLLER_SERVER_TESTS, MessageEncoding) EXPECT_EQ(0x07, testFrame.data[7]); // Request localization label - CANNetworkManager::CANNetwork.receive_can_message(test_helpers::create_message(5, - 0xCB00, - internalECU, - partnerClient, - { 0x21, - 0xFF, - 0xFF, - 0xFF, - 0xFF, - 0xFF, - 0xFF, - 0xFF })); + CANNetworkManager::CANNetwork.process_receive_can_message_frame(test_helpers::create_message_frame(5, + 0xCB00, + internalECU, + partnerClient, + { 0x21, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF })); CANNetworkManager::CANNetwork.update(); server.update(); EXPECT_TRUE(readFrameFilterStatus(testPlugin, testFrame)); @@ -518,18 +574,18 @@ TEST(TASK_CONTROLLER_SERVER_TESTS, MessageEncoding) // Make sure a valid label is echoed back server.testLocalizationLabel = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07 }; - CANNetworkManager::CANNetwork.receive_can_message(test_helpers::create_message(5, - 0xCB00, - internalECU, - partnerClient, - { 0x21, - 0x01, - 0x02, - 0x03, - 0x04, - 0x05, - 0x06, - 0x07 })); + CANNetworkManager::CANNetwork.process_receive_can_message_frame(test_helpers::create_message_frame(5, + 0xCB00, + internalECU, + partnerClient, + { 0x21, + 0x01, + 0x02, + 0x03, + 0x04, + 0x05, + 0x06, + 0x07 })); CANNetworkManager::CANNetwork.update(); server.update(); EXPECT_TRUE(readFrameFilterStatus(testPlugin, testFrame)); @@ -544,47 +600,107 @@ TEST(TASK_CONTROLLER_SERVER_TESTS, MessageEncoding) EXPECT_EQ(0x06, testFrame.data[6]); EXPECT_EQ(0x07, testFrame.data[7]); + // Send pool without a request, which is bad but we should tolerate it + { + std::vector data; + data.push_back(0x61); + for (std::size_t i = 0; i < sizeof(testDDOP); i++) + { + data.push_back(testDDOP[i]); + } + + CANMessage message(CANMessage::Type::Receive, CANIdentifier(test_helpers::create_ext_can_id(5, 0xCB00, internalECU, partnerClient)), data, partnerClient, internalECU, 0); + server.test_receive_message(message, &server); + CANNetworkManager::CANNetwork.update(); + server.update(); + EXPECT_TRUE(readFrameFilterStatus(testPlugin, testFrame)); + EXPECT_EQ(testFrame.identifier, 0x14CB8887); // Priority 5, source 0x88, destination 0x87 + EXPECT_EQ(8, testFrame.dataLength); + EXPECT_EQ(0x71, testFrame.data[0]); + EXPECT_EQ(0x00, testFrame.data[1]); // Object pool should have been transferred ok + EXPECT_EQ(static_cast(sizeof(testDDOP) & 0xFF), testFrame.data[2]); + EXPECT_EQ(static_cast((sizeof(testDDOP) >> 8) & 0xFF), testFrame.data[3]); + EXPECT_EQ(static_cast((sizeof(testDDOP) >> 16) & 0xFF), testFrame.data[4]); + EXPECT_EQ(static_cast((sizeof(testDDOP) >> 24) & 0xFF), testFrame.data[5]); + EXPECT_EQ(0xFF, testFrame.data[6]); + EXPECT_EQ(0xFF, testFrame.data[7]); + + // Test receiving messages without parent pointer is not allowed + server.test_receive_message(message, nullptr); + CANNetworkManager::CANNetwork.update(); + server.update(); + EXPECT_FALSE(readFrameFilterStatus(testPlugin, testFrame)); + } + // Request to transfer object pool - CANNetworkManager::CANNetwork.receive_can_message(test_helpers::create_message(5, - 0xCB00, - internalECU, - partnerClient, - { 0x41, - static_cast(sizeof(testDDOP) & 0xFF), - static_cast((sizeof(testDDOP) >> 8) & 0xFF), - static_cast((sizeof(testDDOP) >> 16) & 0xFF), - static_cast((sizeof(testDDOP) >> 24) & 0xFF), - 0xFF, - 0xFF, - 0xFF })); - CANNetworkManager::CANNetwork.update(); - server.update(); - EXPECT_TRUE(readFrameFilterStatus(testPlugin, testFrame)); - EXPECT_EQ(testFrame.identifier, 0x14CB8887); // Priority 5, source 0x88, destination 0x87 - EXPECT_EQ(8, testFrame.dataLength); - EXPECT_EQ(0x51, testFrame.data[0]); // Request to transfer object pool response - EXPECT_EQ(0x00, testFrame.data[1]); // 0 Means there's probably enough memory - EXPECT_EQ(0xFF, testFrame.data[2]); - EXPECT_EQ(0xFF, testFrame.data[3]); - EXPECT_EQ(0xFF, testFrame.data[4]); - EXPECT_EQ(0xFF, testFrame.data[5]); - EXPECT_EQ(0xFF, testFrame.data[6]); - EXPECT_EQ(0xFF, testFrame.data[7]); + { + CANNetworkManager::CANNetwork.process_receive_can_message_frame(test_helpers::create_message_frame(5, + 0xCB00, + internalECU, + partnerClient, + { 0x41, + static_cast(sizeof(testDDOP) & 0xFF), + static_cast((sizeof(testDDOP) >> 8) & 0xFF), + static_cast((sizeof(testDDOP) >> 16) & 0xFF), + static_cast((sizeof(testDDOP) >> 24) & 0xFF), + 0xFF, + 0xFF, + 0xFF })); + CANNetworkManager::CANNetwork.update(); + server.update(); + EXPECT_TRUE(readFrameFilterStatus(testPlugin, testFrame)); + EXPECT_EQ(testFrame.identifier, 0x14CB8887); // Priority 5, source 0x88, destination 0x87 + EXPECT_EQ(8, testFrame.dataLength); + EXPECT_EQ(0x51, testFrame.data[0]); // Request to transfer object pool response + EXPECT_EQ(0x00, testFrame.data[1]); // 0 Means there's probably enough memory + EXPECT_EQ(0xFF, testFrame.data[2]); + EXPECT_EQ(0xFF, testFrame.data[3]); + EXPECT_EQ(0xFF, testFrame.data[4]); + EXPECT_EQ(0xFF, testFrame.data[5]); + EXPECT_EQ(0xFF, testFrame.data[6]); + EXPECT_EQ(0xFF, testFrame.data[7]); + + // Try a failing request + server.enoughMemory = false; + CANNetworkManager::CANNetwork.process_receive_can_message_frame(test_helpers::create_message_frame(5, + 0xCB00, + internalECU, + partnerClient, + { 0x41, + static_cast(sizeof(testDDOP) & 0xFF), + static_cast((sizeof(testDDOP) >> 8) & 0xFF), + static_cast((sizeof(testDDOP) >> 16) & 0xFF), + static_cast((sizeof(testDDOP) >> 24) & 0xFF), + 0xFF, + 0xFF, + 0xFF })); + CANNetworkManager::CANNetwork.update(); + server.update(); + EXPECT_TRUE(readFrameFilterStatus(testPlugin, testFrame)); + EXPECT_EQ(testFrame.identifier, 0x14CB8887); // Priority 5, source 0x88, destination 0x87 + EXPECT_EQ(8, testFrame.dataLength); + EXPECT_EQ(0x51, testFrame.data[0]); // Request to transfer object pool response + EXPECT_EQ(0x01, testFrame.data[1]); // 1 Means there's not enough memory + EXPECT_EQ(0xFF, testFrame.data[2]); + EXPECT_EQ(0xFF, testFrame.data[3]); + EXPECT_EQ(0xFF, testFrame.data[4]); + EXPECT_EQ(0xFF, testFrame.data[5]); + EXPECT_EQ(0xFF, testFrame.data[6]); + EXPECT_EQ(0xFF, testFrame.data[7]); + server.enoughMemory = true; + } // Construct a message to transfer the object pool { - CANMessage message(0); - message.set_identifier(CANIdentifier(test_helpers::create_ext_can_id(5, 0xCB00, internalECU, partnerClient))); - message.set_source_control_function(partnerClient); - message.set_destination_control_function(internalECU); - message.set_data_size(sizeof(testDDOP) + 1); - message.set_data(0x61, 0); - + std::vector data; + data.push_back(0x61); for (std::size_t i = 0; i < sizeof(testDDOP); i++) { - message.set_data(testDDOP[i], i + 1); + data.push_back(testDDOP[i]); } - CANNetworkManager::CANNetwork.receive_can_message(message); + + CANMessage message(CANMessage::Type::Receive, CANIdentifier(test_helpers::create_ext_can_id(5, 0xCB00, internalECU, partnerClient)), data, partnerClient, internalECU, 0); + server.test_receive_message(message, &server); CANNetworkManager::CANNetwork.update(); server.update(); EXPECT_TRUE(readFrameFilterStatus(testPlugin, testFrame)); @@ -600,5 +716,462 @@ TEST(TASK_CONTROLLER_SERVER_TESTS, MessageEncoding) EXPECT_EQ(0xFF, testFrame.data[7]); } + // Send a value request + { + EXPECT_TRUE(server.send_request_value(partnerClient, 1234, 456)); + CANNetworkManager::CANNetwork.update(); + EXPECT_TRUE(readFrameFilterStatus(testPlugin, testFrame)); + + EXPECT_EQ(2, testFrame.data[0] & 0x0F); // Command + EXPECT_EQ(456 & 0x0F, testFrame.data[0] >> 4); // Element + EXPECT_EQ(456 >> 4, testFrame.data[1]); // Element + EXPECT_EQ(1234 & 0xFF, testFrame.data[2]); // DDI + EXPECT_EQ((1234 >> 8), testFrame.data[3]); // DDI + EXPECT_EQ(0xFF, testFrame.data[4]); + EXPECT_EQ(0xFF, testFrame.data[5]); + EXPECT_EQ(0xFF, testFrame.data[6]); + EXPECT_EQ(0xFF, testFrame.data[7]); + EXPECT_EQ(8, testFrame.dataLength); + EXPECT_EQ(0x14CB8887, testFrame.identifier); + } + + // Send time interval measurement command + { + EXPECT_TRUE(server.send_time_interval_measurement_command(partnerClient, 6, 99, 1000)); + CANNetworkManager::CANNetwork.update(); + EXPECT_TRUE(readFrameFilterStatus(testPlugin, testFrame)); + + EXPECT_EQ(4, testFrame.data[0] & 0x0F); // Command + EXPECT_EQ(99 & 0x0F, testFrame.data[0] >> 4); // Element + EXPECT_EQ(99 >> 4, testFrame.data[1]); // Element + EXPECT_EQ(6 & 0xFF, testFrame.data[2]); // DDI + EXPECT_EQ((6 >> 8), testFrame.data[3]); // DDI + EXPECT_EQ(1000 & 0xFF, testFrame.data[4]); + EXPECT_EQ((1000 >> 8) & 0xFF, testFrame.data[5]); + EXPECT_EQ((1000 >> 16) & 0xFF, testFrame.data[6]); + EXPECT_EQ((1000 >> 24) & 0xFF, testFrame.data[7]); + EXPECT_EQ(8, testFrame.dataLength); + EXPECT_EQ(0x14CB8887, testFrame.identifier); + } + + // Send distance interval measurement command + { + EXPECT_TRUE(server.send_distance_interval_measurement_command(partnerClient, 654, 999, 65534)); + CANNetworkManager::CANNetwork.update(); + EXPECT_TRUE(readFrameFilterStatus(testPlugin, testFrame)); + + EXPECT_EQ(5, testFrame.data[0] & 0x0F); // Command + EXPECT_EQ(999 & 0x0F, testFrame.data[0] >> 4); // Element + EXPECT_EQ(999 >> 4, testFrame.data[1]); // Element + EXPECT_EQ(654 & 0xFF, testFrame.data[2]); // DDI + EXPECT_EQ((654 >> 8), testFrame.data[3]); // DDI + EXPECT_EQ(65534 & 0xFF, testFrame.data[4]); + EXPECT_EQ((65534 >> 8) & 0xFF, testFrame.data[5]); + EXPECT_EQ((65534 >> 16) & 0xFF, testFrame.data[6]); + EXPECT_EQ((65534 >> 24) & 0xFF, testFrame.data[7]); + EXPECT_EQ(8, testFrame.dataLength); + EXPECT_EQ(0x14CB8887, testFrame.identifier); + } + + // Send minimum threshold measurement command + { + EXPECT_TRUE(server.send_minimum_threshold_measurement_command(partnerClient, 445, 0, 0x00FFFFFF)); + CANNetworkManager::CANNetwork.update(); + EXPECT_TRUE(readFrameFilterStatus(testPlugin, testFrame)); + + EXPECT_EQ(6, testFrame.data[0] & 0x0F); // Command + EXPECT_EQ(0 & 0x0F, testFrame.data[0] >> 4); // Element + EXPECT_EQ(0 >> 4, testFrame.data[1]); // Element + EXPECT_EQ(445 & 0xFF, testFrame.data[2]); // DDI + EXPECT_EQ((445 >> 8), testFrame.data[3]); // DDI + EXPECT_EQ(0x00FFFFFF & 0xFF, testFrame.data[4]); + EXPECT_EQ((0x00FFFFFF >> 8) & 0xFF, testFrame.data[5]); + EXPECT_EQ((0x00FFFFFF >> 16) & 0xFF, testFrame.data[6]); + EXPECT_EQ((0x00FFFFFF >> 24) & 0xFF, testFrame.data[7]); + EXPECT_EQ(8, testFrame.dataLength); + EXPECT_EQ(0x14CB8887, testFrame.identifier); + } + + // Send maximum threshold measurement command + { + EXPECT_TRUE(server.send_maximum_threshold_measurement_command(partnerClient, 445, 0, 0xFFFFFFFF)); + CANNetworkManager::CANNetwork.update(); + EXPECT_TRUE(readFrameFilterStatus(testPlugin, testFrame)); + + EXPECT_EQ(7, testFrame.data[0] & 0x0F); // Command + EXPECT_EQ(0 & 0x0F, testFrame.data[0] >> 4); // Element + EXPECT_EQ(0 >> 4, testFrame.data[1]); // Element + EXPECT_EQ(445 & 0xFF, testFrame.data[2]); // DDI + EXPECT_EQ((445 >> 8), testFrame.data[3]); // DDI + EXPECT_EQ(0xFFFFFFFF & 0xFF, testFrame.data[4]); + EXPECT_EQ((0xFFFFFFFF >> 8) & 0xFF, testFrame.data[5]); + EXPECT_EQ((0xFFFFFFFF >> 16) & 0xFF, testFrame.data[6]); + EXPECT_EQ((0xFFFFFFFF >> 24) & 0xFF, testFrame.data[7]); + EXPECT_EQ(8, testFrame.dataLength); + EXPECT_EQ(0x14CB8887, testFrame.identifier); + } + + // Send change threshold measurement command + { + EXPECT_TRUE(server.send_change_threshold_measurement_command(partnerClient, 14, 0, 1)); + CANNetworkManager::CANNetwork.update(); + EXPECT_TRUE(readFrameFilterStatus(testPlugin, testFrame)); + + EXPECT_EQ(8, testFrame.data[0] & 0x0F); // Command + EXPECT_EQ(0 & 0x0F, testFrame.data[0] >> 4); // Element + EXPECT_EQ(0 >> 4, testFrame.data[1]); // Element + EXPECT_EQ(14 & 0xFF, testFrame.data[2]); // DDI + EXPECT_EQ((14 >> 8), testFrame.data[3]); // DDI + EXPECT_EQ(1, testFrame.data[4]); + EXPECT_EQ(0, testFrame.data[5]); + EXPECT_EQ(0, testFrame.data[6]); + EXPECT_EQ(0, testFrame.data[7]); + EXPECT_EQ(8, testFrame.dataLength); + EXPECT_EQ(0x14CB8887, testFrame.identifier); + } + + // Set value and ack + { + EXPECT_TRUE(server.send_set_value_and_acknowledge(partnerClient, 14, 0, 600)); + CANNetworkManager::CANNetwork.update(); + EXPECT_TRUE(readFrameFilterStatus(testPlugin, testFrame)); + + EXPECT_EQ(10, testFrame.data[0] & 0x0F); // Command + EXPECT_EQ(0 & 0x0F, testFrame.data[0] >> 4); // Element + EXPECT_EQ(0 >> 4, testFrame.data[1]); // Element + EXPECT_EQ(14 & 0xFF, testFrame.data[2]); // DDI + EXPECT_EQ((14 >> 8), testFrame.data[3]); // DDI + EXPECT_EQ(600 & 0xFF, testFrame.data[4]); + EXPECT_EQ(600 >> 8, testFrame.data[5]); + EXPECT_EQ(0, testFrame.data[6]); + EXPECT_EQ(0, testFrame.data[7]); + EXPECT_EQ(8, testFrame.dataLength); + EXPECT_EQ(0x0ccb8887, testFrame.identifier); // Higher priority than the other messages + } + + // Set value + { + EXPECT_TRUE(server.send_set_value(partnerClient, 2455, 0, 800)); + CANNetworkManager::CANNetwork.update(); + EXPECT_TRUE(readFrameFilterStatus(testPlugin, testFrame)); + + EXPECT_EQ(3, testFrame.data[0] & 0x0F); // Command + EXPECT_EQ(0 & 0x0F, testFrame.data[0] >> 4); // Element + EXPECT_EQ(0 >> 4, testFrame.data[1]); // Element + EXPECT_EQ(2455 & 0xFF, testFrame.data[2]); // DDI + EXPECT_EQ((2455 >> 8), testFrame.data[3]); // DDI + EXPECT_EQ(800 & 0xFF, testFrame.data[4]); + EXPECT_EQ(800 >> 8, testFrame.data[5]); + EXPECT_EQ(0, testFrame.data[6]); + EXPECT_EQ(0, testFrame.data[7]); + EXPECT_EQ(8, testFrame.dataLength); + EXPECT_EQ(0x14CB8887, testFrame.identifier); + } + + // Test task status + { + EXPECT_FALSE(server.get_task_totals_active()); + server.set_task_totals_active(true); + EXPECT_TRUE(server.get_task_totals_active()); + } + + // Test identify TC + { + CANNetworkManager::CANNetwork.process_receive_can_message_frame(test_helpers::create_message_frame(5, + 0xCB00, + internalECU, + partnerClient, + { 0x20, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF })); + CANNetworkManager::CANNetwork.update(); + server.update(); + EXPECT_TRUE(readFrameFilterStatus(testPlugin, testFrame)); + + EXPECT_EQ(8, testFrame.dataLength); + EXPECT_EQ(0x20, testFrame.data[0]); // Response to identify TC + // All other bytes reserved, FFs + EXPECT_EQ(0xFF, testFrame.data[1]); + EXPECT_EQ(0xFF, testFrame.data[2]); + EXPECT_EQ(0xFF, testFrame.data[3]); + EXPECT_EQ(0xFF, testFrame.data[4]); + EXPECT_EQ(0xFF, testFrame.data[5]); + EXPECT_EQ(0xFF, testFrame.data[6]); + EXPECT_EQ(0xFF, testFrame.data[7]); + EXPECT_EQ(1, server.identifyTC); + server.identifyTC = 45; + + // Try a global request as well + CANNetworkManager::CANNetwork.process_receive_can_message_frame(test_helpers::create_message_frame_broadcast(5, + 0xCB00, + internalECU, + { 0x20, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF })); + CANNetworkManager::CANNetwork.update(); + server.update(); + EXPECT_EQ(1, server.identifyTC); + } + + // Test activate object pool + { + CANNetworkManager::CANNetwork.process_receive_can_message_frame(test_helpers::create_message_frame(5, + 0xCB00, + internalECU, + partnerClient, + { 0x81, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF })); + CANNetworkManager::CANNetwork.update(); + server.update(); + EXPECT_TRUE(readFrameFilterStatus(testPlugin, testFrame)); + + if (0xEE == ((testFrame.identifier >> 16) & 0xFF)) + { + // Filter out address violations + EXPECT_TRUE(readFrameFilterStatus(testPlugin, testFrame)); + } + + EXPECT_EQ(0x91, testFrame.data[0]); // Response to activate object pool + EXPECT_EQ(0x00, testFrame.data[1]); // No errors + EXPECT_EQ(0xFF, testFrame.data[2]); // Parent object + EXPECT_EQ(0xFF, testFrame.data[3]); // Parent object + EXPECT_EQ(0xFF, testFrame.data[4]); // Faulting object ID + EXPECT_EQ(0xFF, testFrame.data[5]); // Faulting object ID + EXPECT_EQ(0x00, testFrame.data[6]); // Pool error codes (0 = none) + EXPECT_EQ(0xFF, testFrame.data[7]); // reserved + + // test failing to activate returns reported faulty objects + server.failActivations = true; + CANNetworkManager::CANNetwork.process_receive_can_message_frame(test_helpers::create_message_frame(5, + 0xCB00, + internalECU, + partnerClient, + { 0x81, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF })); + CANNetworkManager::CANNetwork.update(); + server.update(); + EXPECT_TRUE(readFrameFilterStatus(testPlugin, testFrame)); + EXPECT_EQ(0x91, testFrame.data[0]); // Response to activate object pool + EXPECT_EQ(0x01, testFrame.data[1]); // Errors in DDOP + EXPECT_EQ(1234 & 0xFF, testFrame.data[2]); // Parent Object + EXPECT_EQ(1234 >> 8, testFrame.data[3]); // Parent Object + EXPECT_EQ(789 & 0xFF, testFrame.data[4]); // Parent Object + EXPECT_EQ(789 >> 8, testFrame.data[5]); // Parent Object + EXPECT_EQ(0x02, testFrame.data[6]); // Error code + EXPECT_EQ(0xFF, testFrame.data[7]); // reserved + + // Deactivate object pool + server.failActivations = false; + CANNetworkManager::CANNetwork.process_receive_can_message_frame(test_helpers::create_message_frame(5, + 0xCB00, + internalECU, + partnerClient, + { 0x81, + 0x00, // Deactivate. Oxff was activate + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF })); + CANNetworkManager::CANNetwork.update(); + server.update(); + EXPECT_TRUE(readFrameFilterStatus(testPlugin, testFrame)); + EXPECT_EQ(0x91, testFrame.data[0]); // Response to deactivate object pool + EXPECT_EQ(0x00, testFrame.data[1]); // No errors + EXPECT_EQ(0xFF, testFrame.data[2]); // Parent object + EXPECT_EQ(0xFF, testFrame.data[3]); // Parent object + EXPECT_EQ(0xFF, testFrame.data[4]); // Faulting object ID + EXPECT_EQ(0xFF, testFrame.data[5]); // Faulting object ID + EXPECT_EQ(0x00, testFrame.data[6]); // Pool error codes (0 = none) + EXPECT_EQ(0xFF, testFrame.data[7]); // reserved + } + + // Delete object pool + { + CANNetworkManager::CANNetwork.process_receive_can_message_frame(test_helpers::create_message_frame(5, + 0xCB00, + internalECU, + partnerClient, + { 0xA1, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF })); + CANNetworkManager::CANNetwork.update(); + server.update(); + EXPECT_TRUE(readFrameFilterStatus(testPlugin, testFrame)); + EXPECT_EQ(0xB1, testFrame.data[0]); // Response to deactivate object pool + EXPECT_EQ(0x00, testFrame.data[1]); // No errors + EXPECT_EQ(0xFF, testFrame.data[2]); // Error details not available + EXPECT_EQ(0xFF, testFrame.data[3]); // reserved + EXPECT_EQ(0xFF, testFrame.data[4]); // reserved + EXPECT_EQ(0xFF, testFrame.data[5]); // reserved + EXPECT_EQ(0xFF, testFrame.data[6]); // reserved + EXPECT_EQ(0xFF, testFrame.data[7]); // reserved + } + + // test change designator + { + CANNetworkManager::CANNetwork.process_receive_can_message_frame(test_helpers::create_message_frame(5, + 0xCB00, + internalECU, + partnerClient, + { 0xC1, + 0x01, // ID + 0x00, // ID + 0x02, // Length + 'A', + 'B', + 0xFF, + 0xFF })); + CANNetworkManager::CANNetwork.update(); + server.update(); + EXPECT_FALSE(readFrameFilterStatus(testPlugin, testFrame)); // We'd ignore this message ideally + + // Now try with the pool activated + CANNetworkManager::CANNetwork.process_receive_can_message_frame(test_helpers::create_message_frame(5, + 0xCB00, + internalECU, + partnerClient, + { 0x81, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF })); + CANNetworkManager::CANNetwork.process_receive_can_message_frame(test_helpers::create_message_frame(5, + 0xCB00, + internalECU, + partnerClient, + { 0xC1, + 0x01, // ID + 0x00, // ID + 0x02, // Length + 'A', + 'B', + 0xFF, + 0xFF })); + CANNetworkManager::CANNetwork.update(); + server.update(); + EXPECT_TRUE(readFrameFilterStatus(testPlugin, testFrame)); + EXPECT_TRUE(readFrameFilterStatus(testPlugin, testFrame)); + EXPECT_EQ(8, testFrame.dataLength); + EXPECT_EQ(0xD1, testFrame.data[0]); // Response to change designator + EXPECT_EQ(0x01, testFrame.data[1]); // ID + EXPECT_EQ(0x00, testFrame.data[2]); // ID + EXPECT_EQ(0x00, testFrame.data[3]); // Error code + EXPECT_EQ(0xFF, testFrame.data[4]); // reserved + EXPECT_EQ(0xFF, testFrame.data[5]); // reserved + EXPECT_EQ(0xFF, testFrame.data[6]); // reserved + EXPECT_EQ(0xFF, testFrame.data[7]); // reserved + } + + // Test value command and acknowledge works + { + CANNetworkManager::CANNetwork.process_receive_can_message_frame(test_helpers::create_message_frame(5, + 0xCB00, + internalECU, + partnerClient, + { 0x4A, // Element 4 set and ack + 0x00, + 0x07, //DDI LSB + 0x00, + 0x01, // Value LSB + 0x02, + 0x03, + 0x04 })); + CANNetworkManager::CANNetwork.update(); + server.update(); + EXPECT_TRUE(readFrameFilterStatus(testPlugin, testFrame)); + + // Expect PDACK + EXPECT_EQ(8, testFrame.dataLength); + EXPECT_EQ(0x4D, testFrame.data[0]); // PDACK, element 4 + EXPECT_EQ(0x00, testFrame.data[1]); // Element + EXPECT_EQ(0x07, testFrame.data[2]); // DDI + EXPECT_EQ(0x00, testFrame.data[3]); // DDI + EXPECT_EQ(0x00, testFrame.data[4]); // Error codes + EXPECT_EQ(0xFA, testFrame.data[5]); // Command + EXPECT_EQ(0xFF, testFrame.data[6]); // reserved + EXPECT_EQ(0xFF, testFrame.data[7]); // reserved + } + + // Test client task message populated the client's state + { + CANNetworkManager::CANNetwork.process_receive_can_message_frame(test_helpers::create_message_frame(5, + 0xCB00, + internalECU, + partnerClient, + { 0xFF, // Client task + 0xFF, // N/A + 0xFF, // DDI N/A + 0xFF, // DDI N/A + 0x01, // Status (Task active) + 0x00, + 0x00, + 0x00 })); + CANNetworkManager::CANNetwork.update(); + server.update(); + EXPECT_EQ(server.get_client_status(), 1); + } + + // Test status message + { + EXPECT_TRUE(server.send_status()); + EXPECT_TRUE(testPlugin.read_frame(testFrame)); + + EXPECT_EQ(8, testFrame.dataLength); + EXPECT_EQ(0xFE, testFrame.data[0]); + EXPECT_EQ(0xFF, testFrame.data[1]); + EXPECT_EQ(0xFF, testFrame.data[2]); + EXPECT_EQ(0xFF, testFrame.data[3]); + EXPECT_EQ(0x01, testFrame.data[4]); // Task active bit + EXPECT_EQ(0xFE, testFrame.data[5]); // Address of client with executing command + EXPECT_EQ(0x00, testFrame.data[6]); // Executing command + EXPECT_EQ(0xFF, testFrame.data[7]); // Address of client with executing command + + // Disable task active + server.set_task_totals_active(false); + EXPECT_TRUE(server.send_status()); + EXPECT_TRUE(testPlugin.read_frame(testFrame)); + + EXPECT_EQ(8, testFrame.dataLength); + EXPECT_EQ(0xFE, testFrame.data[0]); + EXPECT_EQ(0xFF, testFrame.data[1]); + EXPECT_EQ(0xFF, testFrame.data[2]); + EXPECT_EQ(0xFF, testFrame.data[3]); + EXPECT_EQ(0x00, testFrame.data[4]); // Task active bit + EXPECT_EQ(0xFE, testFrame.data[5]); // Address of client with executing command + EXPECT_EQ(0x00, testFrame.data[6]); // Executing command + EXPECT_EQ(0xFF, testFrame.data[7]); // Address of client with executing command + } CANHardwareInterface::stop(); }