aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorVadim Yanitskiy <vyanitskiy@sysmocom.de>2020-10-13 21:40:24 +0700
committerVadim Yanitskiy <vyanitskiy@sysmocom.de>2020-10-15 19:46:11 +0700
commit967bca7b9e94a7ecb065b92da9008c8b75db9dec (patch)
tree5d85e6a995e3d971675388d0f2ba2d9e97814069
parent9a7acc17448e7486e41292379359239c7f5c81fe (diff)
power_control: implement EWMA based Uplink power filtering
So far the Uplink power control loop did not filter the Uplink RSSI measurements (reported by the BTS) at all. The lack of filtering makes our implementation too quick on the trigger, so in the real deployments there will be unneeded Tx power oscillations. In order to reduce this effect, let's implement a very simple EWMA (also known as Single Pole IIR) filtering that is defined as follows: Avg[n] = a * Pwr[n] + (1 - a) * Avg[n - 1] where parameter 'a' determines how much weight of the latest UL RSSI measurement result 'Pwr[n]' carries vs the weight of the average 'Avg[n - 1]'. The value of 'a' is usually a float in range 0 .. 1, so: - value 0.5 gives equal weight to both 'Pwr[n]' and 'Avg[n - 1]'; - value 1.0 means no filtering at all (pass through); - value 0.0 makes no sense. This formula was further optimized with the use of '+=' operator. The floating point math was also eliminated by scaling everything up (by 100). For more details, see: https://en.wikipedia.org/wiki/Moving_average https://en.wikipedia.org/wiki/Low-pass_filter#Simple_infinite_impulse_response_filter https://tomroelandts.com/articles/low-pass-single-pole-iir-filter The EWMA filtering is now *enabled by default*, but can be disabled or (re-)configured over the VTY at any time: ! Completely disable filtering no uplink-power-filtering ! Enable EWMA smoothing with the given parameters uplink-power-filtering algo ewma beta <1-99> Note that the VTY command expects 'beta' instead of 'alpha': alpha = (100 - beta) and the value must be in %. This is done for simplicity: 1% means lowest smoothing, 99% means highest smoothing. Let's say we have EWMA filtering enabled with alpha = 0.4, and get -98 dBm on the input, while the last output value was -60 dBm. The new output would be: Avg[n] = 0.4 * Pwr[n] + 0.6 * Avg[n - 1] Avg[n] = (0.4 * -98) + (0.6 * -60) Avg[n] = -75.2 => around -75 Of course, this is not a silver bullet, but better than nothing. Change-Id: Ib6dcadbf14ef59696c6a546bd323bda92d399f17 Related: SYS#4916
-rw-r--r--include/osmo-bts/bts.h21
-rw-r--r--include/osmo-bts/gsm_data.h3
-rw-r--r--src/common/bts.c2
-rw-r--r--src/common/power_control.c73
-rw-r--r--src/common/vty.c46
-rw-r--r--tests/power/power_test.c66
-rw-r--r--tests/power/power_test.err7
-rw-r--r--tests/power/power_test.ok23
8 files changed, 238 insertions, 3 deletions
diff --git a/include/osmo-bts/bts.h b/include/osmo-bts/bts.h
index 9df7f087..db8f741d 100644
--- a/include/osmo-bts/bts.h
+++ b/include/osmo-bts/bts.h
@@ -95,6 +95,12 @@ struct bts_smscb_state {
struct smscb_msg *default_msg; /* default broadcast message; NULL if none */
};
+/* Tx power filtering algorithm */
+enum ms_ul_pf_algo {
+ MS_UL_PF_ALGO_NONE = 0,
+ MS_UL_PF_ALGO_EWMA,
+};
+
/* One BTS */
struct gsm_bts {
/* list header in net->bts_list */
@@ -290,8 +296,23 @@ struct gsm_bts {
bool vty_override; /* OML value overridden by VTY */
} radio_link_timeout;
+ /* TODO: move it to bts->ul_power_ctrl struct */
int ul_power_target; /* Uplink Rx power target */
+ /* Uplink power control */
+ struct {
+ /* UL RSSI filtering algorithm */
+ enum ms_ul_pf_algo pf_algo;
+ /* (Optional) filtering parameters */
+ union {
+ /* Exponentially Weighted Moving Average */
+ struct {
+ /* Smoothing factor: higher the value - less smoothing */
+ uint8_t alpha; /* 1 .. 99 (in %) */
+ } ewma;
+ } pf;
+ } ul_power_ctrl;
+
/* used by the sysmoBTS to adjust band */
uint8_t auto_band;
diff --git a/include/osmo-bts/gsm_data.h b/include/osmo-bts/gsm_data.h
index c6fe6090..1c1c5d44 100644
--- a/include/osmo-bts/gsm_data.h
+++ b/include/osmo-bts/gsm_data.h
@@ -286,6 +286,9 @@ struct gsm_lchan {
uint8_t current;
uint8_t max;
bool fixed;
+
+ /* Scaled up (100 times) average UL RSSI */
+ int avg100_ul_rssi;
} ms_power_ctrl;
/* BTS power reduction (in dB) */
diff --git a/src/common/bts.c b/src/common/bts.c
index 5890c1a7..bb0795a7 100644
--- a/src/common/bts.c
+++ b/src/common/bts.c
@@ -303,6 +303,8 @@ int bts_init(struct gsm_bts *bts)
/* configurable via VTY */
bts->paging_state = paging_init(bts, 200, 0);
bts->ul_power_target = -75; /* dBm default */
+ bts->ul_power_ctrl.pf_algo = MS_UL_PF_ALGO_EWMA;
+ bts->ul_power_ctrl.pf.ewma.alpha = 20; /* 80% smoothing */
bts->rtp_jitter_adaptive = false;
bts->rtp_port_range_start = 16384;
bts->rtp_port_range_end = 17407;
diff --git a/src/common/power_control.c b/src/common/power_control.c
index d36f157c..4798f4fa 100644
--- a/src/common/power_control.c
+++ b/src/common/power_control.c
@@ -1,6 +1,7 @@
/* MS Power Control Loop L1 */
/* (C) 2014 by Holger Hans Peter Freyther
+ * Contributions by sysmocom - s.m.f.c. GmbH <info@sysmocom.de>
*
* All Rights Reserved
*
@@ -36,6 +37,60 @@
#define MS_RAISE_MAX_DB 4
#define MS_LOWER_MAX_DB 8
+/* We don't want to deal with floating point, so we scale up */
+#define EWMA_SCALE_FACTOR 100
+
+/* Base Low-Pass Single-Pole IIR Filter (EWMA) formula:
+ *
+ * Avg[n] = a * Pwr[n] + (1 - a) * Avg[n - 1]
+ *
+ * where parameter 'a' determines how much weight of the latest UL RSSI measurement
+ * result 'Pwr[n]' carries vs the weight of the average 'Avg[n - 1]'. The value of
+ * 'a' is usually a float in range 0 .. 1, so:
+ *
+ * - value 0.5 gives equal weight to both 'Pwr[n]' and 'Avg[n - 1]';
+ * - value 1.0 means no filtering at all (pass through);
+ * - value 0.0 makes no sense.
+ *
+ * Further optimization:
+ *
+ * Avg[n] = a * Pwr[n] + Avg[n - 1] - a * Avg[n - 1]
+ * ^^^^^^ ^^^^^^^^^^
+ *
+ * a) this can be implemented in C using '+=' operator:
+ *
+ * Avg += a * Pwr - a * Avg
+ * Avg += a * (Pwr - Avg)
+ *
+ * b) everything is scaled up by 100 to avoid floating point stuff:
+ *
+ * Avg100 += A * (Pwr - Avg)
+ *
+ * where 'Avg100' is 'Avg * 100' and 'A' is 'a * 100'.
+ *
+ * For more details, see:
+ *
+ * https://en.wikipedia.org/wiki/Moving_average
+ * https://en.wikipedia.org/wiki/Low-pass_filter#Simple_infinite_impulse_response_filter
+ * https://tomroelandts.com/articles/low-pass-single-pole-iir-filter
+ */
+static int8_t lchan_ul_pf_ewma(const struct gsm_bts *bts,
+ struct gsm_lchan *lchan,
+ const int8_t Pwr)
+{
+ const uint8_t A = bts->ul_power_ctrl.pf.ewma.alpha;
+ int *Avg100 = &lchan->ms_power_ctrl.avg100_ul_rssi;
+
+ /* We don't have 'Avg[n - 1]' if this is the first run */
+ if (lchan->meas.res_nr == 0) {
+ *Avg100 = Pwr * EWMA_SCALE_FACTOR;
+ return Pwr;
+ }
+
+ *Avg100 += A * (Pwr - *Avg100 / EWMA_SCALE_FACTOR);
+ return *Avg100 / EWMA_SCALE_FACTOR;
+}
+
/*! compute the new MS POWER LEVEL communicated to the MS and store it in lchan.
* \param lchan logical channel for which to compute (and in which to store) new power value.
* \param[in] ms_power_lvl MS Power Level received from Uplink L1 SACCH Header in SACCH block.
@@ -51,6 +106,7 @@ int lchan_ms_pwr_ctrl(struct gsm_lchan *lchan,
enum gsm_band band = bts->band;
int8_t new_power_lvl; /* TS 05.05 power level */
int8_t ms_dbm, new_dbm, current_dbm, bsc_max_dbm;
+ int8_t avg_ul_rssi_dbm;
if (!trx_ms_pwr_ctrl_is_osmo(lchan->ts->trx))
return 0;
@@ -72,9 +128,20 @@ int lchan_ms_pwr_ctrl(struct gsm_lchan *lchan,
return 0;
}
+ /* Filter UL RSSI to reduce unnecessary Tx power oscillations */
+ switch (bts->ul_power_ctrl.pf_algo) {
+ case MS_UL_PF_ALGO_EWMA:
+ avg_ul_rssi_dbm = lchan_ul_pf_ewma(bts, lchan, ul_rssi_dbm);
+ break;
+ case MS_UL_PF_ALGO_NONE:
+ default:
+ /* No filtering (pass through) */
+ avg_ul_rssi_dbm = ul_rssi_dbm;
+ }
+
/* How many dBs measured power should be increased (+) or decreased (-)
to reach expected power. */
- diff = bts->ul_power_target - ul_rssi_dbm;
+ diff = bts->ul_power_target - avg_ul_rssi_dbm;
/* don't ever change more than MS_{LOWER,RAISE}_MAX_DBM during one loop
iteration, i.e. reduce the speed at which the MS transmit power can
@@ -108,7 +175,7 @@ int lchan_ms_pwr_ctrl(struct gsm_lchan *lchan,
"(rx-ms-pwr-lvl %" PRIu8 ", max-ms-pwr-lvl %" PRIu8 ", rx-current %d dBm, rx-target %d dBm)\n",
new_power_lvl, new_dbm,
ms_power_lvl, lchan->ms_power_ctrl.max,
- ul_rssi_dbm, bts->ul_power_target);
+ avg_ul_rssi_dbm, bts->ul_power_target);
return 0;
}
@@ -118,7 +185,7 @@ int lchan_ms_pwr_ctrl(struct gsm_lchan *lchan,
(new_dbm > current_dbm) ? "Raising" : "Lowering",
lchan->ms_power_ctrl.current, current_dbm, new_power_lvl, new_dbm,
ms_power_lvl, lchan->ms_power_ctrl.max,
- ul_rssi_dbm, bts->ul_power_target);
+ avg_ul_rssi_dbm, bts->ul_power_target);
/* store the resulting new MS power level in the lchan */
lchan->ms_power_ctrl.current = new_power_lvl;
diff --git a/src/common/vty.c b/src/common/vty.c
index 534b6328..16ffe729 100644
--- a/src/common/vty.c
+++ b/src/common/vty.c
@@ -256,6 +256,19 @@ static void config_write_bts_single(struct vty *vty, const struct gsm_bts *bts)
vty_out(vty, " paging lifetime %u%s", paging_get_lifetime(bts->paging_state),
VTY_NEWLINE);
vty_out(vty, " uplink-power-target %d%s", bts->ul_power_target, VTY_NEWLINE);
+
+ /* MS Tx power filtering algorithm and parameters */
+ switch (bts->ul_power_ctrl.pf_algo) {
+ case MS_UL_PF_ALGO_EWMA:
+ vty_out(vty, " uplink-power-filtering algo ewma beta %u%s",
+ 100 - bts->ul_power_ctrl.pf.ewma.alpha, VTY_NEWLINE);
+ break;
+ case MS_UL_PF_ALGO_NONE:
+ default:
+ vty_out(vty, " no uplink-power-filtering%s", VTY_NEWLINE);
+ break;
+ }
+
if (bts->agch_queue.thresh_level != GSM_BTS_AGCH_QUEUE_THRESH_LEVEL_DEFAULT
|| bts->agch_queue.low_level != GSM_BTS_AGCH_QUEUE_LOW_LEVEL_DEFAULT
|| bts->agch_queue.high_level != GSM_BTS_AGCH_QUEUE_HIGH_LEVEL_DEFAULT)
@@ -615,6 +628,37 @@ DEFUN_ATTR(cfg_bts_ul_power_target, cfg_bts_ul_power_target_cmd,
return CMD_SUCCESS;
}
+DEFUN_ATTR(cfg_no_bts_ul_power_filter,
+ cfg_bts_no_ul_power_filter_cmd,
+ "no uplink-power-filtering",
+ NO_STR "Disable filtering for uplink power control loop\n",
+ CMD_ATTR_IMMEDIATE)
+{
+ struct gsm_bts *bts = vty->index;
+
+ bts->ul_power_ctrl.pf_algo = MS_UL_PF_ALGO_NONE;
+
+ return CMD_SUCCESS;
+}
+
+DEFUN_ATTR(cfg_bts_ul_power_filter_ewma,
+ cfg_bts_ul_power_filter_ewma_cmd,
+ "uplink-power-filtering algo ewma beta <1-99>",
+ "Configure filtering for uplink power control loop\n"
+ "Select the filtering algorithm\n"
+ "Exponentially Weighted Moving Average (EWMA)\n"
+ "Smoothing factor (in %%): beta = (100 - alpha)\n"
+ "1%% - lowest smoothing, 99%% - highest smoothing\n",
+ CMD_ATTR_IMMEDIATE)
+{
+ struct gsm_bts *bts = vty->index;
+
+ bts->ul_power_ctrl.pf_algo = MS_UL_PF_ALGO_EWMA;
+ bts->ul_power_ctrl.pf.ewma.alpha = 100 - atoi(argv[0]);
+
+ return CMD_SUCCESS;
+}
+
DEFUN_ATTR(cfg_bts_min_qual_rach, cfg_bts_min_qual_rach_cmd,
"min-qual-rach <-100-100>",
"Set the minimum link quality level of Access Bursts to be accepted\n"
@@ -1805,6 +1849,8 @@ int bts_vty_init(struct gsm_bts *bts)
install_element(BTS_NODE, &cfg_bts_agch_queue_mgmt_default_cmd);
install_element(BTS_NODE, &cfg_bts_agch_queue_mgmt_params_cmd);
install_element(BTS_NODE, &cfg_bts_ul_power_target_cmd);
+ install_element(BTS_NODE, &cfg_bts_no_ul_power_filter_cmd);
+ install_element(BTS_NODE, &cfg_bts_ul_power_filter_ewma_cmd);
install_element(BTS_NODE, &cfg_bts_min_qual_rach_cmd);
install_element(BTS_NODE, &cfg_bts_min_qual_norm_cmd);
install_element(BTS_NODE, &cfg_bts_max_ber_rach_cmd);
diff --git a/tests/power/power_test.c b/tests/power/power_test.c
index c3bfdf98..eff722ae 100644
--- a/tests/power/power_test.c
+++ b/tests/power/power_test.c
@@ -61,6 +61,9 @@ static inline void apply_power_test(struct gsm_lchan *lchan, int rxlev, int exp_
old = lchan->ms_power_ctrl.current;
ret = lchan_ms_pwr_ctrl(lchan, lchan->ms_power_ctrl.current, rxlev);
+ /* Keep the measurement counter updated */
+ lchan->meas.res_nr++;
+
printf("lchan_ms_pwr_ctrl(RxLvl=%d dBm) returns %d (expected %d)\n",
rxlev, ret, exp_ret);
printf("\tMS current power %u -> %u (expected %u)\n",
@@ -140,6 +143,68 @@ static void test_power_loop(void)
apply_power_test(lchan, -40, 1, 15);
}
+static void test_pf_algo_ewma(void)
+{
+ struct gsm_lchan *lchan;
+ const int *avg100;
+
+ init_test(__func__);
+ lchan = &g_trx->ts[0].lchan[0];
+ avg100 = &lchan->ms_power_ctrl.avg100_ul_rssi;
+
+ g_bts->ul_power_ctrl.pf_algo = MS_UL_PF_ALGO_EWMA;
+ g_bts->ul_power_ctrl.pf.ewma.alpha = 20; /* 80% smoothing */
+
+ lchan->ms_power_ctrl.current = ms_pwr_ctl_lvl(GSM_BAND_1800, 0);
+ OSMO_ASSERT(lchan->ms_power_ctrl.current == 15);
+ lchan->ms_power_ctrl.max = ms_pwr_ctl_lvl(GSM_BAND_1800, 26);
+ OSMO_ASSERT(lchan->ms_power_ctrl.max == 2);
+
+#define CHECK_UL_RSSI_AVG100(exp) \
+ printf("\tAvg[t] is %2.2f dBm (expected %2.2f dBm)\n", \
+ ((float) *avg100) / 100, exp);
+
+ /* UL RSSI remains constant => no UL power change */
+ apply_power_test(lchan, -75, 0, 15);
+ CHECK_UL_RSSI_AVG100(-75.00);
+
+ /* Avg[t] = (0.2 * -90) + (0.8 * -75) = -78.0 dBm */
+ apply_power_test(lchan, -90, 1, 13);
+ CHECK_UL_RSSI_AVG100(-78.00);
+
+ /* Avg[t] = (0.2 * -90) + (0.8 * -78) = -80.4 dBm */
+ apply_power_test(lchan, -90, 1, 11);
+ CHECK_UL_RSSI_AVG100(-80.40);
+
+ /* Avg[t] = (0.2 * -70) + (0.8 * -80.4) = -78.32 dBm,
+ * but due to up-/down-scaling artefacts we get the following:
+ * Avg100[t] = Avg100[t - 1] + A * (Pwr - Avg[t] / 100)
+ * Avg100[t] = -8040 + 20 * (-70 - (-8040 / 100))
+ * Avg100[t] = -8040 + 20 * (-70 - (-8040 / 100))
+ * Avg100[t] = -8040 + 20 * (-70 + 80)
+ * Avg100[t] = -8040 + 200 = -7840
+ * Avg[t] = -7840 / 100 = -78.4 */
+ apply_power_test(lchan, -70, 1, 9);
+ CHECK_UL_RSSI_AVG100(-78.40);
+
+ g_bts->ul_power_ctrl.pf.ewma.alpha = 70; /* 30% smoothing */
+ lchan->ms_power_ctrl.current = 15;
+ lchan->meas.res_nr = 0;
+
+ /* This is the first sample, the filter outputs it as-is */
+ apply_power_test(lchan, -50, 0, 15);
+ CHECK_UL_RSSI_AVG100(-50.00);
+
+ /* Avg[t] = (0.7 * -50) + (0.3 * -50) = -50.0 dBm */
+ apply_power_test(lchan, -50, 0, 15);
+ CHECK_UL_RSSI_AVG100(-50.0);
+
+ /* Simulate SACCH block loss (-110 dBm):
+ * Avg[t] = (0.7 * -110) + (0.3 * -50) = -92.0 dBm */
+ apply_power_test(lchan, -110, 1, 13);
+ CHECK_UL_RSSI_AVG100(-92.0);
+}
+
int main(int argc, char **argv)
{
printf("Testing power loop...\n");
@@ -154,6 +219,7 @@ int main(int argc, char **argv)
log_set_use_color(osmo_stderr_target, 0);
test_power_loop();
+ test_pf_algo_ewma();
printf("Power loop test OK\n");
diff --git a/tests/power/power_test.err b/tests/power/power_test.err
index e4d821a8..786f2a7b 100644
--- a/tests/power/power_test.err
+++ b/tests/power/power_test.err
@@ -21,3 +21,10 @@
(bts=0,trx=0,ts=0,ss=0) Raising MS power from control level 10 (10 dBm) to 9, 12 dBm (rx-ms-pwr-lvl 10, max-ms-pwr-lvl 29, rx-current -77 dBm, rx-target -75 dBm)
(bts=0,trx=0,ts=0,ss=0) Lowering MS power from control level 9 (12 dBm) to 14, 2 dBm (rx-ms-pwr-lvl 9, max-ms-pwr-lvl 14, rx-current -73 dBm, rx-target -75 dBm)
(bts=0,trx=0,ts=0,ss=0) Lowering MS power from control level 14 (2 dBm) to 15, 0 dBm (rx-ms-pwr-lvl 14, max-ms-pwr-lvl 0, rx-current -40 dBm, rx-target -75 dBm)
+(bts=0,trx=0,ts=0,ss=0) Keeping MS power at control level 15, 0 dBm (rx-ms-pwr-lvl 15, max-ms-pwr-lvl 2, rx-current -75 dBm, rx-target -75 dBm)
+(bts=0,trx=0,ts=0,ss=0) Raising MS power from control level 15 (0 dBm) to 13, 3 dBm (rx-ms-pwr-lvl 15, max-ms-pwr-lvl 2, rx-current -78 dBm, rx-target -75 dBm)
+(bts=0,trx=0,ts=0,ss=0) Raising MS power from control level 13 (4 dBm) to 11, 8 dBm (rx-ms-pwr-lvl 13, max-ms-pwr-lvl 2, rx-current -80 dBm, rx-target -75 dBm)
+(bts=0,trx=0,ts=0,ss=0) Raising MS power from control level 11 (8 dBm) to 9, 11 dBm (rx-ms-pwr-lvl 11, max-ms-pwr-lvl 2, rx-current -78 dBm, rx-target -75 dBm)
+(bts=0,trx=0,ts=0,ss=0) Keeping MS power at control level 15, 0 dBm (rx-ms-pwr-lvl 15, max-ms-pwr-lvl 2, rx-current -50 dBm, rx-target -75 dBm)
+(bts=0,trx=0,ts=0,ss=0) Keeping MS power at control level 15, 0 dBm (rx-ms-pwr-lvl 15, max-ms-pwr-lvl 2, rx-current -50 dBm, rx-target -75 dBm)
+(bts=0,trx=0,ts=0,ss=0) Raising MS power from control level 15 (0 dBm) to 13, 4 dBm (rx-ms-pwr-lvl 15, max-ms-pwr-lvl 2, rx-current -92 dBm, rx-target -75 dBm)
diff --git a/tests/power/power_test.ok b/tests/power/power_test.ok
index 6b7a0d46..25551d82 100644
--- a/tests/power/power_test.ok
+++ b/tests/power/power_test.ok
@@ -49,4 +49,27 @@ lchan_ms_pwr_ctrl(RxLvl=-60 dBm) returns 0 (expected 0)
MS current power 14 -> 14 (expected 14)
lchan_ms_pwr_ctrl(RxLvl=-40 dBm) returns 1 (expected 1)
MS current power 14 -> 15 (expected 15)
+
+Starting test case 'test_pf_algo_ewma'
+lchan_ms_pwr_ctrl(RxLvl=-75 dBm) returns 0 (expected 0)
+ MS current power 15 -> 15 (expected 15)
+ Avg[t] is -75.00 dBm (expected -75.00 dBm)
+lchan_ms_pwr_ctrl(RxLvl=-90 dBm) returns 1 (expected 1)
+ MS current power 15 -> 13 (expected 13)
+ Avg[t] is -78.00 dBm (expected -78.00 dBm)
+lchan_ms_pwr_ctrl(RxLvl=-90 dBm) returns 1 (expected 1)
+ MS current power 13 -> 11 (expected 11)
+ Avg[t] is -80.40 dBm (expected -80.40 dBm)
+lchan_ms_pwr_ctrl(RxLvl=-70 dBm) returns 1 (expected 1)
+ MS current power 11 -> 9 (expected 9)
+ Avg[t] is -78.40 dBm (expected -78.40 dBm)
+lchan_ms_pwr_ctrl(RxLvl=-50 dBm) returns 0 (expected 0)
+ MS current power 15 -> 15 (expected 15)
+ Avg[t] is -50.00 dBm (expected -50.00 dBm)
+lchan_ms_pwr_ctrl(RxLvl=-50 dBm) returns 0 (expected 0)
+ MS current power 15 -> 15 (expected 15)
+ Avg[t] is -50.00 dBm (expected -50.00 dBm)
+lchan_ms_pwr_ctrl(RxLvl=-110 dBm) returns 1 (expected 1)
+ MS current power 15 -> 13 (expected 13)
+ Avg[t] is -92.00 dBm (expected -92.00 dBm)
Power loop test OK