diff --git a/CMakeLists.txt b/CMakeLists.txt index a42329e9e..854a385e7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -115,7 +115,7 @@ ign_find_package(SQLite3 # Configure the build #============================================================================ ign_configure_build(QUIT_IF_BUILD_ERRORS - COMPONENTS log) + COMPONENTS log parameters) #============================================================================ # ign command line support diff --git a/log/include/ignition/transport/log/Recorder.hh b/log/include/ignition/transport/log/Recorder.hh index d2bfcfc30..5c28cca6e 100644 --- a/log/include/ignition/transport/log/Recorder.hh +++ b/log/include/ignition/transport/log/Recorder.hh @@ -73,7 +73,7 @@ namespace ignition /// \brief Begin recording topics /// \param[in] _file path to log file - /// \return NO_ERROR if recording was successfully started. If the file + /// \return SUCCESS if recording was successfully started. If the file /// already existed, this will return FAILED_TO_OPEN. public: RecorderError Start(const std::string &_file); @@ -86,7 +86,7 @@ namespace ignition /// \param[in] _topic The exact topic name /// \note This method attempts to subscribe to the topic immediately. /// The subscription will be kept until this is destructed. - /// \return NO_ERROR if the subscription was created. + /// \return SUCCESS if the subscription was created. public: RecorderError AddTopic(const std::string &_topic); /// \brief Add a topic to be recorded (regex match) diff --git a/parameters/include/ignition/transport/CMakeLists.txt b/parameters/include/ignition/transport/CMakeLists.txt new file mode 100644 index 000000000..9ac8dae51 --- /dev/null +++ b/parameters/include/ignition/transport/CMakeLists.txt @@ -0,0 +1,2 @@ + +ign_install_all_headers(COMPONENT parameters) diff --git a/parameters/include/ignition/transport/parameters/Client.hh b/parameters/include/ignition/transport/parameters/Client.hh new file mode 100644 index 000000000..659f26b3e --- /dev/null +++ b/parameters/include/ignition/transport/parameters/Client.hh @@ -0,0 +1,115 @@ +/* + * Copyright (C) 2022 Open Source Robotics Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * +*/ + +#ifndef IGNITION_TRANSPORT_PARAMETERS_CLIENT_HH_ +#define IGNITION_TRANSPORT_PARAMETERS_CLIENT_HH_ + +#include +#include + +#include "google/protobuf/message.h" + +#include "ignition/msgs/parameter_declarations.pb.h" + +#include "ignition/transport/config.hh" +#include "ignition/transport/Node.hh" +#include "ignition/transport/parameters/Export.hh" +#include "ignition/transport/parameters/Interface.hh" + +namespace ignition +{ + namespace transport + { + namespace parameters + { + + // Inline bracket to help doxygen filtering. + inline namespace IGNITION_TRANSPORT_VERSION_NAMESPACE { + + struct ParametersClientPrivate; + + /// \brief Allow to get, set, declare or list parameters + /// \brief in a remote registry. + class IGNITION_TRANSPORT_PARAMETERS_VISIBLE ParametersClient + : public ParametersInterface + { + /// \brief Constructor. + /// \param[in] _serverNamespace Namespace of the parameters registry + /// services. The client will send requests to: + /// * /${_serverNamespace}/get_parameter + /// * /${_serverNamespace}/list_parameters + /// * /${_serverNamespace}/set_parameter + /// * /${_serverNamespace}/declare_parameter + /// \param[in] _timeoutMs Time to wait for the server to respond. + public: ParametersClient( + const std::string & _serverNamespace = "", + unsigned int _timeoutMs = kDefaultTimeoutMs); + + /// \brief Destructor. + public: ~ParametersClient(); + + /// \brief No copy constructor. + public: ParametersClient(const ParametersClient &) = delete; + /// \brief No copy assignment. + public: ParametersClient & operator=( + const ParametersClient &) = delete; + /// \brief Default move constructor. + public: ParametersClient(ParametersClient &&); + /// \brief Default move assignment. + public: ParametersClient & operator=( + ParametersClient &&); + + /// \brief Declare a new parameter. + /// See ParametersInterface::DeclareParameter(). + public: ParameterResult DeclareParameter( + const std::string & _parameterName, + const google::protobuf::Message & _msg) final; + + /// \brief Request the value of a parameter. + /// See ParametersInterface::Parameter(). + public: ParameterResult Parameter( + const std::string & _parameterName, + google::protobuf::Message & _parameter) const final; + + public: ParameterResult Parameter( + const std::string & _parameterName, + std::unique_ptr & _parameter) const final; + + /// \brief Set the value of a parameter. + /// See ParametersInterface::SetParameter(). + public: ParameterResult SetParameter( + const std::string & _parameterName, + const google::protobuf::Message & _msg) final; + + /// \brief List all parameters. + /// \return Protobuf message with a list of all declared parameter + /// names and their types. + public: ignition::msgs::ParameterDeclarations + ListParameters() const final; + + private: + /// \brief Pointer to implementation. + private: std::unique_ptr dataPtr; + + constexpr static inline unsigned int kDefaultTimeoutMs = 5000; + }; + } + } + } +} + +#endif diff --git a/parameters/include/ignition/transport/parameters/Interface.hh b/parameters/include/ignition/transport/parameters/Interface.hh new file mode 100644 index 000000000..c13422e1e --- /dev/null +++ b/parameters/include/ignition/transport/parameters/Interface.hh @@ -0,0 +1,112 @@ +/* + * Copyright (C) 2022 Open Source Robotics Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * +*/ + +#ifndef IGNITION_TRANSPORT_PARAMETERS_INTERFACE_HH_ +#define IGNITION_TRANSPORT_PARAMETERS_INTERFACE_HH_ + +#include +#include +#include +#include + +#include + +#include + +#include "ignition/transport/config.hh" +#include "ignition/transport/parameters/result.hh" +#include "ignition/transport/parameters/Export.hh" + +namespace ignition +{ + namespace transport + { + namespace parameters + { + // Inline bracket to help doxygen filtering. + inline namespace IGNITION_TRANSPORT_VERSION_NAMESPACE { + + /// \brief Common interface, implemented by ParametersRegistry + /// (local updates) and by ParametersClients (remote requests). + class IGNITION_TRANSPORT_PARAMETERS_VISIBLE ParametersInterface + { + /// Default virtual destructor. + public: virtual ~ParametersInterface() = default; + + /// \brief Declare a new parameter. + /// \param[in] _parameterName Name of the parameter to be declared. + /// \param[in] _msg Protobuf message to be used as the initial + /// parameter value. + /// \return A ParameterResult return code, can return error types: + /// - ParameterResultType::AlreadyDeclared if the parameter was already + /// declared. + /// - ParameterResultType::InvalidType if the parameter type is not + /// valid. + public: virtual ParameterResult DeclareParameter( + const std::string & _parameterName, + const google::protobuf::Message & _msg) = 0; + + /// \brief Request the value of a parameter. + /// \param[in] _parameterName Name of the parameter to be requested. + /// \param[out] _parameter Output were the parameter value will be set. + /// \return A ParameterResult return code, can return error types: + /// - ParameterResultType::NotDeclared if the parameter was not + /// declared. + /// - ParameterResultType::InvalidType if the parameter type was + /// invalid. + /// - ParameterResultType::Unexpected, if an unexpected error happened. + public: virtual ParameterResult Parameter( + const std::string & _parameterName, + google::protobuf::Message & _parameter) const = 0; + + /// \brief Request the value of a parameter. + /// Similar to the other overload, but it allocates a message of the + /// right type. + /// + /// \param[in] _parameterName Name of the parameter to be requested. + /// \param[out] _parameter Output were the parameter value will be set. + /// \return A ParameterResult return code, can return error types: + /// - ParameterResultType::NotDeclared if the parameter was not + /// declared. + /// - ParameterResultType::Unexpected, if an unexpected error happened. + public: virtual ParameterResult Parameter( + const std::string & _parameterName, + std::unique_ptr & _parameter) const = 0; + + /// \brief Set the value of a parameter. + /// \param[in] _parameterName Name of the parameter to be set. + /// \param[in] _msg Protobuf message to be used as the parameter value. + /// \return A ParameterResult return code, can return error types: + /// - ParameterResultType::NotDeclared if the parameter was not + /// declared. + /// - ParameterResultType::InvalidType if the parameter type was + /// invalid. + public: virtual ParameterResult SetParameter( + const std::string & _parameterName, + const google::protobuf::Message & _msg) = 0; + + /// \brief List all existing parameters. + /// \return The name and types of existing parameters. + public: virtual ignition::msgs::ParameterDeclarations + ListParameters() const = 0; + }; + } + } + } +} + +#endif diff --git a/parameters/include/ignition/transport/parameters/Registry.hh b/parameters/include/ignition/transport/parameters/Registry.hh new file mode 100644 index 000000000..32bd25f20 --- /dev/null +++ b/parameters/include/ignition/transport/parameters/Registry.hh @@ -0,0 +1,136 @@ +/* + * Copyright (C) 2022 Open Source Robotics Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * +*/ + +#ifndef IGNITION_TRANSPORT_PARAMETERS_REGISTRY_HH_ +#define IGNITION_TRANSPORT_PARAMETERS_REGISTRY_HH_ + +#include +#include +#include + +#include + +#include + +#include "ignition/transport/config.hh" +#include "ignition/transport/parameters/result.hh" +#include "ignition/transport/parameters/Export.hh" +#include "ignition/transport/parameters/Interface.hh" + +namespace ignition +{ + namespace transport + { + namespace parameters + { + // Inline bracket to help doxygen filtering. + inline namespace IGNITION_TRANSPORT_VERSION_NAMESPACE { + + struct ParametersRegistryPrivate; + + /// \brief Provides a parameter registry. + /// Parameters can be declared, get or set in the registry. + /// It also provides services, so the parameters can be get, set or + /// listed from other processes. + /// + /// Provided services: + /// * /${_parametersServicesNamespace}/get_parameter + /// * /${_parametersServicesNamespace}/list_parameters + /// * /${_parametersServicesNamespace}/set_parameter + /// * /${_parametersServicesNamespace}/declare_parameter + class IGNITION_TRANSPORT_PARAMETERS_VISIBLE ParametersRegistry + : public ParametersInterface + { + /// \brief Constructor. + /// \param[in] _parametersServicesNamespace Namespace that will be used + /// in all the created services names. + public: explicit ParametersRegistry( + const std::string & _parametersServicesNamespace); + + /// \brief Destructor. + public: ~ParametersRegistry(); + + /// \brief No copy constructor. + public: ParametersRegistry(const ParametersRegistry &) = delete; + /// \brief No copy assignment. + public: ParametersRegistry & + operator=(const ParametersRegistry &) = delete; + /// \brief Default move constructor. + public: ParametersRegistry(ParametersRegistry &&); + /// \brief Default move assignment. + public: ParametersRegistry & + operator=(ParametersRegistry &&); + + /// \brief Declare a new parameter. + /// See ParametersInterface::DeclareParameter(). + public: ParameterResult DeclareParameter( + const std::string & _parameterName, + const google::protobuf::Message & _msg) final; + + /// \brief Request the value of a parameter. + /// See ParametersInterface::Parameter(). + public: ParameterResult Parameter( + const std::string & _parameterName, + google::protobuf::Message & _parameter) const final; + + public: ParameterResult Parameter( + const std::string & _parameterName, + std::unique_ptr & _parameter) const final; + + /// \brief Set the value of a parameter. + /// See ParametersInterface::SetParameter(). + public: ParameterResult SetParameter( + const std::string & _parameterName, + const google::protobuf::Message & _msg) final; + + /// \brief List all parameters. + /// \return Protobuf message with a list of all declared parameter + /// names and their types. + public: ignition::msgs::ParameterDeclarations + ListParameters() const final; + + /// \brief Declare a new parameter. + /// \param[in] _parameterName Name of the parameter. + /// \param[in] _initialValue The initial value of the parameter. + /// The parameter type will be deduced from the type of the message. + /// \throw std::invalid_argument if `_initialValue` is `nullptr`. + /// \throw ParameterAlreadyDeclaredException if a parameter with the + /// same name was declared before. + public: ParameterResult DeclareParameter( + const std::string & _parameterName, + std::unique_ptr _initialValue); + + /// \brief Set the value of a parameter. + /// \param[in] _parameterName Name of the parameter to set. + /// \param[in] _value The value of the parameter. + /// \throw ParameterNotDeclaredException if a parameter of that name + /// was not declared before. + /// \throw ParameterInvalidTypeException if the type does not match + /// the type of the parameter when it was declared. + public: ParameterResult SetParameter( + const std::string & _parameterName, + std::unique_ptr _value); + + /// \brief Pointer to implementation. + private: std::unique_ptr dataPtr; + }; + } + } + } +} + +#endif diff --git a/parameters/include/ignition/transport/parameters/result.hh b/parameters/include/ignition/transport/parameters/result.hh new file mode 100644 index 000000000..158749eef --- /dev/null +++ b/parameters/include/ignition/transport/parameters/result.hh @@ -0,0 +1,100 @@ +/* + * Copyright (C) 2022 Open Source Robotics Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * +*/ + +#ifndef IGNITION_TRANSPORT_PARAMETERS_EXCEPTIONS_HH_ +#define IGNITION_TRANSPORT_PARAMETERS_EXCEPTIONS_HH_ + +#include +#include + +#include "ignition/transport/config.hh" +#include "ignition/transport/parameters/Export.hh" + +namespace ignition +{ + namespace transport + { + namespace parameters + { + // Inline bracket to help doxygen filtering. + inline namespace IGNITION_TRANSPORT_VERSION_NAMESPACE { + enum class IGNITION_TRANSPORT_PARAMETERS_VISIBLE + /// \brief Possible result types of the different parameters opeartions. + ParameterResultType { + Success, + AlreadyDeclared, + InvalidType, + NotDeclared, + ClientTimeout, + Unexpected, + }; + + /// \brief The return type used in all falible parameters methods. + class ParameterResult { + /// \brief Construct. + /// \param _resultType Type of result of the operation. + public: IGNITION_TRANSPORT_PARAMETERS_VISIBLE + explicit ParameterResult(ParameterResultType _resultType); + + /// \brief Construct. + /// \param _resultType Type of result of the operation. + /// \param _paramName Name of the related parameter. + public: IGNITION_TRANSPORT_PARAMETERS_VISIBLE + ParameterResult( + ParameterResultType _resultType, const std::string & _paramName); + + /// \brief Construct. + /// \param _resultType Type of result of the operation. + /// \param _paramName Name of the related parameter. + /// \param _paramType Type of the related parameter. + public: IGNITION_TRANSPORT_PARAMETERS_VISIBLE + ParameterResult( + ParameterResultType _resultType, + const std::string & _paramName, + const std::string & _paramType); + + /// \brief Return the result type. + public: IGNITION_TRANSPORT_PARAMETERS_VISIBLE + ParameterResultType ResultType() const; + + /// \brief Return the related parameter name. + public: IGNITION_TRANSPORT_PARAMETERS_VISIBLE + const std::string & ParamName() const; + + /// \brief Return the related parameter type. + public: IGNITION_TRANSPORT_PARAMETERS_VISIBLE + const std::string & ParamType() const; + + /// \brief Coercion to bool type. + /// True if ParameterErrorType::Success, else False. + public: IGNITION_TRANSPORT_PARAMETERS_VISIBLE + explicit operator bool() const; + + private: ParameterResultType resultType; + private: std::string paramName; + private: std::string paramType; + }; + + /// \brief Stream operator, for debug output. + IGNITION_TRANSPORT_PARAMETERS_VISIBLE + std::ostream & operator<<(std::ostream &, const ParameterResult &); + } + } + } +} + +#endif diff --git a/parameters/src/CMakeLists.txt b/parameters/src/CMakeLists.txt new file mode 100644 index 000000000..9282a281c --- /dev/null +++ b/parameters/src/CMakeLists.txt @@ -0,0 +1,22 @@ + +ign_get_libsources_and_unittests(sources gtest_sources) +list(APPEND sources cmd/ParamCommandAPI.cc) +list(APPEND gtest_sources cmd/ParamCommandAPI_TEST.cc) + +ign_add_component( + parameters SOURCES ${sources} GET_TARGET_NAME param_lib_target) + +target_link_libraries(${param_lib_target} + PUBLIC + ignition-utils${IGN_UTILS_VER}::ignition-utils${IGN_UTILS_VER}) + +# Unit tests +ign_build_tests( + TYPE "UNIT" + SOURCES ${gtest_sources} + LIB_DEPS ${param_lib_target} ${EXTRA_TEST_LIB_DEPS} +) + +if(NOT WIN32) + add_subdirectory(cmd) +endif() diff --git a/parameters/src/Client.cc b/parameters/src/Client.cc new file mode 100644 index 000000000..cf80d658d --- /dev/null +++ b/parameters/src/Client.cc @@ -0,0 +1,244 @@ +/* + * Copyright (C) 2022 Open Source Robotics Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * +*/ + +#include "ignition/transport/parameters/Client.hh" + +#include +#include +#include + +#include "ignition/msgs/boolean.pb.h" +#include +#include "ignition/msgs/parameter_name.pb.h" +#include "ignition/msgs/parameter_value.pb.h" + +#include "ignition/transport/parameters/result.hh" + +#include "Utils.hh" + +using namespace ignition; +using namespace transport; +using namespace parameters; + +struct ignition::transport::parameters::ParametersClientPrivate +{ + ParametersClientPrivate( + const std::string & _serverNamespace, + unsigned int _timeoutMs) + : serverNamespace{_serverNamespace}, + timeoutMs{_timeoutMs} + {} + std::string serverNamespace; + mutable ignition::transport::Node node; + unsigned int timeoutMs; +}; + +////////////////////////////////////////////////// +ParametersClient::~ParametersClient() = default; + +////////////////////////////////////////////////// +ParametersClient::ParametersClient(ParametersClient &&) = default; + +////////////////////////////////////////////////// +ParametersClient & ParametersClient::operator=(ParametersClient &&) = default; + +////////////////////////////////////////////////// +ParametersClient::ParametersClient( + const std::string & _serverNamespace, + unsigned int _timeoutMs) +: dataPtr{std::make_unique( + _serverNamespace, + _timeoutMs)} +{} + +////////////////////////////////////////////////// +static ParameterResult +getParameterCommon( + const ParametersClientPrivate & _dataPtr, + const std::string & _parameterName, + msgs::ParameterValue & _parameterValue) +{ + bool result{false}; + const std::string service{_dataPtr.serverNamespace + "/get_parameter"}; + + msgs::ParameterName req; + + req.set_name(_parameterName); + + if (!_dataPtr.node.Request( + service, req, _dataPtr.timeoutMs, _parameterValue, result)) + { + return ParameterResult{ParameterResultType::ClientTimeout, _parameterName}; + } + if (!result) + { + return ParameterResult{ParameterResultType::NotDeclared, _parameterName}; + } + return ParameterResult{ParameterResultType::Success}; +} + +////////////////////////////////////////////////// +ParameterResult +ParametersClient::Parameter( + const std::string & _parameterName, + google::protobuf::Message & _parameter) const +{ + msgs::ParameterValue res; + auto ret = getParameterCommon(*this->dataPtr, _parameterName, res); + auto ignTypeOpt = getIgnTypeFromAnyProto(res.data()); + if (!ignTypeOpt) { + return ParameterResult{ + ParameterResultType::Unexpected, + _parameterName}; + } + auto ignType = *ignTypeOpt; + if (ignType != _parameter.GetDescriptor()->name()) { + return ParameterResult{ + ParameterResultType::InvalidType, _parameterName, ignType}; + } + if (!res.data().UnpackTo(&_parameter)) { + return ParameterResult{ + ParameterResultType::Unexpected, _parameterName, ignType}; + } + return ParameterResult{ParameterResultType::Success}; +} + +////////////////////////////////////////////////// +ParameterResult +ParametersClient::Parameter( + const std::string & _parameterName, + std::unique_ptr & _parameter) const +{ + msgs::ParameterValue res; + auto ret = getParameterCommon(*this->dataPtr, _parameterName, res); + auto ignTypeOpt = getIgnTypeFromAnyProto(res.data()); + if (!ignTypeOpt) { + return ParameterResult{ + ParameterResultType::Unexpected, + _parameterName}; + } + auto ignType = *ignTypeOpt; + _parameter = ignition::msgs::Factory::New(ignType); + if (!_parameter) { + return ParameterResult{ + ParameterResultType::Unexpected, _parameterName, ignType}; + } + if (!res.data().UnpackTo(_parameter.get())) { + return ParameterResult{ + ParameterResultType::Unexpected, _parameterName, ignType}; + } + return ParameterResult{ParameterResultType::Success}; +} + +////////////////////////////////////////////////// +ParameterResult +ParametersClient::SetParameter( + const std::string & _parameterName, + const google::protobuf::Message & _msg) +{ + bool result{false}; + const std::string service{dataPtr->serverNamespace + "/set_parameter"}; + + msgs::Parameter req; + msgs::ParameterError res; + + req.set_name(_parameterName); + req.mutable_value()->PackFrom(_msg); + + if (!dataPtr->node.Request(service, req, dataPtr->timeoutMs, res, result)) + { + return ParameterResult{ParameterResultType::ClientTimeout, _parameterName}; + } + if (!result) + { + return ParameterResult{ParameterResultType::Unexpected, _parameterName}; + } + if (res.data() == msgs::ParameterError::SUCCESS) { + return ParameterResult{ParameterResultType::Success}; + } + if (res.data() == msgs::ParameterError::NOT_DECLARED) { + return ParameterResult{ParameterResultType::NotDeclared, _parameterName}; + } + if (res.data() == msgs::ParameterError::INVALID_TYPE) { + return ParameterResult{ + ParameterResultType::InvalidType, + _parameterName}; + } + return ParameterResult{ParameterResultType::Unexpected, _parameterName}; +} + +////////////////////////////////////////////////// +ParameterResult +ParametersClient::DeclareParameter( + const std::string & _parameterName, + const google::protobuf::Message & _msg) +{ + bool result{false}; + const std::string service{dataPtr->serverNamespace + "/declare_parameter"}; + + msgs::Parameter req; + msgs::ParameterError res; + + req.set_name(_parameterName); + req.mutable_value()->PackFrom(_msg); + + if (!dataPtr->node.Request(service, req, dataPtr->timeoutMs, res, result)) + { + return ParameterResult{ParameterResultType::ClientTimeout, _parameterName}; + } + if (!result) + { + return ParameterResult{ParameterResultType::Unexpected, _parameterName}; + } + if (res.data() == msgs::ParameterError::SUCCESS) { + return ParameterResult{ParameterResultType::Success}; + } + if (res.data() == msgs::ParameterError::ALREADY_DECLARED) { + return ParameterResult{ + ParameterResultType::AlreadyDeclared, _parameterName}; + } + if (res.data() == msgs::ParameterError::INVALID_TYPE) { + return ParameterResult{ + ParameterResultType::InvalidType, + _parameterName, + _msg.GetDescriptor()->name()}; + } + return ParameterResult{ParameterResultType::Unexpected, _parameterName}; +} + +////////////////////////////////////////////////// +msgs::ParameterDeclarations +ParametersClient::ListParameters() const +{ + bool result{false}; + const std::string service{dataPtr->serverNamespace + "/list_parameters"}; + + msgs::Empty req; + msgs::ParameterDeclarations res; + + if (!dataPtr->node.Request(service, req, dataPtr->timeoutMs, res, result)) + { + throw std::runtime_error{ + "ParametersClient::ListParameters(): request timed out"}; + } + if (!result) + { + throw std::runtime_error { + "ParametersClient::ListParameters(): unexpected error"}; + } + return res; +} diff --git a/parameters/src/Client_TEST.cc b/parameters/src/Client_TEST.cc new file mode 100644 index 000000000..c84abfaed --- /dev/null +++ b/parameters/src/Client_TEST.cc @@ -0,0 +1,214 @@ +/* + * Copyright (C) 2022 Open Source Robotics Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * +*/ + +#include "ignition/transport/parameters/Client.hh" +#include "ignition/transport/parameters/Registry.hh" + +#include +#include + +#include "gtest/gtest.h" + +using namespace ignition; +using namespace ignition::transport; +using namespace ignition::transport::parameters; + +class ParametersClientTest : public ::testing::Test { + protected: + void SetUp() override { + registry_.DeclareParameter("parameter1", std::make_unique()); + registry_.DeclareParameter( + "parameter2", std::make_unique()); + auto anotherStrMsg = std::make_unique(); + anotherStrMsg->set_data("asd"); + registry_.DeclareParameter("parameter3", std::move(anotherStrMsg)); + registry_other_ns_.DeclareParameter( + "another_param1", std::make_unique()); + anotherStrMsg = std::make_unique(); + anotherStrMsg->set_data("bsd"); + registry_other_ns_.DeclareParameter( + "another_param2", std::move(anotherStrMsg)); + } + + void TearDown() override {} + + ParametersRegistry registry_{""}; + ParametersRegistry registry_other_ns_{"/ns"}; +}; + +////////////////////////////////////////////////// +TEST(ParametersClient, ConstructDestruct) +{ + ParametersClient client; +} + +////////////////////////////////////////////////// +TEST_F(ParametersClientTest, Parameter) +{ + { + ParametersClient client; + { + msgs::Boolean msg; + EXPECT_TRUE(client.Parameter("parameter1", msg)); + EXPECT_EQ(msg.data(), false); + } + { + std::unique_ptr msg; + EXPECT_TRUE(client.Parameter("parameter2", msg)); + EXPECT_TRUE(msg); + auto downcastedMsg = dynamic_cast(msg.get()); + EXPECT_EQ(downcastedMsg->data(), ""); + } + { + msgs::Boolean msg; + auto ret = client.Parameter("parameter3", msg); + EXPECT_FALSE(ret); + EXPECT_EQ(ret.ResultType(), ParameterResultType::InvalidType); + EXPECT_EQ(ret.ParamName(), "parameter3"); + } + } + { + ParametersClient client{"/ns"}; + { + msgs::Boolean msg; + EXPECT_TRUE(client.Parameter("another_param1", msg)); + EXPECT_EQ(msg.data(), false); + } + { + msgs::StringMsg msg; + EXPECT_TRUE(client.Parameter("another_param2", msg)); + EXPECT_EQ(msg.data(), "bsd"); + } + } +} + +////////////////////////////////////////////////// +TEST_F(ParametersClientTest, SetParameter) +{ + { + ParametersClient client; + msgs::StringMsg msg; + msg.set_data("testing"); + client.SetParameter("parameter2", msg); + msgs::StringMsg msg_got; + EXPECT_TRUE(registry_.Parameter("parameter2", msg_got)); + EXPECT_EQ(msg_got.data(), "testing"); + } + { + ParametersClient client; + msgs::Boolean msg; + auto ret = client.SetParameter("parameter2", msg); + EXPECT_FALSE(ret); + EXPECT_EQ(ret.ResultType(), ParameterResultType::InvalidType); + EXPECT_EQ(ret.ParamName(), "parameter2"); + } + { + ParametersClient client; + msgs::Boolean msg; + auto ret = client.SetParameter("parameter_doesnt_exist", msg); + EXPECT_FALSE(ret); + EXPECT_EQ(ret.ResultType(), ParameterResultType::NotDeclared); + EXPECT_EQ(ret.ParamName(), "parameter_doesnt_exist"); + } + { + ParametersClient client{"/ns"}; + msgs::Boolean msg; + msg.set_data(true); + client.SetParameter("another_param1", msg); + msgs::Boolean msg_got; + EXPECT_TRUE(registry_other_ns_.Parameter("another_param1", msg_got)); + EXPECT_EQ(msg_got.data(), true); + } +} + +////////////////////////////////////////////////// +TEST_F(ParametersClientTest, DeclareParameter) +{ + { + ParametersClient client; + msgs::StringMsg msg; + msg.set_data("declaring"); + client.DeclareParameter("new_parameter", msg); + msgs::StringMsg msg_got; + EXPECT_TRUE(registry_.Parameter("new_parameter", msg_got)); + EXPECT_EQ(msg_got.data(), "declaring"); + } + { + ParametersClient client; + msgs::Boolean msg; + auto ret = client.DeclareParameter("parameter1", msg); + EXPECT_FALSE(ret); + EXPECT_EQ(ret.ResultType(), ParameterResultType::AlreadyDeclared); + } + { + ParametersClient client{"/ns"}; + msgs::Boolean msg; + msg.set_data(true); + msgs::Boolean msg_got; + client.DeclareParameter("new_parameter", msg); + EXPECT_TRUE(registry_other_ns_.Parameter("new_parameter", msg_got)); + EXPECT_EQ(msg_got.data(), true); + } +} + +////////////////////////////////////////////////// +TEST_F(ParametersClientTest, ListParameters) +{ + { + ParametersClient client; + auto declarations = client.ListParameters(); + bool foundParam1{false}; + bool foundParam2{false}; + bool foundParam3{false}; + for (auto decl : declarations.parameter_declarations()) { + if (decl.name() == "parameter1" && decl.type() == "ign_msgs.Boolean") { + foundParam1 = true; + } + if (decl.name() == "parameter2" && decl.type() == "ign_msgs.StringMsg") { + foundParam2 = true; + } + if (decl.name() == "parameter3" && decl.type() == "ign_msgs.StringMsg") { + foundParam3 = true; + } + } + EXPECT_TRUE(foundParam1) << "expected to find declaration for parameter1"; + EXPECT_TRUE(foundParam2) << "expected to find declaration for parameter2"; + EXPECT_TRUE(foundParam3) << "expected to find declaration for parameter3"; + } + { + ParametersClient client{"/ns"}; + auto declarations = client.ListParameters(); + bool foundParam1{false}; + bool foundParam2{false}; + for (auto decl : declarations.parameter_declarations()) { + if ( + decl.name() == "another_param1" && decl.type() == "ign_msgs.Boolean") + { + foundParam1 = true; + } + if ( + decl.name() == "another_param2" && decl.type() == "ign_msgs.StringMsg") + { + foundParam2 = true; + } + } + EXPECT_TRUE(foundParam1) + << "expected to find declaration for another_param1"; + EXPECT_TRUE(foundParam2) + << "expected to find declaration for another_param2"; + } +} diff --git a/parameters/src/Registry.cc b/parameters/src/Registry.cc new file mode 100644 index 000000000..b79e17838 --- /dev/null +++ b/parameters/src/Registry.cc @@ -0,0 +1,360 @@ +/* + * Copyright (C) 2022 Open Source Robotics Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * +*/ + +#include "ignition/transport/parameters/Registry.hh" + +#include +#include +#include +#include + +#include "google/protobuf/message.h" +#include "google/protobuf/any.h" + +#include "ignition/transport/Node.hh" + +#include +#include +#include +#include +#include + +#include + +#include "Utils.hh" + +using namespace ignition; +using namespace transport; +using namespace parameters; + +struct ignition::transport::parameters::ParametersRegistryPrivate +{ + using ParametersMapT = std::unordered_map< + std::string, std::unique_ptr>; + + /// \brief Get parameter service callback. + /// \param[in] _req Request specifying the parameter name. + /// \param[out] _res The value of the parameter. + /// \return True if successful. + bool GetParameter(const msgs::ParameterName &_req, + msgs::ParameterValue &_res); + + /// \brief List parameter service callback. + /// \param[in] _req unused. + /// \param[out] _res List of available parameters. + /// \return True if successful. + bool ListParameters(const msgs::Empty &_req, + msgs::ParameterDeclarations &_res); + + /// \brief Set parameter service callback. + /// \param[in] _req Request specifying which parameter to set and its value. + /// \param[out] _res Unused. + /// \return True if successful. + bool SetParameter(const msgs::Parameter &_req, msgs::ParameterError &_res); + + /// \brief Declare parameter service callback. + /// \param[in] _req Request specifying which parameter to be declared. + /// \param[out] _res Unused. + /// \return True if successful. + bool DeclareParameter( + const msgs::Parameter &_req, msgs::ParameterError &_res); + + ignition::transport::Node node; + std::mutex parametersMapMutex; + ParametersMapT parametersMap; +}; + +////////////////////////////////////////////////// +ParametersRegistry::ParametersRegistry( + const std::string & _parametersServicesNamespace) + : dataPtr{std::make_unique()} +{ + std::string getParameterSrvName{ + _parametersServicesNamespace + "/get_parameter"}; + this->dataPtr->node.Advertise(getParameterSrvName, + &ParametersRegistryPrivate::GetParameter, this->dataPtr.get()); + + std::string listParametersSrvName{ + _parametersServicesNamespace + "/list_parameters"}; + this->dataPtr->node.Advertise(listParametersSrvName, + &ParametersRegistryPrivate::ListParameters, this->dataPtr.get()); + + std::string setParameterSrvName{ + _parametersServicesNamespace + "/set_parameter"}; + this->dataPtr->node.Advertise(setParameterSrvName, + &ParametersRegistryPrivate::SetParameter, this->dataPtr.get()); + + std::string declareParameterSrvName{ + _parametersServicesNamespace + "/declare_parameter"}; + this->dataPtr->node.Advertise(declareParameterSrvName, + &ParametersRegistryPrivate::DeclareParameter, this->dataPtr.get()); +} + +////////////////////////////////////////////////// +ParametersRegistry::~ParametersRegistry() = default; + +////////////////////////////////////////////////// +ParametersRegistry::ParametersRegistry(ParametersRegistry &&) = default; + +////////////////////////////////////////////////// +ParametersRegistry & ParametersRegistry::operator=( + ParametersRegistry &&) = default; + +////////////////////////////////////////////////// +bool ParametersRegistryPrivate::GetParameter(const msgs::ParameterName &_req, + msgs::ParameterValue &_res) +{ + { + std::lock_guard guard{this->parametersMapMutex}; + auto it = this->parametersMap.find(_req.name()); + if (it == this->parametersMap.end()) { + return false; + } + _res.mutable_data()->PackFrom(*it->second, "ign_msgs"); + } + return true; +} + +////////////////////////////////////////////////// +bool ParametersRegistryPrivate::ListParameters(const msgs::Empty &, + msgs::ParameterDeclarations &_res) +{ + // TODO(ivanpauno): Maybe the response should only include parameter names, + // maybe only names and types (?) + // Including the component key doesn't seem to matter much, + // though it's also not wrong. + { + std::lock_guard guard{this->parametersMapMutex}; + for (const auto & paramPair : this->parametersMap) { + auto * decl = _res.add_parameter_declarations(); + decl->set_name(paramPair.first); + decl->set_type(addIgnMsgsPrefix( + paramPair.second->GetDescriptor()->name())); + } + } + return true; +} + +////////////////////////////////////////////////// +bool ParametersRegistryPrivate::SetParameter( + const msgs::Parameter &_req, msgs::ParameterError &_res) +{ + (void)_res; + const auto & paramName = _req.name(); + { + std::lock_guard guard{this->parametersMapMutex}; + auto it = this->parametersMap.find(paramName); + if (it == this->parametersMap.end()) { + _res.set_data(msgs::ParameterError::NOT_DECLARED); + return true; + } + auto requestedIgnTypeOpt = getIgnTypeFromAnyProto( + _req.value()); + if (!requestedIgnTypeOpt) { + _res.set_data(msgs::ParameterError::INVALID_TYPE); + return true; + } + auto requestedIgnType = *requestedIgnTypeOpt; + if (it->second->GetDescriptor()->name() != requestedIgnType) { + _res.set_data(msgs::ParameterError::INVALID_TYPE); + return true; + } + if (!_req.value().UnpackTo(it->second.get())) { + // unexpected error + return false; + } + } + return true; +} + +////////////////////////////////////////////////// +bool ParametersRegistryPrivate::DeclareParameter( + const msgs::Parameter &_req, msgs::ParameterError &_res) +{ + (void)_res; + const auto & ignTypeOpt = getIgnTypeFromAnyProto(_req.value()); + if (!ignTypeOpt) { + _res.set_data(msgs::ParameterError::INVALID_TYPE); + return true; + } + auto ignType = addIgnMsgsPrefix(*ignTypeOpt); + auto paramValue = ignition::msgs::Factory::New(ignType); + if (!paramValue) { + _res.set_data(msgs::ParameterError::INVALID_TYPE); + return true; + } + if (!_req.value().UnpackTo(paramValue.get())) { + // unexpected error + return false; + } + std::lock_guard guard{this->parametersMapMutex}; + auto it_emplaced_pair = this->parametersMap.emplace( + std::make_pair(_req.name(), std::move(paramValue))); + if (!it_emplaced_pair.second) { + _res.set_data(msgs::ParameterError::ALREADY_DECLARED); + } + return true; +} + +////////////////////////////////////////////////// +ParameterResult +ParametersRegistry::DeclareParameter( + const std::string & _parameterName, + std::unique_ptr _initialValue) +{ + if (!_initialValue) { + throw std::invalid_argument{ + "ParametersRegistry::DeclareParameter(): `_parameterName` is nullptr"}; + } + std::lock_guard guard{this->dataPtr->parametersMapMutex}; + auto it_emplaced_pair = this->dataPtr->parametersMap.emplace( + std::make_pair(_parameterName, std::move(_initialValue))); + if (!it_emplaced_pair.second) { + return ParameterResult{ + ParameterResultType::AlreadyDeclared, + _parameterName}; + } + return ParameterResult{ParameterResultType::Success}; +} + +////////////////////////////////////////////////// +ParameterResult +ParametersRegistry::DeclareParameter( + const std::string & _parameterName, + const google::protobuf::Message & _msg) +{ + auto protoType = addIgnMsgsPrefix(_msg.GetDescriptor()->name()); + auto newParam = ignition::msgs::Factory::New(protoType); + if (!newParam) { + return ParameterResult{ + ParameterResultType::Unexpected, + _parameterName, + protoType}; + } + newParam->CopyFrom(_msg); + this->DeclareParameter(_parameterName, std::move(newParam)); + return ParameterResult{ParameterResultType::Success}; +} + +////////////////////////////////////////////////// +ParameterResult +ParametersRegistry::Parameter( + const std::string & _parameterName, + google::protobuf::Message & _parameter) const +{ + std::lock_guard guard{this->dataPtr->parametersMapMutex}; + auto it = dataPtr->parametersMap.find(_parameterName); + if (it == dataPtr->parametersMap.end()) { + return ParameterResult{ + ParameterResultType::NotDeclared, + _parameterName}; + } + const auto & newProtoType = _parameter.GetDescriptor()->name(); + const auto & protoType = it->second->GetDescriptor()->name(); + if (newProtoType != protoType) { + return ParameterResult{ + ParameterResultType::InvalidType, + _parameterName, + addIgnMsgsPrefix(protoType)}; + } + _parameter.CopyFrom(*it->second); + return ParameterResult{ParameterResultType::Success}; +} + +////////////////////////////////////////////////// +ParameterResult +ParametersRegistry::Parameter( + const std::string & _parameterName, + std::unique_ptr & _parameter) const +{ + std::lock_guard guard{this->dataPtr->parametersMapMutex}; + auto it = dataPtr->parametersMap.find(_parameterName); + if (it == dataPtr->parametersMap.end()) { + return ParameterResult{ + ParameterResultType::NotDeclared, + _parameterName}; + } + const auto & protoType = it->second->GetDescriptor()->name(); + _parameter = ignition::msgs::Factory::New(protoType); + if (!_parameter) { + return ParameterResult{ + ParameterResultType::InvalidType, + _parameterName, + addIgnMsgsPrefix(protoType)}; + + } + _parameter->CopyFrom(*it->second); + return ParameterResult{ParameterResultType::Success}; +} + +////////////////////////////////////////////////// +ParameterResult +ParametersRegistry::SetParameter( + const std::string & _parameterName, + std::unique_ptr _value) +{ + std::lock_guard guard{this->dataPtr->parametersMapMutex}; + auto it = this->dataPtr->parametersMap.find(_parameterName); + if (it == this->dataPtr->parametersMap.end()) { + return ParameterResult{ + ParameterResultType::NotDeclared, + _parameterName}; + } + // Validate the type matches before copying. + if (it->second->GetDescriptor() != _value->GetDescriptor()) { + return ParameterResult{ + ParameterResultType::InvalidType, + _parameterName, + addIgnMsgsPrefix(it->second->GetDescriptor()->name())}; + } + it->second = std::move(_value); + return ParameterResult{ParameterResultType::Success}; +} + +////////////////////////////////////////////////// +ParameterResult +ParametersRegistry::SetParameter( + const std::string & _parameterName, + const google::protobuf::Message & _value) +{ + std::lock_guard guard{this->dataPtr->parametersMapMutex}; + auto it = this->dataPtr->parametersMap.find(_parameterName); + if (it == this->dataPtr->parametersMap.end()) { + return ParameterResult{ + ParameterResultType::NotDeclared, + _parameterName}; + } + // Validate the type matches before copying. + if (it->second->GetDescriptor() != _value.GetDescriptor()) { + return ParameterResult{ + ParameterResultType::InvalidType, + _parameterName}; + } + it->second->CopyFrom(_value); + return ParameterResult{ParameterResultType::Success}; +} + +////////////////////////////////////////////////// +ignition::msgs::ParameterDeclarations +ParametersRegistry::ListParameters() const +{ + ignition::msgs::ParameterDeclarations ret; + ignition::msgs::Empty unused; + + dataPtr->ListParameters(unused, ret); + return ret; +} diff --git a/parameters/src/Registry_TEST.cc b/parameters/src/Registry_TEST.cc new file mode 100644 index 000000000..84689042c --- /dev/null +++ b/parameters/src/Registry_TEST.cc @@ -0,0 +1,128 @@ +/* + * Copyright (C) 2022 Open Source Robotics Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * +*/ + +#include "ignition/transport/parameters/Registry.hh" + +#include +#include + +#include "gtest/gtest.h" + +using namespace ignition; +using namespace ignition::transport; +using namespace ignition::transport::parameters; + +////////////////////////////////////////////////// +TEST(ParametersRegistry, ConstructDestruct) +{ + ParametersRegistry registry{""}; +} + +////////////////////////////////////////////////// +TEST(ParametersRegistry, DeclareParameter) +{ + ParametersRegistry registry{""}; + EXPECT_THROW( + registry.DeclareParameter("will_fail", nullptr), + std::invalid_argument); + registry.DeclareParameter( + "parameter1", std::make_unique()); + auto ret = registry.DeclareParameter( + "parameter1", std::make_unique()); + EXPECT_FALSE(ret); + EXPECT_EQ(ret.ResultType(), ParameterResultType::AlreadyDeclared); + EXPECT_EQ(ret.ParamName(), "parameter1"); +} + +////////////////////////////////////////////////// +TEST(ParametersRegistry, Parameter) +{ + ParametersRegistry registry{""}; + ignition::msgs::Boolean msg; + auto ret = registry.Parameter("will_fail", msg); + EXPECT_FALSE(ret); + EXPECT_EQ(ret.ResultType(), ParameterResultType::NotDeclared); + EXPECT_EQ(ret.ParamName(), "will_fail"); + EXPECT_TRUE(registry.DeclareParameter( + "parameter1", std::make_unique())); + EXPECT_TRUE(registry.Parameter("parameter1", msg)); + EXPECT_FALSE(msg.data()); + ignition::msgs::StringMsg msg2; + ret = registry.Parameter("parameter1", msg2); + EXPECT_FALSE(ret); + EXPECT_EQ(ret.ResultType(), ParameterResultType::InvalidType); + EXPECT_EQ(ret.ParamName(), "parameter1"); + EXPECT_EQ(ret.ParamType(), "ign_msgs.Boolean"); +} + +////////////////////////////////////////////////// +TEST(ParametersRegistry, SetParameter) +{ + ParametersRegistry registry{""}; + auto ret = registry.SetParameter( + "will_fail", std::make_unique()); + EXPECT_FALSE(ret); + EXPECT_EQ(ret.ResultType(), ParameterResultType::NotDeclared); + EXPECT_EQ(ret.ParamName(), "will_fail"); + registry.DeclareParameter( + "parameter1", std::make_unique()); + auto unique = std::make_unique(); + unique->set_data(true); + EXPECT_TRUE(registry.SetParameter("parameter1", std::move(unique))); + ignition::msgs::Boolean msg; + EXPECT_TRUE(registry.Parameter("parameter1", msg)); + EXPECT_TRUE(msg.data()); + ret = registry.SetParameter( + "parameter1", std::make_unique()); + EXPECT_FALSE(ret); + EXPECT_EQ(ret.ResultType(), ParameterResultType::InvalidType); + EXPECT_EQ(ret.ParamName(), "parameter1"); + EXPECT_EQ(ret.ParamType(), "ign_msgs.Boolean"); +} + +////////////////////////////////////////////////// +TEST(ParametersRegistry, ListParameters) +{ + ParametersRegistry registry{""}; + auto declarations = registry.ListParameters(); + EXPECT_EQ(declarations.parameter_declarations_size(), 0); + registry.DeclareParameter( + "parameter1", std::make_unique()); + registry.DeclareParameter( + "parameter2", std::make_unique()); + declarations = registry.ListParameters(); + EXPECT_EQ(declarations.parameter_declarations_size(), 2); + bool foundParam1 = false; + bool foundParam2 = false; + for (auto decl : declarations.parameter_declarations()) { + if (decl.name() == "parameter1" && decl.type() == "ign_msgs.Boolean") { + foundParam1 = true; + } + if (decl.name() == "parameter2" && decl.type() == "ign_msgs.StringMsg") { + foundParam2 = true; + } + } + EXPECT_TRUE(foundParam1) << "expected to find declaration for parameter1"; + EXPECT_TRUE(foundParam2) << "expected to find declaration for parameter2"; +} + +////////////////////////////////////////////////// +int main(int argc, char **argv) +{ + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} diff --git a/parameters/src/Utils.cc b/parameters/src/Utils.cc new file mode 100644 index 000000000..a46e219e7 --- /dev/null +++ b/parameters/src/Utils.cc @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2022 Open Source Robotics Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * +*/ + +#include "Utils.hh" + +#include +#include +#include + +////////////////////////////////////////////////// +std::string +ignition::transport::parameters::addIgnMsgsPrefix( + const std::string & ignType) +{ + std::ostringstream oss{"ign_msgs.", std::ios_base::ate}; + oss << ignType; + return oss.str(); +} + +////////////////////////////////////////////////// +std::optional +ignition::transport::parameters::getIgnTypeFromAnyProto( + const google::protobuf::Any & any) +{ + auto typeUrl = any.type_url(); + auto pos = typeUrl.rfind('/'); + if (pos == std::string::npos) { + return std::nullopt; + } + const char prefix[] = "ignition.msgs."; + auto ret = typeUrl.substr(pos + 1); + if (!ret.compare(0, sizeof(prefix), prefix)) { + return std::nullopt; + } + return ret.substr(sizeof(prefix) - 1); +} diff --git a/parameters/src/Utils.hh b/parameters/src/Utils.hh new file mode 100644 index 000000000..25cc4939a --- /dev/null +++ b/parameters/src/Utils.hh @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2022 Open Source Robotics Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * +*/ + +#ifndef IGNITION_TRANSPORT_PARAMETERS_UTILS_HH_ +#define IGNITION_TRANSPORT_PARAMETERS_UTILS_HH_ + +#include +#include + +#include "ignition/transport/config.hh" +#include "ignition/transport/parameters/Export.hh" + +#include + +namespace ignition +{ + namespace transport + { + namespace parameters + { + // Inline bracket to help doxygen filtering. + inline namespace IGNITION_TRANSPORT_VERSION_NAMESPACE { + + /// \brief Return the protobuf type prefixed with "ign_msgs." + /// \param ignType Type name to be prefixed. + /// \return The protobuf type with the prefix added. + IGNITION_TRANSPORT_PARAMETERS_VISIBLE + std::string addIgnMsgsPrefix(const std::string & ignType); + + /// \brief Get the ignition message type from a protobuf message. + /// \param any Message to get the type. + /// \return A string with the ignition protobuf type, + /// or nullopt if it fails. + IGNITION_TRANSPORT_PARAMETERS_VISIBLE + std::optional getIgnTypeFromAnyProto( + const google::protobuf::Any & any); + } + } + } +} + +#endif diff --git a/parameters/src/Utils_TEST.cc b/parameters/src/Utils_TEST.cc new file mode 100644 index 000000000..13f4d3dc5 --- /dev/null +++ b/parameters/src/Utils_TEST.cc @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2022 Open Source Robotics Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * +*/ + +#include "Utils.hh" + +#include +#include + +#include "gtest/gtest.h" + +using namespace ignition; +using namespace ignition::transport; +using namespace ignition::transport::parameters; + +////////////////////////////////////////////////// +TEST(ParametersUtils, addIgnMsgsPrefix) +{ + EXPECT_EQ(addIgnMsgsPrefix("asd"), "ign_msgs.asd"); +} + +////////////////////////////////////////////////// +TEST(ParametersUtils, getIgnTypeFromAnyProto) +{ + google::protobuf::Any any; + + ignition::msgs::Boolean boolMsg; + any.PackFrom(boolMsg); + EXPECT_EQ(*getIgnTypeFromAnyProto(any), "Boolean"); + ignition::msgs::StringMsg strMsg; + any.PackFrom(strMsg); + EXPECT_EQ(*getIgnTypeFromAnyProto(any), "StringMsg"); +} diff --git a/parameters/src/cmd/CMakeLists.txt b/parameters/src/cmd/CMakeLists.txt new file mode 100644 index 000000000..4547b404b --- /dev/null +++ b/parameters/src/cmd/CMakeLists.txt @@ -0,0 +1,60 @@ +#=============================================================================== +# Generate the ruby script for internal testing. +# Note that the major version of the library is included in the name. +# TODO(anyone): We do not currently have tests for the `ign param` command, so this is currently going unused. +set(cmd_param_ruby_test_dir "${CMAKE_BINARY_DIR}/param/test/lib/ruby/ignition") +set(cmd_param_script_generated_test "${cmd_param_ruby_test_dir}/cmdparam${PROJECT_VERSION_MAJOR}.rb") +set(cmd_param_script_configured_test "${cmd_param_script_generated_test}.configured") + +# Set the param_library_location variable to the full path of the library file +# within the build directory +set(param_library_location "$") + +configure_file( + "cmdparam.rb.in" + "${cmd_param_script_configured_test}" + @ONLY) + +file(GENERATE + OUTPUT "${cmd_param_script_generated_test}" + INPUT "${cmd_param_script_configured_test}") + +# Used for internal testing. +set(ign_param_ruby_path "${cmd_param_script_generated_test}") + +configure_file( + "transportparam.yaml.in" + "${cmd_param_ruby_test_dir}/transportparam${PROJECT_VERSION_MAJOR}.yaml") + + +#=============================================================================== +# Used for the installed version. +# Generate the ruby script that gets installed. +# Note that the major version of the library is included in the name. +set(cmd_param_script_generated "${CMAKE_CURRENT_BINARY_DIR}/cmdparam${PROJECT_VERSION_MAJOR}.rb") +set(cmd_param_script_configured "${cmd_param_script_generated}.configured") + +# Set the param_library_location variable to the relative path to the library file +# within the install directory structure. +set(param_library_location "../../../${CMAKE_INSTALL_LIBDIR}/$") + +configure_file( + "cmdparam.rb.in" + "${cmd_param_script_configured}" + @ONLY) + +file(GENERATE + OUTPUT "${cmd_param_script_generated}" + INPUT "${cmd_param_script_configured}") + +install(FILES ${cmd_param_script_generated} DESTINATION lib/ruby/ignition) + +# Used for the installed version. +set(ign_param_ruby_path "${CMAKE_INSTALL_PREFIX}/lib/ruby/ignition/cmdparam${PROJECT_VERSION_MAJOR}") + +set(transportparam_configured "${CMAKE_CURRENT_BINARY_DIR}/transportparam${PROJECT_VERSION_MAJOR}.yaml") +configure_file( + "transportparam.yaml.in" + ${transportparam_configured}) + +install(FILES ${transportparam_configured} DESTINATION ${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_DATAROOTDIR}/ignition/) diff --git a/parameters/src/cmd/ParamCommandAPI.cc b/parameters/src/cmd/ParamCommandAPI.cc new file mode 100644 index 000000000..3afab7a35 --- /dev/null +++ b/parameters/src/cmd/ParamCommandAPI.cc @@ -0,0 +1,115 @@ +/* + * Copyright (C) 2022 Open Source Robotics Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * +*/ + +#include "ParamCommandAPI.hh" + +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include + +#include + +using namespace ignition; +using namespace transport; + +////////////////////////////////////////////////// +extern "C" void cmdParametersList(const char * _ns) +{ + parameters::ParametersClient client{_ns}; + + std::cout << std::endl << "Listing parameters, registry namespace [" << _ns + << "]..." << std::endl << std::endl; + + auto res = client.ListParameters(); + if (!res.parameter_declarations_size()) { + std::cout << "No parameters available" << std::endl; + return; + } + for (const auto & decl : res.parameter_declarations()) { + std::cout << decl.name() << " [" << decl.type() << "]" + << std::endl; + } +} + +////////////////////////////////////////////////// +extern "C" void cmdParameterGet(const char * _ns, const char *_paramName) { + parameters::ParametersClient client{_ns}; + + std::cout << std::endl << "Getting parameter [" << _paramName + << "] for registry namespace [" << _ns << "]..." << std::endl; + + std::unique_ptr value; + auto ret = client.Parameter(_paramName, value); + if (!ret) { + std::cerr << "Failed to get parameter: " << ret << std::endl; + return; + } + + std::string msgType = "ign_msgs."; + msgType += value->GetDescriptor()->name(); + std::cout << "Parameter type [" << msgType << "]" << std::endl << std::endl + << "------------------------------------------------" + << std::endl; + { + google::protobuf::io::OstreamOutputStream fos{&std::cout}; + if (!google::protobuf::TextFormat::Print(*value, &fos)) { + std::cerr << "failed to convert the parameter value to a string" + << std::endl; + return; + } + } + std::cout << "------------------------------------------------" + << std::endl; +} + +extern "C" void cmdParameterSet( + const char * _ns, const char *_paramName, const char * _paramType, + const char *_paramValue) +{ + parameters::ParametersClient client{_ns}; + + std::cout << std::endl << "Setting parameter [" << _paramName + << "] for registry namespace [" << _ns << "]..." << std::endl; + + auto msg = ignition::msgs::Factory::New(_paramType); + if (!msg) { + std::cerr << "Could not create a message of type [" << _paramType + << "]. The message type is invalid." + << std::endl; + return; + } + if (!google::protobuf::TextFormat::ParseFromString(_paramValue, msg.get())) { + std::cerr << "Could not create a message of type [" << _paramType + << "]. The message string representation is invalid." + << std::endl; + return; + } + auto ret = client.SetParameter(_paramName, *msg); + if (!ret) { + std::cerr << "Failed to set parameter: " << ret << std::endl; + } + std::cout << "Parameter successfully set!" << std::endl; +} diff --git a/parameters/src/cmd/ParamCommandAPI.hh b/parameters/src/cmd/ParamCommandAPI.hh new file mode 100644 index 000000000..47c447c61 --- /dev/null +++ b/parameters/src/cmd/ParamCommandAPI.hh @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2022 Open Source Robotics Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * +*/ + +#ifndef IGNITION_TRANSPORT_PARAMETERS_CMD_PARAMCOMMANDAPI_HH_ +#define IGNITION_TRANSPORT_PARAMETERS_CMD_PARAMCOMMANDAPI_HH_ + +#include "ignition/transport/parameters/Export.hh" + +/// \brief External hook to get a list of available models. +/// \param[in] _ns Namespace of the parameter registry. + +extern "C" +IGNITION_TRANSPORT_PARAMETERS_VISIBLE +void cmdParametersList(const char * _ns); + +/// \brief External hook to dump a parameter. +/// \param[in] _ns Namespace of the parameter registry. +/// \param[in] _paramName Parameter name. + +extern "C" +IGNITION_TRANSPORT_PARAMETERS_VISIBLE +void cmdParameterGet(const char * _ns, const char *_paramName); + +/// \brief External hook to set a parameter. +/// \param[in] _ns Namespace of the parameter registry. +/// \param[in] _paramName Parameter name. +/// \param[in] _paramType Parameter protobuf type. +/// \param[in] _paramValue String representation of the parameter value. + +extern "C" +IGNITION_TRANSPORT_PARAMETERS_VISIBLE +void cmdParameterSet( + const char * _ns, const char *_paramName, const char * _paramType, + const char *_paramValue); + +#endif diff --git a/parameters/src/cmd/ParamCommandAPI_TEST.cc b/parameters/src/cmd/ParamCommandAPI_TEST.cc new file mode 100644 index 000000000..504fa14f2 --- /dev/null +++ b/parameters/src/cmd/ParamCommandAPI_TEST.cc @@ -0,0 +1,203 @@ +/* + * Copyright (C) 2022 Open Source Robotics Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * +*/ + +#include +#include + +#include "ignition/transport/parameters/Registry.hh" +#include "ParamCommandAPI.hh" + +#include +#include + +#include "gtest/gtest.h" + +using namespace ignition; +using namespace ignition::transport; +using namespace ignition::transport::parameters; + +class ParametersClientTest : public ::testing::Test { + protected: + void SetUp() override { + registry_.DeclareParameter("parameter1", std::make_unique()); + registry_.DeclareParameter( + "parameter2", std::make_unique()); + auto anotherStrMsg = std::make_unique(); + anotherStrMsg->set_data("asd"); + registry_.DeclareParameter("parameter3", std::move(anotherStrMsg)); + registry_other_ns_.DeclareParameter( + "another_param1", std::make_unique()); + anotherStrMsg = std::make_unique(); + anotherStrMsg->set_data("bsd"); + registry_other_ns_.DeclareParameter( + "another_param2", std::move(anotherStrMsg)); + } + + void TearDown() override {} + + ParametersRegistry registry_{""}; + ParametersRegistry registry_other_ns_{"/ns"}; +}; + +class CaptureStreamScoped { +public: + explicit CaptureStreamScoped(std::ostream & stream) + : streamToCapture{stream}, + originalCerrStreamBuf{stream.rdbuf()} + { + stream.rdbuf(capturedBuffer.rdbuf()); + } + + ~CaptureStreamScoped() + { + streamToCapture.rdbuf(originalCerrStreamBuf); + } + + std::string str() + { + return capturedBuffer.str(); + } + +private: + std::ostream & streamToCapture; + std::streambuf * originalCerrStreamBuf; + std::ostringstream capturedBuffer; +}; + +class CaptureCerrScoped : public CaptureStreamScoped { +public: + CaptureCerrScoped() + : CaptureStreamScoped(std::cerr) + {} +}; + +class CaptureCoutScoped : public CaptureStreamScoped { +public: + CaptureCoutScoped() + : CaptureStreamScoped(std::cout) + {} +}; + +////////////////////////////////////////////////// +TEST_F(ParametersClientTest, cmdParameterGet) +{ + { + CaptureCoutScoped coutCapture; + CaptureCerrScoped cerrCapture; + cmdParameterGet("", "parameter1"); + auto output = coutCapture.str(); + EXPECT_NE(std::string::npos, output.find("ign_msgs.Boolean")); + EXPECT_EQ(cerrCapture.str(), ""); + } + { + CaptureCoutScoped coutCapture; + CaptureCerrScoped cerrCapture; + cmdParameterGet("", "parameter2"); + auto output = coutCapture.str(); + EXPECT_NE(std::string::npos, output.find("ign_msgs.StringMsg")); + EXPECT_EQ(cerrCapture.str(), ""); + } + { + CaptureCoutScoped coutCapture; + CaptureCerrScoped cerrCapture; + cmdParameterGet("/ns", "another_param2"); + auto output = coutCapture.str(); + EXPECT_NE(std::string::npos, output.find("ign_msgs.StringMsg")); + EXPECT_NE(std::string::npos, output.find("bsd")); + EXPECT_EQ(cerrCapture.str(), ""); + } + { + CaptureCerrScoped cerrCapture; + cmdParameterGet("", "paramaaaterDoesntExist"); + auto output = cerrCapture.str(); + EXPECT_NE(std::string::npos, output.find("Failed to get parameter:")); + } +} + +////////////////////////////////////////////////// +TEST_F(ParametersClientTest, SetParameter) +{ + { + CaptureCoutScoped coutCapture; + CaptureCerrScoped cerrCapture; + cmdParameterSet("", "parameter1", "ign_msgs.Boolean", "data: true"); + auto output = coutCapture.str(); + EXPECT_NE(std::string::npos, output.find("successfully")); + EXPECT_EQ(cerrCapture.str(), ""); + } + { + CaptureCoutScoped coutCapture; + CaptureCerrScoped cerrCapture; + cmdParameterSet("", "parameter2", "ign_msgs.StringMsg", "data: \"foobar\""); + auto output = coutCapture.str(); + EXPECT_NE(std::string::npos, output.find("successfully")); + EXPECT_EQ(cerrCapture.str(), ""); + } + { + CaptureCoutScoped coutCapture; + CaptureCerrScoped cerrCapture; + cmdParameterSet("/ns", "another_param1", "ign_msgs.Boolean", "data: true"); + auto output = coutCapture.str(); + EXPECT_NE(std::string::npos, output.find("successfully")); + EXPECT_EQ(cerrCapture.str(), ""); + } + { + CaptureCerrScoped cerrCapture; + cmdParameterSet("", "parameter2", "ign_msgs.Boolean", "data: true"); + auto output = cerrCapture.str(); + EXPECT_NE(std::string::npos, output.find("Failed to set parameter")); + EXPECT_NE(std::string::npos, output.find("type")); + } + { + CaptureCerrScoped cerrCapture; + cmdParameterSet( + "", "parameter2", "ign_msgs.StringMsg", "not_a_field: \"foo\""); + auto output = cerrCapture.str(); + EXPECT_NE( + std::string::npos, output.find("string representation is invalid")); + } + { + CaptureCerrScoped cerrCapture; + cmdParameterSet("", "parameter2", "ign_msgs.NotAValidType", ""); + auto output = cerrCapture.str(); + EXPECT_NE(std::string::npos, output.find("message type is invalid")); + } +} + +////////////////////////////////////////////////// +TEST_F(ParametersClientTest, ListParameters) +{ + { + CaptureCoutScoped coutCapture; + CaptureCerrScoped cerrCapture; + cmdParametersList(""); + auto output = coutCapture.str(); + EXPECT_NE(std::string::npos, output.find("parameter1")); + EXPECT_NE(std::string::npos, output.find("parameter2")); + EXPECT_NE(std::string::npos, output.find("parameter3")); + EXPECT_EQ(cerrCapture.str(), ""); + } + { + CaptureCoutScoped coutCapture; + CaptureCerrScoped cerrCapture; + cmdParametersList("/ns"); + auto output = coutCapture.str(); + EXPECT_NE(std::string::npos, output.find("another_param1")); + EXPECT_NE(std::string::npos, output.find("another_param2")); + EXPECT_EQ(cerrCapture.str(), ""); + } +} diff --git a/parameters/src/cmd/cmdparam.rb.in b/parameters/src/cmd/cmdparam.rb.in new file mode 100644 index 000000000..0b62507d0 --- /dev/null +++ b/parameters/src/cmd/cmdparam.rb.in @@ -0,0 +1,187 @@ +#!/usr/bin/ruby + +# Copyright (C) 2022 Open Source Robotics Foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# We use 'dl' for Ruby <= 1.9.x and 'fiddle' for Ruby >= 2.0.x +if RUBY_VERSION.split('.')[0] < '2' + require 'dl' + require 'dl/import' + include DL +else + require 'fiddle' + require 'fiddle/import' + include Fiddle +end + +require 'optparse' + +# Constants. +LIBRARY_NAME = '@param_library_location@' +LIBRARY_VERSION = '@PROJECT_VERSION_FULL@' + +COMMON_OPTIONS = + " -h [--help] Print this help message.\n"\ + " \n" + + " --force-version Use a specific library version.\n"\ + " \n" + + ' --versions Show the available versions.' + +COMMANDS = { 'param' => + "List, set or get parameters.\n\n"+ + " \n"\ + " ign param [options] \n"\ + " \n"\ + "Available Options: \n"\ + " -r [--registry] Namespace of the parameter registry to be \n"\ + " queried. \n"\ + " -l [--list] Get a list of the available parameters. \n"\ + " -g [--get] Get a parameter. \n"\ + " Requires the -n option. \n"\ + " -s [--set] Set a parameter. \n"\ + " Requires the -n, -t, -v options. \n"\ + " -n [--name] arg The parameter name. \n"\ + " -t [--type] arg The parameter type. \n"\ + " -m [--value] arg The parameter value. \n\n"+ + COMMON_OPTIONS +} + +# +# Class for the Ignition Gazebo Param command line tools. +# +class Cmd + + # + # Return a structure describing the options. + # + def parse(args) + options = { + } + usage = COMMANDS[args[0]] + + opt_parser = OptionParser.new do |opts| + opts.banner = usage + + opts.on('-h', '--help') do + puts usage + exit + end + opts.on('-r', '--registry [arg]', String, 'Namespace of the parameter registry') do |m| + options['registry'] = m + end + opts.on('-l', '--list', Integer, 'List parameters available') do + options['list'] = 1 + end + opts.on('-g', '--get', Integer, 'Get a parameter') do + options['get'] = 1 + end + opts.on('-s', '--set', Integer, 'Set a parameter') do + options['set'] = 1 + end + opts.on('-n', '--name [arg]', String, 'Parameter name') do |m| + options['name'] = m + end + opts.on('-t', '--type [arg]', String, 'Parameter type') do |m| + options['type'] = m + end + opts.on('-m', '--value [arg]', String, 'Parameter value') do |m| + options['value'] = m + end + end # opt_parser do + begin + opt_parser.parse!(args) + rescue + puts usage + exit(-1) + end + + # Check that there is at least one command and there is a plugin that knows + # how to handle it. + if ARGV.empty? || !COMMANDS.key?(ARGV[0]) || + options.empty? + puts usage + exit(-1) + end + + # Check that an option was selected. + if !(options.key?('list') || options.key?('get') || options.key?('set')) + puts usage + exit(-1) + end + + options['command'] = args[0] + + options + end # parse() + + def execute(args) + options = parse(args) + + # Read the plugin that handles the command. + if LIBRARY_NAME[0] == '/' + # If the first character is a slash, we'll assume that we've been given an + # absolute path to the library. This is only used during test mode. + plugin = LIBRARY_NAME + else + # We're assuming that the library path is relative to the current + # location of this script. + plugin = File.expand_path(File.join(File.dirname(__FILE__), LIBRARY_NAME)) + end + conf_version = LIBRARY_VERSION + + begin + Importer.dlload plugin + rescue DLError => e + puts "Library error for [#{plugin}]: #{e.to_s}" + exit(-1) + end + + if options.key?('list') + Importer.extern 'void cmdParametersList(const char *)' + if not options.key?('registry') + puts "ERROR: -r [--registry] is required for -l [--list]\n" + exit(-1) + end + Importer.cmdParametersList(options['registry']) + exit(0) + elsif options.key?('get') + Importer.extern 'void cmdParameterGet(const char *, const char *)' + if not options.key?('registry') + puts "ERROR: -r [--registry] is required for -g [--get]\n" + exit(-1) + end + if not options.key?('name') + puts "ERROR: -n [--name] is required for -g [--get]\n" + exit(-1) + end + Importer.cmdParameterGet(options['registry'], options['name']) + elsif options.key?('set') + if not options.key?('registry') + puts "ERROR: -r [--registry] is required for -s [--set]\n" + exit(-1) + end + if !options.key?('name') || !options.key?('type') || !options.key?('value') + puts "ERROR: -n [--name], -t [--type], -m [--value] are required for -s [--set]\n" + exit(-1) + end + Importer.extern 'void cmdParameterSet(const char *, const char *, const char *, const char *)' + Importer.cmdParameterSet(options['registry'], options['name'], options['type'], options['value']) + else + puts 'Command error: I do not have an implementation for '\ + "command [ign #{options['command']}]." + end + # # execute + end +# class +end \ No newline at end of file diff --git a/parameters/src/cmd/transportparam.yaml.in b/parameters/src/cmd/transportparam.yaml.in new file mode 100644 index 000000000..73eb8b0d7 --- /dev/null +++ b/parameters/src/cmd/transportparam.yaml.in @@ -0,0 +1,8 @@ +--- # Subcommands available inside ignition-transport-param. +format: 1.0.0 +library_name: ignition-transport-param +library_version: @PROJECT_VERSION_FULL@ +library_path: @ign_param_ruby_path@ +commands: + - param : List, get or set parameters. +--- diff --git a/parameters/src/result.cc b/parameters/src/result.cc new file mode 100644 index 000000000..72a0a741d --- /dev/null +++ b/parameters/src/result.cc @@ -0,0 +1,107 @@ +/* + * Copyright (C) 2022 Open Source Robotics Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * +*/ + +#include +#include + +#include "ignition/transport/parameters/result.hh" + +using namespace ignition; +using namespace transport; +using namespace parameters; + +////////////////////////////////////////////////// +ParameterResult::ParameterResult(ParameterResultType _resultType) +: resultType{_resultType} +{} + +////////////////////////////////////////////////// +ParameterResult::ParameterResult( + ParameterResultType _resultType, const std::string & _paramName) +: resultType{_resultType}, paramName{_paramName} +{} + +////////////////////////////////////////////////// +ParameterResult::ParameterResult( + ParameterResultType _resultType, + const std::string & _paramName, + const std::string & _paramType) +: resultType{_resultType} +, paramName{_paramName} +, paramType{_paramType} +{} + +////////////////////////////////////////////////// +ParameterResultType ParameterResult::ResultType() const +{ + return resultType; +} + +////////////////////////////////////////////////// +const std::string & ParameterResult::ParamName() const +{ + return paramName; +} + +////////////////////////////////////////////////// +const std::string & ParameterResult::ParamType() const +{ + return paramType; +} + +////////////////////////////////////////////////// +ParameterResult::operator bool() const +{ + return resultType == ParameterResultType::Success; +} + +////////////////////////////////////////////////// +std::ostream & +ignition::transport::parameters::operator<<( + std::ostream & os, const ParameterResult & ret) +{ + std::ostringstream ss; + switch (ret.ResultType()) { + case ParameterResultType::Success: + ss << "parameter operation succeeded"; + break; + case ParameterResultType::AlreadyDeclared: + ss << "parameter already declared"; + break; + case ParameterResultType::NotDeclared: + ss << "parameter not declared"; + break; + case ParameterResultType::InvalidType: + ss << "parameter type is not valid"; + break; + case ParameterResultType::ClientTimeout: + ss << "parameter client timed out"; + break; + case ParameterResultType::Unexpected: + default: + ss << "parameter operation unexpected error"; + break; + } + if (ret.ParamName() != "") { + ss << ", parameter name [" << ret.ParamName() << "]"; + } + if (ret.ParamType() != "") { + ss << ", parameter type [" << ret.ParamType() << "]"; + } + os << ss.str(); + return os; +} diff --git a/parameters/src/result_TEST.cc b/parameters/src/result_TEST.cc new file mode 100644 index 000000000..d99c6383d --- /dev/null +++ b/parameters/src/result_TEST.cc @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2022 Open Source Robotics Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * +*/ + +#include "ignition/transport/parameters/result.hh" + +#include "gtest/gtest.h" +using namespace ignition; +using namespace ignition::transport; +using namespace ignition::transport::parameters; + +////////////////////////////////////////////////// +TEST(ParameterResult, ConstructorsAndGetters) +{ + { + ParameterResult res{ParameterResultType::Success}; + EXPECT_EQ(res.ResultType(), ParameterResultType::Success); + EXPECT_EQ(res.ParamName(), ""); + EXPECT_EQ(res.ParamType(), ""); + } + { + ParameterResult res{ParameterResultType::InvalidType, "bsd"}; + EXPECT_EQ(res.ResultType(), ParameterResultType::InvalidType); + EXPECT_EQ(res.ParamName(), "bsd"); + EXPECT_EQ(res.ParamType(), ""); + } + { + ParameterResult res{ + ParameterResultType::InvalidType, "asd", "ign_msgs.Type"}; + EXPECT_EQ(res.ResultType(), ParameterResultType::InvalidType); + EXPECT_EQ(res.ParamName(), "asd"); + EXPECT_EQ(res.ParamType(), "ign_msgs.Type"); + } +} + +////////////////////////////////////////////////// +TEST(ParameterResult, BoolCoercion) +{ + EXPECT_TRUE(ParameterResult{ParameterResultType::Success}); + EXPECT_FALSE(ParameterResult{ParameterResultType::AlreadyDeclared}); + EXPECT_FALSE(ParameterResult{ParameterResultType::InvalidType}); + EXPECT_FALSE(ParameterResult{ParameterResultType::NotDeclared}); + EXPECT_FALSE(ParameterResult{ParameterResultType::ClientTimeout}); + EXPECT_FALSE(ParameterResult{ParameterResultType::Unexpected}); +} + +// /// \brief Stream operator, for debug output. +// std::ostream & operator<<(std::ostream &, const ParameterResult &); +// } + +////////////////////////////////////////////////// +int main(int argc, char **argv) +{ + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +}