blob: ac2c6bae9addb99c4bd04633cdd4a8068fc7da51 [file] [log] [blame]
/*
* Copyright (c) 2018, The OpenThread Authors.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. Neither the name of the copyright holder nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
/**
* @file
* This file implements Channel Manager.
*
*/
#include "channel_manager.hpp"
#if OPENTHREAD_CONFIG_CHANNEL_MANAGER_ENABLE && OPENTHREAD_FTD
#include "common/code_utils.hpp"
#include "common/instance.hpp"
#include "common/locator_getters.hpp"
#include "common/logging.hpp"
#include "common/random.hpp"
#include "common/string.hpp"
#include "meshcop/dataset_updater.hpp"
#include "radio/radio.hpp"
namespace ot {
namespace Utils {
ChannelManager::ChannelManager(Instance &aInstance)
: InstanceLocator(aInstance)
, mSupportedChannelMask(0)
, mFavoredChannelMask(0)
, mDelay(kMinimumDelay)
, mChannel(0)
, mState(kStateIdle)
, mTimer(aInstance, ChannelManager::HandleTimer)
, mAutoSelectInterval(kDefaultAutoSelectInterval)
, mAutoSelectEnabled(false)
, mCcaFailureRateThreshold(kCcaFailureRateThreshold)
{
}
void ChannelManager::RequestChannelChange(uint8_t aChannel)
{
otLogInfoUtil("ChannelManager: Request to change to channel %d with delay %d sec", aChannel, mDelay);
if (aChannel == Get<Mac::Mac>().GetPanChannel())
{
otLogInfoUtil("ChannelManager: Already operating on the requested channel %d", aChannel);
ExitNow();
}
if (mState == kStateChangeInProgress)
{
VerifyOrExit(mChannel != aChannel);
}
mState = kStateChangeRequested;
mChannel = aChannel;
mTimer.Start(1 + Random::NonCrypto::GetUint32InRange(0, kRequestStartJitterInterval));
Get<Notifier>().Signal(kEventChannelManagerNewChannelChanged);
exit:
return;
}
Error ChannelManager::SetDelay(uint16_t aDelay)
{
Error error = kErrorNone;
VerifyOrExit(aDelay >= kMinimumDelay, error = kErrorInvalidArgs);
mDelay = aDelay;
exit:
return error;
}
void ChannelManager::StartDatasetUpdate(void)
{
MeshCoP::Dataset::Info dataset;
dataset.Clear();
dataset.SetChannel(mChannel);
dataset.SetDelay(Time::SecToMsec(mDelay));
switch (Get<MeshCoP::DatasetUpdater>().RequestUpdate(dataset, HandleDatasetUpdateDone, this))
{
case kErrorNone:
mState = kStateChangeInProgress;
// Wait for the `HandleDatasetUpdateDone()` callback.
break;
case kErrorBusy:
case kErrorNoBufs:
mTimer.Start(kPendingDatasetTxRetryInterval);
break;
case kErrorInvalidState:
otLogInfoUtil("ChannelManager: Request to change to channel %d failed. Device is disabled", mChannel);
OT_FALL_THROUGH;
default:
mState = kStateIdle;
StartAutoSelectTimer();
break;
}
}
void ChannelManager::HandleDatasetUpdateDone(Error aError, void *aContext)
{
static_cast<ChannelManager *>(aContext)->HandleDatasetUpdateDone(aError);
}
void ChannelManager::HandleDatasetUpdateDone(Error aError)
{
if (aError == kErrorNone)
{
otLogInfoUtil("ChannelManager: Channel changed to %d", mChannel);
}
else
{
otLogInfoUtil("ChannelManager: Canceling channel change to %d%s", mChannel,
(aError == kErrorAlready) ? " since current ActiveDataset is more recent" : "");
}
mState = kStateIdle;
StartAutoSelectTimer();
}
void ChannelManager::HandleTimer(Timer &aTimer)
{
aTimer.Get<ChannelManager>().HandleTimer();
}
void ChannelManager::HandleTimer(void)
{
switch (mState)
{
case kStateIdle:
otLogInfoUtil("ChannelManager: Auto-triggered channel select");
IgnoreError(RequestChannelSelect(false));
StartAutoSelectTimer();
break;
case kStateChangeRequested:
StartDatasetUpdate();
break;
case kStateChangeInProgress:
break;
}
}
#if OPENTHREAD_CONFIG_CHANNEL_MONITOR_ENABLE
Error ChannelManager::FindBetterChannel(uint8_t &aNewChannel, uint16_t &aOccupancy)
{
Error error = kErrorNone;
Mac::ChannelMask favoredAndSupported;
Mac::ChannelMask favoredBest;
Mac::ChannelMask supportedBest;
uint16_t favoredOccupancy;
uint16_t supportedOccupancy;
if (Get<ChannelMonitor>().GetSampleCount() <= kMinChannelMonitorSampleCount)
{
otLogInfoUtil("ChannelManager: Too few samples (%d <= %d) to select channel",
Get<ChannelMonitor>().GetSampleCount(), kMinChannelMonitorSampleCount);
ExitNow(error = kErrorInvalidState);
}
favoredAndSupported = mFavoredChannelMask;
favoredAndSupported.Intersect(mSupportedChannelMask);
favoredBest = Get<ChannelMonitor>().FindBestChannels(favoredAndSupported, favoredOccupancy);
supportedBest = Get<ChannelMonitor>().FindBestChannels(mSupportedChannelMask, supportedOccupancy);
otLogInfoUtil("ChannelManager: Best favored %s, occupancy 0x%04x", favoredBest.ToString().AsCString(),
favoredOccupancy);
otLogInfoUtil("ChannelManager: Best overall %s, occupancy 0x%04x", supportedBest.ToString().AsCString(),
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("ChannelManager: Preferring an unfavored channel due to high occupancy rate diff");
}
favoredBest = supportedBest;
favoredOccupancy = supportedOccupancy;
}
VerifyOrExit(!favoredBest.IsEmpty(), error = kErrorNotFound);
aNewChannel = favoredBest.ChooseRandomChannel();
aOccupancy = favoredOccupancy;
exit:
return error;
}
bool ChannelManager::ShouldAttemptChannelChange(void)
{
uint16_t ccaFailureRate = Get<Mac::Mac>().GetCcaFailureRate();
bool shouldAttempt = (ccaFailureRate >= mCcaFailureRateThreshold);
otLogInfoUtil("ChannelManager: CCA-err-rate: 0x%04x %s 0x%04x, selecting channel: %s", ccaFailureRate,
shouldAttempt ? ">=" : "<", mCcaFailureRateThreshold, ToYesNo(shouldAttempt));
return shouldAttempt;
}
Error ChannelManager::RequestChannelSelect(bool aSkipQualityCheck)
{
Error error = kErrorNone;
uint8_t curChannel, newChannel;
uint16_t curOccupancy, newOccupancy;
otLogInfoUtil("ChannelManager: Request to select channel (skip quality check: %s)", ToYesNo(aSkipQualityCheck));
VerifyOrExit(!Get<Mle::Mle>().IsDisabled(), error = kErrorInvalidState);
VerifyOrExit(aSkipQualityCheck || ShouldAttemptChannelChange());
SuccessOrExit(error = FindBetterChannel(newChannel, newOccupancy));
curChannel = Get<Mac::Mac>().GetPanChannel();
curOccupancy = Get<ChannelMonitor>().GetChannelOccupancy(curChannel);
if (newChannel == curChannel)
{
otLogInfoUtil("ChannelManager: Already on best possible channel %d", curChannel);
ExitNow();
}
otLogInfoUtil("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("ChannelManager: Occupancy rate diff too small to change channel");
ExitNow();
}
RequestChannelChange(newChannel);
exit:
if (error != kErrorNone)
{
otLogInfoUtil("ChannelManager: Request to select better channel failed, error: %s", ErrorToString(error));
}
return error;
}
#endif // OPENTHREAD_CONFIG_CHANNEL_MONITOR_ENABLE
void ChannelManager::StartAutoSelectTimer(void)
{
VerifyOrExit(mState == kStateIdle);
if (mAutoSelectEnabled)
{
mTimer.Start(Time::SecToMsec(mAutoSelectInterval));
}
else
{
mTimer.Stop();
}
exit:
return;
}
void ChannelManager::SetAutoChannelSelectionEnabled(bool aEnabled)
{
if (aEnabled != mAutoSelectEnabled)
{
mAutoSelectEnabled = aEnabled;
IgnoreError(RequestChannelSelect(false));
StartAutoSelectTimer();
}
}
Error ChannelManager::SetAutoChannelSelectionInterval(uint32_t aInterval)
{
Error error = kErrorNone;
uint32_t prevInterval = mAutoSelectInterval;
VerifyOrExit((aInterval != 0) && (aInterval <= Time::MsecToSec(Timer::kMaxDelay)), error = kErrorInvalidArgs);
mAutoSelectInterval = aInterval;
if (mAutoSelectEnabled && (mState == kStateIdle) && mTimer.IsRunning() && (prevInterval != aInterval))
{
mTimer.StartAt(mTimer.GetFireTime() - Time::SecToMsec(prevInterval), Time::SecToMsec(aInterval));
}
exit:
return error;
}
void ChannelManager::SetSupportedChannels(uint32_t aChannelMask)
{
mSupportedChannelMask.SetMask(aChannelMask & Get<Mac::Mac>().GetSupportedChannelMask().GetMask());
otLogInfoUtil("ChannelManager: Supported channels: %s", mSupportedChannelMask.ToString().AsCString());
}
void ChannelManager::SetFavoredChannels(uint32_t aChannelMask)
{
mFavoredChannelMask.SetMask(aChannelMask & Get<Mac::Mac>().GetSupportedChannelMask().GetMask());
otLogInfoUtil("ChannelManager: Favored channels: %s", mFavoredChannelMask.ToString().AsCString());
}
void ChannelManager::SetCcaFailureRateThreshold(uint16_t aThreshold)
{
mCcaFailureRateThreshold = aThreshold;
otLogInfoUtil("ChannelManager: CCA threshold: 0x%04x", mCcaFailureRateThreshold);
}
} // namespace Utils
} // namespace ot
#endif // #if OPENTHREAD_CONFIG_CHANNEL_MANAGER_ENABLE