From e504e6d9943186ba4d963ed977cbdb461bead755 Mon Sep 17 00:00:00 2001 From: Martin Braun Date: Wed, 17 Jan 2018 16:02:55 -0800 Subject: uhdlib: Update constrained_device_args_t - Allow enums to be non-consecutive - Move to uhdlib/ - Add unit tests - Updated N230 use of constrained_device_args_t --- .../uhdlib/usrp/constrained_device_args.hpp | 301 +++++++++++++++++++++ host/lib/usrp/common/constrained_device_args.hpp | 271 ------------------- host/lib/usrp/n230/n230_device_args.hpp | 8 +- host/tests/CMakeLists.txt | 3 + host/tests/constrained_device_args_test.cpp | 104 +++++++ 5 files changed, 413 insertions(+), 274 deletions(-) create mode 100644 host/lib/include/uhdlib/usrp/constrained_device_args.hpp delete mode 100644 host/lib/usrp/common/constrained_device_args.hpp create mode 100644 host/tests/constrained_device_args_test.cpp diff --git a/host/lib/include/uhdlib/usrp/constrained_device_args.hpp b/host/lib/include/uhdlib/usrp/constrained_device_args.hpp new file mode 100644 index 000000000..c9d573e5f --- /dev/null +++ b/host/lib/include/uhdlib/usrp/constrained_device_args.hpp @@ -0,0 +1,301 @@ +// +// Copyright 2014 Ettus Research LLC +// +// SPDX-License-Identifier: GPL-3.0+ +// + +#ifndef INCLUDED_LIBUHD_USRP_COMMON_CONSTRAINED_DEV_ARGS_HPP +#define INCLUDED_LIBUHD_USRP_COMMON_CONSTRAINED_DEV_ARGS_HPP + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace uhd { +namespace usrp { + + /*! + * constrained_device_args_t provides a base and utilities to + * map key=value pairs passed in through the device creation + * args interface (device_addr_t). + * + * Inherit from this class to create typed device specific + * arguments and use the base class methods to handle parsing + * the device_addr or any key=value string to populate the args + * + * This file contains a library of different types of args the + * the user can pass in. The library can be extended to support + * non-intrinsic types by the client. + * + */ + class constrained_device_args_t { + public: //Types + + /*! + * Base argument type. All other arguments inherit from this. + */ + class generic_arg { + public: + generic_arg(const std::string& key): _key(key) {} + inline const std::string& key() const { return _key; } + inline virtual std::string to_string() const = 0; + private: + std::string _key; + }; + + /*! + * String argument type. Can be case sensitive or insensitive + */ + template + class str_arg : public generic_arg { + public: + str_arg(const std::string& name, const std::string& default_value) : + generic_arg(name) { set(default_value); } + + inline void set(const std::string& value) { + _value = case_sensitive ? value : boost::algorithm::to_lower_copy(value); + } + inline const std::string& get() const { + return _value; + } + inline void parse(const std::string& str_rep) { + set(str_rep); + } + inline virtual std::string to_string() const { + return key() + "=" + get(); + } + inline bool operator==(const std::string& rhs) const { + return get() == boost::algorithm::to_lower_copy(rhs); + } + private: + std::string _value; + }; + typedef str_arg str_ci_arg; + typedef str_arg str_cs_arg; + + /*! + * Numeric argument type. The template type data_t allows the + * client to constrain the type of the number. + */ + template + class num_arg : public generic_arg { + public: + num_arg(const std::string& name, const data_t default_value) : + generic_arg(name) { set(default_value); } + + inline void set(const data_t value) { + _value = value; + } + inline const data_t get() const { + return _value; + } + inline void parse(const std::string& str_rep) { + try { + _value = boost::lexical_cast(str_rep); + } catch (std::exception& ex) { + throw uhd::value_error(str(boost::format( + "Error parsing numeric parameter %s: %s.") % + key() % ex.what() + )); + } + } + inline virtual std::string to_string() const { + return key() + "=" + std::to_string(get()); + } + private: + data_t _value; + }; + + /*! + * Enumeration argument type. The template type enum_t allows the + * client to use their own enum and specify a string mapping for + * the values of the enum + */ + template + class enum_arg : public generic_arg { + public: + enum_arg( + const std::string& name, + const enum_t default_value, + const std::unordered_map& values) : + generic_arg(name), _str_values(_enum_map_to_lowercase(values)) + { + set(default_value); + } + inline void set(const enum_t value) { + _value = value; + } + inline const enum_t get() const { + return _value; + } + inline void parse( + const std::string& str_rep, + const bool assert_invalid = true + ) { + const std::string str_rep_lowercase = + boost::algorithm::to_lower_copy(str_rep); + if (_str_values.count(str_rep_lowercase) == 0) { + if (assert_invalid) { + std::string valid_values_str = ""; + for (const auto &value : _str_values) { + valid_values_str += + (valid_values_str.empty()?"":", ") + + value.first; + } + throw uhd::value_error(str(boost::format( + "Invalid device arg value: %s=%s (Valid: {%s})") % + key() % str_rep % valid_values_str + )); + } else { + return; + } + } + + set(_str_values.at(str_rep_lowercase)); + } + inline virtual std::string to_string() const { + std::string repr; + for (const auto& value : _str_values) { + if (value.second == _value) { + repr = value.first; + break; + } + } + + UHD_ASSERT_THROW(!repr.empty()); + return key() + "=" + repr; + } + + private: + enum_t _value; + const std::unordered_map _str_values; + }; + + /*! + * Boolean argument type. + */ + class bool_arg : public generic_arg { + public: + bool_arg(const std::string& name, const bool default_value) : + generic_arg(name) { set(default_value); } + + inline void set(const bool value) { + _value = value; + } + inline bool get() const { + return _value; + } + inline void parse(const std::string& str_rep) { + try { + _value = (std::stoi(str_rep) != 0); + } catch (std::exception& ex) { + if (str_rep.empty()) { + //If str_rep is empty then the device_addr was set + //without a value which means that the user "set" the flag + _value = true; + } else if (boost::algorithm::to_lower_copy(str_rep) == "true" || + boost::algorithm::to_lower_copy(str_rep) == "yes" || + boost::algorithm::to_lower_copy(str_rep) == "y") { + _value = true; + } else if (boost::algorithm::to_lower_copy(str_rep) == "false" || + boost::algorithm::to_lower_copy(str_rep) == "no" || + boost::algorithm::to_lower_copy(str_rep) == "n") { + _value = false; + } else { + throw uhd::value_error(str(boost::format( + "Error parsing boolean parameter %s: %s.") % + key() % ex.what() + )); + } + } + } + inline virtual std::string to_string() const { + return key() + "=" + (get() ? "true" : "false"); + } + private: + bool _value; + }; + + public: //Methods + constrained_device_args_t() {} + virtual ~constrained_device_args_t() {} + + void parse(const std::string& str_args) { + device_addr_t dev_args(str_args); + _parse(dev_args); + } + + void parse(const device_addr_t& dev_args) { + _parse(dev_args); + } + + inline virtual std::string to_string() const = 0; + + protected: //Methods + //Override _parse to provide an implementation to parse all + //client specific device args + virtual void _parse(const device_addr_t& dev_args) = 0; + + /*! + * Utility: Ensure that the value of the device arg is between min and max + */ + template + static inline void _enforce_range(const num_arg& arg, const num_data_t& min, const num_data_t& max) { + if (arg.get() > max || arg.get() < min) { + throw uhd::value_error(str(boost::format( + "Invalid device arg value: %s (Minimum: %s, Maximum: %s)") % + arg.to_string() % + std::to_string(min) % std::to_string(max))); + } + } + + /*! + * Utility: Ensure that the value of the device arg is is contained in valid_values + */ + template + static inline void _enforce_discrete(const arg_t& arg, const std::vector& valid_values) { + bool match = false; + for(const data_t& val: valid_values) { + if (val == arg.get()) { + match = true; + break; + } + } + if (!match) { + std::string valid_values_str; + for (size_t i = 0; i < valid_values.size(); i++) { + valid_values_str += ((i==0)?"":", ") + std::to_string(valid_values[i]); + throw uhd::value_error(str(boost::format( + "Invalid device arg value: %s (Valid: {%s})") % + arg.to_string() % valid_values_str + )); + } + } + } + + //! Helper for enum_arg: Create a new map where keys are converted to + // lowercase. + template + static std::unordered_map _enum_map_to_lowercase( + const std::unordered_map& in_map + ) { + std::unordered_map new_map; + for (const auto& str_to_enum : in_map) { + new_map.insert( + std::pair( + boost::algorithm::to_lower_copy(str_to_enum.first), + str_to_enum.second + ) + ); + } + return new_map; + } + }; +}} //namespaces + +#endif /* INCLUDED_LIBUHD_USRP_COMMON_CONSTRAINED_DEV_ARGS_HPP */ diff --git a/host/lib/usrp/common/constrained_device_args.hpp b/host/lib/usrp/common/constrained_device_args.hpp deleted file mode 100644 index 8bf1d2a55..000000000 --- a/host/lib/usrp/common/constrained_device_args.hpp +++ /dev/null @@ -1,271 +0,0 @@ -// -// Copyright 2014 Ettus Research LLC -// -// SPDX-License-Identifier: GPL-3.0 -// - -#ifndef INCLUDED_LIBUHD_USRP_COMMON_CONSTRAINED_DEV_ARGS_HPP -#define INCLUDED_LIBUHD_USRP_COMMON_CONSTRAINED_DEV_ARGS_HPP - -#include -#include -#include -#include -#include -#include -#include - -namespace uhd { -namespace usrp { - - /*! - * constrained_device_args_t provides a base and utilities to - * map key=value pairs passed in through the device creation - * args interface (device_addr_t). - * - * Inherit from this class to create typed device specific - * arguments and use the base class methods to handle parsing - * the device_addr or any key=value string to populate the args - * - * This file contains a library of different types of args the - * the user can pass in. The library can be extended to support - * non-intrinsic types by the client. - * - */ - class constrained_device_args_t { - public: //Types - - /*! - * Base argument type. All other arguments inherit from this. - */ - class generic_arg { - public: - generic_arg(const std::string& key): _key(key) {} - inline const std::string& key() const { return _key; } - inline virtual std::string to_string() const = 0; - private: - std::string _key; - }; - - /*! - * String argument type. Can be case sensitive or insensitive - */ - template - class str_arg : public generic_arg { - public: - str_arg(const std::string& name, const std::string& default_value) : - generic_arg(name) { set(default_value); } - - inline void set(const std::string& value) { - _value = case_sensitive ? value : boost::algorithm::to_lower_copy(value); - } - inline const std::string& get() const { - return _value; - } - inline void parse(const std::string& str_rep) { - set(str_rep); - } - inline virtual std::string to_string() const { - return key() + "=" + get(); - } - inline bool operator==(const std::string& rhs) const { - return get() == boost::algorithm::to_lower_copy(rhs); - } - private: - std::string _value; - }; - typedef str_arg str_ci_arg; - typedef str_arg str_cs_arg; - - /*! - * Numeric argument type. The template type data_t allows the - * client to constrain the type of the number. - */ - template - class num_arg : public generic_arg { - public: - num_arg(const std::string& name, const data_t default_value) : - generic_arg(name) { set(default_value); } - - inline void set(const data_t value) { - _value = value; - } - inline const data_t get() const { - return _value; - } - inline void parse(const std::string& str_rep) { - try { - _value = boost::lexical_cast(str_rep); - } catch (std::exception& ex) { - throw uhd::value_error(str(boost::format( - "Error parsing numeric parameter %s: %s.") % - key() % ex.what() - )); - } - } - inline virtual std::string to_string() const { - return key() + "=" + std::to_string(get()); - } - private: - data_t _value; - }; - - /*! - * Enumeration argument type. The template type enum_t allows the - * client to use their own enum and specify a string mapping for - * the values of the enum - * - * NOTE: The constraint on enum_t is that the values must start with - * 0 and be sequential - */ - template - class enum_arg : public generic_arg { - public: - enum_arg( - const std::string& name, - const enum_t default_value, - const std::vector& values) : - generic_arg(name), _str_values(values) - { set(default_value); } - - inline void set(const enum_t value) { - _value = value; - } - inline const enum_t get() const { - return _value; - } - inline void parse(const std::string& str_rep, bool assert_invalid = true) { - std::string valid_values_str; - for (size_t i = 0; i < _str_values.size(); i++) { - if (boost::algorithm::to_lower_copy(str_rep) == - boost::algorithm::to_lower_copy(_str_values[i])) - { - valid_values_str += ((i==0)?"":", ") + _str_values[i]; - set(static_cast(static_cast(i))); - return; - } - } - //If we reach here then, the string enum value was invalid - if (assert_invalid) { - throw uhd::value_error(str(boost::format( - "Invalid device arg value: %s=%s (Valid: {%s})") % - key() % str_rep % valid_values_str - )); - } - } - inline virtual std::string to_string() const { - size_t index = static_cast(static_cast(_value)); - UHD_ASSERT_THROW(index < _str_values.size()); - return key() + "=" + _str_values[index]; - } - - private: - enum_t _value; - std::vector _str_values; - }; - - /*! - * Boolean argument type. - */ - class bool_arg : public generic_arg { - public: - bool_arg(const std::string& name, const bool default_value) : - generic_arg(name) { set(default_value); } - - inline void set(const bool value) { - _value = value; - } - inline bool get() const { - return _value; - } - inline void parse(const std::string& str_rep) { - try { - _value = (std::stoi(str_rep) != 0); - } catch (std::exception& ex) { - if (str_rep.empty()) { - //If str_rep is empty then the device_addr was set - //without a value which means that the user "set" the flag - _value = true; - } else if (boost::algorithm::to_lower_copy(str_rep) == "true" || - boost::algorithm::to_lower_copy(str_rep) == "yes" || - boost::algorithm::to_lower_copy(str_rep) == "y") { - _value = true; - } else if (boost::algorithm::to_lower_copy(str_rep) == "false" || - boost::algorithm::to_lower_copy(str_rep) == "no" || - boost::algorithm::to_lower_copy(str_rep) == "n") { - _value = false; - } else { - throw uhd::value_error(str(boost::format( - "Error parsing boolean parameter %s: %s.") % - key() % ex.what() - )); - } - } - } - inline virtual std::string to_string() const { - return key() + "=" + (get() ? "true" : "false"); - } - private: - bool _value; - }; - - public: //Methods - constrained_device_args_t() {} - virtual ~constrained_device_args_t() {} - - void parse(const std::string& str_args) { - device_addr_t dev_args(str_args); - _parse(dev_args); - } - - void parse(const device_addr_t& dev_args) { - _parse(dev_args); - } - - inline virtual std::string to_string() const = 0; - - protected: //Methods - //Override _parse to provide an implementation to parse all - //client specific device args - virtual void _parse(const device_addr_t& dev_args) = 0; - - /*! - * Utility: Ensure that the value of the device arg is between min and max - */ - template - static inline void _enforce_range(const num_arg& arg, const num_data_t& min, const num_data_t& max) { - if (arg.get() > max || arg.get() < min) { - throw uhd::value_error(str(boost::format( - "Invalid device arg value: %s (Minimum: %s, Maximum: %s)") % - arg.to_string() % - std::to_string(min) % std::to_string(max))); - } - } - - /*! - * Utility: Ensure that the value of the device arg is is contained in valid_values - */ - template - static inline void _enforce_discrete(const arg_t& arg, const std::vector& valid_values) { - bool match = false; - for(const data_t& val: valid_values) { - if (val == arg.get()) { - match = true; - break; - } - } - if (!match) { - std::string valid_values_str; - for (size_t i = 0; i < valid_values.size(); i++) { - valid_values_str += ((i==0)?"":", ") + std::to_string(valid_values[i]); - throw uhd::value_error(str(boost::format( - "Invalid device arg value: %s (Valid: {%s})") % - arg.to_string() % valid_values_str - )); - } - } - } - }; -}} //namespaces - -#endif /* INCLUDED_LIBUHD_USRP_COMMON_CONSTRAINED_DEV_ARGS_HPP */ diff --git a/host/lib/usrp/n230/n230_device_args.hpp b/host/lib/usrp/n230/n230_device_args.hpp index de326a309..b0a4a23c9 100644 --- a/host/lib/usrp/n230/n230_device_args.hpp +++ b/host/lib/usrp/n230/n230_device_args.hpp @@ -9,9 +9,9 @@ #include #include -#include -#include "../common/constrained_device_args.hpp" +#include #include "n230_defaults.h" +#include namespace uhd { namespace usrp { namespace n230 { @@ -29,7 +29,9 @@ public: _send_buff_size("send_buff_size", n230::DEFAULT_SEND_BUFF_SIZE), _recv_buff_size("recv_buff_size", n230::DEFAULT_RECV_BUFF_SIZE), _safe_mode("safe_mode", false), - _loopback_mode("loopback_mode", LOOPBACK_OFF, boost::assign::list_of("off")("radio")("codec")) + _loopback_mode("loopback_mode", + LOOPBACK_OFF, + {{"off", LOOPBACK_OFF}, {"radio", LOOPBACK_RADIO}, {"codec", LOOPBACK_CODEC}}) {} double get_master_clock_rate() const { diff --git a/host/tests/CMakeLists.txt b/host/tests/CMakeLists.txt index 6f78f8d3e..0741191f2 100644 --- a/host/tests/CMakeLists.txt +++ b/host/tests/CMakeLists.txt @@ -19,6 +19,7 @@ SET(test_sources cast_test.cpp cal_container_test.cpp chdr_test.cpp + constrained_device_args_test.cpp convert_test.cpp dict_test.cpp error_test.cpp @@ -69,6 +70,8 @@ IF(ENABLE_C_API) ) ENDIF(ENABLE_C_API) +INCLUDE_DIRECTORIES("${CMAKE_SOURCE_DIR}/lib/include") + #for each source: build an executable, register it as a test FOREACH(test_source ${test_sources}) GET_FILENAME_COMPONENT(test_name ${test_source} NAME_WE) diff --git a/host/tests/constrained_device_args_test.cpp b/host/tests/constrained_device_args_test.cpp new file mode 100644 index 000000000..c5f256e9e --- /dev/null +++ b/host/tests/constrained_device_args_test.cpp @@ -0,0 +1,104 @@ +// +// Copyright 2018 Ettus Research, a National Instruments Company +// +// SPDX-License-Identifier: GPL-3.0+ +// + +#include +#include +#include + +using uhd::usrp::constrained_device_args_t; + +namespace { + enum test_enum_t { VALUE1, VALUE2, VALUE3=4 }; + + static constexpr double MAX_DOUBLE_ARG = 1e6; + static constexpr double MIN_DOUBLE_ARG = 0.125; + + static constexpr double DEFAULT_DOUBLE_ARG = 2.25; + static constexpr size_t DEFAULT_SIZE_T_ARG = 42; + static constexpr bool DEFAULT_BOOL_ARG = true; + static constexpr test_enum_t DEFAULT_ENUM_ARG = VALUE1; + + + class test_device_args_t : public constrained_device_args_t + { + public: + test_device_args_t() {} + test_device_args_t(const std::string& dev_args) { parse(dev_args); } + + double get_double_arg() const { + return _double_arg.get(); + } + size_t get_size_t_arg() const { + return _size_t_arg.get(); + } + bool get_bool_arg() const { + return _bool_arg.get(); + } + test_enum_t get_enum_arg() const { + return _enum_arg.get(); + } + + inline virtual std::string to_string() const { + return _double_arg.to_string() + ", " + + _size_t_arg.to_string() + ", " + + _bool_arg.to_string() + ", " + + _enum_arg.to_string(); + } + private: + virtual void _parse(const uhd::device_addr_t& dev_args) { + if (dev_args.has_key(_double_arg.key())) + _double_arg.parse(dev_args[_double_arg.key()]); + if (dev_args.has_key(_size_t_arg.key())) + _size_t_arg.parse(dev_args[_size_t_arg.key()]); + if (dev_args.has_key(_bool_arg.key())) + _bool_arg.parse(dev_args[_bool_arg.key()]); + if (dev_args.has_key(_enum_arg.key())) + _enum_arg.parse(dev_args[_enum_arg.key()]); + _enforce_range(_double_arg, MIN_DOUBLE_ARG, MAX_DOUBLE_ARG); + } + + constrained_device_args_t::num_arg _double_arg + {"double_arg", DEFAULT_DOUBLE_ARG}; + constrained_device_args_t::num_arg _size_t_arg + {"size_t_arg", DEFAULT_SIZE_T_ARG}; + constrained_device_args_t::bool_arg _bool_arg + {"bool_arg", DEFAULT_BOOL_ARG}; + constrained_device_args_t::enum_arg _enum_arg + {"enum_arg", DEFAULT_ENUM_ARG, + {{"value1", VALUE1}, {"value2", VALUE2}, {"VALUE3", VALUE3}}}; + }; + +} + +BOOST_AUTO_TEST_CASE(test_constrained_device_args) { + test_device_args_t test_dev_args("double_arg=3.5,bool_arg=0,foo=bar"); + BOOST_CHECK_EQUAL(test_dev_args.get_double_arg(), 3.5); + BOOST_CHECK_EQUAL( + test_dev_args.get_size_t_arg(), + DEFAULT_SIZE_T_ARG + ); + BOOST_CHECK_EQUAL(test_dev_args.get_bool_arg(), false); + BOOST_CHECK_EQUAL( + test_dev_args.get_enum_arg(), + DEFAULT_ENUM_ARG + ); + BOOST_REQUIRE_THROW( + test_dev_args.parse("double_arg=2e6"), + uhd::value_error + ); // Note: test_dev_args is now in a bad state until we fix it! + test_dev_args.parse("double_arg=2.6"), + test_dev_args.parse("enum_arg=vaLue2"); + BOOST_CHECK_EQUAL( + test_dev_args.get_enum_arg(), + VALUE2 + ); + test_dev_args.parse("enum_arg=VALUE3"); + BOOST_CHECK_EQUAL( + test_dev_args.get_enum_arg(), + VALUE3 + ); +} + -- cgit v1.2.3