diff --git a/doc/spinel-protocol-src/spinel-feature-channel-monitor.md b/doc/spinel-protocol-src/spinel-feature-channel-monitor.md index 8125c307f..a9d7c1cdf 100644 --- a/doc/spinel-protocol-src/spinel-feature-channel-monitor.md +++ b/doc/spinel-protocol-src/spinel-feature-channel-monitor.md @@ -49,7 +49,7 @@ Total number of RSSI samples (per channel) taken by the channel monitoring module since its start (since Thread network interface was enabled). -### PROP 4618: SPINEL_PROP_CHANNEL_MONITOR_CHANNEL_QUALITY (#prop-channel-monitor-channel-quality) +### PROP 4618: SPINEL_PROP_CHANNEL_MONITOR_CHANNEL_OCCUPANCY (#prop-channel-monitor-channel-occupancy) * Type: Read-Only * Packing-Encoding: `A(t(cU))` @@ -57,9 +57,9 @@ was enabled). Data per item is: * `C`: Channel - * `U`: Channel quality indicator + * `U`: Channel occupancy indicator -The channel quality value represents the average rate/percentage of +The channel occupancy value represents the average rate/percentage of RSSI samples that were above RSSI threshold ("bad" RSSI samples) within (approximately) latest sample window RSSI samples. diff --git a/examples/platforms/posix/radio.c b/examples/platforms/posix/radio.c index b57af7e36..82fa79707 100644 --- a/examples/platforms/posix/radio.c +++ b/examples/platforms/posix/radio.c @@ -33,6 +33,7 @@ #include #include #include +#include #include "utils/code_utils.h" @@ -82,6 +83,10 @@ enum { POSIX_RECEIVE_SENSITIVITY = -100, // dBm POSIX_MAX_SRC_MATCH_ENTRIES = OPENTHREAD_CONFIG_MAX_CHILDREN, + + POSIX_HIGH_RSSI_SAMPLE = -30, // dBm + POSIX_LOW_RSSI_SAMPLE = -98, // dBm + POSIX_HIGH_RSSI_PROB_INC_PER_CHANNEL = 5, }; OT_TOOL_PACKED_BEGIN @@ -504,8 +509,27 @@ otRadioFrame *otPlatRadioGetTransmitBuffer(otInstance *aInstance) int8_t otPlatRadioGetRssi(otInstance *aInstance) { + int8_t rssi = POSIX_LOW_RSSI_SAMPLE; + uint8_t channel = sReceiveFrame.mChannel; + uint32_t probabilityThreshold; + (void)aInstance; - return 0; + + otEXPECT((OT_RADIO_CHANNEL_MIN <= channel) && channel <= (OT_RADIO_CHANNEL_MAX)); + + // To emulate a simple interference model, we return either a high or + // a low RSSI value with a fixed probability per each channel. The + // probability is increased per channel by a constant. + + probabilityThreshold = (channel - OT_RADIO_CHANNEL_MIN) * POSIX_HIGH_RSSI_PROB_INC_PER_CHANNEL; + + if ((otPlatRandomGet() & 0xffff) < (probabilityThreshold * 0xffff / 100)) + { + rssi = POSIX_HIGH_RSSI_SAMPLE; + } + +exit: + return rssi; } otRadioCaps otPlatRadioGetCaps(otInstance *aInstance) diff --git a/include/openthread/channel_manager.h b/include/openthread/channel_manager.h index 247fd4e84..f91d868d4 100644 --- a/include/openthread/channel_manager.h +++ b/include/openthread/channel_manager.h @@ -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); + /** * @} * diff --git a/include/openthread/channel_monitor.h b/include/openthread/channel_monitor.h index 042e8b45a..669b8cf9d 100644 --- a/include/openthread/channel_monitor.h +++ b/include/openthread/channel_monitor.h @@ -56,7 +56,8 @@ extern "C" { * When channel monitoring is active, a zero-duration Energy Scan is performed, collecting a single RSSI sample on * every channel per sample interval. The RSSI samples are compared with a pre-specified RSSI threshold. As an * indicator of channel quality, the channel monitoring module maintains and provides the average rate/percentage of - * RSSI samples that are above the threshold within (approximately) a specified sample window. + * RSSI samples that are above the threshold within (approximately) a specified sample window (referred to as channel + * occupancy). * * @{ * @@ -134,25 +135,25 @@ uint32_t otChannelMonitorGetSampleWindow(otInstance *aInstance); uint32_t otChannelMonitorGetSampleCount(otInstance *aInstance); /** - * Gets the current channel quality value for a given channel. + * Gets the current channel occupancy for a given channel. * - * The channel quality value represents the average rate/percentage of RSSI samples that were above RSSI threshold + * The channel occupancy value represents the average rate/percentage of RSSI samples that were above RSSI threshold * ("bad" RSSI samples). * * For the first "sample window" samples, the average is maintained as the actual percentage (i.e., ratio of number * of "bad" samples by total number of samples). After "window" samples, the averager uses an exponentially - * weighted moving average. Practically, this means the quality is representative of up to `3 * window` last samples - * with highest weight given to latest `kSampleWindow` samples. + * weighted moving average. Practically, this means the average is representative of up to `3 * window` last samples + * with highest weight given to the latest `kSampleWindow` samples. * * Max value of `0xffff` indicates all RSSI samples were above RSSI threshold (i.e. 100% of samples were "bad"). * * @param[in] aInstance A pointer to an OpenThread instance. - * @param[in] aChannel The channel for which to get the link quality. + * @param[in] aChannel The channel for which to get the link occupancy. * - * @returns The current channel quality value for the given channel. + * @returns The current channel occupancy for the given channel. * */ -uint16_t otChannelMonitorGetChannelQuality(otInstance *aInstance, uint8_t aChannel); +uint16_t otChannelMonitorGetChannelOccupancy(otInstance *aInstance, uint8_t aChannel); /** * @} diff --git a/include/openthread/instance.h b/include/openthread/instance.h index 6c2eaf853..4dd0f397a 100644 --- a/include/openthread/instance.h +++ b/include/openthread/instance.h @@ -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 }; /** diff --git a/src/core/api/channel_manager_api.cpp b/src/core/api/channel_manager_api.cpp index e8afed6db..317ac25c3 100644 --- a/src/core/api/channel_manager_api.cpp +++ b/src/core/api/channel_manager_api.cpp @@ -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(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(aInstance); + + return instance.GetChannelManager().RequestChannelSelect(aSkipQualityCheck); +} + +void otChannelManagerSetAutoChannelSelectionEnabled(otInstance *aInstance, bool aEnabled) +{ + Instance &instance = *static_cast(aInstance); + + instance.GetChannelManager().SetAutoChannelSelectionEnabled(aEnabled); +} + +bool otChannelManagerGetAutoChannelSelectionEnabled(otInstance *aInstance) +{ + Instance &instance = *static_cast(aInstance); + + return instance.GetChannelManager().GetAutoChannelSelectionEnabled(); +} + +otError otChannelManagerSetAutoChannelSelectionInterval(otInstance *aInstance, uint32_t aInterval) +{ + Instance &instance = *static_cast(aInstance); + + return instance.GetChannelManager().SetAutoChannelSelectionInterval(aInterval); +} + +uint32_t otChannelManagerGetAutoChannelSelectionInterval(otInstance *aInstance) +{ + Instance &instance = *static_cast(aInstance); + + return instance.GetChannelManager().GetAutoChannelSelectionInterval(); +} + uint32_t otChannelManagerGetSupportedChannels(otInstance *aInstance) { Instance &instance = *static_cast(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(aInstance); + + return instance.GetChannelManager().GetFavoredChannels(); +} + +void otChannelManagerSetFavoredChannels(otInstance *aInstance, uint32_t aChannelMask) +{ + Instance &instance = *static_cast(aInstance); + + return instance.GetChannelManager().SetFavoredChannels(aChannelMask); +} + #endif // OPENTHREAD_ENABLE_CHANNEL_MANAGER && OPENTHREAD_FTD diff --git a/src/core/api/channel_monitor_api.cpp b/src/core/api/channel_monitor_api.cpp index 48bbb2546..48b0b5ab7 100644 --- a/src/core/api/channel_monitor_api.cpp +++ b/src/core/api/channel_monitor_api.cpp @@ -82,11 +82,11 @@ uint32_t otChannelMonitorGetSampleCount(otInstance *aInstance) return instance.GetChannelMonitor().GetSampleCount(); } -uint16_t otChannelMonitorGetChannelQuality(otInstance *aInstance, uint8_t aChannel) +uint16_t otChannelMonitorGetChannelOccupancy(otInstance *aInstance, uint8_t aChannel) { Instance &instance = *static_cast(aInstance); - return instance.GetChannelMonitor().GetChannelQuality(aChannel); + return instance.GetChannelMonitor().GetChannelOccupancy(aChannel); } #endif // OPENTHREAD_ENABLE_CHANNEL_MONITOR diff --git a/src/core/common/notifier.cpp b/src/core/common/notifier.cpp index 4e115d6b7..29e414c95 100644 --- a/src/core/common/notifier.cpp +++ b/src/core/common/notifier.cpp @@ -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; } diff --git a/src/core/meshcop/meshcop_tlvs.hpp b/src/core/meshcop/meshcop_tlvs.hpp index 6889116be..d41ca829a 100644 --- a/src/core/meshcop/meshcop_tlvs.hpp +++ b/src/core/meshcop/meshcop_tlvs.hpp @@ -1261,9 +1261,19 @@ 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: diff --git a/src/core/openthread-core-default-config.h b/src/core/openthread-core-default-config.h index 5b9283d65..207484f4d 100644 --- a/src/core/openthread-core-default-config.h +++ b/src/core/openthread-core-default-config.h @@ -527,6 +527,32 @@ #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 ms. 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 ms. 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 +1124,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 +1136,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 * diff --git a/src/core/utils/channel_manager.cpp b/src/core/utils/channel_manager.cpp index 00d4fb855..f244d0b37 100644 --- a/src/core/utils/channel_manager.cpp +++ b/src/core/utils/channel_manager.cpp @@ -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().GetChannel()); - - if ((mSupportedChannels & (1U << aChannel)) == 0) + if (aChannel == GetInstance().Get().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().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().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().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().GetRole() != OT_DEVICE_ROLE_DISABLED, error = OT_ERROR_INVALID_STATE); + + VerifyOrExit(aSkipQualityCheck || ShouldAttamptChannelChange()); + + SuccessOrExit(error = FindBetterChannel(newChannel, newOccupancy)); + + curChannel = GetInstance().Get().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(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 diff --git a/src/core/utils/channel_manager.hpp b/src/core/utils/channel_manager.hpp index 0a355cfd4..bfe939057 100644 --- a/src/core/utils/channel_manager.hpp +++ b/src/core/utils/channel_manager.hpp @@ -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 diff --git a/src/core/utils/channel_monitor.cpp b/src/core/utils/channel_monitor.cpp index 42efe564b..9efae0c8e 100644 --- a/src/core/utils/channel_monitor.cpp +++ b/src/core/utils/channel_monitor.cpp @@ -56,7 +56,7 @@ ChannelMonitor::ChannelMonitor(Instance &aInstance) , mSampleCount(0) , mTimer(aInstance, &ChannelMonitor::HandleTimer, this) { - memset(mChannelQuality, 0, sizeof(mChannelQuality)); + memset(mChannelOccupancy, 0, sizeof(mChannelOccupancy)); } otError ChannelMonitor::Start(void) @@ -88,20 +88,20 @@ void ChannelMonitor::Clear(void) { mChannelMaskIndex = 0; mSampleCount = 0; - memset(mChannelQuality, 0, sizeof(mChannelQuality)); + memset(mChannelOccupancy, 0, sizeof(mChannelOccupancy)); otLogDebgUtil(GetInstance(), "ChannelMonitor: Clearing data"); } -uint16_t ChannelMonitor::GetChannelQuality(uint8_t aChannel) const +uint16_t ChannelMonitor::GetChannelOccupancy(uint8_t aChannel) const { - uint16_t quality = 0; + uint16_t occupancy = 0; VerifyOrExit((OT_RADIO_CHANNEL_MIN <= aChannel) && (aChannel <= OT_RADIO_CHANNEL_MAX)); - quality = mChannelQuality[aChannel - OT_RADIO_CHANNEL_MIN]; + occupancy = mChannelOccupancy[aChannel - OT_RADIO_CHANNEL_MIN]; exit: - return quality; + return occupancy; } void ChannelMonitor::RestartTimer(void) @@ -162,7 +162,7 @@ void ChannelMonitor::HandleEnergyScanResult(otEnergyScanResult *aResult) else { uint8_t channelIndex = (aResult->mChannel - OT_RADIO_CHANNEL_MIN); - uint32_t newAverage = mChannelQuality[channelIndex]; + uint32_t newAverage = mChannelOccupancy[channelIndex]; uint32_t newValue = 0; uint32_t weight; @@ -172,19 +172,19 @@ void ChannelMonitor::HandleEnergyScanResult(otEnergyScanResult *aResult) if (aResult->mMaxRssi != OT_RADIO_RSSI_INVALID) { - newValue = (aResult->mMaxRssi >= kRssiThreshold) ? kMaxQualityIndicator : 0; + newValue = (aResult->mMaxRssi >= kRssiThreshold) ? kMaxOccupancy : 0; } - // `mChannelQuality` stores the average rate/percentage of RSS samples - // that are higher than a given RSS threshold ("bad" RSS samples). For - // the first `kSampleWindow` samples, the average is maintained as the - // actual percentage (i.e., ratio of number of "bad" samples by total - // number of samples). After `kSampleWindow` samples, the averager - // uses an exponentially weighted moving average logic with weight - // coefficient `1/kSampleWindow` for new values. Practically, this - // means the quality is representative of up to `3 * kSampleWindow` - // last samples with highest weight given to latest `kSampleWindow` - // samples. + // `mChannelOccupancy` stores the average rate/percentage of RSS + // samples that are higher than a given RSS threshold ("bad" RSS + // samples). For the first `kSampleWindow` samples, the average is + // maintained as the actual percentage (i.e., ratio of number of + // "bad" samples by total number of samples). After `kSampleWindow` + // samples, the averager uses an exponentially weighted moving + // average logic with weight coefficient `1/kSampleWindow` for new + // values. Practically, this means the average is representative + // of up to `3 * kSampleWindow` samples with highest weight given + // to the latest `kSampleWindow` samples. if (mSampleCount >= kSampleWindow) { @@ -197,7 +197,7 @@ void ChannelMonitor::HandleEnergyScanResult(otEnergyScanResult *aResult) newAverage = (newAverage * weight + newValue) / (weight + 1); - mChannelQuality[channelIndex] = static_cast(newAverage); + mChannelOccupancy[channelIndex] = static_cast(newAverage); } } @@ -206,11 +206,42 @@ void ChannelMonitor::LogResults(void) otLogInfoUtil( GetInstance(), "ChannelMonitor: %u [%02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x]", - mSampleCount, mChannelQuality[0] >> 8, mChannelQuality[1] >> 8, mChannelQuality[2] >> 8, - mChannelQuality[3] >> 8, mChannelQuality[4] >> 8, mChannelQuality[5] >> 8, mChannelQuality[6] >> 8, - mChannelQuality[7] >> 8, mChannelQuality[8] >> 8, mChannelQuality[9] >> 8, mChannelQuality[10] >> 8, - mChannelQuality[11] >> 8, mChannelQuality[12] >> 8, mChannelQuality[13] >> 8, mChannelQuality[14] >> 8, - mChannelQuality[15] >> 8); + mSampleCount, mChannelOccupancy[0] >> 8, mChannelOccupancy[1] >> 8, mChannelOccupancy[2] >> 8, + mChannelOccupancy[3] >> 8, mChannelOccupancy[4] >> 8, mChannelOccupancy[5] >> 8, mChannelOccupancy[6] >> 8, + mChannelOccupancy[7] >> 8, mChannelOccupancy[8] >> 8, mChannelOccupancy[9] >> 8, mChannelOccupancy[10] >> 8, + mChannelOccupancy[11] >> 8, mChannelOccupancy[12] >> 8, mChannelOccupancy[13] >> 8, mChannelOccupancy[14] >> 8, + 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 diff --git a/src/core/utils/channel_monitor.hpp b/src/core/utils/channel_monitor.hpp index d54f7f0f3..cdabaded2 100644 --- a/src/core/utils/channel_monitor.hpp +++ b/src/core/utils/channel_monitor.hpp @@ -41,6 +41,7 @@ #include "common/locator.hpp" #include "common/timer.hpp" +#include "mac/mac.hpp" namespace ot { namespace Utils { @@ -66,7 +67,7 @@ namespace Utils { * channel collecting a single RSSI sample per channel. The RSSI samples are compared with a pre-specified RSSI * threshold `kRssiThreshold`. As an indicator of channel quality, the `ChannelMonitor` maintains and provides the * average rate/percentage of RSSI samples that are above the threshold within (approximately) a specified sample - * window. + * window (referred to as "channel occupancy"). * */ class ChannelMonitor : public InstanceLocator @@ -148,34 +149,49 @@ public: uint32_t GetSampleCount(void) const { return mSampleCount; } /** - * This method returns the current channel quality value for a given channel. + * This method returns the current channel occupancy for a given channel. * - * The channel quality value represents the average rate/percentage of RSSI samples that were above RSSI threshold + * The channel occupancy represents the average rate/percentage of RSSI samples that were above RSSI threshold * `kRssiThreshold` ("bad" RSSI samples). * * For the first `kSampleWindow` samples, the average is maintained as the actual percentage (i.e., ratio of number * of "bad" samples by total number of samples). After `kSampleWindow` samples, the averager uses an exponentially * weighted moving average logic with weight coefficient `1/kSampleWindow` for new values. Practically, this means - * the quality is representative of up to `3 * kSampleWindow` last samples with highest weight given to latest - * `kSampleWindow` samples. + * the occupancy is representative of up to `3 * kSampleWindow` last samples with highest weight given to the + * latest `kSampleWindow` samples. * * Max value of `0xffff` indicates all RSSI samples were above RSSI threshold (i.e. 100% of samples were "bad"). * - * @param[in] aChannel The channel for which to get the link quality. + * @param[in] aChannel The channel for which to get the link occupancy. * - * @returns the current channel quality value for the given channel. + * @returns the current channel occupancy for the given channel. * */ - uint16_t GetChannelQuality(uint8_t aChannel) const; + 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 { - kNumChannels = (OT_RADIO_CHANNEL_MAX - OT_RADIO_CHANNEL_MIN + 1), - kNumChannelMasks = 4, - kTimerInterval = (kSampleInterval / kNumChannelMasks), - kMaxJitterInterval = 4096, - kMaxQualityIndicator = 0xffff, + kNumChannels = (OT_RADIO_CHANNEL_MAX - OT_RADIO_CHANNEL_MIN + 1), + kNumChannelMasks = 4, + kTimerInterval = (kSampleInterval / kNumChannelMasks), + kMaxJitterInterval = 4096, + kMaxOccupancy = 0xffff, }; void RestartTimer(void); @@ -189,7 +205,7 @@ private: uint8_t mChannelMaskIndex : 2; uint32_t mSampleCount : 30; - uint16_t mChannelQuality[kNumChannels]; + uint16_t mChannelOccupancy[kNumChannels]; TimerMilli mTimer; }; diff --git a/src/ncp/changed_props_set.cpp b/src/ncp/changed_props_set.cpp index 2b59c0d05..54a8da408 100644 --- a/src/ncp/changed_props_set.cpp +++ b/src/ncp/changed_props_set.cpp @@ -79,6 +79,10 @@ const ChangedPropsSet::Entry ChangedPropsSet::mSupportedProps[] = { SPINEL_PROP_NET_XPANID, SPINEL_STATUS_OK, true }, // 25 { SPINEL_PROP_NET_MASTER_KEY, SPINEL_STATUS_OK, true }, // 26 { SPINEL_PROP_NET_PSKC, SPINEL_STATUS_OK, true }, // 27 +#if OPENTHREAD_ENABLE_CHANNEL_MANAGER + { SPINEL_PROP_CHANNEL_MANAGER_NEW_CHANNEL, SPINEL_STATUS_OK, true }, // 28 +#endif + }; uint8_t ChangedPropsSet::GetNumEntries(void) const diff --git a/src/ncp/ncp_base.cpp b/src/ncp/ncp_base.cpp index fa1f41977..e1b65339b 100644 --- a/src/ncp/ncp_base.cpp +++ b/src/ncp/ncp_base.cpp @@ -161,7 +161,7 @@ const NcpBase::PropertyHandlerEntry NcpBase::mGetPropertyHandlerTable[] = NCP_GET_PROP_HANDLER_ENTRY(CHANNEL_MONITOR_RSSI_THRESHOLD), NCP_GET_PROP_HANDLER_ENTRY(CHANNEL_MONITOR_SAMPLE_WINDOW), NCP_GET_PROP_HANDLER_ENTRY(CHANNEL_MONITOR_SAMPLE_COUNT), - NCP_GET_PROP_HANDLER_ENTRY(CHANNEL_MONITOR_CHANNEL_QUALITY), + NCP_GET_PROP_HANDLER_ENTRY(CHANNEL_MONITOR_CHANNEL_OCCUPANCY), #endif #if OPENTHREAD_ENABLE_LEGACY NCP_GET_PROP_HANDLER_ENTRY(NEST_LEGACY_ULA_PREFIX), @@ -245,6 +245,10 @@ const NcpBase::PropertyHandlerEntry NcpBase::mGetPropertyHandlerTable[] = NCP_GET_PROP_HANDLER_ENTRY(CHANNEL_MANAGER_NEW_CHANNEL), NCP_GET_PROP_HANDLER_ENTRY(CHANNEL_MANAGER_DELAY), NCP_GET_PROP_HANDLER_ENTRY(CHANNEL_MANAGER_SUPPORTED_CHANNELS), + NCP_GET_PROP_HANDLER_ENTRY(CHANNEL_MANAGER_FAVORED_CHANNELS), + NCP_GET_PROP_HANDLER_ENTRY(CHANNEL_MANAGER_CHANNEL_SELECT), + NCP_GET_PROP_HANDLER_ENTRY(CHANNEL_MANAGER_AUTO_SELECT_ENABLED), + NCP_GET_PROP_HANDLER_ENTRY(CHANNEL_MANAGER_AUTO_SELECT_INTERVAL), #endif #endif // OPENTHREAD_FTD @@ -347,6 +351,10 @@ const NcpBase::PropertyHandlerEntry NcpBase::mSetPropertyHandlerTable[] = NCP_SET_PROP_HANDLER_ENTRY(CHANNEL_MANAGER_NEW_CHANNEL), NCP_SET_PROP_HANDLER_ENTRY(CHANNEL_MANAGER_DELAY), NCP_SET_PROP_HANDLER_ENTRY(CHANNEL_MANAGER_SUPPORTED_CHANNELS), + NCP_SET_PROP_HANDLER_ENTRY(CHANNEL_MANAGER_FAVORED_CHANNELS), + NCP_SET_PROP_HANDLER_ENTRY(CHANNEL_MANAGER_CHANNEL_SELECT), + NCP_SET_PROP_HANDLER_ENTRY(CHANNEL_MANAGER_AUTO_SELECT_ENABLED), + NCP_SET_PROP_HANDLER_ENTRY(CHANNEL_MANAGER_AUTO_SELECT_INTERVAL), #endif #endif // #if OPENTHREAD_FTD }; @@ -1253,8 +1261,12 @@ otError NcpBase::HandleCommandPropertySet(uint8_t aHeader, spinel_prop_key_t aKe ExitNow(error = PrepareLastStatusResponse(aHeader, SPINEL_STATUS_PROP_NOT_FOUND)); } + mDisableStreamWrite = false; + error = (this->*handler)(); + mDisableStreamWrite = true; + if (error == OT_ERROR_NONE) { error = PrepareSetResponse(aHeader, aKey); @@ -1304,8 +1316,12 @@ otError NcpBase::HandleCommandPropertyInsertRemove(uint8_t aHeader, spinel_prop_ mDecoder.ReadData(valuePtr, valueLen); mDecoder.ResetToSaved(); + mDisableStreamWrite = false; + error = (this->*handler)(); + mDisableStreamWrite = true; + VerifyOrExit(error == OT_ERROR_NONE, error = PrepareLastStatusResponse(aHeader, ThreadErrorToSpinelStatus(error))); error = WritePropertyValueInsertedRemovedFrame(aHeader, responseCommand, aKey, valuePtr, valueLen); diff --git a/src/ncp/ncp_base.hpp b/src/ncp/ncp_base.hpp index dd6551823..667b2d8b7 100644 --- a/src/ncp/ncp_base.hpp +++ b/src/ncp/ncp_base.hpp @@ -605,7 +605,7 @@ protected: NCP_GET_PROP_HANDLER(CHANNEL_MONITOR_RSSI_THRESHOLD); NCP_GET_PROP_HANDLER(CHANNEL_MONITOR_SAMPLE_WINDOW); NCP_GET_PROP_HANDLER(CHANNEL_MONITOR_SAMPLE_COUNT); - NCP_GET_PROP_HANDLER(CHANNEL_MONITOR_CHANNEL_QUALITY); + NCP_GET_PROP_HANDLER(CHANNEL_MONITOR_CHANNEL_OCCUPANCY); #endif #if OPENTHREAD_ENABLE_LEGACY @@ -671,6 +671,14 @@ protected: NCP_SET_PROP_HANDLER(CHANNEL_MANAGER_DELAY); NCP_GET_PROP_HANDLER(CHANNEL_MANAGER_SUPPORTED_CHANNELS); NCP_SET_PROP_HANDLER(CHANNEL_MANAGER_SUPPORTED_CHANNELS); + NCP_GET_PROP_HANDLER(CHANNEL_MANAGER_FAVORED_CHANNELS); + NCP_SET_PROP_HANDLER(CHANNEL_MANAGER_FAVORED_CHANNELS); + NCP_GET_PROP_HANDLER(CHANNEL_MANAGER_CHANNEL_SELECT); + NCP_SET_PROP_HANDLER(CHANNEL_MANAGER_CHANNEL_SELECT); + NCP_GET_PROP_HANDLER(CHANNEL_MANAGER_AUTO_SELECT_ENABLED); + NCP_SET_PROP_HANDLER(CHANNEL_MANAGER_AUTO_SELECT_ENABLED); + NCP_GET_PROP_HANDLER(CHANNEL_MANAGER_AUTO_SELECT_INTERVAL); + NCP_SET_PROP_HANDLER(CHANNEL_MANAGER_AUTO_SELECT_INTERVAL); #endif #endif // OPENTHREAD_FTD diff --git a/src/ncp/ncp_base_ftd.cpp b/src/ncp/ncp_base_ftd.cpp index be217788b..2de6453ce 100644 --- a/src/ncp/ncp_base_ftd.cpp +++ b/src/ncp/ncp_base_ftd.cpp @@ -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; @@ -913,6 +913,74 @@ exit: return error; } +otError NcpBase::GetPropertyHandler_CHANNEL_MANAGER_FAVORED_CHANNELS(void) +{ + return EncodeChannelMask(otChannelManagerGetFavoredChannels(mInstance)); +} + +otError NcpBase::SetPropertyHandler_CHANNEL_MANAGER_FAVORED_CHANNELS(void) +{ + uint32_t channelMask = 0; + otError error = OT_ERROR_NONE; + + SuccessOrExit(error = DecodeChannelMask(channelMask)); + otChannelManagerSetFavoredChannels(mInstance, channelMask); + +exit: + return error; +} + +otError NcpBase::GetPropertyHandler_CHANNEL_MANAGER_CHANNEL_SELECT(void) +{ + return mEncoder.WriteBool(false); +} + +otError NcpBase::SetPropertyHandler_CHANNEL_MANAGER_CHANNEL_SELECT(void) +{ + bool skipQualityCheck = false; + otError error = OT_ERROR_NONE; + + SuccessOrExit(error = mDecoder.ReadBool(skipQualityCheck)); + error = otChannelManagerRequestChannelSelect(mInstance, skipQualityCheck); + +exit: + return error; +} + +otError NcpBase::GetPropertyHandler_CHANNEL_MANAGER_AUTO_SELECT_ENABLED(void) +{ + return mEncoder.WriteBool(otChannelManagerGetAutoChannelSelectionEnabled(mInstance)); +} + +otError NcpBase::SetPropertyHandler_CHANNEL_MANAGER_AUTO_SELECT_ENABLED(void) +{ + bool enabled = false; + otError error = OT_ERROR_NONE; + + SuccessOrExit(error = mDecoder.ReadBool(enabled)); + otChannelManagerSetAutoChannelSelectionEnabled(mInstance, enabled); + +exit: + return error; +} + +otError NcpBase::GetPropertyHandler_CHANNEL_MANAGER_AUTO_SELECT_INTERVAL(void) +{ + return mEncoder.WriteUint32(otChannelManagerGetAutoChannelSelectionInterval(mInstance)); +} + +otError NcpBase::SetPropertyHandler_CHANNEL_MANAGER_AUTO_SELECT_INTERVAL(void) +{ + uint32_t interval; + otError error = OT_ERROR_NONE; + + SuccessOrExit(error = mDecoder.ReadUint32(interval)); + error = otChannelManagerSetAutoChannelSelectionInterval(mInstance, interval); + +exit: + return error; +} + #endif // OPENTHREAD_ENABLE_CHANNEL_MANAGER } // namespace Ncp diff --git a/src/ncp/ncp_base_mtd.cpp b/src/ncp/ncp_base_mtd.cpp index 65767b1d1..4f519634e 100644 --- a/src/ncp/ncp_base_mtd.cpp +++ b/src/ncp/ncp_base_mtd.cpp @@ -1565,7 +1565,7 @@ otError NcpBase::GetPropertyHandler_CHANNEL_MONITOR_SAMPLE_COUNT(void) return mEncoder.WriteUint32(otChannelMonitorGetSampleCount(mInstance)); } -otError NcpBase::GetPropertyHandler_CHANNEL_MONITOR_CHANNEL_QUALITY(void) +otError NcpBase::GetPropertyHandler_CHANNEL_MONITOR_CHANNEL_OCCUPANCY(void) { otError error = OT_ERROR_NONE; @@ -1574,7 +1574,7 @@ otError NcpBase::GetPropertyHandler_CHANNEL_MONITOR_CHANNEL_QUALITY(void) SuccessOrExit(error = mEncoder.OpenStruct()); SuccessOrExit(error = mEncoder.WriteUint8(channel)); - SuccessOrExit(error = mEncoder.WriteUint16(otChannelMonitorGetChannelQuality(mInstance, channel))); + SuccessOrExit(error = mEncoder.WriteUint16(otChannelMonitorGetChannelOccupancy(mInstance, channel))); SuccessOrExit(error = mEncoder.CloseStruct()); } @@ -2969,6 +2969,7 @@ void NcpBase::ProcessThreadChangedFlags(void) { OT_CHANGED_THREAD_EXT_PANID, SPINEL_PROP_NET_XPANID }, { OT_CHANGED_MASTER_KEY, SPINEL_PROP_NET_MASTER_KEY }, { OT_CHANGED_PSKC, SPINEL_PROP_NET_PSKC }, + { OT_CHANGED_CHANNEL_MANAGER_NEW_CHANNEL, SPINEL_PROP_CHANNEL_MANAGER_NEW_CHANNEL }, }; VerifyOrExit(mThreadChangedFlags != 0); diff --git a/src/ncp/spinel.c b/src/ncp/spinel.c index ede17b583..79f7b335d 100644 --- a/src/ncp/spinel.c +++ b/src/ncp/spinel.c @@ -1209,8 +1209,8 @@ spinel_prop_key_to_cstr(spinel_prop_key_t prop_key) ret = "PROP_CHANNEL_MONITOR_SAMPLE_COUNT"; break; - case SPINEL_PROP_CHANNEL_MONITOR_CHANNEL_QUALITY: - ret = "PROP_CHANNEL_MONITOR_CHANNEL_QUALITY"; + case SPINEL_PROP_CHANNEL_MONITOR_CHANNEL_OCCUPANCY: + ret = "PROP_CHANNEL_MONITOR_CHANNEL_OCCUPANCY"; break; case SPINEL_PROP_MAC_SCAN_STATE: @@ -1605,6 +1605,22 @@ spinel_prop_key_to_cstr(spinel_prop_key_t prop_key) ret = "PROP_CHANNEL_MANAGER_SUPPORTED_CHANNELS"; break; + case SPINEL_PROP_CHANNEL_MANAGER_FAVORED_CHANNELS: + ret = "PROP_CHANNEL_MANAGER_FAVORED_CHANNELS"; + break; + + case SPINEL_PROP_CHANNEL_MANAGER_CHANNEL_SELECT: + ret = "PROP_CHANNEL_MANAGER_CHANNEL_SELECT"; + break; + + case SPINEL_PROP_CHANNEL_MANAGER_AUTO_SELECT_ENABLED: + ret = "PROP_CHANNEL_MANAGER_AUTO_SELECT_ENABLED"; + break; + + case SPINEL_PROP_CHANNEL_MANAGER_AUTO_SELECT_INTERVAL: + ret = "PROP_CHANNEL_MANAGER_AUTO_SELECT_INTERVAL"; + break; + case SPINEL_PROP_UART_BITRATE: ret = "PROP_UART_BITRATE"; break; diff --git a/src/ncp/spinel.h b/src/ncp/spinel.h index e046aabad..7abfd6246 100644 --- a/src/ncp/spinel.h +++ b/src/ncp/spinel.h @@ -768,7 +768,7 @@ typedef enum SPINEL_PROP_CHANNEL_MONITOR_SAMPLE_COUNT = SPINEL_PROP_PHY_EXT__BEGIN + 9, - /// Channel monitoring channel quality + /// Channel monitoring channel occupancy /** Format: `A(t(CU))` (read-only) * * Required capability: SPINEL_CAP_CHANNEL_MONITOR @@ -776,9 +776,9 @@ typedef enum * Data per item is: * * `C`: Channel - * `U`: Channel quality indicator + * `U`: Channel occupancy indicator * - * The channel quality value represents the average rate/percentage of + * The channel occupancy value represents the average rate/percentage of * RSSI samples that were above RSSI threshold ("bad" RSSI samples) within * (approximately) sample window latest RSSI samples. * @@ -786,7 +786,7 @@ typedef enum * threshold (i.e. 100% of samples were "bad"). * */ - SPINEL_PROP_CHANNEL_MONITOR_CHANNEL_QUALITY + SPINEL_PROP_CHANNEL_MONITOR_CHANNEL_OCCUPANCY = SPINEL_PROP_PHY_EXT__BEGIN + 10, SPINEL_PROP_PHY_EXT__END = 0x1300, @@ -1500,6 +1500,70 @@ typedef enum SPINEL_PROP_CHANNEL_MANAGER_SUPPORTED_CHANNELS = SPINEL_PROP_OPENTHREAD__BEGIN + 2, + /// Channel Manager Favored Channels + /** Format 'A(C)' + * + * Required capability: SPINEL_CAP_CHANNEL_MANAGER + * + * This property specifies the list of favored channels (when `ChannelManager` is asked to select channel) + * + */ + SPINEL_PROP_CHANNEL_MANAGER_FAVORED_CHANNELS + = SPINEL_PROP_OPENTHREAD__BEGIN + 3, + + /// Channel Manager Channel Select Trigger + /** Format 'b' + * + * Required capability: SPINEL_CAP_CHANNEL_MANAGER + * + * Writing to this property triggers a request on `ChannelManager` to select a new channel. + * + * Once a Channel Select is triggered, 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 in the input + * boolean to this property is set to `true` (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 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. + * + * 3) If the newly selected channel is different from the current channel, `ChannelManager` requests/starts the + * channel change process. + * + * Reading this property always yields `false`. + * + */ + SPINEL_PROP_CHANNEL_MANAGER_CHANNEL_SELECT + = SPINEL_PROP_OPENTHREAD__BEGIN + 4, + + /// Channel Manager Auto Channel Selection Enabled + /** Format 'b' + * + * Required capability: SPINEL_CAP_CHANNEL_MANAGER + * + * This property indicates if auto-channel-selection functionality is enabled/disabled on `ChannelManager`. + * + * When enabled, `ChannelManager` will periodically checks and attempts to select a new channel. The period interval + * is specified by `SPINEL_PROP_CHANNEL_MANAGER_AUTO_SELECT_INTERVAL`. + * + */ + SPINEL_PROP_CHANNEL_MANAGER_AUTO_SELECT_ENABLED + = SPINEL_PROP_OPENTHREAD__BEGIN + 5, + + /// Channel Manager Auto Channel Selection Interval + /** Format 'L' + * units: seconds + * + * Required capability: SPINEL_CAP_CHANNEL_MANAGER + * + * This property specifies the auto-channel-selection check interval (in seconds). + * + */ + SPINEL_PROP_CHANNEL_MANAGER_AUTO_SELECT_INTERVAL + = SPINEL_PROP_OPENTHREAD__BEGIN + 6, + SPINEL_PROP_OPENTHREAD__END = 0x2000, /// UART Bitrate diff --git a/tests/toranj/openthread-core-toranj-config.h b/tests/toranj/openthread-core-toranj-config.h index d7fe3939a..9e78dc6a1 100644 --- a/tests/toranj/openthread-core-toranj-config.h +++ b/tests/toranj/openthread-core-toranj-config.h @@ -183,6 +183,61 @@ */ #define OPENTHREAD_CONFIG_MLE_SEND_LINK_REQUEST_ON_ADV_TIMEOUT 1 +/** + * @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 should preferably be longer than maximum data poll interval used by all sleepy-end-devices within + * the Thread network. + * + * Applicable only if Channel Manager feature is enabled (i.e., `OPENTHREAD_ENABLE_CHANNEL_MANAGER` is set). + * + */ +#define OPENTHREAD_CONFIG_CHANNEL_MANAGER_MINIMUM_DELAY 2 + +/** + * @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). + * + */ +#define OPENTHREAD_CONFIG_CHANNEL_MANAGER_THRESHOLD_TO_SKIP_FAVORED (0xffff * 7 / 100) + +/** + * @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). + * + */ +#define OPENTHREAD_CONFIG_CHANNEL_MANAGER_THRESHOLD_TO_CHANGE_CHANNEL (0xffff * 10 / 100) + +/** + * @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. + * + * For `toranj` test script the value is decreased so that the tests can be run faster. + * + */ +#define OPENTHREAD_CONFIG_MESHCOP_PENDING_DATASET_MINIMUM_DELAY 1000 /** * @def OPENTHREAD_CONFIG_NCP_ENABLE_MCU_POWER_STATE_CONTROL diff --git a/tests/toranj/start.sh b/tests/toranj/start.sh index 7dd640f3a..443fdcc75 100755 --- a/tests/toranj/start.sh +++ b/tests/toranj/start.sh @@ -95,6 +95,7 @@ cd ../.. --enable-mac-filter \ --enable-service \ --enable-channel-monitor \ + --enable-channel-manager \ --disable-docs \ --disable-test || die @@ -116,4 +117,8 @@ run test-007-traffic-router-sleepy.py run test-008-permit-join.py run test-100-mcu-power-state.py +run test-600-channel-manager-properties.py +run test-601-channel-manager-channel-change.py +run test-602-channel-manager-channel-select.py + exit 0 diff --git a/tests/toranj/test-600-channel-manager-properties.py b/tests/toranj/test-600-channel-manager-properties.py new file mode 100644 index 000000000..83058dc71 --- /dev/null +++ b/tests/toranj/test-600-channel-manager-properties.py @@ -0,0 +1,120 @@ +#!/usr/bin/env python +# +# 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. + +import time +import wpan +from wpan import verify + +#----------------------------------------------------------------------------------------------------------------------- +# Test description: This test verifies wpantund properties related to `ChannelManager` feature + +test_name = __file__[:-3] if __file__.endswith('.py') else __file__ +print '-' * 120 +print 'Starting \'{}\''.format(test_name) + +#----------------------------------------------------------------------------------------------------------------------- +# Creating `wpan.Nodes` instances + +node = wpan.Node() + +#----------------------------------------------------------------------------------------------------------------------- +# Init all nodes + +wpan.Node.init_all_nodes() + +#----------------------------------------------------------------------------------------------------------------------- +# Build network topology + +node.form("channel-manager", channel=11) + +#----------------------------------------------------------------------------------------------------------------------- +# Test implementation + +# Check default property values + +verify(int(node.get(wpan.WPAN_CHANNEL_MANAGER_NEW_CHANNEL), 0) == 0) +verify(node.get(wpan.WPAN_CHANNEL_MANAGER_AUTO_SELECT_ENABLED) == 'false') +verify(int(node.get(wpan.WPAN_CHANNEL_MANAGER_SUPPORTED_CHANNEL_MASK), 0) == 0) +verify(int(node.get(wpan.WPAN_CHANNEL_MANAGER_FAVORED_CHANNEL_MASK), 0) == 0) + +# Set different wpan Channel Manager properties and get and check the output + +node.set(wpan.WPAN_CHANNEL_MANAGER_DELAY, '180') +verify(int(node.get(wpan.WPAN_CHANNEL_MANAGER_DELAY), 0) == 180) + +node.set(wpan.WPAN_CHANNEL_MANAGER_AUTO_SELECT_ENABLED, '1') +verify(node.get(wpan.WPAN_CHANNEL_MANAGER_AUTO_SELECT_ENABLED) == 'true') + +node.set(wpan.WPAN_CHANNEL_MANAGER_AUTO_SELECT_ENABLED, '0') +verify(node.get(wpan.WPAN_CHANNEL_MANAGER_AUTO_SELECT_ENABLED) == 'false') + +node.set(wpan.WPAN_CHANNEL_MANAGER_AUTO_SELECT_INTERVAL, '1000') +verify(int(node.get(wpan.WPAN_CHANNEL_MANAGER_AUTO_SELECT_INTERVAL), 0) == 1000) + +all_channls_mask = int('0x7fff800', 0) +chan_11_mask = int('0x800', 0) +chan_11_to_13_mask = int('0x3800', 0) + +node.set(wpan.WPAN_CHANNEL_MANAGER_SUPPORTED_CHANNEL_MASK, str(all_channls_mask)) +verify(int(node.get(wpan.WPAN_CHANNEL_MANAGER_SUPPORTED_CHANNEL_MASK), 0) == all_channls_mask) + +node.set(wpan.WPAN_CHANNEL_MANAGER_FAVORED_CHANNEL_MASK, str(chan_11_mask)) +verify(int(node.get(wpan.WPAN_CHANNEL_MANAGER_FAVORED_CHANNEL_MASK), 0) == chan_11_mask) + +node.set(wpan.WPAN_CHANNEL_MANAGER_SUPPORTED_CHANNEL_MASK, str(chan_11_to_13_mask)) +verify(int(node.get(wpan.WPAN_CHANNEL_MANAGER_SUPPORTED_CHANNEL_MASK), 0) == chan_11_to_13_mask) + +node.set(wpan.WPAN_CHANNEL_MANAGER_FAVORED_CHANNEL_MASK, str(all_channls_mask)) +verify(int(node.get(wpan.WPAN_CHANNEL_MANAGER_FAVORED_CHANNEL_MASK), 0) == all_channls_mask) + +node.set(wpan.WPAN_CHANNEL_MANAGER_AUTO_SELECT_ENABLED, '1') +verify(node.get(wpan.WPAN_CHANNEL_MANAGER_AUTO_SELECT_ENABLED) == 'true') + +# Check to ensure the property values are retained after an NCP reset + +node.reset() + +start_time = time.time() +wait_time = 20 + +while node.get(wpan.WPAN_STATE) != wpan.STATE_ASSOCIATED: + if time.time() - start_time > wait_time: + print 'Took too long to restore after reset ({}>{} sec)'.format(time.time() - start_time, wait_time) + exit(1) + time.sleep(2) + +verify(node.get(wpan.WPAN_CHANNEL_MANAGER_AUTO_SELECT_ENABLED) == 'true') +verify(int(node.get(wpan.WPAN_CHANNEL_MANAGER_FAVORED_CHANNEL_MASK), 0) == all_channls_mask) +verify(int(node.get(wpan.WPAN_CHANNEL_MANAGER_SUPPORTED_CHANNEL_MASK), 0) == chan_11_to_13_mask) +verify(int(node.get(wpan.WPAN_CHANNEL_MANAGER_AUTO_SELECT_INTERVAL), 0) == 1000) +verify(int(node.get(wpan.WPAN_CHANNEL_MANAGER_DELAY), 0) == 180) + +#----------------------------------------------------------------------------------------------------------------------- +# Test finished + +print '\'{}\' passed.'.format(test_name) diff --git a/tests/toranj/test-601-channel-manager-channel-change.py b/tests/toranj/test-601-channel-manager-channel-change.py new file mode 100644 index 000000000..1c8be0582 --- /dev/null +++ b/tests/toranj/test-601-channel-manager-channel-change.py @@ -0,0 +1,146 @@ +#!/usr/bin/env python +# +# 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. + +import time +import wpan +from wpan import verify + +#----------------------------------------------------------------------------------------------------------------------- +# Test description: verifies `ChannelManager` channel change process + +test_name = __file__[:-3] if __file__.endswith('.py') else __file__ +print '-' * 120 +print 'Starting \'{}\''.format(test_name) + + +def verify_channel(nodes, new_channel, wait_time=20): + """ + This function checks the channel on a given list of `nodes` and verifies that all nodes + switch to a given `new_channel` (as int) within certain `wait_time` (int and in seconds) + """ + start_time = time.time() + + while not all([ (new_channel == int(node.get(wpan.WPAN_CHANNEL), 0)) for node in nodes ]): + if time.time() - start_time > wait_time: + print 'Took too long to switch to channel {} ({}>{} sec)'.format(new_channel, time.time() - start_time, + wait_time) + exit(1) + time.sleep(0.1) + + +#----------------------------------------------------------------------------------------------------------------------- +# Creating `wpan.Nodes` instances + +speedup = 4 +wpan.Node.set_time_speedup_factor(speedup) + +r1 = wpan.Node() +r2 = wpan.Node() +r3 = wpan.Node() +sc1 = wpan.Node() +ec1 = wpan.Node() +sc2 = wpan.Node() +sc3 = wpan.Node() + +all_nodes = [r1, r2, r3, sc1, ec1, sc2, sc3] + +#----------------------------------------------------------------------------------------------------------------------- +# Init all nodes + +wpan.Node.init_all_nodes() + +#----------------------------------------------------------------------------------------------------------------------- +# Build network topology + +for node in all_nodes: + node.set(wpan.WPAN_OT_LOG_LEVEL, '0') + +r1.whitelist_node(r2) +r2.whitelist_node(r1) +r1.whitelist_node(r3) +r3.whitelist_node(r1) + +r1.whitelist_node(sc1) +r1.whitelist_node(ec1) +r2.whitelist_node(sc2) +r3.whitelist_node(sc3) + +r1.form('channel-manager', channel=12) +r2.join_node(r1, node_type=wpan.JOIN_TYPE_ROUTER) +r3.join_node(r1, node_type=wpan.JOIN_TYPE_ROUTER) +sc1.join_node(r1, node_type=wpan.JOIN_TYPE_SLEEPY_END_DEVICE) +ec1.join_node(r1, node_type=wpan.JOIN_TYPE_END_DEVICE) +sc2.join_node(r2, node_type=wpan.JOIN_TYPE_SLEEPY_END_DEVICE) +sc3.join_node(r3, node_type=wpan.JOIN_TYPE_SLEEPY_END_DEVICE) + +sc1.set(wpan.WPAN_POLL_INTERVAL, '500') +sc2.set(wpan.WPAN_POLL_INTERVAL, '500') +sc3.set(wpan.WPAN_POLL_INTERVAL, '500') + +#----------------------------------------------------------------------------------------------------------------------- +# Test implementation + +# The channel manager delay is set from "openthread-core-toranj-config.h". Verify that it is 2 seconds. + +verify(int(r1.get(wpan.WPAN_CHANNEL_MANAGER_DELAY), 0) == 2) + +verify_channel(all_nodes, 12) + +# Request a channel change to channel 13 from router r1 + +r1.set(wpan.WPAN_CHANNEL_MANAGER_NEW_CHANNEL, '13') +verify(int(r1.get(wpan.WPAN_CHANNEL_MANAGER_NEW_CHANNEL), 0) == 13) +verify_channel(all_nodes, 13) + +# Request same channel change on multiple routers at the same time + +r1.set(wpan.WPAN_CHANNEL_MANAGER_NEW_CHANNEL, '14') +r2.set(wpan.WPAN_CHANNEL_MANAGER_NEW_CHANNEL, '14') +r3.set(wpan.WPAN_CHANNEL_MANAGER_NEW_CHANNEL, '14') +verify_channel(all_nodes, 14) + +# Request different channel changes from same router (router r1). + +r1.set(wpan.WPAN_CHANNEL_MANAGER_NEW_CHANNEL, '15') +verify_channel(all_nodes, 14) +r1.set(wpan.WPAN_CHANNEL_MANAGER_NEW_CHANNEL, '16') +verify_channel(all_nodes, 16) + +# Request different channels from two routers (r1 and r2) + +r1.set(wpan.WPAN_CHANNEL_MANAGER_DELAY, '20') # increase the time to ensure r1 change is in process +r1.set(wpan.WPAN_CHANNEL_MANAGER_NEW_CHANNEL, '17') +time.sleep(10.5 / speedup) +verify_channel(all_nodes, 16) +r2.set(wpan.WPAN_CHANNEL_MANAGER_NEW_CHANNEL, '18') +verify_channel(all_nodes, 18) + +#----------------------------------------------------------------------------------------------------------------------- +# Test finished + +print '\'{}\' passed.'.format(test_name) diff --git a/tests/toranj/test-602-channel-manager-channel-select.py b/tests/toranj/test-602-channel-manager-channel-select.py new file mode 100644 index 000000000..8b5dddc4d --- /dev/null +++ b/tests/toranj/test-602-channel-manager-channel-select.py @@ -0,0 +1,151 @@ +#!/usr/bin/env python +# +# 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. + +import time +import wpan +from wpan import verify + +#----------------------------------------------------------------------------------------------------------------------- +# Test description: verifies `ChannelManager` channel selection procedure + +test_name = __file__[:-3] if __file__.endswith('.py') else __file__ +print '-' * 120 +print 'Starting \'{}\''.format(test_name) + +def verify_channel(nodes, new_channel, wait_time=20): + """ + This function checks the channel on a given list of `nodes` and verifies that all nodes + switch to a given `new_channel` (as int) within certain `wait_time` (int and in seconds) + """ + start_time = time.time() + + while not all([ (new_channel == int(node.get(wpan.WPAN_CHANNEL), 0)) for node in nodes ]): + if time.time() - start_time > wait_time: + print 'Took too long to switch to channel {} ({}>{} sec)'.format(new_channel, time.time() - start_time, + wait_time) + exit(1) + time.sleep(0.1) + +#----------------------------------------------------------------------------------------------------------------------- +# Creating `wpan.Nodes` instances + +# Run the test with 10,000 time speedup factor +wpan.Node.set_time_speedup_factor(10000) + +node = wpan.Node() + +#----------------------------------------------------------------------------------------------------------------------- +# Init all nodes + +wpan.Node.init_all_nodes() + +node.set(wpan.WPAN_OT_LOG_LEVEL, '0') + +#----------------------------------------------------------------------------------------------------------------------- +# Build network topology + +node.form('channel-manager', channel=24) + +#----------------------------------------------------------------------------------------------------------------------- +# Test implementation + +all_channls_mask = int('0x7fff800', 0) +chan_12_to_15_mask = int('0x000f000', 0) +chan_15_to_17_mask = int('0x0038000', 0) + +# Set supported channel mask to be all channels +node.set(wpan.WPAN_CHANNEL_MANAGER_SUPPORTED_CHANNEL_MASK, str(all_channls_mask)) +verify(int(node.get(wpan.WPAN_CHANNEL_MANAGER_SUPPORTED_CHANNEL_MASK), 0) == all_channls_mask) + +# Sleep for 4 second with speedup factor of 10,000 this is more than 11 hours. +time.sleep(4) + +verify(int(node.get(wpan.WPAN_CHANNEL_MONITOR_SAMPLE_COUNT), 0) > 970) + +# Verify the initial value of `NEW_CHANNEL` (should be zero if there has been no channel change so far). + +verify(int(node.get(wpan.WPAN_CHANNEL_MANAGER_NEW_CHANNEL), 0) == 0) + +# Issue a channel-select with quality check enabled, and verify that no action is taken. + +node.set(wpan.WPAN_CHANNEL_MANAGER_CHANNEL_SELECT, 'false') +verify(int(node.get(wpan.WPAN_CHANNEL_MANAGER_NEW_CHANNEL), 0) == 0) + +# Issue a channel-select with quality check disabled, verify that channel is switched to channel 11. + +node.set(wpan.WPAN_CHANNEL_MANAGER_CHANNEL_SELECT, 'true') +verify(int(node.get(wpan.WPAN_CHANNEL_MANAGER_NEW_CHANNEL), 0) == 11) +verify_channel([node], 11) + +# Set channels 12-15 as favorable and request a channel select, verify that channel is switched to 12. +# +# Even though 11 would be best, quality difference between 11 and 12 is not high enough for selection +# algorithm to pick an unfavored channel. + +node.set(wpan.WPAN_CHANNEL_MANAGER_FAVORED_CHANNEL_MASK, str(chan_12_to_15_mask)) +node.set(wpan.WPAN_CHANNEL_MANAGER_NEW_CHANNEL, '25') # request a channel change to 25 +verify_channel([node], 25) +node.set(wpan.WPAN_CHANNEL_MANAGER_CHANNEL_SELECT, 'true') +verify(int(node.get(wpan.WPAN_CHANNEL_MANAGER_NEW_CHANNEL), 0) == 12) +verify_channel([node], 12) + +# Set channels 15-17 as favorables and request a channel select, verify that channel is switched to 11. +# +# This time the quality difference between 11 and 15 should be high enough for selection +# algorithm to pick the best though unfavored channel (i.e., channel 11). + +node.set(wpan.WPAN_CHANNEL_MANAGER_NEW_CHANNEL, '25') # request a channel change to 25 +verify_channel([node], 25) +node.set(wpan.WPAN_CHANNEL_MANAGER_FAVORED_CHANNEL_MASK, str(chan_15_to_17_mask)) +node.set(wpan.WPAN_CHANNEL_MANAGER_CHANNEL_SELECT, 'true') +verify(int(node.get(wpan.WPAN_CHANNEL_MANAGER_NEW_CHANNEL), 0) == 11) +verify_channel([node], 11) + +# Set channels 12-15 as favorable and request a channel select, verify that channel is not switched. + +node.set(wpan.WPAN_CHANNEL_MANAGER_FAVORED_CHANNEL_MASK, str(chan_12_to_15_mask)) +node.set(wpan.WPAN_CHANNEL_MANAGER_CHANNEL_SELECT, 'true') +verify(int(node.get(wpan.WPAN_CHANNEL_MANAGER_NEW_CHANNEL), 0) == 11) +verify_channel([node], 11) + +# Starting from channel 12 and issuing a channel select (which would pick 11 as best channel). +# However, since quality difference between current channel 12 and new best channel 11 is not large +# enough, no action should be taken. + +node.set(wpan.WPAN_CHANNEL_MANAGER_NEW_CHANNEL, '12') # request a channel change to 12 +verify(int(node.get(wpan.WPAN_CHANNEL_MANAGER_NEW_CHANNEL), 0) == 12) +verify_channel([node], 12) +node.set(wpan.WPAN_CHANNEL_MANAGER_FAVORED_CHANNEL_MASK, str(all_channls_mask)) +node.set(wpan.WPAN_CHANNEL_MANAGER_CHANNEL_SELECT, 'true') +verify(int(node.get(wpan.WPAN_CHANNEL_MANAGER_NEW_CHANNEL), 0) == 12) +verify_channel([node], 12) + +#----------------------------------------------------------------------------------------------------------------------- +# Test finished + +print '\'{}\' passed.'.format(test_name) diff --git a/tests/toranj/wpan.py b/tests/toranj/wpan.py index 7721b7dc2..487f6a883 100644 --- a/tests/toranj/wpan.py +++ b/tests/toranj/wpan.py @@ -125,6 +125,14 @@ WPAN_CHANNEL_MONITOR_SAMPLE_COUNT = "ChannelMonitor:SampleCount" WPAN_CHANNEL_MONITOR_CHANNEL_QUALITY = "ChannelMonitor:ChannelQuality" WPAN_CHANNEL_MONITOR_CHANNEL_QUALITY_ASVALMAP = "ChannelMonitor:ChannelQuality:AsValMap" +WPAN_CHANNEL_MANAGER_NEW_CHANNEL = "ChannelManager:NewChannel" +WPAN_CHANNEL_MANAGER_DELAY = "ChannelManager:Delay" +WPAN_CHANNEL_MANAGER_CHANNEL_SELECT = "ChannelManager:ChannelSelect" +WPAN_CHANNEL_MANAGER_AUTO_SELECT_ENABLED = "ChannelManager:AutoSelect:Enabled" +WPAN_CHANNEL_MANAGER_AUTO_SELECT_INTERVAL = "ChannelManager:AutoSelect:Interval" +WPAN_CHANNEL_MANAGER_SUPPORTED_CHANNEL_MASK = "ChannelManager:SupportedChannelMask" +WPAN_CHANNEL_MANAGER_FAVORED_CHANNEL_MASK = "ChannelManager:FavoredChannelMask" + #---------------------------------------------------------------------------------------------------------------------- # Valid state values