mirror of
https://github.com/espressif/openthread.git
synced 2026-08-07 11:17:46 +00:00
[channel-manager] channel selection (#2634)
This commit is contained in:
@@ -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.
|
||||
|
||||
|
||||
@@ -33,6 +33,7 @@
|
||||
#include <openthread/platform/alarm-milli.h>
|
||||
#include <openthread/platform/diag.h>
|
||||
#include <openthread/platform/radio.h>
|
||||
#include <openthread/platform/random.h>
|
||||
|
||||
#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)
|
||||
|
||||
@@ -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);
|
||||
|
||||
/**
|
||||
* @}
|
||||
*
|
||||
|
||||
@@ -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);
|
||||
|
||||
/**
|
||||
* @}
|
||||
|
||||
@@ -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
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -41,11 +41,11 @@ using namespace ot;
|
||||
|
||||
#if OPENTHREAD_ENABLE_CHANNEL_MANAGER && OPENTHREAD_FTD
|
||||
|
||||
otError otChannelManagerRequestChannelChange(otInstance *aInstance, uint8_t aChannel)
|
||||
void otChannelManagerRequestChannelChange(otInstance *aInstance, uint8_t aChannel)
|
||||
{
|
||||
Instance &instance = *static_cast<Instance *>(aInstance);
|
||||
|
||||
return instance.GetChannelManager().RequestChannelChange(aChannel);
|
||||
instance.GetChannelManager().RequestChannelChange(aChannel);
|
||||
}
|
||||
|
||||
uint8_t otChannelManagerGetRequestedChannel(otInstance *aInstance)
|
||||
@@ -69,6 +69,41 @@ otError otChannelManagerSetDelay(otInstance *aInstance, uint16_t aMinDelay)
|
||||
return instance.GetChannelManager().SetDelay(aMinDelay);
|
||||
}
|
||||
|
||||
otError otChannelManagerRequestChannelSelect(otInstance *aInstance, bool aSkipQualityCheck)
|
||||
{
|
||||
Instance &instance = *static_cast<Instance *>(aInstance);
|
||||
|
||||
return instance.GetChannelManager().RequestChannelSelect(aSkipQualityCheck);
|
||||
}
|
||||
|
||||
void otChannelManagerSetAutoChannelSelectionEnabled(otInstance *aInstance, bool aEnabled)
|
||||
{
|
||||
Instance &instance = *static_cast<Instance *>(aInstance);
|
||||
|
||||
instance.GetChannelManager().SetAutoChannelSelectionEnabled(aEnabled);
|
||||
}
|
||||
|
||||
bool otChannelManagerGetAutoChannelSelectionEnabled(otInstance *aInstance)
|
||||
{
|
||||
Instance &instance = *static_cast<Instance *>(aInstance);
|
||||
|
||||
return instance.GetChannelManager().GetAutoChannelSelectionEnabled();
|
||||
}
|
||||
|
||||
otError otChannelManagerSetAutoChannelSelectionInterval(otInstance *aInstance, uint32_t aInterval)
|
||||
{
|
||||
Instance &instance = *static_cast<Instance *>(aInstance);
|
||||
|
||||
return instance.GetChannelManager().SetAutoChannelSelectionInterval(aInterval);
|
||||
}
|
||||
|
||||
uint32_t otChannelManagerGetAutoChannelSelectionInterval(otInstance *aInstance)
|
||||
{
|
||||
Instance &instance = *static_cast<Instance *>(aInstance);
|
||||
|
||||
return instance.GetChannelManager().GetAutoChannelSelectionInterval();
|
||||
}
|
||||
|
||||
uint32_t otChannelManagerGetSupportedChannels(otInstance *aInstance)
|
||||
{
|
||||
Instance &instance = *static_cast<Instance *>(aInstance);
|
||||
@@ -83,4 +118,18 @@ void otChannelManagerSetSupportedChannels(otInstance *aInstance, uint32_t aChann
|
||||
return instance.GetChannelManager().SetSupportedChannels(aChannelMask);
|
||||
}
|
||||
|
||||
uint32_t otChannelManagerGetFavoredChannels(otInstance *aInstance)
|
||||
{
|
||||
Instance &instance = *static_cast<Instance *>(aInstance);
|
||||
|
||||
return instance.GetChannelManager().GetFavoredChannels();
|
||||
}
|
||||
|
||||
void otChannelManagerSetFavoredChannels(otInstance *aInstance, uint32_t aChannelMask)
|
||||
{
|
||||
Instance &instance = *static_cast<Instance *>(aInstance);
|
||||
|
||||
return instance.GetChannelManager().SetFavoredChannels(aChannelMask);
|
||||
}
|
||||
|
||||
#endif // OPENTHREAD_ENABLE_CHANNEL_MANAGER && OPENTHREAD_FTD
|
||||
|
||||
@@ -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<Instance *>(aInstance);
|
||||
|
||||
return instance.GetChannelMonitor().GetChannelQuality(aChannel);
|
||||
return instance.GetChannelMonitor().GetChannelOccupancy(aChannel);
|
||||
}
|
||||
|
||||
#endif // OPENTHREAD_ENABLE_CHANNEL_MONITOR
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
*
|
||||
|
||||
@@ -49,30 +49,28 @@ namespace Utils {
|
||||
|
||||
ChannelManager::ChannelManager(Instance &aInstance)
|
||||
: InstanceLocator(aInstance)
|
||||
, mSupportedChannels(kDefaultSupprotedChannelMask)
|
||||
, mSupportedChannelMask(0)
|
||||
, mFavoredChannelMask(0)
|
||||
, mActiveTimestamp(0)
|
||||
, mNotifierCallback(&ChannelManager::HandleStateChanged, this)
|
||||
, mDelay(kMinimumDelay)
|
||||
, mChannel(0)
|
||||
, mState(kStateIdle)
|
||||
, mTimer(aInstance, &ChannelManager::HandleTimer, this)
|
||||
, mAutoSelectInterval(kDefaultAutoSelectInterval)
|
||||
, mAutoSelectEnabled(false)
|
||||
{
|
||||
aInstance.GetNotifier().RegisterCallback(mNotifierCallback);
|
||||
}
|
||||
|
||||
otError ChannelManager::RequestChannelChange(uint8_t aChannel)
|
||||
void ChannelManager::RequestChannelChange(uint8_t aChannel)
|
||||
{
|
||||
otError error = OT_ERROR_NONE;
|
||||
|
||||
otLogInfoUtil(GetInstance(), "ChannelManager: Request to change to channel %d with delay %d sec", aChannel, mDelay);
|
||||
|
||||
VerifyOrExit(aChannel != GetInstance().Get<Mac::Mac>().GetChannel());
|
||||
|
||||
if ((mSupportedChannels & (1U << aChannel)) == 0)
|
||||
if (aChannel == GetInstance().Get<Mac::Mac>().GetChannel())
|
||||
{
|
||||
otLogInfoUtil(GetInstance(), "ChannelManager: Request rejected! Channel %d not in supported mask 0x%x",
|
||||
aChannel, mSupportedChannels);
|
||||
|
||||
ExitNow(error = OT_ERROR_INVALID_ARGS);
|
||||
otLogInfoUtil(GetInstance(), "ChannelManager: Already operating on the requested channel %d", aChannel);
|
||||
ExitNow();
|
||||
}
|
||||
|
||||
mState = kStateChangeRequested;
|
||||
@@ -81,8 +79,10 @@ otError ChannelManager::RequestChannelChange(uint8_t aChannel)
|
||||
|
||||
mTimer.Start(1 + Random::GetUint32InRange(0, kRequestStartJitterInterval));
|
||||
|
||||
GetNotifier().SetFlags(OT_CHANGED_CHANNEL_MANAGER_NEW_CHANNEL);
|
||||
|
||||
exit:
|
||||
return error;
|
||||
return;
|
||||
}
|
||||
|
||||
otError ChannelManager::SetDelay(uint16_t aDelay)
|
||||
@@ -109,14 +109,6 @@ void ChannelManager::PreparePendingDataset(void)
|
||||
|
||||
VerifyOrExit(mChannel != GetInstance().Get<Mac::Mac>().GetChannel());
|
||||
|
||||
if ((mSupportedChannels & (1U << mChannel)) == 0)
|
||||
{
|
||||
otLogInfoUtil(GetInstance(), "ChannelManager: Request rejected! Channel %d not in supported mask 0x%x",
|
||||
mChannel, mSupportedChannels);
|
||||
mState = kStateIdle;
|
||||
ExitNow();
|
||||
}
|
||||
|
||||
if (netif.GetPendingDataset().Get(dataset) == OT_ERROR_NONE)
|
||||
{
|
||||
if (dataset.mIsPendingTimestampSet)
|
||||
@@ -156,9 +148,11 @@ void ChannelManager::PreparePendingDataset(void)
|
||||
}
|
||||
else
|
||||
{
|
||||
mState = kStateIdle;
|
||||
otLogInfoUtil(GetInstance(), "ChannelManager: Request to change to channel %d failed. Device is disabled",
|
||||
mChannel);
|
||||
|
||||
mState = kStateIdle;
|
||||
StartAutoSelectTimer();
|
||||
}
|
||||
|
||||
ExitNow();
|
||||
@@ -205,17 +199,14 @@ void ChannelManager::PreparePendingDataset(void)
|
||||
mActiveTimestamp = dataset.mActiveTimestamp + 1 + Random::GetUint32InRange(0, kMaxTimestampIncrease);
|
||||
}
|
||||
|
||||
dataset.mActiveTimestamp = mActiveTimestamp;
|
||||
dataset.mIsActiveTimestampSet = true;
|
||||
|
||||
dataset.mChannel = mChannel;
|
||||
dataset.mIsChannelSet = true;
|
||||
|
||||
dataset.mActiveTimestamp = mActiveTimestamp;
|
||||
dataset.mIsActiveTimestampSet = true;
|
||||
dataset.mChannel = mChannel;
|
||||
dataset.mIsChannelSet = true;
|
||||
dataset.mPendingTimestamp = pendingTimestamp;
|
||||
dataset.mIsPendingTimestampSet = true;
|
||||
|
||||
dataset.mDelay = delayInMs;
|
||||
dataset.mIsDelaySet = true;
|
||||
dataset.mDelay = delayInMs;
|
||||
dataset.mIsDelaySet = true;
|
||||
|
||||
error = netif.GetPendingDataset().SendSetRequest(dataset, NULL, 0);
|
||||
|
||||
@@ -248,6 +239,9 @@ void ChannelManager::HandleTimer(void)
|
||||
switch (mState)
|
||||
{
|
||||
case kStateIdle:
|
||||
otLogInfoUtil(GetInstance(), "ChannelManager: Auto-triggered channel select");
|
||||
IgnoreReturnValue(RequestChannelSelect(false));
|
||||
StartAutoSelectTimer();
|
||||
break;
|
||||
|
||||
case kStateSentMgmtPendingDataset:
|
||||
@@ -273,7 +267,7 @@ void ChannelManager::HandleStateChanged(uint32_t aFlags)
|
||||
VerifyOrExit(mChannel == GetInstance().Get<Mac::Mac>().GetChannel());
|
||||
|
||||
mState = kStateIdle;
|
||||
mTimer.Stop();
|
||||
StartAutoSelectTimer();
|
||||
|
||||
otLogInfoUtil(GetInstance(), "ChannelManager: Channel successfully changed to %d", mChannel);
|
||||
|
||||
@@ -281,6 +275,240 @@ exit:
|
||||
return;
|
||||
}
|
||||
|
||||
#if OPENTHREAD_ENABLE_CHANNEL_MONITOR
|
||||
|
||||
/**
|
||||
* This function randomly chooses a channel from a given channel mask.
|
||||
*
|
||||
* @param[in] aMask A channel mask.
|
||||
*
|
||||
* @returns A randomly chosen channel from the given mask, or `ChannelMask::kChannelIteratorFirst` if the mask is empty.
|
||||
*
|
||||
*/
|
||||
static uint8_t ChooseRandomChannel(const Mac::ChannelMask &aMask)
|
||||
{
|
||||
uint8_t channel = Mac::ChannelMask::kChannelIteratorFirst;
|
||||
uint8_t numChannels = 0;
|
||||
uint8_t randomIndex;
|
||||
|
||||
VerifyOrExit(!aMask.IsEmpty());
|
||||
|
||||
while (aMask.GetNextChannel(channel) == OT_ERROR_NONE)
|
||||
{
|
||||
numChannels++;
|
||||
}
|
||||
|
||||
randomIndex = Random::GetUint8InRange(0, numChannels);
|
||||
|
||||
channel = Mac::ChannelMask::kChannelIteratorFirst;
|
||||
SuccessOrExit(aMask.GetNextChannel(channel));
|
||||
|
||||
while (randomIndex-- != 0)
|
||||
{
|
||||
SuccessOrExit(aMask.GetNextChannel(channel));
|
||||
}
|
||||
|
||||
exit:
|
||||
return channel;
|
||||
}
|
||||
|
||||
otError ChannelManager::FindBetterChannel(uint8_t &aNewChannel, uint16_t &aOccupancy)
|
||||
{
|
||||
otError error = OT_ERROR_NONE;
|
||||
ChannelMonitor & monitor = GetInstance().GetChannelMonitor();
|
||||
Mac::ChannelMask favoredAndSupported;
|
||||
Mac::ChannelMask favoredBest;
|
||||
Mac::ChannelMask supportedBest;
|
||||
uint16_t favoredOccupancy;
|
||||
uint16_t supportedOccupancy;
|
||||
char string[Mac::ChannelMask::kInfoStringSize];
|
||||
|
||||
if (monitor.GetSampleCount() <= kMinChannelMonitorSampleCount)
|
||||
{
|
||||
otLogInfoUtil(GetInstance(), "ChannelManager: Too few samples (%d <= %d) to select channel",
|
||||
monitor.GetSampleCount(), kMinChannelMonitorSampleCount);
|
||||
ExitNow(error = OT_ERROR_INVALID_STATE);
|
||||
}
|
||||
|
||||
favoredAndSupported = mFavoredChannelMask;
|
||||
favoredAndSupported.Intersect(mSupportedChannelMask);
|
||||
|
||||
favoredBest = monitor.FindBestChannels(favoredAndSupported, favoredOccupancy);
|
||||
supportedBest = monitor.FindBestChannels(mSupportedChannelMask, supportedOccupancy);
|
||||
|
||||
otLogInfoUtil(GetInstance(), "ChannelManager: Best favored %s, occupancy 0x%04x",
|
||||
favoredBest.ToString(string, sizeof(string)), favoredOccupancy);
|
||||
otLogInfoUtil(GetInstance(), "ChannelManager: Best overall %s, occupancy 0x%04x",
|
||||
supportedBest.ToString(string, sizeof(string)), supportedOccupancy);
|
||||
|
||||
// Prefer favored channels unless there is no favored channel,
|
||||
// or the occupancy rate of the best favored channel is worse
|
||||
// than the best overall by at least `kThresholdToSkipFavored`.
|
||||
|
||||
if (favoredBest.IsEmpty() || ((favoredOccupancy >= kThresholdToSkipFavored) &&
|
||||
(supportedOccupancy < favoredOccupancy - kThresholdToSkipFavored)))
|
||||
{
|
||||
if (!favoredBest.IsEmpty())
|
||||
{
|
||||
otLogInfoUtil(GetInstance(),
|
||||
"ChannelManager: Preferring an unfavored channel due to high occupancy rate diff");
|
||||
}
|
||||
|
||||
favoredBest = supportedBest;
|
||||
favoredOccupancy = supportedOccupancy;
|
||||
}
|
||||
|
||||
VerifyOrExit(!favoredBest.IsEmpty(), error = OT_ERROR_NOT_FOUND);
|
||||
|
||||
aNewChannel = ChooseRandomChannel(favoredBest);
|
||||
aOccupancy = favoredOccupancy;
|
||||
|
||||
OT_UNUSED_VARIABLE(string);
|
||||
|
||||
exit:
|
||||
return error;
|
||||
}
|
||||
|
||||
#else // OPENTHREAD_ENABLE_CHANNEL_MONITOR
|
||||
|
||||
otError ChannelManager::FindBetterChannel(uint8_t &, uint16_t &)
|
||||
{
|
||||
otLogInfoUtil(GetInstance(), "ChannelManager: ChannelMonitor feature is disabled - cannot select channel");
|
||||
return OT_ERROR_DISABLED_FEATURE;
|
||||
}
|
||||
|
||||
#endif // OPENTHREAD_ENABLE_CHANNEL_MONITOR
|
||||
|
||||
bool ChannelManager::ShouldAttamptChannelChange(void)
|
||||
{
|
||||
uint16_t ccaFailureRate = GetInstance().Get<Mac::Mac>().GetCcaFailureRate();
|
||||
bool shouldAttempt = (ccaFailureRate >= kCcaFailureRateThreshold);
|
||||
|
||||
otLogInfoUtil(GetInstance(), "ChannelManager: CCA-err-rate: 0x%04x %s 0x%04x, selecting channel: %s",
|
||||
ccaFailureRate, shouldAttempt ? ">=" : "<", kCcaFailureRateThreshold, shouldAttempt ? "yes" : "no");
|
||||
|
||||
return shouldAttempt;
|
||||
}
|
||||
|
||||
otError ChannelManager::RequestChannelSelect(bool aSkipQualityCheck)
|
||||
{
|
||||
otError error = OT_ERROR_NONE;
|
||||
uint8_t curChannel, newChannel;
|
||||
uint16_t curOccupancy, newOccupancy;
|
||||
|
||||
otLogInfoUtil(GetInstance(), "ChannelManager: Request to select channel (skip quality check: %s)",
|
||||
aSkipQualityCheck ? "yes" : "no");
|
||||
|
||||
VerifyOrExit(GetInstance().Get<Mle::Mle>().GetRole() != OT_DEVICE_ROLE_DISABLED, error = OT_ERROR_INVALID_STATE);
|
||||
|
||||
VerifyOrExit(aSkipQualityCheck || ShouldAttamptChannelChange());
|
||||
|
||||
SuccessOrExit(error = FindBetterChannel(newChannel, newOccupancy));
|
||||
|
||||
curChannel = GetInstance().Get<Mac::Mac>().GetChannel();
|
||||
curOccupancy = GetInstance().GetChannelMonitor().GetChannelOccupancy(curChannel);
|
||||
|
||||
if (newChannel == curChannel)
|
||||
{
|
||||
otLogInfoUtil(GetInstance(), "ChannelManager: Already on best possible channel %d", curChannel);
|
||||
ExitNow();
|
||||
}
|
||||
|
||||
otLogInfoUtil(GetInstance(), "ChannelManager: Cur channel %d, occupancy 0x%04x - Best channel %d, occupancy 0x%04x",
|
||||
curChannel, curOccupancy, newChannel, newOccupancy);
|
||||
|
||||
// Switch only if new channel's occupancy rate is better than current
|
||||
// channel's occupancy rate by threshold `kThresholdToChangeChannel`.
|
||||
|
||||
if ((newOccupancy >= curOccupancy) ||
|
||||
(static_cast<uint16_t>(curOccupancy - newOccupancy) < kThresholdToChangeChannel))
|
||||
{
|
||||
otLogInfoUtil(GetInstance(), "ChannelManager: Occupancy rate diff too small to change channel");
|
||||
ExitNow();
|
||||
}
|
||||
|
||||
RequestChannelChange(newChannel);
|
||||
|
||||
exit:
|
||||
|
||||
if (error != OT_ERROR_NONE)
|
||||
{
|
||||
otLogInfoUtil(GetInstance(), "ChannelManager: Request to select better channel failed, error: %s",
|
||||
otThreadErrorToString(error));
|
||||
}
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
void ChannelManager::StartAutoSelectTimer(void)
|
||||
{
|
||||
VerifyOrExit(mState == kStateIdle);
|
||||
|
||||
if (mAutoSelectEnabled)
|
||||
{
|
||||
mTimer.Start(TimerMilli::SecToMsec(mAutoSelectInterval));
|
||||
}
|
||||
else
|
||||
{
|
||||
mTimer.Stop();
|
||||
}
|
||||
|
||||
exit:
|
||||
return;
|
||||
}
|
||||
|
||||
void ChannelManager::SetAutoChannelSelectionEnabled(bool aEnabled)
|
||||
{
|
||||
if (aEnabled != mAutoSelectEnabled)
|
||||
{
|
||||
mAutoSelectEnabled = aEnabled;
|
||||
IgnoreReturnValue(RequestChannelSelect(false));
|
||||
StartAutoSelectTimer();
|
||||
}
|
||||
}
|
||||
|
||||
otError ChannelManager::SetAutoChannelSelectionInterval(uint32_t aInterval)
|
||||
{
|
||||
otError error = OT_ERROR_NONE;
|
||||
uint32_t prevInterval = mAutoSelectInterval;
|
||||
|
||||
VerifyOrExit((aInterval != 0) && (aInterval < TimerMilli::MsecToSec(Timer::kMaxDt)), error = OT_ERROR_INVALID_ARGS);
|
||||
|
||||
mAutoSelectInterval = aInterval;
|
||||
|
||||
if (mAutoSelectEnabled && (mState == kStateIdle) && mTimer.IsRunning() && (prevInterval != aInterval))
|
||||
{
|
||||
mTimer.StartAt(mTimer.GetFireTime() - prevInterval, aInterval);
|
||||
}
|
||||
|
||||
exit:
|
||||
return error;
|
||||
}
|
||||
|
||||
void ChannelManager::SetSupportedChannels(uint32_t aChannelMask)
|
||||
{
|
||||
char string[Mac::ChannelMask::kInfoStringSize];
|
||||
|
||||
mSupportedChannelMask.SetMask(aChannelMask & OT_RADIO_SUPPORTED_CHANNELS);
|
||||
|
||||
otLogInfoUtil(GetInstance(), "ChannelManager: Supported channels: %s",
|
||||
mSupportedChannelMask.ToString(string, sizeof(string)));
|
||||
|
||||
OT_UNUSED_VARIABLE(string);
|
||||
}
|
||||
|
||||
void ChannelManager::SetFavoredChannels(uint32_t aChannelMask)
|
||||
{
|
||||
char string[Mac::ChannelMask::kInfoStringSize];
|
||||
|
||||
mFavoredChannelMask.SetMask(aChannelMask & OT_RADIO_SUPPORTED_CHANNELS);
|
||||
|
||||
otLogInfoUtil(GetInstance(), "ChannelManager: Favored channels: %s",
|
||||
mFavoredChannelMask.ToString(string, sizeof(string)));
|
||||
|
||||
OT_UNUSED_VARIABLE(string);
|
||||
}
|
||||
|
||||
} // namespace Utils
|
||||
} // namespace ot
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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<uint16_t>(newAverage);
|
||||
mChannelOccupancy[channelIndex] = static_cast<uint16_t>(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
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
+17
-1
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
+18
-2
@@ -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;
|
||||
|
||||
+68
-4
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user