1#!/usr/bin/env python3
2#
3#  Copyright (c) 2022, The OpenThread Authors.
4#  All rights reserved.
5#
6#  Redistribution and use in source and binary forms, with or without
7#  modification, are permitted provided that the following conditions are met:
8#  1. Redistributions of source code must retain the above copyright
9#     notice, this list of conditions and the following disclaimer.
10#  2. Redistributions in binary form must reproduce the above copyright
11#     notice, this list of conditions and the following disclaimer in the
12#     documentation and/or other materials provided with the distribution.
13#  3. Neither the name of the copyright holder nor the
14#     names of its contributors may be used to endorse or promote products
15#     derived from this software without specific prior written permission.
16#
17#  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
18#  AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19#  IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20#  ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
21#  LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
22#  CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
23#  SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
24#  INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
25#  CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
26#  ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27#  POSSIBILITY OF SUCH DAMAGE.
28
29from cli import verify
30from cli import verify_within
31import cli
32import time
33
34# -----------------------------------------------------------------------------------------------------------------------
35# Test description:
36#
37# Verifies `ChannelManager` channel selection procedure
38
39test_name = __file__[:-3] if __file__.endswith('.py') else __file__
40print('-' * 120)
41print('Starting \'{}\''.format(test_name))
42
43# -----------------------------------------------------------------------------------------------------------------------
44# Creating `cli.Node` instances
45
46# Run the test with 10,000 time speedup factor
47speedup = 10000
48cli.Node.set_time_speedup_factor(speedup)
49
50node = cli.Node()
51
52# -----------------------------------------------------------------------------------------------------------------------
53# Form topology
54
55node.form('chan-sel', channel=24)
56
57verify(node.get_state() == 'leader')
58
59# -----------------------------------------------------------------------------------------------------------------------
60# Test Implementation
61
62channel = 24
63
64
65def check_channel():
66    verify(int(node.get_channel()) == channel)
67
68
69delay = int(node.cli('channel manager delay')[0])
70# add kRequestStartJitterInterval=10000ms to expected channel manager delay
71delay += 10 / speedup
72
73check_channel()
74
75all_channels_mask = int('0x7fff800', 0)
76chan_12_to_15_mask = int('0x000f000', 0)
77chan_15_to_17_mask = int('0x0038000', 0)
78
79# Set supported channel mask to be all channels
80node.cli('channel manager supported', all_channels_mask)
81
82# Sleep for 4.5 second with speedup factor of 10,000 this is more than 12
83# hours. We sleep instead of immediately checking the sample counter in
84# order to not add more actions/events into simulation (specially since
85# we are running at very high speedup).
86time.sleep(4.5)
87
88result = cli.Node.parse_list(node.cli('channel monitor')[:5])
89verify(result['enabled'] == '1')
90verify(int(result['count']) > 970)
91
92# Issue a channel-select with quality check enabled, and verify that no
93# action is taken.
94
95node.cli('channel manager select 0')
96result = cli.Node.parse_list(node.cli('channel manager'))
97verify(result['channel'] == '0')
98
99# Issue a channel-select with quality check disabled, verify that channel
100# is switched to channel 11.
101
102node.cli('channel manager select 1')
103result = cli.Node.parse_list(node.cli('channel manager'))
104verify(result['channel'] == '11')
105channel = 11
106verify_within(check_channel, delay)
107
108# Set channels 12-15 as favorable and request a channel select, verify
109# that channel is switched to 12.
110#
111# Even though 11 would be best, quality difference between 11 and 12
112# is not high enough for selection algorithm to pick an unfavored
113# channel.
114
115node.cli('channel manager favored', chan_12_to_15_mask)
116
117channel = 25
118node.cli('channel manager change', channel)
119verify_within(check_channel, delay)
120
121node.cli('channel manager select 1')
122result = cli.Node.parse_list(node.cli('channel manager'))
123verify(result['channel'] == '12')
124channel = 12
125verify_within(check_channel, delay)
126
127# Set channels 15-17 as favorables and request a channel select,
128# verify that channel is switched to 11.
129#
130# This time the quality difference between 11 and 15 should be high
131# enough for selection algorithm to pick the best though unfavored
132# channel (i.e., channel 11).
133
134channel = 25
135node.cli('channel manager change', channel)
136verify_within(check_channel, delay)
137
138node.cli('channel manager favored', chan_15_to_17_mask)
139
140node.cli('channel manager select 1')
141result = cli.Node.parse_list(node.cli('channel manager'))
142verify(result['channel'] == '11')
143channel = 11
144verify_within(check_channel, delay)
145
146# Set channels 12-15 as favorable and request a channel select, verify
147# that channel is not switched.
148
149node.cli('channel manager favored', chan_12_to_15_mask)
150
151node.cli('channel manager select 1')
152
153result = cli.Node.parse_list(node.cli('channel manager'))
154verify(result['channel'] == '11')
155channel = 11
156verify_within(check_channel, delay)
157
158# Starting from channel 12 and issuing a channel select (which would
159# pick 11 as best channel). However, since quality difference between
160# current channel 12 and new best channel 11 is not large enough, no
161# action should be taken.
162
163channel = 12
164node.cli('channel manager change', channel)
165verify_within(check_channel, delay)
166
167node.cli('channel manager favored', all_channels_mask)
168
169node.cli('channel manager select 1')
170result = cli.Node.parse_list(node.cli('channel manager'))
171verify(result['channel'] == '12')
172verify_within(check_channel, delay)
173
174# -----------------------------------------------------------------------------------------------------------------------
175# Auto Select
176
177# Set channel manager cca failure rate threshold to 0
178# as we cannot control cca success in simulation
179node.cli('channel manager threshold 0')
180
181# Set short channel selection interval to speedup
182interval = 30
183node.cli(f'channel manager interval {interval}')
184
185# Set channels 15-17 as favorable and request a channel select, verify
186# that channel is switched to 11.
187
188channel = 25
189node.cli('channel manager change', channel)
190verify_within(check_channel, delay)
191node.cli('channel manager favored', chan_15_to_17_mask)
192
193# Active auto channel selection
194node.cli('channel manager auto 1')
195
196channel = 11
197result = cli.Node.parse_list(node.cli('channel manager'))
198verify(result['auto'] == '1')
199verify(result['channel'] == str(channel))
200
201verify_within(check_channel, delay)
202
203# while channel selection timer is running change to channel 25,
204# set channels 12-15 as favorable, wait for auto channel selection
205# and verify that channel is switched to 12.
206
207node.cli('channel manager favored', chan_12_to_15_mask)
208channel = 25
209node.cli('channel manager change', channel)
210
211# wait for timeout of auto selection timer
212time.sleep(2 * interval / speedup)
213
214channel = 12
215result = cli.Node.parse_list(node.cli('channel manager'))
216verify(result['channel'] == str(channel))
217
218verify_within(check_channel, delay)
219
220# -----------------------------------------------------------------------------------------------------------------------
221# Test finished
222
223cli.Node.finalize_all_nodes()
224
225print('\'{}\' passed.'.format(test_name))
226