From ee79ec93a1a1db06cb71d45aee7ea268374ecca7 Mon Sep 17 00:00:00 2001 From: Adrian Del Grosso <10929341+ad3154@users.noreply.github.com> Date: Sat, 10 Feb 2024 13:46:01 -0700 Subject: [PATCH] [TC]: Add product abstraction to DDOP helper This provides a way to get rate/product information from a DDOP. --- ..._device_descriptor_object_pool_helpers.hpp | 107 ++++- ...obus_standard_data_description_indices.hpp | 3 +- ..._device_descriptor_object_pool_helpers.cpp | 443 ++++++++++++++++-- test/tc_server_tests.cpp | 30 +- 4 files changed, 528 insertions(+), 55 deletions(-) diff --git a/isobus/include/isobus/isobus/isobus_device_descriptor_object_pool_helpers.hpp b/isobus/include/isobus/isobus/isobus_device_descriptor_object_pool_helpers.hpp index e76096097..2dcc96fa0 100644 --- a/isobus/include/isobus/isobus/isobus_device_descriptor_object_pool_helpers.hpp +++ b/isobus/include/isobus/isobus/isobus_device_descriptor_object_pool_helpers.hpp @@ -12,8 +12,9 @@ #ifndef ISOBUS_DEVICE_DESCRIPTOR_OBJECT_POOL_HELPERS_HPP #define ISOBUS_DEVICE_DESCRIPTOR_OBJECT_POOL_HELPERS_HPP +#include "isobus/isobus/can_constants.hpp" #include "isobus/isobus/isobus_device_descriptor_object_pool.hpp" -#include "isobus_standard_data_description_indices.hpp" +#include "isobus/isobus/isobus_standard_data_description_indices.hpp" namespace isobus { @@ -52,7 +53,7 @@ namespace isobus /// @returns The value if it exists, otherwise 0. std::int32_t get() const; - private: + protected: friend class DeviceDescriptorObjectPoolHelper; ///< Allow our helper to change the values std::int32_t value = 0; ///< The value being wrapped by this object @@ -60,6 +61,34 @@ namespace isobus bool isSettable = false; ///< Stores if the value can be set, such as on a DPD's value }; + /// @brief A helper class that groups a DDI with an object id + class RateMetadata : public ObjectPoolValue + { + public: + std::uint16_t dataDictionaryIdentifier = static_cast(DataDescriptionIndex::Reserved); ///< The data dictionary index of the product control rate + std::uint16_t objectID = NULL_OBJECT_ID; ///< The object ID of the rate + + protected: + friend class DeviceDescriptorObjectPoolHelper; ///< Allow our helper to change the values + }; + + /// @brief A helper class that groups product rate infomation together. + /// A TC server could use this to know quickly what rates are available and how to interact with them. + /// Use the associated element number and DDI to perform value commands and requests with a client. + class ProductControlInformation + { + public: + /// @brief Returns true if any rate information is populated. + /// @returns true if any rate information is populated, otherwise false + bool is_valid() const; + + RateMetadata rateSetpoint; ///< The info needed to interact with the rate setpoint + RateMetadata rateActual; ///< The info needed to get the actual rate + RateMetadata rateDefault; ///< The info needed to interact with the default rate + RateMetadata rateMinimum; ///< The info needed to interact with the minimum rate + RateMetadata rateMaximum; ///< The info needed to interact with the maximum rate + }; + /// @brief A helper class that describes an individual section of a boom. /// This is used to describe the sections of a boom. Units are defined in mm as specified /// in the ISO 11783-10 standard. X offsets are fore/aft. Y offsets are left/right again as @@ -67,23 +96,20 @@ namespace isobus class Section { public: - /// @brief Default constructor for a helper class that describes an individual section of a boom - Section(); - ObjectPoolValue xOffset_mm; ///< The x offset of the section in mm. X offsets are fore+/aft-. ObjectPoolValue yOffset_mm; ///< The y offset of the section in mm. Y offsets are left-/right+. ObjectPoolValue zOffset_mm; ///< The z offset of the section in mm. Z offsets are up+/down-. ObjectPoolValue width_mm; ///< The width of the section in mm. + std::vector rates; ///< If the section has rates, this will contain the associated data needed to control the product. + std::uint16_t elementNumber = NULL_OBJECT_ID; ///< The element number of the section, which can be used to avoid further parsing of the DDOP when issuing commands. }; /// @brief A helper class that describes a sub boom (not all devices support this) class SubBoom { public: - /// @brief Default constructor for a helper class that describes a sub boom - SubBoom(); - std::vector
sections; ///< The sections of the sub boom + std::vector rates; ///< If the sub-boom has rates, this will contain the associated data needed to control the product. ObjectPoolValue xOffset_mm; ///< The x offset of the sub boom in mm. X offsets are fore+/aft-. ObjectPoolValue yOffset_mm; ///< The y offset of the sub boom in mm. Y offsets are left-/right+. ObjectPoolValue zOffset_mm; ///< The z offset of the sub boom in mm. Z offsets are up+/down-. @@ -97,6 +123,7 @@ namespace isobus public: std::vector
sections; ///< The sections of the boom std::vector subBooms; ///< The sub booms of the boom + std::vector rates; ///< If the boom has rates, this will contain the associated data needed to control the product. ObjectPoolValue xOffset_mm; ///< The x offset of the sub boom in mm. X offsets are fore+/aft-. ObjectPoolValue yOffset_mm; ///< The y offset of the sub boom in mm. Y offsets are left-/right+. ObjectPoolValue zOffset_mm; ///< The z offset of the sub boom in mm. Z offsets are up+/down-. @@ -137,21 +164,73 @@ namespace isobus static SubBoom parse_sub_boom(DeviceDescriptorObjectPool &ddop, std::shared_ptr elementObject); + /// @brief Parses a bin element of the DDOP + /// @param[in] ddop The DDOP to get the implement product control info from + /// @param[in] elementObject The element to parse + /// @returns The parsed product control information, or a default product control information if the elementObject is invalid or has no relevant data + static ProductControlInformation parse_bin(DeviceDescriptorObjectPool &ddop, + std::shared_ptr elementObject); + /// @brief Sets the value and presence based on a DDI match. /// @param[in,out] objectPoolValue The object pool value to set. /// @param[in] property The device property object. /// @param[in] ddi The DDI to check against. - static void setValueFromProperty(ObjectPoolValue &objectPoolValue, - const std::shared_ptr &property, - DataDescriptionIndex ddi); + static void set_value_from_property(ObjectPoolValue &objectPoolValue, + const std::shared_ptr &property, + DataDescriptionIndex ddi); /// @brief Sets the settable flag based on a DDI match for process data. /// @param[in,out] objectPoolValue The object pool value to update. /// @param[in] processData The device process data object. /// @param[in] ddi The DDI to check against. - static void setEditableFromProcessData(ObjectPoolValue &objectPoolValue, - const std::shared_ptr &processData, - DataDescriptionIndex ddi); + static void set_editable_from_process_data(ObjectPoolValue &objectPoolValue, + const std::shared_ptr &processData, + DataDescriptionIndex ddi); + + /// @brief Sets the max rate field of the product control information based on the supplied object + /// if the DDI is known to be a max rate DDI. + /// @param[in,out] productControlInformation The product control information to update. + /// @param[in] object The object to use to update the product control information. + /// @param[in] ddi The DDI to check against. + static void set_product_control_information_max_rate(ProductControlInformation &productControlInformation, + const std::shared_ptr &object, + std::uint16_t ddi); + + /// @brief Sets the minimum rate field of the product control information based on the supplied object + /// if the DDI is known to be a minimum rate DDI. + /// @param[in,out] productControlInformation The product control information to update. + /// @param[in] object The object to use to update the product control information. + /// @param[in] ddi The DDI to check against. + static void set_product_control_information_min_rate(ProductControlInformation &productControlInformation, + const std::shared_ptr &object, + std::uint16_t ddi); + + /// @brief Sets the default rate field of the product control information based on the supplied object + /// if the DDI is known to be a default rate DDI. + /// @param[in,out] productControlInformation The product control information to update. + /// @param[in] object The object to use to update the product control information. + /// @param[in] ddi The DDI to check against. + static void set_product_control_information_default_rate(ProductControlInformation &productControlInformation, + const std::shared_ptr &object, + std::uint16_t ddi); + + /// @brief Sets the setpoint rate field of the product control information based on the supplied object + /// if the DDI is known to be a setpoint rate DDI. + /// @param[in,out] productControlInformation The product control information to update. + /// @param[in] object The object to use to update the product control information. + /// @param[in] ddi The DDI to check against. + static void set_product_control_information_setpoint_rate(ProductControlInformation &productControlInformation, + const std::shared_ptr &object, + std::uint16_t ddi); + + /// @brief Sets the actual rate field of the product control information based on the supplied object + /// if the DDI is known to be a actual rate DDI. + /// @param[in,out] productControlInformation The product control information to update. + /// @param[in] object The object to use to update the product control information. + /// @param[in] ddi The DDI to check against. + static void set_product_control_information_actual_rate(ProductControlInformation &productControlInformation, + const std::shared_ptr &object, + std::uint16_t ddi); }; } // namespace isobus diff --git a/isobus/include/isobus/isobus/isobus_standard_data_description_indices.hpp b/isobus/include/isobus/isobus/isobus_standard_data_description_indices.hpp index 3fae628e6..376363b88 100644 --- a/isobus/include/isobus/isobus/isobus_standard_data_description_indices.hpp +++ b/isobus/include/isobus/isobus/isobus_standard_data_description_indices.hpp @@ -541,7 +541,8 @@ namespace isobus ActualCoolingFluidTemperature = 0x020E, ///< The actual temperature of the cooling fluid for the machine. LastBaleCapacity = 0x0210, ///< The capacity of the bale that leaves the machine. PGNBasedData = 0xDFFE, ///< This DDI is used in the XML files to identify PGN based data. - RequestDefaultProcessData = 0xDFFF ///< Request Default Process Data. This DDE is the highest ISO assigned entity. The range above this number is reserved for manufacture specific DDE's. + RequestDefaultProcessData = 0xDFFF, ///< Request Default Process Data. This DDE is the highest ISO assigned entity. The range above this number is reserved for manufacture specific DDE's. + Reserved = 0xFFFF }; } diff --git a/isobus/src/isobus_device_descriptor_object_pool_helpers.cpp b/isobus/src/isobus_device_descriptor_object_pool_helpers.cpp index 2161d0ad1..63ea8e2e5 100644 --- a/isobus/src/isobus_device_descriptor_object_pool_helpers.cpp +++ b/isobus/src/isobus_device_descriptor_object_pool_helpers.cpp @@ -32,12 +32,13 @@ namespace isobus return value; } - DeviceDescriptorObjectPoolHelper::Section::Section() - { - } - - DeviceDescriptorObjectPoolHelper::SubBoom::SubBoom() + bool DeviceDescriptorObjectPoolHelper::ProductControlInformation::is_valid() const { + return ((rateActual.dataDictionaryIdentifier != static_cast(DataDescriptionIndex::Reserved)) || + (rateDefault.dataDictionaryIdentifier != static_cast(DataDescriptionIndex::Reserved)) || + (rateMaximum.dataDictionaryIdentifier != static_cast(DataDescriptionIndex::Reserved)) || + (rateMinimum.dataDictionaryIdentifier != static_cast(DataDescriptionIndex::Reserved)) || + (rateSetpoint.dataDictionaryIdentifier != static_cast(DataDescriptionIndex::Reserved))); } DeviceDescriptorObjectPoolHelper::Implement DeviceDescriptorObjectPoolHelper::get_implement_geometry(DeviceDescriptorObjectPool &ddop) @@ -96,6 +97,26 @@ namespace isobus // If we didn't find a function, the device element object is the root of the boom. // So we'll reparse the device element object to get the sections and properties we care about. parse_element(ddop, deviceElementObject, retVal); + + // Search all elements whose parent is the device element object + // To look for bins as well, since we didn't find any functions. + for (std::uint16_t k = 0; k < ddop.size(); k++) + { + auto potentialBin = ddop.get_object_by_index(k); + + if ((nullptr != potentialBin) && + (task_controller_object::ObjectTypes::DeviceElement == potentialBin->get_object_type()) && + (std::static_pointer_cast(potentialBin)->get_parent_object() == deviceElementObject->get_object_id()) && + (task_controller_object::DeviceElementObject::Type::Bin == std::static_pointer_cast(potentialBin)->get_type())) + { + auto binInfo = parse_bin(ddop, std::static_pointer_cast(potentialBin)); + + if (binInfo.is_valid() && !retVal.booms.empty()) + { + retVal.booms[0].rates.push_back(binInfo); + } + } + } } return retVal; } @@ -120,10 +141,21 @@ namespace isobus if ((nullptr != element) && (task_controller_object::ObjectTypes::DeviceElement == element->get_object_type()) && - (std::static_pointer_cast(element)->get_parent_object() == elementObject->get_object_id()) && - (task_controller_object::DeviceElementObject::Type::Function == std::static_pointer_cast(element)->get_type())) + (std::static_pointer_cast(element)->get_parent_object() == elementObject->get_object_id())) { - boomToPopulate.subBooms.push_back(parse_sub_boom(ddop, std::static_pointer_cast(element))); + if (task_controller_object::DeviceElementObject::Type::Function == std::static_pointer_cast(element)->get_type()) + { + boomToPopulate.subBooms.push_back(parse_sub_boom(ddop, std::static_pointer_cast(element))); + } + else if (task_controller_object::DeviceElementObject::Type::Bin == std::static_pointer_cast(element)->get_type()) + { + auto binInfo = parse_bin(ddop, std::static_pointer_cast(element)); + + if (binInfo.is_valid()) + { + boomToPopulate.rates.push_back(binInfo); + } + } } } } @@ -154,16 +186,26 @@ namespace isobus if (task_controller_object::ObjectTypes::DeviceProperty == child->get_object_type()) { auto property = std::static_pointer_cast(child); - setValueFromProperty(boomToPopulate.xOffset_mm, property, DataDescriptionIndex::DeviceElementOffsetX); - setValueFromProperty(boomToPopulate.yOffset_mm, property, DataDescriptionIndex::DeviceElementOffsetY); - setValueFromProperty(boomToPopulate.zOffset_mm, property, DataDescriptionIndex::DeviceElementOffsetZ); + set_value_from_property(boomToPopulate.xOffset_mm, property, DataDescriptionIndex::DeviceElementOffsetX); + set_value_from_property(boomToPopulate.yOffset_mm, property, DataDescriptionIndex::DeviceElementOffsetY); + set_value_from_property(boomToPopulate.zOffset_mm, property, DataDescriptionIndex::DeviceElementOffsetZ); } else if (task_controller_object::ObjectTypes::DeviceProcessData == child->get_object_type()) { auto processData = std::static_pointer_cast(child); - setEditableFromProcessData(boomToPopulate.xOffset_mm, processData, DataDescriptionIndex::DeviceElementOffsetX); - setEditableFromProcessData(boomToPopulate.yOffset_mm, processData, DataDescriptionIndex::DeviceElementOffsetY); - setEditableFromProcessData(boomToPopulate.zOffset_mm, processData, DataDescriptionIndex::DeviceElementOffsetZ); + set_editable_from_process_data(boomToPopulate.xOffset_mm, processData, DataDescriptionIndex::DeviceElementOffsetX); + set_editable_from_process_data(boomToPopulate.yOffset_mm, processData, DataDescriptionIndex::DeviceElementOffsetY); + set_editable_from_process_data(boomToPopulate.zOffset_mm, processData, DataDescriptionIndex::DeviceElementOffsetZ); + } + else if ((task_controller_object::ObjectTypes::DeviceElement == child->get_object_type()) && + (task_controller_object::DeviceElementObject::Type::Bin == std::static_pointer_cast(child)->get_type())) + { + auto binInfo = parse_bin(ddop, std::static_pointer_cast(child)); + + if (binInfo.is_valid()) + { + boomToPopulate.rates.push_back(binInfo); + } } } } @@ -189,21 +231,32 @@ namespace isobus if (task_controller_object::ObjectTypes::DeviceProperty == sectionChildObject->get_object_type()) { auto property = std::static_pointer_cast(sectionChildObject); - setValueFromProperty(retVal.xOffset_mm, property, DataDescriptionIndex::DeviceElementOffsetX); - setValueFromProperty(retVal.yOffset_mm, property, DataDescriptionIndex::DeviceElementOffsetY); - setValueFromProperty(retVal.zOffset_mm, property, DataDescriptionIndex::DeviceElementOffsetZ); - setValueFromProperty(retVal.width_mm, property, DataDescriptionIndex::ActualWorkingWidth); + set_value_from_property(retVal.xOffset_mm, property, DataDescriptionIndex::DeviceElementOffsetX); + set_value_from_property(retVal.yOffset_mm, property, DataDescriptionIndex::DeviceElementOffsetY); + set_value_from_property(retVal.zOffset_mm, property, DataDescriptionIndex::DeviceElementOffsetZ); + set_value_from_property(retVal.width_mm, property, DataDescriptionIndex::ActualWorkingWidth); } else if (task_controller_object::ObjectTypes::DeviceProcessData == sectionChildObject->get_object_type()) { auto processData = std::static_pointer_cast(sectionChildObject); - setEditableFromProcessData(retVal.xOffset_mm, processData, DataDescriptionIndex::DeviceElementOffsetX); - setEditableFromProcessData(retVal.yOffset_mm, processData, DataDescriptionIndex::DeviceElementOffsetY); - setEditableFromProcessData(retVal.zOffset_mm, processData, DataDescriptionIndex::DeviceElementOffsetZ); - setEditableFromProcessData(retVal.width_mm, processData, DataDescriptionIndex::ActualWorkingWidth); + set_editable_from_process_data(retVal.xOffset_mm, processData, DataDescriptionIndex::DeviceElementOffsetX); + set_editable_from_process_data(retVal.yOffset_mm, processData, DataDescriptionIndex::DeviceElementOffsetY); + set_editable_from_process_data(retVal.zOffset_mm, processData, DataDescriptionIndex::DeviceElementOffsetZ); + set_editable_from_process_data(retVal.width_mm, processData, DataDescriptionIndex::ActualWorkingWidth); + } + else if ((task_controller_object::ObjectTypes::DeviceElement == sectionChildObject->get_object_type()) && + (task_controller_object::DeviceElementObject::Type::Bin == std::static_pointer_cast(sectionChildObject)->get_type())) + { + auto binInfo = parse_bin(ddop, std::static_pointer_cast(sectionChildObject)); + + if (binInfo.is_valid()) + { + retVal.rates.push_back(binInfo); + } } } } + retVal.elementNumber = elementObject->get_element_number(); return retVal; } @@ -216,7 +269,7 @@ namespace isobus // We again have to search the whole pool because elements have parent links, not child links for (std::uint16_t i = 0; i < ddop.size(); i++) { - auto section = ddop.get_object_by_index(static_cast(i)); + auto section = ddop.get_object_by_index(i); if ((nullptr != section) && (task_controller_object::ObjectTypes::DeviceElement == section->get_object_type()) && @@ -237,27 +290,76 @@ namespace isobus if (task_controller_object::ObjectTypes::DeviceProperty == childObject->get_object_type()) { auto property = std::static_pointer_cast(childObject); - setValueFromProperty(retVal.xOffset_mm, property, DataDescriptionIndex::DeviceElementOffsetX); - setValueFromProperty(retVal.yOffset_mm, property, DataDescriptionIndex::DeviceElementOffsetY); - setValueFromProperty(retVal.zOffset_mm, property, DataDescriptionIndex::DeviceElementOffsetZ); - setValueFromProperty(retVal.width_mm, property, DataDescriptionIndex::ActualWorkingWidth); + set_value_from_property(retVal.xOffset_mm, property, DataDescriptionIndex::DeviceElementOffsetX); + set_value_from_property(retVal.yOffset_mm, property, DataDescriptionIndex::DeviceElementOffsetY); + set_value_from_property(retVal.zOffset_mm, property, DataDescriptionIndex::DeviceElementOffsetZ); + set_value_from_property(retVal.width_mm, property, DataDescriptionIndex::ActualWorkingWidth); } else if (task_controller_object::ObjectTypes::DeviceProcessData == childObject->get_object_type()) { auto processData = std::static_pointer_cast(childObject); - setEditableFromProcessData(retVal.xOffset_mm, processData, DataDescriptionIndex::DeviceElementOffsetX); - setEditableFromProcessData(retVal.yOffset_mm, processData, DataDescriptionIndex::DeviceElementOffsetY); - setEditableFromProcessData(retVal.zOffset_mm, processData, DataDescriptionIndex::DeviceElementOffsetZ); - setEditableFromProcessData(retVal.width_mm, processData, DataDescriptionIndex::ActualWorkingWidth); + set_editable_from_process_data(retVal.xOffset_mm, processData, DataDescriptionIndex::DeviceElementOffsetX); + set_editable_from_process_data(retVal.yOffset_mm, processData, DataDescriptionIndex::DeviceElementOffsetY); + set_editable_from_process_data(retVal.zOffset_mm, processData, DataDescriptionIndex::DeviceElementOffsetZ); + set_editable_from_process_data(retVal.width_mm, processData, DataDescriptionIndex::ActualWorkingWidth); + } + else if ((task_controller_object::ObjectTypes::DeviceElement == childObject->get_object_type()) && + (task_controller_object::DeviceElementObject::Type::Bin == std::static_pointer_cast(childObject)->get_type())) + { + auto binInfo = parse_bin(ddop, std::static_pointer_cast(childObject)); + + if (binInfo.is_valid()) + { + retVal.rates.push_back(binInfo); + } } } } return retVal; } - void DeviceDescriptorObjectPoolHelper::setValueFromProperty(ObjectPoolValue &objectPoolValue, - const std::shared_ptr &property, - DataDescriptionIndex ddi) + DeviceDescriptorObjectPoolHelper::ProductControlInformation DeviceDescriptorObjectPoolHelper::parse_bin(DeviceDescriptorObjectPool &ddop, + std::shared_ptr elementObject) + { + // A bin is used to identify a product. + // We'll use this to populate the product control information. + ProductControlInformation retVal; + + if (task_controller_object::DeviceElementObject::Type::Bin == elementObject->get_type()) + { + for (std::uint16_t i = 0; i < elementObject->get_number_child_objects(); i++) + { + auto object = ddop.get_object_by_id(elementObject->get_child_object_id(i)); + + if (nullptr != object) + { + if (task_controller_object::ObjectTypes::DeviceProcessData == object->get_object_type()) + { + std::uint16_t ddi = std::static_pointer_cast(object)->get_ddi(); + set_product_control_information_max_rate(retVal, object, ddi); + set_product_control_information_min_rate(retVal, object, ddi); + set_product_control_information_default_rate(retVal, object, ddi); + set_product_control_information_setpoint_rate(retVal, object, ddi); + set_product_control_information_actual_rate(retVal, object, ddi); + } + else if (task_controller_object::ObjectTypes::DeviceProperty == object->get_object_type()) + { + std::uint16_t ddi = std::static_pointer_cast(object)->get_ddi(); + set_product_control_information_max_rate(retVal, object, ddi); + set_product_control_information_min_rate(retVal, object, ddi); + set_product_control_information_default_rate(retVal, object, ddi); + set_product_control_information_setpoint_rate(retVal, object, ddi); + set_product_control_information_actual_rate(retVal, object, ddi); + } + } + } + } + return retVal; + } + + void DeviceDescriptorObjectPoolHelper::set_value_from_property(ObjectPoolValue &objectPoolValue, + const std::shared_ptr &property, + DataDescriptionIndex ddi) { if (property->get_ddi() == static_cast(ddi)) { @@ -266,13 +368,278 @@ namespace isobus } } - void DeviceDescriptorObjectPoolHelper::setEditableFromProcessData(ObjectPoolValue &objectPoolValue, - const std::shared_ptr &processData, - DataDescriptionIndex ddi) + void DeviceDescriptorObjectPoolHelper::set_editable_from_process_data(ObjectPoolValue &objectPoolValue, + const std::shared_ptr &processData, + DataDescriptionIndex ddi) { if (processData->get_ddi() == static_cast(ddi)) { - objectPoolValue.isSettable = true; + objectPoolValue.isSettable = (0 != (static_cast(task_controller_object::DeviceProcessDataObject::PropertiesBit::Settable) & processData->get_properties_bitfield())); + } + } + + void DeviceDescriptorObjectPoolHelper::set_product_control_information_max_rate(ProductControlInformation &productControlInformation, + const std::shared_ptr &object, + std::uint16_t ddi) + { + switch (ddi) + { + case static_cast(DataDescriptionIndex::MaximumApplicationRateOfAmmonium): + case static_cast(DataDescriptionIndex::MaximumApplicationRateOfDryMatter): + case static_cast(DataDescriptionIndex::MaximumApplicationRateOfNitrogen): + case static_cast(DataDescriptionIndex::MaximumApplicationRateOfPhosphor): + case static_cast(DataDescriptionIndex::MaximumApplicationRateOfPotassium): + case static_cast(DataDescriptionIndex::MaximumCountPerAreaApplicationRate): + case static_cast(DataDescriptionIndex::MaximumCountPerTimeApplicationRate): + case static_cast(DataDescriptionIndex::MaximumMassPerAreaApplicationRate): + case static_cast(DataDescriptionIndex::MaximumMassPerMassApplicationRate): + case static_cast(DataDescriptionIndex::MaximumMassPerTimeApplicationRate): + case static_cast(DataDescriptionIndex::MaximumVolumePerAreaApplicationRate): + case static_cast(DataDescriptionIndex::MaximumVolumePerMassApplicationRate): + case static_cast(DataDescriptionIndex::MaximumVolumePerVolumeApplicationRate): + case static_cast(DataDescriptionIndex::MaximumVolumePerTimeApplicationRate): + case static_cast(DataDescriptionIndex::MaximumSpacingApplicationRate): + case static_cast(DataDescriptionIndex::MaximumRevolutionsPerTime): + { + if (task_controller_object::ObjectTypes::DeviceProcessData == object->get_object_type()) + { + auto processData = std::static_pointer_cast(object); + + if (static_cast(ddi) == processData->get_ddi()) + { + productControlInformation.rateMaximum.isSettable = (0 != (static_cast(task_controller_object::DeviceProcessDataObject::PropertiesBit::Settable) & processData->get_properties_bitfield())); + productControlInformation.rateMaximum.objectID = processData->get_object_id(); + productControlInformation.rateMaximum.dataDictionaryIdentifier = processData->get_ddi(); + } + } + else if (task_controller_object::ObjectTypes::DeviceProperty == object->get_object_type()) + { + auto property = std::static_pointer_cast(object); + + if (static_cast(ddi) == property->get_ddi()) + { + productControlInformation.rateMaximum.objectID = property->get_object_id(); + productControlInformation.rateMaximum.isValuePresent = true; + productControlInformation.rateMaximum.value = property->get_value(); + productControlInformation.rateMaximum.dataDictionaryIdentifier = property->get_ddi(); + } + } + } + break; + + default: + break; + } + } + + void DeviceDescriptorObjectPoolHelper::set_product_control_information_min_rate(ProductControlInformation &productControlInformation, + const std::shared_ptr &object, + std::uint16_t ddi) + { + switch (ddi) + { + case static_cast(DataDescriptionIndex::MinimumApplicationRateOfAmmonium): + case static_cast(DataDescriptionIndex::MinimumApplicationRateOfDryMatter): + case static_cast(DataDescriptionIndex::MinimumApplicationRateOfNitrogen): + case static_cast(DataDescriptionIndex::MinimumApplicationRateOfPhosphor): + case static_cast(DataDescriptionIndex::MinimumApplicationRateOfPotassium): + case static_cast(DataDescriptionIndex::MinimumCountPerAreaApplicationRate): + case static_cast(DataDescriptionIndex::MinimumCountPerTimeApplicationRate): + case static_cast(DataDescriptionIndex::MinimumMassPerAreaApplicationRate): + case static_cast(DataDescriptionIndex::MinimumMassPerMassApplicationRate): + case static_cast(DataDescriptionIndex::MinimumMassPerTimeApplicationRate): + case static_cast(DataDescriptionIndex::MinimumVolumePerAreaApplicationRate): + case static_cast(DataDescriptionIndex::MinimumVolumePerMassApplicationRate): + case static_cast(DataDescriptionIndex::MinimumVolumePerVolumeApplicationRate): + case static_cast(DataDescriptionIndex::MinimumVolumePerTimeApplicationRate): + case static_cast(DataDescriptionIndex::MinimumSpacingApplicationRate): + case static_cast(DataDescriptionIndex::MinimumRevolutionsPerTime): + { + if (task_controller_object::ObjectTypes::DeviceProcessData == object->get_object_type()) + { + auto processData = std::static_pointer_cast(object); + + if (static_cast(ddi) == processData->get_ddi()) + { + productControlInformation.rateMinimum.isSettable = (0 != (static_cast(task_controller_object::DeviceProcessDataObject::PropertiesBit::Settable) & processData->get_properties_bitfield())); + productControlInformation.rateMinimum.objectID = processData->get_object_id(); + productControlInformation.rateMinimum.dataDictionaryIdentifier = processData->get_ddi(); + } + } + else if (task_controller_object::ObjectTypes::DeviceProperty == object->get_object_type()) + { + auto property = std::static_pointer_cast(object); + + if (static_cast(ddi) == property->get_ddi()) + { + productControlInformation.rateMinimum.objectID = property->get_object_id(); + productControlInformation.rateMinimum.isValuePresent = true; + productControlInformation.rateMinimum.value = property->get_value(); + productControlInformation.rateMinimum.dataDictionaryIdentifier = property->get_ddi(); + } + } + } + break; + + default: + break; + } + } + + void DeviceDescriptorObjectPoolHelper::set_product_control_information_default_rate(ProductControlInformation &productControlInformation, + const std::shared_ptr &object, + std::uint16_t ddi) + { + switch (ddi) + { + case static_cast(DataDescriptionIndex::DefaultRevolutionsPerTime): + case static_cast(DataDescriptionIndex::DefaultCountPerAreaApplicationRate): + case static_cast(DataDescriptionIndex::DefaultCountPerTimeApplicationRate): + case static_cast(DataDescriptionIndex::DefaultMassPerAreaApplicationRate): + case static_cast(DataDescriptionIndex::DefaultMassPerMassApplicationRate): + case static_cast(DataDescriptionIndex::DefaultMassPerTimeApplicationRate): + case static_cast(DataDescriptionIndex::DefaultVolumePerAreaApplicationRate): + case static_cast(DataDescriptionIndex::DefaultVolumePerMassApplicationRate): + case static_cast(DataDescriptionIndex::DefaultVolumePerVolumeApplicationRate): + case static_cast(DataDescriptionIndex::DefaultVolumePerTimeApplicationRate): + case static_cast(DataDescriptionIndex::DefaultSpacingApplicationRate): + { + if (task_controller_object::ObjectTypes::DeviceProcessData == object->get_object_type()) + { + auto processData = std::static_pointer_cast(object); + + if (static_cast(ddi) == processData->get_ddi()) + { + productControlInformation.rateDefault.isSettable = (0 != (static_cast(task_controller_object::DeviceProcessDataObject::PropertiesBit::Settable) & processData->get_properties_bitfield())); + productControlInformation.rateDefault.objectID = processData->get_object_id(); + productControlInformation.rateDefault.dataDictionaryIdentifier = processData->get_ddi(); + } + } + else if (task_controller_object::ObjectTypes::DeviceProperty == object->get_object_type()) + { + auto property = std::static_pointer_cast(object); + + if (static_cast(ddi) == property->get_ddi()) + { + productControlInformation.rateDefault.objectID = property->get_object_id(); + productControlInformation.rateDefault.isValuePresent = true; + productControlInformation.rateDefault.value = property->get_value(); + productControlInformation.rateDefault.dataDictionaryIdentifier = property->get_ddi(); + } + } + } + break; + + default: + break; + } + } + + void DeviceDescriptorObjectPoolHelper::set_product_control_information_setpoint_rate(ProductControlInformation &productControlInformation, + const std::shared_ptr &object, + std::uint16_t ddi) + { + switch (ddi) + { + case static_cast(DataDescriptionIndex::SetpointApplicationRateOfAmmonium): + case static_cast(DataDescriptionIndex::SetpointApplicationRateOfDryMatter): + case static_cast(DataDescriptionIndex::SetpointApplicationRateOfNitrogen): + case static_cast(DataDescriptionIndex::SetpointApplicationRateOfPhosphor): + case static_cast(DataDescriptionIndex::SetpointApplicationRateOfPotassium): + case static_cast(DataDescriptionIndex::SetpointCountPerAreaApplicationRate): + case static_cast(DataDescriptionIndex::SetpointCountPerTimeApplicationRate): + case static_cast(DataDescriptionIndex::SetpointMassPerAreaApplicationRate): + case static_cast(DataDescriptionIndex::SetpointMassPerMassApplicationRate): + case static_cast(DataDescriptionIndex::SetpointMassPerTimeApplicationRate): + case static_cast(DataDescriptionIndex::SetpointVolumePerAreaApplicationRate): + case static_cast(DataDescriptionIndex::SetpointVolumePerMassApplicationRate): + case static_cast(DataDescriptionIndex::SetpointVolumePerVolumeApplicationRate): + case static_cast(DataDescriptionIndex::SetpointVolumePerTimeApplicationRate): + case static_cast(DataDescriptionIndex::SetpointSpacingApplicationRate): + case static_cast(DataDescriptionIndex::SetpointRevolutionsSpecifiedAsCountPerTime): + { + if (task_controller_object::ObjectTypes::DeviceProcessData == object->get_object_type()) + { + auto processData = std::static_pointer_cast(object); + + if (static_cast(ddi) == processData->get_ddi()) + { + productControlInformation.rateSetpoint.isSettable = (0 != (static_cast(task_controller_object::DeviceProcessDataObject::PropertiesBit::Settable) & processData->get_properties_bitfield())); + productControlInformation.rateSetpoint.objectID = processData->get_object_id(); + productControlInformation.rateSetpoint.dataDictionaryIdentifier = processData->get_ddi(); + } + } + else if (task_controller_object::ObjectTypes::DeviceProperty == object->get_object_type()) + { + auto property = std::static_pointer_cast(object); + + if (static_cast(ddi) == property->get_ddi()) + { + productControlInformation.rateSetpoint.objectID = property->get_object_id(); + productControlInformation.rateSetpoint.isValuePresent = true; + productControlInformation.rateSetpoint.value = property->get_value(); + productControlInformation.rateSetpoint.dataDictionaryIdentifier = property->get_ddi(); + } + } + } + break; + + default: + break; + } + } + + void DeviceDescriptorObjectPoolHelper::set_product_control_information_actual_rate(ProductControlInformation &productControlInformation, + const std::shared_ptr &object, + std::uint16_t ddi) + { + switch (ddi) + { + case static_cast(DataDescriptionIndex::ActualApplicationRateOfAmmonium): + case static_cast(DataDescriptionIndex::ActualApplicationRateOfDryMatter): + case static_cast(DataDescriptionIndex::ActualApplicationRateOfNitrogen): + case static_cast(DataDescriptionIndex::ActualApplicationRateOfPhosphor): + case static_cast(DataDescriptionIndex::ActualApplicationRateOfPotassium): + case static_cast(DataDescriptionIndex::ActualCountPerAreaApplicationRate): + case static_cast(DataDescriptionIndex::ActualCountPerTimeApplicationRate): + case static_cast(DataDescriptionIndex::ActualMassPerAreaApplicationRate): + case static_cast(DataDescriptionIndex::ActualMassPerMassApplicationRate): + case static_cast(DataDescriptionIndex::ActualMassPerTimeApplicationRate): + case static_cast(DataDescriptionIndex::ActualVolumePerAreaApplicationRate): + case static_cast(DataDescriptionIndex::ActualVolumePerMassApplicationRate): + case static_cast(DataDescriptionIndex::ActualVolumePerVolumeApplicationRate): + case static_cast(DataDescriptionIndex::ActualVolumePerTimeApplicationRate): + case static_cast(DataDescriptionIndex::ActualSpacingApplicationRate): + case static_cast(DataDescriptionIndex::ActualRevolutionsPerTime): + { + if (task_controller_object::ObjectTypes::DeviceProcessData == object->get_object_type()) + { + auto processData = std::static_pointer_cast(object); + + if (static_cast(ddi) == processData->get_ddi()) + { + productControlInformation.rateActual.isSettable = (0 != (static_cast(task_controller_object::DeviceProcessDataObject::PropertiesBit::Settable) & processData->get_properties_bitfield())); + productControlInformation.rateActual.objectID = processData->get_object_id(); + productControlInformation.rateActual.dataDictionaryIdentifier = processData->get_ddi(); + } + } + else if (task_controller_object::ObjectTypes::DeviceProperty == object->get_object_type()) + { + auto property = std::static_pointer_cast(object); + + if (static_cast(ddi) == property->get_ddi()) + { + productControlInformation.rateActual.objectID = property->get_object_id(); + productControlInformation.rateActual.isValuePresent = true; + productControlInformation.rateActual.value = property->get_value(); + productControlInformation.rateActual.dataDictionaryIdentifier = property->get_ddi(); + } + } + } + break; + + default: + break; } } } // namespace isobus diff --git a/test/tc_server_tests.cpp b/test/tc_server_tests.cpp index 2e0fbe93b..b9ecdc095 100644 --- a/test/tc_server_tests.cpp +++ b/test/tc_server_tests.cpp @@ -1186,12 +1186,17 @@ TEST(TASK_CONTROLLER_SERVER_TESTS, DDOPHelper_SeederExample) ASSERT_EQ(1, implement.booms.size()); ASSERT_EQ(16, implement.booms.at(0).sections.size()); + ASSERT_EQ(1, implement.booms.at(0).rates.size()); EXPECT_TRUE(implement.booms.at(0).subBooms.empty()); EXPECT_TRUE(implement.booms.at(0).xOffset_mm); EXPECT_TRUE(implement.booms.at(0).yOffset_mm); EXPECT_TRUE(implement.booms.at(0).zOffset_mm); + EXPECT_EQ(1, implement.booms.at(0).rates.at(0).rateSetpoint.dataDictionaryIdentifier); // Setpoint Application Rate specified as volume per area + EXPECT_EQ(2, implement.booms.at(0).rates.at(0).rateActual.dataDictionaryIdentifier); // Actual Application Rate specified as volume per area + EXPECT_TRUE(implement.booms.at(0).rates.at(0).rateSetpoint.editable()); + for (std::size_t i = 0; i < 16; i++) { EXPECT_TRUE(implement.booms.at(0).sections.at(i).width_mm); @@ -1215,6 +1220,7 @@ TEST(TASK_CONTROLLER_SERVER_TESTS, DDOPHelper_SubBooms) ddop.add_device_element("SubBoom2", 0, 11, isobus::task_controller_object::DeviceElementObject::Type::Function, 3); ddop.add_device_element("Section1", 0, 2, isobus::task_controller_object::DeviceElementObject::Type::Section, 4); ddop.add_device_element("Section2", 0, 3, isobus::task_controller_object::DeviceElementObject::Type::Section, 5); + ddop.add_device_element("SubBoomProduct", 0, 2, isobus::task_controller_object::DeviceElementObject::Type::Bin, 40); ddop.add_device_property("Xoffset", 2000, static_cast(DataDescriptionIndex::DeviceElementOffsetX), 0xFFFF, 6); ddop.add_device_property("yoffset", 3000, static_cast(DataDescriptionIndex::DeviceElementOffsetY), 0xFFFF, 7); ddop.add_device_property("zoffset", 4000, static_cast(DataDescriptionIndex::DeviceElementOffsetZ), 0xFFFF, 8); @@ -1223,10 +1229,12 @@ TEST(TASK_CONTROLLER_SERVER_TESTS, DDOPHelper_SubBooms) ddop.add_device_property("SBzoffset", 7000, static_cast(DataDescriptionIndex::DeviceElementOffsetZ), 0xFFFF, 12); ddop.add_device_process_data("SBxoffset", static_cast(DataDescriptionIndex::DeviceElementOffsetX), 0xFFFF, 0, 0, 13); ddop.add_device_process_data("secTestDPD", static_cast(DataDescriptionIndex::DeviceElementOffsetX), 0xFFFF, 0, 0, 14); + ddop.add_device_process_data("SBRate", static_cast(DataDescriptionIndex::ActualApplicationRateOfPhosphor), 0xFFFF, 0, 0, 41); auto section1 = std::static_pointer_cast(ddop.get_object_by_id(4)); auto section2 = std::static_pointer_cast(ddop.get_object_by_id(5)); auto subBoom1 = std::static_pointer_cast(ddop.get_object_by_id(2)); + auto bin1 = std::static_pointer_cast(ddop.get_object_by_id(40)); ASSERT_NE(nullptr, section1); ASSERT_NE(nullptr, section2); @@ -1240,6 +1248,8 @@ TEST(TASK_CONTROLLER_SERVER_TESTS, DDOPHelper_SubBooms) section2->add_reference_to_child_object(10); subBoom1->add_reference_to_child_object(12); subBoom1->add_reference_to_child_object(13); + subBoom1->add_reference_to_child_object(40); + bin1->add_reference_to_child_object(41); auto implement = DeviceDescriptorObjectPoolHelper::get_implement_geometry(ddop); @@ -1248,12 +1258,13 @@ TEST(TASK_CONTROLLER_SERVER_TESTS, DDOPHelper_SubBooms) ASSERT_EQ(2, implement.booms.at(0).subBooms.size()); ASSERT_EQ(1, implement.booms.at(0).subBooms.at(0).sections.size()); ASSERT_EQ(1, implement.booms.at(0).subBooms.at(1).sections.size()); + ASSERT_EQ(1, implement.booms.at(0).subBooms.at(0).rates.size()); EXPECT_FALSE(implement.booms.at(0).xOffset_mm); EXPECT_FALSE(implement.booms.at(0).yOffset_mm); EXPECT_FALSE(implement.booms.at(0).zOffset_mm); EXPECT_FALSE(implement.booms.at(0).subBooms.at(0).xOffset_mm); - EXPECT_TRUE(implement.booms.at(0).subBooms.at(0).xOffset_mm.editable()); + EXPECT_FALSE(implement.booms.at(0).subBooms.at(0).xOffset_mm.editable()); // Settable bit is unset EXPECT_FALSE(implement.booms.at(0).subBooms.at(0).yOffset_mm); EXPECT_FALSE(implement.booms.at(0).subBooms.at(0).yOffset_mm.editable()); EXPECT_TRUE(implement.booms.at(0).subBooms.at(0).zOffset_mm); @@ -1265,7 +1276,7 @@ TEST(TASK_CONTROLLER_SERVER_TESTS, DDOPHelper_SubBooms) EXPECT_TRUE(implement.booms.at(0).subBooms.at(0).sections.at(0).zOffset_mm); EXPECT_TRUE(implement.booms.at(0).subBooms.at(1).sections.at(0).width_mm); EXPECT_FALSE(implement.booms.at(0).subBooms.at(1).sections.at(0).xOffset_mm); - EXPECT_TRUE(implement.booms.at(0).subBooms.at(1).sections.at(0).xOffset_mm.editable()); + EXPECT_FALSE(implement.booms.at(0).subBooms.at(1).sections.at(0).xOffset_mm.editable()); // Settable bit is unset EXPECT_TRUE(implement.booms.at(0).subBooms.at(1).sections.at(0).yOffset_mm); EXPECT_TRUE(implement.booms.at(0).subBooms.at(1).sections.at(0).zOffset_mm); @@ -1294,16 +1305,21 @@ TEST(TASK_CONTROLLER_SERVER_TESTS, DDOPHelper_NoFunctions) ddop.add_device("TEST", "123", "123", "1234567", { 1, 2, 3, 4, 5, 6, 7 }, {}, 0); ddop.add_device_element("Section1", 0, 1, isobus::task_controller_object::DeviceElementObject::Type::Section, 4); ddop.add_device_element("Section2", 0, 1, isobus::task_controller_object::DeviceElementObject::Type::Section, 5); + ddop.add_device_element("Product", 0, 1, isobus::task_controller_object::DeviceElementObject::Type::Bin, 45); ddop.add_device_property("Xoffset", 2000, static_cast(DataDescriptionIndex::DeviceElementOffsetX), 0xFFFF, 6); ddop.add_device_property("yoffset", 3000, static_cast(DataDescriptionIndex::DeviceElementOffsetY), 0xFFFF, 7); ddop.add_device_property("zoffset", 4000, static_cast(DataDescriptionIndex::DeviceElementOffsetZ), 0xFFFF, 8); ddop.add_device_property("width1", 5000, static_cast(DataDescriptionIndex::ActualWorkingWidth), 0xFFFF, 9); ddop.add_device_property("width2", 6000, static_cast(DataDescriptionIndex::ActualWorkingWidth), 0xFFFF, 10); + ddop.add_device_property("Rate Setpoint", 7000, static_cast(DataDescriptionIndex::SetpointMassPerAreaApplicationRate), 0xFFFF, 46); + ddop.add_device_property("Rate Default", 8000, static_cast(DataDescriptionIndex::DefaultMassPerAreaApplicationRate), 0xFFFF, 47); auto section1 = std::static_pointer_cast(ddop.get_object_by_id(4)); auto section2 = std::static_pointer_cast(ddop.get_object_by_id(5)); + auto product = std::static_pointer_cast(ddop.get_object_by_id(45)); ASSERT_NE(nullptr, section1); ASSERT_NE(nullptr, section2); + ASSERT_NE(nullptr, product); section1->add_reference_to_child_object(6); section1->add_reference_to_child_object(7); @@ -1313,12 +1329,22 @@ TEST(TASK_CONTROLLER_SERVER_TESTS, DDOPHelper_NoFunctions) section2->add_reference_to_child_object(7); section2->add_reference_to_child_object(8); section2->add_reference_to_child_object(10); + product->add_reference_to_child_object(46); + product->add_reference_to_child_object(47); auto implement = DeviceDescriptorObjectPoolHelper::get_implement_geometry(ddop); ASSERT_EQ(1, implement.booms.size()); ASSERT_EQ(2, implement.booms.at(0).sections.size()); ASSERT_EQ(0, implement.booms.at(0).subBooms.size()); + ASSERT_EQ(1, implement.booms.at(0).rates.size()); + + EXPECT_EQ(7000, implement.booms.at(0).rates.at(0).rateSetpoint.get()); + EXPECT_EQ(8000, implement.booms.at(0).rates.at(0).rateDefault.get()); + EXPECT_EQ(6, implement.booms.at(0).rates.at(0).rateSetpoint.dataDictionaryIdentifier); + EXPECT_EQ(8, implement.booms.at(0).rates.at(0).rateDefault.dataDictionaryIdentifier); + EXPECT_FALSE(implement.booms.at(0).rates.at(0).rateSetpoint.editable()); + EXPECT_FALSE(implement.booms.at(0).rates.at(0).rateDefault.editable()); EXPECT_FALSE(implement.booms.at(0).xOffset_mm); EXPECT_FALSE(implement.booms.at(0).yOffset_mm);