From 00ec62e485c23b2e7ce365d629082b86b9511202 Mon Sep 17 00:00:00 2001 From: Abtin Keshavarzian Date: Wed, 21 Mar 2018 11:16:21 -0700 Subject: [PATCH] [tests] add new toranj test scripts for "ChannelManager" module This commit adds new `toranj` test scripts to test newly added functionality by `ChannelManager` including: - Test to check new spinel/wpan properties, - Test to check channel change request feature, - Test to check channel selection feature. This commit also updates the posix/radio `otPlatRadioGetRssi()` to emulate a simple interference model where it would return either a high or a low RSSI value with a fixed probability per each channel. This is used to verify the `ChannelMonitor` sampling logic and channel selection functionality. --- examples/platforms/posix/radio.c | 26 ++- src/core/meshcop/meshcop_tlvs.hpp | 1 + src/core/openthread-core-default-config.h | 5 +- tests/toranj/openthread-core-toranj-config.h | 55 +++++++ tests/toranj/start.sh | 5 + .../test-600-channel-manager-properties.py | 120 ++++++++++++++ ...test-601-channel-manager-channel-change.py | 146 +++++++++++++++++ ...test-602-channel-manager-channel-select.py | 151 ++++++++++++++++++ tests/toranj/wpan.py | 8 + 9 files changed, 514 insertions(+), 3 deletions(-) create mode 100644 tests/toranj/test-600-channel-manager-properties.py create mode 100644 tests/toranj/test-601-channel-manager-channel-change.py create mode 100644 tests/toranj/test-602-channel-manager-channel-select.py diff --git a/examples/platforms/posix/radio.c b/examples/platforms/posix/radio.c index b57af7e36..82fa79707 100644 --- a/examples/platforms/posix/radio.c +++ b/examples/platforms/posix/radio.c @@ -33,6 +33,7 @@ #include #include #include +#include #include "utils/code_utils.h" @@ -82,6 +83,10 @@ enum { POSIX_RECEIVE_SENSITIVITY = -100, // dBm POSIX_MAX_SRC_MATCH_ENTRIES = OPENTHREAD_CONFIG_MAX_CHILDREN, + + POSIX_HIGH_RSSI_SAMPLE = -30, // dBm + POSIX_LOW_RSSI_SAMPLE = -98, // dBm + POSIX_HIGH_RSSI_PROB_INC_PER_CHANNEL = 5, }; OT_TOOL_PACKED_BEGIN @@ -504,8 +509,27 @@ otRadioFrame *otPlatRadioGetTransmitBuffer(otInstance *aInstance) int8_t otPlatRadioGetRssi(otInstance *aInstance) { + int8_t rssi = POSIX_LOW_RSSI_SAMPLE; + uint8_t channel = sReceiveFrame.mChannel; + uint32_t probabilityThreshold; + (void)aInstance; - return 0; + + otEXPECT((OT_RADIO_CHANNEL_MIN <= channel) && channel <= (OT_RADIO_CHANNEL_MAX)); + + // To emulate a simple interference model, we return either a high or + // a low RSSI value with a fixed probability per each channel. The + // probability is increased per channel by a constant. + + probabilityThreshold = (channel - OT_RADIO_CHANNEL_MIN) * POSIX_HIGH_RSSI_PROB_INC_PER_CHANNEL; + + if ((otPlatRandomGet() & 0xffff) < (probabilityThreshold * 0xffff / 100)) + { + rssi = POSIX_HIGH_RSSI_SAMPLE; + } + +exit: + return rssi; } otRadioCaps otPlatRadioGetCaps(otInstance *aInstance) diff --git a/src/core/meshcop/meshcop_tlvs.hpp b/src/core/meshcop/meshcop_tlvs.hpp index eef66f114..d41ca829a 100644 --- a/src/core/meshcop/meshcop_tlvs.hpp +++ b/src/core/meshcop/meshcop_tlvs.hpp @@ -1271,6 +1271,7 @@ public: /** * Default Delay Timer value for a Pending Operational Dataset (ms) + * */ kDelayTimerDefault = OPENTHREAD_CONFIG_MESHCOP_PENDING_DATASET_DEFAULT_DELAY, }; diff --git a/src/core/openthread-core-default-config.h b/src/core/openthread-core-default-config.h index 4ea774b9f..207484f4d 100644 --- a/src/core/openthread-core-default-config.h +++ b/src/core/openthread-core-default-config.h @@ -532,7 +532,8 @@ * * 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. + * 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 @@ -544,7 +545,7 @@ * * Default Delay Timer value for a Pending Operational Dataset (in ms). * - * Thread specification defines this value as 300,000. Changing from the specified value should be done for testing + * Thread specification defines this value as 300,000 ms. Changing from the specified value should be done for testing * only. * */ diff --git a/tests/toranj/openthread-core-toranj-config.h b/tests/toranj/openthread-core-toranj-config.h index 6da446eca..c095f42f6 100644 --- a/tests/toranj/openthread-core-toranj-config.h +++ b/tests/toranj/openthread-core-toranj-config.h @@ -183,6 +183,61 @@ */ #define OPENTHREAD_CONFIG_MLE_SEND_LINK_REQUEST_ON_ADV_TIMEOUT 1 +/** + * @def OPENTHREAD_CONFIG_CHANNEL_MANAGER_MINIMUM_DELAY + * + * The minimum delay in seconds used by Channel Manager module for performing a channel change. + * + * The minimum delay should preferably be longer than maximum data poll interval used by all sleepy-end-devices within + * the Thread network. + * + * Applicable only if Channel Manager feature is enabled (i.e., `OPENTHREAD_ENABLE_CHANNEL_MANAGER` is set). + * + */ +#define OPENTHREAD_CONFIG_CHANNEL_MANAGER_MINIMUM_DELAY 2 + +/** + * @def OPENTHREAD_CONFIG_CHANNEL_MANAGER_THRESHOLD_TO_SKIP_FAVORED + * + * This threshold specifies the minimum occupancy rate difference between two channels for the Channel Manager to + * prefer an unfavored channel over the best favored one. This is used when (auto) selecting a channel based on the + * collected channel quality data by "channel monitor" feature. + * + * The difference is based on the `ChannelMonitor::GetChannelOccupancy()` definition which provides the average + * percentage of RSSI samples (within a time window) indicating that channel was busy (i.e., RSSI value higher than + * a threshold). Value 0 maps to 0% and 0xffff maps to 100%. + * + * Applicable only if Channel Manager feature is enabled (i.e., `OPENTHREAD_ENABLE_CHANNEL_MANAGER` is set). + * + */ +#define OPENTHREAD_CONFIG_CHANNEL_MANAGER_THRESHOLD_TO_SKIP_FAVORED (0xffff * 7 / 100) + +/** + * @def OPENTHREAD_CONFIG_CHANNEL_MANAGER_THRESHOLD_TO_CHANGE_CHANNEL + * + * This threshold specifies the minimum occupancy rate difference required between the current channel and a newly + * selected channel for Channel Manager to allow channel change to the new channel. + * + * The difference is based on the `ChannelMonitor::GetChannelOccupancy()` definition which provides the average + * percentage of RSSI samples (within a time window) indicating that channel was busy (i.e., RSSI value higher than + * a threshold). Value 0 maps to 0% rate and 0xffff maps to 100%. + * + * Applicable only if Channel Manager feature is enabled (i.e., `OPENTHREAD_ENABLE_CHANNEL_MANAGER` is set). + * + */ +#define OPENTHREAD_CONFIG_CHANNEL_MANAGER_THRESHOLD_TO_CHANGE_CHANNEL (0xffff * 10 / 100) + +/** + * @def OPENTHREAD_CONFIG_MESHCOP_PENDING_DATASET_MINIMUM_DELAY + * + * Minimum Delay Timer value for a Pending Operational Dataset (in ms). + * + * Thread specification defines this value as 30,000. Changing from the specified value should be done for testing only. + * + * For `toranj` test script the value is decreased so that the tests can be run faster. + * + */ +#define OPENTHREAD_CONFIG_MESHCOP_PENDING_DATASET_MINIMUM_DELAY 1000 #endif /* OPENTHREAD_CORE_TORANJ_CONFIG_H_ */ diff --git a/tests/toranj/start.sh b/tests/toranj/start.sh index 9dc622234..e169caee0 100755 --- a/tests/toranj/start.sh +++ b/tests/toranj/start.sh @@ -95,6 +95,7 @@ cd ../.. --enable-mac-filter \ --enable-service \ --enable-channel-monitor \ + --enable-channel-manager \ --disable-docs \ --disable-test || die @@ -115,4 +116,8 @@ run test-006-traffic-router-end-device.py run test-007-traffic-router-sleepy.py run test-008-permit-join.py +run test-600-channel-manager-properties.py +run test-601-channel-manager-channel-change.py +run test-602-channel-manager-channel-select.py + exit 0 diff --git a/tests/toranj/test-600-channel-manager-properties.py b/tests/toranj/test-600-channel-manager-properties.py new file mode 100644 index 000000000..83058dc71 --- /dev/null +++ b/tests/toranj/test-600-channel-manager-properties.py @@ -0,0 +1,120 @@ +#!/usr/bin/env python +# +# Copyright (c) 2018, The OpenThread Authors. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# 3. Neither the name of the copyright holder nor the +# names of its contributors may be used to endorse or promote products +# derived from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. + +import time +import wpan +from wpan import verify + +#----------------------------------------------------------------------------------------------------------------------- +# Test description: This test verifies wpantund properties related to `ChannelManager` feature + +test_name = __file__[:-3] if __file__.endswith('.py') else __file__ +print '-' * 120 +print 'Starting \'{}\''.format(test_name) + +#----------------------------------------------------------------------------------------------------------------------- +# Creating `wpan.Nodes` instances + +node = wpan.Node() + +#----------------------------------------------------------------------------------------------------------------------- +# Init all nodes + +wpan.Node.init_all_nodes() + +#----------------------------------------------------------------------------------------------------------------------- +# Build network topology + +node.form("channel-manager", channel=11) + +#----------------------------------------------------------------------------------------------------------------------- +# Test implementation + +# Check default property values + +verify(int(node.get(wpan.WPAN_CHANNEL_MANAGER_NEW_CHANNEL), 0) == 0) +verify(node.get(wpan.WPAN_CHANNEL_MANAGER_AUTO_SELECT_ENABLED) == 'false') +verify(int(node.get(wpan.WPAN_CHANNEL_MANAGER_SUPPORTED_CHANNEL_MASK), 0) == 0) +verify(int(node.get(wpan.WPAN_CHANNEL_MANAGER_FAVORED_CHANNEL_MASK), 0) == 0) + +# Set different wpan Channel Manager properties and get and check the output + +node.set(wpan.WPAN_CHANNEL_MANAGER_DELAY, '180') +verify(int(node.get(wpan.WPAN_CHANNEL_MANAGER_DELAY), 0) == 180) + +node.set(wpan.WPAN_CHANNEL_MANAGER_AUTO_SELECT_ENABLED, '1') +verify(node.get(wpan.WPAN_CHANNEL_MANAGER_AUTO_SELECT_ENABLED) == 'true') + +node.set(wpan.WPAN_CHANNEL_MANAGER_AUTO_SELECT_ENABLED, '0') +verify(node.get(wpan.WPAN_CHANNEL_MANAGER_AUTO_SELECT_ENABLED) == 'false') + +node.set(wpan.WPAN_CHANNEL_MANAGER_AUTO_SELECT_INTERVAL, '1000') +verify(int(node.get(wpan.WPAN_CHANNEL_MANAGER_AUTO_SELECT_INTERVAL), 0) == 1000) + +all_channls_mask = int('0x7fff800', 0) +chan_11_mask = int('0x800', 0) +chan_11_to_13_mask = int('0x3800', 0) + +node.set(wpan.WPAN_CHANNEL_MANAGER_SUPPORTED_CHANNEL_MASK, str(all_channls_mask)) +verify(int(node.get(wpan.WPAN_CHANNEL_MANAGER_SUPPORTED_CHANNEL_MASK), 0) == all_channls_mask) + +node.set(wpan.WPAN_CHANNEL_MANAGER_FAVORED_CHANNEL_MASK, str(chan_11_mask)) +verify(int(node.get(wpan.WPAN_CHANNEL_MANAGER_FAVORED_CHANNEL_MASK), 0) == chan_11_mask) + +node.set(wpan.WPAN_CHANNEL_MANAGER_SUPPORTED_CHANNEL_MASK, str(chan_11_to_13_mask)) +verify(int(node.get(wpan.WPAN_CHANNEL_MANAGER_SUPPORTED_CHANNEL_MASK), 0) == chan_11_to_13_mask) + +node.set(wpan.WPAN_CHANNEL_MANAGER_FAVORED_CHANNEL_MASK, str(all_channls_mask)) +verify(int(node.get(wpan.WPAN_CHANNEL_MANAGER_FAVORED_CHANNEL_MASK), 0) == all_channls_mask) + +node.set(wpan.WPAN_CHANNEL_MANAGER_AUTO_SELECT_ENABLED, '1') +verify(node.get(wpan.WPAN_CHANNEL_MANAGER_AUTO_SELECT_ENABLED) == 'true') + +# Check to ensure the property values are retained after an NCP reset + +node.reset() + +start_time = time.time() +wait_time = 20 + +while node.get(wpan.WPAN_STATE) != wpan.STATE_ASSOCIATED: + if time.time() - start_time > wait_time: + print 'Took too long to restore after reset ({}>{} sec)'.format(time.time() - start_time, wait_time) + exit(1) + time.sleep(2) + +verify(node.get(wpan.WPAN_CHANNEL_MANAGER_AUTO_SELECT_ENABLED) == 'true') +verify(int(node.get(wpan.WPAN_CHANNEL_MANAGER_FAVORED_CHANNEL_MASK), 0) == all_channls_mask) +verify(int(node.get(wpan.WPAN_CHANNEL_MANAGER_SUPPORTED_CHANNEL_MASK), 0) == chan_11_to_13_mask) +verify(int(node.get(wpan.WPAN_CHANNEL_MANAGER_AUTO_SELECT_INTERVAL), 0) == 1000) +verify(int(node.get(wpan.WPAN_CHANNEL_MANAGER_DELAY), 0) == 180) + +#----------------------------------------------------------------------------------------------------------------------- +# Test finished + +print '\'{}\' passed.'.format(test_name) diff --git a/tests/toranj/test-601-channel-manager-channel-change.py b/tests/toranj/test-601-channel-manager-channel-change.py new file mode 100644 index 000000000..1c8be0582 --- /dev/null +++ b/tests/toranj/test-601-channel-manager-channel-change.py @@ -0,0 +1,146 @@ +#!/usr/bin/env python +# +# Copyright (c) 2018, The OpenThread Authors. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# 3. Neither the name of the copyright holder nor the +# names of its contributors may be used to endorse or promote products +# derived from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. + +import time +import wpan +from wpan import verify + +#----------------------------------------------------------------------------------------------------------------------- +# Test description: verifies `ChannelManager` channel change process + +test_name = __file__[:-3] if __file__.endswith('.py') else __file__ +print '-' * 120 +print 'Starting \'{}\''.format(test_name) + + +def verify_channel(nodes, new_channel, wait_time=20): + """ + This function checks the channel on a given list of `nodes` and verifies that all nodes + switch to a given `new_channel` (as int) within certain `wait_time` (int and in seconds) + """ + start_time = time.time() + + while not all([ (new_channel == int(node.get(wpan.WPAN_CHANNEL), 0)) for node in nodes ]): + if time.time() - start_time > wait_time: + print 'Took too long to switch to channel {} ({}>{} sec)'.format(new_channel, time.time() - start_time, + wait_time) + exit(1) + time.sleep(0.1) + + +#----------------------------------------------------------------------------------------------------------------------- +# Creating `wpan.Nodes` instances + +speedup = 4 +wpan.Node.set_time_speedup_factor(speedup) + +r1 = wpan.Node() +r2 = wpan.Node() +r3 = wpan.Node() +sc1 = wpan.Node() +ec1 = wpan.Node() +sc2 = wpan.Node() +sc3 = wpan.Node() + +all_nodes = [r1, r2, r3, sc1, ec1, sc2, sc3] + +#----------------------------------------------------------------------------------------------------------------------- +# Init all nodes + +wpan.Node.init_all_nodes() + +#----------------------------------------------------------------------------------------------------------------------- +# Build network topology + +for node in all_nodes: + node.set(wpan.WPAN_OT_LOG_LEVEL, '0') + +r1.whitelist_node(r2) +r2.whitelist_node(r1) +r1.whitelist_node(r3) +r3.whitelist_node(r1) + +r1.whitelist_node(sc1) +r1.whitelist_node(ec1) +r2.whitelist_node(sc2) +r3.whitelist_node(sc3) + +r1.form('channel-manager', channel=12) +r2.join_node(r1, node_type=wpan.JOIN_TYPE_ROUTER) +r3.join_node(r1, node_type=wpan.JOIN_TYPE_ROUTER) +sc1.join_node(r1, node_type=wpan.JOIN_TYPE_SLEEPY_END_DEVICE) +ec1.join_node(r1, node_type=wpan.JOIN_TYPE_END_DEVICE) +sc2.join_node(r2, node_type=wpan.JOIN_TYPE_SLEEPY_END_DEVICE) +sc3.join_node(r3, node_type=wpan.JOIN_TYPE_SLEEPY_END_DEVICE) + +sc1.set(wpan.WPAN_POLL_INTERVAL, '500') +sc2.set(wpan.WPAN_POLL_INTERVAL, '500') +sc3.set(wpan.WPAN_POLL_INTERVAL, '500') + +#----------------------------------------------------------------------------------------------------------------------- +# Test implementation + +# The channel manager delay is set from "openthread-core-toranj-config.h". Verify that it is 2 seconds. + +verify(int(r1.get(wpan.WPAN_CHANNEL_MANAGER_DELAY), 0) == 2) + +verify_channel(all_nodes, 12) + +# Request a channel change to channel 13 from router r1 + +r1.set(wpan.WPAN_CHANNEL_MANAGER_NEW_CHANNEL, '13') +verify(int(r1.get(wpan.WPAN_CHANNEL_MANAGER_NEW_CHANNEL), 0) == 13) +verify_channel(all_nodes, 13) + +# Request same channel change on multiple routers at the same time + +r1.set(wpan.WPAN_CHANNEL_MANAGER_NEW_CHANNEL, '14') +r2.set(wpan.WPAN_CHANNEL_MANAGER_NEW_CHANNEL, '14') +r3.set(wpan.WPAN_CHANNEL_MANAGER_NEW_CHANNEL, '14') +verify_channel(all_nodes, 14) + +# Request different channel changes from same router (router r1). + +r1.set(wpan.WPAN_CHANNEL_MANAGER_NEW_CHANNEL, '15') +verify_channel(all_nodes, 14) +r1.set(wpan.WPAN_CHANNEL_MANAGER_NEW_CHANNEL, '16') +verify_channel(all_nodes, 16) + +# Request different channels from two routers (r1 and r2) + +r1.set(wpan.WPAN_CHANNEL_MANAGER_DELAY, '20') # increase the time to ensure r1 change is in process +r1.set(wpan.WPAN_CHANNEL_MANAGER_NEW_CHANNEL, '17') +time.sleep(10.5 / speedup) +verify_channel(all_nodes, 16) +r2.set(wpan.WPAN_CHANNEL_MANAGER_NEW_CHANNEL, '18') +verify_channel(all_nodes, 18) + +#----------------------------------------------------------------------------------------------------------------------- +# Test finished + +print '\'{}\' passed.'.format(test_name) diff --git a/tests/toranj/test-602-channel-manager-channel-select.py b/tests/toranj/test-602-channel-manager-channel-select.py new file mode 100644 index 000000000..8b5dddc4d --- /dev/null +++ b/tests/toranj/test-602-channel-manager-channel-select.py @@ -0,0 +1,151 @@ +#!/usr/bin/env python +# +# Copyright (c) 2018, The OpenThread Authors. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# 3. Neither the name of the copyright holder nor the +# names of its contributors may be used to endorse or promote products +# derived from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. + +import time +import wpan +from wpan import verify + +#----------------------------------------------------------------------------------------------------------------------- +# Test description: verifies `ChannelManager` channel selection procedure + +test_name = __file__[:-3] if __file__.endswith('.py') else __file__ +print '-' * 120 +print 'Starting \'{}\''.format(test_name) + +def verify_channel(nodes, new_channel, wait_time=20): + """ + This function checks the channel on a given list of `nodes` and verifies that all nodes + switch to a given `new_channel` (as int) within certain `wait_time` (int and in seconds) + """ + start_time = time.time() + + while not all([ (new_channel == int(node.get(wpan.WPAN_CHANNEL), 0)) for node in nodes ]): + if time.time() - start_time > wait_time: + print 'Took too long to switch to channel {} ({}>{} sec)'.format(new_channel, time.time() - start_time, + wait_time) + exit(1) + time.sleep(0.1) + +#----------------------------------------------------------------------------------------------------------------------- +# Creating `wpan.Nodes` instances + +# Run the test with 10,000 time speedup factor +wpan.Node.set_time_speedup_factor(10000) + +node = wpan.Node() + +#----------------------------------------------------------------------------------------------------------------------- +# Init all nodes + +wpan.Node.init_all_nodes() + +node.set(wpan.WPAN_OT_LOG_LEVEL, '0') + +#----------------------------------------------------------------------------------------------------------------------- +# Build network topology + +node.form('channel-manager', channel=24) + +#----------------------------------------------------------------------------------------------------------------------- +# Test implementation + +all_channls_mask = int('0x7fff800', 0) +chan_12_to_15_mask = int('0x000f000', 0) +chan_15_to_17_mask = int('0x0038000', 0) + +# Set supported channel mask to be all channels +node.set(wpan.WPAN_CHANNEL_MANAGER_SUPPORTED_CHANNEL_MASK, str(all_channls_mask)) +verify(int(node.get(wpan.WPAN_CHANNEL_MANAGER_SUPPORTED_CHANNEL_MASK), 0) == all_channls_mask) + +# Sleep for 4 second with speedup factor of 10,000 this is more than 11 hours. +time.sleep(4) + +verify(int(node.get(wpan.WPAN_CHANNEL_MONITOR_SAMPLE_COUNT), 0) > 970) + +# Verify the initial value of `NEW_CHANNEL` (should be zero if there has been no channel change so far). + +verify(int(node.get(wpan.WPAN_CHANNEL_MANAGER_NEW_CHANNEL), 0) == 0) + +# Issue a channel-select with quality check enabled, and verify that no action is taken. + +node.set(wpan.WPAN_CHANNEL_MANAGER_CHANNEL_SELECT, 'false') +verify(int(node.get(wpan.WPAN_CHANNEL_MANAGER_NEW_CHANNEL), 0) == 0) + +# Issue a channel-select with quality check disabled, verify that channel is switched to channel 11. + +node.set(wpan.WPAN_CHANNEL_MANAGER_CHANNEL_SELECT, 'true') +verify(int(node.get(wpan.WPAN_CHANNEL_MANAGER_NEW_CHANNEL), 0) == 11) +verify_channel([node], 11) + +# Set channels 12-15 as favorable and request a channel select, verify that channel is switched to 12. +# +# Even though 11 would be best, quality difference between 11 and 12 is not high enough for selection +# algorithm to pick an unfavored channel. + +node.set(wpan.WPAN_CHANNEL_MANAGER_FAVORED_CHANNEL_MASK, str(chan_12_to_15_mask)) +node.set(wpan.WPAN_CHANNEL_MANAGER_NEW_CHANNEL, '25') # request a channel change to 25 +verify_channel([node], 25) +node.set(wpan.WPAN_CHANNEL_MANAGER_CHANNEL_SELECT, 'true') +verify(int(node.get(wpan.WPAN_CHANNEL_MANAGER_NEW_CHANNEL), 0) == 12) +verify_channel([node], 12) + +# Set channels 15-17 as favorables and request a channel select, verify that channel is switched to 11. +# +# This time the quality difference between 11 and 15 should be high enough for selection +# algorithm to pick the best though unfavored channel (i.e., channel 11). + +node.set(wpan.WPAN_CHANNEL_MANAGER_NEW_CHANNEL, '25') # request a channel change to 25 +verify_channel([node], 25) +node.set(wpan.WPAN_CHANNEL_MANAGER_FAVORED_CHANNEL_MASK, str(chan_15_to_17_mask)) +node.set(wpan.WPAN_CHANNEL_MANAGER_CHANNEL_SELECT, 'true') +verify(int(node.get(wpan.WPAN_CHANNEL_MANAGER_NEW_CHANNEL), 0) == 11) +verify_channel([node], 11) + +# Set channels 12-15 as favorable and request a channel select, verify that channel is not switched. + +node.set(wpan.WPAN_CHANNEL_MANAGER_FAVORED_CHANNEL_MASK, str(chan_12_to_15_mask)) +node.set(wpan.WPAN_CHANNEL_MANAGER_CHANNEL_SELECT, 'true') +verify(int(node.get(wpan.WPAN_CHANNEL_MANAGER_NEW_CHANNEL), 0) == 11) +verify_channel([node], 11) + +# Starting from channel 12 and issuing a channel select (which would pick 11 as best channel). +# However, since quality difference between current channel 12 and new best channel 11 is not large +# enough, no action should be taken. + +node.set(wpan.WPAN_CHANNEL_MANAGER_NEW_CHANNEL, '12') # request a channel change to 12 +verify(int(node.get(wpan.WPAN_CHANNEL_MANAGER_NEW_CHANNEL), 0) == 12) +verify_channel([node], 12) +node.set(wpan.WPAN_CHANNEL_MANAGER_FAVORED_CHANNEL_MASK, str(all_channls_mask)) +node.set(wpan.WPAN_CHANNEL_MANAGER_CHANNEL_SELECT, 'true') +verify(int(node.get(wpan.WPAN_CHANNEL_MANAGER_NEW_CHANNEL), 0) == 12) +verify_channel([node], 12) + +#----------------------------------------------------------------------------------------------------------------------- +# Test finished + +print '\'{}\' passed.'.format(test_name) diff --git a/tests/toranj/wpan.py b/tests/toranj/wpan.py index 2881ce8b0..024c63cc4 100644 --- a/tests/toranj/wpan.py +++ b/tests/toranj/wpan.py @@ -123,6 +123,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