1 //
2 // Copyright 2020 Ettus Research, a National Instruments Brand
3 //
4 // SPDX-License-Identifier: GPL-3.0-or-later
5 //
6 
7 #include <uhd/cal/database.hpp>
8 #include <uhd/cal/pwr_cal.hpp>
9 #include <uhd/types/direction.hpp>
10 #include <uhd/utils/log.hpp>
11 #include <uhd/utils/math.hpp>
12 #include <uhdlib/usrp/common/pwr_cal_mgr.hpp>
13 #include <unordered_map>
14 #include <boost/algorithm/string.hpp>
15 #include <boost/range/adaptor/filtered.hpp>
16 #include <algorithm>
17 #include <set>
18 
19 using namespace uhd::usrp;
20 
21 namespace {
22 
23 // List of antenna names that are globally known to never have their own cal data
24 const std::set<std::string> INVALID_ANTENNAS{"CAL", "LOCAL"};
25 
26 } // namespace
27 
sanitize_antenna_name(std::string antenna_name)28 std::string pwr_cal_mgr::sanitize_antenna_name(std::string antenna_name)
29 {
30     std::replace(antenna_name.begin(), antenna_name.end(), '/', '+');
31     boost::to_lower(antenna_name);
32     return antenna_name;
33 }
34 
35 // Shorthand for filtering against INVALID_ANTENNAS
is_valid_antenna(const std::string & antenna)36 bool pwr_cal_mgr::is_valid_antenna(const std::string& antenna)
37 {
38     return !INVALID_ANTENNAS.count(antenna);
39 }
40 
41 class pwr_cal_mgr_impl : public pwr_cal_mgr
42 {
43 public:
pwr_cal_mgr_impl(const std::string & serial,const std::string & log_id,get_double_type && get_freq,get_str_type && get_key,uhd::gain_group::sptr gain_group)44     pwr_cal_mgr_impl(const std::string& serial,
45         const std::string& log_id,
46         get_double_type&& get_freq,
47         get_str_type&& get_key,
48         uhd::gain_group::sptr gain_group)
49         : _log_id(log_id)
50         , _get_freq(std::move(get_freq))
51         , _get_key(std::move(get_key))
52         , _gain_group(gain_group)
53         , _hw_gain_name(gain_group->get_names().at(0))
54     {
55         set_serial(serial);
56     }
57 
set_gain_group(uhd::gain_group::sptr gain_group)58     void set_gain_group(uhd::gain_group::sptr gain_group)
59     {
60         _gain_group = gain_group;
61     }
62 
has_power_data()63     bool has_power_data()
64     {
65         const std::string key = _get_key();
66         _load_cal_data(key);
67         return _cal_data.count(key) && bool(_cal_data.at(key));
68     }
69 
populate_subtree(uhd::property_tree::sptr subtree)70     void populate_subtree(uhd::property_tree::sptr subtree)
71     {
72         subtree->create<std::string>(uhd::fs_path("ref_power/key"))
73             .set_coercer([](const std::string&) -> std::string {
74                 throw uhd::runtime_error("Cannot overwrite power cal key!");
75             })
76             .set_publisher([this]() { return _get_key(); });
77         subtree->create<std::string>(uhd::fs_path("ref_power/serial"))
78             .set_coercer([](const std::string&) -> std::string {
79                 throw uhd::runtime_error("Cannot overwrite cal serial!");
80             })
81             .set_publisher([this]() { return _serial; });
82         if (!has_power_data()) {
83             return;
84         }
85         subtree->create<double>(uhd::fs_path("ref_power/value"))
86             .set_coercer(
87                 [this](const double power_dbm) { return this->set_power(power_dbm); })
88             .set_publisher([this]() { return this->get_power(); });
89         subtree->create<uhd::meta_range_t>(uhd::fs_path("ref_power/range"))
90             .set_coercer([](const uhd::meta_range_t&) -> uhd::meta_range_t {
91                 throw uhd::runtime_error("Cannot overwrite power range!");
92             })
93             .set_publisher([this]() { return this->get_power_range(); });
94     }
95 
set_power(const double power_dbm)96     double set_power(const double power_dbm)
97     {
98         const std::string key = _get_key();
99         _load_cal_data(key);
100         UHD_ASSERT_THROW(_cal_data.count(key));
101         _desired_power      = power_dbm;
102         const uint64_t freq = static_cast<uint64_t>(_get_freq());
103         auto& cal_data = _cal_data.at(key);
104         if (!cal_data) {
105             const std::string err_msg = std::string("Attempting to set power for key ")
106                                         + key + ", but no cal data available!";
107             UHD_LOG_ERROR(_log_id, err_msg);
108             throw uhd::runtime_error(err_msg);
109         }
110 
111         const double desired_hw_gain = cal_data->get_gain(power_dbm, freq);
112         // This sets all the gains
113         _gain_group->set_value(desired_hw_gain);
114         const double coerced_hw_gain    = _gain_group->get_value(_hw_gain_name);
115         const double coerced_hw_power   = cal_data->get_power(coerced_hw_gain, freq);
116         const double coerced_total_gain = _gain_group->get_value();
117         const double coerced_total_power =
118             coerced_hw_power + coerced_total_gain - coerced_hw_gain;
119         UHD_LOG_TRACE(_log_id,
120             "Desired power: " << power_dbm << " dBm -> desired gain: " << desired_hw_gain
121                               << " dB; Actual HW power: " << coerced_hw_power
122                               << " dBm -> actual HW gain: " << coerced_hw_gain
123                               << " dB, Actual total power: " << coerced_total_power
124                               << " dBm -> actual total gain: " << coerced_total_gain
125                               << " dB");
126         _mode = tracking_mode::TRACK_POWER;
127         // We directly scale the power with the residual gain
128         return coerced_total_power;
129     }
130 
get_power()131     double get_power()
132     {
133         const std::string key = _get_key();
134         _load_cal_data(key);
135         UHD_ASSERT_THROW(_cal_data.count(key));
136         auto& cal_data         = _cal_data.at(key);
137         if (!cal_data) {
138             const std::string err_msg = std::string("Attempting to get power for key ")
139                                         + key + ", but no cal data available!";
140             UHD_LOG_ERROR(_log_id, err_msg);
141             throw uhd::runtime_error(err_msg);
142         }
143 
144         const uint64_t freq    = static_cast<uint64_t>(_get_freq());
145         const double hw_gain = _gain_group->get_value(_hw_gain_name);
146         const double hw_power = cal_data->get_power(hw_gain, freq);
147         // We directly scale the power with the residual gain
148         return hw_power + (_gain_group->get_value() - hw_gain);
149     }
150 
update_power()151     void update_power()
152     {
153         if (_mode == tracking_mode::TRACK_POWER) {
154             set_power(_desired_power);
155         }
156     }
157 
get_power_range()158     uhd::meta_range_t get_power_range()
159     {
160         const std::string key = _get_key();
161         _load_cal_data(key);
162         UHD_ASSERT_THROW(_cal_data.count(key));
163         auto& cal_data = _cal_data.at(key);
164         if (!cal_data) {
165             const std::string err_msg = std::string("Attempting to get power range for key ")
166                                         + key + ", but no cal data available!";
167             UHD_LOG_ERROR(_log_id, err_msg);
168             throw uhd::runtime_error(err_msg);
169         }
170         const uint64_t freq = static_cast<uint64_t>(_get_freq());
171         return cal_data->get_power_limits(freq);
172     }
173 
set_temperature(const int temp_C)174     void set_temperature(const int temp_C)
175     {
176         for (auto& cal_data : _cal_data) {
177             if (cal_data.second) {
178                 cal_data.second->set_temperature(temp_C);
179             }
180         }
181     }
182 
set_tracking_mode(const tracking_mode mode)183     void set_tracking_mode(const tracking_mode mode)
184     {
185         _mode = mode;
186     }
187 
set_serial(const std::string & serial)188     void set_serial(const std::string& serial)
189     {
190         if (serial == _serial || serial.empty()) {
191             return;
192         }
193         _serial = serial;
194         _cal_data.clear();
195     }
196 
_load_cal_data(const std::string & key)197     void _load_cal_data(const std::string& key)
198     {
199         if (_cal_data.count(key)) {
200             return;
201         }
202         cal::pwr_cal::sptr cal_data(nullptr);
203         UHD_LOG_TRACE(
204             _log_id, "Looking for power cal data for " << key << ", serial " << _serial);
205         bool cal_data_found = false;
206         if (cal::database::has_cal_data(key, _serial)) {
207             try {
208                 cal_data = cal::container::make<cal::pwr_cal>(
209                     cal::database::read_cal_data(key, _serial));
210                 cal_data_found = true;
211             } catch (const uhd::exception& ex) {
212                 UHD_LOG_WARNING(_log_id, "Error loading cal data: " << ex.what());
213             }
214         }
215         _cal_data.insert({key, cal_data});
216         UHD_LOG_TRACE(_log_id,
217             (bool(cal_data) ? "" : "No ") << "power cal data found for key " << key
218                                           << ", key " << key << ", serial " << _serial);
219         if (cal_data_found) {
220             // If we found cal data, check that all the other antennas/keys also
221             // have cal data
222             if (std::any_of(_cal_data.cbegin(),
223                     _cal_data.cend(),
224                     [](const cal_data_map_type::value_type& data) {
225                         return !bool(data.second);
226                     })) {
227                 UHD_LOG_WARNING(_log_id,
228                     "Some ports for " << _serial
229                                       << " have power cal data, others do not. This "
230                                          "will cause inconsistent behaviour across "
231                                          "antenna ports when setting power levels.");
232             }
233         }
234     }
235 
get_serial() const236     std::string get_serial() const
237     {
238         return _serial;
239     }
240 
get_key()241     std::string get_key()
242     {
243         return _get_key();
244     }
245 
246 private:
247     const std::string _log_id;
248     std::string _serial;
249 
250     get_double_type _get_freq;
251     get_str_type _get_key;
252     uhd::gain_group::sptr _gain_group;
253     const std::string _hw_gain_name;
254 
255     //! Store the cal data for every cal key
256     using cal_data_map_type =
257         std::unordered_map<std::string /* key */, uhd::usrp::cal::pwr_cal::sptr>;
258     cal_data_map_type _cal_data;
259 
260     double _desired_power = 0;
261     tracking_mode _mode   = tracking_mode::TRACK_GAIN;
262 };
263 
make(const std::string & serial,const std::string & log_id,pwr_cal_mgr::get_double_type && get_freq,pwr_cal_mgr::get_str_type && get_key,uhd::gain_group::sptr gain_group)264 pwr_cal_mgr::sptr pwr_cal_mgr::make(const std::string& serial,
265     const std::string& log_id,
266     pwr_cal_mgr::get_double_type&& get_freq,
267     pwr_cal_mgr::get_str_type&& get_key,
268     uhd::gain_group::sptr gain_group)
269 {
270     return std::make_shared<pwr_cal_mgr_impl>(serial,
271         log_id,
272         std::move(get_freq),
273         std::move(get_key),
274         gain_group);
275 }
276