mirror of
https://github.com/espressif/openthread.git
synced 2026-11-08 15:33:21 +00:00
[radio] support max-power-table-mbm backed by SPINEL_PROP_PHY_CHAN_TARGET_POWER (#13790)
This commit adds the `max-power-table-mbm` radio URL parameter on POSIX to configure per-channel max transmit power in 0.01 dBm (mBm) units, while keeping the existing 1-dBm `max-power-table` parameter working. - Add `OT_RADIO_POWER_INVALID_IN_MBM` (`INT16_MAX`) and bump `OPENTHREAD_API_VERSION`. - Update `MaxPowerTable` and `RadioSpinel::SetChannelMaxTransmitPower()` to store and transmit per-channel max power in 0.01 dBm using `SPINEL_PROP_PHY_CHAN_TARGET_POWER`, with automatic fallback to `SPINEL_PROP_PHY_CHAN_MAX_POWER` when the RCP does not support `SPINEL_PROP_PHY_CHAN_TARGET_POWER`. - Update POSIX `Radio::ProcessMaxPowerTable()` to parse `max-power-table-mbm` (in 0.01 dBm) and `max-power-table` (in 1 dBm). - Add GTest and expect test coverage for `max-power-table-mbm` and legacy fallback.
This commit is contained in:
@@ -729,6 +729,24 @@ otError otPlatRadioGetTransmitPower(otInstance *aInstance, int8_t *aPower)
|
||||
|
||||
assert(aInstance != NULL);
|
||||
|
||||
#if OPENTHREAD_CONFIG_POWER_CALIBRATION_ENABLE && OPENTHREAD_CONFIG_PLATFORM_POWER_CALIBRATION_ENABLE
|
||||
if (maxPower == OT_RADIO_POWER_INVALID)
|
||||
{
|
||||
int16_t targetPower = OT_RADIO_POWER_INVALID_IN_MBM;
|
||||
int16_t actualPower = OT_RADIO_POWER_INVALID_IN_MBM;
|
||||
uint8_t rawPowerSetting[1] = {0};
|
||||
uint16_t rawPowerSettingLength = sizeof(rawPowerSetting);
|
||||
otError error = otPlatDiagRadioGetPowerSettings(aInstance, sCurrentChannel, &targetPower, &actualPower,
|
||||
rawPowerSetting, &rawPowerSettingLength);
|
||||
OT_UNUSED_VARIABLE(error);
|
||||
|
||||
if (targetPower != OT_RADIO_POWER_INVALID_IN_MBM)
|
||||
{
|
||||
maxPower = (int8_t)(targetPower / 100 - ((targetPower < 0 && targetPower % 100 != 0) ? 1 : 0));
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
*aPower = sTxPower < maxPower ? sTxPower : maxPower;
|
||||
|
||||
return OT_ERROR_NONE;
|
||||
@@ -962,6 +980,17 @@ exit:
|
||||
return error;
|
||||
}
|
||||
|
||||
#if !(OPENTHREAD_CONFIG_POWER_CALIBRATION_ENABLE && OPENTHREAD_CONFIG_PLATFORM_POWER_CALIBRATION_ENABLE)
|
||||
otError otPlatRadioSetChannelTargetPower(otInstance *aInstance, uint8_t aChannel, int16_t aTargetPower)
|
||||
{
|
||||
int16_t powerDbm = aTargetPower / 100 - ((aTargetPower < 0 && aTargetPower % 100 != 0) ? 1 : 0);
|
||||
int8_t maxPower =
|
||||
(aTargetPower == OT_RADIO_POWER_INVALID_IN_MBM) ? (int8_t)OT_RADIO_POWER_INVALID : (int8_t)powerDbm;
|
||||
|
||||
return otPlatRadioSetChannelMaxTransmitPower(aInstance, aChannel, maxPower);
|
||||
}
|
||||
#endif
|
||||
|
||||
#if OPENTHREAD_CONFIG_MLE_LINK_METRICS_SUBJECT_ENABLE
|
||||
otError otPlatRadioConfigureEnhAckProbing(otInstance *aInstance,
|
||||
otLinkMetrics aLinkMetrics,
|
||||
|
||||
@@ -51,7 +51,7 @@ extern "C" {
|
||||
*
|
||||
* @note This number versions both OpenThread platform and user APIs.
|
||||
*/
|
||||
#define OPENTHREAD_API_VERSION (635)
|
||||
#define OPENTHREAD_API_VERSION (636)
|
||||
|
||||
/**
|
||||
* @addtogroup api-instance
|
||||
|
||||
@@ -81,9 +81,10 @@ enum
|
||||
OT_RADIO_SYMBOL_TIME = 1000000 * 1 / OT_RADIO_SYMBOL_RATE, ///< Symbol duration time in unit of microseconds
|
||||
OT_RADIO_TEN_SYMBOLS_TIME = 10 * OT_RADIO_SYMBOL_TIME, ///< Time for 10 symbols in unit of microseconds
|
||||
|
||||
OT_RADIO_LQI_NONE = 0, ///< LQI measurement not supported
|
||||
OT_RADIO_RSSI_INVALID = 127, ///< Invalid or unknown RSSI value
|
||||
OT_RADIO_POWER_INVALID = 127, ///< Invalid or unknown power value
|
||||
OT_RADIO_LQI_NONE = 0, ///< LQI measurement not supported
|
||||
OT_RADIO_RSSI_INVALID = 127, ///< Invalid or unknown RSSI value
|
||||
OT_RADIO_POWER_INVALID = 127, ///< Invalid or unknown power value in dBm
|
||||
OT_RADIO_POWER_INVALID_IN_MBM = INT16_MAX, ///< Invalid or unknown power value in mBm (0.01 dBm)
|
||||
|
||||
OT_RADIO_INVALID_SHORT_ADDR = 0xfffe, ///< Invalid short address.
|
||||
OT_RADIO_BROADCAST_SHORT_ADDR = 0xffff, ///< Broadcast short address.
|
||||
@@ -1936,8 +1937,8 @@ otError otPlatRadioClearCalibratedPowers(otInstance *aInstance);
|
||||
* as possible to the @p aTargetPower. If the @p aTargetPower is lower than the minimum output power supported
|
||||
* by the platform, the output power should be set to the minimum output power supported by the platform. If the
|
||||
* @p aTargetPower is higher than the maximum output power supported by the platform, the output power should be
|
||||
* set to the maximum output power supported by the platform. If the @p aTargetPower is set to `INT16_MAX`, the
|
||||
* corresponding channel is disabled.
|
||||
* set to the maximum output power supported by the platform. If the @p aTargetPower is set to
|
||||
* `OT_RADIO_POWER_INVALID_IN_MBM`, the corresponding channel is disabled.
|
||||
*
|
||||
* @param[in] aInstance The OpenThread instance structure.
|
||||
* @param[in] aChannel The radio channel.
|
||||
|
||||
@@ -36,26 +36,33 @@ namespace ot {
|
||||
class MaxPowerTable
|
||||
{
|
||||
public:
|
||||
static constexpr int8_t kPowerDefault = 30; ///< Default power 1 watt (30 dBm).
|
||||
static constexpr int16_t kPowerDefault = 3000; ///< Default power 1 watt (30 dBm in 0.01 dBm).
|
||||
static constexpr int16_t kPowerInvalid = OT_RADIO_POWER_INVALID_IN_MBM; ///< Invalid power.
|
||||
|
||||
MaxPowerTable(void) { memset(mPowerTable, kPowerDefault, sizeof(mPowerTable)); }
|
||||
MaxPowerTable(void)
|
||||
{
|
||||
for (int16_t &power : mPowerTable)
|
||||
{
|
||||
power = kPowerDefault;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the max allowed transmit power of channel @p aChannel.
|
||||
*
|
||||
* @param[in] aChannel The radio channel number.
|
||||
*
|
||||
* @returns The max supported transmit power in dBm.
|
||||
* @returns The max supported transmit power in 0.01 dBm.
|
||||
*/
|
||||
int8_t GetTransmitPower(uint8_t aChannel) const { return mPowerTable[aChannel - Radio::kChannelMin]; }
|
||||
int16_t GetTransmitPower(uint8_t aChannel) const { return mPowerTable[aChannel - Radio::kChannelMin]; }
|
||||
|
||||
/**
|
||||
* Sets the max allowed transmit power of channel @p aChannel.
|
||||
*
|
||||
* @param[in] aChannel The radio channel number.
|
||||
* @param[in] aPower The max supported transmit power in dBm.
|
||||
* @param[in] aPower The max supported transmit power in 0.01 dBm.
|
||||
*/
|
||||
void SetTransmitPower(uint8_t aChannel, int8_t aPower) { mPowerTable[aChannel - Radio::kChannelMin] = aPower; }
|
||||
void SetTransmitPower(uint8_t aChannel, int16_t aPower) { mPowerTable[aChannel - Radio::kChannelMin] = aPower; }
|
||||
|
||||
/**
|
||||
* Gets the supported channel masks.
|
||||
@@ -66,7 +73,7 @@ public:
|
||||
|
||||
for (uint8_t i = Radio::kChannelMin; i <= Radio::kChannelMax; ++i)
|
||||
{
|
||||
if (mPowerTable[i - Radio::kChannelMin] != OT_RADIO_POWER_INVALID)
|
||||
if (mPowerTable[i - Radio::kChannelMin] != kPowerInvalid)
|
||||
{
|
||||
SetBit<uint32_t>(channelMask, i);
|
||||
}
|
||||
@@ -76,7 +83,7 @@ public:
|
||||
}
|
||||
|
||||
private:
|
||||
int8_t mPowerTable[Radio::kChannelMax - Radio::kChannelMin + 1];
|
||||
int16_t mPowerTable[Radio::kChannelMax - Radio::kChannelMin + 1];
|
||||
};
|
||||
|
||||
} // namespace ot
|
||||
|
||||
@@ -152,6 +152,12 @@ Error PowerCalibration::GetPowerSettings(uint8_t aChannel,
|
||||
chIndex = aChannel - Radio::kChannelMin;
|
||||
targetPower = mTargetPowerTable[chIndex];
|
||||
VerifyOrExit(targetPower != kInvalidPower, error = kErrorNotFound);
|
||||
|
||||
if (aTargetPower != nullptr)
|
||||
{
|
||||
*aTargetPower = targetPower;
|
||||
}
|
||||
|
||||
VerifyOrExit(mCalibratedPowerTables[chIndex].GetLength() > 0, error = kErrorNotFound);
|
||||
|
||||
for (uint8_t i = 0; i < mCalibratedPowerTables[chIndex].GetLength(); i++)
|
||||
|
||||
@@ -44,6 +44,7 @@
|
||||
|
||||
#include "common/code_utils.hpp"
|
||||
#include "common/new.hpp"
|
||||
#include "common/num_utils.hpp"
|
||||
#include "lib/platform/exit_code.h"
|
||||
#include "lib/spinel/logger.hpp"
|
||||
#include "lib/spinel/spinel_driver.hpp"
|
||||
@@ -121,6 +122,7 @@ RadioSpinel::RadioSpinel(void)
|
||||
#endif
|
||||
, mTimeSyncEnabled(false)
|
||||
, mTimeSyncOn(false)
|
||||
, mSupportsTargetPower(true)
|
||||
, mSpinelDriver(nullptr)
|
||||
{
|
||||
memset(&mCallbacks, 0, sizeof(mCallbacks));
|
||||
@@ -2327,12 +2329,28 @@ void RadioSpinel::RestoreProperties(void)
|
||||
{
|
||||
for (uint8_t channel = Radio::kChannelMin; channel <= Radio::kChannelMax; channel++)
|
||||
{
|
||||
int8_t power = mMaxPowerTable.GetTransmitPower(channel);
|
||||
int16_t power = mMaxPowerTable.GetTransmitPower(channel);
|
||||
|
||||
if (power != OT_RADIO_POWER_INVALID)
|
||||
if (power != MaxPowerTable::kPowerInvalid)
|
||||
{
|
||||
otError error =
|
||||
Set(SPINEL_PROP_PHY_CHAN_MAX_POWER, SPINEL_DATATYPE_UINT8_S SPINEL_DATATYPE_INT8_S, channel, power);
|
||||
otError error = OT_ERROR_NONE;
|
||||
|
||||
if (mSupportsTargetPower)
|
||||
{
|
||||
error = Set(SPINEL_PROP_PHY_CHAN_TARGET_POWER, SPINEL_DATATYPE_UINT8_S SPINEL_DATATYPE_INT16_S,
|
||||
channel, power);
|
||||
|
||||
if (error == OT_ERROR_NOT_IMPLEMENTED || error == OT_ERROR_NOT_FOUND)
|
||||
{
|
||||
mSupportsTargetPower = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!mSupportsTargetPower)
|
||||
{
|
||||
error = Set(SPINEL_PROP_PHY_CHAN_MAX_POWER, SPINEL_DATATYPE_UINT8_S SPINEL_DATATYPE_INT8_S, channel,
|
||||
ConvertMbmToDbm(power));
|
||||
}
|
||||
|
||||
// An RCP without `SPINEL_PROP_PHY_CHAN_MAX_POWER` answers with
|
||||
// `SPINEL_STATUS_PROP_NOT_FOUND`, which maps to
|
||||
@@ -2400,20 +2418,55 @@ exit:
|
||||
return error;
|
||||
}
|
||||
|
||||
otError RadioSpinel::SetChannelMaxTransmitPower(uint8_t aChannel, int8_t aMaxPower)
|
||||
int8_t RadioSpinel::ConvertMbmToDbm(int16_t aPowerMbm)
|
||||
{
|
||||
int8_t powerDbm;
|
||||
|
||||
if (aPowerMbm == MaxPowerTable::kPowerInvalid)
|
||||
{
|
||||
powerDbm = static_cast<int8_t>(OT_RADIO_POWER_INVALID);
|
||||
}
|
||||
else
|
||||
{
|
||||
int16_t dbm = aPowerMbm / 100 - ((aPowerMbm < 0 && aPowerMbm % 100 != 0) ? 1 : 0);
|
||||
|
||||
powerDbm = static_cast<int8_t>(Clamp<int16_t>(dbm, INT8_MIN, OT_RADIO_POWER_INVALID - 1));
|
||||
}
|
||||
|
||||
return powerDbm;
|
||||
}
|
||||
|
||||
otError RadioSpinel::SetChannelMaxTransmitPower(uint8_t aChannel, int16_t aMaxPower)
|
||||
{
|
||||
otError error = OT_ERROR_NONE;
|
||||
|
||||
VerifyOrExit(Radio::IsChannelValid(aChannel), error = OT_ERROR_INVALID_ARGS);
|
||||
mMaxPowerTable.SetTransmitPower(aChannel, aMaxPower);
|
||||
SuccessOrExit(error = Set(SPINEL_PROP_PHY_CHAN_MAX_POWER, SPINEL_DATATYPE_UINT8_S SPINEL_DATATYPE_INT8_S, aChannel,
|
||||
aMaxPower));
|
||||
|
||||
#if OPENTHREAD_SPINEL_CONFIG_RCP_RESTORATION_MAX_COUNT > 0
|
||||
mMaxPowerTableSet = true;
|
||||
#endif
|
||||
if (mSupportsTargetPower)
|
||||
{
|
||||
error = Set(SPINEL_PROP_PHY_CHAN_TARGET_POWER, SPINEL_DATATYPE_UINT8_S SPINEL_DATATYPE_INT16_S, aChannel,
|
||||
aMaxPower);
|
||||
|
||||
if (error != OT_ERROR_NOT_IMPLEMENTED && error != OT_ERROR_NOT_FOUND)
|
||||
{
|
||||
ExitNow();
|
||||
}
|
||||
|
||||
mSupportsTargetPower = false;
|
||||
}
|
||||
|
||||
error = Set(SPINEL_PROP_PHY_CHAN_MAX_POWER, SPINEL_DATATYPE_UINT8_S SPINEL_DATATYPE_INT8_S, aChannel,
|
||||
ConvertMbmToDbm(aMaxPower));
|
||||
|
||||
exit:
|
||||
#if OPENTHREAD_SPINEL_CONFIG_RCP_RESTORATION_MAX_COUNT > 0
|
||||
if (error == OT_ERROR_NONE)
|
||||
{
|
||||
mMaxPowerTableSet = true;
|
||||
}
|
||||
#endif
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
|
||||
@@ -855,15 +855,16 @@ public:
|
||||
const char *GetVersion(void) const { return mSpinelDriver->GetVersion(); }
|
||||
|
||||
/**
|
||||
* Sets the max transmit power.
|
||||
* Sets the max transmit power for a given channel.
|
||||
*
|
||||
* @param[in] aChannel The radio channel.
|
||||
* @param[in] aMaxPower The max transmit power in dBm.
|
||||
* @param[in] aMaxPower The max transmit power in 0.01 dBm. Passing `OT_RADIO_POWER_INVALID_IN_MBM` will disable
|
||||
* this channel.
|
||||
*
|
||||
* @retval OT_ERROR_NONE Successfully set the max transmit power.
|
||||
* @retval OT_ERROR_INVALID_ARGS Channel is not in valid range.
|
||||
*/
|
||||
otError SetChannelMaxTransmitPower(uint8_t aChannel, int8_t aMaxPower);
|
||||
otError SetChannelMaxTransmitPower(uint8_t aChannel, int16_t aMaxPower);
|
||||
|
||||
/**
|
||||
* Tries to retrieve a spinel property from OpenThread transceiver.
|
||||
@@ -990,7 +991,8 @@ public:
|
||||
* Set the target power for the given channel.
|
||||
*
|
||||
* @param[in] aChannel The radio channel.
|
||||
* @param[in] aTargetPower The target power in 0.01dBm. Passing `INT16_MAX` will disable this channel.
|
||||
* @param[in] aTargetPower The target power in 0.01dBm. Passing `OT_RADIO_POWER_INVALID_IN_MBM` will disable this
|
||||
* channel.
|
||||
*
|
||||
* @retval OT_ERROR_NONE Successfully set the target power.
|
||||
* @retval OT_ERROR_INVALID_ARGS The @p aChannel or @p aTargetPower is invalid..
|
||||
@@ -1217,6 +1219,8 @@ private:
|
||||
|
||||
void HandleCompatibilityError(void);
|
||||
|
||||
static int8_t ConvertMbmToDbm(int16_t aPowerMbm);
|
||||
|
||||
typedef otRadioSpinelMetrics Metrics;
|
||||
|
||||
class MetricsTracker
|
||||
@@ -1368,6 +1372,7 @@ private:
|
||||
|
||||
bool mTimeSyncEnabled : 1;
|
||||
bool mTimeSyncOn : 1;
|
||||
bool mSupportsTargetPower : 1;
|
||||
|
||||
SpinelDriver *mSpinelDriver;
|
||||
};
|
||||
|
||||
@@ -207,16 +207,28 @@ void Radio::ProcessMaxPowerTable(const RadioUrl &aRadioUrl)
|
||||
OT_UNUSED_VARIABLE(aRadioUrl);
|
||||
|
||||
#if OPENTHREAD_POSIX_CONFIG_MAX_POWER_TABLE_ENABLE
|
||||
otError error;
|
||||
constexpr int8_t kPowerDefault = 30; // Default power 1 watt (30 dBm).
|
||||
char *str = nullptr;
|
||||
char *pSave = nullptr;
|
||||
uint8_t channel = ot::Radio::kChannelMin;
|
||||
int8_t power = kPowerDefault;
|
||||
const char *maxPowerTable = nullptr;
|
||||
char *maxPowerTableCopy = nullptr;
|
||||
otError error;
|
||||
constexpr int16_t kPowerDefault = MaxPowerTable::kPowerDefault;
|
||||
char *str = nullptr;
|
||||
char *pSave = nullptr;
|
||||
uint8_t channel = ot::Radio::kChannelMin;
|
||||
int16_t power = kPowerDefault;
|
||||
const char *maxPowerTable = nullptr;
|
||||
char *maxPowerTableCopy = nullptr;
|
||||
bool inMbm = false;
|
||||
|
||||
VerifyOrExit((maxPowerTable = aRadioUrl.GetValue("max-power-table")) != nullptr);
|
||||
if ((maxPowerTable = aRadioUrl.GetValue("max-power-table-mbm")) != nullptr)
|
||||
{
|
||||
inMbm = true;
|
||||
}
|
||||
else if ((maxPowerTable = aRadioUrl.GetValue("max-power-table")) != nullptr)
|
||||
{
|
||||
inMbm = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
ExitNow();
|
||||
}
|
||||
|
||||
maxPowerTableCopy = strdup(maxPowerTable);
|
||||
VerifyOrDie(maxPowerTableCopy != nullptr, OT_EXIT_FAILURE);
|
||||
@@ -224,7 +236,36 @@ void Radio::ProcessMaxPowerTable(const RadioUrl &aRadioUrl)
|
||||
for (str = strtok_r(maxPowerTableCopy, ",", &pSave); str != nullptr && channel <= ot::Radio::kChannelMax;
|
||||
str = strtok_r(nullptr, ",", &pSave))
|
||||
{
|
||||
power = static_cast<int8_t>(strtol(str, nullptr, 0));
|
||||
long val = strtol(str, nullptr, 0);
|
||||
|
||||
if (inMbm)
|
||||
{
|
||||
if (val >= OT_RADIO_POWER_INVALID_IN_MBM)
|
||||
{
|
||||
power = MaxPowerTable::kPowerInvalid;
|
||||
}
|
||||
else if (val < INT16_MIN)
|
||||
{
|
||||
power = INT16_MIN;
|
||||
}
|
||||
else
|
||||
{
|
||||
power = static_cast<int16_t>(val);
|
||||
}
|
||||
}
|
||||
else if (val < INT8_MIN)
|
||||
{
|
||||
power = INT8_MIN * 100;
|
||||
}
|
||||
else if (val < OT_RADIO_POWER_INVALID)
|
||||
{
|
||||
power = static_cast<int16_t>(val * 100);
|
||||
}
|
||||
else
|
||||
{
|
||||
power = MaxPowerTable::kPowerInvalid;
|
||||
}
|
||||
|
||||
error = mRadioSpinel.SetChannelMaxTransmitPower(channel, power);
|
||||
VerifyOrDie((error == OT_ERROR_NONE) || (error == OT_ERROR_NOT_IMPLEMENTED), OT_EXIT_FAILURE);
|
||||
if (error == OT_ERROR_NOT_IMPLEMENTED)
|
||||
@@ -255,7 +296,6 @@ exit:
|
||||
{
|
||||
free(maxPowerTableCopy);
|
||||
}
|
||||
return;
|
||||
#endif // OPENTHREAD_POSIX_CONFIG_MAX_POWER_TABLE_ENABLE
|
||||
}
|
||||
|
||||
@@ -1023,7 +1063,11 @@ uint8_t otPlatRadioGetCslUncertainty(otInstance *aInstance)
|
||||
otError otPlatRadioSetChannelMaxTransmitPower(otInstance *aInstance, uint8_t aChannel, int8_t aMaxPower)
|
||||
{
|
||||
OT_UNUSED_VARIABLE(aInstance);
|
||||
return GetRadioSpinel().SetChannelMaxTransmitPower(aChannel, aMaxPower);
|
||||
|
||||
int16_t maxPower = (aMaxPower == OT_RADIO_POWER_INVALID) ? ot::MaxPowerTable::kPowerInvalid
|
||||
: static_cast<int16_t>(aMaxPower * 100);
|
||||
|
||||
return GetRadioSpinel().SetChannelMaxTransmitPower(aChannel, maxPower);
|
||||
}
|
||||
|
||||
#if OPENTHREAD_CONFIG_PLATFORM_POWER_CALIBRATION_ENABLE
|
||||
|
||||
@@ -108,7 +108,11 @@ const char *otSysGetRadioUrlHelpString(void)
|
||||
" max-power-table Max power for channels in ascending order separated by commas,\n" \
|
||||
" If the number of values is less than that of supported channels,\n" \
|
||||
" the last value will be applied to all remaining channels.\n" \
|
||||
" Special value 0x7f disables a channel.\n"
|
||||
" Special value 0x7f disables a channel.\n" \
|
||||
" max-power-table-mbm Max power (in 0.01 dBm) for channels in ascending order\n" \
|
||||
" separated by commas. If the number of values is less than that\n" \
|
||||
" of supported channels, the last value will be applied to all\n" \
|
||||
" remaining channels. Special value 0x7fff disables a channel.\n"
|
||||
|
||||
// Only meaningful while max-power-table is compiled in; without it the note
|
||||
// would point at a parameter the help does not list.
|
||||
|
||||
@@ -503,9 +503,9 @@ TEST(RadioSpinelMaxPowerTable, shouldRestoreEachChannelWithItsOwnPower)
|
||||
constexpr uint8_t kChannelA = 11;
|
||||
constexpr uint8_t kChannelB = 15;
|
||||
constexpr uint8_t kChannelC = 26;
|
||||
constexpr int8_t kPowerA = 5;
|
||||
constexpr int8_t kPowerB = 14;
|
||||
constexpr int8_t kPowerC = 20;
|
||||
constexpr int16_t kPowerA = 500;
|
||||
constexpr int16_t kPowerB = 1400;
|
||||
constexpr int16_t kPowerC = 2000;
|
||||
|
||||
FakeCoprocessorPlatform platform;
|
||||
|
||||
@@ -515,29 +515,84 @@ TEST(RadioSpinelMaxPowerTable, shouldRestoreEachChannelWithItsOwnPower)
|
||||
ASSERT_EQ(platform.mRadioSpinel.SetChannelMaxTransmitPower(kChannelB, kPowerB), kErrorNone);
|
||||
ASSERT_EQ(platform.mRadioSpinel.SetChannelMaxTransmitPower(kChannelC, kPowerC), kErrorNone);
|
||||
|
||||
ASSERT_EQ(platform.ChannelMaxTxPowerGet(kChannelA), kPowerA);
|
||||
ASSERT_EQ(platform.ChannelMaxTxPowerGet(kChannelB), kPowerB);
|
||||
ASSERT_EQ(platform.ChannelMaxTxPowerGet(kChannelC), kPowerC);
|
||||
#if OPENTHREAD_CONFIG_POWER_CALIBRATION_ENABLE && OPENTHREAD_CONFIG_PLATFORM_POWER_CALIBRATION_ENABLE
|
||||
{
|
||||
const uint8_t kRawPowerSetting[] = {0x01};
|
||||
int16_t targetPower = 0;
|
||||
int16_t actualPower = 0;
|
||||
uint8_t rawPowerSetting[sizeof(kRawPowerSetting)];
|
||||
uint16_t rawPowerSettingLength;
|
||||
|
||||
for (uint8_t channel = Radio::kChannelMin; channel <= Radio::kChannelMax; channel++)
|
||||
{
|
||||
ASSERT_EQ(platform.mRadioSpinel.AddCalibratedPower(channel, 0, kRawPowerSetting, sizeof(kRawPowerSetting)),
|
||||
kErrorNone);
|
||||
}
|
||||
|
||||
// Forget what the RCP was told, so what follows can only come from the
|
||||
// restore itself.
|
||||
ASSERT_EQ(
|
||||
otPlatRadioSetChannelTargetPower(FakePlatform::CurrentInstance(), kChannelA, OT_RADIO_POWER_INVALID_IN_MBM),
|
||||
kErrorNone);
|
||||
ASSERT_EQ(
|
||||
otPlatRadioSetChannelTargetPower(FakePlatform::CurrentInstance(), kChannelB, OT_RADIO_POWER_INVALID_IN_MBM),
|
||||
kErrorNone);
|
||||
ASSERT_EQ(
|
||||
otPlatRadioSetChannelTargetPower(FakePlatform::CurrentInstance(), kChannelC, OT_RADIO_POWER_INVALID_IN_MBM),
|
||||
kErrorNone);
|
||||
|
||||
platform.mRadioSpinel.RestoreProperties();
|
||||
|
||||
rawPowerSettingLength = sizeof(rawPowerSetting);
|
||||
ASSERT_EQ(otPlatDiagRadioGetPowerSettings(FakePlatform::CurrentInstance(), kChannelA, &targetPower,
|
||||
&actualPower, rawPowerSetting, &rawPowerSettingLength),
|
||||
kErrorNone);
|
||||
EXPECT_EQ(targetPower, kPowerA);
|
||||
|
||||
rawPowerSettingLength = sizeof(rawPowerSetting);
|
||||
ASSERT_EQ(otPlatDiagRadioGetPowerSettings(FakePlatform::CurrentInstance(), kChannelB, &targetPower,
|
||||
&actualPower, rawPowerSetting, &rawPowerSettingLength),
|
||||
kErrorNone);
|
||||
EXPECT_EQ(targetPower, kPowerB);
|
||||
|
||||
rawPowerSettingLength = sizeof(rawPowerSetting);
|
||||
ASSERT_EQ(otPlatDiagRadioGetPowerSettings(FakePlatform::CurrentInstance(), kChannelC, &targetPower,
|
||||
&actualPower, rawPowerSetting, &rawPowerSettingLength),
|
||||
kErrorNone);
|
||||
EXPECT_EQ(targetPower, kPowerC);
|
||||
|
||||
rawPowerSettingLength = sizeof(rawPowerSetting);
|
||||
ASSERT_EQ(otPlatDiagRadioGetPowerSettings(FakePlatform::CurrentInstance(), 12, &targetPower, &actualPower,
|
||||
rawPowerSetting, &rawPowerSettingLength),
|
||||
kErrorNone);
|
||||
constexpr int16_t kDefaultPower = MaxPowerTable::kPowerDefault;
|
||||
EXPECT_EQ(targetPower, kDefaultPower);
|
||||
}
|
||||
#else
|
||||
ASSERT_EQ(platform.ChannelTargetPowerGet(kChannelA), kPowerA);
|
||||
ASSERT_EQ(platform.ChannelTargetPowerGet(kChannelB), kPowerB);
|
||||
ASSERT_EQ(platform.ChannelTargetPowerGet(kChannelC), kPowerC);
|
||||
|
||||
// Forget what the RCP was told, so what follows can only come from the
|
||||
// restore itself.
|
||||
platform.ChannelMaxTxPowerClear();
|
||||
ASSERT_EQ(platform.ChannelMaxTxPowerCount(), 0u);
|
||||
platform.ChannelTargetPowerClear();
|
||||
ASSERT_EQ(platform.ChannelTargetPowerCount(), 0u);
|
||||
|
||||
platform.mRadioSpinel.RestoreProperties();
|
||||
|
||||
// Each configured channel comes back with its own power, not with a
|
||||
// neighbour's and not with the table default.
|
||||
EXPECT_EQ(platform.ChannelMaxTxPowerGet(kChannelA), kPowerA);
|
||||
EXPECT_EQ(platform.ChannelMaxTxPowerGet(kChannelB), kPowerB);
|
||||
EXPECT_EQ(platform.ChannelMaxTxPowerGet(kChannelC), kPowerC);
|
||||
EXPECT_EQ(platform.ChannelTargetPowerGet(kChannelA), kPowerA);
|
||||
EXPECT_EQ(platform.ChannelTargetPowerGet(kChannelB), kPowerB);
|
||||
EXPECT_EQ(platform.ChannelTargetPowerGet(kChannelC), kPowerC);
|
||||
|
||||
// Channels never configured keep the table default, which is what the
|
||||
// restore sends for them. Copied into a local first: `kPowerDefault` is a
|
||||
// `static constexpr` with no out-of-line definition, and EXPECT_EQ binds
|
||||
// its arguments by reference, which would odr-use it.
|
||||
constexpr int8_t kDefaultPower = MaxPowerTable::kPowerDefault;
|
||||
EXPECT_EQ(platform.ChannelMaxTxPowerGet(12), kDefaultPower);
|
||||
constexpr int16_t kDefaultPower = MaxPowerTable::kPowerDefault;
|
||||
EXPECT_EQ(platform.ChannelTargetPowerGet(12), kDefaultPower);
|
||||
#endif
|
||||
}
|
||||
|
||||
TEST(RadioSpinelMaxPowerTable, shouldNotTouchTheRcpWhenNoChannelWasConfigured)
|
||||
@@ -547,18 +602,21 @@ TEST(RadioSpinelMaxPowerTable, shouldNotTouchTheRcpWhenNoChannelWasConfigured)
|
||||
ASSERT_EQ(platform.mRadioSpinel.Enable(FakePlatform::CurrentInstance()), kErrorNone);
|
||||
|
||||
platform.ChannelMaxTxPowerClear();
|
||||
platform.ChannelTargetPowerClear();
|
||||
platform.mRadioSpinel.RestoreProperties();
|
||||
|
||||
// Nothing was configured, so the restore must not send the table's
|
||||
// defaults to the RCP -- 16 blocking transactions on every recovery for a
|
||||
// feature nobody asked for.
|
||||
EXPECT_EQ(platform.ChannelMaxTxPowerCount(), 0u);
|
||||
EXPECT_EQ(platform.ChannelTargetPowerCount(), 0u);
|
||||
}
|
||||
|
||||
#if !(OPENTHREAD_CONFIG_POWER_CALIBRATION_ENABLE && OPENTHREAD_CONFIG_PLATFORM_POWER_CALIBRATION_ENABLE)
|
||||
TEST(RadioSpinelMaxPowerTable, shouldSurviveAnRcpThatDoesNotImplementIt)
|
||||
{
|
||||
constexpr uint8_t kChannel = 11;
|
||||
constexpr int8_t kPower = 5;
|
||||
constexpr int16_t kPower = 500;
|
||||
|
||||
FakeCoprocessorPlatform platform;
|
||||
|
||||
@@ -570,22 +628,57 @@ TEST(RadioSpinelMaxPowerTable, shouldSurviveAnRcpThatDoesNotImplementIt)
|
||||
// `SPINEL_STATUS_UNIMPLEMENTED`, but both reach the host as
|
||||
// `OT_ERROR_NOT_IMPLEMENTED`, which is the path under test.
|
||||
platform.ChannelMaxTxPowerFailWith(OT_ERROR_NOT_IMPLEMENTED);
|
||||
platform.ChannelTargetPowerFailWith(OT_ERROR_NOT_IMPLEMENTED);
|
||||
platform.ChannelMaxTxPowerClear();
|
||||
platform.ChannelTargetPowerClear();
|
||||
|
||||
// Must not abort: this used to be a `DieNow()`, so a regression takes the
|
||||
// whole test binary down rather than failing an assertion.
|
||||
platform.mRadioSpinel.RestoreProperties();
|
||||
|
||||
EXPECT_EQ(platform.ChannelMaxTxPowerCount(), 0u);
|
||||
EXPECT_EQ(platform.ChannelTargetPowerCount(), 0u);
|
||||
|
||||
// And it must give up rather than ask again on every later recovery, even
|
||||
// once the RCP would accept it.
|
||||
platform.ChannelMaxTxPowerFailWith(OT_ERROR_NONE);
|
||||
platform.ChannelTargetPowerFailWith(OT_ERROR_NONE);
|
||||
platform.mRadioSpinel.RestoreProperties();
|
||||
|
||||
EXPECT_EQ(platform.ChannelMaxTxPowerCount(), 0u);
|
||||
EXPECT_EQ(platform.ChannelTargetPowerCount(), 0u);
|
||||
}
|
||||
|
||||
TEST(RadioSpinelMaxPowerTable, shouldFallbackToLegacyMaxPowerWhenTargetPowerNotSupported)
|
||||
{
|
||||
constexpr uint8_t kChannel = 11;
|
||||
constexpr uint8_t kNegChannel = 12;
|
||||
constexpr int16_t kPower = 1234;
|
||||
constexpr int16_t kNegPowerMbm = -550;
|
||||
|
||||
FakeCoprocessorPlatform platform;
|
||||
|
||||
ASSERT_EQ(platform.mRadioSpinel.Enable(FakePlatform::CurrentInstance()), kErrorNone);
|
||||
|
||||
platform.ChannelTargetPowerFailWith(OT_ERROR_NOT_IMPLEMENTED);
|
||||
|
||||
ASSERT_EQ(platform.mRadioSpinel.SetChannelMaxTransmitPower(kChannel, kPower), kErrorNone);
|
||||
ASSERT_EQ(platform.mRadioSpinel.SetChannelMaxTransmitPower(kNegChannel, kNegPowerMbm), kErrorNone);
|
||||
|
||||
// Fallback sends SPINEL_PROP_PHY_CHAN_MAX_POWER in 1 dBm (1234 -> 12, -550 -> -6).
|
||||
EXPECT_EQ(platform.ChannelMaxTxPowerGet(kChannel), 12);
|
||||
EXPECT_EQ(platform.ChannelMaxTxPowerGet(kNegChannel), -6);
|
||||
EXPECT_EQ(platform.ChannelTargetPowerCount(), 0u);
|
||||
|
||||
// Restore should also use legacy max power table.
|
||||
platform.ChannelMaxTxPowerClear();
|
||||
platform.mRadioSpinel.RestoreProperties();
|
||||
|
||||
EXPECT_EQ(platform.ChannelMaxTxPowerGet(kChannel), 12);
|
||||
EXPECT_EQ(platform.ChannelMaxTxPowerGet(kNegChannel), -6);
|
||||
}
|
||||
#endif
|
||||
|
||||
TEST(RadioSpinelSrcMatch, shouldNotDuplicateSrcMatchEntriesOnRestoreProperties)
|
||||
{
|
||||
constexpr uint16_t kTestShortAddr = 0x1234;
|
||||
|
||||
@@ -49,6 +49,22 @@ expect_line "Done"
|
||||
send "\x04"
|
||||
expect eof
|
||||
|
||||
# allows 11-25 and forbidden 26 (with mBm values)
|
||||
spawn_node 1 "rcp" "spinel+hdlc+uart://$env(OT_SIMULATION_APPS)/ncp/ot-rcp?max-power-table-mbm=1100,1200,1300,1400,1500,1600,1700,1800,1900,2000,2100,2200,2300,2400,-100,0x7fff&forkpty-arg=1"
|
||||
send "channel supported\n"
|
||||
expect "0x3fff800"
|
||||
expect_line "Done"
|
||||
send "channel preferred\n"
|
||||
expect "0x3fff800"
|
||||
expect_line "Done"
|
||||
send "txpower 20\n"
|
||||
expect_line "Done"
|
||||
send "txpower\n"
|
||||
expect "11 dBm"
|
||||
expect_line "Done"
|
||||
send "\x04"
|
||||
expect eof
|
||||
|
||||
# restore the configuration file
|
||||
spawn mv src/posix/platform/openthread.conf.example.backup src/posix/platform/openthread.conf.example
|
||||
expect eof
|
||||
|
||||
Reference in New Issue
Block a user