diff options
author | Martin Zimmermann <30142883+martinzi@users.noreply.github.com> | 2024-03-25 22:06:29 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-03-25 14:06:29 -0700 |
commit | 4db6520d174f67026c591abea55220e953cf9f7d (patch) | |
tree | 9ddd705ef2bd5c6bfb92f64eed3a705f51921933 | |
parent | 09aa9630aa9b7361907f0e7d364411c5514f7f5a (diff) | |
download | openthread-4db6520d174f67026c591abea55220e953cf9f7d.tar.gz |
[channel-manager] add local csl channel selection on SSED (#9641)
This commit enables channel manager on SSED, together with channel monitor,
auto-selecting a better CSL channel for the link between child and its parent.
It also fixes tracking of CcaSuccessRate on CslChannel and adds toranj tests
for auto-channel selection and thread_cert test for autocsl-channel selection.
-rw-r--r-- | .github/workflows/simulation-1.2.yml | 50 | ||||
-rw-r--r-- | etc/cmake/options.cmake | 1 | ||||
-rw-r--r-- | include/openthread/channel_manager.h | 86 | ||||
-rw-r--r-- | include/openthread/instance.h | 2 | ||||
-rwxr-xr-x | script/make-pretty | 1 | ||||
-rw-r--r-- | src/cli/cli.cpp | 87 | ||||
-rw-r--r-- | src/core/api/channel_manager_api.cpp | 35 | ||||
-rw-r--r-- | src/core/config/channel_manager.h | 12 | ||||
-rw-r--r-- | src/core/instance/instance.cpp | 4 | ||||
-rw-r--r-- | src/core/instance/instance.hpp | 8 | ||||
-rw-r--r-- | src/core/mac/mac.cpp | 6 | ||||
-rw-r--r-- | src/core/utils/channel_manager.cpp | 173 | ||||
-rw-r--r-- | src/core/utils/channel_manager.hpp | 116 | ||||
-rwxr-xr-x | tests/scripts/thread-cert/addon_test_channel_manager_autocsl.py | 143 | ||||
-rwxr-xr-x | tests/scripts/thread-cert/node.py | 89 | ||||
-rw-r--r-- | tests/toranj/cli/cli.py | 14 | ||||
-rwxr-xr-x | tests/toranj/cli/test-602-channel-manager-channel-select.py | 67 |
17 files changed, 818 insertions, 76 deletions
diff --git a/.github/workflows/simulation-1.2.yml b/.github/workflows/simulation-1.2.yml index bc4f75215..0ce8e7600 100644 --- a/.github/workflows/simulation-1.2.yml +++ b/.github/workflows/simulation-1.2.yml @@ -236,6 +236,56 @@ jobs: path: tmp/coverage.info retention-days: 1 + channel-manager-csl: + runs-on: ubuntu-20.04 + env: + CFLAGS: -m32 + CXXFLAGS: -m32 + LDFLAGS: -m32 + COVERAGE: 1 + THREAD_VERSION: 1.3 + VIRTUAL_TIME: 1 + INTER_OP: 1 + INTER_OP_BBR: 1 + ADDON_FEAT_1_2: 1 + steps: + - name: Harden Runner + uses: step-security/harden-runner@63c24ba6bd7ba022e95695ff85de572c04a18142 # v2.7.0 + with: + egress-policy: audit # TODO: change to 'egress-policy: block' after couple of runs + + - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + with: + submodules: true + - name: Bootstrap + run: | + sudo rm /etc/apt/sources.list.d/* && sudo apt-get update + sudo apt-get --no-install-recommends install -y g++-multilib lcov ninja-build python3-setuptools python3-wheel + python3 -m pip install -r tests/scripts/thread-cert/requirements.txt + - name: Build + run: | + OT_OPTIONS="-DOT_CHANNEL_MANAGER_CSL=ON" ./script/test build + - name: Run + run: | + ulimit -c unlimited + ./script/test cert_suite ./tests/scripts/thread-cert/Cert_*.py + ./script/test cert_suite ./tests/scripts/thread-cert/test_*.py + ./script/test cert_suite ./tests/scripts/thread-cert/v1_2_*.py + ./script/test cert_suite ./tests/scripts/thread-cert/addon_test_channel_manager_autocsl*.py + - uses: actions/upload-artifact@694cdabd8bdb0f10b2cea11669e1bf5453eed0a6 # v4.2.0 + if: ${{ failure() }} + with: + name: channel-manager-csl + path: ot_testing + - name: Generate Coverage + run: | + ./script/test generate_coverage gcc + - uses: actions/upload-artifact@694cdabd8bdb0f10b2cea11669e1bf5453eed0a6 # v4.2.0 + with: + name: cov-channel-manager-csl + path: tmp/coverage.info + retention-days: 1 + expects: runs-on: ubuntu-20.04 env: diff --git a/etc/cmake/options.cmake b/etc/cmake/options.cmake index cb106bb63..da77ef01f 100644 --- a/etc/cmake/options.cmake +++ b/etc/cmake/options.cmake @@ -180,6 +180,7 @@ ot_option(OT_BORDER_ROUTING OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE "border rout ot_option(OT_BORDER_ROUTING_DHCP6_PD OPENTHREAD_CONFIG_BORDER_ROUTING_DHCP6_PD_ENABLE "dhcpv6 pd support in border routing") ot_option(OT_BORDER_ROUTING_COUNTERS OPENTHREAD_CONFIG_IP6_BR_COUNTERS_ENABLE "border routing counters") ot_option(OT_CHANNEL_MANAGER OPENTHREAD_CONFIG_CHANNEL_MANAGER_ENABLE "channel manager") +ot_option(OT_CHANNEL_MANAGER_CSL OPENTHREAD_CONFIG_CHANNEL_MANAGER_CSL_CHANNEL_SELECT_ENABLE "channel manager for csl channel") ot_option(OT_CHANNEL_MONITOR OPENTHREAD_CONFIG_CHANNEL_MONITOR_ENABLE "channel monitor") ot_option(OT_COAP OPENTHREAD_CONFIG_COAP_API_ENABLE "coap api") ot_option(OT_COAP_BLOCK OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE "coap block-wise transfer (RFC7959)") diff --git a/include/openthread/channel_manager.h b/include/openthread/channel_manager.h index 6afe3a4aa..e024957e6 100644 --- a/include/openthread/channel_manager.h +++ b/include/openthread/channel_manager.h @@ -47,8 +47,14 @@ extern "C" { * @brief * This module includes functions for Channel Manager. * - * The functions in this module are available when Channel Manager feature - * (`OPENTHREAD_CONFIG_CHANNEL_MANAGER_ENABLE`) is enabled. Channel Manager is available only on an FTD build. + * The functions in this module are available when Channel Manager features + * `OPENTHREAD_CONFIG_CHANNEL_MANAGER_ENABLE` or `OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE && + * OPENTHREAD_CONFIG_CHANNEL_MANAGER_CSL_CHANNEL_SELECT_ENABLE` are enabled. Channel Manager behavior depends on the + * device role. It manages the network-wide PAN channel on a Full Thread Device in rx-on-when-idle mode, or with + * `OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE && OPENTHREAD_CONFIG_CHANNEL_MANAGER_CSL_CHANNEL_SELECT_ENABLE` set, + * selects CSL channel in synchronized rx-off-when-idle mode. On a Minimal Thread Device + * `OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE && OPENTHREAD_CONFIG_CHANNEL_MANAGER_CSL_CHANNEL_SELECT_ENABLE` selects + * the CSL channel. * * @{ * @@ -77,7 +83,9 @@ void otChannelManagerRequestChannelChange(otInstance *aInstance, uint8_t aChanne uint8_t otChannelManagerGetRequestedChannel(otInstance *aInstance); /** - * Gets the delay (in seconds) used by Channel Manager for a channel change. + * Gets the delay (in seconds) used by Channel Manager for a network channel change. + * + * Only available on FTDs. * * @param[in] aInstance A pointer to an OpenThread instance. * @@ -87,10 +95,10 @@ uint8_t otChannelManagerGetRequestedChannel(otInstance *aInstance); uint16_t otChannelManagerGetDelay(otInstance *aInstance); /** - * Sets the delay (in seconds) used for a channel change. + * Sets the delay (in seconds) used for a network channel change. * - * The delay should preferably be longer than the maximum data poll interval used by all sleepy-end-devices within the - * Thread network. + * Only available on FTDs. The delay should preferably be longer than the maximum data poll interval used by all + * Sleepy End Devices within the Thread network. * * @param[in] aInstance A pointer to an OpenThread instance. * @param[in] aDelay Delay in seconds. @@ -117,7 +125,7 @@ otError otChannelManagerSetDelay(otInstance *aInstance, uint16_t aDelay); * * 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. - * (see otChannelManagerSetSupportedChannels() and otChannelManagerSetFavoredChannels()). + * (see `otChannelManagerSetSupportedChannels()` and `otChannelManagerSetFavoredChannels()`). * * 3) If the newly selected channel is different from the current channel, `ChannelManager` requests/starts the * channel change process (internally invoking a `RequestChannelChange()`). @@ -132,10 +140,41 @@ otError otChannelManagerSetDelay(otInstance *aInstance, uint16_t aDelay); otError otChannelManagerRequestChannelSelect(otInstance *aInstance, bool aSkipQualityCheck); /** - * Enables or disables the auto-channel-selection functionality. + * Requests that `ChannelManager` checks and selects a new CSL channel and starts a CSL channel change. + * + * Only available with `OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE && + * OPENTHREAD_CONFIG_CHANNEL_MANAGER_CSL_CHANNEL_SELECT_ENABLE`. This function asks the `ChannelManager` to select a + * channel by itself (based on collected channel quality info). + * + * Once called, the Channel Manager will perform the following 3 steps: + * + * 1) `ChannelManager` decides if the CSL channel change would be helpful. This check can be skipped if + * `aSkipQualityCheck` is set to true (forcing a CSL 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 CSL channel change. + * + * 2) If the first step passes, then `ChannelManager` selects a potentially better CSL channel. It uses the collected + * channel quality data by `ChannelMonitor` module. The supported and favored channels are used at this step. + * (see `otChannelManagerSetSupportedChannels()` and `otChannelManagerSetFavoredChannels()`). + * + * 3) If the newly selected CSL channel is different from the current CSL channel, `ChannelManager` starts the + * CSL channel change process. + * + * @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. + * + */ +otError otChannelManagerRequestCslChannelSelect(otInstance *aInstance, bool aSkipQualityCheck); + +/** + * Enables or disables the auto-channel-selection functionality for network channel. * * When enabled, `ChannelManager` will periodically invoke a `RequestChannelSelect(false)`. The period interval - * can be set by `SetAutoChannelSelectionInterval()`. + * can be set by `otChannelManagerSetAutoChannelSelectionInterval()`. * * @param[in] aInstance A pointer to an OpenThread instance. * @param[in] aEnabled Indicates whether to enable or disable this functionality. @@ -144,7 +183,7 @@ otError otChannelManagerRequestChannelSelect(otInstance *aInstance, bool aSkipQu void otChannelManagerSetAutoChannelSelectionEnabled(otInstance *aInstance, bool aEnabled); /** - * Indicates whether the auto-channel-selection functionality is enabled or not. + * Indicates whether the auto-channel-selection functionality for a network channel is enabled or not. * * @param[in] aInstance A pointer to an OpenThread instance. * @@ -154,6 +193,33 @@ void otChannelManagerSetAutoChannelSelectionEnabled(otInstance *aInstance, bool bool otChannelManagerGetAutoChannelSelectionEnabled(otInstance *aInstance); /** + * Enables or disables the auto-channel-selection functionality for a CSL channel. + * + * Only available with `OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE && + * OPENTHREAD_CONFIG_CHANNEL_MANAGER_CSL_CHANNEL_SELECT_ENABLE`. When enabled, `ChannelManager` will periodically invoke + * a `otChannelManagerRequestCslChannelSelect()`. The period interval can be set by + * `otChannelManagerSetAutoChannelSelectionInterval()`. + * + * @param[in] aInstance A pointer to an OpenThread instance. + * @param[in] aEnabled Indicates whether to enable or disable this functionality. + * + */ +void otChannelManagerSetAutoCslChannelSelectionEnabled(otInstance *aInstance, bool aEnabled); + +/** + * Indicates whether the auto-csl-channel-selection functionality is enabled or not. + * + * Only available with `OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE && + * OPENTHREAD_CONFIG_CHANNEL_MANAGER_CSL_CHANNEL_SELECT_ENABLE`. + * + * @param[in] aInstance A pointer to an OpenThread instance. + * + * @returns TRUE if enabled, FALSE if disabled. + * + */ +bool otChannelManagerGetAutoCslChannelSelectionEnabled(otInstance *aInstance); + +/** * Sets the period interval (in seconds) used by auto-channel-selection functionality. * * @param[in] aInstance A pointer to an OpenThread instance. diff --git a/include/openthread/instance.h b/include/openthread/instance.h index 6371d21f5..84641d422 100644 --- a/include/openthread/instance.h +++ b/include/openthread/instance.h @@ -53,7 +53,7 @@ extern "C" { * @note This number versions both OpenThread platform and user APIs. * */ -#define OPENTHREAD_API_VERSION (400) +#define OPENTHREAD_API_VERSION (401) /** * @addtogroup api-instance diff --git a/script/make-pretty b/script/make-pretty index 491d4acb9..a0921af53 100755 --- a/script/make-pretty +++ b/script/make-pretty @@ -95,6 +95,7 @@ OT_CLANG_TIDY_BUILD_OPTS=( '-DOT_BORDER_ROUTING=ON' '-DOT_BORDER_ROUTING_DHCP6_PD=ON' '-DOT_CHANNEL_MANAGER=ON' + '-DOT_CHANNEL_MANAGER_CSL=ON' '-DOT_CHANNEL_MONITOR=ON' '-DOT_COAP=ON' '-DOT_COAP_BLOCK=ON' diff --git a/src/cli/cli.cpp b/src/cli/cli.cpp index 28de72475..33d1123bc 100644 --- a/src/cli/cli.cpp +++ b/src/cli/cli.cpp @@ -68,7 +68,9 @@ #include <openthread/backbone_router_ftd.h> #endif #endif -#if OPENTHREAD_CONFIG_CHANNEL_MANAGER_ENABLE && OPENTHREAD_FTD +#if OPENTHREAD_CONFIG_CHANNEL_MANAGER_ENABLE && \ + (OPENTHREAD_FTD || \ + (OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE && OPENTHREAD_CONFIG_CHANNEL_MANAGER_CSL_CHANNEL_SELECT_ENABLE)) #include <openthread/channel_manager.h> #endif #if OPENTHREAD_CONFIG_CHANNEL_MONITOR_ENABLE @@ -1424,7 +1426,9 @@ template <> otError Interpreter::Process<Cmd("channel")>(Arg aArgs[]) } } #endif // OPENTHREAD_CONFIG_CHANNEL_MONITOR_ENABLE -#if OPENTHREAD_CONFIG_CHANNEL_MANAGER_ENABLE && OPENTHREAD_FTD +#if OPENTHREAD_CONFIG_CHANNEL_MANAGER_ENABLE && \ + (OPENTHREAD_FTD || \ + (OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE && OPENTHREAD_CONFIG_CHANNEL_MANAGER_CSL_CHANNEL_SELECT_ENABLE)) else if (aArgs[0] == "manager") { /** @@ -1441,26 +1445,42 @@ template <> otError Interpreter::Process<Cmd("channel")>(Arg aArgs[]) * @endcode * @par * Get the channel manager state. - * `OPENTHREAD_CONFIG_CHANNEL_MANAGER_ENABLE` is required. + * `OPENTHREAD_CONFIG_CHANNEL_MANAGER_ENABLE` or `OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE && + * OPENTHREAD_CONFIG_CHANNEL_MANAGER_CSL_CHANNEL_SELECT_ENABLE` is required. * @sa otChannelManagerGetRequestedChannel */ if (aArgs[1].IsEmpty()) { OutputLine("channel: %u", otChannelManagerGetRequestedChannel(GetInstancePtr())); +#if OPENTHREAD_FTD OutputLine("auto: %d", otChannelManagerGetAutoChannelSelectionEnabled(GetInstancePtr())); +#endif +#if (OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE && OPENTHREAD_CONFIG_CHANNEL_MANAGER_CSL_CHANNEL_SELECT_ENABLE) + OutputLine("autocsl: %u", otChannelManagerGetAutoCslChannelSelectionEnabled(GetInstancePtr())); +#endif +#if (OPENTHREAD_FTD && OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE && \ + OPENTHREAD_CONFIG_CHANNEL_MANAGER_CSL_CHANNEL_SELECT_ENABLE) + if (otChannelManagerGetAutoChannelSelectionEnabled(GetInstancePtr()) || + otChannelManagerGetAutoCslChannelSelectionEnabled(GetInstancePtr())) +#elif OPENTHREAD_FTD if (otChannelManagerGetAutoChannelSelectionEnabled(GetInstancePtr())) +#elif (OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE && OPENTHREAD_CONFIG_CHANNEL_MANAGER_CSL_CHANNEL_SELECT_ENABLE) + if (otChannelManagerGetAutoCslChannelSelectionEnabled(GetInstancePtr())) +#endif { Mac::ChannelMask supportedMask(otChannelManagerGetSupportedChannels(GetInstancePtr())); Mac::ChannelMask favoredMask(otChannelManagerGetFavoredChannels(GetInstancePtr())); - +#if OPENTHREAD_FTD OutputLine("delay: %u", otChannelManagerGetDelay(GetInstancePtr())); +#endif OutputLine("interval: %lu", ToUlong(otChannelManagerGetAutoChannelSelectionInterval(GetInstancePtr()))); OutputLine("cca threshold: 0x%04x", otChannelManagerGetCcaFailureRateThreshold(GetInstancePtr())); OutputLine("supported: %s", supportedMask.ToString().AsCString()); OutputLine("favored: %s", favoredMask.ToString().AsCString()); } } +#if OPENTHREAD_FTD /** * @cli channel manager change * @code @@ -1489,7 +1509,9 @@ template <> otError Interpreter::Process<Cmd("channel")>(Arg aArgs[]) * @cparam channel manager select @ca{skip-quality-check} * Use a `1` or `0` for the boolean `skip-quality-check`. * @par - * `OPENTHREAD_CONFIG_CHANNEL_MANAGER_ENABLE` and `OPENTHREAD_CONFIG_CHANNEL_MONITOR_ENABLE` are required. + * `OPENTHREAD_CONFIG_CHANNEL_MANAGER_ENABLE` or `OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE && + * OPENTHREAD_CONFIG_CHANNEL_MANAGER_CSL_CHANNEL_SELECT_ENABLE`, and `OPENTHREAD_CONFIG_CHANNEL_MONITOR_ENABLE` + * are required. * @par api_copy * #otChannelManagerRequestChannelSelect */ @@ -1500,7 +1522,7 @@ template <> otError Interpreter::Process<Cmd("channel")>(Arg aArgs[]) SuccessOrExit(error = aArgs[2].ParseAsBool(enable)); error = otChannelManagerRequestChannelSelect(GetInstancePtr(), enable); } -#endif +#endif // OPENTHREAD_CONFIG_CHANNEL_MONITOR_ENABLE /** * @cli channel manager auto * @code @@ -1511,7 +1533,9 @@ template <> otError Interpreter::Process<Cmd("channel")>(Arg aArgs[]) * @cparam channel manager auto @ca{enable} * `1` is a boolean to `enable`. * @par - * `OPENTHREAD_CONFIG_CHANNEL_MANAGER_ENABLE` and `OPENTHREAD_CONFIG_CHANNEL_MONITOR_ENABLE` are required. + * `OPENTHREAD_CONFIG_CHANNEL_MANAGER_ENABLE` or `OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE && + * OPENTHREAD_CONFIG_CHANNEL_MANAGER_CSL_CHANNEL_SELECT_ENABLE`, and `OPENTHREAD_CONFIG_CHANNEL_MONITOR_ENABLE` + * are required. * @par api_copy * #otChannelManagerSetAutoChannelSelectionEnabled */ @@ -1522,6 +1546,32 @@ template <> otError Interpreter::Process<Cmd("channel")>(Arg aArgs[]) SuccessOrExit(error = aArgs[2].ParseAsBool(enable)); otChannelManagerSetAutoChannelSelectionEnabled(GetInstancePtr(), enable); } +#endif // OPENTHREAD_FTD +#if (OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE && OPENTHREAD_CONFIG_CHANNEL_MANAGER_CSL_CHANNEL_SELECT_ENABLE) + /** + * @cli channel manager autocsl + * @code + * channel manager autocsl 1 + * Done + * @endcode + * @cparam channel manager autocsl @ca{enable} + * `1` is a boolean to `enable`. + * @par + * Enables or disables the auto channel selection functionality for a CSL channel. + * `OPENTHREAD_CONFIG_CHANNEL_MANAGER_ENABLE` or `OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE && + * OPENTHREAD_CONFIG_CHANNEL_MANAGER_CSL_CHANNEL_SELECT_ENABLE`, and `OPENTHREAD_CONFIG_CHANNEL_MONITOR_ENABLE` + * are required. + * @sa otChannelManagerSetAutoCslChannelSelectionEnabled + */ + else if (aArgs[1] == "autocsl") + { + bool enable; + + SuccessOrExit(error = aArgs[2].ParseAsBool(enable)); + otChannelManagerSetAutoCslChannelSelectionEnabled(GetInstancePtr(), enable); + } +#endif // (OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE && OPENTHREAD_CONFIG_CHANNEL_MANAGER_CSL_CHANNEL_SELECT_ENABLE) +#if OPENTHREAD_FTD /** * @cli channel manager delay * @code @@ -1539,6 +1589,7 @@ template <> otError Interpreter::Process<Cmd("channel")>(Arg aArgs[]) { error = ProcessGetSet(aArgs + 2, otChannelManagerGetDelay, otChannelManagerSetDelay); } +#endif /** * @cli channel manager interval * @code @@ -1548,7 +1599,9 @@ template <> otError Interpreter::Process<Cmd("channel")>(Arg aArgs[]) * @endcode * @cparam channel manager interval @ca{interval-seconds} * @par - * `OPENTHREAD_CONFIG_CHANNEL_MANAGER_ENABLE` and `OPENTHREAD_CONFIG_CHANNEL_MONITOR_ENABLE` are required. + * `OPENTHREAD_CONFIG_CHANNEL_MANAGER_ENABLE` or `OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE && + * OPENTHREAD_CONFIG_CHANNEL_MANAGER_CSL_CHANNEL_SELECT_ENABLE`, and `OPENTHREAD_CONFIG_CHANNEL_MONITOR_ENABLE` + * are required. * @par api_copy * #otChannelManagerSetAutoChannelSelectionInterval */ @@ -1565,7 +1618,9 @@ template <> otError Interpreter::Process<Cmd("channel")>(Arg aArgs[]) * @endcode * @cparam channel manager supported @ca{mask} * @par - * `OPENTHREAD_CONFIG_CHANNEL_MANAGER_ENABLE` and `OPENTHREAD_CONFIG_CHANNEL_MONITOR_ENABLE` are required. + * `OPENTHREAD_CONFIG_CHANNEL_MANAGER_ENABLE` or `OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE && + * OPENTHREAD_CONFIG_CHANNEL_MANAGER_CSL_CHANNEL_SELECT_ENABLE`, and `OPENTHREAD_CONFIG_CHANNEL_MONITOR_ENABLE` + * are required. * @par api_copy * #otChannelManagerSetSupportedChannels */ @@ -1582,7 +1637,9 @@ template <> otError Interpreter::Process<Cmd("channel")>(Arg aArgs[]) * @endcode * @cparam channel manager favored @ca{mask} * @par - * `OPENTHREAD_CONFIG_CHANNEL_MANAGER_ENABLE` and `OPENTHREAD_CONFIG_CHANNEL_MONITOR_ENABLE` are required. + * `OPENTHREAD_CONFIG_CHANNEL_MANAGER_ENABLE` or `OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE && + * OPENTHREAD_CONFIG_CHANNEL_MANAGER_CSL_CHANNEL_SELECT_ENABLE`, and `OPENTHREAD_CONFIG_CHANNEL_MONITOR_ENABLE` + * are required. * @par api_copy * #otChannelManagerSetFavoredChannels */ @@ -1600,7 +1657,9 @@ template <> otError Interpreter::Process<Cmd("channel")>(Arg aArgs[]) * @cparam channel manager threshold @ca{threshold-percent} * Use a hex value for `threshold-percent`. `0` maps to 0% and `0xffff` maps to 100%. * @par - * `OPENTHREAD_CONFIG_CHANNEL_MANAGER_ENABLE` and `OPENTHREAD_CONFIG_CHANNEL_MONITOR_ENABLE` are required. + * `OPENTHREAD_CONFIG_CHANNEL_MANAGER_ENABLE` or `OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE && + * OPENTHREAD_CONFIG_CHANNEL_MANAGER_CSL_CHANNEL_SELECT_ENABLE`, and `OPENTHREAD_CONFIG_CHANNEL_MONITOR_ENABLE` + * are required. * @par api_copy * #otChannelManagerSetCcaFailureRateThreshold */ @@ -2465,9 +2524,9 @@ template <> otError Interpreter::Process<Cmd("csl")>(Arg aArgs[]) */ if (aArgs[0].IsEmpty()) { - OutputLine("Channel: %u", otLinkGetCslChannel(GetInstancePtr())); - OutputLine("Period: %luus", ToUlong(otLinkGetCslPeriod(GetInstancePtr()))); - OutputLine("Timeout: %lus", ToUlong(otLinkGetCslTimeout(GetInstancePtr()))); + OutputLine("channel: %u", otLinkGetCslChannel(GetInstancePtr())); + OutputLine("period: %luus", ToUlong(otLinkGetCslPeriod(GetInstancePtr()))); + OutputLine("timeout: %lus", ToUlong(otLinkGetCslTimeout(GetInstancePtr()))); } /** * @cli csl channel diff --git a/src/core/api/channel_manager_api.cpp b/src/core/api/channel_manager_api.cpp index af17e0d8a..3aa5d36a7 100644 --- a/src/core/api/channel_manager_api.cpp +++ b/src/core/api/channel_manager_api.cpp @@ -33,7 +33,9 @@ #include "openthread-core-config.h" -#if OPENTHREAD_CONFIG_CHANNEL_MANAGER_ENABLE && OPENTHREAD_FTD +#if OPENTHREAD_CONFIG_CHANNEL_MANAGER_ENABLE && \ + (OPENTHREAD_FTD || \ + (OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE && OPENTHREAD_CONFIG_CHANNEL_MANAGER_CSL_CHANNEL_SELECT_ENABLE)) #include <openthread/channel_manager.h> @@ -43,16 +45,19 @@ using namespace ot; +#if OPENTHREAD_FTD void otChannelManagerRequestChannelChange(otInstance *aInstance, uint8_t aChannel) { - AsCoreType(aInstance).Get<Utils::ChannelManager>().RequestChannelChange(aChannel); + AsCoreType(aInstance).Get<Utils::ChannelManager>().RequestNetworkChannelChange(aChannel); } +#endif uint8_t otChannelManagerGetRequestedChannel(otInstance *aInstance) { return AsCoreType(aInstance).Get<Utils::ChannelManager>().GetRequestedChannel(); } +#if OPENTHREAD_FTD uint16_t otChannelManagerGetDelay(otInstance *aInstance) { return AsCoreType(aInstance).Get<Utils::ChannelManager>().GetDelay(); @@ -66,19 +71,39 @@ otError otChannelManagerSetDelay(otInstance *aInstance, uint16_t aDelay) #if OPENTHREAD_CONFIG_CHANNEL_MONITOR_ENABLE otError otChannelManagerRequestChannelSelect(otInstance *aInstance, bool aSkipQualityCheck) { - return AsCoreType(aInstance).Get<Utils::ChannelManager>().RequestChannelSelect(aSkipQualityCheck); + return AsCoreType(aInstance).Get<Utils::ChannelManager>().RequestNetworkChannelSelect(aSkipQualityCheck); } #endif void otChannelManagerSetAutoChannelSelectionEnabled(otInstance *aInstance, bool aEnabled) { - AsCoreType(aInstance).Get<Utils::ChannelManager>().SetAutoChannelSelectionEnabled(aEnabled); + AsCoreType(aInstance).Get<Utils::ChannelManager>().SetAutoNetworkChannelSelectionEnabled(aEnabled); } bool otChannelManagerGetAutoChannelSelectionEnabled(otInstance *aInstance) { - return AsCoreType(aInstance).Get<Utils::ChannelManager>().GetAutoChannelSelectionEnabled(); + return AsCoreType(aInstance).Get<Utils::ChannelManager>().GetAutoNetworkChannelSelectionEnabled(); } +#endif // OPENTHREAD_FTD + +#if (OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE && OPENTHREAD_CONFIG_CHANNEL_MANAGER_CSL_CHANNEL_SELECT_ENABLE) +#if OPENTHREAD_CONFIG_CHANNEL_MONITOR_ENABLE +otError otChannelManagerRequestCslChannelSelect(otInstance *aInstance, bool aSkipQualityCheck) +{ + return AsCoreType(aInstance).Get<Utils::ChannelManager>().RequestCslChannelSelect(aSkipQualityCheck); +} +#endif + +void otChannelManagerSetAutoCslChannelSelectionEnabled(otInstance *aInstance, bool aEnabled) +{ + AsCoreType(aInstance).Get<Utils::ChannelManager>().SetAutoCslChannelSelectionEnabled(aEnabled); +} + +bool otChannelManagerGetAutoCslChannelSelectionEnabled(otInstance *aInstance) +{ + return AsCoreType(aInstance).Get<Utils::ChannelManager>().GetAutoCslChannelSelectionEnabled(); +} +#endif otError otChannelManagerSetAutoChannelSelectionInterval(otInstance *aInstance, uint32_t aInterval) { diff --git a/src/core/config/channel_manager.h b/src/core/config/channel_manager.h index 7c481c25d..4ecb3dc5b 100644 --- a/src/core/config/channel_manager.h +++ b/src/core/config/channel_manager.h @@ -56,6 +56,18 @@ #endif /** + * @def OPENTHREAD_CONFIG_CHANNEL_MANAGER_CSL_CHANNEL_SELECT_ENABLE + * + * Define as 1 to enable Channel Manager support for selecting CSL channels. + * + * `OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE` must be enabled in addition. + * + */ +#ifndef OPENTHREAD_CONFIG_CHANNEL_MANAGER_CSL_CHANNEL_SELECT_ENABLE +#define OPENTHREAD_CONFIG_CHANNEL_MANAGER_CSL_CHANNEL_SELECT_ENABLE 0 +#endif + +/** * @def OPENTHREAD_CONFIG_CHANNEL_MANAGER_MINIMUM_DELAY * * The minimum delay (in seconds) used by Channel Manager module for performing a channel change. diff --git a/src/core/instance/instance.cpp b/src/core/instance/instance.cpp index 4735ce325..307611089 100644 --- a/src/core/instance/instance.cpp +++ b/src/core/instance/instance.cpp @@ -230,7 +230,9 @@ Instance::Instance(void) #if OPENTHREAD_CONFIG_CHANNEL_MONITOR_ENABLE , mChannelMonitor(*this) #endif -#if OPENTHREAD_CONFIG_CHANNEL_MANAGER_ENABLE && OPENTHREAD_FTD +#if OPENTHREAD_CONFIG_CHANNEL_MANAGER_ENABLE && \ + (OPENTHREAD_FTD || \ + (OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE && OPENTHREAD_CONFIG_CHANNEL_MANAGER_CSL_CHANNEL_SELECT_ENABLE)) , mChannelManager(*this) #endif #if OPENTHREAD_CONFIG_MESH_DIAG_ENABLE && OPENTHREAD_FTD diff --git a/src/core/instance/instance.hpp b/src/core/instance/instance.hpp index 1ebc01c8d..30b443b34 100644 --- a/src/core/instance/instance.hpp +++ b/src/core/instance/instance.hpp @@ -645,7 +645,9 @@ private: Utils::ChannelMonitor mChannelMonitor; #endif -#if OPENTHREAD_CONFIG_CHANNEL_MANAGER_ENABLE && OPENTHREAD_FTD +#if OPENTHREAD_CONFIG_CHANNEL_MANAGER_ENABLE && \ + (OPENTHREAD_FTD || \ + (OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE && OPENTHREAD_CONFIG_CHANNEL_MANAGER_CSL_CHANNEL_SELECT_ENABLE)) Utils::ChannelManager mChannelManager; #endif @@ -946,7 +948,9 @@ template <> inline Utils::PingSender &Instance::Get(void) { return mPingSender; template <> inline Utils::ChannelMonitor &Instance::Get(void) { return mChannelMonitor; } #endif -#if OPENTHREAD_CONFIG_CHANNEL_MANAGER_ENABLE && OPENTHREAD_FTD +#if OPENTHREAD_CONFIG_CHANNEL_MANAGER_ENABLE && \ + (OPENTHREAD_FTD || \ + (OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE && OPENTHREAD_CONFIG_CHANNEL_MANAGER_CSL_CHANNEL_SELECT_ENABLE)) template <> inline Utils::ChannelManager &Instance::Get(void) { return mChannelManager; } #endif diff --git a/src/core/mac/mac.cpp b/src/core/mac/mac.cpp index 4fe93fe4e..e70fc982b 100644 --- a/src/core/mac/mac.cpp +++ b/src/core/mac/mac.cpp @@ -1134,9 +1134,13 @@ void Mac::RecordCcaStatus(bool aCcaSuccess, uint8_t aChannel) } // Only track the CCA success rate for frame transmissions - // on the PAN channel. + // on the PAN channel or the CSL channel. +#if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE + if ((aChannel == mPanChannel) || (IsCslEnabled() && (aChannel == mCslChannel))) +#else if (aChannel == mPanChannel) +#endif { if (mCcaSampleCount < kMaxCcaSampleCount) { diff --git a/src/core/utils/channel_manager.cpp b/src/core/utils/channel_manager.cpp index 7ed7df486..794656f9a 100644 --- a/src/core/utils/channel_manager.cpp +++ b/src/core/utils/channel_manager.cpp @@ -34,7 +34,9 @@ #include "channel_manager.hpp" -#if OPENTHREAD_CONFIG_CHANNEL_MANAGER_ENABLE && OPENTHREAD_FTD +#if OPENTHREAD_CONFIG_CHANNEL_MANAGER_ENABLE && \ + (OPENTHREAD_FTD || \ + (OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE && OPENTHREAD_CONFIG_CHANNEL_MANAGER_CSL_CHANNEL_SELECT_ENABLE)) #include "common/code_utils.hpp" #include "common/locator_getters.hpp" @@ -54,26 +56,51 @@ ChannelManager::ChannelManager(Instance &aInstance) : InstanceLocator(aInstance) , mSupportedChannelMask(0) , mFavoredChannelMask(0) +#if OPENTHREAD_FTD , mDelay(kMinimumDelay) +#endif , mChannel(0) + , mChannelSelected(0) , mState(kStateIdle) , mTimer(aInstance) , mAutoSelectInterval(kDefaultAutoSelectInterval) +#if OPENTHREAD_FTD , mAutoSelectEnabled(false) +#endif +#if (OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE && OPENTHREAD_CONFIG_CHANNEL_MANAGER_CSL_CHANNEL_SELECT_ENABLE) + , mAutoSelectCslEnabled(false) +#endif , mCcaFailureRateThreshold(kCcaFailureRateThreshold) { } void ChannelManager::RequestChannelChange(uint8_t aChannel) { - LogInfo("Request to change to channel %d with delay %d sec", aChannel, mDelay); +#if OPENTHREAD_FTD + if (Get<Mle::Mle>().IsFullThreadDevice() && Get<Mle::Mle>().IsRxOnWhenIdle() && mAutoSelectEnabled) + { + RequestNetworkChannelChange(aChannel); + } +#endif +#if (OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE && OPENTHREAD_CONFIG_CHANNEL_MANAGER_CSL_CHANNEL_SELECT_ENABLE) + if (mAutoSelectCslEnabled) + { + ChangeCslChannel(aChannel); + } +#endif +} +#if OPENTHREAD_FTD +void ChannelManager::RequestNetworkChannelChange(uint8_t aChannel) +{ + // Check requested channel != current channel if (aChannel == Get<Mac::Mac>().GetPanChannel()) { LogInfo("Already operating on the requested channel %d", aChannel); ExitNow(); } + LogInfo("Request to change to channel %d with delay %d sec", aChannel, mDelay); if (mState == kStateChangeInProgress) { VerifyOrExit(mChannel != aChannel); @@ -89,7 +116,36 @@ void ChannelManager::RequestChannelChange(uint8_t aChannel) exit: return; } +#endif + +#if (OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE && OPENTHREAD_CONFIG_CHANNEL_MANAGER_CSL_CHANNEL_SELECT_ENABLE) +void ChannelManager::ChangeCslChannel(uint8_t aChannel) +{ + if (!(!Get<Mle::Mle>().IsRxOnWhenIdle() && Get<Mac::Mac>().IsCslEnabled())) + { + // cannot select or use other channel + ExitNow(); + } + + if (aChannel == Get<Mac::Mac>().GetCslChannel()) + { + LogInfo("Already operating on the requested channel %d", aChannel); + ExitNow(); + } + + VerifyOrExit(Radio::IsCslChannelValid(aChannel)); + LogInfo("Change to Csl channel %d now.", aChannel); + + mChannel = aChannel; + Get<Mac::Mac>().SetCslChannel(aChannel); + +exit: + return; +} +#endif // (OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE && OPENTHREAD_CONFIG_CHANNEL_MANAGER_CSL_CHANNEL_SELECT_ENABLE) + +#if OPENTHREAD_FTD Error ChannelManager::SetDelay(uint16_t aDelay) { Error error = kErrorNone; @@ -153,6 +209,7 @@ void ChannelManager::HandleDatasetUpdateDone(Error aError) mState = kStateIdle; StartAutoSelectTimer(); } +#endif // OPENTHREAD_FTD void ChannelManager::HandleTimer(void) { @@ -160,12 +217,14 @@ void ChannelManager::HandleTimer(void) { case kStateIdle: LogInfo("Auto-triggered channel select"); - IgnoreError(RequestChannelSelect(false)); + IgnoreError(RequestAutoChannelSelect(false)); StartAutoSelectTimer(); break; case kStateChangeRequested: +#if OPENTHREAD_FTD StartDatasetUpdate(); +#endif break; case kStateChangeInProgress: @@ -236,6 +295,53 @@ bool ChannelManager::ShouldAttemptChannelChange(void) return shouldAttempt; } +#if OPENTHREAD_FTD +Error ChannelManager::RequestNetworkChannelSelect(bool aSkipQualityCheck) +{ + Error error = kErrorNone; + + SuccessOrExit(error = RequestChannelSelect(aSkipQualityCheck)); + RequestNetworkChannelChange(mChannelSelected); + +exit: + if ((error == kErrorAbort) || (error == kErrorAlready)) + { + // ignore aborted channel change + error = kErrorNone; + } + return error; +} +#endif + +#if (OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE && OPENTHREAD_CONFIG_CHANNEL_MANAGER_CSL_CHANNEL_SELECT_ENABLE) +Error ChannelManager::RequestCslChannelSelect(bool aSkipQualityCheck) +{ + Error error = kErrorNone; + + SuccessOrExit(error = RequestChannelSelect(aSkipQualityCheck)); + ChangeCslChannel(mChannelSelected); + +exit: + if ((error == kErrorAbort) || (error == kErrorAlready)) + { + // ignore aborted channel change + error = kErrorNone; + } + return error; +} +#endif + +Error ChannelManager::RequestAutoChannelSelect(bool aSkipQualityCheck) +{ + Error error = kErrorNone; + + SuccessOrExit(error = RequestChannelSelect(aSkipQualityCheck)); + RequestChannelChange(mChannelSelected); + +exit: + return error; +} + Error ChannelManager::RequestChannelSelect(bool aSkipQualityCheck) { Error error = kErrorNone; @@ -246,17 +352,27 @@ Error ChannelManager::RequestChannelSelect(bool aSkipQualityCheck) VerifyOrExit(!Get<Mle::Mle>().IsDisabled(), error = kErrorInvalidState); - VerifyOrExit(aSkipQualityCheck || ShouldAttemptChannelChange()); + VerifyOrExit(aSkipQualityCheck || ShouldAttemptChannelChange(), error = kErrorAbort); SuccessOrExit(error = FindBetterChannel(newChannel, newOccupancy)); - curChannel = Get<Mac::Mac>().GetPanChannel(); +#if (OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE && OPENTHREAD_CONFIG_CHANNEL_MANAGER_CSL_CHANNEL_SELECT_ENABLE) + if (Get<Mac::Mac>().IsCslEnabled() && (Get<Mac::Mac>().GetCslChannel() != 0)) + { + curChannel = Get<Mac::Mac>().GetCslChannel(); + } + else +#endif + { + curChannel = Get<Mac::Mac>().GetPanChannel(); + } + curOccupancy = Get<ChannelMonitor>().GetChannelOccupancy(curChannel); if (newChannel == curChannel) { LogInfo("Already on best possible channel %d", curChannel); - ExitNow(); + ExitNow(error = kErrorAlready); } LogInfo("Cur channel %d, occupancy 0x%04x - Best channel %d, occupancy 0x%04x", curChannel, curOccupancy, @@ -269,11 +385,9 @@ Error ChannelManager::RequestChannelSelect(bool aSkipQualityCheck) (static_cast<uint16_t>(curOccupancy - newOccupancy) < kThresholdToChangeChannel)) { LogInfo("Occupancy rate diff too small to change channel"); - ExitNow(); + ExitNow(error = kErrorAbort); } - - RequestChannelChange(newChannel); - + mChannelSelected = newChannel; exit: if (error != kErrorNone) @@ -289,7 +403,14 @@ void ChannelManager::StartAutoSelectTimer(void) { VerifyOrExit(mState == kStateIdle); +#if (OPENTHREAD_FTD && OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE && \ + OPENTHREAD_CONFIG_CHANNEL_MANAGER_CSL_CHANNEL_SELECT_ENABLE) + if (mAutoSelectEnabled || mAutoSelectCslEnabled) +#elif OPENTHREAD_FTD if (mAutoSelectEnabled) +#elif (OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE && OPENTHREAD_CONFIG_CHANNEL_MANAGER_CSL_CHANNEL_SELECT_ENABLE) + if (mAutoSelectCslEnabled) +#endif { mTimer.Start(Time::SecToMsec(mAutoSelectInterval)); } @@ -302,15 +423,29 @@ exit: return; } -void ChannelManager::SetAutoChannelSelectionEnabled(bool aEnabled) +#if OPENTHREAD_FTD +void ChannelManager::SetAutoNetworkChannelSelectionEnabled(bool aEnabled) { if (aEnabled != mAutoSelectEnabled) { mAutoSelectEnabled = aEnabled; - IgnoreError(RequestChannelSelect(false)); + IgnoreError(RequestNetworkChannelSelect(false)); StartAutoSelectTimer(); } } +#endif + +#if (OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE && OPENTHREAD_CONFIG_CHANNEL_MANAGER_CSL_CHANNEL_SELECT_ENABLE) +void ChannelManager::SetAutoCslChannelSelectionEnabled(bool aEnabled) +{ + if (aEnabled != mAutoSelectCslEnabled) + { + mAutoSelectCslEnabled = aEnabled; + IgnoreError(RequestAutoChannelSelect(false)); + StartAutoSelectTimer(); + } +} +#endif Error ChannelManager::SetAutoChannelSelectionInterval(uint32_t aInterval) { @@ -321,9 +456,19 @@ Error ChannelManager::SetAutoChannelSelectionInterval(uint32_t aInterval) mAutoSelectInterval = aInterval; - if (mAutoSelectEnabled && (mState == kStateIdle) && mTimer.IsRunning() && (prevInterval != aInterval)) +#if (OPENTHREAD_FTD && OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE && \ + OPENTHREAD_CONFIG_CHANNEL_MANAGER_CSL_CHANNEL_SELECT_ENABLE) + if (mAutoSelectEnabled || mAutoSelectCslEnabled) +#elif OPENTHREAD_FTD + if (mAutoSelectEnabled) +#elif (OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE && OPENTHREAD_CONFIG_CHANNEL_MANAGER_CSL_CHANNEL_SELECT_ENABLE) + if (mAutoSelectCslEnabled) +#endif { - mTimer.StartAt(mTimer.GetFireTime() - Time::SecToMsec(prevInterval), Time::SecToMsec(aInterval)); + if ((mState == kStateIdle) && mTimer.IsRunning() && (prevInterval != aInterval)) + { + mTimer.StartAt(mTimer.GetFireTime() - Time::SecToMsec(prevInterval), Time::SecToMsec(aInterval)); + } } exit: diff --git a/src/core/utils/channel_manager.hpp b/src/core/utils/channel_manager.hpp index ad46141e8..02d93053b 100644 --- a/src/core/utils/channel_manager.hpp +++ b/src/core/utils/channel_manager.hpp @@ -36,7 +36,9 @@ #include "openthread-core-config.h" -#if OPENTHREAD_CONFIG_CHANNEL_MANAGER_ENABLE && OPENTHREAD_FTD +#if OPENTHREAD_CONFIG_CHANNEL_MANAGER_ENABLE && \ + (OPENTHREAD_FTD || \ + (OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE && OPENTHREAD_CONFIG_CHANNEL_MANAGER_CSL_CHANNEL_SELECT_ENABLE)) #include <openthread/platform/radio.h> @@ -64,11 +66,13 @@ namespace Utils { class ChannelManager : public InstanceLocator, private NonCopyable { public: +#if OPENTHREAD_FTD /** * Minimum delay (in seconds) used for network channel change. * */ static constexpr uint16_t kMinimumDelay = OPENTHREAD_CONFIG_CHANNEL_MANAGER_MINIMUM_DELAY; +#endif /** * Initializes a `ChanelManager` object. @@ -78,6 +82,7 @@ public: */ explicit ChannelManager(Instance &aInstance); +#if OPENTHREAD_FTD /** * Requests a Thread network channel change. * @@ -91,16 +96,18 @@ public: * @param[in] aChannel The new channel for the Thread network. * */ - void RequestChannelChange(uint8_t aChannel); + void RequestNetworkChannelChange(uint8_t aChannel); +#endif /** - * Gets the channel from the last successful call to `RequestChannelChange()`. + * Gets the channel from the last successful call to `RequestNetworkChannelChange()` or `ChangeCslChannel()`. * * @returns The last requested channel, or zero if there has been no channel change request yet. * */ uint8_t GetRequestedChannel(void) const { return mChannel; } +#if OPENTHREAD_FTD /** * Gets the delay (in seconds) used for a channel change. * @@ -122,9 +129,11 @@ public: * */ Error SetDelay(uint16_t aDelay); +#endif // OPENTHREAD_FTD +#if OPENTHREAD_FTD /** - * Requests that `ChannelManager` checks and selects a new channel and starts a channel change. + * Requests that `ChannelManager` checks and selects a new network channel and starts a network 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). @@ -142,7 +151,7 @@ public: * (@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()`). + * channel change process (internally invoking a `RequestNetworkChannelChange()`). * * * @param[in] aSkipQualityCheck Indicates whether the quality check (step 1) should be skipped. @@ -152,8 +161,40 @@ public: * @retval kErrorInvalidState Thread is not enabled or not enough data to select new channel. * */ - Error RequestChannelSelect(bool aSkipQualityCheck); + Error RequestNetworkChannelSelect(bool aSkipQualityCheck); +#endif // OPENTHREAD_FTD + +#if (OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE && OPENTHREAD_CONFIG_CHANNEL_MANAGER_CSL_CHANNEL_SELECT_ENABLE) + /** + * Requests that `ChannelManager` checks and selects a new Csl channel and starts a channel change. + * + * 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 Csl channel, `ChannelManager` starts the + * channel change process (internally invoking a `ChangeCslChannel()`). + * + * + * @param[in] aSkipQualityCheck Indicates whether the quality check (step 1) should be skipped. + * + * @retval kErrorNone Channel selection finished successfully. + * @retval kErrorNotFound Supported channels is empty, therefore could not select a channel. + * @retval kErrorInvalidState Thread is not enabled or not enough data to select new channel. + * + */ + Error RequestCslChannelSelect(bool aSkipQualityCheck); +#endif // (OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE && OPENTHREAD_CONFIG_CHANNEL_MANAGER_CSL_CHANNEL_SELECT_ENABLE) +#if OPENTHREAD_FTD /** * Enables/disables the auto-channel-selection functionality. * @@ -163,7 +204,7 @@ public: * @param[in] aEnabled Indicates whether to enable or disable this functionality. * */ - void SetAutoChannelSelectionEnabled(bool aEnabled); + void SetAutoNetworkChannelSelectionEnabled(bool aEnabled); /** * Indicates whether the auto-channel-selection functionality is enabled or not. @@ -171,7 +212,29 @@ public: * @returns TRUE if enabled, FALSE if disabled. * */ - bool GetAutoChannelSelectionEnabled(void) const { return mAutoSelectEnabled; } + bool GetAutoNetworkChannelSelectionEnabled(void) const { return mAutoSelectEnabled; } +#endif + +#if (OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE && OPENTHREAD_CONFIG_CHANNEL_MANAGER_CSL_CHANNEL_SELECT_ENABLE) + /** + * 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 SetAutoCslChannelSelectionEnabled(bool aEnabled); + + /** + * Indicates whether the auto-channel-selection functionality is enabled or not. + * + * @returns TRUE if enabled, FALSE if disabled. + * + */ + bool GetAutoCslChannelSelectionEnabled(void) const { return mAutoSelectCslEnabled; } +#endif /** * Sets the period interval (in seconds) used by auto-channel-selection functionality. @@ -244,7 +307,7 @@ private: // Retry interval to resend Pending Dataset in case of tx failure (in ms). static constexpr uint32_t kPendingDatasetTxRetryInterval = 20000; - // Maximum jitter/wait time to start a requested channel change (in ms). + // Maximum jitter/wait time to start a requested network channel change (in ms). static constexpr uint32_t kRequestStartJitterInterval = 10000; // The minimum number of RSSI samples required before using the collected data (by `ChannelMonitor`) to select @@ -273,28 +336,45 @@ private: kStateChangeInProgress, }; +#if OPENTHREAD_FTD void StartDatasetUpdate(void); static void HandleDatasetUpdateDone(Error aError, void *aContext); void HandleDatasetUpdateDone(Error aError); - void HandleTimer(void); - void StartAutoSelectTimer(void); +#endif + void HandleTimer(void); + void StartAutoSelectTimer(void); + Error RequestChannelSelect(bool aSkipQualityCheck); + Error RequestAutoChannelSelect(bool aSkipQualityCheck); + void RequestChannelChange(uint8_t aChannel); #if OPENTHREAD_CONFIG_CHANNEL_MONITOR_ENABLE Error FindBetterChannel(uint8_t &aNewChannel, uint16_t &aOccupancy); bool ShouldAttemptChannelChange(void); #endif +#if (OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE && OPENTHREAD_CONFIG_CHANNEL_MANAGER_CSL_CHANNEL_SELECT_ENABLE) + void ChangeCslChannel(uint8_t aChannel); +#endif + using ManagerTimer = TimerMilliIn<ChannelManager, &ChannelManager::HandleTimer>; Mac::ChannelMask mSupportedChannelMask; Mac::ChannelMask mFavoredChannelMask; - uint16_t mDelay; - uint8_t mChannel; - State mState; - ManagerTimer mTimer; - uint32_t mAutoSelectInterval; - bool mAutoSelectEnabled; - uint16_t mCcaFailureRateThreshold; +#if OPENTHREAD_FTD + uint16_t mDelay; +#endif + uint8_t mChannel; + uint8_t mChannelSelected; + State mState; + ManagerTimer mTimer; + uint32_t mAutoSelectInterval; +#if OPENTHREAD_FTD + bool mAutoSelectEnabled; +#endif +#if (OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE && OPENTHREAD_CONFIG_CHANNEL_MANAGER_CSL_CHANNEL_SELECT_ENABLE) + bool mAutoSelectCslEnabled; +#endif + uint16_t mCcaFailureRateThreshold; }; /** diff --git a/tests/scripts/thread-cert/addon_test_channel_manager_autocsl.py b/tests/scripts/thread-cert/addon_test_channel_manager_autocsl.py new file mode 100755 index 000000000..2eaff4f36 --- /dev/null +++ b/tests/scripts/thread-cert/addon_test_channel_manager_autocsl.py @@ -0,0 +1,143 @@ +#!/usr/bin/env python3 +# +# Copyright (c) 2023, 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 unittest + +import config +import mle +import thread_cert +from pktverify import consts + +LEADER = 1 +ED = 2 +SSED = 3 + + +class SSED_CslChannelManager(thread_cert.TestCase): + TOPOLOGY = { + LEADER: { + 'version': '1.2', + }, + ED: { + 'version': '1.2', + 'is_mtd': False, + 'mode': 'rn', + }, + SSED: { + 'version': '1.2', + 'is_mtd': True, + 'mode': '-', + }, + } + """All nodes are created with default configurations""" + + def test(self): + + self.nodes[SSED].set_csl_period(consts.CSL_DEFAULT_PERIOD) + self.nodes[SSED].set_csl_timeout(consts.CSL_DEFAULT_TIMEOUT) + + self.nodes[SSED].get_csl_info() + + self.nodes[LEADER].start() + self.simulator.go(config.LEADER_STARTUP_DELAY) + self.assertEqual(self.nodes[LEADER].get_state(), 'leader') + channel = self.nodes[LEADER].get_channel() + + self.nodes[SSED].start() + self.simulator.go(7) + self.assertEqual(self.nodes[SSED].get_state(), 'child') + + csl_channel = 0 + csl_config = self.nodes[SSED].get_csl_info() + self.assertTrue(int(csl_config['channel']) == csl_channel) + self.assertTrue(csl_config['period'] == '500000us') + + print('SSED rloc:%s' % self.nodes[SSED].get_rloc()) + self.assertTrue(self.nodes[LEADER].ping(self.nodes[SSED].get_rloc())) + + # let channel monitor collect >970 sample counts + self.simulator.go(980 * 41) + results = self.nodes[SSED].get_channel_monitor_info() + self.assertTrue(int(results['count']) > 970) + + # Configure channel manager channel masks + # Set cca threshold to 0 as we cannot change cca assessment in simulation. + # and shorten interval to speedup test + all_channels_mask = int('0x7fff800', 0) + chan_12_to_15_mask = int('0x000f000', 0) + interval = 30 + self.nodes[SSED].set_channel_manager_supported(all_channels_mask) + self.nodes[SSED].set_channel_manager_favored(chan_12_to_15_mask) + self.nodes[SSED].set_channel_manager_cca_threshold('0x0000') + self.nodes[SSED].set_channel_manager_interval(interval) + + # enable channel manager auto-select and check + # network channel is not changed by channel manager on SSED + # and also csl_channel is unchanged + self.nodes[SSED].set_channel_manager_auto_enable(True) + self.simulator.go(interval + 1) + results = self.nodes[SSED].get_channel_manager_config() + self.assertTrue(int(results['auto']) == 1) + self.assertTrue(results['cca threshold'] == '0x0000') + self.assertTrue(int(results['interval']) == interval) + self.assertTrue('11-26' in results['supported']) + self.simulator.go(1) + self.assertTrue(self.nodes[SSED].get_channel() == channel) + csl_config = self.nodes[SSED].get_csl_info() + self.assertTrue(int(csl_config['channel']) == csl_channel) + + # check SSED can change csl channel + csl_channel = 25 + self.flush_all() + self.nodes[SSED].set_csl_channel(csl_channel) + self.simulator.go(1) + ssed_messages = self.simulator.get_messages_sent_by(SSED) + self.assertIsNotNone(ssed_messages.next_mle_message(mle.CommandType.CHILD_UPDATE_REQUEST)) + self.simulator.go(1) + csl_config = self.nodes[SSED].get_csl_info() + self.assertTrue(int(csl_config['channel']) == csl_channel) + self.simulator.go(1) + self.assertTrue(self.nodes[LEADER].ping(self.nodes[SSED].get_rloc())) + + # enable channel manager autocsl-select in addition + # and check csl channel changed to best favored channel 12 + csl_channel = 12 + self.nodes[SSED].set_channel_manager_autocsl_enable(True) + self.simulator.go(interval + 1) + results = self.nodes[SSED].get_channel_manager_config() + self.assertTrue(int(results['autocsl']) == 1) + self.assertTrue(int(results['channel']) == csl_channel) + csl_config = self.nodes[SSED].get_csl_info() + self.assertTrue(int(csl_config['channel']) == csl_channel) + self.simulator.go(1) + self.assertTrue(self.nodes[LEADER].ping(self.nodes[SSED].get_rloc())) + + +if __name__ == '__main__': + unittest.main() diff --git a/tests/scripts/thread-cert/node.py b/tests/scripts/thread-cert/node.py index f8e291d08..e7aa2bb38 100755 --- a/tests/scripts/thread-cert/node.py +++ b/tests/scripts/thread-cert/node.py @@ -807,6 +807,18 @@ class NodeImpl: results = [line for line in output if self._match_pattern(line, pattern)] return results + def _expect_key_value_pairs(self, pattern, separator=': '): + """Expect 'key: value' in multiple lines. + + Returns: + Dictionary of the key:value pairs. + """ + result = {} + for line in self._expect_results(pattern): + key, val = line.split(separator) + result.update({key: val}) + return result + @staticmethod def _match_pattern(line, pattern): if isinstance(pattern, str): @@ -1799,7 +1811,7 @@ class NodeImpl: def get_csl_info(self): self.send_command('csl') - self._expect_done() + return self._expect_key_value_pairs(r'\S+') def set_csl_channel(self, csl_channel): self.send_command('csl channel %d' % csl_channel) @@ -3598,6 +3610,81 @@ class NodeImpl: line = self._expect_command_output()[0] return [int(item) for item in line.split()] + def get_channel_monitor_info(self) -> Dict: + """ + Returns: + Dict of channel monitor info, e.g. + {'enabled': '1', + 'interval': '41000', + 'threshold': '-75', + 'window': '960', + 'count': '985', + 'occupancies': { + '11': '0.00%', + '12': '3.50%', + '13': '9.89%', + '14': '15.36%', + '15': '20.02%', + '16': '21.95%', + '17': '32.71%', + '18': '35.76%', + '19': '37.97%', + '20': '43.68%', + '21': '48.95%', + '22': '54.05%', + '23': '58.65%', + '24': '68.26%', + '25': '66.73%', + '26': '73.12%' + } + } + """ + config = {} + self.send_command('channel monitor') + + for line in self._expect_results(r'\S+'): + if re.match(r'.*:\s.*', line): + key, val = line.split(':') + config.update({key: val.strip()}) + elif re.match(r'.*:', line): # occupancy + occ_key, val = line.split(':') + val = {} + config.update({occ_key: val}) + elif 'busy' in line: + # channel occupancies + key = line.split()[1] + val = line.split()[3] + config[occ_key].update({key: val}) + return config + + def set_channel_manager_auto_enable(self, enable: bool): + self.send_command(f'channel manager auto {int(enable)}') + self._expect_done() + + def set_channel_manager_autocsl_enable(self, enable: bool): + self.send_command(f'channel manager autocsl {int(enable)}') + self._expect_done() + + def set_channel_manager_supported(self, channel_mask: int): + self.send_command(f'channel manager supported {int(channel_mask)}') + self._expect_done() + + def set_channel_manager_favored(self, channel_mask: int): + self.send_command(f'channel manager favored {int(channel_mask)}') + self._expect_done() + + def set_channel_manager_interval(self, interval: int): + self.send_command(f'channel manager interval {interval}') + self._expect_done() + + def set_channel_manager_cca_threshold(self, hex_value: str): + self.send_command(f'channel manager threshold {hex_value}') + self._expect_done() + + def get_channel_manager_config(self): + self.send_command('channel manager') + return self._expect_key_value_pairs(r'\S+') + class Node(NodeImpl, OtCli): pass diff --git a/tests/toranj/cli/cli.py b/tests/toranj/cli/cli.py index 789c6f654..78c0229f2 100644 --- a/tests/toranj/cli/cli.py +++ b/tests/toranj/cli/cli.py @@ -223,6 +223,17 @@ class Node(object): def set_channel(self, channel): self._cli_no_output('channel', channel) + def get_csl_config(self): + outputs = self.cli('csl') + result = {} + for line in outputs: + fields = line.split(':') + result[fields[0].strip()] = fields[1].strip() + return result + + def set_csl_period(self, period): + self._cli_no_output('csl period', period) + def get_ext_addr(self): return self._cli_single_output('extaddr') @@ -965,7 +976,8 @@ def verify_within(condition_checker_func, wait_time, arg=None, delay_time=0.1): except VerifyError as e: if time.time() - start_time > wait_time: print('Took too long to pass the condition ({}>{} sec)'.format(time.time() - start_time, wait_time)) - print(e.message) + if hasattr(e, 'message'): + print(e.message) raise e except BaseException: raise diff --git a/tests/toranj/cli/test-602-channel-manager-channel-select.py b/tests/toranj/cli/test-602-channel-manager-channel-select.py index 0ddf3584b..596cfdc57 100755 --- a/tests/toranj/cli/test-602-channel-manager-channel-select.py +++ b/tests/toranj/cli/test-602-channel-manager-channel-select.py @@ -66,6 +66,10 @@ def check_channel(): verify(int(node.get_channel()) == channel) +delay = int(node.cli('channel manager delay')[0]) +# add kRequestStartJitterInterval=10000ms to expected channel manager delay +delay += 10 / speedup + check_channel() all_channels_mask = int('0x7fff800', 0) @@ -99,7 +103,7 @@ node.cli('channel manager select 1') result = cli.Node.parse_list(node.cli('channel manager')) verify(result['channel'] == '11') channel = 11 -verify_within(check_channel, 2) +verify_within(check_channel, delay) # Set channels 12-15 as favorable and request a channel select, verify # that channel is switched to 12. @@ -112,13 +116,13 @@ node.cli('channel manager favored', chan_12_to_15_mask) channel = 25 node.cli('channel manager change', channel) -verify_within(check_channel, 2) +verify_within(check_channel, delay) node.cli('channel manager select 1') result = cli.Node.parse_list(node.cli('channel manager')) verify(result['channel'] == '12') channel = 12 -verify_within(check_channel, 2) +verify_within(check_channel, delay) # Set channels 15-17 as favorables and request a channel select, # verify that channel is switched to 11. @@ -129,7 +133,7 @@ verify_within(check_channel, 2) channel = 25 node.cli('channel manager change', channel) -verify_within(check_channel, 2) +verify_within(check_channel, delay) node.cli('channel manager favored', chan_15_to_17_mask) @@ -137,7 +141,7 @@ node.cli('channel manager select 1') result = cli.Node.parse_list(node.cli('channel manager')) verify(result['channel'] == '11') channel = 11 -verify_within(check_channel, 2) +verify_within(check_channel, delay) # Set channels 12-15 as favorable and request a channel select, verify # that channel is not switched. @@ -145,10 +149,11 @@ verify_within(check_channel, 2) node.cli('channel manager favored', chan_12_to_15_mask) node.cli('channel manager select 1') + result = cli.Node.parse_list(node.cli('channel manager')) verify(result['channel'] == '11') channel = 11 -verify_within(check_channel, 2) +verify_within(check_channel, delay) # Starting from channel 12 and issuing a channel select (which would # pick 11 as best channel). However, since quality difference between @@ -157,14 +162,60 @@ verify_within(check_channel, 2) channel = 12 node.cli('channel manager change', channel) -verify_within(check_channel, 2) +verify_within(check_channel, delay) node.cli('channel manager favored', all_channels_mask) node.cli('channel manager select 1') result = cli.Node.parse_list(node.cli('channel manager')) verify(result['channel'] == '12') -verify_within(check_channel, 2) +verify_within(check_channel, delay) + +# ----------------------------------------------------------------------------------------------------------------------- +# Auto Select + +# Set channel manager cca failure rate threshold to 0 +# as we cannot control cca success in simulation +node.cli('channel manager threshold 0') + +# Set short channel selection interval to speedup +interval = 30 +node.cli(f'channel manager interval {interval}') + +# Set channels 15-17 as favorable and request a channel select, verify +# that channel is switched to 11. + +channel = 25 +node.cli('channel manager change', channel) +verify_within(check_channel, delay) +node.cli('channel manager favored', chan_15_to_17_mask) + +# Active auto channel selection +node.cli('channel manager auto 1') + +channel = 11 +result = cli.Node.parse_list(node.cli('channel manager')) +verify(result['auto'] == '1') +verify(result['channel'] == str(channel)) + +verify_within(check_channel, delay) + +# while channel selection timer is running change to channel 25, +# set channels 12-15 as favorable, wait for auto channel selection +# and verify that channel is switched to 12. + +node.cli('channel manager favored', chan_12_to_15_mask) +channel = 25 +node.cli('channel manager change', channel) + +# wait for timeout of auto selection timer +time.sleep(2 * interval / speedup) + +channel = 12 +result = cli.Node.parse_list(node.cli('channel manager')) +verify(result['channel'] == str(channel)) + +verify_within(check_channel, delay) # ----------------------------------------------------------------------------------------------------------------------- # Test finished |