1 // 2 // Copyright 2014 Ettus Research LLC 3 // Copyright 2018 Ettus Research, a National Instruments Company 4 // 5 // SPDX-License-Identifier: GPL-3.0-or-later 6 // 7 8 #pragma once 9 10 #include <uhd/exception.hpp> 11 #include <uhd/types/device_addr.hpp> 12 #include <uhd/utils/cast.hpp> 13 #include <unordered_map> 14 #include <boost/algorithm/string.hpp> 15 #include <boost/assign/list_of.hpp> 16 #include <boost/format.hpp> 17 #include <sstream> 18 #include <string> 19 #include <vector> 20 21 namespace uhd { namespace usrp { 22 23 /*! 24 * constrained_device_args_t provides a base and utilities to 25 * map key=value pairs passed in through the device creation 26 * args interface (device_addr_t). 27 * 28 * Inherit from this class to create typed device specific 29 * arguments and use the base class methods to handle parsing 30 * the device_addr or any key=value string to populate the args 31 * 32 * This file contains a library of different types of args the 33 * the user can pass in. The library can be extended to support 34 * non-intrinsic types by the client. 35 * 36 */ 37 class constrained_device_args_t 38 { 39 public: // Types 40 /*! 41 * Base argument type. All other arguments inherit from this. 42 */ 43 class generic_arg 44 { 45 public: generic_arg(const std::string & key)46 generic_arg(const std::string& key) : _key(key) {} key() const47 inline const std::string& key() const 48 { 49 return _key; 50 } 51 inline virtual std::string to_string() const = 0; 52 53 private: 54 std::string _key; 55 }; 56 57 /*! 58 * String argument type. Can be case sensitive or insensitive 59 */ 60 template <bool case_sensitive> 61 class str_arg : public generic_arg 62 { 63 public: str_arg(const std::string & name,const std::string & default_value)64 str_arg(const std::string& name, const std::string& default_value) 65 : generic_arg(name) 66 { 67 set(default_value); 68 } 69 set(const std::string & value)70 inline void set(const std::string& value) 71 { 72 _value = case_sensitive ? value : boost::algorithm::to_lower_copy(value); 73 } get() const74 inline const std::string& get() const 75 { 76 return _value; 77 } parse(const std::string & str_rep)78 inline void parse(const std::string& str_rep) 79 { 80 set(str_rep); 81 } to_string() const82 inline virtual std::string to_string() const 83 { 84 return key() + "=" + get(); 85 } operator ==(const std::string & rhs) const86 inline bool operator==(const std::string& rhs) const 87 { 88 return get() == boost::algorithm::to_lower_copy(rhs); 89 } 90 91 private: 92 std::string _value; 93 }; 94 typedef str_arg<false> str_ci_arg; 95 typedef str_arg<true> str_cs_arg; 96 97 /*! 98 * Numeric argument type. The template type data_t allows the 99 * client to constrain the type of the number. 100 */ 101 template <typename data_t> 102 class num_arg : public generic_arg 103 { 104 public: num_arg(const std::string & name,const data_t default_value)105 num_arg(const std::string& name, const data_t default_value) : generic_arg(name) 106 { 107 set(default_value); 108 } 109 set(const data_t value)110 inline void set(const data_t value) 111 { 112 _value = value; 113 } get() const114 inline const data_t get() const 115 { 116 return _value; 117 } parse(const std::string & str_rep)118 inline void parse(const std::string& str_rep) 119 { 120 try { 121 _value = boost::lexical_cast<data_t>(str_rep); 122 } catch (std::exception& ex) { 123 throw uhd::value_error( 124 str(boost::format("Error parsing numeric parameter %s: %s.") % key() 125 % ex.what())); 126 } 127 } to_string() const128 inline virtual std::string to_string() const 129 { 130 return key() + "=" + std::to_string(get()); 131 } 132 133 private: 134 data_t _value; 135 }; 136 137 /*! 138 * Enumeration argument type. The template type enum_t allows the 139 * client to use their own enum and specify a string mapping for 140 * the values of the enum 141 */ 142 template <typename enum_t> 143 class enum_arg : public generic_arg 144 { 145 public: enum_arg(const std::string & name,const enum_t default_value,const std::unordered_map<std::string,enum_t> & values)146 enum_arg(const std::string& name, 147 const enum_t default_value, 148 const std::unordered_map<std::string, enum_t>& values) 149 : generic_arg(name), _str_values(_enum_map_to_lowercase<enum_t>(values)) 150 { 151 set(default_value); 152 } set(const enum_t value)153 inline void set(const enum_t value) 154 { 155 _value = value; 156 } get() const157 inline const enum_t get() const 158 { 159 return _value; 160 } parse(const std::string & str_rep,const bool assert_invalid=true)161 inline void parse(const std::string& str_rep, const bool assert_invalid = true) 162 { 163 const std::string str_rep_lowercase = 164 boost::algorithm::to_lower_copy(str_rep); 165 if (_str_values.count(str_rep_lowercase) == 0) { 166 if (assert_invalid) { 167 std::string valid_values_str = ""; 168 for (const auto& value : _str_values) { 169 valid_values_str += 170 (valid_values_str.empty() ? "" : ", ") + value.first; 171 } 172 throw uhd::value_error( 173 str(boost::format("Invalid device arg value: %s=%s (Valid: {%s})") 174 % key() % str_rep % valid_values_str)); 175 } else { 176 return; 177 } 178 } 179 180 set(_str_values.at(str_rep_lowercase)); 181 } to_string() const182 inline virtual std::string to_string() const 183 { 184 std::string repr; 185 for (const auto& value : _str_values) { 186 if (value.second == _value) { 187 repr = value.first; 188 break; 189 } 190 } 191 192 UHD_ASSERT_THROW(!repr.empty()); 193 return key() + "=" + repr; 194 } 195 196 private: 197 enum_t _value; 198 const std::unordered_map<std::string, enum_t> _str_values; 199 }; 200 201 /*! 202 * Boolean argument type. 203 */ 204 class bool_arg : public generic_arg 205 { 206 public: bool_arg(const std::string & name,const bool default_value)207 bool_arg(const std::string& name, const bool default_value) : generic_arg(name) 208 { 209 set(default_value); 210 } 211 set(const bool value)212 inline void set(const bool value) 213 { 214 _value = value; 215 } get() const216 inline bool get() const 217 { 218 return _value; 219 } parse(const std::string & str_rep)220 inline void parse(const std::string& str_rep) 221 { 222 try { 223 if (str_rep.empty()) { 224 // If str_rep is empty, the flag is interpreted as set 225 _value = true; 226 } else { 227 _value = uhd::cast::from_str<bool>(str_rep); 228 } 229 } catch (std::exception& ex) { 230 throw uhd::value_error( 231 str(boost::format("Error parsing boolean parameter %s: %s.") 232 % key() % ex.what())); 233 } 234 } to_string() const235 inline virtual std::string to_string() const 236 { 237 return key() + "=" + (get() ? "true" : "false"); 238 } 239 240 private: 241 bool _value; 242 }; 243 244 public: // Methods constrained_device_args_t()245 constrained_device_args_t() {} ~constrained_device_args_t()246 virtual ~constrained_device_args_t() {} 247 parse(const std::string & str_args)248 void parse(const std::string& str_args) 249 { 250 device_addr_t dev_args(str_args); 251 _parse(dev_args); 252 } 253 parse(const device_addr_t & dev_args)254 void parse(const device_addr_t& dev_args) 255 { 256 _parse(dev_args); 257 } 258 259 inline virtual std::string to_string() const = 0; 260 261 template <typename arg_type> parse_arg_default(const device_addr_t & dev_args,arg_type & constrained_arg)262 void parse_arg_default(const device_addr_t& dev_args, arg_type& constrained_arg) 263 { 264 if (dev_args.has_key(constrained_arg.key())) { 265 constrained_arg.parse(dev_args[constrained_arg.key()]); 266 } 267 } 268 269 protected: // Methods 270 // Override _parse to provide an implementation to parse all 271 // client specific device args 272 virtual void _parse(const device_addr_t& dev_args) = 0; 273 274 /*! 275 * Utility: Ensure that the value of the device arg is between min and max 276 */ 277 template <typename num_data_t> _enforce_range(const num_arg<num_data_t> & arg,const num_data_t & min,const num_data_t & max)278 static inline void _enforce_range( 279 const num_arg<num_data_t>& arg, const num_data_t& min, const num_data_t& max) 280 { 281 if (arg.get() > max || arg.get() < min) { 282 throw uhd::value_error(str( 283 boost::format("Invalid device arg value: %s (Minimum: %s, Maximum: %s)") 284 % arg.to_string() % std::to_string(min) % std::to_string(max))); 285 } 286 } 287 288 /*! 289 * Utility: Ensure that the value of the device arg is is contained in valid_values 290 */ 291 template <typename arg_t, typename data_t> _enforce_discrete(const arg_t & arg,const std::vector<data_t> & valid_values)292 static inline void _enforce_discrete( 293 const arg_t& arg, const std::vector<data_t>& valid_values) 294 { 295 bool match = false; 296 for (const data_t& val : valid_values) { 297 if (val == arg.get()) { 298 match = true; 299 break; 300 } 301 } 302 if (!match) { 303 std::string valid_values_str; 304 for (size_t i = 0; i < valid_values.size(); i++) { 305 std::stringstream valid_values_ss; 306 valid_values_ss << ((i == 0) ? "" : ", ") << valid_values[i]; 307 throw uhd::value_error( 308 str(boost::format("Invalid device arg value: %s (Valid: {%s})") 309 % arg.to_string() % valid_values_ss.str())); 310 } 311 } 312 } 313 314 //! Helper for enum_arg: Create a new map where keys are converted to 315 // lowercase. 316 template <typename enum_t> _enum_map_to_lowercase(const std::unordered_map<std::string,enum_t> & in_map)317 static std::unordered_map<std::string, enum_t> _enum_map_to_lowercase( 318 const std::unordered_map<std::string, enum_t>& in_map) 319 { 320 std::unordered_map<std::string, enum_t> new_map; 321 for (const auto& str_to_enum : in_map) { 322 new_map.insert(std::pair<std::string, enum_t>( 323 boost::algorithm::to_lower_copy(str_to_enum.first), str_to_enum.second)); 324 } 325 return new_map; 326 } 327 }; 328 }} // namespace uhd::usrp 329