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