[channel-manager] add (auto) "channel selection" algorithm

This commit updates `ChannelManager` class to add support for "channel
selection" logic where the `ChannelManager` can select a channel (for
network to switch to) based on the collected channel/link quality data
(by `ChannelMonitor` module). This commit also adds support for auto-
channel-selection functionality where `ChannelManager` will periodically
check to see if network can/should switch to a better channel. This
commit also adds public OpenThread APIs for all newly added
functionalities.
This commit is contained in:
Abtin Keshavarzian
2018-04-16 14:07:19 -07:00
parent df45240665
commit 51ca76c242
11 changed files with 701 additions and 59 deletions
+97 -5
View File
@@ -64,12 +64,9 @@ extern "C" {
*
* @param[in] aInstance A pointer to an OpenThread instance.
* @param[in] aChannel The new channel for the Thread network.
* @retval OT_ERROR_NONE Channel change request successfully processed.
* @retval OT_ERROR_INVALID_ARGS The channel is not a supported channel (@sa otChannelManagerGetSupportedChannels).
*
*/
otError otChannelManagerRequestChannelChange(otInstance *aInstance, uint8_t aChannel);
void otChannelManagerRequestChannelChange(otInstance *aInstance, uint8_t aChannel);
/**
* This function gets the channel from the last successful call to `otChannelManagerRequestChannelChange()`
@@ -104,12 +101,88 @@ uint16_t otChannelManagerGetDelay(otInstance *aInstance);
*/
otError otChannelManagerSetDelay(otInstance *aInstance, uint16_t aDelay);
/**
* This function requests that `ChannelManager` checks and selects a new channel and starts a channel change.
*
* Unlike the `otChannelManagerRequestChannelChange()` where the channel must be given as a parameter, this function
* asks the `ChannelManager` to select a channel by itself (based of collected channel quality info).
*
* Once called, the Channel Manager will perform the following 3 steps:
*
* 1) `ChannelManager` decides if the channel change would be helpful. This check can be skipped if
* `aSkipQualityCheck` is set to true (forcing a channel selection to happen and skipping the quality check).
* This step uses the collected link quality metrics on the device (such as CCA failure rate, frame and message
* error rates per neighbor, etc.) to determine if the current channel quality is at the level that justifies
* a channel change.
*
* 2) If the first step passes, then `ChannelManager` selects a potentially better channel. It uses the collected
* channel quality data by `ChannelMonitor` module. The supported and favored channels are used at this step.
* (@sa otChannelManagerSetSupportedChannels, @sa otChannelManagerSetFavoredChannels).
*
* 3) If the newly selected channel is different from the current channel, `ChannelManager` requests/starts the
* channel change process (internally invoking a `RequestChannelChange()`).
*
* @param[in] aInstance A pointer to an OpenThread instance.
* @param[in] aSkipQualityCheck Indicates whether the quality check (step 1) should be skipped.
*
* @retval OT_ERROR_NONE Channel selection finished successfully.
* @retval OT_ERROR_NOT_FOUND Supported channel mask is empty, therefore could not select a channel.
* @retval OT_ERROR_INVALID_STATE Thread is not enabled or not enough data to select a new channel.
* @retval OT_ERROR_DISABLED_FEATURE `ChannelMonintor` feature is disabled by build-time configuration options.
*
*/
otError otChannelManagerRequestChannelSelect(otInstance *aInstance, bool aSkipQualityCheck);
/**
* This function enables/disables the auto-channel-selection functionality.
*
* When enabled, `ChannelManager` will periodically invoke a `RequestChannelSelect(false)`. The period interval
* can be set by `SetAutoChannelSelectionInterval()`.
*
* @param[in] aInstance A pointer to an OpenThread instance.
* @param[in] aEnabled Indicates whether to enable or disable this functionality.
*
*/
void otChannelManagerSetAutoChannelSelectionEnabled(otInstance *aInstance, bool aEnabled);
/**
* This function indicates whether the auto-channel-selection functionality is enabled or not.
*
* @param[in] aInstance A pointer to an OpenThread instance.
*
* @returns TRUE if enabled, FALSE if disabled.
*
*/
bool otChannelManagerGetAutoChannelSelectionEnabled(otInstance *aInstance);
/**
* This function sets the period interval (in seconds) used by auto-channel-selection functionality.
*
* @param[in] aInstance A pointer to an OpenThread instance.
* @param[in] aInterval The interval in seconds.
*
* @retval OT_ERROR_NONE The interval was set successfully.
* @retval OT_ERROR_INVALID_ARGS The @p aInterval is not valid (zero).
*
*/
otError otChannelManagerSetAutoChannelSelectionInterval(otInstance *aInstance, uint32_t aInterval);
/**
* This function gets the period interval (in seconds) used by auto-channel-selection functionality.
*
* @param[in] aInstance A pointer to an OpenThread instance.
*
* @returns The interval in seconds.
*
*/
uint32_t otChannelManagerGetAutoChannelSelectionInterval(otInstance *aInstance);
/**
* This function gets the supported channel mask.
*
* @param[in] aInstance A pointer to an OpenThread instance.
*
* @returns The supported channels as bit-mask.
* @returns The supported channels as a bit-mask.
*
*/
uint32_t otChannelManagerGetSupportedChannels(otInstance *aInstance);
@@ -123,6 +196,25 @@ uint32_t otChannelManagerGetSupportedChannels(otInstance *aInstance);
*/
void otChannelManagerSetSupportedChannels(otInstance *aInstance, uint32_t aChannelMask);
/**
* This function gets the favored channel mask.
*
* @param[in] aInstance A pointer to an OpenThread instance.
*
* @returns The favored channels as a bit-mask.
*
*/
uint32_t otChannelManagerGetFavoredChannels(otInstance *aInstance);
/**
* This function sets the favored channel mask.
*
* @param[in] aInstance A pointer to an OpenThread instance.
* @param[in] aChannelMask A channel mask.
*
*/
void otChannelManagerSetFavoredChannels(otInstance *aInstance, uint32_t aChannelMask);
/**
* @}
*
+1
View File
@@ -247,6 +247,7 @@ enum
OT_CHANGED_MASTER_KEY = 1 << 20, ///< Master key changed
OT_CHANGED_PSKC = 1 << 21, ///< PSKc changed
OT_CHANGED_SECURITY_POLICY = 1 << 22, ///< Security Policy changed
OT_CHANGED_CHANNEL_MANAGER_NEW_CHANNEL = 1 << 23, ///< Channel Manager new pending Thread channel changed
};
/**
+51 -2
View File
@@ -41,11 +41,11 @@ using namespace ot;
#if OPENTHREAD_ENABLE_CHANNEL_MANAGER && OPENTHREAD_FTD
otError otChannelManagerRequestChannelChange(otInstance *aInstance, uint8_t aChannel)
void otChannelManagerRequestChannelChange(otInstance *aInstance, uint8_t aChannel)
{
Instance &instance = *static_cast<Instance *>(aInstance);
return instance.GetChannelManager().RequestChannelChange(aChannel);
instance.GetChannelManager().RequestChannelChange(aChannel);
}
uint8_t otChannelManagerGetRequestedChannel(otInstance *aInstance)
@@ -69,6 +69,41 @@ otError otChannelManagerSetDelay(otInstance *aInstance, uint16_t aMinDelay)
return instance.GetChannelManager().SetDelay(aMinDelay);
}
otError otChannelManagerRequestChannelSelect(otInstance *aInstance, bool aSkipQualityCheck)
{
Instance &instance = *static_cast<Instance *>(aInstance);
return instance.GetChannelManager().RequestChannelSelect(aSkipQualityCheck);
}
void otChannelManagerSetAutoChannelSelectionEnabled(otInstance *aInstance, bool aEnabled)
{
Instance &instance = *static_cast<Instance *>(aInstance);
instance.GetChannelManager().SetAutoChannelSelectionEnabled(aEnabled);
}
bool otChannelManagerGetAutoChannelSelectionEnabled(otInstance *aInstance)
{
Instance &instance = *static_cast<Instance *>(aInstance);
return instance.GetChannelManager().GetAutoChannelSelectionEnabled();
}
otError otChannelManagerSetAutoChannelSelectionInterval(otInstance *aInstance, uint32_t aInterval)
{
Instance &instance = *static_cast<Instance *>(aInstance);
return instance.GetChannelManager().SetAutoChannelSelectionInterval(aInterval);
}
uint32_t otChannelManagerGetAutoChannelSelectionInterval(otInstance *aInstance)
{
Instance &instance = *static_cast<Instance *>(aInstance);
return instance.GetChannelManager().GetAutoChannelSelectionInterval();
}
uint32_t otChannelManagerGetSupportedChannels(otInstance *aInstance)
{
Instance &instance = *static_cast<Instance *>(aInstance);
@@ -83,4 +118,18 @@ void otChannelManagerSetSupportedChannels(otInstance *aInstance, uint32_t aChann
return instance.GetChannelManager().SetSupportedChannels(aChannelMask);
}
uint32_t otChannelManagerGetFavoredChannels(otInstance *aInstance)
{
Instance &instance = *static_cast<Instance *>(aInstance);
return instance.GetChannelManager().GetFavoredChannels();
}
void otChannelManagerSetFavoredChannels(otInstance *aInstance, uint32_t aChannelMask)
{
Instance &instance = *static_cast<Instance *>(aInstance);
return instance.GetChannelManager().SetFavoredChannels(aChannelMask);
}
#endif // OPENTHREAD_ENABLE_CHANNEL_MANAGER && OPENTHREAD_FTD
+4
View File
@@ -320,6 +320,10 @@ const char *Notifier::FlagToString(uint32_t aFlag) const
retval = "SecPolicy";
break;
case OT_CHANGED_CHANNEL_MANAGER_NEW_CHANNEL:
retval = "CMNewChan";
break;
default:
break;
}
+12 -3
View File
@@ -1261,9 +1261,18 @@ public:
enum
{
kMaxDelayTimer = 259200, ///< maximum delay timer value for a Pending Dataset in seconds
kDelayTimerMinimal = 30000, ///< Minimum Delay Timer value for a Pending Operational Dataset (ms)
kDelayTimerDefault = 300000, ///< Default Delay Timer value for a Pending Operational Dataset (ms)
kMaxDelayTimer = 259200, ///< maximum delay timer value for a Pending Dataset in seconds
/**
* Minimum Delay Timer value for a Pending Operational Dataset (ms)
*
*/
kDelayTimerMinimal = OPENTHREAD_CONFIG_MESHCOP_PENDING_DATASET_MINIMUM_DELAY,
/**
* Default Delay Timer value for a Pending Operational Dataset (ms)
*/
kDelayTimerDefault = OPENTHREAD_CONFIG_MESHCOP_PENDING_DATASET_DEFAULT_DELAY,
};
private:
+101 -1
View File
@@ -527,6 +527,31 @@
#define OPENTHREAD_CONFIG_STORE_FRAME_COUNTER_AHEAD 1000
#endif
/**
* @def OPENTHREAD_CONFIG_MESHCOP_PENDING_DATASET_MINIMUM_DELAY
*
* Minimum Delay Timer value for a Pending Operational Dataset (in ms).
*
* Thread specification defines this value as 30,000. Changing from the specified value should be done for testing only.
*
*/
#ifndef OPENTHREAD_CONFIG_MESHCOP_PENDING_DATASET_MINIMUM_DELAY
#define OPENTHREAD_CONFIG_MESHCOP_PENDING_DATASET_MINIMUM_DELAY 30000
#endif
/**
* @def OPENTHREAD_CONFIG_MESHCOP_PENDING_DATASET_DEFAULT_DELAY
*
* Default Delay Timer value for a Pending Operational Dataset (in ms).
*
* Thread specification defines this value as 300,000. Changing from the specified value should be done for testing
* only.
*
*/
#ifndef OPENTHREAD_CONFIG_MESHCOP_PENDING_DATASET_DEFAULT_DELAY
#define OPENTHREAD_CONFIG_MESHCOP_PENDING_DATASET_DEFAULT_DELAY 300000
#endif
/**
* @def OPENTHREAD_CONFIG_LOG_OUTPUT
*
@@ -1098,7 +1123,7 @@
/**
* @def OPENTHREAD_CONFIG_CHANNEL_MANAGER_MINIMUM_DELAY
*
* The minimum delay in seconds used by Channel Manager module for performing a channel change.
* The minimum delay (in seconds) used by Channel Manager module for performing a channel change.
*
* The minimum delay should preferably be longer than maximum data poll interval used by all sleepy-end-devices within
* the Thread network.
@@ -1110,6 +1135,81 @@
#define OPENTHREAD_CONFIG_CHANNEL_MANAGER_MINIMUM_DELAY 120
#endif
/**
* @def OPENTHREAD_CONFIG_CHANNEL_MANAGER_MINIMUM_MONITOR_SAMPLE_COUNT
*
* The minimum number of RSSI samples per channel by Channel Monitoring feature before the collected data can be used
* by the Channel Manager module to (auto) select a better channel.
*
* Applicable only if Channel Manager and Channel Monitoring features are both enabled (i.e.,
* `OPENTHREAD_ENABLE_CHANNEL_MANAGER` and `OPENTHREAD_ENABLE_CHANNEL_MONITOR` are set).
*
*/
#ifndef OPENTHREAD_CONFIG_CHANNEL_MANAGER_MINIMUM_MONITOR_SAMPLE_COUNT
#define OPENTHREAD_CONFIG_CHANNEL_MANAGER_MINIMUM_MONITOR_SAMPLE_COUNT 500
#endif
/**
* @def OPENTHREAD_CONFIG_CHANNEL_MANAGER_THRESHOLD_TO_SKIP_FAVORED
*
* This threshold specifies the minimum occupancy rate difference between two channels for the Channel Manager to
* prefer an unfavored channel over the best favored one. This is used when (auto) selecting a channel based on the
* collected channel quality data by "channel monitor" feature.
*
* The difference is based on the `ChannelMonitor::GetChannelOccupancy()` definition which provides the average
* percentage of RSSI samples (within a time window) indicating that channel was busy (i.e., RSSI value higher than
* a threshold). Value 0 maps to 0% and 0xffff maps to 100%.
*
* Applicable only if Channel Manager feature is enabled (i.e., `OPENTHREAD_ENABLE_CHANNEL_MANAGER` is set).
*
*/
#ifndef OPENTHREAD_CONFIG_CHANNEL_MANAGER_THRESHOLD_TO_SKIP_FAVORED
#define OPENTHREAD_CONFIG_CHANNEL_MANAGER_THRESHOLD_TO_SKIP_FAVORED (0xffff * 7 / 100)
#endif
/**
* @def OPENTHREAD_CONFIG_CHANNEL_MANAGER_THRESHOLD_TO_CHANGE_CHANNEL
*
* This threshold specifies the minimum occupancy rate difference required between the current channel and a newly
* selected channel for Channel Manager to allow channel change to the new channel.
*
* The difference is based on the `ChannelMonitor::GetChannelOccupancy()` definition which provides the average
* percentage of RSSI samples (within a time window) indicating that channel was busy (i.e., RSSI value higher than
* a threshold). Value 0 maps to 0% rate and 0xffff maps to 100%.
*
* Applicable only if Channel Manager feature is enabled (i.e., `OPENTHREAD_ENABLE_CHANNEL_MANAGER` is set).
*
*/
#ifndef OPENTHREAD_CONFIG_CHANNEL_MANAGER_THRESHOLD_TO_CHANGE_CHANNEL
#define OPENTHREAD_CONFIG_CHANNEL_MANAGER_THRESHOLD_TO_CHANGE_CHANNEL (0xffff * 10 / 100)
#endif
/**
* @def OPENTHREAD_CONFIG_CHANNEL_MANAGER_DEFAULT_AUTO_SELECT_INTERVAL
*
* The default time interval (in seconds) used by Channel Manager for auto-channel-selection functionality.
*
* Applicable only if Channel Manager feature is enabled (i.e., `OPENTHREAD_ENABLE_CHANNEL_MANAGER` is set).
*
*/
#ifndef OPENTHREAD_CONFIG_CHANNEL_MANAGER_DEFAULT_AUTO_SELECT_INTERVAL
#define OPENTHREAD_CONFIG_CHANNEL_MANAGER_DEFAULT_AUTO_SELECT_INTERVAL (3 * 60 * 60)
#endif
/**
* @def OPENTHREAD_CONFIG_CHANNEL_MANAGER_CCA_FAILURE_THRESHOLD
*
* Minimum CCA failure rate threshold on current channel before Channel Manager starts channel selection attempt.
*
* Value 0 maps to 0% and 0xffff maps to 100%.
*
* Applicable only if Channel Manager feature is enabled (i.e., `OPENTHREAD_ENABLE_CHANNEL_MANAGER` is set).
*
*/
#ifndef OPENTHREAD_CONFIG_CHANNEL_MANAGER_CCA_FAILURE_THRESHOLD
#define OPENTHREAD_CONFIG_CHANNEL_MANAGER_CCA_FAILURE_THRESHOLD (0xffff * 14 / 100)
#endif
/**
* @def OPENTHREAD_CONFIG_CHILD_SUPERVISION_INTERVAL
*
+259 -31
View File
@@ -49,30 +49,28 @@ namespace Utils {
ChannelManager::ChannelManager(Instance &aInstance)
: InstanceLocator(aInstance)
, mSupportedChannels(kDefaultSupprotedChannelMask)
, mSupportedChannelMask(0)
, mFavoredChannelMask(0)
, mActiveTimestamp(0)
, mNotifierCallback(&ChannelManager::HandleStateChanged, this)
, mDelay(kMinimumDelay)
, mChannel(0)
, mState(kStateIdle)
, mTimer(aInstance, &ChannelManager::HandleTimer, this)
, mAutoSelectInterval(kDefaultAutoSelectInterval)
, mAutoSelectEnabled(false)
{
aInstance.GetNotifier().RegisterCallback(mNotifierCallback);
}
otError ChannelManager::RequestChannelChange(uint8_t aChannel)
void ChannelManager::RequestChannelChange(uint8_t aChannel)
{
otError error = OT_ERROR_NONE;
otLogInfoUtil(GetInstance(), "ChannelManager: Request to change to channel %d with delay %d sec", aChannel, mDelay);
VerifyOrExit(aChannel != GetInstance().Get<Mac::Mac>().GetChannel());
if ((mSupportedChannels & (1U << aChannel)) == 0)
if (aChannel == GetInstance().Get<Mac::Mac>().GetChannel())
{
otLogInfoUtil(GetInstance(), "ChannelManager: Request rejected! Channel %d not in supported mask 0x%x",
aChannel, mSupportedChannels);
ExitNow(error = OT_ERROR_INVALID_ARGS);
otLogInfoUtil(GetInstance(), "ChannelManager: Already operating on the requested channel %d", aChannel);
ExitNow();
}
mState = kStateChangeRequested;
@@ -81,8 +79,10 @@ otError ChannelManager::RequestChannelChange(uint8_t aChannel)
mTimer.Start(1 + Random::GetUint32InRange(0, kRequestStartJitterInterval));
GetNotifier().SetFlags(OT_CHANGED_CHANNEL_MANAGER_NEW_CHANNEL);
exit:
return error;
return;
}
otError ChannelManager::SetDelay(uint16_t aDelay)
@@ -109,14 +109,6 @@ void ChannelManager::PreparePendingDataset(void)
VerifyOrExit(mChannel != GetInstance().Get<Mac::Mac>().GetChannel());
if ((mSupportedChannels & (1U << mChannel)) == 0)
{
otLogInfoUtil(GetInstance(), "ChannelManager: Request rejected! Channel %d not in supported mask 0x%x",
mChannel, mSupportedChannels);
mState = kStateIdle;
ExitNow();
}
if (netif.GetPendingDataset().Get(dataset) == OT_ERROR_NONE)
{
if (dataset.mIsPendingTimestampSet)
@@ -156,9 +148,11 @@ void ChannelManager::PreparePendingDataset(void)
}
else
{
mState = kStateIdle;
otLogInfoUtil(GetInstance(), "ChannelManager: Request to change to channel %d failed. Device is disabled",
mChannel);
mState = kStateIdle;
StartAutoSelectTimer();
}
ExitNow();
@@ -205,17 +199,14 @@ void ChannelManager::PreparePendingDataset(void)
mActiveTimestamp = dataset.mActiveTimestamp + 1 + Random::GetUint32InRange(0, kMaxTimestampIncrease);
}
dataset.mActiveTimestamp = mActiveTimestamp;
dataset.mIsActiveTimestampSet = true;
dataset.mChannel = mChannel;
dataset.mIsChannelSet = true;
dataset.mActiveTimestamp = mActiveTimestamp;
dataset.mIsActiveTimestampSet = true;
dataset.mChannel = mChannel;
dataset.mIsChannelSet = true;
dataset.mPendingTimestamp = pendingTimestamp;
dataset.mIsPendingTimestampSet = true;
dataset.mDelay = delayInMs;
dataset.mIsDelaySet = true;
dataset.mDelay = delayInMs;
dataset.mIsDelaySet = true;
error = netif.GetPendingDataset().SendSetRequest(dataset, NULL, 0);
@@ -248,6 +239,9 @@ void ChannelManager::HandleTimer(void)
switch (mState)
{
case kStateIdle:
otLogInfoUtil(GetInstance(), "ChannelManager: Auto-triggered channel select");
IgnoreReturnValue(RequestChannelSelect(false));
StartAutoSelectTimer();
break;
case kStateSentMgmtPendingDataset:
@@ -273,7 +267,7 @@ void ChannelManager::HandleStateChanged(uint32_t aFlags)
VerifyOrExit(mChannel == GetInstance().Get<Mac::Mac>().GetChannel());
mState = kStateIdle;
mTimer.Stop();
StartAutoSelectTimer();
otLogInfoUtil(GetInstance(), "ChannelManager: Channel successfully changed to %d", mChannel);
@@ -281,6 +275,240 @@ exit:
return;
}
#if OPENTHREAD_ENABLE_CHANNEL_MONITOR
/**
* This function randomly chooses a channel from a given channel mask.
*
* @param[in] aMask A channel mask.
*
* @returns A randomly chosen channel from the given mask, or `ChannelMask::kChannelIteratorFirst` if the mask is empty.
*
*/
static uint8_t ChooseRandomChannel(const Mac::ChannelMask &aMask)
{
uint8_t channel = Mac::ChannelMask::kChannelIteratorFirst;
uint8_t numChannels = 0;
uint8_t randomIndex;
VerifyOrExit(!aMask.IsEmpty());
while (aMask.GetNextChannel(channel) == OT_ERROR_NONE)
{
numChannels++;
}
randomIndex = Random::GetUint8InRange(0, numChannels);
channel = Mac::ChannelMask::kChannelIteratorFirst;
SuccessOrExit(aMask.GetNextChannel(channel));
while (randomIndex-- != 0)
{
SuccessOrExit(aMask.GetNextChannel(channel));
}
exit:
return channel;
}
otError ChannelManager::FindBetterChannel(uint8_t &aNewChannel, uint16_t &aOccupancy)
{
otError error = OT_ERROR_NONE;
ChannelMonitor & monitor = GetInstance().GetChannelMonitor();
Mac::ChannelMask favoredAndSupported;
Mac::ChannelMask favoredBest;
Mac::ChannelMask supportedBest;
uint16_t favoredOccupancy;
uint16_t supportedOccupancy;
char string[Mac::ChannelMask::kInfoStringSize];
if (monitor.GetSampleCount() <= kMinChannelMonitorSampleCount)
{
otLogInfoUtil(GetInstance(), "ChannelManager: Too few samples (%d <= %d) to select channel",
monitor.GetSampleCount(), kMinChannelMonitorSampleCount);
ExitNow(error = OT_ERROR_INVALID_STATE);
}
favoredAndSupported = mFavoredChannelMask;
favoredAndSupported.Intersect(mSupportedChannelMask);
favoredBest = monitor.FindBestChannels(favoredAndSupported, favoredOccupancy);
supportedBest = monitor.FindBestChannels(mSupportedChannelMask, supportedOccupancy);
otLogInfoUtil(GetInstance(), "ChannelManager: Best favored %s, occupancy 0x%04x",
favoredBest.ToString(string, sizeof(string)), favoredOccupancy);
otLogInfoUtil(GetInstance(), "ChannelManager: Best overall %s, occupancy 0x%04x",
supportedBest.ToString(string, sizeof(string)), supportedOccupancy);
// Prefer favored channels unless there is no favored channel,
// or the occupancy rate of the best favored channel is worse
// than the best overall by at least `kThresholdToSkipFavored`.
if (favoredBest.IsEmpty() || ((favoredOccupancy >= kThresholdToSkipFavored) &&
(supportedOccupancy < favoredOccupancy - kThresholdToSkipFavored)))
{
if (!favoredBest.IsEmpty())
{
otLogInfoUtil(GetInstance(),
"ChannelManager: Preferring an unfavored channel due to high occupancy rate diff");
}
favoredBest = supportedBest;
favoredOccupancy = supportedOccupancy;
}
VerifyOrExit(!favoredBest.IsEmpty(), error = OT_ERROR_NOT_FOUND);
aNewChannel = ChooseRandomChannel(favoredBest);
aOccupancy = favoredOccupancy;
OT_UNUSED_VARIABLE(string);
exit:
return error;
}
#else // OPENTHREAD_ENABLE_CHANNEL_MONITOR
otError ChannelManager::FindBetterChannel(uint8_t &, uint16_t &)
{
otLogInfoUtil(GetInstance(), "ChannelManager: ChannelMonitor feature is disabled - cannot select channel");
return OT_ERROR_DISABLED_FEATURE;
}
#endif // OPENTHREAD_ENABLE_CHANNEL_MONITOR
bool ChannelManager::ShouldAttamptChannelChange(void)
{
uint16_t ccaFailureRate = GetInstance().Get<Mac::Mac>().GetCcaFailureRate();
bool shouldAttempt = (ccaFailureRate >= kCcaFailureRateThreshold);
otLogInfoUtil(GetInstance(), "ChannelManager: CCA-err-rate: 0x%04x %s 0x%04x, selecting channel: %s",
ccaFailureRate, shouldAttempt ? ">=" : "<", kCcaFailureRateThreshold, shouldAttempt ? "yes" : "no");
return shouldAttempt;
}
otError ChannelManager::RequestChannelSelect(bool aSkipQualityCheck)
{
otError error = OT_ERROR_NONE;
uint8_t curChannel, newChannel;
uint16_t curOccupancy, newOccupancy;
otLogInfoUtil(GetInstance(), "ChannelManager: Request to select channel (skip quality check: %s)",
aSkipQualityCheck ? "yes" : "no");
VerifyOrExit(GetInstance().Get<Mle::Mle>().GetRole() != OT_DEVICE_ROLE_DISABLED, error = OT_ERROR_INVALID_STATE);
VerifyOrExit(aSkipQualityCheck || ShouldAttamptChannelChange());
SuccessOrExit(error = FindBetterChannel(newChannel, newOccupancy));
curChannel = GetInstance().Get<Mac::Mac>().GetChannel();
curOccupancy = GetInstance().GetChannelMonitor().GetChannelOccupancy(curChannel);
if (newChannel == curChannel)
{
otLogInfoUtil(GetInstance(), "ChannelManager: Already on best possible channel %d", curChannel);
ExitNow();
}
otLogInfoUtil(GetInstance(), "ChannelManager: Cur channel %d, occupancy 0x%04x - Best channel %d, occupancy 0x%04x",
curChannel, curOccupancy, newChannel, newOccupancy);
// Switch only if new channel's occupancy rate is better than current
// channel's occupancy rate by threshold `kThresholdToChangeChannel`.
if ((newOccupancy >= curOccupancy) ||
(static_cast<uint16_t>(curOccupancy - newOccupancy) < kThresholdToChangeChannel))
{
otLogInfoUtil(GetInstance(), "ChannelManager: Occupancy rate diff too small to change channel");
ExitNow();
}
RequestChannelChange(newChannel);
exit:
if (error != OT_ERROR_NONE)
{
otLogInfoUtil(GetInstance(), "ChannelManager: Request to select better channel failed, error: %s",
otThreadErrorToString(error));
}
return error;
}
void ChannelManager::StartAutoSelectTimer(void)
{
VerifyOrExit(mState == kStateIdle);
if (mAutoSelectEnabled)
{
mTimer.Start(TimerMilli::SecToMsec(mAutoSelectInterval));
}
else
{
mTimer.Stop();
}
exit:
return;
}
void ChannelManager::SetAutoChannelSelectionEnabled(bool aEnabled)
{
if (aEnabled != mAutoSelectEnabled)
{
mAutoSelectEnabled = aEnabled;
IgnoreReturnValue(RequestChannelSelect(false));
StartAutoSelectTimer();
}
}
otError ChannelManager::SetAutoChannelSelectionInterval(uint32_t aInterval)
{
otError error = OT_ERROR_NONE;
uint32_t prevInterval = mAutoSelectInterval;
VerifyOrExit((aInterval != 0) && (aInterval < TimerMilli::MsecToSec(Timer::kMaxDt)), error = OT_ERROR_INVALID_ARGS);
mAutoSelectInterval = aInterval;
if (mAutoSelectEnabled && (mState == kStateIdle) && mTimer.IsRunning() && (prevInterval != aInterval))
{
mTimer.StartAt(mTimer.GetFireTime() - prevInterval, aInterval);
}
exit:
return error;
}
void ChannelManager::SetSupportedChannels(uint32_t aChannelMask)
{
char string[Mac::ChannelMask::kInfoStringSize];
mSupportedChannelMask.SetMask(aChannelMask & OT_RADIO_SUPPORTED_CHANNELS);
otLogInfoUtil(GetInstance(), "ChannelManager: Supported channels: %s",
mSupportedChannelMask.ToString(string, sizeof(string)));
OT_UNUSED_VARIABLE(string);
}
void ChannelManager::SetFavoredChannels(uint32_t aChannelMask)
{
char string[Mac::ChannelMask::kInfoStringSize];
mFavoredChannelMask.SetMask(aChannelMask & OT_RADIO_SUPPORTED_CHANNELS);
otLogInfoUtil(GetInstance(), "ChannelManager: Favored channels: %s",
mFavoredChannelMask.ToString(string, sizeof(string)));
OT_UNUSED_VARIABLE(string);
}
} // namespace Utils
} // namespace ot
+128 -16
View File
@@ -42,6 +42,7 @@
#include "common/locator.hpp"
#include "common/notifier.hpp"
#include "common/timer.hpp"
#include "mac/mac.hpp"
namespace ot {
namespace Utils {
@@ -69,7 +70,7 @@ public:
enum
{
/**
* Minimum delay in seconds used for network channel change.
* Minimum delay (in seconds) used for network channel change.
*
*/
kMinimumDelay = OPENTHREAD_CONFIG_CHANNEL_MANAGER_MINIMUM_DELAY,
@@ -91,13 +92,12 @@ public:
*
* A subsequent call to this method will cancel an ongoing previously requested channel change.
*
* If the requested channel changes, it will trigger a `Notifier` event `OT_CHANGED_CHANNEL_MANAGER_NEW_CHANNEL`.
*
* @param[in] aChannel The new channel for the Thread network.
*
* @retval OT_ERROR_NONE Channel change request successfully processed.
* @retval OT_ERROR_INVALID_ARGS The new channel is not a supported channel.
*
*/
otError RequestChannelChange(uint8_t aChannel);
void RequestChannelChange(uint8_t aChannel);
/**
* This method gets the channel from the last successful call to `RequestChannelChange()`.
@@ -129,13 +129,83 @@ public:
*/
otError SetDelay(uint16_t aDelay);
/**
* This method requests that `ChannelManager` checks and selects a new channel and starts a channel change.
*
* Unlike the `RequestChannelChange()` where the channel must be given as a parameter, this method asks the
* `ChannelManager` to select a channel by itself (based on the collected channel quality info).
*
* Once called, the `ChannelManager` will perform the following 3 steps:
*
* 1) `ChannelManager` decides if the channel change would be helpful. This check can be skipped if
* `aSkipQualityCheck` is set to true (forcing a channel selection to happen and skipping the quality check).
* This step uses the collected link quality metrics on the device (such as CCA failure rate, frame and message
* error rates per neighbor, etc.) to determine if the current channel quality is at the level that justifies
* a channel change.
*
* 2) If the first step passes, then `ChannelManager` selects a potentially better channel. It uses the collected
* channel occupancy data by `ChannelMonitor` module. The supported and favored channels are used at this step.
* (@sa SetSupportedChannels, @sa SetFavoredChannels).
*
* 3) If the newly selected channel is different from the current channel, `ChannelManager` requests/starts the
* channel change process (internally invoking a `RequestChannelChange()`).
*
*
* @param[in] aSkipQualityCheck Indicates whether the quality check (step 1) should be skipped.
*
* @retval OT_ERROR_NONE Channel selection finished successfully.
* @retval OT_ERROR_NOT_FOUND Supported channels is empty, therefore could not select a channel.
* @retval OT_ERROR_INVALID_STATE Thread is not enabled or not enough data to select new channel.
* @retval OT_ERROR_DISABLED_FEATURE `ChannelMonintor` feature is disabled by build-time configuration options.
*
*/
otError RequestChannelSelect(bool aSkipQualityCheck);
/**
* This method enables/disables the auto-channel-selection functionality.
*
* When enabled, `ChannelManager` will periodically invoke a `RequestChannelSelect(false)`. The period interval
* can be set by `SetAutoChannelSelectionInterval()`.
*
* @param[in] aEnabled Indicates whether to enable or disable this functionality.
*
*/
void SetAutoChannelSelectionEnabled(bool aEnabled);
/**
* This method indicates whether the auto-channel-selection functionality is enabled or not.
*
* @returns TRUE if enabled, FALSE if disabled.
*
*/
bool GetAutoChannelSelectionEnabled(void) const { return mAutoSelectEnabled; }
/**
* This method sets the period interval (in seconds) used by auto-channel-selection functionality.
*
* @param[in] aInterval The interval (in seconds).
*
* @retval OT_ERROR_NONE The interval was set successfully.
* @retval OT_ERROR_INVALID_ARGS The @p aInterval is not valid (zero).
*
*/
otError SetAutoChannelSelectionInterval(uint32_t aInterval);
/**
* This method gets the period interval (in seconds) used by auto-channel-selection functionality.
*
* @returns The interval (in seconds).
*
*/
uint32_t GetAutoChannelSelectionInterval(void) { return mAutoSelectInterval; }
/**
* This method gets the supported channel mask.
*
* @returns The supported channels mask.
*
*/
uint32_t GetSupportedChannels(void) const { return mSupportedChannels; }
uint32_t GetSupportedChannels(void) const { return mSupportedChannelMask.GetMask(); }
/**
* This method sets the supported channel mask.
@@ -143,19 +213,55 @@ public:
* @param[in] aChannelMask A channel mask.
*
*/
void SetSupportedChannels(uint32_t aChannelMask)
{
mSupportedChannels = (aChannelMask & OT_RADIO_SUPPORTED_CHANNELS);
}
void SetSupportedChannels(uint32_t aChannelMask);
/**
* This method gets the favored channel mask.
*
* @returns The favored channels mask.
*
*/
uint32_t GetFavoredChannels(void) const { return mFavoredChannelMask.GetMask(); }
/**
* This method sets the favored channel mask.
*
* @param[in] aChannelMask A channel mask.
*
*/
void SetFavoredChannels(uint32_t aChannelMask);
private:
enum
{
kDefaultSupprotedChannelMask = OT_RADIO_SUPPORTED_CHANNELS,
kMaxTimestampIncrease = 128,
kPendingDatasetTxRetryInterval = 20000, // in ms
kChangeCheckWaitInterval = 30000, // in ms
kRequestStartJitterInterval = 10000, // in ms
// Maximum increase of Pending/Active Dataset Timestamp per channel change request.
kMaxTimestampIncrease = 128,
// Retry interval to resend Pending Dataset in case of tx failure (in ms).
kPendingDatasetTxRetryInterval = 20000,
// Wait time after sending Pending Dataset to check whether the channel was changed (in ms).
kChangeCheckWaitInterval = 30000,
// Maximum jitter/wait time to start a requested channel change (in ms).
kRequestStartJitterInterval = 10000,
// The minimum number of RSSI samples required before using the collected data (by `ChannelMonitor`) to select
// a channel.
kMinChannelMonitorSampleCount = OPENTHREAD_CONFIG_CHANNEL_MANAGER_MINIMUM_MONITOR_SAMPLE_COUNT,
// Minimum channel occupancy difference to prefer an unfavored channel over a favored one.
kThresholdToSkipFavored = OPENTHREAD_CONFIG_CHANNEL_MANAGER_THRESHOLD_TO_SKIP_FAVORED,
// Minimum channel occupancy difference between current channel and the selected channel to trigger the channel
// change process to start.
kThresholdToChangeChannel = OPENTHREAD_CONFIG_CHANNEL_MANAGER_THRESHOLD_TO_CHANGE_CHANNEL,
// Default auto-channel-selection period (in seconds).
kDefaultAutoSelectInterval = OPENTHREAD_CONFIG_CHANNEL_MANAGER_DEFAULT_AUTO_SELECT_INTERVAL,
// Minimum CCA failure rate on current channel to start the channel selection process.
kCcaFailureRateThreshold = OPENTHREAD_CONFIG_CHANNEL_MANAGER_CCA_FAILURE_THRESHOLD,
};
enum State
@@ -170,14 +276,20 @@ private:
static void HandleStateChanged(Notifier::Callback &aCallback, uint32_t aChangedFlags);
void HandleStateChanged(uint32_t aChangedFlags);
void PreparePendingDataset(void);
otError FindBetterChannel(uint8_t &aNewChannel, uint16_t &aOccupancy);
bool ShouldAttamptChannelChange(void);
void StartAutoSelectTimer(void);
uint32_t mSupportedChannels;
Mac::ChannelMask mSupportedChannelMask;
Mac::ChannelMask mFavoredChannelMask;
uint64_t mActiveTimestamp;
Notifier::Callback mNotifierCallback;
uint16_t mDelay;
uint8_t mChannel;
State mState;
TimerMilli mTimer;
uint32_t mAutoSelectInterval;
bool mAutoSelectEnabled;
};
#else // OPENTHREAD_FTD
+31
View File
@@ -213,6 +213,37 @@ void ChannelMonitor::LogResults(void)
mChannelOccupancy[15] >> 8);
}
Mac::ChannelMask ChannelMonitor::FindBestChannels(const Mac::ChannelMask &aMask, uint16_t &aOccupancy)
{
uint8_t channel;
Mac::ChannelMask bestMask;
uint16_t minOccupancy = 0xffff;
bestMask.Clear();
channel = Mac::ChannelMask::kChannelIteratorFirst;
while (aMask.GetNextChannel(channel) == OT_ERROR_NONE)
{
uint16_t occupancy = GetChannelOccupancy(channel);
if (bestMask.IsEmpty() || (occupancy <= minOccupancy))
{
if (occupancy < minOccupancy)
{
bestMask.Clear();
}
bestMask.AddChannel(channel);
minOccupancy = occupancy;
}
}
aOccupancy = minOccupancy;
return bestMask;
}
} // namespace Utils
} // namespace ot
+16
View File
@@ -41,6 +41,7 @@
#include "common/locator.hpp"
#include "common/timer.hpp"
#include "mac/mac.hpp"
namespace ot {
namespace Utils {
@@ -168,6 +169,21 @@ public:
*/
uint16_t GetChannelOccupancy(uint8_t aChannel) const;
/**
* This method finds the best channel(s) (with least occupancy rate) in a given channel mask.
*
* The channels are compared based on their occupancy rate from `GetChannelOccupancy()` and lower occupancy rate
* is considered better.
*
* @param[in] aMask A channel mask (the search is limited to channels in @p aMask).
* @param[out] aOccupancy A reference to `uint16` to return the occupancy rate associated with best channel(s).
*
* @returns A channel mask containing the best channels. A mask is returned in case there are more than one
* channel with the same occupancy rate value.
*
*/
Mac::ChannelMask FindBestChannels(const Mac::ChannelMask &aMask, uint16_t &aOccupancy);
private:
enum
{
+1 -1
View File
@@ -872,7 +872,7 @@ otError NcpBase::SetPropertyHandler_CHANNEL_MANAGER_NEW_CHANNEL(void)
SuccessOrExit(error = mDecoder.ReadUint8(channel));
error = otChannelManagerRequestChannelChange(mInstance, channel);
otChannelManagerRequestChannelChange(mInstance, channel);
exit:
return error;